e2e: failing regressions for Playbook paste (#1103, #1104) - #1114
Merged
Conversation
Two end-to-end tests that drive the real TUI in a pseudo-terminal, deliver a genuine bracketed paste, and assert against the daemon's stored document rather than the TUI screen — the screen is already correct today, which is why both bugs are invisible in use. - playbook_paste_reaches_the_daemon (#1103): a paste never leaves the client, and the divergence it creates silently drops every keystroke after it. - playbook_paste_normalizes_carriage_returns (#1104): CR line breaks, which terminals routinely send inside a bracketed paste, are stored verbatim; the block collapses to one line and one addressable block. Saves explicitly after pasting so it isolates CR handling from #1103. Both are #[ignore]d with their issue number in the reason string: they fail on main by design, and landing them red would block every other PR. Each fix removes its own ignore. Also scrub inherited CONSTRUCT_* when spawning the TUI, matching what Daemon::spawn_inner already does. Only --socket was overridden before, so a TUI e2e test run from inside a construct session read the developer's real config and state dirs and came up with their saved layout — these tests failed in setup rather than at their assertions.
edwin-zvs
added a commit
that referenced
this pull request
Aug 1, 2026
…cted Three paths mutated the Playbook buffer and returned without publishing, so the change lived only in that client — and because the next keystroke's anchored edit was then derived from a base the daemon never received, it failed too, and so did every one after it. The buffer kept accepting input and looked correct the whole time. - Paste (#1103): on_paste now runs the same tail handle_playbook_key does — insert, follow scroll, publish cursor, flush. - Template buttons: apply_playbook_template had the same shape, so a brand-new Playbook filled from the empty state never left the client. - Rejected live edits (#1089): the failure was dropped on the floor, which left saved_markdown stale — on_playbook_state then stopped adopting remote changes to protect the "unsaved" edit, severing the document in both directions. It now falls back to the same 3-way merge C-x C-s performs, which rebases, keeps both sides, and reports the merge. Also normalize CR line endings (#1104). Terminals send CR for the line breaks inside a bracketed paste, and stored verbatim they are not line breaks to anything downstream: the renderer paints them as nothing and block splitting is newline-based, so a pasted block collapsed to one visible line and one addressable block. Normalized on the client so the caret is right immediately, and in both daemon write paths so the guarantee belongs to the store rather than to each client. Drops the #[ignore] from the two regressions added in #1114 and adds a third covering the rejected-edit recovery. Specs 0171 (edits publish and re-converge) and 0172 (stored Playbooks are newline-normalized).
edwin-zvs
added a commit
that referenced
this pull request
Aug 1, 2026
…cted (#1116) Three paths mutated the Playbook buffer and returned without publishing, so the change lived only in that client — and because the next keystroke's anchored edit was then derived from a base the daemon never received, it failed too, and so did every one after it. The buffer kept accepting input and looked correct the whole time. - Paste (#1103): on_paste now runs the same tail handle_playbook_key does — insert, follow scroll, publish cursor, flush. - Template buttons: apply_playbook_template had the same shape, so a brand-new Playbook filled from the empty state never left the client. - Rejected live edits (#1089): the failure was dropped on the floor, which left saved_markdown stale — on_playbook_state then stopped adopting remote changes to protect the "unsaved" edit, severing the document in both directions. It now falls back to the same 3-way merge C-x C-s performs, which rebases, keeps both sides, and reports the merge. Also normalize CR line endings (#1104). Terminals send CR for the line breaks inside a bracketed paste, and stored verbatim they are not line breaks to anything downstream: the renderer paints them as nothing and block splitting is newline-based, so a pasted block collapsed to one visible line and one addressable block. Normalized on the client so the caret is right immediately, and in both daemon write paths so the guarantee belongs to the store rather than to each client. Drops the #[ignore] from the two regressions added in #1114 and adds a third covering the rejected-edit recovery. Specs 0171 (edits publish and re-converge) and 0172 (stored Playbooks are newline-normalized).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two e2e regressions for the Playbook paste bugs, written to fail on
maintoday and pass once each fix lands. No production code changes — this is the failing half of #1103 and #1104, landed first so the fixes have something to turn green.Both drive the real TUI in a pseudo-terminal against a real daemon and deliver a genuine bracketed paste (
ESC [ 200 ~ … ESC [ 201 ~), then assert against the daemon's stored document — the copy the owning agent and every other client see. Asserting on the TUI screen would pass today, which is precisely why these bugs are invisible in use.playbook_paste_reaches_the_daemon(#1103)Pastes a list, waits for it to render in the TUI (so a failure is about publishing, not input), then polls
playbook.get:It then types one character and asserts that still syncs, covering the follow-on half of the bug: once the client buffer is ahead of the daemon, every later anchored edit fails to apply and is swallowed (#1089).
playbook_paste_normalizes_carriage_returns(#1104)Same payload with every newline delivered as
\r, as terminals routinely send inside a bracketed paste:The paste is followed by an explicit
C-x C-s, so this test isolates CR handling from #1103 — note the content did reach the daemon here, via the save path. It keeps failing for its own reason after the paste-sync fix, and passes once normalization lands regardless of which fix comes first. It also asserts the user-visible consequence: block splitting is newline-based, so a CR document collapses to a single addressable block — one shimmer target, one selection-Run target, one opaque line for the agent.Why
#[ignore]Both fail on
mainby design. Landing them red would fail the merge-commit build and block every other PR in the repo, so they are#[ignore]d with the issue number in the reason string:Each fix PR removes its own
#[ignore]line. Run them meanwhile with:Harness fix:
Tuinow scrubs inheritedCONSTRUCT_*Daemon::spawn_inneralready strips inheritedCONSTRUCT_*before launching the daemon, for a documented reason: this repo is developed from inside a construct session, and the session-scoped vars change behavior.Tui::spawn_innernever did the same.--socketpoints the TUI at the fixture daemon, but every other var still leaked — so the client read the developer's real config and state dirs and came up with their saved layout instead of a clean first-run TUI. Concretely, the new tests opened onto a two-pane split of someone else's session,C-x Spacewent somewhere else entirely, and they failed in setup withplaybook never opened on the seeded documentrather than at their assertions.Tuinow stripsCONSTRUCT_*and points the client at the same tempdir tree the daemon was given. This affects any TUI e2e test run locally from a construct session; CI was unaffected (nothing to inherit there), which is why the gap survived.Related: #1088 and #1089 are the same missing-flush / swallowed-error shapes on other paths.