# Dock

The dock tree and its dockview layout, the persisted UI state it restores from, and the refresh loop each panel runs.

## femforge\_workbench.dock

The dock tree: panels as tabs in splits and tab groups, its dockview layout JSON, and the rule for restoring a saved arrangement.

Type Aliases:

- **`Builder`** –

  Makes a panel's component from the shell, the panel id and the title.
- **`Layout`** –

  A dockview layout document as JSON.
- **`Node`** –

  A node of the dock tree: a panel, a tab group or a split.

Classes:

- **`Orientation`** –

  The axis a dockview branch lays its children along.
- **`PlacementDirection`** –

  Where add\_panel puts a panel relative to its reference.
- **`Key`** –

  The keys of a dockview layout JSON.
- **`NodeType`** –

  The two node kinds of a dockview grid.
- **`Panel`** –

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

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

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

  Where add\_panel puts one panel: beside, below or within the group of a reference panel.

Functions:

- **`template_name`** –

  The trame template a panel renders through.
- **`panels`** –

  Every panel under a node, in authored order.
- **`panel_ids`** –

  The ids of every panel under a node, in authored order.
- **`placements`** –

  The add\_panel sequence that rebuilds the tree group by group: each sibling beside or below the previous one, tabs within the first.
- **`layout_json`** –

  The dockview serialization of the authored tree: splits alternate orientation from a horizontal root, weights become sizes, each leaf one tab group.
- **`restorable`** –

  The saved layout when it names exactly the authored panels, with popout and floating groups folded into the first grid group; else None.

Attributes:

- **`AXIS`** –

  The nominal extent of each axis the weights are shared over; dockview rescales proportionally.
- **`CONTENT_COMPONENT`** –

  The Dock component that hosts a server template inside a panel, as Dock.tsx names it.

### AXIS

```python
AXIS = 1000
```

The nominal extent of each axis the weights are shared over; dockview rescales proportionally.

### CONTENT\_COMPONENT

```python
CONTENT_COMPONENT = 'DockPanel'
```

The Dock component that hosts a server template inside a panel, as Dock.tsx names it.

### Builder

```python
Builder = Callable[[Workbench, str, str], PanelComponent]
```

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

### Layout

```python
Layout = dict[str, object]
```

A dockview layout document as JSON.

### Node

```python
Node = Panel | Tabs | Split
```

A node of the dock tree: a panel, a tab group or a split.

### Orientation

Bases: `StrEnum`

The axis a dockview branch lays its children along.

Methods:

- **`flipped`** –

  The other orientation.

#### flipped

```python
flipped() -> Orientation
```

The other orientation.

### PlacementDirection

Bases: `StrEnum`

Where add\_panel puts a panel relative to its reference.

### Key

Bases: `StrEnum`

The keys of a dockview layout JSON.

### NodeType

Bases: `StrEnum`

The two node kinds of a dockview grid.

### 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.

### 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.

### 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.

### Placement

```python
Placement(panel: Panel, reference: str | None, direction: PlacementDirection)
```

Where add\_panel puts one panel: beside, below or within the group of a reference panel.

Methods:

- **`add_on`** –

  The position add-on for add\_panel, empty for the first panel.

#### add\_on

```python
add_on() -> Layout
```

The position add-on for add\_panel, empty for the first panel.

### template\_name

```python
template_name(panel_id: str) -> str
```

The trame template a panel renders through.

### panels

```python
panels(node: Node) -> list[Panel]
```

Every panel under a node, in authored order.

### panel\_ids

```python
panel_ids(node: Node) -> list[str]
```

The ids of every panel under a node, in authored order.

### placements

```python
placements(root: Split) -> list[Placement]
```

The add\_panel sequence that rebuilds the tree group by group: each sibling beside or below the previous one, tabs within the first.

### layout\_json

```python
layout_json(root: Split) -> Layout
```

The dockview serialization of the authored tree: splits alternate orientation from a horizontal root, weights become sizes, each leaf one tab group.

### restorable

```python
restorable(saved: Mapping[str, object] | None, ids: Sequence[str]) -> Layout | None
```

The saved layout when it names exactly the authored panels, with popout and floating groups folded into the first grid group; else None.

## femforge\_workbench.persist

UI state persistence: the UiStateStore protocol and a debounced per-user JSON implementation under the user config directory.

Type Aliases:

- **`Data`** –

  A persisted document: JSON-ready values by key.
- **`Scope`** –

  The (user\_id, workbench\_name) a saved document belongs to.

Classes:

- **`StateKey`** –

  The documents the shell itself persists per workbench.
- **`UiStateStore`** –

  Where a workbench keeps its UI state per user: layouts, dropdowns, brushes.
- **`LocalJsonStore`** –

  One JSON file per (user, workbench, key), written at most once per debounce window; a load sees unflushed saves.

Functions:

- **`default_user`** –

  The OS user, the local stand-in for an authenticated user id.
- **`default_root`** –

  The per-user config directory the local store writes under.

### Scope

```python
Scope = tuple[str, str]
```

The (user\_id, workbench\_name) a saved document belongs to.

### Data

```python
Data = dict[str, object]
```

A persisted document: JSON-ready values by key.

### StateKey

Bases: `StrEnum`

The documents the shell itself persists per workbench.

### UiStateStore

Bases: `Protocol`

Where a workbench keeps its UI state per user: layouts, dropdowns, brushes.

Methods:

- **`load`** –

  The document saved under `key` in `scope`, None when there is none.
- **`save`** –

  Saves `data` as the document under `key` in `scope`.
- **`flush`** –

  Writes every pending save now.

#### load

```python
load(scope: Scope, key: str) -> Data | None
```

The document saved under `key` in `scope`, None when there is none.

#### save

```python
save(scope: Scope, key: str, data: Mapping[str, object]) -> None
```

Saves `data` as the document under `key` in `scope`.

Parameters:

- **`scope`** (`Scope`) –

  The user and workbench the document belongs to.
- **`key`** (`str`) –

  The document's name within the scope.
- **`data`** (`Mapping[str, object]`) –

  The JSON-ready document.

#### flush

```python
flush() -> None
```

Writes every pending save now.

### LocalJsonStore

```python
LocalJsonStore(root: Path | None = None, *, debounce: float = DEBOUNCE_SECONDS)
```

One JSON file per (user, workbench, key), written at most once per debounce window; a load sees unflushed saves.

Methods:

- **`path`** –

  The JSON file of one document.
- **`load`** –

  The document: a pending save first, else the file's, None when neither exists.
- **`save`** –

  Queues the document and restarts the debounce timer that writes it.
- **`flush`** –

  Writes every pending document now.

#### path

```python
path(scope: Scope, key: str) -> Path
```

The JSON file of one document.

#### load

```python
load(scope: Scope, key: str) -> Data | None
```

The document: a pending save first, else the file's, None when neither exists.

#### save

```python
save(scope: Scope, key: str, data: Mapping[str, object]) -> None
```

Queues the document and restarts the debounce timer that writes it.

Parameters:

- **`scope`** (`Scope`) –

  The user and workbench the document belongs to.
- **`key`** (`str`) –

  The document's name within the scope.
- **`data`** (`Mapping[str, object]`) –

  The JSON-ready document.

#### flush

```python
flush() -> None
```

Writes every pending document now.

### default\_user

```python
default_user() -> str
```

The OS user, the local stand-in for an authenticated user id.

### default\_root

```python
default_root() -> Path
```

The per-user config directory the local store writes under.

## femforge\_workbench.feeds

A panel's refresh loop: one task per panel, polling off the server loop and publishing on it.

Classes:

- **`Feed`** –

  Runs `poll` in a worker thread every `period` seconds and hands what it returns to `publish` inside a state context.
- **`QueueFeed`** –

  Drains an asyncio queue another party feeds on the server loop, handing each burst of items to `publish` inside a state context.

### Feed

```python
Feed(panel: PanelComponent, period: float, poll: Callable[[], T], publish: Callable[[T], None])
```

Runs `poll` in a worker thread every `period` seconds and hands what it returns to `publish` inside a state context.

Parameters:

- **`panel`** (`PanelComponent`) –

  The panel whose state the publish runs in.
- **`period`** (`float`) –

  Seconds between polls.
- **`poll`** (`Callable[[], T]`) –

  Reads what changed, off the server loop.
- **`publish`** (`Callable[[T], None]`) –

  Writes a poll's result into the panel, on the server loop.

Methods:

- **`start`** –

  Starts the loop unless it already runs; the first poll happens immediately.
- **`stop`** –

  Cancels the loop, so a hidden panel costs nothing.
- **`cycle`** –

  Poll, publish, sleep — until the task is cancelled; a failed poll is logged and the loop carries on.

Attributes:

- **`polls`** –

  How many polls have completed; a panel shows it as its refresh count and the tests wait on it.
- **`running`** (`bool`) –

  Whether the loop runs.

#### polls

```python
polls = 0
```

How many polls have completed; a panel shows it as its refresh count and the tests wait on it.

#### running

```python
running: bool
```

Whether the loop runs.

#### start

```python
start() -> None
```

Starts the loop unless it already runs; the first poll happens immediately.

#### stop

```python
stop() -> None
```

Cancels the loop, so a hidden panel costs nothing.

#### cycle

```python
cycle() -> None
```

Poll, publish, sleep — until the task is cancelled; a failed poll is logged and the loop carries on.

### QueueFeed

```python
QueueFeed(panel: PanelComponent, subscribe: Callable[[AbstractEventLoop], Queue[T]], unsubscribe: Callable[[Queue[T]], None], publish: Callable[[list[T]], None])
```

Drains an asyncio queue another party feeds on the server loop, handing each burst of items to `publish` inside a state context.

Parameters:

- **`panel`** (`PanelComponent`) –

  The panel whose state the publish runs in.
- **`subscribe`** (`Callable[[AbstractEventLoop], Queue[T]]`) –

  Hands out a queue fed on the given loop.
- **`unsubscribe`** (`Callable[[Queue[T]], None]`) –

  Stops feeding a queue `subscribe` handed out.
- **`publish`** (`Callable[[list[T]], None]`) –

  Writes a burst of items into the panel, on the server loop.

Methods:

- **`start`** –

  Subscribes and starts draining unless it already runs.
- **`stop`** –

  Cancels the drain and unsubscribes, so a hidden panel is no longer fed.
- **`cycle`** –

  Wait for an item, take every other one already queued, publish them together — until the task is cancelled.

Attributes:

- **`bursts`** –

  How many bursts have been published; the tests wait on it.
- **`running`** (`bool`) –

  Whether the drain runs.

#### bursts

```python
bursts = 0
```

How many bursts have been published; the tests wait on it.

#### running

```python
running: bool
```

Whether the drain runs.

#### start

```python
start() -> None
```

Subscribes and starts draining unless it already runs.

#### stop

```python
stop() -> None
```

Cancels the drain and unsubscribes, so a hidden panel is no longer fed.

#### cycle

```python
cycle() -> None
```

Wait for an item, take every other one already queued, publish them together — until the task is cancelled.
