Skip to content

id(text/1): TextSurface wrap emits a line wider than the requested width when one grapheme is wider than the width #4877

Open

Description

@dezren39

id(text/1): TextSurface wrap emits lines wider than the requested width when a single grapheme is wider than the width — contradicting the wrap contract

Evidence

pkgs/id/crates/id-core/src/world_presentation.rs — the new text/1
surface from #2339 (commit 0ec941645):

  • wrap_text_line (line ~157) documents: "a word longer than width is
    hard-wrapped on grapheme boundaries so no output line ever exceeds
    width columns
    (zero-width graphemes attach to the preceding column)".
  • The wrap loop only breaks before a grapheme when the current line is
    non-empty (current_width + grapheme_width > width && !current.is_empty()).
    A grapheme whose own display width exceeds width is therefore placed
    whole on its own line, and that line exceeds width.
  • TextSurface::with_width documents its result as "already wrapped to
    width columns" and parse_surface accepts a guest width as small
    as 1, so a guest document {"kind":"text","version":1,"width":1, "lines":["界"]} (界 = U+754C, unicode-width 2) validates and produces
    a stored line of width 2 at width 1.

Verified this run with a laptop probe at origin/main c19c1d8b0
(worktree probe-2026-11-03-laneB-scan19, logs /tmp/probe-scan19.log
and /tmp/probe-scan19b.log on the laptop, read back from disk):

  • TextSurface::with_width(1, vec!["界"]) → Ok, lines == ["界"]
    (display width 2 > requested width 1)
  • parse_surface("{\"kind\":\"text\",\"version\":1,\"width\":1, \"lines\":[\"界\"]}") → Ok(Some(Text(_))) with the same over-wide line
  • Control: with_width(2, vec!["a界b"]) wraps to lines within width 2,
    and the existing tests at width 3 (ab界cd) stay within width — the
    violation needs a grapheme strictly wider than the wrap width itself.

Why it matters

text/1 is the main presentation mode for agents and scripts (#2339):
presenters size their output to the negotiated width and trust the
validated surface to fit it. A validated line that exceeds the width
breaks that contract exactly where it is hardest to see — narrow
terminals / width-1..2 viewports and wide CJK/emoji graphemes — and the
module's own doc states the opposite guarantee, so downstream code has
no reason to re-check. The screen side never faces this because a
ScreenSurface cell names its column span explicitly; the text side
needs its own rule.

Acceptance criteria

  • Code and docs agree, one of:
    (a) TextSurface::with_width / parse_surface refuse a line whose
    single grapheme is wider than the requested width (naming the
    width in the error), or
    (b) the wrap contract is amended in the module docs to state that a
    line may exceed width by the width of one unsplittable grapheme,
    and presenters are pointed at that exception.
  • Regression tests cover: width 1 with a width-2 grapheme, width 2 with
    a width-2 grapheme (accepted, fits exactly), and the existing
    width-3 ab界cd case still passing.
  • cargo test -p id-core --lib world_presentation passes (laptop or CI).

Relates to #2339 (the text/1 contract issue) — this is a specific
wrap-contract violation in the landed implementation, not covered by
that issue's remaining work.

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    task/small-fixQueue packet kind: bounded fix with reproduction and targeted tests
    source/creatorFiled by the new-issue-creator job
    priority/lowNice to have; do when convenient
    queue/queuedHas a queued task in the Supabase agent queue
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    on Nov 3, 2026
  2. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

  3. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done (PR opened)

    Took option (b): the wrap contract now states the one-grapheme exception instead of promising no line ever exceeds the width (dropping or splitting the grapheme would corrupt content). Documented on wrap_text_line, on TextSurface::with_width — with guidance for presenters that must hard-fit a width-1/2 viewport — and on TextSurface. Regression test covers width 1 with a width-2 grapheme, width 2 fitting exactly, and a界b at width 1; the existing width-3 ab界cd case is unchanged. cargo test -p id-core --lib world_presentation: 29 passed.

  4. removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 3, 2026
  5. dezren39 commented on Nov 3, 2026

    @dezren39
    MemberAuthor

    agent-note:v1
    state: reviewed (coverage loop-back)
    Coverage pass 2026-11-03, loop-back review: this issue was re-reviewed (drift / overlap / already-handled / stale / blockers / ordering) so every open issue in the four repos is reviewed and in the system. Verdict: still live; in the system as task 7526 (small_fix, claimed, priority 30) in repo-health-code-pilot. Labels audited and synced. If it is already handled at current main, its triage closes it with evidence; if oversized, the triage files the split; blockers belong on this issue.
    Found via: coverage loop-back 2026-11-03

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/idPackage/app: id (pkgs/ or apps/ in this repo)kind/bugSomething isn't working (prefixed form of the classic bug label)priority/lowNice to have; do when convenientsource/creatorFiled by the new-issue-creator jobtask/small-fixQueue packet kind: bounded fix with reproduction and targeted tests

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions