Skip to content
Merged
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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ Performance invariants — all deliberate, keep them:

- The full index is never shipped to or rendered by the UI. Always paginate.
- Meta parsing reads at most 256KB per file. Parsers are failure-tolerant: session log formats are provider-internal and drift between releases, so skip anything unreadable rather than fail the scan.
- What a tool call hands the person — a plan, a to-do list, an edit — rides its transcript row as `SessionMessage.artifact`, read off the call's own input in main by `src/main/parsers/artifacts.ts` (shared by the three parsers and the stream parsers in `chat.ts` and `acp-core.ts`) and bounded there (files per call, lines per file, line width), because it crosses IPC on every transcript read. A call that failed is marked on its own row (`failed`), matched to its result by id where the log has one. The renderer only folds these for the Work panel (`work.ts`); it never parses tool JSON.
- A parser reports only what its log states (`SessionMeta.logBranch`), never what the disk says. Anything derived from the checkout — `repo`, `isWorktree`, `gitBranch` — is the indexer's `annotate()`, is recomputed on every scan, and is stripped before the stat-cache is written so a renamed remote or a moved worktree can't freeze into it. Branches are the live example: Copilot stopped writing `context.branch` after CLI 1.0.80 and most Codex rollouts carry no `git` block, so for those the checkout's HEAD is the only source.
- A session can span several files: Codex paginates a long thread into a new rollout whose `session_meta.history_base` names where the previous one's history ends. The parser reports that (`SessionMeta.historyBase`), `foldThread` in the indexer folds every file carrying one id into a session on the newest file with the earlier ones as `segments`, and anything that reads or removes a session's log — transcript, search, profile, cleanup — reads every file (`sessionLogFiles`; an earlier page only up to its `endByte` where the thread's history is what counts), never `sourcePath` alone. Only liveness reads just the newest file, the one being written.
- The stat-cache (mtime+size, persisted to userData) means restarts only re-parse changed files; scans yield to the event loop so IPC never blocks.
Expand Down
3 changes: 2 additions & 1 deletion design-system/cockpit/MASTER.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ Reuse these; don't invent parallel variants:
- **`.rail-resizer`** (`RailResizer.tsx`) — the sash on the rail's right edge: the sidebar's width is the person's, dragged between 200px and what the deck can spare (`rail.ts`, remembered per machine in localStorage, never in config), invisible at rest and an accent hairline on hover, focus and while dragging. A `role=separator` whose value is the rail's *measured* width; ← → move it 16px (64 with ⇧), Home/End reach the bounds, double-click resets. Because the rail is dragged, anything that sheds *inside* it asks the rail (`@container rail`), never the window. See `pages/sidebar.md`.
- **Cards:** `.ns-card` — ONE width (`min(760px, 94%)`) shared by every card view (Settings, Agents, Profile, Cleanup, New session); navigating between them must never make the dialog jump sizes. `.composer-card` for the home prompt, in home's own `--home-col`.
- **Card tabs** (`TabList` + `TabPanel`, `Tabs.tsx`) — the one way a card view pages its content: a `.pnl-tabs.ns-tabs` row of `.pnl-pill` tabs (hairline under it) over **one** mounted panel. Settings, Agents, Profile and Cleanup all use it. **A tab replaces the panel; nothing in a card scrolls to a heading further down** — a jump row that scrolled took the title, the row and Close off screen with it, and a card of stacked sections read as one long page. Switching mounts the panel fresh (two panels built alike must never share internal state) and puts the card's scroller back at `scrollTop 0`. The row is one tab stop (roving `tabIndex`; ←/→ wrap, Home/End), only the selected tab carries `aria-controls`, and the panel is named by its tab — so a panel holding one group carries no heading that repeats the pill, and a panel holding several names each group with an `h3` that doesn't either. A tab may carry its count (`.pnl-pill-n`, left off at zero), a warn tone or an amber dot. Controls that govern every tab (Cleanup's threshold, Agents' scope, Profile's headline numbers) sit above the row; the pill row must hold in two rows at the 560px floor. A new card view that grows past a screen gets tabs, not a jump row.
- **Chat:** user bubbles right (accent tint), assistant left with avatar; `.tool-row` = collapsed `<details>` one-liners; `.sys-row` = dotted-left-border annotations; streaming = accent left border.
- **Chat:** user bubbles right (accent tint), assistant left with avatar; `.tool-row` = collapsed `<details>` one-liners, or a `.tool-open` button when the call carries a plan, to-dos or an edit; `.sys-row` = dotted-left-border annotations; streaming = accent left border.
- **`.work-panel`** (`WorkPanel.tsx`) — the agent's plan, to-dos and edits beside the transcript (chat only, `.btn-work` / ⌘J, or one click on a `.tool-row.tool-open` row): card tabs over one scroller, the review's file blocks for edits, and under 720px of `@container chat-deck` it covers the conversation instead. See `pages/chat.md`.
- **`.review`** — the worktree's changes in the transcript's place (chat only, `.btn-review` / ⌘D): the instructions review's `.idiff-*` line grammar with line numbers, a scope switch, and line notes that go back to the agent through the composer. With an open PR it leads with `.review-pr` (what the PR waits on + "Fix with <Agent>") and shows reviewers' unresolved threads (`.review-thread`) under their lines. See `pages/chat.md`.
- **Semantic count pills:** bordered pill = "session count on a repo"; org counts are plain text.
- **`Select`** — the one dropdown (see Native Controls); never a raw `<select>`.
Expand Down
72 changes: 66 additions & 6 deletions design-system/cockpit/pages/chat.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,17 @@ gives way like the branch chip) for a session another session started — then b
cwd that copies its full path — displayed via `cwdLabel`: a worktree as `worktree · <slug>`, or bare
`worktree` when the slug is the branch chip's own name (its location is only where a tool keeps
worktrees), anything else `~`-abbreviated; the full path is always in the tooltip — and "· not started" when no native session yet) · PR affordance · `Changes` ·
`Continue in…`.
`Work` (only once the transcript carries a plan, to-dos or an edit) · `Continue in…`.
Header min-height is 52px — it's the drag region, keep it a real grab target.

- **The header is identity, never settings.** The permission mode lives in the composer
(see below); a header that also carried it lost the session title entirely at the
560px floor.
- **Labels shed to their marks before the title truncates** — ≤780px the `Changes` and
`Continue in…` keys fold to 28px squares (`.lbl` hidden; `DiffIcon`/`HandoffIcon`
stay, each with an `aria-label`), and an open PR's badge drops its state word
- **Labels shed to their marks before the title truncates** — ≤780px the `Changes`
and `Continue in…` keys fold to 28px squares (`.lbl` hidden; `DiffIcon`/`HandoffIcon`
stay, each with an `aria-label`). The `Work` key (`.btn-review.btn-work`) is that
square at every width — `WorkIcon`, named by its `aria-label` and tooltip: a fourth
label cost the title ~70px at 900px, and the rows are the way in. An open PR's badge drops its state word
(`.pr-word`) to keep the number beside its checks glyph, unresolved-thread count
(`.pr-threads`) and review mark — the state stays in the badge's `aria-label` and
tooltip; ≤700px the agent badge drops
Expand Down Expand Up @@ -80,14 +82,23 @@ Header min-height is 52px — it's the drag region, keep it a real grab target.
wide) a peek longer than twelve characters collapses to zero width while a short
one (`.tool-peek-short` — `ok`, `20 passed`) keeps its place; at 40% each, the
command and the verdict were both twelve characters and neither could be read.
- **a tool call that carries work → `.tool-row.tool-open`**, the same one-line
grammar as one `<button>` instead of a `<details>`: chip · headline · `DiffStat` for
an edit · `didn't apply` (warn, the word itself) for a failed call · the `WorkIcon`
at the right edge, accent on hover. The headline is the artifact's own: a plan's
title, `3 of 7 done`, the task it adds, the files an edit touched. A click opens the
Work panel at that row (below); there is no raw JSON to expand — the panel is the
detail. A blank 10px lead keeps its chip in line with the ▸ of the rows around it.
Roundtables pass no `onOpenWork`, so their rows stay ordinary `.tool-row`s.
- paths under the session's cwd render relative to it (`Message`'s `cwd` prop) — the
header already names the directory
- **four or more tool rows in a row fold into one `.tool-run`** — `⚙︎ work · 5 steps ·
Bash ×2 · Read · Grep · Edit`, opening to the rows themselves (`foldToolRuns`,
`runSummary`). A twelve-step run between two paragraphs buried the paragraphs. The
fold breaks wherever the agent speaks, so prose is never swallowed, and the **tail
run of a live turn never folds** — watching the steps arrive is the point while a
turn runs. Earlier runs in that same turn still fold.
turn runs. Earlier runs in that same turn still fold. A plan row never folds either:
a plan is a message of its own, like the agent's prose.
- system → `.sys-row` dotted-left-border annotation, aligned with the assistant column
- **a question waiting on you → `.ask-card`** (`AskPicker.tsx`), the one tool call that
never collapses: the agent's own options are the message, so a `⚙︎` one-liner would
Expand All @@ -104,7 +115,11 @@ Header min-height is 52px — it's the drag region, keep it a real grab target.
question is history and renders as the ordinary tool row. A read-only seat session
gets none: the table owns that conversation. Parsed in main
(`src/shared/asks.ts` — Claude's `AskUserQuestion`/`ExitPlanMode`, Codex's
`request_user_input`), never from the raw JSON in the renderer.
`request_user_input`), never from the raw JSON in the renderer. **A plan gate shows
the plan** above its two answers (`.ask-plan-body`: the plan's markdown on
`--bg-deep`, 320px max, a named, focusable `role=region` so the keyboard can scroll
it), with `Open in the Work panel` (`.btn-ghost.small`) under it — approving a title
was approving something unread.
- Tool/system glyphs are text-presentation unicode (`⚙︎` with U+FE0E, `↳`) — if these
ever grow, switch to SVGs from `logos.tsx`; never bare emoji-presentation glyphs.
- **DOM bound, with a way up:** only the last `RENDER_LAST` (400) messages render, and
Expand Down Expand Up @@ -158,6 +173,51 @@ Header min-height is 52px — it's the drag region, keep it a real grab target.
- Code blocks get a hover/focus Copy button; highlight.js tokens map to app palette
variables — no imported highlight theme.

## Work panel (`WorkPanel.tsx`, `.work-panel`)

What the agent handed the person to look at — a plan, a to-do list, edits — beside the
conversation. Everything in it comes from the agents' own tool calls, parsed in main
(`src/main/parsers/artifacts.ts` → `SessionMessage.artifact`, bounded there) and folded
in the renderer (`work.ts`), never from raw JSON in the renderer.

- **Beside, not instead of.** Under the header the chat is a row, `.chat-deck`: the
conversation (`.chat-main` — transcript or review, the permission card, the composer)
and the panel (`clamp(300px, 38%, 460px)`, hairline left border, `--pane`). The deck is
a `container: chat-deck`, and under 720px of it (the panel's 300px floor beside ~420px
of conversation) the panel covers `.chat-main` instead, on solid `--bg` — the way
Changes takes the transcript's place. Asked of the deck, never the window: the rail is
dragged. Changes (header, ⌘D, or the Edits tab's link) closes a panel that covers the
conversation first — it would otherwise open unseen behind it; beside the
conversation the panel stays, so what the agent said and what is on disk read side
by side.
- Opened by the header's **Work** key (`.btn-review.btn-work`, `aria-pressed`, ⌘J —
offered only once the transcript carries something for it) on the tab that matters
now (`defaultTab`: a plan waiting for approval, else a list still under way, else the
edits), or by a `.tool-open` row at that row. Opening moves focus to the selected tab;
Escape (or the ×, `XIcon`) closes it and hands focus back to what opened it. It closes
when the session's directory changes, like review.
- **Tabs** are the card tabs (`TabList`, `.pnl-pill`) without the card's rule under
them: **Plan** (amber dot while a plan waits for approval), **To-dos** (count = steps
not done), **Edits** (count = files). The body (`.work-body`) is the panel's one
scroller, `tabIndex=0` so a keyboard reaches it, and goes back to the top on a tab
switch. Each tab leads with a `.work-meta` readout in the mono voice; an empty tab
says, in one sentence, what would appear there (`.work-empty`).
- **Plan**: the plan's markdown at `--fs-prose`. Earlier versions are one step away
(`Earlier` / `version 2 of 3` / `Later`, `.btn-ghost.small`); a row opens its own
version. `waiting for your approval` (`.work-flag`, warn) while it is the pending ask.
- **To-dos**: an `<ol>` of `.work-todo` rows, the state as a shape (`TodoMark`: ring,
ring with a dot, ring with a check) *and* an `sr-only` word; the step under way wears
an accent tint, done steps dim their text. `todos` artifacts replace the list; Claude's
`TaskCreate`/`TaskUpdate` fold into it by task number.
- **Edits**: `N edits · M files` + `DiffStat`, then a `.work-note` that the edits are as
the calls described them — with a `Changes` link to the worktree's real diff where
there is one. Files are review file blocks (`.idiff.review-file.work-file` in an
`.idiff-list`), open by default while there are three or fewer (files that arrive
mid-turn follow the same default; only what the person toggled is remembered); each edit under an
`.idiff-rail` (time · tool, `didn't apply` in warn when the call failed), its hunks in
the shared `DiffLines` grammar, unified only. A row that opened the panel opens its
file and rings its edit (`.work-edit.ringed`, an accent inset) for two seconds.

## Permission prompt (`PermissionAsk`, `.perm-card`)

A turn Cockpit drives over ACP can stop mid-turn and ask (`session/request_permission`).
Expand Down
43 changes: 43 additions & 0 deletions docs/guide/chat.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ question, its options with the agent's own one-line descriptions, and **Send ans
Multi-select questions take several picks; a question the conversation has moved past
stays a plain tool row.

When the question is Claude's plan gate, the card shows the plan itself above
**Approve the plan** and **Keep planning**, so you approve what you have read, not a
title. A long plan scrolls inside the card, or **Open in the Work panel** gives it the
whole side of the window.

The pick is sent as your next message, worded from the question (`Answering your
question: - Which layout…? → packages/<runtime>`), so it stands on its own in the
transcript the agent resumes from — and you can always ignore the card and type your
Expand All @@ -84,6 +89,44 @@ and in the Dock badge, whether the session runs in Cockpit or in a terminal; see
[Notifications](/guide/notifications).
:::

## Plans, to-dos and edits: the Work panel

Agents hand you things to look at while they work: a plan to approve, a to-do list they
tick off, and edits to your files. Cockpit reads each of them from the agent's own tool
calls and opens them in the **Work** panel beside the conversation. The panel has three
tabs:

- **Plan**: the plan the agent proposed (Claude Code's plan mode, Copilot CLI's
`exit_plan_mode`), rendered as a document. If the agent revised it, **Earlier** and
**Later** step through each version. While the plan is waiting for your approval, the
tab says so.
- **To-dos**: the agent's list as it stands now: Claude Code's tasks (or its older
`TodoWrite` list), Codex's plan, or an ACP agent's plan. Each step shows as not
started, in progress or done.
- **Edits**: every file the agent changed, in the order it first touched them. Each change
appears the way the call described it: a Claude `Edit` or `Write`, a Codex or Copilot
patch, a Copilot `edit` or `create`. An edit whose call failed is still listed, marked
**didn't apply**.

A tool row that carries one of these is a single click: the row names the plan, the list's
progress or the files and their `+`/`−` counts, and clicking it opens the panel at that
item. The header's **Work** key (⌘J) appears once the conversation holds any of them. It
opens the panel on whatever matters now: a plan waiting for you, else a list still in
progress, else the edits. Escape closes the panel and returns you to where you were.

When the window is too narrow to hold the conversation and the panel side by side, the
panel covers the conversation until you close it, the same way **Changes** does.

::: tip What the agent said, and what is on disk
The Edits tab shows what each call *said* it changed. [**Changes**](/guide/worktrees-and-prs)
(⌘D) is the worktree's own diff, which is the record of what is actually on disk. A Codex
turn that Cockpit is streaming reports which files it changed but not the lines; reopen
the session once the turn ends to read the lines from its log.
:::

Copilot CLI keeps its to-dos in a database of its own rather than in its log, so a
Copilot session's To-dos tab stays empty. Its plans and edits are shown.

## Provider quirks

Cockpit smooths over the differences it can, and is honest about the ones it can't:
Expand Down
Loading
Loading