Design and preview a Parable
Sources
Docs: Pages, Panes, Powers Web App: Open a Composed Page or Pane in Pieces, then use its source and preview surfaces. Ask Ponder to compose it while the Piece is selected.Story
A Parable can use the same approved controls as the app. The shared library contains Parable’s adapted components, typography, colors, square corners, and icon treatments.Structure
Import controls through the Parable-facing entry:react, react/jsx-runtime,
react/jsx-dev-runtime, react-dom, react-dom/client, motion,
motion/react, kysely, d3, three, @psgen/scalar-lib, and
parable/panes/placeholders. Package subpaths outside that list, such as
three/addons/..., unrelated libraries, direct Plot modules, foreign-Parable
imports, and dynamic imports are refused. The source editor marks an import
outside that list. It does not mark a dynamic import() of a listed module, or
a library that a Page may not import (see below); the server refuses both when
the Piece is saved. Ordinary JSX and CSS remain available.
@psgen/scalar-lib parses Parable scalars and mints new identities with
newIdentityUUID(); see Preferences.
A Page is composition only: it may import only its declared Panes and the
runtime layout modules, so shared controls and chart libraries belong in the
Panes it places. A Pane whose primitive is null holds no value but may
still import them.
Sound
A Pane plays Parable’s sounds by naming what happened, never a file:activate, select, toggleOn, toggleOff, disabled,
transitionUp, transitionDown, notification, caution, and
celebration. The preview frame carries no audio: it sends the event to the
app, which plays it under the person’s sound setting and the app’s throttle.
With sound turned off, nothing plays. Play a sound after a committed click or
key press, never on hover. A Page is composition only, so its sounds belong in
the Panes it places.
Outlines
OutlineTree and OutlineNode draw a tree where every level is the same row:
an icon, a name, a description, a count and a chevron. Opening a row shows its
detail under it, and a line joins a row to the rows below it. Team Time Spend’s
Perceptions page draws its named types with them, its Categories page its
category sets, and a mapping Pane draws how one Provider’s rows map into a type.
extra adds to the text, after sits before the count, and rowData sets the
row’s data- attributes. The rows’ look comes with the components, so a Pane
needs no CSS for them.
A tree its reader edits opts into controls; a tree that passes none of them
draws the same markup as before:
actionson a node shows its controls, such as add and remove, over the row’s end on hover or keyboard focus, beside the row’s button rather than inside it.onMoveon the tree gives each node with anida drag grabber at its row’s start. Dropping a row on another callsonMove(id, targetId, position):beforeorafterit on the top or bottom quarter,insideit in the middle. On the grabber, Alt with Arrow Up or Down moves the node among its siblings, Alt with Arrow Right moves it into the sibling above, and Alt with Arrow Left moves it out after its parent.canDrop(sourceId, position)on a node refuses a landing, such as a node into its own subtree. The caller changes its data; the tree redraws from it.
Logos
A Pane shows a company’s real logo by its domain:size is the edge in
pixels, 24 by default. theme is auto by default: it follows Ivory and
Obsidian and draws the variant made for a dark background in Obsidian;
light or dark fixes it. Some companies’ marks are a single white or black
shape on a clear square in both variants, such as Dropbox’s and Okta’s white
ones and Anaplan’s black one. When such a mark would vanish on the page, the
logo draws a tile of the opposite ink behind it, in either theme; a Pane does
nothing for it.
The preview frame has no network. It asks the app for every logo a render
draws in one request; the app fetches each from logo.dev once, keeps it, and
hands it to the frame as image data. A Pane never sees a logo URL or the
logo.dev key and needs neither. useBrandLogo returns that image data, null
when there is no logo, or undefined while it loads. A Page is composition
only, so its logos belong in the Panes it places.
Redacted values
A Pane shows where a person’s data was without showing it:PERSON, EMAIL, PHONE, ADDRESS, DATE_OF_BIRTH,
US_SSN, PAYMENT_CARD and IP_ADDRESS. A chip shows the category’s icon,
its name in Title Case (Person, Email, Phone, Address, Date of Birth, US SSN,
Payment Card, IP Address) and a fixed mask, and screen readers hear “Email,
redacted”. PiiRedaction takes no value, so a chip can never show one.
A Plot writes a token in place of each value: the category between ⟦ and
⟧ (U+27E6 and U+27E7), such as ⟦EMAIL⟧. RedactedText draws every
token in its text as a chip. piiToken('EMAIL') writes a token,
parsePiiText splits text into plain runs and tokens, and piiPlainText
writes each token as “Email ••••••” for a place a chip cannot go, such as a
title or a chart label.
Redact in the Plot. Whatever a Plot returns reaches the reader’s browser, so
redacting in the Plot’s SQL is the only way to keep a value off the page.
These regexp_replace calls replace each kind the patterns can find, applied
from the innermost out; pass 'g' so every match is replaced:
+. SQL cannot check a card number’s Luhn digit or an IPv6 address’s
structure, so the card pattern also replaces other 13 to 19 digit numbers and
the IPv6 pattern also replaces hex-only names in code such as db::add. A
pattern cannot find a name or a street address: select '⟦PERSON⟧' or
'⟦ADDRESS⟧' in place of a column that holds one.
redactPii(text), and redact on RedactedText, run the same patterns in
the browser for a Pane that only has raw text, and also check Luhn and IPv6
structure. The value then reaches the reader’s browser but never the page,
because it is replaced before anything renders. Use it when the Plot cannot
redact, not instead of the Plot.
Artifact bodies
A Pane shows the body of an artifact, such as a ticket’s description, an email or a calendar event’s notes, with its formatting:format says how to read text:
markdown: paragraphs, headings, ordered, unordered and task lists, block quotes, fenced and inline code, bold, italic, strikethrough, links and images. A single newline is a line break, as in a GitHub issue. HTML inside Markdown shows as written text, and so do tables.html: read as structured text. Paragraphs, line breaks, headings, lists, block quotes, preformatted text, bold, italic and links keep their structure; tables and other containers read as paragraphs. Scripts, styles, the head, embedded media, form controls and hidden text, such as an email’s preview text, are dropped.text, the default: every line break, blank line and space stays.
⟦EMAIL⟧ draws as a chip in any format, in code too.
redact replaces the values redactPii finds, both in the text as written
and after HTML’s character references are decoded, so jane@acme.com is
found too. A Plot that redacts in its SQL still keeps the value off the page;
redact keeps it out of what the page draws.
Dates and times
A Pane shows every time in the reader’s time zone and clock, with the same functions the app uses:readerClock() returns both. A reader whose locale uses a 24-hour
clock sees one, such as “17:31 BST”; everyone else sees a 12-hour clock with
am or pm. The short zone name always follows the time, and the year joins the
date when it is not this year. Each function takes a Date, an ISO string, or
milliseconds, and an option object whose timeZone and locale replace the
reader’s. A value that cannot be read formats as an empty string.
A Plot returns instants and the Pane formats them. Plot SQL cannot take the
reader’s time zone as a parameter, because AT TIME ZONE accepts only a quoted
zone name. The helpers read the current time themselves, so a Pane never calls
Date.now() while it renders.
The same Pane, read in New York (en-US) and in London (en-GB):
Categories
A Pane that shows a set of categories, and how each one is decided, uses three parts:CategorySetdraws each category as a card (CategoryCard) with its color, its name, what it amounts to (value, such as hours), the first statement of its decision, and a line for counts (meta). A parent’s card holds its children’s cards, and its line counts them. While a pointer or the keyboard is on a card, a badge names how the category is decided: Decision by Ponder or Decision by Rules.DecisionMethodBadgedraws that badge on its own.DecisionStatementsedits a decision as a list of statements. Enter starts the next statement, and pasting several lines adds one statement per line. It reads and writes one string, the text Ponder reads: one statement as written, or several as-lines. Text with any line that is not a-,*or•bullet is one statement, so a decision written as a paragraph keeps its exact text.splitStatements,joinStatementsandfirstStatementread the same string anywhere else.RulesTableedits Rules. A rule has a priority, the category it decides, an On switch, and conditions, which must all hold. Rules run by priority, and the first that matches decides. Withcategories, the table is the whole set’s ordered list, and dragging a row renumbers it. Without them, it shows one category’s rules with their set-wide numbers;categoryIdnames the category a new rule decides, andallRulestakes the whole set, so a new rule’s priority comes after every rule in it rather than tying a rule of another category. The table then hands back only that category’s rules, which the Pane puts back into the set.
{ id, categoryId, priority, enabled, conditions }. Its conditions
are Filters rules over columns: path is [columnId], and value follows
the operator (one value; a list for is_any_of and is_none_of;
[min, max] for between; none for empty and not_empty). RULE_OPERATORS
lists each column type’s operators. Text is compared, searched, or tested
with matches, a regular expression; numbers are compared or fall between
two values; booleans are true or false; selects are one value or one of a
list. negated: true means the condition must not hold. While a person adds
a condition, it can lack an operator or a value: leave such a condition out
when the rules run.
Icons
A Pane lets a reader choose an icon from Font Awesome’s whole catalog:IconPickerButton is an outline icon button that draws the chosen icon and
opens the picker beside it; a pick calls onChange and closes it. IconPicker
is the picker alone, a search box over a grid, for a Pane that holds it in a
popover or dialog of its own. IconGlyph draws a chosen icon anywhere else.
The value is the icon string Pieces and a setting’s icon are stored as, such
as fa:sharp:solid-duotone:mug-hot; null means no icon. A pick keeps the
value’s style and color, and is Sharp solid two-tone when there is none.
Search is Font Awesome’s own, the same one the app’s icon picker uses, so it
finds icons by meaning: “coffee” finds a steaming mug and a coffee pot. With no
query, the grid shows Font Awesome’s popular icons, and more load as the reader
scrolls. The preview frame has no network: it asks the app, which searches Font
Awesome and draws each glyph the frame shows.
Ponder
A reader who can use Ponder reaches it from any Pane that reads a Plot. Hovering the Pane shows a small sparkle in its top right corner; it opens a menu with Ponder this… and Plot the path to this…. Right-clicking anything in the Pane opens the same menu at the pointer. Shift-right-click, and right-click in a text field, keep the browser’s own menu. A Pane needs no code for this: the Page frame adds it to every Pane that reads a Plot, and to no Page.- Ponder this… puts a draft naming the thing in Ponder’s composer. Nothing is sent until the reader sends it.
- Plot the path to this… opens a side sheet with the Plotted Path: how the answer was arrived at, from the Pane through the Plots its SQL reads to the Provider Pool tables the rows come from. The steps draw at once; then Ponder writes a sentence for each, in a conversation the reader can open with Continue in Ponder. The sheet’s header picks the model: the default, a profile (Simple, Standard, Strong) or any model the Workspace allows, searched by name or maker. Picking one plots the path again.
label is what the reader pointed at, in their words. plot is the handle of
a Plot the Pane reads, and row the values that identify the row. A target
inside a Pane belongs to that Pane.
Pane code asks Ponder with usePonder:
ask runs an ordinary Ponder turn for the reader, with Ponder’s tools and the
reader’s own permissions, in a conversation named for the Pane that the reader
can open in Ponder. answer is the Markdown so far. status is idle,
asking, approval, done, stopped or error. A tool call that would
change something waits for the reader in Ponder’s drawer (approval). One ask
runs at a time; a new ask stops the one before it, and so does stop(). A
stopped ask ends in Ponder too, so its reply runs no further.
model is a model id from usePonderModels(), or simple, standard or
strong for a profile of the default Persona; the Workspace’s AI processors
policy refuses a model it does not allow. available is false where nothing
answers: for a reader without Ponder, under View as, in the preview camera and
in unsaved previews.
PlottedPath draws a path from data a Pane already has, the same component
the sheet uses:
kind is pane, plot, judgment, preference, refresh,
policy or source, which picks its icon and label. A step can carry
summary, facts, details (code, fields, facts or text, shown when
it opens), branches, and a status of restricted or missing with a
note. Walk me through it walks the path one step at a time.
Providers
Every Parable is built for every Provider that could feed it, however rare. A Provider the Workspace has not connected reads as an empty table, so a Parable whose Providers are partly connected still shows what it has.ImproveParable says what a visual is missing and offers to connect it. The
Pane reads the Workspace’s connections with a Plot over workspace.providers
and hands the component the hook that reads them:
QL-PL-015) reads “Hidden by” and the filter’s name,
also without Retry. In a preview that draws Panes without running them (the
bridge’s PREVIEW_NO_DATA), a read is replaced by the kit’s empty state,
“Live data isn’t drawn in previews”, with no Retry.
connections still takes the rows directly, for a host that has them.
The hook returns the Plot’s rows, { provider, connection } with connection
connected or attention, or null while they load.
Return null, never the empty rows of a read still running: no rows means
nothing is connected, and PopulateParable would cover the Page. A Provider with no row is
not connected. The Plot folds each connection’s connector_slug into the
Provider’s name in Plot SQL and reads its health as the Providers directory
does; while a sync runs, the status before it decides:
- Wrapping a visual, it shows a chip at the visual’s bottom right on hover, with the logos of the Providers not yet connected. The chip opens their list: what each adds, and Connect.
- With no children, it is the Improve this Parable band: how many of the Providers are connected, and the list, folded until opened.
PopulateParablewraps a Pane’s content and shows every listed Provider in its place until one is connected, with adescriptionof what the Parable shows once it has data.
provider is the Provider’s name in Plot SQL, zoom in
providers.zoom.meetings; name labels it, domain gives its logo, adds
says in one sentence what connecting it adds here, and group is an optional
heading. Each row ends in a colored status icon: green when the Provider is
connected, amber when its connection needs attention, muted when it is not
connected. A Provider whose connection needs attention counts as connected and
offers Fix. Anyone who can read the Parable sees which Providers are
connected. In the preview camera and under View as, where nothing can open a
setup, the rows offer no Connect or Fix, and the icon says the rest.
Both components draw nothing of their own when every listed Provider is
connected, while the connections are null, or when their read fails.
On a Page with several visuals, wrap each visual. Otherwise put one band at the
top of the Page, in a Pane of its own, since a Page imports only its Panes. The
Pane holding a Parable’s Home content wraps it in PopulateParable.
Connect opens the Provider’s setup in the app, not in the frame: the
Workspace’s own connection when it has one, else the Provider’s setup page.
The setup pages show a back arrow to the Page, Back to the Parable, which
the browser tab keeps through an OAuth sign-in.
Audiences
AudiencePicker chooses who a Policy grant admits: participation levels,
Workspace Roles and people, in one multi-select. Primary is always admitted.
Choosing a level admits it and every level above it; taking one away takes it
and every level below it. Its value is the platform’s grant, so a control Pane
saves it as a This Parable setting that a Policy reads (fromSetting, see
Policies):
{ actor: { kind, id }, name, detail }, where kind is
person or role and id is parsed with parseIdentityUUID from
@psgen/scalar-lib. The choices come from the Pane’s own Plot over the
Workspace’s people and Roles; the picker reads nothing itself. People show with
their initials, since a Pane loads no remote images, and a name the value holds
that the choices lack shows as unknown. audienceWidens(previous, next) says
when a choice admits anyone the saved audience did not, so a Page can ask
before it saves a wider one.
Services
A saved Page loads a library, and a heavy control such as Filters or Data Grid, only when one of its modules imports it. A Page built from light controls loads none of them. An unsaved preview works the same way: the live bundler receives only the shared controls, npm libraries and scalar parser that the Parable’s files import.d3 (7.9.0) and three (0.186.0) are available to Panes whole, as their
namespaces: import * as d3 from 'd3' or named imports such as
import { Scene, WebGLRenderer } from 'three'. A three scene draws into a
canvas the Pane creates.
Kysely is included by default at version 0.29.6, in both the live authoring
preview and compiled Page/Pane runtime. Its real declaration files are also
supplied to the source editor.
plotApi.preview(name, query);
values remain bound, and current input Policies are checked on execution.
See Execution context for how the
requester, definition version, and selected Report reading relate.
Design Powers
The built-in Design Basics and Design Transitions Powers bundles and their provisioning hooks have been removed. Unedited copies are excluded from the catalog and new responses; edited Workspace copies and historical responses are preserved.Storage
Publish compiles every Page and Pane in the candidate version, with its selected CSS. A syntax error or unavailable import stops publication; the published version and deployment pointers stay unchanged. Runtime errors and unavailable data can still occur after a successful build and are shown in the content area. Builds are cached by source and compiler version. Opening the same saved draft again reuses its build without requiring Publish. Changing source creates a new build. Switching between Pages from the same build keeps the renderer running and reuses already evaluated modules: the frame receives the new Page over its open connection and replaces only the Page’s content. Once a Page is on screen, the frame evaluates the build’s other modules while it is idle, so a later move finds its code ready. Every Page enters the same way. The frame draws it unseen until every Pane in it has drawn (at most 1.5 seconds), then it settles in: a fade, an 8px rise and a 3px blur clearing, the shared motion tokens’ page-slide values. On a move to another Page, the current Page stays on screen until then and fades out over--duration-quick. A Pane still loading its data shows skeletons inside the
settled Page. A Plot result the session already read answers at once, so a
return to a Page does not wait for its data; see
Plots for what drops a kept result. With
reduced motion the switch is immediate.
The generating mosaic covers a Page only while it is built where you are
looking: an edit is saved, or Ponder writes the Page. A Parable switch,
another Release, View as, or a reload never shows it. Under the mosaic the Page draws at full speed:
the mosaic is 0.999 opaque, because Chrome gives an iframe that an opaque layer
covers one animation frame a second.
Outside editing mode, a deployment displays its own published version or the
upstream version it has received. Pending Changes are not the displayed content.
Editing mode opens the selected working changes. Personal Parables continue to
use their directly saved content.
When populating takes longer than a brief transition, a screen reader hears
Populating…. Fast navigation announces nothing.
If the preview fails
Read the visible compile or runtime error and repair the smallest failing boundary. Check the Page or Pane source, imported local components, and the attached data or configuration Pieces. The current published version remains unchanged while the repair stays in a Patch.Security
Saved Pages and Panes use compiled code in an isolated frame. That frame has its own opaque origin and cannot read the app’s cookies or document. Data requests travel through the host’s existing authorization checks over a document-specific message channel. Authored React is compiled, never executed, on the server. SQL executes on the server through the query service.view.plots controls source inspection, while permitted Pane use can receive
compiled React and CSS under view.preferences. Executable assets necessarily
reach the browser; hiding the source editor is not a secrecy guarantee.
props.policies contains resolved decisions, not the Policies settings.
Ponder from a Pane acts as the reader, never as more. Each turn is an ordinary
Ponder turn under the reader’s permissions, a conversation the reader can see,
and a tool call that changes something waits for the reader’s approval in
Ponder’s drawer, which the frame cannot reach. A Plotted Path shows a Plot’s
SQL only where the reader’s view.plots allows it. Under View as a Pane gets
no Ponder, because Ponder keeps working as the previewer.
Unsaved edits retain the live sandbox bundler. Saving brings the Page back to
the reusable compiled path. Both paths use the same shared controls and Pane SDK.
Stability
Check:- initial, loading, empty, error, and populated states;
- Ivory and Obsidian themes;
- keyboard order and visible focus;
- reduced-motion behavior;
- text wrapping and long synthetic values; and
- a small and a large device, from the device buttons, where the Pane has layouts for more than one size.