feat(sidebar): pin tasks waiting for input to the top, make task names readable - #250
feat(sidebar): pin tasks waiting for input to the top, make task names readable#250oluies wants to merge 3 commits into
Conversation
|
Thanks — I carefully re-reviewed the complete current head ( I found two cases that I think should be fixed before merge:
A small helper returning the newest open question's |
Address PR johannesjo#250 review. Tray membership came from `getTaskAttentionState(taskId) === 'needs_input'`, but that function returns `error` or `review` before it ever checks question state. In a multi-agent task one agent can exit non-zero — or the task can be flagged for review — while another agent is still blocked on an answer, and the task silently dropped out of "Waiting for you" even though a human answer was still owed. `getTaskOpenQuestion(taskId)` now reports the newest open question directly as `{ agentId, since }`, independent of the prioritized display status, and replaces `getTaskQuestionSince`. `computeNeedsInputTasks` keys off it. The asking set and the onset map are separate structures kept in step by their sole writer, so the one place they meet is the one place that tolerates a skew — `since` is a plain `number` everywhere downstream. Clicking a tray row also restored the *previously* focused panel, so a question from a non-selected agent tab or from a shell landed somewhere else and needed a second click. Each entry now carries the asking panel — `ai-terminal:<agentId>` or `shell:<index>` — and `jumpToWaitingTask` focuses that, re-validating it against the task's current agents at click time so a stale panel degrades to the last focused one instead of focusing nothing. Since `setTaskFocusedPanel` selects the agent behind an ai-terminal panel, a background tab is brought to the front; the three writes are batched so `setActiveTask`'s intermediate selection of the *old* agent never renders. `jumpToWaitingTask` moved from Sidebar.tsx into the store so that ordering is testable. Two display consequences of question-driven membership, fixed here: - The tray's status dot was hardcoded to `needs_input`. A row can now also be errored or awaiting review, so it takes the task's real attention state and no longer contradicts the same task's dot in the list below. The tray header carries the "waiting for you" message on its own. - `getDotTooltip` had no `review` branch, so a review-flagged task with an active agent showed a purple dot reading "Busy". Added. The `shell:<index>` convention was hand-rolled at six sites and `ai-terminal:` at two. `shellPanelId`/`shellPanelIndex`/`isShellPanel` join `aiTerminalPanelId` in focused-panel.ts and every site now goes through them, so the format is built and parsed in one place. The parser takes digits only — `Number('')` is 0 and `Number('1e2')` is 100, so a loose parse would let `shell:` validate as a real panel. Two comments corrected while here: the collapsed sweep in `computeNeedsInputTasks` and the matching `uncollapseTask` guard are inert — `collapseTask` kills every agent and clears its question state — so they no longer claim minimized tasks show up in the tray. Known gap, deliberately not converted: `desktopNotifications.ts` and `remoteStatusSync.ts` still classify `needs_input` from the attention state and so inherit the same masking. Changing when a desktop notification fires is a product decision rather than mechanical follow-through, so it is documented on `getTaskOpenQuestion` and left to the author. Tests: `getTaskOpenQuestion` covered against a mixed error/question task, a review-flagged task, several agents asking at once, a shell question, and a stopped agent; the tray covered for panel resolution and an unplaceable agent; `jumpToWaitingTask` for the ai-terminal and shell targets, the last-focused fallback, select-then-focus ordering, the uncollapse guard, and stale or malformed panels; plus shell-panel-id round-trip and rejection cases, and `getDotTooltip('busy', 'review')`. Verified in the running app on a scratch profile with stub agents, not just in tests: a task whose first agent exits 1 while a second asks stays listed, its dot reads red in both the tray and the list, and clicking the row with the failed agent's tab in front switches to the asking agent's tab. Full suite 1710 passed / 24 skipped.
|
desktopNotifications.ts:97 and remoteStatusSync.ts:38 still classify needs_input from getTaskAttentionState, so they carry the same masking. In the scenario you described, the "Task Needs Input" desktop notification never fires and the mobile overview shows only the error. getTaskOpenQuestion is what both would use. I didn't change it |
The sidebar task list clipped long names mid-word — the name span had no min-width:0 inside its flex row, so text-overflow never applied — and a task that started asking a question stayed wherever it happened to sit in the list. Names now wrap to two lines with the full name as a tooltip, the direct-branch badge moved after the name so the name claims width first, and project group headers ellipsise instead of pushing their count out of view. A new "Waiting for you" tray sits between the New Task button and the scrollable list, holding every task blocked on an answer, newest question first. Ordering needed a question-onset timestamp, which nothing tracked, so taskStatus now stamps one per agent and exposes getTaskQuestionSince(). Tasks stay in their project group as well — the tray is a shortcut, not a move, which keeps drag-reorder indices intact. Clicking a row opens the task and restores its last focused panel so the question can be answered in one click. The tray is a mode (sidebarNeedsInputFirst, persisted, on by default) with an in-tray dismiss and a Settings → Behavior checkbox.
Running the app showed the wrapped names cut mid-glyph. Two separate caps were doing it: rows are flex children of the scrolling task column, so they were shrunk below their content height, and the task-item appear animation tweens max-height to a fixed 40px — shorter than any two-line row, and the class stays on the element. Rows now set flex-shrink: 0 and the keyframe animates only opacity and transform. Also moves the tray's elapsed-time label down to the project line so the name gets the full row width.
Address PR johannesjo#250 review. Tray membership came from `getTaskAttentionState(taskId) === 'needs_input'`, but that function returns `error` or `review` before it ever checks question state. In a multi-agent task one agent can exit non-zero — or the task can be flagged for review — while another agent is still blocked on an answer, and the task silently dropped out of "Waiting for you" even though a human answer was still owed. `getTaskOpenQuestion(taskId)` now reports the newest open question directly as `{ agentId, since }`, independent of the prioritized display status, and replaces `getTaskQuestionSince`. `computeNeedsInputTasks` keys off it. The asking set and the onset map are separate structures kept in step by their sole writer, so the one place they meet is the one place that tolerates a skew — `since` is a plain `number` everywhere downstream. Clicking a tray row also restored the *previously* focused panel, so a question from a non-selected agent tab or from a shell landed somewhere else and needed a second click. Each entry now carries the asking panel — `ai-terminal:<agentId>` or `shell:<index>` — and `jumpToWaitingTask` focuses that, re-validating it against the task's current agents at click time so a stale panel degrades to the last focused one instead of focusing nothing. Since `setTaskFocusedPanel` selects the agent behind an ai-terminal panel, a background tab is brought to the front; the three writes are batched so `setActiveTask`'s intermediate selection of the *old* agent never renders. `jumpToWaitingTask` moved from Sidebar.tsx into the store so that ordering is testable. Two display consequences of question-driven membership, fixed here: - The tray's status dot was hardcoded to `needs_input`. A row can now also be errored or awaiting review, so it takes the task's real attention state and no longer contradicts the same task's dot in the list below. The tray header carries the "waiting for you" message on its own. - `getDotTooltip` had no `review` branch, so a review-flagged task with an active agent showed a purple dot reading "Busy". Added. The `shell:<index>` convention was hand-rolled at six sites and `ai-terminal:` at two. `shellPanelId`/`shellPanelIndex`/`isShellPanel` join `aiTerminalPanelId` in focused-panel.ts and every site now goes through them, so the format is built and parsed in one place. The parser takes digits only — `Number('')` is 0 and `Number('1e2')` is 100, so a loose parse would let `shell:` validate as a real panel. Two comments corrected while here: the collapsed sweep in `computeNeedsInputTasks` and the matching `uncollapseTask` guard are inert — `collapseTask` kills every agent and clears its question state — so they no longer claim minimized tasks show up in the tray. Known gap, deliberately not converted: `desktopNotifications.ts` and `remoteStatusSync.ts` still classify `needs_input` from the attention state and so inherit the same masking. Changing when a desktop notification fires is a product decision rather than mechanical follow-through, so it is documented on `getTaskOpenQuestion` and left to the author. Tests: `getTaskOpenQuestion` covered against a mixed error/question task, a review-flagged task, several agents asking at once, a shell question, and a stopped agent; the tray covered for panel resolution and an unplaceable agent; `jumpToWaitingTask` for the ai-terminal and shell targets, the last-focused fallback, select-then-focus ordering, the uncollapse guard, and stale or malformed panels; plus shell-panel-id round-trip and rejection cases, and `getDotTooltip('busy', 'review')`. Verified in the running app on a scratch profile with stub agents, not just in tests: a task whose first agent exits 1 while a second asks stays listed, its dot reads red in both the tray and the list, and clicking the row with the failed agent's tab in front switches to the asking agent's tab. Full suite 1710 passed / 24 skipped.
a9033ea to
3b289e4
Compare
What
Two changes to the left sidebar task list.
1. A "Waiting for you" tray, pinned under + New Task
When several tasks run in parallel, the one that just stopped to ask you something is wherever it happens to sit in the list — easy to miss, and easy to lose again once the list scrolls. This adds a tray between the + New Task button and the scrollable list holding every task blocked on an answer, newest question first. It sits outside the scroll container, so it never scrolls away.
Each row shows the status dot, the task name, the project it belongs to (the row is out of its project group, so it needs the label), and how long it has been waiting. Clicking a row opens the task and restores its last focused panel, so the question can be answered in one click.
Tasks stay in their project group as well — the tray is a shortcut, not a move. That keeps the
data-task-index↔taskOrdermapping drag-reorder depends on intact, and keeps list positions stable.It's a mode:
sidebarNeedsInputFirst, persisted, on by default, with an × in the tray header and a Settings → Behavior checkbox to turn it back on.Ordering by recency needed a question-onset timestamp, which nothing recorded.
taskStatusnow stamps one per agent when a question appears, clears it when the question goes away, and exposesgetTaskQuestionSince(taskId).2. Task names you can actually read
Long names were clipped mid-word. Three separate causes, all fixed:
min-width: 0, sotext-overflow: ellipsisnever applied and the text was simply cut by the row'soverflow: hidden. Names now wrap to at most two lines with the full name as a tooltip.flex-shrink: 0.task-itemappear animation tweensmax-heightto a fixed 40px, shorter than any two-line row, and the class stays on the element. The keyframe now animates only opacity and transform.Both screenshots above show the result — every name renders on two full lines with a proper ellipsis.
Alongside that: the
directbranch badge moved after the name so the name claims width first (and the badge is capped and ellipsised), project group headers ellipsise while keeping their(n)count visible, and row spacing went 1px → 3px.Testing
npm run typecheck,npm run lint,npm run format:checkclean; full suite 1683 passed / 24 skipped.New
src/store/sidebar-attention.test.tscovers the ordering: newest question first, collapsed tasks included, unknown timestamps sink to the bottom, missing task ids skipped, no task listed twice.Verified in the running app, not just in tests: launched Electron against a scratch profile seeded with two projects and four tasks, driven by stub agents that print a question and wait, so the real PTY question detector fires. The tray appeared with one entry, then two, with the later question sorted above the earlier one; the × persisted the mode to
state.json. The two clipping bugs above were found because of that run — they were invisible to typecheck, lint, and unit tests.