Derived from prototypes 01–22 · becomes the first Supabase migration

The data model

Twenty-four tables in four domains. Everything the mockups show falls out of these entities and the row-level-security policies on top of them. If a screen exists, its rows are here.

Entities by domain

TENANCY & PEOPLE
planner_studioThe top tenant. Owns weddings, the roster, staff, the studio theme.
vendor_accountGlobal vendor identity — one login everywhere; profile, W-9, COI on file.
weddingThe container: mode (planner-led / self-plan), dates, city, theme.
membershipPerson × wedding × role bundle (admin, staff, couple, family_voice, event_lead, observer) + per-module capability overrides.
funding_sideBride's side / groom's side / couple / custom — budget attribution target.
roster_entryStudio × vendor: tier, private notes, usage stats. Auto-upserted per engagement. The planner's IP.
engagementVendor × wedding — the scope of everything a vendor sees.
EVENTS, GUESTS & TRADITIONS
traditions_packVersioned data file: event templates, ritual items, song/shot seeds. New culture = new pack, zero code.
eventHaldi, sangeet, muhurtham… time, venue, dress code, source pack.
householdThe guest unit; side, city, travel status, visa-letter flag.
guestPerson in a household; meal choice, accessibility notes.
inviteHousehold × event — the matrix. Drives RSVP, counts, photo groups, seating.
rsvpGuest × event response + meal; timestamps for the chase list.
muhurat_shortlistCommunity, calendar system, candidate dates + windows, panditji status; chosen date anchors the wedding.
tablePer event: number, display name & description ("Marina Beach — Chennai elders"), capacity, type (round / kids / head), and floor-plan x·y — the arrangement is data, exportable to the venue & decorator brief, never a PDF someone redraws.
seat_assignmentGuest × table × event. Households are the default grouping, not an atomic unit — splits (kids table, friend-table moves) reassign people while RSVP, meals, and counts stay household-true.
outfitLook × event × person: pieces, jewelry, tailor status, fitting dates.
WORK & DECISIONS
decisionProposed → discussed → approved → signed; polymorphic subject (quote, budget_line, event…).
signaturePerson × document version: typed name, timestamp, doc hash. Locks the version. Also serves change orders.
change_orderDelta on a signed subject: scope diff, amount diff, its own signature chain incl. the affected funder.
issueTitle, assignee, due, status — visible wherever its tags point.
tagIssue × any object (event, engagement, decision, budget_line). References, not strings.
taskChecklist items, largely seeded by traditions packs.
announcementMessage + role×event scope + computed audience snapshot; counts against guest cap.
notificationPerson × object × channel × state; digest batching, escalation, language.
MONEY & VENDOR WORK
rfqStructured scope sent to N vendors so quotes compare.
quoteVendor response: line items, total, flags (e.g., teardown excluded). Accepted → engagement + budget_line + decision.
contractSigned doc (hash in R2) + payment schedule.
briefVersioned, typed (shot / song / menu / decor); vendor confirms per version.
info_requestReverse brief: structured fields (day-of contact, arrival…) that flow into run sheets.
budget_lineEvent × funding_side: planned (from quote) vs actual (from invoice) vs paid.
invoiceStructured lines + PDF; posts to actuals.
paymentMilestone × method (stripe_ach / card / off_platform_recorded) × status. Ledger complete either way.
run_sheet_itemEvent timeline row; reads engagements, info_requests, photo groups.
reviewPost-wedding rating + note → upserts roster_entry. The flywheel edge.

Key relationships

RelationShapeWhy it matters
planner_studio → wedding1 : NThe multi-wedding dashboard; self-plan weddings have studio = null until a planner is invited (the handoff = setting this one FK).
studio × vendor_account → roster_entryM : NAuto-upserted whenever an engagement is created. Compounding IP, and the input to date-conflict warnings.
vendor_account × wedding → engagementM : NThe vendor's entire visible universe hangs off this row.
household × event → invite → rsvpmatrixOne truth feeding headcounts, meals, photo groups, seating, dietary counts in the caterer brief.
quote → budget_line ← invoiceplanned / actualVendors and Budget are two views of the same money; over/under is a query, not a spreadsheet.
budget_line → funding_sideN : 1Powers side-scoped family views and funder-routed payment requests & change-order signature chains.
decision → any subject · signature → any versionpolymorphicOne audit + e-sign mechanism serves decisions, contracts, briefs, and change orders.
issue → tag → any objectpolymorphicAn issue surfaces on every page its tagged objects own — write once, appear everywhere.
review → roster_entryupsertSettle feeds Sourcing. The flywheel is literally one foreign key.
traditions_pack ⇒ event · task · brief seedsgeneratorPacks are data, so a new culture never touches the schema.
RLS in one sentence per seat: a vendor can read rows only where engagement_id is theirs; a family voice reads budget_lines only where funding_side is theirs; an event lead writes only where event_id is in their membership; a guest touches only their household's invites and RSVPs; the studio reads everything under its weddings. Permissions are the schema — the app just renders what a row-level policy already allowed.
DATA MODEL v1 · DERIVED FROM MOCKUPS · NEXT STOP: SUPABASE MIGRATION 0001
✎ FEEDBACK