Canvas layout status sticks on "Not saved — keep this tab open" on a real server #395
Open
opened 2026-11-06 12:23:02 +00:00 by kayg
·
14 comments
No Branch/Tag specified
main
j470b-server-mesh-rejoin
mesh-compat-382
j444b-d381-key-wins-flood
j461b-macos-utun-reader-thread
j475b-remove-host-motion
fix-reconnect-new-link
j481-d380
dev
j479-mesh-soak
fix/j476-host-facts
j475-motion-pill-split
j470-mac-node-join
root-upgrade-d376
j474-review-merge
latency-targets-120hz
nightly-suite-shards
gotatun-pin-j469
macos-utun-reader-thread
fix-flood-echo-stall
wip/j416
motion-fast-ease-out
mesh-addhost-j259
takeover-close-terminals-d368
kbd-instant-followups
fix-trust-key-double-click
nightly-suite-partial-report
appwin-icons-j207
rusqlite-slice1
swipe-content-first-d374
mesh-mtu-381
addhost-instant-open
glass-contrast-d365-mock
glass-contrast-d365
signout-followups-442
fix-startup-toast-red
research-gotatun-matrix-j317
themes-sync-j304
fix-load-flakes
fix-nix-zig-patch
fix-compute01-browser-baseline
ratchet-size-baseline-419
predict-auto-d130
revoke-coverage-d367
nightly-suite-queue-timeout
nightly-browser-suite
fix-rustsec-2026-0459
fonts-ioskeley-obel
log-retention-d375
fix-kbd-no-anim-427
fix-dev-red-specs-2
takeover-proof-305-j205
wip/j410-server-pump
ui-consistency-d372
fix-osc52-clipboard
libghostty-vt-1.0
forget-retired-host-d369
j404-merge-426
fix-setup-lock-race
fix-touch-select-426
revoke-access-report-ui
fix-predictive-echo-specs
fix-selinux-agent-exec
wip/j395-checkpoint2
wip/j395
j391-review-scratch
ui-consistency-j195-deferred-rebased
fix-rustc-paths-421
decisions-wording-341
fix-install-suite-red
fix-dev-rtt-specs
fix-pending-removal-align
fix-privileged-skip
fix-install-suite-404
review-j368
mesh-dns-j254
revoke-access-report-d356b
revoke-privilege-round5
fix-removal-confirm
review-j360
gotatun-rotation-367
deps-refresh-340
review-j354
review-J351
review-j350
addhost-takeover-d359
revoke-report-items-347
revoke-end-sessions-d362
review-j348
size-mesh-ssh-346
size-trim-337
revoke-access-diff-d356
j259-pre-rebase-b06c5a6
j305-ended-at-start-spec
addhost-sudo-consent
fix-layout-real-specs-411
fix-shell-open-411
fix-console-hosts-373
fix-shell-exit
fuzz-host-name-353
fix-host-details-410
macos-filter-j197
research-gotatun-tricks
revoke-p3-j216l
stale-state-pill
revoke-followups-j216k
revoke-progress-j216e
backup/macos01-20261106/lume/j197
backup/macos01-20261106/lume/appwin-icons-j207-stash0
backup/macos01-20261106/claude/stash-0
backup/macos01-20261106/claude/stash-1
backup/macos01-20261106/claude/J80-macos-s1
backup/macos01-20261106/claude/J80
backup/macos01-20261106/claude/J80-slice1-20261102
backup/macos01-20261106/claude/J16b-appwin-on-dev
backup/macos01-20261106/claude/appwin-launch-idle-146
backup/macos01-20261106/claude/J80-s1-run
wip/j259
trust-285-p3c
runtime-lock-j216f
mesh-ssh-p3-j175
tun-proof-p3b
trust-285-p3
terminal-polish-j271
tun-proof-p3
ssh-install-flakes-j216i
fuzz-fixes-j264
trust-285-a
trust-285-b
restart-oversized-fixture-j198
backup/macos01-20261106/lume/appwin-icons-j207
restart-bound-j216h
up-preflight-p3-j198
backup/macos01-20261106/lume/j251
up-preflight-j198
wip/up-preflight-j198-j245-p2
wip/up-preflight-j198-j245-p1
agent-fsync-offloop-j196
wip/up-preflight-j198-j245
settings-offscreen-j231
wip/up-preflight-j198-j226-inspection
wip/j103f
themes-j206
wip/up-preflight-j198-j226
flakes-j216c
backup/macos01-20261106/claude/j197
flakes-j216b
backup/macos01-20261106/claude/j197-red
wip/up-preflight-j198-review
flakes-j216
backup/macos01-20261106/claude/appwin-icons-j207
engineering-j208b
appwin-mock-j209
toast-stack-j165
engineering-j208
decisions-d351
decisions-d350
decisions-d349
appearance-presets-mock-j129
decisions-d348
docs-audit-spec
doc-coverage-baseline-raise
decisions-d347
decisions-d346b
decisions-d346
j88h-profile
ui-consistency-j195-deferred
ui-consistency-j195
engineering-practices-j194
preferred-login-ui-j166
signin-pools-263-j174
macos-node-265-s1
decisions-d345
decisions-d344
proxy-cgroup-j164
verify-quick-fuzz-329
proxy-start-io-j168
timing-flakes-j163
up-263-s1
mcp-events-j147
verify-quick-fuzz-j158
proxy-p3-j159
agent-resource-j157-detect
wip/j103d-final
remote-cancel-hang-j155
proxy-traefik-281-j62d
j149-verify-quick
proxy-traefik-281-j62b
j150-drop-unnamed
wip/j79d-red-r4
wip/j103d-d332
rootless-toast-j142
wip/j103d-head
wip/j103d
j125c-mcp
macos-build-j136
backup/macos01-20261106/claude/j136
wip/j79d-red-cli
wip/j79d-red-server
j121-link-liveness
j118c-tracer-failures
wip/j79c-head
release-desktop-j131
j124c-signed-macos
wip/j79c-red-p3
wip/j79c-red-p23
wip/j79c-red-p22
merge-j128-check
perf-deferred-j128
sidebar-toggle-260
j127-shortcut-changed-dot
j125-mcp
j118-tracer-failures
j126-account-summary
settings-sections-mock-j92
j120-pinch-lock
sidebar-292
backup/macos01-20261106/claude/j120
taste-2026-11-03
row-actions-242-215
backup/macos01-20261106/claude/stash-0-untracked
backup/macos01-20261106/claude/J80-265-s1-run
backup/macos01-20261106/claude/J80-macos-s1-20261102
j112-canvas-commands
ui-no-overshoot-scroll
fix-recovery-restart
backup/macos01-20261106/claude/stash-1-untracked
taste-2026-11-02
size-baseline-ratchet
machine-cards-structure-j108
j109-boot-copy-mockups
fix-dev-red
wip/up-263-s1
j95b-settings-rows
proxy-traefik-281
j95-option-rows
docs-human-j91
docs-keys-palette
fix-flood-1013-270-j38d
mesh-ssh-s1
mesh-ssh-s0
addhost-d291-tunnel
test-stability-load
mesh-relay-220
repro-296
shim-thp-off-24
mesh-pins-258-v2
mesh-dns
signin-d311-merged
docs-pages
ui-size-283
fix-ui-batch-specs
wip/j12b-edge-update
gate-on-arm
fix-rename-sync-293
trust-285-j60b
fix-focus-ring-clip
integ-ui-1101
edge-update-v2
signin-d311
taste-2026-11-01
layout-per-tab-250
ui-approved-splitopt-224
ui-approved-splitopt-224-on-dev
icon-text-centre-282
ui-approved-189-212-216
research-reverse-proxy
transfer-budget-320
review-mesh-pins-258-r2
sidebar-pill-259
review-mesh-relay-220
wip/j12-edge-update-v2
nix-package
status-crash-loop-237
fix-dev-test-failures
fix-fixture-socket-path
wip/mesh-relay-220-pre-dev-advance-rebase
fix-canvas-side-clip
research-head-to-head
frosted-real-256
review-mesh-pins-258
wip/mesh-relay-220-pre-final-rebase
review-layout-per-tab-250-r2
broad-suite-triage
nix-devshell
review-status-crash-loop-237-r3
wip/nix-devshell-pre-p3
taste-frosted-real
wip/code-serve-demo-2026-10-30
wip/code-serve-glass-2026-10-30
wip/code-serve-clusters-2026-10-30
wip/code-merge-release94-dev-2026-10-30
wip/rproxy-delta-scratch-2026-10-30
wip/stale-note-257-staged-2026-10-30
wip/d241-size-staged-2026-10-30
review-broad-suite-207
wip/mesh-pins-258-pre-split
wip/mesh-relay-220-pre-rebase
mesh-measure-220
plan-211-update
review-edge-autoupdate-r3
perf24-reconnect
review-stale-note-r2
release-prod-live
d241-shell-marker
review-mesh-pins-server-258
review-revocation-r3
review-setup-token-r2
review-sync-journal-r3
wip/review-mesh-pins-258-prune-review-1995fee46
frosted-scroll-256
sync-journal-r3
revocation-per-account-wip
review-tokio-asyncfd-0386
review-sync-p3s
wip/stash-8by64-stash0-2026-10-30
mesh-drag-r2
setup-token-data-wip
release-9-4
auth-busy-1013
mesh-view-overlap
sync-p3s
review-auth-busy-wake
review-readd-r8-wip
mesh-drag-bounds
audit-auth-93
app-windows-stream-start
review-authority-cap-r3
app-windows-r7-measure
readd-minimal-r6
readd-minimal-r5
app-windows-r7-soak
readd-minimal-r4
app-windows-r7
app-windows-r6
readd-minimal-r3
readd-minimal-r2
readd-minimal
merge-release8-dev
review-busy-page-liveness-r2
review-busy-page-liveness
mesh-view-trim
review-authority-read-retry-r2
review-remove-uninstall-r2
release-retry-reattach
release-retry
review-remove-uninstall
review-authority-read-retry
app-windows-ship
mesh-autoswitch-p2
mesh-ssh
mesh-relay
mesh-macos-node
screen-and-control
session-labels
review-real-logins-r5
review-real-logins-r4
review-real-logins-r3
shim-memory-r2
review-real-logins-r2
shim-memory
shadow-addhost-instant
shadow-real-logins
shadow-same-session-flood
shadow-prod-perf-fix
hosts-from-server
app-windows-real
app-windows-transport
release-prod-6w
mesh-policy-verify
quiet-close-toast
dev-merge-r4
review-mesh-agent-transport
review-app-windows-transport
settings-roundtrip
review-release-pipeline-r4
mesh-agent-transport
ended-strip-fix
review-audit-ca-update-d244
settings-rows
phone-width
signout-clear
fix-restart-tests
dev-merge-rp
signer-hardening
review-audit-ca-update
terminal-fidelity
review-audit-ca-update-wip
mesh-integrate
review-mesh-agent-transport-515d3712
review-mesh-dataplane-feec1988
mesh-dataplane
signout
research-canvas-node
wip/review-renderer-cost
wip/review-terminal-fidelity
padlock-glyph
review-release-pipeline-2e95b689
phone-polish
sidebar-polish
inbox-generic
review-mesh-dataplane-378d4055
prod-batch
ui-inbox
ui-ca-unlock
ui-machine-sections
review-mesh-dataplane-a5f744fb
tls-session-prod
session-bounds-prod
fix-dead-session
session-bounds
tls-session
palette-polish
shortcuts-d222
research-appmeta
phone-gutter
ui-centre-button
wip/ui-centre-button
wip/tls-session
review-addhost-breakit
ui-platform
renderer-cost
remote-agents
mesh-api
ssh-bootstrap
prod-hotfix2
prod-fixes
review-remote-agents
review-mesh-dataplane
session-life
research-browser
review-mesh-api
research-capture
review-mesh-api-round3
review-mesh-api-round1
gate-stability
review-gate-stability
review-relay-direct
review-mesh-api-findings
proto-sessions
font-picker
proto-addhost
proto-windows
mesh-core
latency-gate
research-bsd
research-discovery
keytray-mods
mock-core
ui-tweaks
fix-motion-sel
fix-motion
integrate2
fix-robust
t-web
t-server
vt-ghostty
fix-snap-echo
t-web-auth
review-vt2
t-agent
lane-protocol
lane-shim
lane-auth
s1-base
s1-contracts
s1-workspace
No results found.
No labels
launch-qa
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
kayg/infraternal#395
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
J305 fix job.
Problem. The canvas layout-save status "Not saved — keep this tab open" sticks on a real server.
74b9c4d80, #305 comment 82162): while a refused-shell window is open, the notice stays for at least 20 s and clears when that window closes (compute01:~claude/.cache/codex-scratch/j299/pending.log).real/stale-note-257-reviewfails on clean origin/dev on the same notice once its missingwatchLayoutcall is patched; later reruns ofreal/stale-screen-d309-review.spec.tshit it too (compute01:~claude/j298-real*.log).Scope.
watchLayouttoreal/stale-note-257-review.spec.ts.Branch
fix-layout-not-savedfrom origin/dev. Gate:verify quick, then affected real specs 5× with one worker.J305 start. Branch
fix-layout-not-saved, base origin/dev7b14fbb664cbfb9b2b9b8a56f724d924424accdb.First lead (code reading).
sync.tsmarks every terminal window without asessionIdas unresolved;LayoutJournal.next()holds that window'sopen/splitintent (and so everything queued behind it) until the session id arrives. A refused shell never gets one, so its intent waits forever andstore.pendingstays true. Closing the window cancels the unsent birth incapture(), which matches J299's "clears when that window closes". J298's sticking happens inenrol()on the first fresh shell, with an older server binary (2be58ce2c); that may be a second cause, to be reproduced on a dev-built server.Plan (~3 h).
watchLayoutto the 257 spec. 45 min.verify quick, affected real specs 5× with one worker, evidence here. 45 min.Findings (evidence on compute01). Two separate causes. Neither is the server refusing a save: every
canvas.layout.applyin the traces gotok=true.1. Refused shell (J299): the client held the save forever. A new terminal window has no session id until its shell starts.
sync.tsmarks it unresolved, andLayoutJournal.next()holds itsopenintent, and every edit behind it, until the id arrives. A refused shell never gets an id, sostore.pendingstayed true and the status stayed up. Closing the window cancelled the unsent birth, so it cleared then. The server was never asked.~claude/j305-red.log): the extendedreal/rootless-refusal.spec.tsfails at both viewports withExpected: 0 / Received: 1for the notice, 10 s after the refusal.140548589): an in-memoryopenRefusedflag on the window model. A refused window stays on screen with its strip but leaves the saved document, so nothing waits. A later successful shell is saved as an ordinary birth. Unit test insync.test.ts.2. J298's enrol failures: not a stuck save, a doubled notice card. With
watchLayoutpatched in, the 257 spec failed 5/10 on origin/dev (~claude/j298-real*equivalent:~claude/j305-red257.log). A diagnostic (~claude/j305-diag.log, 6/6 failing) shows every layout call confirmed (pending: [], revision 3) at the failure. ThetoBeHidden()throws at once on a strict-mode violation: "resolved to 2 elements". At sign-in, three edits land within ~500 ms, so the status shows, hides, shows.mountToast.show()only reused live cards, so each re-show built a second card beside the one still in its 190 ms exit: two identical notices stacked.78e6cb914):show()takes a leaving card with the same key back (exit cancelled, open transition reverses). Unit test intoast-stack.test.ts. Red before the fix:Expected length: 1, Received length: 2.Also
d7474aff3:watchLayoutinstale-note-257-review.spec.ts.Not changed: the status still appears for every edit while it waits for its receipt (D312). Delaying it by a round trip would reduce flicker at 300 ms RTT, but that's the owner's call. Question for kayg: should "Not saved — keep this tab open" wait about one round trip before it shows? I'd recommend yes, since an edit that's confirmed in one RTT isn't really "not saved". Safe default: unchanged.
Real-server evidence, 5× with one worker (compute01; server = musl release built from this branch at d7474aff3..78e6cb914, whose server code is identical to origin/dev; tracer built at
78e6cb914). Command:playwright test -c playwright.real.config.ts --workers=1 --repeat-each=5 real/rootless-refusal.spec.ts real/stale-note-257-review.spec.ts real/stale-screen-d309-review.spec.ts real/layout-per-tab.spec.ts. Log:compute01:~claude/j305-green5.log.That's 18 tests × 5: rootless refusal desktop/phone, all 10 cases of the 257 spec, both d309 tests, and the 4 layout-per-tab tests (which assert exactly one notice). Every one passed 5/5, with no flaky retries (
retries: 0).Before (origin/dev
7b14fbb66tracer, same server):~claude/j305-red.log: rootless refusal 2/2 failed, with the notice still visible 10 s after the refusal (Expected: 0, Received: 1).~claude/j305-red257.log: 257 spec withwatchLayout, 5/10 failed (strict mode violation … resolved to 2 elements).~claude/j305-diag.log: diagnostic, 6/6 failed with every layout call confirmed.Since then, the branch merged origin/dev
48358a5fa(tracer change: onlycanvas/windows/frame.ts) and added9358d72b4(doc comments only).Screenshots: refused window after the fix, desktop and phone, with no "Not saved" notice (attached).
J305 done. Branch
fix-layout-not-saved, head9358d72b4686bcc9e5d17f9536dc2604a037c5c4(pushed, not merged).Commits on top of origin/dev:
d7474aff3test(tracer): install the layout observer in the #257 real spec140548589fix(canvas): a refused shell's window no longer holds layout saves78e6cb914fix(ui): a toast shown again while leaving reuses its card5eabddb8emerge origin/dev48358a5fa9358d72b4docs(canvas): doc comments for WindowModel and createDocumentSync (D345)Cause, short version: the server never refused a save. (1) Refused shell: the client held the window's birth forever, waiting for a session id that never comes. (2) J298's enrol failures: a doubled notice card during the sign-in burst of edits, which tripped Playwright's strict mode. Details are in comment 82289. No server test: the server isn't involved, and every apply in the traces came back
ok=true.scripts/verify faston compute01 at9358d72b46(logcompute01:~claude/j305-fast.log, FAST_EXIT=0):Static sizes (report only): x86_64 27777072 B, aarch64 24994464 B. This branch changes no Rust code, so the delta against origin/dev is 0. The "baseline" deltas in the log are against the old D243 baseline.
verify quickbefore that: all 10 rows PASS (~claude/j305-quick.log). The first quick run failedfmton 5 server files that were also unformatted on unmodified origin/dev7b14fbb66; merging dev48358a5facleared it.Real specs, 5×, one worker: 90/90 passed (comment 82364,
~claude/j305-green5.log).Open question for kayg (safe default: unchanged): should "Not saved — keep this tab open" wait about one round trip before it shows, so an edit confirmed within one RTT never shows it? Today it shows on every edit, briefly (D312).
Not done: J299's exact flow on test.ssh.kayg.org was not rerun. That needs this branch deployed there. The rootless real spec reproduces the same state locally (red before the fix, green 5/5 after).
Cleanup: target dir and the baseline worktree on compute01 deleted. Logs and evidence kept in
~claude/j305-*.logand~claude/j305-evidence/.exitreports code 127 and the window does not close #393exitreports code 127 and the window does not close #393J305 D358 progress. Branch head
06c53cb40(one commit on top of the dev merge279484834, force-pushed while amending it).pending-notice.test.ts): 5/5 pass. Red before: the module was missing.test/pending-notice.spec.ts. Red on the old code (~claude/j305-d358-red.log): the notice flashed on a normal resize (Received: 1), and with a held save it showed after 60 ms. Green after (~claude/j305-d358-green.log): 2/2.verify quick: QUICK_EXIT=0 (~claude/j305-quick.log).~claude/j305-final2.log): 25 passed, 5 failed.279484834, the branch head without D358 (~claude/j305-pred358.log): layout-phone "does not move or animate", both layout-session-boundary tests, and layout-session-incarnation. On origin/dev37c93637ethey fail earlier, on the doubled-card strict-mode error this branch fixes (~claude/j305-basecheck.log). These are separate, previously hidden failures and are not touched here.settled()used the notice as its "something still waits" signal, which D358 delays. That's fixed in the commit:settled()now waits until the server's copy holds every drawn window. One window still stays unconfirmed for over 15 s after a split, and I'm diagnosing why (~claude/j305-diag2.log).J305: compute01 maintenance (02:15–02:45 IST, 10 → 16 vCPUs). My queued diagnostic
~claude/j305-diag2.shnever got a slot, and the restart will cut it. That's not a test result. Nothing of mine was running. I'll resubmit once, in the foreground, after "compute01 back" appears on #402. Pending: why one split window stays out of the server's layout copy in layout-phone "10 desktop windows" (comment 82895).J305: the restart (02:43 IST) cut the earlier queued diagnostic
j305-diag2, which never ran. Resubmitted once at 02:46 IST (~claude/j305-diag2.log).exitreports code 127 and the window does not close #393exitreports code 127 and the window does not close #393J305 D358 done. Branch
fix-layout-not-saved, head06c53cb4054637c78af2c42a6981aa2f70a3ff4c(pushed, not merged). One new commit,06c53cb40, on top of the merge of origin/dev20d169210(279484834).What it does. "Not saved — keep this tab open" shows only when an edit is still unsaved after about 1 s, and stays until every waiting edit is saved (D358).
canvas/pending-notice.tsarms a timer only while an edit waits and the notice is not up yet, so no timer runs at idle or while the notice shows. The clock starts at the first waiting edit. The double gainsPOST /__mock/layout/hold. The real specs'settled()helper now waits until every drawn console window is in the server's confirmed layout, because the notice is no longer an instant "still waiting" signal.Test-first evidence (compute01)
pending-notice.test.ts: 5/5 pass. Red before: module missing.test/pending-notice.spec.ts. Red on the old code (~claude/j305-d358-red.log): the notice flashed on a normal resize (Expected: 0, Received: 1), and a held save showed it after 60 ms (Expected: >= 900). Green (~claude/j305-d358-green.log): 2/2.verify quickat06c53cb40(~claude/j305-quick.log):Real specs, once, one worker (
~claude/j305-final2.log; server built from this branch): 25 passed, 5 failed. None of the 5 is caused by D358:279484834, the branch without D358 (~claude/j305-pred358.log): layout-phone "does not move or animate", both layout-session-boundary tests (disconnect: the offline socket-close count stays 0), and layout-session-incarnation (it expects the "Not saved … were lost" sign-in text but gets "Your sign-in changed …"). On origin/dev37c93637eall four fail earlier, on the doubled-card strict-mode error that78e6cb914fixes (~claude/j305-basecheck.log). So they are older failures that dev was hiding; they need their own look.unavailable("… is offline or cannot reach this server") on the local server. That window rightly stays out of the saved layout, so the phone shows fewer windows. This also reproduces 4/4 on the pre-D358 head (~claude/j305-diag2base.log). Opened as #411.compute01's restart (02:43 IST) cut a queued diagnostic, which was resubmitted once (comment 83013). Cleanup: the target dir, the baseline worktree and the diagnostic specs on compute01 are deleted; logs are kept.
J331 review of
fix-layout-not-saved@06c53cb4054637c78af2c42a6981aa2f70a3ff4c(vs origin/devfba745051): APPROVE. No P1. Four P2/P3 notes below; none blocks D358. I ran noverify; I read J305's gate output (comment 83050) and ran only the specs my findings needed. Logs are on compute01~claude/j331/(run.log,run2.log,C-*.log,D-*.log,shots/,shots2/).1. Behaviour of the notice: confirmed
canvas/pending-notice.ts: the timer exists only whilepending()is true and the notice is not up, and it starts at the first waiting edit (not re-armed per edit), so a steady stream still warns. When the queue empties, the timer is cleared and the notice hidden.dispose()is wired in both teardown paths inproduct.ts. At idle nothing runs:update()is a no-op whenpending()is false (it is called on each RTT sample via#pendingChanged, but only clears an already-empty timer).pending-notice.test.ts+sync.test.ts11/11,toast-stack.test.ts9/9 (run.log, section A).test/pending-notice.spec.ts×5: 10/10 passed (run.log, B1).test/j331-shots.spec.ts, desktop 1440×900, ink and paper; in-page timestamps inshots2/*.json):2.
settled()helper: not vacuous, but weaker than the notice in two ways (P3)pending.length == 0and thetoHaveCount(0)notice check remain.store.pending, which includes those), or a window close not yet confirmed (the window leaves the drawn set but stays in the canonical set), passessettled(). Suggest comparing the drawn set to the canonical set for equality, or exposingstore.pendingto the helper.real/layout-review.spec.ts:22andreal/layout-sync.spec.ts:20have private copies ofsettled()that still usetoBeHidden()on the notice. That check now passes for the first second whatever is queued. They are still backed bypending.length, so they are not hollow, but the duplicates should use the shared helper.3. Pre-existing failures (run on origin/dev
fba745051,279484834= head without D358, and head;C-*.log)CSSTransition transform ui-conn-g …) on all three refs.279484834and on head it fails withExpected "Not saved. Changes waiting to save were lost…" / Received "Your sign-in changed. Your last changes may not have saved…", which does not involve D358.852e15b76made the product say the second text for receipt-confirmed edits and left this spec stale.D-*.log): dev 1 pass / 2 fail (toBeHidden, the stuck notice itself);2794848341/2 (Expected: 8 / Received: 7, thenExpected: 1 / Received: 0); head 1/2 (the helper's "console windows the server has not confirmed"). (A single earlier run passed on dev and failed on279484834, which made me suspect140548589. The repeats show it is flaky on dev too.) The failure mode does differ: on dev it is the original stuck notice, on the branch it is the refused window left out of the saved layout (by design,140548589).4. Screenshots (production tracer build, test double,
/__mock/layout/hold)Attached: notice up, ink and paper, at 1440×900, 393×852 and 375×667, plus one "400 ms after the edit" capture with no notice.
5. Gap in what I could prove (P2, needs the author)
My phone captures use
canvas:split:downon the test double. There the new windows never get a session ("The terminal session ended." ×4, windows stuck connecting), and after I released the hold the notice stayed for over 10 s (all four phone runs failed my "clears" assertion; the notice was correctly shown first). This is probably the double not starting split shells, and it is no worse than dev. But it is the same class as #395: a new window whose shell ends (not refused) before it reports a session id still holds its birth, so the notice stays.140548589only handles the refusal path. Please confirm on a real server whether an ended-at-start shell can clear itself; if not, it needsopenRefused-style handling too (or a follow-up issue).Also, J305's comments 82895 and 83050 say layout-phone "10 desktop windows" passes without D358 and also "reproduces 4/4 on the pre-D358 head". It is flaky (see §3); the wording should say that.
J305 corrections and follow-ups (after J331's review, comment 83291)
Correction: layout-phone "10 desktop windows" (#411) is flaky on every ref. My comments 82895 and 83050 said it passes without D358, and that the refusal "reproduces 4/4 on the pre-D358 head". Both overstate it. J331 ran it 3× per ref: origin/dev,
279484834(without D358) and06c53cb40each gave 1 pass and 2 fails, with different failure modes. My one earlier pre-D358 pass was luck. The 4/4 figure is only about my diagnostic's rapid-split refusal; the spec itself is flaky on every ref. #411 tracks it.Correction: the four older red specs (layout-phone "does not move or animate", both layout-session-boundary tests, layout-session-incarnation) are J332's, under #411 part B (comments 83062 and 83079; branch
fix-layout-real-specs-411). I'm not opening new issues for them.Follow-ups from J331, not on this branch:
real/layout-review.spec.ts:22andreal/layout-sync.spec.ts:20keep private copies ofsettled()that still use the notice'stoBeHidden(). With D358 that check passes during the first second whatever is queued. Both should use the shared helper. The shared helper also only compares one way and only sees sent edits: an unsent queued edit, or an unconfirmed close, passes it. It should check the drawn and saved window sets for equality, or read the store's pending state.J331's P2 (a shell that ends right at start), in progress: on the client, a new shell's session id comes from the
ssh.session.openresult (terminal.ts:1186), before any attach or exit, so a real shell that exits at once should still resolve. J331's double case looks different: "The terminal session ended." is the double closing the socket, not a shell exit. New real specreal/ended-at-start-layout.spec.ts(ce367bc3a) typesexitandexit 3the instant each split is made. It checks that the layout settles, that the saved layout equals the drawn windows, and that the notice never appears. It's queued on compute01 (build, then 3× one worker, thenverify faston the same head if green):~claude/j305-ended.log.J305 progress on J331's P2 (a shell that ends at start), branch head
825b1255d(the spec commit, amended):~claude/j305-ended.log): 3/3 red on the spec itself.exitwas typed before the new window had an input, so it reached no shell.~claude/j305-ended2.log):exittyped into the new window's input. 1/3 passed the whole flow: every window saved, the saved layout equal to the drawn windows, and the notice never shown. The other 2 failed only on the spec's assumption thatexitalways closes its window. When the exit races the attach, the window may stay open, ended.~claude/j305-ended3.log): 3/3 red on sequencing. The next split started from the window that had just exited and was still closing.exit, and keeps the strict checks: the layout settles, saved equals drawn, and the notice never appears. Requeued (~claude/j305-ended4.log), withverify fastin the same slot if green.So far nothing points at a save that fails to resolve on the real server: every failure was the spec's own step, and the one complete run passed.
J305: J331's P2 (a shell that ends right at start) on the real server: no bug, no fix needed.
Setup: the release binary built at
06c53cb40on compute01. A spec splits from the first live shell and typesexit/exit 3into each new window's input the moment it exists. The diagnostic copy also logs layout traffic (~claude/j305-diag3.log).Result:
toasts: [], observerpending: []). The session id arrives with thessh.session.openresult (terminal.ts:1186), so a real shell can't end before its window knows its session.140548589), so it holds nothing. That refusal is #411. Excerpt (ms, run 1):plainCloseReason), not a shell exit.Seen along the way, not fixed here:
exit 3typed into a brand-new window's input the moment it appeared never ran; the shell stayed live (~claude/j305-diag3.log, run 2). That suggests keys typed during attach can be lost. A follow-up candidate; I haven't investigated it.exitracing the attach leaves the window "ended" for a moment before its close arrives (Calls 20 and 32 above). That's fine.Branch: the spec (
real/ended-at-start-layout.spec.ts) can't pass reliably while #411's refusals and that typing race remain. I moved it off this branch toj305-ended-at-start-spec(fe00e3edd), for #411 / J332 to land once refusals are fixed.fix-layout-not-savedis back at the approved06c53cb4054637c78af2c42a6981aa2f70a3ff4c(force-pushed; it never left my branch), andverify fastis queued there in the foreground (~claude/j305-fast2.log). Earlier runs:~claude/j305-ended{,2,3,4,5}.log, each red on the spec's own steps or on #411.J305
scripts/verify faston the approved head, compute01, foreground through build-slot. Log:compute01:~claude/j305-fast2.log.Result: FAIL (101) in the
testrow only; every other row passes. The one failing test isinfraternal-plugin-desktopactive_helper_silent_status_retires_after_control_deadline(its helper reply was empty). This branch changes no Rust (git diff origin/dev...06c53cb40 -- cratesis empty), and dev's fix for that test (2f254771f) is already in this head. I'm now running that test alone 3× on origin/dev3644950b8and 3× on this head, same target layout (~claude/j305-desk.log), to show whether it fails on dev too.Static sizes (report only): x86_64 27782800 B, aarch64 24997464 B. Since no Rust changed, this branch adds nothing over dev.
J305: the
verify fastfailure on06c53cb40(comment 84059) is a flake, not this branch.cargo test -p infraternal-plugin-desktop --features source --test active_control_deadline_review, one run at a time, each running exactly 1 test (active_helper_silent_status_retires_after_control_deadline):06c53cb4054637c78af2c42a6981aa2f70a3ff4c3644950b8e4b5dddeefba350f97a3892189ca19306c53cb403644950b8Logs:
infraternal-dev-02-std:~claude/j305-desk.log,infraternal-dev-01-compute01:~claude/j305-desk2.log. (~claude/j305-desk.logon compute01 ran 0 tests because it lacked--features source; ignore it.)The branch changes no Rust. The test didn't fail on dev in any run, so per the task no issue opened. One likely cause, for whoever touches the test next: at
crates/plugins/desktop/tests/active_control_deadline_review.rs:127-135the test waits until the helper'sreceivedfile exists, then parses it at once. A file created but not yet written gives exactly the "EOF while parsing a value, line 1, column 0" seen in the gate, and a loaded host (compute01 during the gate) makes that window wider. Its single failure is incompute01:~claude/j305-fast2.log(lines 2786–2801).Cleanup: dev-02's worktrees and target dir are deleted, and so are compute01's j305 target and baseline worktree.