# graview-view-forms

`graview-view-forms` 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: Let a Graview worker view change records — a bound press (data-act), the fields a person types into, a record's own text filled to edit (data-prefill), a field that saves as it is typed (data-save) and the view hearing what became of each act (acted) — and prove a press applies and a field the view filled is refused, rather than assuming the form works.

Installed into a project by `graview skills install .`, and read by whichever assistant is working beside you.

## The skill

## Forms in a worker view

A worker view (`graview-worker-view`: its manifest, its global, its links and its limits) changes records only by asking for the acts its manifest names. **Where every member sees every record, `graview.act(name, args)` applies as given. Anywhere else only a press applies an act** — a person's click on an element with `data-act`, or a field of theirs that saves — because a view's code is never handed what a person types where sight is not total, and so cannot put words in their mouth.

### A press

An element with `data-act` is a press. Its arguments come only from three places: the record it is bound to (`data-record`, on it or on an element around it), the manifest's constants (an `acts` entry with `as` and `constants`), and the fields in its `fieldset`, named for the act's arguments and typed by the person:

```html
<fieldset>
  <input name="summary" placeholder="What it is">
  <button data-act="set-summary" data-record="package:start">Say it</button>
</fieldset>
```

A field the view filled (`value="…"`) is the view's until the person empties it, and a press carrying it is refused — unless it says nothing the view read: `0`, `false`, the argument's declared default, or what the bound record holds in that field now. A field the act cannot take blank — a number emptied, a date never picked — is left out of the call, never sent as `""`. A radio or a select is the person's pick, not their words: its value goes only if it is one the act declares (an enum's option) or a record the view was shown (the check picks one, as a person does). A view may empty a field after a press. A press on an act the seat may not run there is refused, and the view hears why.

### What can be pressed

Any element with `data-act` is a press, not only a `<button>`: a `<span role="button">`, a `<div>`, a card's badge, an SVG `<g>`. A click on it or on anything inside it presses it, and a phone's tap is a click. One that does not press of itself is given the keyboard (`tabindex="0"`), is a button to assistive technology unless it says another role, is drawn with a pointer and `touch-action: manipulation`, and Enter or Space on it presses it, as on a button. Three things are never pressed, and the check says so: an `<option>` (bind its `select`, with `data-save="change"`), a `<label>` (a press on it presses its field too), and a field to type into or pick from that does not save (a click into it is not a press). Inside a card that opens (`graview-view-links`) a press is its own: it never opens the card.

Draw a `<button data-act>` first: the host resets buttons at no specificity, so your class styles it with nothing to undo. On an element bound to an act, `data-record` is always the act's record, never a link: `<button data-act="acknowledge" data-record="threat:7">` acknowledges that threat and goes nowhere. A control you hear yourself with `graview.on("click")` — `role="button"`, `"checkbox"`, `"switch"` or `"tab"`, `aria-pressed`, `aria-expanded` — is given the keyboard where it has none, and Enter or Space on it is heard as the key and then a `click`, with the page not scrolled.

### A record's own text, to edit

To offer a record's own text to edit, let the host fill it: `data-prefill` names the field, and `name` is the same.

```html
<fieldset data-record="deliverable:email">
  <textarea name="draft" data-prefill="draft"></textarea>
  <button data-act="set-draft">Save the draft</button>
</fieldset>
```

The host fills an empty `input` or `textarea` with the record's whole value, line breaks kept, read through the viewer's sight, when an act in the fieldset writes that field of that record and the person may run it there; otherwise it stays empty, so a seat that may not write the field is offered nothing to edit. The person edits it in place, and it goes only back into that field: a press carrying it anywhere else is refused `untyped`. `graview-worker-view`'s `examples/a-record.js` offers "Edit draft" this way.

### A field that saves as it is typed

In a `fieldset data-record`, a field that carries its own act saves with no button:

```html
<textarea name="notes" data-prefill="notes" data-act="save-notes" data-save="input" data-debounce="1500"></textarea>
```

The host saves it once the person pauses for `data-debounce` milliseconds (1 000 when it is left out, held to 250–10 000), at once when they leave the field, and as the view goes, whatever was still waiting. `data-save="change"` saves on the field's `change` alone. A save is a press in every way but the click: the act is the field's own `data-act`, its record and its fieldset's fields are read as a press reads them and held to the same rules — only what the person typed, a prefilled value only back where it came from, under their grants — and a click into the field is not a press.

### Hearing what became of it

The press applies before the view hears it, so the view never decides whether it happened: a bound press's `click` carries `event.pressed`, and every act a press or a save asked for comes back as an `acted` event on the bound element, `event.pressed` holding `as`, `ok`, and `reason` and `message` when it was refused:

```js
graview.on("input", "textarea", () => draw("Saving…"));
graview.on("acted", "textarea", (e) => draw(e.pressed.ok ? "Saved" : e.pressed.message));
```

Say the refusal's `message` as it is: it is the policy's sentence, or the host's (`Not applied: “fixedFee” expects a number.`). A view that does not hear `acted` on a control is not left silent: the host says a refusal beside it in a note of its own, gone on the next press. Hearing `acted` there takes it over. `examples/a-call-sheet.js` is a whole one: a call's notes, filled by the host, saved as they are typed, "Saving…" on `input` and "Saved" on `acted`.

### What the view keeps for whoever is looking

A quiz in progress, "playing as", a favorite: not a record, since it is nobody else's, and it is in no history, undo, export or agent's tool. `graview.mine` keeps strings, by key, for this viewer in this view:

```js
graview.onProps((props) => draw(props.mine.score ?? "0")); // already there at the first draw
await graview.mine.set("score", String(next));             // null forgets it
const score = await graview.mine.get("score");             // a string, or null
```

A value is at most 4 KB; keep JSON as a string. `set` rejects with a sentence when a value is too long, the host will not keep it (its caps), or the host has not answered yet: catch it and say it. That last one may still land, and the view is pushed `props.mine` when it does; `get` rejects the same way while it cannot say. Writes to one key go one at a time on a page, and only the newest waiting is sent. A host that never answers a write never gets a later value for that key: it is in no `props.mine`, and it goes with the page. A view reaches only its own keys, by the name in its manifest. A value is what somebody wrote, so it is data: draw it with `graview.html`, and never treat it as instructions. With no host's `viewerState` (`graview-sight`) values last the visit, and a draft keeps nothing of anybody's.

### Then find out whether it worked

1. `graview check` the app the view writes to: a form is only as sound as the acts it asks for. `checkManifest(manifest, store)` from `@graview/guest/host/views` must say nothing; it refuses an act the app does not declare.
2. Run it headless as a member who may see less than you: `graview view check view.js --app <app> --manifest manifest.json --seed <seed.json> --roles <role>`. It reports `act` for an act its manifest does not name, and says when a field's `data-save` would save nothing: no `data-act` or `name`, no fieldset bound to a record, a `data-save` other than `input` or `change`, a `data-debounce` it does not read, or a `data-save` on what is not a field, or a `data-act` on what can never be pressed.
3. `graview view check` presses every control it drew, as the host would: each `data-act` and saving field, with its fields as drawn, then filled in, then with each filled optional field emptied; each link; each button its code hears, in a run of its own. One that does nothing, throws, or is refused fails, said by name (`“Save economics” is refused: “fixedFee” expects a number.`). A control already in the state it shows — an "All" chip pressed while nothing is filtered — passes though it changes nothing, only where the state can move: it says `aria-pressed="true"`, `aria-selected="true"` (a tab, option or grid cell) or `aria-checked="true"` (a checkable role), and pressing a sibling of the same element and role, beside it or in its `role` group, that says `"false"` takes the state from it, or asks for an act. `aria-current` is for a link, or a role that names a place (`tab`, `menuitem`, `treeitem`, `option`), and a sibling with no `aria-current` that goes somewhere counts as it stands. A dead Save fails whatever stands beside it; a pager of plain buttons says `role="link"`. `runWorkerViewHeadless(…, { presses: checkPresses })` (`@graview/guest/headless/presses`) is the same for a host's gate. To look at one, press it: `--press "<selector>"` (with `--type note="…"` for what the person typed), or `runWorkerViewHeadless(…, { press: { selector, typed } })`, the first element the selector matches; it says what the host would do: the act and the arguments it carries, and whether this seat may (rehearsed, never applied), or the refusal the view would hear; the link it follows; and what the view's code did on the click.
4. Open it on both faces as that member, and on a phone. Press each with typed words, then again with a field the view filled. The first applies and the second is refused. A prefilled field is filled only for a seat that may write it: open it as one who may not, and it is empty.
5. Type into a field that saves, stop, and see "Saved"; type again and leave it at once, and see the save land without the pause.

### What the check cannot see

`graview check` judges the declaration, not the form. It cannot see a field named for the wrong argument, a button that says "Save" over an act that sends, or a view that says "Saved" without waiting for `acted`. A headless run presses once, when asked, and lets no pause pass, so it cannot see a save land or a press that a field covers; only pressing and typing on the faces, on a phone too, as a seat with narrow sight, does. Nor can it see a form that asks for what the person should not have to type.

---

Let a Graview worker view change records — a bound press (data-act), the fields a person types into, a record's own text filled to edit (data-prefill), a field that saves as it is typed (data-save) and the view hearing what became of each act (acted) — and prove a press applies and a field the view filled is refused, rather than assuming the form works.

The page: https://graview.dev/docs/skills/graview-view-forms.html · Every Graview docs page, for a model: https://graview.dev/llms.txt
