diff --git a/docs/plans/2026-06-20-pico-css-styling.md b/docs/plans/2026-06-20-pico-css-styling.md new file mode 100644 index 0000000..58a0c49 --- /dev/null +++ b/docs/plans/2026-06-20-pico-css-styling.md @@ -0,0 +1,365 @@ +# Pico CSS Styling โ€” Implementation Plan + +**Goal:** Rewrite `spine-render-index` and `spine-render-empty-state` to emit Pico CSS-styled HTML via CDN, replacing the mockup-A `
` structure with Pico-native semantic elements plus a small inline `\n") + (insert "\n\n") + (insert "
\n") + (insert "
\n") + (insert (format "
spine" + "%d books ยท %d reading
\n" + total reading-count)) + ;; Each group in order + (dolist (status order) + (let ((group-books (nreverse (gethash status grouped)))) + (when group-books + (insert (format "

%s

%d
\n" + status (length group-books))) + (dolist (book group-books) + (let ((selected (equal (plist-get book :id) selected-id))) + (insert (format "
\n" + (if selected " spine-selected" ""))) + ;; Format glyph (emoji) + (let ((fmt (plist-get book :format))) + (insert (format "%s\n" + (cond ((equal fmt "hardcover") "๐Ÿ“–") + ((equal fmt "ebook") "๐Ÿ“ฑ") + ((equal fmt "audiobook") "๐ŸŽง") + (t ""))))) + ;; Title + author + (insert "") + (insert (spine--h (plist-get book :title))) + (let ((author (plist-get book :author))) + (when author + (insert (format " %s" + (spine--h author))))) + (insert "\n") + ;; Tags + (let ((tags (plist-get book :tags))) + (when tags + (insert (format "%s\n" + (mapconcat (lambda (tag) (concat ":" tag ":")) + tags " "))))) + ;; Recommendation trail + (let ((rec-by (plist-get book :rec_by)) + (rec-note (plist-get book :rec_note))) + (when (and rec-by (> (length rec-by) 0)) + (insert + (format "%s%s\n" + (spine--h rec-by) + (if (and rec-note (> (length rec-note) 0)) + (concat ": " (spine--h rec-note)) + ""))))) + ;; Status pill + (let ((status (or (plist-get book :status) "WANT"))) + (insert (format "%s\n" + (downcase status) (downcase status)))) + ;; Expanded detail + (when selected + (insert "
\n") + (dolist (note (plist-get book :notes)) + (insert (format "

[%s] %s

\n" + (spine--h (car note)) + (spine--h (cadr note))))) + (let ((parts nil)) + (let ((rating (plist-get book :rating))) + (when (and rating (> (length rating) 0)) + (push (format "Rating: %s/5" (spine--h rating)) parts))) + (let ((isbn (plist-get book :isbn))) + (when (and isbn (> (length isbn) 0)) + (push (format "ISBN: %s" (spine--h isbn)) parts))) + (let ((fmt (plist-get book :format))) + (when (and fmt (> (length fmt) 0)) + (push (format "Format: %s" (spine--h fmt)) parts))) + (let ((added (plist-get book :added))) + (when (and added (> (length added) 0)) + (push (format "Added: %s" (spine--h added)) parts))) + (when parts + (insert (format "
%s
\n" + (mapconcat #'identity (nreverse parts) " ยท "))))) + (insert "
\n")) + (insert "
\n")))))) + (insert "
\n") + (insert "
\n\n\n") + (buffer-string)))) +``` + +- [ ] **Step 2: Verify batch load** + +```bash +emacs --quick --batch --eval "(setq spine-skip-server-start t)" --load spine.el +``` + +Expected: exits cleanly, no errors. + +- [ ] **Step 3: Verify rendering against sample data** + +```bash +emacs --quick --batch \ + --eval "(setq spine-skip-server-start t spine-org-file \"$(pwd)/sample-books.org\")" \ + --load spine.el \ + --eval "(progn (message \"render-index length: %d\" (length (spine-render-index (spine-books) nil))))" +``` + +Expected: non-zero length, no errors. + +- [ ] **Step 4: Commit** + +```bash +git add spine.el +git commit -m "feat: restyle index page with Pico CSS and emoji glyphs" +``` + +--- + +### Task 3: Smoke test end-to-end + +**Files:** +- No changes โ€” verification only. + +- [ ] **Step 1: Kill existing server and start fresh** + +```bash +emacsclient --socket-name=spine --eval '(kill-emacs)' 2>/dev/null +pkill -f "emacs --quick" 2>/dev/null +sleep 1 +emacs --quick \ + --eval "(setq spine-org-file \"$(pwd)/sample-books.org\")" \ + --load spine.el & +EMACS_PID=$! +sleep 5 +``` + +- [ ] **Step 2: Verify Pico CSS is linked** + +```bash +curl -s http://localhost:8080/index | grep -c "picocss" +``` + +Expected: `1` (the CDN `` is present). + +- [ ] **Step 3: Verify no Tabler Icons reference** + +```bash +curl -s http://localhost:8080/index | grep -c "tabler" +``` + +Expected: `0`. + +- [ ] **Step 4: Verify groups in correct order** + +```bash +curl -s http://localhost:8080/index | grep -oP '

[^<]+' +``` + +Expected: +``` +WANT +READING +READ +``` +No ABANDONED. + +- [ ] **Step 5: Verify emoji glyphs and status pills** + +```bash +curl -s http://localhost:8080/index | grep -E "๐Ÿ“–|๐Ÿ“ฑ|๐ŸŽง" | wc -l +``` + +Expected: `3` (three format glyphs present โ€” Piranesi has no format). + +```bash +curl -s http://localhost:8080/index | grep -oP '[^<]+' +``` + +Expected: `want`, `reading`, `read` pills visible. + +- [ ] **Step 6: Verify expanded detail** + +```bash +curl -s "http://localhost:8080/index?id=8c1e-uow" | grep -oP 'spine-detail' +``` + +Expected: `spine-detail` present (2 occurrences โ€” opening and closing). + +```bash +curl -s "http://localhost:8080/index?id=8c1e-uow" | grep -c "logline\|spine-detail" +``` + +Expected: detail section renders. + +- [ ] **Step 7: Verify recommendation trail** + +```bash +curl -s http://localhost:8080/index | grep "Priya" +``` + +Expected: output contains `Priya: If you liked Player of Games, this one will wreck you`. + +- [ ] **Step 8: Verify empty state** + +```bash +kill $EMACS_PID 2>/dev/null +sleep 1 +emacs --quick \ + --eval "(setq spine-org-file \"/tmp/nonexistent-books.org\")" \ + --load spine.el & +EMACS_PID=$! +sleep 5 +curl -s http://localhost:8080/index | grep -c "No books yet" +``` + +Expected: `1`. + +- [ ] **Step 9: Stop server** + +```bash +kill $EMACS_PID 2>/dev/null +``` + +--- + +### Task 4: Run ERT tests and final verification + +**Files:** +- No changes โ€” verification only. + +- [ ] **Step 1: Run full ERT test suite** + +```bash +emacs --quick --batch \ + --directory "$(pwd)" \ + --load test/spine-books-test.el \ + -f ert-run-tests-batch-and-exit +``` + +Expected: all 6 tests pass. + +- [ ] **Step 2: Verify git status** + +```bash +git status --short +``` + +Expected: no modified tracked files (only pre-existing untracked files). + +- [ ] **Step 3: Review commit log** + +```bash +git log --oneline -4 +``` + +Expected: 2-3 new commits on top of previous work.