Delete now moves an artifact to a recoverable Trash instead of hard-removing.
- Model: Artifact.deleted_at (nullable, indexed) = in Trash.
- Soft-delete everywhere: DELETE /api/artifacts/{id} sets deleted_at
(idempotent 204); session POST /a/{id}/delete for the feed's hover Delete
icon; POST /a/{id}/undelete restores; POST /feed/empty-trash purges.
- Trashed artifacts are off-quota (deleting frees quota) and eviction-protected
(never evicted early); hidden from feed, sources, counts, and all API reads
(list/detail/raw).
- Sidebar: "Deleted" and "Keep" system folders at the bottom (counts, hidden
when empty); ?folder=deleted / ?folder=keep views reuse the row rendering.
Deleted view has hover Undelete + an Empty Trash button. Keep = view over
is_kept. Live-poll disabled in folder views.
- Docs (api.md delete section, CLAUDE.md), 10 new/updated tests. 150 passing;
API soft-delete lifecycle verified live.
Purge is manual (Empty Trash) this release; auto purge-on-delete (background,
~3-day retention) is the planned fast-follow.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New artifacts appear at the top of the feed without a manual refresh.
- GET /feed/updates (session-authed): new artifacts since a timestamp marker,
owner-scoped and filter-aware (source/label/kind/q), returned as row HTML.
- Feed JS polls every 5s, paused while the tab is hidden (immediate poll on
refocus). Prepends new rows into today's group without touching the selected
row or the preview iframe — what you're reading stays put and selected.
- Fresh rows show "now", aging into their real time after ~3 min; brief
highlight on arrival.
- Extracted _artifact_rows.html partial, shared by the day-group render and the
updates endpoint, so live rows match exactly.
- Polling chosen over SSE/WebSockets: zero infra on sync-WSGI + SQLite (see
planning/next_steps for the SSE upgrade path). 142 tests pass; feed page +
endpoint verified.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>