# Serve

How a workbench starts: the femforge bridge, the serve entry points, the command line, the shell, the launcher, the demo and the dev reload loop.

## femforge.workbench

The workbench API from the femforge-workbench package, which the workbench extra installs.

Without the extra, importing this module, and so reading `femforge.workbench`, raises an ImportError that names the install command.

Classes:

- **`FieldControlsPanel`** –

  The viewer's controls: the field, mode and colormap selects, the display switches, the colour range, the body tree and the field-line options.
- **`FieldPlot2DPanel`** –

  The plotly view of a 2D run: every body a Mesh3d with hover values, its legend toggling bodies in step with the controls.
- **`FieldViewerPanel`** –

  The 3D view of the field scene, mirrored in the browser by default; its visibility feeds the controller.
- **`FigurePanel`** –

  Shows one plotly figure; `update` swaps it (uirevision keeps brushes and cameras), `bind` routes its events to handlers.
- **`Panel`** –

  One tab: its title, the builder that makes its component once the shell exists, and its share of the axis.
- **`PanelComponent`** –

  A dock panel: its template is built once inside `layout`; state variables go through `key`.
- **`RunBrowserPanel`** –

  The run index as a virtual table: text, status, tag and physics filters narrow it in polars, a row click selects the run.
- **`RunDetailPanel`** –

  One run bundle: its manifest summary, stage timeline, results, artifact inputs, files and retention.
- **`Split`** –

  Panels, tab groups or splits sharing one axis by weight; the axis alternates with nesting, horizontal at the root.
- **`TablePanel`** –

  A read-only table whose rows go over once, the client sorting, filtering, faceting, reordering, hiding and grouping them; `lead` orders the leading columns and pins the first.
- **`Tabs`** –

  Panels sharing one tab group, the first shown.
- **`Window`** –

  Where a served workbench opens.
- **`Workbench`** –

  One trame server with the header, the dock and the panels of a named workbench.

Functions:

- **`artifact_store`** –

  The store the artifact panels read: `FEMFORGE_ARTIFACTS` when set, else the root the index rows name.
- **`field_viewer`** –

  The field viewer dock: the controls beside the 3D view and the 2D plot as tabs.
- **`show`** –

  Serves a workbench until its window closes: a Split as built, a factory or card rebuilt on every save, or the launcher when none is given.

### FieldControlsPanel

```python
FieldControlsPanel(app: Workbench, id: str, title: str, controller: FieldViewerController)
```

Bases: `PanelComponent`

The viewer's controls: the field, mode and colormap selects, the display switches, the colour range, the body tree and the field-line options.

Parameters:

- **`app`** (`Workbench`) –

  The workbench shell the panel is built in.
- **`id`** (`str`) –

  The panel id, which names its template and namespaces its state.
- **`title`** (`str`) –

  The panel's tab title.
- **`controller`** (`FieldViewerController`) –

  The viewer's controller, shared with the 3D view and the 2D plot.

Methods:

- **`build_run_picker`** –

  The indexed-run select and the bundle-path field, both driving the same run variable; built into the app bar, its one place.
- **`opening`** –

  The bundle-path field's Enter: the text is committed and opened in one client-side step.
- **`build_colormap`** –

  The field colormap picker: searchable, filtered by type and favorites, the arrow keys stepping through it while it is closed.
- **`build_body_colormap`** –

  The body colormap picker as a swatch in the color column, reading Same as field while no body takes it.
- **`slider`** –

  A slider over `model` whose thumbs move with the drag and whose scene follows the release.
- **`build_range`** –

  The Auto range switch over the two-thumb colour range across the data range, its ends read out above it.
- **`build_opacity`** –

  The Opacity mapping switch and, while it is on, the opacity at each end of the color range; the body table holds the overall opacity.
- **`build_field`** –

  The field section: what is shown and how it is colored, from the field select to the opacity mapping.
- **`build_bodies`** –

  The body table: the All bodies row with the body colormap, then a row per body and per material group, every row checkbox, name, color and opacity.
- **`build_lines`** –

  The field-line section: the switch, the seeds, the region and the two integration sliders.
- **`build`** –

  Builds the busy bar over the field, field-line and body sections.
- **`dispose`** –

  Detaches from the controller, then releases the panel.

#### build\_run\_picker

```python
build_run_picker() -> None
```

The indexed-run select and the bundle-path field, both driving the same run variable; built into the app bar, its one place.

#### opening

```python
opening() -> Callback
```

The bundle-path field's Enter: the text is committed and opened in one client-side step.

#### build\_colormap

```python
build_colormap() -> None
```

The field colormap picker: searchable, filtered by type and favorites, the arrow keys stepping through it while it is closed.

#### build\_body\_colormap

```python
build_body_colormap() -> None
```

The body colormap picker as a swatch in the color column, reading Same as field while no body takes it.

#### slider

```python
slider(model: FieldVar, label: str, bounds: tuple[float, float | Bind], step: float) -> None
```

A slider over `model` whose thumbs move with the drag and whose scene follows the release.

Parameters:

- **`model`** (`FieldVar`) –

  The state variable holding the thumb values.
- **`label`** (`str`) –

  The slider's accessible name.
- **`bounds`** (`tuple[float, float | Bind]`) –

  The low and high ends; the high end may be a client expression.
- **`step`** (`float`) –

  The step between values.

#### build\_range

```python
build_range() -> None
```

The Auto range switch over the two-thumb colour range across the data range, its ends read out above it.

#### build\_opacity

```python
build_opacity() -> None
```

The Opacity mapping switch and, while it is on, the opacity at each end of the color range; the body table holds the overall opacity.

#### build\_field

```python
build_field() -> None
```

The field section: what is shown and how it is colored, from the field select to the opacity mapping.

#### build\_bodies

```python
build_bodies() -> None
```

The body table: the All bodies row with the body colormap, then a row per body and per material group, every row checkbox, name, color and opacity.

#### build\_lines

```python
build_lines() -> None
```

The field-line section: the switch, the seeds, the region and the two integration sliders.

#### build

```python
build() -> None
```

Builds the busy bar over the field, field-line and body sections.

#### dispose

```python
dispose() -> None
```

Detaches from the controller, then releases the panel.

### FieldPlot2DPanel

```python
FieldPlot2DPanel(app: Workbench, id: str, title: str, controller: FieldViewerController)
```

Bases: `FigurePanel`

The plotly view of a 2D run: every body a Mesh3d with hover values, its legend toggling bodies in step with the controls.

Parameters:

- **`app`** (`Workbench`) –

  The workbench shell the panel is built in.
- **`id`** (`str`) –

  The panel id, which names its template and namespaces its state.
- **`title`** (`str`) –

  The panel's tab title.
- **`controller`** (`FieldViewerController`) –

  The viewer's controller, shared with the controls and the 3D view.

Methods:

- **`show`** –

  Replaces the figure, remembering which body each mesh trace is.
- **`clear`** –

  Empties the figure.
- **`on_visible`** –

  Tells the controller whether the plot is on screen.
- **`dispose`** –

  Detaches from the controller, then releases the panel.
- **`on_restyle`** –

  Mirrors a legend toggle into the visible-bodies state, so the tree and the 3D view follow.

#### show

```python
show(figure: Figure, bodies: Sequence[str]) -> None
```

Replaces the figure, remembering which body each mesh trace is.

#### clear

```python
clear() -> None
```

Empties the figure.

#### on\_visible

```python
on_visible(visible: bool) -> None
```

Tells the controller whether the plot is on screen.

#### dispose

```python
dispose() -> None
```

Detaches from the controller, then releases the panel.

#### on\_restyle

```python
on_restyle(payload: object) -> None
```

Mirrors a legend toggle into the visible-bodies state, so the tree and the 3D view follow.

### FieldViewerPanel

```python
FieldViewerPanel(app: Workbench, id: str, title: str, controller: FieldViewerController)
```

Bases: `ScenePanel`

The 3D view of the field scene, mirrored in the browser by default; its visibility feeds the controller.

Parameters:

- **`app`** (`Workbench`) –

  The workbench shell the panel is built in.
- **`id`** (`str`) –

  The panel id, which names its template and namespaces its state.
- **`title`** (`str`) –

  The panel's tab title.
- **`controller`** (`FieldViewerController`) –

  The viewer's controller, shared with the controls and the 2D plot.

Methods:

- **`clear`** –

  Empties the plotter and pushes the empty frame.
- **`on_visible`** –

  Tells the scene and the controller whether the view is on screen.
- **`dispose`** –

  Detaches from the controller, then frees the view.

#### clear

```python
clear() -> None
```

Empties the plotter and pushes the empty frame.

#### on\_visible

```python
on_visible(visible: bool) -> None
```

Tells the scene and the controller whether the view is on screen.

#### dispose

```python
dispose() -> None
```

Detaches from the controller, then frees the view.

### FigurePanel

```python
FigurePanel(app: Workbench, id: str, title: str, figure: Figure | None = None, *, mode_bar: bool = True)
```

Bases: `PanelComponent`

Shows one plotly figure; `update` swaps it (uirevision keeps brushes and cameras), `bind` routes its events to handlers.

Parameters:

- **`app`** (`Workbench`) –

  The workbench shell the panel is built in.
- **`id`** (`str`) –

  The panel id, which names its template and namespaces its state.
- **`title`** (`str`) –

  The panel's tab title.
- **`figure`** (`Figure | None`, default: `None` ) –

  The figure shown first; None starts empty.
- **`mode_bar`** (`bool`, default: `True` ) –

  Whether plotly's modebar shows.

Methods:

- **`build`** –

  Pushes the figure into the state and builds the plot bound to it.
- **`encode`** –

  The figure as plain JSON with the modebar switch and the export name it carries.
- **`update`** –

  Replaces the figure.
- **`bind`** –

  Routes a plotly event to `handler` with the event payload as its one argument.

#### build

```python
build() -> None
```

Pushes the figure into the state and builds the plot bound to it.

#### encode

```python
encode(figure: Figure) -> Json
```

The figure as plain JSON with the modebar switch and the export name it carries.

#### update

```python
update(figure: Figure) -> None
```

Replaces the figure.

#### bind

```python
bind(event: PlotEvent, handler: EventHandler) -> None
```

Routes a plotly event to `handler` with the event payload as its one argument.

### Panel

```python
Panel(name: str, build: Builder, weight: float = 1, id: str | None = None)
```

One tab: its title, the builder that makes its component once the shell exists, and its share of the axis.

Attributes:

- **`name`** (`str`) –

  The tab title, from which the panel id is derived when `id` is None.
- **`build`** (`Builder`) –

  Makes the panel's component from the shell, the panel id and the title, once the shell exists.
- **`weight`** (`float`) –

  `[-]`: The panel's share of its parent's axis, relative to its siblings' weights.
- **`id`** (`str | None`) –

  The panel id; None derives it from `name`.
- **`panel_id`** (`str`) –

  The id the dock knows the panel by: `id` when given, else `name` lowercased with dashes.

#### name

```python
name: str
```

The tab title, from which the panel id is derived when `id` is None.

#### build

```python
build: Builder
```

Makes the panel's component from the shell, the panel id and the title, once the shell exists.

#### weight

```python
weight: float = 1
```

`[-]`: The panel's share of its parent's axis, relative to its siblings' weights.

#### id

```python
id: str | None = None
```

The panel id; None derives it from `name`.

#### panel\_id

```python
panel_id: str
```

The id the dock knows the panel by: `id` when given, else `name` lowercased with dashes.

### PanelComponent

```python
PanelComponent(app: Workbench, id: str, title: str)
```

Bases: `TrameComponent`

A dock panel: its template is built once inside `layout`; state variables go through `key`.

Parameters:

- **`app`** (`Workbench`) –

  The workbench shell the panel is built in.
- **`id`** (`str`) –

  The panel id, which names its template and namespaces its state.
- **`title`** (`str`) –

  The panel's tab title.

Methods:

- **`build`** –

  Adds the panel's widgets to its layout.
- **`key`** –

  A state variable or trigger name namespaced to this panel.
- **`status`** –

  Reports in the shell's status line.
- **`refresh`** –

  Pushes the template again after its widgets changed.
- **`callback`** –

  A client event handler calling `fn` with the JS `args`, unregistered again when the panel is disposed.
- **`listen`** –

  Registers a state listener like `state.change`, one the panel drops again when disposed.
- **`dispose`** –

  Releases what the panel registered on the server once it left the dock: triggers, state listeners and controller slots.
- **`succeed`** –

  Called on a panel rebuilt under the id of `previous`, already disposed, to carry over what a fresh build would lose.
- **`on_visible`** –

  Called when dockview shows or hides the panel.
- **`on_layout_changed`** –

  Called after the dock re-laid out its groups.

Attributes:

- **`listeners`** (`list[Listener]`) –

  The state listeners registered through `listen`, dropped again by `dispose`.
- **`bound`** (`list[Handler]`) –

  The callables handed to `callback`, whose triggers `dispose` unregisters.
- **`legacy`** (`dict[Carry, object]`) –

  What `dispose` leaves for a successor; read by its `succeed`, never through the reloadable classes.

#### listeners

```python
listeners: list[Listener] = []
```

The state listeners registered through `listen`, dropped again by `dispose`.

#### bound

```python
bound: list[Handler] = []
```

The callables handed to `callback`, whose triggers `dispose` unregisters.

#### legacy

```python
legacy: dict[Carry, object] = {}
```

What `dispose` leaves for a successor; read by its `succeed`, never through the reloadable classes.

#### build

```python
build() -> None
```

Adds the panel's widgets to its layout.

#### key

```python
key(name: str) -> str
```

A state variable or trigger name namespaced to this panel.

#### status

```python
status(message: str) -> None
```

Reports in the shell's status line.

#### refresh

```python
refresh() -> None
```

Pushes the template again after its widgets changed.

#### callback

```python
callback(fn: Handler, args: str | None = None) -> Callback
```

A client event handler calling `fn` with the JS `args`, unregistered again when the panel is disposed.

#### listen

```python
listen(*names: str) -> Callable[[L], L]
```

Registers a state listener like `state.change`, one the panel drops again when disposed.

#### dispose

```python
dispose() -> None
```

Releases what the panel registered on the server once it left the dock: triggers, state listeners and controller slots.

#### succeed

```python
succeed(previous: PanelComponent) -> None
```

Called on a panel rebuilt under the id of `previous`, already disposed, to carry over what a fresh build would lose.

#### on\_visible

```python
on_visible(visible: bool) -> None
```

Called when dockview shows or hides the panel.

#### on\_layout\_changed

```python
on_layout_changed() -> None
```

Called after the dock re-laid out its groups.

### RunBrowserPanel

```python
RunBrowserPanel(app: Workbench, id: str, title: str, *, telemetry: Telemetry)
```

Bases: `TablePanel`

The run index as a virtual table: text, status, tag and physics filters narrow it in polars, a row click selects the run.

Parameters:

- **`app`** (`Workbench`) –

  The workbench shell the panel is built in.
- **`id`** (`str`) –

  The panel id, which names its template and namespaces its state.
- **`title`** (`str`) –

  The panel's tab title.
- **`telemetry`** (`Telemetry`) –

  The telemetry root whose run index the table lists.

Methods:

- **`query`** –

  The run index query before the toolbar's filters.
- **`index`** –

  The narrowed index frame, with the live claims joined on.
- **`table_frame`** –

  One index frame as the table's rows, remembering which of its runs are claimed and where their bundles live.
- **`vocabulary`** –

  The distinct non-null values of one index column, for a filter's choices.
- **`tag_vocabulary`** –

  Every `key=value` pair the indexed runs carry, deduplicated.
- **`build`** –

  Builds the filter toolbar over the run table and listens to the filters.
- **`count`** –

  The status text: how many runs the table holds and how many are live.
- **`chosen`** –

  The one value a filter names, or nothing while it names `any`.
- **`on_narrowing`** –

  Re-queries the index after a toolbar filter changed.
- **`poll`** –

  Refreshed rows while a run is claimed or a claim just came or went, else None: an idle table costs one directory glob.
- **`publish`** –

  Writes one index frame into the table.
- **`on_pick`** –

  Publishes the clicked run for the detail panel.
- **`on_visible`** –

  Runs the live refresh only while the panel is on screen.
- **`dispose`** –

  Stops the live refresh, then releases the panel.

#### query

```python
query() -> RunQuery
```

The run index query before the toolbar's filters.

#### index

```python
index() -> DataFrame
```

The narrowed index frame, with the live claims joined on.

#### table\_frame

```python
table_frame(frame: DataFrame) -> DataFrame
```

One index frame as the table's rows, remembering which of its runs are claimed and where their bundles live.

#### vocabulary

```python
vocabulary(column: RunColumn) -> list[str]
```

The distinct non-null values of one index column, for a filter's choices.

#### tag\_vocabulary

```python
tag_vocabulary() -> list[str]
```

Every `key=value` pair the indexed runs carry, deduplicated.

#### build

```python
build() -> None
```

Builds the filter toolbar over the run table and listens to the filters.

#### count

```python
count() -> str
```

The status text: how many runs the table holds and how many are live.

#### chosen

```python
chosen(name: BrowserVar) -> tuple[str, ...]
```

The one value a filter names, or nothing while it names `any`.

#### on\_narrowing

```python
on_narrowing(**_: object) -> None
```

Re-queries the index after a toolbar filter changed.

#### poll

```python
poll() -> DataFrame | None
```

Refreshed rows while a run is claimed or a claim just came or went, else None: an idle table costs one directory glob.

#### publish

```python
publish(frame: DataFrame | None) -> None
```

Writes one index frame into the table.

#### on\_pick

```python
on_pick(row: Row) -> None
```

Publishes the clicked run for the detail panel.

#### on\_visible

```python
on_visible(visible: bool) -> None
```

Runs the live refresh only while the panel is on screen.

#### dispose

```python
dispose() -> None
```

Stops the live refresh, then releases the panel.

### RunDetailPanel

```python
RunDetailPanel(app: Workbench, id: str, title: str, *, telemetry: Telemetry, store: LocalArtifactStore)
```

Bases: `PanelComponent`

One run bundle: its manifest summary, stage timeline, results, artifact inputs, files and retention.

Parameters:

- **`app`** (`Workbench`) –

  The workbench shell the panel is built in.
- **`id`** (`str`) –

  The panel id, which names its template and namespaces its state.
- **`title`** (`str`) –

  The panel's tab title.
- **`telemetry`** (`Telemetry`) –

  The telemetry root whose run index locates a run's bundle.
- **`store`** (`LocalArtifactStore`) –

  The artifact store a bundle's inputs resolve in and rehydrate from.

Methods:

- **`build`** –

  Builds the toolbar, the bundle's sections and the purge dialog, then follows the shared run selection.
- **`build_header`** –

  Builds the title, the status badge, the bundle path and the manifest summary.
- **`build_stages`** –

  Builds the stage timeline, shown once the run has stages.
- **`build_scalars`** –

  Builds the result scalars, shown once the run has a result.
- **`build_tables`** –

  Builds the component forces, currents, inputs, files and retention passes, each shown once it has rows.
- **`build_purge_dialog`** –

  Builds the purge dialog: a preset, a rule per retention class, and the preview and delete actions.
- **`rule_key`** –

  The state variable holding one retention class's rule in the purge dialog.
- **`bytes_key`** –

  The state variable holding one retention class's planned size in the purge dialog.
- **`load_preset`** –

  Fills every per-class rule from a preset and clears the last preview.
- **`policy`** –

  The policy the dialog's per-class choices describe.
- **`on_preset`** –

  Fills the per-class rules from the preset just chosen.
- **`locate`** –

  The bundle directory of an indexed run, or None when the index does not know it.
- **`load`** –

  Read one bundle into what the panel shows; the whole read happens off the server loop.
- **`summary`** –

  The manifest's identity and provenance as label/value pairs.
- **`publish`** –

  Writes one view into the panel's state variables.
- **`on_selected`** –

  Shows the run the shared selection names.
- **`show`** –

  Loads a bundle off the server loop and publishes it, re-reading it while it solves; a failed read lands in the status line.
- **`reread`** –

  The shown run read again, named so a reply that outlives its selection is dropped.
- **`refresh`** –

  Publishes a re-read of the shown run, and ends the watch once the run has.
- **`selected`** –

  The run the panel shows, reporting in the status line when there is none.
- **`open_fields`** –

  Hands the bundle to the field viewer, or says so when no viewer is registered.
- **`open_ref`** –

  Selects a store-sourced input, so the artifact detail follows.
- **`reveal`** –

  Opens the bundle's directory in the file manager.
- **`rehydrate`** –

  Fetches the bundle's store-sourced inputs back into its directory.
- **`open_purge`** –

  Opens the purge dialog when the panel shows a run.
- **`close_purge`** –

  Closes the purge dialog.
- **`on_dialog`** –

  Follows the dialog the client closed with Escape or the backdrop.
- **`preview_purge`** –

  Plans the dialog's policy against the bundle and shows the bytes each class would free.
- **`apply_purge`** –

  Applies the previewed policy to the bundle and reloads the panel from what is left.
- **`succeed`** –

  Shows the selected run again, which a fresh build would leave blank.
- **`dispose`** –

  Stops the watch, then releases the panel.

Attributes:

- **`run_id`** –

  The run the panel shows, which the watch re-reads while it solves.
- **`showing`** (`Task[None] | None`) –

  The show a rebuild started, held until it finishes.

#### run\_id

```python
run_id = ''
```

The run the panel shows, which the watch re-reads while it solves.

#### showing

```python
showing: Task[None] | None = None
```

The show a rebuild started, held until it finishes.

#### build

```python
build() -> None
```

Builds the toolbar, the bundle's sections and the purge dialog, then follows the shared run selection.

#### build\_header

```python
build_header() -> None
```

Builds the title, the status badge, the bundle path and the manifest summary.

#### build\_stages

```python
build_stages() -> None
```

Builds the stage timeline, shown once the run has stages.

#### build\_scalars

```python
build_scalars() -> None
```

Builds the result scalars, shown once the run has a result.

#### build\_tables

```python
build_tables() -> None
```

Builds the component forces, currents, inputs, files and retention passes, each shown once it has rows.

#### build\_purge\_dialog

```python
build_purge_dialog() -> None
```

Builds the purge dialog: a preset, a rule per retention class, and the preview and delete actions.

#### rule\_key

```python
rule_key(file_class: RunFile) -> str
```

The state variable holding one retention class's rule in the purge dialog.

#### bytes\_key

```python
bytes_key(file_class: RunFile) -> str
```

The state variable holding one retention class's planned size in the purge dialog.

#### load\_preset

```python
load_preset(preset: Preset) -> None
```

Fills every per-class rule from a preset and clears the last preview.

#### policy

```python
policy() -> RetentionPolicy
```

The policy the dialog's per-class choices describe.

#### on\_preset

```python
on_preset(**_: object) -> None
```

Fills the per-class rules from the preset just chosen.

#### locate

```python
locate(run_id: str) -> Path | None
```

The bundle directory of an indexed run, or None when the index does not know it.

#### load

```python
load(run_id: str) -> RunView
```

Read one bundle into what the panel shows; the whole read happens off the server loop.

#### summary

```python
summary(manifest: RunManifest) -> list[Row]
```

The manifest's identity and provenance as label/value pairs.

#### publish

```python
publish(view: RunView) -> None
```

Writes one view into the panel's state variables.

#### on\_selected

```python
on_selected(**_: object) -> None
```

Shows the run the shared selection names.

#### show

```python
show(run_id: str) -> None
```

Loads a bundle off the server loop and publishes it, re-reading it while it solves; a failed read lands in the status line.

#### reread

```python
reread() -> tuple[str, RunView]
```

The shown run read again, named so a reply that outlives its selection is dropped.

#### refresh

```python
refresh(reply: tuple[str, RunView]) -> None
```

Publishes a re-read of the shown run, and ends the watch once the run has.

#### selected

```python
selected() -> Run | None
```

The run the panel shows, reporting in the status line when there is none.

#### open\_fields

```python
open_fields() -> None
```

Hands the bundle to the field viewer, or says so when no viewer is registered.

#### open\_ref

```python
open_ref(path: str) -> None
```

Selects a store-sourced input, so the artifact detail follows.

#### reveal

```python
reveal() -> None
```

Opens the bundle's directory in the file manager.

#### rehydrate

```python
rehydrate() -> None
```

Fetches the bundle's store-sourced inputs back into its directory.

#### open\_purge

```python
open_purge() -> None
```

Opens the purge dialog when the panel shows a run.

#### close\_purge

```python
close_purge() -> None
```

Closes the purge dialog.

#### on\_dialog

```python
on_dialog(open_: object) -> None
```

Follows the dialog the client closed with Escape or the backdrop.

#### preview\_purge

```python
preview_purge() -> None
```

Plans the dialog's policy against the bundle and shows the bytes each class would free.

#### apply\_purge

```python
apply_purge() -> None
```

Applies the previewed policy to the bundle and reloads the panel from what is left.

#### succeed

```python
succeed(previous: PanelComponent) -> None
```

Shows the selected run again, which a fresh build would leave blank.

#### dispose

```python
dispose() -> None
```

Stops the watch, then releases the panel.

### Split

```python
Split(children: Sequence[Node], weight: float = 1)
```

Panels, tab groups or splits sharing one axis by weight; the axis alternates with nesting, horizontal at the root.

Attributes:

- **`children`** (`Sequence[Node]`) –

  The panels, tab groups and splits along the axis, in order.
- **`weight`** (`float`) –

  `[-]`: The split's share of its parent's axis, relative to its siblings' weights; ignored at the root.

#### children

```python
children: Sequence[Node]
```

The panels, tab groups and splits along the axis, in order.

#### weight

```python
weight: float = 1
```

`[-]`: The split's share of its parent's axis, relative to its siblings' weights; ignored at the root.

### TablePanel

```python
TablePanel(app: Workbench, id: str, title: str, frame: DataFrame, *, grouped: bool = False, filterable: bool = False, lead: Sequence[str] = (), hidden: Sequence[str] = (), pick: Pick | None = None, selectable: Selectable = NO_SELECTION, select: Select | None = None, virtual: bool = False)
```

Bases: `PanelComponent`

A read-only table whose rows go over once, the client sorting, filtering, faceting, reordering, hiding and grouping them; `lead` orders the leading columns and pins the first.

Parameters:

- **`app`** (`Workbench`) –

  The workbench shell the panel is built in.
- **`id`** (`str`) –

  The panel id, which names its template and namespaces its state.
- **`title`** (`str`) –

  The panel's tab title.
- **`frame`** (`DataFrame`) –

  The rows to show.
- **`grouped`** (`bool`, default: `False` ) –

  Groups the rows by the `group` label column that `group_rows` adds.
- **`filterable`** (`bool`, default: `False` ) –

  Shows the column filters.
- **`lead`** (`Sequence[str]`, default: `()` ) –

  The columns sent first, in order; the first is pinned.
- **`hidden`** (`Sequence[str]`, default: `()` ) –

  The frame's columns sent with the rows but given no column, so a pick still sees them.
- **`pick`** (`Pick | None`, default: `None` ) –

  Receives a clicked row; None leaves the rows unclickable.
- **`selectable`** (`Selectable`, default: `NO_SELECTION` ) –

  Whether the user selects no rows, one or several.
- **`select`** (`Select | None`, default: `None` ) –

  Receives the selected rows on every change; None ignores the selection.
- **`virtual`** (`bool`, default: `False` ) –

  Renders only the rows in view, for frames too long to draw whole.

Methods:

- **`build`** –

  Pushes the columns, rows and options into the state and builds the table bound to them.
- **`names`** –

  The columns sent: `lead` first, the group label while grouped, then the rest without `hidden`; the client applies the persisted order.
- **`columns`** –

  The column specs: numbers sort, filter and facet as numbers; the group label never facets.
- **`defaults`** –

  The authored layout: grouped by the label column while grouped.
- **`options`** –

  The table options: the layout the last session left, the authored defaults, and the filter, selection and virtual switches.
- **`set_rows`** –

  Replaces the rows; the columns follow a changed schema, the client keeping its order, visibility, sort and filters.
- **`restore`** –

  Applies a layout document on every client, the authored defaults for None.
- **`reset_layout`** –

  Returns the table to the authored layout on every client.
- **`scroll_to_end`** –

  Scrolls the table to its last row.
- **`scroll_to_row`** –

  Scrolls the row at `index` of the shown rows into view.
- **`open_config`** –

  Shows, or hides, the column configuration panel.
- **`on_layout`** –

  Persists the layout the client reports and pushes it back, so a page that reloads is not served the layout this one replaced.
- **`on_click`** –

  Hands a clicked data row to the pick handler.
- **`on_select`** –

  Hands the selected rows to the select handler.

Attributes:

- **`saved`** (`Layout | None`) –

  The layout the last session left, handed to the client through the options.
- **`store_key`** (`str`) –

  The persisted document of this panel's layout.

#### saved

```python
saved: Layout | None = None
```

The layout the last session left, handed to the client through the options.

#### store\_key

```python
store_key: str
```

The persisted document of this panel's layout.

#### build

```python
build() -> None
```

Pushes the columns, rows and options into the state and builds the table bound to them.

#### names

```python
names() -> list[str]
```

The columns sent: `lead` first, the group label while grouped, then the rest without `hidden`; the client applies the persisted order.

#### columns

```python
columns() -> list[ColumnSpec]
```

The column specs: numbers sort, filter and facet as numbers; the group label never facets.

#### defaults

```python
defaults() -> LayoutDefaults
```

The authored layout: grouped by the label column while grouped.

#### options

```python
options() -> TableOptions
```

The table options: the layout the last session left, the authored defaults, and the filter, selection and virtual switches.

#### set\_rows

```python
set_rows(frame: DataFrame) -> None
```

Replaces the rows; the columns follow a changed schema, the client keeping its order, visibility, sort and filters.

#### restore

```python
restore(layout: Layout | None) -> None
```

Applies a layout document on every client, the authored defaults for None.

#### reset\_layout

```python
reset_layout() -> None
```

Returns the table to the authored layout on every client.

#### scroll\_to\_end

```python
scroll_to_end() -> None
```

Scrolls the table to its last row.

#### scroll\_to\_row

```python
scroll_to_row(index: int) -> None
```

Scrolls the row at `index` of the shown rows into view.

#### open\_config

```python
open_config(open: bool = True) -> None
```

Shows, or hides, the column configuration panel.

#### on\_layout

```python
on_layout(document: object) -> None
```

Persists the layout the client reports and pushes it back, so a page that reloads is not served the layout this one replaced.

#### on\_click

```python
on_click(row: object) -> None
```

Hands a clicked data row to the pick handler.

#### on\_select

```python
on_select(selection: object) -> None
```

Hands the selected rows to the select handler.

### Tabs

```python
Tabs(panels: Sequence[Panel], weight: float = 1)
```

Panels sharing one tab group, the first shown.

Attributes:

- **`panels`** (`Sequence[Panel]`) –

  The panels sharing the tab group, the first one shown.
- **`weight`** (`float`) –

  `[-]`: The group's share of its parent's axis, relative to its siblings' weights.

#### panels

```python
panels: Sequence[Panel]
```

The panels sharing the tab group, the first one shown.

#### weight

```python
weight: float = 1
```

`[-]`: The group's share of its parent's axis, relative to its siblings' weights.

### Window

Bases: `StrEnum`

Where a served workbench opens.

Attributes:

- **`BROWSER`** –

  A tab in the default browser.
- **`APP`** –

  A Chrome/Edge app window; closing it stops the server.
- **`WEBVIEW`** –

  pywebview, from the app extra.

#### BROWSER

```python
BROWSER = 'browser'
```

A tab in the default browser.

#### APP

```python
APP = 'app'
```

A Chrome/Edge app window; closing it stops the server.

#### WEBVIEW

```python
WEBVIEW = 'webview'
```

pywebview, from the app extra.

### Workbench

```python
Workbench(name: str, *, palette: Palette | None = None, title: str = 'femforge workbench', server: Server | None = None, persist: UiStateStore | None = None, user: str | None = None, catalogue: Sequence[WorkbenchSpec] | None = None)
```

Bases: `TrameApp`

One trame server with the header, the dock and the panels of a named workbench.

Parameters:

- **`name`** (`str`) –

  The workbench name, which names the trame server and scopes the persisted state.
- **`palette`** (`Palette | None`, default: `None` ) –

  The colours the shell and its panels paint in; None loads the dist's dark palette.
- **`title`** (`str`, default: `'femforge workbench'` ) –

  The title the header and the browser tab show.
- **`server`** (`Server | None`, default: `None` ) –

  The trame server to run on; None makes one named after `name`.
- **`persist`** (`UiStateStore | None`, default: `None` ) –

  Where layouts and opened cards persist; None keeps them in JSON files under the per-user config directory.
- **`user`** (`str | None`, default: `None` ) –

  The user the persisted state is scoped to; None takes the OS user.
- **`catalogue`** (`Sequence[WorkbenchSpec] | None`, default: `None` ) –

  The cards the title menu and a launcher panel list; None lists the discovered ones.

Methods:

- **`load_catalogue`** –

  The discovered specs, empty with a warning when an entry point is broken.
- **`build_ui`** –

  The theme switch, the header and the dock, whose panel bodies are the server templates the placements name.
- **`theme`** –

  The scheme switch bound to the dark state; mounted in the root and in every panel, so a standalone ?ui= page and a popout paint it too.
- **`build_switcher`** –

  The title as a menu over the catalogue.
- **`add`** –

  Builds every panel's component and template; the dock arranges them once the client reports ready.
- **`open`** –

  Adds a Split's panels to the running dock, building those not built yet, and activates the first.
- **`open_spec`** –

  Opens a dock card: its target builds the Split, remembered under the card's name so the next start rebuilds it.
- **`opened_first`** –

  The first panel id of the open card of that name, None when it is not open.
- **`close`** –

  Takes an opened card out of the dock and the shell and stops rebuilding it on save.
- **`close_all`** –

  Closes every opened card.
- **`discard`** –

  Takes an opened card out of the dock and the shell: its panels are removed and disposed, its header widgets dropped, the card forgotten.
- **`register`** –

  Builds each leaf and registers it before the next is built, so a panel can see the ones already standing (a second scene panel must know a mirror is taken).
- **`retire`** –

  Disposes the built panels of those ids and drops them from the shell.
- **`drop_header`** –

  Removes a Split's widgets from the header, those still there.
- **`watch`** –

  Rebuilds the spec's Split after every .py save under its roots once the server runs; a spec of the shell core is left to the dev supervisor.
- **`unwatch`** –

  Stops rebuilding a card on save.
- **`rebuild`** –

  Swaps a card's panels for a fresh build under the same ids, the dock untouched; other ids close and reopen the card, and a card no longer open reopens.
- **`replace_root`** –

  Rebuilds the authored Split in place; changed ids re-lay the dock out from the authored arrangement.
- **`replace_card`** –

  Rebuilds an open card in place; changed ids close and reopen it, and a card not open opens.
- **`swap`** –

  Disposes the retired panels, then builds the Split's leaves, whose templates overwrite those of the same ids; returns the header widgets the build added.
- **`find_spec`** –

  The catalogue card of that name, None when the catalogue has none.
- **`restore_opened`** –

  Reopens the cards the last session left in the dock, dropping any whose card is gone or whose target no longer builds a Split.
- **`remember_opened`** –

  Persists the cards open in the dock, in opening order, and names them in the state for the launcher's cards.
- **`prune_opened`** –

  Forgets an opened Split whose first panel is missing from a settled layout, one that shows every panel handed to the dock since the last.
- **`place`** –

  add\_panel for every placement of a Split, its first panel within `reference`'s group when given; ids already in the dock are left alone.
- **`panel`** –

  The built component of a panel, checked to be of `kind`.
- **`status`** –

  Writes the status line, flushing so it also lands from outside a handler.
- **`marshal`** –

  Runs `fn(*args)` on the server loop from any thread.
- **`when_ready`** –

  Calls `fn` on the server loop: now when the server runs, else once it is ready.
- **`authored_layout`** –

  The dockview layout of the authored Split; RuntimeError before `add`.
- **`on_dock_ready`** –

  Restores the saved arrangement when it names the authored panels, else applies the authored one; add\_panel then fills any gap.
- **`on_layout_changed`** –

  Persists the layout the dock reports, forgets the cards it no longer shows and tells every panel.
- **`on_panel_removed`** –

  Disposes the panel whose tab the user closed; the dock reports only a user close here, never its own teardown, and Reset layout builds a closed panel again.
- **`rebuild_closed`** –

  Rebuilds every card a closed tab left incomplete, header widgets included, so an arrangement naming its panels finds them live.
- **`on_panel_visibility`** –

  Records that the dock showed or hid a panel and tells that panel.
- **`reset_layout`** –

  Rebuilds the authored arrangement, which also recovers a group orphaned by a killed popout window.
- **`open_workbench`** –

  Hands a catalogue selection to the open\_workbench hook, or names the command that opens it.
- **`on_server_ready`** –

  Takes the server loop and opens the app window when one was asked for.
- **`on_server_exited`** –

  Stops every reloader and flushes the persisted state.
- **`open_app_window`** –

  Chrome (Edge fallback) as an app window on its own profile; the server stops when the window process exits. Without either, a system browser tab.
- **`install_break_handler`** –

  Makes a break or termination signal a clean stop, so the exit hooks flush the persisted state; the --dev supervisor stops its child this way.
- **`on_break`** –

  Schedules the server's stop on its loop, which ends the main loop and runs the exit hooks.
- **`run`** –

  Serves until stopped, or until an app window closes.

Attributes:

- **`authored`** (`Opened | None`) –

  The Split add() built, named after the spec it serves when it is to be rebuilt on save.
- **`opened`** (`dict[str, Opened]`) –

  Splits opened into the running dock by their first panel id.
- **`pending`** (`set[str]`) –

  Panel ids handed to the dock since a layout last showed them all; a layout still missing one is a placement in flight, not a close.
- **`on_screen`** (`set[str]`) –

  Panel ids the dock last reported visible; the dock reports a panel only when it mounts, so a panel rebuilt in place learns it from here.
- **`catalogue`** –

  The cards the title menu and a launcher panel list: the discovered ones unless the constructor was given its own.
- **`header`** –

  The header's slot for a workbench's own controls: build widgets inside `with app.header:` and flush\_content() after a later change.
- **`root`** (`Split | None`) –

  The authored Split, None before `add`.
- **`panel_ids`** (`list[str]`) –

  The ids of the built panels, in build order.

#### authored

```python
authored: Opened | None = None
```

The Split add() built, named after the spec it serves when it is to be rebuilt on save.

#### opened

```python
opened: dict[str, Opened] = {}
```

Splits opened into the running dock by their first panel id.

#### pending

```python
pending: set[str] = set()
```

Panel ids handed to the dock since a layout last showed them all; a layout still missing one is a placement in flight, not a close.

#### on\_screen

```python
on_screen: set[str] = set()
```

Panel ids the dock last reported visible; the dock reports a panel only when it mounts, so a panel rebuilt in place learns it from here.

#### catalogue

```python
catalogue = list(catalogue) if catalogue is not None else self.load_catalogue()
```

The cards the title menu and a launcher panel list: the discovered ones unless the constructor was given its own.

#### header

```python
header = DivLayout(self.server, template_name=str(Template.HEADER), classes=str(Cls.HEADER_SLOT))
```

The header's slot for a workbench's own controls: build widgets inside `with app.header:` and flush\_content() after a later change.

#### root

```python
root: Split | None
```

The authored Split, None before `add`.

#### panel\_ids

```python
panel_ids: list[str]
```

The ids of the built panels, in build order.

#### load\_catalogue

```python
load_catalogue() -> list[WorkbenchSpec]
```

The discovered specs, empty with a warning when an entry point is broken.

#### build\_ui

```python
build_ui() -> DivLayout
```

The theme switch, the header and the dock, whose panel bodies are the server templates the placements name.

#### theme

```python
theme() -> Theme
```

The scheme switch bound to the dark state; mounted in the root and in every panel, so a standalone ?ui= page and a popout paint it too.

#### build\_switcher

```python
build_switcher() -> None
```

The title as a menu over the catalogue.

#### add

```python
add(root: Split, *, spec: WorkbenchSpec | None = None, watch: Sequence[Path] = ()) -> None
```

Builds every panel's component and template; the dock arranges them once the client reports ready.

Parameters:

- **`root`** (`Split`) –

  The Split to serve.
- **`spec`** (`WorkbenchSpec | None`, default: `None` ) –

  The card the Split was built from; given, the Split is rebuilt on every save.
- **`watch`** (`Sequence[Path]`, default: `()` ) –

  More directories whose .py saves rebuild the Split, beside the spec's own roots.

#### open

```python
open(root: Split, *, reference: str | None = None, name: str | None = None) -> None
```

Adds a Split's panels to the running dock, building those not built yet, and activates the first.

Parameters:

- **`root`** (`Split`) –

  The Split whose panels to add.
- **`reference`** (`str | None`, default: `None` ) –

  The panel whose group the first panel joins; None lets the dock place it.
- **`name`** (`str | None`, default: `None` ) –

  The card the Split comes from, remembered so the next start reopens it; None for a bare Split.

#### open\_spec

```python
open_spec(spec: WorkbenchSpec, *, reference: str | None = None) -> None
```

Opens a dock card: its target builds the Split, remembered under the card's name so the next start rebuilds it.

#### opened\_first

```python
opened_first(name: str) -> str | None
```

The first panel id of the open card of that name, None when it is not open.

#### close

```python
close(name: str) -> None
```

Takes an opened card out of the dock and the shell and stops rebuilding it on save.

#### close\_all

```python
close_all() -> None
```

Closes every opened card.

#### discard

```python
discard(name: str) -> None
```

Takes an opened card out of the dock and the shell: its panels are removed and disposed, its header widgets dropped, the card forgotten.

#### register

```python
register(leaves: Sequence[Panel]) -> list[PanelComponent]
```

Builds each leaf and registers it before the next is built, so a panel can see the ones already standing (a second scene panel must know a mirror is taken).

#### retire

```python
retire(ids: Sequence[str]) -> None
```

Disposes the built panels of those ids and drops them from the shell.

#### drop\_header

```python
drop_header(widgets: Sequence[object]) -> None
```

Removes a Split's widgets from the header, those still there.

#### watch

```python
watch(spec: WorkbenchSpec, *, reference: str | None = None, extra: Sequence[Path] = ()) -> None
```

Rebuilds the spec's Split after every .py save under its roots once the server runs; a spec of the shell core is left to the dev supervisor.

Parameters:

- **`spec`** (`WorkbenchSpec`) –

  The card whose target builds the Split.
- **`reference`** (`str | None`, default: `None` ) –

  The panel whose group the rebuilt card joins when it is no longer open.
- **`extra`** (`Sequence[Path]`, default: `()` ) –

  More directories to watch, beside the femforge core, the workbench package and the spec's own source root.

#### unwatch

```python
unwatch(name: str) -> None
```

Stops rebuilding a card on save.

#### rebuild

```python
rebuild(name: str, root: Split, *, reference: str | None = None, seconds: float = 0.0) -> None
```

Swaps a card's panels for a fresh build under the same ids, the dock untouched; other ids close and reopen the card, and a card no longer open reopens.

Parameters:

- **`name`** (`str`) –

  The card to rebuild.
- **`root`** (`Split`) –

  The fresh build of its Split.
- **`reference`** (`str | None`, default: `None` ) –

  The panel whose group the card joins when it is no longer open.
- **`seconds`** (`float`, default: `0.0` ) –

  The seconds the build took, added to the rebuild time the status line reports.

#### replace\_root

```python
replace_root(root: Split) -> None
```

Rebuilds the authored Split in place; changed ids re-lay the dock out from the authored arrangement.

#### replace\_card

```python
replace_card(name: str, root: Split, reference: str | None) -> None
```

Rebuilds an open card in place; changed ids close and reopen it, and a card not open opens.

Parameters:

- **`name`** (`str`) –

  The card to rebuild.
- **`root`** (`Split`) –

  The fresh build of its Split.
- **`reference`** (`str | None`) –

  The panel whose group the card joins when it is not open.

#### swap

```python
swap(root: Split, retired: Sequence[str], header: Sequence[object]) -> tuple[object, ...]
```

Disposes the retired panels, then builds the Split's leaves, whose templates overwrite those of the same ids; returns the header widgets the build added.

Parameters:

- **`root`** (`Split`) –

  The Split whose leaves to build.
- **`retired`** (`Sequence[str]`) –

  The ids of the panels to dispose first.
- **`header`** (`Sequence[object]`) –

  The header widgets the retired panels added, dropped with them.

#### find\_spec

```python
find_spec(name: str) -> WorkbenchSpec | None
```

The catalogue card of that name, None when the catalogue has none.

#### restore\_opened

```python
restore_opened() -> None
```

Reopens the cards the last session left in the dock, dropping any whose card is gone or whose target no longer builds a Split.

#### remember\_opened

```python
remember_opened() -> None
```

Persists the cards open in the dock, in opening order, and names them in the state for the launcher's cards.

#### prune\_opened

```python
prune_opened(layout: Mapping[str, object]) -> None
```

Forgets an opened Split whose first panel is missing from a settled layout, one that shows every panel handed to the dock since the last.

#### place

```python
place(root: Split, reference: str | None = None) -> None
```

add\_panel for every placement of a Split, its first panel within `reference`'s group when given; ids already in the dock are left alone.

#### panel

```python
panel(panel_id: str, kind: type[P]) -> P
```

The built component of a panel, checked to be of `kind`.

#### status

```python
status(message: str) -> None
```

Writes the status line, flushing so it also lands from outside a handler.

#### marshal

```python
marshal(fn: Callable[..., object], *args: object) -> None
```

Runs `fn(*args)` on the server loop from any thread.

#### when\_ready

```python
when_ready(fn: Callable[[], None]) -> None
```

Calls `fn` on the server loop: now when the server runs, else once it is ready.

#### authored\_layout

```python
authored_layout() -> Layout
```

The dockview layout of the authored Split; RuntimeError before `add`.

#### on\_dock\_ready

```python
on_dock_ready() -> None
```

Restores the saved arrangement when it names the authored panels, else applies the authored one; add\_panel then fills any gap.

#### on\_layout\_changed

```python
on_layout_changed(layout: Mapping[str, object]) -> None
```

Persists the layout the dock reports, forgets the cards it no longer shows and tells every panel.

#### on\_panel\_removed

```python
on_panel_removed(event: Mapping[str, object]) -> None
```

Disposes the panel whose tab the user closed; the dock reports only a user close here, never its own teardown, and Reset layout builds a closed panel again.

#### rebuild\_closed

```python
rebuild_closed() -> None
```

Rebuilds every card a closed tab left incomplete, header widgets included, so an arrangement naming its panels finds them live.

#### on\_panel\_visibility

```python
on_panel_visibility(change: Mapping[str, object]) -> None
```

Records that the dock showed or hid a panel and tells that panel.

#### reset\_layout

```python
reset_layout() -> None
```

Rebuilds the authored arrangement, which also recovers a group orphaned by a killed popout window.

#### open\_workbench

```python
open_workbench(name: str) -> None
```

Hands a catalogue selection to the open\_workbench hook, or names the command that opens it.

#### on\_server\_ready

```python
on_server_ready(**_: object) -> None
```

Takes the server loop and opens the app window when one was asked for.

#### on\_server\_exited

```python
on_server_exited(**_: object) -> None
```

Stops every reloader and flushes the persisted state.

#### open\_app\_window

```python
open_app_window() -> None
```

Chrome (Edge fallback) as an app window on its own profile; the server stops when the window process exits. Without either, a system browser tab.

#### install\_break\_handler

```python
install_break_handler() -> None
```

Makes a break or termination signal a clean stop, so the exit hooks flush the persisted state; the --dev supervisor stops its child this way.

#### on\_break

```python
on_break(signum: int, frame: FrameType | None) -> None
```

Schedules the server's stop on its loop, which ends the main loop and runs the exit hooks.

#### run

```python
run(*, port: int = 8080, window: Window = BROWSER) -> None
```

Serves until stopped, or until an app window closes.

### artifact\_store

```python
artifact_store(store: Telemetry) -> LocalArtifactStore
```

The store the artifact panels read: `FEMFORGE_ARTIFACTS` when set, else the root the index rows name.

### field\_viewer

```python
field_viewer(run: Result | Location | None = None, *, display: DisplaySpec = DEFAULTS, telemetry: str | Path | None = None) -> Split
```

The field viewer dock: the controls beside the 3D view and the 2D plot as tabs.

Parameters:

- **`run`** (`Result | Location | None`, default: `None` ) –

  The run to open once the server runs: a Result, a Run, a run id or a bundle directory; None opens none.
- **`display`** (`DisplaySpec`, default: `DEFAULTS` ) –

  The display the controls start from.
- **`telemetry`** (`str | Path | None`, default: `None` ) –

  The telemetry root whose run index the run picker lists; None reads FEMFORGE\_TELEMETRY.

### show

```python
show(workbench: Split | Factory | WorkbenchSpec | None = None, *, title: str | None = None, workbenches: Sequence[WorkbenchSpec] = (), window: Window = APP, port: int = DEFAULT_PORT, telemetry: str | Path | None = None, persist: str | Path | None = None, watch: Sequence[str | Path] = ()) -> None
```

Serves a workbench until its window closes: a Split as built, a factory or card rebuilt on every save, or the launcher when none is given.

Parameters:

- **`workbench`** (`Split | Factory | WorkbenchSpec | None`, default: `None` ) –

  The Split, factory or card to serve; None serves the launcher.
- **`title`** (`str | None`, default: `None` ) –

  The header title; None keeps the card's own, or femforge workbench for a Split.
- **`workbenches`** (`Sequence[WorkbenchSpec]`, default: `()` ) –

  Cards the launcher lists beside the discovered ones, and the only cards a single workbench's title menu lists.
- **`window`** (`Window`, default: `APP` ) –

  Where the workbench opens.
- **`port`** (`int`, default: `DEFAULT_PORT` ) –

  The port the server listens on.
- **`telemetry`** (`str | Path | None`, default: `None` ) –

  The telemetry root the panels read, exported as FEMFORGE\_TELEMETRY; None keeps the environment's.
- **`persist`** (`str | Path | None`, default: `None` ) –

  The directory layouts and opened cards persist under; None uses the per-user config directory.
- **`watch`** (`Sequence[str | Path]`, default: `()` ) –

  More directories whose .py saves rebuild a factory or card, beside its own roots.

## femforge\_workbench.serve

Serves a workbench from a script or the command line: a dock, a catalogue card, or the launcher.

Type Aliases:

- **`Factory`** –

  A module-level function building a dock Split or a Renderable.

Classes:

- **`NotAWorkbench`** –

  A target that builds neither a dock Split nor a Renderable.

Functions:

- **`show`** –

  Serves a workbench until its window closes: a Split as built, a factory or card rebuilt on every save, or the launcher when none is given.
- **`card`** –

  The spec to open: the launcher for None, a dock card named after a factory.
- **`build`** –

  A shell holding the spec's dock, rebuilt on every save under its roots, or a viewer of its Renderable.
- **`viewer`** –

  One scene panel that shows the spec's Renderable once the server runs, rebuilding it on every save under its roots.

Attributes:

- **`DEFAULT_PORT`** –

  The port a workbench serves on unless told otherwise.

### DEFAULT\_PORT

```python
DEFAULT_PORT = 8080
```

The port a workbench serves on unless told otherwise.

### Factory

```python
Factory = Callable[[], Split | Renderable]
```

A module-level function building a dock Split or a Renderable.

### NotAWorkbench

Bases: `TypeError`

A target that builds neither a dock Split nor a Renderable.

### show

```python
show(workbench: Split | Factory | WorkbenchSpec | None = None, *, title: str | None = None, workbenches: Sequence[WorkbenchSpec] = (), window: Window = APP, port: int = DEFAULT_PORT, telemetry: str | Path | None = None, persist: str | Path | None = None, watch: Sequence[str | Path] = ()) -> None
```

Serves a workbench until its window closes: a Split as built, a factory or card rebuilt on every save, or the launcher when none is given.

Parameters:

- **`workbench`** (`Split | Factory | WorkbenchSpec | None`, default: `None` ) –

  The Split, factory or card to serve; None serves the launcher.
- **`title`** (`str | None`, default: `None` ) –

  The header title; None keeps the card's own, or femforge workbench for a Split.
- **`workbenches`** (`Sequence[WorkbenchSpec]`, default: `()` ) –

  Cards the launcher lists beside the discovered ones, and the only cards a single workbench's title menu lists.
- **`window`** (`Window`, default: `APP` ) –

  Where the workbench opens.
- **`port`** (`int`, default: `DEFAULT_PORT` ) –

  The port the server listens on.
- **`telemetry`** (`str | Path | None`, default: `None` ) –

  The telemetry root the panels read, exported as FEMFORGE\_TELEMETRY; None keeps the environment's.
- **`persist`** (`str | Path | None`, default: `None` ) –

  The directory layouts and opened cards persist under; None uses the per-user config directory.
- **`watch`** (`Sequence[str | Path]`, default: `()` ) –

  More directories whose .py saves rebuild a factory or card, beside its own roots.

### card

```python
card(workbench: Factory | WorkbenchSpec | None) -> WorkbenchSpec
```

The spec to open: the launcher for None, a dock card named after a factory.

### build

```python
build(spec: WorkbenchSpec, *, catalogue: Sequence[WorkbenchSpec] | None = None, watch: Sequence[Path] = (), persist: UiStateStore | None = None) -> Workbench
```

A shell holding the spec's dock, rebuilt on every save under its roots, or a viewer of its Renderable.

Parameters:

- **`spec`** (`WorkbenchSpec`) –

  The card to serve.
- **`catalogue`** (`Sequence[WorkbenchSpec] | None`, default: `None` ) –

  The cards the title menu lists; None lists the discovered ones.
- **`watch`** (`Sequence[Path]`, default: `()` ) –

  More directories whose .py saves rebuild the card, beside its own roots.
- **`persist`** (`UiStateStore | None`, default: `None` ) –

  Where layouts and opened cards persist; None uses JSON files under the per-user config directory.

### viewer

```python
viewer(app: Workbench, spec: WorkbenchSpec, watch: Sequence[Path]) -> SceneViewerPanel
```

One scene panel that shows the spec's Renderable once the server runs, rebuilding it on every save under its roots.

Parameters:

- **`app`** (`Workbench`) –

  The shell to add the scene panel to.
- **`spec`** (`WorkbenchSpec`) –

  The card whose target builds the Renderable.
- **`watch`** (`Sequence[Path]`) –

  More directories whose .py saves rebuild the Renderable, beside the spec's own roots.

## femforge\_workbench.cli

The femforge-workbench command: parses its flags and shows the launcher, a catalogue entry or a module through serve.show.

Classes:

- **`CliError`** –

  A user-facing failure: main prints the message and exits 2.
- **`Options`** –

  The parsed command line.

Functions:

- **`select`** –

  The spec to open: a catalogue entry by name, a module given as `pkg.module[:attr]` (main() -\> Renderable when no attr), else the launcher.
- **`build_parser`** –

  The `femforge-workbench` argument parser with every flag registered.
- **`main`** –

  Parses the command line and shows the chosen workbench, or supervises the server under --dev.

### CliError

Bases: `Exception`

A user-facing failure: main prints the message and exits 2.

### Options

```python
Options(name: str | None = None, module: str | None = None, app: bool = False, webview: bool = False, dev: bool = False, port: int = DEFAULT_PORT, watch: list[str] = list(), telemetry: str | None = None, persist: str | None = None)
```

The parsed command line.

Attributes:

- **`window`** (`Window`) –

  The window the flags ask for: an app window, pywebview, else a browser tab.

#### window

```python
window: Window
```

The window the flags ask for: an app window, pywebview, else a browser tab.

### select

```python
select(name: str | None, module: str | None) -> WorkbenchSpec
```

The spec to open: a catalogue entry by name, a module given as `pkg.module[:attr]` (main() -\> Renderable when no attr), else the launcher.

### build\_parser

```python
build_parser() -> ArgumentParser
```

The `femforge-workbench` argument parser with every flag registered.

### main

```python
main(argv: Sequence[str] | None = None) -> None
```

Parses the command line and shows the chosen workbench, or supervises the server under --dev.

## femforge\_workbench.app

The workbench shell: a React header over a dock whose panels are trame templates, with persisted layouts and a worker-thread marshal.

Classes:

- **`WidgetLibrary`** –

  A trame widget package whose initialize() enables its client module on a server.
- **`Cls`** –

  The shell's Tailwind compositions; the strings appear literally so the stylesheet build scans them.
- **`Window`** –

  Where a served workbench opens.
- **`Template`** –

  Templates rendered outside the panels.
- **`StateVar`** –

  The shell's own state variables.
- **`Hook`** –

  Controller slots panels fill: the launcher takes OPEN\_WORKBENCH, the field viewer FIELD\_OPEN.
- **`OpenedKey`** –

  The fields of the persisted opened document: the cards open in the dock, in opening order.
- **`CardKey`** –

  The fields of a catalogue card in the state.
- **`Opened`** –

  A Split in the running dock: the card it serves (None for a bare Split), the panel it was anchored to and the header widgets it added.
- **`Watch`** –

  A card rebuilt on save and the reloader watching its target's roots.
- **`Reporting`** –

  Anything with a status line: the shell and its panels.
- **`Workbench`** –

  One trame server with the header, the dock and the panels of a named workbench.

Functions:

- **`check_unique`** –

  Rejects a panel id used twice in one Split.
- **`guard`** –

  Prints a handler's exception with rich and reports it in the status line instead of letting it escape into wslink.

Attributes:

- **`TITLE_VAR`** –

  The state variable trame's client shows as the page title.
- **`PANEL_PARAM`** –

  The render-prop parameter carrying a dock panel's params to its server template.
- **`WIDGET_LIBRARIES`** (`tuple[WidgetLibrary, ...]`) –

  Every widget library a panel may use: a client loads only the modules enabled before it connected, and a card can open such panels later.
- **`QUIET_LIBRARIES`** –

  Stdlib loggers gated to WARNING by every shell, whichever entry point built it: the access log alone writes thousands of lines per page load.

### TITLE\_VAR

```python
TITLE_VAR = 'trame__title'
```

The state variable trame's client shows as the page title.

### PANEL\_PARAM

```python
PANEL_PARAM = 'panel'
```

The render-prop parameter carrying a dock panel's params to its server template.

### WIDGET\_LIBRARIES

```python
WIDGET_LIBRARIES: tuple[WidgetLibrary, ...] = (widgets, rca, vtklocal)
```

Every widget library a panel may use: a client loads only the modules enabled before it connected, and a card can open such panels later.

### QUIET\_LIBRARIES

```python
QUIET_LIBRARIES = ('trame_server', 'trame_client', 'trame_common', 'trame_rca', 'trame_vtklocal', 'wslink', 'aiohttp', 'aiohttp.access', 'watchdog', 'asyncio')
```

Stdlib loggers gated to WARNING by every shell, whichever entry point built it: the access log alone writes thousands of lines per page load.

### WidgetLibrary

Bases: `Protocol`

A trame widget package whose initialize() enables its client module on a server.

Methods:

- **`initialize`** –

  Enables the library's client module on `server`.

#### initialize

```python
initialize(server: Server) -> None
```

Enables the library's client module on `server`.

### Cls

Bases: `StrEnum`

The shell's Tailwind compositions; the strings appear literally so the stylesheet build scans them.

### Window

Bases: `StrEnum`

Where a served workbench opens.

Attributes:

- **`BROWSER`** –

  A tab in the default browser.
- **`APP`** –

  A Chrome/Edge app window; closing it stops the server.
- **`WEBVIEW`** –

  pywebview, from the app extra.

#### BROWSER

```python
BROWSER = 'browser'
```

A tab in the default browser.

#### APP

```python
APP = 'app'
```

A Chrome/Edge app window; closing it stops the server.

#### WEBVIEW

```python
WEBVIEW = 'webview'
```

pywebview, from the app extra.

### Template

Bases: `StrEnum`

Templates rendered outside the panels.

### StateVar

Bases: `StrEnum`

The shell's own state variables.

### Hook

Bases: `StrEnum`

Controller slots panels fill: the launcher takes OPEN\_WORKBENCH, the field viewer FIELD\_OPEN.

### OpenedKey

Bases: `StrEnum`

The fields of the persisted opened document: the cards open in the dock, in opening order.

### CardKey

Bases: `StrEnum`

The fields of a catalogue card in the state.

### Opened

```python
Opened(root: Split, reference: str | None, name: str | None = None, header: tuple[object, ...] = ())
```

A Split in the running dock: the card it serves (None for a bare Split), the panel it was anchored to and the header widgets it added.

### Watch

```python
Watch(name: str, reloader: Reloader[Split])
```

A card rebuilt on save and the reloader watching its target's roots.

### Reporting

Bases: `Protocol`

Anything with a status line: the shell and its panels.

Methods:

- **`status`** –

  Writes `message` to the status line.

#### status

```python
status(message: str) -> None
```

Writes `message` to the status line.

### Workbench

```python
Workbench(name: str, *, palette: Palette | None = None, title: str = 'femforge workbench', server: Server | None = None, persist: UiStateStore | None = None, user: str | None = None, catalogue: Sequence[WorkbenchSpec] | None = None)
```

Bases: `TrameApp`

One trame server with the header, the dock and the panels of a named workbench.

Parameters:

- **`name`** (`str`) –

  The workbench name, which names the trame server and scopes the persisted state.
- **`palette`** (`Palette | None`, default: `None` ) –

  The colours the shell and its panels paint in; None loads the dist's dark palette.
- **`title`** (`str`, default: `'femforge workbench'` ) –

  The title the header and the browser tab show.
- **`server`** (`Server | None`, default: `None` ) –

  The trame server to run on; None makes one named after `name`.
- **`persist`** (`UiStateStore | None`, default: `None` ) –

  Where layouts and opened cards persist; None keeps them in JSON files under the per-user config directory.
- **`user`** (`str | None`, default: `None` ) –

  The user the persisted state is scoped to; None takes the OS user.
- **`catalogue`** (`Sequence[WorkbenchSpec] | None`, default: `None` ) –

  The cards the title menu and a launcher panel list; None lists the discovered ones.

Methods:

- **`load_catalogue`** –

  The discovered specs, empty with a warning when an entry point is broken.
- **`build_ui`** –

  The theme switch, the header and the dock, whose panel bodies are the server templates the placements name.
- **`theme`** –

  The scheme switch bound to the dark state; mounted in the root and in every panel, so a standalone ?ui= page and a popout paint it too.
- **`build_switcher`** –

  The title as a menu over the catalogue.
- **`add`** –

  Builds every panel's component and template; the dock arranges them once the client reports ready.
- **`open`** –

  Adds a Split's panels to the running dock, building those not built yet, and activates the first.
- **`open_spec`** –

  Opens a dock card: its target builds the Split, remembered under the card's name so the next start rebuilds it.
- **`opened_first`** –

  The first panel id of the open card of that name, None when it is not open.
- **`close`** –

  Takes an opened card out of the dock and the shell and stops rebuilding it on save.
- **`close_all`** –

  Closes every opened card.
- **`discard`** –

  Takes an opened card out of the dock and the shell: its panels are removed and disposed, its header widgets dropped, the card forgotten.
- **`register`** –

  Builds each leaf and registers it before the next is built, so a panel can see the ones already standing (a second scene panel must know a mirror is taken).
- **`retire`** –

  Disposes the built panels of those ids and drops them from the shell.
- **`drop_header`** –

  Removes a Split's widgets from the header, those still there.
- **`watch`** –

  Rebuilds the spec's Split after every .py save under its roots once the server runs; a spec of the shell core is left to the dev supervisor.
- **`unwatch`** –

  Stops rebuilding a card on save.
- **`rebuild`** –

  Swaps a card's panels for a fresh build under the same ids, the dock untouched; other ids close and reopen the card, and a card no longer open reopens.
- **`replace_root`** –

  Rebuilds the authored Split in place; changed ids re-lay the dock out from the authored arrangement.
- **`replace_card`** –

  Rebuilds an open card in place; changed ids close and reopen it, and a card not open opens.
- **`swap`** –

  Disposes the retired panels, then builds the Split's leaves, whose templates overwrite those of the same ids; returns the header widgets the build added.
- **`find_spec`** –

  The catalogue card of that name, None when the catalogue has none.
- **`restore_opened`** –

  Reopens the cards the last session left in the dock, dropping any whose card is gone or whose target no longer builds a Split.
- **`remember_opened`** –

  Persists the cards open in the dock, in opening order, and names them in the state for the launcher's cards.
- **`prune_opened`** –

  Forgets an opened Split whose first panel is missing from a settled layout, one that shows every panel handed to the dock since the last.
- **`place`** –

  add\_panel for every placement of a Split, its first panel within `reference`'s group when given; ids already in the dock are left alone.
- **`panel`** –

  The built component of a panel, checked to be of `kind`.
- **`status`** –

  Writes the status line, flushing so it also lands from outside a handler.
- **`marshal`** –

  Runs `fn(*args)` on the server loop from any thread.
- **`when_ready`** –

  Calls `fn` on the server loop: now when the server runs, else once it is ready.
- **`authored_layout`** –

  The dockview layout of the authored Split; RuntimeError before `add`.
- **`on_dock_ready`** –

  Restores the saved arrangement when it names the authored panels, else applies the authored one; add\_panel then fills any gap.
- **`on_layout_changed`** –

  Persists the layout the dock reports, forgets the cards it no longer shows and tells every panel.
- **`on_panel_removed`** –

  Disposes the panel whose tab the user closed; the dock reports only a user close here, never its own teardown, and Reset layout builds a closed panel again.
- **`rebuild_closed`** –

  Rebuilds every card a closed tab left incomplete, header widgets included, so an arrangement naming its panels finds them live.
- **`on_panel_visibility`** –

  Records that the dock showed or hid a panel and tells that panel.
- **`reset_layout`** –

  Rebuilds the authored arrangement, which also recovers a group orphaned by a killed popout window.
- **`open_workbench`** –

  Hands a catalogue selection to the open\_workbench hook, or names the command that opens it.
- **`on_server_ready`** –

  Takes the server loop and opens the app window when one was asked for.
- **`on_server_exited`** –

  Stops every reloader and flushes the persisted state.
- **`open_app_window`** –

  Chrome (Edge fallback) as an app window on its own profile; the server stops when the window process exits. Without either, a system browser tab.
- **`install_break_handler`** –

  Makes a break or termination signal a clean stop, so the exit hooks flush the persisted state; the --dev supervisor stops its child this way.
- **`on_break`** –

  Schedules the server's stop on its loop, which ends the main loop and runs the exit hooks.
- **`run`** –

  Serves until stopped, or until an app window closes.

Attributes:

- **`authored`** (`Opened | None`) –

  The Split add() built, named after the spec it serves when it is to be rebuilt on save.
- **`opened`** (`dict[str, Opened]`) –

  Splits opened into the running dock by their first panel id.
- **`pending`** (`set[str]`) –

  Panel ids handed to the dock since a layout last showed them all; a layout still missing one is a placement in flight, not a close.
- **`on_screen`** (`set[str]`) –

  Panel ids the dock last reported visible; the dock reports a panel only when it mounts, so a panel rebuilt in place learns it from here.
- **`catalogue`** –

  The cards the title menu and a launcher panel list: the discovered ones unless the constructor was given its own.
- **`header`** –

  The header's slot for a workbench's own controls: build widgets inside `with app.header:` and flush\_content() after a later change.
- **`root`** (`Split | None`) –

  The authored Split, None before `add`.
- **`panel_ids`** (`list[str]`) –

  The ids of the built panels, in build order.

#### authored

```python
authored: Opened | None = None
```

The Split add() built, named after the spec it serves when it is to be rebuilt on save.

#### opened

```python
opened: dict[str, Opened] = {}
```

Splits opened into the running dock by their first panel id.

#### pending

```python
pending: set[str] = set()
```

Panel ids handed to the dock since a layout last showed them all; a layout still missing one is a placement in flight, not a close.

#### on\_screen

```python
on_screen: set[str] = set()
```

Panel ids the dock last reported visible; the dock reports a panel only when it mounts, so a panel rebuilt in place learns it from here.

#### catalogue

```python
catalogue = list(catalogue) if catalogue is not None else self.load_catalogue()
```

The cards the title menu and a launcher panel list: the discovered ones unless the constructor was given its own.

#### header

```python
header = DivLayout(self.server, template_name=str(Template.HEADER), classes=str(Cls.HEADER_SLOT))
```

The header's slot for a workbench's own controls: build widgets inside `with app.header:` and flush\_content() after a later change.

#### root

```python
root: Split | None
```

The authored Split, None before `add`.

#### panel\_ids

```python
panel_ids: list[str]
```

The ids of the built panels, in build order.

#### load\_catalogue

```python
load_catalogue() -> list[WorkbenchSpec]
```

The discovered specs, empty with a warning when an entry point is broken.

#### build\_ui

```python
build_ui() -> DivLayout
```

The theme switch, the header and the dock, whose panel bodies are the server templates the placements name.

#### theme

```python
theme() -> Theme
```

The scheme switch bound to the dark state; mounted in the root and in every panel, so a standalone ?ui= page and a popout paint it too.

#### build\_switcher

```python
build_switcher() -> None
```

The title as a menu over the catalogue.

#### add

```python
add(root: Split, *, spec: WorkbenchSpec | None = None, watch: Sequence[Path] = ()) -> None
```

Builds every panel's component and template; the dock arranges them once the client reports ready.

Parameters:

- **`root`** (`Split`) –

  The Split to serve.
- **`spec`** (`WorkbenchSpec | None`, default: `None` ) –

  The card the Split was built from; given, the Split is rebuilt on every save.
- **`watch`** (`Sequence[Path]`, default: `()` ) –

  More directories whose .py saves rebuild the Split, beside the spec's own roots.

#### open

```python
open(root: Split, *, reference: str | None = None, name: str | None = None) -> None
```

Adds a Split's panels to the running dock, building those not built yet, and activates the first.

Parameters:

- **`root`** (`Split`) –

  The Split whose panels to add.
- **`reference`** (`str | None`, default: `None` ) –

  The panel whose group the first panel joins; None lets the dock place it.
- **`name`** (`str | None`, default: `None` ) –

  The card the Split comes from, remembered so the next start reopens it; None for a bare Split.

#### open\_spec

```python
open_spec(spec: WorkbenchSpec, *, reference: str | None = None) -> None
```

Opens a dock card: its target builds the Split, remembered under the card's name so the next start rebuilds it.

#### opened\_first

```python
opened_first(name: str) -> str | None
```

The first panel id of the open card of that name, None when it is not open.

#### close

```python
close(name: str) -> None
```

Takes an opened card out of the dock and the shell and stops rebuilding it on save.

#### close\_all

```python
close_all() -> None
```

Closes every opened card.

#### discard

```python
discard(name: str) -> None
```

Takes an opened card out of the dock and the shell: its panels are removed and disposed, its header widgets dropped, the card forgotten.

#### register

```python
register(leaves: Sequence[Panel]) -> list[PanelComponent]
```

Builds each leaf and registers it before the next is built, so a panel can see the ones already standing (a second scene panel must know a mirror is taken).

#### retire

```python
retire(ids: Sequence[str]) -> None
```

Disposes the built panels of those ids and drops them from the shell.

#### drop\_header

```python
drop_header(widgets: Sequence[object]) -> None
```

Removes a Split's widgets from the header, those still there.

#### watch

```python
watch(spec: WorkbenchSpec, *, reference: str | None = None, extra: Sequence[Path] = ()) -> None
```

Rebuilds the spec's Split after every .py save under its roots once the server runs; a spec of the shell core is left to the dev supervisor.

Parameters:

- **`spec`** (`WorkbenchSpec`) –

  The card whose target builds the Split.
- **`reference`** (`str | None`, default: `None` ) –

  The panel whose group the rebuilt card joins when it is no longer open.
- **`extra`** (`Sequence[Path]`, default: `()` ) –

  More directories to watch, beside the femforge core, the workbench package and the spec's own source root.

#### unwatch

```python
unwatch(name: str) -> None
```

Stops rebuilding a card on save.

#### rebuild

```python
rebuild(name: str, root: Split, *, reference: str | None = None, seconds: float = 0.0) -> None
```

Swaps a card's panels for a fresh build under the same ids, the dock untouched; other ids close and reopen the card, and a card no longer open reopens.

Parameters:

- **`name`** (`str`) –

  The card to rebuild.
- **`root`** (`Split`) –

  The fresh build of its Split.
- **`reference`** (`str | None`, default: `None` ) –

  The panel whose group the card joins when it is no longer open.
- **`seconds`** (`float`, default: `0.0` ) –

  The seconds the build took, added to the rebuild time the status line reports.

#### replace\_root

```python
replace_root(root: Split) -> None
```

Rebuilds the authored Split in place; changed ids re-lay the dock out from the authored arrangement.

#### replace\_card

```python
replace_card(name: str, root: Split, reference: str | None) -> None
```

Rebuilds an open card in place; changed ids close and reopen it, and a card not open opens.

Parameters:

- **`name`** (`str`) –

  The card to rebuild.
- **`root`** (`Split`) –

  The fresh build of its Split.
- **`reference`** (`str | None`) –

  The panel whose group the card joins when it is not open.

#### swap

```python
swap(root: Split, retired: Sequence[str], header: Sequence[object]) -> tuple[object, ...]
```

Disposes the retired panels, then builds the Split's leaves, whose templates overwrite those of the same ids; returns the header widgets the build added.

Parameters:

- **`root`** (`Split`) –

  The Split whose leaves to build.
- **`retired`** (`Sequence[str]`) –

  The ids of the panels to dispose first.
- **`header`** (`Sequence[object]`) –

  The header widgets the retired panels added, dropped with them.

#### find\_spec

```python
find_spec(name: str) -> WorkbenchSpec | None
```

The catalogue card of that name, None when the catalogue has none.

#### restore\_opened

```python
restore_opened() -> None
```

Reopens the cards the last session left in the dock, dropping any whose card is gone or whose target no longer builds a Split.

#### remember\_opened

```python
remember_opened() -> None
```

Persists the cards open in the dock, in opening order, and names them in the state for the launcher's cards.

#### prune\_opened

```python
prune_opened(layout: Mapping[str, object]) -> None
```

Forgets an opened Split whose first panel is missing from a settled layout, one that shows every panel handed to the dock since the last.

#### place

```python
place(root: Split, reference: str | None = None) -> None
```

add\_panel for every placement of a Split, its first panel within `reference`'s group when given; ids already in the dock are left alone.

#### panel

```python
panel(panel_id: str, kind: type[P]) -> P
```

The built component of a panel, checked to be of `kind`.

#### status

```python
status(message: str) -> None
```

Writes the status line, flushing so it also lands from outside a handler.

#### marshal

```python
marshal(fn: Callable[..., object], *args: object) -> None
```

Runs `fn(*args)` on the server loop from any thread.

#### when\_ready

```python
when_ready(fn: Callable[[], None]) -> None
```

Calls `fn` on the server loop: now when the server runs, else once it is ready.

#### authored\_layout

```python
authored_layout() -> Layout
```

The dockview layout of the authored Split; RuntimeError before `add`.

#### on\_dock\_ready

```python
on_dock_ready() -> None
```

Restores the saved arrangement when it names the authored panels, else applies the authored one; add\_panel then fills any gap.

#### on\_layout\_changed

```python
on_layout_changed(layout: Mapping[str, object]) -> None
```

Persists the layout the dock reports, forgets the cards it no longer shows and tells every panel.

#### on\_panel\_removed

```python
on_panel_removed(event: Mapping[str, object]) -> None
```

Disposes the panel whose tab the user closed; the dock reports only a user close here, never its own teardown, and Reset layout builds a closed panel again.

#### rebuild\_closed

```python
rebuild_closed() -> None
```

Rebuilds every card a closed tab left incomplete, header widgets included, so an arrangement naming its panels finds them live.

#### on\_panel\_visibility

```python
on_panel_visibility(change: Mapping[str, object]) -> None
```

Records that the dock showed or hid a panel and tells that panel.

#### reset\_layout

```python
reset_layout() -> None
```

Rebuilds the authored arrangement, which also recovers a group orphaned by a killed popout window.

#### open\_workbench

```python
open_workbench(name: str) -> None
```

Hands a catalogue selection to the open\_workbench hook, or names the command that opens it.

#### on\_server\_ready

```python
on_server_ready(**_: object) -> None
```

Takes the server loop and opens the app window when one was asked for.

#### on\_server\_exited

```python
on_server_exited(**_: object) -> None
```

Stops every reloader and flushes the persisted state.

#### open\_app\_window

```python
open_app_window() -> None
```

Chrome (Edge fallback) as an app window on its own profile; the server stops when the window process exits. Without either, a system browser tab.

#### install\_break\_handler

```python
install_break_handler() -> None
```

Makes a break or termination signal a clean stop, so the exit hooks flush the persisted state; the --dev supervisor stops its child this way.

#### on\_break

```python
on_break(signum: int, frame: FrameType | None) -> None
```

Schedules the server's stop on its loop, which ends the main loop and runs the exit hooks.

#### run

```python
run(*, port: int = 8080, window: Window = BROWSER) -> None
```

Serves until stopped, or until an app window closes.

### check\_unique

```python
check_unique(ids: Sequence[str]) -> None
```

Rejects a panel id used twice in one Split.

### guard

```python
guard(handler: Callable[Concatenate[S, P], None]) -> Callable[Concatenate[S, P], None]
```

Prints a handler's exception with rich and reports it in the status line instead of letting it escape into wslink.

## femforge\_workbench.launcher

The built-in launcher dock: the catalogue cards on the left, the scene, the outputs and the log as tabs on the right.

Functions:

- **`workbench`** –

  The launcher dock.

### workbench

```python
workbench() -> Split
```

The launcher dock.

## femforge\_workbench.demo

The built-in demo workbench: a sphere in the scene, a line figure and a grouped table, exercising the shell without lab data.

Classes:

- **`DemoPanel`** –

  The demo's panel ids.
- **`DemoVar`** –

  The demo's state variables.
- **`Sphere`** –

  A Renderable: a faceted sphere beside a smaller smooth one, painted from the palette.

Functions:

- **`signal_figure`** –

  Two damped sinusoids named for export through layout.meta.title.
- **`rows_frame`** –

  A hundred rows over five series with a group label carrying the count and the peak y.
- **`series_rows`** –

  The grouped rows of one series, or all of them.
- **`workbench`** –

  The demo dock: the sphere on the left, the figure over the table on the right, with a series filter in the app bar.

Attributes:

- **`SPEC`** –

  The catalogue card the workbench entry point exposes.

### SPEC

```python
SPEC = DEMO
```

The catalogue card the workbench entry point exposes.

### DemoPanel

Bases: `StrEnum`

The demo's panel ids.

### DemoVar

Bases: `StrEnum`

The demo's state variables.

### Sphere

```python
Sphere(palette: Palette)
```

A Renderable: a faceted sphere beside a smaller smooth one, painted from the palette.

Methods:

- **`render`** –

  Draws the two spheres into `plotter` and fits the camera.

#### render

```python
render(plotter: Plotter) -> None
```

Draws the two spheres into `plotter` and fits the camera.

### signal\_figure

```python
signal_figure() -> Figure
```

Two damped sinusoids named for export through layout.meta.title.

### rows\_frame

```python
rows_frame() -> DataFrame
```

A hundred rows over five series with a group label carrying the count and the peak y.

### series\_rows

```python
series_rows(frame: DataFrame, selected: str) -> DataFrame
```

The grouped rows of one series, or all of them.

### workbench

```python
workbench() -> Split
```

The demo dock: the sphere on the left, the figure over the table on the right, with a series filter in the app bar.

## femforge\_workbench.settings

Environment switches the workbench reads, each spelled once.

Classes:

- **`Setting`** –

  The environment variables that switch optional workbench behaviour on.

Functions:

- **`enabled`** –

  Whether the switch is set to a true value.

### Setting

Bases: `StrEnum`

The environment variables that switch optional workbench behaviour on.

Attributes:

- **`OCP_VSCODE`** –

  Also push every CompoundRenderable to the OCP CAD Viewer in VS Code.

#### OCP\_VSCODE

```python
OCP_VSCODE = 'FEMFORGE_OCP_VSCODE'
```

Also push every CompoundRenderable to the OCP CAD Viewer in VS Code.

### enabled

```python
enabled(setting: Setting) -> bool
```

Whether the switch is set to a true value.

## femforge\_workbench.reload

Watchdog-driven module reload: a .py save under the roots purges and re-imports a workbench target, builds its result on a worker thread and hands it to the server loop.

Type Aliases:

- **`Apply`** –

  Receives the fresh result and the seconds the purge, import and factory took.

Classes:

- **`Watched`** –

  What the shared purge needs of a reloader: the roots it watches.
- **`Purge`** –

  The last eviction the reloaders shared: when it started and the roots it covered.
- **`SaveWatcher`** –

  Calls `on_save` once per burst of .py saves under the roots (only `files` when given), `debounce` seconds after the last one; a save is an event whose file's mtime changed.
- **`Reloader`** –

  Rebuilds the target on a worker thread after each burst of saves under the roots, one rebuild at a time with at most one more queued.

Functions:

- **`package_root`** –

  The directory of the top-level package a module belongs to.
- **`source_root`** –

  The directory to watch for a module: the `src` directory of a src layout, so its sibling packages reload with it, else the package itself.
- **`persistent`** –

  Whether a module stays loaded across a purge.
- **`shell_files`** –

  The source files of the shell core.
- **`default_roots`** –

  The femforge core's source root, the workbench package's (its panels, widgets and theme rebuild in process) and the module's own.
- **`outermost`** –

  The resolved roots without repeats and without any lying under another, since each is watched recursively.
- **`is_source_save`** –

  The saved .py path an event reports, None for anything else; an editor saves as a modification, or as a create or move of a temp file.

Attributes:

- **`DEBOUNCE_SECONDS`** –

  Seconds after the last save of a burst before it is acted on: an editor writes one file in several events, a save-all several files.
- **`REBUILD_LOCK`** –

  Serializes purge-and-import across the reloaders of every open card: two threads evicting and importing the same modules at once corrupt each other.
- **`WATCHING_LOCK`** –

  Guards the watching list alone, never held across a rebuild, so stopping a reloader does not wait for one.
- **`SHELL_MODULES`** –

  The shell core, relative to the workbench package: never purged in process, restarted by the --dev supervisor instead.
- **`PERSISTENT_PACKAGES`** –

  Modules the purge leaves alive: the shell core and the logging singleton it shares with the reloaded code.
- **`WATCHING`** (`list[Watched]`) –

  The reloaders of the open cards, whose roots the shared purge covers.
- **`PURGED`** –

  Read and written under REBUILD\_LOCK only.

### DEBOUNCE\_SECONDS

```python
DEBOUNCE_SECONDS = 0.3
```

Seconds after the last save of a burst before it is acted on: an editor writes one file in several events, a save-all several files.

### REBUILD\_LOCK

```python
REBUILD_LOCK = threading.Lock()
```

Serializes purge-and-import across the reloaders of every open card: two threads evicting and importing the same modules at once corrupt each other.

### WATCHING\_LOCK

```python
WATCHING_LOCK = threading.Lock()
```

Guards the watching list alone, never held across a rebuild, so stopping a reloader does not wait for one.

### SHELL\_MODULES

```python
SHELL_MODULES = ('app', 'cli', 'dev', 'dock', 'persist', 'registry', 'reload', 'runner', 'serve', 'settings', 'widgets.element', 'widgets.module')
```

The shell core, relative to the workbench package: never purged in process, restarted by the --dev supervisor instead.

### PERSISTENT\_PACKAGES

```python
PERSISTENT_PACKAGES = (*(f'{femforge_workbench.__name__}.{name}' for name in SHELL_MODULES), 'femforge.common.log')
```

Modules the purge leaves alive: the shell core and the logging singleton it shares with the reloaded code.

### WATCHING

```python
WATCHING: list[Watched] = []
```

The reloaders of the open cards, whose roots the shared purge covers.

### PURGED

```python
PURGED = Purge()
```

Read and written under REBUILD\_LOCK only.

### Apply

```python
Apply = Callable[[R, float], None]
```

Receives the fresh result and the seconds the purge, import and factory took.

### Watched

Bases: `Protocol`

What the shared purge needs of a reloader: the roots it watches.

Attributes:

- **`roots`** (`list[Path]`) –

  The directories the reloader watches.

#### roots

```python
roots: list[Path]
```

The directories the reloader watches.

### Purge

```python
Purge(at: float = 0.0, roots: tuple[Path, ...] = ())
```

The last eviction the reloaders shared: when it started and the roots it covered.

Methods:

- **`covers`** –

  Whether this purge followed the save at `requested` and evicted every one of those roots.

#### covers

```python
covers(roots: Sequence[Path], requested: float) -> bool
```

Whether this purge followed the save at `requested` and evicted every one of those roots.

### SaveWatcher

```python
SaveWatcher(roots: Sequence[Path], on_save: Callable[[], None], *, debounce: float = DEBOUNCE_SECONDS, files: Sequence[Path] | None = None)
```

Bases: `FileSystemEventHandler`

Calls `on_save` once per burst of .py saves under the roots (only `files` when given), `debounce` seconds after the last one; a save is an event whose file's mtime changed.

Parameters:

- **`roots`** (`Sequence[Path]`) –

  The directories watched recursively.
- **`on_save`** (`Callable[[], None]`) –

  Called once per burst of saves.
- **`debounce`** (`float`, default: `DEBOUNCE_SECONDS` ) –

  Seconds after the last save of a burst before `on_save` runs.
- **`files`** (`Sequence[Path] | None`, default: `None` ) –

  The only files whose saves count; None counts every .py file under the roots.

Methods:

- **`watched`** –

  The files whose saves count: `files` when given, else every source file under the roots.
- **`changed`** –

  Whether the file's mtime differs from the last one seen; a file never seen counts as changed, a vanished one as unchanged.
- **`start`** –

  Records every watched file's mtime and starts observing the roots.
- **`stop`** –

  Cancels a pending call and stops observing.
- **`on_any_event`** –

  Every save restarts the timer, so only the last of a burst fires.
- **`fire`** –

  Calls `on_save` for the burst that just settled.

Attributes:

- **`stamps`** (`dict[Path, int]`) –

  The mtime last seen per file: watchdog also reports access-time updates, which NTFS writes on reads.
- **`observed`** –

  When the last save of the current burst was seen, which every reloader watching it reads before any purge.

#### stamps

```python
stamps: dict[Path, int] = {}
```

The mtime last seen per file: watchdog also reports access-time updates, which NTFS writes on reads.

#### observed

```python
observed = 0.0
```

When the last save of the current burst was seen, which every reloader watching it reads before any purge.

#### watched

```python
watched() -> list[Path]
```

The files whose saves count: `files` when given, else every source file under the roots.

#### changed

```python
changed(path: Path) -> bool
```

Whether the file's mtime differs from the last one seen; a file never seen counts as changed, a vanished one as unchanged.

#### start

```python
start() -> None
```

Records every watched file's mtime and starts observing the roots.

#### stop

```python
stop() -> None
```

Cancels a pending call and stops observing.

#### on\_any\_event

```python
on_any_event(event: FileSystemEvent) -> None
```

Every save restarts the timer, so only the last of a burst fires.

#### fire

```python
fire() -> None
```

Calls `on_save` for the burst that just settled.

### Reloader

```python
Reloader(target: str, roots: Sequence[Path], apply: Apply[R], *, kind: type[R], marshal: Marshal, failed: Failed | None = None)
```

Rebuilds the target on a worker thread after each burst of saves under the roots, one rebuild at a time with at most one more queued.

Parameters:

- **`target`** (`str`) –

  The `pkg.module:attr` factory rebuilt after every save.
- **`roots`** (`Sequence[Path]`) –

  The directories whose .py saves trigger a rebuild.
- **`apply`** (`Apply[R]`) –

  Receives each result and the seconds it took, on the server loop.
- **`kind`** (`type[R]`) –

  The type the factory must return.
- **`marshal`** (`Marshal`) –

  Runs a callable on the server loop.
- **`failed`** (`Failed | None`, default: `None` ) –

  Receives a failed build's error on the server loop; None drops it.

Methods:

- **`start`** –

  Watches the roots; `request` builds now.
- **`stop`** –

  Stops watching and leaves the shared purge.
- **`request`** –

  Starts a rebuild, or queues one more when a rebuild is running.
- **`work`** –

  Rebuilds until no request is queued.
- **`rebuild`** –

  Builds once and delivers the result, or the failure, to the server loop.
- **`deliver`** –

  Runs `outcome` on the server loop, dropping it when the loop is gone.
- **`report`** –

  Hands a failed build's error to `failed`.
- **`build`** –

  Purges the watched modules, imports the target afresh and calls it, one reloader at a time; a build without a save behind it always purges.
- **`purge`** –

  Drops every loaded module under the watched roots except the persistent packages, once per save however many cards are open.

Attributes:

- **`requested`** –

  When the save that asked for the pending rebuild was seen; a purge older than that is not this rebuild's.
- **`roots`** (`list[Path]`) –

  The directories the watcher observes.

#### requested

```python
requested = 0.0
```

When the save that asked for the pending rebuild was seen; a purge older than that is not this rebuild's.

#### roots

```python
roots: list[Path]
```

The directories the watcher observes.

#### start

```python
start() -> None
```

Watches the roots; `request` builds now.

#### stop

```python
stop() -> None
```

Stops watching and leaves the shared purge.

#### request

```python
request() -> None
```

Starts a rebuild, or queues one more when a rebuild is running.

#### work

```python
work() -> None
```

Rebuilds until no request is queued.

#### rebuild

```python
rebuild() -> None
```

Builds once and delivers the result, or the failure, to the server loop.

#### deliver

```python
deliver(outcome: Callable[[], None]) -> None
```

Runs `outcome` on the server loop, dropping it when the loop is gone.

#### report

```python
report(error: Exception) -> None
```

Hands a failed build's error to `failed`.

#### build

```python
build(requested: float | None = None) -> R
```

Purges the watched modules, imports the target afresh and calls it, one reloader at a time; a build without a save behind it always purges.

#### purge

```python
purge(requested: float) -> None
```

Drops every loaded module under the watched roots except the persistent packages, once per save however many cards are open.

### package\_root

```python
package_root(module_name: str) -> Path
```

The directory of the top-level package a module belongs to.

### source\_root

```python
source_root(module_name: str) -> Path
```

The directory to watch for a module: the `src` directory of a src layout, so its sibling packages reload with it, else the package itself.

### persistent

```python
persistent(name: str) -> bool
```

Whether a module stays loaded across a purge.

### shell\_files

```python
shell_files() -> list[Path]
```

The source files of the shell core.

### default\_roots

```python
default_roots(workbench_module: str) -> list[Path]
```

The femforge core's source root, the workbench package's (its panels, widgets and theme rebuild in process) and the module's own.

### outermost

```python
outermost(roots: Sequence[Path]) -> list[Path]
```

The resolved roots without repeats and without any lying under another, since each is watched recursively.

### is\_source\_save

```python
is_source_save(event: FileSystemEvent) -> str | None
```

The saved .py path an event reports, None for anything else; an editor saves as a modification, or as a create or move of a temp file.

## femforge\_workbench.dev

The dev loop for the shell core: a supervisor that reruns the server after every save of a core module, and the page script that follows it back and reloads on a rebuilt dist.

Classes:

- **`Supervisor`** –

  Runs a command as a child process and restarts it after every burst of .py saves under the roots, or of the `files` when given.

Functions:

- **`reload_script`** –

  Probes the server and the dist each second: the page reloads when the server comes back after a restart, when version.json changes, and when the library failed to install.
- **`without_dev`** –

  The arguments without the dev flag.
- **`child_command`** –

  The server as `python -m module`, given the arguments without the dev flag.
- **`supervise`** –

  Serves `python -m module argv` under a supervisor restarting it on a save of a shell core module, until Ctrl-C; every other save rebuilds in process.

Attributes:

- **`DEV_ENV`** –

  Set in the supervised server's environment; the shell then serves the self-reload script.
- **`POLL_SECONDS`** –

  How often the supervisor looks at its child between saves; a bare wait would hold Ctrl-C on Windows.
- **`PROBE_PAGE`** –

  Probed beside the page's own URL: the root answers HEAD with 403, the page itself with 200.
- **`LIBRARY_GRACE_MS`** –

  How long after load the page waits for the widget library before reloading once more.
- **`STOP_GRACE`** –

  Seconds a child gets to exit on its own after the break, so its exit hooks flush the persisted state.
- **`NEW_GROUP`** –

  The child gets its own console group, so the break reaches it and not the supervisor.

### DEV\_ENV

```python
DEV_ENV = 'FEMFORGE_WORKBENCH_DEV'
```

Set in the supervised server's environment; the shell then serves the self-reload script.

### POLL\_SECONDS

```python
POLL_SECONDS = 0.25
```

How often the supervisor looks at its child between saves; a bare wait would hold Ctrl-C on Windows.

### PROBE\_PAGE

```python
PROBE_PAGE = 'index.html'
```

Probed beside the page's own URL: the root answers HEAD with 403, the page itself with 200.

### LIBRARY\_GRACE\_MS

```python
LIBRARY_GRACE_MS = 2000
```

How long after load the page waits for the widget library before reloading once more.

### STOP\_GRACE

```python
STOP_GRACE = 5.0
```

Seconds a child gets to exit on its own after the break, so its exit hooks flush the persisted state.

### NEW\_GROUP

```python
NEW_GROUP = subprocess.CREATE_NEW_PROCESS_GROUP
```

The child gets its own console group, so the break reaches it and not the supervisor.

### Supervisor

```python
Supervisor(command: Sequence[str], roots: Sequence[Path], *, files: Sequence[Path] | None = None)
```

Runs a command as a child process and restarts it after every burst of .py saves under the roots, or of the `files` when given.

Parameters:

- **`command`** (`Sequence[str]`) –

  The server command the child runs.
- **`roots`** (`Sequence[Path]`) –

  The directories whose .py saves restart the child.
- **`files`** (`Sequence[Path] | None`, default: `None` ) –

  The only files whose saves restart the child; None counts every .py file under the roots.

Methods:

- **`spawn`** –

  Starts the child in its own process group and tree, flagged as supervised.
- **`stop_child`** –

  Asks the child to exit, so its exit hooks flush the persisted state, and kills its tree when it does not within the grace.
- **`release`** –

  Closes the child's tree once the child is gone, which also ends anything the child left behind.
- **`await_save`** –

  Returns at the next save or stop, noting once when the child exits on its own meanwhile.
- **`serve`** –

  Runs the child until Ctrl-C or `stop`, restarting it after each save.
- **`stop`** –

  Ends `serve`, which then stops the child.

#### spawn

```python
spawn() -> Popen[bytes]
```

Starts the child in its own process group and tree, flagged as supervised.

#### stop\_child

```python
stop_child(child: Popen[bytes]) -> None
```

Asks the child to exit, so its exit hooks flush the persisted state, and kills its tree when it does not within the grace.

#### release

```python
release() -> None
```

Closes the child's tree once the child is gone, which also ends anything the child left behind.

#### await\_save

```python
await_save(child: Popen[bytes]) -> None
```

Returns at the next save or stop, noting once when the child exits on its own meanwhile.

#### serve

```python
serve() -> None
```

Runs the child until Ctrl-C or `stop`, restarting it after each save.

#### stop

```python
stop() -> None
```

Ends `serve`, which then stops the child.

### reload\_script

```python
reload_script() -> str
```

Probes the server and the dist each second: the page reloads when the server comes back after a restart, when version.json changes, and when the library failed to install.

### without\_dev

```python
without_dev(argv: Sequence[str]) -> list[str]
```

The arguments without the dev flag.

### child\_command

```python
child_command(module: str, argv: Sequence[str]) -> list[str]
```

The server as `python -m module`, given the arguments without the dev flag.

### supervise

```python
supervise(module: str, argv: Sequence[str]) -> None
```

Serves `python -m module argv` under a supervisor restarting it on a save of a shell core module, until Ctrl-C; every other save rebuilds in process.
