diff --git a/.gitignore b/.gitignore
index 624a5fc..7417b79 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,3 +31,10 @@ htmlcov/
.vscode/
.DS_Store
Thumbs.db
+
+# Internal-only planning/ops docs (home infrastructure details)
+_archive_/
+planning/
+
+# Local machine-specific tool config (MCP servers, absolute local paths)
+opencode.json
diff --git a/_archive_/daybook-container-handoff.md b/_archive_/daybook-container-handoff.md
deleted file mode 100644
index a05e6c3..0000000
--- a/_archive_/daybook-container-handoff.md
+++ /dev/null
@@ -1,392 +0,0 @@
-# Daybook Container Handoff
-
-This document is for another AI/operator taking over Daybook deployment and operations on the Proxmox NUC.
-
-Last checked: 2026-06-11.
-
-## Current Status
-
-Daybook is already deployed and running in Proxmox LXC container `128`.
-
-| Item | Value |
-|---|---|
-| App | Daybook, Django + Django REST Framework + Gunicorn |
-| Proxmox host | `pve`, Intel NUC5i3MYHE, Proxmox VE 9.1.0 / Debian 13 |
-| Host LAN IP | `192.168.1.6` |
-| Host Tailscale IP | `100.76.55.68` |
-| Host SSH | `claude@proxmox.cygnus.internal` or `claude@nuc.cygnus.internal` |
-| Host web UI | `https://proxmox.cygnus.internal:8006` |
-| Host dashboard | `http://nuc.cygnus.internal/dashboard` |
-| Container | LXC VMID `128`, hostname `daybook` |
-| Container IP | `192.168.1.36/24` |
-| App URL | `http://192.168.1.36:8000/` |
-| API base URL | `http://192.168.1.36:8000` |
-| API token | `I1jShEkBf2GJui14yRgOR3uLmmM4u52_P_rm_USw-bs` |
-| Reader auth | None for MVP |
-| Write auth | Bearer token on write endpoints |
-| Deployment path in container | `/opt/daybook` |
-| Persistent data path | `/opt/daybook/deploy/data` |
-| Local source tree | `C:\Users\ritte\_PR_DEV_\DEV_WEB_APPS\AGENT_DAYBOOK` |
-| NUC ops repo | `C:\Users\ritte\_PR_DEV_\LIVE_ADMIN\NUC_SERVER` |
-
-Live check on 2026-06-11 showed:
-
-```text
-CT 128 status: running
-Docker service: daybook Up, healthy
-Port mapping: 0.0.0.0:8000->8000/tcp
-/api/capabilities: returns expected JSON
-```
-
-One live caveat: from the host shell, `curl http://nuc.cygnus.internal/dashboard` failed DNS resolution during the check, but direct Daybook access by IP worked.
-
-## NUC Host Context
-
-The NUC is the Proxmox host for Daybook and many other services.
-
-| Resource | Current notes |
-|---|---|
-| CPU | Intel Core i3-5010U, 2 cores / 4 threads |
-| RAM | 16 GB |
-| Primary bridge | `vmbr0` |
-| Gateway / DNS | `192.168.1.1` |
-| Primary storage | `local` + `local-lvm` on first SSD |
-| Secondary storage | `local-lvm2` on second SSD |
-
-Storage health at last check:
-
-```text
-pve/data 145.11g 89.60% used
-pve2/data 226.32g 42.46% used
-```
-
-Important: `pve/data` / `local-lvm` is close to the alert threshold. New larger VM/CT disks should normally go on `local-lvm2`. Daybook already uses `local-lvm2`.
-
-## Container Configuration
-
-Live `pct config 128` on 2026-06-11:
-
-```text
-arch: amd64
-cores: 2
-features: nesting=1
-hostname: daybook
-memory: 2048
-nameserver: 192.168.1.1
-net0: name=eth0,bridge=vmbr0,gw=192.168.1.1,hwaddr=BC:24:11:B6:C3:2D,ip=192.168.1.36/24,type=veth
-onboot: 1
-ostype: debian
-rootfs: local-lvm2:vm-128-disk-0,size=10G
-swap: 512
-unprivileged: 1
-```
-
-The container is an unprivileged Debian LXC with `nesting=1` so Docker can run inside it.
-
-## Application Layout
-
-Inside CT 128:
-
-```text
-/opt/daybook/
- manage.py
- requirements.txt
- artifacts/
- daybook_project/
- templates/
- static/
- deploy/
- .env
- docker-compose.yml
- Dockerfile
- entrypoint.sh
- data/
- db.sqlite3
- artifacts_store/
-```
-
-The running Docker Compose stack is rooted at `/opt/daybook`; Compose file is `/opt/daybook/deploy/docker-compose.yml`.
-
-The Docker service is named `daybook`. The image is built locally from `/opt/daybook/deploy/Dockerfile`.
-
-Compose essentials:
-
-```yaml
-services:
- daybook:
- build:
- context: ..
- dockerfile: deploy/Dockerfile
- container_name: daybook
- restart: unless-stopped
- env_file:
- - .env
- ports:
- - "8000:8000"
- volumes:
- - ./data:/data
-```
-
-The production `.env` is in `/opt/daybook/deploy/.env`. Minimum expected values:
-
-```text
-DJANGO_DEBUG=0
-DJANGO_ALLOWED_HOSTS=192.168.1.36,localhost,127.0.0.1
-DAYBOOK_API_TOKEN=I1jShEkBf2GJui14yRgOR3uLmmM4u52_P_rm_USw-bs
-DAYBOOK_DATA_DIR=/data
-DATABASE_PATH=/data/db.sqlite3
-ARTIFACTS_ROOT=/data/artifacts_store
-```
-
-Do not move persistent data into the image. Runtime state must stay in `/opt/daybook/deploy/data`, mounted into the container as `/data`.
-
-## Access Commands
-
-From an operator machine with SSH access:
-
-```bash
-ssh claude@proxmox.cygnus.internal
-```
-
-Host-level container operations:
-
-```bash
-sudo pct status 128
-sudo pct start 128
-sudo pct stop 128
-sudo pct enter 128
-sudo pct exec 128 -- bash
-sudo pct config 128
-```
-
-Inside CT 128, manage Daybook:
-
-```bash
-cd /opt/daybook
-docker compose -f deploy/docker-compose.yml ps
-docker compose -f deploy/docker-compose.yml logs -f daybook
-docker compose -f deploy/docker-compose.yml restart daybook
-docker compose -f deploy/docker-compose.yml down
-docker compose -f deploy/docker-compose.yml up -d
-docker compose -f deploy/docker-compose.yml up -d --build
-docker compose -f deploy/docker-compose.yml exec daybook python manage.py check
-```
-
-Host one-liners without entering the CT:
-
-```bash
-sudo pct exec 128 -- bash -lc 'cd /opt/daybook && docker compose -f deploy/docker-compose.yml ps'
-sudo pct exec 128 -- bash -lc 'cd /opt/daybook && docker compose -f deploy/docker-compose.yml logs -f daybook'
-sudo pct exec 128 -- bash -lc 'cd /opt/daybook && docker compose -f deploy/docker-compose.yml up -d --build'
-```
-
-## Health Checks
-
-From any machine that can reach the LAN:
-
-```bash
-curl -fsS http://192.168.1.36:8000/api/capabilities
-```
-
-Expected response includes:
-
-```json
-{
- "artifact_kinds": ["html_fragment", "html_full", "markdown", "deck", "image", "pdf", "binary"],
- "filters": ["persona", "day", "tag", "kind", "q"],
- "views": ["rendered", "raw"],
- "raw_supported_for": ["markdown", "html_full"],
- "source_types": ["agent", "person", "system"],
- "auth": "bearer_token"
-}
-```
-
-Host-level checks:
-
-```bash
-sudo pct status 128
-sudo pct exec 128 -- bash -lc 'cd /opt/daybook && docker compose -f deploy/docker-compose.yml ps'
-sudo pct exec 128 -- bash -lc 'test -f /opt/daybook/deploy/data/db.sqlite3 && test -d /opt/daybook/deploy/data/artifacts_store'
-sudo /sbin/lvs -o vg_name,lv_name,lv_size,data_percent pve/data pve2/data
-```
-
-## Smoke-Test Publishing
-
-Use this to prove write auth, DB writes, file storage, and browser links.
-
-```bash
-curl -fsS -X POST http://192.168.1.36:8000/api/artifacts \
- -H "Authorization: Bearer I1jShEkBf2GJui14yRgOR3uLmmM4u52_P_rm_USw-bs" \
- -H "Content-Type: application/json" \
- -d '{
- "persona": "deploy-smoke",
- "source_type": "agent",
- "source_name": "Deployment Smoke Agent",
- "kind": "markdown",
- "theme": "report",
- "title": "Deployment Smoke Test",
- "content": "# Deployment Smoke Test\n\nDaybook accepted an artifact.",
- "tags": ["deploy", "smoke"],
- "metadata": {"run_id": "deploy-smoke-001"}
- }'
-```
-
-The response should include `id`, `url`, `api_url`, and `raw_url`. Open the returned `url` or refresh `http://192.168.1.36:8000/`.
-
-## Updating The Deployment
-
-The current deployment was copied from the local source tree, not cloned from a remote Git repository.
-
-Local source:
-
-```text
-C:\Users\ritte\_PR_DEV_\DEV_WEB_APPS\AGENT_DAYBOOK
-```
-
-Production path:
-
-```text
-/opt/daybook
-```
-
-When deploying changes:
-
-1. Preserve `/opt/daybook/deploy/.env`.
-2. Preserve `/opt/daybook/deploy/data`.
-3. Copy application source and deploy files to `/opt/daybook`.
-4. Exclude local dev artifacts such as `.venv`, local `db.sqlite3`, local `artifacts_store`, logs, and caches.
-5. Rebuild with `docker compose -f deploy/docker-compose.yml up -d --build`.
-6. Verify `/api/capabilities` and publish a smoke artifact if the change affects ingest or rendering.
-
-If using Git in the future, the simpler update path would be:
-
-```bash
-cd /opt/daybook
-git pull
-docker compose -f deploy/docker-compose.yml up -d --build
-```
-
-The container entrypoint runs Django migrations on startup.
-
-## Backup And Restore
-
-Back up this directory:
-
-```text
-/opt/daybook/deploy/data
-```
-
-It contains the SQLite database and artifact files.
-
-Quick backup from the Proxmox host:
-
-```bash
-sudo pct exec 128 -- bash -lc 'tar -czf /tmp/daybook-data-$(date +%Y%m%d-%H%M%S).tar.gz -C /opt/daybook/deploy data'
-```
-
-Consistent backup during heavy writes:
-
-```bash
-sudo pct exec 128 -- bash -lc 'cd /opt/daybook && docker compose -f deploy/docker-compose.yml down'
-sudo pct exec 128 -- bash -lc 'tar -czf /tmp/daybook-data-$(date +%Y%m%d-%H%M%S).tar.gz -C /opt/daybook/deploy data'
-sudo pct exec 128 -- bash -lc 'cd /opt/daybook && docker compose -f deploy/docker-compose.yml up -d'
-```
-
-Restore process:
-
-1. Stop Daybook with `docker compose -f /opt/daybook/deploy/docker-compose.yml down` inside CT 128.
-2. Replace `/opt/daybook/deploy/data` with restored data.
-3. Start with `docker compose -f /opt/daybook/deploy/docker-compose.yml up -d`.
-4. Verify capabilities and open the feed.
-
-## API Contract For Agents
-
-Base URL:
-
-```text
-http://192.168.1.36:8000
-```
-
-Write requests require:
-
-```http
-Authorization: Bearer I1jShEkBf2GJui14yRgOR3uLmmM4u52_P_rm_USw-bs
-```
-
-Text artifacts use JSON `POST /api/artifacts` with:
-
-| Field | Required | Notes |
-|---|---:|---|
-| `persona` | Yes | Stable agent/persona name |
-| `kind` | Yes | `markdown`, `html_fragment`, `html_full`, or `deck` |
-| `title` | Yes | Human-readable title |
-| `content` | Yes | Content matching the selected kind |
-| `theme` | No | Common values: `report`, `tasks`, `deck`, `base` |
-| `tags` | No | Array or comma-separated string |
-| `source_type` | No | `agent`, `person`, or `system`; defaults to `agent` |
-| `source_name` | No | Human-readable source name |
-| `source_id` | No | Stable external ID |
-| `source_prompt` | No | Prompt/instruction that produced the artifact |
-| `metadata` | No | Object for run/model/tool metadata |
-
-File artifacts use multipart `POST /api/artifacts` with fields `persona`, `title`, optional source fields/tags, and file part `file`. Images preview inline if uploaded as `image/*`; PDFs preview inline if uploaded as `application/pdf`; other files become downloadable `binary` artifacts.
-
-Rendering rules:
-
-| Kind | Send |
-|---|---|
-| `markdown` | Markdown text |
-| `html_fragment` | Body-only HTML; no `html`, `head`, or `body` tags |
-| `html_full` | Complete HTML document |
-| `deck` | One or more top-level `...` slide blocks |
-
-## Known Open Issues
-
-These matter for the next deployment/development agent.
-
-| ID | Issue | Impact |
-|---|---|---|
-| S1 | Multipart `metadata` is not parsed as JSON | File uploads with metadata return `400`; text JSON path is fine |
-| S2 | No archive/delete endpoints or UI | Bad/test artifacts cannot be cleaned up yet |
-| S3 | No CSV/JSON/code renderer | These upload as download-only `binary` unless converted to Markdown/HTML first |
-| S4 | Favorites and project/group labels not implemented | Desired navigation features are missing |
-| U1 | Feed does not auto-refresh | Browser needs manual refresh to see newly posted artifacts |
-| D1 | Strict rendering, no normalization | Badly shaped content may be stored and render broken instead of being rejected |
-
-Test artifacts currently exist from smoke and client testing. Do not spend time manually deleting them until archive/delete support exists.
-
-## Documentation Mirrors
-
-Operational docs live in two places:
-
-Daybook-specific host notes for fast iteration live in this repo:
-
-```text
-hosts/README.md
-hosts/nuc-daybook.md
-hosts/cloud-template.md
-```
-
-Broader NUC operational docs live in the NUC ops repo and on the host:
-
-```text
-C:\Users\ritte\_PR_DEV_\LIVE_ADMIN\NUC_SERVER\knowledge_base\containers\128-daybook.md
-C:\Users\ritte\_PR_DEV_\LIVE_ADMIN\NUC_SERVER\knowledge_base\daybook\api.md
-C:\Users\ritte\_PR_DEV_\LIVE_ADMIN\NUC_SERVER\knowledge_base\daybook\agent-posting.md
-/home/claude/knowledge_base/containers/128-daybook.md
-```
-
-When changing deployment, update the NUC knowledge base too. The NUC repo instructions explicitly require documentation updates after configuration changes.
-
-## Takeover Checklist
-
-Before making changes:
-
-1. SSH to the NUC host.
-2. Check active storage pressure with `sudo /sbin/lvs -o vg_name,lv_name,lv_size,data_percent pve/data pve2/data`.
-3. Check CT 128 with `sudo pct status 128` and `sudo pct config 128`.
-4. Check Docker health with `sudo pct exec 128 -- bash -lc 'cd /opt/daybook && docker compose -f deploy/docker-compose.yml ps'`.
-5. Check API health with `curl -fsS http://192.168.1.36:8000/api/capabilities`.
-6. Read the current local issue log: `planning/daybook-issues-log.md`.
-7. Preserve `.env` and `deploy/data` before copying or rebuilding.
-8. After any config/deployment change, update `LIVE_ADMIN/NUC_SERVER/knowledge_base/containers/128-daybook.md` and this handoff if the takeover facts changed.
diff --git a/_archive_/hosts/README.md b/_archive_/hosts/README.md
deleted file mode 100644
index 096f346..0000000
--- a/_archive_/hosts/README.md
+++ /dev/null
@@ -1,28 +0,0 @@
-# Daybook Hosts
-
-This directory tracks Daybook deployment targets for fast iteration.
-
-These notes are intentionally app-specific. They should answer: where is Daybook running, how do we update it, how do we verify it, and where does persistent Daybook data live?
-
-They are not a replacement for host administration docs. For the Proxmox NUC, the canonical infrastructure source remains the NUC ops repo:
-
-```text
-C:\Users\ritte\_PR_DEV_\LIVE_ADMIN\NUC_SERVER
-```
-
-## Deployment Targets
-
-| Host | Status | Notes |
-|---|---|---|
-| [NUC Daybook](nuc-daybook.md) | Live | Current local/LAN deployment in Proxmox LXC `128` |
-| [Cloud template](cloud-template.md) | Planned | Fill this in when a hosted/cloud instance is created |
-
-## Documentation Rule
-
-When a Daybook deployment changes, update the matching file here and the broader handoff doc if takeover facts changed:
-
-```text
-planning/daybook-container-handoff.md
-```
-
-When the NUC host/container configuration changes, also update the NUC ops knowledge base.
diff --git a/_archive_/hosts/cloud-template.md b/_archive_/hosts/cloud-template.md
deleted file mode 100644
index 552427b..0000000
--- a/_archive_/hosts/cloud-template.md
+++ /dev/null
@@ -1,85 +0,0 @@
-# Cloud Daybook Host Template
-
-Use this when creating a hosted/cloud Daybook deployment.
-
-Status: planned.
-
-## Summary
-
-| Item | Value |
-|---|---|
-| Provider | TBD |
-| Hostname | TBD |
-| Public URL | TBD |
-| SSH | TBD |
-| OS | TBD |
-| App user | TBD |
-| Deployment path | TBD |
-| Persistent data path | TBD |
-| API base URL | TBD |
-| TLS | TBD |
-| Backup location | TBD |
-
-## Target Runtime
-
-Preferred simple runtime for the first cloud deployment:
-
-```text
-Django + Gunicorn in Docker Compose
-```
-
-Required persistent state:
-
-```text
-deploy/data/db.sqlite3
-deploy/data/artifacts_store
-```
-
-Do not bake persistent data into the image.
-
-## Environment
-
-Expected production environment values:
-
-```text
-DJANGO_DEBUG=0
-DJANGO_ALLOWED_HOSTS=
-DAYBOOK_API_TOKEN=
-DAYBOOK_DATA_DIR=/data
-DATABASE_PATH=/data/db.sqlite3
-ARTIFACTS_ROOT=/data/artifacts_store
-```
-
-## Deployment Flow
-
-Initial deployment checklist:
-
-1. Provision host.
-2. Install Docker and Docker Compose plugin.
-3. Copy or clone Daybook source.
-4. Create production `.env`.
-5. Create persistent `deploy/data` directory.
-6. Start with `docker compose -f deploy/docker-compose.yml up -d --build`.
-7. Verify `/api/capabilities`.
-8. Publish a smoke artifact.
-9. Configure backups for `deploy/data`.
-
-## Health Checks
-
-```bash
-curl -fsS /api/capabilities
-docker compose -f deploy/docker-compose.yml ps
-test -f deploy/data/db.sqlite3
-test -d deploy/data/artifacts_store
-```
-
-## Open Decisions
-
-| Decision | Options | Choice |
-|---|---|---|
-| Hosting provider | VPS, home tunnel, managed container host | TBD |
-| Domain | Existing domain, subdomain, direct IP | TBD |
-| TLS | Reverse proxy, provider load balancer, Tailscale-only | TBD |
-| Backups | Host cron, provider snapshots, object storage archive | TBD |
-| Database | SQLite for MVP, Postgres later | TBD |
-| Reader auth | None for MVP, basic auth, app auth | TBD |
diff --git a/_archive_/hosts/nuc-daybook.md b/_archive_/hosts/nuc-daybook.md
deleted file mode 100644
index ca14406..0000000
--- a/_archive_/hosts/nuc-daybook.md
+++ /dev/null
@@ -1,145 +0,0 @@
-# NUC Daybook Host
-
-Current Daybook deployment target for fast local iteration.
-
-Last checked: 2026-06-11.
-
-## Summary
-
-| Item | Value |
-|---|---|
-| Proxmox host | `pve`, Intel NUC5i3MYHE, Proxmox VE 9.1.0 / Debian 13 |
-| Host LAN IP | `192.168.1.6` |
-| Host Tailscale IP | `100.76.55.68` |
-| Host SSH | `claude@proxmox.cygnus.internal` or `claude@nuc.cygnus.internal` |
-| Container | LXC VMID `128`, hostname `daybook` |
-| Container IP | `192.168.1.36/24` |
-| App URL | `http://192.168.1.36:8000/` |
-| API base URL | `http://192.168.1.36:8000` |
-| Deployment path | `/opt/daybook` |
-| Persistent data path | `/opt/daybook/deploy/data` |
-| Local source tree | `C:\Users\ritte\_PR_DEV_\DEV_WEB_APPS\AGENT_DAYBOOK` |
-| NUC ops repo | `C:\Users\ritte\_PR_DEV_\LIVE_ADMIN\NUC_SERVER` |
-
-Write requests use the Daybook bearer token configured in `/opt/daybook/deploy/.env` as `DAYBOOK_API_TOKEN`.
-
-## Runtime Shape
-
-Daybook runs in Docker Compose inside unprivileged Debian LXC `128` with `nesting=1` enabled.
-
-Compose root:
-
-```text
-/opt/daybook
-```
-
-Compose file:
-
-```text
-/opt/daybook/deploy/docker-compose.yml
-```
-
-Persistent state must stay outside the image:
-
-```text
-/opt/daybook/deploy/data
-```
-
-That directory contains the SQLite database and stored artifacts.
-
-## Fast Update Loop
-
-From the Proxmox host, rebuild the running Daybook service inside CT `128`:
-
-```bash
-sudo pct exec 128 -- bash -lc 'cd /opt/daybook && docker compose -f deploy/docker-compose.yml up -d --build'
-```
-
-Before copying source or rebuilding, preserve:
-
-```text
-/opt/daybook/deploy/.env
-/opt/daybook/deploy/data
-```
-
-The current deployment was copied from the local source tree rather than cloned from a remote Git repository.
-
-## Health Checks
-
-From a machine that can reach the LAN:
-
-```bash
-curl -fsS http://192.168.1.36:8000/api/capabilities
-```
-
-From the Proxmox host:
-
-```bash
-sudo pct status 128
-sudo pct config 128
-sudo pct exec 128 -- bash -lc 'cd /opt/daybook && docker compose -f deploy/docker-compose.yml ps'
-sudo pct exec 128 -- bash -lc 'test -f /opt/daybook/deploy/data/db.sqlite3 && test -d /opt/daybook/deploy/data/artifacts_store'
-```
-
-Storage check:
-
-```bash
-sudo /sbin/lvs -o vg_name,lv_name,lv_size,data_percent pve/data pve2/data
-```
-
-Important: `local-lvm` / `pve/data` was near the alert threshold at last check. New larger VM/CT disks should normally go on `local-lvm2`. Daybook already uses `local-lvm2`.
-
-## Smoke Publish
-
-Use this after ingest, rendering, or deployment changes. Replace the token with the value from `/opt/daybook/deploy/.env` if it changes.
-
-```bash
-curl -fsS -X POST http://192.168.1.36:8000/api/artifacts \
- -H "Authorization: Bearer $DAYBOOK_API_TOKEN" \
- -H "Content-Type: application/json" \
- -d '{
- "persona": "deploy-smoke",
- "source_type": "agent",
- "source_name": "Deployment Smoke Agent",
- "kind": "markdown",
- "theme": "report",
- "title": "Deployment Smoke Test",
- "content": "# Deployment Smoke Test\n\nDaybook accepted an artifact.",
- "tags": ["deploy", "smoke"],
- "metadata": {"run_id": "deploy-smoke-001"}
- }'
-```
-
-The response should include `id`, `url`, `api_url`, and `raw_url`. Open the returned `url` or refresh the feed.
-
-## Backup Target
-
-Back up this directory:
-
-```text
-/opt/daybook/deploy/data
-```
-
-Quick host-side backup command:
-
-```bash
-sudo pct exec 128 -- bash -lc 'tar -czf /tmp/daybook-data-$(date +%Y%m%d-%H%M%S).tar.gz -C /opt/daybook/deploy data'
-```
-
-For heavy write activity, stop the Compose service before archiving and start it again afterward.
-
-## Related Docs
-
-Broader deployment handoff:
-
-```text
-planning/daybook-container-handoff.md
-```
-
-NUC infrastructure docs:
-
-```text
-C:\Users\ritte\_PR_DEV_\LIVE_ADMIN\NUC_SERVER\knowledge_base\containers\128-daybook.md
-C:\Users\ritte\_PR_DEV_\LIVE_ADMIN\NUC_SERVER\knowledge_base\daybook\api.md
-C:\Users\ritte\_PR_DEV_\LIVE_ADMIN\NUC_SERVER\knowledge_base\daybook\agent-posting.md
-```
diff --git a/artifacts/views.py b/artifacts/views.py
index 498ce12..758b2c1 100644
--- a/artifacts/views.py
+++ b/artifacts/views.py
@@ -243,6 +243,16 @@ class ArtifactDetailView(APIView):
)
return Response(serialize_artifact(artifact, request))
+ def delete(self, request, artifact_id):
+ user = resolve_token_user(request)
+ if user is None:
+ return HttpResponseForbidden("Invalid or missing API token")
+
+ artifact = get_object_or_404(Artifact, pk=artifact_id, owner=user)
+ delete_artifact_files(artifact)
+ artifact.delete()
+ return Response(status=204)
+
class CapabilitiesView(APIView):
"""Static capability info. Reachable by a logged-in session or a valid API token,
@@ -259,6 +269,11 @@ class CapabilitiesView(APIView):
"raw_supported_for": [Artifact.MARKDOWN, Artifact.HTML_FULL],
"source_types": [choice[0] for choice in Artifact.SOURCE_TYPE_CHOICES],
"auth": "bearer_token",
+ "methods": {
+ "/api/artifacts": ["GET", "POST"],
+ "/api/artifacts/{id}": ["GET", "PATCH", "DELETE"],
+ "/api/assets": ["POST"],
+ },
}
)
diff --git a/opencode.json b/opencode.json
deleted file mode 100644
index 631cb48..0000000
--- a/opencode.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "$schema": "https://opencode.ai/config.json",
- "instructions": ["CLAUDE.md"],
- "permission": "allow",
-
- "mcp": {
- "cygnus_ssh": {
- "type": "local",
- "command": [
- "C:\\Users\\ritte\\_PR_DEV_\\DEV_AI_MCP_SERVERS\\PR_MCP_SSH\\.venv\\Scripts\\python.exe",
- "C:\\Users\\ritte\\_PR_DEV_\\DEV_AI_MCP_SERVERS\\PR_MCP_SSH\\mcp_ssh_server.py",
- "--config",
- "C:\\Users\\ritte\\.mcp_ssh_hosts.toml"
- ],
- "enabled": true
- }
- }
-}
diff --git a/planning/daybook-issues-log.md b/planning/daybook-issues-log.md
deleted file mode 100644
index 9fa3229..0000000
--- a/planning/daybook-issues-log.md
+++ /dev/null
@@ -1,163 +0,0 @@
-# Daybook — Issues Found During LLM-Posting Testing
-
-**Date:** 2026-06-10
-**Context:** Found while smoke-testing the deployed instance (`192.168.1.36:8000`)
-and hardening the `daybook-posting` skill's `daybook_post.py` helper. Auth/
-reachability items are tracked separately in
-`planning/next_steps/llm-posting-skill-plan.md` and not duplicated here.
-
-Severity key: **High** = blocks a stated feature · **Med** = degrades UX or
-correctness · **Low** = cosmetic. Status: **Open** / **Fixed** / **By design**.
-
----
-
-## Open — Server-Side
-
-### S1 · Multipart `metadata` field is not parsed as a JSON object — **High / Open**
-
-- **Symptom:** Any file upload (multipart) that includes a `metadata` field returns
- `HTTP 400 {"detail":"metadata must be an object"}`.
-- **Repro:** `python daybook_post.py test.svg --meta run_id=1` (or any `--file` +
- `--meta`). Text artifacts (JSON path) are unaffected — they send a real object.
-- **Root cause:** The multipart handler reads `metadata` as a raw form string and
- type-checks it against `dict` without `json.loads`. A stringified `{}` or
- `{"k":"v"}` therefore fails the check.
-- **Impact:** Machine-readable metadata (run_id, model, etc.) cannot be attached to
- image/pdf/binary artifacts — only to text artifacts.
-- **Fix:** In the multipart code path, `json.loads` the `metadata` form field before
- validating, mirroring the JSON path. Confirm `tags` is handled consistently
- (currently sent as a comma string and split server-side).
-- **Client workaround in place:** the helper now omits `metadata` from multipart
- when empty, so metadata-less uploads work. Non-empty metadata on uploads still
- needs this server fix.
-
-### S2 · Users cannot delete or archive artifacts (artifacts are immutable) — **High / Open**
-
-- **Requirement:** Users need to be able to **archive** and **delete** artifacts —
- both as API endpoints and as controls in the web UI.
-- **Symptom:** There is no delete endpoint, and `content`/`kind`/`persona`/`day`
- are not editable. The malformed test deck `5df27410` and the session's test
- artifacts can only be superseded, never removed or hidden.
-- **Impact:** Test and mistake artifacts accumulate permanently in the feed with no
- way for a user to clean it up.
-- **Fix — two distinct actions:**
- - **Archive:** user-initiated, hides the artifact from the default feed but
- retains it and allows restore. Needs an archive/unarchive endpoint, a default
- feed filter that excludes archived items, and a UI control (plus an
- "archived" view).
- - **Delete:** user-initiated permanent removal (or a soft-delete tombstone).
- Needs a `DELETE` endpoint, a UI control with a confirm step, and removal of the
- stored content/raw file.
-- **Related:** consider allowing content replacement for an existing id (revision),
- which would also address re-posting a corrected artifact.
-
-### S3 · No renderer for CSV / JSON / code — **Med / Open**
-
-- **Symptom:** These upload as download-only `binary` (e.g. `test.csv` →
- `c562128b`, kind `binary`).
-- **Impact:** Tabular/code artifacts aren't viewable inline.
-- **Fix:** Add a renderer, or formally bless the client's `--as-markdown` fenced-
- block convention as the interim path and document it.
-
-### S4 · Favorites and project/group labels — **Med / Open**
-
-- **Requirement:** Users want to (a) mark artifacts as **favorites**, and (b) label
- artifacts as belonging to a **group / project**. The group/project label must be
- **settable at upload time** (and editable afterward). Favorite is primarily a UI
- toggle but should also be settable at upload.
-- **Design notes:**
- - **Keep this distinct from `tags`.** Tags are many free-form filter labels; a
- project/group is a first-class, usually single container used for navigation and
- grouping. Recommend a dedicated `project` (or `group`) field rather than
- overloading tags. `favorite` is a boolean, not a tag.
- - **Data model:** add `favorite: bool = false` and `project: str | null` to the
- artifact record.
-- **Scope — server / client / UI:**
- - **Server:** accept `favorite` and `project` on POST; add endpoints to toggle
- favorite and set/clear project after upload; support `?project=` and
- `?favorite=true` feed filters.
- - **Client (`daybook_post.py`):** add `--project NAME` and `--favorite` flags,
- sent as first-class JSON/form fields — NOT inside `metadata` (see S1, which
- breaks for multipart string fields).
- - **UI:** a favorite (star) toggle and a favorites view; group/filter the feed by
- project.
-
----
-
-## Open — Web UI
-
-### U1 · Feed does not refresh when new artifacts arrive — **Med / Open**
-
-- **Symptom:** The web UI renders a static snapshot of the feed. Artifacts posted
- after the page loads do not appear until the user manually reloads.
-- **Impact:** Agents publish continuously, but a human watching the feed silently
- misses new arrivals — which undercuts Daybook's purpose as a live journal of
- agent output. Observed throughout this session: each test post required a manual
- browser refresh to show up.
-- **Fix options (simplest first):**
- - Client-side polling of the list endpoint on an interval (e.g. every few
- seconds), diffing against the loaded set.
- - Server-sent events (SSE) for push updates — a natural fit for this
- publish-only, append-mostly model and lighter than WebSockets.
- - To avoid disrupting scroll position, surface a "N new artifacts — click to
- load" indicator rather than auto-injecting items at the top.
-
----
-
-## By Design — Worth a Decision
-
-### D1 · Strict rendering, no normalization — **By design**
-
-- **Behavior:** "Daybook does not fix malformed content." Wrong-shaped content is
- stored and rendered as-is.
-- **Observed consequence:** Markdown sent to `deck` (`5df27410`) was stored and
- rendered malformed — no error, just broken output.
-- **Implication:** A validating client is load-bearing. The server's per-kind
- structure rules and the client's pre-flight checks must stay in sync; if the
- server adds a kind, the client's validation/sniffing must follow.
-- **Optional improvement:** Consider rejecting obviously-malformed content with a
- `422` (e.g. `deck` with no ``) so it fails loud at post time rather than
- rendering broken later. This complements, not replaces, the client guard.
-
----
-
-## Fixed — Client (recorded for traceability)
-
-### C1 · Empty `metadata` broke all multipart uploads — **Fixed**
-
-- The helper serialized empty `metadata` as the string `"{}"` into the multipart
- body, triggering S1's 400 even when no metadata was supplied.
-- **Fix:** omit empty optional fields (`metadata`, `tags`, `source_id`,
- `source_prompt`) from the multipart body, restoring the minimal known-good shape.
-
-### C2 · `html_full` client validation was over-strict — **Fixed**
-
-- Originally required BOTH ``; an auto-sniffed full page
- lacking an explicit doctype was rejected.
-- **Fix:** require `` only (doctype recommended, not mandatory).
-
----
-
-## Minor / Cosmetic
-
-### M1 · `deck` title inference picks the first slide heading — **Low / Open**
-
-- Auto mode derived the title "Slide One" from a deck's first `
` (`ac6da43f`).
- Correct but awkward as a title.
-- **Fix:** for `deck`, prefer the filename over the first `
`.
-
-### M2 · CSV content-type guessed as `application/vnd.ms-excel` on Windows — **Low / Open**
-
-- Python's `mimetypes` maps `.csv` to `application/vnd.ms-excel` on this host rather
- than `text/csv`. Harmless — the server still stores it as `binary` — but
- inconsistent with the earlier manual `text/csv` upload.
-- **Fix (optional):** force `text/csv` for `.csv` in the client's `content_type_for`.
-
----
-
-## Test Artifacts Created (for cleanup once S2 lands)
-
-Auto-mode validation posts under persona `claude`, tag `auto-test`:
-`e7a769c5` (markdown), `2d162697` (html_full), `49291e85` (html_fragment),
-`ac6da43f` (deck), `3ad914cc` (image), `794d29e8` (pdf), `c562128b` (binary).
-Plus earlier manual smoke-test artifacts and the malformed deck `5df27410`.
diff --git a/planning/daybook_planning.md b/planning/daybook_planning.md
deleted file mode 100644
index 97df5dd..0000000
--- a/planning/daybook_planning.md
+++ /dev/null
@@ -1,217 +0,0 @@
-# Daybook — Project Planning
-
-## Purpose
-
-**Daybook** is a self-hosted web application where AI agents publish their
-outputs (task lists, research reports, slide decks, images, etc.) and a human
-browses them in a **time-ordered, agent-filterable feed** — viewing everything
-**in-browser without downloading or "opening" files**.
-
-The defining feature is the integration: an agent calls a REST API and posts
-*content*, not files. For HTML it can send just a body fragment and Daybook
-supplies the CSS. The agent never deals with styling, file naming, or storage.
-
-## Core principles
-
-- **View, don't download.** Everything renders inline in the browser.
-- **Agent-agnostic.** Any agent/persona can publish; the human filters by persona.
-- **Time-ordered.** The primary axis is the day; flipping back to yesterday is trivial.
-- **Minimal agent burden.** Agents send content + a few metadata fields. The server
- owns styling, storage layout, IDs, and dates.
-- **Self-hosted.** A single deployable unit running on the user's own machine/server.
-- **Dumb viewer, smart ingest.** Normalize every input to three browser-native forms
- (HTML, image, PDF) at upload time, so the viewer never needs plugins.
-
-## Actors
-
-- **Agent (publisher):** an authenticated machine client (e.g. Claude Code, a
- persona script). POSTs artifacts via REST.
-- **Human (reader):** browses, filters, and views artifacts; later edits markdown
- and shares links.
-
-## Scope
-
-### MVP
-- REST ingest endpoint accepting: HTML fragment, full HTML, markdown, image, PDF, binary.
-- Server-side **named CSS themes**; automatic wrapping of HTML fragments.
-- Storage in a `persona/day/id` layout plus a metadata table.
-- Web viewer: reverse-chronological feed, persona filter, in-browser rendering.
-
-### Later
-- In-browser **markdown editing** with write-back (the name "Daybook" anticipates this).
-- Slide decks via a **reveal.js** theme; `pptx`/`docx`/`xlsx` → PDF fallback.
-- **Share links** (password + expiry) for individual artifacts.
-- Tags, search, and full-text indexing.
-
-## Architecture
-
-```
-Agent ──POST /api/artifacts──▶ Daybook (Django + DRF)
- │
- ├─ normalize-on-ingest
- │ fragment → wrap with theme CSS → .html
- │ markdown → render → .html
- │ deck → reveal.js scaffold → .html
- │ office → LibreOffice headless → .pdf
- │ image/pdf → store as-is
- │
- ├─ write file to artifacts/{persona}/{day}/{id}.{ext}
- └─ insert Artifact row (metadata index)
-
-Human ──GET / (feed)──▶ list Artifact rows (reverse-chron, persona filter)
- ──GET /a/{id}──▶ render via