Records: the multi-view pattern

The records feature is a generic data point wired to the shell's entity-view kit: one field config powers 9 interchangeable views plus a create/edit form. Adding a new data point by cloning it costs ~3 small edits instead of any view code. It is the pattern underneath the demo apps — pnpm new <slug> --demo crm|tasks|catalog generates a domain in exactly this shape, which is the fastest path; clone records by hand when you want full control from the first line.

The 9 views

  • table — sortable columns, sticky first column
  • list — compact rows
  • grid — cards
  • board — kanban with drag, grouped by the status field
  • gallery — image-led cards
  • timeline — ordered by the date field
  • calendar — month view with paging
  • stats — aggregates and totals
  • canvas — free-form draggable positions

Views come from @geenius-mvp/shell (all entrypoints): EntityViews, ViewSwitcher, EntityDetail, availableViews, plus individual EntityTable/List/Grid/Board/Gallery/Timeline/Calendar/Stats/Canvas if you compose them separately. They are data-agnostic (rows are objects with a string id; callbacks onOpen/onPatch/onDelete), themed by the shell's CSS variables, and already handle sorting, kanban drag, canvas drag, month paging and aggregates. Do NOT rewrite view code per domain.

EntityConfig role keys

One CONFIG: EntityConfig drives everything. fields is a FieldDef[] of key/label/type — types are text | longtext | number | boolean | select | tags | date | image | position, with options (and tones) for selects. Role keys map fields to views:

  • titleKey (required) — the display title everywhere
  • statusKey — enables board + stats grouping
  • dateKey — enables timeline + calendar
  • imageKey — enables gallery
  • positionKey — enables canvas
  • amountKey — enables stats totals

Omit a role key and its views hide automatically (availableViews computes the set).

The clone recipe (3 edits)

  • 1. Schema — copy the records table block (Convex convex/schema.ts / Supabase supabase/schema.sql), rename it, keep or drop columns.
  • 2. Backend — copy convex/records.ts + convex/recordsShared.ts and rename the table, adjusting field validators/constants. On Supabase copy just src/lib/recordsShared.ts — RLS is the backend.
  • 3. Frontend — copy src/features/records/ (Convex) or src/features/records.tsx (Supabase), edit the one CONFIG: EntityConfig, then register the feature in src/features/index.tsx (one import + one line).
# after backend changes, always:
npx convex codegen && pnpm exec tsc --noEmit
If a genuinely new view type is needed, extend the kit in packages/shell so every app gets it — never fork view code into one app.