Files
spine/spine-spec.md

6.3 KiB
Raw Permalink Blame History

Spine — design brief

A personal library and reading tracker. Tracks books I want to read, am reading, have read, and what I think about them; captures notes repeatedly through a read; and records who recommended a book and why.

This document is a starting point for a coding agent. Several decisions are deliberately left open (see "Open decisions"). Do not invent answers for those — scaffold around them or ask.

Core features

  • Track each book through a reading lifecycle: want → reading → read (or abandoned).
  • Capture multiple timestamped notes across a single read, not one review at the end.
  • Record recommendations: who recommended a book and a short "why it's on my radar" note. This is context shown inline, not a search/analytics concern.
  • Flag the format consumed: hardcover, ebook, or audiobook.
  • Rate finished books.
  • Browse "on deck" (want-to-read) and optionally filter by category.

Architecture

  • Source of truth is a single Org file. No database.
  • Emacs owns the file. All reads and writes go through a running Emacs via emacsclient, so org-element does the parsing and serialization canonically. This avoids third-party Org parser fidelity problems on write.
  • No read/search index initially. Queries are simple status + tag filters answered live by org-ql. A derived SQLite index can be added later if the recommendation data ever needs real querying; it is explicitly out of scope now.
  • The web layer is a thin client over emacsclient. Its language and framework are undecided (see Open decisions).

Data model (Org)

Each book is a top-level headline whose TODO state is its lifecycle status.

#+TITLE: Spine
#+TODO: WANT(w) READING(r) | READ(d) ABANDONED(a)
#+STARTUP: logdrawer

* READING Use of Weapons
:PROPERTIES:
:AUTHOR:  Iain M. Banks
:FORMAT:  audiobook
:REC_BY:  Priya
:REC_NOTE: If you liked Player of Games, this one will wreck you in a better way.
:RATING:  
:ADDED:   [2026-02-20]
:ID:      8c1e-uow
:END:
:LOGBOOK:
- State "READING" from "WANT" [2026-02-20]
:END:
- [2026-03-02] The two-track structure is doing something I can't name yet.
- [2026-03-07] Zakalwe's competence reads as a wound. Banks never lets a skill be free.
- [2026-03-09] The chapter numbering. Oh.

Field notes:

  • Status: the TODO keyword. WANT READING | READ ABANDONED.
  • Status history: comes free from the LOGBOOK drawer when org-log-into-drawer is enabled. Distinct from reading notes.
  • Reading notes: a plain Org list in the headline body, each item prefixed with an inactive timestamp [YYYY-MM-DD]. Appending a note is inserting one list item. Notes are not individually queried.
  • Format: single :FORMAT: property — hardcover | ebook | audiobook. (See Open decisions for the multi-format case.)
  • Recommendation: :REC_BY: plus optional :REC_NOTE:. Surfaced inline as context, never ranked or searched.
  • Category: Org tags on the headline (:scifi:literary:), not a property — tags filter natively and inherit.
  • Rating: :RATING: 15, set when moved to READ.

emacsclient integration

Read: on-deck list

(require 'org-ql)
(require 'json)

(defun spine-ondeck (&optional tag)
  "JSON of WANT books, optionally filtered by TAG."
  (json-serialize
   (vconcat
    (org-ql-select "~/spine/books.org"
      (if tag `(and (todo "WANT") (tags ,tag)) '(todo "WANT"))
      :action
      (lambda ()
        (list :title  (org-get-heading t t t t)
              :author (org-entry-get nil "AUTHOR")
              :format (org-entry-get nil "FORMAT")
              :rec_by (org-entry-get nil "REC_BY")
              :tags   (vconcat (org-get-tags))))))))

(defun spine-ondeck-file (path &optional tag)
  "Write `spine-ondeck' output to PATH (avoids emacsclient quoting)."
  (with-temp-file path (insert (spine-ondeck tag))))

Invoke: emacsclient --eval '(spine-ondeck-file "/tmp/spine.json")', then read the file from the web layer. (emacsclient --eval prints values with prin1, so returning a JSON string directly comes back quoted/escaped — the temp-file hop sidesteps that. A small simple-httpd endpoint is the alternative; undecided.)

Write: to implement

  • spine-add-note — append a [today] text list item under a book's headline.
  • spine-add-book — insert a new WANT headline with initial properties.
  • spine-set-status / spine-set-rating / spine-set-format.

These set state through org-element / standard Org commands, not text munging. Not yet written.

UI

Two directions are mocked as standalone HTML (spine-mockup-a-agenda.html, spine-mockup-b-journal.html). They render the same Org record through different front doors.

  • Direction A — agenda (TUI). An org-agenda-style surface grouped by status. Format shows as a leading glyph, the recommendation rides on the row as "why it's here," the selected book expands inline to show its reading log, and capture happens in a minibuffer rather than a form. This is the home surface.

  • Direction B — journal card. A book detail card. Format is a segmented control, the reading log is a thread you append to with an inline composer, and the recommendation has an explicit "who / why" capture form. This is the drill-in view.

Recommended composition: hybrid. Direction A as the home/browse surface, Direction B as the detail view when a book is selected. Agenda answers "what now," card answers "where am I with this one." Not locked in — building one alone is fine.

Open decisions

  • Glue language + web framework — undecided. Possibly elisp end-to-end (Emacs serves the UI too), possibly an external thin client shelling out to emacsclient. Leave this unbound; don't hardwire a stack.
  • Read transport: temp-file hop vs simple-httpd endpoint in Emacs.
  • Per-note format: book-level :FORMAT: is the default. If a book is regularly consumed across media (hardcover at home, audio on a commute), format may need to move to per-note. Single-value for now.
  • Hybrid vs single UI direction — see UI section.
  • Search index: deferred. Not now.

Out of scope (for now)

  • Recommender ranking / "hit rate" analytics.
  • Full-text search across notes.
  • Any external book-metadata API (covers, ISBN lookup) — placeholder cover blocks in the mockups are decoration only.