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.