Skip to content

feat(items): expose item restore via CLI + MCP (TASK-1828) - #734

Merged
xarmian merged 1 commit into
mainfrom
feat/task-1828-item-restore
Jun 15, 2026
Merged

feat(items): expose item restore via CLI + MCP (TASK-1828)#734
xarmian merged 1 commit into
mainfrom
feat/task-1828-item-restore

Conversation

@xarmian

@xarmian xarmian commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes TASK-1828 (child of BUG-1791). Adds the agent-facing restore surface so an archived item discovered via pad item list --all can be recovered without dropping to the web UI.

The server already had restore end-to-end — Store.RestoreItem + handleRestoreItem at POST /items/{ref}/restore, used by the web UI and bulk ops. This wires the two missing surfaces:

  • CLI: pad item restore <ref> (internal/cli/client.go RestoreItem → the existing endpoint, which resolves the ref with include-deleted semantics server-side — the normal resolver 404s on archived items). Mirrors pad item delete's structured JSON envelope: {ref, title, restored: true}.
  • MCP: pad_item action=restore for both transports — passThrough(["item","restore"]) for stdio, plus a routeTable entry (POST .../items/{ref}/restore) for the HTTP transport so cloud/HTTP MCP clients work too. Restore is non-destructive (it un-deletes), so it's safe to expose — unlike a hard delete. The action auto-joins the schema's action enum (derived from the Actions map), is documented in the tool description, and ref is listed as required for it.

Conflict case (slug / invocation_slug reclaimed while the item was archived) is already handled by handleRestoreItem (409) and surfaced cleanly by the CLI client's handleResponse.

Tests

  • The single-item restore endpoint is already covered (handlers_items_test.go).
  • MCP contract: restore added to the catalog ⇄ cmdhelp bijection + dispatch tests (catalog_readonly_test.go).
  • make check green.

Part of the BUG-1791 follow-through: TASK-1827 shipped in #733; TASK-1829 (web direct-access recovery) next.

Adds the agent-facing restore surface so an archived item discovered via
`pad item list --all` can be recovered without dropping to the web UI. The
server already had restore end-to-end (Store.RestoreItem + handleRestoreItem
at POST /items/{ref}/restore, used by the web UI and bulk ops); this wires
the two missing surfaces:

- CLI: `pad item restore <ref>` (cli.Client.RestoreItem → the existing
  endpoint, which resolves the ref include-deleted server-side). Mirrors
  `pad item delete`'s structured JSON envelope: {ref, title, restored: true}.
- MCP: pad_item action=restore via passThrough(["item","restore"]). Restore
  is non-destructive, so it's safe to expose. The action auto-joins the
  schema's action enum (derived from the Actions map) and is documented in
  the tool description.

Conflict case (slug/invocation_slug reclaimed while archived) is already
handled by handleRestoreItem (409) and surfaced by the client's
handleResponse.

Tests: restore endpoint already covered (handlers_items_test.go); restore
added to the MCP catalog<->cmdhelp bijection + dispatch tests. Child of
BUG-1791 (TASK-1827 shipped in #733).
@xarmian
xarmian merged commit 248f7c5 into main Jun 15, 2026
4 checks passed
@xarmian
xarmian deleted the feat/task-1828-item-restore branch June 15, 2026 19:49
xarmian added a commit that referenced this pull request Jun 15, 2026
…#735)

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).
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