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|
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 });
+
+```
+
+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`: 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`: `` 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: ``. 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 |