> ## Documentation Index
> Fetch the complete documentation index at: https://docs.transluce.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Visualization and custom renderers

> Watch an experiment in the viewer, and teach the viewer how to show your own types and steps.

fxtr ships a web **viewer** that shows a job as it runs and after it finishes: the graph of
workflows and steps, the arrays flowing between them, and the entities they reference. Everything
has a default rendering, so a new project gets a usable viewer with no extra work. However, you can
customize the visualization by writing **renderers**, small React components that take over how a
particular entity type, step, or workflow is shown, which fxtr loads them into the viewer alongside
its own.

## Overview

* **Open the viewer** with `fxtr view` to watch an experiment and inspect its results.
* **Define renderers** as a TypeScript package inside your project, in its `views/` directory,
  using the `fxtr-view` library.
* **Build the package** into a view bundle and the viewer picks it up, so your data and your steps
  appear the way you designed.

## The experiment viewer

From inside your project:

```bash theme={null}
uv run fxtr view
```

This serves the viewer at `http://127.0.0.1:8000/` and opens it in your browser. Every launcher
prints a link of the form `http://127.0.0.1:8000/#job=<job id>` that opens its job directly, and
you can switch between the project's jobs from a menu in the viewer. A running job updates live.

For one job, the viewer shows:

* **The tree of workflow invocations.** The root workflow at the top, with each child workflow
  one level down. A mapped child appears once per key.
* **A card for every step and child workflow** a workflow scheduled, showing its docstring, how it
  was mapped, its progress, and its log lines. A card links to the function's code in your editor.
* **The arrays each operation received and produced**, as tables you can page through and slice
  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.

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.

## Customizing visualizations

The viewer decides how to draw each thing by looking up a **renderer** for it. A renderer is
registered for a **slot**, which is a place in the viewer where something is drawn, and a
**subject**, which says what kind of thing it draws there:

| Slot | Where it appears | Subject |
| - | - | - |
| `EntityPanel` | An entity opened in the detail pane | The entity's type tag |
| `EntityInlineLink` | An entity where it is referenced, as a link | The entity's type tag |
| `EntityHoverPreview` | A compact card shown when a link is hovered | The entity's type tag |
| `InvocationPanel` | A full-pane view of one or more invocations of a step or workflow | The step's or workflow's registered name |

For entities, the subject is the `fxtr_type` string from the entity's definition, which is why
every entity type needs one. For invocations, it is the `name=` given to `@step` or `@workflow` (or, by default, the Python function's module-qualified name).
If no renderer is registered for a subject, the viewer falls back to generic ones that can be used with any value.

Renderers are implemented using two libraries:

* **`fxtr-view`**: the small, stable contract between a
  renderer and the viewer: the slot declarations, `renderer(...)` for registering a component,
  `useEntity(id)` and `unwrap(...)` for loading an entity's data, and `EntityLink` for linking to
  another entity.
* **`fxtr-view-kit`**: a set of conveniences for building renderers: layouts, a `Kind` label,
  `FieldList`, message lists for conversations, `singleInvocation` and `eachInvocation` for wrapping
  a view of one invocation into a panel, and `LoadEach` for loading many entities at once.

A renderer for an entity type is an ordinary component that receives the entity's ID:

```tsx views/src/answer.tsx theme={null}
import { EntityLink, unwrap, useEntity, type EntityID, type EntityViewProps } from "fxtr-view";
import { FieldList, Kind, layout } from "fxtr-view-kit";

interface AnswerData {
  config: EntityID;
  question: string;
  text: string;
}

export function AnswerPanel({ id }: EntityViewProps) {
  const answer = unwrap(useEntity(id as EntityID<AnswerData>));
  return (
    <div className={layout.panel}>
      <div className={layout.kindLine}><Kind>Answer</Kind></div>
      <h2 className={layout.title}>{answer.question}</h2>
      <p className={layout.prose}>{answer.text}</p>
      <FieldList fields={[["config", <EntityLink id={answer.config} />]]} />
    </div>
  );
}
```

`unwrap(useEntity(id))` returns the entity's decoded data, or suspends until it arrives, so the
component body can assume the data is there and leave the loading state to the viewer. The data is
the entity's stored form: a map with the entity's fields, where references to other entities arrive
as `EntityID`s that `EntityLink` turns into links.

The project's **bundle** collects and exports its renderers:

```ts views/src/bundle.ts theme={null}
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)),
  ],
};

export default bundle;
```

Each registration names the slot, the subject, an ID for the renderer, and the component. Prefix
renderer IDs with your project's name: an ID that collides with another renderer for the same
slot and subject, including one of the viewer's own, keeps the whole bundle from loading. Several
renderers may target the same slot and subject, in which case the viewer offers a switcher.

### Results overviews

The most valuable renderer in most projects is an `InvocationPanel` for the **root workflow**. The
viewer offers it as a full-pane view of the whole run, in place of the trace, and it is where you
present the experiment's results: the charts, tables, and comparisons someone would want to see
first. It usually reads a **report** entity that the root workflow returns, which references every
result array worth presenting, so one load gives the overview everything it needs.

A good overview shows what matters most, which may come from several steps rather than only the
last. Where there is room, show distributions rather than only averages. Give charts of the same
kind the same axes. And make anything that corresponds to an entity, such as a verdict or a
conversation, a link to it, so a reader can go from a summary to the evidence behind it in one
click.

### Styling

Write every color as one of the viewer's design tokens, such as `var(--fg)`, `var(--bg)`,
`var(--line)`, or `var(--select-accent)`, never as a literal like `#fff`. The viewer has a light
and a dark theme, and the tokens switch with it, while a literal stays the same in both and can end
up unreadable. For the data in a chart, the chart colors `var(--chart-1)` to `var(--chart-8)`
and the kit's `sequentialFill` and `divergingFill` are a default palette that stays readable for
color-blind readers in both themes. Styles local to your package go in CSS Modules
(`*.module.css`) next to the components that use them. The viewer's look in full, and how to
build charts and controls that fit it, is the `view-design.md` guide of the fxtr Claude Code
plugin.

## Building and loading custom renderers

A project's renderers live in a TypeScript package of their own, `views/` in a project created
by `fxtr new`, named in `pyproject.toml` under `[tool.fxtr.views]`. It is an ordinary npm package
that depends on `fxtr-view`, `fxtr-view-kit`, and the `fxtr-view-bundle` build preset. Those
packages are not on npm: fxtr carries them, and the project keeps its own copy in
`views/vendor/fxtr/`, which `fxtr views vendor` refreshes after a fxtr upgrade. What makes it a
fxtr renderer package is how it is built. The package is built with Vite, and its whole
Vite configuration is one call to the preset, naming the bundle's entry file:

```ts views/vite.config.ts theme={null}
import { defineViewBundleConfig } from "fxtr-view-bundle";

export default defineViewBundleConfig({ entry: "src/bundle.ts" });
```

The preset builds the entry into a **view bundle**, a small fxtr-defined format: an ES module
holding the renderers, an optional stylesheet, and a manifest naming both and recording the
version of the view API they were built against. React, jotai, and `fxtr-view` are not bundled
in; the viewer hands its own copies to the bundle when it loads it, so renderers share the
viewer's hook state and context. Everything else the renderers import, the kit, a chart library,
your own components, is bundled in.

The viewer loads the project's bundle when its page loads, checks that the bundle's view API
version matches its own, and adds the bundle's renderers after its defaults. Nothing about a
project is compiled into the viewer itself, so one viewer serves every project, and the bundle is
the only thing that changes between them. `fxtr views build` produces the bundle, and `fxtr view`
rebuilds it on startup when the renderer sources have changed. The steps, from installing the
package's dependencies to what to do when the viewer reports a missing or stale bundle, are in
[Reporting and viewing results](/fxtr/guides/viewing-results).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.