feat(appearance): ghost text size control and live preview#651
Merged
Conversation
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.
# 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
10 new tests: multiplier scaling/floor in
GhostFontMetrics, store default/clamp/round-trip, and model default/clamp/persist. The 15 existingMirrorOverlayLayouttests stay green (the defaultedsizeMultiplierkeeps prior behavior byte-for-byte).UI: rendered the real
GhostTextPreviewheadlessly withImageRendererto 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
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.project.pbxprojadds one file (GhostTextPreview.swift); regenerated with xcodegen, no drift.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.pointSizeand inMirrorOverlayLayout.layout, preserving exact 1.0× rendering for all existing users.SuggestionSettingsStore/Data/Model: NewcotabbyGhostTextSizeMultiplierUserDefaultskey; clamped on load with anisFiniteguard; defaults to 1.0 so fresh installs and missing keys behave identically to today.GhostFontMetrics.pointSize:sizeMultiplier: CGFloat = 1parameter scales the auto-resolved size after the[min, max]clamp, then re-applies a 9pt absolute legibility floor.MirrorOverlayLayout.layoutmirrors this for the popup card path.GhostTextPreview/AppearancePaneView: New non-interactive preview composited fromTextconcatenation (typed prefix + ghost run) with matching color/opacity split; a newTickMarkSliderrow drivessetGhostTextSizeMultiplier.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
sizeMultiplier: CGFloat = 1parameter applied post-clamp with a 9pt absolute floor; backward-compatible default; math is correct and fully unit-tested.sizeMultiplier: CGFloat = 1to the layout factory; correctly scales text-width measurement and card height together; keycap reservation intentionally kept unscaled; default preserves prior layout byte-for-byte.cotabbyGhostTextSizeMultiplierkey with nil-check defaulting,isFiniteguard in the clamp helper, and consistent save/load/saveAll wiring.@Published ghostTextSizeMultiplierproperty andsetGhostTextSizeMultipliermutator; model clamps before persisting, matching the opacity pattern.resolvedGhostTextColorhelper correctly delegates to existingautomaticGhostTextColor.ghostTextSizeMultiplierinto both the mirror-layout and font-metrics call sites; minimal, correct change.GhostTextSizeSettingsTestsclass covers fresh-install default, out-of-range clamping, and round-trip persistence; correctly follows the same isolated-UserDefaults pattern used by the opacity tests.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 --> LReviews (2): Last reviewed commit: "docs(appearance): correct baseFontSize r..." | Re-trigger Greptile