Skip to content

Single-line inputs strip pasted line breaks and never paint a second line#140

Merged
ctate merged 5 commits into
mainfrom
fix/single-line-paste-newlines
Jul 17, 2026
Merged

Single-line inputs strip pasted line breaks and never paint a second line#140
ctate merged 5 commits into
mainfrom
fix/single-line-paste-newlines

Conversation

@ctate

@ctate ctate commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Fixes the reported issue: pasting content containing line breaks into a single-line input displayed multiple lines — with the extra line painting outside the field's border.

Two independent layers:

  • Sanitize at the edit seam. Single-line kinds (input, text field, search field, combobox) strip \r/\n from inserted text per the WHATWG value-sanitization rule for single-line inputs (Chromium's paste behavior; documented with citations at the sanitize site). The rule sits in the v0.5.2 edit-derivation choke point with the order derive → sanitize → stamp → apply, so the retained editor, the core's on_input mirror (both tiers), and replay all hear byte-identical bytes — covering clipboard paste, context-menu paste, typed/automation text_input, IME composition (sanitized at preview so a commit can never land a break), and direct runtime writes, with the app-side fallback derivation applying the identical rule. An insert that strips to nothing is suppressed (pasting bare newlines never eats a live selection), and Enter-in-input is pinned as not-an-insert even against hosts that stuff a newline into the key event. Textareas are untouched.
  • Defensive render containment. Independent of sanitization, a single-line field whose value holds a break (a model-set value can — legitimately, from a Zig core) presents \n/\r as spaces byte-for-byte, so caret/selection/composition/hit-test offsets address the raw value unchanged while layout produces exactly one line on the GPU engine, the reference renderer, and packet hosts; the content-rect clip is additionally forced whenever the raw value holds a break. Copy, semantics, and automation still report the raw value.

Replay needed no journal changes — sanitization is deterministic derivation over the recorded raw events, pinned by extending the reference recording with a textarea→search-field multi-line paste.

Tests across events/ui/clipboard/text/session suites, all sabotage-verified (disabling the rule fails 10+ tests; disabling presentation or the forced clip fails the containment pins). Full battery, validate, tooling, examples, ts-core e2e all green.

ctate and others added 3 commits July 17, 2026 01:37
- Sanitize derived edits for input/text_field/search_field/combobox at the keyboard choke point, BEFORE the stamp, so the retained editor, the app's on_input mirror, and replay all hear identical stripped bytes (HTML value-sanitization rule; covers shortcut and context-menu paste, typed/automation text_input, and IME composition).
- Suppress inserts that strip to nothing so pasting bare newlines inserts nothing and a host-stuffed Enter payload never deletes a live selection; the app-side fallback derivation applies the same rule so both derivations agree.
- Sanitize direct runtime editCanvasWidgetText writes through the same shared rule.

Co-authored-by: IFTC-XLKJ <151902522+IFTC-XLKJ@users.noreply.github.com>
- Lay out, measure, and paint a single-line field's value with \n/\r presented as spaces (byte-for-byte, so caret/selection/hit-test offsets address the raw value) — one line on the GPU engine, the reference renderer, and packet hosts alike; copy, semantics, and automation still read the raw model value.
- Force the content-rect clip whenever the raw value holds a line break, the independent guard that keeps even a presentation-scratch fallback inside the field's rounded border.
- Persist presented bytes into a render-walk pool (the chart-label scratch precedent) so emitted commands survive until the runtime copies the display list.

Co-authored-by: IFTC-XLKJ <151902522+IFTC-XLKJ@users.noreply.github.com>
…ries

- Paste, automation set_text, IME composition, and the Tree fallback derivation all pin stripped inserts (textarea keeps its breaks); bare-newline pastes insert nothing and Enter stays not-an-insert even with a host-stuffed newline payload.
- Model-set values holding a newline pin one presented line under a forced clip in both render walks and through the runtime display list, with semantics still reporting the raw value.
- The session reference recording now copies a textarea's two lines and pastes them into the search field, pinning that a recorded multi-line paste replays to the identical sanitized value and fingerprint.

Co-authored-by: IFTC-XLKJ <151902522+IFTC-XLKJ@users.noreply.github.com>
@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
native-sdk Ready Ready Preview, Comment Jul 17, 2026 7:57am

ctate added 2 commits July 17, 2026 02:34
- clampCanvasWidgetPasteText (shared by the cmd+V shortcut and the context-menu Paste) now strips single-line targets' line breaks BEFORE measuring against remaining capacity, so a near-limit paste never spends free bytes on breaks the seam strips anyway ("a\nbc" into 3 free bytes lands "abc", not "ab"), and a paste that sanitizes to nothing is not reported truncated.
- Boundary tests pin both paste paths at exactly three free bytes: retained editor and the app's stamped edit hear the identical whole sanitized suffix with no false truncation flag.
- The builder contract (pinned at Builder.allocPathElements) lets a display list accumulate across several emit calls or be held while another builder emits; presented single-line values and formatted chart labels lived in thread-local pools RESET at each emit entry, so a second emit overwrote text an earlier list still sliced.
- Builder now owns the bytes beside its path-element store: allocChartLabelBytes keeps the chart label budget's loud ChartLabelBytesFull, and allocTextBytes holds presented values under a per-view-text-budget-sized store (lockstep-tested against max_canvas_text_bytes_per_view) with the raw-value fallback intact; both thread-local pools and the per-emit resets are gone.
- Regression tests emit twice — into another builder and accumulated into one — and pin both lists' draw_text bytes intact for presented fields and tick labels; both fail against the pool-reset design.
@ctate
ctate merged commit 1dfb0db into main Jul 17, 2026
21 checks passed
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