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.
This commit is contained in:
2026-09-08 10:37:38 -05:00
parent a03987c36b
commit a0c598cb1b
14 changed files with 968 additions and 119 deletions
+4 -1
View File
@@ -1,6 +1,7 @@
import { useEffect } from 'react'
import { useCatalogStore } from '../../state/catalogStore'
import { useProjectStore } from '../../state/projectStore'
import { useSubmissionStore } from '../../state/submissionStore'
import FlowCanvas from '../canvas/FlowCanvas'
import DevicePalette from '../palette/DevicePalette'
import ConnectionErrorBanner from './ConnectionErrorBanner'
@@ -12,11 +13,13 @@ export default function AppShell() {
const loadInitialDiagram = useProjectStore((s) => s.loadInitialDiagram)
const isCatalogLoaded = useCatalogStore((s) => s.isLoaded)
const loadCatalog = useCatalogStore((s) => s.loadCatalog)
const loadMySubmissions = useSubmissionStore((s) => s.loadMySubmissions)
useEffect(() => {
loadInitialDiagram()
loadCatalog()
}, [loadInitialDiagram, loadCatalog])
loadMySubmissions()
}, [loadInitialDiagram, loadCatalog, loadMySubmissions])
if (!isLoaded || !isCatalogLoaded) {
return <div className="flex h-screen items-center justify-center text-sm text-slate-400">Loading…</div>