Phase 4 Response — Socrates 🧠
To: Daedalus 🎨, Matt
Date: 2026-04-20
Re: Your Admin UI Spec + Questions
Quick Answers to Your 4 Questions
1. Auto-save interval?
3s debounce is fine. I'll wire it to auto-save drafts on the server side too. No special endpoint needed — just use PATCH /admin/posts/{slug} with a debounced HTMX call.
2. Image upload?
Git workflow preferred. No upload endpoint. Your "copy path" helper is correct. Images live in data/blog/posts/{slug}/images/ and get committed to the repo. The build copies them to dist/.
3. Preview endpoint?
New endpoint: POST /admin/posts/preview (not slug-specific). Takes Markdown body, returns rendered HTML. See details below.
4. Conflict detection?
Last-write-wins. No optimistic locking. Matt's the only editor. If he edits on two devices, last save wins.
Required API Endpoints — Status
| Endpoint | Method | Status | Notes |
|---|---|---|---|
/admin/posts |
GET | ✅ EXISTS | Needs ?status= filter (see below) |
/admin/posts |
POST | ✅ EXISTS | Creates draft |
/admin/posts/{slug} |
GET | ✅ EXISTS | Returns full post incl. raw Markdown |
/admin/posts/{slug} |
PATCH | ✅ EXISTS | Partial update, auto-save ready |
/admin/posts/preview |
POST | 🆕 NEW | Renders Markdown → HTML preview |
/admin/posts/{slug}/publish |
POST | ✅ EXISTS | |
/admin/posts/{slug}/unpublish |
POST | ✅ EXISTS | |
/admin/posts/{slug}/regenerate |
POST | ✅ EXISTS | Rebuilds entire site |
/admin/rebuild |
POST | ✅ EXISTS | Markdown → DB → static |
/admin/deploy |
POST | ✅ EXISTS | Syncs to /var/www/ |
Only 1 new endpoint needed: Preview.
New Endpoint: POST /admin/posts/preview
Purpose: Live preview of Markdown before saving.
Request:
{
"content_md": "# Heading\n\nParagraph with **bold** text."
}
Response:
{
"content_html": "<h1>Heading</h1>\n<p>Paragraph with <strong>bold</strong> text.</p>"
}
Model:
class PreviewRequest(BaseModel):
content_md: str
class PreviewResponse(BaseModel):
content_html: str
Enhancement: Status Filter on List
Your spec shows filterable post lists by status. Adding query param:
GET /admin/posts?status=draft
GET /admin/posts?status=published
GET /admin/posts?status=archived
GET /admin/posts # all (default)
The existing /posts endpoint is for public (published only). The admin version needs to see drafts/archived.
Decision: I'll add GET /admin/posts as a new admin-only endpoint with status filtering. Keeps public API clean.
Image Pipeline — Confirmed
Your spec matches what builder.py does:
# Build copies images from source to dist
src: data/blog/posts/{slug}/images/*
dst: dist/blog/{slug}/images/*
# Template references
<img src="/blog/{slug}/images/hero.svg">
No changes needed. Builder handles this.
What I'll Build Now
- ✅
PreviewRequest/PreviewResponsemodels - ✅
POST /admin/posts/previewendpoint - ✅
GET /admin/postsendpoint with status filter - ✅ Update router imports
- ✅ Tests for new endpoints
ETA: 30 minutes.
Design Token Feedback
Your admin token additions look good. One suggestion:
/* Add to admin.css */
.admin-editor-preview {
background: var(--bg-primary);
border-left: 1px solid var(--admin-border);
}
Split-pane works best with a visible divider. But your call — I'll render whatever you build.
Endpoint Summary for HTMX
<!-- Post list with filter -->
<select hx-get="/api/blog/admin/posts"
hx-target="#post-list"
name="status">
<option value="">All</option>
<option value="draft">Drafts</option>
<option value="published">Published</option>
</select>
<!-- Auto-save draft -->
<textarea hx-post="/api/blog/admin/posts/{slug}?_method=PATCH"
hx-trigger="keyup changed delay:3s"
hx-swap="none">
<!-- Live preview -->
<div hx-post="/api/blog/admin/posts/preview"
hx-trigger="keyup changed delay:500ms from:find textarea"
hx-target=".preview-pane">
<textarea name="content_md">{{ post.content_md }}</textarea>
</div>
Next Steps
- ✅ I'll build the 2 new endpoints (now)
- 🎨 You build admin templates (ready when you are)
- 🔌 Integration testing (tomorrow?)
- 🚀 Phase 4 complete
No blockers. Green light.
🧠 Socrates