graview-view-specs
graview-view-specs is one of Graview's 31 authoring skills: instructions an AI coding assistant such as Claude Code or Codex reads before it changes a Graview app. What it teaches: Say how a Graview kind's card, row and page read, as data — blocks from a closed set bound by {field} templates and rule-language conditions, headlines, figures and lists of related records — and the values worked out from others (computed) that they show, verified by graview check and the page rather than by assuming the spec drew.
Installed into a project by graview skills install ., and read by whichever assistant is working beside you.
The skill
A kind's views, as data
A kind declared is drawn already: a card, a row, a page. When that default is wrong but a component would be too much, say the card, the row and the page as data. Nothing in a spec runs, so a chat can write one as safely as a person, and every face draws the same words from it. The kind itself — fields, edges, label, roles — is graview-node-kind.
Do this
- Say the card, the row and the page in
defineApp({ viewSpecs })— blocks from a closed set, bound by{field}templates and rule-language conditions:
``ts viewSpecs: { fixture: { card: [{ title: "{name}" }, { badge: "{status}", tone: { expr: "if(status == 'fitted', 'good', 'neutral')" } }, { field: "cost", as: "money" }], row: [{ title: "{name}" }, { badge: "{status}" }], page: [{ progress: { value: "done", max: "total" }, label: "Fitted" }], } } ``
In a document the same is the top-level views.<kind>, and the edit is { "op": "set-view", "kind": "fixture", "slot": "card", "blocks": [...] } (null gives the default back).
card is drawn beside the focus and on the pages face's gallery, row as the one line a member is among many (a focused group, the list page), and page above the default record. Tones are good, warn, bad, neutral and accent, the theme's own. A card shows three lines of a text block and a row one; { text: "{awards}", lines: 6 } shows six, lines: 0 the text whole (view-lines refuses past 12). Nothing in a spec runs: no CSS, no markup, and the only link drawn is an http(s) url field's own value, or a listed record. The scaffold's views() draws them (registerViewSpecs); so does the embed, by itself.
A row is a link to its record, so it is always named: one whose blocks show no title, no headline and no words holding the record's whole name ({severity} · {status} alone) is drawn with the name first, and describe_place says it so. Lead with { "title": "{name}" } to choose how the name reads.
A kind that also has a worker view of one record draws, on its record page, the view, then the page blocks, then the facts they did not say. A view whose manifest says replaces: "page" is the whole page: the page blocks are never drawn, and describePlace, told the host's views, says so as a problem (shadowedPageSlots, @graview/core/describe). Draw what they said in the view, or leave replaces out.
- Say more with three blocks (FR-81, FR-82):
{ headline: "{count(out('includes'))} offers" };{ figure: "net", as: "money", label: "{name}, net" }(number,money,percent; the label is a template;figure: trueis still the picture); and
``ts { list: "out('includes')", as: "row", sort: { by: "net", direction: "desc" }, limit: 5, group: { by: "type", headings: { pain: "What hurts" } }, empty: "None yet." } ``
— records by a walk or all('kind'), each drawn by its own card or row and a link; direction: "choices" is a choice's declared order. Lists show only what the seat sees, and nest three deep, then say names. The same blocks write the home (views.home) and a blocks place (graview-shipped-lens).
- Give a block a name a face can claim. Any block may say
key: "fixtures"(unsaid, its sort and place:list.2): the name a face claims it by (BlockSlots, seegraview-pages), never its words.
- Work a value out; never store it. A value that follows from others is declared once, never kept in step by hand.
computednames it and says it in the rule language:
``ts computed: { net: { expr: "sum(out('includes'), list * units) * (100 - either(first(all('party') where role == 'client').discount, 0)) / 100", label: "After the discount" }, } ``
Every template, view spec, rule, sum and sort reads net like a field ({ field: "net", as: "money" }, sort(all('package'), [standing, net], 'desc')); no act or edit form offers it, and an act that sets one is refused (computed-written). It is worked out over the graph the reader holds, so a seat is served one from what it may see, and reads the same on a row as on the page: a card's smaller budget holds only walks its template writes itself, and count(in('inGenre')) is read from the links whatever their number. A glance may name one (set-glance takes it, not one kept hidden), and a kind works out at most 60; a label or describe only one worked out from the record alone (no graph is in hand). A document's top-level computed holds facts of the whole graph, read as fact('name'); a kind's is changed with set-computed. A computed value may be words or a moment, not only a number: heading: "concat(name, ' · ', format(day, 'EEE d MMM'))" and startsAt: "datetime(day, '09:30')" build a title and a start time (with addMinutes, weekday, startOfWeek beside them), said in the app's brand.locale on its brand.timeZone — see graview-invariant for the pattern pieces and what graview check refuses. { expr, format: "percent" } (or "money", "duration", a unit; set-format sets it) says it so on its page, cards and CSV; round(x, 3) rounds; hidden: true keeps a helper off its page, cards and CSV while lists, lenses, rules and views still sort and read by it.
- Say counts and times in words. Templates say counts in words:
{n | words}("three"),{n | plural: 'offer'}("offers"),{out('includes') | and}("A, B and C"),{n | words | capital}("Three"),{at | time}("10:47 AM"). A field's label, a group's heading and what an empty list says are words, not templates: braces there would be drawn as typed, andview-braceswarns of them. Adescribeorlabelwritten in code is not a template either: say the value in code (momentAsRead(node.startsAt)), anddescribe-template-in-codewarns of{field}returned there.
When a person makes a page better
A right-click anywhere on a page the app draws by itself — a row, the heading, a record's page, the home — offers Better UI to a seat that may change the app. It tries the spec first: a draft of the spec's own edits only (set-view, set-glance, set-page-fields, arrange-pages' sort, group and filters, set-label, set-format), compiled by the checker and drawn on that page before anyone keeps it. A list groups only by a choice or a yes-or-no field. What the spec cannot say, or a page your own design draws without reading the spec, is offered as a new design (a worker view), judged against the page before it is shown; ?drawn=built-in still reaches the page the app draws. A host lends composeChange, onProposeChange and onDraftChange for the first and KeptViews.fork for the second (checkViewForkHost checks it).
Then find out whether it worked
pnpm build && npx graview check ./dist/domain/app.js
npx graview check --document app.json # a documentReport the actual output, including warnings. What it catches here:
view-field,view-name,view-tone— a view spec naming a field the kind lacks, or a tone outside the kit;view-key— two blocks under one key;view-template,view-expression— a template or a condition it cannot read;view-depth— lists nested past threeview-whole-text— a card or row putting a longtextfield whole in a text block, unless it sayslines(graview-listssays how a card shows its start)view-headline-long— a headline bound to a longtext, Markdown or long string field whole, which a focus card drew at display size: say it in atextblock, or head it with{call|excerpt: 60}(a headline past about 90 characters is drawn a step smaller and clamped to four lines)computed-cycle— values worked out from each other;computed-clash— a computed name that is already a field;computed-cost— work that grows with the cube of the graph (a warning at the square)
Then look: graview describe <entry> --place <slug> says each record's card and row as the page draws them, and the page itself shows the blocks in both schemes. Open it as a seat that may not see a listed record: the list is shorter, and the computed value is worked out without it.
What the check cannot see
- Whether the card says what a person glancing at it needs. The check holds a spec to the vocabulary; only reading the cards on a full list says whether they are the right few facts.
- Whether a computed value is the RIGHT sum. The check proves it terminates and is affordable; work one record out by hand and compare.