Skip to content

Persist and sync the message send-later queue across restarts and clients - #1062

Open
ijry wants to merge 2 commits into
spacering-net:mainfrom
ijry:feat/persist-sync-message-queue
Open

ijry wants to merge 2 commits into
spacering-net:mainfrom
ijry:feat/persist-sync-message-queue

Conversation

@ijry

@ijry ijry commented Oct 29, 2026 •

Copy link
Copy Markdown

Summary

The conversation "send later" queue (the message-queue panel backed by `useMessageQueue`) was held purely in React state. It was never persisted and never shared across clients, so:

  • Restarting the app lost the whole queue.
  • A second window or a web client started from an empty queue — the two never saw each other's items.

This PR makes the queue persisted (SQLite) and multi-client synced (the existing `EventEmitter` broadcast), while keeping the in-memory behavior for unbound draft tabs.

What changed

  • New table `message_queue_item` (SeaORM entity + migration `m20261030_000001_message_queue`), keyed by `owner_id` (the conversation id) with a client-generated `item_uid` per item. The draft is stored as JSON because `PromptDraft` is a frontend-only type.
  • Shared service `message_queue_service` with `list` and a transactional full-replace `save` (delete-then-insert in one txn, last-writer-wins) — mirrors the existing `quick_message_service` layout.
  • Both run modes wired up: a Tauri command (`message_queue_list` / `message_queue_save`) and an axum web handler (`/message_queue_list`, `/message_queue_save`).
  • Cross-client sync: every save emits `message-queue://changed` (`owner_id`, `origin`, `items`) through the existing `EventEmitter` (Tauri webview + WebSocket web clients), so every client converges to the same queue. Each client ignores its own `origin` echo.
  • Frontend hook (`useMessageQueue`) now takes an optional `ownerId`. When present it loads the persisted queue on mount, persists every mutation, and applies remote broadcasts. When absent (unbound draft tab) it stays purely in-memory and behaves exactly as before — so the existing no-owner unit tests keep passing unchanged.

Verification

  • `cargo check -p codeg` compiles the new message-queue code (entity, model, service, migration, command, web handler, event payload) with no errors.
  • The two remaining `atomic_try_update` compile errors in this sandbox are pre-existing and unrelated — they live in `src/workspace_state/mod.rs` and `src/browser/services.rs`, which this PR does not touch; they stem from a local toolchain mismatch and are not introduced here.

Test plan

  • Open a conversation, queue a few messages, restart the app → queue restored.
  • Open the same conversation in a second window / web client, enqueue in one → appears in the other.
  • Reorder / edit / remove in one client → reflected in others.
  • Unit tests: `pnpm test` (the no-owner hook tests remain green).

jry added 2 commits October 30, 2026 07:30
…ents

The conversation send-later queue was held purely in React state
(useMessageQueue) with no persistence and no cross-client sync: a
restart or a second window/web client started from an empty queue.

- Add a message_queue_item SQLite table (SeaORM entity + migration),
  keyed by owner_id (the conversation id) with a client-generated
  item_uid per item. The draft is stored as JSON since PromptDraft
  is a frontend-only type.
- Add message_queue_service (list / full-replace save in one
  transaction) shared by both run modes, a Tauri command and an axum
  web handler (/message_queue_list, /message_queue_save).
- Broadcast message-queue://changed (owner_id, origin, items) on every
  save via the existing EventEmitter, so every Tauri window and web
  client converges to the same queue. Each client ignores its own echo.
- The hook persists + subscribes only when an ownerId is present;
  unbound draft tabs stay in-memory and behave exactly as before, so the
  existing no-owner unit tests keep passing.

Closes the queue-lost-on-restart / not-shared-across-clients gap.
…ing type imports

- drop unused ConnectionTrait / IntoActiveModel imports: clippy -D warnings
  failed all 9 Rust jobs on them
- sync ownerIdRef / editingItemIdRef in effects instead of during render:
  the react-hooks lint bans ref writes in render
- import PersistedQueueItem / MessageQueueSaveItem in api.ts: the build's
  type-check (TS2304) was never reached in CI because lint failed first
- run prettier over the hook (import wrapping, indentation)
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