Skip to content

refactor(sidebar): replace schema-grouped tree with picker footer (TablePlus pattern)#1308

Merged
datlechin merged 11 commits into
mainfrom
feat/schema-picker-footer
May 17, 2026
Merged

refactor(sidebar): replace schema-grouped tree with picker footer (TablePlus pattern)#1308
datlechin merged 11 commits into
mainfrom
feat/schema-picker-footer

Conversation

@datlechin
Copy link
Copy Markdown
Member

Layers on top of the merged #1300 to address bugs and replace the schema-grouped tree UX with a TablePlus-style schema picker footer. Original work on #1307 was opened against pre-#1300 main; this is the version rebased onto current main.

Why this refactor

#1300 added schemas-as-collapsible-sections in the sidebar tree. Real issues from testing:

  • Cross-schema SELECT was unqualified. Clicking `auth.audit_log_entries` produced `SELECT * FROM "audit_log_entries"` because `TableSelectionAction.navigate` decomposed TableInfo into `(tableName, isView)` and dropped the schema. PG's search_path didn't include `auth` → "relation does not exist".
  • TableInfo identity regression for ALL users, not just grouped-mode. `PluginDriverAdapter.fetchTables()` over-applied `schema: table.schema ?? resolvedSchema` → TableInfo.id changed from `users_TABLE` to `public.users_TABLE`, breaking selection sets, tab restoration, ER Diagram layouts.
  • Schema fan-out: 3 * N concurrent queries on a single connection (`runSchemaGroupedLoad` parallelized tables + procedures + functions per schema).
  • Tree of all schemas doesn't scale. Supabase has 8+ schemas (auth, storage, realtime, extensions, graphql, pgsodium, public, vault) — the tree becomes noise. MSSQL enterprise hits 50+.
  • Conflates two concepts: "browse schemas" (the tree implied) vs "switch active schema" (what users actually want).

What this PR does

Replaces the tree with a TablePlus-style picker footer:

  1. `SchemaPickerFooter` at sidebar bottom — native `NSPopUpButton(pullsDown: true)` via `NSViewRepresentable`. Real HIG bezel, chevron-on-right, opens upward as a real `NSMenu`. System schemas hidden by default; "Show System Schemas" toggle menu item. Footer hidden when ≤ 1 schema or driver doesn't support schemas.

  2. Sidebar shows ONE active schema at a time — flat by capability (Tables / Views / Procedures / Functions), same as today's flat mode.

  3. DatabaseSwitcher popover loses its schema mode — used to have a segmented control toggling Databases/Schemas. Databases-only now; schemas live in the picker footer.

  4. `AppEvents.currentSchemaChanged: PassthroughSubject<UUID, Never>` fired centrally in `DatabaseManager.switchSchema`. All four callers (MCP bridge, TabRouter, coordinator, picker footer) get consistent observer notification. `SchemaService.handleSchemaSwitch` listens and invalidates + reloads.

Fixes the bugs PR #1300 had:

  1. Cross-schema SELECT qualifies properly. `openTableTab(_ table: TableInfo)` canonical entry forwards to `addTableTab(name, schema:, ...)` → `QueryTab.buildBaseTableQuery(schemaName:)` → qualified SQL. Three click paths fixed: double-click (`onDoubleClick`), single-click (`TableSelectionAction.navigate(table: TableInfo)`), new-window (`EditorTabPayload` → `SessionStateFactory`).

  2. Tab title shows `schema.tableName` only when schema differs from the connection's default schema. `users` from `public` stays clean; `auth.audit_log_entries` shows the schema for disambiguation.

  3. TableInfo identity preserved. `PluginDriverAdapter.fetchTables()` doesn't apply schema fallback (preserves nil-passthrough). Only `fetchTables(schema:)` does.

  4. `TableSelectionAction.navigate(table: TableInfo)` carries the whole value instead of decomposing — eliminates the bug class.

Deletions

  • `SidebarSettings.displaySchemas` infrastructure (settings field, storage key, AppEvent, view, tests, docs) — ~200 lines
  • `SidebarViewModel.schemaExpanded`, `cachedTablesPerSchema`, `filteredTables(in:from:)`, `rebuildSchemaCachesIfNeeded` — ~80 lines
  • `SchemaService.runSchemaGroupedLoad`, `fetchTablesAcrossSchemas`, `fetchRoutinesAcrossSchemas`, `visibleSchemasForGroupedReload`, `handleSidebarSettingsChange` — ~120 lines
  • `SidebarPersistenceKey.schemaExpanded(...)` — no more per-schema UserDefaults keys leaking
  • `DatabaseSwitcherViewModel.Mode` + `isSchemaMode` + schema fetch/filter/commit branches — ~80 lines

Net: substantial code reduction. Architecture simplifies because there's now ONE schema model (current = active) instead of two (current + visible-in-tree).

CLAUDE.md compliance

  • No comments added in code paths (per CLAUDE.md mandate)
  • No em-dashes in user-facing strings
  • No banned words (seamless, robust, comprehensive, etc.)
  • `String(localized:)` used correctly throughout

Test plan

  • PostgreSQL: schema picker opens upward from sidebar bottom, native pulldown look
  • Picker title shows current schema
  • Select another schema → sidebar reloads with that schema's tables
  • System schemas hidden by default; "Show System Schemas" reveals them
  • Refresh menu item actually reloads (not empty state)
  • Click `audit_log_entries` in `auth` schema → SELECT qualified as `"auth"."audit_log_entries"`
  • Tab title for `public.users` is just `users`; for `auth.audit_log_entries` is `auth.audit_log_entries`
  • DatabaseSwitcher popover shows DATABASES only, no Schemas tab
  • MySQL/SQLite/Redis: picker footer hidden
  • PostgreSQL with only `public` schema: picker hidden
  • `swiftlint lint --strict` passes
  • `xcodebuild test` passes

@datlechin datlechin merged commit f6958ca into main May 17, 2026
2 checks passed
@datlechin datlechin deleted the feat/schema-picker-footer branch May 17, 2026 17:25
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