Add autosave, crash recovery, and project history - #1602
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis change adds IndexedDB project snapshots, debounced autosave, crash recovery, and a Project History interface. It also adds undoable whole-project restoration and connects history actions to the desktop shell. ChangesProject history lifecycle
Estimated code review effort: 4 (Complex) | ~45 minutes Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
🔍 Cloudflare PR preview
|
🔍 GitHub Pages PR preview
|
There was a problem hiding this comment.
🟡 Not ready to approve
Snapshot restore currently clears undo/redo history (so it isn’t undoable as described) and the history/recovery init path needs safer handling of IndexedDB/localStorage failures.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
This PR adds a client-side autosave + recovery mechanism and a “Project → History” UI entry, integrating it into the GeoLibre desktop/web shell via a new IndexedDB-backed snapshot store and a React hook that debounces writes and exposes restore/discard actions.
Changes:
- Add an IndexedDB “project history” ring buffer store with size/count caps and deduping.
- Introduce
useProjectHistoryto autosave debounced snapshots, detect unclean sessions, and support recovery flows. - Add “History…” to the Project menu plus new History/Recovery dialogs and i18n strings.
File summaries
| File | Description |
|---|---|
| apps/geolibre-desktop/src/lib/ui-profile.ts | Adds the project.history menu catalog entry. |
| apps/geolibre-desktop/src/lib/project-history-store.ts | Implements IndexedDB persistence, snapshot retention limits, and CRUD helpers. |
| apps/geolibre-desktop/src/i18n/locales/en.json | Adds strings for Project History + recovery prompt and the menu label. |
| apps/geolibre-desktop/src/hooks/useProjectHistory.ts | Autosave debounce, recovery detection, and restore/discard logic wiring to the store. |
| apps/geolibre-desktop/src/components/layout/TopToolbar.tsx | Threads a new onOpenProjectHistory callback into the toolbar → project menu. |
| apps/geolibre-desktop/src/components/layout/toolbar/ProjectMenu.tsx | Adds the Project → History menu item and handler plumbing. |
| apps/geolibre-desktop/src/components/layout/ProjectRecoveryDialog.tsx | Adds a recovery prompt dialog for restoring/discarding the latest autosave. |
| apps/geolibre-desktop/src/components/layout/ProjectHistoryDialog.tsx | Adds a dialog to browse snapshots and restore one. |
| apps/geolibre-desktop/src/components/layout/DesktopShell.tsx | Mounts the dialogs and hooks them up to shell state and menu actions. |
Review details
Suppressed comments (2)
apps/geolibre-desktop/src/hooks/useProjectHistory.ts:75
- This fire-and-forget delete operation doesn’t handle failures. If
deleteProjectSnapshot()rejects, the rejection will be unhandled and the snapshot list may not refresh.
const discardRecovery = useCallback(() => {
if (recoverySnapshot) void deleteProjectSnapshot(recoverySnapshot.id).then(refresh);
setRecoverySnapshot(null);
apps/geolibre-desktop/src/hooks/useProjectHistory.ts:47
- This fire-and-forget clear operation doesn’t handle failures. If
clearProjectSnapshots()rejects (e.g. transient IndexedDB issue), the rejection will be unhandled andrefreshwon’t run.
if (state.projectPath === null && state.layers.length === 0) {
void clearProjectSnapshots().then(refresh);
}
- Files reviewed: 9/9 changed files
- Comments generated: 2
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
Code reviewBugs
Performance
Quality
Security
CLAUDE.md
|
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/geolibre-desktop/src/components/layout/DesktopShell.tsx`:
- Around line 2342-2352: Update useProjectHistory.restore, used by both
ProjectHistoryDialog and ProjectRecoveryDialog, to capture the current canonical
project state before calling loadProject, then register a single undoable
operation that can restore that pre-restore state. Preserve the existing
snapshot restoration behavior while ensuring both history and crash-recovery
restores support undo without losing unsaved work.
In `@apps/geolibre-desktop/src/components/layout/ProjectHistoryDialog.tsx`:
- Around line 53-56: Update the zoom formatting in the ProjectHistoryDialog
summary around the snapshot rendering to use the active locale’s number
formatter instead of zoom.toFixed(1), while preserving one decimal place and the
existing translation parameters.
In `@apps/geolibre-desktop/src/hooks/useProjectHistory.ts`:
- Around line 39-42: Update useProjectHistory and the project-writing flow to
stop deriving LAST_SAVE_KEY from the isDirty transition subscription. Introduce
or reuse an explicit markSaved() callback, and invoke it only after the project
file is successfully written; ensure newProject() and project loads do not
update LAST_SAVE_KEY.
In `@apps/geolibre-desktop/src/lib/project-history-store.ts`:
- Around line 69-72: Update addProjectSnapshot so the size-limit rejection when
size exceeds MAX_SNAPSHOT_BYTES surfaces an actionable reason instead of
silently returning false; at minimum log the rejection with the measured and
allowed sizes, or return a reason code that useProjectHistory.ts can distinguish
and present to the user while preserving normal duplicate-skip behavior.
- Around line 86-93: Update the retention loop that builds keepIds from the
newest-first retained list to break when MAX_SNAPSHOTS or MAX_TOTAL_BYTES would
be exceeded, rather than continue, so older entries cannot fill gaps after a
rejected snapshot.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 15b74572-72fb-460e-bcb5-b8df2343be6f
📒 Files selected for processing (9)
apps/geolibre-desktop/src/components/layout/DesktopShell.tsxapps/geolibre-desktop/src/components/layout/ProjectHistoryDialog.tsxapps/geolibre-desktop/src/components/layout/ProjectRecoveryDialog.tsxapps/geolibre-desktop/src/components/layout/TopToolbar.tsxapps/geolibre-desktop/src/components/layout/toolbar/ProjectMenu.tsxapps/geolibre-desktop/src/hooks/useProjectHistory.tsapps/geolibre-desktop/src/i18n/locales/en.jsonapps/geolibre-desktop/src/lib/project-history-store.tsapps/geolibre-desktop/src/lib/ui-profile.ts
Code reviewBugs
Security
Performance
Quality
CLAUDE.md
|
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/geolibre-desktop/src/components/layout/ProjectHistoryDialog.tsx (1)
64-66: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winKeep the history dialog open when restoration fails.
onOpenChange(false)runs after everyonRestore(snapshot)call.useProjectHistory.restorecatches parse and load errors and only logs them, so an invalid snapshot closes the dialog without an error message or retry path. MakeonRestorereturn a success result, or expose a restore error, and close the dialog only after success.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/geolibre-desktop/src/components/layout/ProjectHistoryDialog.tsx` around lines 64 - 66, Update the ProjectHistoryDialog restore flow so onRestore reports whether restoration succeeded, and call onOpenChange(false) only after a successful restore. Preserve the dialog’s open state when useProjectHistory.restore encounters parse or load errors, allowing the user to retry or see the existing error handling.apps/geolibre-desktop/src/lib/project-history-store.ts (1)
42-67: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winAdd an
onblockedhandler inopenDatabase.
indexedDB.open(DB_NAME, 2)runs a version-upgrade request. If an older database connection remains open in another tab/window,onblockedfires beforeonupgradeneeded; without a handler, this request can miss bothonsuccessandonerror. This blocks every awaited call site:listProjectSnapshots,addProjectSnapshot,deleteProjectSnapshot,clearProjectSnapshots,ProjectHistoryDialog, andNewProjectDialog.Add a handler that rejects the existing promise so callers can fail fast when the version upgrade is blocked.
🔒 Proposed fix
function openDatabase(): Promise<IDBDatabase> { return new Promise((resolve, reject) => { const request = indexedDB.open(DB_NAME, DB_VERSION); + request.onblocked = () => + reject(new Error("Could not open project history: blocked by another open connection.")); request.onupgradeneeded = () => {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/geolibre-desktop/src/lib/project-history-store.ts` around lines 42 - 67, Update openDatabase to register an onblocked handler on the IndexedDB open request and reject the existing promise with an appropriate error when the version upgrade is blocked, while preserving the current onsuccess, onerror, and upgrade behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/geolibre-desktop/src/hooks/useProjectHistory.ts`:
- Around line 81-99: Update the useProjectHistory hook’s restore callback and
return value to expose restore failures instead of only logging them. Add
restoreError state, set it with a user-facing message when parseProject or
loadProject throws, clear it before a new restore and after a successful
restore, and return it so recovery and Project History callers can display the
failure.
In `@packages/core/src/store.ts`:
- Around line 2208-2224: Wrap the loadProject calls in both the undo() and
redo() project-restore branches with try/catch, logging failures via
console.error consistently with useProjectHistory.ts restore(). Ensure thrown
loadProject errors are handled without escaping undo()/redo(), while preserving
the existing finally cleanup and history/state handoff behavior for successful
restores.
- Around line 2208-2224: Clear the module-level projectRestoreRedo pointer when
a new edit is committed after project-restore undo, before redo can reuse the
stale entry. Update the normal temporal commit path, not the loadProject restore
flow guarded by applyingProjectRestoreHistory, so the pointer is preserved
during undo restoration and invalidated only by the next user edit.
In `@tests/undo-redo.test.ts`:
- Around line 11-17: Add a regression case in the undo/redo tests covering a
restore undo followed by a normal store edit that adds an entry to
temporal.pastStates before redo(); assert that redo preserves the intervening
edit rather than applying the stale after snapshot. Reuse the existing restore
setup and helpers such as registerProjectRestoreHistory, undo, redo, and
useAppStore, while keeping the direct undo-then-redo test unchanged.
---
Outside diff comments:
In `@apps/geolibre-desktop/src/components/layout/ProjectHistoryDialog.tsx`:
- Around line 64-66: Update the ProjectHistoryDialog restore flow so onRestore
reports whether restoration succeeded, and call onOpenChange(false) only after a
successful restore. Preserve the dialog’s open state when
useProjectHistory.restore encounters parse or load errors, allowing the user to
retry or see the existing error handling.
In `@apps/geolibre-desktop/src/lib/project-history-store.ts`:
- Around line 42-67: Update openDatabase to register an onblocked handler on the
IndexedDB open request and reject the existing promise with an appropriate error
when the version upgrade is blocked, while preserving the current onsuccess,
onerror, and upgrade behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 54c2e177-aaae-452b-bab5-d4e14bc8b842
📒 Files selected for processing (12)
apps/geolibre-desktop/src/components/layout/DesktopShell.tsxapps/geolibre-desktop/src/components/layout/NewProjectDialog.tsxapps/geolibre-desktop/src/components/layout/ProjectHistoryDialog.tsxapps/geolibre-desktop/src/components/layout/ProjectRecoveryDialog.tsxapps/geolibre-desktop/src/components/layout/toolbar/EditMenu.tsxapps/geolibre-desktop/src/hooks/useProjectFileActions.tsapps/geolibre-desktop/src/hooks/useProjectHistory.tsapps/geolibre-desktop/src/lib/project-history-session.tsapps/geolibre-desktop/src/lib/project-history-store.tspackages/core/src/index.tspackages/core/src/store.tstests/undo-redo.test.ts
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/geolibre-desktop/src/lib/project-history-store.ts`:
- Around line 69-74: Update the IndexedDB request handlers in the
project-history initialization flow to track whether the Promise has already
settled. When `onblocked` rejects and `onsuccess` later fires, close
`request.result` instead of resolving; retain normal resolution when success
occurs first.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 3907010f-d897-4eaf-88e4-1bb6bd2f0446
📒 Files selected for processing (2)
apps/geolibre-desktop/src/hooks/useProjectHistory.tsapps/geolibre-desktop/src/lib/project-history-store.ts
|
All six inline comments posted successfully. Code reviewBugs
Security
Performance
Quality
CLAUDE.md
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/geolibre-desktop/src/components/layout/DesktopShell.tsx`:
- Around line 2346-2351: Update the DesktopShell dialog flow and
useProjectHistory integration so the shared restoreError is cleared whenever
either ProjectHistoryDialog or ProjectRecoveryDialog closes, discards, or is
dismissed. Add and pass a clearRestoreError callback, wiring it to every
relevant close/discard handler rather than leaving stale error state for the
other dialog.
In `@apps/geolibre-desktop/src/hooks/useProjectHistory.ts`:
- Around line 102-106: Update the restore error handling in useProjectHistory so
setRestoreError stores a translated, non-empty projectHistory message via t(),
rather than exposing error.message or using the literal fallback. Keep the
original exception available only to console.error, and ensure
ProjectHistoryDialog and ProjectRecoveryDialog render the translated
restoreError without duplicating fallback logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: c68879a7-a03a-4239-a16c-2ea19689ff49
📒 Files selected for processing (6)
apps/geolibre-desktop/src/components/layout/DesktopShell.tsxapps/geolibre-desktop/src/components/layout/ProjectHistoryDialog.tsxapps/geolibre-desktop/src/components/layout/ProjectRecoveryDialog.tsxapps/geolibre-desktop/src/hooks/useProjectHistory.tspackages/core/src/store.tstests/undo-redo.test.ts
| } | ||
| } | ||
|
|
||
| export async function clearProjectSnapshots(): Promise<void> { |
There was a problem hiding this comment.
Bug: clearProjectSnapshots unconditionally clears the entire snapshots/snapshot-metadata object stores — every project's autosave history, not just the one being abandoned. It's called from NewProjectDialog.tsx whenever the user creates a blank project or starts from a template, so opening "New Project" (or a template) wipes the Project History/recovery data for every other project the user has ever worked on in this browser/profile, including unrelated saved projects that still have valid autosaves. Since ProjectHistorySnapshot already carries a projectKey, this should instead delete only entries matching the current (soon-to-be-replaced) project key, e.g. a clearProjectSnapshots(projectKey: string) that filters listProjectSnapshotMetadata()/getAll() and deletes matching ids, mirroring how listProjectSnapshots(projectKey) already filters.
Confidence: medium-high — the intent stated in the PR description ("clear recovery history when starting a blank new project") is about the abandoned draft, not a global wipe, and the multi-project design elsewhere (per-key filtering in listProjectSnapshots/addProjectSnapshot) suggests this was meant to be scoped too.
| const entries = await listProjectSnapshots(); | ||
| setSnapshots(entries.filter((entry) => entry.projectKey === currentProjectKey())); | ||
| if (crashRecoveryEnabled) { | ||
| const previousSession = readProjectSessionState(); | ||
| const lastSave = readLastExplicitProjectSave(); | ||
| const latest = entries[0]; |
There was a problem hiding this comment.
Bug: entries here comes from listProjectSnapshots() called with no projectKey, so it contains snapshots for every project stored in IndexedDB, sorted by createdAt across all of them. latest = entries[0] is therefore the globally most-recent autosave, not necessarily one belonging to the project that's about to open (currentProjectKey(), used two lines above to filter snapshots). If another project was autosaved more recently than the current project's own last snapshot, the crash-recovery dialog will offer to restore a completely unrelated project's content — mislabeled as "GeoLibre did not close cleanly" for the project the user is currently in. Restoring it would then overwrite the current session with the wrong project's data.
This compounds with recordExplicitProjectSave/readLastExplicitProjectSave also being global (not per-project), so lastSave doesn't correctly gate this either.
Fix: filter entries to entry.projectKey === currentProjectKey() (like the snapshots filter right above) before taking entries[0].
Confidence: medium-high.
| const project = parseProject(content); | ||
| const contentHash = hashContent(content); | ||
| const existing = await listProjectSnapshotMetadata(); | ||
| if (existing[0]?.contentHash === contentHash) return "duplicate"; |
There was a problem hiding this comment.
Bug (minor): the duplicate check compares the new snapshot's hash against existing[0], the globally most-recently-created snapshot across all projects (since listProjectSnapshotMetadata() isn't filtered by projectKey). When more than one project is being autosaved (e.g. two tabs, or switching projects), this can miss real duplicates for the current project — if some other project autosaved more recently, an unchanged repeat save of this project's state won't be recognized as identical to its own last snapshot, so it gets written again anyway, consuming a slot in the shared 20-entry/50MB ring buffer and potentially evicting another project's older snapshot unnecessarily. The PR description promises "identical serialized states are skipped," which doesn't hold across concurrent/multi-project use.
Suggest comparing against the latest entry filtered to projectKey instead of existing[0].
Confidence: medium.
| ); | ||
| return "too-large"; | ||
| } | ||
| const project = parseProject(content); |
There was a problem hiding this comment.
Quality/perf nit: addProjectSnapshot re-parses the just-serialized content string back into a GeoLibreProject purely to read name/layers.length/basemapStyleUrl/mapView for the metadata record. The only caller (useProjectHistory.ts) already has the pre-serialization GeoLibreProject object from buildProjectSnapshot(...) before calling serializeProject(...) — this forces an extra parse of a potentially multi-MB JSON string on every autosave (debounced to at most once per 3s, so not hot-path-critical, but still avoidable). Consider accepting the already-built project object (or just the needed metadata fields) alongside the serialized string, instead of parsing it back out.
Confidence: low — purely a minor inefficiency, not a correctness issue.
Code reviewBugs
Security
Performance
Quality
CLAUDE.md
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/geolibre-desktop/src/hooks/useProjectHistory.ts (1)
50-58: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winScope the crash-recovery snapshot to the current project.
Line 51 filters
entriesbycurrentProjectKey()before showing them in the History list.latestat line 55 comes from the same, unfilteredentriesarray, sorted newest-first across every project. If the user worked on more than one project in the same browser session before an unclean shutdown,latestcan belong to a different project than the one currently loaded.If the recovery prompt is then accepted,
restore(latest)callsloadProject, replacing the currently open project's content with an unrelated project's autosave. The recovery dialog does show the snapshot'sname, but a user acting on the prompt without reading it closely can overwrite unrelated, unrecovered work.Filter
entriesbycurrentProjectKey()before pickinglatest, the same way line 51 already does for the visible list.🐛 Proposed fix
- const latest = entries[0]; + const latest = entries.find((entry) => entry.projectKey === currentProjectKey());🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/geolibre-desktop/src/hooks/useProjectHistory.ts` around lines 50 - 58, Scope crash recovery to the current project by filtering entries with currentProjectKey() before selecting latest in the recovery block. Reuse the project-filtered snapshots for both setSnapshots and latest, while preserving the existing newest-first and recovery eligibility checks.apps/geolibre-desktop/src/lib/project-history-store.ts (1)
110-124: 🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy liftAvoid loading full snapshot content just to list/filter by project.
listProjectSnapshotsreads every stored snapshot's fullcontentfield viagetAll()onSTORE_NAME(up to 10 MB each, 50 MB total), then filters and sorts in JavaScript.useProjectHistory.tscalls this function on hook initialization and again after every successful autosave (roughly every 3 seconds while the project stays dirty), so the entire ring buffer's content is deserialized into memory on a steady cadence, even when no history UI is visible.
listProjectSnapshotMetadata()already exists in this file and is used internally byaddProjectSnapshotfor the same kind of read (dedup/retention) without touchingcontent. Expose an equivalent metadata-only listing for the browsing/display path, and fetch fullcontentonly when a specific snapshot is restored.♻️ Suggested direction
-export async function listProjectSnapshots(projectKey?: string): Promise<ProjectHistorySnapshot[]> { - if (!available()) return []; - const db = await openDatabase(); - try { - const transaction = db.transaction(STORE_NAME, "readonly"); - const snapshots = await requestResult( - transaction.objectStore(STORE_NAME).getAll() as IDBRequest<ProjectHistorySnapshot[]>, - ); - return snapshots - .filter((snapshot) => projectKey === undefined || snapshot.projectKey === projectKey) - .sort((a, b) => b.createdAt.localeCompare(a.createdAt)); - } finally { - db.close(); - } -} +export async function listProjectSnapshotSummaries( + projectKey?: string, +): Promise<ProjectHistoryMetadata[]> { + const metadata = await listProjectSnapshotMetadata(); + return metadata.filter( + (entry) => projectKey === undefined || entry.projectKey === projectKey, + ); +}Then have
useProjectHistory.tsrender from the metadata list and load fullcontenton demand (e.g. agetProjectSnapshot(id)helper) only when the user restores a specific entry.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/geolibre-desktop/src/lib/project-history-store.ts` around lines 110 - 124, Change listProjectSnapshots to return metadata without deserializing snapshot content, reusing the existing listProjectSnapshotMetadata pattern for project filtering and newest-first sorting. Add or expose a getProjectSnapshot helper that retrieves full content by snapshot ID, then update useProjectHistory to render metadata and fetch the selected snapshot only during restore.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@apps/geolibre-desktop/src/hooks/useProjectHistory.ts`:
- Around line 50-58: Scope crash recovery to the current project by filtering
entries with currentProjectKey() before selecting latest in the recovery block.
Reuse the project-filtered snapshots for both setSnapshots and latest, while
preserving the existing newest-first and recovery eligibility checks.
In `@apps/geolibre-desktop/src/lib/project-history-store.ts`:
- Around line 110-124: Change listProjectSnapshots to return metadata without
deserializing snapshot content, reusing the existing listProjectSnapshotMetadata
pattern for project filtering and newest-first sorting. Add or expose a
getProjectSnapshot helper that retrieves full content by snapshot ID, then
update useProjectHistory to render metadata and fetch the selected snapshot only
during restore.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 186e15dc-639a-488c-babd-968f6435703c
📒 Files selected for processing (8)
apps/geolibre-desktop/src/components/layout/DesktopShell.tsxapps/geolibre-desktop/src/hooks/useProjectHistory.tsapps/geolibre-desktop/src/hooks/useUndoRedoShortcuts.tsapps/geolibre-desktop/src/i18n/locales/en.jsonapps/geolibre-desktop/src/lib/project-history-session.tsapps/geolibre-desktop/src/lib/project-history-store.tspackages/core/src/store.tstests/undo-redo.test.ts
Summary
Snapshots are capped at 10 MB each, 20 entries, and 50 MB total. Identical serialized states are skipped, and autosave never writes to the user project file.
Verification
npm run buildnpm run lint -- --quietnpm run test:frontend(4,629 passed, 1 skipped)pre-commit run --files ...Fixes #1522
Summary by CodeRabbit