Skip to content
This repository was archived by the owner on Oct 20, 2026. It is now read-only.

Resize by handles re-renders, baked text gets its face, importer ignores comments - #5

Merged
drietsch merged 5 commits into
mainfrom
fix/films-bugs
Oct 13, 2026
Merged

drietsch merged 5 commits into
mainfrom
fix/films-bugs

Conversation

@drietsch

Copy link
Copy Markdown
Contributor

Defects found by recording the paged.media films in the real editor. Each fix landed with a test that failed first; each was also checked in the real editor (WebGPU, film-like launch) before and after, with the published 0.1.0-canary.14 and with this branch packed as 0.1.0-canary.15.

1. A web frame resized by its handles kept its old, clipped render

  • Cause. The auto renderer follows host.document.onDidChange, which reports mutations, undos and redos. The editor resizes a frame by its handles as a canvas gesture (beginGesture / updateGesture / commitGesture, a { kind: "resize" } gesture in the editor's ViewportCanvas), answered gestureCommitted — which the SDK's door does not forward (plugin-sdk host-impl.ts, document.onDidChange: mutation/undo/redo only). So no pass ran until "Render frame". The control bar's W/H fields write frameBounds with a mutation and already re-rendered (reproduced in the editor on canary.14); the journeys resize with resizeFrame, which is why they never saw it.
  • Fix. document-edited.ts: the door's events plus the editor client's gestureCommitted broadcast (the host.editor escape hatch, the way document-opened.ts hears an open on an older host). The auto renderer and the source panel's preview size follow both; a commit that changed no size still renders nothing.
  • Tests. Real host + real Blitz: a resize gesture driven through the engine's gesture messages re-renders the frame and its undo renders again (the test client fans every reply out to subscribers, as the editor's client does); the frameBounds (W/H) path pinned; unit tests for the auto renderer and the preview size.
  • Editor check. canary.14: handle drag resized the frame, no re-render; this branch: re-rendered.
  • Upstream. The lasting fix is for document.onDidChange to report gesture commits; then the raw listener here can go.

2. Baked text was flagged as a missing font

  • Cause. A baked run names the face the engine shaped it in. The film's mailer names "IBM Plex Sans", which no document holds, so every run shaped — and baked — in the bundled Inter (measured in the editor: all runs Inter; after Bake the Fonts collection read Inter, isMissing: true). The document's font registry held no Inter face: the editor loads Inter as the document default font bytes, not under a family, and the core flags a family with no registered face. The same held for a source's @font-face faces.
  • Fix. Before the bake writes its text (and before it measures it), every family the runs name that the document holds no face for (assets.getFontFace answers from the document registry) gets the bytes the engine shaped with: the bundled face, or the @font-face faces in their declared styles. A family the document holds is left alone; a host without assets.fonts@1 gets nothing. There is no plugin-API door for a document face (assets.registerFont is scene-layer only), so the engine's registerFont with scope: "document" goes through the host.editor escape hatch until the SDK has one. ADR 411 amended.
  • Tests. Real host + real Blitz with the editor's asset source: the mailer bakes in Inter and Inter is not missing; a flow bake; an @font-face face; a family the document already holds is not registered again. The 200-run bake budget gains one capability probe (207 → 208 door calls).
  • Editor check. canary.14: missing after Bake ["Inter"]; this branch: [].
  • Related, editor side. A document that itself names Inter is still flagged in the editor, which holds Inter only as the default font bytes.

3. The HTML importer read a <style> named in a comment as the CSS

  • Cause. Two regular expressions over the whole file: a <style> (or <body>) inside a comment, a <title> or a script string was taken for one.
  • Fix. A scanner that treats comments and raw-text element content as text; a body that never closes runs to the end of the file.
  • Tests. Comment, raw-text, legacy CDO/CDC, unclosed body; hostile fragments never throw.

Investigated, not changed here

  • The Web menu with two frames selected. Editor side: the context "Web" menu shows only while the selection is something a plugin frame claims, and that resolution needs exactly one selected element (packages/shell/src/state/use-selection-object-type.tsx, useSelectionEditContext; MenuBar.tsx, showContextMenu). Measured: one web frame selected → "Web" context menu; two → none. "Thread flow into frames" stays reachable from Plugins ▸ Web (enabled), not only from the palette.
  • A freeze when clicking a threaded web frame under a screencast (two of three film takes). Not reproduced: 24 clicks on a threaded source and its recipient, and three takes in the film's order (thread, then select the source, edit and save its CSS, click the recipient) under the film's capture (3200×1800 JPEG q92, frames written synchronously, film-kit pointer motion), at load averages of 15–30, on canary.14 and on this branch. Every heartbeat answered in 1–4 ms; across the click rounds there were zero scene-layer submits or clears, zero mutations and zero long tasks (> 50 ms). Ruled out: a render loop (a click starts no pass; a pass renders only on a changed label, size or bound value), a synchronous wait (the bundle never blocks on the worker; its only loops are the awaiting idle() helpers), a re-entrant reflow (no mutation on selection). Note: CDP screencast sends frames only on visual change — after a click or a save it delivered none for 2.8–3.5 s while the page answered at once, so "frames stopped" is not by itself a freeze.

Verification

  • pnpm -r typecheck — exit 0.
  • REQUIRE_REAL_ENGINE=1 pnpm -r test — exit 0: web-model 290 passed / 9 skipped (corpus, as on main), web-conformance 103, web-bundle 334 (45 files).
  • scripts/package-hash.mjs --check — ok, 0.1.0-canary.15 is a new version (the bump is in this PR so main's publish run does not fail on changed sources). Not merged, tagged or published.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QMsNAAdpKNFJHkAku215dT

drietsch and others added 5 commits October 12, 2026 20:11
A `.html` file's `<style>` blocks become the frame's CSS and its `<body>`
the HTML. The importer found them with two regular expressions over the
whole file, so a `<style>` or `<body>` named inside a comment, a `<title>`
or a script string was taken for one: the spring mailer opened for the
paged.web film said in its head comment that "the <style> block" becomes
the CSS, and the CSS then began inside that comment and ran over the head.

The importer now scans the markup the way a tokenizer does as far as tags
go: comments and the content of raw-text elements are text. A body that
never closes runs to the end of the file. Tests: comment, raw-text, legacy
CDO/CDC and unclosed-body cases, plus hostile fragments that must not throw.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QMsNAAdpKNFJHkAku215dT
The canvas follows the document through `document.onDidChange`, which
reports mutations, undos and redos. The editor resizes a frame by its
handles as a canvas gesture: begun, updated and committed on the engine,
answered by `gestureCommitted`, which that door does not report. A single
web frame resized by its handles therefore kept its old, clipped render
until "Render frame" was run by hand, and the source panel's preview kept
its old size. (The journeys resized with a `resizeFrame` mutation, which
the door does report.)

`document-edited.ts` adds the editor client's gesture commits to the
door's events (the `host.editor` escape hatch, as `document-opened.ts`
hears an open on an older host); the auto renderer and the preview size
follow both. A commit that changed no size renders nothing, as before.

Tests: against the real host and engine, a resize gesture driven through
the engine's gesture messages (with the client fanning every reply out to
its subscribers, as the editor's does) re-renders the frame and its undo
renders again; the control bar's W/H path (`frameBounds`) is pinned too;
unit tests for the auto renderer and the panel's preview size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QMsNAAdpKNFJHkAku215dT
A baked run names the face the engine shaped it in. The web film's mailer
names "IBM Plex Sans", which no document holds, so the engine shaped every
run in its bundled Inter, and the bake wrote native text in Inter, a face
the document's font registry did not hold either: the Fonts collection
flagged it missing ("Missing fonts: 1") and the canvas drew the baked text
in a substitute. The same held for a face a source's `@font-face` rule
loads.

Before the bake writes its text, each family its runs name that the
document holds no face for (`assets.getFontFace` answers from the document
registry) is given the bytes the engine shaped with: the bundled face, or
the `@font-face` faces in their declared styles. It happens before the
runs are measured, so they are measured in their face too. A family the
document holds is left alone; a host without `assets.fonts@1` gets nothing.
The plugin API has no door for a document face (`assets.registerFont` is
scene-layer only), so the engine's `registerFont` with `scope: "document"`
goes through the `host.editor` escape hatch until the SDK has one
(ADR 411, amendment 2026-10-12).

Tests: against the real host and engine with the editor's asset source
(bytes from the document registry): the mailer bakes in Inter and Inter is
not missing; a flow bake; an `@font-face` face; a family the document
already holds is not registered again. The 200-run bake budget gains the
one capability probe (207 -> 208 door calls).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QMsNAAdpKNFJHkAku215dT
…fixes

status: the importer reads no tags out of comments or raw text; a resize by
the frame's handles re-renders; the bake gives the document the faces its
text names. architecture: the two `host.editor` escape-hatch uses (gesture
commits; the document-font registration) and the bake's `getFontFace` read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QMsNAAdpKNFJHkAku215dT
@drietsch
drietsch merged commit ed45a44 into main Oct 13, 2026
3 of 4 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant