feat(lint): font loading + invalid capture path composition rules#989
feat(lint): font loading + invalid capture path composition rules#989ukimsanov wants to merge 1 commit into
Conversation
Two new composition lint rules catching failure modes that recurred across the 11-round website-to-video eval. Both ship with vitest coverage; total lint suite goes from 148 to 151 tests. **`fonts.ts` (new) — two warnings** - `google_fonts_import`: composition loads fonts from `fonts.googleapis.com` via `<link>` or `@import url(...)`. External font requests fail in sandboxed/offline renders and add latency. Fix hint points to root-relative `capture/assets/fonts/...woff2` with a local `@font-face` declaration. - `font_family_without_font_face`: CSS uses a font-family that isn't declared with `@font-face` and isn't in the auto-bundled font set (Inter, JetBrains Mono, etc.). Text would silently fall back to system-ui — the visual fidelity loss the eval kept hitting. Fix hint points to the captured woff2 files. **`composition.ts` invalid_capture_path (new) — one error** Sub-compositions live in `compositions/` but get served with the project root as their base URL. `<img src="../capture/...">` works on disk but 404s in Studio and renders. Errors with a fix hint saying replace `../capture/` with root-relative `capture/`. Three vitest cases: `<img>` triggers, multi-occurrence url()s are counted, root-relative paths stay clean. Registry source files and installed blocks are exempted. **Wiring** `hyperframeLinter.ts` runs the new fonts rules alongside the existing rule set; the composition rule was added inline so it picks up automatically.
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
Three concrete bugs found while auditing PR #991: 1. html-in-canvas-patterns.md (#1 in catalog, 3D Rotation with Bloom): The code example used `new THREE.EffectComposer(renderer)` UMD-style namespace access while the ESM imports right below pull them in as bare named imports. Three.js r150+ removed the UMD `examples/js/` globals, so as written the example throws `TypeError: THREE.EffectComposer is not a constructor`. Switched to the bare names matching the imports. THREE.Vector2 stays as-is — Vector2 is on the THREE namespace. 2. techniques.md (#5, Lottie Animation): The CDN path `@lottiefiles/dotlottie-web/dist/dotlottie-player.js` returns 404. `@lottiefiles/dotlottie-web` is the JavaScript SDK, not a web component — its `main` is `dist/index.cjs`. The web-component package is `@lottiefiles/dotlottie-wc` and the custom element is `<dotlottie-wc>`, not `<dotlottie-player>`. Updated both. 3. techniques.md (5 occurrences across Lottie / lottie-web / Video / @font-face examples): asset paths used the `../capture/` pattern that PR #989's `invalid_capture_path` lint rule emits an error for. Replaced all with root-relative `capture/...`. PRs #989 and #991 are no longer self-contradictory.
Three concrete bugs found while auditing PR #991: 1. html-in-canvas-patterns.md (#1 in catalog, 3D Rotation with Bloom): The code example used `new THREE.EffectComposer(renderer)` UMD-style namespace access while the ESM imports right below pull them in as bare named imports. Three.js r150+ removed the UMD `examples/js/` globals, so as written the example throws `TypeError: THREE.EffectComposer is not a constructor`. Switched to the bare names matching the imports. THREE.Vector2 stays as-is — Vector2 is on the THREE namespace. 2. techniques.md (#5, Lottie Animation): The CDN path `@lottiefiles/dotlottie-web/dist/dotlottie-player.js` returns 404. `@lottiefiles/dotlottie-web` is the JavaScript SDK, not a web component — its `main` is `dist/index.cjs`. The web-component package is `@lottiefiles/dotlottie-wc` and the custom element is `<dotlottie-wc>`, not `<dotlottie-player>`. Updated both. 3. techniques.md (5 occurrences across Lottie / lottie-web / Video / @font-face examples): asset paths used the `../capture/` pattern that PR #989's `invalid_capture_path` lint rule emits an error for. Replaced all with root-relative `capture/...`. PRs #989 and #991 are no longer self-contradictory.
Three concrete bugs found while auditing PR #991: 1. html-in-canvas-patterns.md (#1 in catalog, 3D Rotation with Bloom): The code example used `new THREE.EffectComposer(renderer)` UMD-style namespace access while the ESM imports right below pull them in as bare named imports. Three.js r150+ removed the UMD `examples/js/` globals, so as written the example throws `TypeError: THREE.EffectComposer is not a constructor`. Switched to the bare names matching the imports. THREE.Vector2 stays as-is — Vector2 is on the THREE namespace. 2. techniques.md (#5, Lottie Animation): The CDN path `@lottiefiles/dotlottie-web/dist/dotlottie-player.js` returns 404. `@lottiefiles/dotlottie-web` is the JavaScript SDK, not a web component — its `main` is `dist/index.cjs`. The web-component package is `@lottiefiles/dotlottie-wc` and the custom element is `<dotlottie-wc>`, not `<dotlottie-player>`. Updated both. 3. techniques.md (5 occurrences across Lottie / lottie-web / Video / @font-face examples): asset paths used the `../capture/` pattern that PR #989's `invalid_capture_path` lint rule emits an error for. Replaced all with root-relative `capture/...`. PRs #989 and #991 are no longer self-contradictory.
Three concrete bugs found while auditing PR #991: 1. html-in-canvas-patterns.md (#1 in catalog, 3D Rotation with Bloom): The code example used `new THREE.EffectComposer(renderer)` UMD-style namespace access while the ESM imports right below pull them in as bare named imports. Three.js r150+ removed the UMD `examples/js/` globals, so as written the example throws `TypeError: THREE.EffectComposer is not a constructor`. Switched to the bare names matching the imports. THREE.Vector2 stays as-is — Vector2 is on the THREE namespace. 2. techniques.md (#5, Lottie Animation): The CDN path `@lottiefiles/dotlottie-web/dist/dotlottie-player.js` returns 404. `@lottiefiles/dotlottie-web` is the JavaScript SDK, not a web component — its `main` is `dist/index.cjs`. The web-component package is `@lottiefiles/dotlottie-wc` and the custom element is `<dotlottie-wc>`, not `<dotlottie-player>`. Updated both. 3. techniques.md (5 occurrences across Lottie / lottie-web / Video / @font-face examples): asset paths used the `../capture/` pattern that PR #989's `invalid_capture_path` lint rule emits an error for. Replaced all with root-relative `capture/...`. PRs #989 and #991 are no longer self-contradictory.
Three concrete bugs found while auditing PR #991: 1. html-in-canvas-patterns.md (#1 in catalog, 3D Rotation with Bloom): The code example used `new THREE.EffectComposer(renderer)` UMD-style namespace access while the ESM imports right below pull them in as bare named imports. Three.js r150+ removed the UMD `examples/js/` globals, so as written the example throws `TypeError: THREE.EffectComposer is not a constructor`. Switched to the bare names matching the imports. THREE.Vector2 stays as-is — Vector2 is on the THREE namespace. 2. techniques.md (#5, Lottie Animation): The CDN path `@lottiefiles/dotlottie-web/dist/dotlottie-player.js` returns 404. `@lottiefiles/dotlottie-web` is the JavaScript SDK, not a web component — its `main` is `dist/index.cjs`. The web-component package is `@lottiefiles/dotlottie-wc` and the custom element is `<dotlottie-wc>`, not `<dotlottie-player>`. Updated both. 3. techniques.md (5 occurrences across Lottie / lottie-web / Video / @font-face examples): asset paths used the `../capture/` pattern that PR #989's `invalid_capture_path` lint rule emits an error for. Replaced all with root-relative `capture/...`. PRs #989 and #991 are no longer self-contradictory.
miguel-heygen
left a comment
There was a problem hiding this comment.
Clean lint rules. google_fonts_import catches both and @import. font_family_without_font_face correctly skips generics, producer-bundled fonts, and font-family inside @font-face blocks. invalid_capture_path scoped correctly — only flags ../capture/ traversal. Tests comprehensive (12 cases).
jrusso1020
left a comment
There was a problem hiding this comment.
Approve at 06e71a39. Magi covered the test count (148 → 151) + producer-bundled-font / generic-family skip lists. Additive:
-
invalid_capture_pathrule scope — counts../capture/matches across HTML + CSS + inline scripts and reports total. Registry source files + installed blocks exempted via existingisRegistrySourceFile/isRegistryInstalledFileguards. Worth confirming: are there legitimate top-level project files (e.g., atools/script) that reference../capture/for off-line preview reasons? If yes, they'd hit this rule. Counter-evidence: this is a lint rule (not a build error), so it surfaces as feedback rather than gating, mitigating false-positive damage. -
google_fonts_importrule fix-hint — points tocapture/assets/fonts/...woff2(root-relative). One thing to verify against hf#988's capture pipeline: doescapture/assets/fonts/actually land at the root-relative path the fix hint claims, OR is it undercapture/extracted/fonts/? Cross-PR consistency between the rule's fix-hint and the capture pipeline's actual output directory will save users an "I followed the hint and it 404'd" moment. -
font_family_without_font_facerule — auto-bundled font set check is solid. The fallback-to-system-ui silent failure was a real eval-blocker; this catches it at lint time. ✓ -
Test coverage — 11 new tests for fonts + 3 for the capture-path rule. Both rules have explicit positive AND negative cases (e.g., auto-bundled families stay clean) — that's the right shape for a lint rule.
Stack base correctly set to feat/capture-pipeline-improvements.
— Rames Jusso

What
Two new composition lint rules catching real failure modes from the website-to-video eval. Total lint suite goes from 148 to 151 tests; all 151 pass.
3 of 5 in the pipeline-quality stack. Stacked on #988.
Why
Both rules were chosen because the failure modes recurred across multiple eval rounds:
<link href="https://fonts.googleapis.com/css2?...">in compositions. Works locally; fails in sandboxed/offline renders, adds latency, and isn't deterministic.@font-face— CSS used a font-family with no declaration and no entry in the auto-bundled set. Text silently fell back to system-ui. Visual fidelity loss across multiple v2 videos (Mercury fonts, GeistMono, "Inter Variable").../capture/paths — Sub-compositions live incompositions/but are served with the project root as their base URL.<img src="../capture/logo.svg">works on disk but 404s in Studio and renders. Four of six framer sub-agents got this wrong; mercury had four of five.How
packages/core/src/lint/rules/fonts.ts(new)google_fonts_import— warning. Detects<link>tofonts.googleapis.comand@import url(...fonts.googleapis.com...). Fix hint points to root-relativecapture/assets/fonts/...woff2with a local@font-facedeclaration.font_family_without_font_face— warning. Checks every used family against@font-facedeclarations + the auto-bundled font set. Fix hint also points to root-relativecapture/assets/fonts/.packages/core/src/lint/rules/composition.tsinvalid_capture_path— error. Counts../capture/matches across the entire file (HTML + CSS + inline scripts) and reports the total. Registry source files and installed blocks are exempted via the existingisRegistrySourceFile/isRegistryInstalledFileguards.Tests
fonts.test.ts(new) — 11 tests covering Google Fonts in<link>and@import, undeclared families, auto-bundled families staying clean.composition.test.ts— three new cases forinvalid_capture_path:<img>triggers, multi-occurrence url()s counted, root-relativecapture/clean.Wiring —
hyperframeLinter.tsruns the new fonts rules alongside the existing rule set.Test plan
../capture/paths produces the expected error.