Skip to content

Conversation

@andreasasprou
Copy link
Collaborator

@andreasasprou andreasasprou commented Jan 4, 2026

Summary

  • Add 3-color workspace status indicators showing agent lifecycle (amber=working, red=permission needed, green=ready for review)
  • Integrate with Claude Code hooks (UserPromptSubmit, Stop, PermissionRequest) and OpenCode plugin (session.status, permission.ask)
  • Harden dev/prod separation to prevent cross-talk when running both environments
  • Add robust fallbacks for stuck indicators (terminal exit, ESC/Ctrl+C detection)

Demo Loom

P.s. the bug in the loom is fixed now.

Why / Context

Users managing multiple agent workspaces need visual feedback on agent state without constantly switching tabs. Status indicators surface:

  • When an agent is actively working (so you know it's processing)
  • When an agent needs permission (requires immediate attention)
  • When an agent completed while you were away (ready for review)

How It Works

Status Flow:

  1. Agent hooks (Claude Code) or plugin events (OpenCode) fire on lifecycle changes
  2. notify.sh script sends HTTP POST to local notification server (port 31416)
  3. Server emits events via tRPC subscription to renderer
  4. useAgentHookListener updates pane status in Zustand store
  5. UI components display colored indicators based on aggregated status

Status Priority: permission (red) > working (amber) > review (green)

UI Locations:

  • Group tabs (top bar): Shows status per tab via GroupItem + StatusIndicator
  • Workspace sidebar: Shows aggregated status per workspace via WorkspaceListItem + StatusIndicator

Click Behavior:

  • reviewidle (acknowledged)
  • permissionworking (assumes permission granted, agent continues)
  • working → unchanged (persists until Stop event)

Manual QA Checklist

Claude Code Integration

  • Start Claude Code → amber pulsing indicator appears in group tab AND sidebar
  • Agent completes (while on different workspace) → green static indicator in both locations
  • Permission request → red pulsing indicator in both locations
  • Click workspace with indicator → status clears appropriately
  • ESC to interrupt → amber indicator clears immediately
  • Ctrl+C to quit → amber indicator clears immediately
  • Exit terminal while working → amber indicator clears on exit

OpenCode Integration

  • Start OpenCode → amber indicator on session.status with status.type === "busy"
  • Agent completes → green indicator on session.status with status.type === "idle"
  • Permission request → red indicator on permission.ask
  • Subagent completion (oh-my-opencode) → NO indicator (filtered by parentID)

Indicator Colors

  • Amber (pulsing) shows for "working" status
  • Red (pulsing) shows for "permission" status
  • Green (static) shows for "review" status
  • Blue (static) shows for manually "unread" workspaces (separate from agent status)

App Lifecycle

  • App restart clears "working" and "permission" statuses
  • App restart preserves "review" status (missed completions)

Dev/Prod Separation

  • Running dev and prod simultaneously causes no cross-talk
  • Unknown event types are silently ignored (forward compatibility)

Testing

  • bun run typecheck
  • bun run lint
  • bun test - 450/451 pass (1 unrelated pre-existing failure)
  • Manual testing with Claude Code and OpenCode

Debug Mode

Enable debug logging for future QA with:

SUPERSET_DEBUG=1 bun run desktop

Logs: [debug:notifications] (server), [debug:agent-hooks] (renderer)

Design Decisions

  • Why UserPromptSubmit for Start instead of PreToolUse: Simpler mental model; user submits prompt = agent starts working. Tool-level granularity adds complexity without clear UX benefit.
  • Why click permission → working (not idle): When user clicks to dismiss permission indicator, they likely granted permission, so agent continues working.
  • Why filter subagent sessions: oh-my-opencode spawns background agents (explore, librarian) that emit session.idle on completion. Without filtering, users would get notification spam.
  • Why separate unread (blue) from status indicators: The isUnread flag is a manual marker (Mark as Unread context menu), independent of agent lifecycle. Blue dot only shows when no agent status is active.
  • Why clear on terminal exit: Terminal exit is a reliable signal from node-pty. If terminal dies, agent can't be working anymore. This provides a robust fallback for stuck indicators.
  • Why clear on ESC/Ctrl+C: These are explicit user interrupt signals. ESC stops generation in Claude Code, Ctrl+C interrupts the process. Both indicate user intent to stop.

Known Limitations

These are external limitations of Claude Code / OpenCode hook systems:

Limitation Impact Mitigation
Permission denied No hook fires when user denies permission Terminal exit clears status; click workspace to clear manually
Tool failures No hook fires on tool execution failure Status continues until agent stops or terminal exits

Solved limitations:

  • User interrupt (Ctrl+C) → Now detected in terminal, clears status immediately
  • ESC to stop generation → Now detected in terminal, clears status immediately
  • Terminal exit → Now clears "working" and "permission" status automatically

Documented inline in useAgentHookListener.ts.

Commits

  • a9cd65d4 - Base feature: 3-color workspace status indicators
  • 6e4a8d0e - Fix: Use StatusIndicator in workspace sidebar
  • 649258f7 - Fix: Correct OpenCode event types for status indicators
  • 60c36400 - Fix: Clear pane status on terminal exit
  • 56548784 - Fix: Clear status on ESC/Ctrl+C to handle agent interrupts
  • 252f7f3d - Fix: Use session.status event for OpenCode busy detection
  • 1889ad4f - Feat: Add debug logging utility with SUPERSET_DEBUG flag

Follow-ups

  • Codex full support (currently review-only, no working indicator due to hook limitations)

Summary by CodeRabbit

  • New Features

    • Status indicators for pane states (idle, working, permission, review) replace simple attention flags.
    • Agent lifecycle notifications introduced (agent-lifecycle) with Start/Stop/Permission semantics.
  • Improvements

    • Better lifecycle handling and resilient event parsing; terminal env now includes hook protocol/version.
    • Template-driven plugin/notify script generation and startup cleanup for legacy plugins.
    • Scoped debug logging added.
  • Documentation

    • New guide describing external files, environment dirs, and integration.
  • Tests

    • Added tests for event mapping and terminal env metadata.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 4, 2026

📝 Walkthrough

Walkthrough

Replaces per-pane boolean attention with a typed PaneStatus system, renames AGENT_COMPLETE → AGENT_LIFECYCLE across notification plumbing, adds shared notification types, templates for notify/opencode scripts, introduces status aggregation/utilities, and propagates status-driven UI, store, terminal env, and hook-server changes.

Changes

Cohort / File(s) Summary
Docs & Gitignore
\.gitignore, apps/desktop/docs/EXTERNAL_FILES.md
.gitignore newline fix; add EXTERNAL_FILES.md describing external files, shell RC integration, env vars, and debugging steps.
Shared types & helpers
apps/desktop/src/shared/tabs-types.ts, apps/desktop/src/shared/notification-types.ts, apps/desktop/src/shared/constants.ts, apps/desktop/src/shared/debug.ts
Add PaneStatus/ActivePaneStatus, STATUS_PRIORITY, pickHigherStatus/getHighestPriorityStatus; add NotificationIds and AgentLifecycleEvent; rename NOTIFICATION_EVENTS.AGENT_COMPLETE → AGENT_LIFECYCLE; add debugLog/isDebug.
Notification server & tests
apps/desktop/src/main/lib/notifications/server.ts, .../server.test.ts
Re-export shared types, add SERVER_ENV, mapEventType, resolvePaneId; normalize incoming events; emit AGENT_LIFECYCLE with resolved paneId; add tests for mapEventType.
TRPC router wiring
apps/desktop/src/lib/trpc/routers/notifications.ts
Rename AgentCompleteEvent → AgentLifecycleEvent; switch emits/subscriptions to AGENT_LIFECYCLE; update callback names and teardown.
Main window notifications
apps/desktop/src/main/windows/main.ts
Subscribe to AGENT_LIFECYCLE; handler uses AgentLifecycleEvent and ignores Start events for UI notifications.
Agent hook templates & script generation
apps/desktop/src/main/lib/agent-setup/templates/*.template.*, apps/desktop/src/main/lib/agent-setup/notify-hook.ts
Add notify-hook and opencode plugin templates; switch notify script and plugin generation to template-driven I/O; improve parsing/timeouts and mapping for event types.
Agent wrappers & setup
apps/desktop/src/main/lib/agent-setup/agent-wrappers.ts, apps/desktop/src/main/lib/agent-setup/index.ts
Bump OPENCODE_PLUGIN_MARKER/version, move to template-based content, add cleanupGlobalOpenCodePlugin(), invoke cleanup during setup; log creation/cleanup.
Terminal env & tests
apps/desktop/src/main/lib/terminal/env.ts, .../env.test.ts
Export HOOK_PROTOCOL_VERSION = "2"; include SUPERSET_ENV and SUPERSET_HOOK_VERSION in terminal env; remove GOOGLE_API_KEY; add tests.
Renderer tabs store & types
apps/desktop/src/renderer/stores/tabs/store.ts, .../types.ts
Migrate from needsAttentionstatus; add setPaneStatus, clearWorkspaceAttentionStatus; persist storage v2 migration/merge; add guards for unknown panes.
Agent hook listener (renderer)
apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
Map lifecycle events to statuses: Start→working, PermissionRequest→permission, Stop→idle/review based on focus; add debug logging and focus checks.
Resolve notification target import
apps/desktop/src/renderer/stores/tabs/utils/resolve-notification-target.ts
Update NotificationIds import to shared/notification-types (no logic change).
UI schema & components
apps/desktop/src/lib/trpc/routers/ui-state/index.ts, apps/desktop/src/renderer/screens/main/components/StatusIndicator/*, .../WorkspaceSidebar/WorkspaceListItem.tsx, .../GroupStrip/*.tsx
Replace needsAttention with status in schema; add StatusIndicator component and exports; propagate aggregated status-based rendering (pickHigherStatus/getHighestPriorityStatus) across workspace/group UI.
Terminal component tweaks
apps/desktop/src/renderer/screens/main/.../Terminal/Terminal.tsx
Clear transient statuses ("working"/"permission" → "idle") on process exit, interrupt, and pending flushes; use store access for immediate resets.
Hotkey rendering optimization
apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceSidebarControl.tsx
Cache formatted hotkey display to avoid per-render recomputation.
Tooling & TS config
biome.jsonc, apps/desktop/tsconfig.json
Exclude template files from tooling includes; exclude src//templates/ from TypeScript compilation.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Hook as Hook Script (terminal)
  participant Server as Hook Server (/hook/complete)
  participant Emitter as notificationsEmitter
  participant Renderer as Renderer (useAgentHookListener / store)
  participant UI as Workspace UI (StatusIndicator)

  Note over Hook,Server: Local hook posts normalized lifecycle payload
  Hook->>Server: POST /hook/complete { eventType, paneId?, tabId?, workspaceId?, env, version }
  Server->>Emitter: emit next { type: "agent-lifecycle", data: AgentLifecycleEvent }
  Emitter->>Renderer: deliver AgentLifecycleEvent
  Renderer->>Renderer: mapEventType() -> ("Start"|"Stop"|"PermissionRequest") or null
  Renderer->>Renderer: resolvePaneId(...) -> paneId?
  Renderer->>Renderer: determine status (working/permission/review/idle)
  Renderer->>Renderer: setPaneStatus(paneId, status)
  Renderer->>UI: UI re-renders affected panes, aggregate via getHighestPriorityStatus/pickHigherStatus
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through hooks and tiny dots of light,

Turned booleans into statuses — idle, busy, and bright.
Templates hum, lifecycles sing, stale plugins cleared with care,
Panes wear little badges now — the rabbit gives a cheer!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(desktop): add 3-color workspace status indicators' directly and concisely describes the main feature added in this changeset.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering summary, context, implementation details, QA checklist, testing results, design decisions, known limitations, and follow-ups, matching the expected template structure with clear sections.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Jan 4, 2026

🚀 Preview Deployment

🔗 Preview Links

Service Status Link
Neon Database (Neon) View Branch
Fly.io Electric (Fly.io) View App
Vercel API (Vercel) Open Preview
Vercel Web (Vercel) Open Preview
Vercel Marketing (Vercel) Open Preview
Vercel Admin (Vercel) Open Preview
Vercel Docs (Vercel) Open Preview

Preview updates automatically with new commits

@andreasasprou andreasasprou changed the base branch from workspace-sidebar to persistent-terminals January 4, 2026 08:36
@andreasasprou andreasasprou force-pushed the persistent-terminals branch 3 times, most recently from 9788ed3 to bcc8317 Compare January 4, 2026 20:16
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI Agents
In
@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx:
- Around line 228-233: The handler currently reads pane?.status from the render
closure (stale) when deciding to call setPaneStatus(paneId, "idle"); instead,
read the current pane status at event time (e.g., call the state
getter/selectors or store getter for the pane ID such as getPane(paneId) or
usePaneStore.getState().panes[paneId]) and base the check on that fresh value,
or use a functional/state-getter API to update only if the current status is
"working" or "permission" (for example, call a get-current-pane-status(paneId)
and then setPaneStatus(paneId, "idle") only if that returns "working" or
"permission"). Ensure you reference paneId when fetching the live pane and
remove reliance on the closed-over pane variable.
🧹 Nitpick comments (4)
apps/desktop/src/main/lib/agent-setup/templates/notify-hook.template.sh (1)

37-46: Consider adding & for background execution to fully decouple.

While the timeouts prevent long blocking, the curl call still waits up to 2 seconds. For truly non-blocking behavior, consider backgrounding the curl:

🔎 Optional: Background the curl call
 # Timeouts prevent blocking agent completion if notification server is unresponsive
 curl -sG "http://127.0.0.1:${SUPERSET_PORT:-{{DEFAULT_PORT}}}/hook/complete" \
   --connect-timeout 1 --max-time 2 \
   --data-urlencode "paneId=$SUPERSET_PANE_ID" \
   --data-urlencode "tabId=$SUPERSET_TAB_ID" \
   --data-urlencode "workspaceId=$SUPERSET_WORKSPACE_ID" \
   --data-urlencode "eventType=$EVENT_TYPE" \
   --data-urlencode "env=$SUPERSET_ENV" \
   --data-urlencode "version=$SUPERSET_HOOK_VERSION" \
-  > /dev/null 2>&1
+  > /dev/null 2>&1 &
apps/desktop/src/main/lib/agent-setup/templates/opencode-plugin.template.js (1)

67-90: Consider bounding the childSessionCache size.

The childSessionCache Map grows indefinitely during long-running sessions. While unlikely to be problematic in practice, consider adding a size limit or LRU eviction for very long sessions with many subagents.

🔎 Optional: Add cache size limit
   const childSessionCache = new Map();
+  const MAX_CACHE_SIZE = 100;
   const isChildSession = async (sessionID) => {
     if (!sessionID) return true; // No sessionID = can't verify, skip
     if (!client?.session?.list) return true; // Can't check, skip

     // Check cache first
     if (childSessionCache.has(sessionID)) {
       return childSessionCache.get(sessionID);
     }

     try {
       const sessions = await client.session.list();
       const session = sessions.data?.find((s) => s.id === sessionID);
       const isChild = !!session?.parentID;
+      // Prevent unbounded cache growth
+      if (childSessionCache.size >= MAX_CACHE_SIZE) {
+        const firstKey = childSessionCache.keys().next().value;
+        childSessionCache.delete(firstKey);
+      }
       childSessionCache.set(sessionID, isChild);
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupItem.tsx (1)

12-12: Consider using ActivePaneStatus | null for stricter typing.

The prop type is PaneStatus | null, but GroupStrip passes ActivePaneStatus | null (which excludes "idle"). Using ActivePaneStatus | null here would:

  1. More accurately reflect the actual data being passed
  2. Make the status !== "idle" check on line 43 unnecessary

This is a minor type refinement and the current implementation is functionally correct.

🔎 Suggested type refinement
+import type { ActivePaneStatus } from "shared/tabs-types";
-import type { PaneStatus, Tab } from "renderer/stores/tabs/types";
+import type { Tab } from "renderer/stores/tabs/types";

 interface GroupItemProps {
 	tab: Tab;
 	isActive: boolean;
-	status: PaneStatus | null;
+	status: ActivePaneStatus | null;
 	onSelect: () => void;
 	onClose: () => void;
 }

And simplify line 43:

-{status && status !== "idle" && <StatusIndicator status={status} />}
+{status && <StatusIndicator status={status} />}
apps/desktop/src/renderer/stores/tabs/store.ts (1)

551-583: Consider extracting duplicate status transition logic.

The status transition logic (lines 567-578) is identical to lines 206-217 in setActiveTab. Consider extracting this to a helper function to reduce duplication and ensure consistent behavior.

🔎 Suggested refactor
// Helper at module level or within the store
function transitionStatusOnActivation(
  panes: Record<string, Pane>,
  paneIds: string[],
): { newPanes: Record<string, Pane>; hasChanges: boolean } {
  const newPanes = { ...panes };
  let hasChanges = false;
  for (const paneId of paneIds) {
    const currentStatus = newPanes[paneId]?.status;
    if (currentStatus === "review") {
      newPanes[paneId] = { ...newPanes[paneId], status: "idle" };
      hasChanges = true;
    } else if (currentStatus === "permission") {
      newPanes[paneId] = { ...newPanes[paneId], status: "working" };
      hasChanges = true;
    }
  }
  return { newPanes, hasChanges };
}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 399a777 and 8c2a9a3.

📒 Files selected for processing (30)
  • .gitignore
  • apps/desktop/docs/EXTERNAL_FILES.md
  • apps/desktop/src/lib/trpc/routers/notifications.ts
  • apps/desktop/src/lib/trpc/routers/ui-state/index.ts
  • apps/desktop/src/main/lib/agent-setup/agent-wrappers.ts
  • apps/desktop/src/main/lib/agent-setup/index.ts
  • apps/desktop/src/main/lib/agent-setup/notify-hook.ts
  • apps/desktop/src/main/lib/agent-setup/templates/notify-hook.template.sh
  • apps/desktop/src/main/lib/agent-setup/templates/opencode-plugin.template.js
  • apps/desktop/src/main/lib/notifications/server.test.ts
  • apps/desktop/src/main/lib/notifications/server.ts
  • apps/desktop/src/main/lib/terminal/env.test.ts
  • apps/desktop/src/main/lib/terminal/env.ts
  • apps/desktop/src/main/windows/main.ts
  • apps/desktop/src/renderer/screens/main/components/StatusIndicator/StatusIndicator.tsx
  • apps/desktop/src/renderer/screens/main/components/StatusIndicator/index.ts
  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceSidebarControl.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/stores/tabs/store.ts
  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/stores/tabs/utils/resolve-notification-target.ts
  • apps/desktop/src/shared/constants.ts
  • apps/desktop/src/shared/debug.ts
  • apps/desktop/src/shared/notification-types.ts
  • apps/desktop/src/shared/tabs-types.ts
  • biome.jsonc
🚧 Files skipped from review as they are similar to previous changes (12)
  • apps/desktop/src/main/windows/main.ts
  • apps/desktop/src/main/lib/notifications/server.test.ts
  • apps/desktop/src/shared/notification-types.ts
  • apps/desktop/src/renderer/screens/main/components/StatusIndicator/index.ts
  • apps/desktop/src/main/lib/terminal/env.ts
  • apps/desktop/src/main/lib/terminal/env.test.ts
  • apps/desktop/src/shared/debug.ts
  • .gitignore
  • apps/desktop/src/lib/trpc/routers/notifications.ts
  • apps/desktop/src/lib/trpc/routers/ui-state/index.ts
  • apps/desktop/src/renderer/stores/tabs/utils/resolve-notification-target.ts
  • apps/desktop/src/renderer/screens/main/components/TopBar/WorkspaceSidebarControl.tsx
🧰 Additional context used
📓 Path-based instructions (6)
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc
Use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.
For tRPC subscriptions with trpc-electron, ALWAYS use the observable pattern from @trpc/server/observable instead of async generators, as the library explicitly checks isObservable(result) and throws an error otherwise

Files:

  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/screens/main/components/StatusIndicator/StatusIndicator.tsx
  • apps/desktop/src/main/lib/agent-setup/notify-hook.ts
  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
  • apps/desktop/src/shared/tabs-types.ts
  • apps/desktop/src/main/lib/notifications/server.ts
  • apps/desktop/src/shared/constants.ts
  • apps/desktop/src/main/lib/agent-setup/agent-wrappers.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupItem.tsx
  • apps/desktop/src/renderer/stores/tabs/store.ts
  • apps/desktop/src/main/lib/agent-setup/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use object parameters for functions with 2+ parameters instead of positional arguments
Functions with 2+ parameters should accept a single params object with named properties for self-documentation and extensibility
Use prefixed console logging with context pattern: [domain/operation] message
Extract magic numbers and hardcoded values to named constants at module top
Use lookup objects/maps instead of repeated if (type === ...) conditionals
Avoid using any type - maintain type safety in TypeScript code
Never swallow errors silently - at minimum log them with context
Import from concrete files directly when possible - avoid barrel file abuse that creates circular dependencies
Avoid deep nesting (4+ levels) - use early returns, extract functions, and invert conditions
Use named properties in options objects instead of boolean parameters to avoid boolean blindness

Files:

  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/screens/main/components/StatusIndicator/StatusIndicator.tsx
  • apps/desktop/src/main/lib/agent-setup/notify-hook.ts
  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
  • apps/desktop/src/shared/tabs-types.ts
  • apps/desktop/src/main/lib/notifications/server.ts
  • apps/desktop/src/shared/constants.ts
  • apps/desktop/src/main/lib/agent-setup/agent-wrappers.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupItem.tsx
  • apps/desktop/src/renderer/stores/tabs/store.ts
  • apps/desktop/src/main/lib/agent-setup/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
apps/desktop/src/renderer/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never import Node.js modules (fs, path, os, net) in renderer process or shared code - they are externalized for browser compatibility

Files:

  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/screens/main/components/StatusIndicator/StatusIndicator.tsx
  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupItem.tsx
  • apps/desktop/src/renderer/stores/tabs/store.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
apps/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Drizzle ORM for all database operations - never use raw SQL

Files:

  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/screens/main/components/StatusIndicator/StatusIndicator.tsx
  • apps/desktop/src/main/lib/agent-setup/notify-hook.ts
  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
  • apps/desktop/src/shared/tabs-types.ts
  • apps/desktop/src/main/lib/notifications/server.ts
  • apps/desktop/src/shared/constants.ts
  • apps/desktop/src/main/lib/agent-setup/agent-wrappers.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupItem.tsx
  • apps/desktop/src/renderer/stores/tabs/store.ts
  • apps/desktop/src/main/lib/agent-setup/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Biome for formatting and linting - run at root level with bun run lint:fix or biome check --write

Files:

  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/src/renderer/screens/main/components/StatusIndicator/StatusIndicator.tsx
  • apps/desktop/src/main/lib/agent-setup/notify-hook.ts
  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx
  • apps/desktop/src/main/lib/agent-setup/templates/opencode-plugin.template.js
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
  • apps/desktop/src/shared/tabs-types.ts
  • apps/desktop/src/main/lib/notifications/server.ts
  • apps/desktop/src/shared/constants.ts
  • apps/desktop/src/main/lib/agent-setup/agent-wrappers.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupItem.tsx
  • apps/desktop/src/renderer/stores/tabs/store.ts
  • apps/desktop/src/main/lib/agent-setup/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

One component per file - do not create multi-component files

Files:

  • apps/desktop/src/renderer/screens/main/components/StatusIndicator/StatusIndicator.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
🧠 Learnings (3)
📚 Learning: 2025-11-24T21:32:21.725Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/CLAUDE.md:0-0
Timestamp: 2025-11-24T21:32:21.725Z
Learning: Applies to apps/desktop/**/AGENTS.md : Document agent responsibilities, capabilities, and interaction patterns in AGENTS.md

Applied to files:

  • apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts
  • apps/desktop/docs/EXTERNAL_FILES.md
  • apps/desktop/src/main/lib/agent-setup/templates/notify-hook.template.sh
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use Biome for formatting and linting - run at root level with `bun run lint:fix` or `biome check --write`

Applied to files:

  • biome.jsonc
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/desktop/src/shared/ipc-channels.ts : Define IPC channel types in apps/desktop/src/shared/ipc-channels.ts before implementing handlers

Applied to files:

  • apps/desktop/src/shared/tabs-types.ts
  • apps/desktop/src/main/lib/notifications/server.ts
🧬 Code graph analysis (8)
apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts (2)
apps/desktop/src/shared/constants.ts (1)
  • NOTIFICATION_EVENTS (42-45)
apps/desktop/src/shared/debug.ts (1)
  • debugLog (26-30)
apps/desktop/src/renderer/screens/main/components/StatusIndicator/StatusIndicator.tsx (2)
apps/desktop/src/shared/tabs-types.ts (1)
  • ActivePaneStatus (23-23)
apps/desktop/src/renderer/screens/main/components/StatusIndicator/index.ts (3)
  • ActivePaneStatus (2-2)
  • StatusIndicator (4-4)
  • getStatusTooltip (3-3)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
apps/desktop/src/shared/tabs-types.ts (1)
  • PaneStatus (20-20)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx (3)
apps/desktop/src/renderer/screens/main/components/StatusIndicator/StatusIndicator.tsx (1)
  • ActivePaneStatus (5-5)
apps/desktop/src/shared/tabs-types.ts (2)
  • ActivePaneStatus (23-23)
  • pickHigherStatus (40-48)
apps/desktop/src/renderer/screens/main/components/StatusIndicator/index.ts (1)
  • ActivePaneStatus (2-2)
apps/desktop/src/shared/tabs-types.ts (3)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
  • PaneStatus (12-12)
apps/desktop/src/renderer/screens/main/components/StatusIndicator/StatusIndicator.tsx (1)
  • ActivePaneStatus (5-5)
apps/desktop/src/renderer/screens/main/components/StatusIndicator/index.ts (1)
  • ActivePaneStatus (2-2)
apps/desktop/src/main/lib/agent-setup/agent-wrappers.ts (2)
apps/desktop/src/main/lib/agent-setup/templates/opencode-plugin.template.js (1)
  • notifyPath (32-32)
apps/desktop/src/main/lib/agent-setup/notify-hook.ts (1)
  • getNotifyScriptPath (15-17)
apps/desktop/src/renderer/stores/tabs/store.ts (2)
apps/desktop/src/renderer/stores/tabs/utils.ts (1)
  • extractPaneIdsFromLayout (54-65)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
  • TabsState (26-28)
apps/desktop/src/main/lib/agent-setup/index.ts (1)
apps/desktop/src/main/lib/agent-setup/agent-wrappers.ts (1)
  • cleanupGlobalOpenCodePlugin (191-209)
🪛 LanguageTool
apps/desktop/docs/EXTERNAL_FILES.md

[style] ~85-~85: This phrase is redundant. Consider using “outside”.
Context: ...External Files Before adding new files outside of ~/.superset[-dev]/: 1. **Consider if...

(OUTSIDE_OF)

🪛 Shellcheck (0.11.0)
apps/desktop/src/main/lib/agent-setup/templates/notify-hook.template.sh

[error] 2-2: You need a space after the '{'.

(SC1054)


[error] 2-2: Couldn't parse this brace group. Fix to allow more checks.

(SC1073)


[error] 2-2: You need a space after the '{'.

(SC1054)


[warning] 2-2: This } is literal. Check expression (missing ;/\n?) or quote it.

(SC1083)


[warning] 2-2: This } is literal. Check expression (missing ;/\n?) or quote it.

(SC1083)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Deploy Web
  • GitHub Check: Deploy Marketing
  • GitHub Check: Deploy API
  • GitHub Check: Deploy Admin
  • GitHub Check: Deploy Docs
  • GitHub Check: Build
🔇 Additional comments (42)
biome.jsonc (1)

9-9: LGTM!

The template file exclusions are appropriate since {{MARKER}} and similar placeholders in the template files would cause Biome parsing errors. This aligns with the template-driven approach introduced in this PR.

apps/desktop/docs/EXTERNAL_FILES.md (1)

1-99: Well-documented external file management.

This documentation clearly explains the environment-specific directories, shell integration, and debugging steps. It will be valuable for preventing dev/prod conflicts and onboarding new contributors.

apps/desktop/src/main/lib/agent-setup/agent-wrappers.ts (4)

15-24: LGTM! Good versioning and template approach.

The versioned marker allows detection of stale plugins during cleanup, and the template path approach improves maintainability as noted in prior review feedback.


80-80: LGTM! UserPromptSubmit hook enables "Start" event detection.

Adding this hook allows the shell script to detect when a user submits a prompt, enabling the working indicator to activate immediately rather than waiting for agent activity.


191-209: LGTM! Safe cleanup with signature verification.

The cleanup function correctly:

  1. Checks for file existence before reading
  2. Verifies the signature to avoid deleting user plugins
  3. Catches and logs errors without crashing
  4. Uses informative logging

143-148: Verify that the templates/ directory exists and is properly bundled with the application.

The OPENCODE_PLUGIN_TEMPLATE_PATH resolves to path.join(__dirname, "../../templates/opencode-plugin-template.txt"). In production builds, __dirname points to the directory of the bundled main process. Ensure the template file is either:

  • Present in the expected location relative to the bundled output, or
  • Configured to be copied as an asset during the build process
apps/desktop/src/main/lib/agent-setup/index.ts (1)

38-39: LGTM! Correct cleanup ordering.

Placing the global plugin cleanup after directory creation but before creating new plugins ensures the migration from global to environment-specific paths is clean.

apps/desktop/src/main/lib/agent-setup/notify-hook.ts (1)

9-24: LGTM! Consistent template-based generation.

The template approach matches the pattern used in agent-wrappers.ts for the OpenCode plugin. The placeholder replacement for {{MARKER}} and {{DEFAULT_PORT}} is straightforward and correct.

apps/desktop/src/main/lib/agent-setup/templates/notify-hook.template.sh (2)

1-3: Shellcheck errors are false positives for template placeholders.

The Shellcheck errors on line 2 (SC1054, SC1073, SC1083) are expected because {{MARKER}} is a template placeholder that will be replaced with # Superset agent notification hook at generation time. The resulting script will be valid.


15-24: Robust event type extraction with good fallback handling.

The regex patterns handle optional whitespace around JSON colons, and the Codex-specific mapping (agent-turn-completeStop) is correct. The decision to not default to "Stop" on parse failure (lines 26-28) prevents false completion notifications.

apps/desktop/src/main/lib/agent-setup/templates/opencode-plugin.template.js (3)

25-28: LGTM! Singleton pattern prevents duplicate registration.

Using a global flag (__supersetOpencodeNotifyPluginV8) ensures the plugin only registers once even if the module is loaded multiple times. The version suffix allows future versions to re-register if needed.


96-143: Well-designed state machine for session lifecycle.

The handleBusy and handleStop functions correctly implement:

  • Root session tracking (first busy session becomes root)
  • State transitions with deduplication (idle→busy for Start, busy→idle for Stop)
  • Reset of rootSessionID after Stop to allow tracking new conversations

The stopSent flag prevents duplicate Stop notifications within a busy period.


145-186: LGTM! Comprehensive event handling with backward compatibility.

The event handler covers:

  • Modern session.status with busy/idle subtypes
  • Legacy session.busy/session.idle events
  • Error handling via session.error
  • Permission requests via permission.ask

Child session filtering at line 151 ensures subagent events don't trigger notifications.

apps/desktop/src/shared/constants.ts (1)

43-43: LGTM!

The rename from AGENT_COMPLETE to AGENT_LIFECYCLE accurately reflects the broader lifecycle event model (idle → working → permission → review) introduced in this PR.

apps/desktop/src/shared/tabs-types.ts (4)

13-34: Well-designed type system with proper documentation.

The PaneStatus hierarchy and STATUS_PRIORITY constant provide a clear, type-safe foundation for status aggregation. Good use of satisfies to enforce the Record<PaneStatus, number> constraint while preserving literal types.


40-48: LGTM!

The pickHigherStatus function correctly implements priority comparison with proper handling of undefined values.


54-69: Efficient implementation with early exit optimization.

The getHighestPriorityStatus function correctly aggregates statuses with an early exit for maximum priority ("permission"). Returning null for all-idle scenarios is semantically appropriate for UI components.


114-114: LGTM!

The migration from needsAttention?: boolean to status?: PaneStatus provides richer state modeling. The migration path (version 2) in the store handles the breaking change appropriately.

apps/desktop/src/renderer/stores/tabs/types.ts (1)

3-12: LGTM!

The type imports and API method updates (setPaneStatus, clearWorkspaceAttentionStatus) are consistent with the new status-based model and maintain clear, descriptive naming.

Also applies to: 82-83

apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (3)

318-325: LGTM!

Correctly uses getState() to access current pane status within the effect, avoiding stale closure issues.


387-394: LGTM!

The Ctrl+C handler correctly uses getState() to read and update pane status, clearing transient indicators when the user interrupts agent execution.


395-403: LGTM!

The Escape handler correctly uses getState() to read and update pane status, supporting agent stop operations (e.g., Claude Code "stop generating").

apps/desktop/src/renderer/screens/main/components/StatusIndicator/StatusIndicator.tsx (3)

7-30: Excellent use of lookup object pattern.

The STATUS_CONFIG lookup follows the coding guideline to use lookup objects instead of repeated conditionals, and the satisfies constraint ensures type safety while preserving literal types.


43-64: LGTM!

The StatusIndicator component cleanly implements the three-state visual system (red pulsing, amber pulsing, green static) with proper conditional rendering and accessibility structure.


67-69: LGTM!

The getStatusTooltip helper provides a clean public API for tooltip integration while maintaining the single source of truth in STATUS_CONFIG.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupStrip.tsx (2)

78-89: LGTM! Clean status aggregation logic.

The tabStatusMap correctly aggregates pane statuses per tab using the shared pickHigherStatus utility. The logic properly filters out idle statuses and only stores active statuses in the map.


140-146: LGTM!

The status prop correctly falls back to null when no aggregated status exists for the tab, matching the expected PaneStatus | null type in GroupItem.

apps/desktop/src/renderer/stores/tabs/useAgentHookListener.ts (3)

10-34: Excellent documentation of status mapping and known limitations.

The comprehensive JSDoc clearly documents the status transitions and external limitations (Ctrl+C, permission denied, tool failures). This will help maintainers understand the expected behavior and edge cases.


68-73: LGTM!

The Start and PermissionRequest event handling correctly sets the appropriate statuses, with permission properly overriding working as per the priority system.


74-98: LGTM! Well-reasoned Stop event handling.

The logic correctly distinguishes between active and inactive panes—setting idle when the user is already watching (avoiding unnecessary indicators) and review when they need to be notified. The debug logging will be valuable for troubleshooting.

apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx (3)

115-132: Nice optimization using a generator to avoid array allocation.

The generator pattern in paneStatuses() is a good performance optimization, avoiding intermediate array creation when computing the aggregate status. The memoization dependencies are correct.


134-141: LGTM!

The click handler correctly clears workspace attention status when the user selects a workspace, ensuring indicators are acknowledged.


228-239: LGTM! Correct priority ordering for indicators.

Status indicators correctly take precedence over unread dots, ensuring users see the most important information first.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/GroupStrip/GroupItem.tsx (1)

40-44: LGTM!

The StatusIndicator is correctly positioned within the tab button and conditionally rendered based on status.

apps/desktop/src/renderer/stores/tabs/store.ts (4)

202-230: LGTM! Well-designed status clearing on tab activation.

The status transition logic is correct:

  • reviewidle: User acknowledged completion
  • permissionworking: Assume permission granted
  • working persists: Only cleared by Stop event

The hasChanges gate prevents unnecessary state updates.


538-549: LGTM!

The guard correctly prevents corrupting the panes map when called with an unknown paneId. This defensive approach protects against race conditions where a pane might be removed between the lifecycle event and status update.


799-828: LGTM! Robust migration and merge logic.

The migration correctly converts legacy needsAttention to the new status field. The merge logic appropriately resets transient states (working, permission) on startup while preserving review so users don't miss completion notifications.


525-536: LGTM! Consistent guard pattern across pane mutations.

The guards in markPaneAsUsed, updatePaneCwd, and clearPaneInitialData consistently protect against corrupting the panes map when called with unknown pane IDs.

Also applies to: 585-617

apps/desktop/src/main/lib/notifications/server.ts (4)

40-68: LGTM! Well-documented event normalization.

The mapEventType function correctly normalizes various event type strings to canonical lifecycle events. The explicit null return for unknown events ensures forward compatibility, and the documentation clearly explains the design decision to not default missing eventType to "Stop".


78-116: LGTM! Robust pane resolution with fallbacks.

The resolution logic correctly validates that paneId exists in state before returning it, preventing stale references from corrupting the store. The fallback chain (paneId → tabId → workspaceId) provides resilience when direct pane IDs aren't available.


119-181: LGTM! Well-designed non-blocking endpoint.

The endpoint handler correctly:

  • Logs environment mismatches without blocking the agent
  • Handles unknown event types gracefully for forward compatibility
  • Includes resolved paneId in the response for debugging
  • Emits to AGENT_LIFECYCLE with the normalized event type

10-14: LGTM!

Re-exporting types from the shared module maintains backward compatibility for existing consumers while centralizing type definitions.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (1)

228-238: Consider extracting the transient status clearing pattern.

The pattern of checking and clearing transient statuses (working | permissionidle) is repeated four times throughout the file. Extracting this into a helper would reduce duplication and make the intent clearer.

🔎 Proposed helper function

Add at module level:

/**
 * Clear transient pane status (working/permission) if currently set.
 * Review status persists (user needs to acknowledge completed work).
 */
function clearTransientPaneStatus(paneId: string) {
  const currentPane = useTabsStore.getState().panes[paneId];
  if (
    currentPane?.status === "working" ||
    currentPane?.status === "permission"
  ) {
    useTabsStore.getState().setPaneStatus(paneId, "idle");
  }
}

Then replace the four duplicated blocks with:

-const currentPane = useTabsStore.getState().panes[paneId];
-if (
-  currentPane?.status === "working" ||
-  currentPane?.status === "permission"
-) {
-  setPaneStatus(paneId, "idle");
-}
+clearTransientPaneStatus(paneId);

Also applies to: 323-330, 392-408

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c2a9a3 and f133d5a.

📒 Files selected for processing (2)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/tsconfig.json
🧰 Additional context used
📓 Path-based instructions (6)
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc
Use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.
For tRPC subscriptions with trpc-electron, ALWAYS use the observable pattern from @trpc/server/observable instead of async generators, as the library explicitly checks isObservable(result) and throws an error otherwise

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use object parameters for functions with 2+ parameters instead of positional arguments
Functions with 2+ parameters should accept a single params object with named properties for self-documentation and extensibility
Use prefixed console logging with context pattern: [domain/operation] message
Extract magic numbers and hardcoded values to named constants at module top
Use lookup objects/maps instead of repeated if (type === ...) conditionals
Avoid using any type - maintain type safety in TypeScript code
Never swallow errors silently - at minimum log them with context
Import from concrete files directly when possible - avoid barrel file abuse that creates circular dependencies
Avoid deep nesting (4+ levels) - use early returns, extract functions, and invert conditions
Use named properties in options objects instead of boolean parameters to avoid boolean blindness

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
apps/desktop/src/renderer/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never import Node.js modules (fs, path, os, net) in renderer process or shared code - they are externalized for browser compatibility

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

One component per file - do not create multi-component files

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
apps/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Drizzle ORM for all database operations - never use raw SQL

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Biome for formatting and linting - run at root level with bun run lint:fix or biome check --write

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
🧠 Learnings (7)
📚 Learning: 2025-12-21T04:39:28.543Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-12-21T04:39:28.543Z
Learning: Applies to apps/desktop/**/*.{ts,tsx} : Use alias as defined in `tsconfig.json` when possible

Applied to files:

  • apps/desktop/tsconfig.json
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/desktop/src/renderer/**/*.{ts,tsx} : Never import Node.js modules (fs, path, os, net) in renderer process or shared code - they are externalized for browser compatibility

Applied to files:

  • apps/desktop/tsconfig.json
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/*/src/components/{ui,ai-elements,react-flow}/*.tsx : Use kebab-case single files for shadcn/ui components (e.g., button.tsx, base-node.tsx) in src/components/ui/, src/components/ai-elements, and src/components/react-flow/

Applied to files:

  • apps/desktop/tsconfig.json
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to **/*.{ts,tsx} : Avoid using `any` type - maintain type safety in TypeScript code

Applied to files:

  • apps/desktop/tsconfig.json
📚 Learning: 2025-12-21T04:39:28.543Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-12-21T04:39:28.543Z
Learning: Applies to apps/desktop/**/*.{ts,tsx} : For Electron interprocess communication, ALWAYS use tRPC as defined in `src/lib/trpc`

Applied to files:

  • apps/desktop/tsconfig.json
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/desktop/src/lib/*.ts : Never import Node.js modules in shared code like electron-router-dom.ts - it runs in both main and renderer processes

Applied to files:

  • apps/desktop/tsconfig.json
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/desktop/src/lib/electron-router-dom.ts : Do not import Node.js modules like node:path or dotenv in electron-router-dom.ts and similar shared files - they run in both main and renderer processes

Applied to files:

  • apps/desktop/tsconfig.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Deploy Docs
  • GitHub Check: Deploy Admin
  • GitHub Check: Deploy API
  • GitHub Check: Deploy Marketing
  • GitHub Check: Deploy Web
  • GitHub Check: Build
🔇 Additional comments (2)
apps/desktop/tsconfig.json (1)

17-23: LGTM! Appropriate exclusion of template files.

The addition of "src/**/templates/**" correctly excludes the runtime template assets (.template.js, .template.sh) introduced in this PR from TypeScript compilation. These files are copied/used as-is at runtime and should not be type-checked or compiled.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (1)

228-238: Race condition fix confirmed—correctly uses fresh state.

The previous race condition has been properly addressed. Line 232 now reads useTabsStore.getState().panes[paneId] to get fresh pane status at event time instead of relying on the stale closure. This ensures the status check reflects the current state when the terminal exit event arrives.

@Kitenite Kitenite merged commit 9a2417a into main Jan 6, 2026
12 checks passed
@Kitenite Kitenite deleted the working-indicator branch January 6, 2026 20:29
andreasasprou added a commit that referenced this pull request Jan 7, 2026
PR #588 replaced needsAttention with PaneStatus (idle/working/permission/review).
Remove the obsolete setNeedsAttention method and update plan doc references.
andreasasprou added a commit that referenced this pull request Jan 9, 2026
PR #588 replaced needsAttention with PaneStatus (idle/working/permission/review).
Remove the obsolete setNeedsAttention method and update plan doc references.
andreasasprou added a commit that referenced this pull request Jan 13, 2026
PR #588 replaced needsAttention with PaneStatus (idle/working/permission/review).
Remove the obsolete setNeedsAttention method and update plan doc references.
AviPeltz pushed a commit that referenced this pull request Jan 15, 2026
* feat(desktop): terminal persistence via daemon process

Add terminal session persistence using a background daemon process that
survives app restarts. Terminals can now be resumed with full scrollback
and TUI state (like Claude Code) exactly where they left off.

Key changes:
- New terminal host daemon with per-session PTY subprocesses
- DaemonTerminalManager as drop-in replacement for TerminalManager
- Settings toggle for terminal persistence (requires app restart)
- Schema migration for terminal_persistence setting
- Smooth workspace/tab switching via CSS visibility (avoids remount)

* fix(desktop): add @xterm/headless dependency and fix type error

- Add missing @xterm/headless package to desktop dependencies
- Use @ts-expect-error for known xterm addon type mismatch
  (SerializeAddon types expect @xterm/xterm but works with @xterm/headless)

* fix(desktop): address CodeRabbit review feedback

- Await async handler in terminal-host/index.ts to prevent unhandled promise rejections
- Track session cleanup timeouts in daemon-manager.ts and clear on dispose to prevent memory leaks
- Move headless-emulator.test.ts to co-locate with implementation (from __tests__/ subfolder)
- Fix timeout race conditions in test files by tracking settlement state and clearing timeouts

* fix(desktop): fix CI errors after test file move

- Update relative imports in headless-emulator.test.ts after moving from __tests__/
- Externalize @xterm/* packages in Vite config (incorrect package exports for bundlers)

* refactor(desktop): centralize DEFAULT_TERMINAL_PERSISTENCE constant

Extract the terminal persistence default value (false) into a shared
constant to avoid duplicating magic values across the codebase.

* refactor(desktop): address PR review comments

- Add SUPERSET_TERMINAL_DEBUG env var for conditional debug logging
- Wrap verbose console.log statements in debug checks (terminal router
  and terminal host client)
- Make sendRequest<T> generic to eliminate type casts in public API
- Use switch statement for event payload narrowing
- Remove unused imports (IpcSuccessResponse, IpcErrorResponse)

* fix(desktop): align @xterm/headless version with @xterm/xterm

Downgrade @xterm/headless from ^6.0.0 to ^5.5.0 to match @xterm/xterm
version. Mismatched major versions can cause runtime failures.

Also removes now-unnecessary @ts-expect-error directive since matching
versions resolve the type compatibility issue.

* fix(desktop): address code review feedback for terminal persistence

P0 Fixes:
- Use getActiveTerminalManager() instead of direct terminalManager import
  in workspaces.ts, projects.ts, and main/windows/main.ts
- Add missing await for async getSessionCountByWorkspaceId() call
- Update reconcileOnStartup() to preserve sessions for true app restart
  persistence, only killing orphaned sessions (deleted workspaces)

P1 Fixes:
- Call shutdownOrphanedDaemon() on startup when persistence is disabled
- Fix error code mismatch: QUEUE_FULL -> WRITE_QUEUE_FULL in client.ts

P2 Fixes:
- Only keep terminal-containing tabs mounted when persistence enabled,
  non-terminal tabs use normal unmount behavior to save memory

Additional Fixes (from runtime testing):
- Fix resize race condition: forward resize to daemon regardless of
  local session cache state (handles startup race)
- Add safety checks in initHistoryWriter: validate scrollback is string
  and cap at 512KB to prevent RangeError: Invalid array length

* fix(desktop): guard pane updates against deleted panes

When async processes like Claude Code still hold pane references
after the terminal is closed, calling setNeedsAttention (or similar)
would create an undefined entry in the panes record. This caused
GroupStrip to crash when iterating Object.values(panes).

Now all pane update functions check if the pane exists first and
return early (no-op) if it doesn't, preventing undefined entries.

* fix(desktop): split terminal host control/stream sockets

Prevents createOrAttach timeouts by removing head-of-line blocking when terminal output backpressures.

Adds protocol v2 hello with clientId/role, pairs control+stream sockets, and adds a backpressure isolation integration test.

* chore(desktop): biome format + archive execplan

Formats a few files to satisfy biome format checks and moves the terminal host dual-socket ExecPlan to apps/desktop/plans/done/.

* fix(desktop): spawn daemon when token missing

Read terminal-host auth token after ensuring a daemon exists; if token is missing with a live socket, restart the daemon to re-create a coherent socket+token pair.

* fix(desktop): harden terminal persistence data perms

Ensure ~/.superset* is created/repair-chmodded to 0700 and history/log files are written with 0600 where applicable.

Also closes the daemon.log fd in the parent after spawning the daemon.

* fix(desktop): address terminal persistence review feedback

- Keep terminal tabs mounted even when switching to empty workspaces

- Gate noisy logs behind SUPERSET_TERMINAL_DEBUG

- Use TRPCError for settings validation

- Fix history reinit sizing + avoid silent catch blocks

- Gate PTY subprocess spawn logging

* fix(desktop): bundle @xterm packages for terminal-host daemon

The daemon runs as standalone Node.js outside app.asar and needs
@xterm/headless and @xterm/addon-serialize bundled to function.

* wip: dx hardening plan

* fix(desktop): remove obsolete setNeedsAttention after rebase

PR #588 replaced needsAttention with PaneStatus (idle/working/permission/review).
Remove the obsolete setNeedsAttention method and update plan doc references.

* docs(desktop): update terminal persistence exec plan

* fix(desktop): harden terminal persistence DX

* fix(desktop): prevent history init buffer loop

* fix(desktop): enable terminal stream when snapshot empty

* fix(desktop): prevent scheduler deadlock on React StrictMode unmount

React StrictMode simulates mount → unmount → mount cycles. The terminal
attach scheduler was deadlocking because:

1. First mount starts a task (inFlight++)
2. Unmount cancels the task but it's still executing
3. Second mount queues a new task
4. tRPC callbacks for unmounted components don't fire reliably
5. done() never gets called → inFlight stays stuck at MAX_CONCURRENT

Fix: Track running tasks per paneId and immediately decrement inFlight
when canceling a running task. Also add optional debug logging (enable
via localStorage.setItem('SUPERSET_TERMINAL_DEBUG', '1')).

* fix(desktop): address PR review blocking issues

- P0: Fix attach-scheduler race condition where inFlight counter could be
  double-decremented when cancel() and done() both fire for the same task.
  Added `released` flag to ensure idempotent completion.

- P1: Fix sendRequestOnStream NDJSON parsing bug that dropped messages
  arriving in the same TCP read as the hello response. Now feeds remainder
  data to streamParser after parsing first response.

- P1: Fix maybeApplyInitialState catch block that would wedge terminal on
  restoration error. Now fail-open by setting isStreamReady and flushing
  pending events even on error.

- P2: Fix coldRestoreState memory leak by cleaning up on unmount. Previously
  scrollback (potentially MBs per pane) was only cleared on "Start Shell"
  click, not on component unmount.

* fix(desktop): address oracle feedback on PR fixes

- Move coldRestoreState cleanup into detachTimeout to preserve StrictMode
  unmount/remount semantics. The module-level Map is specifically designed
  to survive quick remounts, so deleting immediately on unmount was wrong.

- Harden attach-scheduler cancel path: when a running task is canceled,
  re-queue any waiting task for the same paneId. This mirrors the done()
  behavior and protects against the "done never fires" scenario.

* docs(desktop): document ordering assumption in sendRequestOnStream

Per oracle review suggestion: add JSDoc comment explaining that the daemon's
hello handler guarantees response is first frame. Documents when this assumption
would need to change (if daemon ever emits events before hello response).

* fix(desktop): resolve type errors after rebase onto main

- Add stub terminal-history module (real impl in Phase 4)
- Fix port-manager: add checkOutputForHint method, await async calls
- Fix TabsContent: add panes store selector
- Fix TabView: add Pane type import
- Fix daemon-manager: handle null scrollback from stub reader

* fix(desktop): implement daemon signal() support for SIGINT/SIGTERM

The daemon's signal() method was a no-op, which meant Ctrl+C through
the daemon pathway would silently fail. This adds full signal support:

- Add SignalRequest type and signal to RequestTypeMap
- Add Signal IPC frame type (6) distinct from Kill
- Implement handleSignal in pty-subprocess without kill escalation
- Add sendSignal chain through session → terminal-host → daemon → client
- Update daemon-manager to use client.signal() instead of no-op

Unlike kill(), signal() does not mark the session as terminating and
does not escalate to SIGKILL, allowing the process to continue running.

* docs(desktop): add terminal host event semantics documentation

Documents the event delivery model for the daemon protocol:
- Event types (data, exit, error)
- Dual-socket model (control vs stream)
- At-most-once delivery semantics (no durability/retries)
- In-order guarantees within sessions
- Multi-level backpressure handling
- Error codes and race condition handling

* feat(desktop): implement cold restore terminal history persistence

Replaces the stub terminal-history.ts with a working implementation
for Phase 4 of terminal persistence. Enables terminal recovery after
app/system restarts when the daemon is not running.

Storage format:
- scrollback.bin: Raw PTY output (append-only)
- meta.json: Session metadata (cols, rows, cwd, timestamps)

Cold restore detection:
- meta.json without endedAt → unclean shutdown → can restore
- meta.json with endedAt → clean shutdown → no restore

HistoryWriter API:
- init(initialScrollback?) - create directory and files
- write(data) - append PTY output
- flush() - flush pending writes
- close(exitCode?) - write endedAt to meta.json
- reinitialize() - reset for clear scrollback
- deleteHistory() - remove all files

HistoryReader API:
- exists() - check if history available
- readMetadata() - get cols/rows/cwd/endedAt
- readScrollback() - get terminal content
- cleanup() - delete history files

* feat(desktop): add telemetry for terminal persistence events

Adds tracking for key terminal persistence lifecycle events:
- terminal_cold_restored: Triggered when recovering terminal after reboot
  with scrollback_bytes to measure restoration payload size
- terminal_warm_attached: Triggered when reconnecting to existing daemon
  session with snapshot_bytes for payload metrics
- terminal_daemon_disconnected: Triggered on daemon connection loss with
  active_session_count for impact assessment

* fix(desktop): trigger cold restore on daemon session loss

When daemon restarts and loses sessions, "Session not found" errors
were treated as non-fatal (just showed toast). This prevented the
retry UI from appearing and cold restore from triggering.

Changes:
- Promote "Session not found" WRITE_FAILED errors to connection error
  so retry UI appears instead of endless toast spam
- Handle isColdRestore in handleRetryConnection so clicking retry
  can trigger cold restore if disk history is available
- Update both event handler locations for consistency

Now when daemon dies and user clicks retry, cold restore kicks in
if history exists on disk (meta.json without endedAt).

* fix(desktop): suppress toast when showing retry UI for session loss

When daemon restarts and loses terminal sessions, show only the retry UI
without also showing a toast notification. This prevents confusing UX
where both a toast and the retry overlay appear simultaneously.

* fix(desktop): suppress toast for transient PTY not spawned errors

During daemon recovery, writes may arrive before the PTY subprocess
is fully initialized. Treat "PTY not spawned" as a transient error
that doesn't need a toast notification - just log to terminal.

* fix(desktop): clear connection error on successful initial attach

When daemon restarts and component remounts, the background
createOrAttach may succeed while the error overlay is still visible.
Clear connectionError on success to dismiss the overlay automatically.

* fix(desktop): trigger cold restore on daemon session loss

When "Session not found" error occurs, clear the stale cache entry
so the next createOrAttach properly checks disk history and triggers
cold restore instead of creating a new session.

* fix(desktop): re-focus terminal after successful retry connection

After clicking "Retry Connection" and the connection succeeds,
re-focus the terminal so keyboard input works immediately.
Skip focus for cold restore since user needs to click overlay button.

* fix(desktop): cold restore for TUI apps with empty scrollback

Two fixes:
1. Check rawScrollback === null instead of !rawScrollback. TUI apps
   in alternate screen may have empty normal buffer, which is still
   valid for cold restore (empty string is truthy check fix).

2. Use fresh xterm ref in handleRetryConnection onSuccess callback
   to handle potential component remount during async operation.

* fix(desktop): focus terminal after clicking Start Shell

After cold restore, clicking "Start Shell" creates a new session but
wasn't focusing the terminal, causing keystrokes to go elsewhere.

* fix(desktop): keep terminal stream alive on exit

* chore(desktop): fix biome check

* chore(desktop): fix lint warnings

* fix(desktop): address persistence review blockers

* fix(desktop): harden terminal history caps

* docs(desktop): add terminal runtime abstraction plan

* docs(desktop): expand plan for Terminal.tsx decomposition

* docs(desktop): add target architecture snippets to plan

* docs(desktop): refine terminal runtime abstraction plan

* docs(desktop): add remote runner notes to plan

* docs(desktop): align terminal runtime plan with cloud provider direction

* docs(desktop): add terminal runtime architecture review packet

* docs(desktop): narrow changes router reference list

* docs(desktop): incorporate architecture feedback into runtime rewrite plan

* refactor(desktop): introduce WorkspaceRuntime abstraction

Adds a provider-neutral runtime layer that abstracts terminal backend selection:

- WorkspaceRuntimeRegistry: process-scoped registry for runtime selection
- LocalTerminalRuntime: adapts TerminalManager/DaemonTerminalManager
- Capability-based checks: uses `terminal.management !== null` instead of
  `instanceof DaemonTerminalManager`

Key changes:
- New workspace-runtime module with types, registry, and local implementation
- tRPC terminal router migrated to use registry pattern
- All call sites updated to use getForWorkspaceId when workspaceId is in-hand
- Regression tests for capability presence and stream contract

This foundation enables future cloud workspace providers without spreading
backend-specific branching throughout the codebase.

* WIP: route-based settings/dashboard structure alignment with upstream

- Add _dashboard route group with workspace/tasks pages
- Rebuild settings routing/layout/sidebar under routes
- Port settings pages: account, appearance, keyboard, presets, team, ringtones, project, workspace
- Add terminal settings page at /settings/terminal
- Update navigation components to use router instead of app-state
- Remove obsolete route pages that conflict with upstream _dashboard structure

Still needs:
- Full merge with origin/main
- Complete app-state removal
- Terminal backend conflict resolution

* fix(desktop): improve terminal kill-all reliability and UI feedback

Fixes two bugs in the terminal settings page:

1. UI not refreshing after kill: Added optimistic UI clearing
   - Clear session list immediately on click for instant feedback
   - Rollback on error, delayed refetch on success

2. Sessions surviving kill: Added wait/verify loop in backend
   - Poll daemon up to 10x100ms until sessions are actually dead
   - Return accurate killedCount and remainingCount
   - Show warning toast if some sessions survive

Also adds diagnostic logging for troubleshooting kill operations.

* fix(desktop): keep killed terminals dead

* fix(desktop): disable terminal session actions when none

* fix(desktop): memoize terminal session lists

* remove old code

* fix(desktop): log best-effort failures in terminal utils

* fix: harden terminal persistence paths and logs

* chore: format terminal router and client
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants