feat: add running-terminal indicator on tasks#800
feat: add running-terminal indicator on tasks#800nthomsencph wants to merge 4 commits intogeneralaction:mainfrom
Conversation
Expose pty:exited broadcast, pty:list handler, and taskId on pty:started so the renderer can track which tasks have active terminal sessions. - Add broadcastToAll() helper with error logging - Export getPtyTaskId() to extract taskId from PTY IDs - Add listPtyIds() to ptyManager for seeding renderer state on reload - Broadcast pty:exited at all PTY teardown points - Enrich pty:started broadcasts with taskId - Expose ptyList, onPtyExited in preload and type declarations Closes generalaction#781 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a three-state icon to TaskItem: spinner (busy), green terminal (has active PTY, idle), or git-branch (no terminal). Backed by a TerminalLivenessStore singleton that tracks active PTYs per task via IPC events, with race-safe seeding on app reload. Closes generalaction#781 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- getPtyTaskId: standard IDs, chat terminals, malformed input, underscores/digits, greedy regex limitation with -main- in taskIds - terminalLivenessStore: start/exit transitions, multi-PTY partial exit, chat filtering, null taskId, ptyList seeding, early-exit race guard, unsubscribe Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Someone is attempting to deploy a commit to the General Action Team on Vercel. A member of the Team first needs to authorize it. |
Greptile OverviewGreptile SummaryThis PR adds a three-state task icon driven by PTY liveness: the main process now broadcasts The overall wiring is coherent (preload exposes the new APIs; renderer subscribes once and seeds from current PTYs), but there are a couple of correctness issues that can leave UI state wrong:
Additionally, the Electron API typings have an internal inconsistency for Confidence Score: 2/5
|
| Filename | Overview |
|---|---|
| src/main/preload.ts | Exposes new PTY IPC APIs (ptyList, onPtyExited) and extends onPtyStarted payload with taskId; looks consistent with main process broadcasts. |
| src/main/services/ptyIpc.ts | Adds broadcastToAll, getPtyTaskId, pty:list handler and more pty:started/exited broadcasts; missing pty:exited on manual kill and taskId parsing can misattribute terminals when taskId contains '-main-'. |
| src/main/services/ptyManager.ts | Adds listPtyIds() to expose current PTY IDs for renderer seeding; straightforward. |
| src/renderer/components/TaskItem.tsx | Adds terminal indicator state (spinner vs green terminal vs branch) using new useTaskHasTerminal hook; minimal UI-only change. |
| src/renderer/hooks/useTaskHasTerminal.ts | New hook subscribes to terminalLivenessStore for a taskId and returns boolean state; depends on store correctness. |
| src/renderer/lib/terminalLivenessStore.ts | New singleton store tracks active PTYs per task via pty:started/pty:exited and initial ptyList seed; can permanently fail to subscribe if window.electronAPI is undefined on first subscribe. |
| src/renderer/types/electron-api.d.ts | Extends electronAPI typings for new PTY APIs; onPtyStarted taskId optionality is inconsistent between global Window.electronAPI and exported ElectronAPI interface. |
| src/test/main/ptyIpc.test.ts | Adds tests for getPtyTaskId behavior and keeps existing IPC lifecycle tests; documents current greedy parsing limitation. |
| src/test/renderer/terminalLivenessStore.test.ts | Adds unit tests for terminalLivenessStore transitions, multi-PTY, chat filtering, seeding, early-exit race guard, and unsubscribe. |
Sequence Diagram
sequenceDiagram
participant R as Renderer (Task list)
participant S as terminalLivenessStore
participant P as Preload (electronAPI)
participant I as Main (ptyIpc)
participant M as Main (ptyManager)
R->>S: subscribe(taskId, listener)
S->>P: electronAPI.onPtyStarted(cb)
S->>P: electronAPI.onPtyExited(cb)
S->>P: electronAPI.ptyList()
P->>I: ipcRenderer.invoke('pty:list')
I->>M: listPtyIds()
M-->>I: [ptyIds]
I-->>P: [{id, taskId}]
P-->>S: seed items
S-->>R: listener(hasTerminal)
Note over I,M: PTY lifecycle
I-->>P: broadcast 'pty:started' {id, taskId}
P-->>S: onPtyStarted callback
S-->>R: listener(true)
I-->>P: broadcast 'pty:exited' {id, taskId}
P-->>S: onPtyExited callback
S-->>R: listener(false when last PTY exits)
| /** Extract taskId from a main-terminal PTY ID, or null. */ | ||
| export function getPtyTaskId(id: string): string | null { | ||
| const m = /^[a-z0-9_-]+-main-(.+)$/.exec(id); | ||
| return m ? m[1] : null; | ||
| } |
There was a problem hiding this comment.
TaskId misattribution
getPtyTaskId() uses a greedy regex (/^[a-z0-9_-]+-main-(.+)$/) that will extract the wrong taskId if the taskId itself contains -main- (the added tests explicitly encode this behavior). That will cause terminal liveness (and any other future task-scoped behavior using this helper) to be attributed to the wrong task.
There was a problem hiding this comment.
As discussed briefly with @arnestrickmann, this is a known limitation of the greedy regex approach. Added a test that documents the behavior (handles taskIds that themselves contain -main- (known greedy limitation)).
The proper fix is likely to store taskId as metadata on the PTY record at creation time rather than reverse-engineering it from the ID string, which would also eliminate the coupling to the ID format. That's a ptyManager structural change so figured it should be scoped as a follow-up.
Additional Comments (1)
|
- Fix one-shot subscribe bug: defer setting `subscribed = true` until after confirming `window.electronAPI` exists, so a retry succeeds if preload hasn't attached yet - Broadcast pty:exited on manual pty:kill so the liveness indicator doesn't get stuck on - Align onPtyStarted taskId type to `string | null` in exported ElectronAPI interface (was still optional) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixed in f957449 — pty:kill now broadcasts pty:exited before cleanup. |
pty:started,pty:exited,pty:list) instead of pollingCloses #781
Changes
Main process
ptyManager.ts— AddedlistPtyIds()for renderer seedingptyIpc.ts— AddedbroadcastToAll()helper, exportedgetPtyTaskId()for taskId extraction,pty:listhandler,pty:exitedbroadcasts at all PTY teardown points, enrichedpty:startedwithtaskIdpreload.ts— ExposedptyList,onPtyExited, updatedonPtyStartedpayloadRenderer
terminalLivenessStore.ts(new) — Singleton store tracking active PTYs per task with transition-only emission and race-safe seedinguseTaskHasTerminal.ts(new) — React hook wrapping the storeTaskItem.tsx— Three-state icon usinguseTaskHasTerminalTypes
electron-api.d.ts— AddedptyList,onPtyExited, consistenttaskId: string | nullon both eventsTests
ptyIpc.test.ts— 5 tests forgetPtyTaskIdregexterminalLivenessStore.test.ts(new) — 9 tests covering start/exit transitions, multi-PTY, chat filtering, seeding, race guard, unsubscribe