Web UI: library, book detail, edit, kobo, errata + OIDC + Reading Room design #2

Merged
arne merged 26 commits from webui into main 2026-04-11 07:59:46 +02:00
Owner

Plan 2 of 4. Adds the human-facing surface of the books server, the Reading Room visual design, and switches the project to self-hosted Newsreader fonts so books never contacts an external server at runtime.

What's in here

Design — Reading Room

A literary, serif-only visual language. Single typeface (Newsreader) at every size and weight, ink on aged paper, no rectangular chrome, no buttons, no input boxes. Site nav is a single fixed dropdown in the top-right corner. Direction notes in design/specs/2026-04-10-reading-room.md; full design system at design/index.html with three preview pages under design/preview/. Newsreader fonts are self-hosted (six woff2 subsets, ~516 KB) and embedded into the binary.

Auth

  • internal/auth — go-oidc/v3 verifier wrap, session lifecycle (create / lookup / sliding renewal / delete), RequireSession middleware, RequireOrigin CSRF guard
  • Sessions table CRUD with sliding 30-day expiry; auto-renewed by middleware when past half-life
  • Single allowed subject enforced both at exchange time and on every request
  • Session cookie HttpOnly + Secure + SameSite=Lax; OAuth state cookie matches

Store additions

  • sessions CRUD (CreateSession, GetSession, RenewSession, DeleteSession, DeleteExpiredSessions)
  • devices CRUD (CreateDevice, ListDevices, GetDeviceByToken, GetDeviceByID, DeleteDevice, TouchDeviceLastSeen)
  • books additions: GetBookByUUID, SearchBooks (LIKE on title and author, case-insensitive), UpdateBookMetadata (preserves file_path / file_hash / added_at / uuid)

Library additions

  • Rename and RenameCover for metadata-edit-driven on-disk moves; cleans up empty author directories

Inbox additions

  • ListErrata + RetryErratum for the errata page; retry rejects path traversal

Web

  • internal/web — Handler, Renderer, base layout + 5 page templates (library, book, edit, kobo, errata)
  • One file per page handler: library.go, book.go, edit.go, kobo.go, errata.go, auth.go, healthz.go (collapsed into handler.go)
  • Byline parser/formatter for the editorial 'Smith, Jones & Brown' format, roundtrip-tested
  • Cover and epub download served from disk via http.ServeFile, with safe Content-Disposition encoding
  • Background goroutine cleans expired sessions hourly

main.go

  • Embeds design/ via //go:embed, serves at /static/
  • OIDC discovery at startup
  • Mounts HTTP on 127.0.0.1:8090
  • Signal-handled shutdown with 10-second drain
  • Inbox watcher and session cleanup as background goroutines

Out of scope (plans 3-4)

  • /api/kobo/{token}/v1/* sync endpoints (the kobo page generates URLs that 404 until plan 3)
  • Snapshot algorithm and reading-state push/pull
  • Deployment script, systemd unit, Caddy config
  • Logout link in the page-nav (POST to /auth/logout works)
  • Cover thumbnails on the library list (text-only for now)
  • Templated error pages with incident IDs

Test plan

  • go test ./... clean across all 8 packages (config, store, library, epub, importer, inbox, auth, web)
  • go test -race ./... clean
  • Two startup error paths verified hands-off: missing config exits 1 fast, bogus OIDC issuer fails at discovery and exits 1
  • Full smoke test (you, after merge): set BOOKS_OIDC_* against pocket-id, run, log in, drop a real epub, browse the library, edit metadata, register a Kobo, drop junk into inbox, retry from /errata

Known follow-ups (deferred)

  • /api/kobo/* — entirely plan 3
  • internal/store/devices.go: getDevice uses string concat for the WHERE column (constants only, no injection risk; cosmetic)
  • humanTime returns 'last week' for everything 7-30 days old (cosmetic)
  • Auth.Store() accessor is unused (dead code, harmless)

Plan: docs/superpowers/plans/2026-04-10-webui-auth.md
Design spec: design/specs/2026-04-10-reading-room.md

Plan 2 of 4. Adds the human-facing surface of the books server, the Reading Room visual design, and switches the project to self-hosted Newsreader fonts so books never contacts an external server at runtime. ## What's in here ### Design — Reading Room A literary, serif-only visual language. Single typeface (Newsreader) at every size and weight, ink on aged paper, no rectangular chrome, no buttons, no input boxes. Site nav is a single fixed dropdown in the top-right corner. Direction notes in design/specs/2026-04-10-reading-room.md; full design system at design/index.html with three preview pages under design/preview/. Newsreader fonts are self-hosted (six woff2 subsets, ~516 KB) and embedded into the binary. ### Auth - internal/auth — go-oidc/v3 verifier wrap, session lifecycle (create / lookup / sliding renewal / delete), RequireSession middleware, RequireOrigin CSRF guard - Sessions table CRUD with sliding 30-day expiry; auto-renewed by middleware when past half-life - Single allowed subject enforced both at exchange time and on every request - Session cookie HttpOnly + Secure + SameSite=Lax; OAuth state cookie matches ### Store additions - sessions CRUD (CreateSession, GetSession, RenewSession, DeleteSession, DeleteExpiredSessions) - devices CRUD (CreateDevice, ListDevices, GetDeviceByToken, GetDeviceByID, DeleteDevice, TouchDeviceLastSeen) - books additions: GetBookByUUID, SearchBooks (LIKE on title and author, case-insensitive), UpdateBookMetadata (preserves file_path / file_hash / added_at / uuid) ### Library additions - Rename and RenameCover for metadata-edit-driven on-disk moves; cleans up empty author directories ### Inbox additions - ListErrata + RetryErratum for the errata page; retry rejects path traversal ### Web - internal/web — Handler, Renderer, base layout + 5 page templates (library, book, edit, kobo, errata) - One file per page handler: library.go, book.go, edit.go, kobo.go, errata.go, auth.go, healthz.go (collapsed into handler.go) - Byline parser/formatter for the editorial 'Smith, Jones & Brown' format, roundtrip-tested - Cover and epub download served from disk via http.ServeFile, with safe Content-Disposition encoding - Background goroutine cleans expired sessions hourly ### main.go - Embeds design/ via //go:embed, serves at /static/ - OIDC discovery at startup - Mounts HTTP on 127.0.0.1:8090 - Signal-handled shutdown with 10-second drain - Inbox watcher and session cleanup as background goroutines ## Out of scope (plans 3-4) - /api/kobo/{token}/v1/* sync endpoints (the kobo page generates URLs that 404 until plan 3) - Snapshot algorithm and reading-state push/pull - Deployment script, systemd unit, Caddy config - Logout link in the page-nav (POST to /auth/logout works) - Cover thumbnails on the library list (text-only for now) - Templated error pages with incident IDs ## Test plan - [x] go test ./... clean across all 8 packages (config, store, library, epub, importer, inbox, auth, web) - [x] go test -race ./... clean - [x] Two startup error paths verified hands-off: missing config exits 1 fast, bogus OIDC issuer fails at discovery and exits 1 - [ ] Full smoke test (you, after merge): set BOOKS_OIDC_* against pocket-id, run, log in, drop a real epub, browse the library, edit metadata, register a Kobo, drop junk into inbox, retry from /errata ## Known follow-ups (deferred) - /api/kobo/* — entirely plan 3 - internal/store/devices.go: getDevice uses string concat for the WHERE column (constants only, no injection risk; cosmetic) - humanTime returns 'last week' for everything 7-30 days old (cosmetic) - Auth.Store() accessor is unused (dead code, harmless) Plan: docs/superpowers/plans/2026-04-10-webui-auth.md Design spec: design/specs/2026-04-10-reading-room.md
arne added 26 commits 2026-04-11 07:20:35 +02:00
A literary visual direction for the books web UI: single typeface
(Newsreader), aged-paper colour palette, no rectangular chrome, no
buttons, no input boxes. The library list is a typeset bibliography;
metadata editing feels like marking up galley proofs. Direction note
in design/specs/2026-04-10-reading-room.md, design system showcase in
design/index.html, library and book detail mockups under
design/preview/.
The service name now sits as italic small-caps at marginalia size in
soft ink — a footer-style signature, not a logo. The page name (folio-
page) takes the visual focus instead, set in display Newsreader italic
at h4 size in primary ink.
The header is gone entirely. The whole site navigation is one quiet
<select> fixed to the top-right corner of every page. Native dropdown,
no JavaScript framework, accessible by default. Page identity is now
carried by the content itself instead of a header band — a library
list is the library, a book heading is the book.
Replaces the native <select> with a button + styled list panel so the
dropdown options can be set in italic Newsreader on paper instead of
falling back to native browser chrome. ~40 lines of vanilla JS in
nav.js handles open/close/outside-click/Escape; the trigger is a real
button, the chevron rotates 180° on open, the current page is marked
with a → in the panel.
Two related rename passes through the spec, design system page, design
spec, and preview HTML.

The /failed page becomes /errata — bookish, accurate, in voice with the
Reading Room aesthetic. (The on-disk inbox/.failed/ directory is left
alone; it's implementation detail and renaming touches plan 1 code.)

The /devices page becomes /kobo (singular) to reflect the actual scope:
this server is exactly one user with exactly one Kobo. The devices
table and CRUD stay multi-row capable, but the web UI only ever surfaces
the single row.
- inbox/.failed/ becomes inbox/.errata/ in importer.go, scanner.go,
  their tests, and the spec, so on-disk and user-facing names match.
  Test func renamed accordingly.
- Drop the uuid line and the sha256 from the book detail marginalia.
  The 'file' label becomes 'size'. UUIDs are an internal id surfaced
  to the Kobo, never useful in the human UI.
A two-column edit form: labels hang in the right-aligned left column
like marginalia, inputs flow in the right column. Inputs use display
Newsreader at h5 for short fields and text Newsreader for prose, all
with the bottom-rule no-box treatment. Cover field shows a small thumb
plus a file input. Stamp 'unsaved changes' marker is in scope. The
prototype-header breadcrumb in book.html and index.html now links to
the new edit preview.
The metadata form's authors field becomes a single-line input following
the editorial convention 'Smith, Jones & Brown' — comma-separated, with
& before the last name. Reads more like a book's title page byline than
a list of names. Updated edit preview, design system context demo, and
the spec.
Embed all six woff2 subsets (latin, latin-ext, vietnamese × normal,
italic) directly under design/fonts/. Total ~516 KB. style.css
@font-face rules now load from the local files; the family is just
'Newsreader' (variable font, optical sizing handled by the browser).
Removed every Google Fonts <link> from design/index.html, design/
preview/*.html, and the plan-2 base.html template. The runtime binary
contacts no external font server.
arne merged commit b6e461c53b into main 2026-04-11 07:59:46 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
arne/books!2
No description provided.