Skip to content

feat(editor): warn on app exit when query editors have unsaved changes (#121)#135

Merged
NicolaasGrobler merged 3 commits into
mainfrom
fix/issue-121-unsaved-query-exit-warning
May 22, 2026
Merged

feat(editor): warn on app exit when query editors have unsaved changes (#121)#135
NicolaasGrobler merged 3 commits into
mainfrom
fix/issue-121-unsaved-query-exit-warning

Conversation

@NicolaasGrobler
Copy link
Copy Markdown
Collaborator

Closes #121.

Summary

Currently, closing the app window (X / Alt+F4 / Cmd+Q) with typed-but-unsaved query content silently discards the work. This adds an "Unsaved queries" confirm dialog that lists the dirty tabs by name and offers Discard / Cancel before letting the window die.

  • Dirty detection: new pure helper src/utils/editorDirty.ts with 8 Vitest cases covering untitled+empty (clean), untitled+content (dirty), saved+unmodified (clean), saved+modified (dirty), plus legacy-undefined originalQuery fallback and whitespace-sensitivity.
  • Per-tab state: added optional originalQuery: string to QueryTab (in src/components/layout/MainContent.tsx). Set when the tab is created in addNewTab and refreshed when the active tab is saved via Ctrl+S. Empty-tab default keeps blank tabs clean.
  • Close interception: new useEffect in MainContent lazy-imports @tauri-apps/api/window's getCurrentWindow().onCloseRequested(...), event.preventDefault()s when there are dirty tabs, and routes through the existing useConfirmDialog (already in the provider tree). Discard → window.destroy(). Cancel → no-op.
  • Dialog body: lists up to 5 dirty tab names with bullet markers; appends "…and N more" past that.

Design constraints (scope-check)

These are deliberately fixed for this PR — see my reply on #121:

  • Scope: only the unsaved-query case. Not unsaved connections, not unsaved settings, not running queries.
  • Multi-tab: if multiple tabs are dirty, the dialog lists them by name (or "Untitled" for unnamed) and Discard discards them all. Per-tab "Save" buttons are out of scope for this PR.
  • Untitled empty tabs: an untitled tab with empty content is NOT dirty. Only an untitled tab with content is dirty.
  • Saved query that was modified: dirty when current text != last loaded text.
  • No autosave hookup — autosave is a separate issue (Auto save #122), not touched here.

Test plan

Pure-function coverage runs in CI:

  • npm test (137/137 passing including 8 new editorDirty tests)
  • npx tsc --noEmit (clean)

Manual verification (couldn't run npm run tauri dev reliably in this worktree — maintainer please confirm in a local dev build):

  • Open app, leave the default empty tab → Alt+F4 / Cmd+Q closes immediately (no prompt).
  • Type "SELECT 1" in a fresh tab → Alt+F4 → "Unsaved queries" dialog appears listing that tab → Cancel keeps the app open.
  • Repeat → Discard → app closes.
  • Open a saved query, don't change it → Alt+F4 closes immediately.
  • Open a saved query, modify it → Alt+F4 prompts.
  • Save with Ctrl+S → prompt no longer appears on next Alt+F4.
  • Two dirty tabs + one clean tab → dialog body lists exactly the two dirty tabs.

Follow-ups (not in this PR)

  • Auto save #122 — autosave hookup. With originalQuery now snapshotted per tab, an autosave timer can update it on every persistence cycle and the prompt naturally degrades to "no autosaved tabs are dirty".

#121)

Closes #121.

Intercept window close (X / Alt+F4 / Cmd+Q) via
`getCurrentWindow().onCloseRequested` and, when any query tab is dirty,
preventDefault and show a confirm dialog listing the dirty tab names.
Discard → `window.destroy()`. Cancel → keep the app open.

Dirty detection is a new pure helper `src/utils/editorDirty.ts` keyed off
a per-tab `originalQuery` snapshot — set on tab creation and refreshed
on Ctrl+S save. Untitled+empty tabs stay clean; untitled tabs with
typed content are dirty; saved tabs are dirty iff text diverges.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
queryden Ready Ready Preview, Comment May 22, 2026 3:39pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

Warning

Rate limit exceeded

@NicolaasGrobler has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 6 minutes and 51 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4ad939c7-e0a9-4889-95b9-8aeb001fc346

📥 Commits

Reviewing files that changed from the base of the PR and between f5d41c9 and d510139.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • src/components/layout/MainContent.tsx
  • src/utils/editorDirty.test.ts
  • src/utils/editorDirty.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-121-unsaved-query-exit-warning

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.

@NicolaasGrobler NicolaasGrobler merged commit 2f88f88 into main May 22, 2026
10 checks passed
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.

Exiting app

1 participant