feat(web): show archived banner in-place on live archive (TASK-1833) (#736)

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.
This commit is contained in:
xarmian
2026-06-15 16:52:35 -04:00
committed by GitHub
parent 372d95cb2a
commit 8c8e858e1a
@@ -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;
}