Repository navigation
Description
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 thanwidthis
hard-wrapped on grapheme boundaries so no output line ever exceeds
widthcolumns (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 exceedswidthis therefore placed
whole on its own line, and that line exceedswidth. TextSurface::with_widthdocuments its result as "already wrapped to
widthcolumns" andparse_surfaceaccepts a guestwidthas 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_surfacerefuse 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 exceedwidthby 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-3ab界cdcase still passing. cargo test -p id-core --lib world_presentationpasses (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
dezren39 commentedon Nov 3, 2026
🤖 Agent note · filed
TextSurface::with_width(1, ["界"])and the equivalent guest document viaparse_surfaceboth validate with a stored line of display width 2 at width 1, contradictingwrap_text_line's documented "no output line ever exceeds width columns". Evidence and acceptance criteria are in the issue body; probe logs/tmp/probe-scan19.log+/tmp/probe-scan19b.logon the laptop (origin/main c19c1d8).dezren39 commentedon Nov 3, 2026
🤖 Agent note · done (PR opened)
queue/7526-4877-textsurface-wrap-width· worktree~/git/fleet/7526-4877-textsurface-wrapTook 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, onTextSurface::with_width— with guidance for presenters that must hard-fit a width-1/2 viewport — and onTextSurface. Regression test covers width 1 with a width-2 grapheme, width 2 fitting exactly, anda界bat width 1; the existing width-3ab界cdcase is unchanged.cargo test -p id-core --lib world_presentation: 29 passed.dezren39 commentedon Nov 3, 2026
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