Skip to content

A text line changes with its dirty flag unset, and carries another element's attributes, one idle run in three #199

Description

@Briany4717

What happens

Running crates/byard-cli/examples/weather under byard dev (a debug build)
trips the text pipeline's debug-only consistency assert, roughly one run in
three
, after ten to twenty idle frames with no interaction at all:

thread 'main' panicked at crates/byard-core/src/encoder/text_glyph.rs:225:5:
a text primitive changed while its upstream dirty flag stayed unset. The glyphs
themselves are still correct, shaping is content-addressed - but `dirty` is also
what builds the incremental redraw region, so this line may not be repainted
where it changed.

Reproduce:

cd crates/byard-cli/examples/weather
cargo run -p byard-cli -- dev     # leave it idle ~15s; repeat if it survives

What the offending line looked like

Instrumenting the assert to dump the pool caught it once:

i=6 text="Nairobi" size=14 color=[0.60, 0.63, 0.67, 1.0] wrap=Some(608.0)

"Nairobi" is a Button label, written #[color: 0xFFFFFF] with no size.
The line carrying it had size 14 and colour 0x9AA0AC — the attributes of
the Text lines inside the (unmounted) when state == "ok" branch. So this is
not only a missing dirty flag: on that frame a line's content and its
attributes came from two different elements.

The statusline shows the box count oscillating 6 ↔ 7 on the frames around it,
with nothing in the view that should mount or unmount while idle.

What is not the cause

Ruled out with tests that now live in crates/byard-compiler/tests/http_capability.rs
or were written and discarded for proving nothing:

  • Hovering a button. A headless sweep across all four city buttons changes
    no text line's resolved bytes, and does not change the box count either — so
    hover is not what oscillates.
  • A wrap-width change from a resize. Rendering the same tree at 900 px and
    then 500 px marks every line whose position moved; a_narrower_viewport_marks_the_lines_whose_wrap_width_it_changed
    covers it and passes.
  • The controller call itself. The same view with inject Http and the
    http.get(…) call removed is stable across runs, on this branch and on
    main. So is examples/todo (0 trips in 3 runs, versus 1 in 3 for weather).

Every headless reproduction attempt passes. It has only ever been seen in a
live window.

Why it matters

The assert's own documentation is precise about the consequence: the glyphs are
correct, because shaping is content-addressed, but dirty is what
dirty_text_bounds unions into the incremental redraw scissor. A line that
changed with the flag unset is shaped correctly and then clipped out of the
region that gets redrawn
— which looks identical to a stale-glyph bug on
screen and has a completely different cause. In a release build the assert is
absent and the symptom is a text line that does not repaint where it changed.

The content/attribute mismatch is the more alarming half, and it is the
instance-identity-across-passes
failure mode: lowering, layout and paint each have to carry the same row's
identity, and any disagreement between them fails silently.

Not caused by RFC-0029

Filed from the RFC-0029 HTTP/JSON work because that is what surfaced it, but
the code paths that change there are inert for this view until a button is
pressed, and the trip happens while idle. This is in the encoder/paint identity
area, not the controller boundary.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions