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.
The four other fields are required. Extra root fields and index signatures are refused.
A Pane that shows Meetings and keeps a chosen window declares both:
BasePerception<Primitive, DeclaredPlots> or a structured
plots field. The resolved policies contract cannot be replaced or narrowed.
Scalar-lib types
Every field of a Pane’s primitive and its Preferences is a scalar-lib type, the types of the Provider Pool, imported from@psgen/scalar-lib:
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.
An older Refresh can carry a timestamp label on a column its SQL cast to
DATE. Reading it uses the column’s DATE type when the old label no longer
fits, while still requiring the saved output contract to match. Text where the
contract declares a Temporal.Date remains an error; a compatible older
Refresh does not need a new Publish or Refresh just to correct that label.
A value with several fields is a named interface of these types, declared in
the Perception. An array or a Record holds one scalar-lib type or one named
interface, and an array never holds arrays. There is no inline object type, no
unknown or other JSON, no tuple and no union of different types. 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.
PA-AU-051, naming the Pane, the field and what to write instead:
“The Perception’s primitive.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 (day, week, period, published_at,
starts_on), so a count of days or a duration in minutes stays a number. 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.
The old friendly names of parable/foundation (Text, Number, YesNo,
Empty, List, Day, Moment, Time, Duration and Map) are retired.
They stay in the foundation only because published Versions are immutable
history: an older Version, and a reader held on one, still compiles the
Perception it was published with. Write the scalar-lib names instead. Each
retired name 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 Moment in such a Perception, saved in Preferences, travels as its UTC
timestamp string and becomes a JavaScript Date again when the Pane reads it.
A failed save still rejects the Pane’s request and writes a browser-console
message naming the Pane and setting. Invalid nested input names the field that
could not be sent.
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 primitive, names the Provider with@provider, and its one Plot returns that Provider’s rows of the type. The
type’s Pane owns one Plot that combines its mapping Panes by name.
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 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 primitive is null holds no value but can still import the
shared controls and read its Plots, as in Declare a Plot.
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.
Declare a Plot
A Pane reads a Plot only when its Perception declares it. Pass the Plots as the second type argument ofBasePerception, each under the handle of a Plot the
Pane owns:
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.
PlotContractcomes fromparable/foundation. A bare row type, a function type, or an import from any other module is refused.- 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 asreadonly [since: string, limit: number]. - The second argument is the array of rows, with one field for every column the SQL selects, under the exact column name, and no others.
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 asperceptionSource, together with the compiled perceptionManifest, and
versions both with the rest of its definition. Saving checks the declaration
with the TypeScript compiler and derives value validation and reference
bindings. 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 generated ParableResolvedPolicies decisions. 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.
Private declarations are not required merely to render an allowed value;
representation metadata tells the renderer how to reconstruct native values.
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. A native Date is restored
as a JavaScript Date, while branded Temporal.DateTime remains a string.
Native Date wire values require an explicit timezone and an instant representable
in milliseconds. More precise values are refused instead of being truncated.
SQL output canonicalizes native Dates to UTC, including Dates inside objects
and arrays. Branded timestamp strings retain their supported fractional precision.
@psgen/scalar-lib supplies the same scalar parsers used by the platform.
Private representation metadata omits literal constraints. If several matching
union branches would require different native representations, loading refuses
with an ambiguous-representation error rather than supplying the wrong value.
Use the same representation across those branches or separate Pane types for
that case today. An explicit Date | string may remain a string.
web-api keeps compiled Perceptions for reuse. A compile is keyed by everything
it reads: the declaration, the tree’s Pane graph (every Perception and every
Page and Pane handle), and the Plots the Pane owns. An unchanged declaration in
an unchanged tree compiles once per process, and any change to those inputs
compiles it again, so an edit to one Perception or handle compiles every Page
and Pane of that tree again. A declaration that does not compile is not kept,
so it compiles again on every read. Reads that need the same compile while it
runs wait for it rather than compiling it again.
A process keeps two pools of up to 2,048 compiled Perceptions each and drops
the least recently used one when a pool needs room. The published-Plot catalog
has one pool to itself: it reads published Versions, which do not change, so a
draft’s saves cannot evict it. Every other read uses the other pool. On
2026-10-07 Dogfood’s live Parables needed 835 compiled Perceptions, and its
catalog 95 to 410. The pools count Perceptions, not bytes: Dogfood’s averaged
about 69 KB, most of it the compiled JSON validator, so both pools full hold
about 280 MB, and larger declarations cost more.
A read compiles each Page or Pane at most once. The published-Plot catalog that
Query Layer reads on every catalog build compiles each Pane once for all of its
Plots. When it compiles anything, web-api logs compiled Perceptions for the published-Plot catalog with the number of compiles, the Versions it left out
(skipped) and its pool’s size and evictions; a warm catalog compiles nothing.
Caching saves only repeated compiles: the first catalog read after a restart
compiles every published Pane.
A published Version can stop compiling without changing, when a platform
release narrows the types a Perception may use. On the Integration copy on
2026-10-13, three scratch Parables still declared
Versioned<PlotSql<'pipeline'>> after PlotSql became non-generic
(PARABLE-6161). The catalog leaves such a Version out and lists every other
Parable in the Workspace. For each Version it leaves out, web-api logs left a Version out of the published-Plot catalog: its Perceptions do not compile
with the Workspace, Parable, line, Version and Plot ids and the compiler’s
error. A failed compile is not kept, so the warning repeats on every catalog
read. Until the Parable publishes a Version that compiles, or is pocketed, its
Plots are missing from Query Layer and GET /api/parable/plots/{plotId}/published
answers 404 for them. Before PARABLE-6161, one such Version failed the whole
catalog read, so Query Layer stopped serving every Plot in the Workspace.
Each compile type-checks the declaration against the ES2023, DOM and
DOM.Iterable libraries. A process parses those libraries once, on its first
compile, and every later compile reuses them, which holds about 30 MB. On
2026-10-08 that brought the first package read of Engineering Delivery (88
compiles) in a new process from 3.5 s to 2.0 s.