Skip to content

feat(appearance): ghost text size control and live preview#651

Merged
FuJacob merged 3 commits into
mainfrom
feat/ghost-text-size-and-preview
Jun 9, 2026
Merged

feat(appearance): ghost text size control and live preview#651
FuJacob merged 3 commits into
mainfrom
feat/ghost-text-size-and-preview

Conversation

@FuJacob

@FuJacob FuJacob commented Jun 9, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a Ghost Text Size control to Settings > Appearance plus a live preview of how suggestions look. The size knob is a multiplier (0.7x to 1.3x, default 1.0x) on Cotabby's caret-approximated ghost size, for the recurring "suggestions look too big" complaint; it applies to both the inline ghost and the popup card. The preview is a fake, non-interactive sample so users can judge color, opacity, and size choices at a glance without switching apps to trigger a real suggestion.

Validation

swiftlint lint --quiet
# exit 0

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

xcodebuild ... test CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO \
  -only-testing:CotabbyTests/GhostFontMetricsTests \
  -only-testing:CotabbyTests/MirrorOverlayLayoutTests \
  -only-testing:CotabbyTests/SuggestionSettingsStoreTests \
  -only-testing:CotabbyTests/GhostTextSizeSettingsTests \
  -only-testing:CotabbyTests/GhostTextOpacitySettingsTests
# ** TEST SUCCEEDED **  51 tests, 0 failures

10 new tests: multiplier scaling/floor in GhostFontMetrics, store default/clamp/round-trip, and model default/clamp/persist. The 15 existing MirrorOverlayLayout tests stay green (the defaulted sizeMultiplier keeps prior behavior byte-for-byte).

UI: rendered the real GhostTextPreview headlessly with ImageRenderer to confirm all three dimensions reflect correctly (Automatic gray vs Blue, 90% vs visibly fainter 50%, and 0.7x / 1.0x / 1.3x with a clean two-line wrap at the largest size). I did not launch the full menu-bar app, to avoid triggering its accessibility / input-monitoring permission flow.

Linked issues

None.

Risk / rollout notes

  • New persisted setting cotabbyGhostTextSizeMultiplier (Double). Fresh installs and absent values resolve to 1.0 (no behavior change); values are finite-guarded and clamped to [0.7, 1.3] on load and write. No existing keys are migrated.
  • Sizing: the multiplier is applied after the existing [14, 24] caret clamp for the inline ghost (with a 9pt absolute legibility floor so a low value can't render illegibly), and scales the popup card's fixed 13pt base, so the setting stays consistent across inline and popup modes. 1.0x reproduces current rendering exactly.
  • project.pbxproj adds one file (GhostTextPreview.swift); regenerated with xcodegen, no drift.
  • Rebased onto current main; merged cleanly with the per-power-source engine work (fix(power): popup edits update active power profile; disable redundant model picker #644) that also touched the settings files.

Greptile Summary

This PR adds a Ghost Text Size multiplier control (0.7×–1.3×, default 1.0×) to the Appearance settings pane, plus a live preview widget that renders the user's color, opacity, and size choices without needing a real suggestion trigger. The multiplier is applied post-clamp in GhostFontMetrics.pointSize and in MirrorOverlayLayout.layout, preserving exact 1.0× rendering for all existing users.

  • SuggestionSettingsStore / Data / Model: New cotabbyGhostTextSizeMultiplier UserDefaults key; clamped on load with an isFinite guard; defaults to 1.0 so fresh installs and missing keys behave identically to today.
  • GhostFontMetrics.pointSize: sizeMultiplier: CGFloat = 1 parameter scales the auto-resolved size after the [min, max] clamp, then re-applies a 9pt absolute legibility floor. MirrorOverlayLayout.layout mirrors this for the popup card path.
  • GhostTextPreview / AppearancePaneView: New non-interactive preview composited from Text concatenation (typed prefix + ghost run) with matching color/opacity split; a new TickMarkSlider row drives setGhostTextSizeMultiplier.

Confidence Score: 5/5

Safe to merge — new setting defaults to 1.0 so no existing behavior changes, all clamp/floor math is unit-tested, and the feature touches only presentation logic with no generation-facing code paths.

The change is additive: the multiplier parameter carries a default of 1 everywhere it's threaded, UserDefaults missing-key resolution falls back to 1.0, and the 9pt absolute floor keeps the absolute-worst-case input from producing illegible text. Ten targeted new tests cover scaling, clamping, the absolute floor, default-on-fresh-install, and round-trip persistence. The 15 existing MirrorOverlayLayout tests remain green. No data model migration, no generation logic, no auth or permission boundaries touched.

No files require special attention.

Important Files Changed

Filename Overview
Cotabby/Support/GhostFontMetrics.swift Adds sizeMultiplier: CGFloat = 1 parameter applied post-clamp with a 9pt absolute floor; backward-compatible default; math is correct and fully unit-tested.
Cotabby/Support/MirrorOverlayLayout.swift Adds sizeMultiplier: CGFloat = 1 to the layout factory; correctly scales text-width measurement and card height together; keycap reservation intentionally kept unscaled; default preserves prior layout byte-for-byte.
Cotabby/Support/SuggestionSettingsStore.swift New cotabbyGhostTextSizeMultiplier key with nil-check defaulting, isFinite guard in the clamp helper, and consistent save/load/saveAll wiring.
Cotabby/Models/SuggestionSettingsModel.swift New @Published ghostTextSizeMultiplier property and setGhostTextSizeMultiplier mutator; model clamps before persisting, matching the opacity pattern.
Cotabby/UI/Settings/Components/GhostTextPreview.swift New non-interactive preview view; uses Text concatenation for correct single-paragraph wrapping at large sizes; accessibility label provided.
Cotabby/UI/Settings/Panes/AppearancePaneView.swift Adds GhostTextPreview at the top of the Appearance section and a TickMarkSlider row for size; new resolvedGhostTextColor helper correctly delegates to existing automaticGhostTextColor.
Cotabby/Services/UI/OverlayController.swift Threads ghostTextSizeMultiplier into both the mirror-layout and font-metrics call sites; minimal, correct change.
CotabbyTests/GhostFontMetricsTests.swift Four new tests cover: default-multiplier backward compatibility, scaling in both directions, post-clamp application, and absolute-floor enforcement.
CotabbyTests/ModelAndPresentationValueTests.swift New GhostTextSizeSettingsTests class covers fresh-install default, out-of-range clamping, and round-trip persistence; correctly follows the same isolated-UserDefaults pattern used by the opacity tests.
CotabbyTests/SuggestionSettingsStoreTests.swift Two new store-level tests (default-when-unset and out-of-range clamp) plus the round-trip save/load assertion; correct and consistent with existing test structure.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User adjusts Ghost Text Size slider
0.7x - 1.3x] --> B[AppearancePaneView
ghostTextSizeBinding]
    B --> C[SuggestionSettingsModel
setGhostTextSizeMultiplier]
    C --> D{clampedGhostTextSizeMultiplier
isFinite + clamp to 0.7-1.3}
    D --> E[SuggestionSettingsStore
saveGhostTextSizeMultiplier
cotabbyGhostTextSizeMultiplier]
    D --> F[GhostTextPreview
baseFontSize x multiplier
live preview updates]
    E --> G[UserDefaults persisted]
    H[OverlayController
next suggestion render] --> I{Display mode?}
    I -->|inline ghost| J[GhostFontMetrics.pointSize
autoSize = clamp caretHeight x ratio to min-max
result = max 9pt, autoSize x multiplier]
    I -->|mirror popup| K[MirrorOverlayLayout.layout
scaledFontSize = max 9pt, Metrics.fontSize x multiplier
scales measuredTextWidth + cardHeight]
    J --> L[Ghost text rendered at user-chosen size]
    K --> L
Loading

Reviews (2): Last reviewed commit: "docs(appearance): correct baseFontSize r..." | Re-trigger Greptile

Add a "Ghost Text Size" multiplier (0.7x-1.3x, default 1.0x) that scales
Cotabby's caret-approximated ghost size for users who find suggestions too
big, applied to both the inline ghost (GhostFontMetrics) and the popup card
(MirrorOverlayLayout, common since the mid-line caret popup landed).

Add a live, non-interactive preview to the Appearance pane so users can see
how their color, opacity, and size choices look without switching apps to
trigger a real suggestion. The preview reads the resolved ghost color at the
chosen opacity and a representative base size scaled by the same multiplier.
Comment thread Cotabby/UI/Settings/Components/GhostTextPreview.swift Outdated
FuJacob added 2 commits June 9, 2026 00:19
# Conflicts:
#	Cotabby.xcodeproj/project.pbxproj
Addresses Greptile feedback: 16pt sits near the lower end of the [14, 24]
band, not mid-way (midpoint is 19). Reword to describe it as a typical
small-to-medium field size rather than the range midpoint.
@FuJacob FuJacob merged commit 9848f02 into main Jun 9, 2026
4 checks passed
@FuJacob FuJacob deleted the feat/ghost-text-size-and-preview branch June 9, 2026 07:20
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