Commit Graph
13 Commits
Author SHA1 Message Date
aarbit 4c45b5afa7 Add Roles & Admin/Super-Admin interface
Per organized-ideas.md §6: role assignment, account ban/unban/delete, and
direct Admin/Super-Admin CRUD of public catalog entries outside the
submission workflow.

Backend:
- list_users_for_admin(): Super-Admin-gated SECURITY DEFINER function
  joining profiles + auth.users (username, email, role, banned_until) —
  auth.users isn't exposed through PostgREST, so this is the only way to
  list accounts at all.
- New Edge Function admin-user-action (ban/unban/delete), using
  @supabase/server's `auth: 'user'` mode to verify the caller's JWT, then
  Supabase Auth's Admin API for the actual mutation. This is deliberately
  an Edge Function rather than a Postgres function like everything else in
  this codebase: touching auth.users needs the Admin API, the stable
  documented interface, not a direct write to a schema Supabase manages
  internally. Self-action guard; verify_jwt = true at the gateway on top of
  the function's own JWT verification.
- 5 new pgTAP tests (43/43 total) for list_users_for_admin (Super-Admin-only,
  even regular Admins get 42501).
- CatalogRepository gains admin* methods (direct edit of a public port/cable/
  device entry, plus adminUnpublish which flips is_public rather than
  deleting) — the update methods were already ownership-agnostic (RLS's
  is_admin() clause is what actually permits it), so these are thin aliases,
  not duplicated logic.

Frontend:
- authStore/AdminUserRepository: minimal role plumbing, shared UserRole type.
- adminUserStore + AdminUsersModal: list/role-dropdown/ban/unban/delete,
  gated to Super Admin only via a new "Manage Users" TopBar button.
- PortTypeManager/CableTypeManager/DevicePalette: built-in entries now show
  direct "Edit"/"Unpublish" for Admins (regular Admin included, per §6's
  capability table — not Super-Admin-exclusive) instead of "Suggest edit";
  unpublish reuses the review-queue's impact-check RPC before confirming.
- DeviceTemplateEditor gains an `adminMode` save path alongside its existing
  submissionMode/resubmitId ones.

Verified: tsc -b and oxlint clean; supabase db reset + 43/43 pgTAP tests
pass; confirmed both new privileged endpoints (the SQL function and the
Edge Function) actually work through the real REST API via live curl
calls — signup, email confirm, role promotion, ban/unban/delete round
trips, self-action guard, non-super-admin rejection, and verify_jwt=true
compatibility all exercised directly, not just asserted.
2026-09-08 16:01:01 -05:00
aarbit 1f8d49345e Add the Admin review queue
Lets an Admin/Super-Admin review pending catalog submissions and approve
(in place, same id) or reject (with a required reason) them, per
organized-ideas.md §3/§9.

Backend (supabase/migrations/20260910010000_admin_review_queue.sql):
- Per-user pending-submission cap (10), enforced in catalog_submissions'
  insert policy rather than trusted to the client.
- catalog_entity_usage_impact(entity_type, entity_id): a SECURITY DEFINER,
  admin-gated aggregate function answering "how many diagrams reference
  this, and a short sample" by scanning diagrams.data JSONB — never raw
  diagram content, and available to regular Admins even though they don't
  otherwise have diagram visibility (only Super Admins do, per §6).
- catalog_submission_submitters(ids[]): same admin-gated pattern, batched,
  so the queue can show who submitted something without opening general
  profile browsing to regular Admins.
- Follow-up migration: a rejected submission had no way out (the delete
  policy only allowed withdrawing 'pending') — extended to allow 'rejected'
  too, so a submitter can dismiss one they don't intend to revise.
- 12 new pgTAP tests (38/38 total) covering the cap, both privileged
  functions (including the non-admin-gets-rejected case), and withdrawing
  pending vs. rejected submissions.

Frontend:
- authStore: minimal role awareness, replacing TopBar's local username
  fetch, used to gate the Review Queue UI.
- AdminSubmissionRepository/SupabaseAdminSubmissionRepository +
  adminReviewStore: list all submissions, approve/reject, usage impact,
  submitter usernames.
- AdminReviewModal: per-submission diff view (current vs. proposed, both
  row-shaped via the existing catalog<->row mappers), a duplicate-detection
  nudge (Levenshtein distance against existing public device names) for
  new device submissions, and an inline impact-check for edits to
  already-public entries before approving.
- TopBar: role-gated "Review Queue" button with a pending-count badge; "My
  Submissions" gets an unseen-outcome badge (localStorage-tracked, like the
  existing hidden-template preference) so a submitter notices a decision
  without having to keep reopening the modal.
- Deliberately deferred: the site-wide announcement banner (its own
  follow-up, per discussion) and the Admin/Super-Admin role-assignment UI
  (§9's later phase — becoming an Admin locally still means setting
  profiles.role via SQL/Studio).

Verified: tsc -b and oxlint clean; supabase db reset + 38/38 pgTAP tests
pass; confirmed the two new RPC functions are actually reachable through
PostgREST (not just raw SQL) via a live curl call; manually tested
submit -> review -> approve/reject -> (for rejected) dismiss end to end.
2026-09-08 13:01:56 -05:00
aarbit a0c598cb1b Add the user-submission flow for the catalog
Lets users submit a private catalog entry (port type, cable type, device
template) for promotion to the public catalog, or suggest an edit to an
existing public entry — both go into the catalog_submissions review queue
per organized-ideas.md §3. No Admin review UI yet (next sub-phase); this
covers the submitter's side only.

- data/SubmissionRepository + SupabaseSubmissionRepository: submit,
  resubmit, withdraw, list-mine, backed by the existing catalog_submissions
  RLS policies (no schema changes needed).
- data/catalogRowMapping.ts: shared domain<->row mappers, in both
  directions, so a submission's proposed_data is always shaped like the
  underlying table row (what an eventual admin-approval would write
  directly) and can be turned back into form-editable fields for revision.
- state/submissionStore.ts: mySubmissions + submit/resubmit/withdraw, plus
  syncProposedData — called from catalogStore's updateCustom* actions so a
  submission about your own still-private entry never goes stale relative
  to it (edits from the library and from My Submissions are the same
  action and always agree).
- UI: "Submit"/"Suggest edit" wired into PortTypeManager, CableTypeManager,
  DevicePalette/DeviceTemplateEditor; new MySubmissionsModal (opened from
  TopBar, with a pending-count badge) shows status, rejection reasons, and
  lets you edit/resubmit or withdraw.
- Deliberately deferred: device_category submissions (no listing UI to
  hang a button on yet) and the normalized manufacturer catalog.

Verified: tsc -b and oxlint clean; supabase db reset + 23/23 pgTAP RLS
tests still pass (no schema changes this round); manually tested submit,
suggest-edit, edit-from-either-side sync, reject/resubmit, and withdraw.
2026-09-08 10:37:38 -05:00
aarbit a03987c36b Show the signed-in user's username in the top bar 2026-09-07 23:46:48 -05:00
aarbit b97777444a Wire the app to the shared Supabase catalog (public catalog read integration)
Moves port/cable/category/device-template data from per-diagram embedded
storage to the global public/private catalog backed by Supabase, per
organized-ideas.md's "live reference, not snapshot" decision.

- domain/types.ts, project.ts, compatibility.ts, bom.ts: lookup functions
  now take an explicit Catalog parameter instead of deriving data from
  Project — Project is reduced to just diagram-scoped fields.
- New CatalogRepository/SupabaseCatalogRepository (mirrors the
  DiagramRepository pattern) and catalogStore.ts, replacing the
  catalog-related actions that used to live in projectStore.
- device_templates gets a plain-text manufacturer column for now (the
  normalized manufacturer catalog from organized-ideas.md §3 is its own
  future pass, not blocking this one).
- domain/library.ts is no longer imported by the app — it's now only the
  source scripts/generate-seed.mjs reads to produce supabase/seed.sql.
- Swept every UI call site via tsc -b until clean; oxlint clean; 23/23
  pgTAP RLS tests still passing after a `supabase db reset`.
2026-09-07 23:42:00 -05:00
aarbitandClaude Sonnet 5 2f1b83e0a9 Add multi-diagram management
- DiagramRepository interface redesigned around multiple diagrams:
  list()/loadById()/deleteById() replace the old single-diagram
  load()/clear(). Both implementations updated to match.
- SupabaseDiagramRepository.save() now does an explicit update-or-insert
  instead of a blind upsert, so owner_id is only ever set at creation --
  an upsert would resend it on every save and let whoever saves last
  silently reassign ownership. Not reachable yet (no collaborator UI),
  but a real landmine once diagram sharing (organized-ideas.md §8) lands,
  and cheap to avoid now.
- LocalStorageDiagramRepository now stores diagrams keyed by id (was a
  single fixed key), keeping it a genuine working fallback rather than
  a stale reference implementing an old interface.
- Store: loadInitialDiagram (renamed from loadFromStorage) opens the
  last diagram you had open (tracked in localStorage -- a UI
  preference, not app data), falling back to the most recently updated
  one, falling back to a fresh empty diagram. New actions:
  refreshDiagramList, switchToDiagram, deleteDiagram. newProject and
  importProject now persist immediately (not just via the debounced
  autosave) so a new/imported diagram shows up in the list right away;
  importProject also assigns a fresh id so it can't collide with an
  existing diagram.
- New DiagramManagerModal (list/open/delete/+New), opened from a
  "Diagrams" button in TopBar that replaces the old single-diagram
  "New" button and its now-unnecessary confirmation dialog -- nothing
  is lost by creating a new diagram anymore, since the old one stays
  saved and reachable from the list.

Verified insert/list/update(-preserves-owner)/loadById/delete against
the real local stack; RLS test suite still 23/23 after a fresh reset.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
2026-09-07 21:19:24 -05:00
aarbitandClaude Sonnet 5 027455cc7d Add Google SSO
- Migration: profiles.username is now nullable -- Google's OAuth
  redirect can't collect a username up front the way the email/password
  signup form does, so a first-time Google sign-in's profile is created
  with no username.
- supabase/config.toml: [auth.external.google] enabled, credentials via
  env() substitution (SUPABASE_AUTH_EXTERNAL_GOOGLE_CLIENT_ID/_SECRET
  in .env.local, which the CLI auto-loads). skip_nonce_check is on,
  which Supabase's own docs call out as required for local sign-in.
- LoginScreen: "Continue with Google" alongside the existing
  email/password form.
- CompleteProfileScreen: one-time gate for a signed-in user with no
  username yet (i.e. first Google sign-in) -- same hard-gate spirit as
  email verification, nothing else is usable until a username is set.
- App.tsx now checks profiles.username after establishing a session and
  routes to CompleteProfileScreen before AppShell when it's missing.

RLS test suite re-run clean (23/23) after the schema change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
2026-09-06 23:28:25 -05:00
aarbitandClaude Sonnet 5 1ae967c8a4 Wire the app to the Supabase backend, replacing localStorage-only
- DiagramRepository (renamed from ProjectRepository, per the
  organized-ideas.md §8 naming decision) now has a Supabase-backed
  implementation as the active repository. LocalStorageDiagramRepository
  stays in the codebase as a reference implementation / fallback, just
  no longer wired in. Only the storage layer's naming changed here --
  the domain type, store, and UI copy still say "Project"; that's a
  separate, larger mechanical rename tracked on its own.
- Minimal email/password auth gate (src/components/auth/LoginScreen.tsx)
  since Supabase RLS requires a real signed-in user to do anything --
  this is NOT the Phase 2 experience (Google SSO, polished signup),
  just enough of the same schema (username + email + password) to make
  the backend foundation usable end to end before that phase exists.
  Respects the hard email-verification gate from config.toml.
- .env.example documents the required VITE_SUPABASE_URL /
  VITE_SUPABASE_ANON_KEY (local dev values, not secrets); .env.local
  has the actual local values and is gitignored.

Verified end-to-end against the local stack: signup creates a
confirmed-pending user, the handle_new_user trigger creates their
profile, sign-in is blocked until confirmed, and a signed-in session
can upsert/read back its own diagram row exactly as the app's
save()/load() do it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
2026-09-05 00:33:28 -05:00
aarbitandClaude Sonnet 5 e424e40f1b Simplify connection cost field label
Just "Cost", not "Cost override" -- there's no per-type default cost
set, and there isn't meant to be one right now (pricing varies too
much across cable grades), so "override" implied a relationship that
doesn't exist yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
2026-09-04 23:04:32 -05:00
aarbitandClaude Sonnet 5 e8654a39d6 Add BOM cost tracking
Suggested-default cost on CableType/DeviceTemplate, copied onto
placed Device instances (same pattern as ports) and overridable
per-connection/per-device in the diagram. BOM view shows per-line
and grand-total cost, excluding (and flagging) anything without a
computable cost rather than treating it as zero.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
2026-09-04 15:55:57 -05:00
aarbitandClaude Sonnet 5 7cf786e107 Updates UI arrows to match look and feel
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
2026-09-01 15:04:19 -05:00
aarbit 2c8759bf88 Adds connection status color to ports 2026-09-01 14:56:50 -05:00
aarbit 9710f1ee5a Adds first version of the app. Local storage only. 2026-09-01 14:44:49 -05:00