# Concise Default View — design spec ## Problem The index page shows every book grouped by status. As the library grows, this becomes too much information at a glance. The user needs a focused default showing only what's active right now, with easy paths to browse the rest. ## Design The index view takes an optional `filter` query parameter. With no filter, it shows a concise default: only books in-flight (READING) and the 5 most recently added WANT books. All other books are accessible via filter links. With a filter (`all`, `read`, `want`), it shows the full matching subset. ## Behaviour ### Default / no filter (concise view) - **READING group**: all READING books (unchanged from current). - **WANT group**: sorted by `:ADDED:` date descending, limited to 5. - **READ / ABANDONED**: replaced by summary links — e.g. "Read · 3" as a clickable link to `/index?filter=read`. - **Filter nav bar** shown at the top: links to `All books (N)`, `Want list`, `Read (N)`. ### `?filter=all` Full index, all groups, no truncation — identical to today's default. ### `?filter=read` Only the READ group. Single group in the listing. ### `?filter=want` Only the WANT group. Single group in the listing. ### Error handling - An unrecognised/empty filter value is treated as `nil` (concise default). - A WANT book with no `:ADDED:` property sorts before dated entries (treated as oldest). - Fewer than 5 WANT books → all are shown, no padding or placeholder rows. ## Implementation ### Model: `spine-index-model` Signature changes from: ``` spine-index-model (books &optional selected-id) ``` to: ``` spine-index-model (books &optional filter selected-id) ``` When `filter` is nil, after building the group alist: - Sort the `:want` group entries by `added` descending (ISO date string; nils sort first = oldest). - Truncate to 5 entries. - Replace `:read` and `:abandoned` groups with a `:summary-groups` list. Each entry is a plist matching the existing model convention: ``` (:label "read" :count 3 :href "/index?filter=read") ``` The template renders these as clickable `.row` links. When `filter` is non-nil, `:summary-groups` is absent. When `filter` is non-nil, return only the matching group (or all groups for `"all"`). ### Handler: `defservlet index` Read the `filter` key from the request query alist. Pass it to `spine-index-model`. Pass `current_filter` in the template context for conditional rendering. ### Template: `index.mustache` - **Filter bar** (above groups): shown when `current_filter` is nil (links to expanded views) or truthy (link back to concise view). - **Summary groups**: new `{{#summary_groups}}` block renders a simple `.row` link per suppressed group (READ, ABANDONED). - Existing group rendering unchanged — still handles full listing when `filter=all` or single-group views. ### Sorting Within the WANT group for the concise view: sort by `added` field descending. The `added` field is already part of each book's model entry. Nil dates sort before dated entries (oldest). ## Out of scope - Pagination for large WANT lists (5 is a design choice; if the user wants more, the "All books" link is there). - Remembering the user's last selected filter across requests (stateless is simpler). - Keyboard-driven filter switching (plain links for now).