fix: bundle deterministic nerd font fallback for renderer#11
Merged
Conversation
448c812 to
299e943
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
User-facing / automation-facing behavior changes
Validation
Design-doc deviations
Review artifacts
📋 Implementation Plan
Plan: Deterministic Nerd Font fallback for the reference renderer
Recommendation
Adopt a deterministic bundled fallback-font approach for the Ghostty Web reference renderer:
ghostty-webinitializes,This is the most sensible long-term option for
agent-terminalbecause the project treats the renderer as reference visual truth and relies on reproducible screenshots, WebM exports, and artifact hashes. I am comfortable maintaining explicit bundled assets + strict profile metadata + tests. I am not comfortable making the reference renderer depend on host font discovery, system package state, or browser-permission-driven font APIs.Context and verified evidence
src/renderer/bundledFont.tscurrently defines and hashes one bundled font asset:JetBrainsMono-Regular-latin.woff2.src/renderer/types.tscurrently models render profiles withfontFamilyplus an optional singlefontAssetIdentity.src/renderer/profiles.tsuses that single bundled JetBrains Mono asset for both built-in profiles:reference-darkandreference-light.src/renderer/profiles.ts#hashProfile()includes font metadata in the render-profile hash today, so the renderer already has a good reproducibility hook.src/renderer/ghosttyWeb/backend.tsserves a single bundled font route, andsrc/renderer/ghosttyWeb/harness.htmlhas a single-fontloadBundledFont()path that hard-codes JetBrains Mono.test/unit/renderer/profiles.test.tstest/unit/renderer/ghosttyWebBackend.test.tstest/integration/renderer-backend.test.tstest/e2e/unicode-grid.test.tstest/e2e/renderer-slice.test.tsdogfood/, with PNG screenshots, JSON envelopes, and WebM/asciicast artifacts.Why this option is the best fit
What I am comfortable maintaining long term
What I do not recommend taking on now
Why I am rejecting the other options for now
System-font fallback only
Low implementation cost, but screenshots would vary by machine and CI image. That conflicts with the current renderer philosophy and makes artifact review less trustworthy.
Browser/local-font discovery
Potentially clever, but too environment-specific and brittle for a core renderer feature. It also adds browser-permission and API-surface complexity that does not pay for itself here.
Full patched Nerd Font replacement as the main font
That is heavier than necessary. The practical issue is missing icon/symbol glyphs, so a focused fallback font is the smaller and cleaner solution.
Proposed implementation workstreams
1. Add multi-font profile metadata without overcomplicating custom profiles
Files / symbols
src/renderer/types.tssrc/renderer/profiles.tsRenderProfileConfigPlan
RenderProfileConfigwith an optional ordered bundled-font descriptor list, e.g.fontAssetsor similar.fontFamilyas the final CSS stack string used by the browser/renderer.familyassetIdentityrouteweightstylefontFamilyand no bundled font descriptors should still validate and behave as best-effort system-font profiles.hashProfile()so the ordered bundled font descriptors participate in the canonical hash.Acceptance criteria
2. Refactor the single bundled font into a small registry
Files / symbols
src/renderer/bundledFont.tssrc/renderer/ghosttyWeb/assets/*Plan
Acceptance criteria
3. Update Ghostty Web asset serving and font preloading
Files / symbols
src/renderer/ghosttyWeb/backend.tssrc/renderer/ghosttyWeb/harness.htmlPlan
loadBundledFont()path with aloadBundledFonts()loop that loads every declared bundled face beforeinit().Acceptance criteria
4. Update built-in reference profiles to use a primary + fallback stack
Files / symbols
src/renderer/profiles.tsPlan
reference-darkandreference-lightto use a CSS stack like:monospace)Acceptance criteria
5. Add targeted tests at the right layers
Files / symbols
test/unit/renderer/profiles.test.tstest/unit/renderer/ghosttyWebBackend.test.tstest/integration/renderer-backend.test.tstest/e2e/unicode-grid.test.tsor a new focused glyph fixture/testtest/fixtures/apps/*Plan
unicode-grid) that prints representative Nerd Font icons used in the observed LazyVim/Claude Code workflow.Acceptance criteria
6. Dogfooding and reviewer proof bundle
Files / symbols
dogfood/new scenario bundlesrc/cli/main.tsPlan
AGENT_TERMINAL_HOME.Dogfooding acceptance criteria
Validation / quality gates
Required automated validation
mise run test(or the repo-equivalent split commands if a narrower pass is needed during development)mise run buildRequired manual validation
Risks and decision points
Asset choice / licensing
Visual metrics drift
Hash churn
Scope control
Out of scope for the first implementation
Final recommendation for execution
If we implement this, the safest and most maintainable path is:
That gives us better screenshots without sacrificing determinism, which is the key tradeoff I care about most for long-term maintenance in this codebase.
Generated with
mux• Model:openai:gpt-5.4• Thinking:high