Sources
Docs: Panes, Perceptions, Preferences, Plots, and Policies. Web App: Each Piece’s editor lists its Warnings & Errors under the code, and the Publish action stays off until none is left.Structure
Warnings & Errors are what the checks find in a Piece: the compile, the type check, the Code check (React rules and classes that nothing styles), a Plot’s SQL check, and the Parable rules on this page. Every Parable rule is an error. Each finding names its rule, such asparable(versioned-removed), says
what is wrong and what to write instead, and links to its section below.
The rules keep every Parable on one model:
- A Pane has one type. The type is what the Pane shows and what people save on it.
- People save values as rows of that type, one row for each item and each person, team, Workspace or default.
- Values that the Parable owns are the Pane’s own data, and only a proposal changes them.
- Links between items are typed references, and the platform checks them.
When the rules run
- Save. A save is refused when it adds a finding. A Parable that still holds an older pattern keeps saving each step that removes it. An archive import is a save: it is refused for a finding it adds, with the same message.
- Prepare and Publish. They are refused while any finding is left, even one that the last Release already held. A small change also removes every older pattern.
- Reading. Opening a Piece shows the findings of the tree it reads. The server judges each tree once and keeps the findings, so a read of the same tree does not run the checks again.
id, and a customer’s write of
a Platform Parable’s defaults, data or Pieces.
Perception rules
These rules read a Page’s or Pane’s Perception.versioned-removed
Finds:Versioned<T> or Versioned<PlotSql> in a Perception.
Why: a Pane could change the Parable itself by saving a value, outside the
review that every other change gets. A Pane that uses Versioned<> gets only
this finding, so two rules never send you two ways.
Write instead:
- If the Parable owns the value, such as an ontology or a list of Provider
descriptions, declare it as the Pane’s
dataand change it withpropose.change(...). - If each person or Workspace sets the value, make it rows of a saved-data Pane.
- For Plot SQL, propose a change to the Plot.
live-wrapper
Finds:Live<T> in a Perception.
Why: every Preference is Live now, so the wrapper says nothing.
Write instead: remove the Live<> wrapper.
one-type
Finds: apreferences field in a Perception, also readonly preferences: null.
Why: a Pane saves rows of its own type. A second type for saved values
splits one thing into two shapes that nothing keeps in step.
Write instead:
- For
readonly preferences: null, remove the field and add| nullto the Pane’s type, as inBasePerception<Meeting | null>. The field let the Pane draw with no value from its parent, and| nullkeeps that. The Plots that the Pane owns still return rows of itstype. - If
preferencesis the same type as the Pane’stype, remove the field. - If the Parable owns the saved value, declare it as the Pane’s
data. - If each person or Workspace sets it, move it into its own saved-data Pane,
and read that Pane with
panes.<name>.preferences.get(id).
no-raw-json
Finds: a field of a Pane’s type, or of its data, that is raw JSON:Generic.JSON, unknown, any, or Readonly<Record<string, T>>.
Why: nothing can check raw JSON, Plots cannot read it as columns, and a
change of its shape breaks saved values without a warning.
Write instead: give the field a real type: a scalar-lib type, a
Ref<T>, a named interface, or a list of those. If it holds many items, make
them rows of a Pane, keyed by an id field.
bound-field
Finds: a bound Pane whose field is missing from its parent’s type, has a different type, or is bound twice. Why: a bound Pane takes one field of its parent’s row as its value (props.value). The field must exist, with the bound Pane’s type, and feed
one Pane.
Write instead: bind the Pane to a field that the parent’s type has, with
the same type, once:
bound-once
Finds: a Pane that two Panes bind, each withBound<>.
Why: a bound Pane uses the Policies of the one Pane that binds it. Two
parents would give it two sets of Policies, and a reader that one parent
refuses could read the field through the other.
Write instead: bind the Pane from one Pane only. If two Panes need the
same kind of field, give each parent a bound Pane of its own.
bound-policy
Finds: a bound Pane whose Policies differ from the defaults that a new Pane gets. Why: a bound Pane uses its parent’s Policies and never reads its own. Policies of its own would look like protection that does not apply. A bound Pane cannot narrow its parent’s Policies yet. Write instead: reset the bound Pane’s Policies to the defaults, and set who may see and change the field on the parent Pane.primitive-renamed
Finds: a Perception that still declaresreadonly primitive: ....
Why: the Perception field is now type. extends BasePerception<T>
already declares type: T.
Write instead: remove the primitive line, and put the type in
BasePerception<T>.
Policy rules
setting-item-id
Finds: a Policy that reads a row by a legacy handle:fromSetting: { paneKey, handle } or enabledFromSetting: { paneKey, handle }.
Why: Typed Panes name each row by its item id. The row conversion keeps
a single value’s handle as its item id, but a value that held a list of items
becomes one row for each item, so the handle names no row any more. The
Policy would fall back to its own grant without a word.
Write instead: name the row by its item id:
Pane code rules
The Code check runs these rules on a Page’s or Pane’s code.values-api
Finds: a read ofpreferences.values, preferences.mode or
preferences.plotSql, on the Pane’s own module or on a module of a Pane it
declares.
Why: preferences.values saved one whole value under a handle, outside
the Pane’s type, and the modes are gone.
Write instead:
- To show a value, use
preferences.get(id), or thepreferences.useItem(id)hook. The value is already layered for the reader. - To edit one actor’s value, read it with
preferences.get(id, { for })and save it withpreferences.save({ id, value, for }), whereforis'me','group:<id>','workspace'or'default'. - To change a Plot’s SQL, propose it with
propose.change(plot, { sql }, { expectedRevision }).
data-in-code
Finds: one literal of data above 8 KB in a Page’s or Pane’s code, such as a long list of objects or a long string. Why: the checks cannot see data in code, and nobody can change or customize it without changing the code. Write instead: move the data into the Pane’sdata with a proposal, or
into Preference rows if each actor needs a copy of their own.
Plot SQL rules
These rules read a Plot’s SQL.typed-read
Finds: Plot SQL that readsworkspace.preferences.
Why: workspace.preferences holds every Pane’s saved values as raw JSON,
with no type. It is only for looking at values in the SQL console.
Write instead: read the Pane by name.
parables.<parable>.<pane>returns typed rows of the Pane’s type, layered for the reader.parables.<parable>.<pane>_by_actorreturns each actor’s own rows, withactor_kindandactor_id, for example every team’s.parables.<parable>.<pane>_datareturns the Pane’s own data.
filters-in-comments
Finds: a SQL comment that starts with-- Filter:.
Why: nothing checks a filter that is written in a comment, and a Policy
cannot read it.
Write instead: make each filter a typed row of a saved-data Pane, and
read it in the SQL by name. If a Policy reads these rows, let only people who
may edit Policies write them.
Data and Release rules
use-ref
Finds: a text field of a Pane’s data whose values are all ids of one Pane’s items. Why: a link that is text is not checked. A rename or a delete leaves it pointing at nothing. Write instead: make the field a typed reference, such asRef<Concept>.
A save checks that each linked item exists and that the saver can see it. The
one-time row conversion checks the links of each row it converts, and fails a
row whose link finds no item.
This rule reads Pane data only. A text link between Preference rows is not
found by it. Make such a field a Ref<T> when you rewrite the Pane, so a save
and the conversion check it.
data-grants
Finds: a Pane that holds data, and whose Policies name noview.data
grant.
Why: a missing grant admits nobody, so nobody can read the data. When the
Policies name no data grant at all, nobody can change the data either. A
Pane that was converted before Convert a Versioned Pane copied the grants
holds its data under such Policies.
Write instead: add the data grants to the Pane’s Policies. To keep the
access that the Pane’s values had before they became data, give each data
grant the same grant as preferences has for that action:
edit.policies. Prepare, Publish and the Warnings &
Errors view run this rule. A save does not, so a set of Changes can hold the
steps that add the grants. A bound Pane uses its parent’s Policies, so this
rule does not judge it.
shape-grow
Finds: a change to the type of a Pane that holds saved rows, compared with the line’s last Release, that removes, renames, retypes or narrows a field, or adds a required one. Why: saved rows were checked against the old type. A Release may only add optional fields or widen a union, so every saved row still fits. Write instead: keep the old field and add a new optional one. A union may take a new member, and a field may become optional or take| null.
saved-data-plot
Finds: a Pane that owns a Plot and has saved rows, and that does not declareoverrides: true:
- a Pane that declares
preload, so it shows the rows that people save, and also owns a Plot; - a Pane that was a saved-data Pane in the line’s last Release and that now owns a Plot.
PA-AU-040).
Write instead: move the Plot to a Pane of its own, and read that Pane’s
Plot from the saved-data Pane by name. If people save rows over the Plot’s
rows, declare overrides: true in the Pane’s Perception instead.
The Warnings & Errors view, a save (an archive import too), Prepare and
Publish run the first check. Only Prepare and Publish run the second, because
it compares with the last Release.