Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion docs/agent-states.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ that distinction.
| 4 | `working` + delegated `partial` | Some of that work came back; the rest runs on | outlined blue dot, or the ring when `partialDoneIndicator` is off | no | the remaining work, or the grace |
| 5 | `done` | The turn ended | solid blue dot | YES | focusing the tab, or the next submit |
| 6 | `done` / `idle` + delegated | The turn ended and left something running | blue dot, then the dashed ring once acknowledged | yes, once | the leftovers finishing, or the next turn |
| 7 | attention (`unread.reason`) | The agent is blocked ON YOU: a permission prompt, a question | bell | YES | answering it |
| 7 | attention (`unread.reason`) | The agent is blocked ON YOU: a permission prompt, a question | bell | YES | answering it (a key in that terminal, Escape, Ctrl-C) or the agent's done hook; NOT looking at it |
| 8 | interrupted | You pressed Escape or Ctrl-C | nothing | no | (already over) |
| 9 | failed | A run or setup script exited non-zero | red triangle | no | a re-run |
| 10 | ceiling | termic gave up waiting after 20 minutes | nothing (clears to `idle`) | NO | the next heartbeat re-arms working |
Expand Down Expand Up @@ -129,6 +129,25 @@ ring, because the rest is still running. The next piece of work to report
back sets it again, and the last one ends the turn with the usual done.
Nothing but the badges reads the flag, so clearing it changes the mark only.

### A question is not answered by looking at it

Every other mark clears when the tab is in front of you (`setActiveTask`,
`setActiveTabId`, `useSeenWhenWatched`). Attention does not
(`unreadClearsOnSight` in `lib/taskWorkState.ts`): the agent is still
blocked while you read its question. It clears on an answer, meaning any key
you type in that terminal (claude's permission prompt takes a bare digit, no
Enter), a bare Escape or Ctrl-C, or the agent's own done hook (the turn is
over, so nothing in it is waiting: a question answered through claude's
remote control, or one it gave up on). xterm's automated replies begin with
ESC and arrow keys do too, so neither counts. A working heartbeat does not
count either: parallel subagents fire tool hooks while one of them sits on a
permission prompt. The board's "mark settled" drop still clears it, as an
explicit command.

It used to clear on sight like the rest. A question you had glanced at and
left read as a finished turn: off the bell, and under Settled on the board and
in the sidebar's status section, while the agent sat waiting.

### Agent messages while delegated

In the delegated and partially-done states the agent's own loop has
Expand Down
5 changes: 4 additions & 1 deletion docs/e2e-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ until `make e2e` is green and this file reflects it.
| ✅ App shell | Renders; `__termic` exposes real store state | `app.e2e.ts` |
| ✅ Navigation | Dashboard ↔ History via real clicks | `app.e2e.ts` |
| ✅ Kanban view (GH #318) | The Kanban nav entry opens the overlay; untouched tasks land in Not started under their agent's lane divider (one per agent in use); submitting a prompt through the real input path runs the fake agent's busy→idle cycle and moves the card into Settled while its untouched sibling stays behind; clicking a card activates the task and closes the board; dragging a card within its same-project group reorders and persists via `task_reorder`; dragging to another column snaps back with no dialog and no write; dropping on the Archived column runs the real confirm dialog and the card lands in the Archived column; the card's column accent edge is asserted by computed style so a dropped color-mix cannot ship invisibly; under a custom archive limit the column renders exactly the N most recent archived cards, newest first, the older ones (and a task archived before them) fall off, and the badge keeps the full count; Settings → Tasks' three-way limit control (default / unlimited / custom) reveals the number input only on custom and stores a typed number as-is, with no bounds | `board.e2e.ts`, `settings.e2e.ts` |
| ✅ Sidebar status section (#298) | Off by default; the Project list options check row turns it on, open, above the PROJECTS header; tasks nobody has opened are counted under Not started, folded, and unfold into exactly that many rows in the tree's relative order; a status row carries `data-status-task-id` and none of the tree's `data-sidebar-*` attributes, so each task still has one `[data-sidebar-task-id]`; attention seeded on a background agent lists it under Needs attention with `status-work-badge` while the tree keeps its own `work-badge` and the copy adds none; clicking the row opens the task and re-expands its folded project in the tree, and the row STAYS under Needs attention (opening is not answering) carrying the active mark and a painted background, measured, until a bare digit typed into the terminal answers it and it moves to Not started; the STATUS header is a plain label like PROJECTS, not a fold; each bucket's fold is written to scoped localStorage and survives the section being turned off and on; a held open PR lookup on a worktree task with a persisted identity lists it under In review with `status-pr-badge` while the first bare `task-pr-badge` stays the tree's, and a merge moves it out; a task group (founded and joined through the real IPC) sits whole under Not started, then moves as one unit under Needs attention when one member's agent asks, with the bell only on that member, its caption and rail measured in the same colour as the tree's caption, the tree's `data-task-group-id` block still the only one, and the bucket count equal to its rows; folding its caption hides the members and puts their bell and count on the caption, is stored, and leaves the tree's block for the same group showing its members; answering moves it back whole; a task running two agents shows the tree's `(2)` collapsed and expands to one child per agent tab, in tab order, each its own agent, while the tree's row for the same task keeps its own state; a child opens its tab and then carries the selection; the expansion survives the section being turned off and on; Settings > Appearance > Sidebar's switch and the menu row write the same pref; the icon rail carries no section and the hover overlay carries exactly one. Working is covered by `src/lib/sidebarStatus.test.ts` and the fan-out pins, not by racing the fake agent | `sidebar-status.e2e.ts` |
| ✅ History scrolling | The archive pane fills its overlay instead of sizing to its content, so an archive taller than the window overflows INSIDE the scroller: the last row starts out of view and scrolling brings it in. Filtering down and back keeps the pane full-height | `app.e2e.ts` |
| ✅ Create (wizard) | NewTaskDialog: name + shell CLI + Main-checkout → Create → task exists | `task.e2e.ts` |
| ✅ Check out an existing branch | NewTaskDialog's "Existing branch" mode: a remote-only branch picked from the list becomes a worktree task on a local branch TRACKING it (HEAD on the colleague's commit, upstream set); a branch never fetched is typed, flagged as unfetched, and fetched on create; an unknown name fails the pending task with the "no branch" error and leaves no local branch behind (the old path cut a fresh one from main); "New branch instead" restores the ordinary form; archiving the checkout with delete-branch and restoring it puts it back on the colleague's commit, not a branch cut from main. The resolution rules (local wins, remote prefix, fetch on/off, no remote, invalid names) are pinned by `checkout_existing_branch` tests in `lib.rs`; restore's branch recreation by the `ensure_restore_branch` tests; the picker rows by `existingBranch.test.ts` | `task.e2e.ts` |
Expand Down Expand Up @@ -66,6 +67,7 @@ until `make e2e` is green and this file reflects it.
| ✅ One turn, one notification | A two-stage turn produces TWO sidebar dots and ONE newsworthy edge (the thing the notifier turns into a banner), down BOTH paths: the heuristic one via the title (`#stage`) and the hook one via `OSC 133;C/;D` (`#hookstage`), which reach `fireDone` under opposite guards. GH #276 | `agent.e2e.ts` |
| ✅ Split restore | A pane whose tabs are all gone collapses instead of restoring a blank leg; a task whose main tabs were all non-durable still restores with a main tab and a real activeTab | `tabs-layout.e2e.ts` |
| ✅ Agent notifications | OSC 9 raises attention carrying the agent's verbatim body; the "waiting for your input" idle nag raises nothing | `agent.e2e.ts` |
| ✅ Seen vs answered | Back at a focused window on the badged tab, a done dot from a turn that finished while away clears on sight, while an attention bell raised while away survives the return (both the tab strip and the sidebar row) and clears only on a keystroke in that terminal: a bare digit, no Enter | `agent.e2e.ts` |
| ✅ Welcome wizard layout step | The wizard opens on the step that explains projects, tasks and the worktree vs main-checkout distinction, and its LAST step is still the project picker that carries Finish. Asserts the five pips by label, because inserting a step at the front renumbers every other one and an off-by-one there shows the wrong body or strands Finish | `app.e2e.ts` |
| ✅ Work-mark switches | Settings → Notifications draws one switch per state a tab can report, each rendering the REAL badge it governs, in the order that encodes the model (every mid-turn mark under Working). A mark that changes without its row changing fails here | `settings.e2e.ts` |
| ✅ Delegated work (agent hooks) | A done hook that finds work OUTSTANDING reports it (`#delegated`, the real `agent delegated: ...` wire format) instead of writing nothing. Three cases, one each for what the measurements found: a `subagent` keeps the turn open, names itself on the badge (`data-delegated`) and is NOT cut short by the detached grace; a report whose ids all predate the turn ends it with a real announced done, which is the per-session hold that used to swallow every later turn; and a detached `shell` nobody resumes ends at the grace (shortened via `localStorage.delegatedGraceMs`) rather than at the silent 20-minute ceiling. See docs/agent-hooks.md "Delegated work" | `agent.e2e.ts` |
Expand Down Expand Up @@ -145,7 +147,7 @@ until `make e2e` is green and this file reflects it.
| ✅ Resize drags | Sidebar edge widens + clamps at its minimum (persisted); split divider moves the ratio inside its clamp | `tabs-layout.e2e.ts` |
| ✅ Sidebar project drags | Reorder two projects; drop one into a group folder; move a whole folder as one block | `projects.e2e.ts` |
| ✅ Sidebar task drags | Reorder tasks inside a project (siblings keep their relative order); the new order persists to the task files, so a cold load reads it back; a task dragged at another project's row clamps to its own list instead of moving | `task.e2e.ts` |
| ✅ Sidebar task filter (GH #324) | The project row's filter icon opens a bar under the header (focused input, bell to its right) and closes it again, and lights only while text or the bell filters; typing keeps only tasks whose name or stable agent tab title matches (case and surrounding space ignored) and pins the hover bar with the pointer elsewhere; a CLI `rename` moves a task into and back out of the filtered list live; the clear button and Escape both empty it, drop the filter from the store and let the bar hide again; an empty list shows a "No matching tasks" row whose action clears it, and the row never appears under the active task the filter keeps on screen; turning a filter on expands a collapsed project, and a real header click still collapses and re-expands it with the filter on; the bell keeps only tasks with a notification (an OSC 9 attention from the fixture), and the active task stays listed after opening it clears that notification, dropping out once another task is selected. The predicate, including `liveTitle` being ignored and the bell agreeing with the tray's numeral, is unit-tested in `src/lib/taskFilter.test.ts` | `projects.e2e.ts` |
| ✅ Sidebar task filter (GH #324) | The project row's filter icon opens a bar under the header (focused input, bell to its right) and closes it again, and lights only while text or the bell filters; typing keeps only tasks whose name or stable agent tab title matches (case and surrounding space ignored) and pins the hover bar with the pointer elsewhere; a CLI `rename` moves a task into and back out of the filtered list live; the clear button and Escape both empty it, drop the filter from the store and let the bar hide again; an empty list shows a "No matching tasks" row whose action clears it, and the row never appears under the active task the filter keeps on screen; turning a filter on expands a collapsed project, and a real header click still collapses and re-expands it with the filter on; the bell keeps only tasks with a notification (an OSC 9 attention from the fixture), opening that task keeps its notification (a question is not answered by looking at it), and the active task stays listed after a keystroke answers it, dropping out once another task is selected. The predicate, including `liveTitle` being ignored and the bell agreeing with the tray's numeral, is unit-tested in `src/lib/taskFilter.test.ts` | `projects.e2e.ts` |
| ✅ Task groups | `termic new` run with `$TERMIC_TASK_ID` founds a group led by the caller, more tasks and a worker's own tasks join it (flat), `--no-group` / no env / a stale id stay out; the unnamed label follows the lead's rename; rename and recolour from the caption menu; drag a row in (joins) and out (leaves); Remove from group leaves a lone lead grouped; Move to group > New group asks for a name, and moves a task between groups; compact rail; collapsing shows one of each member mark (attention and done seeded on real agent tabs), navigating to a member expands it, and a collapsed group keeps the active task's row; caption drag moves the whole block (above another group, below a loose row, persisted); caption and rail alignment and the rename input's font, measured; Ungroup tasks. MCP `task_new` groups under the caller from the `X-Termic-Task` header with no argument, and `task_group` with no `task` names the caller's group. Spawn links: a task spawned into another project is linked (`spawned_by`, CLI reply) and not grouped, its ↳ mark names the parent and goes there, a same-project child is grouped with no mark, hover draws lines to parent and children (end points measured) and only while hovered, a legacy cross-project group draws as plain rows, and the dashboard's flat rows mark every spawned child (same-group ones too, the sidebar's suppression inverted) with the mark going to the parent | `task.e2e.ts`, `mcp.e2e.ts` |
| ✅ Settings reorder drags | Prompt rows reorder by their grip (and a click without movement does not); agent pills reorder within their kind | `settings.e2e.ts` |
| ✅ Project default CLI vs the agent registry | The Default CLI select shows the SAVED agent id even when the registry no longer offers it (React re-points a valueless `<select>` at its first option, which used to make the page name an agent the project was never set to); reordering the agent pills leaves the saved default alone; renaming a custom agent carries every project pinned to it onto the new id | `settings.e2e.ts` |
Expand Down Expand Up @@ -311,6 +313,7 @@ These are intentionally NOT covered by written specs — asserting them would be
- **Terminal content is not in the DOM** (WebGL canvas) — assert `lastOutputAt`/`liveTitle`/store, never innerText, for PTY output.
- **`workState === "working"`** won't flip from a raw `ipc.ptyWrite`; termic gates it on a real submit through the input path.
- **Radix menus open on pointerdown** — dispatch `pointerdown`/`pointerup`, not just `.click()` (see `tabs-layout.e2e.ts`).
- **CSS transitions can stall for seconds.** They run on `document.timeline`, which advances only while the window paints frames, and the suite's window often paints none: measured, the timeline moved 13 ms over about 1.5 s while a `transition-colors` row sat at alpha 0.016 of its selection colour. A computed colour read through a transition is therefore a race. Read it with `el.style.transition = "none"` (and put it back), which measures the end state the class asks for (`sidebar-status.e2e.ts`). Unrelated to the WKWebView `transition-colors` border bug in docs/gotchas.md, which never settles in any window.
- **Hover-gated controls** (theme picker, History "Restore →") need a dispatched `mouseover`/`mouseenter` first, or drive the underlying store/IPC.
- **rAF-deferred effects are frozen when the window is occluded** (e.g. the command palette's `act()` → `requestAnimationFrame`). Assert the synchronous part, or drive the underlying store, rather than the deferred side effect.
- **Run/Setup tab PTY spawn is rAF-gated** in TerminalPane, so a newly-added run tab's PTY lags on an occluded/offscreen window (CI). Assert the tab is *created* (launch wiring); PTY spawn/execution is covered by task-spawn's agent PTY.
Expand Down
Loading
Loading