- Add spine--format-defs defconst shared between index and book models - Add spine-book-model with status extraction, format selector, notes, recommendation - Add templates/book.mustache (Pico CSS, journal-style layout) - Add httpd/book handler serving the detail page - Link book titles in index view to /book?id=X - Add spine-book-model-test.el with 13 tests
4.4 KiB
Book Detail View Design
Date: 2026-06-22 Status: Draft
Summary
Add a dedicated book detail page (/book?id=X) following the journal-style mockup (Direction B). The page shows full book metadata, format badges, reading log, note composer, and recommendation section. Uses Pico CSS and Tabler icons, matching the add-book template style.
Route
GET /book?id=<id>— renders the full detail page- No POST handling (editing flows go through existing
/edit) - Book not found → redirect to
/index
Model: spine-book-model
Builds an ht (hashtable) view model from one book plist.
Fields
| Field | Type | Source | Notes |
|---|---|---|---|
cover_icon |
string | hardcoded "ti-book" |
Placeholder cover icon |
title |
string | :title minus TODO prefix |
Strip leading WANT , READING , READ |
author |
string | :author |
Empty string when nil |
status_class |
string | TODO prefix → lowercase | "want" / "reading" / "read" / "" |
status_label |
string | TODO prefix → capitalized | "Want" / "Reading" / "Read" / "" |
meta |
string | format + added date | e.g. "started 20 Feb · audiobook". Omits date if not present, format if not present |
progress_label |
string | — | Empty string; reserved for future progress tracking |
formats |
array | all 3 known formats | [{icon, label, active}]. Active matches :format property |
notes |
array | :notes |
[{date, text}]. Empty array when no notes |
recommendation |
object or nil | :rec_by + :rec_note |
{initials, by, note}. nil when no rec_by |
Status extraction
Book headlines have TODO keyword prefixes (WANT, READING, READ) as part of their title text. The model function:
- Strips the prefix from the title for display
- Maps recognized prefixes to status classes
Format list
Hardcoded list of three known formats:
hardcover→ti-book→ "Hardcover"ebook→ti-device-tablet→ "eBook"audiobook→ti-headphones→ "Audiobook"
The book's :format property determines which is active: true; the others are active: false.
Recommendation
Generated from :rec_by (name) and :rec_note (text). No date stored currently.
initials derived from the name: uppercase first letter of each space-separated part. e.g. "Priya" → "P", "John Doe" → "JD".
Template: templates/book.mustache
Based on spine-mockup-b-journal.mustache with adaptations:
- Pico CSS framework
- Tabler icons for icons
- Progress-related text omitted from the composer placeholder (says "Add a note…" without percentage)
- Back link at top to return to index with this book selected (
/index?id=X) - No recommendation date field (not stored in data model)
- Conditional rendering of recommendation section
Handler: httpd/book
Registered as httpd/book matching the existing add/edit handler pattern:
(defun httpd/book (proc uri-path query request)
"Show the journal-style detail page for a single book."
(let* ((id (cadr (assoc "id" query)))
(books (spine-books))
(book (cl-find id books :key (lambda (b) (plist-get b :id)) :test #'equal)))
(if book
(insert (spine-render "book.mustache" (spine-book-model book)))
(httpd-redirect proc "/index" 302))))
| Action | File | Description |
|---|---|---|
| New | templates/book.mustache |
Pico CSS + Tabler detail page |
| Modify | spine.el |
Add spine-book-model, httpd/book handler, title link in index model |
| New | test/spine-book-model-test.el |
Tests for spine-book-model |
| Modify | test/spine-index-model-test.el |
Tests for title link in index model |
Edge cases
- No TODO prefix on headline → title shown as-is, status empty
- Unknown TODO prefix (not WANT/READING/READ) → title shown with prefix, status empty
- Book not found → redirect to
/index - No format property → no format active in the selector
- No author → empty string
- No notes → empty reading log section
- No recommendation → section not rendered (optional block)
Testing
Test file test/spine-book-model-test.el covers:
- Title stripped of TODO prefix
- Status class derived correctly for WANT/READING/READ
- Meta string from format + added date
- Format list with correct active flag
- Notes mapping
- Recommendation with initials
- No recommendation when rec_by absent
- Book with no TODO prefix (handles gracefully)
- Book with no format or date (meta is empty)