Reference module

The anatomy of an entity module — copy this pattern

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 functionsapi.priv_<app>_<entity>_list / _get / _save / _delete, uniform jsonb envelopes, validation in data.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.fetch under the hood, envelope + pagination); the detail page fetches _get by its int-constrained id param.
  • Write: create/edit go through ONE former-driven offcanvas posting to _save — server validation lands per-field via data.fields; delete goes through g.confirm then _delete, then list.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-search in 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, refresh at the lister); delete is always g.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.