Skip to content

Redesign emoji + suggestion popups as a shared minimal dark HUD#753

Open
FuJacob wants to merge 4 commits into
mainfrom
emoji-picker-minimal
Open

Redesign emoji + suggestion popups as a shared minimal dark HUD#753
FuJacob wants to merge 4 commits into
mainfrom
emoji-picker-minimal

Conversation

@FuJacob

@FuJacob FuJacob commented Jun 20, 2026

Copy link
Copy Markdown
Owner

Summary

Makes the two floating popups feel like ephemeral system overlays instead of bright cards competing with the document. The :emoji: picker and the suggestion popup card (mirror mode) each carried their own .regularMaterial styling and had drifted apart; both now route through one shared, committed-dark HUD (PopupChrome) that reads the same dark over a white host as over a dark one. The emoji picker is also restructured from a tall vertical list into a compact two-row layout: the live :query on top, a horizontal ribbon of ranked glyphs below, moved with the arrow keys.

Validation

xcodebuild ... build           -> ** BUILD SUCCEEDED **
xcodebuild ... build-for-testing -> ** TEST BUILD SUCCEEDED **
xcodebuild ... test -only-testing:CotabbyTests/EmojiPickerPanelLayoutTests \
  -only-testing:CotabbyTests/EmojiPickerControllerTests \
  -only-testing:CotabbyTests/MirrorOverlayLayoutTests CODE_SIGNING_ALLOWED=NO
  -> ** TEST SUCCEEDED **  (incl. 3 new arrow-key nav tests)
swiftlint lint --quiet <changed files> -> exit 0

Rendered the redesigned popups over both a light and a dark host with an ImageRenderer harness (since removed): the committed-dark chrome, hairline, and light text are identical across host appearances, and the ribbon's soft selection chip lands on the highlighted glyph. (ImageRenderer does not lay out ScrollView subviews, so the real ribbon row snapshots blank; verified the cells via a non-scrolling proxy. The live NSHostingView panel scrolls normally.)

Linked issues

None.

Risk / rollout notes

  • New file Cotabby/UI/PopupChrome.swift -> ran xcodegen generate; the project.pbxproj change is only the added file reference.
  • Emoji nav behavior change: Left/Right arrows now move the ribbon selection (previously they dismissed the picker). Up/Down still navigate. With no matches, all arrows still pass through so the host caret moves and the capture closes. Covered by new controller tests.
  • Committed dark: the suggestion popup card and the macro preview are now dark in every host appearance (previously adaptive light/dark). The mirror card is also ~4pt slimmer (vertical padding 6 -> 4); MirrorOverlayLayout and its geometry tests were updated in lockstep.
  • Onboarding's emoji replica (DemoEmojiPopup) was updated to the new ribbon so onboarding doesn't show a stale design.
  • No settings, schema, or persistence migrations.

Greptile Summary

This PR consolidates the emoji picker, suggestion popup card, and macro inline preview into a single shared dark HUD (PopupChrome), replacing per-surface .regularMaterial styling with a deterministic charcoal gradient that reads identically over both light and dark host documents. The emoji picker is also restructured from a scrollable vertical list into a compact two-row layout (:query row + horizontal glyph ribbon) with Left/Right arrow navigation.

  • PopupChrome.swift introduces PopupTheme (tokens) and PopupHUDChrome (modifier), applied uniformly across EmojiPickerView, InlinePreviewView, and MirrorOverlayView — reducing per-site color branching to zero.
  • EmojiPickerController remaps Left/Right arrows from dismissExternally to navigate(.up/.down), with pass-through preserved when no matches are present; three new controller tests cover all branches.
  • MirrorOverlayLayout removes the minCardWidth floor so cards hug short suggestions, and trims verticalPadding from 6→4 in lockstep with the view change; geometry tests were updated accordingly.

Confidence Score: 5/5

Safe to merge. The changes are scoped to visual chrome and keyboard routing with no persistence, settings, or schema impact, and all new behavior is covered by tests.

The shared PopupChrome is a well-contained new file with deterministic, non-adaptive styling applied consistently across three surfaces. The arrow-key behavior change in EmojiPickerController is explicit, fully commented, and covered by three new tests that verify both the consume and pass-through branches. Layout metric changes in MirrorOverlayLayout are reflected in the updated geometry tests. No logic paths were left untested or undocumented.

No files require special attention. The only nits are in the onboarding demo replica, which does not affect production behavior.

Important Files Changed

Filename Overview
Cotabby/UI/PopupChrome.swift New shared HUD chrome. PopupTheme centralises all visual tokens; PopupHUDChrome modifier correctly applies background, hairline overlay, clip shape, and forced dark environment. Design is clean and well-documented.
Cotabby/UI/EmojiPickerView.swift Restructured from vertical list to horizontal ribbon. Uses EmojiPickerMetrics constants throughout, animated ScrollViewReader centering on selection change, and de-published acceptKeyLabel as intended. No issues.
Cotabby/UI/Onboarding/OnboardingFeatureShowcase.swift Demo popup updated to match new ribbon design. DemoEmojiRibbonCell correctly uses EmojiPickerMetrics.cellSize, but DemoEmojiPopup still hardcodes frame width (174) and raw spacing/padding literals instead of the shared metrics constants.
Cotabby/App/Coordinators/EmojiPickerController.swift Left/Right arrows now drive ribbon navigation instead of dismissing. The 117 (Forward-Delete) case correctly kept as dismissExternally. New controller tests cover all three branches.
Cotabby/Support/MirrorOverlayLayout.swift Removed minCardWidth floor so the card hugs short suggestions; verticalPadding dropped from 6→4 in lockstep with the view padding change. Geometry tests updated consistently.
Cotabby/Services/UI/OverlayController.swift Adaptive backdropColor/borderColor helpers removed; ghost text now uses fixed dark-scheme values. popupHUDChrome() applied; vertical padding reduced from 6→4. colorScheme environment property removed as no longer needed.
Cotabby/UI/InlinePreviewView.swift Switched from .regularMaterial to popupHUDChrome(); keycap colors updated to use PopupTheme tokens. Height trimmed from 30→28. All changes consistent with the shared chrome contract.
CotabbyTests/EmojiPickerControllerTests.swift Three new tests added for ribbon arrow-key navigation: right with matches (consume), left with matches (consume), and any arrow with no matches (passThrough + panel hidden). Good coverage of the new behavior contract.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    KEY[Key event from global tap]
    CTRL[EmojiPickerController.decideCaptureKey]
    MATCHES{matches.isEmpty?}

    KEY --> CTRL

    CTRL -->|Left 123 / Up 126| NAV_UP[navigate up]
    CTRL -->|Right 124 / Down 125| NAV_DOWN[navigate down]
    CTRL -->|Escape| DISMISS[dismiss]
    CTRL -->|Return / Enter| DISMISS_EXT[dismissExternally]
    CTRL -->|Forward-Delete 117| DISMISS_EXT

    NAV_UP --> MATCHES
    NAV_DOWN --> MATCHES

    MATCHES -->|No| PASSTHROUGH[passThrough: host caret moves, capture closes]
    MATCHES -->|Yes| CONSUME[consume: selectedIndex updates, ribbon scrolls]

    CONSUME --> VIEW[EmojiPickerView]
    VIEW --> RIBBON[HStack ribbon]
    VIEW --> QUERYROW[queryRow]
    RIBBON -->|onChange selectedIndex| SCROLL[proxy.scrollTo center, animated]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    KEY[Key event from global tap]
    CTRL[EmojiPickerController.decideCaptureKey]
    MATCHES{matches.isEmpty?}

    KEY --> CTRL

    CTRL -->|Left 123 / Up 126| NAV_UP[navigate up]
    CTRL -->|Right 124 / Down 125| NAV_DOWN[navigate down]
    CTRL -->|Escape| DISMISS[dismiss]
    CTRL -->|Return / Enter| DISMISS_EXT[dismissExternally]
    CTRL -->|Forward-Delete 117| DISMISS_EXT

    NAV_UP --> MATCHES
    NAV_DOWN --> MATCHES

    MATCHES -->|No| PASSTHROUGH[passThrough: host caret moves, capture closes]
    MATCHES -->|Yes| CONSUME[consume: selectedIndex updates, ribbon scrolls]

    CONSUME --> VIEW[EmojiPickerView]
    VIEW --> RIBBON[HStack ribbon]
    VIEW --> QUERYROW[queryRow]
    RIBBON -->|onChange selectedIndex| SCROLL[proxy.scrollTo center, animated]
Loading

Comments Outside Diff (1)

  1. Cotabby/Support/EmojiPickerPanelLayout.swift, line 1-12 (link)

    P2 Doubled file-level doc comment after the merge

    The old "Pure geometry for the emoji picker panel: how big it is…" paragraph and the new "Pure geometry for the two-row emoji picker…" paragraph were both left as a single contiguous doc comment block before the EmojiPickerMetrics enum. Only one should be the file overview; the other is now redundant or misplaced as an enum-level doc comment.

    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

Reviews (3): Last reviewed commit: "Fix mirror layout clamp test" | Re-trigger Greptile

The :emoji: picker and the suggestion "popup" card each carried their own
.regularMaterial styling and had drifted apart. Route both (plus the macro
preview) through one committed-dark HUD (PopupChrome) so they read as ephemeral
overlays instead of bright cards, identical dark over light and dark hosts.

The emoji picker becomes a compact two-row layout: the live :query on top, a
horizontal ribbon of ranked glyphs below, moved with the arrow keys (Left/Right
now navigate the ribbon; previously they dismissed it). The suggestion card is
darker and a touch slimmer, and onboarding's emoji replica is updated to match.
Comment thread Cotabby/Services/UI/OverlayController.swift
Comment thread Cotabby/UI/Onboarding/OnboardingFeatureShowcase.swift Outdated
Comment thread Cotabby/UI/EmojiPickerView.swift Outdated
Adpros7 added a commit to Adpros7/cotabby that referenced this pull request Jun 23, 2026
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