buildBulletItem computed `leading` as the median over ALL paragraph line gaps. In a bullet list
the bullet-to-bullet gap (~14.2pt) is larger than the within-bullet wrap gap (~12.2pt) and dominates
the median, so a newly-wrapped continuation line was placed ~2pt too low (visible extra spacing
between e.g. the Architecture line and its "Design Patterns…" continuation).
Use the WRAP spacing instead: prefer the edited item's own internal line gap (if it already wraps),
then any hanging-continuation gap in the block, then the all-lines median, then font*1.2. Only
affects multi-line (wrapped) bullet items; single-line edits emit no continuation so are unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Typing at a run boundary (e.g. near "Async" in a bullet) often lands the new text in a
browser-created wrapper element whose only child is that text. resolveStyleEl's sibling-only
search then found no data-fid run and fell back to dominantFid — which for a bullet is the bold
LABEL run (its first run) — so the inserted text rendered bold.
Make resolveStyleEl CLIMB toward the editor root and scan siblings at each level (nearest
preceding run first, then following), so it reaches the real run spans alongside the wrapper and
the inserted text continues the adjacent run's font. data-advances is still read only from the
exact wrapping span, so inherited fonts correctly re-measure. Text typed inside an existing run
is unaffected (step 1 returns its span immediately).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two bugs made centered/right-aligned heading edits vanish on commit:
1. Gateway: ReflowParagraphData.align was Literal["left","justify"], so a center/right reflow
commit failed Pydantic validation -> 422 Unprocessable Entity. Widened to include
"center","right" (matches the frontend + engine, which already handle them).
2. Frontend: gatewayService.applyEdits caught ALL errors (incl. HTTP 422/403/500) and returned
fake success with a phantom `${documentId}_edited` id. The app then adopted that id and 404'd
on every fetch, so the edit silently reverted instead of showing an error. Now only genuine
network failures use the offline fallback; real HTTP errors throw the gateway's detail so the
caller toasts a clear message and stays on the current document.
Gateway runs with --reload (auto-picks up the model change); frontend needs a refresh.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
build_wasm.ps1 built the `wasm` STUB preset (PDFENGINE_WITH_PDFIUM=OFF, reflow #ifdef'd out)
and deployed it to the UNUSED pdfengine.{mjs,wasm}. The frontend actually loads
pdfium-engine.{mjs,wasm} (the `wasm-pdfium` preset). So "rebuild the WASM" silently shipped a
reflow-less stub to the wrong file — engine changes never reached the browser.
- Build the `wasm-pdfium` preset (PDFium ON) into pdfeng-build/wasm-pdfium (outside OneDrive).
- Deploy bin/pdfengine.{mjs,wasm} -> public/pdfium-engine.{mjs,wasm} (the rename the frontend expects).
- Set EMSDK_QUIET before sourcing emsdk_env.ps1 (its stderr tripped $ErrorActionPreference='Stop').
- Run a new pdfium-engine.smoke.mjs (buildInfo + live-preview exports) instead of pdfengine.test.mjs,
which targets the stub facade (mock render / engineHasSkia / getDocumentFonts) and can't pass here.
- Relax EAP around the smoke node call so a real failure reports cleanly, not as NativeCommandError.
- pdfengine.test.mjs: de-brittle the buildInfo assertion (was pinned to a stale exact phrase).
Verified: scripts/build_wasm.ps1 runs green end-to-end and deploys pdfium-engine.wasm (md5 53d1ec9f).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The live-preview engine the frontend loads is pdfium-engine.wasm, built by the
`wasm-pdfium` CMake preset (PDFENGINE_WITH_PDFIUM=ON). The reflow code is inside
`#ifdef PDFENGINE_WITH_PDFIUM`, so the `wasm` stub preset (PDFium OFF) — which
scripts/build_wasm.ps1 builds and copies to the UNUSED pdfengine.wasm — never
contained reflow at all. Result: every prior "WASM rebuilt" step updated the wrong,
reflow-less artifact, so the caret x0 fix (and center/right) never reached the browser.
Rebuilt the wasm-pdfium preset and deployed bin/pdfengine.{mjs,wasm} ->
public/pdfium-engine.{mjs,wasm} (md5 now 53d1ec9f, reflow object 631KB vs the 18KB
stub). Bumped the cache-buster so browsers fetch the new engine.
Engine .pyd + overlay-diff gate unchanged (1.70%/1.69%, render identical); the x0
change is caret-only metadata.
Follow-up: build_wasm.ps1 builds the wrong (stub) preset and deploys the wrong file.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The reflow caret layout hardcoded x0 = columnLeft. That matched only left-aligned
lines; for the new center/right headings the line renders at a shifted x (e.g. a
centered title starts ~215pt while columnLeft is the page margin ~54pt), so the
frontend mapped clicks and drew the caret ~160pt off the actual glyphs.
Record the line's true starting x (after lineX/center/right) as x0 instead. x0 is
caret-only metadata read by the live editor — it does not affect glyph emission or
the rendered page, so output is unchanged (overlay-diff gate identical: 1.70%/1.69%,
STRESS PASS). Rebuilt .pyd + WASM; cache version bumped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reflow (primary editor):
- Geometric backstop in reflow_paragraph: adopt any text object fully inside the paragraph
bbox that the model's objectIndices omitted (PDFium glyph->object map can return -1), so it's
deleted + its original font is resolved. Fixes both the leftover-glyph "bulge/merge" and the
font-substitution-on-edit (same root cause). No-op on correctly-indexed paragraphs (gate-proven
byte-identical: overlay diff unchanged at 1.69%/1.70%).
- Preserve data-fid on edited contentEditable nodes (extractFlatRuns climbs to nearest styled
ancestor / inherits from adjacent run) so edits keep their real font instead of the dominant.
- Converge preview & commit through one buildReflowData (no preview-OK/commit-wrong drift).
- Center/right alignment: additive emission branch (greedy path only) + heading-align inference.
- IME composition handling (suppress render mid-composition; don't commit on composing Enter).
- Fix WASM document-handle leak (free superseded versions on documentId change/unmount).
- Quiet the backstop instrumentation (warn -> debug) now the root cause is confirmed.
Raw Text / StreamEditor (beta companion):
- Permission-gate both text_objects endpoints behind canModify; label tool "(beta)".
- P1a: preserve TJ kerning numbers for same-length edits (redistribute into original slots),
fall back to single-string collapse otherwise (never worse than before).
- P1b: reject edits with characters unencodable in the run's font (clear 400 instead of corruption).
- P2a: commit Raw Text edits as a new document version (add_document) and adopt the id via
pushHistory, so they join undo/redo instead of mutating in place.
- P2b: exact hit-boxes from real font metrics (re-measured once @font-face loads).
WASM preview rebuilt with the reflow changes; cache version bumped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>