Skip to content

Cap derived-quality ghost font size separately from exact#386

Open
FuJacob wants to merge 1 commit into
mainfrom
fix/tighten-derived-ghost-font-cap
Open

Cap derived-quality ghost font size separately from exact#386
FuJacob wants to merge 1 commit into
mainfrom
fix/tighten-derived-ghost-font-cap

Conversation

@FuJacob
Copy link
Copy Markdown
Owner

@FuJacob FuJacob commented May 28, 2026

Summary

Split the ghost-text font-size ceiling in OverlayController.resolvedGhostFontSize so .derived caret quality no longer shares the .exact cap. Today .exact and .derived both clamp at 24pt; in hosts that emit .derived geometry from line-box bounds (Slack, several web editors) the reported caret height inflates against the host's real font size, and ghost text renders comically oversized. The observed failure mode is too-large, not too-small.

New tiers:

Quality Cap (pt) Rationale
.exact 24 Measured text bounds, trusted to scale up in larger editors.
.derived 17 Line-box geometry; more reliable than estimated but still not measured.
.estimated 16 Full-field-frame fallback, least trustworthy. Unchanged.

GhostFontSizeStabilizer still does per-focus-session downward smoothing on top of these caps, so a momentarily-better reading inside one session still narrows the ceiling.

Validation

swiftlint lint --quiet Cotabby/Services/UI/OverlayController.swift
# exit 0

xcodebuild -project Cotabby.xcodeproj -scheme Cotabby -destination 'platform=macOS' build
# ** BUILD SUCCEEDED **

Local full-test execution hits the documented Team ID code-signing mismatch on this machine; CI runs the test target against the merge ref.

Linked issues

None filed; reported in-conversation.

Risk / rollout notes

  • Behavior change is bounded: in apps that today render correct ghost text at the upper end of the 17-24pt range with .derived quality, ghost text will now cap at 17pt and look slightly small. The current state is worse for affected hosts ("comically oversized") than for unaffected hosts (slightly small), so the asymmetry favors the cap.
  • .exact paths (most native AppKit fields, Gmail/Outlook text-marker path) are unchanged.
  • Pure value math; no new files, no project.yml changes, XcodeGen check stays green.
  • If a future host turns out to need a higher derived ceiling, the constant is a one-line bump in Layout.

Greptile Summary

This PR splits the ghost-text font-size ceiling in resolvedGhostFontSize so .derived caret quality gets its own cap (17 pt) instead of sharing the .exact cap (24 pt), fixing oversized ghost text in hosts like Slack that inflate caret height from line-box geometry.

  • New constant maximumDerivedGhostFontSize = 17 is added to Layout; the old binary ternary (.estimated vs everything else) is replaced with an exhaustive switch over all three CaretGeometryQuality cases.
  • .exact (24 pt) and .estimated (16 pt) caps are unchanged; only .derived is tightened, isolating the regression risk to hosts that already report inflated derived geometry.
  • The switch is exhaustive — Swift's compiler enforces coverage, so any future addition of a quality case will require a matching arm here.

Confidence Score: 5/5

Safe to merge — the change is pure value math on three named constants, the switch is exhaustive and compiler-enforced, and the only affected path is .derived quality which is currently producing the worse "comically oversized" outcome.

The change is a tightly scoped constant split with an exhaustive switch. Adding CaretGeometryQuality cases in the future would produce a compile error rather than silently falling through to the wrong cap. The .exact and .estimated paths are byte-for-byte identical to before, and .derived tightening only affects hosts already exhibiting the oversized-text failure mode.

No files require special attention. The single changed file is self-contained value math with no side effects outside the font-size calculation.

Important Files Changed

Filename Overview
Cotabby/Services/UI/OverlayController.swift Adds maximumDerivedGhostFontSize = 17 and expands the binary ternary into an exhaustive switch over all three CaretGeometryQuality cases, giving .derived its own cap between .exact (24) and .estimated (16).

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["resolvedGhostFontSize(caretHeight:caretQuality:)"] --> B["proposedSize = max(minFont, caretHeight × 0.78)"]
    B --> C{caretQuality}
    C -- ".exact" --> D["qualityCap = 24 pt\n(maximumGhostFontSize)"]
    C -- ".derived" --> E["qualityCap = 17 pt\n(maximumDerivedGhostFontSize)\n★ NEW"]
    C -- ".estimated" --> F["qualityCap = 16 pt\n(maximumEstimatedGhostFontSize)"]
    D --> G["return min(proposedSize, qualityCap)"]
    E --> G
    F --> G
Loading

Fix All in Codex Fix All in Claude Code

Reviews (1): Last reviewed commit: "Cap derived-quality ghost font size sepa..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

`resolvedGhostFontSize` treated `.exact` and `.derived` carets
identically, capping both at 24pt. In hosts that emit `.derived`
geometry (Slack, several web editors), the line-box height commonly
inflates against the real font size, so ghost text rendered with a
~20pt cap looked oversized next to ~13pt host text — the "insanely
big derived" failure mode users reported.

Split the cap into three tiers keyed off `CaretGeometryQuality`:
24pt for `.exact` (measured text bounds, still trusted), 17pt for
`.derived` (just above the estimated cap, since derived geometry is
more reliable than estimated but still not measured), 16pt for
`.estimated` (unchanged). The observed failure mode is too-large,
not too-small, so biasing the derived ceiling toward the smaller
end is the right tradeoff. `GhostFontSizeStabilizer` still handles
per-session downward smoothing on top of this.
Comment on lines 14 to 21
static let minimumGhostFontSize: CGFloat = 14
static let maximumGhostFontSize: CGFloat = 24
/// Derived caret rects come from line-box geometry rather than measured text bounds, so the
/// reported height often inflates against the host's real font size (Slack, several web
/// editors). Keep the ceiling well below the exact cap so a wrong derived reading cannot
/// render oversized ghost text — the observed failure mode is too-large, not too-small.
static let maximumDerivedGhostFontSize: CGFloat = 17
static let maximumEstimatedGhostFontSize: CGFloat = 16
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 Now that all three caps are per-quality constants, maximumGhostFontSize is the odd one out — it's the only name that doesn't signal which quality tier it governs. Renaming it to match the pattern of its siblings makes the Layout enum self-documenting and avoids any future confusion about whether the generic name applies to all qualities.

Suggested change
static let minimumGhostFontSize: CGFloat = 14
static let maximumGhostFontSize: CGFloat = 24
/// Derived caret rects come from line-box geometry rather than measured text bounds, so the
/// reported height often inflates against the host's real font size (Slack, several web
/// editors). Keep the ceiling well below the exact cap so a wrong derived reading cannot
/// render oversized ghost text — the observed failure mode is too-large, not too-small.
static let maximumDerivedGhostFontSize: CGFloat = 17
static let maximumEstimatedGhostFontSize: CGFloat = 16
static let minimumGhostFontSize: CGFloat = 14
/// Measured text bounds from the accessibility API; trusted to scale up in larger editors.
static let maximumExactGhostFontSize: CGFloat = 24
/// Derived caret rects come from line-box geometry rather than measured text bounds, so the
/// reported height often inflates against the host's real font size (Slack, several web
/// editors). Keep the ceiling well below the exact cap so a wrong derived reading cannot
/// render oversized ghost text — the observed failure mode is too-large, not too-small.
static let maximumDerivedGhostFontSize: CGFloat = 17
static let maximumEstimatedGhostFontSize: CGFloat = 16

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Codex Fix in Claude Code

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.

1 participant