Skip to content

id(web): world-surface presenter accepts cells the Rust wire validator rejects — symbol width/grapheme and continuation-symbol checks missing #724

Closed

Description

@dezren39

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 == 0 continuation cell must have an empty symbol (!cell.symbol.is_empty() → reject)
  • a columns == 1|2 cell must contain exactly one grapheme (cell.symbol.graphemes(true).count() != 1 → reject)
  • the grapheme's UnicodeWidthStr::width must equal columns
  • 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} → returns true, rendered as gridColumn: 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} → returns true, rendered text is just x — the Z glyph is silently dropped (Rust: narrow glyph in 2 columns and non-empty continuation both reject)
  • {symbol:"ab", columns:1} → returns true (two graphemes in one cell; Rust rejects)
  • {symbol:"", columns:1} → returns true (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

  • renderWorldSurface returns false (leaving the existing view untouched, as today) for:
    • a columns == 1|2 cell whose symbol is not exactly one grapheme
    • a cell whose symbol's terminal width does not equal columns (e.g. 界 with columns:1, x with columns:2, "" with columns:1)
    • a columns == 0 continuation cell with a non-empty symbol
  • Width measurement matches Rust's unicode-width semantics 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.ts mirroring the Rust-side invariants; existing 3 presenter tests still pass

Activity

  1. added 3 commits that reference this issue on Oct 24, 2026
    d8fe072
    d746446
    6e9548f
  2. dezren39 commented on Oct 24, 2026

    @dezren39
    MemberAuthor

    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).

  3. added a commit that references this issue on Oct 24, 2026
    8c0f035
  4. dezren39 commented on Oct 26, 2026

    @dezren39
    MemberAuthor

    Intake review — fleet monitor, 2026-10-26T08:26:24Z.

    Still live — verified on current main this run: renderWorldSurface in pkgs/id/web/src/world-surface.ts on 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 8c0f035d this 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.

  5. added
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    effort/mMedium: expected under 1 day of work
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    meta/enrichedIntake review done: acceptance criteria, links, and normalized labels added
    priority/highImportant and time-sensitive
    on Oct 26, 2026
  6. added 2 commits that reference this issue on Oct 26, 2026
    cc9ebe4
    ff6cb4d
  7. dezren39 commented on Oct 26, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

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)effort/mMedium: expected under 1 day of workkind/bugSomething isn't working (prefixed form of the classic bug label)meta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedpriority/highImportant and time-sensitivestatus:done

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions