Files
spine/docs/specs/2026-06-22-book-detail-view-design.md
T
jbrechtel df1a7aebc9 feat: add book detail view with spine-book-model, httpd/book handler, and title links
- 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
2026-06-22 22:15:51 -04:00

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:

  1. Strips the prefix from the title for display
  2. Maps recognized prefixes to status classes

Format list

Hardcoded list of three known formats:

  • hardcoverti-book → "Hardcover"
  • ebookti-device-tablet → "eBook"
  • audiobookti-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)