> ## Documentation Index
> Fetch the complete documentation index at: https://docs.parable.work/llms.txt
> Use this file to discover all available pages before exploring further.

# Warnings & Errors

> What each Parable rule finds in a Page, Pane or Plot, why it is an error, and exactly what to write instead.

## Sources

Docs: [Panes](/ponder/parables/pieces/panes.md), [Perceptions](/ponder/parables/pieces/perceptions.md), [Preferences](/ponder/parables/pieces/preferences.md), [Plots](/ponder/parables/pieces/plots.md), and [Policies](/ponder/parables/pieces/policies.md).

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 as `parable(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.

The server refuses the same things at run time, so the checks and the server
never disagree: a value above 64 KB, raw JSON, a link to an item that does not
exist, a row whose id differs from its value's `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 `data` and change it with
  `propose.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.

```ts theme={null}
export interface Concept { readonly id: Generic.Text; readonly label: Generic.Text; readonly parent: Ref<Concept> | null }
export interface Perception extends BasePerception<null> {
  readonly data: readonly Concept[];
  readonly panes: {};
}
```

### 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:** a `preferences` 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 `| null` to the
  Pane's type, as in `BasePerception<Meeting | null>`. The field let the Pane
  draw with no value from its parent, and `| null` keeps that. The Plots that
  the Pane owns still return rows of its `type`.
* If `preferences` is the same type as the Pane's `type`, 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:

```ts theme={null}
export interface Meeting { readonly id: Generic.Text; readonly title: Generic.Text; readonly attendees: readonly Ref<Person>[] }
export interface Perception extends BasePerception<Meeting> {
  readonly panes: { attendees: Bound<Panes["meetingAttendees"], "attendees"> };
}
```

### bound-once

**Finds:** a Pane that two Panes bind, each with `Bound<>`.

**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 declares `readonly 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:

```ts theme={null}
edit: { policies: { participantsWith: { participantPermission: ["primary"] },
  fromSetting: { paneKey: "<Audiences Pane>", itemId: "editors" } } }
```

## Pane code rules

The Code check runs these rules on a Page's or Pane's code.

### values-api

**Finds:** a read of `preferences.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 the `preferences.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 with `preferences.save({ id, value, for })`, where `for` is `'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's `data` 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 reads `workspace.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_actor` returns each actor's own rows, with
  `actor_kind` and `actor_id`, for example every team's.
* `parables.<parable>.<pane>_data` returns 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 as `Ref<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 no `view.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:

```ts theme={null}
view: { preferences: G1, data: G1 }, create: { preferences: G2, data: G2 },
edit: { preferences: G3, data: G3 }, delete: { preferences: G4, data: G4 }
```

Changing Policies needs `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
declare `overrides: 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.

**Why:** a Pane that owns a Plot is a Plot Pane. It shows the Plot's rows and
takes no saved rows. Without this rule, a Plot that you add to a saved-data
Pane makes it a Plot Pane with no warning, and every save of a row on it is
refused (`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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.