Skip to content

feat(desktop): add 3-color workspace status indicators#588

Merged
Kitenite merged 24 commits intomainfrom
working-indicator
Jan 6, 2026
Merged

feat(desktop): add 3-color workspace status indicators#588
Kitenite merged 24 commits intomainfrom
working-indicator

Conversation

@andreasasprou
Copy link
Copy Markdown
Contributor

@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
Copy Markdown
Contributor

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
Copy Markdown
Contributor

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
When user presses ESC (Claude Code "stop generating") or Ctrl+C while
an agent is working, clear the status immediately. This handles the
case where the agent is interrupted but the terminal is still running,
so the terminal exit handler doesn't trigger.

The oracle recommended this approach as the most robust solution since
ESC/Ctrl+C is an explicit user intent to stop the agent.
OpenCode doesn't emit a separate 'session.busy' event - the busy state
is communicated via 'session.status' with status.type === "busy".

The previous fix incorrectly assumed separate events existed. Now we:
- Listen to session.status for both busy and idle transitions
- Keep session.idle as a deprecated backup for idle detection
- Bump plugin version to v6
Bumped to v7. Added console.log statements to understand what events
OpenCode emits when transitioning between busy and idle states.
Will be removed once the issue is diagnosed.
… flow

Adding temporary logs to trace:
1. Server: notification received
2. Renderer: event received by listener
3. Stop handler: isAlreadyActive check result

Will help diagnose why green indicator doesn't show after OpenCode completion.
Added shared/debug.ts with debugLog() function that only logs when
SUPERSET_DEBUG=1 is set. Replaced hardcoded console.log statements
in notification server and agent hook listener with debugLog calls.

Enable debug mode:
  SUPERSET_DEBUG=1 bun run desktop

Or add to .env:
  SUPERSET_DEBUG=1
Implements Oracle's recommendations for robustness:

1. **Safe defaults on error**: isChildSession now returns true (skip) on
   error instead of false. This prevents race conditions where failed
   lookups cause child session events to be treated as root events.

2. **Session-scoping**: Tracks the root sessionID when first busy event
   arrives. Subsequent events from other sessions are ignored, preventing
   child session events from interfering with root session state.

3. **State deduplication**: Tracks currentState (idle/busy) and stopSent
   flag. Only sends Start on idle→busy transition, only sends Stop once
   per busy period. Prevents duplicate/out-of-order notifications.

4. **Caching**: Caches isChildSession lookups to avoid repeated async
   calls for the same session.

5. **Debug logging**: When SUPERSET_DEBUG=1 is set, logs all events,
   state transitions, and notification attempts for troubleshooting.

These changes address intermittent failures where the green "finished"
indicator wouldn't appear after OpenCode completion.
P0: Remove thoughts/ directory and add to .gitignore

P1: Memoize workspacePaneIds in WorkspaceListItem
- workspacePaneIds now properly memoized with useMemo
- Uses direct pane lookup by ID for O(workspacePanes) instead of O(totalPanes)
- Prevents sidebar jank with many workspaces/panes

P1: Add session.busy fallback for OpenCode version compatibility
- Now handles both session.status with nested type AND separate
  session.busy/session.idle events for different OpenCode versions

P2: Move NotificationIds type to shared/
- Prevents renderer importing from main/ (boundary violation)
- Re-exports from server.ts for backwards compatibility
When OpenCode starts a new conversation/session within the same process,
we need to be able to track it. Previously we stayed locked to the first
session's ID forever. Now we reset rootSessionID after sending Stop so
the next busy event can establish a new root session.
Extract duplicated priority order (permission > working > review)
into shared utilities:

- Add STATUS_PRIORITY constant as single source of truth
- Add pickHigherStatus() for map aggregation patterns
- Add getHighestPriorityStatus() for iterable scanning
- Use generators to avoid array allocations

Simplifies WorkspaceListItem (22 → 9 lines) and GroupStrip (18 → 9 lines)
while making priority order easily testable and future-proof.
* and replacing the notify path placeholder.
*/
export function getOpenCodePluginContent(notifyPath: string): string {
// Build "${" via char codes to avoid JS template literal interpolation in generated code
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@andreasasprou fyi i moved this into a js file and uses template replace instead. we get the benefit of linting and making the script more maintainable

Copy link
Copy Markdown
Contributor

@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
Copy Markdown
Contributor

@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.

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.

2 participants