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.
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`.
- Catalog table ids (device_categories, port_types, cable_types,
device_templates) switch from uuid to text so the existing stable,
human-readable ids (pt-hdmi, dt-display, ...) survive the move
instead of every diagram's references silently orphaning.
- supabase/seed.sql is generated (scripts/generate-seed.mjs), not
hand-written, so the seed data can't drift from the actual source of
truth in domain/library.ts. Re-run the script after editing the
built-in library.
- Also committing ideas.md/organized-ideas.md, which have been driving
every backend decision this whole project but were never actually
checked in.
RLS test suite re-run clean (23/23) after both the schema change and
the seed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
- 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
- supabase/config.toml: local dev stack config, pinned to the app's
fixed dev server port, email confirmation required (hard
verification gate per organized-ideas.md).
- Initial schema migration: profiles/roles, the public/private
catalog tables (manufacturers, device categories, port types, cable
types, device templates + ports) with the shared is_public/owner_id
RLS pattern, a generalized catalog_submissions review-queue table,
and diagrams as JSONB documents (+ collaborators, snapshots) rather
than fully normalized -- see the migration's header comment for why.
- pgTAP RLS test suite (23 assertions) covering catalog visibility and
promotion-in-place, diagram owner/collaborator/admin/super-admin
visibility and edit permissions, submission visibility, and role
escalation. Caught and fixed a real infinite-recursion bug between
the diagrams and diagram_collaborators policies before this ever
touched real data.
- vite.config.ts: pinned dev server port so Supabase Auth's redirect
allow-list doesn't silently break if Vite floats to another port.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x