Skip to content

refactor: rebuild Welcome as Xcode-style launcher with HIG-correct primitives#1055

Merged
datlechin merged 8 commits into
mainfrom
refactor/welcome-launcher-hig
May 7, 2026
Merged

refactor: rebuild Welcome as Xcode-style launcher with HIG-correct primitives#1055
datlechin merged 8 commits into
mainfrom
refactor/welcome-launcher-hig

Conversation

@datlechin

Copy link
Copy Markdown
Member

Why this exists

The Welcome window's runtime behavior is launcher-shaped: open the window, pick a connection, the connection opens in its own window, the launcher is dismissed/backgrounded. Apple's documented analogue is Xcode's Welcome window. PR #1052 picked the Mail/Notes-style NavigationSplitView + toolbar-search frame, which fixed the white-search-on-vibrancy bug correctly using documented APIs but forced a "sidebar isn't really a sidebar" mismatch (Apple HIG: sidebar = navigation; the Welcome left column is branding + a primary CTA).

This PR reverses the structural choice while keeping the API hygiene from #1052 and folds in audit findings (drop DoubleClickDetector, drop per-row .contextMenu, single sheet path for license activation, search debounce, single filter source, localization gaps, comments).

Architecture

  • HStack { WelcomeActionsPanel, Divider, ConnectionsPanel }. No NavigationSplitView. No structural sidebar.
  • Window: 820x500, .windowResizability(.contentSize), .windowStyle(.hiddenTitleBar). Launcher exception per HIG Launching page.
  • Actions panel (left, 240pt, .regularMaterial): app icon + version + license + three action buttons (Create Connection prominent, Try Sample, Import Connections) + sync indicator + keyboard hints. Localized version line and Check-for-Updates link.
  • Connections panel (right, opaque controlBackgroundColor): inline header with + / "New Group" buttons + NativeSearchField + connection list. Search field renders correctly because the panel background is opaque, not vibrancy.

HIG-correct primitives

Concern API
Single-purpose launcher window Window scene + .windowResizability(.contentSize)
Search field NSSearchField via NativeSearchField wrapper, on opaque background
Cmd-F focus .onKeyPress(characters: .init(charactersIn: "f")) on connections panel sets focus = .search
Double-click + context menu contextMenu(forSelectionType: UUID.self, menu:, primaryAction:) on the List, identical pattern to FavoritesTabView.swift:145
Multi-select context menu Selection set is passed to the menu closure, which dispatches to single/multi/empty branches
License activation One sheet path through vm.activeSheet = .activation; SyncStatusIndicator accepts a callback
Search debounce 150ms Task cancellation; immediate rebuild on entering/leaving search
Hover affordance for "Activate License" .buttonStyle(.link) (system link cursor) instead of custom NSCursor.push/pop

Audit cleanups

  • Drop DoubleClickDetector from welcome surfaces (WelcomeConnectionRow.swift, linked-folder row).
  • Drop per-row .contextMenu in TreeRowsView. Replaced by container-level contextMenu(forSelectionType:).
  • Drop vm.filteredConnections (dual filter source); empty-state checks vm.treeItems.isEmpty && vm.linkedConnections.isEmpty.
  • Drop searchText.didSet { rebuildTree() } synchronous path; replaced with debounced scheduleRebuildTree.
  • Drop SyncStatusIndicator's local .sheet. Routes through callback.
  • Drop three commented blocks: SyncStatusIndicator.swift:5-6 header, WelcomeConnectionRow.swift:108-113 doc, WelcomeViewModel.swift:156-157 planning note.
  • Wrap unlocalized strings: Version %@, Check for Updates..., Pro, Activate License, New, Settings.
  • Replace License expired — sync paused with License expired, sync paused (em-dash removed per CLAUDE.md style).
  • Extract hasGroups computed in TreeRowsView to replace inline if case .group = $0 predicate.

Out of scope

  • Other DoubleClickDetector callers (SidebarView.swift:196, DatabaseSwitcherSheet.swift:254, DatabaseTypeChooserSheet.swift:78, QuickSwitcherView.swift:171). Each warrants its own selection-type-driven refactor in a follow-up.
  • "Recents" section. DatabaseConnection has no lastUsedAt; data-model change first.
  • WindowChromeConfigurator / WindowOpenerBridge NSViewRepresentable bridges. SwiftUI has no first-class API for hiding individual standard window buttons; these are unavoidable.

CHANGELOG

No entry. The Welcome SwiftUI scene rework is already in [Unreleased] Changed; this PR refines that unreleased work, which CLAUDE.md says not to double-log. Same logic as #1052.

Source material

Test plan

  • Welcome opens at 820x500, non-resizable, no title bar, no sidebar toggle button.
  • Left (actions) panel shows three buttons; Create is prominent, Sample and Import are bordered.
  • Right (connections) panel: + and "New Group" inline buttons, search field renders without white box bezel.
  • Cmd-F focuses the search field. Esc clears.
  • Type to filter; debounced (~150ms).
  • Single-click selects, double-click connects (via primaryAction).
  • Right-click on selected row in a multi-selection shows the multi-selection menu.
  • Right-click on an unselected row shows the single-row menu (collapsed selection).
  • Right-click on empty space in the list shows the new-connection menu.
  • Empty state shows "Try Sample Database" CTA.
  • License activation works from both the version-line link and the sync indicator.
  • Linked folder rows still connect via the same primaryAction.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@datlechin datlechin merged commit f1d4061 into main May 7, 2026
2 checks passed
@datlechin datlechin deleted the refactor/welcome-launcher-hig branch May 7, 2026 01:57
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