Separates the pre-integration numbers, which described a tree 28 commits
behind, from the ones that describe what the PR now proposes.
The GPU test count is the integration proof rather than merely a pass: it
went 199 to 202, and e547a90 added exactly three tests to pmacs-gpu,
which is the entire delta on main since the merge base. Both sides' tests
are therefore present and running, and neither was dropped by the
auto-merge. Confirmed structurally as well -- main's fix survives as the
deferred closure form rather than the eager one that panicked, with its
regression test, alongside this lane's math work in the same file.
The lane was 28 commits behind. Merged rather than rebased, per the
#135/#137 precedent: the PR is awaiting review rounds and a rebase would
break every review anchor.
The only conflict was docs/active-work.md, where both sides add lanes.
Kept both: main's lanes verbatim, with this lane leading since it is the
one in flight. The conflict was pre-existing rather than introduced by
the dired or Lean 4 ledger commits -- it already conflicted against main
at e745068.
The integration surface, derived from git diff merge-base..main rather
than from another PR's file list, is pmacs-gpu/src/main.rs: main gained
72 lines there from e547a90, the minimap all-blank-slab divide-by-zero
fix, and this lane rewrites large parts of the same file. Git auto-merged
it textually. A clean auto-merge is not evidence the tree compiles, so
the full gate suite is what discharges it; the ledger records the
post-integration numbers separately from the pre-integration ones, which
described a tree 28 commits behind.
Lands the approved dired framing on main as its own docs PR, and brings
the two required docs current after find-file merged as #162.
The framing was approved after two review rounds (seven findings, then
six) and revised twice more since: revision 4 recorded what implementing
Stage 0 falsified in the approved text, and revision 5 adds the coherence
impact statement that #163 made mandatory for every framing.
The coherence statement is new work, not a restatement. COHERENCE.md
section 20 Priority 1 already names this arc -- a find-file surface and
directory-argument handling -- so the framing now states which journey
steps it touches (7, and partially 3), that it adds no interaction island
because its keys are a mode-scoped keymap through the ordinary registry
and wdired is a mode swap rather than a modal layer, that it adopts the
config registry for dired.kill-when-opening, and that it inherits the
worker-attribution gap for its read_dir jobs without worsening it. It
also draws the boundary against the adjacent Journey Stage 1 arc: CLI
directory handling belongs there, the two meet at resolve_target_buffer,
and dired supplies the buffer a directory should resolve to rather than
growing a second directory surface.
One convergence worth recording: section 2 grades the golden journey
broken at step 3 because pmacs on a directory exits 1, and the mechanism
it cites -- File::open succeeding on a directory, then read_to_end
returning EISDIR -- is the same one Stage 0 pinned in its
accepting-a-directory test, where the pcall turns it into a status
message instead.
The handoff snapshot was stale through eight merges. It now anchors on
main at 2af1ab3, records COHERENCE.md as required reading and a required
framing input, and carries the two minibuffer facts find-file
established: a custom completion source cannot descend directories, and
a selected candidate shadows typed text -- both of which apply to M-x and
switch-buffer, not just find-file.
The ledger gains the dired lane with Stage 1's scope, the reason its one
Rust change cannot be done in Lua, and the rebase note for the dired
branch, whose framing commits become redundant when this lands.
Review round 1 flagged that neither ledger knew about this branch, and
`docs/active-work.md`'s stated job is exactly the volatile open lanes.
Records the branch, base, framing revision, what Stage 1 ships, the
discharged Q#LN1 obligation, the Q#LN4 blast radius, and the four
implementation findings that are not in the framing (the `warning`
colour collision with `number`, `Some(1)` resolving to `@function`
rather than `@constructor`, the `module > declaration > def` nesting,
and `injection_aliases` being a write-only proxy). Also carries forward
the two Stage 2 corrections the framing already holds, since that lane
starts next.
Deliberately ADDITIVE ONLY -- one new section, zero deleted lines. PR
#156 is open against both this file and `docs/agent-handoff.md` and owns
the snapshot header, the canonical-base line, and the bottom-panel
lane's status. Touching those here would collide with a PR already in
review, which is the "frozen reviewed PRs do not absorb moving
overlapping work" lesson from #135/#137.
`docs/agent-handoff.md` is deliberately untouched: its §1 snapshot
describes what is ON `main`, so it gets updated when this merges, not
while it is in review.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The approved framing for Arc 8, revision 4, after three review rounds.
Seven stages: grammar/mode, multi-root LSP affinity, the Lean language
server, the Unicode input method, the goal view, the #eval output
channel, and module hierarchy. 19 decisions, 64 acceptance criteria.
Committed as this branch first commit per the house workflow; the
implementation of Stage 1 follows.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The two halves that touch live rendering, landed together because the
acceptance criteria that make either honest need both.
Suppression (Q#MS3/MS4/MS5/MS11). Detection runs in the per-line chunk
builder — the chunk-build path, never the edit path — and substitutes
each suppressed span's source bytes with ONE spacer chunk BEFORE tab
expansion, so a literal tab inside a span vanishes with it while tabs
outside keep their SourceTab provenance. The gate reads the EFFECTIVE
caret (own_cursor, which optimistic edits predict forward — F4's
no-flap requirement holds by construction) plus both own-selection
endpoints. Three motion paths can flip a gate without a content
change, and each now re-runs the per-line chunk compare, gated on a
one-scan "does the visible slice hold a $" check: the CursorByte arm,
finish_optimistic_edit (the text re-chunks under the OLD caret there;
without the hook a typed char rendered one keystroke stale), and the
Decorations arm — whose "no decoration change needs a reshape" premise
acquires exactly one exception, the Selection endpoints Q#MS11 made
suppression inputs.
The line-reuse predicate (acceptance 11, the #120 edge). Per-line
math state is cached in lockstep with line_chunk_cache: every detected
span with the gate bit it was built under. The scroll-reuse path
refuses a retained line whose cached bits disagree with the CURRENT
caret/selection — content is unchanged on every reuse path, so the
cached span set is authoritative and the gate bits are the only
variable. The acceptance test drives the stale-gate case through
rebuild_lines_reusing_scroll directly and fails if the gate is removed
from the predicate.
The hit map (B1'). hit_test_source_byte rebuilds its runs from a
whole-slice chunk walk, so it now reads the substitutions BACK from
the per-line caches — never re-planned under a possibly-newer caret —
keeping the map and the shaped glyphs one source of truth.
The draw pass (Q#MS6/MS7). Every MathItem::Glyph draws from its own
mini-buffer with Attrs pinned to the bundled math family (F8b), placed
at layout's exact x and the shaped line's REAL baseline; the
mini-buffer itself is positioned by the line_y cosmic-text actually
produced for it, so no font-metric rederivation can drift. Fraction
rules ride the bg quad batch after the decoration washes and under the
glyphs. Wash geometry gains Q#MS11's intersection rule: a wash
touching a suppressed span widens to the box's whole reserved
rectangle (a match strictly inside the span produced a zero-width
interval before), while the round-3 exclusive-end fix keeps a
non-intersecting wash off the box.
Acceptance (framing §5). Criteria 5-11 and 14-16 run on real pixels
through render_to_view: drawn ink where a literal-spacer control
renders none, with the before-region pixel-identical; the fraction
rule as a full-width run with operand ink both sides; caret-inside
rendering EXACTLY as math-disabled (driven through the real
CursorByte arm, which owns the refresh — a direct helper call would
not have pinned the wiring); every failure mode (unbalanced, unknown
command, $$, uncoverable glyph) pixel-equal to disabled; box clicks
snapping to the span start with the trailing edge landing after the
span; the scroll-reuse stale-gate bite; reflow confined to the
affected line with the after-text shifted by exactly the quantized
projection difference; selection gating and the whole-rectangle wash;
and the licence provenance pair. Criterion 17 is discharged
differentially: cargo tree -e features output for ttf-parser is
byte-identical with and without this crate's dependency line.
Also folded in, per the round-3 close-out: the F6 documenting test
($a$$b$ is eaten by the $$-opaque rule; one separating character
restores both spans), the depth-search bound raised 6 -> 8 so a
metric shift cannot make the "floor is dead code" expect fire with a
misleading message, the MathBox { end, .. } pattern nit, and the
active-work.md lane entry.
Named v0 approximations, deliberate: the peer-caret half of
acceptance 14 is pinned at the mapping level (unit tests), not
pixels; a soft-wrapped spacer draws its box whole at the first run's
origin (the one-rectangle model); the fit budget reads the bundled
code face even under a custom set_font family — the draw anchors to
the real shaped baseline either way, so only the fit margin is
approximate.
Clippy is CLEAN across the workspace at -D warnings for the first
time on this branch: the draw pass consumed every formerly-dead item,
and the three lints it could not fix (a test-only accessor, one doc
string, one manual midpoint) are fixed here.
Gates: cargo fmt --check; cargo clippy --workspace --all-targets
-- -D warnings; 1,815 default + 1,992 CRDT library tests; M4 121
(basedpyright skipped); 199 pmacs-gpu tests under PMACS_REQUIRE_GPU=1;
workspace sweep 3,131 across 88 suites (isolated XDG_CONFIG_HOME);
git diff --check.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
F1 was a real bug pinned by my own committed test. `end` in
ChunkSource::MathBox is EXCLUSIVE, so source position `end` is the first byte
AFTER the span — but the arm claimed it for the box's left edge, and the test
asserted that wrong value while calling the byte "interior". Consequences it
would have caused once overlays land: a search match starting just after a
span washes the whole box it does not intersect, violating Q#MS11; a peer
caret after the span draws at the box's left edge; caret geometry jumps
backwards. The same class existed in projected_to_source for a line-FINAL box,
where `within` clamps to the run length and the arm returned `start`
unconditionally, so a click past end-of-line landed on the span start. Both
committed hit tests put a chunk after the box, so that edge was never
exercised; there is now a test with the box last.
F2: parse_scripts peeked for the next marker without skipping whitespace, so
`x^2 _i` built a NESTED script — drawing the subscript displaced right by the
superscript's width — and `x^2 ^3` parsed where TeX errors, contradicting the
module's own "whitespace is insignificant" rule.
F3: layout is now fallible. A character the math font cannot draw used to
yield zero metrics and still emit a Glyph item, rendering tofu at zero advance
over its neighbour. Q#MS8's rule is "failure is always show the source", and
the draw pass needs a refusal signal — changed now, before that pass consumes
the API.
F4: the fraction gap was a hardcoded `thickness * 2.0` while the MATH table's
FractionNumeratorGapMin / FractionDenominatorGapMin went unread. Reading them
moved the flagship \frac{a}{b} from 0.732 to 0.867 and the fallback boundary
from depth 3 to depth 5. The round-2 review's hand-arithmetic estimate of
~0.85 was right; my 0.732 was inflated by the guess. The depth-SEARCHING test
absorbed the change without edits, which is the property it was written for.
F5: TeX's \epsilon and \phi are the lunate/symbol forms (U+03F5, U+03D5), not
U+03B5/U+03C6. Their italic mappings had to land with the seed change, since
both sit outside math_italic's U+03B1..03C9 run and would otherwise render
upright beside italic neighbours.
F7: the line-box budget derivation moved out of the test into
`line_box_budget`, so the draw pass and the acceptance test cannot compute
different splits while both stay green.
F8: the live-code clippy items are cleared. The 25 that remain are all
dead-code awaiting the draw pass.
189 pmacs-gpu tests pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PR #155 review round 2, self-review of the round-2 commit.
The round-2 change labelled "minor" — resolving both arms of
pmacs.window.buffer() through the acting frontend for uniformity — made
the NO-ARGUMENT arm fallible. `acting_frontend` follows the interactive
origin, which can name a frontend that has no registered view: a bare
`dispatch_key` from an unattached peer does exactly that. `selected_window`
then raises "acting frontend has no layout" instead of answering.
Nothing surfaced that error, because the runtime callers do not pcall it.
killring, syntax, autosave, pair, indent and comment all read
pmacs.window.buffer() on ordinary edits, so the raise silently dropped
the operation: kill_ring_acceptance went 30/30 to 25/5, with
frontend_detached_drops_per_frontend_state reporting only "B has kill
state". main is 30/30, and reverting this one file restored it.
The no-arg arm is back on ambient active_buffer_id() and now documents
why that is deliberate rather than an oversight: dispatch sets
active_frontend to the acting frontend before running a command, so the
two agree on every real path, while only the ambient resolver has the
fallback that makes it total. The explicit-window arm keeps its Q#BP11
layout validation, which is what the arc actually needed.
acc19c pins it through the real path — a buffer.after-edit subscriber
reading pmacs.window.buffer() during a viewless peer's dispatch_key —
rather than by calling the binding directly. Bite-verified:
scripts/bite bbe4152 src/lua_bindings/mod.rs --test
bottom_panel_stage1_acceptance -- acc19c goes red with the exact
"acting frontend has no layout" traceback.
The ledger also records two gating facts found on the way: the workspace
sweep must run with an isolated XDG_CONFIG_HOME, because the real user
init.lua installs a local package and the losing race leaks a status
message into painted-frame comparisons; and a latent pre-existing main
bug in the buffer CRDT undo path, which is not this branch's and whose
proptest seed is deliberately not committed here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012j4omtTMn9v1UfmHQb9ap6
MathBox/MathItem composition for the Q#MS2 subset: characters advance a pen,
scripts shift by the MATH table's superscript/subscript amounts at script
scale, and \frac stacks its operands around a rule at the math axis. Inline
\frac sets its operands one style down, which is TeX's rule and also what the
parent framing's Tier 3 specifies — and it is load-bearing for Q#MS10, since
full-size operands would not fit the line at all.
The height budget is now measured rather than assumed, and the round-2 review
was right to insist on that. Two things were wrong.
First, my own test derived the budget from the MATH font's metrics. Q#MS10
says the budget is the LINE BOX, whose baseline the CODE font places —
JetBrains Mono ascends 16.32 px and descends 4.80 px at 16 px inside the 22 px
line, against Latin Modern Math's 12.90/3.10. Using the wrong font made a
plain \frac{a}{b} score 0.485 and appear to fall below the floor, which would
have meant the flagship case never rendering.
Second, with the budget derived correctly, B6 holds — \frac{a}{b} scales to
0.732 — but rev 3's guessed fallback case does not. A doubly-nested fraction
scores 0.744 and still renders; the floor is not tripped until depth 3, at
0.580. Round 2 predicted precisely this surprise-pass. Worth keeping: depth 2
scores HIGHER than depth 1, because the binding constraint flips from descent
to ascent as nesting grows asymmetrically, so "deeper is always tighter" is
false.
The test therefore SEARCHES for the tripping depth instead of hardcoding it,
and fails if no depth trips the floor at all — which would mean the fallback
arm is unreachable and the floor is dead code. Acceptance 12 records the
measured table.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Font, licence, and the metrics half of Tier 3.
The bundled font is Latin Modern Math under the GUST Font License, added as
fonts/GUST-FONT-LICENSE.txt — deliberately a separate file from fonts/OFL.txt,
which covers JetBrains Mono only. GFL is LPPL-derived, not the SIL OFL; the
framing's F6 corrected that error and this is the discharge. At 733,736 bytes
the font is now the largest embedded asset in the repository.
ttf-parser is declared with default-features = false and only
"opentype-layout". Verified differentially: the ttf-parser feature set from
`cargo tree -e features` is byte-identical with and without this dependency
line, so the declaration widens nothing and forces no rebuild of the font
chain.
That check also corrected acceptance 17, which asserted `std` would be absent.
It is not — fontdb already enables it via `std = ["ttf-parser/std"]`, upstream
and independent of us. As written the criterion would have failed a correct
implementation, so it is now stated as the differential property that actually
matters.
MathConstants reads only what the Q#MS2 subset needs — axis height, script
scale percent, the two script shifts, and fraction rule thickness. Reading
more would be speculative: constants for deferred constructs have no consumer
to validate them, which is the Q#LX5 discipline applied to metrics. A font
with no MATH table is a typed error rather than plausible-looking zeros, so a
bundled-font regression cannot be silent (Q#MS7).
math_italic implements TeX's convention as the framing's table states it:
ASCII letters and lowercase Greek italic, uppercase Greek upright, digits and
operators unchanged, with U+210E for `h` because the 1D4xx run has a hole
there and arithmetic would land on a reserved codepoint.
Five tests, all against the real embedded bytes rather than fixtures, since B5
is the bet that would sink Tier 3 if false. One goes beyond the framing: every
italic mapping must resolve to a glyph the bundled font actually has, because
a mapping that produced tofu would be worse than the roman fallback it
replaces.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four findings, one of them a real gap.
R2-2 is the substantive one. Rev 2's math-italic fix stopped at ASCII, which
reintroduced the same defect one symbol over: the parser resolves \alpha to
U+03B1, so $\alpha x$ would have drawn an upright alpha beside an italic x —
mixed styles inside a single expression, with the Greek seed map being the
slice's second flagship case after x^2. The mapping now follows TeX's actual
convention and states it as a table: ASCII letters and lowercase Greek in math
italic (U+1D434 and U+1D6FC runs, with the U+210E hole for h), uppercase Greek
deliberately upright, digits and operators unchanged. Acceptance 13 gains
$\alpha$ and $\Gamma$ cases so the uniformity is pinned rather than assumed.
R2-1 was self-inflicted: rev 2's Q#MS3 rewrite added a corrected copy of the
tree-sitter paragraph without deleting the original, leaving the stale rev-1
text immediately below the new one. Removed.
R2-4 sharpens Q#MS11 from "covers" to "intersects". For selections the
distinction is vacuous, but a search match can genuinely half-overlap a span —
searching "2$ af" in "before $x^2$ after" matches from inside the span to
outside it — and the whole-rectangle rule must apply there too, since the box
has no interior byte map to place a partial wash against.
R2-3 moves the follow-up section after prior art; the sections now run 0
through 9 in order.
Acceptance 12 also absorbs a round-2 non-finding: the reviewer's arithmetic
puts \frac{a}{b} near 0.85 and suggests \frac{x^2}{y} also clears the 0.6
floor, so the fallback case has to be computed against the real font rather
than guessed — otherwise the fallback half of the test surprise-passes by
rendering. The criterion now says so and asks for the computed scale to be
pinned.
Framing only; no implementation, no runtime code.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nine findings, two of them decisions the implementation could not have
proceeded without, one a compliance error. Two rev-1 claims were wrong rather
than imprecise and are withdrawn where they occur.
F1, the fraction height budget, was hidden inside "a box that would exceed the
line" without saying width or height. Lines cannot grow — metrics are uniform
at 16 px over a 22 px line — and a textstyle fraction does not fit. Q#MS10 now
picks fit-to-line scaling with a 0.6x floor and source fallback below it,
records why overdraw and line growth were rejected, and states the honest
consequence: v0 shrinks nested math uniformly where TeX would use style
levels. B6 makes the floor falsifiable against the flagship case.
F3 was a genuine contradiction: acceptance 10 demanded source-width boxes
while Q#MS4 implied layout-chosen width, and that gap IS the caret-toggle
reflow question. Resolved for layout-chosen, because pinning to source width
leaves every fraction sitting in a large permanent gap, whereas reflow is
transient and tied to a deliberate caret move. Acceptance 10 is rewritten to
bound it rather than forbid it.
F2 withdrew the zero-glyph strut: a RichChunk's only width is its text, so the
mechanism is the SourceTab precedent — projected spacer text, width quantized
to whole advances. B1 is restated against the mechanism that exists.
F5 would have bitten real prose immediately: the rev-1 rule renders "prices
are $5 and $6 today" as math. Pandoc currency guards adopted, spans confined
to one line, both in acceptance.
F6 is factual and a licence matter. Latin Modern Math is the GUST Font
License, not OFL, and is 733,736 bytes rather than ~200 KB — verified against
a local TeX Live copy. The bundled licence file must say GFL, and this becomes
the largest embedded asset in the repo. The parent framing carries the same
error; correcting it is recorded as a follow-up rather than done here, since
the parent is already merged.
F4 names the new shaping-invalidation edge as the #120 trap class, including
that suppression must read the effective caret or flap during optimistic
typing. F7 puts math italic in scope, without which the flagship case renders
a roman x. F8 clarifies that glyph IDs are still resolved internally and that
drawing must pin Attrs to the math family. F9 closes the smaller gaps: the
detection hook wording, an overlay rule for selections and washes, degradation
of deferred display syntax, and folding Char/Symbol.
Framing only; no implementation, no runtime code.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`TerminalViewStatus.scroll_offset` is the retained rows between the
VIEWPORT and the live tail, so it necessarily tracks viewport height: an
assertion that it survives a panel height change unchanged is either
vacuous or wrong, and it went red once under a loaded sweep for exactly
that reason. Q#BP7's invariant is that the ANCHOR is frozen, so acc32
and acc33 now compare the first visible row's text across the change,
and additionally pin the follow behavior that distinguishes them: a
shrink never re-arms follow, growth reaching the tail does, and growth
with a frozen selection does not.
Both also wait for the child's last line before sampling, so neither
races further output.
Also records the round in docs/active-work.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The obvious next unit was Tier 2 (parser) plus Tier 3 (layout). This framing
rejects that for the arc's own reason: the substrate lane's Q#LX5 refused to
land the parser ahead of layout because MathNode's shape is only validated by
a consumer, and that argument does not stop at Tier 2. MathBox is only
validated once a renderer consumes it, so Tiers 2+3 with no Tier 4 reproduce
the same objection one layer up.
The unit is therefore thin and vertical: characters, sub/superscript and
fractions, detected, parsed, laid out against a real OpenType MATH table, and
drawn — with breadth deferred to follow-on work against an API that has been
exercised rather than only designed.
The scout also found a load-bearing error in the parent's integration table.
It places math_parse.rs and math_layout.rs in the core crate, which cannot
work: pmacs-gpu depends only on pmacs-protocol and never on pmacs, so a
core-crate parser is unreachable from the frontend that renders it, and
ttf-parser reaches only pmacs-gpu. Both also contradict the parent's own
"instance never learns a pixel" contract and its "pure frontend
responsibility in v0" protocol section. Q#MS1 moves both modules into
pmacs-gpu.
Two decisions buy the slice most of its simplicity. Rendering is suppressed
whenever the caret is inside a span, which makes editing-shows-source and
moving-away-renders mutually exclusive states and removes the
caret-inside-math problem rather than approximating it. And layout positions
characters rather than glyph IDs, which is sufficient for this subset and
explicitly does not generalise: glyph variants are what stretchy fences and
big operators need, and both are deferred.
Acceptance puts every claim that something reaches the screen on real pixels
through the existing headless render seam, because a layout engine wired to
nothing would pass any weaker test.
Framing only; no implementation, no runtime code.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review finding, verified: rev 2's "no new build cost" was true only under
an unstated condition. ttf-parser re-exports `math` behind
`#[cfg(feature = "opentype-layout")]`, and it is compiled today only
because fontdb requests that feature — with `default-features = false` and
a set that is NOT ttf-parser's own default (fontdb's adds no-std-float and
omits std). A plain `ttf-parser = "0.25"` therefore unions std in and
forces a one-time rebuild of ttf-parser, fontdb, cosmic-text and glyphon.
Record the zero-rebuild spelling, `default-features = false, features =
["opentype-layout"]`, in Tier 3 §A and in the component table, so the
Tier 3 implementer declares it deliberately rather than tripping over it.
The C1 row points at the detail rather than repeating it.
Also note in the header that every anchor was re-checked at f07b75b. The
scout pin stays at ddaa80d because that is when the scouting happened;
#153 landed between the two and is test-only, moving no anchor cited here.
Framing only; no implementation, no runtime code.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds the lane to docs/active-work.md: branch, base, what Stage 1
implemented, the verification run, and the two known local-only test
caveats (the parallel-load GPU flake and compile_mode_acceptance's
single-thread requirement).
The durable handoff snapshot stays untouched until the PR merges, per
its own update protocol.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Revision 1 was written against protocol v18, before LaTeX Stage 1 (#144),
web grammars (#146), folding Stages 1-2 (#142/#149) and the GPU initial
target (#148) landed. Revision 2 changes no design decision; it corrects
the ground truth those merges invalidated and records the staging decision
the sibling substrate framing already took. A new section 0 lists every
correction so a reader who knows revision 1 can read it alone.
Two corrections change implementation choices rather than line numbers:
- The MATH-table dependency story was wrong in both directions. Revision 1
said a crate must be added and that "neither is in the tree today";
ttf-parser 0.25.1 already reaches pmacs-gpu non-optionally through
fontdb -> cosmic-text -> glyphon, the same fontdb the frontend already
calls. And the choice is not "one of ttf-parser or read-fonts": only
ttf-parser exposes the MATH table, supplying exactly the constants Tier
3 names. read-fonts 0.37.0 is present but has none, so selecting it
would be a dead end.
- Tier 2's staging was already decided elsewhere and this note did not say
so. The sibling framing's Q#LX5 puts the parser beside its Tier 3
consumer, never ahead of it, because MathNode's shape is only validated
by a layout consumer. That makes Tier 2 not independently shippable,
which is worth stating explicitly: it is pure and conflict-free, so
landing it alone while other lanes hold the render path is exactly the
tempting move Q#LX5 refused.
Tier 1 is materially de-risked: the LaTeX grammar already exposes
math_environment and math_delimiter, and the in-repo query overlay this
tier proposed already exists and captures both, so the mechanism is proven
rather than speculative. The guessed node name (math_expression) is
corrected to the grammar's own. Markdown still needs the overlay
treatment.
Tier 4 gains a contention note. Revision 1 described the GPU render path
as though math were its only claimant; folding Stage 3 and the
bottom-panel arc's Stage 2 now converge on it, so whichever lands second
re-scouts against the first.
Framing only; no implementation, no runtime code.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds the approved framing for the bottom-panel arc: a buffer displayed
in a fixed-height window pinned to the bottom of the frame, targeted by
policy rather than by stealing the selected window, plus the missing
display-buffer/window-parameter concept underneath it.
Revision 4 follows three review rounds, an integration review, and a
landed-state audit against GPU initial target (#148, protocol v20) and
folding Stage 2 (#149).
Amended before branching by the pre-implementation dependency
verification recorded in section 0.6:
- the folding dependency is cleared and re-verified against canonical
main at ddaa80d (nothing in flight, folding Stage 2 acceptance 48/48
green, every borrowed anchor reproducing, and folding's only window.rs
edit confined to one 22-line hunk that leaves the layout functions
pre-folding code);
- R5-B1: Layout::compute has TWO production callers, not one. The
second, the peer-presence overlay pass in src/overlay_paint.rs, builds
its own text-area rect from active_layout() and never routes through
window_placements, so the planned compute(area, fixed) signature change
would otherwise leave every peer cursor painted at its no-panel row.
Corrected in section 1.1 and Q#BP2, pinned by acceptance 1, and the
fixed map is now specified to come from one shared helper rather than
being assembled per call site.
Stage 1 (window placement + TUI side windows) changes no wire shape.
Move the lane from active-work.md into its Closed section, retire the
protocol v20 / main-hash references to LANDED form in agent-handoff.md, and
record both review-round lessons (failure-socket containment, upgrade-gated
replica publication) in the ops-lessons ledger.
Integrate folding Stage 2 and its landed-state documentation with the
protocol-v20 GPU initial-target branch. Preserve per-session fold projection
selection in the target bootstrap transaction and retain v19 compatibility
coverage after the later protocol bump.
Shut down bootstrap sockets on every dispatcher-side failure and reject
frontend events whose session state was never installed. This prevents a
lingering failed client from reaching absent render/size state.
Track target-side CRDT upgrades independently from load/create status so a
deduplicated hidden buffer is published to every existing grid replica. Add
real-daemon regressions for both failure containment and replica publication.
Post-merge housekeeping owed from #149, kept as its own docs-only PR per
the #138-#140 / #147 convention. No runtime code.
- active-work.md: base snapshot and the recovery check bump 47581f4 ->
6ed4fe9. The Stage 2 lane is retired and replaced by a folding lane that
records both stages as merged with nothing in flight, and states Stage 3
(GPU) has no branch and no framing yet — carrying its named obligations
(GPU collapse at TUI parity, caret/hit-test fold-awareness, the
BufferSnapshot fold-mirror clear, CRDT-origin unfold, and flipping
FrontendView.fold_projection true for semantic frontends) as that
framing's starting point. "Closed since the last snapshot" gains #149
and #147.
- agent-handoff.md §1: main @ 6ed4fe9, the "Last updated" line and section
date, the Stage 2 bullet flipped from IMPLEMENTED/PR-OPEN to LANDED with
the Stage 3 obligations attached, and the roadmap entry (remaining arcs
now read "6 folding Stage 3").
Both stages' design points are recorded as traps Stage 3 inherits rather
than as history: the merged-hidden-component unit, per-window/per-target
map instances, per-frontend projection, position-not-row normalization,
and the post-intercept edit site.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q5BkezMppbpCgGAYk2ftxV
PR #149 review round 5 flagged this PR as stale: it still claimed `main`
@ `c49a8c7`, Stage 2 framing "rev 2, under review; no implementation, no
PR", while `main` is `47581f4` and Stage 2 is implemented and open.
- Base snapshot and the recovery check bump `c49a8c7` -> `47581f4`.
- The folding Stage 2 lane becomes IMPLEMENTED / PR #149 OPEN: framing
rev 4 approved, the `VisibleLineMap` spine, the base-moved merge (and
why it was merged rather than rebased), and the five review rounds'
design-changing findings — each of which is a trap Stage 3 inherits.
- `main`'s ledger had gone unrefreshed through four merges, not one, so
"Closed since the last snapshot" now also records web grammars HTML +
CSS (#146) and LaTeX Stage 1 (#144) with its inline-math framing
(#145), including their durable lessons.
- agent-handoff §1: `main` @ `47581f4`, the "Last updated" line, the
Stage 2 substrate bullet, and the roadmap entry.
Rebased onto `47581f4` so it stays one documentation-only commit
directly off canonical main.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q5BkezMppbpCgGAYk2ftxV
Post-merge housekeeping owed from #142, kept as its own docs PR (no
runtime code).
- agent-handoff.md §1: bump main to c49a8c7, add the folding Stage 1
substrate bullet (store/View, structural source, C-c @ surface,
command-path unfold, FoldState production; no protocol bump), refresh
the "Last updated" line and the roadmap Arc 6 entry, and note Stage 2
is in framing on folding-tui (the visible-line-map reframe).
- active-work.md: retire the Stage 1 folding lane (PR #142 was OPEN),
add a "Closed since the last snapshot" entry for #142, open the
Stage 2 (grid/daemon collapse) framing lane on folding-tui, and
refresh the canonical base snapshot to c49a8c7.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q5BkezMppbpCgGAYk2ftxV
Keep foreign BufferSnapshot publications out of existing semantic GPU
sessions while retaining grid-replica coherence. Treat dead peer writes as
peer-local failures, restore active-frontend cleanup, deterministic probe
readiness, GPU logging, shared tilde expansion, and accurate docs.
Add focused publication and cleanup coverage and record the two-window
Wayland/Vulkan smoke plus the complete post-review gate results.
`main` moved from c49a8c7 (folding Stage 1, #142) to 47581f4 (web
grammars, #146) while Stage 2 was in framing and implementation. The
text merge is clean, but it is NOT semantically clean: #146 added three
new `Viewport { .. }` literals to `src/highlight.rs`'s unit tests, and
Stage 2 gives `Viewport` a `folds` field. Merged alone, `cargo test
--lib` fails to compile — so the carry-over is resolved here rather
than left for CI to discover.
Merged (not rebased) so the four framing revisions the review rounds
cite by SHA (59410c0, e221f13, 8160d66, 4222ffa) stay reachable.
Full gate suite re-run on the merged tree.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q5BkezMppbpCgGAYk2ftxV
Update the framing, durable handoff, and active-work ledger after integrating
current canonical main and completing the required gates and real GPU smoke.
Add protocol-v20 semantic bootstrap and readiness result framing so
`pmacs --gpu FILE` opens the requested path before the GPU window becomes
ready. Keep target identity scoped to the authenticated frontend, preserve
legacy/no-target attach behavior, and publish fresh buffers coherently to
existing replicas.
Carry Unix path bytes and launcher cwd through the root broker, resolve paths
lexically in the daemon, reuse or create buffers without ambient-view state,
and preserve the managed daemon lifecycle from #141. Add focused parser,
wire, lifecycle, hook, isolation, and real-connector acceptance coverage.
Route command-time visible-line maps through each operation's target
window, while retaining the acting frontend as the projection-policy
owner. Model nested and crossing folds as merged hidden components so
row and byte clamps always resolve to one actually visible head.
Also key projection on the negotiated render selection and correct the
unmerged status of the separate Stage 1 housekeeping PR.
Round 2's three findings + two nits, all verified against c49a8c7:
- F1 (major): fold-aware motion must be frontend-projection scoped. Shared
EditorCore::move_up/down/page_* would make a simultaneous unfolded GPU
session skip source lines it still displays (a grid + a semantic session
can attach to one buffer, daemon.rs:876). Add a per-FrontendView
`fold_projection_active` flag (editor_core.rs:240, set at attach / cleared
at detach); gate ALL command-time visible-line reckoning (motion, paging,
wheel, click, auto-scroll) on it. Render-time clamps are already
grid-path-only. New Q#FD21 + simultaneous TUI+semantic acceptance.
- F2 (major): render maps must be per WINDOW, not per frame — paint_frame
and the presence pass iterate windows with distinct buffer_ids
(editor.rs:2922, overlay_paint.rs:124). Specify one map per rendered
nonterminal window (keyed on window buffer_id + TextView); peer presence
uses the recipient window's map. New split-of-different-buffers acceptance.
- F3 (moderate): hidden positions need COLUMN projection, not only row
clamping. Add `visible_position_of(pos)` -> outermost fold's range.start
(end of visible head line, Stage 1's point-move target) for local/peer
carets and selection endpoints; hidden interiors still drop. New
hidden-cursor-column-differs acceptance.
- Nits: fix the Viewport<'a> typo; state build cost honestly as O(folds)
with a byte->line lookup per fold (B4).
PR #147 (the #142 housekeeping) confirmed clean by the reviewer, no findings.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q5BkezMppbpCgGAYk2ftxV
Review found that adding ("attribute", fg(3)) for HTML/CSS also colours the
@attribute capture three already-bundled grammars emit — rust (attribute_item),
lua (<const>), yaml (directives) — which were previously unpainted. Verified on
a Rust buffer: #[derive(Debug)] now paints uniformly yellow (fg 3), an
improvement over unpainted and the distinct-attribute convention most editors
follow.
Name this retro-paint as intended in the framing (Q#WEB4, rev 4) and pin it with
rust_attribute_repaints_via_shared_attribute_capture so it is a chosen effect,
not incidental. @tag is unaffected (HTML/CSS only).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Round 1's five findings + two rulings, all verified against c49a8c7:
- F1 (major): nested folds. `head_of`(innermost) could clamp onto a
still-hidden inner head. Replace with `visible_head_of` (outermost
visible head); `view_top` clamps BACKWARD to the head, not forward
past the fold; relative numbers anchor on the clamped visible cursor.
- F2 (major): the consumer census was incomplete. Add the full §2.2
table — local selection (editor.rs:3241), peer presence
(overlay_paint.rs:159, after paint_frame), mode-line indicator
(editor.rs:3803), style/search/completion overlays — and make TUI
peer-presence fold behavior explicit scope.
- F3 (major): line numbers default Off => gutter_w==0 => no sign cell.
Make the fold glyph conditional: off => ellipsis only; on => sign
cell with diagnostic priority. Dedicated column (unconditional) named
as a deferred layout change.
- F4 (major): a frame-pinned map can't serve command-time motion, and
Viewport is Copy. Reframe as one derivation primitive with per-phase
short-lived instances (render via Option<&VisibleLineMap> on a
lifetime-bearing Viewport, preserving Copy; after-frame direct;
command-time fresh); home usable from EditorCore.
- F5 (moderate): key the Lua-path widening on InteractiveCommandOrigin
(editor.rs:53), hook the common run_buffer_edit (not only
run_managed_edit) so bypass_intercept edits don't escape, require the
target to be the invoking frontend's active-window buffer, and
explicitly DEFER undo/redo unfold.
Rulings: Q#FD17 include (normalize a hidden cursor to the visible head
before stepping); #142 housekeeping stays a separate docs PR.
Acceptance expanded to pin nested-fold/shared-cursor, local selection,
peer presence, an ordinary overlay across a fold, completion anchoring,
the scroll indicator, and both gutter-off/gutter-on fold-marker cases.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q5BkezMppbpCgGAYk2ftxV
Reframe Arc 6 folding Stage 2 in detail off canonical main @ c49a8c7
(Stage 1 / #142 merged), per the parent framing's §8/§14. Continues the
Q#FD scheme from Q#FD12.
Stage 2 makes the daemon grid renderer fold-aware: collapse hidden lines,
head-line ellipsis + gutter fold glyph, fold-aware line numbers
(visible-line relative distance), diagnostic-sign clamp-to-head, caret
clamp, visible-line viewport/scroll accounting, and the interactive-Lua
unfold widening (yank/query-replace/comment). No wire schema or protocol
change — FoldState production (Stage 1) is untouched; the GPU path is
Stage 3.
Scout findings that shaped the framing:
- The TUI has NO non-identity source-line->display-row map today; the
identity `view_top + row` is baked into ~7 sites. Folding is the first
such map, so Stage 2's spine is one shared per-frame visible-line map
(Q#FD12) that the render loop and every view_top-arithmetic site
consult; collapse lives in TextView::render, not the diff shell.
- Correcting the parent's premise: yank + query-replace are
apply_active_edit callers (local), not Lua-mutator callers; only
comment-toggle/yank-pop take the Lua path (shared with the
remote/optimistic-CRDT apply that stays deferred to Stage 3). The
widening hooks the local funnels only (Q#FD19).
One open scope fork flagged for the user: Q#FD17 (fold-aware vertical
line-motion vs render-time caret clamp only).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q5BkezMppbpCgGAYk2ftxV
Advance the active lane to Revision 2 and record closure of all four
non-structural framing findings. Keep implementation gated on explicit user
approval.
Pin launcher-owned tilde expansion, require same-buffer dedup hooks, fail
closed when hooks kill the target, and document stderr feedback during the
pre-window bootstrap wait. Record the observed protocol-version echo and
non-Unicode argv panic.
Add tree-sitter HTML and CSS grammars for .html/.htm/.xhtml and .css
highlighting, and light up HTML's <script>->javascript / <style>->css
injections (the north-star injection consumer). Both crates export their query
constants over the tree-sitter-language shim (no overlay, ABI-fine). The only
reconciliation is two new capture entries (tag, attribute) in highlight.rs.
Framing approved through three review rounds.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Advance the volatile ledger to the current canonical base and record the
portable Revision 1 framing checkpoint, scope, recovery command, and approval
boundary.