Skip to content

Ghostty Web#2115

Closed
Kitenite wants to merge 10 commits into
mainfrom
Kitenite/innovative-wholesaler
Closed

Ghostty Web#2115
Kitenite wants to merge 10 commits into
mainfrom
Kitenite/innovative-wholesaler

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Mar 6, 2026


Summary by cubic

Switched the terminal to Ghostty Web for faster rendering and lower latency. Bundled a Nerd Font and tightened event handling so restores, focus, and desktop notifications are more reliable.

  • New Features

    • Integrated ghostty-web runtime/renderers; removed xterm add-ons/CSS and updated Fit/resize logic.
    • Bundled JetBrainsMono Nerd Font (“Superset Terminal Mono”) with icon fallback helpers; preview shows glyphs; tests added.
    • Added a lightweight terminal search adapter and buffered writes with PTY-first resize; keyed Terminal by paneId for clean mounts.
    • Forwarded app hotkeys via a custom event and improved click-to-cursor/focus behavior.
  • Bug Fixes

    • Added Claude Notification hooks; server maps lower-case subtypes and falls back to notificationType; notify script robustly parses JSON.
    • Propagated sessionGeneration through IPC/TRPC/streams and restore; ignore stale events until attach.
    • Fixed ScrollToBottom detection across renderers.
    • Scoped tab selection to the active workspace with shallow selectors to reduce re-renders.

Written for commit 45ec31b. Summary will update on new commits.

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced terminal font rendering with improved fallback support and dynamic font loading
    • Added notification hooks for Claude integration with better event type mapping
    • Improved terminal search capabilities
  • Bug Fixes

    • Fixed text selection behavior in input and editable elements
    • Improved terminal focus and input handling for better responsiveness
  • Performance

    • Optimized terminal write performance through data buffering
    • Enhanced terminal lifecycle management and resource cleanup

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 6, 2026

📝 Walkthrough

Walkthrough

This PR transitions the terminal implementation from xterm.js with multiple addons to ghostty-web, introduces Claude notification hook support in the agent setup with event type mapping, and refactors terminal font management, search handling, and lifecycle coordination. Multiple terminal utilities are updated to support the new architecture.

Changes

Cohort / File(s) Summary
Dependencies & Configuration
apps/desktop/package.json
Removed xterm addon packages (clipboard, fit, image, ligatures, search, unicode11, webgl); added ghostty-web dependency.
Claude Notification System
apps/desktop/src/main/lib/agent-setup/agent-wrappers-claude-codex-opencode.ts, apps/desktop/src/main/lib/agent-setup/agent-wrappers.test.ts
Added Claude notification hook definitions (idle_prompt, permission_prompt, elicitation_dialog) with command hooks pointing to notify.sh; added test coverage for new hook mappings.
Notification Event Mapping
apps/desktop/src/main/lib/notifications/map-event-type.ts, apps/desktop/src/main/lib/notifications/server.ts, apps/desktop/src/main/lib/notifications/server.test.ts, apps/desktop/src/main/lib/agent-setup/templates/notify-hook.template.sh
Updated event type normalization to handle notification subtypes (permission_prompt, elicitation_dialog map to PermissionRequest; idle_prompt, auth_success map to Stop); added notificationType extraction and fallback logic in server and hook template.
Terminal Search Refactor
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSearch/terminal-search-adapter.ts, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSearch/TerminalSearch.tsx
Replaced SearchAddon with custom TerminalSearchAdapter implementation; supports findNext, findPrevious, and clearDecorations with regex and case-sensitive options.
Terminal Core Refactor – Library Migration
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/config.ts
Migrated from xterm.js with GPU rendering to ghostty-web; removed addon loading, replaced sync resize with async resizeAsync, integrated ghostty runtime lifecycle (isRendererReady, ensureGhosttyReady); updated TERMINAL_OPTIONS to remove legacy xterm properties.
Terminal Font Management
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/font-family.ts, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/font-family.test.ts
Added comprehensive font resolution utilities: CSS font-family parsing, generic font family detection, browser font availability checking, Nerd Font variant handling, and fallback to terminal icon font; includes unit tests for formatting and deduplication.
Terminal Utilities & Helpers
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/utils.ts, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/ghostty-runtime.ts, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.test.ts
Added isTerminalAtBottom() for scroll state detection; added ghostty runtime readiness helpers (isGhosttyReady, ensureGhosttyReady); added focus/blur/click-to-move-cursor/resize handler utilities; expanded test coverage for keyboard, focus, and input handling.
Terminal Hooks – Lifecycle & Data Flow
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalStream.ts, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalConnection.ts, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/...
Refactored lifecycle to support isRendererReady gate, replaced SearchAddon with TerminalSearchAdapter, added schedulePtyFirstResize for coalesced PTY resizing, introduced write buffer coalescing in stream handler; added resizeAsync mutation path; removed FitAddon and didFirstRenderRef dependencies; updated focus helpers.
Terminal Suppression & Type Updates
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/suppressQueryResponses.ts, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/types.ts
Deleted suppressQueryResponses module; added TerminalResizeMutateAsync type for async resize mutations.
Workspace & Tab Management
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx, apps/desktop/src/renderer/stores/tabs/store.ts
Optimized tab selection via useCallback selector for activeTabId resolution; scoped availableTabs to current workspace; added acknowledgedStatus guard in pane focus logic to prevent redundant status updates.
Hotkey & Input Management
apps/desktop/src/renderer/stores/hotkeys/store.ts, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/ScrollToBottomButton/ScrollToBottomButton.tsx
Added forwardAppHotkeyEvent function for DOM CustomEvent-based hotkey forwarding; replaced direct scroll position computation with isTerminalAtBottom helper, removed onWriteParsed dependency.
Styling
apps/desktop/src/renderer/globals.css
Added global user-select: text styles for input, textarea, and contenteditable elements to ensure text selectability.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Terminal Client
    participant Hook as notify-hook.sh
    participant Server as Notification Server
    
    Client->>Hook: Execute hook with event data<br/>(hook_event_name, notificationType, etc.)
    Hook->>Hook: Extract fields via extract_json_field()<br/>SESSION_ID, EVENT_TYPE, NOTIFICATION_TYPE
    Hook->>Hook: Map notification subtypes<br/>(permission_prompt → PermissionRequest,<br/>idle_prompt → Stop, etc.)
    Hook->>Server: POST /dispatch with eventType<br/>and notificationType parameter
    Server->>Server: mapEventType(eventType)
    Server->>Server: If falsy, fallback to<br/>mapEventType(notificationType)
    Server->>Server: Route to appropriate<br/>notification handler
    Server-->>Hook: 200 OK
    Hook-->>Client: Hook execution complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • superset-sh/superset#588 — Modifies the same Claude hook definitions, notify-hook template, and notification event mapping code paths.
  • superset-sh/superset#117 — Updates terminal trpc integration (resize/create/attach mutations) and terminal connection types that correspond with the async resize mutation type additions here.

Poem

🐰 A terminal was worn from xterm's age,
So ghostty-web stepped onto the stage,
With fonts that resolve and notifications that chime,
Claude's hooks now listen—what perfect timing!
Search adapters dance, and lifecycle flows anew,
A rabbit hops onward—refactoring's through! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Ghostty Web' is vague and does not clearly convey the primary changes in the changeset. Use a more descriptive title like 'Replace xterm with Ghostty Web terminal renderer' to clarify the main change and help teammates understand the scope.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The PR description is comprehensive and well-structured, covering all major aspects of the changes including new features (Ghostty Web integration, Nerd Font bundling, terminal search adapter) and bug fixes (Claude notification hooks, event propagation).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch Kitenite/innovative-wholesaler

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 Mar 6, 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

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: 5

🧹 Nitpick comments (5)
apps/desktop/src/main/lib/notifications/server.test.ts (1)

46-51: Cover the raw normalized subtype forms too.

mapEventType() now accepts no-underscore variants after lowercasing (permissionprompt, elicitationdialog, idleprompt, authsuccess), but this case only exercises the underscored inputs. Add at least one mixed-case/no-underscore sample per bucket so the server-side fallback path can't regress silently.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/lib/notifications/server.test.ts` around lines 46 - 51,
The test only checks underscored Claude subtypes but mapEventType also accepts
lowercased no-underscore variants (e.g., permissionprompt, elicitationdialog,
idleprompt, authsuccess), so extend the test in server.test.ts to include at
least one no-underscore/mixed-case sample per mapping bucket to ensure the
server-side fallback path is exercised; update the "should map Claude
notification subtypes" test to call mapEventType with both underscored and
normalized forms (referencing mapEventType) and assert the same expected
outputs.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/ghostty-runtime.ts (1)

10-26: Add a small regression test for the init gate.

This module’s behavior lives in the shared module state: concurrent callers should share one init(), failures should clear the cached promise, and the next call should retry. A focused test around those three cases would make this bootstrap path much safer to change.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/ghostty-runtime.ts`
around lines 10 - 26, Add a focused regression test for the init gate: write
tests that call ensureGhosttyReady concurrently to verify multiple callers share
a single init() invocation, simulate init() rejecting to ensure
ghosttyInitPromise is cleared and ghosttyReady stays false, and then call
ensureGhosttyReady again to verify a retry invokes init() a second time;
reference ensureGhosttyReady, init, ghosttyInitPromise and ghosttyReady in the
test to assert the shared-promise behavior, failure-clearance, and retry
semantics.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.test.ts (1)

116-158: Remove the wall-clock dependency from this regression test.

The retry assertion still depends on a real-time sleep, which makes it slower and more timing-sensitive in CI. If makeScheduler() accepts injected time/timer primitives, this case can drive the retry path deterministically without waiting on wall clock time.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.test.ts`
around lines 116 - 158, The test depends on real wall-clock sleep; change it to
use deterministic timers by updating makeScheduler to accept injected time/timer
primitives (e.g., a mock setTimeout/Date.now or a clock object) and in the test
provide a fake clock so you can advance time instead of awaiting setTimeout;
specifically, call makeScheduler with the injected clock, set state.lastRunAt
relative to the fake clock, call schedule(false) and flush(), then advance the
fake clock by the remaining throttle window (e.g., 70ms) to trigger the
scheduled retry and call flush() again to assert calls === 1, referencing
makeScheduler, schedule, flush and state.lastRunAt to locate the changes.
apps/desktop/src/main/lib/agent-setup/templates/notify-hook.template.sh (1)

45-59: Avoid keeping a second notification mapping table here.

This block re-encodes part of mapEventType() in the hook, and the two tables are already different (permissionprompt/authsuccess are only handled server-side). Forward the raw hook_event_name/notificationType and let the server stay authoritative; otherwise hook debug output and final classification can drift.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/lib/agent-setup/templates/notify-hook.template.sh`
around lines 45 - 59, The hook currently re-maps notification subtypes (using
NOTIFICATION_TYPE, EVENT_TYPE and the case block after calling
extract_json_field) which duplicates and diverges from server-side
mapEventType(); remove the case mapping that overrides EVENT_TYPE (the
permission_prompt/idle_prompt branches) so the script preserves the original
EVENT_TYPE="Notification" and raw NOTIFICATION_TYPE (from extract_json_field or
matcher) and forwards hook_event_name/notificationType to the server unchanged;
keep the extraction logic (NOTIFICATION_TYPE=$(extract_json_field ...)) but do
not mutate EVENT_TYPE in this template.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts (1)

448-454: Let ResizeObserver own resize detection here.

The extra window.resize listener makes each window resize hit this path even when the terminal container size did not change, and duplicates the observer path when it did. Dropping it keeps resize work aligned with actual pane size changes.

💡 Suggested simplification
 	const resizeObserver = new ResizeObserver(handleResize);
 	resizeObserver.observe(container);
-	window.addEventListener("resize", handleResize);
 
 	return () => {
-		window.removeEventListener("resize", handleResize);
 		resizeObserver.disconnect();
 	};
 }

Based on learnings, in the superset desktop app's flex-based mosaic layout (WorkspaceView/BrowserPane), every position change of a pane also triggers a size change, so ResizeObserver alone is sufficient to keep the persistent webview bounds synchronized without needing additional scroll or window resize listeners.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts`
around lines 448 - 454, Remove the extra global window resize listener and rely
solely on ResizeObserver for resize detection: delete the
window.addEventListener("resize", handleResize) call and its corresponding
window.removeEventListener("resize", handleResize) cleanup, keeping
ResizeObserver(handleResize), resizeObserver.observe(container), and
resizeObserver.disconnect() intact so handleResize is only invoked by the
ResizeObserver.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/font-family.ts`:
- Around line 31-35: The splitFontFamilyList function currently uses
value.split(",") which breaks quoted font names like "My Font, VF"; update
splitFontFamilyList to parse the CSS font-family string by iterating characters
and splitting only on commas that are not inside single or double quotes and not
escaped — i.e., implement a small state machine tracking current quote char and
escape sequences, accumulate characters into a buffer, on an unquoted comma push
the trimmed buffer (and strip surrounding quotes) into the result array, and
after the loop push any remaining buffer; ensure handling of both single and
double quotes and backslash escapes so quoted families remain intact.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts`:
- Around line 243-249: The deferred zero-delay blur can steal focus if the pane
becomes focused before the timer fires; inside the setTimeout callback in
useTerminalLifecycle, re-check the current focus state via isFocusedRef.current
(in addition to existing isUnmounted and xtermRef.current checks) and bail out
if the pane is now focused so blurTerminalInput(xterm) is skipped; reference
blurTerminalInput, xtermRef, isUnmounted and isFocusedRef to locate and update
the timeout guard.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalStream.ts`:
- Around line 59-72: The flushPendingData function can return early without
clearing pendingWriteBufferRef, allowing buffered bytes to later flush into a
new terminal; to fix, capture the terminal instance and readiness at the top
(e.g., const activeTerminal = xtermRef.current; const ready =
isStreamReadyRef.current) and if activeTerminal is null or ready is false then
clear/discard pendingWriteBufferRef.current before returning, or alternatively
bind the pending buffer to the captured terminal instance and only flush when
the same instance is still current; update references to updateModesRef and
updateCwdRef so they run only after confirming the captured activeTerminal is
valid.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx`:
- Around line 130-140: The current useEffect swallows ensureGhosttyReady()
rejections and never updates component state, leaving the terminal blank; update
the effect to capture the error and set a renderer error state (e.g., add or use
a state setter like setRendererError / setIsRendererError) inside the .catch so
the component can render an explicit fallback and a retry action; also expose a
retry handler (e.g., retryInitialize that calls ensureGhosttyReady and updates
setIsRendererReady and clears the error) and ensure the render path checks
isRendererReady and rendererError to show either the terminal, an error message,
and a retry button.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSearch/terminal-search-adapter.ts`:
- Around line 128-133: The previous-search logic in findPrevious is reselecting
the current match because startColumn is set to the match end; change the anchor
so the search begins before the current match: when hasSameQuery && lastMatch
and direction === "previous" set startColumn = lastMatch.column - 1 (or
otherwise compare against lastMatch.column + lastMatch.length to exclude the
current span) so findPreviousInLine will skip the current match and advance to
the prior hit; update references in findPrevious/lastMatch/startColumn
accordingly.

---

Nitpick comments:
In `@apps/desktop/src/main/lib/agent-setup/templates/notify-hook.template.sh`:
- Around line 45-59: The hook currently re-maps notification subtypes (using
NOTIFICATION_TYPE, EVENT_TYPE and the case block after calling
extract_json_field) which duplicates and diverges from server-side
mapEventType(); remove the case mapping that overrides EVENT_TYPE (the
permission_prompt/idle_prompt branches) so the script preserves the original
EVENT_TYPE="Notification" and raw NOTIFICATION_TYPE (from extract_json_field or
matcher) and forwards hook_event_name/notificationType to the server unchanged;
keep the extraction logic (NOTIFICATION_TYPE=$(extract_json_field ...)) but do
not mutate EVENT_TYPE in this template.

In `@apps/desktop/src/main/lib/notifications/server.test.ts`:
- Around line 46-51: The test only checks underscored Claude subtypes but
mapEventType also accepts lowercased no-underscore variants (e.g.,
permissionprompt, elicitationdialog, idleprompt, authsuccess), so extend the
test in server.test.ts to include at least one no-underscore/mixed-case sample
per mapping bucket to ensure the server-side fallback path is exercised; update
the "should map Claude notification subtypes" test to call mapEventType with
both underscored and normalized forms (referencing mapEventType) and assert the
same expected outputs.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/ghostty-runtime.ts`:
- Around line 10-26: Add a focused regression test for the init gate: write
tests that call ensureGhosttyReady concurrently to verify multiple callers share
a single init() invocation, simulate init() rejecting to ensure
ghosttyInitPromise is cleared and ghosttyReady stays false, and then call
ensureGhosttyReady again to verify a retry invokes init() a second time;
reference ensureGhosttyReady, init, ghosttyInitPromise and ghosttyReady in the
test to assert the shared-promise behavior, failure-clearance, and retry
semantics.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts`:
- Around line 448-454: Remove the extra global window resize listener and rely
solely on ResizeObserver for resize detection: delete the
window.addEventListener("resize", handleResize) call and its corresponding
window.removeEventListener("resize", handleResize) cleanup, keeping
ResizeObserver(handleResize), resizeObserver.observe(container), and
resizeObserver.disconnect() intact so handleResize is only invoked by the
ResizeObserver.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.test.ts`:
- Around line 116-158: The test depends on real wall-clock sleep; change it to
use deterministic timers by updating makeScheduler to accept injected time/timer
primitives (e.g., a mock setTimeout/Date.now or a clock object) and in the test
provide a fake clock so you can advance time instead of awaiting setTimeout;
specifically, call makeScheduler with the injected clock, set state.lastRunAt
relative to the fake clock, call schedule(false) and flush(), then advance the
fake clock by the remaining throttle window (e.g., 70ms) to trigger the
scheduled retry and call flush() again to assert calls === 1, referencing
makeScheduler, schedule, flush and state.lastRunAt to locate the changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 58ed4b39-7e68-40ff-808e-97d6361783f5

📥 Commits

Reviewing files that changed from the base of the PR and between 99a3a3c and 512ebd5.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (32)
  • apps/desktop/package.json
  • apps/desktop/src/main/lib/agent-setup/agent-wrappers-claude-codex-opencode.ts
  • apps/desktop/src/main/lib/agent-setup/agent-wrappers.test.ts
  • apps/desktop/src/main/lib/agent-setup/templates/notify-hook.template.sh
  • apps/desktop/src/main/lib/notifications/map-event-type.ts
  • apps/desktop/src/main/lib/notifications/server.test.ts
  • apps/desktop/src/main/lib/notifications/server.ts
  • apps/desktop/src/renderer/globals.css
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/ScrollToBottomButton/ScrollToBottomButton.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSearch/TerminalSearch.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSearch/terminal-search-adapter.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/config.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/font-family.test.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/font-family.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/ghostty-runtime.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.test.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalColdRestore.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalConnection.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalHotkeys.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.test.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalRestore.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalStream.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/suppressQueryResponses.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/utils.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/index.tsx
  • apps/desktop/src/renderer/stores/hotkeys/store.ts
  • apps/desktop/src/renderer/stores/tabs/store.ts
💤 Files with no reviewable changes (2)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalHotkeys.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/suppressQueryResponses.ts

Comment on lines +31 to +35
export function splitFontFamilyList(value: string): string[] {
return value
.split(",")
.map((part) => part.trim())
.filter(Boolean);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Don't split CSS font lists on raw commas.

Quoted families like "My Font, VF", monospace are valid, but value.split(",") turns them into broken entries. That cascades into wrong formatting, bad availability probes, and fallback reordering across the rest of this module.

💡 Suggested parser fix
 export function splitFontFamilyList(value: string): string[] {
-	return value
-		.split(",")
-		.map((part) => part.trim())
-		.filter(Boolean);
+	const parts: string[] = [];
+	let current = "";
+	let quote: '"' | "'" | null = null;
+	let escaped = false;
+
+	for (const char of value) {
+		if (escaped) {
+			current += char;
+			escaped = false;
+			continue;
+		}
+
+		if (quote) {
+			current += char;
+			if (char === "\\") escaped = true;
+			else if (char === quote) quote = null;
+			continue;
+		}
+
+		if (char === '"' || char === "'") {
+			quote = char;
+			current += char;
+			continue;
+		}
+
+		if (char === ",") {
+			const part = current.trim();
+			if (part) parts.push(part);
+			current = "";
+			continue;
+		}
+
+		current += char;
+	}
+
+	const part = current.trim();
+	if (part) parts.push(part);
+	return parts;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function splitFontFamilyList(value: string): string[] {
return value
.split(",")
.map((part) => part.trim())
.filter(Boolean);
export function splitFontFamilyList(value: string): string[] {
const parts: string[] = [];
let current = "";
let quote: '"' | "'" | null = null;
let escaped = false;
for (const char of value) {
if (escaped) {
current += char;
escaped = false;
continue;
}
if (quote) {
current += char;
if (char === "\\") escaped = true;
else if (char === quote) quote = null;
continue;
}
if (char === '"' || char === "'") {
quote = char;
current += char;
continue;
}
if (char === ",") {
const part = current.trim();
if (part) parts.push(part);
current = "";
continue;
}
current += char;
}
const part = current.trim();
if (part) parts.push(part);
return parts;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/font-family.ts`
around lines 31 - 35, The splitFontFamilyList function currently uses
value.split(",") which breaks quoted font names like "My Font, VF"; update
splitFontFamilyList to parse the CSS font-family string by iterating characters
and splitting only on commas that are not inside single or double quotes and not
escaped — i.e., implement a small state machine tracking current quote char and
escape sequences, accumulate characters into a buffer, on an unquoted comma push
the trimmed buffer (and strip surrounding quotes) into the result array, and
after the loop push any remaining buffer; ensure handling of both single and
double quotes and backslash escapes so quoted families remain intact.

Comment on lines +243 to +249
} else {
// ghostty-web focuses during open(); counter that for background panes.
blurTerminalInput(xterm);
setTimeout(() => {
if (isUnmounted || xtermRef.current !== xterm) return;
blurTerminalInput(xterm);
}, 0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Re-check focus before the deferred blur.

The zero-delay blur still runs if this pane becomes focused before the timer fires, so a newly activated terminal can immediately lose focus again. Guard the timeout with the latest isFocusedRef.current.

💡 Suggested fix
 			setTimeout(() => {
-				if (isUnmounted || xtermRef.current !== xterm) return;
+				if (
+					isUnmounted ||
+					xtermRef.current !== xterm ||
+					isFocusedRef.current
+				) {
+					return;
+				}
 				blurTerminalInput(xterm);
 			}, 0);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts`
around lines 243 - 249, The deferred zero-delay blur can steal focus if the pane
becomes focused before the timer fires; inside the setTimeout callback in
useTerminalLifecycle, re-check the current focus state via isFocusedRef.current
(in addition to existing isUnmounted and xtermRef.current checks) and bail out
if the pane is now focused so blurTerminalInput(xterm) is skipped; reference
blurTerminalInput, xtermRef, isUnmounted and isFocusedRef to locate and update
the timeout guard.

Comment on lines +59 to +72
const flushPendingData = useCallback(() => {
isWriteFlushScheduledRef.current = false;

const pending = pendingWriteBufferRef.current;
if (!pending) return;

const activeTerminal = xtermRef.current;
if (!activeTerminal || !isStreamReadyRef.current) return;

pendingWriteBufferRef.current = "";
updateModesRef.current(pending);
activeTerminal.write(pending);
updateCwdRef.current(pending);
}, [xtermRef, isStreamReadyRef]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't let buffered output survive terminal replacement.

If a queued microtask reaches this function after xtermRef.current is cleared/replaced or isStreamReadyRef.current flips false, the function returns without clearing pendingWriteBufferRef. Those bytes can then flush into the next terminal instance on a later event, reordering output across sessions. Clear/discard the buffer on this path, or associate the buffer with the terminal instance that produced it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalStream.ts`
around lines 59 - 72, The flushPendingData function can return early without
clearing pendingWriteBufferRef, allowing buffered bytes to later flush into a
new terminal; to fix, capture the terminal instance and readiness at the top
(e.g., const activeTerminal = xtermRef.current; const ready =
isStreamReadyRef.current) and if activeTerminal is null or ready is false then
clear/discard pendingWriteBufferRef.current before returning, or alternatively
bind the pending buffer to the captured terminal instance and only flush when
the same instance is still current; update references to updateModesRef and
updateCwdRef so they run only after confirming the captured activeTerminal is
valid.

Comment on lines +130 to +140
useEffect(() => {
let cancelled = false;
ensureGhosttyReady()
.then(() => {
if (!cancelled) {
setIsRendererReady(true);
}
})
.catch((error) => {
console.error("[Terminal] Failed to initialize ghostty-web:", error);
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't turn renderer init failures into a silent blank terminal.

If ensureGhosttyReady() rejects, this branch only logs and leaves isRendererReady false. The component never surfaces that failure or offers a retry path, so terminal startup can fail with an empty pane and no actionable feedback. Please route this through terminal error state or render an explicit fallback.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx`
around lines 130 - 140, The current useEffect swallows ensureGhosttyReady()
rejections and never updates component state, leaving the terminal blank; update
the effect to capture the error and set a renderer error state (e.g., add or use
a state setter like setRendererError / setIsRendererError) inside the .catch so
the component can render an explicit fallback and a retry action; also expose a
retry handler (e.g., retryInitialize that calls ensureGhosttyReady and updates
setIsRendererReady and clears the error) and ensure the render path checks
isRendererReady and rendererError to show either the terminal, an error message,
and a retry button.

Comment on lines +128 to +133
} else if (hasSameQuery && lastMatch) {
startRow = lastMatch.row;
startColumn =
direction === "next"
? lastMatch.column + 1
: lastMatch.column + lastMatch.length - 1;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

findPrevious can reselect the current match forever.

When the same query is repeated, the previous-search anchor is set to the end of lastMatch. findPreviousInLine() only checks the match start column, so the current match still qualifies and repeated “previous” calls never move backward. Start from lastMatch.column - 1 (or compare against the match end) so the search actually advances to the prior hit.

♻️ Minimal fix
 		} else if (hasSameQuery && lastMatch) {
 			startRow = lastMatch.row;
 			startColumn =
 				direction === "next"
 					? lastMatch.column + 1
-					: lastMatch.column + lastMatch.length - 1;
+					: lastMatch.column - 1;
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else if (hasSameQuery && lastMatch) {
startRow = lastMatch.row;
startColumn =
direction === "next"
? lastMatch.column + 1
: lastMatch.column + lastMatch.length - 1;
} else if (hasSameQuery && lastMatch) {
startRow = lastMatch.row;
startColumn =
direction === "next"
? lastMatch.column + 1
: lastMatch.column - 1;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSearch/terminal-search-adapter.ts`
around lines 128 - 133, The previous-search logic in findPrevious is reselecting
the current match because startColumn is set to the match end; change the anchor
so the search begins before the current match: when hasSameQuery && lastMatch
and direction === "previous" set startColumn = lastMatch.column - 1 (or
otherwise compare against lastMatch.column + lastMatch.length to exclude the
current span) so findPreviousInLine will skip the current match and advance to
the prior hit; update references in findPrevious/lastMatch/startColumn
accordingly.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 33 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/font-family.ts">

<violation number="1" location="apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/font-family.ts:33">
P2: Font-family parsing is incorrect for quoted names containing commas because the list is split on every comma.</violation>
</file>

<file name="apps/desktop/src/main/lib/notifications/server.ts">

<violation number="1" location="apps/desktop/src/main/lib/notifications/server.ts:139">
P2: Validate `notificationType` is a string before passing it to `mapEventType`; the current type assertion can trigger a runtime `trim` error on non-string query values.</violation>
</file>

<file name="apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSearch/terminal-search-adapter.ts">

<violation number="1" location="apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSearch/terminal-search-adapter.ts:122">
P2: Selection coordinates are treated as 0-based even though xterm returns 1-based buffer positions, causing off-by-one search navigation after a selection.</violation>
</file>

<file name="apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts">

<violation number="1" location="apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts:563">
P2: Add `.catch` handler to the `runResize()` floating promise.

(Based on your team's feedback about handling async calls with proper await and catch.) [FEEDBACK_USED]</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.


export function splitFontFamilyList(value: string): string[] {
return value
.split(",")
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 6, 2026

Choose a reason for hiding this comment

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

P2: Font-family parsing is incorrect for quoted names containing commas because the list is split on every comma.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/font-family.ts, line 33:

<comment>Font-family parsing is incorrect for quoted names containing commas because the list is split on every comma.</comment>

<file context>
@@ -0,0 +1,180 @@
+
+export function splitFontFamilyList(value: string): string[] {
+	return value
+		.split(",")
+		.map((part) => part.trim())
+		.filter(Boolean);
</file context>
Fix with Cubic

const mappedEventType = mapEventType(eventType as string | undefined);
const mappedEventType =
mapEventType(eventType as string | undefined) ??
mapEventType(notificationType as string | undefined);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 6, 2026

Choose a reason for hiding this comment

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

P2: Validate notificationType is a string before passing it to mapEventType; the current type assertion can trigger a runtime trim error on non-string query values.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/main/lib/notifications/server.ts, line 139:

<comment>Validate `notificationType` is a string before passing it to `mapEventType`; the current type assertion can trigger a runtime `trim` error on non-string query values.</comment>

<file context>
@@ -133,7 +134,9 @@ app.get("/hook/complete", (req, res) => {
-	const mappedEventType = mapEventType(eventType as string | undefined);
+	const mappedEventType =
+		mapEventType(eventType as string | undefined) ??
+		mapEventType(notificationType as string | undefined);
 
 	// Unknown or missing eventType: return success but don't process
</file context>
Suggested change
mapEventType(notificationType as string | undefined);
mapEventType(
typeof notificationType === "string" ? notificationType : undefined,
);
Fix with Cubic


if (selection) {
if (direction === "next") {
startRow = selection.end.y;
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 6, 2026

Choose a reason for hiding this comment

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

P2: Selection coordinates are treated as 0-based even though xterm returns 1-based buffer positions, causing off-by-one search navigation after a selection.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSearch/terminal-search-adapter.ts, line 122:

<comment>Selection coordinates are treated as 0-based even though xterm returns 1-based buffer positions, causing off-by-one search navigation after a selection.</comment>

<file context>
@@ -0,0 +1,174 @@
+
+		if (selection) {
+			if (direction === "next") {
+				startRow = selection.end.y;
+				startColumn = selection.end.x + 1;
+			} else {
</file context>
Fix with Cubic

});
};

void runResize().finally(() => {
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 6, 2026

Choose a reason for hiding this comment

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

P2: Add .catch handler to the runResize() floating promise.

(Based on your team's feedback about handling async calls with proper await and catch.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts, line 563:

<comment>Add `.catch` handler to the `runResize()` floating promise.

(Based on your team's feedback about handling async calls with proper await and catch.) </comment>

<file context>
@@ -516,14 +490,94 @@ export function useTerminalLifecycle({
+					});
+				};
+
+				void runResize().finally(() => {
+					resizeInFlight = false;
+					if (!pendingResize) return;
</file context>
Suggested change
void runResize().finally(() => {
void runResize()
.catch((error) => console.warn("[Terminal] Failed to run resize:", error))
.finally(() => {
Fix with Cubic

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 55 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/desktop/src/main/lib/terminal-host/types.ts">

<violation number="1" location="apps/desktop/src/main/lib/terminal-host/types.ts:176">
P2: `CreateOrAttachResponse.sessionGeneration` is required even though protocol changes are supposed to be additive-only. If a new client talks to an older daemon that doesn’t send this field, the type promises a string but runtime will be `undefined`. Make this optional (or bump the protocol version and handle missing fields explicitly).</violation>
</file>

<file name="apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSearch/terminal-search-adapter.ts">

<violation number="1" location="apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSearch/terminal-search-adapter.ts:138">
P2: Search wrap-around is incomplete: the starting row is only partially scanned once, so valid matches can be missed.</violation>
</file>

<file name="apps/desktop/src/main/lib/notifications/server.ts">

<violation number="1" location="apps/desktop/src/main/lib/notifications/server.ts:139">
P2: Validate `notificationType` is a string before mapping; otherwise malformed query input can throw at runtime in `mapEventType`.</violation>
</file>

<file name="apps/desktop/src/lib/trpc/routers/terminal/terminal.ts">

<violation number="1" location="apps/desktop/src/lib/trpc/routers/terminal/terminal.ts:480">
P1: The `onExit` handler signature was changed to expect a `payload` object, but `terminal.emit` in the `write` procedure still uses positional arguments.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

signal?: number,
reason?: "killed" | "exited" | "error",
) => {
const onExit = (payload: {
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 6, 2026

Choose a reason for hiding this comment

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

P1: The onExit handler signature was changed to expect a payload object, but terminal.emit in the write procedure still uses positional arguments.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/lib/trpc/routers/terminal/terminal.ts, line 480:

<comment>The `onExit` handler signature was changed to expect a `payload` object, but `terminal.emit` in the `write` procedure still uses positional arguments.</comment>

<file context>
@@ -437,52 +438,64 @@ export const createTerminalRouter = () => {
-						signal?: number,
-						reason?: "killed" | "exited" | "error",
-					) => {
+					const onExit = (payload: {
+						exitCode: number;
+						signal?: number;
</file context>
Fix with Cubic

isNew: boolean;
snapshot: TerminalSnapshot;
wasRecovered: boolean;
sessionGeneration: string;
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 6, 2026

Choose a reason for hiding this comment

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

P2: CreateOrAttachResponse.sessionGeneration is required even though protocol changes are supposed to be additive-only. If a new client talks to an older daemon that doesn’t send this field, the type promises a string but runtime will be undefined. Make this optional (or bump the protocol version and handle missing fields explicitly).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/main/lib/terminal-host/types.ts, line 176:

<comment>`CreateOrAttachResponse.sessionGeneration` is required even though protocol changes are supposed to be additive-only. If a new client talks to an older daemon that doesn’t send this field, the type promises a string but runtime will be `undefined`. Make this optional (or bump the protocol version and handle missing fields explicitly).</comment>

<file context>
@@ -173,6 +173,7 @@ export interface CreateOrAttachResponse {
 	isNew: boolean;
 	snapshot: TerminalSnapshot;
 	wasRecovered: boolean;
+	sessionGeneration: string;
 	/** PTY process ID for port scanning (null if not yet spawned or exited) */
 	pid: number | null;
</file context>
Fix with Cubic


startRow = Math.max(0, Math.min(totalRows - 1, startRow));

for (let index = 0; index < totalRows; index++) {
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 6, 2026

Choose a reason for hiding this comment

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

P2: Search wrap-around is incomplete: the starting row is only partially scanned once, so valid matches can be missed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/TerminalSearch/terminal-search-adapter.ts, line 138:

<comment>Search wrap-around is incomplete: the starting row is only partially scanned once, so valid matches can be missed.</comment>

<file context>
@@ -0,0 +1,174 @@
+
+		startRow = Math.max(0, Math.min(totalRows - 1, startRow));
+
+		for (let index = 0; index < totalRows; index++) {
+			const row =
+				direction === "next"
</file context>
Fix with Cubic

const mappedEventType = mapEventType(eventType as string | undefined);
const mappedEventType =
mapEventType(eventType as string | undefined) ??
mapEventType(notificationType as string | undefined);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 6, 2026

Choose a reason for hiding this comment

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

P2: Validate notificationType is a string before mapping; otherwise malformed query input can throw at runtime in mapEventType.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/main/lib/notifications/server.ts, line 139:

<comment>Validate `notificationType` is a string before mapping; otherwise malformed query input can throw at runtime in `mapEventType`.</comment>

<file context>
@@ -133,7 +134,9 @@ app.get("/hook/complete", (req, res) => {
-	const mappedEventType = mapEventType(eventType as string | undefined);
+	const mappedEventType =
+		mapEventType(eventType as string | undefined) ??
+		mapEventType(notificationType as string | undefined);
 
 	// Unknown or missing eventType: return success but don't process
</file context>
Fix with Cubic

@Kitenite Kitenite closed this Mar 7, 2026
@Kitenite Kitenite deleted the Kitenite/innovative-wholesaler branch March 7, 2026 03:22
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.

1 participant