Repository navigation
Description
Summary
The web world-screen presenter (renderWorldSurface) accepts wire cells that the Rust validator for the same wire model rejects, and renders them with a grid span that does not match the glyph — or silently drops a glyph entirely. The two validators for one wire format have diverged.
Evidence (verified this run on current main)
Rust canonical validator — pkgs/id/src/world_presentation.rs, ScreenSurface::render_ansi (lines ~334–365):
- a
columns == 0continuation cell must have an empty symbol (!cell.symbol.is_empty()→ reject) - a
columns == 1|2cell must contain exactly one grapheme (cell.symbol.graphemes(true).count() != 1→ reject) - the grapheme's
UnicodeWidthStr::widthmust equalcolumns - the continuation of a wide glyph must also have an empty symbol
TypeScript presenter — pkgs/id/web/src/world-surface.ts, renderWorldSurface (lines ~57–147):
- only checks
cell.symbol.length > 16(UTF-16 units) and rejects control characters - never checks grapheme count, never checks symbol width against
columns, never checks a continuation cell's symbol is empty
Probe run with vitest against current main (temporary test, since removed):
{symbol:"界", columns:1}→ returnstrue, rendered asgridColumn: 1 / span 1— a width-2 glyph squeezed into one column (Rust: width 2 ≠ 1, reject){symbol:"x", columns:2}+ continuation{symbol:"Z", columns:0}→ returnstrue, rendered text is justx— theZglyph is silently dropped (Rust: narrow glyph in 2 columns and non-empty continuation both reject){symbol:"ab", columns:1}→ returnstrue(two graphemes in one cell; Rust rejects){symbol:"", columns:1}→ returnstrue(zero graphemes; Rust rejects)
Existing tests: pkgs/id/web/src/world-surface.test.ts covers bounds, control characters and mismatched wide-cell styles, but no symbol/width or continuation-symbol cases.
Why it matters
The presenter is the browser's safety/validity boundary for guest-supplied screen data ("Render a validated cell grid… return false on invalid wire data"). Accepting cells the producer's own validator would never emit means a malformed or hostile surface can misalign the whole grid (wide glyph in a 1-column span, narrow glyph spanning 2) or lose glyphs with no error — and the divergence will keep growing as either side is edited, since nothing pins them to the same rules.
Acceptance criteria
renderWorldSurfacereturnsfalse(leaving the existing view untouched, as today) for:- a
columns == 1|2cell whose symbol is not exactly one grapheme - a cell whose symbol's terminal width does not equal
columns(e.g.界withcolumns:1,xwithcolumns:2,""withcolumns:1) - a
columns == 0continuation cell with a non-empty symbol
- a
- Width measurement matches Rust's
unicode-widthsemantics for the tested cases (at minimum: ASCII = 1, CJK/wide = 2, combining/ZWJ grapheme clusters handled as one grapheme); the chosen approach (small table, Intl APIs, or a dependency) is documented in the file header - New cases added to
world-surface.test.tsmirroring the Rust-side invariants; existing 3 presenter tests still pass
Activity
fix(web): match world surface presenter validation with rust wire rules
fix(web): measure astral, ZWJ and FE0F glyphs as width 2 in world pre…
fix(web): match world surface presenter validation with rust wire rules
dezren39 commentedon Oct 24, 2026
id train triage (2026-10-24): needs-decision — Muse: assign to Session A (phases 0-10 review, owns the web client); world-surface presenter diverges from the Rust wire validator, and draft PR #759 must not close it as-is (see #761).
fix(web): match world surface presenter validation with rust wire rules
dezren39 commentedon Oct 26, 2026
Intake review — fleet monitor, 2026-10-26T08:26:24Z.
Still live — verified on current main this run:
renderWorldSurfaceinpkgs/id/web/src/world-surface.tson main still contains no grapheme-count, symbol-width, or continuation-symbol checks (the width logic exists only on the unmerged PR #759 branch). The presenter still accepts every probe case in the issue body that the Rust validator rejects.Related: #761 (the PR #759 implementation shortfall — astral/ZWJ/⚠️ width — verified still present at PR head
8c0f035dthis run); PR #759 (OPEN, draft; shepherded by queue task 1058); #732 is the sibling world-input finding. The id-train triage note on this issue assigns the web client to Session A.Suggested approach: land the presenter checks via PR #759 with #761's width fix folded in, plus the shared Rust/TS width test-vector table both validators pin to — that is what stops the two validators diverging again, which is this issue's root cause.
Risks: closing this via #759 without #761 fixed would declare victory with emoji/astral surfaces still unrenderable — the monitor has flagged that path on both issues.
fix(web): match world surface presenter validation with rust wire rules
fix(web): match world surface presenter validation with rust wire rules
dezren39 commentedon Oct 26, 2026
🤖 Agent note · done