From 8c8e858e1ab663afd0ae6a2e343232d7d267d18b Mon Sep 17 00:00:00 2001 From: xarmian Date: Mon, 15 Jun 2026 16:52:35 -0400 Subject: [PATCH] feat(web): show archived banner in-place on live archive (TASK-1833) (#736) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to TASK-1829 (Codex review of #735). When the open item was archived live — via the SSE item_archived handler or the sync-resume deleted path — the detail route redirected back to the collection, so the new in-place Archived banner only appeared on a fresh direct load. Now both handlers re-fetch the item (GET returns soft-deleted items with deleted_at, #733) and render the banner in place. - SSE item_archived: re-fetch and show the banner instead of goto(). - Sync-resume deleted: re-fetch — an archived item (still resolvable, 200) shows the banner; a hard-deleted one (404) still redirects. - Both keep the prior redirect when mid-edit (saveStatus==='saving' || editingTitle): an in-flight save against an archived row would fail and a re-fetch would clobber the editor (the original Codex-round-2 reasoning). - Race guards mirror the handlers' existing pattern (capture item id before await, bail if navigated away). The actor's own archive still navigates via handleDelete's goto; this only changes the someone-else-archived-it case. svelte-check + web build green. --- .../[collection]/[slug]/+page.svelte | 54 ++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/web/src/routes/[username]/[workspace]/[collection]/[slug]/+page.svelte b/web/src/routes/[username]/[workspace]/[collection]/[slug]/+page.svelte index ae47d553..ef8f1947 100644 --- a/web/src/routes/[username]/[workspace]/[collection]/[slug]/+page.svelte +++ b/web/src/routes/[username]/[workspace]/[collection]/[slug]/+page.svelte @@ -338,7 +338,33 @@ // them on a non-existent item is worse than discarding the // edit. Per Codex review round 2. if (event.type === 'item_archived') { - goto(`/${username}/${wsSlug}/${collSlug}`); + // The open item was archived (another tab / user). GET still + // returns soft-deleted items (deleted_at set, #733), so re-fetch + // and show the in-place Archived banner instead of yanking the + // user back to the collection (TASK-1833). Two cases keep the + // prior redirect: mid-edit (an in-flight save will fail against + // the archived row and re-fetching would clobber the editor — + // the Codex-round-2 reasoning still holds there), and a re-fetch + // 404 (item hard-deleted — gone for real). + // editorStore.dirty catches unsaved content BEFORE the 1.2s + // debounced save flips saveStatus to 'saving' — without it a + // live archive in that window would re-fetch and clobber the + // user's in-progress edit (Codex P1). + if (saveStatus === 'saving' || editingTitle || editorStore.dirty) { + goto(`/${username}/${wsSlug}/${collSlug}`); + return; + } + const archItemId = item.id; + const archWsSlug = wsSlug; + const archItemSlug = itemSlug; + try { + const archived = await api.items.get(archWsSlug, archItemSlug); + if (!item || item.id !== archItemId) return; + item = withInflightTags(archived); + } catch { + if (!item || item.id !== archItemId) return; + goto(`/${username}/${wsSlug}/${collSlug}`); + } return; } @@ -410,7 +436,31 @@ // Check this BEFORE the edit-conflict guard so a deleted item // doesn't sit there gated by an in-flight save. if (result.type === 'incremental' && result.changes.deleted.includes(item.id)) { - goto(`/${username}/${wsSlug}/${collSlug}`); + // Archived or hard-deleted while we were away. GET returns + // soft-deleted items (deleted_at set) but 404s on a hard delete, + // so re-fetch: an archived item shows the in-place banner, a + // hard-deleted one redirects (TASK-1833). Keep the prior redirect + // when mid-edit (don't clobber an in-flight save against a + // now-archived row). + // editorStore.dirty catches unsaved content BEFORE the 1.2s + // debounced save flips saveStatus to 'saving' — without it a + // live archive in that window would re-fetch and clobber the + // user's in-progress edit (Codex P1). + if (saveStatus === 'saving' || editingTitle || editorStore.dirty) { + goto(`/${username}/${wsSlug}/${collSlug}`); + return; + } + const delItemId = item.id; + const delWsSlug = wsSlug; + const delItemSlug = itemSlug; + try { + const refreshed = await api.items.get(delWsSlug, delItemSlug); + if (!item || item.id !== delItemId) return; + item = withInflightTags(refreshed); + } catch { + if (!item || item.id !== delItemId) return; + goto(`/${username}/${wsSlug}/${collSlug}`); + } return; }