Skip to main content

Sources

Docs: Panes, Perceptions, Policies, Plots, Warnings & Errors, and Workspace Pool. Web App: A Pane’s own controls save a value when you choose to keep it, such as a category, a date range or a judgment, and show it again when the Page reloads. Other people see a shared change at once, without a reload.

Structure

A Preference is a value that people save on a Pane: a category, a date range, a judgment, an audience, a switch. Preferences keep the choices people make while they use a Parable, let them change, and keep their history, without a change to the Parable itself. Every Preference is Live: a save applies at once, is not part of a Version, and never publishes or opens a set of Changes. A Pane has one type, declared in its Perception: what the Pane shows and what people save on it. Preferences are rows of that type. The type’s id field, Generic.Text or Identity.UUID, names each item, such as the category m-customer-care, and Parable stores it byte for byte. Parable keeps one row for each item and each actor.
A row holds only the fields that its actor sets. A row is a partial value of the type, so a default is never copied into it, and a later change to the default still reaches every row that does not set that field. A row can also hide an item that a wider layer holds, with removed: true.

Actors

Every row belongs to one actor. The product names each actor the same way wherever one is shown or chosen: Each Workspace’s copy of a Parable has its own This Parable rows, so the actor reads This Parable, not Workspace. A team names its group by the id that a Plot of the Parable gives it, in canonical form. Parable keeps no list of teams: a Pane offers the teams that its own Plot reads, one section per level, named as the Parable names the level. A Default row never names people, Roles, teams or Workspaces, and every copy of the Parable gets the Default rows with each Release.

Layers

A read merges the rows of an item field by field, narrowest first:
  1. Me.
  2. A team, only when the read names it: preferences.get(id, { group }).
  3. This Parable.
  4. Default.
  5. The default in the Pane’s code, for fields that no row sets.
A read with no reader, such as a Refresh, a Pipeline or a Report run, merges This Parable and Default only. Another Parable cannot yet read a published Plot whose SQL reads these rows by name. Under View as, a read skips person rows. A Pane that offers several actors labels each choice with its actor, such as Hide for Me and Hide for This Parable.

Fixed defaults

A Pane can declare readonly fixedDefaults: true in its Perception. Then no Me, team or This Parable row can change a Default row:
  • A save of a Me, team or This Parable row with the id of a Default row is refused: “The Parable’s Default sets this row, and no other row can change it. Add a new row instead.” (PA-AU-094).
  • A row with that id that was saved before the Default row arrived, or before the Pane declared fixedDefaults, stays stored, but it changes no read. The Pane, Plot SQL, a Refresh and Ponder read the Default row as it is, and a removed row does not hide it. Its actor can still delete it, and _by_actor still shows it as that actor’s own row.
  • People still add their own rows with new ids.
The Pane’s initial load follows the same fixed-Default rule as later reads. The current definition’s rule also applies while you read an older Release or a set of Changes. The Pane and its Plot use the same rule. Only a Pane whose type has an id field can declare it. For example, a Pane of Proofs keeps Default Proofs that run in every Workspace: a Workspace or a person adds Proofs, and never changes or turns off a Default Proof.

Rows about a Parable

A Pane can hold rows about another Parable, such as reviews of a Patch. Give the Pane’s type one top-level field of the type Parable.Subject. Each row names, in that field, the Parable that it is about: a Patch at a revision, a Release, a Refresh, or one Piece at one of those (Field types). A field of the type Identity.Signer holds the person who signed the row.
  • Who reads a row. A row shows only to a person who can read the Parable that it is about, with the rule of a Platform Parable’s tab. For a row about one Piece, the person’s Pieces tree must also list that Piece. Peeking lists Pages only. Every read applies this rule: the Pane, its first load, the row list, the item’s history, Plot SQL and Ponder’s tools. Ordinary SQL reads no row about a subject. A Plot must be bound to that subject to read its rows.
  • Which rows a Pane reads. In a Platform Parable’s tab, the Pane reads the rows about the Parable beside the tab and about its Pieces. Outside a tab, the Pane reads no rows of this kind.
  • Changing the subject. When you navigate to another composed Parable, Patch, Release, Refresh or Piece, the tab clears its previous reviews and reads the current subject’s reviews. A response from the previous subject cannot replace them, even when you return to that subject.
  • Who saves a row. The Pane’s Policies decide who saves, as for every row. The person must also read the Parable that the row is about: “You cannot read the Parable that this row is about.” (PA-AU-095).
  • Who signs a row. The server sets the signer of a Me, team or This Parable row to the person who first saves it. Leave the field out. A value that names another person is refused (PA-AU-096). Default rows have no signer.
  • Who changes a row. Only the person who signed a row changes or removes it: “Only the person who signed this row can change it.” (PA-AU-096). This holds for Primary on the Parable too. After the Patch is published, Pocketed or closed, or after a newer Release or Refresh replaces the one that the row is about, Primary on the Parable’s Deployment can remove the row. Its history keeps the signer.
The signature protects each saved row within Me, a team, or This Parable. A layered item can still combine values from different people’s rows with the same id and subject. Use individual saved rows, not layered items, as signed review evidence. A Pane of rows about a Parable uses Identity.UUID ids, so that an id never says which Parable a row is about: live notices carry ids. It takes no Default rows, and it cannot share its rows across Workspaces. The Perception check refuses a text id, a second Parable.Subject field, a field below the top level, and acrossWorkspaces on such a Pane. In Pane code, subjectKey() gives the Parable beside the tab, and subjectKey({ piece: true }) gives the Piece in focus there. For a Patch, the key names the Changes revision sealed by Propose, not its observed working revision. It stays the same after a clean move onto a newer Release. A Pending Patch without retained Changes has no key for a new review. Earlier signed rows keep their original addresses. A Plot of the tab reads the rows about the Parable beside the tab that its reader can read, and a read with no subject reads none (Parable Pool). A Refresh reads none of these rows, and workspace.preferences holds none. A retained Refresh result is withheld if its captured Preferences include a private row, a row from a Subject Pane, or a row whose privacy cannot be checked. This applies to the selected Release, the current Pane, and the accepted retained definition of a removed Pane. The saved Preferences and result stay unchanged. A result computed from private inputs cannot be made safe by hiding individual output rows, because those inputs may affect a total. Removing an ordinary Page or Pane does not by itself hide its retained Refresh results. A later ordinary Preference Policy denial still limits raw captured Preferences for that person. It does not alone revoke a stored Plot result whose existing output and recorded-source Policies permit that person. A Refresh with no captured Preferences keeps those existing result checks.

Rows for named people

A Pane can hold rows that are for named people, such as Pings. Give the Pane’s type one top-level list of the type Identity.Recipient, and a field of the type Identity.Signer. Each row names, in the list, the people that it is for (Field types).
  • Who reads a row. A row shows only to the person who signed it and the people it names. No other person reads it: not Primary, not Parable staff, and not View as. Every read applies this rule: the Pane, its first load, the row list, the item’s history, a Ref from another row, live updates and Ponder’s tools. SQL reads none of these rows: workspace.preferences, Plot SQL and Report runs leave out every row of such a Pane. A row keeps this rule after a later Version removes the field.
  • Who saves a row. The Pane’s Policies decide who saves, as for every row. Each row is a This Parable row. A Me, team or Default row is refused (PA-AU-065).
  • Who a row names. A row names 1 to 100 people (PA-AU-101). Each person must be an active member of the Workspace: “This row names a person who is not an active member of this Workspace.” (PA-AU-100).
  • Who signs and changes a row. The server signs the row with the person who first saves it, as on a Pane of rows about a Parable. Only that person changes or removes the row (PA-AU-096).
  • How many rows. One actor keeps at most 100,000 rows on such a Pane, because every person of the Workspace saves This Parable rows there.
The Perception check refuses a second Identity.Recipient field, a field below the top level or one that is not a list, a type with no Identity.Signer field, and a type that also has a Parable.Subject field.

What Preferences are not

Values that the Parable itself owns, such as an ontology or the descriptions of Provider tables, are the Pane’s own data, declared as data in its Perception. Pane data travels with each Release, and only a proposal changes it. The app itself — its Pages, Panes, Perceptions, Policies, Palettes, Plots and Pane data — is versioned with the Parable. A Preference never holds React, CSS or SQL, and never changes the text of a Policy, though a Policy can read one (Security).

Services

In Pane code

A Pane reads and saves through its generated module’s preferences. The binding belongs to the calling Pane; a Pane it declares is read as panes.<name>.preferences, under that Pane’s Policies. Rendering and reading never create a row.
A Pane whose Perception sets preload gets its rows with its load: 'all' for a bounded set, such as categories, so its reads send no request, or 'page' for rows that grow, such as judgments, so the load carries one page. Without preload, rows load when the code reads them, and the ids that one moment asks for go to the server together. A save takes expectedRevision. A positive number replaces the row at that revision, and the save fails with PA-AU-059 when the row is missing or has another revision. 0 or no number saves on the newest row. Only two edits to the same item conflict: read the item again, show both values, and save again without losing the draft. Saves to different items never conflict. preferences.values, preferences.mode and preferences.plotSql are removed. The Code check refuses them (values-api).

In Ponder

The Workspace API has these operations, and Ponder can use each as a tool: A save names the Pane, the actor, and the rows:
A row of the API can also set createOnly. true creates the row and fails with PA-AU-059 when the actor already has one. The API reads 0 and null alike, so create-only is its own field. It returns each row’s id and new revision, not its value. Ponder asks you before it saves a Default row or a row that a Policy reads, and names the change. In a shared Problem, a tool result never shows another person’s own rows.

Live updates

A save sends every reader that the Pane admits a short notice with no value, and each open Page reads the changed rows and updates them in place, without a reload. Only the Plots that read the changed Pane by name, or read a Plot that does, run again. A person’s own save reaches only that person. On a Pane of rows about a Parable, the notice holds only the row’s random id, and each open Page reads the row again with the rule of Rows about a Parable.

Storage

Each row records the item id, the actor, the value, a revision, and the hash of the type that checked it. A row is at most 64 KB, and one actor can keep at most 10,000 rows on one Pane. Every write keeps the earlier value in the item’s history. A Pane of rows about a Parable has other caps. One actor keeps at most 100,000 rows on it. One Parable that rows are about has at most 1,000 rows there: “This Patch already has 1,000 rows here.” (PA-AU-097). One person signs at most 100 rows about one Parable: “You already saved 100 rows about this Patch.” (PA-AU-091). The counts include rows about Patches that are closed. The server checks every value against the Pane’s type as a partial value when it is saved. That type comes from the line’s current definition: the newest Release on a published Parable, and the Parable as it stands on a Personal Parable. It never comes from a set of Changes or from the Release that the reader views. So a Pane that exists only in an open Patch takes no rows until that Patch is published. A save, a removal or an Undo of its rows is refused with HTTP 409 (PA-AU-005) and this sentence: “This Pane is new in a Patch that is not published yet. You can change its Preferences once that Patch is published.” Only a person whose seat on the Parable opens that Patch gets this sentence. A person with no seat gets not found (HTTP 404, PA-AU-001), as for a Pane that no open Patch holds. The server refuses a value that does not fit (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). A Release may only grow a Pane’s type, by optional fields or a wider union, so every saved row still fits (shape-grow). A Personal Parable has no Release, so its save checks the rows instead. A save that changes a Pane’s type, or gives a Pane with saved rows a Plot, is refused only when a saved row would stop fitting (PA-AU-073). The refusal names the Pane and up to 10 of its rows with the reason for each. It counts the rows of other people, signed rows, rows about a subject and rows for named people, without naming them. It gives the steps that work: keep the old field and add a new optional one, or first make the saver’s own rows fit the new type. The saver cannot change the rows of other people. Ponder gets these steps without the rows (Perceptions). A change that every saved row still fits saves at once. A setting saved before its Pane’s type changed in another way can stop fitting. A typed Pane reads each part that no longer fits as not set and keeps the parts that fit. A missing required part at the top level is not a misfit. A nested object that is set must still include its required parts. A top-level part that the viewed type does not declare is not a misfit either. A newer Release can add an optional part, so an older Release reads that part as it was saved, with no notice. A save from the older Release keeps the part when the Pane code passes it back. Inside a nested object, a part that the viewed type does not declare still makes that object a misfit. For a part that no longer fits, the window says A saved setting no longer fits, with the Pane’s name, once per Pane per page. For a merged item, the notice names no layer and offers no action. A read of a named row says whose setting it was, including The Default setting for a Default row. When that row is your own setting (Me), the notice has Remove. History reads omit parts that no longer fit without a notice. These reads do not change the saved setting. A legacy Pane keeps its existing whole-setting check and notice for each saved revision. If Pane code saves a row again, the save replaces that row and removes the parts that no longer fit, because the Pane did not receive them. An older Release then no longer reads those parts from that row. Remove deletes the setting at the revision the read saw, and the window says Removed your saved setting. The Pane can then save a new setting under that id. If the delete is refused, for example because the setting changed since the read, the window says Could not remove your saved setting and the reason. A shared setting’s notice has no Remove. For a team or This Parable setting, a person with Primary, Publishing, Pinging or Peeking whom the Pane’s delete Preferences Policy allows can delete it, or ask Ponder to. Only Primary may change a Default setting. Rows are not part of a Version: publishing does not copy, reset or roll them back, and an older Version reads today’s rows under today’s Policies. The one exception is Default: each Release stores the line’s Default rows with it. When a Workspace copy takes a Release, the copy’s Default rows become the ones that Release stores. A Delivery can apply itself. Otherwise, a person with Publishing or Primary on the copy Pulls the Release and Publishes it. A Pull alone does not change the Default rows. The server’s read of rows checks only the reader’s access, with the Policies of the line’s current definition, and never checks a row’s shape. A Pane that the current definition does not hold shows its rows to no reader, in any Release. A Plot reads a Pane’s rows by name:
For the checks below, Plot SQL reads a saved field that does not fit the Pane’s type as not set (NULL). The other fields and rows still show. This includes a choice that the type no longer lists. A top-level field that a saved row leaves out reads as not set without a warning, even when the type requires it. Nested required-field checks are not active yet. Pane data items must fit as whole values. A read leaves out an item with a value that does not fit these checks, and returns the other items. This includes a missing top-level required field that does not allow null. Open Plot shows warnings in the Results caption in Ponder. Each warning names the Pane relation and field. A warning for an omitted Pane data item also names its id. No warning shows the stored value. The read changes nothing that is saved. These checks cover scalar reads, listed plain-text choices, and top-level required fields that do not allow null. Choice limits on numbers or true/false values, and the presence requirement for a field that allows null, are not checked. Those constraints keep today’s reading behavior. A Report run keeps the moment it started, and its Plots read the rows as they were then, layered with no reader: an old run reads the same rows later. A run reads a Pane’s rows only when the Pane’s view.preferences grant admits the person who runs it (for a scheduled run, the person who holds Primary on the line), and a Pane’s data only when view.data admits them. A person who reads a Plot as of a stored run reads only the Panes that their own grants admit. When a row that a Pipeline read changes after the Refresh that made its result started, the result says These settings changed since the last run and offers Preview and Run. workspace.preferences holds every Pane’s rows as raw JSON for the SQL console; a Plot never reads it (typed-read). Values that Panes saved before Preferences had one type are converted once, when the line’s first Version on this model publishes: a whole value that held a list becomes one row for each item, and every other row takes its value’s id, else its old handle, as its item id. A value saved alone, such as a switch saved as true, sets the type’s one field. A saved setting’s name and icon go into the type’s name and icon fields when the type has them. Prepare reports how many rows the conversion converts, drops and fails, with the reason of each group. Publish refuses a drop or a failure until the publisher accepts that report; a report that changed after Prepare needs Prepare again. On a Personal Parable, which has no Publish, the save that makes a Pane typed converts its rows, and a drop or a failure refuses the save with each group and its reason. IDs are Identity UUIDs in the canonical base62 form the API returns. Pass the IDs that reads and saves return rather than a hyphenated UUID. A new item id is a short, stable text id or newIdentityUUID() from @psgen/scalar-lib, never crypto.randomUUID().

Security

Preferences need an active Workspace membership on a live account. A suspended, revoked or missing member, or a deleted account, reaches no rows, even with a seat on the Parable. A person’s own rows are theirs alone, in every read: the Pane, SQL, _by_actor, history, live notices and Ponder’s tools. The one exception is a Pane that shares its rows across Workspaces (below): it shows the value of each row, without who saved it. A person reads their own rows only while the Pane’s view.preferences admits them, in every one of those reads, workspace.preferences included. A shared row, a team’s, This Parable’s or the Default, follows the Pane’s current Policies on the line: view.preferences permits reading, create.preferences the first save, edit.preferences a change, and delete.preferences a delete. These are separate grants. A team’s and This Parable’s writes also pass the Pane’s edit filters. Selecting an older Version does not restore its former grants. Under View as, every save, undo and proposal is refused (PA-AU-044). The server checks whose row a write names (PA-AU-065). Only Primary on the line that owns the Parable writes a Default row. The Pane’s current create.data, edit.data, or delete.data Policy must also allow that action, because a Default row is the Parable’s own value for every reader, like the Pane’s data (PA-AU-065). A Default row that names people, Roles, teams or Workspaces is refused (PA-AU-066). On a Platform Parable, a Workspace writes Me, team and This Parable rows only (PA-AU-067). On a Pane that declares fixedDefaults, a Me, team or This Parable row with the id of a Default row is refused (PA-AU-094, Fixed defaults). A row about a Parable shows only to a person who can read that Parable, and only its signer changes it (Rows about a Parable). A Pane of a Platform Parable can share its rows across Workspaces, for example the ideas and votes of a feedback portal. Such a Pane takes Me and This Parable rows only, and a row that names people, Roles, teams or Workspaces is refused (PA-AU-066). Every Workspace reads the shared rows and their count. Only Parable staff, on the Parable’s own line in Dogfood, read which Workspace saved each row, and no reader reads which person saved it. A link to another item is a typed reference, Ref<T>. A save checks every link of a row in one read, as the saver: 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). Deleting an item that narrower rows link to gives no warning, and those links then show Unknown. The row conversion checks the links of each row it converts. A row is data unless a Policy reads it. A grant in a Page’s or Pane’s Policies can take its value from one item of a Pane (fromSetting, { paneKey, itemId }), and a filter can switch on one (enabledFromSetting). The Policy reads This Parable’s row of that item, else the Default row. Changing that row also needs permission to edit the Policies that read it, its value must be a valid grant, and every change is recorded. A Policy of another Parable can also read the row (Read another Parable). Then the Policies of this Pane alone decide who may change the row. Set the Pane’s own Policies so that only people who may edit those Policies write it. A save or Prepare after which no active member could edit the Policies that a row governs is refused (PA-AU-068); a grant that admits nobody is still allowed. The first load of every preloading Pane uses shared values for any item that a Policy reads, just as later reads do. Me and team rows do not change that item’s value. A Pane’s code never acts with more rights than its author. A save from Pane code of a row that a Policy reads, and every proposal, ask the reader to confirm the change in a dialog that the host draws outside the Pane (PA-AU-069), and the server refuses the write unless the Pane’s author also holds the right (PA-AU-070). Removing someone from the Parable’s participants removes their access to its shared rows on their next request.