diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 1f91f26..3173b90 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -21,7 +21,7 @@ "name": "fxtr", "source": "./plugins/fxtr", "description": "Skills for writing, running, and viewing fxtr experiments, and for calling models from them with behaviors", - "version": "0.1.2", + "version": "0.1.3", "author": { "name": "TransluceAI" }, diff --git a/plugins/fxtr/.claude-plugin/plugin.json b/plugins/fxtr/.claude-plugin/plugin.json index 1871c0a..76cbe75 100644 --- a/plugins/fxtr/.claude-plugin/plugin.json +++ b/plugins/fxtr/.claude-plugin/plugin.json @@ -4,5 +4,5 @@ "author": { "name": "TransluceAI" }, - "version": "0.1.2" + "version": "0.1.3" } diff --git a/plugins/fxtr/skills/behaviors/provenance.json b/plugins/fxtr/skills/behaviors/provenance.json index 4aebdeb..6ec4d6a 100644 --- a/plugins/fxtr/skills/behaviors/provenance.json +++ b/plugins/fxtr/skills/behaviors/provenance.json @@ -5,7 +5,7 @@ "fxtr": "0.0.1a4", "behaviors": "0.0.1a3" }, - "revision": "051a5d3ab1f6df872092af2365b34c5d0f6671d3", + "revision": "c3782b939f40b8fbffa00c43c984c0ff83087cb6", "modified": false, "content_sha256": "9ca8c278e92e77f29f281313b17717930e6ecc67e13027fedde3152fc37ea5b9" } diff --git a/plugins/fxtr/skills/first-experiment/provenance.json b/plugins/fxtr/skills/first-experiment/provenance.json index a43d0a9..fd13c40 100644 --- a/plugins/fxtr/skills/first-experiment/provenance.json +++ b/plugins/fxtr/skills/first-experiment/provenance.json @@ -5,7 +5,7 @@ "fxtr": "0.0.1a4", "behaviors": "0.0.1a3" }, - "revision": "051a5d3ab1f6df872092af2365b34c5d0f6671d3", + "revision": "c3782b939f40b8fbffa00c43c984c0ff83087cb6", "modified": false, "content_sha256": "1b39e083738e6d10b515568bf7912241a1874cda150eb40908b92e7927d38b5f" } diff --git a/plugins/fxtr/skills/fxtr/SKILL.md b/plugins/fxtr/skills/fxtr/SKILL.md index e109e08..3ccbdb6 100644 --- a/plugins/fxtr/skills/fxtr/SKILL.md +++ b/plugins/fxtr/skills/fxtr/SKILL.md @@ -77,6 +77,10 @@ Do these by default, unless the user asks otherwise. * **Write each docstring's first sentence for the viewer.** The operation's card shows it, so make it a short imperative sentence saying what the operation does ([Watch jobs in the viewer](references/guides/running-and-viewing-jobs.mdx#watch-jobs-in-the-viewer)). +* **Describe the experiment in `EXPERIMENT.md`** at the project root, which the viewer's rail + links to: the question, what is compared or measured and why, and how scores are defined, in + plain English for a reader new to the experiment. Keep it current as the experiment changes + ([Watch jobs in the viewer](references/guides/running-and-viewing-jobs.mdx#watch-jobs-in-the-viewer)). * **Return a report from the root workflow**: one entity that references every result worth presenting, for the launcher and the results overview to read ([Read results](references/guides/running-and-viewing-jobs.mdx#read-results)). @@ -101,13 +105,11 @@ before [adding one](references/reference/renderers.mdx#adding-a-package-to-a-pro * **Give each step an `InvocationPanel`**, registered under the step's name, whenever there is something useful to show beyond the output array itself. The step's pane already shows its arrays as tables, so don't redraw them ([Where a view appears](references/guides/designing-views.mdx#where-a-view-appears)). - The panel becomes the step's default view, in its pane and in its card's preview, and it is + The panel becomes the step's default view, in the pane its card opens, and it is handed every invocation the viewer's selection picks out, up to all of a mapped step's. Show one in full and several together (a chart or a summary), loading their outputs with `LoadEach` - ([Step panels](references/reference/renderers.mdx#step-panels)). - Wrapping a view of one invocation in `singleInvocation` asks the reader to select one when - there are several, so keep it for the root workflow and for steps that are neither mapped nor - replicated. + ([Step panels](references/reference/renderers.mdx#step-panels)). Never show a note + asking the reader to select one; `singleInvocation`, which does that, is deprecated. * **Give the root workflow a results overview**: an `InvocationPanel` that reads the workflow's report and presents the experiment's findings ([Results overviews](references/concepts/visualization-and-custom-renderers.mdx#results-overviews)). diff --git a/plugins/fxtr/skills/fxtr/provenance.json b/plugins/fxtr/skills/fxtr/provenance.json index 5c5ec05..8c43586 100644 --- a/plugins/fxtr/skills/fxtr/provenance.json +++ b/plugins/fxtr/skills/fxtr/provenance.json @@ -5,7 +5,7 @@ "fxtr": "0.0.1a4", "behaviors": "0.0.1a3" }, - "revision": "051a5d3ab1f6df872092af2365b34c5d0f6671d3", + "revision": "c3782b939f40b8fbffa00c43c984c0ff83087cb6", "modified": false, - "content_sha256": "51ba3d88cfa95b80fee856d1d9a8672f7df95e54d5ae269b9ceee5fcee63bbe9" + "content_sha256": "c4930667ed6ac4665628fab04edcd9a755516598d49cdd08199f1cb544779c7d" } diff --git a/plugins/fxtr/skills/fxtr/references/INDEX.md b/plugins/fxtr/skills/fxtr/references/INDEX.md index a330ac6..357e8f7 100644 --- a/plugins/fxtr/skills/fxtr/references/INDEX.md +++ b/plugins/fxtr/skills/fxtr/references/INDEX.md @@ -33,4 +33,4 @@ in the order of the site's navigation. - [Designing views](guides/designing-views.mdx): How a renderer's views should look and behave in the viewer: surfaces, type, color, charts, links, controls, and how to check them. - [Array queries](reference/array-queries.mdx): Filter, reshape, aggregate, and join arrays with the query builder or SQL, in a workflow or in a step. - [Project configuration](reference/project-configuration.mdx): What fxtr new writes, and the settings in pyproject.toml and fxtr.local.toml. -- [Renderers](reference/renderers.mdx): The renderer API: slots and registration, loading entities and invocations, step panels, links, panel state, the kit, and building the renderer package. +- [Renderers](reference/renderers.mdx): The renderer API: slots and registration, loading entities and invocations, step panels, links and detail views, panel state, the kit, and building the renderer package. diff --git a/plugins/fxtr/skills/fxtr/references/concepts/entities-and-custom-types.mdx b/plugins/fxtr/skills/fxtr/references/concepts/entities-and-custom-types.mdx index 97b6819..24392dd 100644 --- a/plugins/fxtr/skills/fxtr/references/concepts/entities-and-custom-types.mdx +++ b/plugins/fxtr/skills/fxtr/references/concepts/entities-and-custom-types.mdx @@ -90,8 +90,11 @@ an entity. fxtr currently converts dataclasses to and from their stored form with the [cattrs](https://catt.rs/) library, with strict settings: unknown fields, missing fields, and - values of the wrong type are rejected rather than coerced. This implementation detail may change - in a future version; the dataclass surface described here is what to rely on. + values of the wrong type are rejected rather than coerced. Storing an entity checks that it will + load back under these rules, so a value they reject, such as an `int` in a `float` field or a + subclass in a field annotated with its base class, fails when it is stored rather than when it is + next read. This implementation detail may change in a future version; the dataclass surface + described here is what to rely on. ### Advanced use: low-level entity classes diff --git a/plugins/fxtr/skills/fxtr/references/concepts/visualization-and-custom-renderers.mdx b/plugins/fxtr/skills/fxtr/references/concepts/visualization-and-custom-renderers.mdx index b122652..4efad38 100644 --- a/plugins/fxtr/skills/fxtr/references/concepts/visualization-and-custom-renderers.mdx +++ b/plugins/fxtr/skills/fxtr/references/concepts/visualization-and-custom-renderers.mdx @@ -65,14 +65,13 @@ ID, and the component: ```ts views/src/bundle.ts import { EntityPanel, InvocationPanel, renderer, type ViewBundle } from "fxtr-view"; -import { singleInvocation } from "fxtr-view-kit"; import { AnswerPanel } from "./answer"; import { Overview } from "./overview"; const bundle: ViewBundle = { renderers: [ renderer(EntityPanel, "org.example.qa.Answer.v1", "my-project.AnswerPanel", AnswerPanel), - renderer(InvocationPanel, "qa.evaluate", "my-project.Overview", singleInvocation(Overview)), + renderer(InvocationPanel, "qa.evaluate", "my-project.Overview", Overview), ], }; @@ -81,7 +80,7 @@ export default bundle; Renderers are built with two libraries. **`fxtr-view`** is the small, stable contract between a renderer and the viewer: the slots, `renderer(...)`, and hooks for loading data and linking to -entities. **`fxtr-view-kit`** has building blocks that match the viewer: layout classes, chart +entities and to other views. **`fxtr-view-kit`** has building blocks that match the viewer: layout classes, tables, chart parts and controls, number formats, and adapters for showing invocations and loading many entities at once. The [renderer reference](../reference/renderers.mdx) describes both, with the rules for registering renderers. diff --git a/plugins/fxtr/skills/fxtr/references/guides/designing-views.mdx b/plugins/fxtr/skills/fxtr/references/guides/designing-views.mdx index b9ac98d..a905891 100644 --- a/plugins/fxtr/skills/fxtr/references/guides/designing-views.mdx +++ b/plugins/fxtr/skills/fxtr/references/guides/designing-views.mdx @@ -15,19 +15,31 @@ covers registering renderers, loading data, and the kit's API. A project written with `fxtr new --example` has a complete small example in `views/src/overview.tsx`: a results overview with a chart that follows everything below. +## What a view is for + +A view gives its reader an accurate and detailed picture of what happened in the experiment and +what data it produced, even when the reader knows the experiment only at a high level. A view +that says only "A scored 40 and B scored 50" leaves them asking what the scores are for and how +they were computed. Answer that with the data itself: a good visualization shows the important +facts in its marks, and links let the reader dig into the details behind each one +([Linking to detail views](../reference/renderers.mdx#linking-to-detail-views)). Leave out +explanatory prose the data can show instead. The project's `EXPERIMENT.md` +([Watch jobs in the viewer](running-and-viewing-jobs.mdx#watch-jobs-in-the-viewer)) is +where the experiment itself is explained. + ## Where a view appears -| Surface | Renderer | Room | Base type | Surface under it | -| ---------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------- | --------- | ---------------- | -| Results overview | `InvocationPanel` of the root workflow | the page, up to 1400px wide, 12px side margins | 13px | `--bg-alt` | -| A pane | `EntityPanel`, or a step's `InvocationPanel` | resizable; can be 300px or less | 12px | `--bg-alt` | -| A card's preview | the node's default view | beside each card while no pane is open, about half the page wide, as tall as the card, scrolling inside | 12px | `--bg-alt` | -| Hover card | `EntityHoverPreview` | 360px wide, at most 300px tall, the full cid below it | 11px | `--bg` | -| Inline link | `EntityInlineLink` | one line inside a pill, cut off with an ellipsis | 11px | the pill | +| Surface | Renderer | Room | Base type | Surface under it | +| ---------------- | -------------------------------------------- | ----------------------------------------------------- | --------- | ---------------- | +| Results overview | `InvocationPanel` of the root workflow | the page, up to 1400px wide, 12px side margins | 13px | `--bg-alt` | +| A pane | `EntityPanel`, or a step's `InvocationPanel` | 600px wide when it opens; resizable | 12px | `--bg-alt` | +| Hover card | `EntityHoverPreview` | 360px wide, at most 300px tall, the full cid below it | 11px | `--bg` | +| Inline link | `EntityInlineLink` | one line inside a pill, cut off with an ellipsis | 11px | the pill | -A project's own renderer becomes the default view of its node, so the same component may draw at -full page width, in a narrow pane, and in a card's preview. Design for every width from about 320px +A project's own renderer becomes the default view of its node's pane, so the same component may +draw at full page width and in a narrow pane. Design for every width from about 320px to 1400px, and put the finding that matters most at the top left, where every surface shows it. +Where you can, fit a pane's opening width of 600px without overflowing or scrolling sideways. The viewer draws the frame. Leave out what it already gives: @@ -124,6 +136,31 @@ Where marks need separating, leave real space between them, not a stroke or bord * Shadows only on what floats (the tooltip). Rules and borders are 1px. * No animation or transition of your own; the viewer animates only what is running. +## Tables + +A list of records a reader may want to reorder or search (every prompt, every reading, a run's +items) goes in the kit's `DataTable`, not a table written by hand: its columns sort, filter, and +resize, and its header stays in view as the rows scroll. + +```tsx + r.id} + columns={[ + { key: "judge", label: "judge", value: (r) => r.judge }, + { key: "score", label: "score", numeric: true, value: (r) => r.score, cell: (r) => formatNumber(r.score, 2) }, + ]} +/> +``` + +A column's `value` is what it sorts and filters by, and its `cell` draws it (a bar, a link); +`numeric` right-aligns it in tabular figures. `renderRow(row, tr)` draws a row through `tr(props, +selected)`, to make rows pressable or show one as selected. To open a detail view from a row, +return a component that calls `useLink` and draws the row with `tr(link.props, link.open)` +([Linking to detail views](../reference/renderers.mdx#linking-to-detail-views)). Write a table by +hand, with `layout.table`, only for a matrix or a chart laid out as a table, such as the heatmap +[below](#example-a-heatmap). + ## Charts ### Choose the form @@ -175,7 +212,10 @@ draws follows this guide: its own colors, fonts, tooltips, and fixed sizes do no 3 to 4px radius; lines 1.5 to 2px. Direct labels (`layout.chartLabel`) on the few values that matter, not a number on every mark. * **Legends.** A `Legend` for two or more series, placed above the chart; none for one series - (its heading names it). A `ScaleLegend` for a sequential or diverging fill. + (its heading names it). A `ScaleLegend` for a sequential or diverging fill. Explain any other + encoding with a legend drawn from the chart's own marks, not a sentence: a filled square + labeled "won" beside an open one labeled "lost", rather than "a square is filled when the poet + won". * **Text in SVG** inherits the viewer's sans; size and color it with the `layout` classes or the type tokens. Align with SVG attributes (`textAnchor`, `dominantBaseline`). @@ -199,6 +239,10 @@ draws follows this guide: its own colors, fonts, tooltips, and fixed sizes do no focus, cleared on leave and blur: the mark's name in bold, its value, and a line of context. Nothing in a tooltip can be pressed, so links go in the chart or under it. Never use SVG `` or the `title` attribute for this. +* **Details beside the view.** A pick whose detail needs room of its own (a point's readings, a + row's transcript) opens a detail view in a pane beside this one, through `useLink`, rather than + growing the view ([Linking to detail views](../reference/renderers.mdx#linking-to-detail-views)). + Spread the link's props onto the mark and draw it with `data-open={link?.open}`. * **Picks within the view.** A reader's pick that stays inside the view (a series focused, a point whose detail shows under the chart) is drawn with `data-picked="true"` (the same ink ring), the rest `layout.faded`; its detail goes in a `DetailBox`. @@ -320,4 +364,4 @@ grep -rnE 'font-family: *[^v ]|font-size: *[0-9]|color-scheme|!important|<title> The rest is a look at the view in the viewer. The usual things to catch are a theme it reads badly in (the toggle is at the foot of the viewer's left rail) and a width it breaks at: drag a pane -narrow, or close every pane to see the card previews. +narrow. diff --git a/plugins/fxtr/skills/fxtr/references/guides/running-and-viewing-jobs.mdx b/plugins/fxtr/skills/fxtr/references/guides/running-and-viewing-jobs.mdx index 99277ee..105b678 100644 --- a/plugins/fxtr/skills/fxtr/references/guides/running-and-viewing-jobs.mdx +++ b/plugins/fxtr/skills/fxtr/references/guides/running-and-viewing-jobs.mdx @@ -121,7 +121,7 @@ For one job, the viewer shows: by dimension. A value that references an entity is a link. * **Entity details**, opened from any link, in a pane you can keep alongside the trace. Hovering a link shows a preview. -* **Your project's README**, linked from the side rail. +* **Your project's `EXPERIMENT.md`**, linked from the side rail. The first sentence of each step's and workflow's docstring is what appears on its card, so write those as short imperative sentences that tell a reader what the operation does, such as "Compute @@ -129,6 +129,15 @@ mean scores across judges." Leave out edge cases and what the step is mapped ove already shows. For progress worth reading while a step runs, write log lines with `context.log(...)`. +`EXPERIMENT.md`, at the project root, describes the experiment for someone reading its results: +the question it asks, what it compares or measures and why, and how each score or judgment is +defined. Keep it current as the experiment changes. + +* Write it in plain English for a reader who knows the field but not this experiment. Define any + term specific to the experiment, or better, avoid it. +* Leave out the steps of the computation, which the viewer's graph already shows, and leave out + code-level details and how to run it (the project's `README.md` is for those). + All of this is drawn by the viewer's default renderers until the project adds its own, which can also give the root workflow a results overview: a view of the run that presents its results. See [Visualization and custom renderers](../concepts/visualization-and-custom-renderers.mdx). diff --git a/plugins/fxtr/skills/fxtr/references/reference/renderers.mdx b/plugins/fxtr/skills/fxtr/references/reference/renderers.mdx index dce7971..ef9a08c 100644 --- a/plugins/fxtr/skills/fxtr/references/reference/renderers.mdx +++ b/plugins/fxtr/skills/fxtr/references/reference/renderers.mdx @@ -1,6 +1,6 @@ --- title: "Renderers" -description: "The renderer API: slots and registration, loading entities and invocations, step panels, links, panel state, the kit, and building the renderer package." +description: "The renderer API: slots and registration, loading entities and invocations, step panels, links and detail views, panel state, the kit, and building the renderer package." --- A project's renderers are React components, written in TypeScript in the project's renderer @@ -19,13 +19,12 @@ subject. The project's bundle, `views/src/bundle.ts`, exports its registrations ```ts views/src/bundle.ts import { EntityPanel, InvocationPanel, renderer, type ViewBundle } from "fxtr-view"; -import { singleInvocation } from "fxtr-view-kit"; import { DocumentPanel, Overview } from "./components"; const bundle: ViewBundle = { renderers: [ renderer(EntityPanel, "org.example.my_experiment.Document.v1", "my-project.DocumentPanel", DocumentPanel), - renderer(InvocationPanel, "my_experiment.run", "my-project.Overview", singleInvocation(Overview)), + renderer(InvocationPanel, "my_experiment.run", "my-project.Overview", Overview), ], }; @@ -67,25 +66,26 @@ outside the component, or memoized). Never call `useEntity` in a loop. ``` An `InvocationPanel` is handed every invocation the viewer's selection picks out (its -`invocations` prop, each with its key in the run), and must show any number of them -([Step panels](#step-panels) says what each holds). The kit's -adapters turn a view of one loaded invocation record into such a panel: - -* `singleInvocation(View)` loads the record when there is one invocation and passes it to `View` - as `invocation`; for any other count it asks for one invocation to be selected. -* `eachInvocation(View)` shows several as collapsible sections, one per invocation, each loading - its own record when opened. - -A root workflow's panel is always given its one invocation. An invocation record has the -function's `name`, its `state` and `error`, its `inputs` by argument name, and its `output`, each -an array entity's ID to load in turn (`output` is null until there is one). The kit's -`invocationInput(invocation, name)` reads a named input, and `InvocationOutcome` shows a run that -has no output yet. +`invocations` prop, each with its key in the run and its output array; +[Step panels](#step-panels) says what each holds). It must be useful as soon as it is opened, +however many it is handed: a step or child workflow mapped over a dim hands its view every +invocation. Show one in full, and several in a view of their own (an aggregate, or a row or card +each that links to that one alone), never a note asking the reader to select one. A root +workflow's panel is always given its one invocation. + +`useInvocation(spawn.ref)` loads an invocation's record, which has the function's `name`, its +`state` and `error`, its `inputs` by argument name, and its `output`, each an array entity's ID to +load in turn (`output` is null until there is one). The kit's `invocationInput(invocation, name)` +reads a named input, and `InvocationOutcome` shows a run that has no output yet. Where the view of +several invocations is just the view of each in turn, the kit's `eachInvocation(View)` shows a +collapsible section per invocation, each loading its own record when opened and passing it to +`View` as `invocation`. (The kit's `singleInvocation(View)`, which shows a view of one record and +otherwise asks for one invocation to be selected, is deprecated.) ## Step panels -A step's `InvocationPanel` is its default view: the viewer draws it in the step's pane and as the -preview beside its card in the job's trace, so a reader sees it without asking. What it is given: +A step's `InvocationPanel` is its default view: the viewer draws it in the step's pane, which a +reader opens from the step's card in the job's trace. What it is given: * **Spawns, not records.** `invocations` holds one `Spawn` per invocation the selection picks out: every invocation of the step, or fewer when the reader slices the level's dims (`slice dims`). @@ -170,8 +170,8 @@ export function JudgePanel({ invocations }: InvocationPanelProps) { ``` Register it with `renderer(InvocationPanel, "my_experiment.judge", "my-project.JudgePanel", -JudgePanel)`. `singleInvocation` and `eachInvocation` suit a view of one invocation's record; a -panel over many spawns, like this one, reads them directly. +JudgePanel)`. `eachInvocation` suits a view of one invocation's record repeated per invocation; a +panel that shows many spawns together, like this one, reads them directly. ## Links @@ -181,6 +181,62 @@ chart's mark can't be a chip, so it uses `useEntityLinks()`, whose `open(id)` do the entity's link does, and whose `isOpen(id)` says whether a pane shows the entity. [Designing views](../guides/designing-views.mdx#interaction) shows how marks use them. +### Linking to detail views + +A view that opens a detail of something picked in it (a chart's point, a table's row) declares +the detail as a view of its own, which reads the params the pick adds, and links to it with +`useLink`. Declare views apart from their components, with `declareView(slot, subject, id, +options)`, in a module that imports no components, so views can link to each other without +import cycles: + +```ts views/src/views.ts +import { InvocationPanel, declareView } from "fxtr-view"; + +export const SCORES = declareView(InvocationPanel, "my_experiment.score", "my-project.Scores"); +export const FIT = declareView(InvocationPanel, "my_experiment.score", "fit", { within: SCORES, params: ["proposition"] }); +``` + +Register a declared view with `renderer(SCORES, ScoresPanel)` and `renderer(FIT, FitPanel)`. In +`ScoresPanel`, each mark links to the detail with the row's keys and the params the detail reads, +by name, and spreads the link's props onto the element: + +```tsx +const link = useLink(FIT, { target_model, proposition }); +<circle {...link?.props} className={layout.mark} data-open={link?.open} /> +``` + +The viewer opens the detail beside the view, hands it the invocations its params pick out +(`target_model`, a dim of the step) and the params it declares (`proposition`; a link that leaves +one out is a type error), and keeps it following the view. Type the detail's component with +`ViewProps<typeof FIT>`: its `params` then binds each declared param (`params.proposition`), so +the view only checks the values (a string, one of a few names). `useLink` returns null where the +viewer cannot open a view from here; the view then shows the detail in place. + +Declare a detail `within` the view that links to it when only that view does: its ID is named +below that view's (`my-project.Scores/fit`), and its pane closes when the view before it changes +to another. Declare a detail that several views link to on its own, and link to it from each. + +A link can also name a location, relative to the view's own or absolute, opened in that +location's default view or a declared one: `useLink({ to: "../sibling_step" }, keys)`, +`useLink({ view: REPLIES, to: "/respond" }, keys)`. A location is a step path from the run's root +workflow (`/score`, or `/round/judge` for a step inside a workflow step), then a field path into +each invocation's record: `.output.call` for a field of the step's output, `.inputs.prompt` for an +input. From a view of a step, `.output…` is below it and `../step` is a sibling. + +An `EntityLink` to an entity inside what the view was handed says where the entity sits, with +`at`: `<EntityLink id={call} at={{ keys: spawn.key, path: ["call"] }} />` for the `call` field of +one invocation's output, or `at={{ path: ["messages", 2] }}` in an entity's view. The pane it opens +follows the view as the view moves. For an entity better shown somewhere else (a step's input, +another step's output), name the location instead, with `to` (and `view`, for a declared view) and +the `params` it is opened at: `<EntityLink id={id} to=".inputs.prompt" params={spawn.key} />`. A +link takes one or the other; with neither, it opens the entity alone. + +A view of the whole run (a chart across several steps) is a view of the root workflow. A detail +that several views link to and that belongs to no one step can be a **page**: +`declarePage(name, { params })` declares it and `page(decl, Component)` registers it. A page's +component is handed the run (`run`, the root workflow's invocation), its `params`, and `storage` +(`PagePanelProps`). A page opens only from links to it. + ## Panel state A panel's `storage` prop is a JSON value the viewer keeps for the panel while it is open. Keep @@ -194,9 +250,8 @@ const [order, setOrder] = useStoredState(storage, "order", "guest"); Stored values are trusted as the initial value's type unless you pass a type guard, `isValid`, to reject an obsolete choice or state from another version of the view; a rejected value reads as -the initial one. The initial value and the guard are captured once for each storage and key. A view -wrapped with `singleInvocation` receives `storage` when its props declare it -(`{ invocation, storage }`). Keep hover and focus in `useState`. State a component keeps in +the initial one. The initial value and the guard are captured once for each storage and key. +Keep hover and focus in `useState`. State a component keeps in `useState` starts over when a rebuild replaces the renderers; state in `storage` stays. ## Styles @@ -217,13 +272,15 @@ every use against them. From `fxtr-view`: -| Name | What it is | -| -------------------------------------------------------- | ----------------------------------------------------------------------------------- | -| `renderer`, the slots, `ViewBundle` | registration ([above](#registering-renderers)) | -| `useEntity`, `unwrap`, `useInvocation`, `idKey` | loading an entity or an invocation record, and an ID's key | -| `isArrayData`, `scalarEntityId`, `scalarValue`, `rowsOf` | reading an array entity: a step's output ([Step panels](#step-panels)) | -| `EntityLink({ id })` | the viewer's link to an entity: a chip that opens it, with a hover preview | -| `useEntityLinks()` | `{ open(id), isOpen(id) }`: open an entity from a mark, and whether a pane shows it | +| Name | What it is | +| ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `renderer`, the slots, `ViewBundle` | registration ([above](#registering-renderers)) | +| `useEntity`, `unwrap`, `useInvocation`, `idKey` | loading an entity or an invocation record, and an ID's key | +| `isArrayData`, `scalarEntityId`, `scalarValue`, `rowsOf` | reading an array entity: a step's output ([Step panels](#step-panels)) | +| `EntityLink({ id, at })`, `EntityLink({ id, to, view, params })` | the viewer's link to an entity: a chip that opens it, with a hover preview ([Links](#links)) | +| `useEntityLinks()` | `{ open(id), isOpen(id) }`: open an entity from a mark, and whether a pane shows it | +| `declareView`, `ViewProps`, `useLink` | declaring a view other views link to, its component's props, and a link to it ([Linking to detail views](#linking-to-detail-views)) | +| `declarePage`, `page`, `PagePanelProps` | a page: a view of the run reached only by links | From `fxtr-view-kit`: @@ -243,7 +300,8 @@ From `fxtr-view-kit`: | `DetailBox({ title, onClose, children })` | the box showing a pick under a chart | | `formatNumber(v, digits)`, `formatPercent(share, digits)`, `formatSigned(v, digits)`, `countLabel(n, noun)`, `MISSING` | numbers as the viewer writes them | | `useStoredState(storage, key, initial, isValid?)` | `useState` kept in the panel's storage ([above](#panel-state)) | -| `singleInvocation`, `eachInvocation`, `invocationInput`, `LoadEach` | invocation adapters and loading ([above](#loading-data)) | +| `DataTable({ rows, columns, rowKey, renderRow })` | a table of records whose columns sort, filter, and resize ([Designing views](../guides/designing-views.mdx#tables)) | +| `eachInvocation`, `invocationInput`, `LoadEach` | an invocation adapter, and loading ([above](#loading-data)); `singleInvocation` is deprecated | | `spawnKeyLabel(spawn)` | a spawn's key as a label ([Step panels](#step-panels)) | | `MessageList`, `excerpt` | chat turns, and text cut to a length |