Files
av-planner/organized-ideas.md
T
aarbitandClaude Sonnet 5 93cb4a9617 Seed the public catalog from domain/library.ts
- 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
2026-09-07 23:02:12 -05:00

148 lines
22 KiB
Markdown

# AV Planner — Robustness Plan
Organized from `ideas.md`, plus decisions made while working through it together on 2026-09-04. Decided items are marked **Decided**; items I'm recommending but that are still yours to confirm are marked **Recommended**; things nobody's answered yet are marked **Open Question** and are also collected in one list at the end.
## 0. Guiding principle: local-first, no premature lock-in
Stated explicitly during planning: this needs to run **entirely locally** during development, with zero cost and zero cloud dependency, while still getting BaaS-level convenience (auth, roles, storage) instead of hand-rolling all of it. The choice to go to a specific cloud vendor is deferred until the app is actually ready for production. Every recommendation below is filtered through that constraint — anything that only runs as someone else's hosted service is out.
- **Decided: this is a development-time constraint, not a shipped product feature.** The deployed app is a normal networked web app that talks to a real backend — no offline mode, local cache/sync layer, or conflict resolution for disconnected use is in scope. "Local-first" describes how *you* build it, not a capability end users get.
---
## 1. Backend & Data Architecture
- **Decided: Supabase, self-hosted.** Postgres underneath, with built-in auth (Google OAuth included), storage, and row-level security (RLS) that maps cleanly onto the regular/admin/super-admin model in section 2.
- **Decided (resolves "determine relational or NoSQL"): relational / Postgres.** Falls directly out of the Supabase choice, and fits this data anyway — devices, ports, cables, diagrams, and users all have real relationships and referential integrity matters (e.g. a diagram shouldn't reference a device type that's been deleted).
- **Local dev:** the Supabase CLI (`supabase start`) spins up the full stack — Postgres, Auth (GoTrue), REST API (PostgREST), Storage, Realtime, and a local Studio admin UI — in Docker, on your machine, for free. No account or network access needed to develop.
- **No lock-in path:** because it's just Postgres + open-source services under the hood, "going to production" later is a real choice, not a foregone conclusion — options are Supabase Cloud (managed, easiest), self-hosting the same Docker stack on your own server, or exporting the Postgres database to any other Postgres host. None of the application code needs to change based on which of these you pick.
- **Frontend integration:** the app already isolates storage behind a `ProjectRepository` interface (see `src/data/`), specifically so a real backend could be swapped in without touching UI/state code. A Supabase-backed repository implementation slots into that seam directly.
- **Revised: diagrams reference catalog entries by id (live reference), not a full embedded snapshot.** Superseded from an earlier pass — see the discussion recorded in §3, which walked through concrete scenarios and decided live references are net better: they let your own catalog edits (e.g. a price change on a custom cable type) propagate across all your diagrams automatically, and let cosmetic fixes to public entries reach diagrams that already used them, at the cost of a public entry's *compatibility-affecting* fields being able to affect existing diagrams if edited carelessly — which §3's impact-check-before-editing and public-entries-are-never-deleted decisions specifically guard against. Placing a device still copies its *ports* (fresh ids, independent of the template afterward) — that part of the original pattern is unchanged; it's specifically port/cable/category/manufacturer *type* definitions that are referenced live, not copied.
- **Decided: schema changes go through git-tracked migrations via the Supabase CLI**, not ad hoc edits in the local Studio UI — keeps local dev and eventual production reproducible and reviewable, same as any other code change.
- **Decided: automated tests specifically for the RLS policies.** These are the actual security boundary once roles matter, and they're easy to get subtly wrong in a way that fails open (exposes data that shouldn't be visible) rather than fails loud — worth explicit test coverage even if the rest of the app is tested more lightly.
## 2. Authentication & Users
**Roles** (restated as a table):
| Role | Can do |
|---|---|
| Regular | CRUD own devices; create/own diagrams; use public devices in diagrams; submit new devices or edits to existing ones for review |
| Admin | Everything Regular can, plus: view/approve/reject device & port/cable-type submissions |
| Super Admin | Everything Admin can, plus: CRUD *all* devices/ports/cables (public or anyone's); view and CRUD *any* user's diagrams; CRUD user accounts. Cannot see/edit a user's password (Supabase Auth stores credentials itself — the app never has access to raw or hashed passwords). |
- **Recommended:** enforce this at the database layer with Postgres row-level security policies keyed off role, not just in application code — it's the natural fit for Supabase and means a bug in the UI can't accidentally expose another user's data.
- **Signup/signin:** Google SSO (username collected up front, email pulled from Google) or manual signup (username, password, verified email) — both handled by Supabase Auth directly, so no custom password-hashing/session code needed. Username must be unique — enforced with a DB unique constraint.
- **Decided: accounts auto-link on matching verified email.** If someone signs up manually with an email and later signs in with Google using that same address, treat it as one account rather than creating a second, confusing one — matches the far more common real-world case (same person) over the rarer risk case.
- **Decided: baseline abuse prevention from the start**, not deferred — basic rate limiting on auth (leaning on what Supabase Auth provides out of the box) plus a soft cap on pending submissions per user (see §3). Cheap to build in now, expensive to retrofit once there's real usage and real abuse; self-hosting means infra abuse cost lands on you directly, not a SaaS vendor.
- **Decided: email verification is a hard gate.** A manually-signed-up user must verify their email before creating diagrams, submitting devices, or doing anything else — no unverified account accumulates real data. Google-SSO users are implicitly verified via Google, so this only affects the manual-signup path.
### Determine: best option for sending emails
**Recommended.** Auth emails (signup confirmation, password reset) go through Supabase Auth's SMTP integration — Supabase doesn't send mail itself, it relays through whatever SMTP server you configure, which keeps this swappable-by-design (change SMTP credentials, not code). For local dev, the Supabase CLI includes **Inbucket**, a fake mail catcher with a web UI, so you can test signup/reset flows locally without sending real email or paying anything. For production, plug in any standard transactional-email provider over SMTP — **Resend** is a reasonable default (generous free tier, simple setup), with Postmark or Amazon SES as alternatives if deliverability or cost at higher volume becomes a concern. This is a config change, not an architecture decision, and doesn't need to be locked in now.
### Determine: path for allowing users to change their email
**Recommended.** Supabase Auth has this built in (`updateUser({ email })`): the user requests the change, a confirmation link is sent to the *new* address, and the email only actually updates once that's confirmed. No custom flow to build — just wire up the UI for it. Username stays separate from email and keeps its own uniqueness check.
### Determine: path for migrating a user account from one Google account to another
**Recommended**, two-tier:
1. **Self-service (has access to both accounts):** Supabase Auth supports linking a second OAuth identity to an existing account (`linkIdentity`). The user signs in with the old Google account, links the new one, then unlinks the old — same internal account/UUID throughout, so all owned diagrams and devices carry over with zero data migration.
2. **Lost access to the old account:** needs a human — a Super Admin tool to reassign an account's linked identity or merge two accounts (transfer diagrams/devices from A to B). This is an edge case, not needed for MVP; flagging it now so the Super Admin "CRUD user accounts" capability is built with this in mind rather than needing rework later.
---
## 3. Device / Port / Cable Type Library (Public vs. User-Defined)
- Devices, port types, and cable types all follow the **same pattern**: a public, curated catalog plus each user's own private catalog.
- Public catalog entries can't be edited directly by regular users; they can only submit a new entry or a suggested edit to an existing one, which goes into a review queue for Admins.
- A user's own catalog entries are visible only to that user (and Admins/Super Admins), and can be submitted for promotion to the public catalog.
- **Seeding:** the app's current hardcoded libraries (`domain/library.ts` — port types, cable types, device templates, categories) become the initial seed data for the public catalog once the backend exists, rather than being thrown away.
- **Decided: review/approval experience.** An Admin reviewing a submission sees a **diff against the current public entry** (not just the raw submitted version), and the submitter is **notified either way** — approved or rejected, with a reason attached on rejection rather than a silent disappearance.
- **Decided: notify in-app, not just by email.** A bell icon/activity feed surfaces submission outcomes for submitters and a pending count for Admins — email is fine as the durable record, but a faster in-app loop matters for an Admin actively working through the queue.
- **Decided: a light duplicate-detection nudge for Admins** reviewing new device submissions — fuzzy-match the proposed name against existing public entries and flag likely duplicates, so the catalog doesn't slowly fill with near-identical entries as more people submit.
- **Decided: a soft cap on pending submissions per user** (exact number TBD when this is built), as part of the baseline abuse prevention in §2 — stops one user from flooding the review queue.
- **Decided: approval promotes the submitter's entry in place**, rather than creating a separate new public entry. Same id, just flips from private to public — the submitter's existing diagrams (which already snapshotted it, per §1) don't need re-pointing, and there's no leftover duplicate private copy sitting alongside the new public one.
- **Decided: rejected submissions stay editable for resubmission**, not deleted or dead-ended. The submitter can revise based on the Admin's stated reason and resubmit the same submission rather than starting over from scratch.
- **Decided: "manufacturer" becomes a normalized, submittable catalog** — the same public/user-defined pattern already used for device categories, rather than free text on each device. Keeps the manufacturer↔category browse view in §5 clean and gives the duplicate-detection nudge above a much more reliable signal (matching manufacturer + similar model name) than name-matching alone.
- **Decided: catalog entries reference by id, accepting the risk that a careless edit to a public entry could affect diagrams already using it** (see the revised §1 note) — worked through via concrete scenarios: this is strictly better for the common cases (your own edits propagating across your diagrams, cosmetic fixes to public entries reaching existing users), and if an edit does surface a real problem, that's arguably diagnosing something that was actually wrong rather than "breaking" a diagram. Two things make the remaining risk manageable rather than ignored:
- **Impact check before an Admin acts.** Before an edit-submission is approved (or a Super Admin edits/hides a public entry directly), they see how many diagrams reference that entry and a short sample (diagram name + owner username) — computed by a privileged, admin-only function that returns only that aggregate, *not* raw access into other users' diagram contents. Regular Admins still don't get diagram visibility (only Super Admins do, per §6) — this doesn't change that boundary, it just answers "what's the blast radius" without needing it.
- **Public catalog entries are never hard-deleted**, only hidden/unpublished from new use (the same pattern already used for hiding built-in device templates) — closes off the one scenario (an entry disappearing out from under a diagram, showing "Unknown type") that live references can't otherwise defend against.
- **Decided: site-wide announcements.** A new lightweight concept: an Admin can post a dismissible banner that every signed-in user sees (e.g. "the XLR-to-TRS adapter cable's compatibility changed on 2026-09-08, check any diagrams using it") — offered as a follow-up step right after acting on an impact-flagged change, so users aren't surprised by something that already shipped.
## 4. Canvas — Compact & Expanded Device Views
**Compact view:**
- Shows device name only (no make/model) plus aggregate connected/open counts per input/output/bidirectional group, so you can tell what's still available at a glance without seeing every individual port.
- Connections to another device are drawn as a single "virtual cable" per connection-type group rather than one line per physical port, when more than one real connection of that type exists between the same two devices.
- Aggregated virtual cables need a visual marker (tag/label/style) so it's obvious at a glance that a line represents more than one real cable.
**Expanded view:**
- What the app already does today — every port shown individually with name and type, full device name/make/model, one real line per real connection.
- **Recommended:** whatever visual grouping happens in compact view must be purely a canvas rendering concern — the BOM math (cable counts, lengths, totals) always operates on the real underlying connections, never on the aggregated display. Otherwise compact view could silently produce a wrong shopping list.
- **Implementation detail, still loose:** exactly what "connection-type group" means for aggregation purposes isn't fully pinned down — grouped by cable type, by port family, by direction, some combination? Low-stakes to leave open since this is frontend-only and easy to iterate on visually once it's being built.
- **Decided: toggle scope.** Per-device (each device node expands/collapses independently), defaulting to **compact** for a newly-placed device — consistent with the palette categories already defaulting to collapsed. Plus **"Expand all" / "Collapse all"** buttons on the canvas for quickly toggling every device at once, rather than clicking through each one individually.
## 5. Device Menus
- **Public device menu:** searchable; browsable both by category→manufacturer and manufacturer→category (two view modes, not a single fixed hierarchy).
- **User device menu:** searchable, browsed by category (the app's existing category system — see the categories feature already built — extends naturally here). Visible only to its owner (and Admins/Super Admins). Entries can be submitted for public promotion from here.
## 6. Admin / Super Admin Interface
| Capability | Admin | Super Admin |
|---|---|---|
| View/approve/reject device & port/cable-type submissions | ✓ | ✓ |
| CRUD public devices/ports/cables | ✓ | ✓ |
| CRUD *any* device/port/cable (public or user-owned) | | ✓ |
| View any user's diagrams | | ✓ |
| CRUD any user's diagrams | | ✓ |
| CRUD user accounts | | ✓ |
| Suspend/ban a user account (reversible, without deleting their data) | | ✓ |
| View/edit a user's password | never — nobody, at any role |
- **Decided: suspend/ban is a separate, lighter capability from delete.** Blocks login without destroying the account's data, so a problem user can be dealt with without an irreversible action.
## 7. Bill of Materials Enhancements
- Cables of the same type grouped together; expand with a chevron to see the individual cable-length entries within that type.
- Cables that also share the same *length* get their own subtotal count and total length.
- Cost input for each cable (by type+length) and for each device in the diagram; a grand total cost across the diagram.
- **Recommended:** store cost as an optional *suggested default* on the device/cable type definition (public or user-defined), but let each diagram override it — public catalog prices will drift out of date and vary by vendor/region, so the diagram-level number should always win. This also means public catalog entries don't need price moderation as part of the review workflow.
## 8. Diagram Management
- A user can see, load, and delete their own saved diagrams.
- **Decided:** diagrams can be shared with specific people (not public-gallery-by-default, not strictly private-only) — this adds a collaborators concept to the data model (diagram owner + a list of users with access).
- **Decided: collaborator permissions.** Configurable per-collaborator — the owner picks view-only or edit access for each person they share with individually, rather than one fixed permission level for everyone the diagram is shared with.
- **Implementation note:** this makes the diagram RLS policies meaningfully more complex than a simple "owner_id = you" check — they need to account for owner access, per-collaborator permission level, and Super Admin override all composing correctly. Worth keeping specifically in mind when the RLS test coverage from §1 gets written for diagrams.
- **Decided: co-editing model.** Turn-based shared access, not real-time simultaneous co-editing. Collaborators (with edit access) can make changes, but there's no live-cursor/multiplayer layer — no realtime conflict-resolution engine needed for this.
- **Decided: naming.** Rename "Project" to "Diagram" throughout the app (code, UI, docs) for consistency with the notes and this plan. This includes renaming the `ProjectRepository` interface and its implementations (see §1) as part of that pass.
- **Future idea, not in scope now:** a "Project" could later become its own concept above Diagram — a folder-like container holding multiple related Diagrams (e.g. all the diagrams for one physical rig or one client). Worth keeping the data model open to this later without building it now.
- **Decided: keep a rolling window of recent diagram snapshots** (e.g. last N saves, or last 30 days — exact policy TBD when this is built) as an undo/recovery safety net. Not full version control, but now that turn-based collaborators can edit a diagram, a bad edit — yours or theirs — needs to be recoverable.
- **Decided: migration path for existing local data is the app's existing JSON export/import**, not a dedicated importer. Export the current local diagram, sign up for a real account, import it in — no new code required.
---
## 9. Suggested Phased Rollout
The full scope above is a lot to build at once. A rough sequence that keeps each phase shippable and testable on its own:
1. **Local backend foundation** — stand up self-hosted Supabase locally with git-tracked migrations from day one, migrate from localStorage-only to a real `DiagramRepository` implementation backed by it (renamed from today's `ProjectRepository`, per the naming decision in §8), single-user only (no roles/sharing/public library yet). Diagrams already snapshot catalog data rather than live-referencing it, per §1. This alone turns the app from a browser-toy into something with durable, real storage.
2. **Auth** — Google SSO + manual signup/signin via Supabase Auth, tied to the diagrams from phase 1 (one user, own diagrams). Includes account auto-linking on matching email, the hard email-verification gate, and baseline abuse-prevention/rate-limiting from §2.
3. **Multi-diagram management** — the diagram list/load/delete UI, since today's app only ever has one active diagram (currently called "Project" in code, pending the rename). Existing JSON export/import doubles as the migration path for current local data, per §8.
4. **Public vs. private device/port/cable catalogs + submission workflow** — the biggest chunk of new complexity; probably worth its own sub-phasing (public *read* catalog first, then user-submission, then the Admin review queue with its diff view, in-app notifications, duplicate-detection nudge, in-place promotion on approval, resubmittable rejections, and per-user submission cap, all per §3). Includes the normalized manufacturer catalog alongside categories.
5. **Roles & Admin/Super Admin interface** — layer RBAC in once there's something (submissions, other users' data) that actually needs gating. Includes account suspend/ban (§6). RLS policy test coverage (§1) belongs here too, alongside the policies themselves — especially for diagrams, once collaborator permissions make those policies non-trivial (§8).
6. **Diagram sharing/collaborators** — turn-based access with per-collaborator view/edit permissions, plus the rolling-snapshot undo/recovery safety net, per §8 — the two are related (recoverability matters more once someone besides the owner can edit).
7. **BOM cost tracking** — largely independent of the backend work; could realistically be pulled earlier if you want a quick visible win.
8. **Compact/expanded canvas views + aggregated cables** — also largely independent/frontend-only; another candidate to pull earlier if desired.
This is a suggested order, not a commitment — happy to reshuffle if priorities change.
---
## 10. Status
Everything raised across all three planning passes has been answered and folded into the relevant sections above as **Decided** — nothing is currently blocked on a decision. Two things intentionally left loose rather than answered, both low-stakes and easy to settle when actually built: the future "Project groups multiple Diagrams" idea noted in §8, and the exact grouping rule for aggregated virtual cables in compact view (§4).
A fourth pass (while implementing §3) revised the §1 snapshot-vs-reference decision — catalog entries are referenced live by id, not embedded as full copies — and added the impact-check-before-editing, never-hard-delete-public-entries, and site-announcements decisions to §3 as the mitigations that make that safe in practice.