Commit Graph
333 Commits
Author SHA1 Message Date
Moheeb Zara 4aa6646499 fix(onboarding): atomic DB append for step completion (deferred bug L3)
markOnboardingStepDone read space_members.onboarding_progress, appended the step
id in JS, and wrote the whole jsonb object back — a lost-update race where two
near-simultaneous completions (double-submit, or two steps ticked quickly) both
read the same base array and the second write clobbered the first, silently
dropping a step.

Replace it with mark_onboarding_step_done (migration 055): a single atomic
UPDATE that dedup-appends the step id into onboarding_progress.completed_step_ids
under the row lock — no update can be lost (READ COMMITTED re-evaluates the jsonb
expression against the latest tuple via EvalPlanQual), other keys are preserved,
and re-appending an existing id is a no-op. Ownership-guarded by auth.uid() so a
caller can only complete steps on their own membership row; the no-JWT service
path is trusted and scoped by p_member_id (mirrors class_signup_tx et al.).

integration/onboarding-progress.test.ts covers dedup/idempotency, key
preservation, the auth.uid ownership gate, and the point of the fix: 12
concurrent completions all survive.

Migration 055 folded into schema.sql; types/database.ts regenerated.
typecheck 0 · lint 0 errors · 645 unit · 58 integration · build ok
2026-08-04 22:36:12 -07:00
Moheeb Zara b2b463491a refactor(types): regenerate database types from canonical schema; drop TODO(types) join casts
Regenerate types/database.ts from a local Supabase built from the canonical
scripts/schema.sql. The running dev DB was stale (missing the 'stripe'
payment_platform value, members_with_permission, spaces.host_requires_card,
payments.link_status, dues_payment_methods.space_id), so it was rebuilt from
schema.sql first.

With real relationship metadata the 11 `as unknown as … // TODO(types)` join
casts are no longer needed:
- me portal, session-attendance, member-certifications, equipment-reservations:
  narrow the action result with `'data' in res` instead of a blanket cast.
- stripe / door embeds read the now-typed relation directly.
- completeSession + listEquipmentReservations route the embedded-select query
  through the fully-typed createClient() rather than the wide ServerSupabase gate
  client (the pattern documented in auth-helpers.ts); equipment disambiguates its
  two space_members FKs with an explicit !fkey hint.

Collapse the hand-maintained governance rows/enums in types/domain/governance.ts
into Tables<>/Enums<> aliases.

Tighten duesPaymentMethodSchema.platform to the DUES_LINK_PLATFORMS literal union
(the old `as unknown as [string,...]` erased it), which the now-correct
payment_platform enum requires.

The hand-added class_signup_tx / class_cancel_tx RPC entries are now generated
natively.

typecheck 0 · lint 0 errors · 645 tests · build ok
2026-08-04 22:21:29 -07:00
Moheeb ZaraandClaude Opus 4.8 4f4e3096ca docs: add HANDOFF-audit-cleanup.md (state, verify steps, follow-ups, merge path)
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-04 21:27:26 -07:00
Moheeb ZaraandClaude Opus 4.8 2226f8f746 polish(ops): friendly not-found message on cross-space area-lead update
updateAreaLead used .single(), which errors (raw PostgREST message) rather than
returning null when the id isn't in the caller's space. Switch to .maybeSingle()
so the existing 'Area lead not found' guard is reached, matching deleteAreaLead.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-04 21:26:55 -07:00
Moheeb ZaraandClaude Opus 4.8 8ddc8b7b88 docs: record the SoC pass + browser smoke test; update known limitations
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-04 19:12:28 -07:00
Moheeb ZaraandClaude Opus 4.8 61ffc2e01f fix(invites): distinguish CAS contention from a real use-cap (retry vs reject)
claimInviteUse returned null both when the cap was truly reached and when it
lost the compare-and-swap 5× under load, so a legitimate joiner on a busy
high-cap invite got a false 'invite has reached its use cap'. Return a
discriminated result; map contention to a distinct 'try again' message.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-04 19:10:52 -07:00
Moheeb ZaraandClaude Opus 4.8 33b2475e67 fix: revalidate /customize (not /settings) after customize-panel mutations
The invites/onboarding/areas/roles/tiers admin panels moved to /customize, but
their actions still revalidated the old /settings route, so the /customize
Router Cache went stale after a write. Point them at /customize (keeping the
other cross-page revalidations).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-04 19:10:52 -07:00
Moheeb ZaraandClaude Opus 4.8 2dbd52e2e4 test: add authz/scope/anti-impersonation tests for the new actions
Real mock-client tests for createAreaLead/updateAreaLead/deleteAreaLead (admin
gate + space scope), sendMessage (identity from session not input; foreign
channel rejected), and payments member-in-space rejection.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-04 19:10:42 -07:00
Moheeb ZaraandClaude Opus 4.8 3243e4ce98 fix(payments): verify member_id belongs to caller's space (IDOR)
logCashPayment and linkPaymentToMember accepted a uuid member_id without
checking it belongs to the caller's space (the equipment/class paths were
already hardened for exactly this). Look up the member scoped to the caller's
space and reject a foreign id before writing.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-04 19:10:42 -07:00
Moheeb ZaraandClaude Opus 4.8 1aa0cd818a fix(comms): send messages via a server action, derive identity server-side (SoC)
The chat composer inserted comms_messages straight from the browser with
CLIENT-SUPPLIED user_id/display_name/handle/space_id — a member could forge
another person's name/handle. Add a sendMessage server action that derives
identity from the session, verifies the channel belongs to the caller's space,
validates content, and logs the send. The optimistic insert + realtime
subscription are preserved, so latency is unchanged. loadMessages stays on the
browser client (the realtime subscription needs it) with a comment noting it.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-04 19:10:42 -07:00
Moheeb ZaraandClaude Opus 4.8 510187a58d fix(ops): route area-lead roster writes through server actions (SoC)
The ops UI created/updated/deleted area_leads directly from the browser (the
delete had no admin gate or space scope). Add createAreaLead/updateAreaLead/
deleteAreaLead (admin-gated, space-scoped, validated, audited) and wire the
modal + delete handler to them, dropping the direct browser writes. The
distinct area_code-keyed upsertAreaLead interface is left untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-04 19:10:42 -07:00
Moheeb ZaraandClaude Opus 4.8 46ea2f1cce docs: add ARCHITECTURE.md; mark Phase C+D complete in the audit log (Phase D)
ARCHITECTURE.md documents the layered structure, the server-action contract +
space-scope invariant, feature-folder conventions, and an add-a-feature guide.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-04 06:20:38 -07:00
Moheeb ZaraandClaude Opus 4.8 f775f954ad refactor: dead-code cleanup + drop the lib/types shim (slice 9)
- Delete orphaned components (theme-provider, task-claim-button), the unused
  roleDisplayName helper, the fully-dead cleanupRateLimits, the 6 never-imported
  validation schemas (signIn/signUp/createSpace/joinSpace — a replaced auth flow;
  proposalStatuses; updateAreaLeadRoleSchema).
- Migrate all @/lib/types importers to the canonical @/types/domain and delete
  the compatibility shim.

tsc 0, 635 tests green, lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-04 06:19:11 -07:00
Moheeb ZaraandClaude Opus 4.8 71ba558522 fix(secrets): store ops-created secrets encrypted via createSecret (security)
The ops 'Add Secret' modal wrote the secret value straight to the DB from the
browser in PLAINTEXT — despite the 'Stored encrypted at rest' label — because
the AES key is server-only. Route it through the createSecret server action,
which encrypts the value (AES-256-GCM), enforces the admin/board gate, validates
input, and logs the activity. Existing plaintext rows (encryption_version 0)
still reveal correctly; new secrets are encrypted when a key is configured.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-04 06:15:45 -07:00
Moheeb ZaraandClaude Opus 4.8 9100532bef refactor(members): decompose members-client + fix form-field focus bug (slice 8)
663-line members-client.tsx → a 255-line orchestrator + members/components/
{member-form-dialog,members-table,members-toolbar}.tsx + types.ts. The add/edit
form fields (previously defined INSIDE render, which remounted and lost input
focus every keystroke) are now a stable top-level component in member-form-dialog
— fixing that footgun. tsc 0, build ok.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-04 06:14:25 -07:00
Moheeb ZaraandClaude Opus 4.8 543b1d65eb refactor(settings): decompose settings-client into panels (slice 7)
663-line settings-client.tsx → a 79-line orchestrator (activeTab + shared save
status) + settings/panels/{space,integrations,dues,webhooks}-panel.tsx each
owning its own form state + types.ts. Server-action calls and fields preserved
verbatim. tsc 0, build ok.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-04 06:14:24 -07:00
Moheeb ZaraandClaude Opus 4.8 7bc6609ac2 refactor(ops): decompose ops-client into panels + components (slice 6)
956-line ops-client.tsx → a 230-line orchestrator + ops/panels/{kb,secrets,
area-leads}-panel.tsx (kb+processes share kb-panel via a variant prop) +
ops/components/{kb-modal,secret-modal,area-lead-modal,secret-row,kb-entry-row}.tsx
+ types.ts, following the customize/ exemplar. Behavior preserved (direct writes
kept as-is for now — the secrets one is fixed in the next commit). tsc 0, build ok.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-04 06:14:24 -07:00
Moheeb ZaraandClaude Opus 4.8 f86fff62d0 refactor(actions): split forms.ts into a folder (slice 5)
805-line lib/actions/forms.ts → lib/actions/forms/{manage,submit,member}.ts
('use server') + _guard.ts (requireFormsManager/isUniqueViolation) + index.ts
barrel. All 15 actions preserved verbatim (incl. linkSubmissionsByEmail, still
imported by members.ts). tsc 0, tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-04 06:01:02 -07:00
Moheeb ZaraandClaude Opus 4.8 39bcdd9677 refactor(actions): split door.ts into a folder (slice 4)
813-line lib/actions/door.ts → lib/actions/door/{connections,operate,self-entry}.ts
('use server') + _guards.ts + _audit.ts + index.ts barrel. All 14 actions
preserved verbatim; lib/door/ (executor/ingest/secret) untouched. tsc 0, tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-04 06:01:02 -07:00
Moheeb ZaraandClaude Opus 4.8 dbaae4851b refactor(actions): split classes.ts into a folder (slice 3)
908-line lib/actions/classes.ts → lib/actions/classes/{definitions,sessions,
signups,instructor}.ts ('use server') + _guard.ts (requirePermission/Gate) +
_forms.ts (findSpaceForm/hasFormSubmission) + a plain index.ts barrel. All 14
actions preserved verbatim; lib/actions/index.ts unchanged. Helpers stay
internal (not re-exported). tsc 0, tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-04 06:01:02 -07:00
Moheeb ZaraandClaude Opus 4.8 e205ce54c0 docs: record CI + Phase C slices 0-2 in the audit log
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-02 04:22:32 -07:00
Moheeb ZaraandClaude Opus 4.8 3f7e9d96fa refactor(types): move hand-written domain types to types/domain/* (slice 2)
lib/types.ts (aliases + governance shapes + auth re-exports) split into
types/domain/{index,governance,reexports}.ts, making the generated/hand-written
seam explicit (generated = types/database.ts, hand-written = types/domain/*).
lib/types.ts is now a re-export shim so all 11 importers are untouched. Fixed
the stale 'not yet in types/database.ts' comment — those tables/enums are now
generated; de-duplicating the governance rows into Tables<>/Enums<> is a noted
follow-up. tsc 0, 635 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-02 04:21:19 -07:00
Moheeb ZaraandClaude Opus 4.8 18d5e22ad4 refactor(validations): split lib/validations.ts into a barrel'd folder (slice 1)
1141-line lib/validations.ts → lib/validations/<feature>.ts + primitives.ts +
an index.ts barrel. '@/lib/validations' now resolves to the barrel, so all 40
importers are untouched. Every one of the 143 exports is preserved verbatim
(verified by symbol-diff); schemas moved unchanged. tsc 0, 635 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-02 04:19:11 -07:00
Moheeb ZaraandClaude Opus 4.8 c8c60358c7 refactor(types): mark types/database.ts as generated (slice 0)
Banner documenting the regenerate command and that hand-written domain types
belong in types/domain/*, not here. No code change.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-02 04:11:25 -07:00
Moheeb ZaraandClaude Opus 4.8 f571001ac2 ci: add a checks-only workflow (typecheck + lint + unit tests)
Runs on every PR and on main after merge, fully separate from deploy.yml so it
can never affect deploys. Catches type/lint/test regressions that previously
had no CI at all. Integration + e2e (which need a DB service) are a follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-02 04:10:57 -07:00
Moheeb ZaraandClaude Opus 4.8 edffb2780c docs: record Phase A + Phase B progress in the audit log
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-01 21:33:40 -07:00
Moheeb ZaraandClaude Opus 4.8 ad010edb41 test: cover the authz primitives (getAuthMember/requireMember/requireMemberWithRole)
These gate every server action but had no direct tests. Add cases for: no user,
no active membership, active member; role allow/deny; the require_approval
unverified gate; and late members keeping their role.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-01 21:33:15 -07:00
Moheeb ZaraandClaude Opus 4.8 9bd21b185d fix(import): guard unparseable CSV dates and never wedge the button (L7)
A non-empty invalid transaction_date ('N/A') passed presence-only validation,
then new Date(str).toISOString() threw mid-import: nothing imported, no toast,
and setImporting(false) never ran, sticking the button on 'Importing...'.
Extracted parsePaymentRow to lib/import-logic.ts with a date guard, and wrapped
runImport so the importing flag always clears.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-01 21:31:51 -07:00
Moheeb ZaraandClaude Opus 4.8 8274af7d5c fix(equipment): verify booked-for member belongs to caller's space (L1)
reserveEquipment used a manager-supplied memberId directly, so a booking could
be stamped for a member id from another space. Mirror signUpForClass: look up
the target by (id, space_id=caller) and reject if absent.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-01 21:31:51 -07:00
Moheeb ZaraandClaude Opus 4.8 c7a6cd3853 fix(proposals): block unverified members from governance writes (L2)
createProposal/openProposal/withdrawProposal/castVote gated only with
requireMember, which admits status='unverified' — so a member pending approval
in a require_approval space could propose and cast counted votes before vetting.
Add the isPrivilegeEligible gate (same as requireMemberWithRole) to all four.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-01 21:31:51 -07:00
Moheeb ZaraandClaude Opus 4.8 7fc08c5165 fix(incidents): restrict appealIncident to admin/board, no orphan proposal (M2)
appealIncident was gated by requireMember but the incident UPDATE is RLS-
restricted to admin/board, so a reporter's appeal inserted a draft proposal,
updated 0 incident rows, and returned success — an orphan proposal. Gate with
ADMIN_ROLES to match the write, and assert the link UPDATE affected a row
(.select), rolling back the draft proposal if it did not.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-01 21:31:51 -07:00
Moheeb ZaraandClaude Opus 4.8 7fe3ffc632 fix(invites): block re-arming an admin invite without role change (M1)
updateInvite ran the canAssignInviteRole guard only when the payload included
'role', so a board member could re-enable/extend an exhausted admin-granting
invite (is_enabled/max_uses/expires_at) and distribute admin access they cannot
mint. Load the existing invite and require canAssignInviteRole against its
effective role whenever any privilege-affecting field is (re-)armed.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-01 21:31:51 -07:00
Moheeb ZaraandClaude Opus 4.8 4b98c21e2b fix(payments): advance-only last_paid_at, no dues regression (M4/M5)
logCashPayment and linkPaymentToMember unconditionally set last_paid_at to the
(possibly backdated) transaction_date and forced payment_status='current', so
logging/linking a historical payment moved a member's dues state backward.
New pure resolveDuesAdvance() keeps the later of existing/incoming date and only
marks 'current' when the payment actually advances; both sites read the current
date first. Identical in the common (newest-payment) case.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-01 21:31:34 -07:00
Moheeb ZaraandClaude Opus 4.8 90c68faa25 fix(door): correct parseHeatSyncLog stale time + token reuse (L5)
(a) Reset h/mi/e after each event so a later event with no H:M:E of its own no
longer inherits the previous event's time-of-day (fabricated audit timestamp +
dedupeKey). (b) Track consumed high-half token indices so one g/d token can no
longer be paired with two adjacent grants (wrong reconstructed card numbers).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-01 21:31:34 -07:00
Moheeb ZaraandClaude Opus 4.8 a3e9b97292 fix(door): block hex IPv4-mapped IPv6 in the SSRF filter (M3)
isBlockedDoorIp blocked the dotted IPv4-mapped form (::ffff:169.254.169.254)
but the hex form ::ffff:a9fe:a9fe (=169.254.169.254 IMDS) / ::ffff:7f00:1
(=127.0.0.1) fell through the IPv6 branch and returned false. Decode the hex
IPv4-mapped form to dotted IPv4 and run the existing blocked-range checks.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-01 21:31:34 -07:00
Moheeb ZaraandClaude Opus 4.8 f337a85682 build: remove typescript.ignoreBuildErrors now that tsc is clean
tsc --noEmit reports 0 errors (was 586, all masked). next build now
type-checks on every build and prod deploy, so a type regression fails CI
instead of shipping. 649 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-01 21:23:22 -07:00
Moheeb ZaraandClaude Opus 4.8 d18d6acaff test: replace tautological actions.test with real authz/IDOR tests
The old file asserted on string literals it defined itself (e.g.
expect(".eq('space_id', ...)").toContain('space_id')) and never invoked an
action, so it stayed green regardless of the code — false confidence on exactly
the authz/scoping it claimed to cover.

Rewrite drives the real updateMember/removeMember/logCashPayment through a
recording mock Supabase client and asserts: unauthenticated + non-privileged
callers are rejected with no write; admin/treasurer writes are pinned to the
caller's own space_id (the IDOR guard). Removing a role gate or a space_id
scope now fails a test.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-01 21:23:04 -07:00
Moheeb ZaraandClaude Opus 4.8 0913346741 fix(payments): guard nullable display_name in the link-payment picker (L6)
The candidate-member initials rendered m.display_name.split(' '); display_name
is nullable, so a member with no name crashed the link dialog. Fall back to
email then '?'.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-01 21:19:29 -07:00
Moheeb ZaraandClaude Opus 4.8 70b85a20fa fix(members): repair broken member-directory visibility gate
member_directory_visibility is the directory_visibility enum
(board_only|member_count_visible|members_visible|public_members_visible), but
the gate compared against 'board_visible'/'admin_only' — values from other
enums that never match — so every setting except members_visible silently
restricted the directory, including public_members_visible (meant to be the
most open). Map the gate to the real enum values.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-01 21:19:29 -07:00
Moheeb ZaraandClaude Opus 4.8 da11127ca4 fix(members): guard nullable display_name/email in the members list (M6)
Typing into the members search box called .toLowerCase() on display_name/email,
both of which are nullable (e.g. imported members) — a single null row threw a
TypeError and blanked the entire members page. Optional-chain the search match,
and handle nullable values when seeding the edit form and optimistic update.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-01 21:19:29 -07:00
Moheeb ZaraandClaude Opus 4.8 2ee929658e fix(invites): close single-use invite max_uses TOCTOU (H1)
Redeeming an invite read uses_count, checked the cap in JS, then did a
non-atomic uses_count = uses_count + 1 write AFTER creating the membership.
Two concurrent redemptions of a max_uses=1 invite both passed the check and
both created a member, and the counter under-counted.

Fix: claim a use ATOMICALLY before creating the membership via a
compare-and-swap on uses_count (lib/invite-claim.ts) — the UPDATE only writes
when uses_count still equals the value the cap decision was based on, so of two
racing claims exactly one wins. The loser re-reads and retries while capacity
remains (multi-use invites unaffected). On a failed membership insert the claim
is rolled back so a slot is not burned.

Extracted to lib/invite-claim.ts (out of the 'use server' surface, unit-tested).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-01 21:16:12 -07:00
Moheeb ZaraandClaude Opus 4.8 624a708d8c tooling: add ESLint 9 flat config and a typecheck script
- eslint.config.mjs: flat config built on eslint-config-next's flat array
  (core-web-vitals + typescript + Next plugin), with build/generated artifacts
  ignored and the aggressive react-hooks v6 React-Compiler rules downgraded to
  warnings so `pnpm run lint` passes clean (0 errors, 88 warnings of burn-down
  debt) instead of failing. Previously `eslint .` failed outright — no config
  and no eslint binary were installed.
- Install eslint, @eslint/js, eslint-config-next, typescript-eslint, globals as
  devDeps (dev-only; prod Docker build is unaffected).
- package.json: add `typecheck` (tsc --noEmit) and `test:run` scripts.
- app/not-found.tsx: escape apostrophes (react/no-unescaped-entities).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-01 20:37:56 -07:00
Moheeb ZaraandClaude Opus 4.8 b1edb5c460 types: clear remaining tsc errors across 41 files (76→14, behavior-preserving)
Per-file type-only fixes surfaced after the Supabase client was correctly
typed. All fixes preserve runtime behavior; no any/ts-ignore/ts-expect-error.

- lib/supabase/client.ts: annotate the browser client as SupabaseClient<Database>
  (same ssr/js drift as server.ts; fixes it centrally for all callers).
- Supabase embedded-select joins whose FK metadata is missing from the
  generated types are bridged with `as unknown as <target>` + a TODO to remove
  after regenerating types/database.ts.
- setState optimistic-update variance and raw <select> enum values cast to the
  precise element/union type; .insert payload fields (Json, enums) given the
  precise column type via satisfies/field casts.
- Added narrowing guards where a value was legitimately nullable (comms channel,
  public form page) without changing the intended path.

The 14 remaining errors are confined to the four files fixed in Phase B (real
bugs + the tautological actions.test rewrite). 639 tests still green.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-01 18:56:11 -07:00
Moheeb ZaraandClaude Opus 4.8 1766780bca types: restore Supabase row typing, fix parseInput inference (586→76 tsc errors)
The installed supabase-js (2.99, 5 generics) is incompatible with
@supabase/[email protected] (calls it with 3), collapsing the server client's
schema to `never` — the root cause of ~450 of the 586 hidden type errors
(next.config typescript.ignoreBuildErrors masks them all).

- lib/supabase/server.ts: annotate the factory as SupabaseClient<Database>
  and bridge the ssr/js impedance mismatch at the one documented boundary;
  type the setAll cookie params.
- lib/supabase/proxy.ts, proxy.ts: type the setAll cookie params.
- lib/auth-helpers.ts: parseInput now infers its result from the schema via
  z.infer (was a fragile ZodSchema<T> that widened outputs to {}).
- lib/validations.ts: annotate flexibleDateTime's output type.
- types/database.ts: add class_signup_tx / class_cancel_tx RPC signatures
  (called but absent; pending full regeneration).
- tsconfig.json: target ES2020 (door-log-logic uses BigInt literals).
- vitest.setup.ts: import beforeAll/afterAll instead of relying on globals.

No runtime behavior change; 639 tests still green. ignoreBuildErrors stays
on until the remaining errors reach zero.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-01 18:40:33 -07:00
Moheeb Zara 9ac0c2486c Use the real hack.build logo mark (wrench-in-ring), not a reconstruction
The mark is the actual brand PNG (already transparent) — just trimmed and
downscaled. The prior version wrongly ran a saturation mask that stripped the
white ring and mangled the wrench into a blob.
2026-07-29 02:34:26 -07:00
Moheeb Zara ec923a845e Use the real hack.build logo and fix the showcase grid
- Replace the hand-drawn wrench with the actual hack.build mark: isolate the
  pink wrench from the brand PNG onto transparent (public/hackbuild-mark.png)
  and use it for the talk tile and the Resources card.
- Showcase grid: 7 columns so the seven projects fill one row, and draw the
  hairlines from per-tile borders instead of a grey container showing through
  gaps — no more grey empty cells on an incomplete row.
- Drop the now-unused HackBuildIcon.
2026-07-29 02:11:35 -07:00
Moheeb Zara 3f1ce77900 Add the talk to the landing showcase; point Atlas at its subdomain
- Landing 'From hackerspace.sh' showcase: add a 'Hacking Your Hackerspace'
  tile (07 projects) linking the deck on hack.build, with a new HackBuildIcon
  (the brand's pink open-end wrench).
- Point the Atlas tile at https://atlas.hackerspace.sh (now live) instead of
  the old internal /atlas route.
- Use the same hack.build wrench for the talk card on the Resources page.
2026-07-28 23:37:46 -07:00
Moheeb Zara b6e50c0950 Add talk to resources, public-site nav, and homepage link
- Resources: 7th project card linking the 'Hacking Your Hackerspace' talk
  (hack.build), with a PlayIcon; bump the section count to 07.
- Sidebar: NavLink now supports external targets; add a 'Public Site' section
  linking the homepage and the public resources page.
- Login: a back-to-homepage link.
2026-07-28 23:18:50 -07:00
Moheeb Zara 62c1c5f4b7 glitchtip: pin image to 6.1.6 for reproducibility; HANDOFF observability complete 2026-05-22 01:17:41 -07:00
Moheeb Zara 65458b71ec docs: HANDOFF observability Phase 2 complete (GlitchTip live on prod, DSN wired) 2026-05-22 01:09:16 -07:00