# 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=`** — 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: - `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: ```elisp (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)