Skip to content

feat(web): archived-item recovery on the item detail page (TASK-1829) - #735

Merged
xarmian merged 1 commit into
mainfrom
feat/task-1829-web-archived-recovery
Jun 15, 2026
Merged

feat(web): archived-item recovery on the item detail page (TASK-1829)#735
xarmian merged 1 commit into
mainfrom
feat/task-1829-web-archived-recovery

Conversation

@xarmian

@xarmian xarmian commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

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_at populated, 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)

  • isArchived derived from item.deleted_at.
  • Folded archived state into the existing canEdit derived → every edit affordance (title, content editor, FieldEditor, delete, status) disables while archived.
  • A separate canRestore derived (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.
  • A read-only Archived banner at the top of the item view: archive date (via the file's relativeTime helper), 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 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.

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.
  • Production web build (make web) green.
  • Codex review loop: CLEAN (round 1 → permission-gate the Restore button + defer the live-archive redirect).

Completes the BUG-1791 follow-through: TASK-1827 (#733), TASK-1828 (#734), TASK-1829 (this).

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
xarmian merged commit 372d95c into main Jun 15, 2026
4 checks passed
@xarmian
xarmian deleted the feat/task-1829-web-archived-recovery branch June 15, 2026 20:07
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.
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