Files
spine/docs/specs/2026-06-22-shelves-data-model-design.md
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.3 KiB

Shelves data model

Replace the TODO-state-based book model with shelf-based organization. Shelves are top-level Org headings; books are nested underneath them.

Motivation

Books-as-top-level-headings with TODO states (WANT/READING/READ/ABANDONED) made the file's structure reflect lifecycle status rather than organizing concepts. The user wants arbitrary grouping ("shelves") as the primary organizing axis. TODO states are removed — logbook entries (already present) record when reading happened.

Org data model

#+TITLE: Spine

* Fiction
** Piranesi
:PROPERTIES:
:AUTHOR:  Susanna Clarke
:FORMAT:  ebook
:REC_BY:  Alex
:REC_NOTE: Le Guin at her most human
:RATING:
:ADDED:   [2026-04-01]
:LAST_MODIFIED: [2026-04-01]
:ID:      5e8d-pir
:END:
::LOGBOOK:
- State "READ" from "WANT" [2026-01-10]
::END:
- [2025-11-22] The pronoun game is a genuinely fresh take on identity.

* Non-Fiction
** The Checklist Manifesto
...
  • Shelves are level-1 headlines (*)
  • Books are level-2 headlines (**)
  • Books keep their TODO keyword syntax for org-mode compatibility, but the app ignores it entirely
  • The #+TODO: line is removed — no TODO states
  • Empty shelves (a level-1 headline with no children) are valid and appear in the UI
  • LOGBOOK drawers remain for historical state-change records but are not surfaced by the UI

Key functions

spine-shelves (new)

Returns an ordered list of shelf names (strings), one per level-1 headline that is not a COMMENT or archive target. Empty shelves are included.

spine-books (modified)

Walks level-1 headlines as shelves. For each shelf, walks level-2 children as books. Returns flat plist list; each book plist gains a :shelf key with the parent headline text. No status/TODO filtering.

Places that previously checked Todo state (e.g. spine-set-status) are removed — the only remaining mutable fields are notes, rating, format.

spine-index-model (modified)

Groups books by :shelf instead of :status. Accepts an optional shelf filter (instead of read/want/all) to show one shelf. Includes shelf nav items for all shelves (even empty ones, from spine-shelves).

Removes:

  • Summary groups (READ/ABANDONED links)
  • Concise view truncation
  • Status-related book model fields (status_class, status_label)

spine-add-book (modified)

Takes required :shelf argument. Inserts the new book heading under the matching level-1 shelf headline instead of at file end. If the shelf doesn't exist, signals an error (shelves are created manually).

Removes:

  • org-todo "WANT" call
  • #+TODO: file initialization
  • Category field logic (replaced by shelf)

Functions removed

  • spine-set-status — no status to change. Logbook records history.
  • All HTTP endpoints and UI actions that invoke it.

UI changes

Index view (index.mustache)

  • Titlebar: N books · M shelves
  • Filter bar: replaced with shelf nav — links for each shelf name plus "All" (default). Active shelf highlighted.
  • Groups: one section per shelf. Each shelf shows its books in the existing row format, minus the status pill.
  • Empty shelves: shown as a group with the shelf name and "(empty)" subtext. No expandable books.
  • Minibuffer (when a book is selected): simplified to two actions — Add note, Set rating. Same for every book.

Add form (add.mustache)

  • "Category" free-text field replaced with "Shelf" <select> dropdown populated from spine-shelves.
  • Required. No datalist — only existing shelves.
  • All other fields unchanged.

Sample file

sample-books.org is restructured to the new shelf layout with shelves like "Fiction", "Non-Fiction", "Science Fiction".

Test changes

  • spine-books-parses-sample: loads restructured sample, asserts flat book list with :shelf keys.
  • spine-books-first-book-has-all-fields: adds :shelf assertion.
  • spine-books-missing-properties-are-nil: adds :shelf check.
  • spine-index-model-*: rewritten — tests shelf grouping, shelf filtering, empty-shelf inclusion. Status-based tests removed.
  • spine-add-book-*: tests require :shelf arg. Removes status check. Tests that books land under the correct shelf heading.
  • spine-edit-*: keeps note/rating tests. Removes status-change tests. Updates model-assertion tests for new minibuffer shape.
  • New spine-shelves tests: parses shelf names, includes empty shelves.