Skip to content

cue-hardening: leak fix, panel layout, themed scrollbars + GeneralTab cleanup#742

Merged
reachrazamair merged 6 commits intorcfrom
cue-polish
Apr 6, 2026
Merged

cue-hardening: leak fix, panel layout, themed scrollbars + GeneralTab cleanup#742
reachrazamair merged 6 commits intorcfrom
cue-polish

Conversation

@reachrazamair
Copy link
Copy Markdown
Contributor

@reachrazamair reachrazamair commented Apr 6, 2026

Summary

Four-commit polish pass on the Cue feature and the surrounding theme/UI surface:

  1. Group-chat → Cue leak fix. Tightens the boundary so group-chat transcripts can no longer end up inside chained Cue pipeline output (or any other process channel).
  2. Agent config panel layout. Fixes three collapsed-view bugs in the Cue pipeline editor: cramped multi-trigger left rail, weirdly small output box, and wasted vertical space in single-trigger mode.
  3. App-wide themed scrollbars. Replaces the hardcoded rgba(255,255,255,0.15) thumb (invisible on light themes) with a unified, theme-driven scrollbar system that automatically picks up the active theme. Includes a new <ScrollArea> wrapper component for new call sites.
  4. GeneralTab dead code cleanup. Drops 22 pre-existing eslint warnings left over from the Usage & Stats / WakaTime move to the Encore Features tab.

npm run lint, npm run lint:eslint, and the relevant test suites are all clean — see the test plan below.


1. fix(cue): prevent group-chat output from leaking into Cue pipeline runs2664050d

Investigation started from a user report that group-chat output was appearing inside chained Cue pipeline runs. The audit found three independent paths that could either cause or amplify the leak; this commit closes all of them.

Root cause: greedy regex on group-chat session IDs

REGEX_PARTICIPANT_TIMESTAMP was /^group-chat-(.+)-participant-(.+)-(\d{13,})$/ — both captures greedy. groupChatId is always a uuidv4() (see group-chat-storage.ts:createGroupChat), but participant names are user-supplied. A participant whose name contains the literal substring -participant- would cause the regex to backtrack to the last occurrence and parse out the wrong (groupChatId, participantName) pair. Output chunks buffered in output-buffer.ts would then get flushed against the wrong owner on process exit.

Fix: anchor groupChatId on the canonical UUID format, make the participant-name capture lazy, add a strict ^group-chat- prefix guard in the parser, and strip -recovery from UUID-suffixed IDs (previously only handled in the timestamp branch).

const UUID_PATTERN = '[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}';
export const REGEX_PARTICIPANT_UUID = new RegExp(
    `^group-chat-(${UUID_PATTERN})-participant-(.+?)-(${UUID_PATTERN})$`,
    'i'
);

Containment guards in data-listener and exit-listener

The bigger problem: if a group-chat--prefixed sessionId failed to match the moderator branch and failed to parse as a participant, both data-listener and exit-listener would fall through to the regular safeSend('process:data' | 'process:exit', ...) path, the web broadcast path, and cueEngine.notifyAgentCompleted. Tightening the regex would have exposed this for any legacy ID that no longer parses.

Fix: added explicit "if it starts with GROUP_CHAT_PREFIX, drop it" guards in both listeners, after the moderator/participant branches but before any forwarding. Group-chat bytes are now structurally contained to the group-chat domain.

// data-listener.ts
if (isGroupChatSession) {
    debugLog('GroupChat:Debug',
      `WARNING: unrecognized group-chat sessionId shape — dropping ${data.length} bytes to prevent cross-domain leak: ${sessionId}`);
    return;
}

Source-output invariant in cue-engine

notifyAgentCompleted builds the sourceOutput payload field that becomes {{CUE_SOURCE_OUTPUT}} in downstream prompts. Audit confirmed it's built exclusively from completionData.stdout with no fallback to any session-store or buffer. The exit-listener path passes { status, exitCode } only — no stdout — so the resulting sourceOutput is ''.

This invariant is load-bearing: any future fallback to a session output store, group-chat buffer, or live process buffer would re-introduce the leak. The commit adds an explicit comment block in cue-engine.ts documenting this and a regression test (produces empty sourceOutput when completionData has no stdout) that will fail loudly if anyone adds such a fallback.

Fan-in source dedupe

While auditing cue-fan-in-tracker, found a latent bug where YAML listing the same session by both name and ID (e.g. source_session: ['Agent A', 'agent-a']) would cause sources.length to overcount and the fan-in to hang forever waiting for a phantom second completion. The tracker Map is keyed by sessionId so the count would never reach the target.

Fix: added a resolveSourcesToIds() helper that resolves user-authored source names/IDs to a deduped Set<string> of canonical session IDs. Both handleCompletion and handleFanInTimeout now use this set as the source of truth instead of the raw sources array.

Files

  • src/main/constants.ts — UUID-anchored regexes
  • src/main/group-chat/session-parser.ts — strict prefix guard, recovery suffix handling
  • src/main/process-listeners/data-listener.ts — domain containment guard
  • src/main/process-listeners/exit-listener.ts — domain containment guard
  • src/main/cue/cue-engine.ts — source-output invariant comment + raw stdout extraction
  • src/main/cue/cue-fan-in-tracker.tsresolveSourcesToIds, dedupe in completion + timeout paths
  • src/__tests__/main/constants.test.ts — fixtures updated to UUIDs + adversarial cases
  • src/__tests__/main/group-chat/session-parser.test.ts — adversarial inputs (-participant- in name, UUID-shaped names, 13-digit numbers in name, recovery suffix)
  • src/__tests__/main/cue/cue-completion-chains.test.ts — empty-stdout regression + fan-in dedupe regression
  • src/__tests__/main/performance-optimizations.test.ts — fixtures updated to UUIDs
  • src/main/process-listeners/__tests__/data-listener.test.ts — drops unrecognized group-chat session
  • src/main/process-listeners/__tests__/exit-listener.test.ts — drops unrecognized group-chat session, asserts only {status, exitCode} passed to Cue

2. chore(settings): remove dead Stats/WakaTime code from GeneralTab946c6e00

Pre-existing eslint warnings (22 in total) reported by npm run lint:eslint. Commit 5a79180f ("refactor: move Usage & Stats settings from General tab to Encore Features tab") moved the UI but left the destructured props, state, callbacks, and a useEffect behind in GeneralTab.tsx.

Removed

  • Trash2 icon import (unused)
  • 8 destructured useSettings() fields: statsCollectionEnabled + setter, defaultStatsTimeRange + setter, setWakatimeEnabled, wakatimeApiKey, wakatimeDetailedTracking + setter
  • 7 useState declarations: statsDbSize, statsEarliestDate, statsClearing, statsClearResult, wakatimeCliStatus, wakatimeKeyValid, wakatimeKeyValidating
  • handleWakatimeApiKeyChange callback
  • WakaTime CLI availability useEffect (~50 lines, no consumer)
  • Stale // Load sync settings and stats data when modal opens comment

Files

  • src/renderer/components/Settings/tabs/GeneralTab.tsx (−90 lines)

npm run lint:eslint now exits clean. All 84 GeneralTab component tests still pass.


3. fix(cue-pipeline): output box fills column and multi-trigger rows scrolla833af66

Three layout bugs in the agent config panel's collapsed view, all reported in the same iteration:

Bug 3.1 — Output prompt box collapses to its content min in multi-trigger mode

// Before
flex: hasMultipleTriggers ? 0 : 1

In multi-trigger mode the right column's flex: 0 made the output textarea sit at its minHeight: 80 with empty space below it, while the input column on the left took all the remaining width. Fix: output column always uses flex: 1 and the textarea always uses flex: 1, minHeight: 88 (or 72 with fan-in). Output now fills its column whether single- or multi-trigger.

Bug 3.2 — Multi-trigger left rail had no scroll, rows visually overlapped

Each EdgePromptRow used flex: 1 plus textarea minHeight: 68. With 3+ triggers in a ~280px collapsed content area, the parent column couldn't satisfy each row's intrinsic min, so flex layout shrank rows below their content size — the result was the bottom row's title rendering on top of the textarea above it. The user's wording ("no two input prompt box titles must overlap to the stuff on their y axis") matches exactly.

Fix: each row now uses flexShrink: 0 (intrinsic content height) instead of flex: 1 in collapsed mode. Title spans get explicit flexShrink: 0 + marginBottom: 4 so flex layout can never collapse them. The parent column gets its own overflowY: auto with paddingRight: 6 reserving the scrollbar gutter, so additional rows scroll instead of squeezing each other.

Bug 3.3 — Single-trigger collapsed wasted vertical space

Input/output textareas were gated expanded ? flex:1 : minHeight:80, so collapsed mode never filled the column even when the panel was tall. Fix: both now always use flex: 1, minHeight: 88 regardless of expanded state.

Bonus — collapsedHeight formula

Replaced the hardcoded ladder (320 / 360 / 420) with a formula that accounts for triggerEdgeCount:

const base = hasUpstreamAgents ? 300 : 280;
const fanInBoost = hasFanIn ? 130 : 0;
const triggerBoost = hasMultipleTriggers
    ? Math.min(120, (triggerEdgeCount - 1) * 60)
    : 0;
return base + fanInBoost + triggerBoost;

Tighter for the common single-trigger case, larger when multi-trigger or fan-in needs the room. Capped so the panel can never eat the entire canvas.

Bonus — single source of scroll per axis

Removed the redundant outer overflowY: auto on AgentConfigPanel and the overflow: auto on the input/output split row. NodeConfigPanel's content wrapper is now overflow: hidden for agent nodes — the inner panels manage their own scroll regions (left rail in multi-trigger mode, nothing for the rest). Eliminates triple-nested scroll regions.

Files

  • src/renderer/components/CuePipelineEditor/panels/NodeConfigPanel.tsx
  • src/renderer/components/CuePipelineEditor/panels/AgentConfigPanel.tsx
  • src/renderer/components/CuePipelineEditor/panels/EdgePromptRow.tsx

4. feat(theme): app-wide themed scrollbars driven by CSS variables14cbb62b

The previous .scrollbar-thin rule used rgba(255, 255, 255, 0.15) for the thumb — works on dark themes, invisible on light themes (GitHub, One Light, Catppuccin Latte, Ayu Light). This commit replaces the hardcoded color with a unified, theme-driven system that automatically picks up the active theme on every scrollable element in the app.

Architecture (3 layers)

Layer 1 — Theme bridge (useThemeStyles.ts)

The hook is the single bridge between the React theme system and CSS. It now injects six CSS variables on :root:

Variable Source Used by
--accent-color theme.accent Existing animations, legacy refs
--highlight-color theme.accent Legacy alias kept for backwards compat
--scrollbar-thumb theme.border Idle thumb (theme-aware — works on light AND dark)
--scrollbar-thumb-hover theme.textDim Hover thumb
--scrollbar-thumb-active theme.accent Active scrolling
--scrollbar-track theme.bgActivity Track tint

The ThemeColors interface gained border, textDim, bgActivity. App.tsx already passes the full theme.colors palette so callers are unaffected — the structural type just sees more fields now.

Layer 2 — Global CSS (index.css)

  • *::-webkit-scrollbar — themes EVERY scrollable element in the app, including the ~200 inline overflow: auto containers that don't carry a class. 10px width, 6px border-radius, 2px transparent inset border via background-clip: padding-box for a "floated" thumb look.
  • Firefox scrollbar-color on * — cross-browser fallback for non-WebKit Electron variants.
  • .scrollbar-thin — slim 6px variant for tight UI areas (~50 components: sidebars, autocomplete dropdowns, command palettes, the agent config panel's left rail). Rewritten to use the same CSS variables.
  • .scrollbar-thin.scrolling/.fading — stateful brighten-on-scroll → fade-after-1s animation, kept intact and managed by the existing JS in useThemeStyles. Only fires for elements with .scrollbar-thin to avoid wiring scroll listeners to every element in the app.
  • Every var() has a sensible fallback so initial paint (before useThemeStyles runs) renders correctly.

Layer 3 — <ScrollArea> wrapper component (new)

Thin React wrapper at src/renderer/components/ScrollArea.tsx for documenting intent at new call sites. Existing .scrollbar-thin usages keep working unchanged — this is purely additive, not a mass migration.

<ScrollArea variant="thin" axis="y" style={{ maxHeight: 300 }}>
  {items.map(...)}
</ScrollArea>
  • Variants: default (10px, matches global app default) | thin (6px, with fade-on-idle)
  • Axis: both (default) | x | y | none
  • hideScrollbar opt-out (applies .no-scrollbar)
  • Full ref forwarding via forwardRef
  • Full HTML attribute passthrough (id, role, aria-*, onScroll, etc.)

Why hybrid (CSS variables + global rules + opt-in component)

Pure-component approach would require touching ~50 components and ~200 inline overflow: auto containers. CSS-variable + global-rule approach themes them all with zero per-component changes, while the <ScrollArea> component gives new code a clean call-site primitive when intent matters.

Files

  • src/renderer/hooks/ui/useThemeStyles.ts — expanded CSS variable injection + interface
  • src/renderer/index.css — global themed scrollbar rules (3 layers)
  • src/renderer/components/ScrollArea.tsx — new wrapper component
  • src/__tests__/renderer/hooks/useThemeStyles.test.ts — new (6 tests)
  • src/__tests__/renderer/components/ScrollArea.test.tsx — new (13 tests)

Test plan

  • npm run lint (3× tsc) — clean, exit 0
  • npm run lint:eslint — clean, 0 warnings, exit 0
  • npm run test filtered to relevant files — all passing:
    • 1052 tests across src/__tests__/main/cue, src/__tests__/main/group-chat, and src/main/process-listeners/__tests__
    • 276 tests across src/__tests__/renderer/components/CuePipelineEditor
    • 84 tests in src/__tests__/renderer/components/Settings/tabs/GeneralTab.test.tsx (cleanup)
    • 19 new tests across src/__tests__/renderer/hooks/useThemeStyles.test.ts and src/__tests__/renderer/components/ScrollArea.test.tsx (themed scrollbars)
  • Manual: participant whose name contains -participant- no longer causes group-chat output to leak.
  • Manual: complex multi-trigger pipeline with 3+ triggers — left rail scrolls, output box fills column, no title overlap, single-trigger view no longer wastes space.
  • Manual: light themes (GitHub, One Light, Catppuccin Latte, Ayu Light) now show visible themed scrollbars instead of invisible white thumbs.
  • Manual: dark themes (Dracula, Tokyo Night, Nord) still look correct.
  • Manual: theme switching is instant — every scrollbar in the app picks up the new colors without reload.

Notes for reviewers

  • The four commits are deliberately split by concern and can be reviewed independently. Commit 1 is the highest-stakes (production correctness); commits 2–4 are pure UI/cleanup.
  • The greedy-regex / containment-guard fix in commit 1 also closes a class of bugs that wasn't user-reported but was provably exploitable: any sessionId of the shape group-chat-<weird> would have leaked through to the regular renderer channel. The defense-in-depth guards in data-listener and exit-listener are intentional belt-and-suspenders.
  • The ScrollArea component is opt-in for new code only — please don't mass-migrate existing .scrollbar-thin usages in this PR. Reach for it when adding new scrollable regions or when refactoring areas touched for other reasons.

Summary by CodeRabbit

  • New Features

    • Added a ScrollArea component for themed, ref-forwarding scroll containers.
  • Bug Fixes

    • Stricter group-chat session ID parsing and containment guards to prevent unrecognized group-chat events from being forwarded.
    • Exit/data listeners now avoid leaking unrecognized group-chat events.
  • Improvements

    • App-wide, theme-driven scrollbar styling and expanded theme tokens; hook updates to drive CSS variables.
    • Layout and sizing refinements in editor panels; fan-in deduplication and tighter completion payload behavior.
  • Tests

    • Extensive new and hardened regression tests for parsing, fan-in, listeners, hooks, components, and adversarial inputs.

Hardens the boundary between the group-chat domain and the Cue automation
engine to stop group-chat transcripts from appearing in chained Cue
pipeline output.

- Tighten group-chat session-id regexes in main/constants.ts to anchor
  groupChatId on the canonical UUID format. Eliminates greedy-backtrack
  ambiguity when participant names contain sentinel substrings like
  "-participant-" — the previous greedy capture would mis-parse and
  flush buffered output against the wrong owner.
- session-parser: add strict "group-chat-" prefix guard and strip
  "-recovery" from UUID-suffixed IDs.
- data-listener / exit-listener: drop any sessionId that starts with
  GROUP_CHAT_PREFIX but matches neither the moderator nor participant
  shape. Without this guard a malformed group-chat ID would fall through
  to safeSend('process:data'), the web broadcast path, and
  cueEngine.notifyAgentCompleted, leaking transcript bytes and firing
  spurious agent.completed subscriptions.
- cue-engine.notifyAgentCompleted: document the load-bearing invariant
  that sourceOutput is built EXCLUSIVELY from completionData.stdout
  with no fallback to any session-store or group-chat buffer. Adding
  such a fallback would re-introduce the leak.
- cue-fan-in-tracker: dedupe sources by resolving them to canonical
  session IDs upfront so a yaml that lists the same session by both
  name and id no longer hangs waiting for a phantom second completion.

Tests cover adversarial regex inputs, the empty-stdout invariant from
the exit-listener path, fan-in dedupe, and the cross-domain containment
guards in both listeners.
Drops eslint warnings introduced when commit 5a79180 moved the Usage &
Stats and WakaTime sections to the Encore Features tab but left their
destructured props, state, helpers, and effect behind in GeneralTab.tsx.

Removed:
- Trash2 icon import (unused)
- 8 destructured useSettings fields (statsCollectionEnabled +
  setStatsCollectionEnabled, defaultStatsTimeRange + setter,
  setWakatimeEnabled, wakatimeApiKey, wakatimeDetailedTracking + setter)
- 7 useState declarations (statsDbSize, statsEarliestDate, statsClearing,
  statsClearResult, wakatimeCliStatus, wakatimeKeyValid,
  wakatimeKeyValidating)
- handleWakatimeApiKeyChange callback
- WakaTime CLI availability useEffect (~50 lines, no consumer)
- Stale "Load sync settings and stats data" comment

`npm run lint:eslint` now exits clean. The 84 GeneralTab tests still
pass — none referenced the removed members.
Fixes three layout bugs in the agent config panel's collapsed view:

1. Output prompt box was hard-coded to flex: hasMultipleTriggers ? 0 : 1
   so in multi-trigger mode the column collapsed to its content min and
   the textarea sat with empty space below it. Output now always uses
   flex: 1 — fills the column whether single- or multi-trigger.

2. Multi-trigger left rail had no scroll. Each EdgePromptRow used flex: 1
   plus a textarea minHeight, so 3+ triggers exceeded the parent's
   intrinsic min and rows visually overlapped — bottom row's title
   rendered over the textarea above. Each row now uses flexShrink: 0
   (intrinsic content height) and the parent column has its own
   overflowY: auto with a 6px scrollbar gutter so additional rows scroll
   instead of squeezing each other.

3. Single-trigger collapsed wasted vertical space. Input/output textareas
   were gated `expanded ? flex:1 : minHeight:80` so collapsed mode never
   filled the column even when the panel was tall. Both now always use
   flex: 1, minHeight: 88 (or 72 with fan-in).

Also:
- collapsedHeight in NodeConfigPanel replaced with a formula that
  accounts for triggerEdgeCount: single trigger 280, +60/extra trigger
  capped at +120, +130 if fan-in. Tighter for the common case, larger
  when needed.
- Removed the redundant outer overflow: auto on AgentConfigPanel and the
  input/output split row. NodeConfigPanel content wrapper is now
  overflow: hidden for agent nodes — single source of scroll per axis.
- Title spans get explicit flexShrink: 0 + marginBottom so flex layout
  can never collapse them under the textarea. Load-bearing.
Replaces the hard-coded rgba(255,255,255,0.15) scrollbar thumb (which
was invisible on light themes like GitHub, One Light, Catppuccin Latte,
Ayu Light) with a unified, theme-driven scrollbar system that picks up
the active theme automatically — no per-component changes needed.

Three layers:

1. useThemeStyles now injects six CSS variables on :root:
     --accent-color           = theme.accent
     --highlight-color        = theme.accent (legacy alias)
     --scrollbar-thumb        = theme.border
     --scrollbar-thumb-hover  = theme.textDim
     --scrollbar-thumb-active = theme.accent
     --scrollbar-track        = theme.bgActivity
   Hook updates them in a useEffect so theme switches are instant. The
   ThemeColors interface gained `border`, `textDim`, `bgActivity` —
   App.tsx already passes the full theme.colors so callers are
   unaffected.

2. index.css gets three layers of scrollbar rules:
   - Global *::-webkit-scrollbar — themes EVERY scrollable element in
     the app (~200 inline `overflow: auto` containers that don't carry
     a class). 10px width, soft inset border for a "floated" thumb,
     transparent track.
   - .scrollbar-thin — slim 6px variant for tight UI areas (~50
     components), now uses the same CSS variables instead of hardcoded
     white rgba.
   - .scrollbar-thin.scrolling/.fading — stateful brighten-on-scroll
     then fade-after-1s animation, kept intact and managed by the
     existing JS in useThemeStyles.
   Firefox `scrollbar-color` is also set on `*` for cross-browser
   support. Every var() has a sensible fallback so initial paint
   (before useThemeStyles runs) renders correctly.

3. New ScrollArea component (src/renderer/components/ScrollArea.tsx) —
   thin wrapper for new call sites. Two variants (default 10px / thin
   6px), four axis modes (both/x/y/none), hideScrollbar opt-out, full
   ref forwarding and HTML attribute passthrough. Existing
   .scrollbar-thin usages keep working unchanged — this is purely
   additive.

Tests pin the contract:
- useThemeStyles.test.ts (6 tests) — verifies all six CSS variables are
  set, mapping is correct, vars update on theme change, light theme
  produces visible thumb (regression).
- ScrollArea.test.tsx (13 tests) — variant class composition, axis
  modes, ref forwarding, attribute passthrough, hideScrollbar override.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 6, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c22b3706-6adc-4c9f-8619-6dc0f67302b7

📥 Commits

Reviewing files that changed from the base of the PR and between 23f9b41 and 1ae948d.

📒 Files selected for processing (1)
  • src/__tests__/main/group-chat/session-parser.test.ts

📝 Walkthrough

Walkthrough

Tightens group-chat session parsing to require UUIDs, drops unrecognized group-chat sessions early in process listeners, deduplicates fan-in sources and centralizes stdout handling in Cue, adds a ScrollArea component, expands theme scrollbar tokens/styles, and adds/updates tests across parsing, cue, listeners, and renderer.

Changes

Cohort / File(s) Summary
Constants & Session Parsing
src/main/constants.ts, src/main/group-chat/session-parser.ts
Regexes now anchor groupChatId to a UUID pattern; participant capture made non-greedy; parser enforces group-chat- prefix and documents/implements recovery-only stripping for timestamp branch.
Process Listeners (containment) & Tests
src/main/process-listeners/data-listener.ts, src/main/process-listeners/exit-listener.ts, src/main/process-listeners/__tests__/*, src/main/process-listeners/__tests__/exit-listener.test.ts, src/main/process-listeners/__tests__/data-listener.test.ts
Added early-return guards for group-chat-* IDs that fail parsing (log + return); prevents safeSend, broadcasts, output buffer appends, and Cue notifications for unrecognized shapes; tests assert containment behavior.
Cue completion & Fan-In
src/main/cue/cue-engine.ts, src/main/cue/cue-fan-in-tracker.ts, src/__tests__/main/cue/*
notifyAgentCompleted centralizes stdout into rawStdout and derives sourceOutput/outputTruncated from it; fan-in adds resolveSourcesToIds to dedupe mixed name/ID references and uses deduped counts for timeouts/remaining work; tests cover missing stdout and de-duplication.
Session/Regex Tests & Parser Tests
src/__tests__/main/constants.test.ts, src/__tests__/main/group-chat/session-parser.test.ts, src/__tests__/main/performance-optimizations.test.ts
Test fixtures switched to UUID constants; added negative/adversarial cases (non-UUID rejection, literal -participant- in names, -recovery handling); replaced in-test regex/mocks with production imports.
Process Exit/Data Listener Integration Tests
src/main/process-listeners/__tests__/*
New regression suites asserting group-chat containment on both data and exit events and that normal forwarding still works for non-group-chat sessions.
ScrollArea UI & Tests
src/renderer/components/ScrollArea.tsx, src/__tests__/renderer/components/ScrollArea.test.tsx
New exported ScrollArea component with variant, axis, hideScrollbar, ref forwarding, passthrough attributes; tests cover variants, axis overflow, ref forwarding, attribute passthrough, onScroll, and children rendering.
Theme system & CSS & Hook Tests
src/renderer/hooks/ui/useThemeStyles.ts, src/renderer/index.css, src/__tests__/renderer/hooks/useThemeStyles.test.ts
Expanded ThemeColors with border, textDim, bgActivity; hook writes scrollbar-related CSS variables; global CSS switched to token-driven scrollbar rules; tests validate CSS variable injection and updates.
Renderer Layout Adjustments
src/renderer/components/CuePipelineEditor/panels/...
Adjusted flex/overflow/sizing for AgentConfigPanel, EdgePromptRow, NodeConfigPanel to stabilize scrolling and compute collapsed heights based on triggers/fan-in; spacing tweaks.
Settings Cleanup
src/renderer/components/Settings/tabs/GeneralTab.tsx
Removed Stats and WakaTime UI/state/effects; modal load focuses on sync/storage only; added Sentry capture on sync load errors.
Other Tests
src/__tests__/main/cue/cue-completion-chains.test.ts
Added regression tests for empty/missing completionData stdout and fan-in dedup behavior when a session is referenced by name and ID.
Misc. Test Adjustments
src/__tests__/main/constants.test.ts
Updated regex fixtures to use fixed UUID constants and added negative/adversarial assertions.

Sequence Diagram(s)

sequenceDiagram
    participant ProcessManager as ProcessManager
    participant DataListener as DataListener
    participant Parser as SessionParser
    participant SafeSend as safeSend / Renderer
    participant WebServer as WebServer
    participant OutputBuf as OutputBuffer

    ProcessManager->>DataListener: emit 'data' (sessionId, payload)
    DataListener->>Parser: parseModerator(sessionId) / parseParticipant(sessionId)
    Parser-->>DataListener: no match (unrecognized group-chat shape)
    DataListener->>DataListener: log warning "unrecognized group-chat sessionId shape"
    Note over DataListener,SafeSend: Early return — no safeSend, no broadcast, no append
Loading
sequenceDiagram
    participant ProcessManager as ProcessManager
    participant ExitListener as ExitListener
    participant Parser as SessionParser
    participant CueEngine as CueEngine
    participant SafeSend as safeSend / Renderer

    ProcessManager->>ExitListener: emit 'exit' (sessionId, {status, exitCode, completionData?})
    ExitListener->>Parser: parseModerator / parseParticipant
    alt recognized group-chat
        ExitListener->>CueEngine: notifyAgentCompleted({...completionData...})
    else unrecognized group-chat
        ExitListener->>ExitListener: log warning and return
    end
    ExitListener->>SafeSend: (only if non-group-chat or recognized) safeSend('process:exit', ...)
Loading
sequenceDiagram
    participant CueEngine as CueEngine
    participant Subscriber as CompletionSubscriber
    participant FanIn as FanInTracker

    CueEngine->>CueEngine: rawStdout = completionData?.stdout ?? ''
    CueEngine->>Subscriber: for each subscriber (single source_session)
    Note over CueEngine,FanIn: Fan-in dedup uses resolveSourcesToIds(sources) -> Set(ids)
    CueEngine->>FanIn: send event.payload { sourceOutput: rawStdout.slice(-N), outputTruncated: rawStdout.length > N }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I sniffed the IDs, each hyphen and hue,

UUIDs in place, no stray bits through.
Guards on the gate, they shoo the wrong code,
ScrollArea cushions each comfy load.
Theme tokens hum — a tidy rabbit's ode. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the four main commits: Cue leak fix (UUID-anchored regex + containment guards), panel layout fixes, themed scrollbars, and GeneralTab cleanup. It is specific, concise, and reflects the primary changes.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cue-polish

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.

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 6, 2026

Greptile Summary

This is a four-commit polish pass that closes a group-chat→Cue output leak via UUID-anchored regexes and defense-in-depth domain-containment guards in both data-listener and exit-listener, fixes three layout bugs in the collapsed Cue pipeline editor (multi-trigger left rail overflow, output column sizing, single-trigger wasted space), replaces the hardcoded rgba(255,255,255,0.15) invisible-on-light-themes scrollbar thumb with a CSS-variable-driven themed system, and removes 90 lines of dead Stats/WakaTime code from GeneralTab. All changes are well-reasoned and come with targeted regression tests.

Confidence Score: 5/5

Safe to merge — all remaining findings are P2 style suggestions with no behavioral impact.

The high-stakes leak fix is sound: UUID-anchored regexes remove the greedy-backtrack root cause, domain-containment guards in both listeners provide defense-in-depth, the source-output invariant in cue-engine.ts is clearly documented with a regression test, and adversarial test cases cover the key edge cases. The UI layout and CSS changes are additive and well-isolated. The only finding is a cosmetic log-message inconsistency (sources.length vs resolvedSourceIds.size in two timeout log messages) with zero behavioral impact.

src/main/cue/cue-fan-in-tracker.ts — two timeout log messages reference the raw sources.length rather than resolvedSourceIds.size; no functional impact.

Important Files Changed

Filename Overview
src/main/constants.ts UUID-anchored regex patterns replace greedy captures for group-chat session IDs, preventing mis-parse with adversarial participant names containing '-participant-'
src/main/cue/cue-engine.ts Adds documented source-output invariant comment and raw stdout extraction guard for single-source agent.completed subscriptions
src/main/cue/cue-fan-in-tracker.ts Adds resolveSourcesToIds() dedup helper; completion counting and timeout logic use canonical ID sets, but two log messages still reference sources.length
src/main/group-chat/session-parser.ts Adds strict prefix guard and -recovery suffix stripping in UUID/timestamp branches; fallback pattern restricts to UUID-anchored groupChatId
src/main/process-listeners/data-listener.ts Adds domain-containment guard that drops unrecognized group-chat sessionIds before the regular process:data path and web broadcast
src/main/process-listeners/exit-listener.ts Adds matching domain-containment guard for exit events, blocking group-chat IDs from reaching process:exit, web broadcast, and Cue's notifyAgentCompleted
src/renderer/components/CuePipelineEditor/panels/AgentConfigPanel.tsx Output column always uses flex:1; multi-trigger left rail gets overflowY:auto with paddingRight gutter, fixing collapsed layout bugs
src/renderer/components/CuePipelineEditor/panels/EdgePromptRow.tsx Collapsed mode uses flexShrink:0 (intrinsic height) instead of flex:1, preventing label/textarea visual overlap when 3+ triggers are attached
src/renderer/components/CuePipelineEditor/panels/NodeConfigPanel.tsx Replaces hardcoded collapsedHeight ladder with formula based on triggerEdgeCount; agent-node content wrapper uses overflow:hidden for single scroll source
src/renderer/components/ScrollArea.tsx New opt-in wrapper component for scrollable regions with variant/axis/hideScrollbar props and full ref forwarding
src/renderer/components/Settings/tabs/GeneralTab.tsx Removes 90 lines of dead Stats/WakaTime code (8 destructured settings, 7 state vars, 1 effect) left over after Encore Features migration
src/renderer/hooks/ui/useThemeStyles.ts Injects six CSS variables on :root including --scrollbar-thumb from theme.border, fixing invisible thumbs on light themes
src/renderer/index.css Three-layer themed scrollbar system: global *::-webkit-scrollbar, .scrollbar-thin variant, stateful .scrolling/.fading classes — all via CSS variables

Sequence Diagram

sequenceDiagram
    participant P as Process Manager
    participant DL as data-listener
    participant EL as exit-listener
    participant GC as Group Chat Domain
    participant CE as Cue Engine
    participant R as Renderer

    P->>DL: data(sessionId, chunk)
    alt sessionId starts with group-chat-
        DL->>DL: moderatorMatch?
        alt moderator match
            DL->>GC: appendToGroupChatBuffer()
            DL-->>P: return (contained)
        else participant match
            DL->>GC: appendToGroupChatBuffer()
            DL-->>P: return (contained)
        else unrecognized group-chat shape (NEW GUARD)
            DL-->>P: return (DROP — prevents cross-domain leak)
        end
    else regular session
        DL->>R: safeSend(process:data)
    end

    P->>EL: exit(sessionId, code)
    alt sessionId starts with group-chat-
        EL->>EL: moderatorMatch?
        alt moderator match
            EL->>GC: routeModeratorResponse()
            EL-->>P: return (contained)
        else participant match
            EL->>GC: routeAgentResponse()
            EL-->>P: return (contained)
        else unrecognized group-chat shape (NEW GUARD)
            EL-->>P: return (DROP — blocks spurious Cue triggers)
        end
    else regular session
        EL->>R: safeSend(process:exit)
        EL->>CE: notifyAgentCompleted(status, exitCode only)
    end
Loading

Comments Outside Diff (2)

  1. src/main/cue/cue-fan-in-tracker.ts, line 122 (link)

    P2 Timeout log uses raw sources.length instead of deduped resolvedSourceIds.size

    The log message in continue mode (and the parallel one in break mode on line 136) reports the completion target using sources.length — the raw, potentially duplicate-containing user-authored list — rather than resolvedSourceIds.size, the canonical deduped count that actually controls when fan-in fires. When a YAML config references the same session by both name and ID (e.g. source_session: ['Agent A', 'agent-a']), this emits "1/2 sources" when the real deduped target was 1, which could mislead anyone reading the log.

  2. src/main/cue/cue-fan-in-tracker.ts, line 136 (link)

    P2 Break-mode timeout log also uses sources.length instead of resolvedSourceIds.size

    Same issue as line 122 — the denominator should be resolvedSourceIds.size so the ratio reflects the actual deduped target count.

Reviews (1): Last reviewed commit: "feat(theme): app-wide themed scrollbars ..." | Re-trigger Greptile

@pedramamini
Copy link
Copy Markdown
Collaborator

Thanks for this contribution, @reachrazamair! Really solid work across all four commits.

The leak fix is particularly well-done — UUID-anchored regexes to eliminate the greedy-backtrack ambiguity, defense-in-depth containment guards in both listeners, the documented source-output invariant with a regression test, and the fan-in dedupe fix. The adversarial test cases are a nice touch.

The themed scrollbar system, pipeline editor layout fixes, and GeneralTab cleanup are all clean and well-isolated.

One minor note flagged by Greptile (P2, no behavioral impact): in cue-fan-in-tracker.ts lines 122 and 136, the timeout log messages reference sources.length instead of resolvedSourceIds.size. Since you went to the trouble of deduping, the log denominator should reflect the actual deduped count. Not blocking — can be addressed in a follow-up if you prefer.

LGTM — approving.

Copy link
Copy Markdown

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/renderer/components/Settings/tabs/GeneralTab.tsx (1)

162-165: ⚠️ Potential issue | 🟠 Major

Report sync-load failures to Sentry (not console only).

This catch handles UI fallback, but it currently only logs to console, so production failures won’t be captured with context.

Proposed fix
 import { useState, useEffect, useCallback } from 'react';
+import { captureException } from '../../../utils/sentry';
 ...
 			.catch((err) => {
-				console.error('Failed to load sync settings:', err);
+				captureException(err instanceof Error ? err : new Error(String(err)), {
+					extra: { context: 'GeneralTab: load sync settings' },
+				});
 				setSyncError('Failed to load storage settings');
 			});

As per coding guidelines: src/**/*.{ts,tsx} — “Do not silently swallow errors… Use Sentry utilities (captureException, captureMessage) from src/utils/sentry.ts for explicit error reporting with context.”

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

In `@src/renderer/components/Settings/tabs/GeneralTab.tsx` around lines 162 - 165,
The catch block that currently console.errors in the sync settings load (the
.catch(...) near setSyncError in GeneralTab.tsx) must report the error to
Sentry: import the Sentry helper (captureException) from src/utils/sentry.ts and
call captureException(err, { extra: { context: 'Failed to load sync/settings in
GeneralTab' } }) (and optionally captureMessage with a short string) before or
after calling setSyncError('Failed to load storage settings'), so production
failures are sent to Sentry with context instead of only being logged to
console.
src/renderer/hooks/ui/useThemeStyles.ts (1)

136-139: ⚠️ Potential issue | 🟠 Major

Guard non-Element scroll targets before classList access.

On Line 137, e.target is force-cast to Element; if the target is not an Element, classList access can throw and break scrollbar-state updates.

Proposed fix
-		const handleScroll = (e: Event) => {
-			const target = e.target as Element;
-			if (!target.classList.contains('scrollbar-thin')) return;
+		const handleScroll = (e: Event) => {
+			const target = e.target;
+			if (!(target instanceof Element) || !target.classList.contains('scrollbar-thin')) {
+				return;
+			}
 
 			// Batch updates via requestAnimationFrame to avoid blocking scroll
 			pendingUpdates.add(target);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/hooks/ui/useThemeStyles.ts` around lines 136 - 139, The handler
handleScroll currently force-casts e.target to Element and then accesses
classList, which can throw if the target isn't an Element; update handleScroll
to first check the runtime type (e.g., if (!(e.target instanceof Element))
return) before accessing classList, then proceed with the existing classname
check and logic so non-Element events are safely ignored.
🧹 Nitpick comments (2)
src/main/process-listeners/exit-listener.ts (1)

463-477: Log this containment drop before returning.

This guard is load-bearing, but right now a malformed group-chat-* exit just disappears. If the parser or regexes regress again, production users will have no breadcrumb for why those exits vanished even though logger is already available in this file.

Suggested change
 		if (isGroupChatSession) {
+			logger.warn(
+				'[GroupChat] Unrecognized group-chat sessionId shape on exit; dropping to prevent cross-domain leak',
+				'ProcessListener',
+				{ sessionId, exitCode: code }
+			);
 			return;
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/process-listeners/exit-listener.ts` around lines 463 - 477, The
guard using isGroupChatSession currently returns silently; add a log statement
using the existing logger immediately before the return so malformed
"group-chat-*" exits are recorded (e.g., logger.warn or logger.info with context
like the sessionId and a message about containment drop) in the function in
exit-listener.ts where isGroupChatSession is checked; ensure the log is concise
and preserves the early-return behavior.
src/main/cue/cue-fan-in-tracker.ts (1)

94-103: Use the deduped source count in timeout logs too.

The tracker now waits on resolvedSourceIds, but these timeout messages still interpolate sources.length. With a config like ['Agent A', 'agent-a', 'Agent B'], a timeout will log 1/3 even though only two unique sources exist, which makes the new dedupe behavior look broken while debugging.

Suggested change
 			const resolvedSourceIds = resolveSourcesToIds(sources);
+			const expectedSourceCount = resolvedSourceIds.size;
 			const timedOutSources: string[] = [];
 			for (const resolvedId of resolvedSourceIds) {
 				if (!completedIds.has(resolvedId)) {
 					timedOutSources.push(resolvedId);
 				}
@@
 			deps.onLog(
 				'cue',
-				`[CUE] Fan-in "${sub.name}" timed out (continue mode) — firing with ${completedNames.length}/${sources.length} sources`
+				`[CUE] Fan-in "${sub.name}" timed out (continue mode) — firing with ${completedNames.length}/${expectedSourceCount} sources`
 			);
@@
 			deps.onLog(
 				'cue',
-				`[CUE] Fan-in "${sub.name}" timed out (break mode) — ${completedNames.length}/${sources.length} completed, waiting for: ${timedOutSources.join(', ')}`
+				`[CUE] Fan-in "${sub.name}" timed out (break mode) — ${completedNames.length}/${expectedSourceCount} completed, waiting for: ${timedOutSources.join(', ')}`
 			);

Also applies to: 120-136

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

In `@src/main/cue/cue-fan-in-tracker.ts` around lines 94 - 103, Timeout logs still
use sources.length instead of the deduplicated resolvedSourceIds count, causing
misleading "x/y" messages; update any logging or interpolations that currently
reference sources.length (notably where resolvedSourceIds is computed in
resolveSourcesToIds and where timedOutSources is built) to use
resolvedSourceIds.length (and if needed resolvedSourceIds.size logic) so the
reported total reflects the deduped source count—apply the same replacement for
the later block around the 120-136 region that also logs timeouts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/__tests__/main/cue/cue-completion-chains.test.ts`:
- Around line 211-234: The test creates a CueEngine, calls engine.start() but
never stops it which leaves background state; update the test (in
cue-completion-chains.test.ts) to call engine.stop() after notifyAgentCompleted
so the engine heartbeat/watcher is cleaned up, and add an assertion mirroring
the preceding regression check to verify event.payload.outputTruncated === false
alongside the existing expect(event.payload.sourceOutput).toBe(''); locate the
test by the use of createMockConfig, new CueEngine(deps), engine.start(),
engine.notifyAgentCompleted('agent-a') and modify it to call engine.stop() and
assert outputTruncated is false on the event payload.

In `@src/main/group-chat/session-parser.ts`:
- Around line 39-42: The code is incorrectly stripping "-recovery" from
UUID-suffixed participant IDs; in session-parser.ts where you use
sessionId.match(REGEX_PARTICIPANT_UUID) and set participantName from
uuidMatch[2], stop removing the suffix — remove the .replace(/-recovery$/, '')
so participantName is assigned directly from uuidMatch[2]; if you need to
normalize timestamp-form IDs, handle that separately in the timestamp-parsing
branch instead of altering the UUID branch.

In `@src/renderer/index.css`:
- Around line 74-76: The scrollbar track color is hardcoded to "transparent" in
the WebKit and fallback rules, so the --scrollbar-track token set by
useThemeStyles is never used; update the track rules (e.g., the
*::-webkit-scrollbar-track selector and the corresponding non-WebKit
scrollbar-track rules around the other track blocks) to use
var(--scrollbar-track) instead of "transparent" so the theme token is applied
consistently across WebKit and Firefox fallbacks.

---

Outside diff comments:
In `@src/renderer/components/Settings/tabs/GeneralTab.tsx`:
- Around line 162-165: The catch block that currently console.errors in the sync
settings load (the .catch(...) near setSyncError in GeneralTab.tsx) must report
the error to Sentry: import the Sentry helper (captureException) from
src/utils/sentry.ts and call captureException(err, { extra: { context: 'Failed
to load sync/settings in GeneralTab' } }) (and optionally captureMessage with a
short string) before or after calling setSyncError('Failed to load storage
settings'), so production failures are sent to Sentry with context instead of
only being logged to console.

In `@src/renderer/hooks/ui/useThemeStyles.ts`:
- Around line 136-139: The handler handleScroll currently force-casts e.target
to Element and then accesses classList, which can throw if the target isn't an
Element; update handleScroll to first check the runtime type (e.g., if
(!(e.target instanceof Element)) return) before accessing classList, then
proceed with the existing classname check and logic so non-Element events are
safely ignored.

---

Nitpick comments:
In `@src/main/cue/cue-fan-in-tracker.ts`:
- Around line 94-103: Timeout logs still use sources.length instead of the
deduplicated resolvedSourceIds count, causing misleading "x/y" messages; update
any logging or interpolations that currently reference sources.length (notably
where resolvedSourceIds is computed in resolveSourcesToIds and where
timedOutSources is built) to use resolvedSourceIds.length (and if needed
resolvedSourceIds.size logic) so the reported total reflects the deduped source
count—apply the same replacement for the later block around the 120-136 region
that also logs timeouts.

In `@src/main/process-listeners/exit-listener.ts`:
- Around line 463-477: The guard using isGroupChatSession currently returns
silently; add a log statement using the existing logger immediately before the
return so malformed "group-chat-*" exits are recorded (e.g., logger.warn or
logger.info with context like the sessionId and a message about containment
drop) in the function in exit-listener.ts where isGroupChatSession is checked;
ensure the log is concise and preserves the early-return behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a61945ae-16d2-45a4-b067-4f90e228be6f

📥 Commits

Reviewing files that changed from the base of the PR and between e8121a6 and 14cbb62.

📒 Files selected for processing (21)
  • src/__tests__/main/constants.test.ts
  • src/__tests__/main/cue/cue-completion-chains.test.ts
  • src/__tests__/main/group-chat/session-parser.test.ts
  • src/__tests__/main/performance-optimizations.test.ts
  • src/__tests__/renderer/components/ScrollArea.test.tsx
  • src/__tests__/renderer/hooks/useThemeStyles.test.ts
  • src/main/constants.ts
  • src/main/cue/cue-engine.ts
  • src/main/cue/cue-fan-in-tracker.ts
  • src/main/group-chat/session-parser.ts
  • src/main/process-listeners/__tests__/data-listener.test.ts
  • src/main/process-listeners/__tests__/exit-listener.test.ts
  • src/main/process-listeners/data-listener.ts
  • src/main/process-listeners/exit-listener.ts
  • src/renderer/components/CuePipelineEditor/panels/AgentConfigPanel.tsx
  • src/renderer/components/CuePipelineEditor/panels/EdgePromptRow.tsx
  • src/renderer/components/CuePipelineEditor/panels/NodeConfigPanel.tsx
  • src/renderer/components/ScrollArea.tsx
  • src/renderer/components/Settings/tabs/GeneralTab.tsx
  • src/renderer/hooks/ui/useThemeStyles.ts
  • src/renderer/index.css

Seven independent review findings across the four prior commits, all
verified against current code before fixing.

Inline:
- cue-completion-chains.test: the "produces empty sourceOutput when
  completionData is omitted" regression test was leaking engine state by
  never calling engine.stop(), and only checked sourceOutput without
  also pinning outputTruncated. Both fixed.
- session-parser: stop stripping "-recovery" in the UUID branch.
  Production never combines UUID suffix with -recovery (recovery
  sessions are always minted with timestamp suffix in
  group-chat-router.respawnParticipantWithRecovery), so the strip was
  dead code that would silently truncate a legitimate participant name
  ending in "-recovery". The timestamp branch still handles recovery
  correctly. Test fixture updated to assert the corrected behavior.
- index.css: --scrollbar-track was injected by useThemeStyles but never
  consumed (track was hardcoded to transparent in three places). Wired
  it through *::-webkit-scrollbar-track, *::-webkit-scrollbar-corner,
  the .scrollbar-thin track, and the Firefox scrollbar-color fallback,
  with a `transparent` default so initial paint is preserved.

Outside diff:
- GeneralTab: the sync-settings load .catch() only console.error'd.
  Now also reports to Sentry via captureException with a context tag
  per the project's error-handling policy in CLAUDE.md.
- useThemeStyles handleScroll: e.target was force-cast to Element,
  which would crash on Document/Window scroll events. Added an
  `instanceof Element` guard before classList access.

Nits:
- cue-fan-in-tracker: timeout logs (continue + break modes) referenced
  raw sources.length instead of the deduped resolvedSourceIds.size.
  With duplicate references in YAML this would print misleading "1/2
  completed" when fan-in is actually waiting for 0 more. Hoisted
  totalSources once and used it in both log strings.
- exit-listener: the group-chat containment guard returned silently;
  added a logger.warn so production drops surface in logs with
  sessionId + exitCode context.

Validated:
- npm run lint (3x tsc) clean
- npm run lint:eslint clean
- 204 tests across the 8 impacted suites pass
Copy link
Copy Markdown

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/__tests__/main/group-chat/session-parser.test.ts`:
- Around line 15-17: The comment saying "Real UUID v4s for test fixtures" is
inaccurate because GC_ID_2 is a v1 UUID; update the test to either replace
GC_ID_2 with a true v4 value or change the comment to say "Real UUIDs for test
fixtures" so it no longer asserts v4; edit the constants GC_ID and GC_ID_2 (or
the surrounding comment) in the test file to keep tests correct and the comment
truthful.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8fa12a22-fb12-43dc-b975-037147d77299

📥 Commits

Reviewing files that changed from the base of the PR and between 14cbb62 and 23f9b41.

📒 Files selected for processing (8)
  • src/__tests__/main/cue/cue-completion-chains.test.ts
  • src/__tests__/main/group-chat/session-parser.test.ts
  • src/main/cue/cue-fan-in-tracker.ts
  • src/main/group-chat/session-parser.ts
  • src/main/process-listeners/exit-listener.ts
  • src/renderer/components/Settings/tabs/GeneralTab.tsx
  • src/renderer/hooks/ui/useThemeStyles.ts
  • src/renderer/index.css
✅ Files skipped from review due to trivial changes (1)
  • src/main/process-listeners/exit-listener.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/tests/main/cue/cue-completion-chains.test.ts
  • src/renderer/components/Settings/tabs/GeneralTab.tsx
  • src/renderer/index.css
  • src/main/group-chat/session-parser.ts

GC_ID_2 (6ba7b810-9dad-11d1-80b4-00c04fd430c8) is the RFC 4122 namespace
UUID — a v1, not v4. The regex in constants.ts accepts the canonical
8-4-4-4-12 hex shape regardless of version, so the test still works
correctly, but the comment was inaccurate. Updated to "Real UUIDs" and
documented that the regex is version-agnostic.

No fixture or behavior changes.
@reachrazamair
Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 6, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@reachrazamair reachrazamair merged commit 3347b9c into rc Apr 6, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants