pmacs/pmacs-gpu
Levi Neuwirth 32b529eea5
session 4: Phase A — StyleSpans consumption + rich-text rendering (#42)
First Phase A session. pmacs-gpu now sends FrontendEvent::Viewport
back to the daemon after BufferSnapshot lands, receives the resulting
InstanceMessage::StyleSpans frames, and renders the rope with
per-span colors via cosmic-text's set_rich_text.

What's wired:
- AttachClient gains a write-side Arc<Mutex<UnixStream>> and the
  assigned FrontendId from Hello; new send_viewport method emits
  FrontendEvent::Viewport. The mutex is over-cautious for our
  single-threaded event loop but future-proofs against multi-window
  emission.
- State adds current_buffer_id and current_spans (sorted by
  range.start). BufferSnapshot bootstraps both, then the App's
  user_event handler emits a follow-up Viewport via
  AttachClient::send_viewport.
- StyleSpans handling distinguishes full vs incremental:
  * full=true: replace_style_spans drops prior, takes segments as
    authoritative for the declared viewport.
  * full=false: merge_style_spans applies the M11.4 dirty-segment
    rule — spans fully inside a dirty range drop; spans straddling
    a dirty edge get clipped to outside the range (with the
    straddles-both-edges case splitting into two); new spans append;
    re-sort by start.
- reshape() walks current_text + current_spans, emits (substr,
  Attrs) chunks at every span boundary (with .min(text_len) clamps
  for safety against stale spans past EOF), calls set_rich_text.
- cell_color_to_glyphon converts cell::Color to glyphon::Color via
  the standard xterm-style 256-color palette (16 ANSI + 6x6x6 cube +
  24-step grayscale). Default → None so the renderer's default
  Attrs color stays.

Adversarial verification scope (Phase A probes):
- #1 non-ASCII source: exercised through the UTF-16 col/byte
  conversion already in pmacs's producer side; pmacs-gpu just renders
  what the wire delivers. Non-ASCII files should show correct
  styling at the right byte positions.
- #3 viewport-boundary tokens: the merge_style_spans path is exactly
  bet #1 from the framing pass ('StyleSpans/Decorations dirty-segment
  edges at viewport boundaries — headless-test-blind-spot probe').
  Edits near a span edge exercise the clip-and-merge logic.
- #6 CRLF line endings: implicit — pmacs's rope uses byte offsets so
  styling spans naturally include or exclude the \r as the producer
  decided. pmacs-gpu doesn't special-case line endings.

Known limitation (session 4 acceptable artifact, documented in
set_text): CrdtOp + StyleSpans arrive separately. CrdtOp updates text;
StyleSpans for the new generation comes one tick later. Between the
two, current_spans points at pre-edit byte positions while the text
is post-edit — visually stale for one frame. The .min(text_len) clamp
in reshape() keeps it safe; the artifact is brief.

Headless test gap: there's no Rust-level test of merge_style_spans
or the rich-text segmentation. Phase A's framing intentionally
chose manual validation over headless tests for these paths (the
adversarial probes are visual). A Phase A audit doc lands at session
close with the predicted-vs-actual scoring; per-method unit tests
for the merge logic could land then if findings argue for them.

Gates: fmt; clippy --all-targets -D warnings clean across the whole
workspace; lib 1303 + pmacs-protocol 11 = 1314; m4_acceptance 83;
m11_5_semantic_acceptance --features crdt 2.

Manual validation pending — same daemon+TUI+pmacs-gpu setup as
session 3, now showing colored text in the GPU window.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 00:28:53 +00:00
..
fonts session 2: pmacs-gpu workspace + wgpu/winit/glyphon hello-world 2026-05-20 10:37:26 -04:00
src session 4: Phase A — StyleSpans consumption + rich-text rendering (#42) 2026-05-21 00:28:53 +00:00
Cargo.toml session 3 commit 2/2: attach mode + loro rope reconstruction 2026-05-20 12:23:04 -04:00