Skip to content

Settings revamp: searchable Home hub, ranked reveal-to-row search, and a tinted sidebar#699

Merged
FuJacob merged 2 commits into
mainfrom
settings-delight
Jun 12, 2026
Merged

Settings revamp: searchable Home hub, ranked reveal-to-row search, and a tinted sidebar#699
FuJacob merged 2 commits into
mainfrom
settings-delight

Conversation

@FuJacob

@FuJacob FuJacob commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Summary

The Settings window had grown cluttered: twelve flat sidebar rows, a form-styled Home that mostly repeated the support pitch (which appeared in three different panes), and a search that was a substring filter that could only land you on a pane. This redesigns Settings around finding and understanding things. Home becomes a real landing surface (an identity hero, a prominent search field over the whole settings catalog, live status cards for the global toggle / engine / permissions, tinted quick-link cards, and the feature demos). Search gains true relevance ranking in a new pure SettingsSearchRanker (tiered prefix/word/substring scoring, multi-word queries, plural folding, subsequence typo matching, per-item summaries), and selecting a result now reveals the exact setting: the pane opens, scrolls to the row, and pulses it. The sidebar gets System Settings-style tinted icon tiles with spacing-only group clusters, and Cmd-F jumps to search from any pane.

Validation

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

xcodebuild test -project Cotabby.xcodeproj -scheme Cotabby -destination 'platform=macOS' \
  -derivedDataPath build/DerivedData CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO \
  -skip-testing:CotabbyTests/ScreenshotContextGeneratorTests
# ** TEST SUCCEEDED **  Executed 1602 tests, with 22 tests skipped and 0 failures
# Includes the 10 new SettingsSearchRankerTests and the extended SettingsIndexTests
# (summary coverage, sidebar-group coverage).
# ScreenshotContextGeneratorTests was excluded locally because the Vision/OCR path hangs
# while the screen is locked (the machine was lid-closed during the run; the system log
# shows AX/window access shielded). It is untouched by this diff and runs in PR CI.

swiftlint lint --quiet
# exit 0, no output

Visual verification was done by rendering the new surfaces off-screen through a temporary NSHostingView snapshot harness in the test bundle (removed before commit) and reviewing the PNGs:

  • Home: hero + search field + status cards + two-column quick links (captions confirmed untruncated at the default window width) + demos + footer.
  • Sidebar: tinted tiles, group clusters, attention dot on Permissions.
  • Appearance pane with the arrival highlight active: the pulse wash renders on the Ghost Text Size row. This snapshot diff caught that macOS grouped Form ignores listRowBackground (highlighted and plain renders were pixel-identical); the pulse now draws as a direct row background instead.
  • Ranked search rows for "ghost": the three Ghost Text items rank above keyword-only matches, with summaries and pane chips.

Also launched the app with the new -cotabby-open-settings argument and confirmed via CGWindowList that the Settings window opens at startup. The live scroll-to-row behavior was not exercised end to end in the running window (scripting the unsigned dev build's AX tree turned out to be blocked at the system level); the navigation and ranking logic behind it is unit-tested and the pulse rendering is snapshot-verified.

Linked issues

None directly; this is the Settings UX revamp.

Risk / rollout notes

  • Settings window frame autosave name bumped CotabbySettingsWindowV6 to V7: everyone reopens once at the new default size (1060x720), then their resize sticks again.
  • The window title is now driven by .navigationTitle (pane name, or "Cotabby Settings" on Home). The old async NSWindow.title write raced SwiftUI's own title management and lost whenever the window was not key.
  • SettingsItem.results(for:) now returns ranked results instead of declaration order. Search behavior strictly widens (typo subsequences, plural/reverse-prefix matching, summaries searched); the existing SettingsIndexTests queries all still pass.
  • HomePaneView now takes the navigation model and live status objects; it is the only pane not rendered inside the shared grouped-form scaffold, by design.
  • The Ko-fi support section is no longer duplicated in General; it remains on Home (footer) and About.
  • New launch argument -cotabby-open-settings opens the Settings window at startup for UI tooling; it is a no-op unless passed.

Greptile Summary

This PR redesigns the Settings window around discoverability: Home becomes a rich landing surface (identity hero, global search, at-a-glance status cards, quick-link grid, feature demos), search gains a full relevance-ranking engine (SettingsSearchRanker) with tiered scoring, multi-word queries, typo subsequences, and per-item summaries, and the sidebar adopts System Settings–style tinted icon tiles arranged in spacing-only groups. Selecting a search result now navigates to the exact row, scrolls to it, and pulses it.

  • SettingsSearchRanker is a pure, fully unit-tested ranker (no SwiftUI/app-state dependencies) with 10 new tests; SettingsItem conforms via SettingsSearchable, and SettingsIndex remains a thin catalog accessor.
  • SettingsNavigationModel centralises all navigation state (pane selection, in-flight highlight, pending Cmd-F focus) and replaces the old @Binding var selection threading; SettingsCategory gains tint, summary, and sidebarGroups (with a unit test that pins coverage to allCases).
  • HomePaneView is rewritten from a flat form into a composed page; SettingsCoordinator bumps the autosave name to V7 to reset window frames, and BundleVersion.swift extracts the duplicated version-formatting helper.

Confidence Score: 5/5

Safe to merge. All navigation state flows through SettingsNavigationModel, cancellation is handled correctly, and the ranking logic is fully unit-tested.

The navigation model's Task-based highlight clearing is correct under rapid re-invocations: cancellation propagates through Task.sleep and the guard prevents stale clears. The scroll Tasks in SettingsPaneScaffold are unstructured but their effects on orphaned proxies are benign no-ops. The ranker, catalog, sidebar grouping, and the Cmd-F focus flow are all logically sound, and 1,602 passing tests (including 10 new ranker tests and extended index tests) back that up.

No files require special attention.

Important Files Changed

Filename Overview
Cotabby/Support/SettingsSearchRanker.swift New pure ranking engine; tiered scoring, multi-word, subsequence, and diacritic folding are all correct. normalize calls both .folding(.caseInsensitive) and .lowercased(), which is harmlessly redundant.
Cotabby/UI/Settings/SettingsNavigationModel.swift Single source of truth for selection, highlight, and Cmd-F focus; Task-based highlight clearing is cancellation-aware and correct on rapid re-invocation.
Cotabby/UI/Settings/Components/SettingsPaneScaffold.swift Adds scroll-to-row on highlight change via a staggered unstructured Task; Task is not cancelled when the view disappears, leaving an orphaned proxy call in rapid-switch scenarios (benign no-op in practice).
Cotabby/UI/Settings/Panes/HomePaneView.swift Full rewrite to composed landing page; status cards, quick links, hero search, and showcase all wired correctly to SettingsNavigationModel. onChange(initial: true) correctly handles cross-pane Cmd-F.
Cotabby/UI/Settings/SettingsContainerView.swift Migrated from @State var selection to @StateObject var navigation; dead syncWindowTitle removed; title driven by .navigationTitle; Cmd-F zero-size button wired correctly.
Cotabby/UI/Settings/SettingsSidebarView.swift Refactored to grouped Section-based list using tinted SettingsIconTile rows; search now uses the shared searchResults property for both rendering and the Enter-key action.
Cotabby/UI/Settings/SettingsCategory.swift Added tint, summary, and sidebarGroups; import changed from Foundation to SwiftUI for Color. sidebarGroups coverage is pinned by a new unit test.
Cotabby/UI/Settings/SettingsIndex.swift Adds summary to every item and SettingsSearchable conformance; replaces the old contains-based results(for:) with the ranked SettingsSearchRanker call.
CotabbyTests/SettingsSearchRankerTests.swift New ranker test suite with 10 tests covering exact title, prefix, multi-word, missing-token, subsequence, pane-label, summary, blank, every-item-is-top-result, and diacritic folding scenarios.
Cotabby/App/Core/AppDelegate.swift Adds -cotabby-open-settings launch argument that opens Settings at startup; no-op unless passed, guarded with an explicit arguments.contains check.
Cotabby/Support/BundleVersion.swift New file extracting the duplicated version-formatting helper into Bundle.cotabbyDisplayVersion; both the sidebar header and Home hero now use it.

Sequence Diagram

sequenceDiagram
    actor User
    participant Sidebar as SettingsSidebarView
    participant Home as HomePaneView
    participant Nav as SettingsNavigationModel
    participant Container as SettingsContainerView
    participant Scaffold as SettingsPaneScaffold
    participant Row as SettingsItemAnchor

    User->>Sidebar: types query
    Sidebar->>Sidebar: searchResults (ranked via SettingsSearchRanker)
    User->>Sidebar: selects result / presses Return
    Sidebar->>Nav: reveal(item)
    Nav->>Nav: "selection = item.category"
    Nav->>Nav: "highlightedItem = item"
    Nav->>Nav: start 2.4s clear Task
    Nav-->>Container: "@Published selection changes"
    Container->>Container: .id(selection) rebuilds detail pane
    Container->>Scaffold: mount pane with .settingsHighlightedItem env
    Scaffold->>Scaffold: onAppear → Task: sleep 80ms → scrollTo(item)
    Scaffold->>Scaffold: repair: sleep 350ms → scrollTo(item)
    Scaffold-->>Row: "env settingsHighlightedItem = item"
    Row->>Row: "isHighlighted = true → pulse animation"
    Note over Nav,Row: after 2.4s
    Nav->>Nav: "highlightedItem = nil"
    Row->>Row: "isHighlighted = false → fade out"

    User->>Container: Cmd-F
    Container->>Nav: requestSearchFocus()
    Nav->>Nav: "open(.home) + pendingSearchFocus = true"
    Nav-->>Home: "@Published pendingSearchFocus"
    Home->>Home: "onChange(initial:true) → isSearchFocused = true"
    Home->>Nav: consumeSearchFocusRequest()
Loading

Fix All in Codex Fix All in Claude Code

Reviews (2): Last reviewed commit: "refactor(settings): address review on th..." | Re-trigger Greptile

… a tinted sidebar

The Settings window gets a designed landing surface and a search that lands
on the exact control:

- Home is now a composed page: identity hero, a prominent search field over
  the whole catalog, status cards (global toggle, engine and model,
  permissions) and tinted quick links, with the feature demos and a one-line
  footer below.
- Search relevance moves to a pure SettingsSearchRanker (tiered
  prefix/word/substring/subsequence scoring, multi-token queries, reverse
  prefix for plurals, diacritic folding). Every catalog item gains a
  searchable one-line summary, and results render as ranked rich rows.
- Picking a result reveals the setting: the pane opens, scrolls to the row,
  and pulses it. Every indexed setting carries a .settingsItem anchor.
- Sidebar rows get System Settings-style tinted icon tiles and spacing-only
  group clusters. Cmd-F focuses the Home search from any pane.
- Declutter: the Ko-fi pitch leaves General (it lives on Home and About),
  permission rows gain real status iconography, the window title follows the
  pane via navigationTitle instead of racing NSWindow.title writes.
- New -cotabby-open-settings launch argument opens the Settings window at
  startup so UI work on it can be exercised by tooling.
Comment thread Cotabby/UI/Settings/SettingsContainerView.swift Outdated
Comment thread Cotabby/UI/Settings/Components/SettingsPaneScaffold.swift
Comment thread Cotabby/UI/Settings/SettingsSidebarView.swift
Comment thread Cotabby/UI/Settings/Panes/HomePaneView.swift
- Remove the dead syncWindowTitle helper (navigationTitle owns the window
  title now) and the AppKit import it required.
- Repair-pass the reveal scroll: a second staggered scrollTo covers slow
  layout instead of trusting one 80 ms guess.
- Sidebar Return key opens from the same computed results the list renders.
- Share the display-version formatting between the sidebar header and the
  Home hero via Bundle.cotabbyDisplayVersion.
@FuJacob FuJacob merged commit 403eb7d into main Jun 12, 2026
4 checks passed
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