> ## 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.

# Perceptions

> Declare a Pane's one type, its own data, the Panes it uses and the fields that feed them, in scalar-lib types with typed links.

# Perceptions

A Perception is the type declaration of a Page or Pane. It states the Pane's one type, which other Panes it uses, which Plots it owns, and the Pane's own data when it has any. It is the contract between the values people save, the Pane's code, and every Page that places the Pane, and it is what lets Parable refuse bad data before it is saved. A Perception is part of its Page or Pane, saved and versioned with it; it is not a Piece of its own. It describes what a value may contain. It does not hold saved values, which are Preferences, and it does not decide who may act, which is Policies.

A Perception is a TypeScript declaration that exports an interface named Perception extending BasePerception, with the Pane's type in angle brackets: `BasePerception<Category>` declares `type: Category`. The type is what the Pane shows and what people save on it, so a Pane has one type and never a second one for saved values. A type whose items people save is a named interface with an `id` field, Generic.Text or Identity.UUID, that names each item; Preferences are rows of the type, one for each item and each actor (see the Preferences Power). A Pane draws with no value from its parent when its type takes null (`T | null`), when it is a saved-data Pane, which shows its own rows, or when it owns Pane data; any other Pane shows "Choose a Preference or provide an input." until its parent hands it a value. With no value, its code gets null as `props.primitive`, and the type of `props.primitive` takes null too, so read it with a check for null, such as `props.primitive?.name`. A Page's type is null, and a Page is composition only: its code may import only the Panes it declares and the runtime layout modules, and the compiler refuses anything else. The other parts are the Panes it uses (panes), the Plots it declares (the second type argument), and the resolved Policy decisions (policies), which the host supplies and which cannot be replaced or narrowed. Extra root fields are refused.

There are four kinds of Pane, and the Perception says which one a Pane is. A **Plot Pane**, such as Meetings, owns one Plot, and its type is the kind of the rows the Plot returns. A **bound Pane**, such as a meeting's attendees, has no SQL: one field of its parent's row feeds it as props.value, and it uses its parent's Policies. A **saved-data Pane**, such as Categories, a date range, an audience or a switch, has no SQL: its rows are the values people save. A **layout Pane**, such as a section, has a null type and holds no data. A new kind of thing is a new Pane, and many things of one kind are rows. A field becomes a bound Pane of its own only when it needs its own controls; other fields use the design system's default control for their type. Any Pane can also own data of its own.

Optional fields say more about the Pane. `readonly data: readonly Concept[]` declares the Pane's own data: a list of a named interface with an `id` field, such as an ontology or the descriptions of Provider tables, that the Parable owns. The data travels with each Release, holds no person or Workspace ids, and changes only through a proposal, item by item, under the Pane's data Policy; Pane code reads it in id order 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 SQL reads `parables.<parable>.<pane>_data`. `readonly preload: "all"` carries every saved item with the Pane's load, for a bounded set such as categories; `"page"` carries one page, for rows that grow such as judgments; without it, rows load when the code reads them. `readonly overrides: true` lets a Plot Pane take per-actor rows by id that change only the reader's own view of the Plot's rows, never a Refresh, a Pipeline or a Report. `readonly proposes: readonly ["plot"]` lists the Piece kinds that the Pane's code may propose, such as a Pipeline builder that proposes Plots; a Pane may always propose changes to its own data.

```ts theme={null}
import type { BasePerception, Ref } from "parable/foundation";
import type { Generic } from "@psgen/scalar-lib";
/** One concept of the ontology. */
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 proposes: readonly ["plot"];
  readonly panes: {};
}
```

A bound Pane is declared in its parent's panes with Bound, naming the field that feeds it: `attendees: Bound<Panes["meetingAttendees"], "attendees">`. The field must be in the parent's type, with the bound Pane's type, and a field feeds one Pane. The bound Pane takes the value from its parent's row, so it needs no read of its own, and it cannot narrow its parent's Policies yet: keep its Policies at the defaults.

```ts theme={null}
import type { BasePerception, Bound, Ref } from "parable/foundation";
import type { Generic, Temporal } from "@psgen/scalar-lib";
import type { Panes } from "parable/panes";
import type { Person } from "parable/panes/<Pane ID>/perception";
export interface Meeting {
  readonly id: Generic.Text;
  readonly title: Generic.Text;
  readonly starts: Temporal.DateTime;
  readonly attendees: readonly Ref<Person>[];
}
export interface Perception extends BasePerception<Meeting> {
  readonly panes: { attendees: Bound<Panes["meetingAttendees"], "attendees"> };
}
```

Write every field as a scalar-lib type, the types of the Provider Pool, imported with import type `{ Generic, Temporal }` from "@psgen/scalar-lib" (add Identity, Contact or another namespace when a field needs it): Generic.Text for text, Generic.Float64 for a number, Generic.Int64 for a whole number, Generic.Boolean for yes or no, Temporal.Date for a calendar day such as "2026-10-09", Temporal.DateTime for an instant (an ISO 8601 string), Temporal.Time for a time of day such as "09:30:00", Temporal.Duration for a length such as "90s", and the scalar-lib type that says what a value is where one fits, such as Identity.UUID or Contact.Email. A field that may have no value is T | null, a list is readonly T\[], and an optional field is written with a question mark. A link to another item is a typed reference, `Ref<Category>`, imported from parable/foundation: on the wire it is the item's id, TypeScript refuses a link to a different type, and a save and Prepare check that the item exists. A Ref names a named interface with an `id` field, declared in this Perception or imported from another Pane's Perception. A value with several fields is a named interface of these, declared in the Perception: never an inline object type, a tuple, or a union of different types. Raw JSON is not a type: Generic.JSON, unknown, any and `Readonly<Record<string, T>>` are refused (no-raw-json); items keyed by text are rows of a Pane, or a list of a named interface with an `id` field. A plain string, number or boolean is not a scalar-lib type, and neither is a JavaScript Date. Never import Text, Number, YesNo, Empty, List, Day, Moment, Time, Duration or Map from parable/foundation: write Generic.Text, Generic.Float64, Generic.Boolean, null, readonly T\[], Temporal.Date, Temporal.DateTime, Temporal.Time or Temporal.Duration instead, and each one imported is PA-AU-051. Time is always a Temporal.Date, a Temporal.DateTime or a Temporal.Time, never Generic.Text or a number. A span is a named interface with a field for each end, such as started\_at and ended\_at. A literal union such as "7d" | "30d" is text with fixed values. The platform casts a column holding a type's values in another width or encoding where the Plot returns: any integer, REAL or DECIMAL to a number field, DATE64 to a Temporal.Date, a TIMESTAMP of any unit or zone to a Temporal.DateTime in UTC. Text where a number is declared is still PA-AU-047, and a day or time held as text needs a CAST to DATE or TIMESTAMP.

Older Perceptions used patterns that are now Warnings & Errors, each refused at Prepare and Publish, with a message that says what to write instead: `Versioned<T>` and `Versioned<PlotSql>` (versioned-removed: declare the value as the Pane's data and change it with propose.change, make it rows of a saved-data Pane, or propose a change to the Plot), `Live<T>` (live-wrapper: remove the wrapper), a `preferences` field (one-type: remove it, or move a second type into its own Pane or into data), also `readonly preferences: null` (one-type: remove it and write the type as `T | null`, so the Pane still draws with no value from its parent), raw JSON (no-raw-json), a field bound wrong or twice (bound-field), a bound Pane with Policies of its own (bound-policy), and `readonly primitive` (primitive-renamed: the field is `type`, which `BasePerception<T>` already declares). A save is refused only when it adds one of these, so you can remove them one step at a time.

The Home Page's Perception, and no other, names the Providers the Parable reads as named groups in an optional providers field: readonly providers: `{ readonly code: { readonly title: "Code"; readonly description: "Pull requests, reviews and commits."; readonly role: "populate"; readonly choice: "atLeastOne"; readonly providers: readonly ["github", "gitlab"] } }`. role is "populate" (Required to Populate) or "improve" (Improves the Parable); choice is "atLeastOne", "atMostOne" or "exactlyOne", and a "populate" group is never "atMostOne". Each Provider is its catalog name as in Plot SQL, a Provider can be in several groups, and every value is written in quotes. Put in a group every Provider that the Parable's Plots read. Prepare refuses a Provider that is not in the catalog, but it does not find a Provider that no group names, and a Pane whose Plots read only such Providers shows no Improve this… and no chip.

A named type is vendor neutral: never one Provider's version of another type, and never a field named for a Provider such as zoom\_id. A Provider feeds a type through a mapping Pane whose Perception imports the type from its own Pane (import type `{ Deploy }` from `"parable/panes/<Pane ID>/perception"`), uses it as its type, and names the Provider with @provider in its doc comment; its one Plot returns that Provider's rows of the type, and the type's Pane owns one Plot that combines its mapping Panes by name. Every such type carries provider\_refs: readonly ProviderRef\[], with ProviderRef imported from parable/foundation: provider is the Provider's catalog name as in Plot SQL, provider\_id the record's ID in that Provider as text. A record from one Provider has one entry; a record merged from several has one per source record. Build them in SQL with \[named\_struct('provider', 'github', 'provider\_id', CAST(id AS VARCHAR))] and concatenate them where records merge. A reference to a thing that can be one of several types is two fields, its ID and its kind, such as subject\_id and subject\_kind, with the kind a literal union of the type names; any Plot resolves it by joining on both. A link to one type is a Ref.

A Plot Pane's type is its kind: a named interface, in scalar-lib types, of the rows its one Plot returns. Each Plot that the Pane owns and does not declare returns rows of that type, picked to the columns its SQL returns, so the Pane needs no per-Plot type. A Plot that takes input, or returns rows of another shape, is declared as the second type argument of BasePerception, keyed by the Plot's handle, with a PlotContract of its input and its output:

```ts theme={null}
import type { BasePerception, PlotContract } from "parable/foundation";
import type { Generic } from "@psgen/scalar-lib";
export interface OpenIssues { readonly priority: Generic.Text | null; readonly open_issues: Generic.Int64 }
export interface Perception extends BasePerception<OpenIssues, {
  openIssuesByPriority: PlotContract<readonly [since: Generic.Text], readonly OpenIssues[]>;
}> {
  readonly panes: {};
}
```

Use exactly this form. PlotContract comes from "parable/foundation"; no parable/plots module exists. A bare row type, a function type, or a hand-written manifest is refused. A Pane that owns a Plot never keeps a null type: `BasePerception<null, { ... }>` with a Plot is refused with PA-AU-049, and so is a second Plot on the Pane. The Plot must already belong to the Pane when the Perception is saved, so save the Plot first or send both in one call; a name that matches no owned Plot is refused as "does not bind to an owned Plot". A Page cannot declare Plots. Once the declaration saves, the Pane's code receives plots.openIssuesByPriority(), which returns status, rows, error, and rerun, and the Plot editor's Preview can run. The input is the tuple of positional SQL parameters: readonly \[] for none, or named members such as readonly \[since: Generic.Text, limit: Generic.Int64]. The output is a readonly array of the kind, which has one field for every column the SQL selects, under the exact column name, and no others; give each column a lower-case AS alias and declare that name. Each field is a scalar-lib type: text is Generic.Text, a whole number such as a COUNT is Generic.Int64, any other number, such as an AVG, is Generic.Float64, yes or no is Generic.Boolean, a calendar day is Temporal.Date, and an instant is Temporal.DateTime. Add | null to every column that can be NULL, which includes most columns read straight from a table. Cast decimals and identifiers wider than a safe integer in the SQL, to DOUBLE or VARCHAR, so the planned type stays fixed.

Saving runs the TypeScript compiler and derives value validation, typed links and reference bindings from the declaration. A Perception's source is the only thing an author writes. The server compiles the manifest from it, so the manifest cannot be edited on its own. When the compiler refuses a declaration, the refusal names the Pane and the line; correct that line instead of trying another shape. The editor shows readable handles, while stored references use permanent identifiers, so renaming a Pane keeps references pointed at the same Pane. Another Pane's code can import exported types from a Perception as types only; that does not mount the Pane or grant access to its data. Types disappear when Pane code compiles, so to show what a named type is, read it at runtime instead of copying the types by hand: import `{ namedType }` from `'@parable/<parable>/<pane>'` is the named type the Pane shows, `panes.<name>.namedType` is the one a Pane it declares shows, and the module's namedTypes holds every named type the read describes, by key. A named type has key, name, paneKey (the Pane whose Perception declares it), its doc comment as written in description, extends (the keys of the interfaces it extends), and fields. Each field has name, type (its scalar-lib type, such as Generic.Text, or the name of the named type it holds), namedType (that type's key), ref (the key of the type a Ref names), list, nullable, its doc comment, and declaredIn. namedType is null when the type is null or is not one named type, and when the reader may not view the Perception that declares it.

A Release may only grow the type of a Pane that holds saved rows: add optional fields or widen a union. Each row records the type it was checked against, and Publish refuses a change that removes, renames, retypes or narrows a field, or adds a required one (shape-grow): keep the old field and add a new optional one. Viewing a Perception needs view permission on it, and changing it needs edit permission; the change follows the Parable's Collaboration Mode. Types grant nothing.

1. Agree on meaning first. Ask Ponder what the Pane is for, which kind of Pane it is, and which fields a person sees, edits, or chooses. Settle this with the Panes and Preferences decisions before writing types.
2. Ponder writes the declaration with one concrete type with an `id` field where people save items, Ref links between items, the Pane's data when the Parable owns values, each referenced Pane by its actual handle, and each Plot that takes input as a PlotContract.
3. Ponder compares the declaration with the read-only platform base shown in the editor and with the props the Pane's React code receives: required fields, empty values, list items, links, and dates.
4. Before changing the type of a Pane that already has saved rows, Ponder checks those rows and the Panes that depend on it, and only grows the type. Never loosen a meaningful type to make invalid data disappear; repair the data or the design instead.
5. Save, read the Pane's Warnings & Errors, then prove the contract with one valid value accepted and one invalid value refused.

A good Perception reads like a plain description of the data: every field has a clear meaning, links are Refs, nothing is raw JSON, and references name real Panes. Check the result by saving it with no Warnings & Errors, saving a valid value, seeing an invalid value refused, and confirming a referenced Pane renders and each Plot returns rows in the Pane. A declaration that compiles does not prove that every control in the Pane was designed as a Pane.

Related Powers: Panes, Pages, Preferences, Plots, Policies. Find each by its title; do not guess stored names.


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