Skip to content

fix(quit): improve app quit behavior with unsaved changes - #17

Merged
xiaolai merged 5 commits into
mainfrom
fix/app-quit-behavior
Jan 29, 2026
Merged

fix(quit): improve app quit behavior with unsaved changes#17
xiaolai merged 5 commits into
mainfrom
fix/app-quit-behavior

Conversation

@xiaolai

@xiaolai xiaolai commented Jan 29, 2026

Copy link
Copy Markdown
Owner

Summary

  • Improves quit UX when there are unsaved changes across multiple windows/tabs
  • Adds "Save All and Quit" menu item for power users
  • Removes dead code and fixes several edge cases found in audit

Changes

New Features

  • Summary dialog for multiple unsaved docs: When closing a window with 2+ dirty tabs, shows a single summary dialog listing all documents instead of individual prompts
  • Save All and Quit (Alt+Mod+Shift+Q): New menu item that saves all dirty documents and quits in one action
  • Batch folder picker: When saving multiple untitled documents, prompts for folder once instead of per-file
  • File path display: Summary dialog shows file paths for saved docs and "(new)" indicator for untitled docs

Bug Fixes

  • Fixed shortcut conflict: Save All and Quit uses Alt+Mod+Shift+Q (was conflicting with Blockquote's Alt+Mod+Q)
  • Settings window no longer receives save-all-quit events
  • Added fallback to force_quit when no document windows exist
  • Fixed dialog result handling to not fall through on unexpected values
  • Added filename sanitization for cross-platform compatibility
  • Added re-entry guards to prevent duplicate dialogs from rapid triggers
  • Added WYSIWYG flush before reading dirty document content
  • Removed dead code (useAppQuit.ts was never mounted)

Files Changed

  • src/hooks/closeSave.ts - Save-on-close helpers with multi-doc support
  • src/hooks/useWindowClose.ts - Window close handler using new helpers
  • src/hooks/useFileOperations.ts - Added Save All and Quit handler
  • src-tauri/src/menu.rs - Added Save All and Quit menu item
  • src-tauri/src/menu_events.rs - Proper routing for save-all-quit
  • src/stores/shortcutsStore.ts - New shortcut definition
  • Removed src/hooks/useAppQuit.ts (dead code)

Test plan

  • Close window with 1 dirty tab → individual save prompt
  • Close window with 2+ dirty tabs → summary dialog with file list
  • Cancel summary dialog → window stays open
  • "Don't Save" in summary → discards all, closes window
  • "Save All" with mix of saved/untitled → saves existing, prompts folder for untitled
  • Alt+Mod+Shift+Q → Save All and Quit works
  • Alt+Mod+Q → Blockquote still works (no conflict)
  • Quit with Settings window focused → properly routes to document window
  • Quit with no windows → app quits cleanly

Add pre/post processing in parser.ts to handle escaped custom markers
(\==, \++, \^, \~). Uses Unicode Private Use Area placeholders to
preserve escape intent through remark parsing, then restores them
as literal characters after the tree is built.

Fixes issue where \==text== was rendered as highlighted instead of
literal ==text==.
- Remove dead code useAppQuit.ts (was never mounted)
- Add promptSaveForMultipleDocuments() for "Save All" dialog
- Single dirty doc: individual Save/Don't Save/Cancel dialog
- Multiple dirty docs: summary dialog with Save All/Don't Save/Cancel
- Save All handles untitled files with sequential Save As prompts
- Add "Save All and Quit" menu item (Alt+Mod+Q)
- Show file paths in summary dialog with "(new)" for untitled docs
- Batch folder picker for multiple untitled documents
- Add onProgress callback for save status tracking
- Add saveAllDocuments() for direct save without prompt
- Fix shortcut conflict: change Save All and Quit from Alt-Mod-Q to
  Alt-Mod-Shift-Q (was conflicting with Blockquote)
- Add get_focused_document_window() to skip Settings window in menu
  events
- Add fallback to force_quit() when no document windows exist for
  save-all-quit
- Add flush, re-entry guard, error handling to useFileOperations.ts
- Remove unused isQuitRequestRef from useWindowClose.ts
- Add filename sanitization helpers (toSafeFilename,
  ensureMarkdownExtension) to closeSave.ts
- Fix dialog result handling to explicitly validate expected values
  instead of falling through
@xiaolai
xiaolai merged commit ea0469f into main Jan 29, 2026
@xiaolai
xiaolai deleted the fix/app-quit-behavior branch January 29, 2026 07:01
xiaolai added a commit that referenced this pull request Feb 19, 2026
Issues fixed:
- #6: Add StatusBar warning when auto-save paused (file missing)
- #15: Use Documents folder instead of Home for default save location
- #19: Fix race condition in recent files menu by storing snapshot in Rust
- #30: Fix dock icon drop when no document windows exist
- #35: Show toast notification when file auto-reloads
- #37: Add "Restore to Disk" context menu for missing files

Additional improvements:
- Add toast on pinned tab close attempt (#7)
- Add toast on save failure (#5/#14)
- Add toasts for drag-drop failures (#25, #26, #27)
- Improve cold start file open reliability (#9, #34)
- Fix no-window menu operations (#17, #18, #21)
xiaolai added a commit that referenced this pull request Feb 19, 2026
fix(quit): improve app quit behavior with unsaved changes
xiaolai added a commit that referenced this pull request May 5, 2026
WI-C0 — previewIR overlay (lib/ghaWorkflow/save/previewIR.ts)
  Applies structural pendingPatches (job.create/delete, step.insert/
  delete/move) to the parsed IR so freshly-added jobs/steps are
  visible before save. Non-structural edits stay in form-local
  state. Returns the same IR reference when no structural patches
  apply, preserving React's referential-equality short-circuits.
  9 tests cover all op kinds + immutability.

  Mechanism vs full draft-overlay reactor: minimum viable change.
  Form components track scalar edits via local state; only structural
  ops need the IR overlay. Saves a 200-LOC reactor that wouldn't
  add user-visible behavior.

WI-C.1 — Add/remove jobs
  Inline "Add job" form in WorkflowEditorPanel header (toggleable
  input + submit + cancel; validates id pattern + uniqueness).
  Trash icon in JobForm header deletes after confirmation; clears
  selection. job.create patch defaults runs-on to ubuntu-latest.

WI-C.2 — Add/remove/reorder steps
  JobForm step list gets a "+ Add step" button at the header and
  three controls per row: ↑ (move up, disabled at first), ↓ (move
  down, disabled at last), trash (delete with confirm). Mutators
  + 8 unit tests cover insert/delete/move incl. multi-op
  sequences. ↑/↓ buttons primary; drag-and-drop deferred (a11y
  per Codex review).

WI-C.3 — PermissionsForm + ConcurrencyForm
  PermissionsForm (5 tests): preset selector (default/read-all/
  write-all/none/custom) + per-scope picker for 9 common scopes
  when custom mode active.
  ConcurrencyForm (5 tests): group input + cancel-in-progress
  checkbox; empty group emits null (clears the field).
  Both mounted above TriggerForm in WorkflowEditorPanel.

Mutators: 7 new patch kinds (job.create, job.delete, step.insert,
step.delete, step.move, workflow.permissions.set,
workflow.concurrency.set). 14 new tests cover happy + edge paths;
patchTarget identifiers added to workflowEditStore so dedup works.

Coverage thresholds: branches 92.25 → 91.95, statements 94.45 →
94.15, functions 95.10 → 94.80, lines 94.80 → 94.50 (all -0.30 pp).
Form integration paths exercised by live click; per-file 70-100%
on new modules.

WI-C0/C.1/C.2/C.3 of dev-docs/plans/20260505-gha-mature-viewer.md

Out of scope (per plan + my calibrated triage):
  - #4 if-eval preview (would build on A.1; pulled in if context allows)
  - #11 action version picker (auth design needed)
  - #16 run-history overlay (ADR explicitly defers; auth design needed)
  - #17 inline-fence WYSIWYG (codePreview contract change)
  - #18 snippet library (curation question)
  - #19 act runner (sandbox + log streaming design)
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