Skip to main content

Preferences

A Preference is a value that people save on a Pane, such as a category, a date range, a judgment, an audience or a switch. Preferences let the choices people make while they use a Parable stay, change and keep a history, without a change to the Parable itself. Every Preference is Live: a save takes effect at once, is not part of a Version, and never publishes, proposes or opens a set of Changes. A Pane has one type: what it shows and what people save on it. Preferences are rows of that type. The type is a named interface whose id field, Generic.Text or Identity.UUID, names each item, such as a category’s m-customer-care. Parable keeps one row for each item and each actor, and stores the item id byte for byte. A row holds only the fields that its actor sets, so a row is a partial value of the type, and a default is never copied into it. A row can also hide an item that a wider layer holds, with removed: true. A row is at most 64 KB, and one actor can keep at most 10,000 rows on one Pane. The server refuses a value that does not fit the type (PA-AU-040), raw JSON (PA-AU-075), a row whose id differs from the value’s id (PA-AU-062), a row above 64 KB (PA-AU-060) and a row past the cap (PA-AU-061). There are four actors, and the product names them the same way wherever one is shown or chosen. Me is the signed-in person’s own row, for: 'me'. A team is one group of people that the Parable’s own data describes, at any level, such as a Division or a Department, for: 'group:<id>', where the id is the one a Plot of the Parable gives the group, in canonical form. This Parable is the Workspace’s shared row, for: 'workspace': each Workspace’s copy of a Parable has its own, so never call it the Workspace. Default is the Parable’s own default, for: 'default': only Primary on the line that owns the Parable writes it, it never names people, Roles, teams or Workspaces, and every copy of the Parable gets it with each Release. Parable keeps no list of teams, so a Pane offers the teams that its own Plot reads, one section per level, named as the Parable names the level. A read layers the rows field by field: Me, then This Parable, then Default, then the default in the Pane’s code. A team’s row joins between Me and This Parable only when the read names the team. A read with no reader, such as a Refresh, a Pipeline or a Report run, layers This Parable and Default only, as the rows were when the run started. Under View as, a read skips person rows, and every save, undo and proposal is refused (PA-AU-044). A person’s own rows reach only that person, in every read: the Pane, SQL, history and Ponder’s tools. A team’s rows and This Parable’s are shared: everyone the Pane admits reads them, and the Pane’s Policies and filters decide who writes them. view.preferences permits reading, create.preferences permits the first save, edit.preferences permits a change, and delete.preferences permits a delete; each is separate. A save names whose row it writes, and the server checks it: Me needs the Pane’s preference grant, a team or This Parable also needs the Pane’s edit filters, and Default needs Primary on the owner’s line (PA-AU-065). On a Platform Parable a Workspace writes Me, team and This Parable rows only (PA-AU-067). Pane code reads and saves through its module’s preferences. preferences.get(id) returns the item layered for the reader, or null when no layer holds it, so show the Pane’s own default then. preferences.get(id, { group }) adds that team’s layer. preferences.useItem(id) and preferences.useItems() are React hooks that keep an item or a list live: a save by anyone updates them in place, without a remount. preferences.list({ where, limit, after }) reads one page of items, and its next cursor reads on; a list never stops silently. An editing view reads one actor’s own row with preferences.get(id, { for }), or many actors’ rows in one request with preferences.rows({ for, ids }), and saves it back with the same for. preferences.save({ id, value, for }) replaces that actor’s row with the fields in value; for is ‘me’ when absent. preferences.saveMany(rows, { for }) saves many rows in one transaction, with one update to every reader and one estimate of what runs again. preferences.remove(id, { for }) deletes that actor’s row, so the next wider layer shows. preferences.history(id) lists earlier values newest first, and preferences.undo(id, { for }) puts that actor’s row back. A save takes expectedRevision: 0 creates the row and fails if it exists, another number replaces the row at that revision, and none saves on the newest row. Only two edits to the same item conflict, with PA-AU-059: read the item again, show both values, and save again without losing the draft. preferences.current(value) tells the Page what the Pane shows now, so a Plot that reads the Pane by name reads it; it saves nothing. A declared Pane’s rows are panes.<name>.preferences.get(id), under that Pane’s Policies. preferences.values, preferences.mode and preferences.plotSql are removed: the Code check refuses them (values-api). When a value holds a new id, mint it with newIdentityUUID() from @psgen/scalar-lib, or use a short stable text id such as a slug. Never use crypto.randomUUID(): its hyphenated form is not the canonical id. A link to another item is a typed reference, Ref<Category>, never text: a save checks each link as the saver, so an item that the saver cannot see is the same as a missing one (PA-AU-063), and a row links only to items of its own layer or a wider one (PA-AU-064). A Pane’s preload field decides what its load carries: ‘all’ for a bounded set, such as categories, so reads send no request, or ‘page’ for rows that grow, such as judgments, so the load carries one page. Without it, rows load when the code reads them. Values that the Parable owns are not Preferences. An ontology, a mapping or a list of Provider descriptions is the Pane’s own data, declared as data in its Perception, and only a proposal changes it (see the Perceptions Power). Each item of data is at most 64 KB. A Pane reads its data with data.get(id) for one item, data.list({ limit, after }) for one page, and data.list() or the data.useItems() hook for every item, and changes it with propose.change(pane, { data: { upsert, remove } }), which the reader confirms in a dialog that the host draws. The change goes into the reader’s set of Changes and reaches readers when the set publishes. A Pane whose published Version still holds Versioned<> values is converted once with Convert a Versioned Pane, which copies the current Version’s This Parable values into the Pane’s data in your set of Changes. A team’s Versioned values do not become data: when the set publishes, each one becomes that team’s rows of the Pane’s type, and a value that does not fit the type fails in the row conversion that Prepare shows. A Pane made before Typed Panes has no data grants, and a missing grant admits nobody. The conversion is judged by the Pane’s Preferences grants, and it also writes the data grants: each one is a copy of the Preferences grant of the same action, so the people who could read and change the values read and change the data. That copy needs no edit.policies. After the conversion, the data grants alone decide. A Pane that holds data and whose Policies name no view.data grant is an error (data-grants), because nobody can read its data: add the data grants to its Policies, which needs edit.policies. Some rows decide who may do what. A Policy reads a row through a grant’s fromSetting, { paneKey, itemId }, or a filter’s enabledFromSetting, and reads This Parable’s row, else Default’s. A Policy that names a row by a legacy handle is an error (setting-item-id): name the row by its item id. Changing such a row needs edit.policies on the Page or Pane that holds the Policy, and a filter’s condition reads rows that only people who may edit Policies should write. Two writes need more than the Pane’s grants. A Pane-code save of a row that a Policy reads, and every proposal, are privileged: the reader must click a confirmation that the host draws outside the Pane, and the server also refuses the write unless the Pane’s author holds the right (PA-AU-069, PA-AU-070). A save after which no active member could edit those Policies is refused (PA-AU-068). A Plot reads a Pane’s rows by name, as typed rows: parables.<parable>.<pane> returns items layered for the reader, parables.<parable>.<pane>_by_actor returns each actor’s own rows with actor_kind and actor_id, for example every team’s, and parables.<parable>.<pane>_data returns the Pane’s data. Never read workspace.preferences in a Plot: it holds raw JSON with no type, and the SQL check refuses it (typed-read). A Pipeline result shows that the settings it read changed since its Refresh, with Preview and Run. Ponder’s tools work on rows. List Preference rows takes the Pane, the actors and the items, and pages with after; it returns your own person rows only, never another person’s. Read Preference items reads items by id, layered for you. Save Preference rows takes the Pane, forActor (kind person, group with groupId, workspace or default) and rows of { itemId, value, removed, expectedRevision }, all saved in one transaction. Delete a Preference row deletes one actor’s row, and Undo a Preference change puts one back. Read a Preference item’s past values lists one item’s history. Read a Pane’s data reads its data. Save Preference rows refuses a Default row or a row that a Policy reads when nobody asked the person (PA-AU-069). Rows are saved only on a Pane that holds them: a saved-data Pane, or a Plot Pane that declares overrides: true. A bound Pane holds none, and a Pane that still declares preferences or Versioned<> must be rewritten first (PA-AU-076). In a Problem that other people read, these tools return no person rows, yours included. Save shared Preference rows takes the same input and asks the person first: use it for those rows, and name the change. createOnly true creates a row and fails if the actor already has one.
  1. Name the choice that people keep, and the Pane whose type holds it. A new kind of thing is a new Pane, and many things of one kind are rows.
  2. Give the type an id field and real fields: scalar-lib types, Ref<T> links, named interfaces, and lists of these. Never raw JSON.
  3. Decide which actors the Pane offers: Me, a team, This Parable, Default, or several. Label each choice with its actor, such as Hide for Me and Hide for This Parable, and offer a shared choice only to readers whose Policies allow it.
  4. Set preload for a set that the Pane always shows. Read with get, useItem or useItems, and save with save or saveMany. Keep drafts in React state until the person saves.
  5. In SQL, read the Pane by name, and read _by_actor where each actor’s rows matter.
A good design keeps only real choices as rows and puts the app’s defaults in the Pane’s code or in Default rows. A difference between Workspaces is a row the Parable reads, never a branch in its code or SQL. Check it in the running Parable: a valid value saves and comes back after a reload as exactly one row, a partial row merges with the layers below it, an invalid value is refused, a stale revision conflicts without losing the draft, another person cannot see a personal row, and a shared row follows the Pane’s Policies. Confirm that rendering and reading created no rows. Related Powers: Panes, Perceptions, Policies, Plots. Find each by its title; do not guess stored names.