Skip to content

fix: sidebar not refreshing after creating or dropping tables#354

Merged
datlechin merged 6 commits intomainfrom
fix/sidebar-refresh-stale-cache
Mar 17, 2026
Merged

fix: sidebar not refreshing after creating or dropping tables#354
datlechin merged 6 commits intomainfrom
fix/sidebar-refresh-stale-cache

Conversation

@datlechin
Copy link
Owner

@datlechin datlechin commented Mar 17, 2026

Summary

  • Fix sidebar not updating after CREATE/DROP TABLE or manual refresh
  • LiveTableFetcher.fetchTables() always returned cached data from SQLSchemaProvider — refresh never bypassed the cache
  • Add force: Bool parameter to TableFetcher protocol; when force: true, bypass cache and fetch fresh data from the driver
  • Add targeted invalidateTables() to SQLSchemaProvider that clears only the table list (preserves column cache for autocomplete)
  • reloadSidebar() now invalidates table cache before calling forceLoadTables()
  • Also clears hardcoded PROVISIONING_PROFILE_SPECIFIER that conflicted with automatic signing for local development

Test plan

  • Connect to a database, verify sidebar loads tables
  • CREATE a new table via query editor, verify it appears in sidebar
  • DROP a table via query editor, verify it disappears from sidebar
  • Click the refresh button, verify sidebar updates
  • Verify autocomplete still works after refresh (column cache preserved)
  • 2 new unit tests: force bypass cache, forced fetch updates schema provider
  • Existing unit tests pass with updated fetchTables(force:) signature

Summary by CodeRabbit

  • Bug Fixes
    • Fixed sidebar not refreshing after creating or dropping tables
    • Fixed database disconnection when dropping a table with its tab active

@coderabbitai
Copy link

coderabbitai bot commented Mar 17, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 41326dde-12da-444b-85a9-415d904219e6

📥 Commits

Reviewing files that changed from the base of the PR and between f5247a4 and df7bf14.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • TablePro/Core/Autocomplete/SQLSchemaProvider.swift
  • TablePro/ViewModels/SidebarViewModel.swift
  • TablePro/Views/Main/Extensions/MainContentCoordinator+SaveChanges.swift
  • TablePro/Views/Main/MainContentCoordinator.swift
  • TableProTests/ViewModels/LiveTableFetcherTests.swift
  • TableProTests/ViewModels/SidebarViewModelTests.swift
  • TableProTests/Views/Main/CoordinatorReloadSidebarTests.swift
  • TableProTests/Views/SwitchDatabaseTests.swift

📝 Walkthrough

Walkthrough

This PR introduces a cache-invalidation mechanism for database tables and improves tab management. It adds methods to SQLSchemaProvider for managing cached tables, introduces a force parameter to table fetching to bypass cache when needed, updates the sidebar view model to support forced refreshes, modifies the coordinator's reload logic to invalidate the cache before refreshing, and changes tab-closing behavior to remove only affected tabs when tables are deleted.

Changes

Cohort / File(s) Summary
Schema Provider Cache Management
TablePro/Core/Autocomplete/SQLSchemaProvider.swift
Added three new methods: invalidateTables() clears cached tables, updateTables(_ newTables:) replaces the cache, and fetchFreshTables() fetches fresh tables from the driver and updates cache.
Table Fetching with Force Parameter
TablePro/ViewModels/SidebarViewModel.swift
Introduced force: Bool parameter to TableFetcher protocol and LiveTableFetcher. When force is true, bypasses schema provider cache and fetches fresh data from driver; when false, uses cached data if available. Updated SidebarViewModel methods to propagate the force flag.
Coordinator Reload Logic
TablePro/Views/Main/MainContentCoordinator.swift
Changed reloadSidebar() to asynchronously invalidate schema provider cache before calling forceLoadTables(), ensuring cache is cleared before sidebar refresh.
Tab Management on Save
TablePro/Views/Main/Extensions/MainContentCoordinator+SaveChanges.swift
Replaced window-closing behavior with selective tab removal when tables are deleted. Now only removes tabs corresponding to deleted tables and selects a neighboring tab if available.
Test Updates
TableProTests/ViewModels/LiveTableFetcherTests.swift, TableProTests/ViewModels/SidebarViewModelTests.swift, TableProTests/Views/Main/CoordinatorReloadSidebarTests.swift, TableProTests/Views/SwitchDatabaseTests.swift
Updated all MockTableFetcher and test implementations to include force: Bool parameter in fetchTables(). Added new tests for force-refresh scenarios and force counter tracking.
Changelog
CHANGELOG.md
Added two fixed entries for sidebar refresh after table operations and table disconnection issues.

Sequence Diagram(s)

sequenceDiagram
    participant Coordinator as MainContentCoordinator
    participant ViewModel as SidebarViewModel
    participant Fetcher as LiveTableFetcher
    participant Provider as SQLSchemaProvider
    participant Driver as Database Driver

    Coordinator->>Coordinator: reloadSidebar()
    Coordinator->>Provider: invalidateTables()
    Provider->>Provider: clear cached tables
    Coordinator->>ViewModel: forceLoadTables()
    ViewModel->>ViewModel: loadTables(force: true)
    ViewModel->>Fetcher: fetchTables(force: true)
    Fetcher->>Provider: fetchFreshTables()
    Provider->>Driver: fetch fresh tables
    Driver-->>Provider: return tables
    Provider->>Provider: update cache
    Provider-->>Fetcher: return tables
    Fetcher->>Provider: updateTables(newTables)
    Fetcher-->>ViewModel: return tables
    ViewModel->>ViewModel: update sidebar state
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 Cache invalidation, it's said, is hard,
But now with invalidate and force, we're on guard!
Fresh tables bloom where stale ones did hide,
Tabs drop gracefully—no window genocide! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.39% 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 'fix: sidebar not refreshing after creating or dropping tables' directly aligns with the main objective to fix sidebar refresh issues after CREATE/DROP TABLE operations.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sidebar-refresh-stale-cache
📝 Coding Plan
  • Generate coding plan for human review comments

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: 2

🧹 Nitpick comments (2)
TablePro/Views/Main/MainContentCoordinator.swift (1)

315-319: Move this added coordinator behavior to a dedicated extension file.

Please place reloadSidebar() changes in something like MainContentCoordinator+SidebarRefresh.swift instead of expanding the main coordinator file.

Based on learnings: Applies to TablePro/Views/Main/**/*.swift : When adding coordinator functionality, add a new extension file (e.g., +Alerts, +Filtering, +Pagination) rather than growing the main MainContentCoordinator file.

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

In `@TablePro/Views/Main/MainContentCoordinator.swift` around lines 315 - 319, The
reloadSidebar() method added to MainContentCoordinator should be moved into a
dedicated extension file to avoid growing the main coordinator; create a new
file named something like MainContentCoordinator+SidebarRefresh.swift, add an
extension on MainContentCoordinator containing reloadSidebar(), and ensure it
calls schemaProvider.invalidateTables() and sidebarViewModel?.forceLoadTables()
with the same access level and any required imports so behavior and visibility
remain unchanged.
TableProTests/ViewModels/LiveTableFetcherTests.swift (1)

142-194: Add a force-refresh test for the empty-schema edge case.

Current new tests cover non-empty fresh results only. Add a case where forced fetch returns [] to lock down single-fetch behavior and prevent accidental duplicate DB calls.

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

In `@TableProTests/ViewModels/LiveTableFetcherTests.swift` around lines 142 - 194,
Add a new test function (e.g., forceRefreshEmptySchemaEdgeCase) that mirrors the
existing force tests but covers the empty-schema edge case: create a
MockDatabaseDriver, pre-load SQLSchemaProvider with some initial table(s) via
provider.loadSchema(using:), then call await provider.invalidateTables(), set
mockDriver.tablesToReturn = [] (empty array), instantiate
LiveTableFetcher(connectionId: UUID(), schemaProvider: provider) and call try
await fetcher.fetchTables(force: true), assert the returned result is empty,
assert mockDriver.fetchTablesCallCount incremented by 1, and finally assert
await provider.getTables() returns an empty array; reference symbols:
LiveTableFetcher.fetchTables(force:), SQLSchemaProvider.loadSchema(using:),
SQLSchemaProvider.invalidateTables(), SQLSchemaProvider.getTables(),
MockDatabaseDriver.tablesToReturn and MockDatabaseDriver.fetchTablesCallCount.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@TablePro.xcodeproj/project.pbxproj`:
- Line 1761: The pbxproj currently hardcodes DEVELOPMENT_TEAM = D7HJ5TFYCU which
prevents others from building; replace the literal value with a neutral setting
(e.g. remove the hardcoded value or set DEVELOPMENT_TEAM = $(DEVELOPMENT_TEAM)
or an empty string) so team ID is not locked into the repo; update any matching
entries that set DEVELOPMENT_TEAM (the duplicated occurrences) so the project
uses a variable or no-team default and developers/CI can override it locally or
via environment.

In `@TablePro/Core/Autocomplete/SQLSchemaProvider.swift`:
- Around line 112-117: fetchFreshTables() currently returns [] both when
cachedDriver is nil and when the DB returned an empty schema, conflating “no
driver” with “empty result”; change fetchFreshTables() to return an optional
([TableInfo]?) and return nil when cachedDriver is nil (otherwise return the
fetched array, possibly empty), then update LiveTableFetcher.fetchTables(force:)
to treat any non-nil return (including []) as the definitive fresh result and
only trigger alternative logic when fetchFreshTables() returns nil; ensure all
call sites of fetchFreshTables() are updated to handle the optional result.

---

Nitpick comments:
In `@TablePro/Views/Main/MainContentCoordinator.swift`:
- Around line 315-319: The reloadSidebar() method added to
MainContentCoordinator should be moved into a dedicated extension file to avoid
growing the main coordinator; create a new file named something like
MainContentCoordinator+SidebarRefresh.swift, add an extension on
MainContentCoordinator containing reloadSidebar(), and ensure it calls
schemaProvider.invalidateTables() and sidebarViewModel?.forceLoadTables() with
the same access level and any required imports so behavior and visibility remain
unchanged.

In `@TableProTests/ViewModels/LiveTableFetcherTests.swift`:
- Around line 142-194: Add a new test function (e.g.,
forceRefreshEmptySchemaEdgeCase) that mirrors the existing force tests but
covers the empty-schema edge case: create a MockDatabaseDriver, pre-load
SQLSchemaProvider with some initial table(s) via provider.loadSchema(using:),
then call await provider.invalidateTables(), set mockDriver.tablesToReturn = []
(empty array), instantiate LiveTableFetcher(connectionId: UUID(),
schemaProvider: provider) and call try await fetcher.fetchTables(force: true),
assert the returned result is empty, assert mockDriver.fetchTablesCallCount
incremented by 1, and finally assert await provider.getTables() returns an empty
array; reference symbols: LiveTableFetcher.fetchTables(force:),
SQLSchemaProvider.loadSchema(using:), SQLSchemaProvider.invalidateTables(),
SQLSchemaProvider.getTables(), MockDatabaseDriver.tablesToReturn and
MockDatabaseDriver.fetchTablesCallCount.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 26dd2956-166c-478e-af1e-6ab23f4460ed

📥 Commits

Reviewing files that changed from the base of the PR and between e7b6647 and 837cf3b.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • TablePro.xcodeproj/project.pbxproj
  • TablePro/Core/Autocomplete/SQLSchemaProvider.swift
  • TablePro/ViewModels/SidebarViewModel.swift
  • TablePro/Views/Main/MainContentCoordinator.swift
  • TableProTests/ViewModels/LiveTableFetcherTests.swift
  • TableProTests/ViewModels/SidebarViewModelTests.swift
  • TableProTests/Views/Main/CoordinatorReloadSidebarTests.swift
  • TableProTests/Views/SwitchDatabaseTests.swift

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@TablePro/ViewModels/SidebarViewModel.swift`:
- Around line 163-168: The cancel flow in forceLoadTables cancels loadTask which
can cause the underlying loadTables/loadTablesAsync error handling to treat Task
cancellations as real errors and set errorMessage; update the error handling in
loadTables (and any await of loadTablesAsync) to explicitly ignore
CancellationError/Task.isCancelled (or check
Task.currentPriority/Task.isCancelled) and return without setting isLoading or
errorMessage, and ensure forceLoadTables still clears loadTask and resets
isLoading before calling loadTables(force: true) so cancellations do not surface
spurious user-facing errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 32245f26-3d21-43be-ac82-fbd257146e31

📥 Commits

Reviewing files that changed from the base of the PR and between 837cf3b and d942687.

📒 Files selected for processing (4)
  • TablePro/Core/Autocomplete/SQLSchemaProvider.swift
  • TablePro/ViewModels/SidebarViewModel.swift
  • TablePro/Views/Main/MainContentCoordinator.swift
  • TableProTests/ViewModels/LiveTableFetcherTests.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • TablePro/Views/Main/MainContentCoordinator.swift

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@TablePro/Views/Main/Extensions/MainContentCoordinator`+SaveChanges.swift:
- Around line 236-242: The current logic always resets tabManager.selectedTabId
to the first remaining tab after removing tabs, which changes focus even when
the previous selection still exists; update the removal block around
tabIdsToRemove so it only picks a new selection when the existing
tabManager.selectedTabId is nil or was among the removed IDs (i.e. check whether
tabIdsToRemove.contains(tabManager.selectedTabId) or tabManager.selectedTabId ==
nil), otherwise leave tabManager.selectedTabId unchanged; reference
tabIdsToRemove, tabManager.tabs and tabManager.selectedTabId when applying this
conditional selection change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0b429e91-91fa-4d17-9aa6-14a621f34879

📥 Commits

Reviewing files that changed from the base of the PR and between d942687 and 7690484.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • TablePro/Views/Main/Extensions/MainContentCoordinator+SaveChanges.swift
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md

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.

🧹 Nitpick comments (1)
TablePro/Views/Main/Extensions/MainContentCoordinator+SaveChanges.swift (1)

12-12: Missing explicit access control on extension.

As per coding guidelines: "Always specify access control explicitly (private, internal, public) on extensions and types."

Suggested fix
-extension MainContentCoordinator {
+internal extension MainContentCoordinator {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TablePro/Views/Main/Extensions/MainContentCoordinator`+SaveChanges.swift at
line 12, The extension declaration "extension MainContentCoordinator" lacks an
explicit access modifier; update the declaration to include the correct access
control (for example "internal", "public" or "private") based on the
coordinator's intended visibility so it matches project guidelines—e.g. change
the line declaring the extension of MainContentCoordinator to explicitly specify
access control; ensure any types or members inside that extension do not
conflict with the chosen modifier.
🤖 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/Views/Main/Extensions/MainContentCoordinator`+SaveChanges.swift:
- Line 12: The extension declaration "extension MainContentCoordinator" lacks an
explicit access modifier; update the declaration to include the correct access
control (for example "internal", "public" or "private") based on the
coordinator's intended visibility so it matches project guidelines—e.g. change
the line declaring the extension of MainContentCoordinator to explicitly specify
access control; ensure any types or members inside that extension do not
conflict with the chosen modifier.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b639901d-f2ff-4c7a-a789-a4aaf2ff55b6

📥 Commits

Reviewing files that changed from the base of the PR and between 7690484 and f5247a4.

📒 Files selected for processing (2)
  • TablePro.xcodeproj/project.pbxproj
  • TablePro/Views/Main/Extensions/MainContentCoordinator+SaveChanges.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • TablePro.xcodeproj/project.pbxproj

@datlechin datlechin force-pushed the fix/sidebar-refresh-stale-cache branch from d47fec0 to df7bf14 Compare March 17, 2026 06:49
@datlechin datlechin merged commit eac44d1 into main Mar 17, 2026
2 of 3 checks passed
@datlechin datlechin deleted the fix/sidebar-refresh-stale-cache branch March 17, 2026 06:49
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