Skip to content

Design checks

tablewalk check --design reads an App’s pages, forms and shell for the compositions that come out basic. Each finding is a warning: the smell, then the fix. Warnings never change the exit code.

Terminal window
npx tablewalk check --app . --config tablewalk.json --design
npx tablewalk shot --app . --config tablewalk.json --both # then look at the PNGs

tablewalk shot prints the path of each picture: every view and page, or the routes you name (leads/12). --as <role> signs in (on a tenant App, with --tenant <id> for a role within a tenant: shot writes a temporary membership to the App’s tenant store and revokes it when it ends), --look all tries every look, and --full captures the whole page.

Code Smell Fix
aside-columns A related list beside an aside has more columns than its width holds (4 short ones at a desk, in either menu’s shell); the rest are left out show: the ones that fit, or shell: { placement: 'top' }
aside-summary A summary beside an aside wraps: 4 figures share a row in the top menu’s shell, 3 beside the side menu fewer figures, or shell: { placement: 'top' }
aside-table A list in the aside has more than 2 columns 2 columns, or presentation: cards(...)
aside-metrics 3 or more metric cards beside an aside wrap a summary([...]) of rollups
lone-metric A dashboard’s last metric wraps onto a row alone 3 or 6 metrics, or width: 'half'
dashboard-columns A dashboard’s rows at half, a third or two thirds of the page shows more columns than fit there; the rest are left out show: the ones that fit, or width: 'full'
stacked-lists Two or more related lists stand one under another { listTabs: true } (or false to keep the stack); one row is current(...)
bare-title A page with no header opens with a block of fields or a table header({ status: 'stage', facts: [...] }), then a summary
fields-by-type Fields grouped as Details, Links, Dates, Address a summary, a properties rail, a table sheet
vague-heading A section, or a New form’s group or step, titled Details, Info, Data, Other its content’s name: the record’s noun, Contact, Terms
thin-card A card shows its record’s name only ['owner_id', 'owner_id.city', 'owner_id.phone']
unitless-fact A bare number in a facts line or a header number({ unit: 'mi' }), or { column, label }
shown-twice A headline’s figure repeated as its subfigure drop one
destructive-first The first action (the filled button) cancels or deactivates the forward verb first, confirm: true, or header({ destructive: [...] })
unknown-primary A header’s primary names none of the record’s actions one of their labels
no-forward-verb A record’s only actions end it (cancel, deactivate, end) add the verb its work moves on by, or a related create
thin-workflow A workflow of 2 steps, or most values outside it 3–6 ordered steps; tones for side exits
undressed-vocabulary A status without tones reads as body text tones({ open: 'info', '*': 'neutral' })
undressed-money A decimal named like money has no money() display: { price: money() }
flag-as-number An is_*/*able integer reads as 0 or 1 CHECK (x IN (0, 1)) in the schema
raw-label A recordLabel template names a time, money, or a date with no parent beside it words and references; a day may tell siblings apart beside its parent: '{lease_id} · rent due {due_on}'
required-unfilled A New form or a related create leaves a required column empty, or a status unchosen show it, or defaults: { status: 'new' }
calendar-address Records are addressed under a calendar or board view: 'the-grid' on the resource
default-shell No look, no logo, a flat menu or table icons ui.theme, ui.logo, navGroup, icon:
  • How wide a value is. A column’s width is judged by what it holds (a day, a moment, a number, a word, a chip), not by its values, at a desk 1280 wide in the App’s shell. Whether a long name fits is for tablewalk shot and your eyes.
  • Multi-line text boxes. A New form draws prose-named text (description, notes, body) as several lines already; form.fields.<col>.multiline says otherwise.
  • A tab strip of one. Validation refuses one, so it is never a warning.

A good record page opens with a header() and a summary. Its related lists are tabs of at most 4 columns. The aside holds a card for the parent, the current() child, a properties rail and history(). See pages and looks.