tablewalk check
Run every check on an App in one command, read the exit code and the JSON an agent reads, and look up any code’s fix.
npx tablewalk check --app . --config tablewalk.json# schema types passed tablewalk-schema.d.ts matches the database# typecheck passed tsconfig.json# validation failed 1 problemEvery step runs, each says its verdict on one line, and the exit code names the first that failed. Warnings never fail a step.
Steps and exit codes
Section titled “Steps and exit codes”| Exit | Step | What it does |
|---|---|---|
| 0 | — | Every step passed or was skipped as said |
| 1 | could not check | No App directory, no database, a config listing another App, or a flag that is a mode of its own |
| 2 | schema types | Rewrites tablewalk-schema.d.ts when the database drifted; fails when the database cannot be opened |
| 3 | typecheck | The App’s tsconfig.json; each error as file:line with tsc’s own code (TS2322) and a hint |
| 4 | validation | Loads the App against the database: every problem, with its file, and authority.lock |
| 5 | components | Custom components read for what may never ship |
| 6 | pending | With --pending: the pending migrations failed on a scratch copy (change your schema) |
--routes adds every address the App answers to, and --design adds the
design checks.
For an agent: --json
Section titled “For an agent: --json”--json runs the same steps and prints one document, with the same exit code:
{ "version": 1, "app": { "id": "tick", "name": "Tick" }, "ok": false, "exit": 4, "steps": [{ "step": "validation", "status": "failed", "detail": "Tick: 1 problem" }], "problems": [{ "code": "unknown-column", "severity": "error", "step": "validation", "file": "app.ts", "line": 51, "path": "views.tasks.tabs", "message": "view \"tasks\": \"due\" is not a column on task. Did you mean \"due_on\"?", "docs": "https://tablewalk.dev/docs/check-codes/#unknown-column" }] }Each problem has code, severity (error fails its step; warning and
info never do), step, message and docs, and where known file,
line, column, path (where in the App definition) and fix. The codes
and fields are the contract; a message may change. The document is described
by tablewalk/schema/check.schema.json, and tablewalk observe --mcp has a
check tool that answers it with no server running. Each docs link lands
on the code’s row below.
For an agent’s context: --brief and explain
Section titled “For an agent’s context: --brief and explain”--brief prints one line per problem and nothing else, with the same exit
code: code file:line message — fix: …. A finding the check gave no fix for
borrows the one below; ok is a clean App.
npx tablewalk check --app . --config tablewalk.json --brief# unknown-column app.ts:51 view "tasks": "due" is not a column on task. Did you mean "due_on"? — fix: use a column the table has (…)npx tablewalk explain unknown-columntablewalk explain <code> prints the code’s row from this page (where it comes
from, what it means, the fix) on one screen. A code it does not know lists the
nearest ones. tablewalk schema --brief is the schema in the same spirit, one
line per table: task(id* int, title text, status text {open|done}, owner_id int? → person).
Common messages
Section titled “Common messages”| Message | Fix |
|---|---|
Cannot find module 'tablewalk/app' |
npm install in the App’s project, or keep it in a checkout: start. |
has "fields" and no "access" |
fields needs access and auth with roles: resources and fields. |
"fields", a column ceiling for signed-in roles; it needs "auth" |
Without sign-in, keep the column out in the database: secrets. |
has no reference called "site_code" |
It is a column, not a foreign key: relationships. |
"created_at" is TEXT, so the time window cannot narrow by it |
Declare a date type: dates and types. |
compare "previous period" needs exactly one "last N …" phrase |
One window phrase in query, or a compare in full: dashboards. |
compare "previous period" has no dates to compare |
Give the metric over (a date column), or a compare in full: dashboards. |
"fortnight created_at" is not something to group by |
day, week, month, quarter or year: dashboards. |
nav entry 2 names the view "…", which this App does not declare |
Declare the view, or fix the id: navigation. |
the effective authority differs from the lock |
Review, then tablewalk check --app <dir> --write-authority. |
read process.env.X, which tablewalk.json does not pass to it |
List it in apps[].env: secrets. |
Most messages end with the tablewalk docs <topic> that explains them.
Per-role codes
Section titled “Per-role codes”These belong to the validation step. Problems fail it; warnings explain what a role will not see.
| Code | Kind | What it means | Fix |
|---|---|---|---|
role-cannot-read |
problem | A recordLabel names a column (or a table) some roles cannot read: their records read “(row)” |
Add the column to those roles’ fields.<table>.read, or name the record by columns every role reads |
role-cannot-read |
warning | A list or a page names a column, or a rollup over one, that some roles cannot read: the list is drawn as a plain grid or not at all for them, and the page leaves that section out (or falls back to the default page). A related list’s create a role cannot use, and a headline’s was it cannot read, are left off without a warning: the section stays. Sometimes the point (a moderators’ room) |
Add the column to their fields.<table>.read, or leave it out of the list or page |
command-unreadable |
warning | A role is granted a command whose footprint reads a table the role does not read: its actions and form are never offered to that role | Add the table to the role’s read, or take the command from the role |
new-cannot-form |
warning | A role writes a table that has a New form, a quick add or a related create, but its writable columns leave out one a new row needs: New is not offered to it | Add the column to the role’s fields.<table>.write, or make the rows with a command |
write-unreadable-parent |
warning | A role writes a column that references a table it does not read: a write that sets it is refused, while the rest of the row edits | Let the role read the table, or leave the column out of the role’s fields.<table>.write |
write-generated |
warning | A role’s or a resource’s fields.write names a column the database computes (a generated column): nothing writes it, and a write that sets it is refused; it is read like any other column, with or without a row policy |
Leave the column out of fields.write |
restated |
warning | A value the App writes that the platform says without it: a labels caption the screens already say (also a command field’s label that is its key made words), format: 'number' beside a unit, the App’s own currency, a recordLabel that is the label column, signUp: 'invite', default: true on the only source, targets: 'none' beside public: true, a handler that returns out.parse(…) |
Delete it; the message names which |
role-label |
warning | A role has no label, so the screens that name it to people (accounts, a suite’s grants, a tenant’s members and invitations) say its id made words |
roles.<id>.label: 'Loan officer' |
admin-grants-nothing |
warning | auth.roles names no admin, the role the first account (TABLEWALK_AUTH_ADMIN_EMAIL) and every administrator holds: they sign in to an App that shows them nothing |
Declare roles.admin with what an administrator reads and writes, for example { read: true, write: true } |
outside-ceiling |
problem | A role, or a command, names a resource whose own access does not allow it. A command’s target is always written |
Declare resources.<name>, or for a write access: 'write': the refusal names which |
role-extends |
problem | A role’s extends names a role the App does not declare, loops back to itself, or names a role of another realm or kind of principal |
Name a declared role of the same realm that does not extend this one |
command-and-edit |
problem | A role edits in place a table a command writes and says neither which columns it edits nor has the table a machine; or a role edits the version column a command keeps its revision in |
The role’s fields: { <table>: { write: [...] } }, or a machine on the resource, or take the table out of the role’s write; for the revision, leave version out of the role’s write |
machine-unreachable |
problem | A machine’s state no verb leads to from its first state | Add a verb that leads there, or drop the state |
machine-timer-edge |
problem | A timer has no command edge from the state it waits in | Declare that command under the same state’s on, with its destination |
machine-dead-end |
problem | A machine’s state that no verb leaves and that is not final |
Give it a verb, or final: true |
machine-verb-unrunnable |
problem | A machine’s verb no role may run: a command no role is granted, or a fixed verb on a table no role writes or runs by name | Grant the command, or the verb by its label (roles.<role>.actions) |
machine-command-outside |
warning | A command judges a machine’s column in its when, writes it in its handler, and is not one of the machine’s verbs or timers: its write is refused unless it follows an edge |
Name it as a verb of the states it runs from |
command-unoffered |
warning | A command names a record (targets is not 'none') and no action, form, import or api.commands entry offers it, and no job, workflow or endpoint runs it: preparing it is refused with 403 forbidden, whatever a role is granted |
Offer it with an action on its target (action('…', { command: '…' })), expose it in api.commands, or take it out of the registry |
tenant-delete-unauthorized |
warning | A tenant App’s role writes a tenant-owned table and the tenant policy has no authorizeDeletedRecord: Save and New work and Delete answers 403 forbidden |
Add authorizeDeletedRecord to defineAppTenantPolicy, or take write from the role |
claim-grants-nothing |
problem | A role’s claims gives it from an identity provider’s claims, and it grants nothing: the claims would sign people in to nothing |
Give the role reads, writes or commands, or drop from |
claim-without-provider |
problem | A role has claims, and auth.providers names no oidc('…') or saml('…') provider to read claims from |
Add the identity provider, or drop from |
claim-in-tenant-app |
problem | A tenant App’s role has claims: its roles are members’ memberships in the tenant store |
Grant the membership instead |
identity-provider-unknown |
problem | auth.providers names oidc('<name>') or saml('<name>'), and the config file has no entry by that name |
Add it: identity providers |
identity-provider-kind |
problem | oidc('<name>') names a saml entry, or saml('<name>') an oidc one |
Say the entry’s own kind |
identity-provider-tenant |
problem | oidc('<name>') or saml('<name>') names an entry the config gives to a tenant |
Name an App-wide provider; a tenant’s own signs in through oidc.tenants() or saml.tenants() |
open-sign-up-unproven |
problem | auth.signUp is 'open' with the email provider, and the config file names no email service: nothing sends the link that proves an address, so anyone may make an account under anyone’s email |
Add an email service ("log:" in development), or make signUp 'invite' |
embed-issuer-no-auth |
problem | A host in the config file ("kind": "embed") signs people into an App that signs no one in |
Give the App auth, or take it off the host’s apps: sign in from your app |
embed-issuer-role-unknown |
problem | A host’s roles maps an App role the App does not declare |
Declare it in auth.roles, or map one it does |
scim-no-auth |
problem | A SCIM directory ("kind": "scim") names in its apps an App that signs no one in |
Give the App auth, or take it off the directory’s apps: SCIM provisioning |
scim-provider-unused |
problem | A directory provisions an App that does not sign people in through the directory’s provider |
Add the provider to auth.providers, or take the App off the directory’s apps |
scim-role-unknown |
problem | A directory’s roles maps a role the App does not declare (for a tenant’s directory, a tenant role) |
Declare it in auth.roles, or map one it does |
scim-role-service |
problem | A directory maps a role only a service holds | Map a person’s role |
scim-role-claims |
problem | A directory maps a role the provider’s claims give |
A role is the directory’s or the claims’, never both: drop one |
scim-tenant-roles |
problem | A tenant’s directory maps no roles |
Map a tenant role from the directory’s groups: a member holds one |
public-form-reference |
problem | A public form’s command answers no reference, so every submission would be refused after it ran |
output: shape({ reference: text({ pattern: publicReferencePattern('…') }), message: literal('…') }) |
public-form-picker |
problem | A public form’s reference input picks from a resource auth.public does not open, or by a key or label column its fields leave out, so a visitor could not read its choices |
Open the resource (and those columns) in auth.public, or ask a choice the handler resolves |
money-minor |
problem | A format: 'money' rule says minor: true over a column that is not an integer, or names a currency: { column } that is not another text column of the resource |
Keep minor units in an integer column (or drop minor for a decimal one); name the column each row keeps its ISO 4217 code in |
boolean-keys |
warning | A yes/no column’s (or computed boolean’s) values, tone or icons is keyed by something other than true, false or *: SQLite stores 1 and 0, and only those two keys are read for them |
Key the rule by true and false |
value-label |
problem | A value component’s label(value) throws, returns something other than text, returns more than 80 characters, or is async, for a value of its column’s vocabulary; or its module does not load to be read |
Return the word as a short string, synchronously |
value-label-open |
warning | A value component exports label, but its column has no vocabulary (free text), so it cannot be called over its values and is not used |
Declare values for the known words, or constrain the column with an enum or CHECK list |
value-words-missing |
warning | A value component draws a column that has a vocabulary, with no label export and no values, so breakdowns, chips and the CSV show the stored values |
Export label(value) from the component, or declare values |
drill-none |
warning | A breakdown, chart or matrix groups by a column, or a funnel counts rows, of a table that has no list view, so its groups or stages are not links (an App never opens the database browser) | Add a list view over the table, or say to: false |
drill-ambiguous |
warning | A breakdown, chart or matrix groups by a column, or a funnel counts rows, of a table that has several list views and says none in to, so its groups or stages are not links |
Say which list with to: 'view-id', or to: false |
empty-run |
warning | A record page has three or more related lists in a row, none saying what it does while empty (no empty on the page or the lists, no listTabs), so a record with no history draws that many empty tables |
Say empty: 'collapse' on the page to fold them into one line, or empty: 'hide' where a list is not worth a place |
raw-id |
warning | A view’s label, a page’s label or a section’s title is written as an identifier (billing.order_line, EVENT_ENUM) and is drawn to the reader as it stands |
Say it in words, or leave the label out: the platform says a table’s name as words, without its schema |
form-lines |
problem | A form’s lines, or a related list that edits its rows in place, names a resource the App does not write, a whose that is not one reference to the record, or a column a form does not write |
Declare the child with access: 'write', name its reference to the record, and show only columns a form writes (never whose or order) |
form-live |
problem | A form’s live names something that is not a computed column or rollup of the resource, a rollup of rows its lines do not ask (or one narrowed by a query), or a computed column that reads through a reference |
Live values read the draft and its lines only: add the rows to lines, their columns to show, or roll up a computed column of the line |
document |
problem | A page’s document draws a section paper cannot (an aside, a tab, a timeline, a list with a presentation or editing), names a number that is not a field of its record, takes a key a record page already answers to as a tab, or is named as the page a record opens through |
Keep a document to fields, related lists and notes placed by width; give it a key no tab uses: printable documents |
| Code | Step | What it means | Fix |
|---|---|---|---|
could-not-check |
check | No App directory, no database, a flag that is a mode of its own | Give --app <dir> the App’s directory and the database as <db>, --db <target> or --config <path>, and run a flag that is a mode of its own in a tablewalk check of its own |
pending-migration |
pending | A pending migration failed on the scratch copy, or the migrations and their history disagree | Change your schema |
deploy-sqlite-source |
deploy | check --deploy: a connection the App uses is a SQLite file, lost with a replaced container and not shared by replicas |
Use a postgres:// or mysql:// URL, e.g. "${DATABASE_URL}" |
deploy-default-auth-store |
deploy | check --deploy: the App signs people in and auth.store is not set, so accounts and sessions are a SQLite file in the config directory |
Set auth.store to a postgres:// URL, kept apart from the business database |
deploy-sqlite-auth-store |
deploy | check --deploy: auth.store names a SQLite file |
Set auth.store to a postgres:// URL |
deploy-missing-secret |
deploy | check --deploy: BETTER_AUTH_SECRET is unset or under 32 characters, and a start refuses without it |
openssl rand -base64 32, kept in the platform’s secret store |
deploy-unset-reference |
deploy | check --deploy: a connection or auth.store references a ${NAME} the environment lacks, and a start would refuse |
Export it when you run check; the deployment’s secret store holds the real value |
deploy-public-url |
deploy | check --deploy warning: TABLEWALK_PUBLIC_URL is not set, so cookies and emailed links name the loopback address |
Set it to the public https address |
deploy-per-process-limits |
deploy | check --deploy --replicas <n> warning: more than one replica and no cache service, so each keeps its own rate limits and sign-in lockout |
Add a cache service (Valkey), or run one replica |
unknown-connection |
schema types | The App names a connection the config does not have | Add it to tablewalk.json, or fix the name |
database-unreachable |
schema types | The database could not be opened | Start it, or fix its address |
schema-types-stale |
schema types | tablewalk-schema.d.ts is older than the database (migrate status) |
tablewalk check rewrites it |
schema-types |
schema types | The schema types could not be written, for the reason said | Let tablewalk check write tablewalk-schema.d.ts beside app.ts (the directory writable, the file not open elsewhere) and the database answer, then check again |
typecheck |
typecheck | tsc failed without an error it placed | Run tsc |
unknown-key |
validation | A key this place does not take | The message lists the keys it takes |
unknown-column |
validation | A column a table does not have, in a query, a list, an action, a computed column | The message suggests the nearest |
not-a-reference |
validation | A column where a foreign key goes | Relationships |
unknown-view |
validation | A view id nav, home or an embed names that the App does not declare | Declare the view, or fix the id |
unknown-action |
validation | An action id a header, a queue or a role names that the resource does not declare | Use one of the ids it lists |
unknown-resource |
validation | A resource, a table or a connection that is not there | Fix the name |
computed-column |
validation | A computed column that does not parse or type, or one written, asked for or granted as if stored | Computed columns |
query-grammar |
validation | A query, a when or a measure that does not parse; the message says at which character |
The query language |
not-a-date |
validation | A time window or a timeline over a column that is not a date | Dates and types |
needs-auth |
validation | A column ceiling for signed-in roles in an App without sign-in | Secrets |
authority-lock |
validation | The effective authority differs from authority.lock; the one finding carries each changed fact, + granted now and - only in the lock |
Review, then --write-authority |
env-not-passed |
validation | The App reads an environment variable its config does not pass | apps[].env |
analytics-unknown-step |
validation | A funnel step names a view, action, form (or form step) or event the App does not declare | Use one of the ids the App declares, or declare the custom event |
preview-feature |
validation | Information, not a problem: the App uses a Preview feature (flags, audiences, experiments, behavior analytics, Gantt), which may change in any 1.x release | Nothing to fix; Status lists what is Preview |
analytics-not-collected |
validation | A funnel step counts a kind (views, actions or forms) that analytics.collect does not list |
Add the kind to collect |
experiment-eligibility-audiences-only |
validation | An experiment’s eligibility lists roles or tenants itself |
Declare an audience with those roles or tenants and name it in eligibility.audiences |
analytics-name-clash |
validation | A custom event, funnel or feature declares a name another already holds, or a custom event shares a business event’s name | Rename one; custom and business events share one namespace |
api-write-unsupported |
validation | A generic write the App cannot serve: rows not every role’s, several sources, a resource no role writes, or a delete the App does not allow | Write through a command the App exposes; or, for one source where every role reads all rows, give the resource access: 'write' and delete: true where the API deletes |
api-write-field |
validation | A generic write’s field that is not a column, or a create that leaves out a required column | Add or fix the field |
api-write-no-role |
validation | No API role creates, edits or deletes the resource | Grant a role, or drop the write |
api-write-name-taken |
validation | The commands module registers the name a generic write runs as | Rename the command |
api-multi-source |
validation | An exposed field, filter or sort that reaches a resource on another connection, or an exposed command whose lookup does: one App API request reads one connection | Expose the other resource as its own, or drop the path |
api-source-unsupported |
validation | An exposed resource on a source the App API does not serve: an HTTP API, or an engine it does not read | Expose resources of SQLite or PostgreSQL sources |
api-event-unsupported |
validation | api.events in a tenant App |
Use webhooks |
api-event-unknown |
validation | api.events names an event no command emits |
The message lists the declared events |
policy-events-feed |
validation | api.events in a row-policy App: the feed serves every event, and its rows are scoped per reader |
Deliver events through an effect’s webhook |
policy-service-unnamed |
validation | A jobs principal the row policy’s services does not name: the server does not start |
Add it to services |
policy-service-unused |
warning | services names a principal no job, timer, workflow, inbound endpoint or integration runs as |
Drop it, or run something as it |
policy-service-rows |
warning | The policy gives a service no rows of a table its commands read or write, on the check’s database | Give service:<name> those rows in resolveRows |
notify-send-transport |
validation | A rule’s send: { channel: 'email' } on a server with no email service |
Add a service of "kind": "email" to the config file |
notify-send-links-secret |
validation | The email service sets no links.secret, which signs each message’s unsubscribe link |
Set "links": { "secret" } |
notify-send-timezone |
validation | An email send gives timezone: mail has no quiet hours |
Remove it |
notify-send-source |
validation | The rules that email a record’s address span several connections, or a connection is neither SQLite nor PostgreSQL | Keep the rules’ resources on one SQLite or PostgreSQL connection |
notify-send-unproven |
warning | A public command writes a resource that emails its address, and no form proves the address (link.email.verified): a visitor’s write is not mailed |
Prove the address on the form, or accept that visitors’ writes are not mailed |
notify-send-consent |
warning | An email send names no consent column, so only addresses that opted in are mailed |
Name the column that records consent |
page-layout |
validation | A page’s layout is not conversation, showcase or article; or it is on a tablewalk.json page, a document() or a definePage |
Use one of the three on a page(…); a document has paper’s one layout and a free-form page lays itself out (compose a record page) |
layout-conversation |
validation | A conversation page has no timeline or two, a section after its timeline in the main column, a related list, embed, chart, breakdown or metric in the main column, or a tab there |
A conversation is its one timeline, last in main: put related('Parts used', …) in region: 'aside' |
layout-showcase |
validation | A showcase page’s first main section is not a gallery, it has no aside section or more than three, an aside section names a tab, or it says aside: false |
A showcase opens with its photos: related('Photos', …, { presentation: 'gallery' }) first, its price headline in the aside |
layout-article |
validation | An article page has a section in the aside, says aside: false, or gives a section width: 'third' or 'two-thirds' |
An article has no aside: move card('Home', …) into the column, or leave out layout |
page-file |
validation | A pages/*.page.tsx file that does not compile, has no definePage default, gives it a key it does not take, or no label |
Make the file compile and export default definePage({ label, … }) with only the keys it takes (free-form pages) |
page-path |
validation | A page’s path is not its file’s name, is reserved, or is a view’s already |
Name the file for the path, or change the path |
page-nav |
validation | A page asks for a nav group the App’s nav does not have, or nav names a page | nav: { group } with one of the nav’s groups; a page places itself |
page-reads |
validation | A page declares no reads |
Name the resources it reads |
page-params |
validation | A page’s params are not up to eight lowercase words, or name period |
params: ['list'] |
page-commands |
validation | A page runs a command the commands module does not register, or the App has no roles | Register the command, or drop it |
page-public |
validation | A public page names roles, or reads what auth.public does not open |
Open the resource in auth.public, or make the page signed-in |
page-coverage |
validation | A role the page names is not granted all it reads and runs, or no role is | Grant the role, or narrow the page |
page-loader |
validation | A page names a loader the commands module does not export, or the loader reads what the page does not | export const loaders = defineLoaders({ … }); keep its reads within the page’s |
message-missing |
validation | A msg('…') names a key the source catalog (messages/<the App's language>.ts) does not have, or the App names messages and has no messages/ catalogs |
Add the key to the source catalog, or fix its name |
message-syntax |
validation | A catalog’s text is not ICU MessageFormat, or a messages/ file is not export default defineMessages('<its locale>', { … }) |
Write each text as ICU MessageFormat, every { with its }, and the file as export default defineMessages('<its locale>', { … }) |
message-placeholder |
validation | A translation reads an argument its source text does not | Say only the source’s arguments |
message-arg |
validation | A message’s argument reads no column, a column of the wrong kind (a plural of text), or sits where nothing supplies it: only a record’s name, a notify title, a transition ({to}) and a landing’s slots take arguments |
Bind it to a column (msg('lead.due', { days: 'due_in' })), or say the caption without arguments |
message-untranslated |
validation | Warning: a translation leaves some of the source’s keys to the source’s words | Translate them in messages/<locale>.ts |
message-unused |
validation | Warning: a key no msg() names, or a translation’s key the source catalog does not have |
Name it, or remove it |
route-clash |
routes, validation | An address another App or another room holds | Give one of them a path |
app-on-mysql |
validation | The App’s connection (or one of its sources) is a MySQL or MariaDB database, and an App runs on PostgreSQL or SQLite; the check says it and the server refuses to start | Point the App at a PostgreSQL or SQLite connection. MySQL/MariaDB stays for browsing, in the database browser and its MCP server: status |
mount |
routes | The mount flags or config do not say where the App is served | Build and deploy |
deprecated |
validation | Warning: a key that still works and goes at the next major version; the message names its replacement | What the message says to write instead (compatibility (docs/compatibility.md)) |
theme-contrast |
validation | An App theme’s text color fails a contrast check, in a mode or a palette (C1); the message names the color, what it was measured on, and the nearest color of its hue that passes | Write the color the message says, or tablewalk check --fix |
theme-edge |
validation | An App theme’s control edge (lineControl) reads under 3:1 on a field, the page or a raised panel, in a mode or a palette (C2) |
Write the darker edge the message says, or tablewalk check --fix |
theme-focus |
validation | An App theme’s focus ring (focus, or the accent where it is the ring) reads under 3:1 on a ground a focusable sits on: the panel, the page or a raised row (C3) |
Write the ring color the message says, or tablewalk check --fix |
theme-print |
validation | An App theme’s light accent or link reads under 4.5:1 on white paper, which a printed page is whatever its ground (C12) |
Write the color the message says, or tablewalk check --fix |
theme-budget |
fonts | A theme’s faces are past 150 KB together as served, and each is fetched as the page is first drawn (C11) | Ship fewer weights, or one variable file cut to Latin |
theme-audience |
validation | ui.theme.for names a theme that is neither a built-in look nor one of ui.customThemes, a role auth.roles does not declare, or no audience at all |
Name an offered theme for public and each role, or register the theme in ui.customThemes |
theme-shadow |
validation | Warning: a theme’s shadow is lighter than its ground in a mode, so a card or a menu glows rather than lifts and loses the edge the lift was for (C2) |
A shadow darker than the ground, or leave it out for the look’s |
theme-type |
validation | Warning: a type role’s weight is past 700 in a face the theme does not ship at that weight, so the browser draws a smeared 700 or a Black cut (C8) |
Ship the face at that weight in type.faces, or keep the weight at 700 or under |
theme-part |
validation | A part style (parts) names a part that is not published, a hex or a property past its menu, or a warning that its ink fails on its ground in a mode (C13) |
A published part, a color by its role’s path, an ink that reads on the ground |
theme-band |
validation | Warning: a banded table’s theme gives parts.status or parts.tag an opaque ground, so that cell keeps its own color on the band and shows as a pale column on every other row (C13) |
A translucent ground for the part, or leave its ground out for the look’s |
theme-css |
validation | A theme’s stylesheet is not a .css file beside it, weighs over 8 KB, or has a rule the allowlist refuses: a selector that is not a published part, an at-rule, !important, a size, a place or a hidden part, each by its line (C13) |
Dress [data-part] rules in role colors, edges, corners, lifts and faces; say size and place in the App |
theme-contract |
validation | A published look says a contract this tablewalk does not read, a from that is not its import.meta.url or has no contract, extends a theme that extends a theme, or names a file outside its package (C13) |
Install the look’s release for contract 1, say contract: 1 beside from, and keep its files in its package |
theme-radius |
validation | Warning: a theme’s shape.radius.panel is squarer than its control, so a card’s corner cuts under the buttons it holds |
A panel at least the control’s corner, or leave it out for one and a half controls’ |
theme-dark |
validation | Warning: ui.logo is a picture with no dark, so the light picture is drawn on the dark bar (C10) |
Say ui.logo.dark, or name the same file there when it reads on both |
app-logo |
validation | ui.logo’s src, dark or mark is a URL, not a PNG, JPEG, WebP, GIF or SVG file, missing, outside the App’s directory, or a picture past 256 KB (an SVG past 64 KB) |
A file in the App’s directory, or an App icon’s name |
theme-chart |
validation | Two neighboring chart series of an App theme are too alike for a color-blind reader (C7); the message gives an order of the same series that passes | Write the order the message says, or tablewalk check --fix |
theme-font |
validation | A theme’s type.faces file is missing, outside the App, not WOFF2 or WOFF, or over 1 MB; or, as a warning, a face over 80 KB, a face no stack names, or a stack that leads with a family the theme neither ships nor falls back from |
Ship each face as a WOFF2 file inside the App, subset to under 80 KB, name it in a stack, and lead each stack with a family the theme ships (Themes) |
invalid-app |
validation | Any refusal of the load no other code names: a declaration the parser does not accept, or a refusal the App’s start makes over its database or its --policy and --commands modules (App error: or Policy startup error:, with each because: under it). The App’s other problems are listed beside it |
Correct the declaration at the key the message quotes to the shape it lists (tablewalk docs <topic> shows it in full); for a start’s refusal, read the last because: line, which names the table, column or module |
component-markup |
components | A component writes HTML from text | Draw React children |
component-code-from-text |
components | eval, new Function, a string timer or a javascript: address |
A function |
component-environment |
components | A component reads the server’s environment | Pass what it needs as props |
component-color |
components | Warning: a literal color | A token, var(--accent) |
component-token |
components | Warning: a var(--…) no look defines |
The token named, var(--status-critical) for --negative; the list is tokens from tablewalk/ui |
component-radius |
components | Warning: a literal radius | var(--radius) |
component-motion |
components | Warning: motion over 200 ms, or outside a reduced-motion query | Design |
component-shadow |
components | Warning: a shadow | var(--line) |
component-class |
components | Warning: a class outside the component’s c-<id> name-space |
Name it c-<id>-… |
component-ruler |
components | Warning: placed by the window’s ruler | The kit’s popups |
page-undeclared |
components | A page’s module reads or runs what its definePage does not declare (a warning when it names one with an expression) |
Add it to reads or commands, or name it in quotes |
integration-unknown |
validation | a command calls, or integrations.webhooks maps, an integration the jobs module’s integrations.use does not list |
List it, or fix the name (Integrations) |
integration-action-unknown |
validation | a command calls an action its integration does not declare | Use one of the actions the message lists |
integration-event-unknown |
validation | integrations.webhooks maps an event its integration does not declare, or one that sends no webhooks |
Use one of the events the message lists |
integration-channel-unknown |
validation | a command’s send(...), or a notify rule’s send, names a channel no listed integration provides |
List the integration that provides it, or fix the name |
integration-event-input |
validation | an event’s payload does not fill the input of the command it runs | Match the command’s input to the payload, field for field |
integration-service-role |
validation | the principal an event runs as holds a role that is not service: true, or is not granted the command |
Give the principal service roles that grant the command |
oauth-no-auth |
validation | The config file’s OAuth authorization server issues tokens for an App that signs no one in, and a token is a person’s | Give the App auth, or take it off the server’s apps (OAuth for agents) |
oauth-no-api |
validation | The OAuth authorization server issues tokens for an App with no api |
Declare one, or take the App off the server’s apps |
oauth-not-network |
validation | The OAuth authorization server issues tokens for an App whose api does not say network: true, and an agent reaches the API over the network |
Say network: true, or take the App off the server’s apps |
oauth-role-unknown |
validation | The OAuth authorization server lets a token carry a role the App’s api does not grant |
Name one of the api’s roles |
consumes-path |
validation | A consumes (or restocks) column starts with a column that is not a reference of the line |
Name one of its references, then a number column of the record it names: part_id.on_hand |
consumes-balance |
validation | A consumes balance is not a number column of the record the reference names |
Name the number the record keeps, like on_hand |
consumes-by |
validation | A consumes by is not a number column of the line |
Name the line’s amount, like quantity, or leave by out for one per line |
consumes-decimal |
validation | A whole-number balance is moved by an amount that may be a fraction | Give both columns the same numeric type |
consumes-written |
validation (warning) | A command’s handler writes a line and the balance its consumes or restocks already moves for it: the line would count twice. One that writes only the balance is not named |
Leave the balance to the rule, or drop the rule where the command keeps the balance itself |