📄 phase-4-socrates-response.md 4,530 bytes Apr 20, 2026 📋 Raw

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

  1. PreviewRequest / PreviewResponse models
  2. POST /admin/posts/preview endpoint
  3. GET /admin/posts endpoint with status filter
  4. ✅ Update router imports
  5. ✅ 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

  1. ✅ I'll build the 2 new endpoints (now)
  2. 🎨 You build admin templates (ready when you are)
  3. 🔌 Integration testing (tomorrow?)
  4. 🚀 Phase 4 complete

No blockers. Green light.


🧠 Socrates