Skip to main content

Sources

Docs: Panes, Plots, Policies, and Preferences. Web App: Select a Pane or Page and open Perception to compare its declaration with the read-only platform base.

Schema

A Perception is a TypeScript .d.ts declaration exporting Perception and conforming to BasePerception. It describes allowed values and references. It does not select another actor’s saved data. A Perception is part of its Page or Pane: it is saved and versioned with the rest of that Page or Pane, and it is not a Piece of its own.
type (through BasePerception<T>), panes, plots and policies are required. Extra root fields and index signatures are refused. preferences, primitive, Live<> and Versioned<> are from before Pane types; each is a Warning & Error with what to write instead. A Pane has one type, so a Pane that shows Meetings never keeps a chosen window of its own. The window is a row of a saved-data Pane of its own, which the Meetings Pane declares and reads with panes.window.preferences.get(id):
Declare Plots through BasePerception<Type, DeclaredPlots> or a structured plots field. The resolved policies contract cannot be replaced or narrowed.

Kinds of Pane

The type says which kind of Pane a Pane is: 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.

Bound Panes

A bound Pane shows one field of its parent’s row. The parent declares it in panes with Bound, naming the field:
The field must be in the parent’s type, with the bound Pane’s type, and a field feeds one Pane; anything else is bound-field. The bound Pane has no SQL. It takes the value from its parent’s row as props.value, so it needs no read of its own, and it resolves once for each Page. It uses its parent’s Policies and cannot narrow them yet, so its own Policies stay at the defaults (bound-policy).

Pane data

A Pane can own typed data that the Parable owns, such as an ontology, the descriptions of Provider tables, or the rules of a mapping. Declare it as a list of a named interface with an id field:
Pane data is stored in the Pane, so it travels with each Release, each Delivery and each archive, and it holds no person or Workspace ids. Each item is at most 64 KB. Pane code reads it in id order: data.get(id) reads one item, data.list({ limit, after }) reads one page of limit items after the item after, and data.list() and the data.useItems() hook read every item. The gets of one moment go to the server in one request, and each answer is kept for the Version. A Pane it declares is read as panes.<name>.data.list(); and SQL reads parables.<parable>.<pane>_data. Only a proposal changes Pane data. The Pane’s code calls data.propose({ upsert, remove }), the reader confirms the change in a dialog that the host draws, and the change goes into the reader’s set of Changes. It reaches readers when the set publishes. The code of a Pane also proposes to the data of each Pane that its Perception declares in panes, as panes.<name>.data.propose({ upsert, remove }), typed by that Pane’s items. So a parent edits the data of the Panes it shows. A Pane that it does not declare is refused with PA-AU-071. propose.change(pane, { data }) is the same call by handle or key. The Policy target data of the Pane whose data changes decides who reads the data (view.data) and who may propose a new, changed or removed item (create.data, edit.data, delete.data). A change that also changes the Pane’s Policies needs edit.policies under the Policies the Pane had. Its data is allowed when the Policies before the change or the Policies that the change sets allow it. So one Release can give a Pane its data grants and its first data, or remove both, and a copy takes that Release in one Pull. The proposal also needs the same right for every author of the calling Pane’s code. Edits to different items apply on the newest data, and two edits to the same item get their own refusal, PA-AU-059. A field that points at another item is a typed reference, Ref<T>, imported from parable/foundation. On the wire it is the item’s id. TypeScript refuses a link of the wrong type, a save checks that each linked item exists and that the saver can see it, and the row conversion checks the links of each row it converts. Ref<T> names a named interface with an id field, declared in this Perception or imported from another Pane’s Perception. A link that is text, such as parent: "growth", is not checked; Prepare names such a field in Pane data (use-ref). A Pane that only serves other Panes, such as shared state, a kit of helpers or a preview, says so with @plumbing in the doc comment on its Perception. The tag is stored in the Pane’s manifest (plumbing: true), and Plotted Path never offers such a Pane as a part to open (Writing Panes that explain well).
@plottedPath publish or @plottedPath report in the same comment asks the platform to write the Pane’s Plotted Path ahead of its first reader, as each Version is published or as each Report run completes. It is stored as plottedPath in the manifest (Write it ahead).

Scalar-lib types

Every field of a Pane’s type and its data is a scalar-lib type, the types of the Provider Pool, imported from @psgen/scalar-lib, a Ref<T>, or a named interface of these:
A plain string, number or boolean is not a scalar-lib type, and neither is a JavaScript Date: an instant is a Temporal.DateTime, an ISO 8601 string. A literal union such as "7d" | "30d" is text with fixed values. A type the platform declares, such as ParticipantGrant or ProviderRef from parable/foundation, is accepted as it is written. A column that holds a type’s values in another width or encoding is cast to it where the Plot returns: every integer width, REAL and DECIMAL to a number field, DATE64 to a Temporal.Date, a time of day to a Temporal.Time, a TIMESTAMP of any unit or zone to a Temporal.DateTime in UTC, and a dictionary-encoded string to text. Only a value of another kind, such as text in a number field, is PA-AU-047, and a day or an instant held as text needs a CAST in the SQL. A value with several fields is a named interface of these types, declared in the Perception. An array holds one scalar-lib type or one named interface, and an array never holds arrays. There is no inline object type, no tuple and no 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. Time is always a Temporal.Date, Temporal.DateTime or Temporal.Time, never Generic.Text or a number: published_at is a Temporal.DateTime and day a Temporal.Date. A span is a named interface with a field for each end, such as started_at and ended_at, or first_day and last_day.
A save, Prepare or Publish that adds a field that is not a scalar-lib type is refused with PA-AU-051, naming the Pane, the field and what to write instead: “The Perception’s type.repository is a plain string. Give it a scalar-lib type: Generic.Text, or the one it is, such as Identity.UUID or Contact.Email.” A time is judged by the field’s name. A text field such as day, week, published_at or starts_on holds a time. A number field holds a time only when its name ends in at, date, timestamp or epoch, such as createdAt or sent_timestamp. A count of days, a duration in minutes, an amount per day (peak_day) and an amount to date (completed_quarter_to_date) stay numbers. A label such as period with values "current" | "previous" is not a time. A refused save stores nothing, so the Pane’s Warnings & Errors cannot show the refused field. A field the published Version already holds is history: it shows as info in the Pane’s Warnings & Errors, and it never stops a read, a draw, or another change from saving and publishing. Do not import Text, Number, YesNo, Empty, List, Day, Moment, Time, Duration or Map from parable/foundation. Write the scalar-lib names instead. Each of these names a Perception imports is a PA-AU-051 finding that says what to write: “The Perception’s import of Empty from parable/foundation is a retired friendly name. Write null, as in X | null.” A failed Preferences save rejects the Pane’s request and shows the reason in the Parable’s window, next to the control that asked. Invalid nested input names the field that could not be sent.

Providers a Parable reads

A Parable names the Providers it reads once, on its Home Page’s Perception, as named groups. Every Parable uses the same structure, and a Provider can be in several groups:
Write every value in quotes: the compiler reads the declaration from its types. A save refuses a group whose fields it cannot read, a name that is not a catalog name, a Provider listed twice in one group, and providers on any Page or Pane but the Home Page. Prepare refuses a Provider the catalog does not have, naming it and the groups that name it; a Provider the published Version already names is not judged again.

What a Platform Parable gives

A Platform Parable can give its own Pieces to named places in the product, such as a tab beside every Parable. Its Home Page’s Perception declares each gift once, as a named entry in contributes. Each entry names its place and the Pieces that it gives, by handle:
Write every value in quotes: the compiler reads the declaration from its types. A save refuses each of these, with a sentence that names the entry:
  • a place that is not in the table, or a field that its place does not take;
  • a field that its place needs and that the entry leaves out;
  • a Page, Plot, Pane or Report that the Parable does not have, by its handle;
  • a second ponder.tab or publish.checks entry;
  • contributes on any Page or Pane but the Home Page.
A save or an archive import that declares contributes on a Parable that is not a Platform Parable is refused (PA-AU-085). A save that deletes or renames a Piece that the declaration names is refused too, so a Release never names a Piece that it does not have. The declaration is stored with each Release, and the product reads it from the Release that each Deployment runs, never from a Patch. A Platform Parable is never forked, so every copy runs the Releases that Parable staff publish (Forking). The Parable’s tab shows the ponder.tab Page, and that Page knows the Parable on screen beside it (The subject). A Parable that declares no ponder.tab keeps its Home Page as its tab. Later releases wait for the publish.checks Plot at Publish and run the react Report.

Named types and their Providers

A named type is vendor neutral: a Document is a Document whether it comes from Google Docs or Confluence. No type is one Provider’s version of another. A Provider feeds a type through a mapping Pane: its Perception imports the type from its own Pane and uses it as its type, names the Provider with @provider, and its undeclared Plot returns that Provider’s rows of the type. The type’s Pane owns an undeclared Plot that combines its mapping Panes by name.
Every such type points back to the records it came from with provider_refs, a list of ProviderRef from parable/foundation: provider, the Provider’s catalog name as in Plot SQL, and provider_id, the record’s ID in that Provider, as text. A record from one Provider has one entry; a record merged from several, such as one Meeting from Google Calendar and Zoom, has one per source record. A type does not name Providers in its own fields, so it has no zoom_id. 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 can then resolve the reference by joining on both: the kind picks the table and the ID the row. A Pane’s _by_actor rows do the same with actor_kind and actor_id. A link to items of one type is a Ref<T>. A Plot reference is not a Pane reference. CSS is attached by the renderer and is not a Palette configuration in the Perception declaration. A Page’s Perception is BasePerception<null>, and every Page is composition only: its code may import only the Panes its Perception declares and the runtime layout modules. The compiler refuses any other import, such as a chart or control library, and names the import it refused. The rule is the Page’s alone: a Pane whose type is null holds no value and owns no Plot, but can still import the shared controls and read the Plots of the Panes it declares. Export interfaces and type aliases from a Perception to reuse them in another Pane’s React source. Use import type from the host declaration module parable/panes/<Pane ID>/perception, using its stable Pane identity. Type-only imports do not mount the target Pane or grant access to its settings or runtime API. Runtime Pane imports still require a declared panes reference.

Read a named type at runtime

TypeScript types disappear when a Pane’s code compiles, so a type-only import types the code but cannot show what a named type is. To draw one on screen, such as a Parable’s ontology page listing each type’s fields, read it at runtime. The compiler emits it from the declarations themselves, so it cannot drift from the types the Plots are checked against.
  • A Pane reads the named type it shows, its type’s, as namedType from its module: import { namedType } from '@parable/<parable>/<pane>'.
  • It reads the one a Pane it declares shows as panes.<name>.namedType, from the panes prop or the module’s panes.
  • It follows a key to any other named type through the module’s namedTypes: the types a named type extends, the named type a field holds, and the type that declares a field.
namedTypes holds each named type once, by key, so a parent many types extend is not repeated for each. A key is the declaring Pane’s key, or the platform module, then # and the name: <Pane key>#Meeting, parable/foundation#ProviderRef. namedType is null when the type is null or is not one named type: an inline object, a scalar, a list, or a union of several. It is also null when the reader may not view the Perception that declares the type: a named type is part of its Perception, so a reader without view permission on that Perception receives neither the type nor the fields it gives another type. Otherwise a named type is:
A named type describes the declarations and validates nothing. Editing a doc comment changes what namedType shows and never makes a stored Refresh unreadable.

Declare a Plot

A Plot Pane’s type is its kind: a named interface, in scalar-lib types, of the rows its undeclared Plot returns. The one Plot that the Pane owns and does not declare returns rows of that type, picked to the columns its SQL returns, with no parameters. A Pane may own several Plots, and each other Plot is declared. A Plot that takes parameters, or returns rows of another shape, is declared: pass it as the second type argument of BasePerception, under the handle of the Plot the Pane owns, with an array of the kind as its rows:
With this saved, the Pane’s React code receives plots.openIssuesByPriority() and the Plot editor’s Preview runs. Without it the Pane has no such function and Preview reports that the Plot’s contract is unavailable.
  • PlotContract comes from parable/foundation. A bare row type, a function type, or an import from any other module is refused.
  • The type takes null, OpenIssues | null, so the Pane draws on a Page, which hands it no value. T | null is not a null type.
  • A Plot that the Perception does not declare returns rows of the Pane’s type, so a Pane has at most one such Plot, and then its type is not null. A save, Prepare or Publish that adds a second undeclared Plot, or an undeclared Plot on BasePerception<null, { ... }>, is refused with PA-AU-049, which names the Pane and those Plots. A Pane whose Plots are all declared may have a null type.
  • The name must match the handle of a Plot this Pane owns when the declaration is first saved. Save the Plot before the Perception or in the same save; a name with no owned Plot is refused as “does not bind to an owned Plot”. Renaming the Plot later keeps the binding. A Page cannot declare Plots.
  • The first argument is the tuple of positional SQL parameters: readonly [] for none, or named members such as readonly [since: Generic.Text, limit: Generic.Int64].
  • The second argument is the array of rows of the kind, which has one field for every column the SQL selects, under the exact column name, and no others.
The kind is the Pane’s type, so each field is a scalar-lib type, and a plain string, number or boolean is PA-AU-051: Cast a decimal, or an identifier wider than a safe integer, to DOUBLE or VARCHAR in the SQL so the planned type stays fixed. Plots lists the row shapes SQL output supports. When a save is refused, the response names the Pane and the line the compiler rejected. Ponder receives the same lines for the declarations it submitted and corrects them in its next step.

Storage

The declaration is authored source. Its Page or Pane stores it together with its compiled manifest, and versions both with the rest of its definition. Saving checks the declaration with the TypeScript compiler and derives value validation, typed links and reference bindings. The manifest records a hash of the type; each saved row records the hash it was checked against, and a Release may only grow the type of a Pane that holds rows: add optional fields or widen a union (shape-grow). The manifest cannot be edited on its own to bypass the declaration: a save may omit it or send back the stored value, and the server compiles it from the source. The editor presents readable handles. Stored references use stable identities, so renaming a Pane does not redirect existing references to a new Pane that later takes the old name.

Security

Types describe what a value can contain; Policies decide which actions the requester may perform. BasePerception.policies and React’s props.policies contain the resolved Policy decisions (ResolvedPolicies in parable/foundation). The Policies themselves are a field of the Page or Pane, shown in its Policies tab. The host always supplies the required props. A component may ignore props it does not use, but that does not remove the Pane’s schema or access checks. A reader does not need view permission on the Perception to render an allowed value.

Stability

Invalid references, missing base fields, and attempts to replace the fixed contract are refused. Repair the declaration before saving; a preview error does not itself grant or remove source-inspection permission. Temporal.Date is a validated calendar-day string. Temporal.DateTime is an ISO 8601 string. Branded timestamp strings retain their supported fractional precision. @psgen/scalar-lib supplies the same scalar parsers used by the platform. A published Version can stop compiling without changing, when a platform release narrows the types a Perception may use. Its published Plots then cannot be read until the Parable publishes a Version that compiles, or is pocketed. The published Plots of every other Parable in the Workspace stay readable. Each compile type-checks the declaration against the ES2023, DOM and DOM.Iterable libraries.