Skip to content

e2e: failing regressions for Playbook paste (#1103, #1104) - #1114

Merged
edwin-zvs merged 1 commit into
mainfrom
e2e-playbook-paste-regressions
Aug 1, 2026
Merged

e2e: failing regressions for Playbook paste (#1103, #1104)#1114
edwin-zvs merged 1 commit into
mainfrom
e2e-playbook-paste-regressions

Conversation

@edwin-zvs

Copy link
Copy Markdown
Contributor

Two e2e regressions for the Playbook paste bugs, written to fail on main today 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:

paste never reached the daemon: the TUI renders the pasted block
but the stored document is still "seed\n"

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:

carriage returns were stored verbatim instead of being normalized to
newlines: "seed\r- one\r- two\r  - nested\r\n"

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 main by 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:

test playbook_paste_normalizes_carriage_returns ... ignored, fails on main: CR line breaks in a paste are stored verbatim (#1104)
test playbook_paste_reaches_the_daemon ... ignored, fails on main: paste never reaches the daemon (#1103)

Each fix PR removes its own #[ignore] line. Run them meanwhile with:

cargo test -p construct-e2e --test playbook_paste -- --ignored --nocapture

Harness fix: Tui now scrubs inherited CONSTRUCT_*

Daemon::spawn_inner already strips inherited CONSTRUCT_* 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_inner never did the same.

--socket points 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 Space went somewhere else entirely, and they failed in setup with playbook never opened on the seeded document rather than at their assertions.

Tui now strips CONSTRUCT_* 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.

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
edwin-zvs merged commit 958901c into main Aug 1, 2026
1 check passed
@edwin-zvs
edwin-zvs deleted the e2e-playbook-paste-regressions branch August 1, 2026 19:20
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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant