fix(palette): restore composer remount for never-activated pet widgets - #4605
Yeachan-Heo wants to merge 8 commits into
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Root cause
Pre-#4591, Validation (exact PR head c194e79)
CI on this head (run 31932247745 + 31932247752)Green: affected-path plan, native-build, Red: — |
c194e79 to
b5929c8
Compare
|
Status: rebase complete, exact-head CI driven, human-review boundary reached.
No code or behavior delta vs dev; this PR only adds the regression coverage that would have caught #4591's leak before it shipped. — |
|
Blocked solely on human review — full state for the reviewer
— |
snowykr
left a comment
There was a problem hiding this comment.
Verdict: Request changes
gajae.pr-review-verdict.v1 request-changes sha256:1465ccdfd915bdfdc6b32a6431efa134faeecd6346a209b4ab49cd99aee45faf reviewer:human reviewer-id:snowykr evidence:https://github.com/Yeachan-Heo/gajae-code/actions/runs/32044402879
Reviewed the exact PR head b5929c80eca5074998e3875c0b13a7a67076d9d1 against dev 8711c555da across intent/contract, architecture/correctness, security/trust, verification/CI, and compatibility/platform context.
The regression-test direction is correct, and I found no actionable P1, P3, or P5 issue. The two new assertions cover the ownerless/off → plain-editor and active-owner → framed-editor remount branches, while the existing successor-takeover test preserves the stale-owner contract.
Required changes
1. Make the remount tests model the reusable editor lifecycle
Location: packages/coding-agent/test/gajae-pet-widget.test.ts:650-701
makeStubs() supplies an editor cast to CustomEditor, but the stub has no dispose() implementation. The production #mountEditor() uses editorContainer.clear(), and Container.clear() disposes every current child. CustomEditor.dispose() is not a no-op: it tears down the tab-width listener and resets pending paste state. The new tests therefore prove only child replacement; they can pass even if a repeated palette close/remount path has disposed the reusable editor.
Please either use a real/disposable editor in this regression or add a focused lifecycle assertion that repeats the restore path and verifies the editor remains usable (including the relevant disposal/listener contract). If the intended ownership model is detach-then-readd, the implementation/test should make that explicit rather than relying on a stub that omits disposal.
2. Refresh the exact-head evidence in the PR contract
The PR body’s verdict still points to https://github.com/Yeachan-Heo/gajae-code/actions/runs/32044088151, but that is the older/cancelled run and gh pr checks 4605 still reports its affected-path job as failed. The successful exact-head product run is 32044402879; its targeted gajae-pet-widget test, ts-build, native-build, affected-path plan/evidence, and integration jobs pass, while the contract jobs fail because the body still declares needs-human.
Please update the verdict evidence to the immutable successful exact-head run (or explicitly label the old URL historical), then rerun the exact-head contract gate after the review decision is recorded.
Review lane summary
- P1 Intent / Policy / Contract: clear.
- P2 Architecture / Correctness / Failure: changes requested for lifecycle/test fidelity; active-pet
InteractiveModeintegration remains a useful follow-up gap. - P3 Security / Privacy / Trust: not materially applicable; test-only change with no external or security boundary.
- P4 Verification / Tests / CI: targeted changed-test CI is green, but neighboring host/TUI suites are local-only claims and the body evidence URL is stale.
- P5 Context / Compatibility / Platform: clear; no generated artifacts, runtime/package changes, or changelog required for this test-only diff.
|
Exact-head Owned blockers:
The lane will implement, push, rerun exact-head CI, obtain a fresh approval/verdict, and drive the PR to dev merge. This is not a human-only hold. — |
|
Requested changes addressed — head Both required changes from review 4952815267 are implemented, tested red/green, and pushed to the authorized branch (lease-guarded force push from 1. Remount tests now model the reusable editor lifecycle. The stub editor was replaced with a real
The review also exposed a real production defect, now fixed. 2. Verdict evidence refreshed to the immutable exact-head run. Exact-head Dev CI run 32058210086: 17 code jobs green — native-build, Validation on Follow-up gap (separate issue): share/gist loader, MCP wizard, and tasks-pane paths still use the raw @snowykr — ready for a fresh review of — |
|
Terminal disposition: blocked solely on fresh human review of
Lane holds here; no speculative merge. — |
a890974 to
cce1441
Compare
|
Terminal disposition: blocked solely on fresh human review of
No mutations while review is absent: no self-approval, no verdict change, no source/body edits. Lane returns to external review hold. — |
|
@snowykr Fresh non-author review requested for exact head The prior Current exact-head CI is running. The only expected governance blocker after product CI is a fresh independent approval and matching [repo owner's gaebal-gajae (clawdbot) 🦞] |
097c3b4 to
eef82ef
Compare
|
Reconstruction evidence — PR rebased onto current dev, fresh exact-head review requested
Review request: @snowykr — fresh authorized non-author review of exact head Signed: owner lane for PR #4605, head — |
|
Owner-lane boundary review complete — head The owner lane has finished its adversarial boundary review of exact head
Advisory findings (not merge blockers, recorded for follow-up): the same terminal-disposal defect remains reachable at four pre-existing unguarded open paths — jobs overlay ( @snowykr — the review request from the reconstruction comment still stands: fresh non-author review of exact head — |
snowykr
left a comment
There was a problem hiding this comment.
Verdict: Request changes
gajae.pr-review-verdict.v1 request-changes sha256:59e7bd2abe687245725790324531d00da64e9f77f6e83f31ad47bb6e07680713 reviewer:human reviewer-id:snowykr evidence:https://github.com/Yeachan-Heo/gajae-code/actions/runs/32099895353
Reviewed the new exact head eef82ef5dc5a620f847312057d3ea1eab7bbe55d against dev 27afb732b3d25632d44176687d5bdd78d3419bb3 across the requested P1–P5 axes, including the detach-before-clear production changes and the new lifecycle tests.
The production direction is sound: detaching the reusable editor before Container.clear() preserves the editor's terminally-owned lifecycle, and the exact-head affected-path jobs for the changed suites are green. I found one blocking verification/compatibility issue before approval.
Required change
Update all SelectorController test contexts for the new detachChild requirement
Locations:
packages/coding-agent/test/custom-model-preset-creation.test.ts:854-858packages/coding-agent/test/model-selector-profiles-redteam.test.ts:127-127,356-356packages/coding-agent/test/provider-onboarding-wizard.test.ts:263-269packages/coding-agent/test/provider-onboarding-wizard-redteam.test.ts:320-327
These contexts instantiate SelectorController and exercise selector-opening paths such as showModelSelector() / showCustomProviderWizard(), which all enter SelectorController.showSelector(). The new implementation now unconditionally calls this.ctx.editorContainer.detachChild(this.ctx.editor) before clear(), but these test doubles still provide only clear() and addChild(). Because they are cast to InteractiveModeContext/never, TypeScript does not catch the missing method; the affected tests will fail at runtime with detachChild is not a function when the selector opens.
Please update every lightweight editorContainer double that reaches SelectorController.showSelector() to implement detachChild with the same child-list semantics as the existing updated fixtures. A repository-wide audit should include the session-selector and login-selector test contexts as well, rather than updating only the changed-path suites.
Verification notes
- P1 Intent / Contract: clear; the detach-then-readd change matches the stated lifecycle contract.
- P2 Architecture / Correctness: no production blocker found in the reviewed call sites;
PetFramedEditoris not disposable, and the reusableCustomEditorremains owned byInteractiveMode. - P3 Security / Privacy / Trust: not materially applicable; no new external or sensitive-data boundary.
- P4 Verification / CI: the new lifecycle tests are meaningful, but current CI targets only the changed suites and therefore does not catch the unupdated selector test doubles above.
- P5 Compatibility / Platform: runtime API usage is consistent with
Container; test fixtures need to be brought to the same contract. The changelog entry is appropriate for the user-visible lifecycle fix.
The current exact-head product run is 32099895353; its relevant affected-path jobs pass. The contract jobs remain red by design while the PR body records needs-human.
snowykr
left a comment
There was a problem hiding this comment.
Verdict: Request changes
gajae.pr-review-verdict.v1 request-changes sha256:59e7bd2abe687245725790324531d00da64e9f77f6e83f31ad47bb6e07680713 reviewer:human reviewer-id:snowykr evidence:https://github.com/Yeachan-Heo/gajae-code/actions/runs/32099895353
Follow-up review of the same exact head eef82ef5dc5a620f847312057d3ea1eab7bbe55d found two additional blocking gaps in the fix-forward. The detach lifecycle direction is correct, but the queued-message path and the test/evidence contract are not complete yet.
Required changes
1. Restore the pet-aware composer from the queued-message selector
Location: packages/coding-agent/src/modes/controllers/input-controller.ts:1249-1255
#restoreEditorFocus() now detaches the raw editor and re-adds that raw editor directly. In an active pet session, the mounted child is PetFramedEditor, not ctx.editor; the detach is therefore a no-op, clear() removes the framed wrapper, and the close path mounts the bare editor. The pet remains active and continues emitting its sprite, but the composer reserve/frame is gone after closing or deleting/moving through the queued-message selector.
This is deterministic for the active-pet queue flow and is exactly the kind of reusable-composer contract this PR is fixing. Route the restore through ctx.restoreComposer() (or an equivalent pet-aware mount helper) so the active mode restores PetFramedEditor and off mode restores the plain editor. Add an active-pet queued-selector regression that asserts the framed child and reserve survive open/close.
2. Update every selector test double that now needs detachChild
The first follow-up only updated the changed-path fixtures. Several existing contexts still instantiate SelectorController and enter showSelector(), but provide only clear() and addChild(). Since showSelector() now calls detachChild() unconditionally, these tests can fail at runtime despite being cast through InteractiveModeContext/never:
packages/coding-agent/test/custom-model-preset-creation.test.ts:854-858packages/coding-agent/test/model-selector-profiles-redteam.test.ts:127,356packages/coding-agent/test/provider-onboarding-wizard.test.ts:263-269packages/coding-agent/test/provider-onboarding-wizard-redteam.test.ts:320-327packages/coding-agent/test/modes/components/theme-selector-input.test.tsselector contextspackages/coding-agent/test/modes/components/thinking-selector.test.tsselector contextspackages/coding-agent/test/selector-controller-resume-model.test.ts:11-15packages/coding-agent/test/selector-controller-session-delete.test.ts:11-15 and 49-57
Please audit all SelectorController fixtures that reach showSelector() and add a child-list-correct detachChild implementation, not just the affected-path files. The current targeted CI does not execute all of these suites.
3. Refresh the exact-head evidence references
The PR body still identifies 32099837032 / 32099835720 as the current exact-head evidence. gh reports 32099837032 cancelled and 32099835720 failed. The completed current-head product run is 32099895353; its product/affected jobs pass, while 32099895625 is the separate contract run that fails only because the body still records needs-human.
Update the body’s exact-evidence section and verdict URL to the completed run, and describe the contract failure as the expected human-review gate. Do not leave cancelled/failed runs presented as current evidence.
Additional verification note
The new real-editor widget lifecycle tests at packages/coding-agent/test/gajae-pet-widget.test.ts:671-702 and the preceding active/off tests should explicitly dispose the CustomEditor fixture after each test. widget.dispose() does not own the editor, and CustomEditor registers a process-wide tab-width listener; the first two real-editor tests can leak that listener into later tests. The repeated lifecycle test’s red control disposes it intentionally, but the other real-editor fixtures need equivalent cleanup.
The existing raw editorContainer.clear() swaps outside SelectorController.showSelector() (OAuth code input, jobs/tasks panes, command-controller loaders, and runtime-MCP wizard) also deserve a lifecycle audit before claiming the reusable composer contract is complete; I am treating those as follow-up scope unless they are intended to be covered by this PR’s broad selector/overlay fix.
Lane summary
- P1 Intent / Contract: changes requested for queued pet-aware restore and incomplete fixture contract.
- P2 Architecture / Correctness: active-pet queued selector loses
PetFramedEditordeterministically. - P3 Security / Privacy / Trust: no actionable finding; not materially applicable.
- P4 Verification / CI: lifecycle coverage is improved, but unupdated fixtures and stale evidence remain.
- P5 Compatibility / Platform: runtime
Containerusage is consistent; all lightweight contexts must match the new method contract.
eef82ef to
11f772a
Compare
11f772a to
6d12e76
Compare
…y opens Issue #4657. Four overlay-open paths still cleared editorContainer with the live composer attached: the jobs overlay (alt+j //monitors), the tasks pane (alt+t), the OAuth API-key paste input, and the /debug log + raw-SSE viewers. Container.clear() disposes children terminally and Editor.dispose() tears down the tab-width change listener, so the first round-trip on any of these paths silently killed the composer's listener; every later restore re-mounted a dead editor and runtime tab-width changes stopped re-deriving composer layout. Each open now detaches the reusable editor first (Container.detachChild, the same detach-then-readd reuse contract showSelector and the extension-ui and pet-aware restore paths already follow from PR #4605), so the terminal clear disposes only the transient overlay. User-facing behavior is unchanged: the overlay mounts exactly as before and the composer is re-added on close. Regression coverage: one deterministic test per named path in composer-detach-overlay-paths.test.ts driving the real production open/close code (real JobsOverlayComponent/TasksPaneComponent focus lists, the captured production onPrompt closure over a real Input, and the real DebugSelector selection into the real DebugLogViewerComponent with an isolated agent-dir log source), each asserting the tab-width listener still fires across four overlay cycles, with a red control proving the probe detects genuine disposal. Lore-id: issue-4657-composer-detach Constraint: preserve PR #4605 lanes untouched (showSelector/extension-ui/pet restore shapes unchanged) Tested: red/green — 4/4 fail on unfixed tree, 4/4 pass with fix Tested: 107 pass across 9 adjacent suites (pet widget, editor dispose, tasks, jobs, debug, login) Tested: 190 pass across 7 selector/palette/input suites Tested: bun run --cwd=packages/coding-agent check + build clean; ci:test:smoke ok Tested: check:tools, node20-baseline, public-sync, schemas, docker-context, gjc-ui gates pass Confidence: high Scope-risk: narrow Reversibility: trivial
|
Addressed in full — fixture audit complete (review 4958123531) Every New exact head for re-review: — Fixed in Deterministic regressions added in
All 65 tests in that suite green locally. — Fixture audit. Every test context that instantiates
Verified non-fixtures: Fixture disposal. The three real- — Evidence. The PR body's exact-evidence section now names head Adjacent raw — |
…y opens Issue #4657. Four overlay-open paths still cleared editorContainer with the live composer attached: the jobs overlay (alt+j //monitors), the tasks pane (alt+t), the OAuth API-key paste input, and the /debug log + raw-SSE viewers. Container.clear() disposes children terminally and Editor.dispose() tears down the tab-width change listener, so the first round-trip on any of these paths silently killed the composer's listener; every later restore re-mounted a dead editor and runtime tab-width changes stopped re-deriving composer layout. Each open now detaches the reusable editor first (Container.detachChild, the same detach-then-readd reuse contract showSelector and the extension-ui and pet-aware restore paths already follow from PR #4605), so the terminal clear disposes only the transient overlay. User-facing behavior is unchanged: the overlay mounts exactly as before and the composer is re-added on close. Regression coverage: one deterministic test per named path in composer-detach-overlay-paths.test.ts driving the real production open/close code (real JobsOverlayComponent/TasksPaneComponent focus lists, the captured production onPrompt closure over a real Input, and the real DebugSelector selection into the real DebugLogViewerComponent with an isolated agent-dir log source), each asserting the tab-width listener still fires across four overlay cycles, with a red control proving the probe detects genuine disposal. Lore-id: issue-4657-composer-detach Constraint: preserve PR #4605 lanes untouched (showSelector/extension-ui/pet restore shapes unchanged) Tested: red/green — 4/4 fail on unfixed tree, 4/4 pass with fix Tested: 107 pass across 9 adjacent suites (pet widget, editor dispose, tasks, jobs, debug, login) Tested: 190 pass across 7 selector/palette/input suites Tested: bun run --cwd=packages/coding-agent check + build clean; ci:test:smoke ok Tested: check:tools, node20-baseline, public-sync, schemas, docker-context, gjc-ui gates pass Confidence: high Scope-risk: narrow Reversibility: trivial
|
Fix-forward complete on head
Remaining blocker (human-only): a fresh non-author exact-head review of — |
snowykr
left a comment
There was a problem hiding this comment.
Verdict
Request changes. The reviewed commit is 6d12e769e65f174a4615b87da9aea5d9b1b1318d. Two evidence-grounded P1 issues remain: one leaves equivalent composer lifecycle defects in production paths, and one leaves the changed queued-message path without a regression test that exercises the relevant disposal contract.
Summary
The detach-before-clear repair is correctly applied in the reviewed selector, queued-message, and pet-remount paths. However, other production flows still clear a container with the reusable composer attached, and the queued-message tests use doubles that cannot detect the Container.clear() disposal behavior being fixed.
Findings / Required Changes
- [P1] Repair the remaining composer replacement paths before clearing.
packages/coding-agent/src/modes/controllers/command-controller.ts:191-199still clearseditorContainerwhile the reusable editor is attached in/share, then re-adds that disposed instance; the default Gist branch repeats the pattern at lines 248-256. The same lifecycle defect is reported in the MCP-add wizard, OAuth prompt, Jobs overlay, and Tasks pane. BecauseContainer.clear()disposes attached children, these paths can remove the editor's tab-width listener and clear its paste state. Detach the reusable editor (or its framed owner) before clearing in each affected transition, and add focused regression coverage. - [P1] Cover the queued-message selector with real disposal semantics.
packages/coding-agent/test/input-controller-keybindings.test.ts:198-207uses a fake editor and aclear()mock that only truncates an array. Those tests would still pass if the newdetachChild(editor)immediately beforeInputController'sclear()were removed, so they do not prove that the changed path avoidsContainer.clear()→Editor.dispose(). Add an InputController-level regression using a realContainerandCustomEditor; open and restore the queued-message selector, then verify the restored composer remains usable and responds to a tab-width change.
CI / Verification
No PR code, tests, linting, formatting, or local verification commands were run by the reviewers. GitHub-observed CI on the reviewed head showed the requested changed-test checks succeeding, including command-palette controller, resume-model controller, session-delete controller, command-palette interactive host, input-controller keybindings, and the relevant pet-widget suite; the overall Dev CI run was marked failed because PR contract bootstrap failed while its product checks succeeded.
Axis Coverage
- A1 — Intent/architecture: Approved the scoped detach-before-clear lifecycle repair and changelog placement; no finding.
- A2 — Correctness: Approved the changed selector, queued-message restoration, pet ownership, and error-path behavior; no finding.
- A3 — Security: Found no actionable security, privacy, or trust issue.
- A4 — Verification: Raised the queued-message real-disposal regression coverage gap.
- A5 — Compatibility/completeness: Raised the remaining equivalent production composer-replacement lifecycle defects.
6d12e76 to
d9af002
Compare
…g mounts Fix-forward on PR #4687 (snowykr CHANGES_REQUESTED on 39df3bc), closing all five findings on current dev (7932f8a): P1 — the /share custom-share loader, the /share gist loader (command-controller.ts #shareExport) and the /mcp add wizard (runtime-mcp-command-controller.ts #handleAdd) still called editorContainer.clear() with the live reusable CustomEditor attached, terminally disposing its tab-width listener on first round-trip — the same defect PR #4605 fixed at the five highest-traffic paths and #4687 at the selector/jobs/tasks/OAuth/debug paths. All three opens now detach the composer before clearing, and the regression suite grows live-controller cycles for both paths: /share runs handleShareCommand end to end against a gated share.ts seeded in an exclusively owned agent dir (asserting the composer probe still fires while the loader owns the container, and again after the handler restores it); /mcp add drives controller.handle("/mcp add") through two full wizard mount/cancel cycles. P2 — the /debug log viewer restored the composer (selector onDone) before awaiting log-source reads, then unconditionally cleared and mounted, so a newer overlay (or a stopped UI) during the await was disposed and replaced by the stale completion. #handleViewLogs now verifies the operation's starting restore state still holds — the composer live-mounted in the editor container — and a stale completion returns without touching the active container. The guard is pet-aware: InteractiveMode.restoreComposer delegates to GajaePetWidget.remountComposer, which mounts the PetFramedEditor wrapper (not the bare editor) as the container's content, so the check accepts either shape; a naive bare-editor-only guard was verified red against a new pet-wrapped regression (the viewer never mounts). A second regression opens logs and seizes the container with the jobs overlay before any event-loop turn, then asserts the overlay survives as the sole child and the composer stays alive while detached. P2 — the /share and /debug fixtures were not hermetic under the canonical config override: GJC_CONFIG_DIR takes precedence over PI_CONFIG_DIR, so a caller-provided override could redirect log seeding and cleanup outside the test's own tree, and cleanup removed a fixed home path. Both config selectors are now pinned to one per-run UUID-named exclusive root with a mkdtemp agent dir, the resolved config root is asserted to sit inside that exclusive tree before anything is seeded (a provenance-refused override fails loudly instead of touching the real config), and the restore closure returns the exact prior env/agent-dir state and removes only the exclusive trees. P2 — the fixture hard-reset theme/tab width instead of restoring prior values and used unbounded polls plus fixed sleeps. Each test now snapshots and exactly restores lifecycle globals (tab width plus the prior theme name through the public theme API), and every wait is a bounded condition wait (waitUntil) or an explicit mount signal. P2 — the declared TestContext.editorContainer contract in selector-controller-session-delete.test.ts now includes detachChild, so the double can no longer drift from the container contract the production open path exercises. P3 — the seeded dated log is written with Bun.write per the repo file conventions (node:fs/promises stays for directory operations), and the /share wizard double in share-command-temp-security.test.ts implements the non-disposing detach contract. Lore-id: issue-4657-composer-detach Constraint: fix-forward on PR #4687 head 39df3bc; authorship preserved Test: composer-detach-overlay-paths 8 pass (tamper-red verified: stale guard, pet-unaware guard, /share detach, /mcp add detach each fail red) Test: 545 pass across modes/ + debug + pet-widget suites Test: 133 pass across the 13 PR-touched suites Test: bun run --cwd=packages/coding-agent check clean; ci:test:smoke ok Confidence: high Scope-risk: narrow Reversibility: trivial
…y opens Issue #4657. Four overlay-open paths still cleared editorContainer with the live composer attached: the jobs overlay (alt+j //monitors), the tasks pane (alt+t), the OAuth API-key paste input, and the /debug log + raw-SSE viewers. Container.clear() disposes children terminally and Editor.dispose() tears down the tab-width change listener, so the first round-trip on any of these paths silently killed the composer's listener; every later restore re-mounted a dead editor and runtime tab-width changes stopped re-deriving composer layout. Each open now detaches the reusable editor first (Container.detachChild, the same detach-then-readd reuse contract showSelector and the extension-ui and pet-aware restore paths already follow from PR #4605), so the terminal clear disposes only the transient overlay. User-facing behavior is unchanged: the overlay mounts exactly as before and the composer is re-added on close. Regression coverage: one deterministic test per named path in composer-detach-overlay-paths.test.ts driving the real production open/close code (real JobsOverlayComponent/TasksPaneComponent focus lists, the captured production onPrompt closure over a real Input, and the real DebugSelector selection into the real DebugLogViewerComponent with an isolated agent-dir log source), each asserting the tab-width listener still fires across four overlay cycles, with a red control proving the probe detects genuine disposal. Lore-id: issue-4657-composer-detach Constraint: preserve PR #4605 lanes untouched (showSelector/extension-ui/pet restore shapes unchanged) Tested: red/green — 4/4 fail on unfixed tree, 4/4 pass with fix Tested: 107 pass across 9 adjacent suites (pet widget, editor dispose, tasks, jobs, debug, login) Tested: 190 pass across 7 selector/palette/input suites Tested: bun run --cwd=packages/coding-agent check + build clean; ci:test:smoke ok Tested: check:tools, node20-baseline, public-sync, schemas, docker-context, gjc-ui gates pass Confidence: high Scope-risk: narrow Reversibility: trivial
…w-SSE Review follow-up on PR #4687 (snowykr CHANGES_REQUESTED): P1 — the branch assumed the generic SelectorController.showSelector open detach had already landed (PR #4605 is still unmerged), so production /debug and bare /login still disposed the reusable composer at that boundary before any downstream detach could run. showSelector now detaches the editor before its clear(), making this branch self-contained at every open path it claims. P2 — the debug regression only exercised the log viewer branch; it now alternates logs and raw-SSE entries across the four cycles, so both changed viewer branches carry the composer-lifecycle assertion. The harness also drives the real showDebugSelector/showSelector boundary instead of a local scaffold, so the P1 fix is covered by the same test (verified: reverting only the showSelector detach makes exactly this test fail). Lightweight test doubles whose editorContainer stubs predate detachChild (model-selector batch/profiles/profiles-redteam, provider-onboarding wizard and redteam, custom-model-preset-creation, login-preset-recommendation) gain the method, matching how PR #4605 updated its own doubles. Lore-id: issue-4657-composer-detach Constraint: preserve PR #4605 lanes (no other showSelector-site changes) Tested: stash of only the showSelector detach -> debug test fails (red, 3/4) Tested: 477 pass across 28 affected suites incl. the 4-path regression file Tested: bun run --cwd=packages/coding-agent check clean Confidence: high Scope-risk: narrow Reversibility: trivial
…g mounts Fix-forward on PR #4687 (snowykr CHANGES_REQUESTED on 39df3bc), closing all five findings on current dev (7932f8a): P1 — the /share custom-share loader, the /share gist loader (command-controller.ts #shareExport) and the /mcp add wizard (runtime-mcp-command-controller.ts #handleAdd) still called editorContainer.clear() with the live reusable CustomEditor attached, terminally disposing its tab-width listener on first round-trip — the same defect PR #4605 fixed at the five highest-traffic paths and #4687 at the selector/jobs/tasks/OAuth/debug paths. All three opens now detach the composer before clearing, and the regression suite grows live-controller cycles for both paths: /share runs handleShareCommand end to end against a gated share.ts seeded in an exclusively owned agent dir (asserting the composer probe still fires while the loader owns the container, and again after the handler restores it); /mcp add drives controller.handle("/mcp add") through two full wizard mount/cancel cycles. P2 — the /debug log viewer restored the composer (selector onDone) before awaiting log-source reads, then unconditionally cleared and mounted, so a newer overlay (or a stopped UI) during the await was disposed and replaced by the stale completion. #handleViewLogs now verifies the operation's starting restore state still holds — the composer live-mounted in the editor container — and a stale completion returns without touching the active container. The guard is pet-aware: InteractiveMode.restoreComposer delegates to GajaePetWidget.remountComposer, which mounts the PetFramedEditor wrapper (not the bare editor) as the container's content, so the check accepts either shape; a naive bare-editor-only guard was verified red against a new pet-wrapped regression (the viewer never mounts). A second regression opens logs and seizes the container with the jobs overlay before any event-loop turn, then asserts the overlay survives as the sole child and the composer stays alive while detached. P2 — the /share and /debug fixtures were not hermetic under the canonical config override: GJC_CONFIG_DIR takes precedence over PI_CONFIG_DIR, so a caller-provided override could redirect log seeding and cleanup outside the test's own tree, and cleanup removed a fixed home path. Both config selectors are now pinned to one per-run UUID-named exclusive root with a mkdtemp agent dir, the resolved config root is asserted to sit inside that exclusive tree before anything is seeded (a provenance-refused override fails loudly instead of touching the real config), and the restore closure returns the exact prior env/agent-dir state and removes only the exclusive trees. P2 — the fixture hard-reset theme/tab width instead of restoring prior values and used unbounded polls plus fixed sleeps. Each test now snapshots and exactly restores lifecycle globals (tab width plus the prior theme name through the public theme API), and every wait is a bounded condition wait (waitUntil) or an explicit mount signal. P2 — the declared TestContext.editorContainer contract in selector-controller-session-delete.test.ts now includes detachChild, so the double can no longer drift from the container contract the production open path exercises. P3 — the seeded dated log is written with Bun.write per the repo file conventions (node:fs/promises stays for directory operations), and the /share wizard double in share-command-temp-security.test.ts implements the non-disposing detach contract. Lore-id: issue-4657-composer-detach Constraint: fix-forward on PR #4687 head 39df3bc; authorship preserved Test: composer-detach-overlay-paths 8 pass (tamper-red verified: stale guard, pet-unaware guard, /share detach, /mcp add detach each fail red) Test: 545 pass across modes/ + debug + pet-widget suites Test: 133 pass across the 13 PR-touched suites Test: bun run --cwd=packages/coding-agent check clean; ci:test:smoke ok Confidence: high Scope-risk: narrow Reversibility: trivial
snowykr
left a comment
There was a problem hiding this comment.
Verdict
CHANGES_REQUESTED
Summary
The five-axis review completed against the exact head and identified 1 actionable issue, led by Required context method breaks implementer compatibility. These findings require changes before approval.
Findings / Required Changes
- [P2] Required context method breaks implementer compatibility.
Reference:packages/coding-agent/src/modes/types.ts:294
detachComposer()is newly mandatory on InteractiveModeContext, so downstream hosts and lightweight implementations must add it even though controllers already support absent methods. Make it optional with guarded use, or document and update every public implementer.
CI / Verification
- Reviewed the exact remote head:
5387eba0cd549c50be7ded5484b2d8f687da757a. - CI summary: 31 passing, 2 failing, 10 pending/cancelled/skipped.
- Failing checks:
Validate exact-head PR contract,PR contract bootstrap. - Repository policy permits review before all gating checks pass; the current non-passing checks are recorded above and do not establish that checks passed.
Axis Coverage
| Axis | Verdict | Coverage |
|---|---|---|
| A1. Intent / Policy / Contract | CHANGES_REQUESTED | Overlay lifecycle correctness is established; the main compatibility risk is the newly mandatory InteractiveModeContext method. |
| A2. Architecture / Correctness / Failure | APPROVED | Composer detach/restore paths preserve reusable editor ownership and pet-aware mounting; no correctness or concurrency risk was established. |
| A3. Security / Privacy / Trust | APPROVED | Security and privacy risk was not increased by the reviewed changes; no new sensitive-data handling or trust-boundary changes were found. |
| A4. Verification / Tests / CI | APPROVED | A4 verification evidence is broadly passing, with no test-backed regression found; contract validation and skipped platform jobs remain bounded risks. |
| A5. Context / Compatibility / Platform | APPROVED | Integration and compatibility behavior appears correct under passing validation; skipped Windows and Darwin ARM checks leave platform-specific risk unverified. |
Limitations
- Exact-head PR contract and bootstrap CI checks failed, so required repository contract enforcement was not established.
- PR contract bootstrap and exact-head contract jobs failed, so PR contract compliance was not established.
- Windows and darwin-arm64 platform-specific jobs were skipped, so those platform paths were not verified.
- Windows native build toolchain and darwin-arm64 tab-worker smoke checks were skipped, so those platform paths were not validated.
5387eba to
5e601ef
Compare
which only admits the current overlay owner or a widget whose epoch still matches. A widget that never activated (pet.mode "off" at startup) claims neither, so InteractiveMode.restoreComposer() -- which always routes through petWidget.remountComposer() once init() creates the widget -- silently no-opped. Palette cancel/close paths then left their CommandPaletteComponent mounted in editorContainer, leaking the modal (issue #4604, three command-palette-interactive-host failures). Composer-mount authority and overlay ownership are separate concerns: a never-activated widget still owns its host's composer mount and must remount the plain editor exactly like the no-pet fallback, while only disposal or a live successor widget revokes the mount. The active-owner case now also remounts the framed editor instead of relying on the epoch clause alone. Lore-id: 4604-palette-pet-remount Constraint: must not weaken #4591 successor-takeover isolation Tested: bun test command-palette-interactive-host.test.ts (8/8) Tested: bun test gajae-pet-widget.test.ts (62 pass incl. 2 new regressions) Tested: bun test packages/tui/test/gajae-pet.test.ts (19 pass) Not-tested: live iTerm2 pet drag interaction (requires real terminal) Confidence: high Scope-risk: narrow Reversibility: trivial
…store Container.clear() disposes children terminally, and Editor.dispose() tears down the tab-width change listener. Opening a selector cleared editorContainer with the live composer attached, so the first palette round-trip silently killed the editor's listener; every later restore re-mounted a dead editor and runtime tab-width changes stopped re-deriving composer layout. Overlay open paths now detach the reusable editor first (Container.detachChild, the tui detach-then-readd reuse contract), and the pet-aware restores (#mountEditor, restoreComposer, queued-message restore) detach both reusable mounts before clearing. Successor ownership isolation in remountComposer is unchanged. Regression coverage: a real-CustomEditor lifecycle test in the widget suite (disposal observably stops tab-width invalidations) and an end-to-end host test that fails on the pre-fix open path and passes with the fix (verified red/green by reverting the source change). Lore-id: pr4605-composer-lifecycle Constraint: preserve #4591 successor-takeover isolation (remountComposer untouched) Tested: red/green on command-palette-interactive-host (8+1 pass with fix; new test fails without) Tested: 264 pass across 12 adjacent suites; gajae-pet-widget 63 pass Tested: tui + coding-agent check clean; coding-agent build clean Confidence: high Scope-risk: moderate Reversibility: trivial
The framed-remount regression test constructs a real editor to exercise container disposal semantics, but does not otherwise need the local binding. Removing it keeps the affected-package check warning-free. Lore-id: pr4605-lint-cleanup Tested: bun test packages/coding-agent/test/gajae-pet-widget.test.ts (63 pass) Tested: bun run --cwd=packages/coding-agent check Confidence: high Scope-risk: trivial Reversibility: trivial
…ctor Review 4958165282 on the exact head found the queued-message close path still swapped in the raw editor: in an active pet session the mounted composer child is PetFramedEditor, so #restoreEditorFocus() detached the plain editor (a no-op), let clear() drop the framed wrapper, and mounted the bare editor — the pet kept emitting while the composer lost its reserve. Route the restore through ctx.restoreComposer() (the same pet-aware helper SelectorController.showSelector's done() already prefers), falling back to the plain editor swap for contexts that predate it. Also bring every remaining SelectorController test double to the new detachChild contract (child-list-correct, not a no-op cast) and dispose the real CustomEditor fixtures in the pet-widget lifecycle tests so their process-wide tab-width listeners cannot leak into later tests. Lore-id: pr4605-review-4958165282 Constraint: preserve contributor authorship and one-PR scope Tested: bun test input-controller-keybindings (65), gajae-pet-widget, command-palette-interactive-host, selector-controller-command-palette, custom-model-preset-creation, model-selector-profiles-redteam, provider-onboarding-wizard(+redteam), theme-selector-input, thinking-selector, selector-controller-resume-model, selector-controller-session-delete, login-preset-recommendation, model-selector-controller-batch, model-selector-profiles (all green) Tested: bun run --cwd=packages/coding-agent check Confidence: high Scope-risk: moderate Reversibility: trivial
… paths Review 4964398363 on the exact head found two P1 gaps: 1. Equivalent composer-replacement defects remained in production paths: /share custom-export and default-gist loaders, the OAuth API-key code input, the jobs overlay, the tasks pane, the MCP-add wizard, and the /debug log/raw-SSE viewers all cleared editorContainer with the live composer attached, so Container.clear() disposed the editor and its tab-width listener. Each open path now detaches the reusable composer first; close-path clears were already safe once their opens detach. This closes the follow-up scope filed in #4657. 2. The queued-message regressions used an array-double editorContainer, which cannot detect Container.clear() disposal. Added an InputController-level regression over a real Container + CustomEditor: three open/restore cycles through the queue selector with tab-width toggles proving the restored composer keeps its listener and input state (red-control verified: removing the open-path detach fails it). Also gave the /share temp-security double child-list-correct detachChild semantics. Lore-id: pr4605-review-4964398363 Tested: bun test input-controller-keybindings (66 incl. new real-disposal regression), share-command-temp-security, debug/log-viewer, and the full 15-suite focused surface (264 pass) Tested: bun run --cwd=packages/coding-agent check Confidence: high Scope-risk: moderate Reversibility: trivial
Review 4966516047 on the exact head found pet-enabled overlay flows still detached and restored only the raw editor while the mounted child is PetFramedEditor: the raw-editor detach was a no-op in pet mode, so the terminal clear() disposed the framed composer and the close path reattached a disposed child. The regression coverage also never exercised the controller-level pet path. Add InteractiveModeContext.detachComposer() backed by GajaePetWidget.detachComposer() (detaches both reusable mounts) and route every overlay open through it — queued-message selector, SelectorController.showSelector, OAuth code input, jobs overlay, tasks pane, /share loaders, MCP-add wizard, /debug viewers — with the raw-editor detach kept only as the fallback for pre-pet contexts. Restores already route through the pet-aware restoreComposer(). Add a controller-level active-pet regression over a real GajaePetWidget + CustomEditor + Container: queued-selector cancel, final-item delete, and move/refresh all preserve the framed mount identity, the composer stays writable, and tab-width invalidation still reaches it (red-control verified both directions: raw-editor detach or raw-editor restore each fail the test). Also restore the custom-share loader's setFocus/requestRender transition. Lore-id: pr4605-review-4966516047 Tested: bun test input-controller-keybindings (67 incl. active-pet controller regression), the 17-suite focused surface (282 pass) Tested: bun run --cwd=packages/coding-agent check Confidence: high Scope-risk: moderate Reversibility: trivial
… path Review findings on the a578085 head (snowykr, exact-head review) proved the open-path detach contract was asymmetric: jobs/tasks pane closes, the OAuth manual-code submit, both /share restore closures, and the MCP-add wizard completion still cleared the container and re-added the raw editor, so an active pet lost its PetFramedEditor mount and reserve. All six close paths now route through ctx.restoreComposer() with the plain editor-swap fallback for hosts that predate it, and regression coverage proves the framed mount survives each transition. Lore-id: 4604-close-path-restore Constraint: keep #4591 successor-takeover isolation untouched Tested: bun test qa-pet-close-path-redteam.test.ts (5 pass, 4 red without fix) Tested: bun test modes/controllers/share-command-temp-security.test.ts (6 pass, 2 new red without fix) Tested: bun test command-palette-interactive-host + gajae-pet-widget + input-controller-keybindings + qa-pet-restore-redteam (145 pass) Confidence: high Scope-risk: narrow Reversibility: trivial Co-authored-by: snowykr <snowykr@users.noreply.github.com>
InteractiveModeContext has lightweight ACP and test-context implementers that do not expose the pet-aware detach hook. Keep the hook optional while retaining guarded controller use, so the lifecycle repair does not make downstream context construction mandatory.\n\nLore-id: pr4605-review-5387eba0\nConstraint: preserve compatibility for lightweight InteractiveModeContext hosts\nTested: bun --cwd=packages/coding-agent run check\nTested: bun test packages/coding-agent/test/gajae-pet-widget.test.ts packages/coding-agent/test/qa-pet-close-path-redteam.test.ts packages/coding-agent/test/input-controller-keybindings.test.ts packages/coding-agent/test/command-palette-interactive-host.test.ts packages/coding-agent/test/modes/controllers/share-command-temp-security.test.ts packages/coding-agent/test/qa-pet-restore-redteam.test.ts\nConfidence: high\nScope-risk: narrow\nReversibility: trivial\nCo-authored-by: snowykr <snowykr@users.noreply.github.com>
5e601ef to
dfee62b
Compare
…y opens Issue #4657. Four overlay-open paths still cleared editorContainer with the live composer attached: the jobs overlay (alt+j //monitors), the tasks pane (alt+t), the OAuth API-key paste input, and the /debug log + raw-SSE viewers. Container.clear() disposes children terminally and Editor.dispose() tears down the tab-width change listener, so the first round-trip on any of these paths silently killed the composer's listener; every later restore re-mounted a dead editor and runtime tab-width changes stopped re-deriving composer layout. Each open now detaches the reusable editor first (Container.detachChild, the same detach-then-readd reuse contract showSelector and the extension-ui and pet-aware restore paths already follow from PR #4605), so the terminal clear disposes only the transient overlay. User-facing behavior is unchanged: the overlay mounts exactly as before and the composer is re-added on close. Regression coverage: one deterministic test per named path in composer-detach-overlay-paths.test.ts driving the real production open/close code (real JobsOverlayComponent/TasksPaneComponent focus lists, the captured production onPrompt closure over a real Input, and the real DebugSelector selection into the real DebugLogViewerComponent with an isolated agent-dir log source), each asserting the tab-width listener still fires across four overlay cycles, with a red control proving the probe detects genuine disposal. Lore-id: issue-4657-composer-detach Constraint: preserve PR #4605 lanes untouched (showSelector/extension-ui/pet restore shapes unchanged) Tested: red/green — 4/4 fail on unfixed tree, 4/4 pass with fix Tested: 107 pass across 9 adjacent suites (pet widget, editor dispose, tasks, jobs, debug, login) Tested: 190 pass across 7 selector/palette/input suites Tested: bun run --cwd=packages/coding-agent check + build clean; ci:test:smoke ok Tested: check:tools, node20-baseline, public-sync, schemas, docker-context, gjc-ui gates pass Confidence: high Scope-risk: narrow Reversibility: trivial
…w-SSE Review follow-up on PR #4687 (snowykr CHANGES_REQUESTED): P1 — the branch assumed the generic SelectorController.showSelector open detach had already landed (PR #4605 is still unmerged), so production /debug and bare /login still disposed the reusable composer at that boundary before any downstream detach could run. showSelector now detaches the editor before its clear(), making this branch self-contained at every open path it claims. P2 — the debug regression only exercised the log viewer branch; it now alternates logs and raw-SSE entries across the four cycles, so both changed viewer branches carry the composer-lifecycle assertion. The harness also drives the real showDebugSelector/showSelector boundary instead of a local scaffold, so the P1 fix is covered by the same test (verified: reverting only the showSelector detach makes exactly this test fail). Lightweight test doubles whose editorContainer stubs predate detachChild (model-selector batch/profiles/profiles-redteam, provider-onboarding wizard and redteam, custom-model-preset-creation, login-preset-recommendation) gain the method, matching how PR #4605 updated its own doubles. Lore-id: issue-4657-composer-detach Constraint: preserve PR #4605 lanes (no other showSelector-site changes) Tested: stash of only the showSelector detach -> debug test fails (red, 3/4) Tested: 477 pass across 28 affected suites incl. the 4-path regression file Tested: bun run --cwd=packages/coding-agent check clean Confidence: high Scope-risk: narrow Reversibility: trivial
…g mounts Fix-forward on PR #4687 (snowykr CHANGES_REQUESTED on 39df3bc), closing all five findings on current dev (7932f8a): P1 — the /share custom-share loader, the /share gist loader (command-controller.ts #shareExport) and the /mcp add wizard (runtime-mcp-command-controller.ts #handleAdd) still called editorContainer.clear() with the live reusable CustomEditor attached, terminally disposing its tab-width listener on first round-trip — the same defect PR #4605 fixed at the five highest-traffic paths and #4687 at the selector/jobs/tasks/OAuth/debug paths. All three opens now detach the composer before clearing, and the regression suite grows live-controller cycles for both paths: /share runs handleShareCommand end to end against a gated share.ts seeded in an exclusively owned agent dir (asserting the composer probe still fires while the loader owns the container, and again after the handler restores it); /mcp add drives controller.handle("/mcp add") through two full wizard mount/cancel cycles. P2 — the /debug log viewer restored the composer (selector onDone) before awaiting log-source reads, then unconditionally cleared and mounted, so a newer overlay (or a stopped UI) during the await was disposed and replaced by the stale completion. #handleViewLogs now verifies the operation's starting restore state still holds — the composer live-mounted in the editor container — and a stale completion returns without touching the active container. The guard is pet-aware: InteractiveMode.restoreComposer delegates to GajaePetWidget.remountComposer, which mounts the PetFramedEditor wrapper (not the bare editor) as the container's content, so the check accepts either shape; a naive bare-editor-only guard was verified red against a new pet-wrapped regression (the viewer never mounts). A second regression opens logs and seizes the container with the jobs overlay before any event-loop turn, then asserts the overlay survives as the sole child and the composer stays alive while detached. P2 — the /share and /debug fixtures were not hermetic under the canonical config override: GJC_CONFIG_DIR takes precedence over PI_CONFIG_DIR, so a caller-provided override could redirect log seeding and cleanup outside the test's own tree, and cleanup removed a fixed home path. Both config selectors are now pinned to one per-run UUID-named exclusive root with a mkdtemp agent dir, the resolved config root is asserted to sit inside that exclusive tree before anything is seeded (a provenance-refused override fails loudly instead of touching the real config), and the restore closure returns the exact prior env/agent-dir state and removes only the exclusive trees. P2 — the fixture hard-reset theme/tab width instead of restoring prior values and used unbounded polls plus fixed sleeps. Each test now snapshots and exactly restores lifecycle globals (tab width plus the prior theme name through the public theme API), and every wait is a bounded condition wait (waitUntil) or an explicit mount signal. P2 — the declared TestContext.editorContainer contract in selector-controller-session-delete.test.ts now includes detachChild, so the double can no longer drift from the container contract the production open path exercises. P3 — the seeded dated log is written with Bun.write per the repo file conventions (node:fs/promises stays for directory operations), and the /share wizard double in share-command-temp-security.test.ts implements the non-disposing detach contract. Lore-id: issue-4657-composer-detach Constraint: fix-forward on PR #4687 head 39df3bc; authorship preserved Test: composer-detach-overlay-paths 8 pass (tamper-red verified: stale guard, pet-unaware guard, /share detach, /mcp add detach each fail red) Test: 545 pass across modes/ + debug + pet-widget suites Test: 133 pass across the 13 PR-touched suites Test: bun run --cwd=packages/coding-agent check clean; ci:test:smoke ok Confidence: high Scope-risk: narrow Reversibility: trivial
…y opens Issue #4657. Four overlay-open paths still cleared editorContainer with the live composer attached: the jobs overlay (alt+j //monitors), the tasks pane (alt+t), the OAuth API-key paste input, and the /debug log + raw-SSE viewers. Container.clear() disposes children terminally and Editor.dispose() tears down the tab-width change listener, so the first round-trip on any of these paths silently killed the composer's listener; every later restore re-mounted a dead editor and runtime tab-width changes stopped re-deriving composer layout. Each open now detaches the reusable editor first (Container.detachChild, the same detach-then-readd reuse contract showSelector and the extension-ui and pet-aware restore paths already follow from PR #4605), so the terminal clear disposes only the transient overlay. User-facing behavior is unchanged: the overlay mounts exactly as before and the composer is re-added on close. Regression coverage: one deterministic test per named path in composer-detach-overlay-paths.test.ts driving the real production open/close code (real JobsOverlayComponent/TasksPaneComponent focus lists, the captured production onPrompt closure over a real Input, and the real DebugSelector selection into the real DebugLogViewerComponent with an isolated agent-dir log source), each asserting the tab-width listener still fires across four overlay cycles, with a red control proving the probe detects genuine disposal. Lore-id: issue-4657-composer-detach Constraint: preserve PR #4605 lanes untouched (showSelector/extension-ui/pet restore shapes unchanged) Tested: red/green — 4/4 fail on unfixed tree, 4/4 pass with fix Tested: 107 pass across 9 adjacent suites (pet widget, editor dispose, tasks, jobs, debug, login) Tested: 190 pass across 7 selector/palette/input suites Tested: bun run --cwd=packages/coding-agent check + build clean; ci:test:smoke ok Tested: check:tools, node20-baseline, public-sync, schemas, docker-context, gjc-ui gates pass Confidence: high Scope-risk: narrow Reversibility: trivial
…w-SSE Review follow-up on PR #4687 (snowykr CHANGES_REQUESTED): P1 — the branch assumed the generic SelectorController.showSelector open detach had already landed (PR #4605 is still unmerged), so production /debug and bare /login still disposed the reusable composer at that boundary before any downstream detach could run. showSelector now detaches the editor before its clear(), making this branch self-contained at every open path it claims. P2 — the debug regression only exercised the log viewer branch; it now alternates logs and raw-SSE entries across the four cycles, so both changed viewer branches carry the composer-lifecycle assertion. The harness also drives the real showDebugSelector/showSelector boundary instead of a local scaffold, so the P1 fix is covered by the same test (verified: reverting only the showSelector detach makes exactly this test fail). Lightweight test doubles whose editorContainer stubs predate detachChild (model-selector batch/profiles/profiles-redteam, provider-onboarding wizard and redteam, custom-model-preset-creation, login-preset-recommendation) gain the method, matching how PR #4605 updated its own doubles. Lore-id: issue-4657-composer-detach Constraint: preserve PR #4605 lanes (no other showSelector-site changes) Tested: stash of only the showSelector detach -> debug test fails (red, 3/4) Tested: 477 pass across 28 affected suites incl. the 4-path regression file Tested: bun run --cwd=packages/coding-agent check clean Confidence: high Scope-risk: narrow Reversibility: trivial
…g mounts Fix-forward on PR #4687 (snowykr CHANGES_REQUESTED on 39df3bc), closing all five findings on current dev (7932f8a): P1 — the /share custom-share loader, the /share gist loader (command-controller.ts #shareExport) and the /mcp add wizard (runtime-mcp-command-controller.ts #handleAdd) still called editorContainer.clear() with the live reusable CustomEditor attached, terminally disposing its tab-width listener on first round-trip — the same defect PR #4605 fixed at the five highest-traffic paths and #4687 at the selector/jobs/tasks/OAuth/debug paths. All three opens now detach the composer before clearing, and the regression suite grows live-controller cycles for both paths: /share runs handleShareCommand end to end against a gated share.ts seeded in an exclusively owned agent dir (asserting the composer probe still fires while the loader owns the container, and again after the handler restores it); /mcp add drives controller.handle("/mcp add") through two full wizard mount/cancel cycles. P2 — the /debug log viewer restored the composer (selector onDone) before awaiting log-source reads, then unconditionally cleared and mounted, so a newer overlay (or a stopped UI) during the await was disposed and replaced by the stale completion. #handleViewLogs now verifies the operation's starting restore state still holds — the composer live-mounted in the editor container — and a stale completion returns without touching the active container. The guard is pet-aware: InteractiveMode.restoreComposer delegates to GajaePetWidget.remountComposer, which mounts the PetFramedEditor wrapper (not the bare editor) as the container's content, so the check accepts either shape; a naive bare-editor-only guard was verified red against a new pet-wrapped regression (the viewer never mounts). A second regression opens logs and seizes the container with the jobs overlay before any event-loop turn, then asserts the overlay survives as the sole child and the composer stays alive while detached. P2 — the /share and /debug fixtures were not hermetic under the canonical config override: GJC_CONFIG_DIR takes precedence over PI_CONFIG_DIR, so a caller-provided override could redirect log seeding and cleanup outside the test's own tree, and cleanup removed a fixed home path. Both config selectors are now pinned to one per-run UUID-named exclusive root with a mkdtemp agent dir, the resolved config root is asserted to sit inside that exclusive tree before anything is seeded (a provenance-refused override fails loudly instead of touching the real config), and the restore closure returns the exact prior env/agent-dir state and removes only the exclusive trees. P2 — the fixture hard-reset theme/tab width instead of restoring prior values and used unbounded polls plus fixed sleeps. Each test now snapshots and exactly restores lifecycle globals (tab width plus the prior theme name through the public theme API), and every wait is a bounded condition wait (waitUntil) or an explicit mount signal. P2 — the declared TestContext.editorContainer contract in selector-controller-session-delete.test.ts now includes detachChild, so the double can no longer drift from the container contract the production open path exercises. P3 — the seeded dated log is written with Bun.write per the repo file conventions (node:fs/promises stays for directory operations), and the /share wizard double in share-command-temp-security.test.ts implements the non-disposing detach contract. Lore-id: issue-4657-composer-detach Constraint: fix-forward on PR #4687 head 39df3bc; authorship preserved Test: composer-detach-overlay-paths 8 pass (tamper-red verified: stale guard, pet-unaware guard, /share detach, /mcp add detach each fail red) Test: 545 pass across modes/ + debug + pet-widget suites Test: 133 pass across the 13 PR-touched suites Test: bun run --cwd=packages/coding-agent check clean; ci:test:smoke ok Confidence: high Scope-risk: narrow Reversibility: trivial
|
Replacement head dfee62b is pushed and bound to the current dev base 79fe412 (diff digest sha256:969e0d4b01a592b059acc3ae2c432ca1612128cb53b2044a7deabf48f79da73a). The latest exact-head P2 compatibility finding is fixed: InteractiveModeContext.detachComposer is optional and all controller call sites remain guarded. Local focused lifecycle coverage is 156/156 green and coding-agent check is clean; exact-head affected-path CI is green for the completed jobs. Please review this exact head and record an authenticated APPROVED review if clean; the PR contract remains needs-human until that independent review exists. |
|
Evidence-backed close for PR #4605. Replacement head dfee62b is pushed and contains the preserved fix history plus the latest exact-head compatibility correction. The binary diff digest is sha256:969e0d4b01a592b059acc3ae2c432ca1612128cb53b2044a7deabf48f79da73a. Local focused lifecycle/red-team coverage is 156/156 green; coding-agent check, affected-path CI, native build, and state gates passed. The remaining exact-head contract blocker is the required independent authenticated review, which was requested but not obtained. Closing without merge preserves the review gate and source evidence; no bypass or release was performed. |
PR #4605's queued-selector fix was not merged into current dev, leaving one reachable clear-with-composer path outside the prior #4687 enumeration. Detach the reusable composer before queued-selector open and prove real Container disposal semantics across repeated restore cycles.\n\nIssue: #4657\nLore-id: issue-4657-composer-detach\nConstraint: complete the remaining overlay-open lifecycle on current dev without duplicating merged equivalents\nTested: bun test packages/coding-agent/test/input-controller-keybindings.test.ts\nConfidence: high\nScope-risk: moderate\nReversibility: trivial
…y opens Issue #4657. Four overlay-open paths still cleared editorContainer with the live composer attached: the jobs overlay (alt+j //monitors), the tasks pane (alt+t), the OAuth API-key paste input, and the /debug log + raw-SSE viewers. Container.clear() disposes children terminally and Editor.dispose() tears down the tab-width change listener, so the first round-trip on any of these paths silently killed the composer's listener; every later restore re-mounted a dead editor and runtime tab-width changes stopped re-deriving composer layout. Each open now detaches the reusable editor first (Container.detachChild, the same detach-then-readd reuse contract showSelector and the extension-ui and pet-aware restore paths already follow from PR #4605), so the terminal clear disposes only the transient overlay. User-facing behavior is unchanged: the overlay mounts exactly as before and the composer is re-added on close. Regression coverage: one deterministic test per named path in composer-detach-overlay-paths.test.ts driving the real production open/close code (real JobsOverlayComponent/TasksPaneComponent focus lists, the captured production onPrompt closure over a real Input, and the real DebugSelector selection into the real DebugLogViewerComponent with an isolated agent-dir log source), each asserting the tab-width listener still fires across four overlay cycles, with a red control proving the probe detects genuine disposal. Lore-id: issue-4657-composer-detach Constraint: preserve PR #4605 lanes untouched (showSelector/extension-ui/pet restore shapes unchanged) Tested: red/green — 4/4 fail on unfixed tree, 4/4 pass with fix Tested: 107 pass across 9 adjacent suites (pet widget, editor dispose, tasks, jobs, debug, login) Tested: 190 pass across 7 selector/palette/input suites Tested: bun run --cwd=packages/coding-agent check + build clean; ci:test:smoke ok Tested: check:tools, node20-baseline, public-sync, schemas, docker-context, gjc-ui gates pass Confidence: high Scope-risk: narrow Reversibility: trivial
…w-SSE Review follow-up on PR #4687 (snowykr CHANGES_REQUESTED): P1 — the branch assumed the generic SelectorController.showSelector open detach had already landed (PR #4605 is still unmerged), so production /debug and bare /login still disposed the reusable composer at that boundary before any downstream detach could run. showSelector now detaches the editor before its clear(), making this branch self-contained at every open path it claims. P2 — the debug regression only exercised the log viewer branch; it now alternates logs and raw-SSE entries across the four cycles, so both changed viewer branches carry the composer-lifecycle assertion. The harness also drives the real showDebugSelector/showSelector boundary instead of a local scaffold, so the P1 fix is covered by the same test (verified: reverting only the showSelector detach makes exactly this test fail). Lightweight test doubles whose editorContainer stubs predate detachChild (model-selector batch/profiles/profiles-redteam, provider-onboarding wizard and redteam, custom-model-preset-creation, login-preset-recommendation) gain the method, matching how PR #4605 updated its own doubles. Lore-id: issue-4657-composer-detach Constraint: preserve PR #4605 lanes (no other showSelector-site changes) Tested: stash of only the showSelector detach -> debug test fails (red, 3/4) Tested: 477 pass across 28 affected suites incl. the 4-path regression file Tested: bun run --cwd=packages/coding-agent check clean Confidence: high Scope-risk: narrow Reversibility: trivial
…g mounts Fix-forward on PR #4687 (snowykr CHANGES_REQUESTED on 39df3bc), closing all five findings on current dev (7932f8a): P1 — the /share custom-share loader, the /share gist loader (command-controller.ts #shareExport) and the /mcp add wizard (runtime-mcp-command-controller.ts #handleAdd) still called editorContainer.clear() with the live reusable CustomEditor attached, terminally disposing its tab-width listener on first round-trip — the same defect PR #4605 fixed at the five highest-traffic paths and #4687 at the selector/jobs/tasks/OAuth/debug paths. All three opens now detach the composer before clearing, and the regression suite grows live-controller cycles for both paths: /share runs handleShareCommand end to end against a gated share.ts seeded in an exclusively owned agent dir (asserting the composer probe still fires while the loader owns the container, and again after the handler restores it); /mcp add drives controller.handle("/mcp add") through two full wizard mount/cancel cycles. P2 — the /debug log viewer restored the composer (selector onDone) before awaiting log-source reads, then unconditionally cleared and mounted, so a newer overlay (or a stopped UI) during the await was disposed and replaced by the stale completion. #handleViewLogs now verifies the operation's starting restore state still holds — the composer live-mounted in the editor container — and a stale completion returns without touching the active container. The guard is pet-aware: InteractiveMode.restoreComposer delegates to GajaePetWidget.remountComposer, which mounts the PetFramedEditor wrapper (not the bare editor) as the container's content, so the check accepts either shape; a naive bare-editor-only guard was verified red against a new pet-wrapped regression (the viewer never mounts). A second regression opens logs and seizes the container with the jobs overlay before any event-loop turn, then asserts the overlay survives as the sole child and the composer stays alive while detached. P2 — the /share and /debug fixtures were not hermetic under the canonical config override: GJC_CONFIG_DIR takes precedence over PI_CONFIG_DIR, so a caller-provided override could redirect log seeding and cleanup outside the test's own tree, and cleanup removed a fixed home path. Both config selectors are now pinned to one per-run UUID-named exclusive root with a mkdtemp agent dir, the resolved config root is asserted to sit inside that exclusive tree before anything is seeded (a provenance-refused override fails loudly instead of touching the real config), and the restore closure returns the exact prior env/agent-dir state and removes only the exclusive trees. P2 — the fixture hard-reset theme/tab width instead of restoring prior values and used unbounded polls plus fixed sleeps. Each test now snapshots and exactly restores lifecycle globals (tab width plus the prior theme name through the public theme API), and every wait is a bounded condition wait (waitUntil) or an explicit mount signal. P2 — the declared TestContext.editorContainer contract in selector-controller-session-delete.test.ts now includes detachChild, so the double can no longer drift from the container contract the production open path exercises. P3 — the seeded dated log is written with Bun.write per the repo file conventions (node:fs/promises stays for directory operations), and the /share wizard double in share-command-temp-security.test.ts implements the non-disposing detach contract. Lore-id: issue-4657-composer-detach Constraint: fix-forward on PR #4687 head 39df3bc; authorship preserved Test: composer-detach-overlay-paths 8 pass (tamper-red verified: stale guard, pet-unaware guard, /share detach, /mcp add detach each fail red) Test: 545 pass across modes/ + debug + pet-widget suites Test: 133 pass across the 13 PR-touched suites Test: bun run --cwd=packages/coding-agent check clean; ci:test:smoke ok Confidence: high Scope-risk: narrow Reversibility: trivial
PR #4605's queued-selector fix was not merged into current dev, leaving one reachable clear-with-composer path outside the prior #4687 enumeration. Detach the reusable composer before queued-selector open and prove real Container disposal semantics across repeated restore cycles.\n\nIssue: #4657\nLore-id: issue-4657-composer-detach\nConstraint: complete the remaining overlay-open lifecycle on current dev without duplicating merged equivalents\nTested: bun test packages/coding-agent/test/input-controller-keybindings.test.ts\nConfidence: high\nScope-risk: moderate\nReversibility: trivial
What
Fixes a live editor-lifecycle defect in selector and command-palette overlay transitions, with regression coverage that exercises disposal semantics instead of merely checking child replacement.
Container.clear()terminally disposes attached children. Before this change, selector opens and queued-message-selector opens calledclear()while the reusable composer was attached;Editor.dispose()then removed the tab-width listener. Later restores re-mounted that disposed editor, so runtime tab-width changes stopped updating composer layout after the first overlay cycle.The implementation uses the established TUI detach-then-readd contract:
52dad458's successor-ownership guard inremountComposer()unchanged;restoreComposer()so an active pet keeps itsPetFramedEditormount and composer reserve after closing/deleting/moving through the queue selector.Review round 3 fix-forward (owner lane)
The 2026-08-20 exact-head review (snowykr, 4 P1 findings) proved the open-path detach contract was asymmetric — close paths still re-added the raw editor. All findings are fixed in
dfee62b9f:selector-controller.ts:3662-3670) → the jobs overlay close, tasks pane close, and OAuth manual-code submit now route throughctx.restoreComposer()(plain-editor-swap fallback for lightweight hosts), matching theshowSelectorclose contract.command-controller.ts:205-208) → the custom-sharerestoreEditorclosure restores throughctx.restoreComposer().command-controller.ts:205-207) → the default-gistrestoreEditorclosure restores throughctx.restoreComposer()as well.command-controller.ts:204-207) → both share paths above; plus the adjacent same-pattern siteruntime-mcp-command-controller.ts:471-475(MCP-add wizarddone()) fixed identically.New regression coverage (all proven red on the unfixed head, green on
dfee62b9f):qa-pet-close-path-redteam.test.ts(5 tests: jobs, tasks, MCP wizard, OAuth manual-code, legacy-fallback) and 2 new cases inshare-command-temp-security.test.ts(custom share, gist upload, with a fakegh).Latest exact-head review fix-forward
The exact-head review of
dfee62b9fidentified a P2 compatibility gap:detachComposer()had become mandatory onInteractiveModeContext, even though ACP and lightweight test hosts intentionally omit optional lifecycle helpers. The replacement headdfee62b9fmakes that method optional and retains guarded use at every controller call site; the concreteInteractiveModeimplementation remains pet-aware.Reconstruction
The original six commits were rebased onto current dev (latest:
e73212bfb) withgit rebase --committer-date-is-author-date, preserving contributor authorshipYeachan Heo <yeachan.heo@gmail.com>and original author dates. The fix-forward commit carriesCo-authored-by: snowykrfor the review findings. The source delta against dev is unchanged from the previously reviewed scope plus the close-path fix-forward; intervening dev commits touch onlypackages/ai, with zero file overlap.Exact evidence
dfee62b9f4af5e61513371f5600b7ba03e11af16dev79fe412259c7e27c37de699cc5edf0c64bd82864(head contains base;git merge-base --is-ancestorverified)sha256:969e0d4b01a592b059acc3ae2c432ca1612128cb53b2044a7deabf48f79da73afromgit diff --binary --full-index --no-ext-diff 79fe412259...dfee62b9f4dfee62b9f: 156 tests green across the 6 core suites (qa-pet-close-path-redteam 5, share-command-temp-security 6, command-palette-interactive-host 9, gajae-pet-widget 63, input-controller-keybindings 67, qa-pet-restore-redteam 6); 321 more green across the remaining PR-touched selector/controller/onboarding suites and adjacent pet/input suites (178 + 117 + 148 in three runs);bun --cwd=packages/coding-agent run checkclean (biome + tsc); repository fast gate (bun scripts/verify-gjc-state-writers.ts --fail) PASS.32509577137has the affected-path plan, evidence producer, coding-agent check, native build, CLI smoke, all 17 affected tests, and state gates passing; the only failing current-head checks arePR contract bootstrapandValidate exact-head PR contractin32509577137/32509571550, both intentionally rejecting theneeds-humanverdict until independent approval.32347673567ona578085809(superseded by the replacement head),32185025067(3196de229c),32181321301(6e5670266b),32154701523(6d12e769e6),32099895353(eef82ef5dc), cancelled32099837032/32181054337, contract-red32099835720/32099895625/32181321780/32497550441(superseded by the replacement;needs-humanremained correct pending independent approval).Risk classification
low-risk— ordinary fix/maintenance; the repository owner may use the explicitmerge-self-approvedsolo verdict (no independent human review; the verdict name itself records this) with a risk-record comment bound to the exact head.regression-risk— fix with material regression risk; requires one assigned independent domain reviewer whose authenticated exact-headAPPROVEDreview the gate verifies (extra:independent:<login>; the token alone never suffices).high-risk— large refactor, feature, or materially high-risk change (security/auth/install/remove/public API/destructive lifecycle/architecture); requires one assigned independent domain reviewer with an authenticated exact-headAPPROVEDreview (extra:independent:<login>).Review state
Prior
CHANGES_REQUESTEDreviews by @snowykr are addressed head-on throughdfee62b9f; the latest P2 compatibility finding is fixed by makingInteractiveModeContext.detachComposeroptional while keeping every controller call guarded. Owner: @Yeachan-Heo. The merge blocker is a fresh non-author exact-headAPPROVEDreview from @snowykr (write permission verified) ondfee62b9f.GJC verdict
devbun checkpasses[repo owner's gaebal-gajae (clawdbot) 🦞]