Skip to content

Fix tab reset and status bar on database hot-swap#904

Merged
malibio merged 3 commits into
mainfrom
feature/issue-900-fix-tab-reset-on-db-swap
Feb 23, 2026
Merged

Fix tab reset and status bar on database hot-swap#904
malibio merged 3 commits into
mainfrom
feature/issue-900-fix-tab-reset-on-db-swap

Conversation

@malibio

@malibio malibio commented Feb 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to PR #903 — fixes issues found during manual testing of database hot-swap:

  • Stale tab cleanup: clearAllTabs() removes all old tabs (they point to nodes in the old database), then opens a fresh Today tab — DateNodeViewer mounts from scratch and sets its own title
  • Auto-close stale tabs: BaseNodeViewer now calls onNodeNotFound when the referenced node doesn't exist after loading, and pane-content.svelte closes the tab. Handles stale persisted state, deleted nodes, wrong database. Date tabs are exempt (lazy creation).
  • Status bar feedback: Shows new database path for 5 seconds after swap via statusBar.success()
  • Status bar message stomping fix: Stale nodes poller was calling clearMessage() every 5s even with count=0, wiping other messages. Now only clears when transitioning from count > 0 to 0.
  • Success message duration: Increased from 3s to 5s

Files Changed

File Change
stores/navigation.ts Add clearAllTabs()
layout/app-shell.svelte Clear tabs + open Today, show db path, fix poller
layout/pane-content.svelte Wire onNodeNotFoundcloseTab
design/components/base-node-viewer.svelte Add onNodeNotFound callback
stores/status-bar.ts 3s → 5s success duration

Test plan

  • Switch database from Settings → all old tabs close, Today tab opens with correct title
  • Status bar shows new database path for ~5 seconds after swap
  • Persisted tabs pointing to deleted nodes auto-close on app restart
  • Date tabs (Today) do NOT auto-close (lazy node creation)
  • bun run test:all — no new failures (baseline: 3586 passed)

🤖 Generated with Claude Code

malibio and others added 2 commits February 23, 2026 17:12
- Clear all stale tabs instead of resetting to hardcoded "Daily Journal"
- Open fresh Today tab so DateNodeViewer sets its own title
- Show new database path in status bar after swap

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add onNodeNotFound callback to BaseNodeViewer — auto-closes tabs
  when the referenced node doesn't exist (stale persisted state,
  deleted nodes, wrong database)
- Fix stale nodes poller stomping on status bar messages by only
  clearing when transitioning from count > 0 to 0
- Increase success message display from 3s to 5s

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@malibio

malibio commented Feb 23, 2026

Copy link
Copy Markdown
Collaborator Author

Pragmatic Code Review -- PR #904

Review Type: Initial Review
Branch: feature/issue-900-fix-tab-reset-on-db-swap
Issue: #900 -- Reset frontend state on database hot-swap


Requirements Check (Issue #900 Acceptance Criteria)

# Criterion Status Notes
1 Flush pending writes before clearing state Already implemented in PR #903 (commit e0d1c5a), line visible in full app-shell handler
2 Clear SharedNodeStore node cache sharedNodeStore.clearAll() called
3 Clear ReactiveStructureTree structureTree.clear() called
4 Reset collections data and reload collectionsState.reset() + collectionsData.loadCollections()
5 Reset tab state to default Improved over original spec: uses clearAllTabs() + fresh addTab() instead of resetTabState() with hardcoded title
6 Reload app settings loadSettings() called
7 Clear status bar messages Improved: shows new database path via statusBar.success() instead of just clearing
8 Verify domain event forwarder emits from new DB Backend concern, already handled by PR #899

All acceptance criteria met.


Code Review Findings

No Critical Issues Found

The change is clean, well-scoped, and addresses a real set of UX issues. The two commits are logically separated (core swap fix vs. stale tab auto-close + poller fix).


Suggested Improvements

🟡 [Improvement] closeTab guard when it is the last tab in the last pane
File: /packages/desktop-app/src/lib/stores/navigation.ts, lines 396-401 (existing code, not changed in this PR)
File: /packages/desktop-app/src/lib/components/layout/pane-content.svelte, line 101

closeTab() silently refuses to close the last tab in the last pane (navigation.ts:398-401). If a user opens a single non-date tab pointing to a node that gets deleted (or a stale persisted tab on restart), onNodeNotFound fires closeTab(), which does nothing -- the user is stuck on a broken tab with no feedback.

This is not a regression from this PR (the edge case existed before), and the database hot-swap flow itself is safe because clearAllTabs() runs first and then a fresh Today tab is added. However, since this PR introduces the onNodeNotFound -> closeTab wiring, it is worth noting that the "last tab" guard could leave the user stranded. A future improvement could replace the stale last-tab content with the Today view instead of silently no-op-ing.

Verdict: Not a blocker. Document as a known limitation or future enhancement.


🟡 [Improvement] success() timer is not cancellable
File: /packages/desktop-app/src/lib/stores/status-bar.ts, lines 54-58

The setTimeout in success() is fire-and-forget. If two success messages are shown in rapid succession (e.g., database swap followed immediately by another operation), the first timer will clear the second message prematurely. For instance:

T=0s: statusBar.success("Database: /path/a")  → schedules clear at T=5s
T=2s: statusBar.success("Import complete")     → schedules clear at T=7s
T=5s: first timer fires → clears "Import complete" (too early)

A simple fix would be to store the timer ID and clear the previous one:

let successTimer: ReturnType<typeof setTimeout> | undefined;

success(message: string) {
  if (successTimer) clearTimeout(successTimer);
  update((state) => ({ ...state, message, type: 'success', progress: undefined }));
  successTimer = setTimeout(() => {
    update((state) => ({ ...state, message: '', type: 'info' }));
    successTimer = undefined;
  }, 5000);
},

Verdict: Low risk in practice for this PR since database swaps are infrequent, but the pattern is a latent bug. Worth fixing in a follow-up.


Nitpicks

Nit: /packages/desktop-app/src/lib/components/layout/app-shell.svelte, database-changed handler, line ~329:
The new Today tab is created with title: '' (empty string). This works because DateNodeViewer sets its own title on mount. However, there is a brief render frame where the tab bar may flash an empty title. Consider setting a reasonable placeholder like 'Today' for a smoother visual transition. Very minor.

Nit: /packages/desktop-app/src/lib/design/components/base-node-viewer.svelte, line 325:
The log message "Node ${nodeId} not found — closing stale tab" uses an em dash. This is fine for display but inconsistent with other log messages in the codebase that use standard hyphens or colons. Purely cosmetic.


Architecture & Design Assessment

Positive observations:

  1. Two-step clear-then-add pattern (clearAllTabs() + addTab()) is superior to the original resetTabState() approach. It avoids hardcoding the "Daily Journal" title in the navigation store and lets DateNodeViewer own its title -- proper separation of concerns.

  2. Stale poller fix (tracking lastStaleCount to avoid unconditional clearMessage()) is a good catch. The previous code would stomp on unrelated status bar messages every 5 seconds. The fix correctly transitions: only clear when going from count > 0 to 0.

  3. onNodeNotFound callback pattern is well-designed -- optional, clean interface, properly guarded by isDestroyed check to avoid race conditions during component teardown. Date tabs are naturally exempt because DateNodeViewer does not forward this prop to BaseNodeViewer, allowing lazy node creation to proceed normally.

  4. Typed event (listen<string>('database-changed', ...)) is a small but meaningful improvement over the untyped version.


Final Recommendation

APPROVE

This PR is a clear net positive. It fixes real UX issues (stale tabs, status bar stomping, broken tab titles after database swap), the code is clean and well-structured, and there are no security, performance, or architectural concerns. The two improvement suggestions above are non-blocking and can be addressed in follow-up work.


Reviewed by: Principal Engineer Reviewer (Pragmatic Quality Framework)
Model: Claude Opus 4.6

@malibio malibio left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

APPROVE (submitted as comment — GitHub blocks self-approval). Net positive change. All acceptance criteria met. Clean implementation with good separation of concerns. Two non-blocking improvement suggestions noted in the detailed review comment above.

Clear previous timer before setting a new one so rapid successive
success() calls don't have the first timer clear the second message.

Skipped: closeTab last-tab guard — pre-existing behavior, not
introduced by this PR, and database swap always adds a fresh tab.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@malibio
malibio merged commit f8d27d3 into main Feb 23, 2026
@malibio
malibio deleted the feature/issue-900-fix-tab-reset-on-db-swap branch February 23, 2026 16:47
malibio added a commit that referenced this pull request Feb 26, 2026
* Fix tab reset on database hot-swap (#900)

- Clear all stale tabs instead of resetting to hardcoded "Daily Journal"
- Open fresh Today tab so DateNodeViewer sets its own title
- Show new database path in status bar after swap

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Auto-close stale tabs and fix status bar message timing

- Add onNodeNotFound callback to BaseNodeViewer — auto-closes tabs
  when the referenced node doesn't exist (stale persisted state,
  deleted nodes, wrong database)
- Fix stale nodes poller stomping on status bar messages by only
  clearing when transitioning from count > 0 to 0
- Increase success message display from 3s to 5s

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Address review: fix success() timer stomping

Clear previous timer before setting a new one so rapid successive
success() calls don't have the first timer clear the second message.

Skipped: closeTab last-tab guard — pre-existing behavior, not
introduced by this PR, and database swap always adds a fresh tab.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
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