Woodpecker's linter flags steps with no event filter as a "bad habit" (they'd
otherwise run on every event type, including ones added to Woodpecker in the
future). Scoped to [push, deployment] specifically — not just push — since a
production deploy re-runs the whole pipeline as a deployment event, and
deploy-production's build needs these steps (especially install's node_modules)
to have actually run first.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
Caught from the linter warnings on the first real pipeline run (#1) — the
production-promotion step's event filter used \`deploy\`, an invalid event
name, so it would never have matched Woodpecker's actual deploy-button
event (\`deployment\`) and the production step would have silently never
run. Staging's own deploy (a push-triggered step) was unaffected and
verified working on that same run.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
Adds .woodpecker.yml (lint + typecheck on every push, auto-deploy to a
shared staging environment on every push, manual Deploy-button promotion to
production on main) and env.staging blocks in both wrangler configs so
staging gets its own Workers (diagrav-app-staging/diagrav-site-staging at
staging-app.diagrav.com/staging.diagrav.com) rather than sharing anything
with production.
Staging also got its own fully separate Supabase Cloud project (own
database, own Auth config reusing the same Google OAuth client with an
extra redirect URI, own Resend-backed SMTP) — migrated, seeded with the
public catalog, and verified end-to-end with a real sign-in through the
deployed app before wiring any of this into CI.
Updates deployment-plan.md's CI/CD section to match what actually got
built, superseding the earlier manual-pushbutton-for-both-environments
version of the plan.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
Adds a dev:lan script (vite --host) so testing on a real device doesn't
require permanently changing vite.config.ts's default 127.0.0.1-only
binding. Pairs with a small supabaseClient change: when the configured
Supabase URL is local (127.0.0.1/localhost), the client now swaps in
whatever hostname the page itself was loaded from, so a phone hitting the
dev server's LAN IP gets API calls routed to that same IP automatically —
no more hand-editing VITE_SUPABASE_URL per network. Never touches a real
production Supabase URL.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
Below md width, the app now forces view-only (same canEdit mechanism as a
shared view-only diagram) rather than trying to support touch editing:
palette and inspector panels hide, editing controls disable, and the top
bar collapses to just Diagrams + Account. A bottom tab bar swaps between a
full-screen canvas and a full-screen Bill of Materials, since there's no
room for both side by side. Canvas gets a few mobile-specific trims too:
MiniMap and the interactivity lock button hide whenever canEdit is false
(the lock button doesn't actually grant editing either way — nodesDraggable/
nodesConnectable already override it — so leaving it visible would just be
misleading), and h-screen is replaced with h-dvh throughout so the layout
doesn't get clipped by a mobile browser's collapsing address bar.
Also fixes quick-add's anti-stack offset (24px — far smaller than a device
node) to actually tile devices apart instead of leaving them nearly
overlapping, and logs a deferred "Auto-arrange" layout feature in
organized-ideas.md, surfaced by mobile visitors having no way to fix an
overlapping diagram themselves.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
The hover-reveal button row used opacity, not display, so it always
reserved its full width even while invisible — squeezing long names down
to a few characters at rest, worst for an Admin's 5-button rows. Switched
to hidden/group-hover:flex so idle rows show the full name, and made the
row flex-wrap so a crowded button set drops to its own line on hover
instead of squeezing the name further.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
Adds Workers + static-assets configs for the app (app.diagrav.com) and a
separate marketing/landing site (diagrav.com), plus the deployment runbook
used to stand up Supabase Cloud, Google OAuth, Resend SMTP, and Cloudflare
DNS/custom domains. Renames the app from "AV Planner" to "Diagrav" to match
the new domain.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
Replaces the old ungendered catalog (plain XLR, TS/TRS 1/4", Speakon
NL2/4/8, etc.) with a full Male/Female-aware set covering audio, video,
network/data, USB (with Host/Device role split for USB-C), power, RF coax,
and fiber connectors, plus real-world bridging/adapter cables — enough to
model gender benders and specify exact cable types in the BOM instead of
just a connector family. The catalog now lives entirely in the DB (seeded
via supabase/seed.sql, captured with the new export-seed.mjs) rather than
partly hardcoded in domain/library.ts, which is now unused.
Existing seeded diagrams/devices were intentionally not migrated to the
new port types — there was nothing important in them yet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
Announcements: a Super Admin (or an Admin individually flagged via
profiles.can_post_announcements) can post/retire a site-wide banner.
Account migration: a Super Admin can move a locked-out user's diagrams,
private catalog entries, and submissions to another account, with a
migration-history log; ProfileModal adds the self-service half (link a new
Google identity via Supabase manual linking, then unlink the old one,
while signed in as the account being migrated). Also reworks the top bar's
flat button row into grouped dropdown menus (Diagram / Admin / Account) now
that there are enough entries to need it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
Canvas devices can now collapse to a compact per-direction-group view; when
collapsed, cables between the same two devices of the same type merge into
one dashed "virtual" edge with a ×N marker so a busy rig doesn't turn into
a wall of parallel lines. Adds CableBundle so several real Connections
(a stereo RCA pair, a snake's channels) can be grouped and bought as one
BOM line while keeping each leg's own family-checked cable type intact.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
Normalizes manufacturer as a shared catalog entity (like device categories)
instead of free text on each device, giving the admin duplicate-detection
nudge a reliable signal. Adds full CRUD (including Admin direct-publish,
bypassing the submission queue) for categories, manufacturers, port types,
and cable types, plus a Categories & Manufacturers library modal and a
browse-by-manufacturer/search view in the device palette. Adds
Port.builtInCable so a captive/permanently-attached cable (a keyboard's USB
lead, a budget AVR's power cord) can be flagged and excluded from the BOM.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
RLS policies alone don't grant table-level access in Postgres — missing
GRANTs caused "permission denied for table profiles" in production even
though the RLS policies were correct.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
CompleteProfileScreen threw a raw PostgrestError (not an Error instance),
so `err instanceof Error` was false and the real message got swallowed.
LoginScreen's Mailpit hint was showing in production; gated behind DEV.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
Per organized-ideas.md §8: the storage layer (DiagramRepository etc.) was
already renamed in an earlier phase; this finishes it everywhere else.
- domain/types.ts: Project -> Diagram. domain/project.ts -> domain/diagram.ts
(createEmptyProject -> createEmptyDiagram, default name "Untitled Diagram").
- domain/compatibility.ts, domain/bom.ts: Project param/type -> Diagram.
- data/exportImport.ts: ProjectImportError -> DiagramImportError,
projectToJson/downloadProjectFile/readProjectFile/normalizeProject -> their
Diagram equivalents.
- state/projectStore.ts -> state/diagramStore.ts: useProjectStore ->
useDiagramStore, the `project` field -> `diagram`, newProject/renameProject/
importProject/applyRestoredProject -> *Diagram, restoredProjectUpdatedAt ->
restoredDiagramUpdatedAt.
- Every component updated to match, compiler-guided (tsc -b enumerated each
remaining call site after the core rename, the same approach used for the
earlier catalog-parameter refactor).
- README updated for the terminology, and to match the repository class
names (which had already been renamed but the README hadn't caught up).
No backend/schema changes: the JSON shape stored in diagrams.data never
changed, only TypeScript-side identifiers, so existing diagrams are
unaffected. One SQL comment fixed for accuracy (no migration needed).
Verified: tsc -b and oxlint clean; grepped src/ for any remaining
Project/project reference (none) after the sweep.
Per organized-ideas.md §7: cables of the same type sharing the same length
now get their own subtotal (count + total length + cost), revealed by
expanding that cable type's row rather than only showing one blended total.
Closes out the last open item in §7 — cost input, per-diagram override, and
the grand total were already in place.
- domain/bom.ts: computeBom groups each cable type's connections by
distance too (a separate bucket for connections with no length entered),
sorted shortest first.
- BomPanel: a chevron toggles the breakdown, only shown when a cable type
actually has more than one distinct length group (nothing to reveal
otherwise).
Verified: tsc -b and oxlint clean. No backend involved.
RLS is the real guard (diagram_collaborators_insert/update now reject
user_id = the diagram's owner, regardless of who's performing the write —
covers a Super Admin acting on someone else's diagram too, not just the
normal owner path); the client-side check in
SupabaseDiagramCollaboratorRepository.add is just there to surface a
friendly message instead of the raw 42501.
Verified: tsc -b and oxlint clean; supabase db reset + 53/53 pgTAP tests
pass (1 new test).
Per organized-ideas.md §8. Backend tables/RLS (diagrams, diagram_collaborators,
diagram_snapshots) already existed from an earlier phase — this is the
frontend for them, plus two small backend additions.
Backend (supabase/migrations/20260913000000_diagram_sharing.sql):
- find_user_id_by_username(text): lets any authenticated user resolve a
username to an id for "share with @username" — unlike general profile
browsing (blocked by profiles_select_self_or_super_admin), a username is
meant to be a shareable handle, so this is deliberately not gated.
- diagram_collaborator_usernames / diagram_snapshot_saved_by_usernames:
same pattern as the admin-review-queue phase's submitter-username
lookup — batched per diagram, gated to "can you see this diagram at all"
(reusing diagrams_select's own helper functions).
- prune_diagram_snapshots trigger: keeps the 50 most recent snapshots per
diagram, enforced at write time rather than a scheduled job (diagram_
snapshots has no update/delete policy for regular users at all).
- 14 new pgTAP tests (52/52 total).
Frontend:
- DiagramCollaboratorRepository/store + DiagramSharingModal: add/remove
collaborators by username, per-person view/edit permission, owner-only
controls.
- DiagramSnapshotRepository/store + VersionHistoryModal (its own top-bar
button, not nested under Share — moved there after review): periodic
checkpoints (one per 5 min of active editing) written as a side effect
of normal saves, list + restore.
- Restore's duplicate-snapshot problem: repeatedly jumping between old
versions without editing in between was writing a near-duplicate safety
snapshot on every jump. Fixed by having projectStore track which
snapshot the diagram was last restored from and its updatedAt at that
moment (touch() always advances updatedAt on a genuine edit) — a restore
skips the safety snapshot when nothing has changed since the last one,
and the tracking clears on any real edit so in-progress work stays
protected.
- DiagramRepository gains getAccess() (owner id + your own permission for
the open diagram) — surfaced in projectStore as `access`.
- View-only enforcement: FlowCanvas disables drag/connect/drop
(nodesDraggable/nodesConnectable + guarded handlers), DeviceInspector/
ConnectionInspector wrap their controls in a disabled <fieldset>,
DevicePalette disables adding devices to the canvas, TopBar disables the
rename field, and a ViewOnlyBanner makes the restriction visible instead
of leaving a collaborator to discover it as controls that just don't
work. Autosave itself also refuses to write for a view-only user, as a
backstop behind the UI-level lockdown.
Verified: tsc -b and oxlint clean; supabase db reset + 52/52 pgTAP tests
pass; confirmed find_user_id_by_username works through the real REST API
via a live curl call (signup, confirm, resolve). Manually tested two-
account sharing (view vs. edit), restoring history, and the duplicate-
snapshot fix.
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.
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.
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.
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
- 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
- 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
Vite's default host resolves "localhost" to the IPv6 loopback (::1)
on this machine, so it never actually bound 127.0.0.1 -- but that's
the exact address Supabase Auth's email confirmation links redirect
to (config.toml uses 127.0.0.1 throughout). Binding explicitly to
127.0.0.1 fixes confirmation links without needing to hand-edit them.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
- 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
- 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
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
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