Repository navigation
fix(terminal): mask the agent composer's dim placeholder during a preedit - #17377
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📥 CommitsReviewing files that changed from the base of the PR and between 7f822a7 and 1f5fcec808668373591e33f04c31395aea1fece9. 📒 Files selected for processing (12)
🚧 Files skipped from review as they are similar to previous changes (11)
Included review availability: Your plan provides up to 10 included reviews per hour; 5 remain after this review. 📝 WalkthroughWalkthroughThe change adds structural terminal cursor-context types and symmetric row-window reads. It separates composer placeholder detection from draft detection. Renderer panes now mask eligible Codex and Claude placeholders during active IME composition and remove the mask on session end, blur, or disposal. CSS hides the composition remainder under the mask. Unit tests, Korean preedit e2e coverage, and reliability gates validate masking, typed text protection, cleanup, and bounded session handling. Merge Risk: 🔵 Low · up to The PR safely narrows placeholder masking to recognized empty rows and includes targeted validation, but two new reliability gates are not backed by corresponding recorded runs, leaving the new coverage audit trail incomplete; the change is mergeable with explicit owner follow-up. 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Full details: Description checkExplanation The description explains the change, rationale, implementation scope, dependency, testing, mutation results, and AI use. It omits required template content, including a linked issue, visual proof or an explicit N/A explanation, testing checklist details, review notes, and the final checklist. Resolution Add the missing template sections. Provide a linked issue after “Fixes #”. Attach before-and-after visual proof, or write N/A with a reason. Complete the testing checklist with platforms tested. Complete the review, boundary, notes, and checklist sections, or mark each item as not applicable with an explanation. Full details: Docstring CoverageExplanation Docstring coverage is 20.69% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 29 functions across 17 files. (2 skipped: 2 unsupported.)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
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. Comment |
a9e9931 to
0ec925c
Compare
Greptile SummaryThis PR masks structurally recognized Codex and Claude placeholder tails while an IME preedit owns the composer row.
Confidence Score: 5/5The PR appears safe to merge, with no blocking failure remaining in the current review scope. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| src/renderer/src/components/terminal-pane/terminal-ime-composer-placeholder-mask.ts | Adds bounded composition-session ownership, render-time reclassification, and blur/disposal cleanup for placeholder masking. |
| src/shared/terminal-composer-draft.ts | Refactors composer detection into a shared classifier and exposes stock-placeholder detection without suppressing rows containing typed text. |
| src/shared/terminal-cursor-line-context.ts | Moves the cursor-context reader into shared code and generalizes its terminal source interface for renderer and headless use. |
| src/renderer/src/assets/terminal.css | Hides only the patched xterm composition remainder while the terminal owns a verified composer placeholder. |
| src/renderer/src/components/terminal-pane/use-terminal-pane-lifecycle.ts | Installs and disposes the placeholder-mask binding with each terminal pane. |
| tests/e2e/terminal-korean-midline-preedit-occlusion.spec.ts | Extends rendered geometry coverage to verify that a Codex placeholder remains masked during Korean preedit. |
Sequence Diagram
sequenceDiagram
participant IME as IME / xterm
participant Mask as Placeholder mask binding
participant Reader as Cursor context reader
participant Classifier as Composer classifier
participant DOM as Terminal DOM
IME->>Mask: composition session start
Mask->>Reader: read visible cursor context
Reader->>Classifier: classify composer row
Classifier-->>Mask: stock placeholder and no typed text
Mask->>DOM: add ownership class
DOM->>DOM: hide composition remainder
IME->>Mask: render updates
Mask->>Reader: re-evaluate ownership
IME->>Mask: session end or blur
Mask->>DOM: remove ownership class
Reviews (4): Last reviewed commit: "fix(terminal): mask the agent composer's..." | Re-trigger Greptile
0ec925c to
5c7d395
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 12c50b2b-d205-42ab-806d-626ac07a57b9
📥 Commits
Reviewing files that changed from the base of the PR and between 58a52a8 and 0ec925ce49bb886ae1cc027aadd0cca484404982.
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (21)
config/patches/@xterm__xterm@6.1.0-beta.287.patchconfig/patches/xterm-src/@xterm__xterm@6.1.0-beta.287.src.patchconfig/reliability-gates.jsoncsrc/main/daemon/headless-emulator.tssrc/main/daemon/terminal-cursor-line-context.test.tssrc/renderer/src/assets/terminal.csssrc/renderer/src/components/terminal-pane/terminal-ime-composer-placeholder-mask.test.tssrc/renderer/src/components/terminal-pane/terminal-ime-composer-placeholder-mask.tssrc/renderer/src/components/terminal-pane/terminal-ime-xterm-midline-preedit-tail.test.tssrc/renderer/src/components/terminal-pane/terminal-ime-xterm-resumed-preedit-visibility.test.tssrc/renderer/src/components/terminal-pane/terminal-ime-xterm-trailing-preedit-occlusion.test.tssrc/renderer/src/components/terminal-pane/use-terminal-pane-lifecycle.tssrc/renderer/src/lib/pane-manager/terminal-ime-candidate-anchor.test.tssrc/renderer/src/lib/pane-manager/terminal-ime-candidate-anchor.tssrc/shared/terminal-composer-draft.test.tssrc/shared/terminal-composer-draft.tssrc/shared/terminal-cursor-line-context.tstests/e2e/terminal-ime-midline-occlusion-probe.tstests/e2e/terminal-ime-pane-arena.tstests/e2e/terminal-korean-endofrow-preedit-cell-span.spec.tstests/e2e/terminal-korean-midline-preedit-occlusion.spec.ts
Included review availability: Your plan provides up to 10 included reviews per hour; 0 remain after this review.
| "coverageNotes": "Local macOS and containerized Linux evidence, deterministic renderer-unit coverage for the Linux/Sogou candidate-key policy including the legacy orphaned-keyup fallback, xterm preedit visibility and row-occlusion behavior, Electron/CDP preedit geometry, Electron/CDP live-PTY Sogou-style repros, and macOS renderer/main slices for Preview kitty commits and layout-native Option input. The Option slice covers complete CSI-u flag fields including lock state, repeat/release event types, authoritative committed associated text, native dead-key release suppression, functional-versus-printable numpad codes, press-pinned release identity, live keyup modifiers, pending-layout text fallback, atomic layout refresh, rapid in-focus input-source changes, stable native layout characters, and both native/web preload fallbacks. Snapshot flag provenance keeps a proven 0, a proven non-zero, and an absent field distinct from daemon through the sequenced remote SnapshotStart frame. Real Linux/Sogou OS IME automation, native Windows candidate-window placement, Windows ConPTY post-agent reset, and the CJK/Vietnamese/Arabic matrix remain registered gaps.", | ||
| "motivatingLinks": [ | ||
| "https://github.com/stablyai/orca/pull/6699", | ||
| "https://github.com/stablyai/orca/pull/6682", | ||
| "https://github.com/stablyai/orca/pull/6513", | ||
| "https://github.com/stablyai/orca/pull/6999" | ||
| "https://github.com/stablyai/orca/pull/6999", | ||
| "https://github.com/stablyai/orca/issues/12545", | ||
| "https://github.com/stablyai/orca/issues/12729", | ||
| "https://github.com/stablyai/orca/pull/17170" | ||
| ], | ||
| "invariant": "Composition, native text forwarding, synthetic input, paste, and platform keyboard bypass paths must not send preedit/control bytes before commit and must commit text exactly once to the intended PTY. Layout-native Option text must preserve negotiated CSI-u alternate/associated fields and lock modifiers, distinguish repeat from press, and resolve releases with the press's primary identity plus the actual keyup modifiers without exposing a partial or stale layout refresh. An in-focus macOS input-source change must invalidate both renderer authorities before the next key can use the old source. When the snapshot source proves kitty state, macOS Preview commits must use the application's effective flags after the sequenced snapshot and every proven replay/live transition, and a claimed press delivered under report_event_types must produce exactly one matching release regardless of whether keyup arrives before or after insertText and regardless of xterm's defensive internal reset.", | ||
| "oracle": "Exact outbound bytes from the REAL native-text forwarder behind installPreviewImeBridge after initial snapshot restore, replacement resync, live transitions, and both keydown->insertText->keyup and keydown->keyup->insertText orderings; a wiring-only mock is supporting evidence, not the oracle. The current renderer-unit slice asserts native text commits route to the intended PTY once, composition/preedit bookkeeping does not leak premature text, input-source classification handles synthetic/native paths, paste/runtime forwarding avoids duplicate terminal payloads for covered fixtures, and Linux/Sogou candidate Space/digit selectors do not leak keydown/keypress/keyup while ordinary and long-held letter-to-digit typing remains available. A main/preload/renderer notification chain proves every live window invalidates immediately, the old native read settles before a coalesced refresh signal, and older refreshes are generation-fenced. The Electron/CDP live-PTY repro verifies Sogou-style Space and digit selectors submit only the committed Chinese text, while the legacy orphaned-letter-keyup sequence sends no selector byte to the PTY. Real legacy IME commit preservation and the full CJK/Vietnamese/Arabic/JIS-yen matrix run in follow-up platform soak where automation is possible.", | ||
| "invariant": "Composition, native text forwarding, synthetic input, paste, and platform keyboard bypass paths must not send preedit/control bytes before commit and must commit text exactly once to the intended PTY. A live preedit must stay visible across an IME-resumed transaction, preserve every committed row-tail cell hidden by its opaque overlay, mask only a structurally verified Codex or Claude placeholder and never a row carrying typed text, keep the themed insertion caret and native candidate anchor inside the terminal at the final column, and clean up without stale overlay children or ownership classes. Layout-native Option text must preserve negotiated CSI-u alternate/associated fields and lock modifiers, distinguish repeat from press, and resolve releases with the press's primary identity plus the actual keyup modifiers without exposing a partial or stale layout refresh. An in-focus macOS input-source change must invalidate both renderer authorities before the next key can use the old source. When the snapshot source proves kitty state, macOS Preview commits must use the application's effective flags after the sequenced snapshot and every proven replay/live transition, and a claimed press delivered under report_event_types must produce exactly one matching release regardless of whether keyup arrives before or after insertText and regardless of xterm's defensive internal reset.", | ||
| "oracle": "Exact outbound bytes from the REAL native-text forwarder behind installPreviewImeBridge after initial snapshot restore, replacement resync, live transitions, and both keydown->insertText->keyup and keydown->keyup->insertText orderings; a wiring-only mock is supporting evidence, not the oracle. The current renderer-unit slice asserts native text commits route to the intended PTY once, composition/preedit bookkeeping does not leak premature text, resumed Windows event traces reactivate the visible preedit, an opaque mid-line overlay reproduces the committed row tail it covers, arbitrary DIM output remains visible, and final-column layout keeps both caret and textarea candidate anchor within the available screen width. The candidate-anchor slice reads back the inline `textarea.style.left` xterm's own listener and Orca's anchor listener both write during one composition event, so the assertion is the position the OS would sample rather than either writer's intent. A Codex frame whose prompt row is bold and whose dimmed model footer sits below a blank gap acquires the mask class while composing; the same placeholder text without that footer, a framed-versus-unframed Claude prompt, a row of arbitrary dimmed output, and a row carrying typed text do not. The class is gone after composition end, after a blur that is not followed by a session end, and after disposal. Rendered Electron/CDP geometry independently proves the overlay reproduces every committed cell it hides, that a real Codex placeholder at column 2 is masked rather than repeated, and that the final-column caret, preedit, and textarea right edges all land inside the screen. Input-source classification handles synthetic/native paths, paste/runtime forwarding avoids duplicate terminal payloads for covered fixtures, and Linux/Sogou candidate Space/digit selectors do not leak keydown/keypress/keyup while ordinary and long-held letter-to-digit typing remains available. A main/preload/renderer notification chain proves every live window invalidates immediately, the old native read settles before a coalesced refresh signal, and older refreshes are generation-fenced. The Electron/CDP live-PTY repro verifies Sogou-style Space and digit selectors submit only the committed Chinese text, while the legacy orphaned-letter-keyup sequence sends no selector byte to the PTY. Real legacy IME commit preservation, native candidate-window placement, and the full CJK/Vietnamese/Arabic/JIS-yen matrix run in follow-up platform soak where automation is possible.", | ||
| "commands": [ | ||
| "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/terminal-ime-native-text-forwarder.test.ts src/renderer/src/components/terminal-pane/terminal-ime-substituted-text-commit.test.ts src/renderer/src/components/terminal-pane/terminal-ime-macos-keybinding-dict-trace.test.ts src/renderer/src/components/terminal-pane/terminal-paste-runtime.test.ts src/renderer/src/components/terminal-pane/terminal-ime-composition-tracker.test.ts src/renderer/src/components/terminal-pane/terminal-ime-candidate-key-release-guard.test.ts src/renderer/src/components/terminal-pane/xterm-bypass-policy-non-mac.test.ts src/renderer/src/components/terminal-pane/xterm-bypass-policy.test.ts", | ||
| "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/terminal-ime-linux-candidate-state.test.ts", | ||
| "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/terminal-ime-xterm-midline-preedit-tail.test.ts src/renderer/src/components/terminal-pane/terminal-ime-xterm-resumed-preedit-visibility.test.ts src/renderer/src/components/terminal-pane/terminal-ime-xterm-trailing-preedit-occlusion.test.ts src/renderer/src/lib/pane-manager/terminal-ime-candidate-anchor.test.ts src/renderer/src/components/terminal-pane/terminal-ime-composer-placeholder-mask.test.ts src/shared/terminal-composer-draft.test.ts src/main/daemon/terminal-cursor-line-context.test.ts", | ||
| "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/dashboard-popout/preview-terminal-ime-bridge-kitty-bytes.test.ts src/renderer/src/components/dashboard-popout/preview-terminal-snapshot-replay.test.ts src/renderer/src/components/dashboard-popout/AgentTerminalPreview.test.tsx src/renderer/src/components/dashboard-popout/AgentTerminalPreview.option-dead-key.test.tsx src/shared/terminal-kitty-keyboard-mode-tracker.test.ts src/main/ipc/terminal-preview.test.ts src/main/ipc/terminal-preview-output-stream.test.ts src/renderer/src/runtime/remote-runtime-terminal-snapshot-kitty-flags.test.ts src/main/daemon/daemon-pty-adapter.test.ts", | ||
| "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/terminal-kitty-csi-u-encoding.test.ts src/renderer/src/components/terminal-pane/terminal-shortcut-option-compose.test.ts src/renderer/src/components/terminal-pane/terminal-option-kitty-release.test.ts src/renderer/src/lib/keyboard-layout/layout-base-character.test.ts src/renderer/src/lib/keyboard-layout/option-as-alt-probe.test.ts src/main/ipc/macos-keyboard-layout-snapshot.test.ts src/main/ipc/macos-keyboard-layout-change-notifications.test.ts src/preload/app-restart-checkpoint-routing.test.ts src/renderer/src/web/web-preload-api-ui.test.ts", | ||
| "pnpm run test:e2e -- tests/e2e/chinese-ime-chat-input-repro.spec.ts", | ||
| "pnpm run test:e2e -- tests/e2e/terminal-korean-endofrow-preedit-cell-span.spec.ts tests/e2e/terminal-korean-midline-preedit-occlusion.spec.ts", |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Add evidenceRuns entries for the two newly registered commands.
This range adds two new commands to the terminal-input.ime-and-synthetic-forwarding gate:
- A 7-file vitest command covering
terminal-ime-xterm-midline-preedit-tail.test.ts,terminal-ime-xterm-resumed-preedit-visibility.test.ts,terminal-ime-xterm-trailing-preedit-occlusion.test.ts,terminal-ime-candidate-anchor.test.ts,terminal-ime-composer-placeholder-mask.test.ts,terminal-composer-draft.test.ts, andterminal-cursor-line-context.test.ts. - An e2e command for
terminal-korean-endofrow-preedit-cell-span.spec.tsandterminal-korean-midline-preedit-occlusion.spec.ts.
The evidenceRuns array for this gate has no entry matching either command. Every other command in this gate has at least one matching evidenceRuns entry with a date, platform, and result. Add evidenceRuns entries that record an actual pass for both new commands. Without them, this reliability gate claims coverage for the new preedit-overlay, candidate-anchor, and placeholder-mask behavior with no recorded evidence that the commands were executed against this build.
| if (!context || context.cursorHidden || context.rows.length === 0) { | ||
| return null | ||
| } | ||
| const cursorIndex = context.rows.length - 1 | ||
| const codexFooterIndex = findCodexFooterIndex(context) | ||
| let afterCursor = context.afterCursor || context.rawAfterCursor | ||
| let continuationRows = composerContinuationRows(context, afterCursor, codexFooterIndex) | ||
| if (isStockPlaceholder(afterCursor, continuationRows)) { | ||
| const placeholder = isStockPlaceholder(afterCursor, continuationRows) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Do not classify undimmed draft text as a placeholder.
Line 113 accepts context.afterCursor when it matches a placeholder phrase. That value contains typed, undimmed text. If a user moves the cursor before a draft beginning with Try " or Ask Codex to do anything, this branch clears the draft and can mask its row during composition.
Require an empty context.afterCursor before classifying rawAfterCursor as a stock placeholder. Add a regression test with typed matching text to the right of the cursor.
Proposed fix
- const placeholder = isStockPlaceholder(afterCursor, continuationRows)
+ const placeholder =
+ context.afterCursor.length === 0 &&
+ isStockPlaceholder(afterCursor, continuationRows)📝 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.
| const placeholder = isStockPlaceholder(afterCursor, continuationRows) | |
| const placeholder = | |
| context.afterCursor.length === 0 && | |
| isStockPlaceholder(afterCursor, continuationRows) |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Shared classifier split —
detectTerminalComposerDraftinsrc/shared/terminal-composer-draft.tsis refactored into an internaldetectTerminalComposerreturning aplaceholderflag, withdetectTerminalComposerDraft(filters placeholders, preserving the prior daemon/runtime contract) andhasTerminalComposerPlaceholderas the two public entry points. Theplaceholder: !text && placeholderguard ensures a row carrying typed text is never masked even when the stock placeholder is still rendered to its right. - Cursor-context reader move —
readTerminalCursorLineContextmoves fromsrc/main/daemontosrc/shared/terminal-cursor-line-context.ts, switching from the@xterm/headlessTerminaltype to the structuralTerminalCursorContextSourceso both headless and browser terminals satisfy it. Daemon call sites are import-only; the parameter is renamedrowsAbove→rowsAroundCursorto match its existing symmetric use. - Renderer mask module —
installTerminalImeComposerPlaceholderMasktoggles theorca-ime-composer-placeholderclass on the terminal element while a composition session owns a structurally verified Codex/Claude placeholder, re-evaluating ononRender, and clearing on session end, blur, and dispose. - CSS —
.xterm.orca-ime-composer-placeholder .xterm-composition-remainder { visibility: hidden; }hides the reproduced row tail while preserving layout space. - Tests and gates — new renderer unit test, shared draft test coverage, e2e assertion via
remainderVisibility, and reliability-gate entries for the new files.
I verified the changed unit tests pass (3 files / 39 tests) and check:reliability-gates validates all 99 gates. The event names and .xterm-composition-remainder class the CSS keys off match what the stacked #17170 patch emits, and the mask's session-id validation mirrors terminal-ime-composition-route.ts.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
Important
The two candidate-anchor writers still disagree for a preedit wider than the whole row — see inline.
Reviewed changes (since prior pullfrog review)
- Pure rebase of this PR's commit —
5c7d3958d5is byte-identical to the previously reviewed0ec925ce49(same 12 files, 567 insertions / 55 deletions). No new findings in the placeholder-mask work itself. - Stacked candidate-anchor agreement change —
0bb0b58ba7moves the right-edge clamp into xterm's patchedCompositionHelper.updateCompositionElementsso it applies on theonRenderpath too, and keeps Orca'sterminal-ime-candidate-anchor.tsapplying the "same" clamp on composition events; a new render-path test replaces the old setTimeout-recovery test.
The placeholder-mask unit tests still pass on this head (3 files / 39 tests) and check:reliability-gates still validates 99 gates.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
| - this._textarea.style.left = cursorLeft + 'px'; | ||
| + const anchorBounds = | ||
| + (this._compositionPreedit ?? this._compositionView).getBoundingClientRect(); | ||
| + const anchorLeft = cursorLeft + Math.min(0, maxWidth - anchorBounds.width); |
There was a problem hiding this comment.
This clamp is missing the Math.max(0, …) floor that terminal-ime-candidate-anchor.ts:87 applies. xterm's cursorLeft + Math.min(0, maxWidth - anchorBounds.width) reduces to min(cursorLeft, cols·cellWidth − width), so for a preedit wider than the whole row (width > cols·cellWidth) it writes a negative left, while Orca's Math.max(0, Math.min(…)) clamps to 0. Because this path also runs from onRender (the last writer), the negative value can persist and push the candidate window off the left edge — the exact invariant pinned by terminal-ime-candidate-anchor.test.ts ("never anchors left of the terminal when the preedit outgrows the whole row").
5c7d395 to
1f5fcec
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
…edit Split out of stablyai#17170, which now carries only the xterm composition-overlay work. Codex and Claude draw an all-dim, full-row ghost placeholder. The opaque preedit overlay reproduces the committed row tail it covers, so without this the ghost is repeated to the right of the composing syllable instead of staying masked. The binding keys off the `.xterm-composition-remainder` class that stablyai#17170 adds and hides it through CSS while a composition owns a structurally verified placeholder row — bold prompt glyph plus a dimmed model footer below a blank gap for Codex, a frame line above the prompt for Claude. Arbitrary dim output, shell lookalikes, and any row carrying typed text keep their tail visible. readTerminalCursorLineContext moves from src/main/daemon to src/shared because the renderer now needs the same reader the daemon uses; the move is import-only. Depends on stablyai#17170.
1f5fcec to
252d8de
Compare
…edit (stablyai#17377) Split out of stablyai#17170, which now carries only the xterm composition-overlay work. Codex and Claude draw an all-dim, full-row ghost placeholder. The opaque preedit overlay reproduces the committed row tail it covers, so without this the ghost is repeated to the right of the composing syllable instead of staying masked. The binding keys off the `.xterm-composition-remainder` class that stablyai#17170 adds and hides it through CSS while a composition owns a structurally verified placeholder row — bold prompt glyph plus a dimmed model footer below a blank gap for Codex, a frame line above the prompt for Claude. Arbitrary dim output, shell lookalikes, and any row carrying typed text keep their tail visible. readTerminalCursorLineContext moves from src/main/daemon to src/shared because the renderer now needs the same reader the daemon uses; the move is import-only. Depends on stablyai#17170.

Split out of #17170 at review request. #17170 now carries only the xterm composition-overlay work; this carries the Codex/Claude placeholder masking, which is a different domain at a different layer.
Stacked on #17170. The CSS here keys off the
.xterm-composition-remainderclass that PR adds; the dependency is one-way. GitHub shows the combined diff againstmainuntil #17170 merges — the reviewable change is the last commit only (5c7d3958d50).What changed
detectTerminalComposersodetectTerminalComposerDraftand the newhasTerminalComposerPlaceholdershare one classifier.readTerminalCursorLineContextfromsrc/main/daemontosrc/sharedso the renderer can use the same reader the daemon does. Import-only move.Why
Codex and Claude draw an all-dim, full-row ghost placeholder. #17170 makes the opaque preedit overlay reproduce the committed row tail it covers — correct for ordinary text, wrong for a ghost, which would then be repeated to the right of the composing syllable rather than staying masked.
Masking is gated on structure, not on the placeholder string: a bold prompt glyph plus a dimmed model footer below a blank gap for Codex, a frame line above the prompt for Claude. Arbitrary dim output, shell prompts that happen to use the same glyph, and any row carrying typed text all keep their tail visible.
Verification
Run on head
5c7d3958d50:pnpm tc: passed.src/renderer/src/components/terminal-pane/+terminal-composer-draft.test.ts+terminal-cursor-line-context.test.ts: 411 files / 3,983 tests passed.pnpm run check:reliability-gates: 99 gates valid.Mutation results
Every line this PR adds or changes was broken, the suite re-run, and the line restored.
placeholder: !text && placeholdertoplaceholderhandleBlurno longer clearsactiveSessionIdBoth were surviving mutations in the original #17170 and are the reason these two tests exist.
The blur test dispatches on
terminal.elementwith a synthetic session start. That matters: a real textarea blur also ends the composition session, so ownership would clear throughhandleSessionEndand the test would pass whether or not the blur listener does anything at all.The
!textguard test covers a composer row that has typed text and still matches the stock-placeholder heuristic. Without the guard, that row is classified as a placeholder — hiding a row containing real user text with everything still green.What runs where
The renderer-unit slice is deterministic and runs on every PR. The rendered Electron/CDP arm (
terminal-korean-midline-preedit-occlusion.spec.ts) drives composition through CDP, not a real IME. No CI runner exercises a real OS IME, and the PR gate has no macOS runner. The gate records that as a standing gap rather than claiming coverage.AI disclosure
AI-assisted implementation, test construction, and review.