fix(sync): honor expectedRuntimeKey in archive actions

`useSessionUIStore.archiveSessions` declared an `options` parameter and
discarded it, so any caller passing a captured runtime key got a silent
no-op. The archive path also never rechecked the runtime, letting a
response produced by a previous runtime reconcile the live and global
session stores of the runtime the user had switched to.

Move the batch to a canonical `archiveSessions()` action, add an optional
`expectedRuntimeKey` to `archiveSession()`, `patchSessionMetadata()`, and
`cleanupReviewMetadataBeforeDelete()`, and recheck that key before every
store reconciliation. A guarded batch stops at the first observed runtime
change: server-confirmed sessions stay in `archivedIds` and every
unconfirmed ID is returned in `failedIds`, so existing partial-failure
feedback stays truthful. Callers that pass no key keep prior behavior.

Type the store option as `ArchiveSessionsOptions` instead of
`Record<string, unknown>`, since the loose type allowed the drop.
This commit is contained in:
Alexandre Reyes Martins
2026-08-02 11:55:02 +00:00
parent 7afec99f80
commit 2e8fc6e192
5 changed files with 207 additions and 15 deletions
+11 -1
View File
@@ -213,10 +213,20 @@ Examples of global-store updates performed in `session-actions.ts`:
- `createSession()` -> `upsertSession(session)`
- `updateSessionTitle()` -> `upsertSession(result.data)`
- `shareSession()` / `unshareSession()` -> `upsertSession(result.data)`
- `archiveSession()` -> waits for server confirmation, then upserts the archived session
- `archiveSession()` / `archiveSessions()` -> wait for server confirmation, then upsert each archived session
- `deleteSession()` -> waits for server confirmation or `404`, then removes the session and its persisted state
- `moveSessionToDirectory()` -> move the session between directory stores and update the global directory index
Archive callers whose confirmation spans asynchronous SDK calls may capture the
runtime key at operation start and pass it as `expectedRuntimeKey`. The guarded
action rechecks that key before every store reconciliation, so a response
produced by the previous runtime is rejected instead of mutating the current
runtime's live or global session state. A guarded batch stops at the first
observed runtime change: sessions the server already confirmed remain archived
and stay in `archivedIds`, while every ID not confirmed on the captured runtime
is returned in `failedIds` so existing partial-failure feedback stays truthful.
Callers that pass no runtime key keep the previous unguarded behavior.
## The golden rule
When creating a draft in `handleDirectoryEvent`, **only clone the state fields the event will mutate**. Never spread all fields eagerly.