Files
opencode/specs/10-global-sync-message-part-cleanup.md
2026-01-27 15:25:07 -06:00

107 lines
3.1 KiB
Markdown

## GlobalSync message/part cleanup
Prevent stale message parts and per-session maps from accumulating
---
### Summary
`packages/app/src/context/global-sync.tsx` keeps per-directory child stores that include:
- `message: { [sessionID]: Message[] }`
- `part: { [messageID]: Part[] }`
Currently:
- `message.removed` removes the message from `message[sessionID]` but does not delete `part[messageID]`.
- `session.deleted` / archived sessions remove the session from the list but do not clear `message[...]`, `part[...]`, `session_diff[...]`, `todo[...]`, etc.
This can retain large arrays long after they are no longer reachable from UI.
This spec adds explicit cleanup on the relevant events without changing cache strategy or introducing new cross-module eviction utilities.
---
### Scoped files (parallel-safe)
- `packages/app/src/context/global-sync.tsx`
---
### Goals
- Delete `part[messageID]` when a message is removed
- Clear per-session maps when a session is deleted or archived
- Keep changes limited to event handling in `global-sync.tsx`
---
### Non-goals
- Implementing LRU/TTL eviction across sessions/directories (separate work)
- Changing how `sync.tsx` or layout prefetch populates message caches
---
### Current state
- Message list and part map can diverge over time.
- Deleting/archiving sessions does not reclaim memory for message history, parts, diffs, todos, permissions, questions.
---
### Proposed approach
Add small helper functions inside `createGlobalSync()` to keep event handler readable:
- `purgeMessageParts(setStore, messageID)`
- `purgeSessionData(store, setStore, sessionID)`
- delete `message[sessionID]`
- for each known message in that list, delete `part[messageID]`
- delete `session_diff[sessionID]`, `todo[sessionID]`, `permission[sessionID]`, `question[sessionID]`, `session_status[sessionID]`
Then wire these into the existing event switch:
- `message.removed`: after removing from `message[sessionID]`, also delete `part[messageID]`
- `session.updated` when `time.archived` is set: call `purgeSessionData(...)`
- `session.deleted`: call `purgeSessionData(...)`
Notes:
- Use `setStore(produce(...))` to `delete` object keys safely.
- Purge should be idempotent (safe if called when keys are missing).
---
### Implementation steps
1. Add purge helpers near the event listener in `global-sync.tsx`
2. Update event handlers
- `message.removed`
- `session.updated` (archived path)
- `session.deleted`
3. (Optional) Tighten `message.part.removed`
- If a part list becomes empty after removal, optionally delete `part[messageID]` as well.
---
### Acceptance criteria
- After a `message.removed` event, `store.part[messageID]` is `undefined`
- After `session.deleted` or archive, all per-session maps for that `sessionID` are removed
- No runtime errors when purging sessions/messages that were never hydrated
---
### Validation plan
- Manual:
- Load a session with many messages
- Trigger message delete/remove events (or simulate by calling handlers in dev)
- Confirm the associated `part` entries are removed
- Delete/archive a session and confirm globalSync store no longer holds its message/part data