fix: auto-scroll follows thinking streams + Codex tool display#410
Conversation
…erver The auto-scroll effect previously depended on filteredLogs.length, which missed in-place text updates during thinking/tool streaming (the array length doesn't change when thinking chunks update the last log entry). Replace with a MutationObserver on the scroll container that detects all DOM mutations — childList for new nodes, characterData for text changes, subtree to watch all descendants. This catches both new log entries and in-place thinking stream growth. Also adds a clarifying comment to the detect-new-messages effect explaining why it intentionally still uses filteredLogs.length (unread badge counts should only increment on new entries, not text updates). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ausing Adds isProgrammaticScrollRef that the MutationObserver scrollToBottom sets before calling scrollTo() and clears via queueMicrotask. The scroll handler checks this flag before pausing auto-scroll, preventing the race condition where a programmatic scroll triggers onScroll with a stale scrollTop and falsely sets autoScrollPaused=true. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…scroll guard Add thinking stream auto-scroll tests verifying: - In-place text growth triggers auto-scroll via MutationObserver - Unread badge does not increment on thinking text growth - Tool event appended after thinking triggers auto-scroll Add programmatic scroll guard test verifying: - Genuine user scroll-up still pauses auto-scroll Includes MutationObserver mock (class-based) for JSDOM compatibility. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…, handle array commands Add lastToolName tracking in Codex parser to carry tool name from tool_call to tool_result events (Codex emits these as separate item.completed events). Fix TerminalOutput tool detail extraction to handle array-typed command args (Codex uses ['ls', '-la'] format). Add Codex-specific arg patterns (path, cmd, code, content). Truncate large tool outputs to 10K chars. Fixes missing command details in Codex bash tool execution display. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 📝 WalkthroughWalkthroughAdds tool-name carryover and output truncation to CodexOutputParser, replaces filteredLogs-triggered auto-scroll with a MutationObserver-driven flow in TerminalOutput (with a programmatic-scroll guard and Codex command/content handling), and adds test scaffolding for MutationObserver plus parser tests for carryover and truncation. Changes
Sequence Diagram(s)sequenceDiagram
participant User as User
participant Terminal as TerminalOutput
participant Observer as MutationObserver
participant Container as Scroll Container
User->>Container: Scroll away from bottom
Terminal->>Terminal: Detect user scroll -> pause auto-scroll
Note over Observer,Container: Streaming updates mutate DOM
Observer->>Terminal: Mutation callback
Terminal->>Terminal: shouldAutoScroll() false -> do not scroll
User->>Terminal: Scroll to bottom or re-enable auto-scroll
Terminal->>Terminal: Set isProgrammaticScrollRef = true
Terminal->>Container: scrollToBottom()
Terminal->>Terminal: Clear isProgrammaticScrollRef
sequenceDiagram
participant ToolCall as tool_call Event
participant Parser as CodexOutputParser
participant Storage as lastToolName
participant ToolResult as tool_result Event
ToolCall->>Parser: Receive tool_call (toolName='shell')
Parser->>Storage: Store lastToolName = 'shell'
Parser->>Parser: Emit tool_call event
ToolResult->>Parser: Receive tool_result (no toolName)
Parser->>Storage: Read lastToolName ('shell')
Parser->>Parser: Emit tool_result with toolName='shell' and status='completed'
Parser->>Storage: Clear lastToolName
ToolResult->>Parser: Receive another tool_result
Parser->>Storage: Read lastToolName (null)
Parser->>Parser: Emit tool_result with toolName=undefined
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
Greptile SummaryThis PR fixes two critical auto-scroll bugs and enhances Codex tool display. The MutationObserver-based approach elegantly solves thinking stream auto-scroll failures by observing DOM mutations instead of React state, and the Key Changes:
Architecture Highlights:
Confidence Score: 5/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[New log entry or text update] --> B{Input Mode?}
B -->|Terminal| C[Auto-scroll enabled]
B -->|AI| D{autoScrollAiMode?}
D -->|No| E[No auto-scroll]
D -->|Yes| F{autoScrollPaused?}
F -->|Yes| E
F -->|No| C
C --> G[MutationObserver detects change]
G --> H[Calls scrollToBottom]
H --> I[Set isProgrammaticScrollRef = true]
I --> J[scrollTo top: scrollHeight, behavior: auto]
J --> K[onScroll handler fires synchronously]
K --> L{isProgrammaticScrollRef?}
L -->|Yes| M[Skip pause logic]
L -->|No| N{At bottom?}
N -->|No| O[Set autoScrollPaused = true]
N -->|Yes| P[Clear new message badge]
M --> Q[queueMicrotask clears flag]
Q --> R[Ready for next interaction]
S[User scrolls up] --> T[onScroll handler]
T --> U{isProgrammaticScrollRef?}
U -->|No genuine user scroll| V[Set autoScrollPaused = true]
U -->|Yes| W[Ignore - programmatic]
X[filteredLogs.length changes] --> Y{New entries added?}
Y -->|Yes| Z{At bottom?}
Z -->|No| AA[Increment unread badge]
Z -->|Yes| AB[Update read state]
Y -->|No in-place update| AC[No badge change]
Last reviewed commit: c276056 |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/main/parsers/codex-output-parser.ts`:
- Around line 364-368: Replace the silent catch around Buffer.from(output) so it
reports the caught error to Sentry before falling back: add an import from
'../utils/sentry' and in the catch block capture the exception (e.g.,
Sentry.captureException(err)) where err is the caught error, then set decoded =
output.toString(); ensure you reference the same decoded and output variables
and the Buffer.from(...) call when locating the code to change.
In `@src/renderer/components/TerminalOutput.tsx`:
- Around line 1489-1502: The guard is cleared too early via queueMicrotask
causing a race with the throttled onScroll; update the logic around
isProgrammaticScrollRef and the throttled handler (the ref,
scrollContainerRef.current.scrollTo call, and the useThrottledCallback-based
onScroll) so the guard remains true for the throttle window: either postpone
clearing the guard by using the same throttle delay (e.g., setTimeout with the
throttle duration) after calling scrollTo, or move the guard-reset into the
throttled onScroll callback itself and check isProgrammaticScrollRef at the
start of that throttled handler before it clears the flag; adjust whichever
approach you pick to use the existing throttle duration used by
useThrottledCallback to avoid the race.
…tion Address CodeRabbit review on PR RunMaestro#410: 1. codex-output-parser.ts: Report Buffer.from() decode failures to Sentry with context (operation, outputType, outputLength) instead of silently swallowing the error. 2. TerminalOutput.tsx: Fix race condition where queueMicrotask cleared isProgrammaticScrollRef before the throttled scroll handler (16ms delay) could check it. The guard flag is now consumed (checked and cleared) inside handleScrollInner itself, eliminating the timing dependency between microtasks and the throttle window. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Addressed Review Findings —
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/main/parsers/codex-output-parser.ts`:
- Around line 167-171: The parser currently uses lastToolName to associate
tool_result with the preceding tool_call which breaks when events are
out-of-order; add an optional tool_call_id?: string field to the CodexItem
interface, extract the tool_call_id from incoming Codex output in parseJsonLine
(or the code that builds CodexItem), and replace lastToolName-based pairing with
an ID map (e.g., toolCallIdToName: Map<string,string>) that records tool_call id
→ tool name when handling items of type 'tool_call' and looks up/removes that
entry when handling 'tool_result'; update any places using lastToolName (and the
lastToolName field itself) to use this id-based map so tool_result maps
correctly and the existing codex-session-storage cleanup logic (which expects
tool_call_id) receives the id on CodexItem.
In `@src/renderer/components/TerminalOutput.tsx`:
- Around line 1372-1382: The guard isProgrammaticScrollRef.current can remain
true if scrollToBottom is a no-op, so update the scroll handler around
autoScrollAiMode (the throttled handler that currently checks
isProgrammaticScrollRef.current and calls setAutoScrollPaused(true)) to always
clear isProgrammaticScrollRef.current on any scroll event (not only when true)
and additionally, in the code path that performs scrollToBottom, start a short
fallback timer (>= the throttle delay, e.g. 16ms) that clears
isProgrammaticScrollRef.current after the delay to cover no-scroll cases; ensure
the timer is cancelled or reset on subsequent programmatic scrolls to avoid
races and reuse the same references (isProgrammaticScrollRef, scrollToBottom,
setAutoScrollPaused) so behavior remains consistent.
| // Track tool name from tool_call to carry over to tool_result | ||
| // (Codex emits tool_call and tool_result as separate item.completed events, | ||
| // but tool_result doesn't include the tool name) | ||
| private lastToolName: string | null = null; | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's look at the codex-output-parser.ts file to understand the context
wc -l src/main/parsers/codex-output-parser.tsRepository: RunMaestro/Maestro
Length of output: 105
🏁 Script executed:
# Read the relevant sections of the file
sed -n '160,180p' src/main/parsers/codex-output-parser.ts
sed -n '285,330p' src/main/parsers/codex-output-parser.tsRepository: RunMaestro/Maestro
Length of output: 1903
🏁 Script executed:
# Search for test files or sample data related to Codex
find . -type f \( -name "*codex*" -o -name "*test*" -o -name "*spec*" \) | head -20Repository: RunMaestro/Maestro
Length of output: 853
🏁 Script executed:
# Look for tool_call/tool_result in the codebase
rg "tool_call|tool_result" -t ts -t tsx -t json -C 3 | head -100Repository: RunMaestro/Maestro
Length of output: 89
🏁 Script executed:
# Search for codex parser tests more specifically
find . -type f -name "*.test.ts" -o -name "*.spec.ts" | xargs grep -l "codex\|CodexOutputParser" 2>/dev/null | head -10Repository: RunMaestro/Maestro
Length of output: 570
🏁 Script executed:
# Search for Codex type definitions or interfaces
rg "interface.*Codex|type.*Codex|tool_call|tool_result" -t ts --max-count 20Repository: RunMaestro/Maestro
Length of output: 9060
🏁 Script executed:
# Look for any fixture or test data files
find . -type f \( -name "*fixture*" -o -name "*mock*" -o -name "*.json" \) | grep -i codexRepository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
# Check the entire codex-output-parser.ts file structure
cat -n src/main/parsers/codex-output-parser.ts | head -100Repository: RunMaestro/Maestro
Length of output: 3967
🏁 Script executed:
# Read the test file to understand test coverage
cat -n src/__tests__/main/parsers/codex-output-parser.test.tsRepository: RunMaestro/Maestro
Length of output: 20682
🏁 Script executed:
# Check the CodexItem interface definition
sed -n '1,200p' src/main/parsers/codex-output-parser.ts | grep -A 20 "interface CodexItem"Repository: RunMaestro/Maestro
Length of output: 505
🏁 Script executed:
# Look for more context on tool_call_id usage
rg "tool_call_id" -B 2 -A 2 src/main/parsers/ src/__tests__/main/parsers/Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
# Search for Codex documentation references
rg "codex|Codex" src/main/parsers/codex-output-parser.ts | head -10Repository: RunMaestro/Maestro
Length of output: 501
🏁 Script executed:
# Look at how codex-session-storage actually uses tool_call_id
sed -n '1,50p' src/main/storage/codex-session-storage.ts
sed -n '150,250p' src/main/storage/codex-session-storage.ts | grep -B 5 -A 5 tool_call_idRepository: RunMaestro/Maestro
Length of output: 1740
🏁 Script executed:
# Check if there's any real Codex output sample or fixture
find . -type f \( -name "*.json" -o -name "*.jsonl" -o -name "*.txt" \) -exec grep -l "item.completed.*tool_call\|tool_call.*tool_result" {} \; 2>/dev/nullRepository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
# Verify tool_call_id is actually in CodexItem or if it's missing
rg "tool_call_id" src/main/parsers/codex-output-parser.ts src/__tests__/main/parsers/codex-output-parser.test.tsRepository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
# Get the actual codex-session-storage code that uses tool_call_id
sed -n '150,200p' src/main/storage/codex-session-storage.tsRepository: RunMaestro/Maestro
Length of output: 1481
🏁 Script executed:
# Check the CodexItem definition more carefully for any ID field
sed -n '1,150p' src/main/parsers/codex-output-parser.ts | grep -A 30 "interface CodexItem"Repository: RunMaestro/Maestro
Length of output: 421
🏁 Script executed:
# Search for any evidence of Codex sending tool_call_id or similar ID mapping
rg "tool_call_id\|item\.id.*tool" -C 3 src/main/storage/ src/main/parsers/Repository: RunMaestro/Maestro
Length of output: 44
🏁 Script executed:
# Read more of codex-session-storage.ts to see tool_call_id context
wc -l src/main/storage/codex-session-storage.ts
sed -n '200,300p' src/main/storage/codex-session-storage.tsRepository: RunMaestro/Maestro
Length of output: 3589
🏁 Script executed:
# Search for where tool_call_id is mentioned in session storage
grep -n "tool_call_id" src/main/storage/codex-session-storage.tsRepository: RunMaestro/Maestro
Length of output: 259
🏁 Script executed:
# Check the actual implementation of parseJsonLine to see if it ever extracts or stores IDs
sed -n '200,350p' src/main/parsers/codex-output-parser.tsRepository: RunMaestro/Maestro
Length of output: 3894
🏁 Script executed:
# Check what interface at line 1373 has tool_call_id
sed -n '1360,1380p' src/main/storage/codex-session-storage.tsRepository: RunMaestro/Maestro
Length of output: 548
🏁 Script executed:
# Get the full context of how tool_call_id is used in cleanup logic
sed -n '1455,1475p' src/main/storage/codex-session-storage.tsRepository: RunMaestro/Maestro
Length of output: 865
🏁 Script executed:
# Check if there are any comments in the parser about Codex output format or IDs
sed -n '1,50p' src/main/parsers/codex-output-parser.tsRepository: RunMaestro/Maestro
Length of output: 1870
🏁 Script executed:
# Search for any references to Codex CLI schema or what fields it actually outputs
rg "tool_call_id|Codex.*output|Codex.*schema" -C 2 | head -50Repository: RunMaestro/Maestro
Length of output: 3840
🏁 Script executed:
# Check if there's a reference link to Codex CLI documentation
rg "github.com/openai/codex" -B 5 -A 5Repository: RunMaestro/Maestro
Length of output: 3526
🏁 Script executed:
# Look for any TODO or FIXME related to tool_call_id or ID mapping
rg "TODO|FIXME" src/main/parsers/codex-output-parser.ts src/main/storage/codex-session-storage.tsRepository: RunMaestro/Maestro
Length of output: 44
Add tool_call_id field to CodexItem interface and use ID-based mapping for tool_call/tool_result pairing.
The current implementation relies on strict sequencing via lastToolName, which is fragile if Codex emits multiple tool_calls before tool_results or delivers them out of order. The codex-session-storage.ts cleanup logic already expects tool_call_id to be available (line 1462), but the parser doesn't extract or surface it. Add tool_call_id?: string to the CodexItem interface, extract it from Codex output, and use it for mapping tool results to their corresponding tool calls instead of relying solely on sequential tracking.
Test case showing current failure mode (lines 427–443):
it('should reset lastToolName after tool_result consumption', () => {
const p = new CodexOutputParser();
// tool_call → tool_result (consumes name) → another tool_result (no name)
p.parseJsonLine(JSON.stringify({
type: 'item.completed',
item: { type: 'tool_call', tool: 'shell', args: {} },
}));
p.parseJsonLine(JSON.stringify({
type: 'item.completed',
item: { type: 'tool_result', output: 'ok' },
}));
const orphan = p.parseJsonLine(JSON.stringify({
type: 'item.completed',
item: { type: 'tool_result', output: 'orphan' },
}));
expect(orphan?.toolName).toBeUndefined();
});🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/parsers/codex-output-parser.ts` around lines 167 - 171, The parser
currently uses lastToolName to associate tool_result with the preceding
tool_call which breaks when events are out-of-order; add an optional
tool_call_id?: string field to the CodexItem interface, extract the tool_call_id
from incoming Codex output in parseJsonLine (or the code that builds CodexItem),
and replace lastToolName-based pairing with an ID map (e.g., toolCallIdToName:
Map<string,string>) that records tool_call id → tool name when handling items of
type 'tool_call' and looks up/removes that entry when handling 'tool_result';
update any places using lastToolName (and the lastToolName field itself) to use
this id-based map so tool_result maps correctly and the existing
codex-session-storage cleanup logic (which expects tool_call_id) receives the id
on CodexItem.
| } else if (autoScrollAiMode) { | ||
| // Pause auto-scroll when user scrolls away from bottom | ||
| setAutoScrollPaused(true); | ||
| if (isProgrammaticScrollRef.current) { | ||
| // This scroll event was triggered by our own scrollTo() call — | ||
| // consume the guard flag here inside the throttled handler to avoid | ||
| // the race where queueMicrotask clears the flag before a deferred | ||
| // throttled invocation fires (throttle delay is 16ms > microtask). | ||
| isProgrammaticScrollRef.current = false; | ||
| } else { | ||
| // Genuine user scroll away from bottom — pause auto-scroll | ||
| setAutoScrollPaused(true); | ||
| } |
There was a problem hiding this comment.
Guard can stick true when scrollToBottom doesn’t emit a scroll event.
If the scroll lands at bottom or is a no-op, isProgrammaticScrollRef stays true and the first user scroll away won’t pause auto-scroll. That can yank the user back on the next mutation. Consider clearing the guard on any scroll and add a short fallback timer after scrollToBottom (>= throttle delay) to handle no-scroll cases.
🛠️ Suggested fix
const handleScrollInner = useCallback(() => {
if (!scrollContainerRef.current) return;
const { scrollTop, scrollHeight, clientHeight } = scrollContainerRef.current;
// Consider "at bottom" if within 50px of the bottom
const atBottom = scrollHeight - scrollTop - clientHeight < 50;
+ const wasProgrammatic = isProgrammaticScrollRef.current;
+ if (wasProgrammatic) {
+ isProgrammaticScrollRef.current = false;
+ }
setIsAtBottom(atBottom);
@@
- } else if (autoScrollAiMode) {
- if (isProgrammaticScrollRef.current) {
- // This scroll event was triggered by our own scrollTo() call —
- // consume the guard flag here inside the throttled handler to avoid
- // the race where queueMicrotask clears the flag before a deferred
- // throttled invocation fires (throttle delay is 16ms > microtask).
- isProgrammaticScrollRef.current = false;
- } else {
- // Genuine user scroll away from bottom — pause auto-scroll
- setAutoScrollPaused(true);
- }
- }
+ } else if (autoScrollAiMode) {
+ if (!wasProgrammatic) {
+ // Genuine user scroll away from bottom — pause auto-scroll
+ setAutoScrollPaused(true);
+ }
+ } const scrollToBottom = () => {
if (!scrollContainerRef.current) return;
requestAnimationFrame(() => {
if (scrollContainerRef.current) {
// Set guard flag BEFORE scrollTo — the throttled scroll handler
// checks this flag and consumes it (clears it) when it fires,
// preventing the programmatic scroll from being misinterpreted
// as a user scroll-up that should pause auto-scroll.
isProgrammaticScrollRef.current = true;
scrollContainerRef.current.scrollTo({
top: scrollContainerRef.current.scrollHeight,
behavior: 'auto',
});
+ setTimeout(() => {
+ isProgrammaticScrollRef.current = false;
+ }, 32);
}
});
};Also applies to: 1494-1501
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/renderer/components/TerminalOutput.tsx` around lines 1372 - 1382, The
guard isProgrammaticScrollRef.current can remain true if scrollToBottom is a
no-op, so update the scroll handler around autoScrollAiMode (the throttled
handler that currently checks isProgrammaticScrollRef.current and calls
setAutoScrollPaused(true)) to always clear isProgrammaticScrollRef.current on
any scroll event (not only when true) and additionally, in the code path that
performs scrollToBottom, start a short fallback timer (>= the throttle delay,
e.g. 16ms) that clears isProgrammaticScrollRef.current after the delay to cover
no-scroll cases; ensure the timer is cancelled or reset on subsequent
programmatic scrolls to avoid races and reuse the same references
(isProgrammaticScrollRef, scrollToBottom, setAutoScrollPaused) so behavior
remains consistent.
When scrollTo() is called but the container is already at the bottom, the browser may not fire a scroll event. The guard flag stays true indefinitely, causing the next genuine user scroll-up to be silently consumed instead of pausing auto-scroll. Add a 32ms setTimeout fallback (2x the 16ms throttle window) that clears the flag if the throttled handler hasn't consumed it. If the handler does consume it first, the timeout harmlessly sets false again. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Addressed Review Round 2 —
|
Summary
filteredLogs.lengthauto-scroll dependency withMutationObserverto detect in-place content changes (thinking stream text growth, tool status updates)scrollTo()callstool_calltotool_result, handle array-typedcommandargs, add Codex-specific arg extraction, truncate large outputsMotivation
Auto-scroll stops following thinking/tool streams (Fixes #402, supersedes #406)
The auto-scroll feature (PR #390) depends on
filteredLogs.lengthto trigger scroll-to-bottom. However, thinking chunks update the last log entry's text in-place without changing the array length:Timeline of the bug:
PR #406 proposed a content-fingerprint memo (
lastVisibleLogActivityKey) that creates a composite key from the last log's metadata. This works but couples the scroll trigger to React's data model. Our approach uses a MutationObserver on the scroll container instead, which:filteredLogs,collapsedLogs, or log data shapesProgrammatic scroll falsely pauses auto-scroll (secondary race condition)
When the auto-scroll effect calls
scrollTo({ behavior: 'auto' }), the instant scroll fires theonScrollhandler synchronously. IfscrollHeightjust grew from new content but the scroll position hasn't propagated yet, the handler computesatBottom = falseand setsautoScrollPaused = true— pausing auto-scroll during a programmatic scroll. This creates intermittent freezes where the viewport stops following output mid-stream.Fixed with a ref-based guard (
isProgrammaticScrollRef) set beforescrollTo()and cleared viaqueueMicrotask. The scroll handler checks this flag before pausing.Codex tool executions show no command details
Codex bash tool entries render as just
bash ●/bash ✓with no information about what command was executed. Three sub-bugs:Missing tool name on completion: Codex emits
tool_callandtool_resultas separateitem.completedJSONL events. The parser transformstool_resultwithout atoolNamefield (onlytool_callitems includeitem.tool). StdoutHandler's gate (event.toolNamecheck) silently drops the event — the renderer never receives "completed" status.Array commands not rendered: Codex tool_call items use
args: { command: ['ls', '-la'] }(string array). The renderer'ssafeStr(toolInput.command)returnsnullfor non-strings, so no command detail is displayed. This bug is a new finding not addressed in PR #407's tool fix (commit451f8871), which addedlastToolNametracking and Codex arg patterns but didn't handle the arraycommandformat.No output truncation: Large tool outputs (e.g.,
catof a big file) create oversized log entries with no limit.Changes
Commit 1:
638db6ff— MutationObserver auto-scrollReplace the
filteredLogs.lengthdependency in the auto-scrolluseEffectwith aMutationObserveronscrollContainerRef:The observer is set up and torn down inside the effect, keyed on
[session.inputMode, autoScrollAiMode, autoScrollPaused]. ThefilteredLogs.lengthdependency is completely removed from this effect.Intentionally kept: The "detect new messages" effect and
handleScrollInnerstill usefilteredLogs.lengthfor unread badge counting and read state tracking — in-place text updates should NOT increment unread counts.Commit 2:
35b86947— Programmatic scroll guardisProgrammaticScrollRef(boolean ref)truebeforescrollTo(), clear viaqueueMicrotaskafter!isProgrammaticScrollRef.currentinhandleScrollInnerbefore settingautoScrollPaused = trueWhy
queueMicrotasknotsetTimeout(0): microtasks run after the current task's synchronous work but before the next macro task. Sincebehavior: 'auto'scrolls synchronously, theonScrollevent fires in the same task. The microtask clears the flag before any subsequent user scroll could fire.Commit 3:
fdf8329f— Tests4 new tests in
auto-scroll.test.tsx:Includes a class-based
MockMutationObserverfor JSDOM compatibility.Commit 4:
c276056c— Codex tool displayParser (
codex-output-parser.ts):lastToolNamefield: stored ontool_call, carried totool_result, cleared after useMAX_TOOL_OUTPUT_LENGTH(10K chars) truncation indecodeToolOutputRenderer (
TerminalOutput.tsx):safeCommand()helper: handles both string and string-arraycommandvalues (joins arrays with spaces)truncate()helper for length-limited displaypath,cmd,code,contentsafeStr(toolInput.command)→safeCommand(toolInput.command)Tests (
codex-output-parser.test.ts):tool_calltotool_resultlastToolNamereset after consumption (no stale names)Files Changed (4 files, +375 / -29)
src/renderer/components/TerminalOutput.tsxisProgrammaticScrollRefguard,safeCommand()helper, Codex arg extractionsrc/main/parsers/codex-output-parser.tslastToolNametracking, output truncationsrc/__tests__/renderer/components/auto-scroll.test.tsxsrc/__tests__/main/parsers/codex-output-parser.test.tsRelated
lastToolName+ truncation exist there too, but arraycommandhandling is new here)Test plan
npm run lint— cleannpm run test— 468 test files, 19,984 tests pass🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Tests