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.
npx tablewalk check --app . --config tablewalk.json --designnpx tablewalk shot --app . --config tablewalk.json --both # then look at the PNGstablewalk 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: |
What it does not check
Section titled “What it does not check”- 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 shotand your eyes. - Multi-line text boxes. A New form draws prose-named text (
description,notes,body) as several lines already;form.fields.<col>.multilinesays 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.