Skip to content

Extend Ghostty CJK font-fallback injection to symbol ranges (⬡ U+2B21, ▰/▱ gauges) - #9193

Merged
teamleaderleo merged 13 commits into
manaflow-ai:mainfrom
ammachado:symbol-font-fallback-coralline-2b21
Oct 2, 2026
Merged

teamleaderleo merged 13 commits into
manaflow-ai:mainfrom
ammachado:symbol-font-fallback-coralline-2b21

Conversation

@ammachado

@ammachado ammachado commented Jul 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Ghostty's CTFontCollection scoring in discoverFallback() (src/font/discovery.zig) is monospace-biased: when a codepoint isn't in the configured font, it can pick whatever installed "monospace" font also claims coverage instead of the narrower substitute CoreText's own CTFontCreateForString cascade would choose.
  • cmux already works around this for CJK ranges via loadCJKFontFallbackIfNeeded (fix: add CJK font fallback to prevent decorative font rendering #1017 / GhosttyConfigDiscovery.autoInjectedCJKFontMappings), but that injection is scoped to CJK-only ranges.
  • Nanako0129/coralline#47 traced this exact scoring bug through the Geometric Shapes (▰/▱/●/○) and Miscellaneous Symbols and Arrows (⬡ U+2B21, ⬢ U+2B22) ranges, used by status-line tools rendering gauges/icons inside the terminal — and confirmed it reproduces specifically inside cmux (which vends its own Ghostty fork with the identical discovery.zig fallback logic for the CoreText path).
  • This extends the same font-codepoint-map auto-injection mechanism to those symbol ranges, pointing them at Apple Symbols (the font CoreText's own cascade resolves to for these ranges), so the fallback choice is predictable instead of landing on an arbitrary, potentially much wider "monospace" font.
  • Like the CJK path, injection only happens when the user hasn't already configured font-codepoint-map, an explicit multi-font font-family fallback chain, or a primary font that already covers the range — so it never overrides user-managed fallback.

Changes

  • GhosttyConfigDiscovery.swift: adds symbolRanges, symbolFallbackFont, symbolCoverageSampleCharactersByRange, symbolFontMappings(), autoInjectedSymbolFontMappings(), shouldInjectSymbolFontFallback().
  • GhosttyTerminalView.swift: wires loadSymbolFontFallbackIfNeeded into loadDefaultConfigFilesWithLegacyFallback right after the existing CJK loader, plus thin static forwarders mirroring the CJK ones.
  • GhosttyConfigDiscoveryTests.swift: new GhosttyConfigDiscoverySymbolTests suite covering mapping generation and override precedence.

Test plan

  • swift test --filter GhosttyConfigDiscovery in Packages/macOS/CmuxTerminalCore — 23/23 pass, including the 6 new symbol-fallback tests.
  • Full cmux app-target build (xcodebuild -scheme cmux, via ./scripts/reload.sh) — passes. The earlier zig 0.15.2 gap was environment-specific and resolved with ./scripts/setup.sh (zig 0.16.0, cached GhosttyKit.xcframework). Build then hit an unrelated pre-existing main bug (CLI/CMUXCLI+AgentHookFailureReporting.swift had a non-exhaustive switch missing AgentHookFailureStage.journalAppend, from an unrelated upstream commit); fixed via a cherry-picked commit (fix: handle AgentHookFailureStage.journalAppend in failure reporting) so this branch builds clean end to end.
  • Manual repro: launched a tagged Debug build with an isolated XDG_CONFIG_HOME pointing at font-family = JetBrainsMono Nerd Font (no CJK language, no explicit font-family fallback chain, no font-codepoint-map). Printed ⬡ ⬢ ▰ ▱ ● ○ inside the terminal — all render as compact, near-1-cell-width, predictable glyphs, not an arbitrary wide substitute.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Extends Ghostty's font-fallback auto-injection to symbol codepoints (⬡ U+2B21, ▰/▱ gauges) so status-line glyphs resolve to Apple Symbols instead of an arbitrary wide monospace font. Injection matches CoreText's cascade and only covers codepoints the configured font — or the default embedded JetBrains Mono face, when no font-family is set — doesn't already render.

  • Skips injection when the user has font-codepoint-map or a multi-entry font-family chain, and fails closed when the configured font can't be probed (for both symbol and CJK injection).
  • Shares one config scan and one font-codepoint-map directive builder between the CJK and symbol loaders so the two paths can't drift apart; coverage probing handles non-BMP codepoints via surrogate pairs.
  • Adds tests for per-codepoint filtering, default-face coverage (with a drift check against Ghostty's embedded JetBrains Mono that skips when the submodule isn't checked out), surrogate pairs, and fail-closed behavior.

Written for commit 4049bca. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added automatic symbol/pictographic font fallback by discovering uncovered symbol codepoint ranges and injecting matching font-codepoint-map directives when needed.
  • Bug Fixes

    • Improved fallback injection rules to avoid overriding user-provided font-codepoint-map or explicit multi-step font-family fallback chains.
    • Tightened “fail-closed” behavior so fallback auto-injection is skipped when required fonts can’t be verified.
  • Tests

    • Added coverage for symbol mapping discovery, injection filtering using glyph coverage probing, consistency checks for injection decisions, and CJK fail-closed behavior.

Note

Low Risk
Startup-only Ghostty config injection for font fallbacks; behavior is gated on user config and glyph coverage, with no auth or data-path changes.

Overview
Adds symbol-glyph font-codepoint-map auto-injection alongside the existing CJK workaround so glyphs like ⬡ (U+2B21) and ▰/▱ gauges map to Apple Symbols instead of an arbitrary wide monospace fallback from Ghostty’s CoreText scoring.

Injection is per codepoint (with coverage probing via new fontContainsGlyph, including surrogate pairs), skips user font-codepoint-map / multi-font-family chains, and when there is no font-family only overrides codepoints the embedded default JetBrains Mono lacks. CJK auto-injection now fails closed when the configured font family cannot be resolved, matching the symbol path.

GhosttyTerminalView resolves config scan paths once, runs the new symbol loader after CJK, and shares loadInjectedFontCodepointMap for both. Tests cover override rules, partial Nerd Font coverage, default-face filtering, and vendored-font drift for defaultFaceCoveredSymbolCodepoints.

Reviewed by Cursor Bugbot for commit 79da9c0. Bugbot is set up for automated code reviews on this repo. Configure here.

@cursor

cursor Bot commented Jul 30, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

Ghostty now discovers symbol fallback mappings, checks configured glyph coverage, and injects missing font-codepoint-map directives during default configuration loading. CJK injection now fails closed when font probing is unavailable, with expanded test coverage.

Symbol font fallback

Layer / File(s) Summary
Symbol mapping discovery
Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/GhosttyConfigDiscovery.swift
Defines symbol ranges and fallback mappings, gates injection on configuration and glyph coverage, adds single-codepoint glyph probing, and tightens unavailable-font handling for CJK mappings.
Ghostty configuration injection
Sources/GhosttyTerminalView.swift
Shares resolved scan paths for CJK and symbol discovery, loads symbol fallback mappings during default configuration setup, and forwards discovery APIs through GhosttyApp.
Symbol fallback tests
Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/GhosttyConfigDiscoveryTests.swift
Tests mappings, configuration gates, coverage outcomes, supplementary-plane glyph probing, unavailable font probing, CJK fail-closed behavior, and injection decisions.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GhosttyApp
  participant GhosttyConfigDiscovery
  participant CoreText
  participant GhosttyConfig
  GhosttyApp->>GhosttyConfigDiscovery: resolve symbol fallback mappings
  GhosttyConfigDiscovery->>CoreText: check configured font glyph coverage
  CoreText-->>GhosttyConfigDiscovery: return covered codepoints
  GhosttyConfigDiscovery-->>GhosttyApp: return uncovered mappings
  GhosttyApp->>GhosttyConfig: load font-codepoint-map directives
Loading

Suggested reviewers: azooz2003-bit

🚥 Pre-merge checks | ✅ 24 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (24 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed The new symbol-fallback path is pure config discovery and mirrors the existing CJK loader; it adds no new @MainActor coupling or shared mutable Sendable state.
Cmux Swift Blocking Runtime ✅ Passed New symbol-fallback code adds pure mapping/config loading only; no semaphores, waits, sleeps, asyncAfter, sync, or new locks were introduced.
Cmux Browser Automation Off-Main ✅ Passed Diff only touches Ghostty font-fallback discovery, tests, and startup wiring; no browser.* routing, WebKit waits, or socket-worker code changed.
Cmux Expensive Synchronous Load ✅ Passed No agent-history/JSONL/transcript loader was added; the diff only adds Ghostty config font-fallback scanning on an existing startup/reload path.
Cmux Cache Substitution Correctness ✅ Passed No persistence/history/undo/snapshot path was changed; the new scan-path reuse is per-call, not a stale or opportunistic cache substitution.
Cmux No Hacky Sleeps ✅ Passed PASS: The diff only touches Swift files; the rule targets non-Swift runtime code, and no new fixed waits/polling were introduced.
Cmux Algorithmic Complexity ✅ Passed New symbol logic only iterates a fixed 7-codepoint list; no new nested or hot-path rescans over scalable user collections were introduced.
Cmux Swift Concurrency ✅ Passed The diff adds only synchronous symbol-fallback discovery/wiring and XCTest coverage; no new background queues, Combine state, completion-handler APIs, or fire-and-forget Tasks were introduced.
Cmux Swift @Concurrent ✅ Passed No changed async/nonisolated work or @concurrent annotations; the new symbol-fallback paths are synchronous and remain in existing UI-bound config loading.
Cmux Swift Package Boundaries ✅ Passed Symbol-fallback logic lives in CmuxTerminalCore; the app-target changes only wire package APIs into Ghostty config loading, which is allowed glue.
Cmux Swiftpm Lockfiles ✅ Passed PR only changes Swift source/tests; no Package.swift, Package.resolved, .gitignore, or Xcode project/dependency metadata changed.
Cmux Swift Logging ✅ Passed The commit diff adds no print/debugPrint/dump/NSLog/Logger changes in runtime Swift; only fallback logic and tests were modified.
Cmux User-Facing Error Privacy ✅ Passed The patch only adds config-discovery/fallback logic and tests; it doesn't add or change any user-facing error, alert, or recovery copy.
Cmux Full Internationalization ✅ Passed No user-facing text or locale-catalog changes were introduced; the diff only adds internal config tokens, comments, and tests.
Cmux Swiftui State Layout ✅ Passed Diff only adds font-fallback config loading and CoreText helpers; no new ObservableObject/@State/GeometryReader or render-time state mutation in SwiftUI views.
Cmux Architecture Rethink ✅ Passed Symbol fallback uses the existing GhosttyApp composition path with clear ownership; no sleeps, observers, extra state owners, or duplicate lifecycle wiring were added.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Diff only changes font-fallback discovery/wiring and tests; no NSWindow/NSPanel/NSWindowController/WindowGroup additions or cmuxAuxiliaryWindowIdentifiers changes.
Cmux Source Artifacts ✅ Passed All changed paths are intentional source/test files; no logs, caches, build output, temp dirs, or other artifact paths were added.
Cmux No Test Or Debug Seam In Production Source ✅ Passed Touched Sources add real symbol-fallback behavior used by GhosttyApp; no new debug/test-only seam or test-only accessor was added in production.
Cmux No Ambient Global State ✅ Passed No new ambient global state: the PR adds methods on an owning discovery type and uses existing GhosttyApp composition-root state; only static let constants were added.
Title check ✅ Passed The title clearly and concisely describes extending Ghostty font fallback injection to symbol ranges.
Description check ✅ Passed The description clearly explains the motivation, implementation, safeguards, and testing, with only non-critical template sections omitted.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/GhosttyConfigDiscovery.swift`:
- Around line 113-153: Update autoInjectedSymbolFontMappings so a configured
font family with no rangeCoverageProbe returns nil when
fontProbe.configuredFont(named:size:) cannot produce a font, rather than
retaining mappings and injecting Apple Symbols; preserve coverage-based removal
when probing succeeds. Add a regression test using a configured font-family and
NoFontProbe that verifies no mappings are returned.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ae64ea65-18e0-45d4-910b-ba94b6112a8b

📥 Commits

Reviewing files that changed from the base of the PR and between 3136fca and cc90749.

📒 Files selected for processing (3)
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/GhosttyConfigDiscovery.swift
  • Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/GhosttyConfigDiscoveryTests.swift
  • Sources/GhosttyTerminalView.swift

ammachado added a commit to ammachado/cmux that referenced this pull request Jul 30, 2026
When fontProbe.configuredFont(...) returns nil (family name doesn't
resolve), both autoInjectedSymbolFontMappings and the pre-existing
autoInjectedCJKFontMappings previously fell through and returned every
mapping unfiltered, forcing the injected font (Apple Symbols / a CJK
font) over ranges whose actual coverage is unknown. That's not "keeping
Ghostty's behavior" for the uncertain case, it's overriding it on a
guess. Return nil instead so cmux injects nothing and Ghostty's own
discoverFallback() runs unmodified when we can't tell whether an
override is actually needed.

Addresses CodeRabbit review comment on manaflow-ai#9193:
manaflow-ai#9193 (comment)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 30, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/GhosttyConfigDiscovery.swift (1)

99-101: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Probe the symbol characters this feature is meant to protect.

The geometric-shapes range samples ■ and ●, but the PR also targets ▰, ▱, and ○. A font can cover the sampled characters while missing those glyphs; Line 142 would then remove the entire range and skip the intended Apple Symbols fallback. Include U+25B0, U+25B1, and U+25CB, or probe the actual managed glyph set.

As per path instructions, fallback injection decisions must use a reliable coverage signal for the characters being managed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/GhosttyConfigDiscovery.swift`
around lines 99 - 101, The symbol coverage sampling in
symbolCoverageSampleCharactersByRange does not probe all managed geometric-shape
glyphs, allowing missing glyphs to bypass the Apple Symbols fallback. Add probes
for U+25B0, U+25B1, and U+25CB, or otherwise use the complete managed glyph set
so fallback decisions reflect coverage of every character the feature protects.

Source: Path instructions

Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/GhosttyConfigDiscoveryTests.swift (1)

112-113: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Use #require before force-unwrapping mappings.

#expect(mappings != nil) is non-fatal; if this call returns nil, mappings! traps and masks the assertion failure. Bind the value with try #require(discovery.autoInjectedSymbolFontMappings(...)) and use mappings directly for the range assertion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/GhosttyConfigDiscoveryTests.swift`
around lines 112 - 113, Replace the non-fatal nil assertion and force unwrap in
the mapping test with a throwing `#require` binding around
discovery.autoInjectedSymbolFontMappings(...). Use the resulting mappings value
directly in the Set range assertion, preserving the existing expected range.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/GhosttyConfigDiscovery.swift`:
- Around line 99-101: The symbol coverage sampling in
symbolCoverageSampleCharactersByRange does not probe all managed geometric-shape
glyphs, allowing missing glyphs to bypass the Apple Symbols fallback. Add probes
for U+25B0, U+25B1, and U+25CB, or otherwise use the complete managed glyph set
so fallback decisions reflect coverage of every character the feature protects.

In
`@Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/GhosttyConfigDiscoveryTests.swift`:
- Around line 112-113: Replace the non-fatal nil assertion and force unwrap in
the mapping test with a throwing `#require` binding around
discovery.autoInjectedSymbolFontMappings(...). Use the resulting mappings value
directly in the Set range assertion, preserving the existing expected range.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9d42cabd-d100-4716-a02b-401bcb210f77

📥 Commits

Reviewing files that changed from the base of the PR and between cc90749 and df5f7a8.

📒 Files selected for processing (2)
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/GhosttyConfigDiscovery.swift
  • Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/GhosttyConfigDiscoveryTests.swift

ammachado added a commit to ammachado/cmux that referenced this pull request Jul 30, 2026
Address two follow-up CodeRabbit findings on manaflow-ai#9193:

- symbolCoverageSampleCharactersByRange only sampled U+25A0/U+25CF for
  the Geometric Shapes range, so a font could pass the coverage check
  while still missing U+25B0/U+25B1/U+25CB (the coralline bar glyphs
  ▰/▱/○ this PR exists to fix) and silently skip the Apple Symbols
  fallback for exactly the characters that need it.
- autoInjectedSymbolFontMappingsFiltersRangesCoveredByConfiguredFont
  used #expect(mappings != nil) followed by a force-unwrap, which traps
  instead of failing the assertion cleanly if it regresses. Switched to
  try #require(...).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/GhosttyConfigDiscoveryTests.swift (1)

90-97: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the fallback-chain test independent of Apple Symbols.

The requirement is to skip injection for any explicit multi-entry font-family chain, but this fixture includes Apple Symbols. A regression that special-cases that font could pass without proving the generic chain gate. Use two unrelated font families here; keep a separate Apple Symbols case only if that behavior is independently required.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/GhosttyConfigDiscoveryTests.swift`
around lines 90 - 97, Update
autoInjectedSymbolFontMappingsSkipsWhenExplicitFallbackChainPresent to use two
unrelated font-family values instead of including Apple Symbols, ensuring the
test verifies generic multi-entry fallback-chain detection rather than a
font-specific special case.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/GhosttyConfigDiscoveryTests.swift`:
- Around line 90-97: Update
autoInjectedSymbolFontMappingsSkipsWhenExplicitFallbackChainPresent to use two
unrelated font-family values instead of including Apple Symbols, ensuring the
test verifies generic multi-entry fallback-chain detection rather than a
font-specific special case.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 99d60baf-afd1-4cf1-b40b-3f9803ca1ed6

📥 Commits

Reviewing files that changed from the base of the PR and between df5f7a8 and ac528f0.

📒 Files selected for processing (2)
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/GhosttyConfigDiscovery.swift
  • Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/GhosttyConfigDiscoveryTests.swift

@ammachado

Copy link
Copy Markdown
Contributor Author

Follow-up: fact-checking this fix against Ghostty's actual fallback behavior

I went back and verified the root cause and the mitigation against Ghostty's source (src/font/discovery.zig, src/font/CodepointResolver.zig) and with live CoreText calls, and want to flag a real gap before this lands.

The root-cause mechanism is real, and it's inherited from Ghostty itself

CodepointResolver.getIndex (https://github.com/manaflow-ai/cmux/blob/symbol-font-fallback-coralline-2b21/ghostty/src/font/CodepointResolver.zig#L169-L219) falls back to Discover.discoverFallback for any codepoint not covered by a loaded face. On macOS (CoreText.discoverFallback, https://github.com/manaflow-ai/cmux/blob/symbol-font-fallback-coralline-2b21/ghostty/src/font/discovery.zig#L386-L448) this is a system-wide CTFontCollection charset search across every installed font (no family scoping — see the Descriptor built at CodepointResolver.zig#L175-L181), sorted by sortMatchingDescriptors/Score (discovery.zig#L569-L680). Because Score is a packed struct with fields in increasing precedence, and monospace outranks everything except an exact codepoint match, and every candidate returned by the charset search already matches the codepoint, monospace ends up being the deciding tiebreaker. So any installed monospace font that happens to contain the glyph — even a Nerd Font that only patched in a subset of a Unicode block — can outrank a purpose-built symbol font.

This is unmodified in cmux's fork (only Windows std.fs.Dir → std.Io.Dir differs) — the bug reproduces in vanilla upstream Ghostty too, for anyone whose configured/fallback font set includes a monospace font with partial coverage of the affected range. It's not cmux-specific.

But the mitigation's per-range coverage check is too coarse, and can make Nerd Font users' rendering worse

fontContainsGlyphs / autoInjectedSymbolFontMappings (GhosttyConfigDiscovery.swift#L99-L147, #L377-L388) check coverage per range, not per codepoint — font-codepoint-map only gets injected when all sample scalars for a range are missing from the configured font, and the injected override then applies to the entire range once triggered.

I verified live on a machine with several Nerd Fonts installed (a very plausible stand-in for the coralline reporter, given the issue mentions ▰/▱ gauge glyphs, which are a Nerd/Powerline-adjacent idiom):

== JetBrainsMono Nerd Font (own cmap, no cascade) ==
  U+25A0 -> has=true
  U+25B0 -> has=false
  U+25B1 -> has=false
  U+25CB -> has=true
  U+25CF -> has=true
  U+2B21 -> has=false
  U+2B22 -> has=false

JetBrainsMono Nerd Font has U+25A0/U+25CB/U+25CF in its own cmap (Nerd Fonts patch these in deliberately, monospace-width, visually matched to the rest of the font) but is missing U+25B0/U+25B1. Because the coverage check requires all five sample chars (symbolCoverageSampleCharactersByRange["U+25A0-U+25FF"]) to resolve, this one gap makes cmux inject font-codepoint-map = U+25A0-U+25FF=Apple Symbols for the whole block. Since font-codepoint-map is a hard override checked before Ghostty's normal loaded-font/discovery lookup (CodepointResolver.zig#L132-L137, step 2 in the documented algorithm at #L90-L118), this clobbers the Nerd Font's own U+25A0/U+25CB/U+25CF glyphs — glyphs the user's font already renders correctly and consistently with the rest of their coding font — in favor of Apple Symbols' differently-styled versions.

The "matches a plain CoreText app" justification doesn't hold in this case either

The doc comment on symbolFallbackFont (GhosttyConfigDiscovery.swift#L90-L95) says injecting Apple Symbols "is what CoreText's own cascade resolves to for these ranges." I checked with CTFontCreateForString cascading from a JetBrainsMono Nerd Font base:

base=JetBrainsMono Nerd Font U+25A0 -> cascade resolves to: JetBrainsMonoNF-Regular
base=JetBrainsMono Nerd Font U+25B0 -> cascade resolves to: Menlo-Regular
base=JetBrainsMono Nerd Font U+25B1 -> cascade resolves to: HiraginoSans-W3
base=JetBrainsMono Nerd Font U+25CB -> cascade resolves to: JetBrainsMonoNF-Regular
base=JetBrainsMono Nerd Font U+25CF -> cascade resolves to: JetBrainsMonoNF-Regular
base=JetBrainsMono Nerd Font U+2B21 -> cascade resolves to: AppleSymbols
base=JetBrainsMono Nerd Font U+2B22 -> cascade resolves to: AppleSymbols

Plain CoreText only cascades to Apple Symbols for the codepoints the base font is actually missing (here, the hexagon range). For codepoints the font already has, CoreText keeps using the font's own glyph — it doesn't uniformly redirect the whole block to Apple Symbols. So "matches a plain CoreText app" is only true for the hexagon/arrows range in practice, not for the Geometric Shapes range when a font has partial coverage.

Net effect

For Nerd-Font-using terminal power users — arguably the exact audience most likely to hit gauge/powerline-style glyph bugs like the one in the coralline issue — this PR as currently written can regress rendering for glyphs their font already handles correctly, in the process of fixing the ones it doesn't.

Planned fix

Tighten the coverage check (and the resulting override) to be per-codepoint rather than per-range, so only the specific missing scalars get remapped, and correct the doc comment to reflect that the CoreText-cascade parity claim only holds for genuinely-missing codepoints. Pushing that now.

@cursor

cursor Bot commented Jul 30, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/GhosttyConfigDiscoveryTests.swift (1)

74-81: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the concrete symbol contract, not the production list itself.

The expected set is derived from GhosttyConfigDiscovery.symbolCodepoints, so this test passes even if that production list is incomplete or contains the wrong codepoints. The Set conversion also hides duplicate mappings. Use a literal expected set of required U+... values and assert mappings.count == expected.count.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/GhosttyConfigDiscoveryTests.swift`
around lines 74 - 81, Update symbolFontMappingsCoverAllSymbolCodepoints to use a
literal set of the required U+... codepoint strings instead of deriving expected
from GhosttyConfigDiscovery.symbolCodepoints. Also assert mappings.count equals
expected.count before comparing sets, while preserving the fallback-font
assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/GhosttyConfigDiscoveryTests.swift`:
- Around line 135-145: Replace the system-dependent “Apple Color Emoji” font in
fontContainsGlyphHandlesSupplementaryPlaneCodepoints with a
repository-controlled font fixture whose glyph coverage for 0x1F600 and 0x10FFFE
is validated. If the fixture cannot be resolved or validated, skip the test
rather than asserting against platform-dependent CoreText behavior.

---

Outside diff comments:
In
`@Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/GhosttyConfigDiscoveryTests.swift`:
- Around line 74-81: Update symbolFontMappingsCoverAllSymbolCodepoints to use a
literal set of the required U+... codepoint strings instead of deriving expected
from GhosttyConfigDiscovery.symbolCodepoints. Also assert mappings.count equals
expected.count before comparing sets, while preserving the fallback-font
assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d6f53e99-d0bb-4308-996f-2a44ba9ad52a

📥 Commits

Reviewing files that changed from the base of the PR and between 5070766 and 9bcccf7.

📒 Files selected for processing (3)
  • Packages/macOS/CmuxTerminalCore/Sources/CmuxTerminalCore/ConfigDiscovery/GhosttyConfigDiscovery.swift
  • Packages/macOS/CmuxTerminalCore/Tests/CmuxTerminalCoreTests/GhosttyConfigDiscoveryTests.swift
  • Sources/GhosttyTerminalView.swift

@ammachado
ammachado force-pushed the symbol-font-fallback-coralline-2b21 branch from 55cbe8a to 439dc8e Compare July 31, 2026 04:20
ammachado added a commit to ammachado/cmux that referenced this pull request Jul 31, 2026
When fontProbe.configuredFont(...) returns nil (family name doesn't
resolve), both autoInjectedSymbolFontMappings and the pre-existing
autoInjectedCJKFontMappings previously fell through and returned every
mapping unfiltered, forcing the injected font (Apple Symbols / a CJK
font) over ranges whose actual coverage is unknown. That's not "keeping
Ghostty's behavior" for the uncertain case, it's overriding it on a
guess. Return nil instead so cmux injects nothing and Ghostty's own
discoverFallback() runs unmodified when we can't tell whether an
override is actually needed.

Addresses CodeRabbit review comment on manaflow-ai#9193:
manaflow-ai#9193 (comment)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ammachado added a commit to ammachado/cmux that referenced this pull request Jul 31, 2026
Address two follow-up CodeRabbit findings on manaflow-ai#9193:

- symbolCoverageSampleCharactersByRange only sampled U+25A0/U+25CF for
  the Geometric Shapes range, so a font could pass the coverage check
  while still missing U+25B0/U+25B1/U+25CB (the coralline bar glyphs
  ▰/▱/○ this PR exists to fix) and silently skip the Apple Symbols
  fallback for exactly the characters that need it.
- autoInjectedSymbolFontMappingsFiltersRangesCoveredByConfiguredFont
  used #expect(mappings != nil) followed by a force-unwrap, which traps
  instead of failing the assertion cleanly if it regresses. Switched to
  try #require(...).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ammachado
ammachado force-pushed the symbol-font-fallback-coralline-2b21 branch from 439dc8e to 0602eb7 Compare July 31, 2026 04:23
ammachado added a commit to ammachado/cmux that referenced this pull request Jul 31, 2026
When fontProbe.configuredFont(...) returns nil (family name doesn't
resolve), both autoInjectedSymbolFontMappings and the pre-existing
autoInjectedCJKFontMappings previously fell through and returned every
mapping unfiltered, forcing the injected font (Apple Symbols / a CJK
font) over ranges whose actual coverage is unknown. That's not "keeping
Ghostty's behavior" for the uncertain case, it's overriding it on a
guess. Return nil instead so cmux injects nothing and Ghostty's own
discoverFallback() runs unmodified when we can't tell whether an
override is actually needed.

Addresses CodeRabbit review comment on manaflow-ai#9193:
manaflow-ai#9193 (comment)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ammachado added a commit to ammachado/cmux that referenced this pull request Jul 31, 2026
Address two follow-up CodeRabbit findings on manaflow-ai#9193:

- symbolCoverageSampleCharactersByRange only sampled U+25A0/U+25CF for
  the Geometric Shapes range, so a font could pass the coverage check
  while still missing U+25B0/U+25B1/U+25CB (the coralline bar glyphs
  ▰/▱/○ this PR exists to fix) and silently skip the Apple Symbols
  fallback for exactly the characters that need it.
- autoInjectedSymbolFontMappingsFiltersRangesCoveredByConfiguredFont
  used #expect(mappings != nil) followed by a force-unwrap, which traps
  instead of failing the assertion cleanly if it regresses. Switched to
  try #require(...).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ammachado
ammachado force-pushed the symbol-font-fallback-coralline-2b21 branch from 0602eb7 to 4b08145 Compare August 11, 2026 03:15
ammachado added a commit to ammachado/cmux that referenced this pull request Aug 11, 2026
When fontProbe.configuredFont(...) returns nil (family name doesn't
resolve), both autoInjectedSymbolFontMappings and the pre-existing
autoInjectedCJKFontMappings previously fell through and returned every
mapping unfiltered, forcing the injected font (Apple Symbols / a CJK
font) over ranges whose actual coverage is unknown. That's not "keeping
Ghostty's behavior" for the uncertain case, it's overriding it on a
guess. Return nil instead so cmux injects nothing and Ghostty's own
discoverFallback() runs unmodified when we can't tell whether an
override is actually needed.

Addresses CodeRabbit review comment on manaflow-ai#9193:
manaflow-ai#9193 (comment)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ammachado added a commit to ammachado/cmux that referenced this pull request Aug 11, 2026
Address two follow-up CodeRabbit findings on manaflow-ai#9193:

- symbolCoverageSampleCharactersByRange only sampled U+25A0/U+25CF for
  the Geometric Shapes range, so a font could pass the coverage check
  while still missing U+25B0/U+25B1/U+25CB (the coralline bar glyphs
  ▰/▱/○ this PR exists to fix) and silently skip the Apple Symbols
  fallback for exactly the characters that need it.
- autoInjectedSymbolFontMappingsFiltersRangesCoveredByConfiguredFont
  used #expect(mappings != nil) followed by a force-unwrap, which traps
  instead of failing the assertion cleanly if it regresses. Switched to
  try #require(...).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4b08145. Configure here.

ammachado added a commit to ammachado/cmux that referenced this pull request Aug 11, 2026
With no `font-family` configured, `autoInjectedSymbolFontMappings` returns
every managed codepoint without probing anything. Ghostty's primary face in
that case is its embedded JetBrains Mono, and a `font-codepoint-map` entry
outranks that face, so cmux pushes U+25A0/U+25CB/U+25CF onto Apple Symbols
even though the default face renders them.

This commit adds the test only, so CI shows it red before the fix lands.

Reported by Cursor Bugbot on manaflow-ai#9193.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ammachado ammachado closed this Aug 13, 2026
@ammachado
ammachado force-pushed the symbol-font-fallback-coralline-2b21 branch from 7c77e24 to ccbf7e5 Compare August 13, 2026 02:36
ammachado added a commit to ammachado/cmux that referenced this pull request Aug 13, 2026
When fontProbe.configuredFont(...) returns nil (family name doesn't
resolve), both autoInjectedSymbolFontMappings and the pre-existing
autoInjectedCJKFontMappings previously fell through and returned every
mapping unfiltered, forcing the injected font (Apple Symbols / a CJK
font) over ranges whose actual coverage is unknown. That's not "keeping
Ghostty's behavior" for the uncertain case, it's overriding it on a
guess. Return nil instead so cmux injects nothing and Ghostty's own
discoverFallback() runs unmodified when we can't tell whether an
override is actually needed.

Addresses CodeRabbit review comment on manaflow-ai#9193:
manaflow-ai#9193 (comment)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ammachado added a commit to ammachado/cmux that referenced this pull request Aug 13, 2026
Address two follow-up CodeRabbit findings on manaflow-ai#9193:

- symbolCoverageSampleCharactersByRange only sampled U+25A0/U+25CF for
  the Geometric Shapes range, so a font could pass the coverage check
  while still missing U+25B0/U+25B1/U+25CB (the coralline bar glyphs
  ▰/▱/○ this PR exists to fix) and silently skip the Apple Symbols
  fallback for exactly the characters that need it.
- autoInjectedSymbolFontMappingsFiltersRangesCoveredByConfiguredFont
  used #expect(mappings != nil) followed by a force-unwrap, which traps
  instead of failing the assertion cleanly if it regresses. Switched to
  try #require(...).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ammachado added a commit to ammachado/cmux that referenced this pull request Aug 13, 2026
With no `font-family` configured, `autoInjectedSymbolFontMappings` returns
every managed codepoint without probing anything. Ghostty's primary face in
that case is its embedded JetBrains Mono, and a `font-codepoint-map` entry
outranks that face, so cmux pushes U+25A0/U+25CB/U+25CF onto Apple Symbols
even though the default face renders them.

This commit adds the test only, so CI shows it red before the fix lands.

Reported by Cursor Bugbot on manaflow-ai#9193.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ammachado ammachado reopened this Aug 13, 2026
ammachado added a commit to ammachado/cmux that referenced this pull request Sep 1, 2026
When fontProbe.configuredFont(...) returns nil (family name doesn't
resolve), both autoInjectedSymbolFontMappings and the pre-existing
autoInjectedCJKFontMappings previously fell through and returned every
mapping unfiltered, forcing the injected font (Apple Symbols / a CJK
font) over ranges whose actual coverage is unknown. That's not "keeping
Ghostty's behavior" for the uncertain case, it's overriding it on a
guess. Return nil instead so cmux injects nothing and Ghostty's own
discoverFallback() runs unmodified when we can't tell whether an
override is actually needed.

Addresses CodeRabbit review comment on manaflow-ai#9193:
manaflow-ai#9193 (comment)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ammachado added a commit to ammachado/cmux that referenced this pull request Sep 1, 2026
Address two follow-up CodeRabbit findings on manaflow-ai#9193:

- symbolCoverageSampleCharactersByRange only sampled U+25A0/U+25CF for
  the Geometric Shapes range, so a font could pass the coverage check
  while still missing U+25B0/U+25B1/U+25CB (the coralline bar glyphs
  ▰/▱/○ this PR exists to fix) and silently skip the Apple Symbols
  fallback for exactly the characters that need it.
- autoInjectedSymbolFontMappingsFiltersRangesCoveredByConfiguredFont
  used #expect(mappings != nil) followed by a force-unwrap, which traps
  instead of failing the assertion cleanly if it regresses. Switched to
  try #require(...).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ammachado added a commit to ammachado/cmux that referenced this pull request Sep 1, 2026
With no `font-family` configured, `autoInjectedSymbolFontMappings` returns
every managed codepoint without probing anything. Ghostty's primary face in
that case is its embedded JetBrains Mono, and a `font-codepoint-map` entry
outranks that face, so cmux pushes U+25A0/U+25CB/U+25CF onto Apple Symbols
even though the default face renders them.

This commit adds the test only, so CI shows it red before the fix lands.

Reported by Cursor Bugbot on manaflow-ai#9193.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ammachado
ammachado force-pushed the symbol-font-fallback-coralline-2b21 branch from 58d9097 to 6848d64 Compare September 1, 2026 03:44
@github-actions

github-actions Bot commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 4 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread Resources/Localizable.xcstrings Outdated
Comment thread Sources/GhosttyTerminalView.swift
@ammachado

Copy link
Copy Markdown
Contributor Author

I have read the CLA Document and I hereby sign the CLA

@ammachado
ammachado force-pushed the symbol-font-fallback-coralline-2b21 branch from a5b2ef0 to f7e9bc7 Compare September 6, 2026 16:53
ammachado added a commit to ammachado/cmux that referenced this pull request Sep 6, 2026
When fontProbe.configuredFont(...) returns nil (family name doesn't
resolve), both autoInjectedSymbolFontMappings and the pre-existing
autoInjectedCJKFontMappings previously fell through and returned every
mapping unfiltered, forcing the injected font (Apple Symbols / a CJK
font) over ranges whose actual coverage is unknown. That's not "keeping
Ghostty's behavior" for the uncertain case, it's overriding it on a
guess. Return nil instead so cmux injects nothing and Ghostty's own
discoverFallback() runs unmodified when we can't tell whether an
override is actually needed.

Addresses CodeRabbit review comment on manaflow-ai#9193:
manaflow-ai#9193 (comment)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ammachado added a commit to ammachado/cmux that referenced this pull request Sep 6, 2026
Address two follow-up CodeRabbit findings on manaflow-ai#9193:

- symbolCoverageSampleCharactersByRange only sampled U+25A0/U+25CF for
  the Geometric Shapes range, so a font could pass the coverage check
  while still missing U+25B0/U+25B1/U+25CB (the coralline bar glyphs
  ▰/▱/○ this PR exists to fix) and silently skip the Apple Symbols
  fallback for exactly the characters that need it.
- autoInjectedSymbolFontMappingsFiltersRangesCoveredByConfiguredFont
  used #expect(mappings != nil) followed by a force-unwrap, which traps
  instead of failing the assertion cleanly if it regresses. Switched to
  try #require(...).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ammachado

Copy link
Copy Markdown
Contributor Author

recheck

ammachado and others added 10 commits September 16, 2026 20:02
Ghostty's CTFontCollection scoring in discoverFallback() is
monospace-biased, so a codepoint outside the configured font can land on
whatever installed "monospace" font happens to also claim coverage instead
of the narrower substitute CoreText's own CTFontCreateForString cascade
would pick (see manaflow-ai/ghostty's discovery.zig, and the upstream
analysis at Nanako0129/coralline#47, which
traced this exact scoring bug through the Geometric Shapes and
Miscellaneous Symbols and Arrows ranges: the hexagon glyph U+2B21/U+2B22
and gauge characters like ▰/▱ used by status-line tools).

cmux already works around this class of bug for CJK ranges
(loadCJKFontFallbackIfNeeded, from manaflow-ai#1017). This extends the same
font-codepoint-map injection mechanism to the symbol ranges implicated by
the coralline report, pointing them at Apple Symbols (the font CoreText's
own cascade resolves to), so Ghostty's fallback choice is predictable
instead of landing on an arbitrary wide "monospace" font. Like the CJK
path, this only injects when the user hasn't already configured
font-codepoint-map, an explicit multi-font fallback chain, or a primary
font that already covers the range.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
When fontProbe.configuredFont(...) returns nil (family name doesn't
resolve), both autoInjectedSymbolFontMappings and the pre-existing
autoInjectedCJKFontMappings previously fell through and returned every
mapping unfiltered, forcing the injected font (Apple Symbols / a CJK
font) over ranges whose actual coverage is unknown. That's not "keeping
Ghostty's behavior" for the uncertain case, it's overriding it on a
guess. Return nil instead so cmux injects nothing and Ghostty's own
discoverFallback() runs unmodified when we can't tell whether an
override is actually needed.

Addresses CodeRabbit review comment on manaflow-ai#9193:
manaflow-ai#9193 (comment)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Address two follow-up CodeRabbit findings on manaflow-ai#9193:

- symbolCoverageSampleCharactersByRange only sampled U+25A0/U+25CF for
  the Geometric Shapes range, so a font could pass the coverage check
  while still missing U+25B0/U+25B1/U+25CB (the coralline bar glyphs
  ▰/▱/○ this PR exists to fix) and silently skip the Apple Symbols
  fallback for exactly the characters that need it.
- autoInjectedSymbolFontMappingsFiltersRangesCoveredByConfiguredFont
  used #expect(mappings != nil) followed by a force-unwrap, which traps
  instead of failing the assertion cleanly if it regresses. Switched to
  try #require(...).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fonts (Nerd Fonts especially) often patch in some but not all glyphs
in a Unicode block. The previous all-or-nothing per-range coverage
check meant a single missing glyph (e.g. U+25B0 in JetBrainsMono Nerd
Font) forced the whole Geometric Shapes block onto Apple Symbols,
clobbering glyphs the configured font already rendered correctly.
Verified against Ghostty's discovery.zig/CodepointResolver.zig and
live CoreText coverage checks; see PR discussion for details.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- fontContainsGlyph mishandled codepoints above U+FFFF (single UniChar
  truncation), which would silently force-override any future non-BMP
  symbol codepoint regardless of actual font coverage. Encode a proper
  UTF-16 surrogate pair instead, with a regression test.
- loadCJKFontFallbackIfNeeded and loadSymbolFontFallbackIfNeeded each
  independently re-scanned and re-parsed the same config files on every
  config load. Resolve the scan paths once and share them.
- Flatten symbolCodepointsByRange to symbolCodepoints: the range keys
  were never read (coverage is per-codepoint, not per-range) and implied
  broader coverage than the hardcoded list actually provides.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… font

- symbolFontMappingsCoverAllSymbolCodepoints now asserts against a literal
  expected codepoint set (plus a count check) instead of deriving it from
  the production symbolCodepoints list, so the test can catch a wrong or
  incomplete production list.
- fontContainsGlyphHandlesSupplementaryPlaneCodepoints now guards that
  "Apple Color Emoji" actually resolved before asserting on its glyph
  coverage, since it's a system font rather than a repo-controlled fixture.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
With no `font-family` configured, `autoInjectedSymbolFontMappings` returns
every managed codepoint without probing anything. Ghostty's primary face in
that case is its embedded JetBrains Mono, and a `font-codepoint-map` entry
outranks that face, so cmux pushes U+25A0/U+25CB/U+25CF onto Apple Symbols
even though the default face renders them.

This commit adds the test only, so CI shows it red before the fix lands.

Reported by Cursor Bugbot on manaflow-ai#9193.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Only inject Apple Symbols for the managed codepoints Ghostty's built-in
primary face actually lacks (▰ ▱ ⬡ ⬢), instead of all seven. The default
face is embedded in Ghostty's binary rather than installed system-wide, so
it cannot be resolved by family name through GhosttyFontProbing; its
coverage is recorded in `defaultFaceCoveredSymbolCodepoints`.

`defaultFaceCoveredSymbolCodepointsMatchGhosttysEmbeddedFont` probes the
vendored ghostty/src/font/res/JetBrainsMonoNoNF-Regular.ttf via CoreText, so
the table fails the build if the submodule moves to a default face with
different coverage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
origin/main's journal-events commit (26614b0) added the
.journalAppend case to AgentHookFailureStage but never updated this
switch, leaving it non-exhaustive and the whole target unbuildable.
Unrelated to shell completion; fixed here because it blocked rebasing
this branch onto origin/main.
…oader

- Resources/Localizable.xcstrings: the branch added a second
  `cli.agentHook.error.journalAppend` entry, but origin/main already
  carries that key. An .xcstrings catalog is a dictionary, so the two
  blocks collapse to one on parse and the added translations were dead
  weight. Restore the file to main's version; the CLI switch case that
  consumes the key is already on main, so nothing here was needed.

- GhosttyConfigDiscoveryTests: fontContainsGlyphHandlesSupplementaryPlane
  Codepoints guarded on the resolved family name and returned early,
  so it passed as a silent no-op wherever CoreText substituted another
  font for "Apple Color Emoji". Require the exact family instead, so a
  substitution fails loudly rather than faking coverage validation.

- GhosttyTerminalView: loadSymbolFontFallbackIfNeeded was a near-verbatim
  copy of loadCJKFontFallbackIfNeeded. Extract the shared directive
  builder into loadInjectedFontCodepointMap so the two paths can't drift
  apart when the injection format changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
teamleaderleo and others added 2 commits September 30, 2026 07:46
Co-authored-by: Adriano Machado <60320+ammachado@users.noreply.github.com>
@teamleaderleo

Copy link
Copy Markdown
Collaborator

Review: symbol mappings and per-codepoint coverage look correct; the submodule-free package-test blocker was verified. Fixed: the optional Ghostty font drift check now skips cleanly when the fixture is unavailable (5f15a9c). Left: macOS tests were not runnable on this Linux lane; CI will validate them.

@github-actions

Copy link
Copy Markdown
Contributor

CI failure attribution

CI failed on 5f15a9cd53 (run 36734193355 attempt 2): 1 code.

Job Verdict Why
macos / app-host unit tests (changed suites) code a test failed
Matched log lines
macos / app-host unit tests (changed suites): /tmp/cmux-ci/src/cmuxTests/GhosttyConfigTests.swift:3639: error: -[cmuxTests.GhosttyMouseFocusTests testShouldInjectCJKFontFallbackAllowsSingleFontWithoutExplicitOverrides] : XCTAssertTrue failed

Not re-run automatically: macos / app-host unit tests (changed suites) is not a machine failure.

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's.

@teamleaderleo

Copy link
Copy Markdown
Collaborator

Taking this: checking symbol font fallback against current main, preserving explicit font choices, and refreshing CI.

  • OrchardSpoon g1 🌀

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Merge receipt for 4049bca267, merged 2026-10-02 07:59:25 UTC

  • Not verified at merge: ci-status (not reported)
  • Verified: Web complexity
  • Full suite: runs on main after merge.

Labeled merged-unverified: if main breaks near this merge, look here first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: appearance Themes, light and dark mode, window chrome area: terminal Ghostty surface, rendering, scrollback, escape sequences, fonts merged-unverified A judging check was not green at merge; see the merge receipt comment S3: minor Wrong behavior with a workaround

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants