docs(maestro): add SIDEBAR.md session card contract
This commit is contained in:
@@ -66,6 +66,10 @@ See `docs/INSTALL.md` for end-user install instructions.
|
||||
collisions, what blocks a kill, what `--force` retains, and what reclaim
|
||||
touches. Read it before changing anything that creates or removes one.
|
||||
|
||||
`docs/SIDEBAR.md` is the contract for session cards in the web and TUI
|
||||
sidebars — line layout, activity colours, duration format, state precedence.
|
||||
Read it before changing sidebar rendering.
|
||||
|
||||
`nix run .#dev` (or `just dev`) brings up the whole local stack under
|
||||
process-compose: a debug daemon on 127.0.0.1:7071, then Vite on :5173 once the
|
||||
daemon answers `/api/info`. The SPA proxies `/api/*` back to the daemon, so only
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
# SIDEBAR.md — the session card contract
|
||||
|
||||
One rule for how a session is rendered as a card in the sidebar, so the web
|
||||
sidebar (#107) and the TUI sidebar (#108) describe a session the same way.
|
||||
Read this before changing either. Both clients implement exactly what is
|
||||
written here; if it needs to change, change it here first.
|
||||
|
||||
A card has **line 1** always and **line 2** only when line 2 has something to
|
||||
say.
|
||||
|
||||
## Line 1 — name + actions
|
||||
|
||||
- Left: the session name — `metadata.name` if present, else the command line
|
||||
(same fallback both clients already use).
|
||||
- Right: two actions anchored to the top-right corner: **spawn similar**
|
||||
(`⧉`) and **kill** (`✕`). In the TUI these are keyboard affordances shown on
|
||||
the highlighted row; in the web they are buttons. The glyphs are shared so
|
||||
muscle memory transfers.
|
||||
- Actions reserve their fixed width; the name truncates with `…` before it can
|
||||
ever reach them. **Overlap between the name and the actions is a defect** —
|
||||
that overlap is what started this milestone.
|
||||
|
||||
Line 1 carries no badges. The `⚠ blocked work` badge (#83) and the `exited`
|
||||
badge move off this row into line 2.
|
||||
|
||||
## Line 2 — meta line (adaptive)
|
||||
|
||||
Rendered only when at least one of these holds:
|
||||
|
||||
1. The session has been held in its current activity state longer than
|
||||
**30 seconds** (`activity_since` from #102). A freshly-active session
|
||||
stays one line.
|
||||
2. Its worktree holds blocked work: `blocked_work` is `Some` and not clear
|
||||
(`InTheWay::is_clear()`), rendered in `formatInTheWay`'s phrasing on web
|
||||
and as `⚠ <summary>` on the TUI.
|
||||
3. It has exited: `status == Dead`.
|
||||
4. An agent-reported state (#105) of `Error` is in force (see Precedence).
|
||||
|
||||
Otherwise line 2 is absent — never an empty placeholder row.
|
||||
|
||||
When present, line 2 reads left to right:
|
||||
|
||||
```
|
||||
<duration> [<state badge>] [<blocked work> | exited]
|
||||
```
|
||||
|
||||
## Colour per activity state
|
||||
|
||||
The state word is never spelled out — the colour carries it.
|
||||
|
||||
| Activity state | Web token | ratatui colour |
|
||||
| ---------------- | --------- | ----------------- |
|
||||
| `Active` | `--live` | `Color::Green` |
|
||||
| `Idle` | `--muted` | `Color::DarkGray` |
|
||||
| `AwaitingInput` | `--warn` | `Color::Yellow` |
|
||||
| `Error` | `--danger`| `Color::Red` |
|
||||
|
||||
Web tokens resolve per theme automatically (`styles.css` defines dark, light,
|
||||
and `prefers-color-scheme` variants for each). The TUI uses the plain ratatui
|
||||
named colours above; do not introduce RGB values or a new palette module for
|
||||
this.
|
||||
|
||||
The duration text inherits its state's colour; the blocked-work and exited
|
||||
fragments use the muted colour (`--faint` / default style), since the ⚠ glyph
|
||||
already signals severity.
|
||||
|
||||
## Duration format
|
||||
|
||||
Time spent in the **current activity state**, sourced from `activity_since`
|
||||
(#102) — *not* time since spawn, and *not* time since last PTY output
|
||||
(`last_activity_at`). Both clients compute it identically:
|
||||
|
||||
- `< 60s` → whole seconds, floored: `30s`
|
||||
- `< 60m` → whole minutes, floored: `4m`
|
||||
- `≥ 60m` → whole hours, floored: `2h`
|
||||
|
||||
No days unit, no seconds inside minutes, no rounding up. Floor everywhere so
|
||||
the counter never shows a duration the state hasn't actually reached yet.
|
||||
Clock granularity: re-render at least once every 30s while visible so the
|
||||
cutover points land within one tick.
|
||||
|
||||
## Precedence
|
||||
|
||||
An agent-reported `ActivityState` (#105) overrides the daemon's inference for
|
||||
the TTL the report carries; when the TTL expires, inference resumes. Clients
|
||||
never blend the two: whichever source is in force supplies both the colour and
|
||||
the clock for `activity_since`.
|
||||
|
||||
If a client receives an `ActivityState` variant it does not know, it must not
|
||||
fail rendering: treat it as `Idle` (neutral/dim, line-2 rules apply as usual)
|
||||
until the daemon sends a variant it does know. Rust clients deserialize
|
||||
activity leniently for the same reason.
|
||||
Reference in New Issue
Block a user