Skip to main content

Panes

A Pane is a building block of a Parable: a self-contained part of the application such as a picker, a chart, a form, an editor, or a table. Each Pane bundles everything needed to show and change one meaningful thing, so it can be placed on several Pages, reviewed, and maintained on its own. Treat every application control as a Pane. A label or a layout wrapper does not need one. The Pane editor drawer has Programming for its React code, Perception for its type, its own data and declared Panes, Palette for its CSS, Policies for who may do what, Plots for its SQL computations and a read-only list of its declared Panes’ Plots, and Panes for one tree of the Panes it declares. Each Plot lists its readers under Read by, and a declared Pane or Plot has Open to reach its own editor. The Perception and Policies are fields of the Pane, saved and versioned with it; the Palette and the Plots are Pieces the Pane owns. Deleting a Pane deletes its Palette, and deleting a Pane whose Plots another Piece reads is refused with those readers named. A Pane has one type: what it shows and what people save on it. There are four kinds of Pane. A Plot Pane owns exactly one Plot, and its type, in scalar-lib types, is the kind of the rows that Plot returns (see the Perceptions Power); a second computation gets a Pane of its own, and the Plots that need it read it by name. A bound Pane has no SQL: its parent declares it with Bound and one field of the parent’s row feeds it as props.value, under the parent’s Policies. A saved-data Pane has no SQL: its type has an id field, and the values people save on it are rows of that type, one for each item and each actor (see the Preferences Power), such as Categories, a date range, an audience or a switch. A layout Pane 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, never one Pane for each value. Any Pane can also own data of its own, which only a proposal changes. Parable never picks a saved row on its own: the Pane’s code reads the item it shows. Referencing another Pane lets it render inside this one, and lets this Pane call that Pane’s Plots and read its rows and data under that Pane’s Policies, without rendering it; it does not transfer ownership, copy saved rows, or pass on permission. Only a Page is composition only: a Pane whose type is null owns no Plot, but can still use the shared controls and read the Plots of the Panes it declares. Reading a Pane never creates rows or repairs missing configuration. A save, Prepare or Publish that gives a Pane a Plot while its type is null, or a second Plot, is refused with an error that names the Pane and its Plots: declare the kind, or create a Pane of that kind for the Plot. A Pane the published Version already holds that way shows an info finding and never stops another change. A Plot on a saved-data Pane is refused too (saved-data-plot): a Pane that declares preload, or that was a saved-data Pane in the last Release, would become a Plot Pane and refuse every save of a row. Move the Plot to a Pane of its own and read it by name, or declare overrides: true if people save rows over the Plot’s rows. Prepare and Publish refuse this finding also when the published Version already holds it. Using a Pane and its value requires view permission on its preferences target. Inspecting its code, Perception, SQL, CSS, or Policies each needs its own view permission, and rendering an allowed Pane never requires seeing its source. Every query and every saved write checks current Policies on the server, whatever the component displays. A Pane’s React runs in the reader’s browser inside a sandboxed frame with no network access; it reaches data only through the host, under the reader’s own permissions. The frame does not submit forms, so a form’s onSubmit never runs: handle the button’s click and the field’s Enter in onKeyDown, and never write a form’s onSubmit or action. JavaScript and CSS delivered to a browser cannot be kept secret, while SQL source can stay private because the server runs it by name. The real trust boundary is the author: treat permission to author or propose a Pane like permission to write its SQL, and review Changes before accepting them. A Pane’s code never acts with more rights than its author. Every proposal, and every save of a row that a Policy reads, is a privileged write: the reader must click a confirmation that the host draws outside the Pane, and the server refuses it unless the Pane’s author also holds the right.
  1. State the purpose and the interactions the Pane owns: viewing, configuring, editing, saving, removing, and recovering. Save the purpose as the Pane’s description, in one sentence: every Pane needs one, and a save without it is refused. Ask Ponder to reuse an existing Pane when the meaning is the same; split only when a separate contract, reuse, or lifecycle helps.
  2. Define its type. Ponder writes the Perception with one concrete type, with an id field when people save items, and decides the kind of Pane: Plot, bound, saved-data or layout. It declares the Pane’s data when the Parable owns values, and the references to other Panes.
  3. Add the logic. Ponder gives each Pane that shows data its one SQL Plot, declares it in the Pane’s Perception as a PlotContract so the code can call plots.<name>(), and writes the React code using the shared controls, declared Panes, and approved packages only. The code renders each Plot’s error, running, and empty states before its rows, in that order. The Perceptions Power shows the declaration and the Plots Power shows the states.
  4. Decide persistence. Drafts and temporary choices stay in React state until the person chooses to save. A save is preferences.save({ id, value, for }) or preferences.saveMany for many rows at once, and a change to the Pane’s own data is a proposal, propose.change, that the reader confirms. Reads are preferences.get(id) or the preferences.useItem and preferences.useItems hooks, which stay live. After a revision conflict, read the item again without discarding the draft. While context.viewAs is set, someone is previewing the Parable as another person and every save and proposal is refused, so show the controls read-only.
  5. Style and protect it. Ponder writes the Pane’s Palette with Pane-scoped CSS and sets its Policies for who may view, edit, create, and delete.
  6. Place it on the Pages that need it and preview each placement.
A Pane reads at most 10,000 rows from a Plot, so read only what it draws: a chart or a total reads a Plot that aggregates in SQL, heavy work is read from a Pipeline’s stored output, and a table a person browses reads one page at a time through a Plot that takes the last row shown as parameters and returns the rows after it (see the Plots Power). A hook whose truncated is true drew only part of the answer: say so in the Pane, then fix the Plot. While a Pane’s data loads, draw Skeleton from parable/panes/placeholders where each value will be, at about the size it will take, never a spinner, a “Loading…” label, or a cover over the Page. The platform keeps every Plot result in the reader’s browser, encrypted, for two weeks: a read with the same parameters, Pane values, reading and View as answers at once, after a reload and on a return to the Parable too. A chosen Refresh never reads again. A live Plot on Latest answers at once, then reads again in the background, and newer rows replace the old ones in place while the chart’s marks move to their new values. So a Pane never keeps Plot rows of its own, in React state, localStorage or IndexedDB, and never clears or redraws its chart from nothing when rows change: draw the hook’s rows, keep the same elements, and key each mark by its category, not by its index. New parameters keep the old rows on screen, with refreshing true, until the new rows arrive. Results are dropped when Latest moves to a newer Refresh for a Plot that reads a stored output, and when the reader’s access changes. A save, by the reader or anyone else, updates the rows of the Panes it changed in place, without a reload, and runs again only the Plots that read a changed Pane by name, or read a Plot that does. When a Pane needs newer rows than that, call the Plot hook’s breakCache(): the read goes to the server and replaces the kept result; rerun() is answered from the kept result. A Page needs no loading screen or entrance animation of its own: the renderer draws it unseen until its Panes are on screen, then settles it in, and covers it with the generating mosaic only while it is being built, as when Ponder writes it. Show a company’s real logo with BrandLogo from the shared controls: <BrandLogo domain="github.com" name="GitHub" />. Use it for vendors, tools, AI models and Providers, such as GitHub, Linear, Claude or Cursor, where a logo helps a reader find a name: a list of tools, a Provider column, a legend. Never use it for a person or an internal team; show a person by name or with an Avatar, and a team by its name. Pass the company’s own domain, such as anthropic.com for Claude and openai.com for ChatGPT, and its name, which labels the logo and supplies the initials drawn when there is no logo. The frame has no network and the host fetches every logo, so never write a logo URL or load an image from the internet. A logo follows the light and dark theme. useBrandLogo(domain) returns the image source for a logo you draw yourself, such as an SVG image beside a chart axis. A row that stands for a record in a Provider, such as a pull request, an issue, a ticket or a meeting, offers that record in its Provider: put ExternalLinkButton from parable/panes/placeholders in the row with the record’s address, <ExternalLinkButton href={row.html_url} label={Open pull request #${row.number} on GitHub} />. Return the address the Provider’s own data carries as a column of the row’s Plot, such as html_url for a GitHub pull request (its url is the API’s address, not the page), rather than building one from parts. The button shows while its row is hovered or holds keyboard focus, and always on touch screens; a row is a table row, a list item, or an element with role=“row” or data-row. Put it beside the row’s own button, never inside it. Only an https address draws it, so a record with none shows no button. A click opens the record in a new browser tab and leaves the row’s own click alone. To open a link from a control of your own, call openExternal(url) from the Pane’s module in its click handler: the Parable page opens only https links, only just after a click or key press in the frame, so never call it on mount or on a timer. Every Pane gets Contextual Ponder from the Page frame: a sparkle just right of its top edge, outside the Pane, and a right-click menu with “Ponder this…”, “Plot the path to this…” and, where the Parable’s Provider groups feed the Pane, “Improve this…”; write no code for them. A Pane adds entries of its own and turns any off with useContextualPonder from parable/panes/placeholders, passing a constant or a useMemo object: const MENU = { entries: [{ id: 'viewProof', label: 'View Proof', icon: 'file-certificate', onSelect: target => openProof(target) }], off: ['plotPath'] }; useContextualPonder(MENU);. Never build a second right-click menu. To let the menu name a finer thing, such as a bar, a row or a number, wrap it in ContextualPonder: <ContextualPonder asChild sparkle={false} target={{ label: 'West region · $42.5k', plot: 'salesByRegion', row: { region: 'west' } }} entries={[…]}><rect … /></ContextualPonder>. label is what the reader sees there, in their words; plot is the handle of a Plot the Pane reads; row holds the values that identify the row; an entry has id, label, an optional Font Awesome icon and disabled (true or why in a few words). Write ContextualPonder, never PonderTarget. A Pane that asks Ponder itself uses usePonder(): ask(prompt, { model }) runs a Ponder turn for the reader with Ponder’s tools and the reader’s own permissions, answer is the Markdown so far, to draw with ArtifactBody format=“markdown”, and status is idle, asking, approval, done, stopped or error. Check available first: a reader without Ponder, View as and a Pane preview inside the conversation have none. Ask from an event handler, such as a button’s click, never on every render, because each ask is a turn in a conversation the reader sees in Ponder. model is an id from usePonderModels() or simple, standard or strong. PlottedPath draws a path from data the Pane already has. Every Parable is built for every Provider that could feed it, however rare, and for every stream it reads, whichever ones a Workspace turned on. A Provider the Workspace has not connected, or a stream it has not turned on, reads as an empty table, so a visual whose Providers are partly connected still shows what it has, and a Pane whose Plot returns no rows shows its empty state (see the Plots Power). Say what a visual is missing with ImproveParable from parable/panes/placeholders. List the Providers that feed it, each with provider (its name in Plot SQL: zoom in providers.zoom.meetings), name, domain for its logo, adds (one short sentence on what connecting it adds here) and group, the kind of data it holds, such as Calendars or People, and pass the Workspace’s connections, which are Pane data like any other: a Plot providerConnections over workspace.providers returns one row per connected Provider, { provider: string; connection: string }, read only from connections whose setup finished (WHERE setup_finished: a setup started and never finished, with no credential saved, is not connected, and Connect continues it), with provider the connector_slug folded to its Plot SQL name (lower case, each run of other characters one underscore, trimmed, p_ before a leading digit) and connection ‘attention’ when the connection is disabled or its status (last_non_sync_status while connection_status says it is syncing) is sync_partial_success, transform_quality_degraded, ingestion_stale, schedule_paused, manual_validation_required, validation_pending, sync_failed, config_invalid, credential_error, internal_error or disabled, else ‘connected’, grouped by provider with any connected row winning; a Provider whose connection is healthy but none of whose connections has a row in workspace.stream_data (one row per stream whose data has reached the Pool, its provider_id joining workspace.providers.id) is ‘waiting’: connected, but no data has arrived yet, as for a push Provider such as Claude Telemetry, Slack Events or Parable Pulse until its senders deliver, or a pull Provider until its first sync lands. When the Parable reads a stream a Provider can leave off, such as Slack’s replies, a connection that does not have the stream on is also ‘attention’: join workspace.provider_connections on provider_id and check enabled for the stream’s platform_tap_name. Such a connection is not healthy, so its Provider reads ‘attention’ rather than ‘waiting’ while none of its connections has the stream on, since no data comes from a stream that is off. Its row then offers Fix, which opens the connection where the stream is turned on, and its adds sentence says what turning the stream on adds. Hand the component the hook that reads it, never the hook’s result: function useConnections() { const read = plots.providerConnections(); return read.status === 'ready' ? read.rows : null; } and <ImproveParable providers={[{ provider: 'zoom', name: 'Zoom', domain: 'zoom.us', adds: 'Calls held in Zoom, with how long each person stayed.' }]} useConnections={useConnections}>{chart}</ImproveParable>. The component calls the hook inside its own error boundary, so a refused or failed read leaves the visual drawing without the chip; called in the Pane’s body instead, a refused read replaces the whole Pane with “Could not read”. Return null while it reads, never empty rows, because no rows means nothing is connected. Once the Parable declares its Provider groups on its Home Page’s Perception (see the Perceptions Power), the components use those groups and the Workspace’s connections the Page frame reads, each visual showing the groups its Plots read, and a Pane’s own list only lends its Providers’ logos; a visual whose Plots read no Provider that a group names shows no chip and no Improve this…, so put every Provider the Parable reads in a group; until then the Providers of one group form a group that one connected Provider meets. Pass emphasis, the groups the visual needs most, such as [‘People’] on adoption by team, and they lead (see the Populate Power). Wrapping a visual, it shows a chip with the unmet groups’ Providers’ logos on hover, centered on the visual’s footer row when the visual ends in a footer element, which opens the one Improve panel: Required to Populate first, then Improves the Parable, each group with its title, rule and description, its missing Providers with Connect and its connected ones folded. With no children it is the “Improve this Parable” band. On a Page with several visuals, wrap each visual; otherwise one band goes at the top of the Page, in a Pane of its own, because a Page imports only Panes. PopulateParable takes the same providers and useConnections, wraps a Pane’s content and draws the panel in its place while a Required to Populate group has fewer connected than its rule asks or no Provider has data or needs attention, headed “Waiting for first data from” and the Provider’s name while a connected Provider waits for its first data, so the Pane holding a Parable’s Home content wraps it in PopulateParable with a sentence on what the Parable shows once it has data. Connect opens the Provider’s setup in the app with a way back to the Page. A Provider waiting for its first data offers neither Connect nor Fix. ImproveParable draws nothing of its own when no unmet group has a Provider left to offer, and PopulateParable when each Required to Populate group has as many connected as its rule asks and a Provider has data or needs attention; a group with more connected than its rule allows, such as two in an Exactly 1 group, never hides the Page or adds a chip, and neither draws anything while the connections are null or when their read fails, so never add a second notice of your own for missing data. Show every time in the reader’s own time zone and clock, never in UTC or on a 24-hour clock their locale does not use. Import formatTime and formatDateTime from parable/panes/placeholders; they read the reader’s browser the same way the app does. formatTime(at) reads “12:31pm ET” today and “Oct 6, 12:31pm ET” on another day, and a reader whose locale uses a 24-hour clock sees “17:31 BST”. formatDateTime(at) always shows the date, and formatDateTime(at, { long: true }) spells the whole moment out for a tooltip. Show a relative age with formatAge(at), such as “3 hrs ago”, and put the exact time on its hover with a title. The helpers read the current time themselves, so a Pane never calls Date.now() while it renders. readerClock() returns the reader’s timeZone and locale. 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. Write no extraneous copy. A label names its control or value in the fewest words, and a Pane adds no sentence that explains a control, says where a value came from, tells the reader they can change something, or repeats what a control already shows. When a control seems to need such a sentence, fix the control or its label. Some sentences are not extraneous, because the reader needs them to act. A control the reader cannot use stays on screen, disabled, with one short sentence on why: when policies.seatCapabilities allows it, what turns it on (“Switch to Publishing to change this.”), otherwise who can. The same goes for the reading, empty and cut-off states the Plots Power describes, an action’s error message, and the adds sentence of each Provider in ImproveParable and the sentence PopulateParable carries. Every Pane must render alone. Selected by itself in Pieces, with no Page or parent Pane around it, it draws with sensible defaults instead of failing. When Panes share state through a declared Pane’s module, that module’s hook returns the state the Page or parent provides and, when nothing provides it, the module’s own state with defaults, shared by every Pane in the frame. A Pane that shows nothing until something is chosen shows its first sensible choice or a clear empty state. When a Pane does not compile, has Warnings & Errors, or does not render, find out exactly why before changing it. Publishing waits until no Piece has Warnings & Errors, so fix them as you go. Read a Piece’s problems returns the Pane’s code, Palette, Perception and Policies, with every current warning and error: compile errors, TypeScript type errors, Code check findings (React rules, classes nothing styles, and the Parable rules such as values-api and data-in-code), Plot SQL validation, and the Parable rules on the Perception and the SQL, each with its line, column, the text of its line, and for a Parable rule a link to the page that teaches its fix. Follow the finding’s own message: it names what to write instead. A class is known when the shared controls’ stylesheet or any of the Parable’s Palettes styles it; one nothing styles is a warning, so define it in the Palette or remove it. Fix one problem at a time with Edit part of a Piece’s source: replace the smallest passage that occurs exactly once and fixes the problem, and pass the revision and the version that the read returned. Its answer carries the problems after the save; repeat until none is an error, then preview the Pane. A refused save names the import or handle at fault and where it is. Each Pane compiles on its own, so a broken Pane shows its error in its place while the rest of the Page still renders. A good Pane renders a permitted value, shows a clear state when there is no saved row or access is denied, shows that a Plot is still reading or that its read failed instead of an empty result, accepts valid values and refuses invalid ones, survives save and reload, and refuses a stale write without losing the draft. It behaves correctly when the reader, the Workspace, or the selected Report changes. The same Pane placed on two Pages shares its saved rows instead of duplicating them. Check these in the running Parable; a Pane preview inside the conversation uses captured data and cannot run live SQL or save rows. The rule that every control is a Pane is design discipline, and the compiler does not reject every raw control, so never claim stricter enforcement than the product provides. Related Powers: Perceptions, Plots, Preferences, Policies, Palettes, Pages. Find each by its title; do not guess stored names.