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.
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:
Declare Plots through 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.
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 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.
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 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 of BasePerception, each under the handle of a Plot the Pane owns:
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 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: 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 as perceptionSource, 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.

Reference

Generated Workspace API reference for this Piece: