Reading the schema as a set of product commitments, not just a data model. These are the places where the schema implies a product vision that should be stated explicitly — because migrating from Airtable is the moment where every "eh, we'll see" becomes a concrete choice that's expensive to reverse.
The mutable owner_org_id with placeholder-and-transfer is a major design commitment. It makes sense only if a contractor can legitimately start a project for a prospective client — with real phases, real quotes, real budget breakdowns — before the client has signed up. When the deal closes, the project transfers and nothing breaks.
That's a big product claim: Reno as the contractor's client-acquisition surface, not just their client-management surface.
- Is this the intent?
- If yes, it should be named as a goal — it's the kind of thing that justifies the snapshot/projection machinery.
- If no, placeholder-and-transfer is expensive complexity without a user story to pay for it.
The schema shape says contractors pay, homeowners get access through grants:
- Org-scoped vendor/contact/payment lists (a vendor list that spans 20 client projects is a contractor concept)
- Margin lines, billed-through, standard_rate on vendor (pro bookkeeping)
- Project ownership starts under an Org that already has reusable reference data
If the business model is actually homeowner-first (homeowner pays, invites their GC), some of this gets awkward. A homeowner doesn't have a cross-project vendor list the way a GC does — they have one reno, maybe two.
What's the pricing/monetization model, and does the schema match it?
The schema treats money seriously:
numeric(14,2)explicitly chosen- Payment applications as a real junction (one payment across many invoices, split-payment amounts tracked)
- Credit applications as a first-class concept
- Margin lines, offset transactions excluded from budget math but preserved
- Invoice total stored separately from the transaction roll-up (matches the physical PDF)
This is "your CPA can use this" territory, not "track your spend" territory.
- Is that the claim?
- If yes, this deserves a sanity check against a real CPA's mental model before shipping — ideally one pass where an accountant tries to reconcile a completed reno.
- If no, some of this (credit applications, margin reciprocity) could be simplified for v1 and grown later.
The grant/snapshot/public-projection layer is the single biggest source of complexity in the schema. If it's deferred, v1 gets meaningfully simpler: single-Org projects, no grants, no snapshots, no projection helpers.
The doc assumes day-one because Airtable has it in spirit. But:
- How many of today's Airtable projects actually have cross-Org collaboration in a form that needs this shape?
- Could v1 ship with just "Orgs + users + projects," and the grant/snapshot layer land in v1.5 once there's real usage data about who actually needs what access?
The forward-compat wiring already preserves this option — the question is whether to preserve it and defer the implementation.
Real renos are photo-heavy: before/progress/after shots, product spec sheets, reference images, inspirations, paint chips, floor plans. The schema treats all of these as generic attachment rows.
- Is that the intent for v1 — photos are inert files attached to the thing they document?
- Or is there a future "photos are first-class, with EXIF, location, chronology, gallery view" concept that would deserve its own model?
If the latter, worth naming now even if it's deferred, because it affects how you think about attachment. A gallery-first product has different needs than a document-storage product.
A completed reno has real long-tail value: warranty lookups, "what paint did we use in the hallway," resale disclosures, tax records, insurance claims. That read pattern is different from an active project — it's sparse, occasional, and sometimes happens years later.
The schema has a free-text status that can be "complete" or "archived," but no archival semantics beyond that.
- Is long-tail read a real use case, or does a completed project just sit there?
- If real: does it get a different UI? Different access rules (e.g., grants auto-expire but archival read stays)? Different export (PDF-of-everything)?
This is the kind of thing that's much cheaper to design in than to retrofit.
The schema is clear about what Reno is not, and that's a feature — it's a system of record, not a collaboration surface:
- No messaging/comments/threads
- No calendar/scheduling/availability
- No document editing (attachments are inert)
- No vendor marketplace/discovery/ratings
Worth stating these omissions as deliberate in the product vision doc, not just the schema doc. They're the shape of the product as much as what's in it. Conversation happens in text/email/Slack — Reno captures the decisions and transactions that result.
Smaller items that are schema decisions rather than product-vision questions, but that are cheaper to settle now than after data lands.
(owner_type, owner_id) with no FK loses referential integrity on the one table where orphans are user-visible (a file appears in the UI that references a deleted transaction). Alternatives:
- One junction per owner (
invoice_attachment,transaction_attachment, ...). Verbose but real FKs. - Keep polymorphic but run a reconciliation job.
Worth asking: what's the attachment volume distribution? If 90% are on invoices/transactions, a 2-junction version is almost as simple as polymorphic and much safer.
Two columns that must stay in sync is a class of consistency bug. A transaction_margin { base_transaction_id, margin_transaction_id } junction with a unique constraint enforces "one margin line per base" at the DB level. Small refactor, removes an invariant the app has to maintain forever.
Four vendor snapshot fields already on transaction, and contact/billed-through snapshots are coming. If the public projection grows, the table gets very wide.
Alternative: store snapshots as jsonb (vendor_snapshot jsonb) rather than N flat columns. Matches the "frozen historical blob" semantic exactly. Public projection changes don't require migrations. Tradeoff: filtering on snapshot fields gets awkward — but those queries may not matter, since the live FK is there for current-state reads.
With mutable ownership, revocable grants, and snapshot semantics, "who did what when" becomes a natural question. The snapshots give you frozen rows but no timeline.
A generic append-only event { project_id, org_id, actor_user_id, event_type, payload_jsonb, occurred_at } is cheap in v1 and gives you: project-transfer auditing, grant issuance/revocation history, and a foundation for a future activity feed. Much easier to add now than to backfill later.
Fine for v1 if the role column is the source of truth for capabilities and permission checks are computed from role in app code (canEditBudget(role), canReconcilePayments(role)). Scope expansion later is then additive, not a breaking change.
Worth confirming that's the plan, so per-table permission logic doesn't accidentally get hardcoded to scope === 'read-write'.
You've sketched phase-groups as a future extension (contractor-view phases vs homeowner-view phases over the same project). If you're confident it's coming:
- Adding
phase_group_idnow, with one default group per project, costs almost nothing. - It preempts a later "add column, backfill, enforce NOT NULL" migration on what will by then be a lot of data.
Worth deciding: is this a maybe future, or a probably future? If probably, do the trivial version now.
Confirm credits never span projects. If a GC ever issues a goodwill credit on Project B for a screwup on Project A, the project_id column becomes awkward. (Probably fine — just worth naming as a constraint.)
Stored is the right call — it matches the physical document. But worth a validation layer that warns when the stored total doesn't reconcile with the sum of attached transactions, at least as a soft check at write time.
The schema is well-thought-through and the forward-compat discipline is real (central publicProjection fn, snapshot helpers, one query helper for cross-Org reads — you're preserving options without building the config system prematurely). The risk is not schema quality; it's schema/vision mismatch.
Before migrating: can you write a one-pager of the product vision, and does it match what the schema is quietly asserting? If yes, migrate. If the schema is ahead of where the vision has gone, that's worth reconciling first — because Airtable lets you be ambiguous about these things, and Postgres doesn't.