The anatomy — one entity, five pieces
- The table — a migration in your app's chain: tenant columns + composite FKs per the table standard. Contract:
genus-php/docs/db-contract.md+ the genus-app-db skill. - The API functions —
api.priv_<app>_<entity>_list / _get / _save / _delete, uniform jsonb envelopes, validation indata.validated/data.fields. Same doc. - The routes — VIRTUAL mappings in the app's API registry pointing at those functions (a node handler only when pg cannot do the job):
genus-php/docs/api.md. - The pages — two route folders: the list (/pages/list, or /pages/grid for card-shaped records) and the detail (/pages/detail). Copy from THOSE sources.
- The menu entry — one row/config item pointing at the list route; the detail page is reached from rows, never from the menu.
The flow — how the pieces talk
- Read: the list page's lister calls the list endpoint (
g.fetchunder the hood, envelope + pagination); the detail page fetches_getby its int-constrained id param. - Write: create/edit go through ONE former-driven offcanvas posting to
_save— server validation lands per-field viadata.fields; delete goes throughg.confirmthen_delete, thenlist.reload(). - On the examples site the endpoints are the
/examples-demo/*mocks (file-backed, nothing persists) — the page code is IDENTICAL to a real module; only the endpoint paths differ. - Every UI piece follows the standards: layout, UI, states — a new module should look like it was always part of the app.
Starting a new module — begin minimal, grow as the entity earns it
- The FIRST version of a list page is minimal: search + the New button + rows with edit/delete — a lister with only
lister-searchin the toolbar and no panel. Add the filter panel, ordering, bulk selection and expandable rows ONLY when the entity needs them (the full dress). - The FIRST detail page is the identity header (avatar + name + status, Back + Edit actions) and ONE facts card. Grow toward tabs, the aside and related records only when there is real content for them (the full dress).
- Create/edit start in the standard OFFCANVAS on former (fill, dirty guard, save pipeline,
refreshat the lister); delete is alwaysg.confirm— these do not change as the module grows. - Strip what you do not need from the full-dress sources rather than inventing new arrangements — anything not shown in the examples is custom, outside the standard.