Commit Graph

10 Commits

Author SHA1 Message Date
Levi Neuwirth a67cb8a6f1 Close pmacs-gpu phase A audit 2026-05-28 12:49:23 -04:00
Levi Neuwirth a6503529ff Render file style summary minimap 2026-05-27 14:21:42 -04:00
Levi Neuwirth 71b21dee1e Render inline adornments in pmacs-gpu 2026-05-27 10:24:20 -04:00
Levi Neuwirth 0902a9e173 Revert "session 5 fixup: clear styling on CrdtOp ..."
The clear-on-CrdtOp change broke the producer's incremental-update
contract. The producer ships dirty-range spans only on `full=false`
frames; the frontend is expected to retain non-dirty spans across
edits. Emptying both vectors meant the frontend ended up with only
the small dirty-range spans, missing the rest of the viewport — all
colors disappeared after an edit.

Reverting here. The proper fix lives in pmacs core (T M11.7):
producer must force `full=true` on generation transitions so the
frontend gets a complete replacement set on every text edit. Once
that lands, session-5's CrdtOp handler doesn't need to clear
anything — the next frame's `full=true` does it via
`replace_style_spans` / `replace_decorations`.

This reverts commit 49785c4. Returns the consumer behavior to
session-5's original "one-frame stale" artifact pending the core
fix.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 15:32:47 -04:00
Levi Neuwirth abd6f46eee session 5 fixup: clear styling on CrdtOp to kill stale-position artifacts
Surfaced during session-5 manual validation (probe #3, the bet-#1
shape from the framing pass): editing at a diagnostic boundary
left stale color fragments visible against now-different text. The
session-4 PR documented this as a "one-frame stale" artifact, but
in practice the LSP re-analysis window stretches the wrong-color
period to 100ms–5s — human-perceptible and confusing.

Mechanism: CrdtOp updates `current_text` but `current_spans` /
`current_decorations` still index into pre-edit byte positions.
`reshape()` paints them at those stale positions against the new
text, producing colored fragments over wrong characters until the
producer ships an updated frame. Between CrdtOp arrival and
clangd's next publishDiagnostics (LSP debounce + re-analysis), no
Decorations frame ships at all (the producer's
`changed_intervals(prev, curr)` sees identical sets because clangd
hasn't republished yet).

Fix: drop both vectors in the CrdtOp arm before `set_text`. Tree-
sitter re-emits StyleSpans within ~one frame; LSP decorations
re-emit when clangd republishes. Cost = a brief uncolored window
per edit. Gain = no wrong-position color persists.

Manual revalidation (post-rebase on #44 + #45 + #46):
edit-at-boundary in the TUI now drops the old diagnostic color
cleanly. New diagnostic colors paint once clangd republishes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 15:32:47 -04:00
Levi Neuwirth 2076a3682d session 5: Phase A — Decorations consumption (diagnostics as fg)
Second Phase A session. pmacs-gpu now consumes
`InstanceMessage::Decorations` with the same M11.4 dirty-merge shape
as `StyleSpans`. Diagnostic kinds render as foreground color
overrides; background-needing kinds (selection, search match, current
line) accumulate in state but stay unpainted pending a quad pipeline.

What's wired

- `State.current_decorations: Vec<Decoration>`, sorted by
  `range.start`, cleared on `BufferSnapshot` like `current_spans`.
- `apply_attach_message` gains a `Decorations` arm — `full=true` →
  `replace_decorations`, `full=false` → `merge_decorations` (M11.4
  clip/drop/split, structurally identical to `merge_style_spans`).
- `reshape()` rewritten as a sorted-boundary sweep over both spans
  and decorations: every coverage edge becomes a chunk break.
  Effective fg color = first matching decoration with a renderable
  color, else span color, else default.
- `decoration_kind_to_color`: red error / yellow warning / blue info
  / dim hint; selection/search/current-line return `None`.

Session-5 findings (rule iii, both deferred)

- **M11.4 merge logic duplicated** between `StyleSpan` and
  `Decoration`. Structural-but-minor; defer until a third instance
  surfaces (peer-cursor decorations from `PresenceUpdate` are the
  likely third point) so the generic shape is inducted from three
  examples, not two.
- **Background-kind decorations need a wgpu quad pipeline**. glyphon
  0.11 / cosmic-text 0.18 `Attrs` is foreground-only. Structural —
  new render pass + composition story with text. Its own session,
  not absorbed into Phase A.

Adversarial-verification framing

Probe #5 (active diagnostics + multi-frontend `PresenceUpdate`
overlap) — the diagnostics half is exercised; PresenceUpdate is its
own family and isn't consumed yet. Probe #3 (viewport-boundary
edges) gets re-tested: `merge_decorations` is the same code shape as
`merge_style_spans`, so an edge-case finding there would replicate.

Gates

`cargo fmt`; `cargo clippy --all-targets --workspace -D warnings`
clean; lib 1303 + protocol 11 = 1314; m4 83; m11_5 (--features crdt)
2.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 15:32:47 -04:00
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
Levi Neuwirth 1506975ddb session 3 commit 3/3: doc the daemon's --features crdt requirement
Surfaced during manual validation: the pmacs-gpu window sits on
'(connecting...)' forever when attaching to a daemon built without
--features crdt. Handshake succeeds (negotiation reports semantic_render
+ crdt_replica as agreed by both sides), but the daemon's
crdt_replica default is cfg!(feature='crdt')=false in that build, so
send_buffer_snapshots() never fires and pmacs-gpu has nothing to
render.

Classified small under rule (iii). The structural answer (should the
daemon return a clearer signal when crdt_replica was negotiated but
isn't actually compiled in?) is genuine but deferred; for session 3
the failure mode is now documented inline at the build-AttachRequest
site so the next user to hit it recognizes the symptom.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 13:12:13 -04:00
Levi Neuwirth 62cee9118c session 3 commit 2/2: attach mode + loro rope reconstruction
pmacs-gpu now has two run modes:
- no args: hello-world (session-2 behavior preserved)
- --attach <socket>: connect to a pmacs daemon, negotiate
  semantic_render + crdt_replica, import BufferSnapshot into a local
  loro replica, render the rope text. Live CrdtOp updates apply as
  they arrive.

Architecture:
- pmacs-gpu/src/attach.rs (new): UnixStream connect + Hello /
  AttachRequest handshake on the main thread; spawns a reader thread
  that pumps decoded InstanceMessage frames through the winit
  EventLoopProxy as AppEvent::Attach(AttachEvent::Message). Clean EOF
  or transport errors surface as AttachEvent::Disconnected. Reader
  thread holds the read half of the stream; AttachClient retains the
  write half (unused yet — session 4 wires FrontendEvents back).
- pmacs-gpu/src/main.rs: ApplicationHandler<AppEvent> with a
  user_event handler that dispatches Message variants. BufferSnapshot
  builds a fresh LoroDoc, imports the snapshot bytes, extracts text
  via doc.get_text('body').to_string(), and re-shapes the glyphon
  buffer. CrdtOp passes the op bytes through doc.import (loro
  accepts both shapes), re-extracts text, re-shapes. Other
  InstanceMessage variants are intentionally ignored at session 3.
- Font size dropped from 48pt to 16pt now that we may render full
  files (the hello-world 48pt was fine for one line, awful for code).
- Initial text is '(connecting...)' in attach mode, 'hello, pmacs' in
  hello-world; attach failure falls back to '(attach failed; see
  stderr)' so the window still opens.

One small finding logged in attach.rs's connect() doc: AttachRequest's
initial_size field is a CellSize (rows × cols), nominally
TUI-shaped. Sent as a placeholder (24×80) — a structural answer
('what does initial size mean for a pixel frontend?') belongs in its
own protocol thread, not session 3. Classified under rule (iii) as
deferred.

Container id for the loro text container ('body') hardcoded to match
pmacs::crdt::CrdtState — second finding worth pre-recording: the
container name is a wire-adjacent convention that isn't carried on
the wire itself. Both ends have to agree out-of-band. Not blocking
for session 3 but a structural smell for the producer arc. Logged
as deferred (rule iii structural; the answer is probably 'thread the
container id through BufferSnapshot' but it's not session-3 scope).

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

Manual validation pending — agent environment is headless. User
walks through: start a pmacs daemon, run pmacs-gpu --attach <socket>,
confirm the window renders the daemon's file contents.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 12:23:04 -04:00
Levi Neuwirth a706864e04 session 2: pmacs-gpu workspace + wgpu/winit/glyphon hello-world
Adds the pmacs-gpu binary crate to the workspace. wgpu 29.0 + winit
0.30 + glyphon 0.11 (cosmic-text 0.18 via re-export) + pollster +
env_logger; pmacs-protocol in the dep graph but not consumed yet
(session 3 wires the attach loop).

The binary opens an 800x200 window titled 'pmacs-gpu hello-world',
sets up wgpu against its surface, configures glyphon with the bundled
JetBrains Mono Regular, and renders 'hello, pmacs' once per redraw.
Close button or Escape exits. Resize re-configures the surface and
glyphon viewport. Surface acquisition matches wgpu 29's
CurrentSurfaceTexture enum (success/suboptimal render through; lost/
outdated re-configure; timeout/occluded skip the frame).

Bundled assets: pmacs-gpu/fonts/JetBrainsMono-Regular.ttf (268 KB)
and pmacs-gpu/fonts/OFL.txt. Font shipped as required by the SIL
Open Font License 1.1.

One finding surfaced during the move and absorbed under rule (iii)
of the framing pass (small / no structural change): the design doc
recorded JetBrains Mono as Apache 2.0; the actual license has been
OFL since the family's open-source release. Doc corrected in
docs/pmacs-gpu-design.md.

Gates: cargo fmt + cargo clippy --all-targets -D warnings clean for
the whole workspace; cargo test --lib still 1314 (pmacs main crate
untouched); m4_acceptance 83; m11_5_semantic_acceptance --features
crdt 2.

Visual confirmation pending — agent environment is headless, so
'window opens, text renders' is user-side validation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 10:37:26 -04:00