feat(web): archived-item recovery on the item detail page (TASK-1829) - #735
Merged
Conversation
With GET now returning soft-deleted items read-only (deleted_at populated, shipped in #733), the detail route can show an archived item instead of a hard 404. Adds the recovery UI: - isArchived derived from item.deleted_at; folded into the existing canEdit derived so every edit affordance disables while archived. - A read-only "Archived" banner at the top of the item view (date via the file's relativeTime helper) with a Restore button -> api.items.restore, then re-fetches the item so the banner clears and editing re-enables. Success/error via the existing toastStore; a 409 reclaimed-slug conflict surfaces verbatim. - restoring in-flight flag; handleRestore is a standalone async function (not an effect) per CONVE-1688 / CONVE-606. No API/client/server change — GET is already ungated (#733) and api.items.restore already existed. Child of BUG-1791 (TASK-1827 in #733, TASK-1828 in #734).
xarmian
added a commit
that referenced
this pull request
Jun 15, 2026
…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 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.
Summary
Closes TASK-1829 (child of BUG-1791). Adds in-place recovery for archived items on the item detail route.
With GET now returning soft-deleted items read-only (
deleted_atpopulated, shipped in #733), visiting an archived item's URL no longer dead-ends on "cannot be found" — it shows a recovery UI instead.Changes (one file: the detail route
+page.svelte)isArchivedderived fromitem.deleted_at.canEditderived → every edit affordance (title, content editor, FieldEditor, delete, status) disables while archived.canRestorederived (raw edit permission, independent of the archived gate) so the Restore button only renders for users who can actually restore — read-only viewers still see the banner (so they know it's archived) but get no dead CTA.relativeTimehelper), a read-only hint, and (for editors) a Restore button →api.items.restore, which re-fetches the item so the banner clears and editing re-enables. Success/error via the existingtoastStore; a 409 reclaimed-slug conflict surfaces verbatim.restoringin-flight flag;handleRestoreis a standalone async function (not an effect) per CONVE-1688 / CONVE-606.No API / client / server change — GET is already ungated (#733) and
api.items.restorealready existed.Scope note / follow-up
Codex flagged (High) that the SSE / sync handlers still
goto()back to the collection when the open item is archived live (from another tab/user/sync), so the in-place banner currently only appears on a fresh direct load. That redirect is pre-existing, deliberately-tuned destructive-event handling (a prior Codex round added it to avoid stranding a mid-edit user on a stale row) and is outside TASK-1829's direct-URL-access scope. Tracked as TASK-1833 (in-place banner on live archive, with the archive-vs-hard-delete + in-flight-save care it needs).Tests
cd web && npm run check(svelte-check): 0 errors.make web) green.Completes the BUG-1791 follow-through: TASK-1827 (#733), TASK-1828 (#734), TASK-1829 (this).