Skip to content

feat: restore open tabs and active tab across sessions#224

Merged
fuzzzerd merged 1 commit into
masterfrom
fuzzz/session-restore
May 21, 2026
Merged

feat: restore open tabs and active tab across sessions#224
fuzzzerd merged 1 commit into
masterfrom
fuzzz/session-restore

Conversation

@fuzzzerd
Copy link
Copy Markdown
Owner

@fuzzzerd fuzzzerd commented May 21, 2026

Summary

Closing the app with tabs open and reopening it now lands the user back on the same tabs in the same order, with the previously active tab focused. Closing with no tabs open is unchanged. A user whose clips were deleted/renamed between sessions gets a clean empty editor with no error dialog.

Design

  • SessionState and TabRef records persist (folderPath, name) per open tab plus the active tab. (folderPath, name) mirrors ClipData's identity — the same pair the repository uses on disk.
  • SessionStateService reads and writes a single JSON file at %LocalAppData%/SharpFM/session.json. It mirrors PluginConfigService's shape (default-path / injectable-path ctors, NullLogger-friendly).
  • MainWindowViewModel.CaptureSessionState() snapshots the current tab strip. RestoreSessionState(state) resolves each TabRef against FileMakerClips and opens it via OpenAsPermanent; the saved active tab is set if it resolves.
  • App.axaml.cs calls Load + RestoreSessionState right after the VM constructor has finished loading the repository.
  • MainWindow.Closing calls Save(vm.CaptureSessionState()).

Failure handling

  • Save IO error: caught, logged, swallowed — never crashes app exit.
  • Load with missing file: returns SessionState.Empty.
  • Load with malformed JSON or read failure: caught, logged, returns Empty.
  • Load with valid-but-partial JSON (e.g. {}): System.Text.Json produces a SessionState with null OpenTabs; Load coerces to [] so the restore path can iterate safely.
  • Restore with missing clip: silently skipped per the AC.
  • Restore with missing active tab: leaves whichever was last successfully opened as active (the natural side-effect of OpenAsPermanent).
  • Restore with all clips missing: editor opens empty, no crash.

Known v1 limits (intentional, called out for review)

  • Renames invalidate the saved ref. A clip renamed between sessions won't restore — its tab is treated the same as "deleted." Fixing this needs a stable clip ID separate from name; out of scope here.
  • Cursor/scroll positions per tab — not persisted; explicit non-goal in the AC.
  • Tab dirty/unsaved state — not persisted. A user with dirty edits closes the app, reopens, sees the last-saved XML. Avoids accidentally restoring stale changes.

Tests

  • SessionStateServiceTests (7): empty-load, round-trip, null-active, directory creation, malformed-JSON tolerance, partial-JSON coercion, IO-failure swallowed.
  • SessionRestoreTests (8): empty capture, ordered capture w/ active, restore resolves, restored tabs are not preview, skips missing, all-missing leaves empty, missing-active leaves last-opened, full capture→restore round-trip across two VM instances.
  • Full suite 1468 green.

Manual smoke test plan

  • Open three clips, mark one active, close window, reopen — all three tabs back, same active.
  • Open one clip, delete it from disk while app is closed, reopen — clean empty editor, no dialog.
  • Hand-edit session.json to {} and reopen — clean empty editor.
  • Hand-edit session.json to invalid JSON and reopen — clean empty editor.

@github-actions
Copy link
Copy Markdown

Test Results

✔️ Tests 1469 / 1469 - passed in 18.2s
✔️ Coverage 79.56% - passed with 70% threshold
📏 15457 / 17887 lines covered 🌿 5244 / 8132 branches covered
🔍 click here for more details

✏️ updated for commit 56ba05c

@fuzzzerd fuzzzerd merged commit 34fa043 into master May 21, 2026
6 checks passed
@fuzzzerd fuzzzerd deleted the fuzzz/session-restore branch May 21, 2026 03:59
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