Przeglądaj źródła

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 miesiąc temu
rodzic
commit
a89c793054
7 zmienionych plików z 254 dodań i 78 usunięć
  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
 **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
-- [ ] 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
 
 - All changes should maintain thread safety (mutex usage)

+ 27 - 22
docs/Feature-Tagging.md

@@ -1,12 +1,14 @@
 # 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
 
-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
   CREATE TABLE IF NOT EXISTS tags (
     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
   CREATE TABLE IF NOT EXISTS book_tags (
     book_id TEXT NOT NULL,
@@ -27,7 +29,7 @@ The database schema will be updated to support tags.
 
 ## 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
   class Book {
     // ... 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
 
-- 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
 
-- 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`)
    - Compute SHA-256 id for each selected file.
    - Extract metadata and cover (EPUB via libzip/tinyxml2, PDF via poppler).
+   - Detect duplicate imports via hash matching.
 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.
+   - Manage book-tag associations.
 3. **Model** (`BookList`)
    - Load books from the database into memory.
    - Expose CRUD helpers with signal emission.
    - Provide thread-safe enqueue helpers for background imports.
-4. **Presentation** (`BookShelf`, `BookTile`)
+4. **Presentation** (`BookShelf`, `BookTile`, `BookDetails`)
    - Mirror the model into a `Gio::ListStore`.
    - Render a grid of tiles, expose activation, and filter results.
+   - Show detailed view with metadata and tag management.
 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.
+   - Handle keyboard navigation and user feedback via toasts.
 
 ## Module documentation
 
@@ -29,6 +33,7 @@ through the system.
 - [BookList](booklist.md)
 - [BookShelf](bookshelf.md)
 - [BookTile](booktile.md)
+- [BookDetails](bookdetails.md)
 - [BibliothecaWindow](bibliotheca_window.md)
 - [SHA-256 helper](sha256.md)
 - [Entry point](main.md)
@@ -44,5 +49,9 @@ through the system.
 ## Future work
 
 - 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.
+- 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` 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
 
-- 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
   to `BookShelf::setFilter()`.
 - Host a `Gtk::Stack` that swaps between:
   - Empty library placeholder.
   - Library grid (`BookShelf`).
   - "No results" message when filtering yields nothing.
+  - Book details view (`BookDetails`).
 - 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
 
@@ -25,34 +28,49 @@ Gtk::Window (BibliothecaWindow)
  └── Gtk::Box m_mainBox (vertical)
      ├── Gtk::SearchBar m_searchBar
      │    └── 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
 
 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**:
    - Clicking the search toggle (or pressing Ctrl+F) toggles the `Gtk::SearchBar`.
    - 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**:
    - `onAddBookClicked()` opens `Gtk::FileDialog::open_multiple()`.
    - Each selected file is processed on a background thread: compute SHA-256,
      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
      `BookList::upsertMany()`, and the UI refreshes.
+   - Toast notifications show import results ("Added N books", "N duplicates").
 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
 
@@ -60,24 +78,35 @@ BibliothecaWindow subscribes to the relevant `BookList` signals to call
 `updateVisibleView()` whenever the library changes. This keeps the stack in sync
 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
 
-- **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
   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
 
 `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
-auto window = Gtk::make_managed<BibliothecaWindow>(bookList);
+auto window = Gtk::make_managed<BibliothecaWindow>(db, bookList);
 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
 `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
 
 - **Custom tile content**: tweak `BookTile` (e.g., add badges or context menus);

+ 38 - 4
docs/database_manager.md

@@ -27,7 +27,9 @@ operation.
 
 ## Schema
 
-`ensure_schema()` creates a single table named `books`:
+`ensure_schema()` creates the following tables:
+
+### books table
 
 ```sql
 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
 manager is the canonical place to evolve storage.
 
 ## Public API
 
+### Book operations
+
 | Method                | Description                                   |
 |-----------------------|-----------------------------------------------|
 | `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.|
 | `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
-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