Skip to content

[cmux] add global font magnification across terminals and chrome - #3145

Open
the-gigi wants to merge 5 commits into
manaflow-ai:mainfrom
the-gigi:main
Open

[cmux] add global font magnification across terminals and chrome#3145
the-gigi wants to merge 5 commits into
manaflow-ai:mainfrom
the-gigi:main

Conversation

@the-gigi

@the-gigi the-gigi commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #3147

  • What changed?

Added a global font magnification setting (50–200%, default 100%, 10% steps) that scales every font in cmux: terminals, tab bar, sidebar, workspace titles, and all SwiftUI chrome. Per-pane zoom (Cmd+=/-/0) is unchanged and still overrides the global value for the focused pane.

Note that browser panes are not impacted by auto magnification because this is content we don't control and maybe it doesn't look right with magnification. For now, the user can use the stadnard browser zoom if needed. If magnification should be applied to browser panes too it can be added later in a subsequent PR.

  • Why?

The fixed font size is too small for people with poor vision — workspace names, tab titles, and chrome are hard to read, and zooming each terminal pane individually isn't a viable workaround.

  • Why cap at 200%?

cmux's chrome has fixed-size budgets in places (titlebar height, command palette rows, badge frames, button hit areas). At ≤200% these absorb the scaled labels without clipping. For larger zoom users should reach for macOS Accessibility → Zoom, which scales the whole screen.

Testing

  • How did you test this change?

Built a dev build and verified that the global font magnification works, that pane zoom overrides work, that reset works, and that "Reset All Settings" returns the magnification to 100%.

CMUX_SKIP_ZIG_BUILD=1 ./scripts/reload.sh --tag magnify --launch
  • What did you verify manually?

See above.

Demo Video

For UI or behavior changes, include a short demo video (GitHub upload, Loom, or other direct link).

Review Trigger (Copy/Paste as PR comment)

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

Checklist

  • I tested the change locally
  • I added or updated tests for behavior changes
  • I updated docs/changelog if needed
  • I requested bot reviews after my latest commit (copy/paste block above or equivalent)
  • All code review bot comments are resolved
  • All human review comments are resolved

Summary by CodeRabbit

  • New Features

    • Global Font Magnification: new 50–200% setting (default 100%) that scales fonts across terminals, tab bar, sidebar and UI; changes apply immediately and per-pane zoom shortcuts still work.
    • Settings UI: added control and reset for the global magnification with immediate effect.
  • Style

    • Unified typography across many views for consistent font rendering.
  • Localization

    • Added localized labels/subtitles and reset label for the new setting (en/ja).

@vercel

vercel Bot commented Apr 25, 2026

Copy link
Copy Markdown

@the-gigi is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai

coderabbitai Bot commented Apr 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a global font magnification feature (percent-based, 50–200%) with Ghostty/SwiftUI wiring: introduces cmuxFont view modifiers and GlobalFontMagnification storage/notifications, injects scaled terminal font-size into Ghostty configs, migrates many .font(...) call sites to .cmuxFont(...), and surfaces a Settings control with stepper+reset.

Changes

Localization

Layer / File(s) Summary
Localization keys
Resources/Localizable.xcstrings
Added four settings.app.globalFontMagnification keys (label, subtitleOff, subtitleOn, reset) with en/ja values and manual extraction state.

Core Config & Runtime

Layer / File(s) Summary
Ghostty config scaling & storage
Sources/GhosttyConfig.swift, Sources/GhosttyTerminalView.swift
Added GlobalFontMagnification enum (percent key, clamp, scale, notifications); GhosttyConfig.loadFromDisk applies scale to terminal fontSize and surfaceTabBarFontSize; GhosttyTerminalView injects scaled font-size into ghostty configs before finalize.

App Integration & Observers

Layer / File(s) Summary
App observer and cmuxFont infra
Sources/AppDelegate.swift, Sources/cmuxApp.swift
Installed NotificationCenter observer to invalidate Ghostty config cache and reload configuration on magnification changes; added GlobalFontSizeObserver, CmuxTextStyleMetrics, CmuxFontModifier, and View.cmuxFont(...) overloads to apply AppStorage-based scaling.

Settings UI

Layer / File(s) Summary
Settings control & persistence
Sources/cmuxApp.swift, Sources/KeyboardShortcutSettingsFileStore.swift
Adds @AppStorage(GlobalFontMagnification.percentKey) binding in SettingsView, GlobalFontMagnificationControl stepper/reset row, schema & ingestion support for app.globalFontMagnification, and posts GlobalFontMagnification.didChangeNotification on changes or restores.

UI Font Migration

Layer / File(s) Summary
cmux UI font replacements
Sources/ContentView.swift, Sources/NotificationsPage.swift, Sources/SessionIndexView.swift, Sources/Update/UpdatePopoverView.swift, Sources/Update/UpdateTitlebarAccessory.swift, Sources/Panels/BrowserPanelView.swift, Sources/Panels/MarkdownPanelView.swift, Sources/Find/BrowserSearchOverlay.swift, Sources/Find/SurfaceSearchOverlay.swift, Sources/KeyboardShortcutSettings.swift, Sources/ShortcutHintPill.swift, Sources/RightSidebarPanelView.swift, Sources/Settings/ConfigSettingsView.swift, Sources/WorkspaceContentView.swift
Replaced many .font(...) usages with .cmuxFont(...) equivalents (preserving size/weight/monospaced flags) so those views honor global magnification.

Schema

Layer / File(s) Summary
Settings schema
web/data/cmux-settings.schema.json
Added app.globalFontMagnification integer property constrained to 50–200 with default 100 and documentation describing global scaling semantics and interaction with per-pane zoom.

Sequence Diagram

sequenceDiagram
    participant User as "User"
    participant Settings as "Settings View"
    participant Storage as "UserDefaults"
    participant Notification as "NotificationCenter"
    participant AppDelegate as "AppDelegate"
    participant GhosttyConfig as "GhosttyConfig"
    participant Terminal as "Terminal View"

    User->>Settings: change global font percent (stepper/reset)
    Settings->>Storage: write percent to GlobalFontMagnification.percentKey
    Storage->>Notification: post GlobalFontMagnification.didChangeNotification
    Notification->>AppDelegate: observer receives notification
    AppDelegate->>GhosttyConfig: invalidate load cache
    AppDelegate->>GhosttyConfig: reloadConfiguration(source: "globalFontMagnificationDidChange", reloadSettingsFromFile: false)
    GhosttyConfig->>GhosttyConfig: loadFromDisk() -> applyGlobalMagnificationIfNeeded()
    GhosttyConfig->>Terminal: return config with scaled font-size
    Terminal->>Terminal: reflow/redraw with new sizes
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 I nudged the fonts a little higher,

letters stretch like springtime wire.
A slider hums, the chrome grows bright—
clearer hops in morning light.
The rabbit cheers: magnify delight!


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (2 errors, 1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Cmux Swift Actor Isolation ❌ Error GlobalFontMagnification.storedPercent accesses UserDefaults.standard without MainActor isolation; called from non-MainActor initializeGhostty via applyGlobalFontMagnificationOverride. Mark GlobalFontMagnification static properties with @MainActor or use nonisolated(unsafe) for UserDefaults access, or ensure callers are MainActor-isolated.
Cmux Full Internationalization ❌ Error New globalFontMagnification entries only have en/ja translations but should support all 19 app locales; schema description is user-facing in docs. Add globalFontMagnification translations for ar, bs, da, de, es, fr, it, ko, nb, pl, pt-BR, ru, th, tr, uk, zh-Hans, zh-Hant; translate schema description for all web locales.
Docstring Coverage ⚠️ Warning Docstring coverage is 13.04% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Cmux Swift @Concurrent ❓ Inconclusive Rule file .github/review-bot-rules/swift-concurrent-annotation.md does not exist; check instructions are unverifiable. Verify the rule file exists or clarify @concurrent annotation requirements. All PR changes are synchronous.
✅ Passed checks (14 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately and concisely describes the main change: adding global font magnification functionality across all UI components.
Description check ✅ Passed The PR description includes all required sections: summary of changes, rationale, testing approach with reproduction steps, manual verification details, demo video link, and completed checklist items.
Linked Issues check ✅ Passed The PR implementation fully addresses all coding requirements from #3147: global magnification setting (50-200%), font scaling for terminals and SwiftUI chrome via .cmuxFont modifier, per-pane zoom preservation, settings UI, and Reset All Settings integration.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the global font magnification feature: localization strings, font modifier implementation, settings integration, and systematic migration of font call sites across UI components.
Cmux Swift Blocking Runtime ✅ Passed PR introduces no new blocking synchronization patterns. All modified files show zero additions of DispatchSemaphore, NSLock, .wait(), or DispatchQueue.main.sync calls.
Cmux No Hacky Sleeps ✅ Passed PR modifies only Swift source files and configuration; no TypeScript, JavaScript, shell, or build/runtime scripts modified, so the check does not apply.
Cmux Algorithmic Complexity ✅ Passed All code changes maintain O(1) complexity with no nested scans, rescans, or repeated operations. Font modifiers are idempotent, bounded by 50-200% magnification range.
Cmux Swift Concurrency ✅ Passed PR doesn't introduce legacy async patterns. Uses NotificationCenter with .main queue (AppKit boundary), @AppStorage for UserDefaults, no Dispatch/completion/fire-and-forget Tasks.
Cmux Swift File And Package Boundaries ✅ Passed Modest growth (+329 to cmuxApp, +91 to GhosttyConfig). Font scaling glue (ViewModifier, Observer) matches existing AppearanceSettings pattern; not testable feature logic.
Cmux Swift Logging ✅ Passed No new logging violations. All code additions (GlobalFontMagnification, GlobalFontSizeObserver, CmuxFontModifier, cmuxFont modifiers) contain no print/NSLog/debugPrint/dump statements.
Cmux User-Facing Error Privacy ✅ Passed PR adds font magnification with safe user-facing strings only (UI labels, settings descriptions). No vendor names, credentials, tokens, or sensitive data exposed.
Cmux Swiftui State Layout ✅ Passed CmuxFontModifier uses @AppStorage (safe for lazy rows), GlobalFontSizeObserver never injected into views, no render-time mutations or layout-changing GeometryReader.
Cmux Architecture Rethink ✅ Passed PR uses @AppStorage for SwiftUI (avoiding observer spin-loops), NotificationCenter only for app-level side effects, no polling/sleeps/locks, clear ownership boundaries maintained.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR adds font magnification via view modifiers and settings UI. No new auxiliary window classes (NSWindow, NSPanel, NSWindowController, SwiftUI Window, WindowGroup) are introduced.
✨ 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 and usage tips.

@greptile-apps

greptile-apps Bot commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a global font magnification setting (50–200%, default 100%, 10% steps) that scales fonts across terminals, tab bar, sidebar, and all SwiftUI chrome. The implementation uses a two-track approach: a CmuxFontModifier (@AppStorage-backed) for SwiftUI views and applyGlobalFontMagnificationOverride injecting a scaled font-size into the Ghostty ghostty_config_t for terminal rendering.

  • Adds GlobalFontMagnification enum, CmuxFontModifier/cmuxFont() view modifiers, GlobalFontSizeObserver, and a GlobalFontMagnificationControl stepper in Settings; all wired through AppDelegate.installGlobalFontSizeObserver as the single owner of cache invalidation and Ghostty config reload.
  • Replaces every font(.system(...)) / font(.caption) etc. call-site across ~20 files with cmuxFont(...) equivalents that auto-scale; browser pane content is explicitly excluded from magnification.
  • Exposes the setting via JSON-managed config (app.globalFontMagnification) with schema validation and file-store parsing.

Confidence Score: 5/5

Safe to merge — the architecture is sound, the dual-track scaling (Swift-side CmuxFontModifier + Ghostty C-side font-size injection) is correctly separated, and the notification/reload ownership is clearly assigned to AppDelegate with no feedback loops.

The change is a well-scoped UI feature that adds a new setting, migrates all font call-sites to a magnification-aware modifier, and wires a single reload path through AppDelegate. The dual-track scaling approaches for Swift chrome and Ghostty terminal rendering operate on independent data (GhosttyConfig Swift struct vs. ghostty_config_t), so there is no double-scaling risk. The only gap found is the JSON schema not constraining multipleOf: 10, which is cosmetic — non-step values pass through silently but cause no crash or data loss.

No files require special attention. The JSON schema (web/data/cmux-settings.schema.json) has a minor validation gap for step enforcement, and Sources/KeyboardShortcutSettingsFileStore.swift parses the integer setting via jsonDouble (previously flagged), but neither affects runtime correctness.

Important Files Changed

Filename Overview
Sources/GhosttyConfig.swift Adds GlobalFontMagnification enum and applyGlobalMagnificationIfNeeded() that scales the Swift-side fontSize/surfaceTabBarFontSize properties; cleanly separated from the Ghostty C-side scaling in GhosttyTerminalView.
Sources/GhosttyTerminalView.swift Adds applyGlobalFontMagnificationOverride(to:) that reads font-size from ghostty_config_t and injects a scaled value via loadInlineGhosttyConfig before ghostty_config_finalize; applied to both the main config and the fallback config paths.
Sources/AppDelegate.swift Adds installGlobalFontSizeObserver as the single owner of cache invalidation and reloadConfiguration on didChangeNotification; guard prevents double-installation.
Sources/cmuxApp.swift Adds GlobalFontMagnification, GlobalFontSizeObserver, CmuxFontModifier, cmuxFont() view-modifier extensions, and GlobalFontMagnificationControl; all existing font(.system(...)) call-sites replaced with cmuxFont(...).
Sources/KeyboardShortcutSettingsFileStore.swift Adds JSON-driven parsing for globalFontMagnification using jsonDouble (schema declares it integer); applyManagedUserDefaultsValue correctly guards on didMutateStoredValue, removeManagedUserDefaultsValue posts unconditionally on key removal.
web/data/cmux-settings.schema.json Adds globalFontMagnification schema entry with integer type, min 50, max 200, default 100; missing multipleOf: 10 so non-step values (e.g. 73) pass validation and are applied as-is by the runtime.
Sources/ContentView.swift Mechanical replacement of all font(.system(...)) and font(.caption) etc. call-sites with cmuxFont(...) equivalents across command palette, sidebar, rename popover, and tab item views.
Sources/SessionIndexView.swift Mechanical font call-site migration; font(.system(size: 12).monospacedDigit()) correctly converted to cmuxFont(size: 12, monospacedDigit: true) using the dedicated parameter.

Sequence Diagram

sequenceDiagram
    participant User
    participant SettingsView
    participant AppStorage
    participant NotifCenter as NotificationCenter
    participant AppDelegate
    participant GhosttyConfig
    participant GhosttyApp

    User->>SettingsView: Adjusts stepper / clicks Reset
    SettingsView->>AppStorage: write globalFontMagnificationPercent
    SettingsView->>NotifCenter: post(didChangeNotification)
    Note over SettingsView: CmuxFontModifier re-renders via @AppStorage
    NotifCenter->>AppDelegate: installGlobalFontSizeObserver fires
    AppDelegate->>GhosttyConfig: invalidateLoadCache()
    AppDelegate->>GhosttyApp: reloadConfiguration()
    GhosttyApp->>GhosttyApp: applyGlobalFontMagnificationOverride(to: config)
    Note over GhosttyApp: ghostty_config_get font-size → scale → loadInlineGhosttyConfig
    GhosttyApp->>GhosttyApp: ghostty_config_finalize(config)
    Note over GhosttyConfig: Swift-side: load() → applyGlobalMagnificationIfNeeded()
    Note over GhosttyConfig: Scales fontSize and surfaceTabBarFontSize for SwiftUI use
Loading

Reviews (2): Last reviewed commit: "[cmux] scale magnification control text ..." | Re-trigger Greptile

Comment thread Sources/cmuxApp.swift
Comment thread Sources/cmuxApp.swift

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

Caution

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

⚠️ Outside diff range comments (4)
Sources/RightSidebarPanelView.swift (1)

65-65: ⚠️ Potential issue | 🟡 Minor

Mode bar has a fixed 31pt height while its contents scale up to 4×.

modeBar is pinned to .frame(height: 31) (line 65), but with .cmuxFont(size: 11, ...) on the icon and label (lines 97, 99), the text/icon scale to ~44pt at the 400% magnification ceiling and will clip against the container. Either drop the fixed height (let the HStack size itself) or scale the frame height by the same GlobalFontMagnification factor that drives the font.

This applies wherever fixed-height containers wrap newly-scaled .cmuxFont(...) content in this PR.

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

In `@Sources/RightSidebarPanelView.swift` at line 65, The mode bar HStack
currently has a hard-coded .frame(height: 31) which causes clipping when its
child views use .cmuxFont(...) and scale with GlobalFontMagnification; remove
the fixed height so the HStack can size itself OR multiply the fixed value by
the same GlobalFontMagnification used by .cmuxFont (e.g., replace 31 with 31 *
GlobalFontMagnification) where the modeBar is configured, and apply the same
change to any other fixed-height containers wrapping scaled .cmuxFont content in
this file (look for modeBar and other HStacks using .frame(height: ...)).
Sources/Panels/BrowserPanelView.swift (1)

1160-1224: ⚠️ Potential issue | 🟠 Major

Omnibar typed text still appears to bypass global magnification.

The surrounding omnibar chrome was migrated to .cmuxFont, but the editable text is rendered by OmnibarTextFieldRepresentable with a fixed AppKit font (Line 3958: .systemFont(ofSize: 12)). This creates inconsistent scaling in the same control.

Please wire the AppKit text field font to the same magnification source and refresh it in updateNSView when magnification changes.

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

In `@Sources/Panels/BrowserPanelView.swift` around lines 1160 - 1224, The AppKit
text field inside OmnibarTextFieldRepresentable is using a hardcoded
.systemFont(ofSize: 12) which bypasses the global magnification; update the
representable so its NSTextField/NSTextView font is driven by the same
magnification/font source used by .cmuxFont (or a shared FontProvider) and
refresh that font in the representable's updateNSView method when magnification
changes; specifically, replace the fixed .systemFont(ofSize: 12) initialization
in OmnibarTextFieldRepresentable with a lookup to the shared magnification-aware
font (or a passed-in Font/scale) and ensure updateNSView reapplies that font
whenever the magnification or relevant binding changes so inline editable text
scales consistently with the omnibar chrome.
Sources/Update/UpdatePopoverView.swift (1)

60-90: ⚠️ Potential issue | 🟡 Minor

Fixed labelWidth = 60 will truncate localized labels at higher magnification.

The label column width is hard-coded at 60pt in UpdateMetadataView (passed as labelWidth parameter to DetectedBackgroundUpdateView and UpdateAvailableView), and also inline in DetectedBackgroundUpdatePendingView. The row text is sized via .cmuxFont(size: 11), which scales with global font magnification (100–400%). At 400% magnification, the font becomes 44pt; the localized labels—especially "Released:" and non-English translations like "バージョン:" or "发布日期:"—will exceed 60pt and truncate with , creating misaligned columns.

Consider deriving labelWidth from the magnification factor (via GlobalFontMagnification.scale) or using .fixedSize(horizontal: true, vertical: false) to allow labels to reflow naturally. Apply the same fix to the inline frame(width: 60) in DetectedBackgroundUpdatePendingView.

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

In `@Sources/Update/UpdatePopoverView.swift` around lines 60 - 90, The hard-coded
60pt label column truncates localized labels at high magnification; update the
callers that set labelWidth (in UpdateMetadataView,
DetectedBackgroundUpdateView, UpdateAvailableView) to compute it from the global
magnification (use GlobalFontMagnification.scale to multiply a base width) or
remove the fixed width and apply .fixedSize(horizontal: true, vertical: false)
to the label Text views instead; also replace the inline .frame(width: 60) in
DetectedBackgroundUpdatePendingView with the same computed width or .fixedSize
so all uses of labelWidth (and the inline frame) expand correctly with .cmuxFont
scaling.
Sources/Update/UpdateTitlebarAccessory.swift (1)

378-393: ⚠️ Potential issue | 🟠 Major

Titlebar icon/badge use magnified .cmuxFont(...) but their frames remain at hard-coded config.buttonSize/config.badgeSize.

config.buttonSize, config.badgeSize, config.spacing, etc. are static per-style constants; nothing in TitlebarControlsStyleConfig reads the global magnification. With these changes:

  • Line 528: the SF Symbol glyph size is now set by cmuxFont(size: config.iconSize, ...) — at e.g. 200% it becomes ~30pt — but it's painted into .frame(width: config.buttonSize, height: config.buttonSize) (24pt for .classic), so the icon is clipped/cropped against the button frame. The same scaling mismatch applies to the surrounding titlebarHintWidth (line 484), which still measures using a non-magnified NSFont.systemFont(...), so shortcut hint pills will visually drift from the rendered icons.
  • Line 383: the unread-count text is magnified via cmuxFont, but it's clipped into a fixed Circle() of config.badgeSize (12–16pt). At higher magnification "99" will overflow the circle.

Verify at 400% magnification that: (1) SF Symbol icons render fully (not clipped), (2) the unread badge stays readable / non-overflowing, and (3) shortcut-hint pill positions stay aligned with the actual icon glyphs.

If cmuxFont is intended only to scale text inside layouts that are themselves magnified, the cleanest fix is to also derive buttonSize/badgeSize/spacing (and the NSFont used in titlebarHintWidth) from the same magnification factor, or to leave SF Symbol icon labels on plain .font(...) and only magnify text content.

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

In `@Sources/Update/UpdateTitlebarAccessory.swift` around lines 378 - 393, The
icon / badge are being visually scaled via cmuxFont(...) but their containing
geometry (TitlebarControlsStyleConfig properties like buttonSize, badgeSize,
spacing and the titlebarHintWidth measurement that uses NSFont.systemFont(...))
remains static, causing clipping and misalignment; fix by either (A) making
TitlebarControlsStyleConfig compute buttonSize/badgeSize/spacing (and the NSFont
used in titlebarHintWidth) from the same magnification factor used by cmuxFont
so the frames grow with the glyphs, or (B) stop applying cmuxFont to SF Symbol
iconLabel and shortcut hint measurement and instead use unscaled .font(...) /
NSFont for layout while keeping cmuxFont only for inner text; update iconLabel
usage, the ZStack badge sizing (config.badgeSize and Circle) and the
titlebarHintWidth font measurement to use the chosen consistent approach so
icons render fully, badges don't overflow, and hint pills stay aligned.
🧹 Nitpick comments (7)
Sources/Settings/ConfigSettingsView.swift (1)

77-77: Editor body font is left out of the global magnification.

The path label (Line 77), status caption (Line 111), and banner footnote (Line 283) now scale with GlobalFontMagnification, but the actual ConfigSettingsTextView's NSTextView.font is hardcoded at .monospacedSystemFont(ofSize: 12, weight: .regular) (Line 318) and won't follow the magnification setting. For users who enable magnification primarily for readability, the chrome around the editor will grow while the editor contents stay tiny — exactly the inverse of what they want.

Consider applying the same scaling factor (GlobalFontMagnification.storedPercent) to the NSTextView's font size, and observing GlobalFontMagnification.didChangeNotification to refresh it on changes.

Also applies to: 283-283

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

In `@Sources/Settings/ConfigSettingsView.swift` at line 77, The editor's
NSTextView font is hardcoded in ConfigSettingsTextView so it doesn't follow
GlobalFontMagnification; update the font-setting logic in ConfigSettingsTextView
to multiply the base monospaced font size (currently 12) by
GlobalFontMagnification.storedPercent (or an appropriate scaled value) and set
NSTextView.font accordingly, and add an observer for
GlobalFontMagnification.didChangeNotification to recompute and apply the scaled
font when magnification changes; ensure you reference and update the same place
where .monospacedSystemFont(ofSize: 12, weight: .regular) is set so the editor
body scales in sync with the path label, status caption, and banner footnote.
Sources/Panels/MarkdownPanelView.swift (1)

74-100: LGTM — markdown header and unavailable-state fonts now magnification-aware.

Header icon (size: 12) and monospaced path (size: 11, design: .monospaced), plus the file-unavailable icon (size: 40), title (.headline), monospaced path (size: 12, design: .monospaced), and message (.caption) all preserve their prior visual styling while responding to the new global font magnification.

Note (non-blocking): The Markdown body itself is rendered through Markdown(...).markdownTheme(cmuxMarkdownTheme) with hard-coded FontSize(...) values (Lines 121, 129, 141, 152, 161, 170, 179, 190, 204, 219). These won't follow the global magnification, so users who enable magnification for accessibility will see a scaled file-path header but unscaled markdown content. Consider routing those FontSize values through the same scaling factor in a follow-up so the markdown body stays consistent with the rest of the chrome.

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

In `@Sources/Panels/MarkdownPanelView.swift` around lines 74 - 100, Header and
unavailable-state views were updated to be magnification-aware but the Markdown
body still uses hard-coded FontSize(...) values inside
Markdown(...).markdownTheme(cmuxMarkdownTheme), so the body won't scale with
global magnification; update the Markdown theme construction to compute those
FontSize values through the same magnification/scaling helper you used for the
header (or add a new helper like scaledFontSize(_:)) and replace each hard-coded
FontSize(...) used in the Markdown(...).markdownTheme(cmuxMarkdownTheme) calls
so that the markdown body sizes are derived from the global magnification
factor.
Sources/AppDelegate.swift (1)

641-641: Consider renaming observer symbols to “Magnification” for consistency.

globalFontSizeObserver / installGlobalFontSizeObserver() are wired to GlobalFontMagnification.didChangeNotification. Renaming reduces ambiguity with per-pane zoom/font-size logic.

✏️ Suggested rename
-    private var globalFontSizeObserver: NSObjectProtocol?
+    private var globalFontMagnificationObserver: NSObjectProtocol?

-        installGlobalFontSizeObserver()
+        installGlobalFontMagnificationObserver()

-    private func installGlobalFontSizeObserver() {
-        guard globalFontSizeObserver == nil else { return }
-        globalFontSizeObserver = NotificationCenter.default.addObserver(
+    private func installGlobalFontMagnificationObserver() {
+        guard globalFontMagnificationObserver == nil else { return }
+        globalFontMagnificationObserver = NotificationCenter.default.addObserver(

Also applies to: 9044-9045

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

In `@Sources/AppDelegate.swift` at line 641, Rename the globalFontSizeObserver
property and installGlobalFontSizeObserver() to use "Magnification" for clarity:
change globalFontSizeObserver -> globalMagnificationObserver and
installGlobalFontSizeObserver() -> installGlobalMagnificationObserver(), update
any removes/uses (e.g., removeObserver calls, notification handler
registrations) that reference GlobalFontMagnification.didChangeNotification and
adjust all call sites including the equivalents around the other occurrence
(previously lines ~9044-9045) so names are consistent; also update any comments
and tests that reference the old names.
Sources/GhosttyConfig.swift (2)

797-801: Verify numeric type compatibility in storedPercent.

UserDefaults.standard.object(forKey: percentKey) as? Int works for Int values stored via defaults.set(_:forKey:), but if the same key were ever written with a Double (for example from a future JSON parser path that forwards the raw jsonDouble value without rounding), the cast would silently fail and fall back to defaultPercent, masking a managed value.

Currently both writers (setPercent here and KeyboardShortcutSettingsFileStore.parseAppSection at Lines 440-442) round to Int before storing, so this is safe today. Consider hardening the read to tolerate either numeric form so future writers don't silently regress:

🛡️ Proposed defensive read
     static var storedPercent: Int {
-        let value = UserDefaults.standard.object(forKey: percentKey) as? Int
-        let resolved = value ?? defaultPercent
+        let object = UserDefaults.standard.object(forKey: percentKey)
+        let resolved: Int
+        if let intValue = object as? Int {
+            resolved = intValue
+        } else if let number = object as? NSNumber {
+            resolved = Int(number.doubleValue.rounded())
+        } else {
+            resolved = defaultPercent
+        }
         return clamp(resolved)
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/GhosttyConfig.swift` around lines 797 - 801, storedPercent currently
reads UserDefaults with `object(forKey: percentKey) as? Int` which will miss
values written as Double; update the getter in storedPercent to tolerate both
Int and Double (and any NSNumber) by attempting to read as Int first, then as
Double/NSNumber and convert/round to Int before passing to clamp; reference the
existing symbols `storedPercent`, `percentKey`, `defaultPercent`, `clamp`, and
the writers `setPercent` and `KeyboardShortcutSettingsFileStore.parseAppSection`
to ensure the read logic matches how values may be written and preserves current
rounding behavior.

822-832: Gate cache invalidation and notification on actual value change.

setPercent(_:) and resetToDefault() unconditionally invalidate the GhosttyConfig load cache and post didChangeNotification, even when the effective stored percent is unchanged (e.g., rapid Stepper clicks that land on the same clamped value, or repeated resets while already at default). In AppDelegate.swift, this notification triggers GhosttyApp.shared.reloadConfiguration(...) per the AI summary, so duplicate posts cause redundant terminal reloads.

♻️ Proposed refinement
     static func setPercent(_ percent: Int) {
-        UserDefaults.standard.set(clamp(percent), forKey: percentKey)
-        GhosttyConfig.invalidateLoadCache()
-        NotificationCenter.default.post(name: didChangeNotification, object: nil)
+        let clamped = clamp(percent)
+        let previous = storedPercent
+        UserDefaults.standard.set(clamped, forKey: percentKey)
+        guard clamped != previous else { return }
+        GhosttyConfig.invalidateLoadCache()
+        NotificationCenter.default.post(name: didChangeNotification, object: nil)
     }

     static func resetToDefault() {
+        let previous = storedPercent
         UserDefaults.standard.removeObject(forKey: percentKey)
+        guard previous != defaultPercent else { return }
         GhosttyConfig.invalidateLoadCache()
         NotificationCenter.default.post(name: didChangeNotification, object: nil)
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/GhosttyConfig.swift` around lines 822 - 832, setPercent(_:) and
resetToDefault() currently always call GhosttyConfig.invalidateLoadCache() and
post didChangeNotification even when the effective stored percent doesn’t
change; update both to first compute the new effective value (use clamp(percent)
in setPercent(_:)) and compare it against the current stored value (reading
UserDefaults.standard.integer(forKey: percentKey) or checking object(forKey:)
for absence) and only proceed to write to UserDefaults, call
GhosttyConfig.invalidateLoadCache(), and post
NotificationCenter.default.post(name: didChangeNotification, ...) if the value
actually changed; do the analogous check in resetToDefault() (only
removeObject/notify if the stored value is not already the default).
Sources/KeyboardShortcutSettingsFileStore.swift (1)

437-446: Consider adding globalFontMagnification to the default JSONC template.

The key is parsed and added to supportedSettingsJSONPaths, but defaultTemplateSections() (Lines 1229-1245) does not include it in the "app" section. Users who bootstrap a fresh settings.json won't see a commented example for this new setting, reducing discoverability compared to peers like minimalMode, preferredEditor, etc.

💡 Proposed addition in defaultTemplateSections
                     "renameSelectsExistingName": CommandPaletteRenameSelectionSettings.defaultSelectAllOnFocus,
                     "commandPaletteSearchesAllSurfaces": CommandPaletteSwitcherSearchSettings.defaultSearchAllSurfaces,
+                    "globalFontMagnification": GlobalFontMagnification.defaultPercent,
                 ],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/KeyboardShortcutSettingsFileStore.swift` around lines 437 - 446, The
default JSONC template is missing the "globalFontMagnification" example under
the "app" section; update the defaultTemplateSections() implementation to add a
commented example entry for "globalFontMagnification" (using the percent form
and referencing GlobalFontMagnification.defaultPercent or
GlobalFontMagnification.percentKey for the example value) so it matches the key
already parsed in supportedSettingsJSONPaths and the parsing logic in the
globalFontMagnification block; locate defaultTemplateSections() and insert the
commented example in the "app" section alongside peers like "minimalMode" and
"preferredEditor".
Sources/cmuxApp.swift (1)

7153-7199: Route this control through GlobalFontMagnification’s helper APIs.

This view writes the raw @AppStorage value directly and replays side effects via onApply(). The enum in Sources/GhosttyConfig.swift already centralizes clamping, cache invalidation, notification posting, and true reset semantics, so using setPercent(_:) / resetToDefault() here would avoid drift and keep the Reset button from persisting an explicit 100 override.

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

In `@Sources/cmuxApp.swift` around lines 7153 - 7199, The view currently mutates
the raw percent binding and calls onApply(); instead route all changes through
GlobalFontMagnification's helpers: in the Stepper binding call
GlobalFontMagnification.setPercent(_:) with the clamped new value (then call
onApply()), and replace the Reset button body to call
GlobalFontMagnification.resetToDefault() (then call onApply()) instead of
writing percent = ...; keep using GlobalFontMagnification.clamp(...) only for
display if needed, and avoid persisting an explicit 100 override by using
resetToDefault().
🤖 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/cmuxApp.swift`:
- Line 4737: The SettingsView.resetAllSettings() function does not reset the
`@AppStorage-backed` globalFontMagnificationPercent to
GlobalFontMagnification.defaultPercent, so add a step in resetAllSettings() to
explicitly set globalFontMagnificationPercent =
GlobalFontMagnification.defaultPercent (or call a restore method on
GlobalFontMagnification if one exists). Locate the AppStorage property
globalFontMagnificationPercent in cmuxApp.swift and update the
SettingsView.resetAllSettings() implementation to restore that value to the
default constant GlobalFontMagnification.defaultPercent so the “Reset All
Settings” flow clears the magnification toggle.

In `@Sources/ContentView.swift`:
- Line 2634: Several UI chrome elements use hard-coded frame heights while their
labels use the new .cmuxFont(size:_, weight:_) scaled fonts, causing clipping at
large dynamic type sizes; update those chrome sizes (titlebar row,
command-palette rows, help-menu items, unread badge, close affordance) to either
compute their heights from the effective scaled font metrics (use
UIFontMetrics/FontMetrics to get scaled lineHeight or use a scaled font size via
Font.scaled(...) and derive frame height from its lineHeight) or explicitly opt
those chrome components out of scaling (wrap with .dynamicTypeSize(.medium) or
fix their font to an unscaled size) so the frame heights track the .cmuxFont
scaling. Ensure you change the hardcoded numeric heights to calculations based
on the scaled font for the identifiers mentioned rather than leaving the fixed
constants.

In `@Sources/GhosttyTerminalView.swift`:
- Around line 2122-2129: You're currently reading the font-size from
GhosttyConfig.load() which uses the parallel Swift parser; instead derive the
magnified size from the same config being finalized in this method (or accept an
explicit uncached value), e.g. replace the GhosttyConfig.load() call and compute
scaled from the local config state (use the appropriate field or getter on the
`config`/`ghostty_config_t` being built) and pass that `scaled` into
loadInlineGhosttyConfig("font-size = \(scaled)", into: config, prefix:
"cmux-global-font-magnification", logLabel: "global font magnification") so the
preview/reload path uses the single source-of-truth (the `config` passed into
this function) rather than GlobalFontMagnification/ GhosttyConfig.load().

In `@Sources/Panels/BrowserPanelView.swift`:
- Around line 883-885: The controls use scaled fonts via .cmuxFont(...) but are
locked to fixed .frame(width: addressBarButtonHitSize, height:
addressBarButtonHitSize) which causes clipping at high magnification; update
these frames (and the other occurrences you listed) to allow expansion by using
adaptive constraints such as .frame(minWidth: addressBarButtonHitSize,
minHeight: addressBarButtonHitSize, alignment: .center) or .frame(minWidth:...,
idealWidth:..., minHeight:..., idealHeight:...) (or remove fixed width/height
and rely on .fixedSize() / intrinsic sizing) so the .cmuxFont text/icons can
grow without clipping while preserving the same minimum touch target.

---

Outside diff comments:
In `@Sources/Panels/BrowserPanelView.swift`:
- Around line 1160-1224: The AppKit text field inside
OmnibarTextFieldRepresentable is using a hardcoded .systemFont(ofSize: 12) which
bypasses the global magnification; update the representable so its
NSTextField/NSTextView font is driven by the same magnification/font source used
by .cmuxFont (or a shared FontProvider) and refresh that font in the
representable's updateNSView method when magnification changes; specifically,
replace the fixed .systemFont(ofSize: 12) initialization in
OmnibarTextFieldRepresentable with a lookup to the shared magnification-aware
font (or a passed-in Font/scale) and ensure updateNSView reapplies that font
whenever the magnification or relevant binding changes so inline editable text
scales consistently with the omnibar chrome.

In `@Sources/RightSidebarPanelView.swift`:
- Line 65: The mode bar HStack currently has a hard-coded .frame(height: 31)
which causes clipping when its child views use .cmuxFont(...) and scale with
GlobalFontMagnification; remove the fixed height so the HStack can size itself
OR multiply the fixed value by the same GlobalFontMagnification used by
.cmuxFont (e.g., replace 31 with 31 * GlobalFontMagnification) where the modeBar
is configured, and apply the same change to any other fixed-height containers
wrapping scaled .cmuxFont content in this file (look for modeBar and other
HStacks using .frame(height: ...)).

In `@Sources/Update/UpdatePopoverView.swift`:
- Around line 60-90: The hard-coded 60pt label column truncates localized labels
at high magnification; update the callers that set labelWidth (in
UpdateMetadataView, DetectedBackgroundUpdateView, UpdateAvailableView) to
compute it from the global magnification (use GlobalFontMagnification.scale to
multiply a base width) or remove the fixed width and apply
.fixedSize(horizontal: true, vertical: false) to the label Text views instead;
also replace the inline .frame(width: 60) in DetectedBackgroundUpdatePendingView
with the same computed width or .fixedSize so all uses of labelWidth (and the
inline frame) expand correctly with .cmuxFont scaling.

In `@Sources/Update/UpdateTitlebarAccessory.swift`:
- Around line 378-393: The icon / badge are being visually scaled via
cmuxFont(...) but their containing geometry (TitlebarControlsStyleConfig
properties like buttonSize, badgeSize, spacing and the titlebarHintWidth
measurement that uses NSFont.systemFont(...)) remains static, causing clipping
and misalignment; fix by either (A) making TitlebarControlsStyleConfig compute
buttonSize/badgeSize/spacing (and the NSFont used in titlebarHintWidth) from the
same magnification factor used by cmuxFont so the frames grow with the glyphs,
or (B) stop applying cmuxFont to SF Symbol iconLabel and shortcut hint
measurement and instead use unscaled .font(...) / NSFont for layout while
keeping cmuxFont only for inner text; update iconLabel usage, the ZStack badge
sizing (config.badgeSize and Circle) and the titlebarHintWidth font measurement
to use the chosen consistent approach so icons render fully, badges don't
overflow, and hint pills stay aligned.

---

Nitpick comments:
In `@Sources/AppDelegate.swift`:
- Line 641: Rename the globalFontSizeObserver property and
installGlobalFontSizeObserver() to use "Magnification" for clarity: change
globalFontSizeObserver -> globalMagnificationObserver and
installGlobalFontSizeObserver() -> installGlobalMagnificationObserver(), update
any removes/uses (e.g., removeObserver calls, notification handler
registrations) that reference GlobalFontMagnification.didChangeNotification and
adjust all call sites including the equivalents around the other occurrence
(previously lines ~9044-9045) so names are consistent; also update any comments
and tests that reference the old names.

In `@Sources/cmuxApp.swift`:
- Around line 7153-7199: The view currently mutates the raw percent binding and
calls onApply(); instead route all changes through GlobalFontMagnification's
helpers: in the Stepper binding call GlobalFontMagnification.setPercent(_:) with
the clamped new value (then call onApply()), and replace the Reset button body
to call GlobalFontMagnification.resetToDefault() (then call onApply()) instead
of writing percent = ...; keep using GlobalFontMagnification.clamp(...) only for
display if needed, and avoid persisting an explicit 100 override by using
resetToDefault().

In `@Sources/GhosttyConfig.swift`:
- Around line 797-801: storedPercent currently reads UserDefaults with
`object(forKey: percentKey) as? Int` which will miss values written as Double;
update the getter in storedPercent to tolerate both Int and Double (and any
NSNumber) by attempting to read as Int first, then as Double/NSNumber and
convert/round to Int before passing to clamp; reference the existing symbols
`storedPercent`, `percentKey`, `defaultPercent`, `clamp`, and the writers
`setPercent` and `KeyboardShortcutSettingsFileStore.parseAppSection` to ensure
the read logic matches how values may be written and preserves current rounding
behavior.
- Around line 822-832: setPercent(_:) and resetToDefault() currently always call
GhosttyConfig.invalidateLoadCache() and post didChangeNotification even when the
effective stored percent doesn’t change; update both to first compute the new
effective value (use clamp(percent) in setPercent(_:)) and compare it against
the current stored value (reading UserDefaults.standard.integer(forKey:
percentKey) or checking object(forKey:) for absence) and only proceed to write
to UserDefaults, call GhosttyConfig.invalidateLoadCache(), and post
NotificationCenter.default.post(name: didChangeNotification, ...) if the value
actually changed; do the analogous check in resetToDefault() (only
removeObject/notify if the stored value is not already the default).

In `@Sources/KeyboardShortcutSettingsFileStore.swift`:
- Around line 437-446: The default JSONC template is missing the
"globalFontMagnification" example under the "app" section; update the
defaultTemplateSections() implementation to add a commented example entry for
"globalFontMagnification" (using the percent form and referencing
GlobalFontMagnification.defaultPercent or GlobalFontMagnification.percentKey for
the example value) so it matches the key already parsed in
supportedSettingsJSONPaths and the parsing logic in the globalFontMagnification
block; locate defaultTemplateSections() and insert the commented example in the
"app" section alongside peers like "minimalMode" and "preferredEditor".

In `@Sources/Panels/MarkdownPanelView.swift`:
- Around line 74-100: Header and unavailable-state views were updated to be
magnification-aware but the Markdown body still uses hard-coded FontSize(...)
values inside Markdown(...).markdownTheme(cmuxMarkdownTheme), so the body won't
scale with global magnification; update the Markdown theme construction to
compute those FontSize values through the same magnification/scaling helper you
used for the header (or add a new helper like scaledFontSize(_:)) and replace
each hard-coded FontSize(...) used in the
Markdown(...).markdownTheme(cmuxMarkdownTheme) calls so that the markdown body
sizes are derived from the global magnification factor.

In `@Sources/Settings/ConfigSettingsView.swift`:
- Line 77: The editor's NSTextView font is hardcoded in ConfigSettingsTextView
so it doesn't follow GlobalFontMagnification; update the font-setting logic in
ConfigSettingsTextView to multiply the base monospaced font size (currently 12)
by GlobalFontMagnification.storedPercent (or an appropriate scaled value) and
set NSTextView.font accordingly, and add an observer for
GlobalFontMagnification.didChangeNotification to recompute and apply the scaled
font when magnification changes; ensure you reference and update the same place
where .monospacedSystemFont(ofSize: 12, weight: .regular) is set so the editor
body scales in sync with the path label, status caption, and banner footnote.
🪄 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: 181ec36a-45da-4e77-b897-b5707475f607

📥 Commits

Reviewing files that changed from the base of the PR and between 51741a5 and 6ab559f.

📒 Files selected for processing (21)
  • Resources/Localizable.xcstrings
  • Sources/AppDelegate.swift
  • Sources/ContentView.swift
  • Sources/Find/BrowserSearchOverlay.swift
  • Sources/Find/SurfaceSearchOverlay.swift
  • Sources/GhosttyConfig.swift
  • Sources/GhosttyTerminalView.swift
  • Sources/KeyboardShortcutSettings.swift
  • Sources/KeyboardShortcutSettingsFileStore.swift
  • Sources/NotificationsPage.swift
  • Sources/Panels/BrowserPanelView.swift
  • Sources/Panels/MarkdownPanelView.swift
  • Sources/RightSidebarPanelView.swift
  • Sources/SessionIndexView.swift
  • Sources/Settings/ConfigSettingsView.swift
  • Sources/ShortcutHintPill.swift
  • Sources/Update/UpdatePopoverView.swift
  • Sources/Update/UpdateTitlebarAccessory.swift
  • Sources/WorkspaceContentView.swift
  • Sources/cmuxApp.swift
  • web/data/cmux-settings.schema.json

Comment thread Sources/cmuxApp.swift
Comment thread Sources/ContentView.swift
Comment thread Sources/GhosttyTerminalView.swift Outdated
Comment thread Sources/Panels/BrowserPanelView.swift

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

4 issues found across 21 files

You’re at about 93% of the monthly review limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread Sources/GhosttyTerminalView.swift Outdated
Comment thread Sources/Update/UpdatePopoverView.swift
Comment thread Sources/KeyboardShortcutSettingsFileStore.swift
Comment thread Sources/GhosttyTerminalView.swift Outdated

@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

🧹 Nitpick comments (1)
Sources/GhosttyConfig.swift (1)

265-272: Make applyGlobalMagnificationIfNeeded() idempotent or rename to reflect single-shot semantics.

This mutates self in place and is not idempotent — invoking it twice on the same GhosttyConfig value would compound the scale (e.g., 150% → 225%). It is currently only invoked from loadFromDisk, but the public mutating surface combined with a generic name is an easy footgun for future callers (e.g., someone re-applying after a partial reload). Consider either gating it on a stored "already-applied" flag, or making the helper non-mutating (func scaledFontSizes() -> (CGFloat, CGFloat)) and applying once at the call site.

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

In `@Sources/GhosttyConfig.swift` around lines 265 - 272,
applyGlobalMagnificationIfNeeded() on GhosttyConfig mutates fontSize and
surfaceTabBarFontSize and is non‑idempotent; change it to a non‑mutating helper
(e.g., func scaledFontSizes() -> (CGFloat, CGFloat)) that returns scaled values
using GlobalFontMagnification.scale, then update callers (like loadFromDisk) to
assign those returned values once, or alternatively implement an internal
"hasAppliedGlobalMagnification" Bool on GhosttyConfig and early‑return when
already applied; reference applyGlobalMagnificationIfNeeded(), GhosttyConfig,
GlobalFontMagnification, fontSize, surfaceTabBarFontSize and update loadFromDisk
to use the new, safe behavior.
🤖 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/cmuxApp.swift`:
- Around line 7175-7191: The Stepper currently uses an empty label and
.labelsHidden(), leaving VoiceOver without a name; update the Stepper (symbol:
Stepper with the Binding get/set to percent and clamp via
GlobalFontMagnification.clamp, and onApply) to set an explicit accessibility
label by adding .accessibilityLabel(...) (use a localized string like "Global
Font Magnification" or the existing localized key) on the same view chain, and
optionally include the current percent in .accessibilityValue(...) so the
control announces its value (retain
.accessibilityIdentifier("SettingsGlobalFontMagnificationStepper") and
.labelsHidden()).
- Around line 2713-2715: The Text view using Text(String(localized:
"about.appName", defaultValue: "cmux")) currently applies .bold() before
.cmuxFont(.title), which is overridden by cmuxFont; fix by ensuring the weight
is applied after or inside the custom font—either move the weight call after
cmuxFont (e.g., apply .bold() or .fontWeight(.bold) after cmuxFont(.title)) or
update cmuxFont to accept and apply a weight parameter for the .title style so
the title remains bold (reference the Text view and cmuxFont(.title) usage).

In `@Sources/GhosttyConfig.swift`:
- Around line 797-801: The computed property storedPercent currently casts
UserDefaults.standard.object(forKey: percentKey) as? Int which fails for
non-integer NSNumbers; update storedPercent to first try casting the stored
object to NSNumber (or use value(forKey:) as? NSNumber), extract an Int (e.g.
using intValue or rounding if needed), fall back to defaultPercent if conversion
fails, and then return clamp(resolved); reference storedPercent, percentKey,
defaultPercent and clamp when locating where to change.

---

Nitpick comments:
In `@Sources/GhosttyConfig.swift`:
- Around line 265-272: applyGlobalMagnificationIfNeeded() on GhosttyConfig
mutates fontSize and surfaceTabBarFontSize and is non‑idempotent; change it to a
non‑mutating helper (e.g., func scaledFontSizes() -> (CGFloat, CGFloat)) that
returns scaled values using GlobalFontMagnification.scale, then update callers
(like loadFromDisk) to assign those returned values once, or alternatively
implement an internal "hasAppliedGlobalMagnification" Bool on GhosttyConfig and
early‑return when already applied; reference applyGlobalMagnificationIfNeeded(),
GhosttyConfig, GlobalFontMagnification, fontSize, surfaceTabBarFontSize and
update loadFromDisk to use the new, safe behavior.
🪄 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: 1442c33d-ce27-4054-8fe0-3116e4b84567

📥 Commits

Reviewing files that changed from the base of the PR and between 6ab559f and 87f7892.

📒 Files selected for processing (4)
  • Sources/GhosttyConfig.swift
  • Sources/GhosttyTerminalView.swift
  • Sources/KeyboardShortcutSettingsFileStore.swift
  • Sources/cmuxApp.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • Sources/KeyboardShortcutSettingsFileStore.swift

Comment thread Sources/cmuxApp.swift Outdated
Comment thread Sources/cmuxApp.swift
Comment thread Sources/GhosttyConfig.swift

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

🤖 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/cmuxApp.swift`:
- Around line 7237-7243: The subtitle Text inside SettingsCardRow currently
forces .lineLimit(2) which clips scaled subtitles; locate the subtitle handling
(the if let subtitle { Text(subtitle) .cmuxFont(.caption)
.foregroundColor(.secondary) .lineLimit(2) } block) and remove the .lineLimit(2)
(or replace it with .lineLimit(nil)) so the subtitle can wrap freely at larger
accessibility/text-scaling settings.
- Around line 7167-7207: The percent readout Text and the Reset Button aren’t
using the same scaled font as the row (they remain at default size), so apply
the same font-scaling modifier used by the row (e.g. cmuxFont or the
GlobalFontMagnification-based font modifier) to the percent Text (the
Text(String(format: "%d%%", GlobalFontMagnification.clamp(percent))) with
.monospacedDigit()) and to the Reset Button’s label (String(localized:
"settings.app.globalFontMagnification.reset"...)) so both the numeric percent
and the button text scale with GlobalFontMagnification; ensure any explicit
accessibilityValue/Label strings remain correct after scaling.
🪄 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: 57556dfe-13d1-4bc2-931b-7ffb722ab2b3

📥 Commits

Reviewing files that changed from the base of the PR and between 87f7892 and a9401f4.

📒 Files selected for processing (3)
  • Sources/GhosttyConfig.swift
  • Sources/cmuxApp.swift
  • web/data/cmux-settings.schema.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • web/data/cmux-settings.schema.json
  • Sources/GhosttyConfig.swift

Comment thread Sources/cmuxApp.swift
Comment thread Sources/cmuxApp.swift Outdated
@goodship11

Copy link
Copy Markdown

+1 for the global approach — sidebar-only fixes the visible symptom, but tab bar / workspace titles / command palette have the same problem and your modifier solves all of them at once.

@RBarbieri13

Copy link
Copy Markdown

Would love to see this merged — the lack of any sidebar/UI zoom is a real accessibility pain point. Happy to test the build if helpful. Thanks for putting this together!

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.

feat: global font magnification setting (50–200%) for terminals + chrome

3 participants