Skip to content

Fix #1793: skip CJK fallback injection when font-family already covers glyphs - #2241

Merged
austinywang merged 1 commit into
mainfrom
issue-1793-cjk-font-override
Mar 28, 2026
Merged

austinywang merged 1 commit into
mainfrom
issue-1793-cjk-font-override

Conversation

@austinywang

@austinywang austinywang commented Mar 27, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1793.

Summary

  • probe the configured single font-family with CoreText before auto-injecting CJK font-codepoint-map entries
  • skip only the mapped ranges the configured font already covers, while still injecting fallbacks for uncovered ranges
  • preserve the existing behavior for explicit font-codepoint-map settings and multi-font fallback chains
  • add deterministic regression coverage for the range-filtering logic, including Hangul coverage samples to stay aligned with the Korean fallback mapping work from Add Korean font mapping to CJK font fallback system #2037

Verification

  • did not run local tests per repo policy
  • built and launched the tagged dev app with CMUX_SKIP_ZIG_BUILD=1 ./scripts/reload.sh --tag cjk-font-override --launch because the plain reload.sh path currently fails on this machine in an unrelated Ghostty helper link step

Summary by CodeRabbit

  • Improvements

    • Smarter CJK (Chinese, Japanese, Korean) font fallback: automatic fallback injection now skips ranges already covered by the configured primary font, reducing unnecessary font fallbacks and improving text rendering.
    • Added configurable coverage probing to allow alternative glyph-coverage checks when deciding injection.
  • Tests

    • New tests validating auto-injection decisions and coverage-based fallback behavior.

@vercel

vercel Bot commented Mar 27, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Mar 27, 2026 7:18am

@coderabbitai

coderabbitai Bot commented Mar 27, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Reworks CJK font-fallback auto-injection by adding CoreText-based glyph-coverage probing, introducing autoInjectedCJKFontMappings() to skip or filter injections when the configured primary font already contains CJK glyphs, and extending shouldInjectCJKFontFallback() to accept an optional coverage probe. Adds tests for the new behavior.

Changes

Cohort / File(s) Summary
CJK font fallback logic
Sources/GhosttyTerminalView.swift
Added import CoreText. Introduced autoInjectedCJKFontMappings(...), representative CJK sample scalars, CoreText helpers (configuredCTFont, fontContainsGlyphs, normalizedFontName), and changed shouldInjectCJKFontFallback(...) to accept an optional rangeCoverageProbe to allow probe-based filtering of injected mappings.
Unit tests for auto-injection
cmuxTests/GhosttyConfigTests.swift
Added four XCTest cases validating autoInjectedCJKFontMappings and shouldInjectCJKFontFallback behavior using temporary configs and a rangeCoverageProbe callback to simulate glyph coverage outcomes.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant GhosttyTerminalView
    participant Config as Config System
    participant CoreText

    User->>GhosttyTerminalView: Launch terminal (has `font-family`)
    GhosttyTerminalView->>Config: Read font config
    Config-->>GhosttyTerminalView: Return font settings
    GhosttyTerminalView->>GhosttyTerminalView: shouldInjectCJKFontFallback(rangeCoverageProbe?)
    alt Explicit `font-codepoint-map` or multi-entry `font-family`
        GhosttyTerminalView-->>User: Skip auto-injection
    else Use coverage probe / CoreText
        GhosttyTerminalView->>CoreText: configuredCTFont(primary font)
        CoreText-->>GhosttyTerminalView: CTFont reference
        loop for each CJK range
            GhosttyTerminalView->>GhosttyTerminalView: fontContainsGlyphs(CTFont, range) or probe called
            GhosttyTerminalView->>CoreText: CTFontGetGlyphsForCharacters (if used)
            CoreText-->>GhosttyTerminalView: glyph availability
        end
        alt Primary font covers all ranges
            GhosttyTerminalView-->>User: Do not inject mappings
        else
            GhosttyTerminalView-->>User: Inject filtered `font-codepoint-map` for uncovered ranges
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I checked each glyph with a twitch and a grin,
If your font already sings CJK, let it win.
No surprise swaps, no foreign face—
Primary font keeps its rightful place.
Hooray for matching glyphs and rabbit-approved grace! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: skipping CJK fallback injection when the configured font-family already covers glyphs, directly addressing issue #1793.
Description check ✅ Passed The PR description covers the summary, verification approach, and includes issue closure. However, it lacks a dedicated Testing section with local test verification and does not include the requested Review Trigger and Checklist.
Linked Issues check ✅ Passed The changes successfully implement all key requirements from #1793: detecting CJK glyph coverage in configured fonts via CoreText probing, skipping injection only for covered ranges, preserving behavior for explicit font-codepoint-map settings, and adding regression tests with Hangul coverage samples.
Out of Scope Changes check ✅ Passed All changes are focused on the CJK font-codepoint-map auto-injection logic and related tests. No out-of-scope modifications detected outside the stated objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-1793-cjk-font-override

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 and usage tips.

@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.

No issues found across 2 files

@greptile-apps

greptile-apps Bot commented Mar 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes #1793 by teaching cmux's CJK font-fallback injection to first probe the user's configured font-family via CoreText before auto-injecting font-codepoint-map entries, skipping only the Unicode ranges the configured font already covers. It also bundles a refactor of TerminalSurface.configTemplate from the CmuxSurfaceConfigTemplate Swift wrapper to the raw ghostty_surface_config_s C struct.\n\nKey changes:\n- autoInjectedCJKFontMappings() — new function that centralises the guard logic (explicit codepoint map, multi-font fallback chain) and then filters out ranges whose glyphs are already present in the configured font via CTFontGetGlyphsForCharacters.\n- configuredCTFont(named:) — validates that CoreText actually resolved the requested name before returning the font, preventing false-positive coverage verdicts from CoreText's silent fallback substitution.\n- fontContainsGlyphs(_:forRange:) — probes a representative sample of codepoints per range using CTFontGetGlyphsForCharacters; conservatively returns false (keep fallback) when the range has no sample or the font can't be resolved.\n- rangeCoverageProbe injection point — lets tests substitute deterministic coverage answers without real font lookup, enabling fast, hermetic unit tests.\n- TerminalSurface.configTemplate refactor — replaces the intermediate CmuxSurfaceConfigTemplate struct with ghostty_surface_config_s directly, consolidating env-var reading and command/working-directory wiring at the callsite.\n- CLAUDE.md two-commit policy: The regression tests added here (testAutoInjectedCJKFontMappings*, testShouldInjectCJKFontFallbackSkips*) are committed alongside the fix rather than in a prior failing commit. Per CLAUDE.md, regression tests should be added first (causing CI to go red) and the fix added in a second commit so the PR history proves the test genuinely detects the bug.

Confidence Score: 4/5

Safe to merge after addressing the two-commit policy and the minor redundant guard; core CJK probe logic and tests are correct.

The CJK coverage-probe design is sound — the injectable probe, conservative fallback when the font can't be resolved, and per-range sample approach are all correct. The bundled configTemplate refactor looks correct. The only actionable items are a trivial redundant check and the CLAUDE.md commit-ordering policy (not a runtime correctness issue).

Sources/GhosttyTerminalView.swift — redundant inner count guard (line 3607) and the Hangul dead-code entries warrant attention before merge.

Important Files Changed

Filename Overview
Sources/GhosttyTerminalView.swift Adds CoreText glyph-coverage probe to skip CJK fallback injection for already-covered ranges; also refactors TerminalSurface.configTemplate from CmuxSurfaceConfigTemplate to raw ghostty_surface_config_s with a minor redundant count check.
cmuxTests/GhosttyConfigTests.swift Adds deterministic regression tests for autoInjectedCJKFontMappings (all-covered and partially-covered paths) and shouldInjectCJKFontFallback using an injectable rangeCoverageProbe; test logic is sound.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[loadCJKFontFallbackIfNeeded] --> B[autoInjectedCJKFontMappings]
    B --> C{cjkFontMappings returns nil?}
    C -- yes --> Z[return nil — no injection]
    C -- no --> D{containsCodepointMap OR\nhasExplicitFallbackChain?}
    D -- yes --> Z
    D -- no --> E{effectiveFontFamilies.first?}
    E -- nil --> F[return all mappings]
    E -- found --> G{rangeCoverageProbe set?}
    G -- yes --> H[probe each range via closure]
    G -- no --> I[configuredCTFont named:]
    I --> J{font resolved?}
    J -- no --> F
    J -- yes --> K[fontContainsGlyphs per range\nvia CTFontGetGlyphsForCharacters]
    H --> L[removeAll covered ranges]
    K --> L
    L --> M{mappings empty?}
    M -- yes --> Z
    M -- no --> N[return filtered mappings\n→ inject partial fallback]
Loading

Reviews (1): Last reviewed commit: "fix: honor CJK-capable font-family befor..." | Re-trigger Greptile

Comment thread Sources/GhosttyTerminalView.swift Outdated
Comment on lines +3606 to +3607
let count = Int(surfaceConfig.env_var_count)
if count > 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Redundant inner count > 0 guard

The outer condition surfaceConfig.env_var_count > 0 already establishes that env_var_count is positive, so converting to Int and re-checking count > 0 on line 3607 is unreachable as false. It can be removed to reduce nesting and make intent clearer.

Suggested change
let count = Int(surfaceConfig.env_var_count)
if count > 0 {
var env: [String: String] = [:]
if surfaceConfig.env_var_count > 0, let existingEnv = surfaceConfig.env_vars {
let count = Int(surfaceConfig.env_var_count)
for i in 0..<count {

Comment on lines +1407 to +1420
/// Representative scalars used to detect whether the configured primary
/// font already covers the ranges cmux would otherwise auto-map.
private static let cjkCoverageSampleCharactersByRange: [String: [UniChar]] = [
"U+3000-U+303F": [0x3001, 0x300C],
"U+4E00-U+9FFF": [0x4E00, 0x65E5, 0x6C34],
"U+F900-U+FAFF": [0xF900],
"U+FF00-U+FFEF": [0xFF10, 0xFF21],
"U+3400-U+4DBF": [0x3400],
"U+1100-U+11FF": [0x1100, 0x1161],
"U+3130-U+318F": [0x3131, 0x314F],
"U+3040-U+309F": [0x3042, 0x3093],
"U+30A0-U+30FF": [0x30A2, 0x30F3],
"U+AC00-U+D7AF": [0xAC00, 0xD55C],
]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Hangul sample entries are currently unreachable dead code

cjkCoverageSampleCharactersByRange includes three Hangul ranges (U+1100-U+11FF, U+3130-U+318F, U+AC00-U+D7AF) that fontContainsGlyphs would look up when filtering. However, cjkFontMappings has no ko/Korean language branch, so those ranges are never present in the mappings array that drives removeAll. The fontContainsGlyphs call will never be invoked with any of those range keys.

The PR description notes these entries are intentionally forward-looking for #2037 Korean support — a comment in the dictionary explaining this would help future readers understand why entries exist that no current code path exercises.

Suggested change
/// Representative scalars used to detect whether the configured primary
/// font already covers the ranges cmux would otherwise auto-map.
private static let cjkCoverageSampleCharactersByRange: [String: [UniChar]] = [
"U+3000-U+303F": [0x3001, 0x300C],
"U+4E00-U+9FFF": [0x4E00, 0x65E5, 0x6C34],
"U+F900-U+FAFF": [0xF900],
"U+FF00-U+FFEF": [0xFF10, 0xFF21],
"U+3400-U+4DBF": [0x3400],
"U+1100-U+11FF": [0x1100, 0x1161],
"U+3130-U+318F": [0x3131, 0x314F],
"U+3040-U+309F": [0x3042, 0x3093],
"U+30A0-U+30FF": [0x30A2, 0x30F3],
"U+AC00-U+D7AF": [0xAC00, 0xD55C],
]
/// Representative scalars used to detect whether the configured primary
/// font already covers the ranges cmux would otherwise auto-map.
/// Hangul ranges (U+1100, U+3130, U+AC00) are included ahead of Korean
/// language support (see #2037) so the probe table stays in sync when that
/// work lands.
private static let cjkCoverageSampleCharactersByRange: [String: [UniChar]] = [

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sources/GhosttyTerminalView.swift`:
- Around line 1407-1420: The new Hangul probe entries in
cjkCoverageSampleCharactersByRange are never checked because
autoInjectedCJKFontMappings() only filters ranges emitted by cjkFontMappings(),
and cjkFontMappings() currently only yields JP/CN ranges; update
cjkFontMappings() to also emit the Korean ranges ("U+1100-U+11FF",
"U+3130-U+318F", "U+AC00-U+D7AF") when Korean locale is detected (or include
them unconditionally if that's intended), or alternatively change
autoInjectedCJKFontMappings() to consult cjkCoverageSampleCharactersByRange
directly rather than relying solely on cjkFontMappings(); reference the
functions cjkFontMappings(), autoInjectedCJKFontMappings(), and the constant
cjkCoverageSampleCharactersByRange to locate where to add those ranges and
adjust the filtering logic accordingly.
- Around line 1517-1524: The current logic uses fontContainsGlyphs to probe a
few code points and then removes the entire fallback range from mappings, which
incorrectly suppresses fallback for partially-covered Unicode blocks; replace
this with a full-range coverage check (e.g., implement and call a
fontFullyContains(range: RangeType, font: CTFont) that verifies every code
point/glyph in the range is present) and use that result in the
mappings.removeAll closures instead of fontContainsGlyphs; apply the same
replacement where similar logic appears (the rangeCoverageProbe branch and the
branch using configuredCTFont(named:) — references: rangeCoverageProbe,
configuredCTFont(named:), fontContainsGlyphs, and the mappings.removeAll
closures).
- Around line 2965-2966: The stored type change of configTemplate to
ghostty_surface_config_s? breaks callers that still construct
TerminalSurface(configTemplate:) with CmuxSurfaceConfigTemplate? and loses extra
metadata (fontFamily, fontFeatures); revert the stored/initializer type back to
CmuxSurfaceConfigTemplate? (or make TerminalSurface initializer
generic/overloaded to accept CmuxSurfaceConfigTemplate and extract its .cConfig
into ghostty_surface_config_s while preserving metadata fields), updating the
TerminalSurface initializer and the stored property configTemplate so callers
using CmuxSurfaceConfigTemplate continue to compile and metadata is retained.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9af789be-e859-4e80-bd90-4c480a6d7fed

📥 Commits

Reviewing files that changed from the base of the PR and between 1f4fc47 and e640b22.

📒 Files selected for processing (2)
  • Sources/GhosttyTerminalView.swift
  • cmuxTests/GhosttyConfigTests.swift

Comment thread Sources/GhosttyTerminalView.swift
Comment on lines +1517 to +1524
if let rangeCoverageProbe {
mappings.removeAll { range, _ in
rangeCoverageProbe(configuredFontFamily, range)
}
} else if let configuredFont = configuredCTFont(named: configuredFontFamily) {
mappings.removeAll { range, _ in
fontContainsGlyphs(configuredFont, forRange: range)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

A few probe glyphs are not enough to suppress an entire Unicode block.

fontContainsGlyphs only proves that a small sample exists, but Lines 1518-1524 remove the whole fallback range when that returns true. Partial-coverage fonts will therefore skip the injected fallback and still render missing glyphs for unsupported code points inside the same block.

Also applies to: 1579-1589

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/GhosttyTerminalView.swift` around lines 1517 - 1524, The current
logic uses fontContainsGlyphs to probe a few code points and then removes the
entire fallback range from mappings, which incorrectly suppresses fallback for
partially-covered Unicode blocks; replace this with a full-range coverage check
(e.g., implement and call a fontFullyContains(range: RangeType, font: CTFont)
that verifies every code point/glyph in the range is present) and use that
result in the mappings.removeAll closures instead of fontContainsGlyphs; apply
the same replacement where similar logic appears (the rangeCoverageProbe branch
and the branch using configuredCTFont(named:) — references: rangeCoverageProbe,
configuredCTFont(named:), fontContainsGlyphs, and the mappings.removeAll
closures).

Comment thread Sources/GhosttyTerminalView.swift Outdated
Comment on lines 2965 to 2966
private let configTemplate: ghostty_surface_config_s?
private let workingDirectory: String?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

This configTemplate type change is a compile blocker.

Sources/Workspace.swift:5701-5757 still constructs TerminalSurface(configTemplate:) with CmuxSurfaceConfigTemplate?, so switching the stored/initializer type here to ghostty_surface_config_s? stops that call site from type-checking. Even if callers are changed to pass only .cConfig, that also drops the extra template metadata (fontFamily, fontFeatures) still carried by CmuxSurfaceConfigTemplate.

Also applies to: 3050-3054

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/GhosttyTerminalView.swift` around lines 2965 - 2966, The stored type
change of configTemplate to ghostty_surface_config_s? breaks callers that still
construct TerminalSurface(configTemplate:) with CmuxSurfaceConfigTemplate? and
loses extra metadata (fontFamily, fontFeatures); revert the stored/initializer
type back to CmuxSurfaceConfigTemplate? (or make TerminalSurface initializer
generic/overloaded to accept CmuxSurfaceConfigTemplate and extract its .cConfig
into ghostty_surface_config_s while preserving metadata fields), updating the
TerminalSurface initializer and the stored property configTemplate so callers
using CmuxSurfaceConfigTemplate continue to compile and metadata is retained.

@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

♻️ Duplicate comments (2)
Sources/GhosttyTerminalView.swift (2)

1517-1524: ⚠️ Potential issue | 🟠 Major

Sample glyph hits are too weak to drop an entire block.

Lines 1517-1524 remove a whole fallback range when fontContainsGlyphs succeeds, but Lines 1579-1589 only verify a few representative scalars. A partially covered font can therefore lose the injected fallback for missing code points inside that block. This needs a full-range coverage check, or a more conservative suppression rule that only removes ranges you can fully verify.

Also applies to: 1579-1589

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/GhosttyTerminalView.swift` around lines 1517 - 1524, The current
logic removes entire fallback ranges when fontContainsGlyphs reports success but
that function only checks a few representative scalars; instead ensure you only
drop a mapping when you can verify full-range coverage: modify the removal
branch that uses configuredCTFont(named:) and fontContainsGlyphs (and the
alternative rangeCoverageProbe path) so it either (a) calls a new or extended
fontContainsGlyphs that iterates every scalar in the given Range and returns
true only if all code points are present, or (b) changes the removeAll predicate
to be conservative and only remove a range when a full-rangeCoverageProbe
verifies every code point; update both the mappings.removeAll closures (the
branch using rangeCoverageProbe and the branch using configuredCTFont(named:))
so they use the full-range verification before removing the fallback mapping.

1407-1420: ⚠️ Potential issue | 🟠 Major

Hangul filtering still can't run on the real Korean path.

Line 1506 still seeds filtering from cjkFontMappings(...), and that generator only emits Japanese/Chinese ranges in this file. The new U+1100-U+11FF, U+3130-U+318F, and U+AC00-U+D7AF samples can be exercised via rangeCoverageProbe, but ko* locales still produce no Hangul mappings to filter, so the D2Coding-style override regression remains. Please emit Korean mappings from the baseline generator, or drive filtering from the sample-map directly.

Also applies to: 1501-1506

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/GhosttyTerminalView.swift` around lines 1407 - 1420, The current CJK
filtering is still seeded from cjkFontMappings(...) which omits Korean ranges,
so update the filtering source to include Hangul: either extend the baseline
generator function cjkFontMappings to emit mappings for Korean ranges
(U+1100-U+11FF, U+3130-U+318F, U+AC00-U+D7AF) using the sample scalars in
cjkCoverageSampleCharactersByRange, or change the filter initialization (the
place that currently calls cjkFontMappings to seed filtering) to drive
rangeCoverageProbe (or equivalent coverage probing) directly from
cjkCoverageSampleCharactersByRange so ko* locales will produce Hangul mappings
to filter (reference symbols: cjkFontMappings,
cjkCoverageSampleCharactersByRange, rangeCoverageProbe).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmuxTests/GhosttyConfigTests.swift`:
- Around line 2152-2161: The test currently calls
GhosttyApp.autoInjectedCJKFontMappings with a rangeCoverageProbe but never
asserts that the probe was invoked; add a small local flag or counter in the
surrounding withTempConfig closure, increment or set it inside the provided
rangeCoverageProbe (for the call to GhosttyApp.autoInjectedCJKFontMappings) and
after the call assert that the flag/counter is >0 so the test fails if
rangeCoverageProbe was never exercised; apply the same change to the other test
using GhosttyApp.autoInjectedCJKFontMappings at the second location.

---

Duplicate comments:
In `@Sources/GhosttyTerminalView.swift`:
- Around line 1517-1524: The current logic removes entire fallback ranges when
fontContainsGlyphs reports success but that function only checks a few
representative scalars; instead ensure you only drop a mapping when you can
verify full-range coverage: modify the removal branch that uses
configuredCTFont(named:) and fontContainsGlyphs (and the alternative
rangeCoverageProbe path) so it either (a) calls a new or extended
fontContainsGlyphs that iterates every scalar in the given Range and returns
true only if all code points are present, or (b) changes the removeAll predicate
to be conservative and only remove a range when a full-rangeCoverageProbe
verifies every code point; update both the mappings.removeAll closures (the
branch using rangeCoverageProbe and the branch using configuredCTFont(named:))
so they use the full-range verification before removing the fallback mapping.
- Around line 1407-1420: The current CJK filtering is still seeded from
cjkFontMappings(...) which omits Korean ranges, so update the filtering source
to include Hangul: either extend the baseline generator function cjkFontMappings
to emit mappings for Korean ranges (U+1100-U+11FF, U+3130-U+318F, U+AC00-U+D7AF)
using the sample scalars in cjkCoverageSampleCharactersByRange, or change the
filter initialization (the place that currently calls cjkFontMappings to seed
filtering) to drive rangeCoverageProbe (or equivalent coverage probing) directly
from cjkCoverageSampleCharactersByRange so ko* locales will produce Hangul
mappings to filter (reference symbols: cjkFontMappings,
cjkCoverageSampleCharactersByRange, rangeCoverageProbe).
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3677e162-931e-48ff-b0d1-9c3c6603473c

📥 Commits

Reviewing files that changed from the base of the PR and between e640b22 and 089bbe0.

📒 Files selected for processing (2)
  • Sources/GhosttyTerminalView.swift
  • cmuxTests/GhosttyConfigTests.swift

Comment on lines +2152 to +2161
try withTempConfig("font-family = Sarasa Mono K\n") { path in
XCTAssertNil(
GhosttyApp.autoInjectedCJKFontMappings(
preferredLanguages: ["zh-Hans-CN"],
configPaths: [path],
rangeCoverageProbe: { fontFamily, range in
XCTAssertEqual(fontFamily, "Sarasa Mono K")
return coveredRanges.contains(range)
}
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Assert that rangeCoverageProbe is actually invoked to avoid false positives.

Both tests can pass even if the probe path is never exercised. Add an explicit invocation flag/counter and assert it after the call so regressions in probe wiring are caught.

Proposed test-hardening diff
     func testAutoInjectedCJKFontMappingsSkipsRangesCoveredByConfiguredPrimaryFont() throws {
         let coveredRanges: Set<String> = [
             "U+3000-U+303F",
             "U+4E00-U+9FFF",
             "U+F900-U+FAFF",
             "U+FF00-U+FFEF",
             "U+3400-U+4DBF",
         ]

         try withTempConfig("font-family = Sarasa Mono K\n") { path in
+            var probeInvoked = false
             XCTAssertNil(
                 GhosttyApp.autoInjectedCJKFontMappings(
                     preferredLanguages: ["zh-Hans-CN"],
                     configPaths: [path],
                     rangeCoverageProbe: { fontFamily, range in
+                        probeInvoked = true
                         XCTAssertEqual(fontFamily, "Sarasa Mono K")
                         return coveredRanges.contains(range)
                     }
                 )
             )
+            XCTAssertTrue(probeInvoked)
         }
     }

     func testShouldInjectCJKFontFallbackSkipsConfiguredFontThatAlreadyCoversMappedRanges() throws {
         let coveredRanges: Set<String> = [
             "U+3000-U+303F",
             "U+4E00-U+9FFF",
             "U+F900-U+FAFF",
             "U+FF00-U+FFEF",
             "U+3400-U+4DBF",
         ]

         try withTempConfig("font-family = Sarasa Mono K\n") { path in
+            var probeInvoked = false
             XCTAssertFalse(
                 GhosttyApp.shouldInjectCJKFontFallback(
                     preferredLanguages: ["zh-Hans-CN"],
                     configPaths: [path],
                     rangeCoverageProbe: { fontFamily, range in
+                        probeInvoked = true
                         XCTAssertEqual(fontFamily, "Sarasa Mono K")
                         return coveredRanges.contains(range)
                     }
                 )
             )
+            XCTAssertTrue(probeInvoked)
         }
     }

Also applies to: 2446-2455

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/GhosttyConfigTests.swift` around lines 2152 - 2161, The test
currently calls GhosttyApp.autoInjectedCJKFontMappings with a rangeCoverageProbe
but never asserts that the probe was invoked; add a small local flag or counter
in the surrounding withTempConfig closure, increment or set it inside the
provided rangeCoverageProbe (for the call to
GhosttyApp.autoInjectedCJKFontMappings) and after the call assert that the
flag/counter is >0 so the test fails if rangeCoverageProbe was never exercised;
apply the same change to the other test using
GhosttyApp.autoInjectedCJKFontMappings at the second location.

This branch was successfully deployed

1 active deployment
Preview — 089bbe07 Deployed Mar 27, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CJK font-codepoint-map auto-injection overrides font-family that already contains CJK glyphs

1 participant