Pārlūkot izejas kodu

Update documentation for all new features

- Add BookDetails module documentation
- Update README with new features (tagging, toasts, keyboard nav)
- Document toast notifications and keyboard navigation
- Document tag search syntax and selection API
- Document tag tables in database schema
- Mark Feature-Tagging.md as implemented

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Bernardo Magri 1 mēnesi atpakaļ
vecāks
revīzija
a89c793054
7 mainītis faili ar 254 papildinājumiem un 78 dzēšanām
  1. 11 22
      PLAN.md
  2. 27 22
      docs/Feature-Tagging.md
  3. 13 4
      docs/README.md
  4. 55 26
      docs/bibliotheca_window.md
  5. 88 0
      docs/bookdetails.md
  6. 22 0
      docs/bookshelf.md
  7. 38 4
      docs/database_manager.md

+ 11 - 22
PLAN.md

@@ -208,12 +208,19 @@ Tab navigation works through GTK's focus system.
 
 
 ### 4.3 Documentation Updates
 ### 4.3 Documentation Updates
 **Files**: `docs/*.md`
 **Files**: `docs/*.md`
-**Status**: Not Started
+**Status**: COMPLETED
+
+Implemented:
+- [x] Update README with current features (added BookDetails, tagging, toasts)
+- [x] Document BookDetails component (new docs/bookdetails.md)
+- [x] Update bibliotheca_window.md (toast overlay, keyboard nav, details view)
+- [x] Update bookshelf.md (tag search, selection API)
+- [x] Update database_manager.md (tag tables and methods)
+- [x] Update Feature-Tagging.md (marked as implemented)
 
 
-- [ ] Update README with current features
-- [ ] Document BookDetails component
+Remaining for future:
 - [ ] Add user guide/manual
 - [ ] Add user guide/manual
-- [ ] Update architecture diagrams
+- [ ] Add screenshots or diagrams
 
 
 ---
 ---
 
 
@@ -257,24 +264,6 @@ Tab navigation works through GTK's focus system.
 
 
 ---
 ---
 
 
-## Current Working Files
-
-### Modified (uncommitted)
-- `meson.build` - Added BookDetails.cpp
-- `src/BibliothecaWindow.cpp` - Added details view
-- `src/BibliothecaWindow.hpp` - Added members
-- `src/Book.hpp` - Added tags vector
-- `src/DatabaseManager.cpp` - Added tag tables/methods
-- `src/DatabaseManager.hpp` - Added tag signatures
-- `src/main.cpp` - Pass DB to window
-
-### New (untracked)
-- `src/BookDetails.cpp` - Details pane (incomplete)
-- `src/BookDetails.hpp` - Details pane header
-- `docs/Feature-Tagging.md` - Tagging design doc
-
----
-
 ## Notes
 ## Notes
 
 
 - All changes should maintain thread safety (mutex usage)
 - All changes should maintain thread safety (mutex usage)

+ 27 - 22
docs/Feature-Tagging.md

@@ -1,12 +1,14 @@
 # Feature: Tagging
 # Feature: Tagging
 
 
-This document outlines the implementation of a tagging feature for Bibliotheca.
+**Status**: Implemented
+
+This document describes the tagging feature in Bibliotheca.
 
 
 ## 1. Database Schema
 ## 1. Database Schema
 
 
-The database schema will be updated to support tags.
+The database schema includes two tables for tagging support.
 
 
-- A `tags` table will be created to store all available tags.
+- A `tags` table stores all available tags.
   ```sql
   ```sql
   CREATE TABLE IF NOT EXISTS tags (
   CREATE TABLE IF NOT EXISTS tags (
     id INTEGER PRIMARY KEY AUTOINCREMENT,
     id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -14,7 +16,7 @@ The database schema will be updated to support tags.
   );
   );
   ```
   ```
 
 
-- A `book_tags` table will be created to associate tags with books.
+- A `book_tags` junction table associates tags with books.
   ```sql
   ```sql
   CREATE TABLE IF NOT EXISTS book_tags (
   CREATE TABLE IF NOT EXISTS book_tags (
     book_id TEXT NOT NULL,
     book_id TEXT NOT NULL,
@@ -27,7 +29,7 @@ The database schema will be updated to support tags.
 
 
 ## 2. Model
 ## 2. Model
 
 
-- The `Book` class in `Book.hpp` will be extended to include a list of tags.
+- The `Book` class in `Book.hpp` includes a tags vector:
   ```cpp
   ```cpp
   class Book {
   class Book {
     // ... existing members
     // ... existing members
@@ -35,27 +37,30 @@ The database schema will be updated to support tags.
   };
   };
   ```
   ```
 
 
-- The `DatabaseManager` will be updated with methods to:
-  - `add_tag_to_book(book_id, tag_name)`
-  - `remove_tag_from_book(book_id, tag_name)`
-  - `get_tags_for_book(book_id)`
-  - `load_all_books` will be updated to also load the tags for each book.
+- The `DatabaseManager` provides these tag methods:
+  - `add_tag_to_book(book_id, tag_name)` - Associates a tag with a book (creates tag if needed)
+  - `remove_tag_from_book(book_id, tag_name)` - Removes a tag association
+  - `get_tags_for_book(book_id)` - Returns all tags for a book
+  - `load_all_books()` - Returns all books with their tags loaded
 
 
 ## 3. View
 ## 3. View
 
 
-- A new `BookDetails` widget will be created. This widget will:
-  - Display the book's cover, title, author, and other metadata.
-  - Display the tags associated with the book.
-  - Provide a way to add new tags to the book.
-  - Provide a way to remove existing tags from the book.
-  - Have a button to open the book file.
+- The `BookDetails` widget (`src/BookDetails.cpp`) provides:
+  - Book cover, title, author, and file path display
+  - FlowBox of tag chips with removal (X) buttons
+  - Entry field and button to add new tags
+  - Button to open the book file in the default application
+  - Button to delete the book from the library
 
 
-- The `BibliothecaWindow` will be modified to:
-  - Show the `BookDetails` widget when a book is activated.
-  - The `onBookActivated` method will be updated to show the `BookDetails` widget instead of opening the book file directly.
+- The `BibliothecaWindow` navigates to `BookDetails` when a book is activated:
+  - `onBookActivated()` calls `m_bookDetails->set_book()` and switches to details view
+  - Back button returns to the shelf view
+  - Keyboard navigation: Escape or Backspace to go back
 
 
 ## 4. User Interaction
 ## 4. User Interaction
 
 
-- Users will be able to add tags to a book from the `BookDetails` view.
-- Users will be able to remove tags from a book from the `BookDetails` view.
-- The search functionality will be extended to allow searching for books by tag.
+- Users can add tags to a book from the `BookDetails` view by typing in the entry and clicking "Add Tag"
+- Users can remove tags by clicking the X button on any tag chip
+- Search supports tags in two ways:
+  - Regular fuzzy search matches tag names alongside title/author
+  - `tag:fiction` syntax filters to only books with matching tags

+ 13 - 4
docs/README.md

@@ -8,19 +8,23 @@ through the system.
 1. **Import** (`BookImport`, `Sha256`)
 1. **Import** (`BookImport`, `Sha256`)
    - Compute SHA-256 id for each selected file.
    - Compute SHA-256 id for each selected file.
    - Extract metadata and cover (EPUB via libzip/tinyxml2, PDF via poppler).
    - Extract metadata and cover (EPUB via libzip/tinyxml2, PDF via poppler).
+   - Detect duplicate imports via hash matching.
 2. **Persistence** (`DatabaseManager`)
 2. **Persistence** (`DatabaseManager`)
-   - Ensure the SQLite schema exists.
+   - Ensure the SQLite schema exists (books, tags, book_tags tables).
    - Insert/update/remove records using prepared statements.
    - Insert/update/remove records using prepared statements.
+   - Manage book-tag associations.
 3. **Model** (`BookList`)
 3. **Model** (`BookList`)
    - Load books from the database into memory.
    - Load books from the database into memory.
    - Expose CRUD helpers with signal emission.
    - Expose CRUD helpers with signal emission.
    - Provide thread-safe enqueue helpers for background imports.
    - Provide thread-safe enqueue helpers for background imports.
-4. **Presentation** (`BookShelf`, `BookTile`)
+4. **Presentation** (`BookShelf`, `BookTile`, `BookDetails`)
    - Mirror the model into a `Gio::ListStore`.
    - Mirror the model into a `Gio::ListStore`.
    - Render a grid of tiles, expose activation, and filter results.
    - Render a grid of tiles, expose activation, and filter results.
+   - Show detailed view with metadata and tag management.
 5. **Application shell** (`BibliothecaWindow`)
 5. **Application shell** (`BibliothecaWindow`)
-   - Build the UI chrome (header, search bar, stack pages).
+   - Build the UI chrome (header, search bar, stack pages, toast overlay).
    - React to imports, search queries, and user activation.
    - React to imports, search queries, and user activation.
+   - Handle keyboard navigation and user feedback via toasts.
 
 
 ## Module documentation
 ## Module documentation
 
 
@@ -29,6 +33,7 @@ through the system.
 - [BookList](booklist.md)
 - [BookList](booklist.md)
 - [BookShelf](bookshelf.md)
 - [BookShelf](bookshelf.md)
 - [BookTile](booktile.md)
 - [BookTile](booktile.md)
+- [BookDetails](bookdetails.md)
 - [BibliothecaWindow](bibliotheca_window.md)
 - [BibliothecaWindow](bibliotheca_window.md)
 - [SHA-256 helper](sha256.md)
 - [SHA-256 helper](sha256.md)
 - [Entry point](main.md)
 - [Entry point](main.md)
@@ -44,5 +49,9 @@ through the system.
 ## Future work
 ## Future work
 
 
 - Add ADRs (`docs/decisions/`) for larger architectural choices.
 - Add ADRs (`docs/decisions/`) for larger architectural choices.
-- Document additional subsystems as they appear (e.g., reader view, tagging).
+- Document additional subsystems as they appear (e.g., reader view).
 - Add screenshots or diagrams for quick onboarding.
 - Add screenshots or diagrams for quick onboarding.
+- Batch operations (multi-select, bulk tagging).
+- Tag management UI (rename, merge, delete tags).
+- Settings panel.
+- File change detection.

+ 55 - 26
docs/bibliotheca_window.md

@@ -1,22 +1,25 @@
 # BibliothecaWindow Module Overview
 # BibliothecaWindow Module Overview
 
 
 `BibliothecaWindow` is the top-level GTK window that stitches together the data
 `BibliothecaWindow` is the top-level GTK window that stitches together the data
-model (`BookList`) and the UI widgets (`BookShelf`, search bar, placeholders).
-It owns the application chrome and orchestrates imports, filtering, and state
-transitions between empty / shelf / no-results views.
+model (`BookList`) and the UI widgets (`BookShelf`, `BookDetails`, search bar,
+placeholders). It owns the application chrome and orchestrates imports,
+filtering, and state transitions between empty / shelf / no-results / details views.
 
 
 ## Responsibilities
 ## Responsibilities
 
 
-- Build the header bar (add button, search toggle) and main layout (`Gtk::Box`).
+- Build the header bar (add button, back button, search toggle) and main layout.
 - Manage the search UI (`Gtk::SearchBar` + `Gtk::SearchEntry`) and pipe queries
 - Manage the search UI (`Gtk::SearchBar` + `Gtk::SearchEntry`) and pipe queries
   to `BookShelf::setFilter()`.
   to `BookShelf::setFilter()`.
 - Host a `Gtk::Stack` that swaps between:
 - Host a `Gtk::Stack` that swaps between:
   - Empty library placeholder.
   - Empty library placeholder.
   - Library grid (`BookShelf`).
   - Library grid (`BookShelf`).
   - "No results" message when filtering yields nothing.
   - "No results" message when filtering yields nothing.
+  - Book details view (`BookDetails`).
 - Handle "Add books" action: open a file dialog, import metadata/covers on a
 - Handle "Add books" action: open a file dialog, import metadata/covers on a
-  worker thread, and enqueue `BookList::upsertMany()`.
-- Emit simple debug logging for activated books (placeholder behaviour for now).
+  worker thread, detect duplicates, and enqueue `BookList::upsertMany()`.
+- Show toast notifications for import results and errors.
+- Navigate to `BookDetails` when a book is activated.
+- Handle keyboard navigation (Enter, Escape, Backspace).
 
 
 ## Layout overview
 ## Layout overview
 
 
@@ -25,34 +28,49 @@ Gtk::Window (BibliothecaWindow)
  └── Gtk::Box m_mainBox (vertical)
  └── Gtk::Box m_mainBox (vertical)
      ├── Gtk::SearchBar m_searchBar
      ├── Gtk::SearchBar m_searchBar
      │    └── Gtk::SearchEntry m_searchEntry
      │    └── Gtk::SearchEntry m_searchEntry
-     └── Gtk::Stack m_stack
-          ├── Gtk::Box m_placeholder        // empty library
-          ├── BookShelf (GridView)          // main shelf view
-          └── Gtk::Box m_noResults          // "no match" placeholder
+     └── Gtk::Overlay m_overlay
+          ├── Gtk::Stack m_stack
+          │    ├── Gtk::Box m_placeholder        // empty library
+          │    ├── BookShelf (GridView)          // main shelf view
+          │    ├── Gtk::Box m_noResults          // "no match" placeholder
+          │    └── BookDetails                   // book details view
+          └── Gtk::Revealer m_toastRevealer      // toast notification overlay
+               └── Gtk::Box m_toastBox
+                    ├── Gtk::Label m_toastLabel
+                    └── Gtk::Button m_toastCloseButton
 ```
 ```
 
 
-The header bar carries the add (+) button and a search toggle button that
-activates the search bar.
+The header bar carries:
+- Add (+) button to import books
+- Back button (visible on details view)
+- Search toggle button that activates the search bar
 
 
 ## Event flow
 ## Event flow
 
 
 1. **Startup**: constructor calls `buildHeaderBar()`, `buildPlaceholder()`,
 1. **Startup**: constructor calls `buildHeaderBar()`, `buildPlaceholder()`,
-   creates the shelf, search widgets, and stack, then calls `updateVisibleView()`
-   to pick the correct page based on the current library.
+   `buildToast()`, creates the shelf and details widgets, search widgets, and
+   stack, then calls `updateVisibleView()` to pick the correct page.
 2. **Search**:
 2. **Search**:
    - Clicking the search toggle (or pressing Ctrl+F) toggles the `Gtk::SearchBar`.
    - Clicking the search toggle (or pressing Ctrl+F) toggles the `Gtk::SearchBar`.
    - Typing updates `m_lastQuery`, calls `BookShelf::setFilter()`, and refreshes
    - Typing updates `m_lastQuery`, calls `BookShelf::setFilter()`, and refreshes
-     the stack (showing “no results” if the filtered shelf is empty).
-   - Leaving the search mode clears the entry.
+     the stack (showing "no results" if the filtered shelf is empty).
+   - Search query is preserved when navigating to details and restored on return.
+   - Leaving the search mode clears the entry (only on shelf/noresults views).
 3. **Importing books**:
 3. **Importing books**:
    - `onAddBookClicked()` opens `Gtk::FileDialog::open_multiple()`.
    - `onAddBookClicked()` opens `Gtk::FileDialog::open_multiple()`.
    - Each selected file is processed on a background thread: compute SHA-256,
    - Each selected file is processed on a background thread: compute SHA-256,
      call `import_book_assets()`, merge metadata, collect into a vector.
      call `import_book_assets()`, merge metadata, collect into a vector.
+   - Duplicates are detected by matching SHA-256 hash against existing books.
    - Once done, the vector is enqueued back onto the main loop via
    - Once done, the vector is enqueued back onto the main loop via
      `BookList::upsertMany()`, and the UI refreshes.
      `BookList::upsertMany()`, and the UI refreshes.
+   - Toast notifications show import results ("Added N books", "N duplicates").
 4. **Activation**: `BookShelf::signalBookActivated()` connects to
 4. **Activation**: `BookShelf::signalBookActivated()` connects to
-   `BibliothecaWindow::onBookActivated()`, which currently logs a placeholder
-   message (can be extended to open a details pane or reader).
+   `BibliothecaWindow::onBookActivated()`, which navigates to the `BookDetails`
+   view for the selected book.
+5. **Keyboard navigation**:
+   - Enter/Return: Open selected book from shelf view.
+   - Escape: Go back from details, close search bar, or clear selection.
+   - Backspace: Go back from details view.
 
 
 ## Signals & slots
 ## Signals & slots
 
 
@@ -60,24 +78,35 @@ BibliothecaWindow subscribes to the relevant `BookList` signals to call
 `updateVisibleView()` whenever the library changes. This keeps the stack in sync
 `updateVisibleView()` whenever the library changes. This keeps the stack in sync
 with imports, deletions, and resets without manual intervention.
 with imports, deletions, and resets without manual intervention.
 
 
+It also subscribes to `BookDetails::signalBookRemoved()` to navigate back to the
+shelf when the currently displayed book is deleted.
+
+## Toast notifications
+
+The toast system uses a `Gtk::Revealer` overlay to show temporary messages:
+
+- Success messages (green styling) for successful imports.
+- Error messages (red styling) for failed imports.
+- Mixed messages when some imports succeed and others fail.
+- Auto-hide after 4 seconds with manual close button.
+
 ## Extension points
 ## Extension points
 
 
-- **Actions on activation**: replace the placeholder logging with navigation to
-  a details page or opening the file.
 - **Drag-and-drop import**: wire a drop controller onto `m_mainBox` that reuses
 - **Drag-and-drop import**: wire a drop controller onto `m_mainBox` that reuses
   the existing import flow.
   the existing import flow.
-- **Search enhancements**: add filters (e.g. tags, formats) or highlight matches
-  inside the grid by extending `BookShelf`.
+- **Search enhancements**: add tag chips as quick filters in the search UI.
+- **Loading indicator**: add spinner during import operations.
 
 
 ## Expected usage
 ## Expected usage
 
 
 `BibliothecaWindow` is the root window instantiated in `main.cpp`. Other modules
 `BibliothecaWindow` is the root window instantiated in `main.cpp`. Other modules
-should construct it with an existing `BookList`:
+should construct it with an existing `DatabaseManager` and `BookList`:
 
 
 ```cpp
 ```cpp
-auto window = Gtk::make_managed<BibliothecaWindow>(bookList);
+auto window = Gtk::make_managed<BibliothecaWindow>(db, bookList);
 app->add_window(*window);
 app->add_window(*window);
 ```
 ```
 
 
-The window takes ownership of the `BookShelf` widget and manages presentation
-according to the current state of the library and active search query.
+The window takes ownership of the `BookShelf` and `BookDetails` widgets and
+manages presentation according to the current state of the library and active
+search query.

+ 88 - 0
docs/bookdetails.md

@@ -0,0 +1,88 @@
+# BookDetails Module Overview
+
+`BookDetails` is a widget that displays detailed information about a single book
+and provides tag management functionality. It subscribes to `BookList` signals
+to stay synchronized with model changes.
+
+## Responsibilities
+
+- Display book metadata: cover image, title, author, file path.
+- Render tags as interactive chips in a FlowBox.
+- Allow users to add new tags via an entry field.
+- Allow users to remove tags by clicking the X button on each chip.
+- Provide an "Open" button to launch the book file in the default application.
+- Emit `signalBookRemoved()` when the displayed book is deleted.
+
+## Structure
+
+```
+BookDetails (Gtk::Box, vertical)
+ ├── Gtk::Image m_cover           // book cover thumbnail
+ ├── Gtk::Label m_title           // book title (bold markup)
+ ├── Gtk::Label m_author          // book author
+ ├── Gtk::Label (file path)       // path to book file
+ ├── Gtk::Button m_open_button    // "Open" - launches file
+ ├── Gtk::FlowBox m_tags_box      // tag chips with X buttons
+ └── Gtk::Box (horizontal)
+      ├── Gtk::Entry m_tag_entry  // new tag input
+      └── Gtk::Button m_add_tag_button  // "Add Tag"
+```
+
+## Data flow
+
+1. **Display**: `set_book(book)` stores the book ID and calls `refresh_display()`
+   to populate all widgets from the book data.
+
+2. **Tag management**:
+   - `on_add_tag_button_clicked()` calls `m_db.add_tag_to_book()` then `refresh_tags()`
+   - `on_remove_tag_clicked(tag)` calls `m_db.remove_tag_from_book()` then `refresh_tags()`
+   - `refresh_tags()` queries only the tags for this book (no full list reload)
+
+3. **Synchronization**:
+   - Subscribes to `BookList::signalBookRemoved()` - if the displayed book is
+     deleted, emits `signalBookRemoved()` so the window can navigate away
+   - Subscribes to `BookList::signalBookUpdated()` and `signalReset()` to
+     refresh the display when book data changes
+
+4. **Clearing**: `clear()` resets the book ID and all widget contents
+
+## Tag chip widget
+
+Each tag is rendered as a horizontal box containing:
+- A label with the tag name
+- A small button with an X icon to remove the tag
+
+The chips are created by `create_tag_chip()` and added to the FlowBox.
+
+## Signals
+
+| Signal              | Description                                   |
+|---------------------|-----------------------------------------------|
+| `signalBookRemoved` | Emitted when the displayed book is deleted    |
+
+## Thread safety
+
+`BookDetails` operates on the main UI thread. All database operations go through
+`DatabaseManager` which handles its own locking. The widget looks up fresh book
+data via `m_book_list.findById()` rather than storing a raw pointer.
+
+## Expected usage
+
+```cpp
+m_bookDetails = std::make_unique<BookDetails>(db, bookList);
+m_bookDetails->signalBookRemoved().connect([this]() {
+  // Navigate back to shelf view
+});
+m_stack.add(*m_bookDetails, "details");
+
+// When a book is activated:
+m_bookDetails->set_book(&book);
+m_stack.set_visible_child(*m_bookDetails);
+```
+
+## Extension points
+
+- **Additional metadata**: Add labels for format, file size, date added.
+- **Edit metadata**: Add entry fields to modify title/author.
+- **Tag autocomplete**: Suggest existing tags when typing.
+- **Delete confirmation**: Show a dialog before deleting books.

+ 22 - 0
docs/bookshelf.md

@@ -80,6 +80,28 @@ using value semantics when refreshing the grid.
 The helper functions `ascii_lower`, `substring_score`, `subsequence_score`, and
 The helper functions `ascii_lower`, `substring_score`, `subsequence_score`, and
 `book_score` live inside the module to keep the fuzzy logic confined.
 `book_score` live inside the module to keep the fuzzy logic confined.
 
 
+### Tag-based search
+
+The filtering supports a special `tag:` prefix syntax for tag filtering:
+
+- `tag:fiction` - Show only books tagged with "fiction" (or tags containing "fiction")
+- Regular search terms also match against tags in fuzzy mode
+
+The `has_tag_prefix()` helper detects the `tag:` prefix and switches to exact
+tag filtering mode, where only books with matching tags are shown.
+
+## Selection API
+
+The module provides methods for querying and controlling selection:
+
+| Method              | Description                                      |
+|---------------------|--------------------------------------------------|
+| `getSelectedBook()` | Returns the currently selected book, if any      |
+| `clearSelection()`  | Deselects any selected book                      |
+
+These are used by `BibliothecaWindow` for keyboard navigation (Enter to open
+selected book).
+
 ## Extension points
 ## Extension points
 
 
 - **Custom tile content**: tweak `BookTile` (e.g., add badges or context menus);
 - **Custom tile content**: tweak `BookTile` (e.g., add badges or context menus);

+ 38 - 4
docs/database_manager.md

@@ -27,7 +27,9 @@ operation.
 
 
 ## Schema
 ## Schema
 
 
-`ensure_schema()` creates a single table named `books`:
+`ensure_schema()` creates the following tables:
+
+### books table
 
 
 ```sql
 ```sql
 CREATE TABLE IF NOT EXISTS books (
 CREATE TABLE IF NOT EXISTS books (
@@ -39,21 +41,53 @@ CREATE TABLE IF NOT EXISTS books (
 );
 );
 ```
 ```
 
 
+### tags table
+
+```sql
+CREATE TABLE IF NOT EXISTS tags (
+  id INTEGER PRIMARY KEY AUTOINCREMENT,
+  name TEXT NOT NULL UNIQUE
+);
+```
+
+### book_tags junction table
+
+```sql
+CREATE TABLE IF NOT EXISTS book_tags (
+  book_id TEXT NOT NULL,
+  tag_id INTEGER NOT NULL,
+  FOREIGN KEY (book_id) REFERENCES books(id) ON DELETE CASCADE,
+  FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE,
+  PRIMARY KEY (book_id, tag_id)
+);
+```
+
 Additional indices or columns should be added here, keeping in mind that the
 Additional indices or columns should be added here, keeping in mind that the
 manager is the canonical place to evolve storage.
 manager is the canonical place to evolve storage.
 
 
 ## Public API
 ## Public API
 
 
+### Book operations
+
 | Method                | Description                                   |
 | Method                | Description                                   |
 |-----------------------|-----------------------------------------------|
 |-----------------------|-----------------------------------------------|
 | `ensure_schema()`     | Creates tables if they do not exist.          |
 | `ensure_schema()`     | Creates tables if they do not exist.          |
-| `upsert_book(book)`   | INSERT OR REPLACE using the book’s id.        |
+| `upsert_book(book)`   | INSERT OR REPLACE using the book's id.        |
 | `remove_book(id)`     | Deletes the row; returns false if not present.|
 | `remove_book(id)`     | Deletes the row; returns false if not present.|
 | `get_book(id)`        | Loads a single book (optional result).        |
 | `get_book(id)`        | Loads a single book (optional result).        |
-| `load_all_books()`    | Returns a vector of all books.                |
+| `load_all_books()`    | Returns a vector of all books with their tags.|
+
+### Tag operations
+
+| Method                              | Description                              |
+|-------------------------------------|------------------------------------------|
+| `add_tag_to_book(book_id, tag)`     | Associates a tag with a book.            |
+| `remove_tag_from_book(book_id, tag)`| Removes a tag association from a book.   |
+| `get_tags_for_book(book_id)`        | Returns all tags for a specific book.    |
 
 
 Internally each method locks `mtx_`, executes the prepared statement, and
 Internally each method locks `mtx_`, executes the prepared statement, and
-translates SQLite rows into `Book` objects.
+translates SQLite rows into `Book` objects. The `add_tag_to_book` method
+creates the tag if it doesn't exist (INSERT OR IGNORE).
 
 
 ## Busy handling & thread safety
 ## Busy handling & thread safety