Skip to content

feat: multi-select, bulk actions and reorder for welcome window#449

Merged
datlechin merged 4 commits intomainfrom
feat/welcome-window-multi-select
Mar 24, 2026
Merged

feat: multi-select, bulk actions and reorder for welcome window#449
datlechin merged 4 commits intomainfrom
feat/welcome-window-multi-select

Conversation

@datlechin
Copy link
Collaborator

@datlechin datlechin commented Mar 24, 2026

Summary

  • Multi-select connections in Welcome window (Cmd+Click, Shift+Click) with native macOS selection behavior
  • Bulk delete via ⌘⌫ keyboard shortcut with count-aware confirmation dialog
  • Adaptive context menu: single-select shows full actions (Connect, Edit, Duplicate, Copy URL, Move to Group, Delete); multi-select shows bulk actions (Connect N, Move to Group, Remove from Group, Delete N)
  • Move to Group / Remove from Group via context menu with checkmark for current group
  • Multi-connect: Return key opens each selected connection in its own window (fixed tabbingIdentifier so windows don't merge)
  • Reorder connections within groups and reorder groups via drag
  • Batch delete API in ConnectionStorage to avoid N load/save cycles
  • Fix: crash in moveUngroupedConnections caused by mismatch between ungroupedConnections (includes orphaned groupIds) and the move handler (only matched nil groupIds)

Test plan

  • Click connection → row highlights (selection works)
  • Cmd+Click multiple → multi-select, Shift+Click for range
  • ⌘⌫ with selection → confirmation dialog shows correct count
  • Right-click multi-selected → bulk context menu (Connect N, Move to Group, Delete N)
  • Right-click single/unselected → standard context menu
  • Move to Group → existing group moves connection(s)
  • Move to Group → New Group → creates group AND moves connection(s) into it
  • Remove from Group only shows for connections actually in a valid group
  • Return with multi-select → each connection opens in separate window (not merged as macOS tabs)
  • Ctrl+J/K clears multi-select, moves single item
  • Drag reorder within ungrouped connections
  • Drag reorder within a group's connections
  • Drag reorder groups
  • Search active → reorder disabled

Summary by CodeRabbit

  • New Features

    • Multi-connection selection with bulk actions: delete, connect, move to group (supports creating a new group during move)
    • Explicit reordering of connections within groups and reordering groups in the Welcome window
    • Keyboard shortcut (Cmd+Delete) to delete selected connections
  • Bug Fixes

    • Improved message for failed full-value loading
  • Documentation

    • Added keyboard shortcut entry for deleting selected connections

@coderabbitai
Copy link

coderabbitai bot commented Mar 24, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Replace single-selection with multi-selection in the Welcome window; add bulk connect/delete, deferred "Move to New Group", reorder groups and connections within the Welcome window; add batch delete API to storage; update localization, keyboard shortcut docs, CHANGELOG, and Xcode plug‑in build entries.

Changes

Cohort / File(s) Summary
Welcome window UI
TablePro/Views/Connection/WelcomeWindowView.swift
Convert single-selection to multi-selection state (selectedConnectionIds, connectionsToDelete), add pendingMoveToNewGroup, centralized context menus supporting bulk connect/move/delete, keyboard handling (Cmd+Return, Cmd+Delete), and drag reordering for groups and grouped connections.
Storage Layer
TablePro/Core/Storage/ConnectionStorage.swift
Add deleteConnections(_:) to batch-delete connections: mark each in SyncChangeTracker, persist filtered connection list to UserDefaults, and remove all related Keychain items (password, SSH, passphrase, TOTP, plugin secure fields).
Localization & Docs & Changelog
TablePro/Resources/Localizable.xcstrings, docs/features/keyboard-shortcuts.mdx, CHANGELOG.md
Add pluralized strings for bulk actions (Connect/Delete %lld Connections), new keys for group actions and errors, register Cmd+Delete shortcut in docs, and narrow CHANGELOG text to reordering within the Welcome window.
Xcode project
TablePro.xcodeproj/project.pbxproj
Reordered/updated PBXBuildFile, PBXContainerItemProxy, and PBXTargetDependency entries for ClickHouseDriver, MSSQLDriver, RedisDriver (IDs/positions changed) while preserving plug‑in inclusion and copy phase membership.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant UI as WelcomeWindowView
    participant Storage as ConnectionStorage
    participant Tracker as SyncChangeTracker
    participant Defaults as UserDefaults
    participant Keychain as Keychain

    User->>UI: Multi-select connections
    User->>UI: Trigger delete (Cmd+Delete / context menu)
    UI->>UI: Show count-based confirmation
    User->>UI: Confirm
    UI->>Storage: deleteConnections([selected])
    loop per connection
        Storage->>Tracker: mark connection ID deleted
        Storage->>Defaults: load & save filtered connections list
        Storage->>Keychain: delete password/SSH/passphrase/TOTP/plugin fields
    end
    Storage-->>UI: deletion complete
    UI->>UI: clear selection & refresh view
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I hopped through lists and held a view,

Picked many friends instead of just a few;
I moved the groups and cleared some keys,
Tidied rows with nimble ease,
A rabbit cheers — bulk actions, woohoo! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: multi-select, bulk actions and reorder for welcome window' directly and accurately describes the main changes across the codebase, highlighting the three core features implemented.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/welcome-window-multi-select

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
TablePro/Core/Storage/ConnectionStorage.swift (1)

111-111: Consider early return for empty input.

If connectionsToDelete is empty, the method still performs a load/save cycle. An early return would avoid unnecessary I/O.

🔧 Optional: Add guard for empty array
 func deleteConnections(_ connectionsToDelete: [DatabaseConnection]) {
+    guard !connectionsToDelete.isEmpty else { return }
     for conn in connectionsToDelete {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TablePro/Core/Storage/ConnectionStorage.swift` at line 111, The
deleteConnections(_ connectionsToDelete: [DatabaseConnection]) method should
return immediately when connectionsToDelete.isEmpty to avoid unnecessary I/O;
add a guard at the top of deleteConnections checking for an empty array and exit
early before performing the load/save cycle (the existing load/save calls, e.g.,
loadConnections()/saveConnections() or similar methods used in this function).
TablePro/Resources/Localizable.xcstrings (1)

3924-3925: Populate explicit localization payloads for newly added keys.

The new entries appear as empty objects in these hunks. Add at least source-language localizations (and existing supported locales) so these strings are tracked/translated explicitly instead of silently falling back.

Also applies to: 6532-6533, 9309-9310, 12284-12285, 17590-17591, 18133-18134, 23591-23592

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TablePro/Resources/Localizable.xcstrings` around lines 3924 - 3925, The new
localization entries (e.g. the key "Are you sure you want to delete %lld
connections? This cannot be undone.") were added as empty objects; update each
of these keys to include explicit localization payloads by adding a
source-language "localizations" dictionary and entries for existing supported
locales (matching the project’s other keys), e.g., include the source text under
the source locale and provide placeholder translations for other locales so the
strings are tracked for translation; repeat this for the other new keys
mentioned in the review so none remain empty objects.
TablePro/Views/Connection/WelcomeWindowView.swift (2)

947-968: Consider adding defensive bounds check for activeGroupIndices access.

The guard at lines 949-950 validates against active.count, while line 956 accesses activeGroupIndices[$0]. Although activeGroups is derived from filtering groups (so compactMap should always succeed), the index access could fail if there's ever a mismatch.

A defensive check would make this safer:

🛡️ Optional defensive fix
 let activeGroupIndices = active.compactMap { activeGroup in
     groups.firstIndex(where: { $0.id == activeGroup.id })
 }
+guard activeGroupIndices.count == active.count else { return }

 let globalSource = IndexSet(source.map { activeGroupIndices[$0] })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TablePro/Views/Connection/WelcomeWindowView.swift` around lines 947 - 968,
The moveGroups(from:to:) function currently maps activeGroups to
activeGroupIndices and then indexes into that array using source indices, which
can crash if there's a mismatch; update this method to defensively validate that
every index in source exists in activeGroupIndices before accessing (e.g.,
compute global indices with a compactMap that skips invalid mappings and/or
guard that source.max() < activeGroupIndices.count), and if any mapping fails
bail out early; keep use of groups.move(fromOffsets:toOffset:) and
groupStorage.saveGroups(groups) unchanged but ensure globalSource and
globalDestination are computed only from validated indices (referencing
moveGroups(from:to:), activeGroups, activeGroupIndices, groups.move,
groupStorage.saveGroups).

777-781: Consider guarding against redundant close calls during multi-connect.

Each connectToDatabase call executes NSApplication.shared.closeWindows(withId: "welcome"). After the first connection, the welcome window is already closed, making subsequent close calls redundant. Additionally, if an intermediate connection fails, handleConnectionFailure reopens the welcome window while remaining connections are still being processed.

Consider closing the welcome window once before the loop or tracking whether it's already closed:

♻️ Suggested refactor
 private func connectSelectedConnections() {
+    NSApplication.shared.closeWindows(withId: "welcome")
     for connection in selectedConnections {
-        connectToDatabase(connection)
+        connectToDatabaseWithoutClosingWelcome(connection)
     }
 }

Alternatively, extract the window management into a dedicated multi-connect flow that defers error handling until all connections are attempted.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TablePro/Views/Connection/WelcomeWindowView.swift` around lines 777 - 781,
connectSelectedConnections currently calls connectToDatabase for each
selectedConnections which causes NSApplication.shared.closeWindows(withId:
"welcome") to be invoked multiple times and allows handleConnectionFailure to
reopen the welcome window while other connects are still running; fix by moving
the welcome-window close out of the per-connection path and into the
multi-connect flow (call NSApplication.shared.closeWindows(withId: "welcome")
once before the for-loop in connectSelectedConnections or set a boolean flag
like isWelcomeClosed that connectToDatabase checks), and ensure
handleConnectionFailure does not unconditionally reopen the welcome window while
there are still pending connection attempts (defer reopening until all attempts
complete or check the flag).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@TablePro/Core/Storage/ConnectionStorage.swift`:
- Line 111: The deleteConnections(_ connectionsToDelete: [DatabaseConnection])
method should return immediately when connectionsToDelete.isEmpty to avoid
unnecessary I/O; add a guard at the top of deleteConnections checking for an
empty array and exit early before performing the load/save cycle (the existing
load/save calls, e.g., loadConnections()/saveConnections() or similar methods
used in this function).

In `@TablePro/Resources/Localizable.xcstrings`:
- Around line 3924-3925: The new localization entries (e.g. the key "Are you
sure you want to delete %lld connections? This cannot be undone.") were added as
empty objects; update each of these keys to include explicit localization
payloads by adding a source-language "localizations" dictionary and entries for
existing supported locales (matching the project’s other keys), e.g., include
the source text under the source locale and provide placeholder translations for
other locales so the strings are tracked for translation; repeat this for the
other new keys mentioned in the review so none remain empty objects.

In `@TablePro/Views/Connection/WelcomeWindowView.swift`:
- Around line 947-968: The moveGroups(from:to:) function currently maps
activeGroups to activeGroupIndices and then indexes into that array using source
indices, which can crash if there's a mismatch; update this method to
defensively validate that every index in source exists in activeGroupIndices
before accessing (e.g., compute global indices with a compactMap that skips
invalid mappings and/or guard that source.max() < activeGroupIndices.count), and
if any mapping fails bail out early; keep use of
groups.move(fromOffsets:toOffset:) and groupStorage.saveGroups(groups) unchanged
but ensure globalSource and globalDestination are computed only from validated
indices (referencing moveGroups(from:to:), activeGroups, activeGroupIndices,
groups.move, groupStorage.saveGroups).
- Around line 777-781: connectSelectedConnections currently calls
connectToDatabase for each selectedConnections which causes
NSApplication.shared.closeWindows(withId: "welcome") to be invoked multiple
times and allows handleConnectionFailure to reopen the welcome window while
other connects are still running; fix by moving the welcome-window close out of
the per-connection path and into the multi-connect flow (call
NSApplication.shared.closeWindows(withId: "welcome") once before the for-loop in
connectSelectedConnections or set a boolean flag like isWelcomeClosed that
connectToDatabase checks), and ensure handleConnectionFailure does not
unconditionally reopen the welcome window while there are still pending
connection attempts (defer reopening until all attempts complete or check the
flag).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 65b64428-b8a1-491d-88cb-0b4f0ac601dc

📥 Commits

Reviewing files that changed from the base of the PR and between 1a933f4 and 053971a.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • TablePro.xcodeproj/project.pbxproj
  • TablePro/Core/Storage/ConnectionStorage.swift
  • TablePro/Resources/Localizable.xcstrings
  • TablePro/Views/Connection/WelcomeWindowView.swift
  • docs/features/keyboard-shortcuts.mdx

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
TablePro/Resources/Localizable.xcstrings (1)

3924-3926: ⚠️ Potential issue | 🟡 Minor

Add plural variations for the confirmation message.

This count-based string uses %lld but lacks plural variations, unlike the properly pluralized "Connect %lld Connections" and "Delete %lld Connections" entries added in this PR. Without variations, the text will display "1 connections" (grammatically incorrect) for singular counts.

Proposed fix to add plural variations
     "Are you sure you want to delete %lld connections? This cannot be undone." : {
-
+      "localizations" : {
+        "en" : {
+          "variations" : {
+            "plural" : {
+              "one" : {
+                "stringUnit" : {
+                  "state" : "translated",
+                  "value" : "Are you sure you want to delete %lld connection? This cannot be undone."
+                }
+              },
+              "other" : {
+                "stringUnit" : {
+                  "state" : "translated",
+                  "value" : "Are you sure you want to delete %lld connections? This cannot be undone."
+                }
+              }
+            }
+          }
+        }
+      }
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TablePro/Resources/Localizable.xcstrings` around lines 3924 - 3926, Replace
the single-count entry for "Are you sure you want to delete %lld connections?
This cannot be undone." with pluralized variations to match the other pluralized
keys (e.g., provide singular and plural forms consistent with "Connect %lld
Connections" / "Delete %lld Connections"); add a dictionary with the appropriate
NSLocalizedString plural keys (one/other or the project's existing plural key
format) so the message renders "1 connection" for singular and "%lld
connections" for other counts, keeping the same placeholder (%lld) and exact
message text for each variation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@TablePro/Resources/Localizable.xcstrings`:
- Around line 3924-3926: Replace the single-count entry for "Are you sure you
want to delete %lld connections? This cannot be undone." with pluralized
variations to match the other pluralized keys (e.g., provide singular and plural
forms consistent with "Connect %lld Connections" / "Delete %lld Connections");
add a dictionary with the appropriate NSLocalizedString plural keys (one/other
or the project's existing plural key format) so the message renders "1
connection" for singular and "%lld connections" for other counts, keeping the
same placeholder (%lld) and exact message text for each variation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 343bd986-0891-46cf-870f-466e4eec5048

📥 Commits

Reviewing files that changed from the base of the PR and between 053971a and 07ce6d1.

📒 Files selected for processing (1)
  • TablePro/Resources/Localizable.xcstrings

@datlechin datlechin merged commit efddc9c into main Mar 24, 2026
2 of 3 checks passed
@datlechin datlechin deleted the feat/welcome-window-multi-select branch March 24, 2026 17:48
@coderabbitai coderabbitai bot mentioned this pull request Mar 24, 2026
6 tasks
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