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

  1. 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.

  1. 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: true is 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).

  1. 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, see graview-pages), never its words.
  1. Work a value out; never store it. A value that follows from others is declared once, never kept in step by hand. computed names 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.

  1. 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, and view-braces warns of them. A describe or label written in code is not a template either: say the value in code (momentAsRead(node.startsAt)), and describe-template-in-code warns 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 document

Report 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 three
  • view-whole-text — a card or row putting a long text field whole in a text block, unless it says lines (graview-lists says how a card shows its start)
  • view-headline-long — a headline bound to a long text, Markdown or long string field whole, which a focus card drew at display size: say it in a text block, 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.