# graview-tidying

`graview-tidying` 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: Tidy a Graview app without losing anything — set records aside (archive, bring back) instead of deleting them, merge a duplicate into the one that stays, and let the app's own actor link and prune by suggestion a person accepts or declines, held to what people said, and suggest changes to the app itself learned from how several people use it — verified with graview check and by undoing it rather than assuming nothing was lost.

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

## The skill

## Tidy without losing anything

A list that grows fills with what is finished and what was entered twice, and the only tidy a bare store offers is a delete, which takes the record's links and its history with it. Every kind can already set a record aside and fold a duplicate into the one that stays, and an actor that tidies on its own can be told what it may not touch. What a list offers, the order it opens in and how its cards read are `graview-lists`.

### Do this

1. **Set records aside instead of deleting them.** Every kind has `archive-<kind>` and `unarchive-<kind>` (`{ id }`, "Bring it back") and `merge-<kind>` (`{ id, into }`: `into` takes the duplicate's links and the duplicate is archived), permitted through the acts that create the kind. An archived record is out of lists, the scene, counts, computed fields and rules; its own page and search still find it, marked. A list says "+3 archived" and opens them at `?archived=true`; a rule reads them with `all('feature', true)` (`'all'` for both) and `archived()`. A kind that should never be archived says `archives: false` in `defineNode` (a document's `"archives": false`; the edit `set-archives`): it is given no archive, unarchive or merge, and its records are kept or removed. An archived record keeps its fields, its links and its history.

1. **Let the app's own actor tidy, and say what it may not.** An actor that links and prunes on its own is an author that says `automated: true` (`kind: "system"` or `"agent"`). What it is sure of it applies with `lineage: { basis, confidence }`; what it is not it suggests: `suggest(store, calls, { author, lineage })` (from `@graview/core/tidying`) keeps the calls in the log, changes nothing, and they wait under Needs you until a person accepts (`acceptSuggestion`, made as them) or declines them (`declineSuggestion`); `declined(store, calls)` says so, and the actor is refused the same change again — matched by what it means (`meaningKey`): the same link by any act that makes the relation, a record made again under a new id, a merged pair either way round. So is a person's undo of any change, or unlinking a link; a checkpoint keeps declines (`Epoch.declines`). `stopSuggesting(store, id)` holds an author with `tidying: true`, no workflow, to that act and fields (another field is still asked); an undo lifts it. Its run is one Activity row. Give a night's work one `lineage.run`, name the judge in `lineage.actor` and the records it rests on in `lineage.evidence`; give a suggestion an `expiresAt`, after which it leaves the tray and the next run judges it again. A suggestion of several calls (a workflow's run with `approve: "ask"`) lists each change; `acceptSuggestion(store, id, { items: [0, 2] })` takes some, the rest wait, Decline takes the rest. "Not now" is no decline: the person's own state (`Dismissal`, handed to `suggestions(store, who, { dismissed })`), hidden from them alone, the actor bound to nothing, kept in the host's `dismissals`, else the browser; a seat says it with `withNotNow(runtime, dismissals)`. Someone who may see a suggestion and may not make its change — a reader, a visitor by link — is not refused: their Accept and Decline are a vote, for or against (`acceptSuggestion` and `declineSuggestion` answer `voted`; `voteSuggestion(store, id, "for" | "against" | null)` casts, changes or withdraws one), one standing vote each; whoever may decide (`mayDecide`) reads each suggestion's `tally: { for, against, mine }` beside Accept. Say `"tidying": { "autoAcceptAt": 4 }` at the top of the document (the edit `{"op": "set-app-tidying", "autoAcceptAt": 4}`, `null` to take it away) to have the actor make a suggestion at four votes for, the voters in its `lineage.voters`; `graview check` warns with none A seat no one person holds — the host's one signed-out seat (`shared: true` on its principal) or a `visitor` — is refused a vote ("Sign in to vote on this suggestion"), and the tray offers the host's `signIn` instead; `votes: { advisory: "visitors" }` on the store keeps such a vote under the seat's `visitorKey`, in `tally.advisory`, never toward `autoAcceptAt`.

1. **Hold it to what people said.** A person says "Leave this one alone" on a record (`leave-alone-<kind>`, cleared by `allow-tidying-<kind>`; `isLeftAlone(store, id)`), and the declaration says a kind is never tidied or never linked — `tidying: "never" | "no-links"` on `defineNode`, `tidying: "never"` on a relation; in a document the edit `{"op": "set-tidying", "kind": "vendor", "tidying": "no-links"}` (with `"relation"` for one relation, `"allowed"` to undo it). The store refuses the actor each in a sentence; a person is never refused by them.

1. **Let it dream.** `dream(store, { now, limit, ai })` from `@graview/tools/tidy` runs the actor once. Code puts forward near-duplicates, the records a relation might mean, finished records and empty choices; the host's System One model (`ai.fast`, Jev) does the judging, one request per candidate; code routes each answer by how much the change would undo (`BANDS`) — made, suggested with its basis ("Same vendor, entered twice"), or dropped. Without a model, or when it fails or the budget runs out, the floor is the judges' own rules: a shared name or email, a link the record's text names, a finished record unchanged for 30 days. A placeholder name ("—", "Untitled") is no name; records a one-link relation points apart are never paired. It skips what was declined, what waits, what is left alone or changed in the last day, and applies nothing unless the host passes `applyAtLeast`. A host's schedule runs it (`graview dream <entry> --data ./data --model jev-latest --cache ./cache.json`), or reaches the store as the actor through `suggest_changes`, the tool only an `automated` seat is offered. `whileAway(store, { seq }, principal, { dismissed })` is the tray's line, less what the reader said "not now" to; a host that works it out after the first draw hands it over with `handle.setWhileAway(away)` (a React host changes the prop).

1. **Let it learn the app from use.** `learnFromUse({ document, friction, log, records, declined, opens })` from `@graview/core/learn` suggests changes to the declaration itself, each resting on at least `minPeople` (3) distinct people: another word for a kind or a field people searched with and found nothing (`add-alias`; a kind's or field's `aliases`, which search finds and a model is told), a text field whose values repeat retyped to a choice, a date field beside dates written into words (nothing is moved), one act for two people take one after the other (only document acts that set fields), a clearer description for an act agents call wrongly, and a place a host's `opens` counts say nobody opens. It changes nothing: `acceptAppSuggestion` applies the edits through `editDocument` with `lineage: { basis, suggestion: key }`, and `declineAppSuggestion` is kept (`learned.json` beside the store) and handed back as `declined`, never offered again. `graview learn <entry> --data ./data` prints them; `--accept <key>` or `--decline <key>`.

1. **A workflow runs the app's own actor on a schedule** of the app's choosing. A document's `workflows` names each: a `title`, `when` (`{ "every": "sunday 18:00" }`, `cron`, `made`, `changed`, `synced`, `byHand`), `approve` and its `steps` — an act over the records a rule picks, with a `key` so a rerun changes what it made, or a model proposing from a list of acts. Dreaming is the one every app has (`workflowsOf`, `@graview/core/workflows`); `runWorkflow` from `@graview/tools/workflows` runs the rest, the first run after a change a dry run. A `classify` step labels each record with one of a choice field's options — a model's judgment and how sure, or without one the `floor`'s links and words — asked of a person first, and always for an option in `confirm`. The workflows skill to come says how to write one.

1. **A person proposes too.** `propose(store, principal, calls, intent)` keeps the same suggestion op with the person as author and `lineage.proposedBy`, where a grant's `propose` lets them ask for an act they may not run (the permissions skill). The tray lists it apart, "Proposed by Ada"; whoever may run it accepts it as themselves, declines it ("Declined Ada's proposal: …"), or its proposer withdraws it. Accepted, it is in the working state, and reaches readers on the next publish where the app publishes.

### Then find out whether it worked

```sh
graview check src/domain/app.ts
npx graview check --document app.json   # a document
```

Report **the actual output**. In a document it warns `tidying-redundant` where a relation says `"never"` that its kind's own `tidying` already says, and refuses an alias another kind or field is already called (`alias-taken`). The rest is behavior, so try it:

- Archive one record by its act and ask `graview describe <entry> --place <slug>` again: it is gone from the list and the count, and `?archived=true` finds it. Bring it back; its links are still there.
- Merge two records and undo the merge: both come back with their links.
- Have a test actor (`automated: true`) suggest a link and decline it from the tray: `suggest(store, …)` of the same calls then refuses, and a record left alone refuses the actor's change by name.
- Accept a suggestion as a seat the policy grants nothing: the answer says `voted: "for"`, the graph is unchanged, and `suggestions(store, seat)[0].tally` is `{ for: 1, against: 0, mine: "for" }`.
- Run `graview dream` twice: the second run suggests nothing new, and its JSON says each finding is already waiting.
- Run `graview learn` after `--decline <key>`: that suggestion is gone, and one person's hundred searches never make one.

### What the check cannot see

Whether a rule should still count an archived record: it does not unless it asks with `all(kind, true)`, and a total that should include last season's says so on purpose. Whether the actor's confident changes deserved their confidence — read a day of its Activity row before raising what it applies without asking. And which of two duplicates should stay: `into` is the one you name, so name the one with the history.

---

Tidy a Graview app without losing anything — set records aside (archive, bring back) instead of deleting them, merge a duplicate into the one that stays, and let the app's own actor link and prune by suggestion a person accepts or declines, held to what people said, and suggest changes to the app itself learned from how several people use it — verified with graview check and by undoing it rather than assuming nothing was lost.

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