v0.5.0: Trash (soft-delete) + Keep folder
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>
This commit is contained in:
parent
6c1823e65e
commit
4254164bef
14 changed files with 438 additions and 43 deletions
|
|
@ -60,6 +60,9 @@ class Artifact(models.Model):
|
|||
metadata = models.JSONField(default=dict, blank=True)
|
||||
read_at = models.DateTimeField(null=True, blank=True)
|
||||
is_kept = models.BooleanField(default=False)
|
||||
# Soft-delete: non-null = in Trash (hidden from feed/sources, off-quota,
|
||||
# recoverable via undelete, purged by Empty Trash). NULL = live.
|
||||
deleted_at = models.DateTimeField(null=True, blank=True, db_index=True)
|
||||
size_bytes = models.BigIntegerField(default=0)
|
||||
# NULL = not shared. Many rows share NULL (SQL treats each NULL as
|
||||
# distinct, so `unique=True` still holds); a real value must be unique
|
||||
|
|
@ -74,6 +77,10 @@ class Artifact(models.Model):
|
|||
def is_shared(self):
|
||||
return bool(self.share_token)
|
||||
|
||||
@property
|
||||
def is_deleted(self):
|
||||
return self.deleted_at is not None
|
||||
|
||||
class Meta:
|
||||
ordering = ["-created_at"]
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue