# Fisi Dock reference

Everything Fisi Dock draws, where each piece of data comes from, and how it fits into Gentle Shell. For installing and a quick start, see the [README](../README.md).

## What it changes

While Gentle Shell's fullscreen rail is on screen (fullscreen mode, terminal at least 140 columns wide, Status placement `auto` or `right`):

- **Side panel**: replaces the rail's Status section with the Fisi Dock panel: the GENTLESHELL wordmark, then framed `session`, `heads-up` (only when something is left open), `ODD` (always on screen), `RDD`, `CI` (only while the branch has GitHub Actions runs), `agents`, `todo` (always on screen) and `changes` sections, in that order, each section in its own color: its title and the counters on its top rule (`3/7`, `on`) in that color made vivid (see **Vivid labels** below), the frame a neutral gray that fades (design sidebar-chrome/02: the top and bottom borders by column, the sides by row, each brightest at its middle and sinking into the background at the corners) and the rows in their own colors. Between the chat and the panel the rail draws a divider: a `│` in its first column, brightest halfway down and fading out at the top and bottom (blank where it is too dim to see), then a blank column, so the panel is two columns narrower than the rail (45 on the official 47-column rail); on a screen taller than the panel the rail fills its rows so the divider spans them. Context, cost, the model profile, Engram memory and MCP servers are rows inside `session`; reviews have their own `RDD` section; there is no footer under the panel. Gentle Shell builds that wrap rail parts in their own cards (the transparent chrome surface on its `main` branch) get every panel row as a loose row, so the panel keeps its own frames instead of sitting inside a host card that would cut its right border.
- **Only this panel in the rail**: Gentle Shell's own `todo`, `jobs` and `agents` rail sections and the status bar it paints under the rail (branch, permission mode, model, effort, context, cost) are taken over with parts that paint nothing, the same way the Status section and the top row are, so nothing repeats; Gentle Shell gets every original back when Fisi Dock unloads.
- **Short screens**: when the panel is taller than the terminal (its rows minus the top row and the gap under it), it leaves out, in this order and only as far as needed, the `│` spacer rows between ODD steps and between RDD reviews (the gray lines under finished ODD steps and finished RDD reviews drop with them; the active step and a running review keep their line; the gray lines under the `CI` rows drop with them too), then the `changes` section, then the `CI` section when every check passed, then the `CI` section while a check runs, waits or failed, then the `RDD` section together with every ODD row but the latest step while a request runs. The wordmark, `session`, `heads-up`, `ODD` (at least its title and one row), `agents` and `todo` are never left out; if the panel is still too tall, the rail scrolls. `/dock` is not affected: it scrolls and shows everything.
- **Start screen**: when Gentle Shell's `gentle:banner` command exists, the header Pi shows above the chat at startup is Fisi Dock's welcome card instead of Gentle Shell's rose banner (design 08): a gray frame with `Gentle Shell <version>` and a pink `with Fisi Dock`, `Ready in <project> on <branch>.`, the five commands to begin with (`/resume`, `/new`, `/changelog`, `/hotkeys`, `/quit`), and `Tip: alt+k opens the command palette.` under it, centered, 63 columns wide or the screen's width; under 34 columns only the title and ready lines show. The version is Gentle Shell's, from the `provisioned` record for this agent home in the `config.json` one folder above it (`4.6.0-main.dd37ffe` reads `4.6.0-main`); without it the title is `Gentle Shell`. It goes through Pi's `ctx.ui.setHeader`: Gentle Shell sets its banner a little after the session starts, which disposes the card, so the card takes the header back on the next microtask (at most 5 times per session). Without that command (no Gentle Shell) there is no card and no `loaded` box: Pi keeps its own header and listing.
- **Loaded box**: under the welcome card, Pi's list of loaded skills, prompts and extensions (and context files, when Pi lists them) is one box in the same frame (design start-screen/02): `loaded` on its top rule, one row per kind with the kind, its count right-aligned and as many names as fit, cut with `…` (`skills      24  btw, chained-pr, cognitive-doc-design, g…`), and `ctrl+o lists them` on its bottom rule, naming the key Pi binds to `app.tools.expand`: the one Pi's editor holds, live; when Fisi Dock cannot reach the editor's keybindings, the one in Pi's `keybindings.json` (in `PI_CODING_AGENT_DIR`, else `~/.pi/agent`), read once when the box installs, with the legacy `expandTools` id counted and a list of keys joined with `/` (`ctrl+e/alt+o`); with no override there, or a missing or malformed file, Pi's default `ctrl+o`. A key bound to nothing (`[]`) leaves the rule without a hint. A kind with nothing loaded is left out. The box shrinks with the card, and under 34 columns its rows print without a frame or hint. Pressing that key shows Pi's own full listing, grouped by scope with paths, and pressing it again brings the box back. Pi's `[Skill warnings]`, `[Prompt conflicts]`, `[Extension issues]` and `[Theme conflicts]` always print under the box as Pi prints them. The box shows only while the welcome card holds the header (while Gentle Shell's rose briefly replaces it, Pi's listing shows), and only when Pi prints its listing (not with `quietStartup`). How: Fisi Dock finds Pi's loaded-resources container next to the card in the TUI tree and wraps the render of that one instance (never a shared prototype), reading the names from Pi's collapsed sections on each render, so Pi refilling the list on `/reload` keeps the box. `/fisi-dock off` and session shutdown put Pi's own render back. When Pi's tree or its sections have a shape Fisi Dock does not recognize, Pi's listing stays as it is.
- **Top row**: shows only the project path. Model, effort, context and cost are in the panel and on the prompt box, so nothing repeats.
- **Prompt box**: Gentle Shell's prompt is closed into a box with the labels on its top rule: `╭─ ✿ waiting for input ──── ● SUPER-YOLO  opus-5.5 · high ─╮`, then `│ › … │` rows, then a `╰────╯` bottom rule. The left label is Gentle Shell's own (`waiting for input`, `working…`, or the ODD phase while a request runs); when it is an ODD phase (`exploring…`, `implementing…`, `checking…` and the rest as Gentle Shell writes them) the box puts `ODD ` before it: `╭─ ✿ ODD exploring… ──── ● SUPER-YOLO  opus-5.5 · high ─╮`, never for `waiting for input`, `working…` or `queued`. `ODD ` takes 4 columns: the right part still gives way first (it compacts, then drops), and only where even the bare box would not fit with it does the label show without it; the right part is the permission mode after a red dot (only when it is not `NORMAL`, the rule of Gentle Shell's own status bar), then the model and effort: the dot and the mode bold in vivid red, the model muted, the effort in vivid blue (the line under the prompt paints them the same). Gentle Shell's own hint on that rule (`esc again to clear`, the vim mode) stays after the labels; its `[ Panel ]` button (the phone sheet with its rail sections, empty while Fisi Dock paints them) is left out, and a tap on the rule row does nothing. The full panel on the phone is `/dock`. The box paints whenever Fisi Dock paints: Gentle Shell's sidebar state on the terminal, in the wide rail chrome and in the narrow fullscreen chrome. How: Fisi Dock wraps Gentle Shell's editor factory (the wrapper carries Gentle Shell's own `gentle-pi.prompt-owner` marker, so Gentle Shell's shutdown and reinstall treat it as their own), renders Gentle Shell's editor four columns narrower and rebuilds only its frame rows: any rows above it and Pi's autocomplete rows under it stay untouched, the editor's invisible cursor markers (APC sequences) count no columns, and mouse clicks are shifted back by the border. Gentle Shell's own work row and agent-question row above the prompt do not show while Fisi Dock empties the `agents` part they read: a question still opens Gentle Shell's own selector, the `agents` card marks it with `!`, and `/gentle:agents` opens the list. The factory it replaced comes back when Fisi Dock unloads. A Gentle Shell update that changes the shape of its panel rule (`─ ✿ label ──── hint ─`), its blank bottom row or its editor's `topRowCount` can break the box: it then falls back to Gentle Shell's own rows and the line below, and `tests/gentle-shell-keys.test.ts` fails against the installed package when `gentle-pi.prompt-owner`, `framePromptLines`, `panelPromptRule` or `topRowCount` are gone.
- **Prompt box on a narrow rule**: when the width cannot hold the labels beside the idle label (a phone, idle), the label's words drop first and the petal alone keeps them: `╭─ ✿ ──── ● SUPER-YOLO  opus-5.5 · high ─╮`, with Gentle Shell's hint after them when it fits too. The idle label is told by how Gentle Shell paints it, not by its words, so a translated, custom or clipped one (`waiting for i…`) gets the same rule: a label Gentle Shell animates (the working scan) is never idle, and one painted whole is idle unless it is `queued`, `working…` or an ODD phase (whole or clipped with `…`) or the editor's scroll indicator (`↑ 3 more`, text out of view). `working…`, `queued`, the ODD phase and the scroll indicator always keep their words. Still too tight, the labels compact step by step instead of dropping: tight separators (`● SUPER-YOLO opus-5.5·high`), then without the effort (`● SUPER-YOLO opus-5.5`), then the mode alone (`● SUPER-YOLO`; the model alone in normal mode), so working on a phone reads `╭─ ✿ working… ─ ● SUPER-YOLO opus-5.5 ─╮` at 40 columns. Each step is tried beside Gentle Shell's hint first (`● SUPER-YOLO  INSERT`); with no room for both, the hint wins beside a working label, as it does over the full labels, and our labels win beside the idle one. Only when not even the smallest step fits do the labels drop, then the hint; under 20 columns Gentle Shell's rows stay as they are.
- **Working label**: the words come from Gentle Shell (`exploring…`, `implementing…`), with `ODD ` before a phase. In the box the color (pastel pink `#f2a7c9` at the head, pastel lilac `#c3a6ff` beside it, gray elsewhere; soft on purpose, so the passing color does not shout inside the box) moves across the whole `ODD exploring…` by the clock, at a steady pace, however often the screen is redrawn, and the petal before it turns through Gentle Shell's four flowers (`✿ ❀ ❁ ✾`, in its tones) on the same clock, one flower per wave step (120 ms), each one column wide so nothing shifts. Gentle Shell's `potato` animation keeps its own label and petal, with `ODD ` in the label's gray; under `performance` the box moves the wave and the petal by the clock too, since Gentle Shell's own there step once a second.
- **Under the prompt** (fallback): when the box could not be installed (no Gentle Shell editor yet, someone else's editor) or did not fit, one line with the same labels: `SUPER-YOLO · opus-5.5 · high` (`opus-5.5 · high` in normal mode). Too narrow for it, the labels compact the same way (`SUPER-YOLO·opus-5.5·high`, `SUPER-YOLO·opus-5.5`, `SUPER-YOLO`) before the line is cut with `…`. The mode, on the box and on the line, comes from Gentle Shell's permission-mode adapter, asked over `pi.events` on `gentle:yolo:host-ui` like its `/gentle:customize` does, and follows `/gentle:mode` and `/gentle:yolo`; without an answer the labels are model and effort only. The prompt editor itself stays Gentle Shell's.
- **Vivid labels**: section titles, the counters on their top rule and the labels Fisi Dock paints on the prompt box take the vividness of the cover title `#F43888` on their own hue: HSL saturation 90% and lightness 59%, and only for a hue that then reads under 4.5:1 on the `#161616` background, lightness up in steps of 0.01 until it does, at most 0.80 (`vivid` in `lib/palette.ts`). Amber `#e0af68` becomes `#f5a838`, lilac `#c3a6ff` `#9464f7`, coral `#ff9b85` `#f55a38`, teal `#5fd4e0` `#38e3f5`, pink `#f2a7c9` `#f5388e`, deep pink `#c9779f` `#f53894`, green `#8fc98f` `#38f538`, blue `#7b9cff` `#4c76f6`, red `#ff6b6b` `#f53838`, purple `#b48cff` `#945ff7`, rose `#ff76c3` `#f538a2`. The side panel is a little calmer: its section titles and their counters (on the rail, in `/dock` and on the narrow-screen todo and agents cards) go 75% of the way from their own color to the vivid one (`panelVivid` in `lib/palette.ts`; pink `#f2a7c9` becomes `#f4549d`, amber `#f0aa44`, teal `#42dff0`). The working label's wave is the exception and passes in its pastel tones (see **Working label**). Gray has no hue to raise: muted counters (`1 to commit`, the CI sha, `off`) and the model stay muted. Gentle Shell's own label, petal and hint keep the colors Gentle Shell paints them.
- **Theme**: ships the `Fisi-Dock` Pi theme with the design's palette and a pink-tinted selection: its accents (`pink`, `deepPink`, `teal`, `coral`, `amber`, `lilac`, `green`, `blue`, `red`) are the vivid values above, its neutrals and backgrounds the panel's own. The first start with Fisi Dock on applies it once and remembers the theme you had, for `/fisi-dock off` to put back; later starts never call Pi's theme setter (see [Turning Fisi Dock off and on](#turning-fisi-dock-off-and-on)). It is also one more choice in `/settings`. It also ships `Fisi-Dock-Soft`, the same theme with the pastel accents from before 0.3.0, for the `soft` colors of [Look settings](#look-settings), and for its other accents `Fisi-Dock-Teal`, `Fisi-Dock-Lilac`, `Fisi-Dock-Amber`, `Fisi-Dock-Green` and their `-Soft` themes, generated from those two (see **Accent themes** under Look settings), and `Fisi-Dock-Light` for the `light` colors, generated from `Fisi-Dock` (see **Light** under Look settings).

### Narrow screens

On narrow or non-fullscreen terminals (a phone over SSH, a split pane) the rail is not shown. Gentle Shell's bars stay as they are, and Fisi Dock adds:

- **Top row**: Fisi Dock's own row instead of Gentle Shell's: ` project · branch  ▰▰▱▱ 40%  $0.012  ●●●`, with the project in bold, the branch in green, a 4-cell context meter in the session colors (teal, amber from 60%, red from 85%) with its percent, the session cost muted (with the cost at 0 and tokens used, `12.4M tok · ≈$38` instead, as in the `cost` row) and one dot per MCP server (green connected, red failing, gray still connecting or unknown). It never shows the model or effort: the prompt box (or, as the fallback, the line under the prompt) shows them. On a tight screen the dots drop first, then the cost, the meter and the branch; the project is cut last. Clicking it does nothing. Without Gentle Shell's sidebar state the row stays Gentle Shell's.

- **Strip above the prompt**: one line with what the panel would show and the top row does not, each part in its section color and only when it has data: `✓ ODD 2:45` once the request finished (while a phase is active, the prompt box's working label names it, so the strip does not repeat it), `todo 3/7` (only when no todo card shows, see below) and changed files (`±1 file`). Running agents are not counted here: the agents card lists them. A second line appears only for heads-up warnings (`! 1 commit not pushed · ! ODD plan fisi-dock`). On a tight screen trailing parts drop whole; with nothing to show the strip takes no space. It disappears as soon as the rail shows the panel.
- **Agents card under the strip**: while an agent, a 4.6.0 subagent task or background job is running (or a subagent task waits for you), an `agents` card framed like the panel sections but in a flat gray (the rail's frames fade, the cards' do not), with the number running on the right of its top border and one row per running or waiting agent: its turning spinner (an amber `!` for a task that waits for you), name, task (cut to fit) and the time it has been running, `m:ss` (`h:mm:ss` past an hour), ticking each second on the spinner's repaints (with `potato` or `FISI_DOCK_ORBS=off` there is no timer, so it moves only when something else repaints). A job's time counts from the start Gentle Shell reports for it; a job that reports no start counts from when Fisi Dock first saw it running, so one already running when Fisi Dock loaded shows less than its real time. A subagent task's time counts from its first `running` event (until then from its tool call), or from the start in the presence file when it was seeded after a `/reload` (see the `agents` row below). An agent with a model gets the same model label as in the side panel, after its time (`0:42 opus · high`). Up to 4 agents, then `+N more  /dock all`. Queued, finished and failed agents are not listed (the side panel still shows them for a while), and with nothing running the card takes no space.
- **Todo card under the agents card**: while there is a todo list, Fisi Dock's compact `todo` section, framed to the full width in a flat gray: the vivid coral title with `8/10` on the right, `✓ 8 done` with its bar, the active task highlighted with its spinner, the next task and `+N more  /dock all`. It keeps a fixed height however long the list is (the side panel lists the tasks instead); `/dock` shows every task. Its header already says `d/t`, so the strip leaves `todo 3/7` out while the card shows. Gentle Shell's own Todos card stays hidden. The card goes away with the strip when the rail shows the panel.
- **Prompt box**: the same closed box as on wide screens; on a narrow rule its labels compact step by step (see Prompt box on a narrow rule above).
- **`/dock`**: the side panel over the whole screen, scrolled with the arrows or a swipe (see Commands).

### Wide and narrow screens

- **Your messages**: each message you send sits in a gray box with `you` in vivid pink on its top rule (`╭─ you ───╮`), like the prompt box (both drawn in the look's `frames`, see [Look settings](#look-settings)), so it stands apart from tool blocks of the same background. It shows whenever Gentle Shell's sidebar state is on the terminal (fullscreen mode, with or without the wide rail); without Gentle Shell, and on terminals under 10 columns, Pi's own message lines show unchanged.

Without Gentle Shell, Fisi Dock paints nothing on its own (no rail parts, no strip, no agents or todo card, no prompt box, no message boxes, and no welcome card or `loaded` box, so Pi keeps its own startup header and listing); `/dock` still opens the panel when asked.

### Panel data

| Section | Source |
|---|---|
| session | design session-section/02 (Grouped) without its group labels (sidebar-chrome/02): three groups, **where**, **usage** and **connections**, one after the other with no label row or rule between them, and one muted key column (`project`, `context`, `profile`, `memory`, `mcp`) so every value starts at the same place. **where**: `project  fisi-dock  main ↑2`, the project folder in bold, the git branch and the commits to push in green (no `↑N` while the heads-up says it). **usage**: the `context` row from Pi: a meter whose filled cells are a heavy `━` in the state color (teal, amber from 60%, red from 85%) and whose empty cells are a thin faint `─`, with a faint `┼` on the cells holding 60% and 85%, then the readout flush right on the same row, `5% · 53.5k/1M` (the percent bold in the state color, the tokens in context and the window size; `5% used` when Pi gives no token counts); the readout keeps at least 13 columns, and when that would leave the bar under 16 cells (the 34-column rail) the bar takes the whole row and the readout moves right-aligned to the row under it; no row when Pi does not know the percent. Then the `profile` row with the cost flush right (below); without a profile the cost keeps a row of its own (`cost     $0.012`). **connections**: the `memory` row for Engram (see Memory below), then one row per MCP server, at most 4 rows on the rail (see MCP servers below), or a muted `mcp      none configured` without MCP config. The `profile` row: the agent-model profile this session's launches use (`/gentle:profiles`), the label Gentle Shell's own Status section and top row show, which Fisi Dock takes over: `claude` for the global profile, `claude (session)` for one chosen with Use in this session, `team (local)` or `team (repo)` for a pinned one; read from Gentle Shell's in-process profile registries with each Pi read (session start, every turn) and, while no run is active and the panel is on screen, every 10 s; no profile row when the session has no profile or that state cannot be read. The cost: the session cost (`$0.012`), or, when that cost is 0 but the session used tokens (a subscription such as pi-claude-bridge records tokens at cost 0), the session tokens and an API-price estimate: `12.4M tok · ≈$38` (see Tokens and the ≈ estimate below); a long profile is cut before the cost is. The connection rows put the names and the state words in their own columns (names past 12 cells are cut), with one glyph per state so none rests on color alone: green `● connected`, amber `◌ connecting`, red `✗ failed` or `✗ unreachable`, muted `○ not installed`; the gap between name and word closes to one space when two would not fit, and the saved count is the first thing to drop |
| heads-up | red warnings, drawn only when there is one (at most 5 rows, then `+N more`): other open git worktrees, todo tasks still open after a run ended, unfinished ODD plans in the project's `odd/tasks/*.md`, commits not pushed (replaces the branch's `↑N`), local branches not merged into the default branch |
| ODD | always drawn: before the first request one muted row `○ ready · phases show while I work`; while a request runs, the timeline of ODD phases with times (latest 6 steps), where the active step shows an animated braille orb in its phase color and its phase name in gray with a white shine sweeping across it, like the prompt box; when the agent's turn ends while this session's subagents still run (queued, running or waiting), the active step stays active and keeps counting until the last of them finishes or the request settles; under each step one gray line, aligned with its name, says what it did: under the active step what its last tool is doing (`editing …/tables/table-row.ts`, `running npm test`), under each finished step a summary (`edited lib/tables/ · 2 files`, `ran npm test · +2 more`, `read 3 files · 1 search`; past 256 different files it says `256+ files`). Paths are relative to the project folder (without regard to case only on Windows drive and network paths); a line too long for the rail keeps its count whole and cuts the path in the middle at a folder (`edited …/tables/ · 12 files`). Commands, paths and labels come from the model, so escape sequences, control characters and bidi overrides are removed before they reach the terminal; once the request is over, one row `✓ last request 2:45 · 4 phases` (the request's wall time and how many phases it went through). `/dock` keeps listing every step after the request |
| RDD | the session's RDD reviews from Gentle Shell's review events, one row per review, newest last (latest 6, then `+N earlier`), named by the first reviewed file and ` +N` more. A review whose files are not known yet shows as `changes` until they arrive; if the answer is that no review runs (an inspect or status check), that row goes away. On the title's right the RDD switch for the project folder: `on` in vivid green or `off` muted, read from Gentle AI itself with its read-only `gentle-ai review mode status --cwd <folder> --json` at session start and after each turn, one call at a time with a 5 s timeout; a failed, timed-out or unreadable read keeps the last known switch (unknown at first: no switch). The binary is `gentle-ai` on PATH, else the one bundled in the `gentle-pi` package (`.gentle-ai/<version>/gentle-ai.exe` on Windows, `gentle-ai` elsewhere): the package the `gentle-shell` launcher names in `GENTLE_SHELL_CHILD_PACKAGE_INJECTION`, else the global npm one (`%APPDATA%\npm\node_modules\gentle-pi` on Windows; the npm prefix, node's own prefix, `/usr/local`, `/usr` and `~/.npm-global` elsewhere). A running review is highlighted with a turning teal spinner, `reviewing…` (or `fixing…` while findings are fixed) and its time; one waiting for you shows an amber `!` and `needs your OK`; finished ones show `✓ approved`, `✗ outdated · code changed` (changed after approval), `✗ unavailable` or a muted `skipped`, with the local time it ended (`23:05`). Under each review one gray line, aligned with its name, read from the review tools the agent calls (never from the opaque bindings): while it runs, what it is doing (`starting · readability, reliability`, `asks to review · 2 lenses`, `preparing 2 reviewers`, `running 2 reviewers · 1 done`, `fixing · up to 38 lines`, `validating the fix`, `closing · 7 findings, none blocking`, `stopped · 2 findings need your decision`); once it ends with its findings known, what it found (`7 findings · none blocking · 2 lenses`, `3 findings · 1 fix · 2 lenses`, `no findings · 1 lens`); a review that ends without findings known (outdated, unavailable, skipped) has no line. With no review yet: a green `●` and `watching · reviews high-risk changes` when on, `○ off · changes are not reviewed` when off. Not drawn when neither the switch nor any review is known (no Gentle AI) |
| CI | the GitHub Actions checks of the current branch's last pushed commit, read with `gh` in the session folder (`gh run list --branch <branch> --limit 20 --json …`, the branch from the git read): the commit is the newest run's, one row per workflow of that commit (its newest run). On the title's right the commit's short sha, muted. While a check runs, waits or failed: one row per workflow, sorted by name (at most 6, then `+N more`): a running one highlighted with a turning blue spinner, its name bold, `running…` and the time since it started, ticking with the spinner (when no gh read has succeeded for over 90 s, its time stays at the last successful read and its spinner holds still until the next one); a queued one with a faint `○`, its name muted and `queued` on the right; `✓ lint passed` and `✗ tests failed` (red) with their duration, a failed one with a gray line naming the failed job and its first failed step (`node-22 · Run npm test`, from `gh run view <id> --json jobs`, read again only when the run is updated, for example a re-run; a view that names no failed job leaves no line and is not read again, a failed view is retried on the next read); a cancelled or stale one muted with `-`. Once every check passed, one row `✓ 3 checks passed` (`1 check passed` for one) with the local time of the last update (`14:02`) and a gray line with the workflow names and the longest run (`build, lint, tests · longest 2:41`; only the names when gh gave no durations). gh is read at session start, every 30 s while a check runs or waits, every 3 min otherwise, 5 s and 20 s after a `bash` command with `git push` ends, and when the branch changes to another known branch (the shown checks go at once and the new branch is read; a git read that fails to name the branch changes nothing); never while drawing. Every name from gh is cut to 80 characters and loses escape sequences, control characters and bidi overrides. A failed or timed-out gh read keeps what is shown; no `gh`, no GitHub remote, no workflows or no branch: no section |
| agents | Gentle Shell's background jobs, from its `gentle-ai:jobs-sidebar` event (finished ones ride the snapshot a short while, as `✓` or `✗`), then Gentle Shell 4.6.0's own subagent tasks (its `subagent_run` / `subagent_continue` tools) as rows of the same shape, in the order they started; at most 4 agents, then `+N more`. A task's state comes from the `gentle-pi:notification:source` events Gentle Shell publishes for this session (`queued` `○`, `running` spinner, `waiting` `!` on a highlighted row when the subagent asks you something, `completed` `✓`, `failed`/`cancelled`/`timed_out` `✗`), ordered by their sequence; its name (the agent) and task text from the tool call's arguments, and its label, model and effort from the tool result (`details.gentleAgents`), which names the task id. A `subagent_run` / `subagent_continue` call in flight is a running row from the moment it starts, before any event, so a running subagent shows even if its events never arrive: the first event of a new task takes the oldest call still waiting (a continue, its own task), the result sets the state while no event has, a refused launch leaves no row, a refused continue puts the row back as it was, and a call whose end was lost goes when the run settles. A task only events name shows as `subagent` with `task <id>` until details arrive. With calls in parallel, two tasks can show each other's text until their results arrive. Its time counts from its first `running` event, until then from the call. Finished tasks stay 60 s, the tail of Gentle Shell's finished jobs, and fold on the next event, tool end or idle refresh. Limits: Gentle Shell's task store is private, so there are no live tokens, cost, turns or tool counts per task; a task started before Fisi Dock loaded (after a `/reload`) shows from one read of Gentle Shell's presence activity file (`<agent home>/gentle-agents/presence/<sha256(session id)>.<incarnation>.activity.json`, agent home `GENTLE_PI_AGENT_HOME`, else `PI_CODING_AGENT_DIR`, else `~/.pi/agent`) at session start, with its own start time; a missing or unreadable file changes nothing. Tool arguments and results are model text, so they are made printable and bounded (escapes, controls and bidi overrides removed, 120 characters). An agent with a model gets a label at the right end of its row: the model family (`opus`, `sonnet`, `haiku`; a model with none of these shows its short name, `gpt-5.1`, any `provider/` prefix and `claude-` dropped) in its effort's color (`low`/`minimal` amber, `medium` blue, `high`/`xhigh`/`max` purple, any other or no effort gray), then ` · <effort>` in gray (`opus · high`); no effort: the family alone; no model: no label. Each agent stays one row: when it does not fit, the task is cut first, then ` · <effort>` drops, then the label. The label never plays the entrance |
| todo | always drawn: with no todo list or 0 tasks one muted row `○ no tasks · the list shows while I plan` and nothing on the title's right (the todo card above the prompt and the strip's `todo d/t` still show only with tasks). Otherwise Gentle Shell's todo list; on the title's right a progress bar (`▰▱`, up to 10 cells) and `done/total`. Every task in order while there are at most 8 (`✓` done muted, the active one highlighted with its spinner, `○` pending, `◌` blocked, `-` dropped). Past 8, a window of 7 tasks starting at the task before the active one (else the first open task, else the last task), after a `✓ N earlier done` row (`N earlier` when some of them are not done) and before `+N more` with a `/dock all` hint. Without the task list (only counts known yet), at most 4 rows: done count with its bar, active task, next task, `+N more`. `/dock` lists every task (see Commands) |
| changes | files changed since the last commit, one row per file, the most changed first (lines added plus removed): a symbol for what happened (amber `●` edited, green `+` new, red `-` deleted, blue `→` renamed), the file name in bold, then its folder muted (on a narrow rail the folder is cut before the name). On the right, `+12 -3` lines added and removed for edited files (muted when 0), or `new`, `deleted` or `renamed`. `N to commit` on the title's right. At most 4 files, then `+N more · /dock all`; `/dock` lists every file. With nothing to commit: `✓ everything committed`. Not drawn when git is unknown (not a repository, or not read yet) |

Git data (changes, branch, worktrees, unpushed commits, unmerged branches) is read at session start and after every turn and run; ODD plans when a run settles. While no run is active and the panel, the narrow strip or the top row is on screen, an idle session re-reads git, the heads-up git data, the plans, the MCP and Engram state and the model profile every 10 s, through the same one-at-a-time readers as the run triggers, so reads never overlap.

### Tokens and the ≈ estimate

Cost and tokens come from the session's assistant messages, the same entries Pi and Gentle Shell sum for the cost. The cost is the sum of `usage.cost.total`; while it is above 0 it shows as `cost $N.NNN`, as before. The tokens are the sum of `usage.totalTokens` (or `input + output + cacheRead + cacheWrite` when a message has no total), shown as `53.5k` or `12.4M`.

When the cost is 0 and tokens were used, the row shows the tokens and an **estimate** of what they would cost at API prices, marked with `≈`: whole dollars from $10 (`≈$38`), cents below (`≈$4.27`). It is an estimate, not a bill: a subscription is not charged per token. Each message is priced with Pi's own model prices, never a table in Fisi Dock: `ctx.modelRegistry.find(provider, model)` for the message's own provider and model id, and its `cost` rates (dollars per million tokens for input, output, cache read and cache write, with Pi's price tiers and 1h cache writes at twice the input rate, the same math as Pi's `calculateCost`). pi-claude-bridge registers Anthropic's models under the same ids at price 0, so when the own provider has no price the same id under `anthropic` is used. If any message with tokens has no price, no estimate shows (only the tokens): a partial sum would look like the total.

### Memory (Engram)

Engram is not an MCP server: it reaches Pi through the Pi package gentle-engram, which registers tools named `mem_*` (`mem_save`, `mem_search`, `mem_context`, …). The `memory` row reads only what Fisi Dock already has; it starts no process and asks no server:

- muted `○ engram  not installed` when Pi's tool list (`pi.getAllTools()`) has no tool named `mem_save`;
- green `● engram  connected · N saved` otherwise, where N counts the `mem_save` calls that ended without error in this session (Pi's `tool_execution_end` events, `isError` false; `· 0 saved` before the first one);
- red `✗ engram  unreachable` when the latest `mem_*` call ended with an error; the next `mem_*` call that ends without one makes it connected again.

So "connected" means the tools are loaded and the last call did not fail, not that the Engram server answered just now. The tool list is read with every Pi read and by the idle refresh, the calls at each tool end. No row while Pi's tool list cannot be read.

### MCP servers

The `mcp` rows list the servers Pi loads, one row each, in config order: the keys of `mcpServers` (or `servers`) in Pi's global `mcp.json` (`~/.pi/agent/mcp.json`, or `$PI_CODING_AGENT_DIR/mcp.json`), then the project's `.pi/mcp.json`. Servers with `"enabled": false` (Pi's switch) or `"disabled": true` are left out; the project file can switch a global server off or on. Only the server names and that switch are read: other fields (commands, URLs, env, headers, auth) can hold secrets and are never read into the panel, logged or stored. The files are read at session start.

Pi connects the servers itself, in the background; Fisi Dock only looks. Pi registers every tool of a connected server as `mcp__<server>__<tool>` in the namespace `mcp__<server>` (with `-` in the server name replaced by `_`), also when the tools are only reachable from codemode, and keeps them as hidden tools when a server drops or is switched off in `/mcp`. So a server is:

- **green `● connected`** when Pi's tool list (`pi.getAllTools()`) has at least one visible tool with that name prefix or namespace;
- **amber `◌ connecting`** (unknown) during the first 10 s of the session (servers connect late), when it has only hidden tools, or when Pi's tool list cannot be read;
- **red `✗ failed`** (failing) when, 10 s after the session started, it has no tools at all.

The rail shows at most 4 `mcp` rows. With 5 servers or more, 3 of them keep a row, the problems first: failed, then connecting, then connected, each state in config order; the fourth row sums up the rest under the names: `+7 more · 2 connecting · 5 connected`, `+N more` muted and each count in its state's color, failed, connecting and connected in that order and only the states that occur. When the row is too narrow the counts drop from the right, then `+N more` stands alone (the 34-column rail), then it is cut. With 4 servers or fewer every one has its row, in config order, and there is no summary. `/dock` lists every server, in config order. The summary is a session row like any other on a short screen.

With no `mcp.json` (or no enabled server in it) the row says so, muted: `mcp      none configured`.

The state is refreshed with every Pi read (each turn and run) and by the idle refresh. Fisi Dock never handles Pi's `mcp_servers_change` event (handling it would make Fisi Dock the MCP connector). Pi reads a project's `.pi/mcp.json` only for trusted projects; Fisi Dock cannot see the trust setting, so an untrusted project's servers show red.

An ODD plan counts as unfinished when its `## Tasks` section has a task line not marked done (`[x]`, "done", or a recorded commit; "Commit: pending" is open). Files without a `## Tasks` section and files over 200 KB are skipped. To see the heads-up rows for any folder without Pi: `node scripts/heads-up.ts <folder>` (read-only).

The ODD orb follows Gentle Shell's animation level (`/gentle:animations`, read from its `animations.json` at each new request): `quality` and `performance` at full speed, `potato` a still frame and a still gray phase name with a once-per-second refresh. To force the still orb: set `FISI_DOCK_ORBS=off` before starting Gentle Shell.

The running spinner on the active todo task and on running agents (side panel, todo card, agents card and `/dock`) turns with the clock: `⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏`, one frame every 80 ms, repainted by a timer that runs only while an agent or job is running, or a todo task is active while a run is active. A task left in progress after the run settled shows the still `⠹`. It follows the same animation level (read when work starts): `potato` and `FISI_DOCK_ORBS=off` show a still `⠹` with no timer. Done (`✓`), queued (`○`), waiting (`!`) and failed (`✗`) rows do not move.

A row that appears plays a short entrance: a newly running agent (`agents`), the todo task that becomes active (`todo`, also on the todo card and in `/dock`; the agents card plays it for a new running agent too), a new active ODD step (`ODD`) and a new review row (`RDD`). For 3 steps of 75 ms the row and its glyph flash bold in the section color (lilac, coral, amber, rose), fading to the row's own colors; then one white shine (`#ffffff`, `#c8c8c8`, `#9a9a9a`) crosses its text once, one letter per step, and the row looks as before (the active ODD step then goes back to its gray shimmer). It lasts about a second, a little longer for long text. Only the new row moves; rows already on screen when Fisi Dock loads, `changes`, `heads-up`, the strip and the top row never play it. While an entrance plays, the spinner's timer repaints at the 75 ms step and stops when nothing runs or enters. Only colors change, so no row gets wider. With `potato` or `FISI_DOCK_ORBS=off` the row simply appears.

The wordmark reads GENTLESHELL, the words joined and told apart by color: GENTLE gray, SHELL in the pink of Gentle Shell's Gentleman-Sexy theme (`#F43888`). It is drawn in braille dots, GENTLE in a gray fade and SHELL from that pink to the theme's deep pink (`#BF0F50`), no shadow. At 51 columns or more (the panel's 53 in Gentle Shell fisi/custom's 55-column rail) it draws the wide 51-column mark; from 45 to 50 columns, the panel's 45 in the official rail's 47 usable columns included, the 45-column compact mark (the same G, N and L, a narrower E, T, S and H). Narrower rails show it as one bold text row, its letters one column apart (`G E N T L E S H E L L`, 21 columns); from 11 to 20 columns the letters join (`GENTLESHELL`); below 11 columns nothing. The mark is centered over the section frames, at most two columns in.

Fisi Dock reads Gentle Shell only through its shared sidebar state (`Symbol.for("gentle-pi.experimental-sidebar.state")` and `.cache`), its ODD phase registry and its `pi.events` channels; nothing is imported from the `gentle-pi` package, so an update cannot break Fisi Dock's loading. `tests/gentle-shell-keys.test.ts` checks the installed package still defines those keys. The package it checks is `GENTLE_SHELL_DIR` when set, else the one Gentle Shell itself loads: the package the `gentle-shell` launcher names in `GENTLE_SHELL_CHILD_PACKAGE_INJECTION` (inside a session), else the launcher's own, found from the `gentle-shell` on PATH (a pnpm or npm shim, or npm's link) up to its package; else `gentle-pi` in the global npm root (`npm root -g`). Only a folder whose `package.json` is named `gentle-pi` counts, and when several are found the version provisioned for the agent home in `~/.gentle-shell/config.json` (`GENTLE_SHELL_CONFIG` when set; the home is `GENTLE_PI_AGENT_HOME`, else `PI_CODING_AGENT_DIR`, else `~/.gentle-shell/agent`) wins, else the first found. With none (CI) those checks are skipped.

`tests/configs.test.ts` drives Fisi Dock the way Pi and Gentle Shell call it, in memory, under simulated user configurations: a translated idle and working label, no Engram (and Engram failing, then back), ten MCP servers, Vim's `INSERT`/`NORMAL` hint, a 30-column screen, a remapped or unbound expand key, all of them at once, and no Gentle Shell. Every surface (welcome card, loaded box, rail panel, top row, strip, prompt box, line under the prompt, `/dock`) is rendered at every width from 0 to 140, wide and narrow: nothing throws and no line passes the width. Each test points Pi's and Gentle Shell's dirs at a fresh temp folder, so your own configuration is never read.

## Commands

| Command | What it does |
|---|---|
| `/dock` | Shows the full Fisi Dock side panel over the whole screen, live, exactly as the rail draws it (in the logo, frames, colors, accent and motion of [Look settings](#look-settings), without the rail's divider): two rows down from the top (as the rail sits under the top row and its gap), edge to edge with one column of margin each side (at most 80 columns wide; a wider screen centers it), no frame around it, and the same colors as the side panel (no background of its own: it covers the conversation behind it with the terminal's own background). The last row says `Esc to close` and, while the panel is taller than the screen, `↑↓ scroll`. ↑/↓, Page Up/Page Down and the mouse wheel scroll it; on a phone terminal that sends swipes as the wheel (mouse reporting), a finger swipe scrolls it too. Taps do nothing. Esc closes it and returns to the prompt. Useful on narrow screens, works on wide ones too. Here the todo section is not capped: it lists every task in list order, done ones with a green `✓`, the active one highlighted with its spinner, pending ones with `○`, blocked ones with an amber `◌` and dropped ones muted with `-`. While Fisi Dock is off it only says so. |
| `/fisi-dock on` \| `off` | Turns Fisi Dock on or off without uninstalling it, remembered across restarts (see below). `/fisi-dock` alone opens a picker titled with the current state (`Fisi Dock is on`), the change listed first, so Enter switches it and Esc changes nothing (without a picker it only says whether it is on); `settings` is the picker's last choice. Typing `/fisi-dock ` with the space also suggests `on`, `off` and `settings`. Any other argument shows the usage (`Usage: /fisi-dock on | off | settings (alone: pick one)`) and changes nothing. |
| `/fisi-dock settings` | Opens the look menu over the whole screen: the rail logo, the section frames, the colors, the accent, the divider, the motion and the sections shown and their order, with the rail drawn under them as a live preview. Each change is used at once and saved. Works while Fisi Dock is off too. See [Look settings](#look-settings). |

## Look settings

`/fisi-dock settings` picks Fisi Dock's look. Six settings, each with its values in the order ←→ walks them, the default first. The defaults are the look before this menu existed, so a user who never opens it sees no change.

| Setting | Values |
|---|---|
| `logo` | `braille` (default): the GENTLESHELL wordmark as described under **Wordmark** above (wide or compact braille, then spaced or joined text on narrower rails, nothing under 11 columns). `text`: the joined bold `GENTLESHELL` row (GENTLE gray, SHELL in `#F43888`, design rail-wordmark/10) wherever 11 columns fit, centered like the braille mark; nothing narrower. `none`: no logo; the panel starts at its first section. |
| `frames` | `faded` (default): each section in a box whose edges fade toward the corners (design sidebar-chrome/02). `classic`: the flat gray box of 0.3.1 and before. `none`: no box: each title, in its color, on a `─` rule that fades at both ends, the counter on its right as on a top rule (dropped first when the rule is too narrow, then the title is cut with `…`), the rows two columns in, a highlighted row keeping its `▎` in the boxed rows' column, and one blank row between sections so each section's end shows. The prompt box and your messages follow too: `faded` keeps their box and fades its frame like the rail's (the top and bottom rule by column, the sides by row over the box's current height, so a prompt that grows as you type refades); `classic` is their flat gray box of 0.3.1; `none` keeps only their top line, a `─` rule fading at both ends with the labels in their places (`── ✿ waiting for input ──── ● SUPER-YOLO  opus-5.5 · high ──`, `── you ───`), no sides (a space each, so the text keeps its columns) and no bottom rule; a text selection's label keeps its row under the prompt, without a border. |
| `colors` | `vivid` (default): the vivid section titles, counters and prompt labels of 0.3.x (see **Vivid labels**) and the `Fisi-Dock` theme. `soft`: each of them in its own pastel as before 0.3.0 (pink `#f2a7c9`, amber `#e0af68`, blue `#7b9cff` and the rest): the panel's section titles and counters on the rail, in `/dock` and on the narrow-screen todo and agents cards, the permission mode and its dot, the effort and the turning petal on the prompt box and the line under it, and `you` on your messages; and the `Fisi-Dock-Soft` theme, the `Fisi-Dock` theme as it was before 0.3.0. The working label's wave is pastel in both. `light`: everything Fisi Dock paints, repainted for a light terminal background, and the `Fisi-Dock-Light` theme (see **Light** below). |
| `accent` | `pink` (default): Fisi Dock's pink, as before, and the `Fisi-Dock` and `Fisi-Dock-Soft` themes. `teal`, `lilac`, `amber`, `green`: that accent in place of pink, and the theme `Fisi-Dock-<Accent>` (`Fisi-Dock-Teal`, …), `Fisi-Dock-<Accent>-Soft` with `soft` colors. The accent's tone is its panel color (`colors` `vivid`: its vivid label, `soft`: its pastel), lighter where it would read under pink's contrast on `#161616` (5.03:1 vivid, 9.61:1 soft): teal `#38e3f5` / `#5fd4e0`, lilac `#9b6ef8` / `#cab0ff`, amber `#f5a838` / `#e3b675`, green `#38f538` / `#92cb92` (vivid / soft). Fisi Dock paints its own pink in it: `you` on your messages and the petal's pink frames on the prompt box in that tone; the highlight bar `▎` and the active todo spinner on the rail, `/dock` and the todo card, the head of the working label's wave and the menu's title, marker and arrows in its pastel; the petal's deep pink frames and the SHELL half of the logo moved to its hue, as bright as before. The section titles and counters keep their own colors (RDD's rose included), the lilac petal frames and the wave's lilac stay, and the permission mode and its dot stay red, a warning. With `light` colors the accent still saves and still paints Fisi Dock's own pink family, each tone as the light palette paints it (at least 4.5:1 on `#fafafa`: pink `#d20b65` / pastel `#c11d67`, teal `#06707c` / `#177079`, lilac `#7b3ff6` / `#7532ff`, amber `#925907` / `#895d1c`, green `#067706` / `#346d34`, label / pastel), but it does not change the theme: `Fisi-Dock-Light` is the one light theme, for every accent. |
| `divider` | `medium` (default): the rail's divider as described under **Side panel** above, a `│` at most 35% of the text color over the background at its middle (the same peak as the box frames). `off`: no divider glyph; the first column stays blank, so the panel keeps its place and width. `subtle`: at most 20%. `strong`: at most 55%. Every level keeps the same smoothstep fade toward the top and bottom (cells under 2% are blank). Only the rail's divider changes: the box frames keep their fade, and `/dock` has no divider. |
| `motion` | `on` (default): everything moves as described in this page. `off`: every animation shows one fixed frame: the running spinners (todo, agents, RDD, CI and the strip) the still `⠹`, the active ODD step its middle orb frame with its label muted and no shine, the prompt box's working label all muted with no wave and the petal on its first flower (`✿`), and no entrance, so new rows appear at once in their own colors. Every state keeps its glyph (`✓`, `●`, `◌`, `✗`, `!`), so nothing rests on motion alone. The timers that exist only to animate stop (the spinner and entrance timer, the label wave's): the spinner's at once, the wave's at its next step; a request already running when motion goes off keeps its ODD tick until it ends, but only its `m:ss` repaints. Data reads (git, CI, MCP, plans) go on as before, and running times still tick on other repaints, as with `potato` (see **Agents card under the strip**). |
| `sections` | Every section shown, in the order under **Side panel** (default): `heads-up`, `odd`, `rdd`, `ci`, `agents`, `todo`, `changes`. The row shows how many are shown (`7 of 7`, `5 of 7`) and opens the sections list (below). The rail and `/dock` draw `session` first, always, then the shown sections in the saved order. A hidden section is never drawn, even with data (a hidden `todo` or `ODD` leaves no empty box either); a shown one keeps its rules (`heads-up`, `RDD`, `CI`, `agents` and `changes` only with something to show). On a short screen the panel leaves out the same rows and sections in the same steps (see **Short screens**) whatever their place, and a hidden section takes no rows. The narrow-screen todo and agents cards and the strip are not sections of the panel and stay as they are. |

The frames and the logo apply to the rail and `/dock`, and the frames to the prompt box and your messages; the narrow-screen todo and agents cards keep their flat gray frame and take the colors only.

**The menu.** An overlay over the whole screen, every row as wide as the screen: `Fisi Dock look` in bold pink, a blank row, the rows `logo`, `frames`, `colors`, `accent`, `divider` and `motion`, each `label  ‹ value ›`, and `sections  7 of 7 ›`, the selected one marked with a pink `›`, its label bold and its arrows pink (the others muted, with faint arrows; the title, the marker and the arrows take the chosen accent's pastel), then a blank row, the preview, and on the last row the hint `↑↓ choose · ←→ change · Esc close`. It opens on `logo` with the look in use.

| Key | Action |
|---|---|
| ↑ / ↓, or k / j | The row above or below; past the last row it wraps to the first, and back |
| ← / →, or h / l | The previous or next value of the row, wrapping around |
| Enter | The next value, like → |
| →, l or Enter on `sections` | Open the sections list (← there does nothing) |
| Esc, or q | Close and return to the prompt |

**The sections list.** In the same overlay, in place of the rows: the title `Fisi Dock look › sections`, then one row per section in the saved order, ` › [x] heads-up`, `[ ]` for a hidden one, the selected one marked with the accent's `›` and its label bold, then the preview as above, and the hint `space show · shift+↑↓ move · Esc back`. It opens on the first section.

| Key | Action |
|---|---|
| ↑ / ↓, or k / j | The section above or below, wrapping around |
| Space | Show or hide the selected section |
| Shift+↑ / Shift+↓, or K / J | Move the selected section one place up or down, the cursor with it; the first stays first and the last stays last (no wrap) |
| Esc, q, ← or h | Back to the rows, on `sections`; Esc there closes the menu |
| Enter, → or l | Nothing |

Shift+↑ and Shift+↓ are read as the terminal sends them (`ESC [1;2A` and `ESC [1;2B`, or rxvt's `ESC [a` and `ESC [b`); K and J do the same where a terminal sends Shift+arrows otherwise. Every change is saved and repaints the preview, the rail and `/dock` at once.

Pi's own selection keys (`tui.select.up`, `tui.select.down`, `tui.select.confirm`, `tui.select.cancel`) and cursor keys (`tui.editor.cursorLeft`, `tui.editor.cursorRight`) count too, so remapped keys work. Other keys do nothing; taps and the mouse wheel stop at the menu and never reach the conversation behind it.

**The preview** is the rail as Gentle Shell fisi/custom draws it, in the look being chosen: the fading divider, a blank column and the 55-column panel (57 columns, narrower on a screen under 59 columns), one column in from the left, with the session's live data. It takes the rows between the menu and the hint and shrinks there the way the rail does on a short screen. It is left out when that leaves fewer than 4 rows (a screen under 15 rows) or fewer than 20 columns for it. A screen under 11 rows keeps only the setting rows (or sections) that fit, top first, and the hint.

**Each change** is used at once, before the menu closes: the preview, the rail, `/dock`, the cards, the prompt box, the line under the prompt and your messages repaint in the new look. It is saved at once too (see below). When it cannot be saved the menu warns once, `Fisi Dock look not saved to <path>, so a restart keeps the previous look`, and the look still holds for this session.

**Short screens.** With `frames` `none`, the blank rows between sections are the first rows to go when the panel is taller than the screen; only then does the panel leave out the rows and sections listed under **Short screens** above, in that order. The logo is never left out: `braille` takes 3 rows on rails of 45 columns or more and 1 row on narrower ones, `text` 1 row, `none` no row, so `text` and `none` leave more rows for the sections.

**Saved look.** The look is saved in the state file (see [Turning Fisi Dock off and on](#turning-fisi-dock-off-and-on)) under `"look"`: `{ "logo": "braille", "frames": "faded", "colors": "vivid", "accent": "pink", "divider": "medium", "motion": "on", "sections": [{ "id": "heads-up", "shown": true }, { "id": "odd", "shown": true }, …] }`; a look saved before `accent`, `divider`, `motion` and `sections` existed reads them as `pink`, `medium`, `on` and every section shown in today's order. `sections` keeps its saved order and hidden ones (only `"shown": false` hides); an unknown id or a repeated one is dropped, and a missing one is added after them, in today's order, shown. It is read at each session start and at `/fisi-dock on`. Each field is read on its own: a missing field, or a value Fisi Dock does not know, is the default, and the others stay. Every write to the file (the look, `/fisi-dock on|off`, the theme it remembers, the first start's `themeApplied`) writes its own keys over what the file already holds and keeps the rest, so `/fisi-dock on|off` never loses the look and the menu never loses the switch or the remembered theme. A missing or malformed file starts empty.

**Light.** `colors` `light` is for a light terminal background. The terminal's background stays yours: Fisi Dock paints only its own text and the backgrounds of its own rows. Everything it paints itself is drawn as in `vivid` and each color painted for a background of `#fafafa` (`lib/palette.ts` `lightColor`): the rail (every frame style, the fades and the divider), `/dock`, the narrow-screen strip and todo and agents cards, the prompt box's frame, labels, dot, wave and petal, the line under the prompt, the box around your messages and its `you`, the top row (the path, and the narrow header), the welcome card, the `loaded` box, the logo, and the menu with its preview. What Gentle Shell and Pi paint inside them (the label and petal Gentle Shell carried over, your prompt text, your message) keeps its own colors, from the theme. Each color keeps its hue and HSL saturation at the lightness that mirrors its contrast on `#161616` onto `#fafafa`: text `#e1e1e1` turns `#292929` (13.9:1), a fade toward the dark background fades toward the light one (its ends sink into `#fafafa`), and the decorative grays keep the contrast they had on dark: faint `#b1b1b1` (2.05:1), frame `#b8b8b8` (1.90:1), the highlight row `#e5e5e5` (1.21:1; text reads 11.6:1 on it). Muted rises to AA: `#737373`, 4.54:1 (3.45:1 on dark). A hue that reads more than 5.5:1 on dark settles at 5.5:1 (a grayer blend less, by its chroma), so the section colors read AA on the light highlight row too: pink `#c11d67`, teal `#177079`, coral `#c62400`, amber `#895d1c`, lilac `#7532ff`, green `#356e35`, blue `#1751ff`, red `#cf0000`, rose `#c6006f` as themselves; the section titles (session teal `#0a707c`, ODD amber `#905a0c`, agents lilac `#7537f6`, changes green `#0e760e`, todo coral `#c32908`, RDD rose `#c50871`, heads-up red `#d70808`, CI blue `#295cf6`) all 5.0:1 or more; the prompt labels from 4.54:1 (blue) up. The logo's deep pink stays decorative (`#f25f96`, 2.94:1, as on dark).

**The light theme.** `Fisi-Dock-Light` (appearance `light`) is generated with the accent themes, from `themes/Fisi-Dock.json`, by the same `npm run build:themes` and checked the same way. Every var is the panel's light color of it: the backgrounds, borders and tints mirrored onto `#fafafa` (`bg` `#fafafa`, `bgBar` `#efefef`, `bgHighlight` `#e5e5e5`, `border` `#e0e0e0`, the selection `#ebe7ea`, diff added `#d0deca`, tool error `#f4ecee`); the text roles (`text`, `muted`, `dim` and the accents) darkened until they read 4.5:1 on every background text sits on, so about 6.0:1 on `bg` and 5.5:1 on `bgBar`: text `#292929`, muted and dim `#5f5f5f`, pink `#bc095b`, teal `#066a75`, coral `#b72809`, amber `#885407`, lilac `#6e2cf4`, green `#067006`, blue `#134bf3`, red `#c10a0a`; and `deepPink`, the search match background and the quote border, `#f64fa0`, light enough that the text reads 4.5:1 on it and still 3.07:1 on the background. Same vars, roles and export keys as `Fisi-Dock`. One light theme for every accent, never remembered as your theme.

**The theme.** `Fisi-Dock` (vivid), `Fisi-Dock-Soft` (soft), `Fisi-Dock-Light` (light, every accent) and the eight accent themes (`Fisi-Dock-Teal`, `Fisi-Dock-Teal-Soft`, `Fisi-Dock-Lilac`, `Fisi-Dock-Lilac-Soft`, `Fisi-Dock-Amber`, `Fisi-Dock-Amber-Soft`, `Fisi-Dock-Green`, `Fisi-Dock-Green-Soft`) are all Fisi Dock's own themes, and none is ever remembered as your theme. On (the first start and `/fisi-dock on`) sets the one for the saved colors and accent; changing `colors` or `accent` in the menu switches Pi to the new one once, when the menu closes (the panel and the preview change at once), only when it differs from the one for the look the menu opened with, and only while Fisi Dock is on and one of them is in use (and Pi knows the new one). With a theme of your own in use the menu changes Fisi Dock's colors and accent and leaves Pi's theme alone.

**Accent themes.** `scripts/build-themes.mjs` (`npm run build:themes`) writes the eight accent themes from `themes/Fisi-Dock.json` and `themes/Fisi-Dock-Soft.json`; they are not edited by hand, and a test checks that the shipped files are its output. Each is its source with three vars changed: `pink` (the `accent`, `borderAccent`, `mdListBullet` and `thinkingXhigh` roles) becomes the accent's tone above, and `deepPink` (`searchMatchBg`, `mdQuoteBorder`) and `pinkSelection` (`selectedBg`) move to the accent's hue keeping their HSL saturation and their luminance, so search matches and the selection read as before. Every other var, role and key stays. Off puts back your remembered theme, as described below.

**While off**, the menu still opens: the preview draws the project name alone (no live data), and each change is only saved, with no theme change; `/fisi-dock on` then starts in that look. **Without an interactive UI**, `/fisi-dock settings` only says what the look is: `Fisi Dock look: logo braille · frames faded · colors vivid · accent pink · divider medium · motion on · sections 7 of 7 · /fisi-dock settings changes it in the interactive UI`.

## Turning Fisi Dock off and on

Pi cannot unload an extension in the middle of a session, so `/fisi-dock off` puts Fisi Dock to sleep instead, the same way it unloads: Gentle Shell gets back its rail Status section and top row, its `todo`, `jobs` and `agents` sections and its status bar, its prompt editor (the closed box goes) and the lines Fisi Dock hides; the start screen goes back to Pi's own header (Gentle Shell's rose returns on the next start or `/reload`); the panel, the strip, the agents and todo cards and the line under the prompt stop painting, your messages go back to Pi's own lines, and Fisi Dock's timers and readers stop. `/dock` then only says Fisi Dock is off. `/fisi-dock on` takes everything over again, as at the start of a session.

The choice is saved in `<Pi agent dir>/fisi-dock/state.json` (`PI_CODING_AGENT_DIR`, else `~/.pi/agent`; Gentle Shell sets `PI_CODING_AGENT_DIR` to its home, so `~/.gentle-shell/agent/fisi-dock/state.json` by default and `~/.pi/agent/fisi-dock/state.json` with `--link`), for example `{ "enabled": false, "theme": "dark", "themeApplied": true, "look": { "logo": "braille", "frames": "faded", "colors": "vivid" } }`, outside any repository; the `look` key is the [look settings](#look-settings)' and every write keeps the keys it does not set. While it says off, every new session starts with Fisi Dock asleep until `/fisi-dock on`; a missing or unreadable file means on.

The theme follows the switch: on remembers the theme you had (Pi's `ctx.ui.theme.name`) in that same file and sets `Fisi-Dock` (`Fisi-Dock-Soft` with `soft` colors, `Fisi-Dock-Light` with `light`, an accent's theme with another accent); off puts the remembered theme back. When the theme is already `Fisi-Dock` or `Fisi-Dock-Soft` as on runs, the one remembered before is kept: Fisi Dock's own themes are never remembered. A theme chosen as `Fisi-Dock` in `/settings` (not through on) remembers nothing new, so off then gives back the theme remembered before, else `Gentleman-Cute`. Removing Fisi Dock leaves `state.json` and Pi's theme setting as they are; a theme setting of `Fisi-Dock` without the theme loaded makes Pi fall back to its `system` theme. With nothing remembered, or when the remembered theme is gone (Pi no longer knows it, so it is not tried), off sets Gentle Shell's own theme, `Gentleman-Cute`, and says so; if that one is missing too, the theme stays as it is and off warns. It goes through Pi's own theme setter (`ctx.ui.setTheme`), which also saves it as your Pi theme setting, as `/settings` would.

The first start applies the theme once. A session start with Fisi Dock on, a UI and Gentle Shell loaded (its `gentle:banner` command; plain Pi keeps its theme and the step stays pending), while the state file has no `"themeApplied": true` (a fresh install, or a state from before this step), first saves the theme in use to the file (with no theme name, the one remembered before is kept), then sets `Fisi-Dock` (`Fisi-Dock-Soft` with `soft` colors saved), writes `"themeApplied": true` with the theme, and notifies `Fisi Dock applied its theme · /fisi-dock off puts your theme <name> back` (`theme Gentleman-Cute` with nothing remembered). With `Fisi-Dock` or `Fisi-Dock-Soft` already in use it only writes the flag, with no notice. It never repeats, so a theme picked later in `/settings` stays on every following start. The flag stays unwritten, so the next start tries again, when Pi does not know `Fisi-Dock` (`ctx.ui.getTheme`) or its setter fails or throws. When the state file cannot be written the theme is not changed, since the one you had could not be given back. A delegated child session (`GENTLE_PI_AGENTS_CHILD=1`) does none of it, and neither does a start while the state says off: there `/fisi-dock on`, once it set `Fisi-Dock`, writes the flag. Off keeps the flag; deleting `state.json` makes the next start apply the theme again.
