TestDocsRelativeLinks walked only docs/developer; widening it to all of
docs/ immediately surfaced the same class of rot the gate was built
for, outside its old reach:
- docs/index.md was the old monolith's landing page: linked the deleted
api-reference.md, 10 directory-style area links, and escaped the docs
tree to README/AGENTS/.env/compose. Rewritten as a portal whose every
link resolves, with one representative endpoint doc per API area and
pointers to the new writing-ui-docs.md standard.
- directory-style links in user stubs (devices/, ../operations/)
- case-mismatched links (Development.md) in contributing.md
- monolith-era pointer (API_REFERENCE.md, TROUBLESHOOTING.md) and
escaped AGENTS.md links in contributing/development.md
- docs/development/calibre-opf-implementation.md double-up escapes, a
link to the removed repo-root plan file (absorbed here), and a
source-code hyperlink the docs site can't serve (now inline code)
- operations/operations.md linked three never-written guide pages as
aspirational links - they stay plain text until pages exist
HANDS-OFF (project owner, 2026-11-02): docs/user/filters/** and
docs/user/searching-and-filtering.md are exempt as link sources so no
future checker failure can force edits to the filter documentation.
Files linking INTO them remain checked.
The old page was stale in ways the standard now forbids: spatial
language ("top-right"), documented URL query params users should never
need, no mention of smart collections or podcast episodes, no hand-off
to the filter docs.
Ground-truthed against the templates: Static vs lightning-Smart type
choice, Books/Podcast episodes rules scope, Add Items picker ( Books /
Episodes tabs, Add Selected, Remove Selected), Edit Rules -> Filter
tree -> Apply to Collection, Play as Sequence with the listening-order
picker (Feed order / reversed / oldest / newest), Hide finished books
(reading-queue mode) and Show Up Next strip toggles, system shelves
(Continue Reading, Recently Added, Recently Read, Not Started, Continue
Series) and Restore System -> Restore.
Plain language per the user-docs tone contract: short sentences,
pattern-first (one shelf concept, four flavors), every step ends in an
outcome, rules taught by LINK to the filter guide (reference, never
restate). Every quoted control exists as a literal and is glossaried in
ui-controls.md (gate-checked).
The filter manual (docs/user/filters/), folded in from the parked plan
now that the UI matches the backend (group ALL/ANY header toggle,
2344f568):
- index.md — the guide: the three homes of filters (drawer/saved
filters, smart collections, dashboard sections), the books/episodes
scope door, rule anatomy, value picking (chips with x, live counts
under other rules), GROUPS via the new header toggle (+ the row
chips as the fine-tuning tool), the live match count, today's
defaults (reversed listening order, family-lens scoping,
un-downloaded episodes), saving and shareable views.
- cookbook.md — seven exact-clicks recipes (born-reversed briefing,
OR-across-authors, big-unread-fantasy, downloaded-unplayed,
new-arrivals, dashboard shelf, shareable views). Every claim
cross-checked against the generated reference — which caught the
one wrong operator label in my first draft ("within last" vs the
real "within the last"), the drift protection working as designed.
- fields.md — GENERATED from FilterFieldsPayload by
TestFieldReferenceDocIsCurrent: the drift test fails the build if
the registry ever changes without regenerating the docs (user
decision: docs forced to match working code; FILTER_DOCS_REGEN=1
regenerates).
- searching-and-filtering.md cross-links to the folder.
Phases 5b and 5c of the metadata writer.
5b — single-file audio (MP3/FLAC/M4A/M4B): the container's tags are
rewritten with ffmpeg -c copy (no re-encode; -map 0 keeps cover art,
-map_chapters 0 keeps chapters). The key set is exactly what the
scanner's applyAudioTagFallbacks reads back — title+album, subtitle,
artist/album_artist, composer (narrators, Audible's convention),
grouping + iTunes series-part, date, genre — so a write + rescan
round-trips. Output is ffprobe-VALIDATED (duration within 1s/0.5% of
the source) before the atomic rename; a lossy container rewrite never
replaces a good book. Fields the format can't carry per our own read
chain are deliberately not written, and fields_written reports only
what really round-trips (subtitle rides MP3/FLAC — this image's mp4
muxer drops subtitle keys, discovered empirically and pinned by test).
5c — CBZ: ComicInfo.xml injected (or replaced) with the same atomic zip
rewrite as the EPUB path, extended with an append-if-missing mode
since a comic may not carry one yet. Field map mirrors the scanner's
comic block: Title/Series/Number/Count/Volume, Writer (authors),
Summary, Publisher, Year/Month/Day, LanguageISO, Web, Genre+Tags;
fractional series numbers are omitted rather than truncated to a lie.
CBR/CB7 stay on the sidecar path (RAR/7z can't be rewritten in place).
Integration tests generate media at test runtime with ffmpeg (m4b) and
in-memory zips (cbz) — no binary fixtures — pinning the tag round-trip
through ffprobe, post-write rescan titles, the ComicInfo injection and
the validation-gate behavior.
Phase 5a of the metadata-writer feature: the book editor gains a
"Write to file" action (admin) that persists the item's CURRENT
database metadata into the artifact on disk, so the library stays
portable to Calibre and other apps — the thing Save alone can't do.
Dispatch (services/metadata_writer.go): EPUB/KEPUB gets its embedded
OPF spliced in place (fresh dc:* block generated inside the ORIGINAL
metadata open tag; manifest/spine/guide untouched; every other zip
entry copied byte-identical in original order so mimetype stays first
and STORED; original dc:identifiers preserved — the package's
unique-identifier must keep resolving). Everything else takes a
Calibre-style sidecar: metadata.opf when the book is ALONE in its
directory (Calibre's own convention — round-trips through Calibre
too), <basename>.opf when siblings share the folder, so one book's
sidecar can never poison dir-mates' scans (the scanner now reads both,
basename first).
Safety contract: temp file + atomic rename — the original is never
truncated on failure; the generated OPF is unit-tested to ROUND-TRIP
through the scanner's own parser (write side and read side can never
disagree); metadata_overrides are deliberately kept (file and DB agree
after the write; overrides still guard against stale sidecars). The
endpoint re-extracts inline (RescanMediaItem) and reports rescan
failure separately — a failed rescan doesn't undo the write. SHA-256
changes are inherent and documented for users (Kobo/KOReader re-key).
UI: the metadata editor modal carries the file path as a data
attribute; the button confirms before mutating, labels the target
(embedded vs sidecar) and toasts the outcome. Integration tests pin
the 403, missing-file error, EPUB embed + post-write rescan title, the
lone-book metadata.opf case, and the shared-dir <basename>.opf case
with no dir-scoped litter.
Expand the catalog from 11 themes (10 dark, 1 light) to 37 (24/13):
all 18 remaining Omarchy defaults (palettes from each theme's
colors.toml) plus 8 canonical light themes (Tokyo Night Day, Solarized
Light, One Light, Ayu Light, GitHub Light, Gruvbox Light, Everforest
Light, Kanagawa Lotus). Lupine leads the light list as the default
light theme; the app default stays tokyo-night.
Appearance panel: Dark/Light segmented filter (SSR-initialized from the
user's current theme), scrollable theme list, Bookshelf section pinned;
active row revealed on open. Light themes carry color-scheme: light and
the soft-shadow token set. Public-page theme selects get Dark/Light
optgroups. New moon/sun icons.
tailwind.config: safelist the new theme ids — Tailwind v3 purges custom
@layer base class selectors that appear in no scanned content file, so
an unsafelisted id silently ships without its CSS (found the hard way).
theme.ts: applyTheme now swaps only theme-* classes (changing theme
from the login select no longer wipes min-h-screen) and keeps the class
in sync on <html> for the new ThemeFlashGuard — a pre-paint inline
script on the four public pages that applies the saved theme before
first paint.
Kanagawa's UI accent uses crystalBlue (#7e9cd8) instead of Omarchy's
foreground-as-accent so links stay distinguishable from text.
AZW3/KF8 is a proprietary legacy Amazon format (the pipeline moved to
KFX in 2015; KDP dropped MOBI-family uploads in 2022) and does not
render in the web reader. The scanner still ingests .azw/.azw3 files
(code unchanged) — they are simply no longer documented as a supported
expectation.
Android 16+ gates per-app access to local networks behind a runtime
permission; without it the app's LAN logins time out with zero packets
leaving the phone (browser works — it is not per-app-gated). The new
device guide covers the sideload install, first-run server URL, the
permission prompt (and manual re-grant path), and a triage table for
the login screen's error line. Per the user: overlay-VPN addresses are
not a consideration and stay undocumented.
User-facing documentation for the rebuilt web reader (§6.5 of the app
handoff): swipe-to-page / tap-for-menu touch navigation, long-press word
selection with drag extension and handles, the selection popover (color
dots, notes with save-together-on-create, copy incl. plain-HTTP LAN,
edit/delete by tapping a painted highlight), below-the-selection popover
placement rationale, PDF/comic behavior, and annotations drawer.
Linked from the user documentation portal and the docs index quick
links / quick-find tables.
Replace the GPL-3.0 license text with the full GNU Affero General
Public License v3.0 text, strengthening copyleft coverage for the
network-service use case (users interacting with Bookhoard over the
network are entitled to the corresponding source).
- LICENSE: swap GPL-3.0 text for the canonical AGPL-3.0 text (gnu.org)
- README.md: update both license references (Project Status and
License sections) from GPL-3.0 to AGPL-3.0
- docs/user/sync-guide.md: update the footer license reference
The bundled BSD 3-Clause license in internal/sevenzip/LICENSE is a
third-party dependency license and is intentionally left unchanged.
Admin:
- Admin pages live in the sidebar's Administration panel (Dashboard,
Libraries, Hash Conflicts, Users, Settings)
- Library creation is the Create Library modal (Library Name,
Description, Library Type); folders are added afterwards by expanding
the library row and using the Folders section's path input + Browse +
Add — the old Add Library modal with folder and 'Scan on save' fields
no longer exists
- Scanning is via the Scanner API or watch mode (File Watcher status on
the admin dashboard); remove references to the removed per-library
Rescan button and 'Force Rescan' option
Collections:
- Fill in the empty creating/managing placeholders with the real flow:
New Collection button, modal fields (name, description, icon grid,
color swatches), per-collection edit/delete icon buttons, Restore
System button, and dashboard-section visibility via Customize
Dashboard
Dashboard:
- Customize Dashboard is opened from the icon button at the right end
of the Library bar (next to Refresh), and requires a specific library
selected rather than 'All Libraries'
- Library switching uses the Library dropdown in the bar below the top
bar (includes 'All Libraries' with counts)
- Collection sections are shown/hidden from the Customize Dashboard
toggles — the per-collection 'Show on Dashboard' setting is gone
- Mention the hover chevrons for scrolling carousels
Bookshelf:
- Saved filters: document the new toolbar buttons — Filters (opens the
filter drawer with Apply Filters, Esc, and overlay-click close), Save,
Load (Saved Filters dropdown with trash-icon delete), and Clear —
replacing the emoji-labelled Save Filter / Saved Filters buttons
- Tag filtering: filters now live behind the Filters drawer on the All
Books page
- Theme switching lives in the sidebar's Appearance panel (palette
icon): swatch list with a checkmark on the active theme, and the
'Bookshelf' section below it for wood textures; on small screens the
sidebar opens via the top-bar menu button
- Profile: account menu is the username accordion at the bottom of the
sidebar (not top-right); save button is 'Save Changes'
- Remove Wood Light/Dark/Mahogany from the Available Themes list (they
are bookshelf backgrounds, not color themes) and consolidate the
Catppuccin variants
The new-ui redesign replaced the top header with a sidebar and removed
the Settings pages.
- Replace all 'Settings → Devices' paths with the Devices page in the
sidebar
- Conflicts are resolved from the book detail page's Sync Progress
button or the Conflicts page (/conflicts); drop the nonexistent
'Settings → Conflicts' path
- Queue status and unlinked-book references no longer invent per-device
button paths that don't exist on the Devices page
- Reading history now points to the Progress page / book detail
- Export FAQ no longer references a Settings → Export flow that isn't
in the UI
- sync-guide: only Web and KOReader are fully supported; move Kobo to
coming soon, drop fake Q2-Q4 2026 release dates for mobile/Kindle/
Remarkable, and describe the plugin + server-approval registration
flow instead of QR-code/URL approval
- sync-guide: remove cellular/mobile-app advice from battery and
best-practice sections, correct the Calibre compatibility FAQ, update
the changelog to reflect shipped vs. pending sync features, and fix
the license header (GPL-3.0, not MIT)
- user-guide: lead device setup with KOReader; mark the Kobo guide as
coming soon
- calibre-integration: OPDS client list no longer implies native Kobo
support
- auth overview: label the mobile-application token guidance as
'coming later' since no mobile apps exist yet
Native Kobo sync is implemented server-side but not yet supported on
real devices, so stop documenting it as a working feature.
- Rewrite kobo-setup.md as a coming-soon stub: point users to KOReader
(which runs on Kobo hardware) as the supported path today, and list
what native sync will deliver when released
- Add 'Coming Soon' status banners to the Kobo protocol spec, all five
Kobo endpoint docs, and both API references, noting the endpoints are
under active development and may change
- Tag the device shelf endpoints as pending native Kobo support
Replace the outdated Calibre-wireless/Basic-Auth instructions with the
actual current flow: install the bookhoard.koplugin plugin, enter the
server URL in the plugin menu, then approve the pending registration
from Settings → Devices. Registration tokens are delivered to the
plugin automatically after approval (5-minute expiry), so no
credentials are ever typed on the device.
Also document bidirectional sync of position, bookmarks, highlights
(colors mapped between web and KOReader palettes), and notes, plus
format-aware SHA-256 book matching, OPDS delivery, and trimmed
troubleshooting sections covering the new registration flow.
- Add comprehensive API documentation for tags_filter parameter
- Document fuzzy matching behavior with examples
- Add user guide for tag-based filtering
- Document backward compatibility with genre_filter
- Include examples of fuzzy matching ("Sci Fi" → "Science Fiction")
Provides complete documentation for the new tags filter feature,
including API reference and user-facing documentation.
Relates to IMPLEMENTATION_TAGS_FILTER.md Phase 7
Add comprehensive documentation for GET /api/saved-filters/:id endpoint
including Bruno API collection, developer API docs, user documentation,
and implementation plan with frontend integration phase.
Bruno API Collection (bruno/saved-filters/Get Saved Filter By ID.yml):
- New Bruno request file for GET /:id endpoint
- Includes comprehensive documentation with examples
- Documents all status codes (200, 400, 401, 404)
- Provides example curl commands and use cases
- Uses variable placeholders ({{base_url}}, {{filter_id}})
- Follows existing Bruno YAML patterns
API Documentation (docs/developer/api/saved-filters/index.md):
- Added GET /api/saved-filters/:id endpoint documentation
- Example request with UUID parameter
- Example response showing filter object structure
- Error responses documented (400, 401, 404)
- Use cases: Mobile apps, SPAs, editing, verification
User Documentation (docs/user/library-browsing.md):
- Updated "Loading Saved Filters" section
- Removed "feature coming soon" language
- Added step-by-step instructions for loading filters
- Added tips section with visual indicators
- Added "Managing Saved Filters" section
- Added "Common Use Cases" (genre, author, series)
- Emphasizes instant feedback (no page reload)
Implementation Plan (GET_SAVED_FILTER_BY_ID_IMPLEMENTATION.md):
- Added Phase 7: User Documentation Update
- Added Phase 8: Frontend Integration (bookshelf.ts)
- Shows loadFilter() implementation
- Hybrid Alpine.js + HTMX approach
- Maintains SSR-first principles
- API call on user interaction, not page load
- Populates hidden form fields
- Triggers HTMX to apply filter
- Updated Summary of Changes: 7 files, ~344 lines
- Updated Checklist with frontend and user docs tasks
- Added frontend testing tasks
SSR-First Compliance:
- Initial page load: Server renders everything (no API calls)
- User interaction only: API called when user clicks filter
- No async x-init data fetching
- Progressive enhancement maintained
Documentation Structure:
- Developer docs: API reference for integration
- User docs: Step-by-step usage instructions
- Bruno: API contract testing
- Implementation plan: Complete development guide
All documentation follows established patterns and includes examples.
- Document all available color themes with descriptions
- Explain wood paneling feature and available textures
- Provide step-by-step instructions for changing themes
- Add tips for theme/texture pairing
Add documentation for delete_user and reset_user_password endpoints.
Update update_profile to reflect consolidated endpoint. Remove obsolete
documentation for individual update operations. Add profile guide for
end-users. Update API_CONSOLIDATION_PLAN.md with implementation status.
Major architectural improvements:
1. Add generic /api/dashboard/sections JSON endpoint
- Created internal/handlers/dashboard.go (new file)
- Created internal/router/dashboard.go (new file)
- Single source of truth for web UI, mobile apps, plugins
- Follows existing handler/router pattern
2. Update DashboardService to apply user preferences
- GetSectionItems() now accepts sectionOrder and hiddenSections
- filterHiddenSections() removes user's hidden sections
- reorderSections() applies user's custom order
- Ensures consistent behavior across all clients
3. Separate concerns properly
- API handlers in internal/handlers/dashboard.go
- SSR routes remain in internal/router/frontend.go
- Both use same DashboardService (single source of truth)
4. Reorganize implementation phases
- Phase 1-3: Database, service, queries
- Phase 4-6: Handler, router, frontend routes
- Phase 7-9: Templates and settings
- Phase 10-11: TypeScript modules
- Phase 12-13: Documentation and testing
5. Add documentation
- docs/developer/api/dashboard.md (API reference)
- docs/user/dashboard.md (user guide)
6. Bruno tests already exist
- bruno/dashboard/ has 5 comprehensive test files
- Three-context testing (no user, user, admin)
- No additional tests needed
Benefits:
- Uniform dashboard across web, mobile, plugins
- Single source of truth (no duplicate logic)
- User preferences respected by all clients
- Follows established project patterns
- Comprehensive test coverage
Timeline: Updated to reflect 13 phases (19-25 days total with TypeScript)
- Replace username/password authentication with API key in sync URL
- Update configuration examples to show API key in URL path
- Add instructions for copying API key from Device Management
- Update OPDS catalog URL to include token parameter
- Fix troubleshooting section for API key authentication
- Document where to find API key and sync URL in UI
- Update SSL/TLS examples with API key approach
- Renamed root INDEX.md to index.md (lowercase)
- Changed navigation title from "Documentation Index" to "Index"
- Removed caps lock for cleaner appearance
- Renamed subdirectory INDEX.md files to section-specific names:
- user/INDEX.md → user/user-guide.md
- developer/INDEX.md → developer/development.md
- operations/INDEX.md → operations/operations.md
- contributing/INDEX.md → contributing/contributing.md
- developer/api/INDEX.md → developer/api/api-reference.md
- developer/api/collections/INDEX.md → developer/api/collections/collections-api.md
- Updated all internal links to use new filenames
- Updated navigation.go to skip subdirectory INDEX files from sidebar
- Added Dockerfile to include docs directory in container build
This fixes the issue where multiple 'INDEX' links appeared in the sidebar,
making navigation confusing. Now each section has a descriptive name.
BREAKING CHANGE: Documentation URLs have changed
New structure:
- user/ - End-user documentation (device setup, sync guides, frontend)
- developer/ - Developer documentation (API reference, protocols, specs)
- operations/ - Operations documentation (deployment, troubleshooting)
- contributing/ - Contribution guides
Changes:
- Created portal INDEX.md files for each audience section
- Moved device guides to user/devices/ (kobo-setup.md, koreader-setup.md)
- Moved API docs to developer/ (api-reference.md, collections-api.md)
- Moved sync guide to user/sync-guide.md
- Moved troubleshooting to operations/troubleshooting.md
- Moved all split API docs to developer/api/
- Renamed protocol files (kobo-protocol.md, koreader-protocol.md)
- Added placeholder user guides (frontend, user-areas, settings, admin)
- Updated all internal links to new paths
- Updated Go code (http_handler.go, navigation.go) for new paths
- Updated main INDEX.md for audience-based navigation
Benefits:
- Clear separation of user and developer documentation
- Scalable structure for future user guide expansion
- Better organization and discoverability
- Audience-specific landing pages
Related to DOCS_IMPLEMENTATION_PLAN.md Phase 2 completion