Scope pending attachments to committed chat sessions - #1546
Open
lihongguang-0014 wants to merge 21 commits into
Open
Scope pending attachments to committed chat sessions#1546lihongguang-0014 wants to merge 21 commits into
lihongguang-0014 wants to merge 21 commits into
Conversation
…nding-attachments-resume
…nding-attachments-resume # Conflicts: # opensquilla-webui/src/composables/chat/useChatSessionRuntime.ts
…nding-attachments-resume
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Scope
Target:
mainRoot cause
The chat view reused one pending attachment collection across route-level session changes. Asynchronous sniff, read, upload, refresh, durable queue-cancellation, queue staging, and server-list hydration work could write back without proving that the originating composer session, queue lease, composable lifetime, composer contents, WAL identity, and WAL revision were still current. Ordinary committed navigation did not retire the old attachment collection, while detached response-handoff recovery needed to outlive that composer. Concurrent cancellation completion could reorder a multi-item restore or recreate a retained row after another tab deleted it. The initial cancellation tombstone used a blind write, so a delayed tab could recreate a row after peer deletion; staging continuations could similarly overwrite a cancellation tombstone after attachment preparation, enqueue, or hydration completed. Atomic mutations compared raw session-key bytes, rejecting legacy alias rows even when they had the same canonical owner. The later retain CAS also represented both a deleted row and a live revision conflict as
null, so one tab could hide an earlier peer-retained draft and restore a later draft first. Partial object assignment could retain a staleretainAfterCancelflag over a peer's destructive tombstone.popAllrebuilt mixed survivors by category before cancellation settled, moving non-editable rows ahead of earlier drafts. A shared in-flight cancellation promise discarded the stronger destructive intent when Clear Queue followed edit/pop recovery, and the restore callback remained valid until that queued destructive operation reached the WAL, allowing a retained local-only record to reappear in the composer or after reload. Pending-queue ownership and WAL lookup also used raw session aliases, so a canonical-equivalent navigation could strand a durable draft or reject a local reorder. Failed send and response-handoff recovery deduplicated by page-local numeric IDs that can be reused, and generic WAL queue recovery could combine same-numbered attachment IDs independently allocated by multiple tabs; these paths produced duplicate Vue keys in the live composer. The Meta recovery pristine check ignored attachment work before its placeholder existed. Canonical aliases, restored local IDs, and independently cloned concurrent send snapshots exposed related identity, busy-window, and exclusion gaps.Non-goals
Compatibility
No public RPC, upload schema, database schema, persisted configuration, or client contract changes. Existing installations require no migration. Existing pending WAL records stored under legacy default-session aliases remain readable and are atomically normalized to their canonical owner when reordered or mutated. The cancellation and staging compare-and-swap operations use existing WAL fields and require no IndexedDB version change. The implementation is integrated with the current session-read and session-lifecycle boundaries and remains limited to local WebUI composer and pending-queue lifecycle handling plus its browser fixture.
Platform impact
Platform-neutral WebUI behavior. The change uses existing browser FileReader and Promise lifecycle paths and adds no filesystem, subprocess, signal, permission, or OS-specific behavior.
Tests
npm run test:unit: 427 files and 5276 tests passed.npm run typecheck: architecture, RPC, security, style, localization, and Vue type checks passed.npm run build:artifact: production artifact build and bundle guards passed; 395 files verified.npm run verify:release-dist: release artifact verification passed.attachment-drag-upload.spec.ts,history-hydration.spec.ts, andsession-switch-transport.spec.ts: 15 passed.git diff --check: passed.Safety and limitations
A retired HTTP upload may still finish server-side and expire through the existing temporary-file lifecycle; its stale browser callback is ignored. Pending-queue staging keeps its existing park-and-retry behavior, while browser-backed staging and hydration transitions now fail closed on an exact-identity or WAL-revision conflict. A queue-to-composer cancellation acquires and retains its tombstone only while the captured canonical session, queue lease, composable lifetime, composer revision, exact WAL identity, and WAL revision remain current. A failed predecessor or live peer-owned CAS conflict blocks later batch restoration, while an explicit destructive clear is serialized after any retained cancellation already in flight and immediately invalidates its restore callback. Mixed queue survivors stay in their original order throughout that wait. If navigation, user edits, attachment changes, intent changes, or teardown wins the race, the durable local-only record remains available for later hydration unless a destructive operation owns the record; if a peer explicitly deletes it, atomic revision ownership prevents resurrection. Queue and rejected-send restoration may replace a colliding page-local attachment ID, but do not alter its upload identity or persisted payload. Detached response-handoff recovery can still complete across navigation. Browser regression used a local isolated gateway with mocked chat transport. No live provider credentials, multi-device workflow, or manual cross-device validation was used.
Release note status
Not added: this is a focused WebUI correctness fix with no public interface or migration.
Linked issue
None
Third-party origin
None. The implementation and tests are original OpenSquilla work.