Purpose: Map every user story to the specific technical components required to implement it. This document is the bridge between what the user experiences and how the system delivers it.
Last updated: 2026-03-17
Note on story numbering: This document uses a sub-numbered scheme (e.g., US-1.3.2 for Book Detail, US-1.4.1 for Search, US-1.5.x for shelf movements, US-8.1.x for GDPR) that was established before the user stories document (
docs/user-stories.md) was reorganised. The user stories doc now uses US-1.4.1 for Book Detail, US-1.5.x for Search, US-1.6.x for the reading journey, and US-8.x (without the.1sub-level) for GDPR. Both documents refer to the same features; the mapping here is authoritative for implementation details.Data model note (v1.5): As of 2026-03-17, the
bookstable represents works (logical books), not editions. A newbook_editionstable holds ISBNs, formats, and edition-specific metadata. The ISBN hard gate is enforced atbook_editions.isbn. Shelf placements, reviews, and blog associations reference works; price snapshots and partner inventory reference editions. Seedocs/technical-architecture.mdsection 7 for the full schema.
- Implementation Phases
- Service Interaction Matrix
- Dependency Graph
- Story-by-Story Mapping
- 1. Core Book Management
- 2. Enrichment & Discovery
- 3. Third Spaces
- 4. Content Moderation
- 5. Metrics Dashboard
- 6. RSS Feeds
- 7. Marketplace (Classifieds)
- 8. GDPR & Privacy
- 9. Partner Integration
- 10. Visibility & Privacy
- 11. Groups
- 12. Blog & Writing
- 13. Comments & Offers
- 14. Authentication
- 15. Home & Navigation
- 16. Error Handling
- 17. Settings
- 18. Looking for a Home Shelf
- 19. Accessibility
| Phase | Name | Stories | Rationale |
|---|---|---|---|
| Phase 1 | MVP | US-1.1.1, US-1.1.2, US-1.1.3, US-1.1.5, US-1.1.6, US-1.1.7, US-1.1.8, US-1.2.1, US-1.2.2, US-1.2.3, US-1.2.4, US-1.2.5, US-1.3.1, US-1.3.2, US-1.4.1, US-1.5.1, US-1.5.2, US-1.5.3, US-1.5.4, US-1.6.4, US-1.6.5 | The core loop: upload photo(s), identify book(s), verify ("We think this is…"), place on shelf, browse and manage. Includes multi-format merge (US-1.1.8). Everything a single user needs to start using The Stacks. |
| Phase 2 | Enrichment | US-2.1.1, US-2.2.1, US-2.2.2, US-2.3.1, US-2.4.1, US-2.5.1, US-2.5.2, US-2.5.3 | Layer intelligence on top of the book graph: reviews, prices, author info, events, source discovery, geographic sweep (US-2.5.2), and business opt-out (US-2.5.3). |
| Phase 3 | Partner Integration | US-9.1.1, US-9.1.2, US-9.2.1, US-9.2.2, US-9.3.1, US-9.3.2, US-9.4.1, US-9.4.2, US-9.5.1, US-9.6.1, US-9.6.2, US-9.7.1, US-9.7.2, US-9.8.1 | Inbound partner API, dashboard, CSV import. Depends on Third Spaces cork board and ISBN resolution from Phases 1–2. EDA and Protobuf land here as cross-cutting infrastructure. |
| Phase 4 | Polish | US-3.1.1, US-5.1.1, US-6.1.1 | Community features (Third Spaces scraping), operational visibility (Metrics), and sharing (RSS/OPDS). |
| Phase 5 | Marketplace (Classifieds) | US-7.1, US-7.2, US-7.3, US-13.2.1, US-13.2.2 | Classifieds board (see ADR 013). US-7.1.1 (listings + state machine + expiry) is implemented. Payments (#054b), shipping (#054c), offer threads, and seller KYC are deferred. |
| Phase 6 | Social Graph & Visibility | US-10.1.1, US-10.1.2, US-10.2.1, US-10.2.2, US-10.2.3, US-10.3.1, US-10.4.1, US-11.1.1, US-11.1.2, US-11.1.3, US-11.1.4, US-11.1.5 | Profile visibility, shelf/placement/post visibility, ceiling rule enforcement, view-as mode, user blocks, groups, and group content feeds (US-11.1.5). Requires resolve_visibility/2 context and ViewAsPlug. |
| Phase 7 | Blog & Comments | US-12.1.1, US-12.1.2, US-12.1.3, US-13.1.1, US-13.1.2 | Native blog posts, LLM book associations via PostBookAssociationWorker, threaded comments with block filtering. Requires Phase 6 visibility infrastructure. |
| Phase 1 (extended) | Auth, Navigation, Errors, Settings | US-14.1.1, US-14.1.2, US-14.2.1, US-14.3.1, US-14.3.2, US-14.3.3, US-15.1.1, US-15.2.1, US-15.2.2, US-15.3.1, US-16.1.1, US-16.2.1, US-16.3.1, US-17.1.1, US-17.2.1, US-17.2.2, US-17.2.3, US-17.3.1, US-18.1.1, US-19.1.1, US-19.1.2, US-19.2.1 | Authentication (including onboarding US-14.1.2), home page, global navigation with user menu dropdown (US-14.3.3), error handling, settings hub (US-17.1.1) with profile (US-17.2.1), location (US-17.2.2), password (US-17.2.3), notifications (US-17.3.1), the fifth bookshelf with community wear (US-18.1.1), and accessibility (US-19.x). |
| Cross-cutting | GDPR, Moderation, Age, EDA | US-4.1, US-4.2, US-8.1, US-8.2, US-8.3, US-8.4, US-8.5 | Built incrementally across all phases. Moderation pipeline ships with Phase 1; GDPR primitives land in Phase 1 and mature through Phase 3. Event bus (Oban) and Protobuf schema contracts land in Phase 3. Phases 6–7 add new GDPR-covered entities: blog_posts, comments, listings, groups, user_blocks. offer_threads and offer_messages tables exist but are unused (see ADR 013). |
Each cell indicates the role: R = Read, W = Write, RW = Read/Write, -- = not involved.
| Story | Elm | Phoenix | Vision Service | Rust Scraper | PostgreSQL | dbt | External APIs |
|---|---|---|---|---|---|---|---|
| US-1.1.1 | W (upload form, verify, shelf picker) | RW (two-step: identify then confirm) | RW (vision) | -- | W (books, book_editions, images) | -- | Modal vision service, Open Library, Google Books |
| US-1.1.2 | R (error display) | R (validation) | -- | -- | R (books) | -- | Open Library, Google Books |
| US-1.1.3 | R (error display) | R (validation) | R (classification) | -- | W (audit_log) | -- | Modal (Qwen2.5-VL-7B-Instruct) |
| US-1.1.7 | RW (bulk drop zone, review screen, shelf selector) | RW (batch intake, pre-process, grouping, batch jobs) | RW (classify + extract per image) | -- | W (books, images, batch_id, group_id) | -- | Modal vision service, Open Library, Google Books |
| US-1.1.4 | R (gate UI) | RW (flag + gate) | -- | -- | RW (books, audit_log) | R (BISAC view) | -- |
| US-1.1.5 | RW (ISBN form) | RW (validate + create) | -- | -- | W (books, bookshelf_placements) | -- | Open Library, Google Books |
| US-1.1.6 | RW (duplicate UI + merge prompt) | R (dedup check + fuzzy match) | -- | -- | R (books, book_editions) | -- | -- |
| US-1.1.8 | RW (merge confirmation) | RW (create edition under existing work) | -- | -- | RW (book_editions) | -- | -- |
| US-1.2.1 | RW (shelf view) | R (shelf data) | -- | -- | R (bookshelves, bookshelf_placements, books) | -- | -- |
| US-1.2.2 | RW (shelf view) | R (shelf data) | -- | -- | R (bookshelves, bookshelf_placements, books) | -- | -- |
| US-1.2.3 | RW (shelf view) | R (shelf data) | -- | -- | R (bookshelves, bookshelf_placements, books) | -- | -- |
| US-1.2.4 | RW (pile view) | R (shelf data) | -- | -- | R (bookshelves, bookshelf_placements, books) | -- | -- |
| US-1.2.5 | RW (navigation) | -- | -- | -- | -- | -- | -- |
| US-1.3.1 | RW (spine render) | R (book data) | -- | -- | R (books, bookshelf_placements) | -- | -- |
| US-1.3.2 | RW (detail overlay) | R (book + editions + related) | -- | -- | R (books, book_editions, authors, review_snapshots, price_snapshots) | R (community_read_count) | -- |
| US-1.4.1 | RW (search UI) | R (search query) | -- | -- | R (books, authors, bookshelves) | -- | -- |
| US-1.5.1 | RW (move action) | RW (placement) | -- | -- | RW (bookshelf_placements, bookshelf_placement_history) | -- | -- |
| US-1.5.2 | RW (abandon action) | RW (placement) | -- | -- | RW (bookshelf_placements, bookshelf_placement_history) | -- | -- |
| US-1.5.3 | RW (re-read action) | RW (placement) | -- | -- | RW (bookshelf_placements, bookshelf_placement_history, books) | -- | -- |
| US-1.5.4 | RW (format picker) | RW (edition creation) | -- | -- | RW (book_editions) | -- | Open Library, Google Books (resolve new ISBN) |
| US-1.6.4 | RW (remove action) | RW (soft delete) | -- | -- | RW (bookshelf_placements, bookshelf_placement_history) | -- | -- |
| US-1.6.5 | R (empty states) | R (shelf data) | -- | -- | R (bookshelves, bookshelf_placements) | -- | -- |
| US-2.1.1 | R (review display) | RW (aggregation) | -- | -- | RW (review_snapshots) | R (sentiment view) | GoodReads, Reddit, Storygraph |
| US-2.2.1 | R (price display) | R (price data) | -- | RW (scrape) | RW (price_snapshots, bookstores) | R (price history view) | -- |
| US-2.2.2 | -- | RW (config mgmt) | -- | R (config) | RW (bookstores) | -- | -- |
| US-2.3.1 | R (author panel) | RW (author intel) | -- | -- | RW (authors, discovered_sources) | R (author view) | Brave Search, RSS feeds |
| US-2.4.1 | R (events display) | RW (event matching) | -- | -- | RW (bookstore_events, bookstores) | R (event view) | Brave Search, SearXNG |
| US-2.5.1 | R (approval UI) | RW (discovery + approval) | -- | -- | RW (discovered_sources, audit_log) | -- | Brave Search, SearXNG |
| US-2.5.2 | R (Third Spaces loading) | RW (geographic sweep) | -- | -- | RW (discovered_sources, third_spaces) | -- | Brave Search, SearXNG |
| US-2.5.3 | RW (opt-out form) | RW (exclusion) | -- | -- | RW (discovered_sources, third_spaces) | -- | Resend / Postmark (confirmation email) |
| US-3.1.1 | RW (cork board) | RW (spaces) | -- | -- | RW (third_spaces, third_space_events) | -- | Brave Search, SearXNG |
| US-4.1.1 | R (status display) | RW (pipeline) | RW (classification) | -- | RW (books, audit_log) | -- | Modal vision service, Open Library, Google Books |
| US-4.1.2 | RW (verification) | RW (KYC flow) | -- | -- | RW (audit_log) | -- | Smile Identity / Yoti / Sumsub |
| US-5.1.1 | RW (dashboard) | R (metrics) | -- | -- | R (all schemas) | RW (metric models) | -- |
| US-6.1.1 | -- | RW (feed gen) | -- | -- | R (bookshelves, bookshelf_placements, books) | -- | -- |
| US-7.1 | RW (listing form) | RW (listing) | -- | -- | RW (listings, bookshelf_placements, uploaded_images) | -- | -- |
| US-7.2 | -- | -- | -- | -- | -- | -- | DEFERRED (ADR 013) |
| US-7.3 | -- | -- | -- | -- | -- | -- | DEFERRED (ADR 013) |
| US-10.1.1 | RW (privacy settings) | RW (profile visibility) | -- | -- | RW (users) | -- | -- |
| US-10.1.2 | RW (block UI) | RW (block) | -- | -- | RW (user_blocks) | -- | -- |
| US-10.2.1 | RW (shelf settings) | RW (shelf visibility) | -- | -- | RW (bookshelves, groups) | -- | -- |
| US-10.2.2 | RW (placement menu) | RW (placement visibility) | -- | -- | RW (bookshelf_placements, visibility_grants) | -- | -- |
| US-10.2.3 | RW (post editor) | RW (post visibility) | -- | -- | RW (blog_posts, groups) | -- | -- |
| US-10.3.1 | RW (preview mode) | R (resolve_visibility as viewer) | -- | -- | R (all user content tables, user_blocks, group_members) | -- | -- |
| US-10.4.1 | -- | R (noindex headers) | -- | -- | -- | -- | -- |
| US-11.1.1 | RW (group creation) | RW (group) | -- | -- | RW (groups) | -- | -- |
| US-11.1.2 | RW (invite UI) | RW (invitation) | -- | -- | RW (group_invitations, group_members) | -- | -- |
| US-11.1.3 | RW (leave group) | RW (membership) | -- | -- | RW (group_members) | -- | -- |
| US-11.1.4 | RW (member list) | RW (member mgmt) | -- | -- | RW (group_members, group_invitations) | -- | -- |
| US-12.1.1 | RW (post editor) | RW (post) | -- | -- | RW (blog_posts) | -- | -- |
| US-12.1.2 | R (associations display) | R (associations) | RW (LLM call) | -- | RW (post_book_associations) | -- | Together AI (LLM — future feature) |
| US-12.1.3 | R (blog archive) | R (posts) | -- | -- | R (blog_posts, post_book_associations) | -- | -- |
| US-13.1.1 | RW (comment UI) | RW (comment) | -- | -- | RW (comments) | -- | -- |
| US-13.1.2 | R (filtered thread) | R (recursive CTE) | -- | -- | R (comments, user_blocks) | -- | -- |
| US-13.2.1 | RW (Q&A UI) | RW (comment) | -- | -- | RW (comments) | -- | -- |
| US-13.2.2 | -- | -- | -- | -- | -- | -- | DEFERRED (ADR 013) |
| US-8.1.1 | RW (export UI) | RW (export gen) | -- | -- | R (all user data) | -- | -- |
| US-8.1.2 | RW (delete flow) | RW (cascade) | -- | -- | RW (all user data, wh anonymise) | -- | -- |
| US-8.1.3 | RW (consent UI) | RW (consent) | -- | -- | RW (audit_log) | -- | -- |
| US-8.1.4 | -- | RW (cleanup job) | -- | -- | RW (uploaded_images) | -- | -- |
| US-8.1.5 | -- | W (logging) | -- | -- | W (audit_log) | -- | -- |
| US-9.1.1 | RW (registration form) | RW (partner CRUD) | -- | -- | RW (partners, audit_log) | -- | -- |
| US-9.1.2 | RW (key mgmt UI) | RW (key rotation) | -- | -- | RW (partners) | -- | -- |
| US-9.2.1 | -- | RW (ingest + validate) | -- | -- | RW (partner_inventory, books, event_log) | R (availability view) | Open Library, Google Books (ISBN resolution for unknown books) |
| US-9.2.2 | RW (CSV upload UI) | RW (parse + validate) | -- | -- | RW (partner_inventory, books) | -- | Open Library, Google Books |
| US-9.3.1 | -- | RW (event ingest) | -- | -- | RW (partner_events, event_log) | -- | -- |
| US-9.3.2 | RW (event form) | RW (event CRUD) | -- | -- | RW (partner_events) | -- | -- |
| US-9.4.1 | -- | RW (space ingest) | -- | -- | RW (partner_spaces, event_log) | -- | -- |
| US-9.4.2 | RW (suggestion form) | RW (suggestion) | -- | -- | RW (third_spaces, audit_log) | -- | -- |
| US-9.5.1 | RW (metrics dashboard) | R (aggregate queries) | -- | -- | R (event_log, partner_inventory, partner_events, partner_spaces) | R (partner metrics views) | -- |
| US-9.6.1 | RW (approval UI) | RW (moderation) | -- | -- | RW (partners, partner_events, partner_spaces, audit_log) | -- | -- |
| US-9.6.2 | -- | RW (validation pipeline) | -- | -- | R (partners) | -- | -- |
| US-9.7.1 | RW (status page) | R (partner status) | -- | -- | R (partners) | -- | Resend / Postmark (confirmation email) |
| US-9.7.2 | RW (profile form) | RW (partner update) | -- | -- | RW (partners, audit_log) | -- | -- |
| US-9.8.1 | R (availability display) | R (partner inventory) | -- | -- | R (partner_inventory, book_editions) | -- | -- |
| US-11.1.5 | R (group feed) | R (aggregated content) | -- | -- | R (blog_posts, bookshelf_placements, groups, group_members, user_blocks) | -- | -- |
| US-14.1.1 | RW (registration form) | RW (account creation) | -- | -- | W (users, audit_log) | -- | -- |
| US-14.1.2 | RW (onboarding overlay) | RW (upload + confirm + onboarding flag) | RW (vision) | -- | RW (users, books, book_editions, bookshelf_placements) | -- | Modal, Open Library, Google Books |
| US-14.2.1 | RW (login form) | RW (authentication) | -- | -- | R (users), W (audit_log) | -- | -- |
| US-14.3.1 | R (nav state) | R (current user) | -- | -- | R (users) | -- | -- |
| US-14.3.2 | R (token refresh) | RW (token lifecycle) | -- | -- | R (users) | -- | -- |
| US-14.3.3 | RW (dropdown menu + sign out) | RW (token revocation) | -- | -- | W (audit_log) | -- | -- |
| US-15.1.1 | R (landing page or redirect) | R (auth check) | -- | -- | R (users) | -- | -- |
| US-15.2.1 | RW (navigation + user dropdown) | -- | -- | -- | -- | -- | -- |
| US-15.2.2 | RW (swipe nav) | -- | -- | -- | -- | -- | -- |
| US-15.3.1 | R (footer) | -- | -- | -- | -- | -- | -- |
| US-16.1.1 | R (404 page) | R (error response) | -- | -- | -- | -- | -- |
| US-16.2.1 | R (error display) | R (error responses) | -- | -- | -- | -- | -- |
| US-16.3.1 | R (auth redirect) | R (401 response) | -- | -- | -- | -- | -- |
| US-17.1.1 | RW (settings hub + sidebar) | R (user data) | -- | -- | R (users) | -- | -- |
| US-17.2.1 | RW (profile form) | RW (profile update) | -- | -- | RW (users) | -- | -- |
| US-17.2.2 | RW (location form) | RW (location + trigger sweep) | -- | -- | RW (users) | -- | Brave Search, SearXNG (via geographic sweep) |
| US-17.2.3 | RW (password form) | RW (password change) | -- | -- | RW (users, audit_log) | -- | -- |
| US-17.3.1 | RW (notification toggles) | RW (preferences) | -- | -- | RW (users) | -- | -- |
| US-18.1.1 | RW (shelf view) | R (shelf data + community stats) | -- | -- | R (bookshelves, bookshelf_placements, books) | R (mart_community_read_count) | -- |
| US-19.1.1 | RW (ARIA labels) | R (spine + shelf metadata) | -- | -- | R (books, book_editions, bookshelves) | -- | -- |
| US-19.1.2 | RW (keyboard handlers) | -- | -- | -- | -- | -- | -- |
| US-19.2.1 | RW (list/spine toggle) | R (shelf data) | -- | -- | R (bookshelves, bookshelf_placements, books, book_editions) | -- | -- |
Legend: A --> B means "A must be built before B"
US-1.1.1 (Upload + Verify + Shelve — two-step: identify then confirm)
|
+--> US-1.1.2 (ISBN Hard Gate) -- validation within upload
| |
| +--> US-1.1.5 (Manual ISBN Entry) -- fallback when vision fails
+--> US-1.1.3 (Non-Book Rejection) -- classification within upload
+--> US-1.1.6 (Duplicate Detection) -- dedup check within upload
| |
| +--> US-1.1.8 (Multi-Format Merge) -- triggered when ISBN matches different edition of existing work
+--> US-4.1.1 (Moderation Pipeline) -- runs as part of upload
| |
| +--> US-1.1.4 (Age-Gated Content) -- needs BISAC classification from moderation
| |
| +--> US-4.1.2 (Age Verification) -- needed to unlock gated content
|
+--> US-1.2.1 (Library shelf)
+--> US-1.2.2 (AntiLibrary shelf)
+--> US-1.2.3 (WishList shelf)
|
+------+------+
| |
v v
US-1.2.4 US-1.2.5
(Reading Pile) (Navigate Between Shelves)
|
v
US-1.3.1 (Spine Rendering)
|
v
US-1.3.2 (Book Detail Page)
|
+----+----+----+----+
| | | | |
v v v v v
1.5.1 1.5.2 1.5.3 1.5.4 1.4.1 1.6.4
(Move) (Abandon)(Re-read)(Format)(Search)(Remove)
US-1.2.1--4 (All shelves)
|
+--> US-1.6.5 (Empty Shelf States) -- each shelf needs an empty state
--- Phase 2 branches off US-1.3.2 ---
US-1.3.2
|
+--> US-2.1.1 (Review Aggregation)
+--> US-2.2.1 (Price Tracking) --> US-2.2.2 (Scraper Config)
+--> US-2.3.1 (Author Intelligence)
+--> US-2.4.1 (Bookstore Events) -- depends on US-2.2.2 (bookstores exist)
+--> US-2.5.1 (Source Discovery Agent) -- feeds 2.1.1, 2.3.1, 2.4.1
|
+--> US-2.5.2 (Geographic Discovery Sweep) -- location-triggered, same infra
+--> US-2.5.3 (Business Opt-Out) -- exclusion list for discovered sources
--- Phase 3 branches independently ---
US-2.5.1 --> US-3.1.1 (Third Spaces) -- uses same discovery infra
US-17.2.2 --> US-2.5.2 (Location set triggers geographic sweep)
US-1.2.x --> US-6.1.1 (RSS Feeds) -- needs shelf data
US-8.1.5 --> US-5.1.1 (Metrics Dashboard) -- needs audit log
--- Phase 3: Partner Integration ---
US-9.1.1 (Partner Registration)
|
+--> US-9.1.2 (API Key Management)
|
+--> US-9.2.1 (Push Inventory API)
| |
| +--> US-9.2.2 (CSV Import) -- uses same ingest pipeline
|
+--> US-9.3.1 (Push Events API)
| |
| +--> US-9.3.2 (Event Dashboard) -- uses same storage
|
+--> US-9.4.1 (Register Space)
|
+--> US-9.5.1 (Partner Metrics) -- needs inventory + events data
|
+--> US-9.6.1 (Owner Reviews Content) -- cross-cuts all partner data
+--> US-9.6.2 (Automated Validation) -- cross-cuts all partner API endpoints
US-9.4.2 (User-Submitted Spaces) -- independent, needs Third Spaces cork board
US-9.1.1 --> US-9.7.1 (Registration Status) -- partner checks their application
US-9.1.1 --> US-9.7.2 (Profile Self-Service) -- partner updates their details
US-9.2.1 --> US-9.8.1 (Partner Availability on Book Detail) -- reader sees local stock
--- Phase 5: Marketplace ---
US-1.3.2 --> US-7.1.1 (List for Sale — from any shelf)
US-7.1.1 --> US-7.2.1 (Buy a Book + post-sale lifecycle)
US-4.1.2 --> US-7.3.1 (Seller Verification)
US-7.3.1 --> US-7.1.1 (must be verified to list)
--- Phase 6 additions ---
US-11.1.1 --> US-11.1.5 (Group Content Feed — aggregated blog + shelf activity)
--- Cross-cutting (incremental) ---
US-8.1.5 (Audit Logging) -- Phase 1, grows each phase
US-8.1.3 (Consent Management) -- Phase 1, required before any data collection
US-8.1.4 (Image Retention) -- Phase 1, runs alongside upload
US-8.1.1 (Export Data) -- Phase 2
US-8.1.2 (Delete Account) -- Phase 2
EDA (Event Bus + event_log) -- Phase 3, benefits all phases retroactively
Protobuf (.proto schemas) -- Phase 3, enforced in CI from here on
- US-8.1.5 Audit Logging (foundation -- everything logs to it)
- US-8.1.3 Consent Management (must exist before user data is collected)
- US-1.1.1 Upload Photos to Add a Book (two-step: identify then confirm, default WishList)
- US-1.1.2 ISBN Hard Gate (part of upload)
- US-1.1.3 Non-Book Image Rejection (part of upload)
- US-4.1.1 Content Moderation Pipeline (part of upload)
- US-8.1.4 Image Retention (cleanup job for uploads)
- US-1.2.1 -- US-1.2.4 All four shelves
- US-1.6.5 Empty Shelf States (ships with shelves)
- US-1.2.5 Shelf navigation
- US-1.3.1 Spine rendering
- US-1.3.2 Book detail overlay (not a route — overlay on top of current page)
- US-1.4.1 Search and sort 13a. US-1.5.3 Platform-wide discovery search (after basic search works)
- US-1.5.1 -- US-1.5.3 Shelf movement (all 5 shelves as valid targets), abandon, re-read
- US-1.5.4 Format tracking (now creates
book_editionsrows, not a TEXT[]) 15a. US-1.1.8 Multi-Format Book Merging (after format tracking) - US-1.6.4 Remove a Book from Collection
- US-1.1.5 Manual ISBN Entry (fallback for failed vision)
- US-1.1.6 Duplicate Book Detection (includes multi-format merge prompt)
- US-1.1.4 Age-gated content
- US-4.1.2 Age verification
- US-2.5.1 Source Discovery Agent
- US-2.1.1 Review Aggregation
- US-2.2.2 Bookshop Scraper Config
- US-2.2.1 Price Tracking
- US-2.3.1 Author Intelligence
- US-2.4.1 Bookstore Events
- US-8.1.1 Export Personal Data
- US-8.1.2 Delete Account and Data
- Protobuf Schema contracts (.proto files, buf CI, code generation) — must land before EDA (event envelope is a proto)
- EDA Event bus infrastructure (event_log table, emit/subscribe, Oban workers) — retroactively benefits Phases 1-2 (shelf moves, enrichment fan-out can emit events)
- US-9.1.1 Partner Registration
- US-9.1.2 API Key Management
- US-9.7.1 Partner Registration Status (ships with registration)
- US-9.7.2 Partner Profile Self-Service
- US-9.6.2 Automated Content Validation (before ingest endpoints)
- US-9.2.1 Push Inventory API
- US-9.2.2 CSV Import
- US-9.8.1 Partner Availability on Book Detail (ships with inventory)
- US-9.3.1 Push Events API
- US-9.3.2 Event Dashboard
- US-9.4.1 Register a Third Space
- US-9.4.2 User-Submitted Third Spaces
- US-9.6.1 Owner Reviews Partner Content
- US-9.5.1 Partner Metrics
- US-3.1.1 Third Spaces (scraping — extends cork board)
- US-6.1.1 RSS Feeds
- US-5.1.1 Metrics Dashboard
- US-7.3.1 Seller Verification
- US-7.1.1 List a Book for Sale
- US-7.2.1 Buy a Book (+ post-sale buyer prompt)
Phase 1 (extended) — new stories: 50. US-14.1.2 First-Time Onboarding Flow (after registration) 51. US-14.3.3 Log Out (display name dropdown with Settings + Sign Out) 52. US-17.1.1 Settings Index Page (hub with sidebar navigation) 53. US-17.2.1 Profile Management (display name, email, website) 54. US-17.2.2 Location Settings (city/country, triggers geographic sweep) 55. US-17.2.3 Change Password 56. US-17.3.1 Email Notification Preferences 57. US-19.1.1 ARIA Labels for Visual Elements 58. US-19.1.2 Keyboard Navigation 59. US-19.2.1 List View Toggle
Phase 2 — new stories: 60. US-2.5.2 Geographic Discovery Sweep (after US-17.2.2 and US-2.5.1) 61. US-2.5.3 Business Opt-Out (after US-2.5.1)
Phase 6 — new stories: 62. US-11.1.5 Group Content Feed (after groups infrastructure)
| Dimension | Detail |
|---|---|
| Summary | Two-step flow: (1) upload image → system identifies candidate → returns for user verification ("We think this is…"); (2) user confirms + chooses shelf (default WishList) → book created. Works/editions model: creates a books work + book_editions edition. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Upload module — multi-step state machine: Uploading → Verifying IdentifiedBook → ChoosingShelf IdentifiedBook → Complete. Drag-and-drop / file picker. Verification step shows uploaded image alongside identified book ("We think this is…"). Shelf picker defaults to WishList. Post-success: "Add another" / "View on shelf". Types: UploadStep, IdentifiedBook, ShelfPicker. Ports for file-input interop. |
| Backend (Phoenix) | Step 1: StacksWeb.UploadController.identify/2 (POST /api/upload/identify) — reads Plug temp file, base64-encodes, inserts uploaded_images record, enqueues IdentifyBookJob, returns candidate(s) to frontend. Step 2: StacksWeb.BookController.confirm/2 (POST /api/books/confirm) — receives confirmed ISBN + target shelf. Checks book_editions for existing ISBN → if found, checks for same-work merge (US-1.1.8). If new, creates books work + book_editions edition + bookshelf_placements. Stacks.Books context — Books.create_work_with_edition/2, Books.find_or_create_author/1. |
| Database | Write: op.books (work), op.book_editions (edition with ISBN, format, cover), op.authors, op.uploaded_images, op.bookshelf_placements, op.audit_log. Read: op.book_editions (dedup check by ISBN), op.books (fuzzy match for multi-format merge). |
| Jobs (Oban) | Stacks.Workers.IdentifyBookJob — sends base64 image to Modal vision service over HMAC-authenticated HTTPS. Returns candidates to caller. Stacks.Workers.EnrichBookJob — fetch metadata from Open Library / Google Books after ISBN resolved. |
| External Services | Modal (Qwen2.5-VL-7B-Instruct on A10G GPU) for image classification and book extraction. Open Library API for ISBN lookup + work/edition metadata. Google Books API as fallback. |
| dbt Models | None directly; feeds stg_books, stg_book_editions, stg_uploaded_images staging models. |
| Infrastructure | Modal hosts the Python vision service (separate from Fly.io). No object storage for uploads — image bytes live in Oban job args (Postgres) for the duration of processing. |
| Dependencies | US-8.1.5 (audit logging), US-8.1.3 (consent). |
| Dimension | Detail |
|---|---|
| Summary | If no ISBN can be resolved from uploaded photos, the book is rejected with a clear message. No ISBN = no book. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Extends Page.Upload -- IdentificationFailed variant in result type. Displays friendly rejection message with suggestion to try another photo or enter ISBN manually. |
| Backend (Phoenix) | Stacks.Books -- validation step after vision response. Returns {:error, :no_isbn}. Controller maps to 422 with structured error. |
| Database | Write: op.audit_log (record rejection). Read: none additional. |
| Jobs (Oban) | Handled within IdentifyBookJob -- no separate job. |
| External Services | Same as US-1.1.1 (failure path). |
| dbt Models | int_upload_rejection_rate (metrics). |
| Infrastructure | None additional. |
| Dependencies | US-1.1.1. |
| Dimension | Detail |
|---|---|
| Summary | Images of pets, nudes, memes, etc. are rejected at the first classification step before ISBN resolution is attempted. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Extends Page.Upload -- NotABook variant. Displays rejection with humour/clarity. |
| Backend (Phoenix) | Stacks.Books.classify_image/1 -- first step in pipeline. Calls Modal vision service /classify endpoint with classification prompt. Returns {:error, :not_a_book, category}. |
| Database | Write: op.audit_log (classification result + rejection reason). |
| Jobs (Oban) | Part of IdentifyBookJob pipeline (step 1). |
| External Services | Modal / Qwen2.5-VL-7B-Instruct (same vision model, classification prompt). |
| dbt Models | int_upload_rejection_rate, int_rejection_categories. |
| Infrastructure | None additional. |
| Dependencies | US-1.1.1. |
| Dimension | Detail |
|---|---|
| Summary | Books with sensitive subjects (via BISAC codes) are flagged; only 18+ verified users can view them. |
| Phase | Phase 1 (late) / Cross-cutting |
| Layer | Components |
|---|---|
| Frontend (Elm) | Components.AgeGate -- interstitial overlay. Page.BookDetail checks visibility_tier before rendering. Blurred placeholder for gated books in shelf views. |
| Backend (Phoenix) | Stacks.Moderation context -- Moderation.classify_subject/1 using BISAC code lookup. Stacks.Accounts -- age_verified?/1 check. Plug StacksWeb.Plugs.AgeGate on relevant routes. |
| Database | Read: op.books (BISAC codes, visibility_tier). Write: op.books (set visibility_tier on classification). op.audit_log. |
| Jobs (Oban) | Part of moderation pipeline job (US-4.1.1). |
| External Services | None (BISAC lookup is local data). |
| dbt Models | int_content_classification. |
| Infrastructure | None additional. |
| Dependencies | US-4.1.1, US-4.1.2. |
| Dimension | Detail |
|---|---|
| Summary | Fallback when vision model fails: user types ISBN manually, client-side checksum validation, then standard ISBN resolution pipeline. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Upload -- ManualISBNEntry variant. Components.ISBNInput with client-side ISBN-10/ISBN-13 checksum validation (mod-10/mod-13). Inline validation feedback before submit. |
| Backend (Phoenix) | Stacks.Books.create_from_isbn/1 -- skips vision, goes straight to ISBN resolution. Same ISBNResolver pipeline as US-1.1.1. |
| Database | Same as US-1.1.1 (books, bookshelf_placements, audit_log). |
| Jobs (Oban) | EnrichBookJob -- same as US-1.1.1. |
| External Services | Open Library, Google Books (ISBN resolution). |
| dbt Models | int_upload_method (track manual vs. vision entry rates). |
| Infrastructure | None additional. |
| Dependencies | US-1.1.2 (ISBN Hard Gate — same validation applies). |
| Dimension | Detail |
|---|---|
| Summary | When a resolved ISBN already exists in the user's collection, show the existing book with options: view it, move it to another shelf, or close. When the ISBN is different but the title+author match an existing work, offer multi-format merge (US-1.1.8). |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Components.DuplicateDetected — shows existing book cover + current shelf, with "View Book", "Move to Shelf", and "Close" actions. For multi-format: shows both editions side-by-side with "Yes, same book" (merge) and "No, it's different" (create new). |
| Backend (Phoenix) | Stacks.Books.find_existing/1 — ISBN dedup check against book_editions.isbn. Stacks.Books.find_same_work/2 — fuzzy match on title+author (Jaro-Winkler > 0.8) against existing books works. Returns existing book data or merge candidate. |
| Database | Read: op.book_editions (ISBN lookup), op.books (fuzzy title+author match), op.bookshelf_placements (current shelf). |
| Jobs (Oban) | None. |
| External Services | None. |
| dbt Models | int_duplicate_detection_rate. |
| Infrastructure | None additional. |
| Dependencies | US-1.1.1 (part of upload flow). |
| Dimension | Detail |
|---|---|
| Summary | When a user adds a different format of a book they already own (e.g., Kindle edition of existing hardcover), the system offers to merge it as a new edition under the same work rather than creating a duplicate. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Components.FormatMerge — side-by-side view of existing and new edition covers. "You own [Title] as a [format]. Add the [new format] edition?" with merge/decline buttons. Reuses warm blue state from duplicate detection. |
| Backend (Phoenix) | Stacks.Books.merge_edition/2 — creates a new book_editions row under the existing books work. Links the new ISBN. Sets is_primary = false (existing edition remains primary). No new shelf placement created. StacksWeb.BookController.merge_format/2 (POST /api/books/:id/merge-format). |
| Database | Write: op.book_editions (new edition row). Read: op.books (existing work), op.book_editions (existing editions). |
| Jobs (Oban) | EnrichBookJob — fetch edition-specific metadata (page count, cover) for the new edition. TriggerPriceScrapeJob — scrape prices for the new ISBN. |
| External Services | Open Library, Google Books (edition-specific metadata). |
| dbt Models | stg_book_editions, int_format_distribution. |
| Infrastructure | None additional. |
| Dependencies | US-1.1.6 (triggered during duplicate detection). |
| Dimension | Detail |
|---|---|
| Summary | User drops N images; system classifies, extracts, and groups related images (same book → same group); user reviews a confirmation screen with one card per detected book; shelf is chosen per card; confirmed books go through standard ISBN pipeline. Multi-book images (shelfie, screenshot of reading list) produce multiple cards from a single image. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Upload extended — bulk drop zone accepting N files. Page.Upload.Review — card grid with confirmed/ambiguous/rejected buckets. Components.BookReviewCard — thumbnail, title, author, shelf selector, confirm/dismiss actions. Components.BulkProgress — processing indicator. |
| Backend (Phoenix) | StacksWeb.UploadController.create_batch/2 — accepts N images, stores each, enqueues BatchIdentifyJob. StacksWeb.UploadController.batch_status/2 — polls overall batch progress. Stacks.Books.group_by_isbn/1 — merges images that resolved to the same ISBN into a single group. |
| Database | Write: op.uploaded_images with batch_id UUID and group_id UUID columns (new — migration required). Write: op.books, op.bookshelf_placements, op.audit_log per confirmed book. |
| Jobs (Oban) | Stacks.Workers.BatchIdentifyJob — orchestrator: fans out one IdentifyBookJob per image, collects results, performs grouping, writes batch status. |
| External Services | Modal vision service (classify + extract per image). Open Library, Google Books (ISBN resolution per confirmed book). |
| dbt Models | int_bulk_upload_batch_size, int_bulk_upload_confirmation_rate (how many detected books users confirm vs. dismiss). |
| Infrastructure | No new services. Requires batch_id and group_id migration on op.uploaded_images. |
| Dependencies | US-1.1.1 (single-image pipeline); Issue #008 (multi-book extraction API — /extract must return books: list before this can be implemented); Issue #009 (bulk upload issue). |
| Dimension | Detail |
|---|---|
| Summary | Dark walnut shelf, green damask background. Displays books the user has read. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Bookshelf.Library module. Shared Shelf.View component with theme config (ShelfTheme type: { wood: DarkWalnut, backdrop: GreenDamask }). Renders list of spines via Components.Spine. |
| Backend (Phoenix) | Stacks.Shelving context -- Shelving.get_bookshelf_books/2 with bookshelf_name :library. StacksWeb.BookshelfController.show/2. |
| Database | Read: op.bookshelves, op.bookshelf_placements, op.books, op.authors. |
| Jobs (Oban) | None. |
| External Services | None. |
| dbt Models | stg_bookshelf_placements, int_shelf_composition. |
| Infrastructure | None additional. |
| Dependencies | US-1.1.1 (books must exist). |
| Dimension | Detail |
|---|---|
| Summary | Lighter oak shelf, botanical prints. Books owned but not yet read. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Bookshelf.AntiLibrary module. ShelfTheme config: { wood: LightOak, backdrop: BotanicalPrints }. Same Shelf.View component as Library. |
| Backend (Phoenix) | Same Stacks.Shelving context, bookshelf_name :antilibrary. |
| Database | Same as US-1.2.1. |
| Jobs (Oban) | None. |
| External Services | None. |
| dbt Models | Same as US-1.2.1. |
| Infrastructure | None additional. |
| Dependencies | US-1.1.1. |
| Dimension | Detail |
|---|---|
| Summary | Blue-grey shelf, watercolour florals. Books the user wants but does not own. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Bookshelf.WishList module. ShelfTheme config: { wood: BlueGrey, backdrop: WatercolourFlorals }. |
| Backend (Phoenix) | Same Stacks.Shelving context, bookshelf_name :wishlist. |
| Database | Same as US-1.2.1. |
| Jobs (Oban) | None. |
| External Services | None. |
| dbt Models | Same as US-1.2.1. |
| Infrastructure | None additional. |
| Dependencies | US-1.1.1. |
| Dimension | Detail |
|---|---|
| Summary | Books stacked on a side table next to an armchair, face-on view showing spines. Cosy aesthetic. Currently reading. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Bookshelf.ReadingPile module. Different layout from shelves -- vertical stack / pile metaphor rather than horizontal shelf. PileView component with armchair background. |
| Backend (Phoenix) | Same Stacks.Shelving context, bookshelf_name :reading_pile. May include reading progress data. |
| Database | Same as US-1.2.1. |
| Jobs (Oban) | None. |
| External Services | None. |
| dbt Models | Same as US-1.2.1. |
| Infrastructure | None additional. |
| Dependencies | US-1.1.1. |
| Dimension | Detail |
|---|---|
| Summary | Horizontal slide animation for adjacent shelves; room-transition animation for different metaphors (shelf vs. pile). |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Navigation.ShelfRouter module. Elm Browser.application with URL-driven routing. Animation.SlideTransition and Animation.RoomTransition modules using elm-animator or CSS keyframe ports. Swipe gesture detection via ports for mobile. |
| Backend (Phoenix) | None -- purely client-side routing. Phoenix handles initial page load and falls back to SSR for direct URL access. |
| Database | None. |
| Jobs (Oban) | None. |
| External Services | None. |
| dbt Models | None. |
| Infrastructure | None additional. |
| Dependencies | US-1.2.1, US-1.2.2, US-1.2.3, US-1.2.4 (all shelves must exist to navigate between them). |
| Dimension | Detail |
|---|---|
| Summary | Spine thickness proportional to page count. Wear level by engagement (pristine, softened, cracking, well-read, well-loved). Bookmark icon for books with linked writing. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Components.Spine module. Types: SpineData { pageCount: Int, wearLevel: WearLevel, hasWriting: Bool, colour: SpineColour }. WearLevel = `Pristine |
| Backend (Phoenix) | Stacks.Shelving.spine_data/1 -- computes wear level from bookshelf_placement_history (number of reads, time on shelves). Returns precomputed spine metadata. |
| Database | Read: op.books (page_count), op.bookshelf_placement_history (engagement calc). |
| Jobs (Oban) | Stacks.Workers.RecalculateWearJob -- periodic recalculation of wear levels (or on shelf-move events). |
| External Services | None. |
| dbt Models | int_book_engagement (materialized view for wear calculation). |
| Infrastructure | None additional. |
| Dependencies | US-1.2.1 -- US-1.2.4 (shelves to render spines on). |
| Dimension | Detail |
|---|---|
| Summary | Click spine to open an overlay (not a route) showing: cover image, editions, metadata, reviews, prices per edition, author info, writing links, move-to-shelf action, search-surfaced enrichment. Dismissable via X, click-outside, or Escape. URL does not change. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | BookDetailOverlay type in model (Maybe BookDetailOverlay). Not a route — the overlay is UI state managed in the parent page's model. Sub-components: Components.CoverImage, Components.BookMeta, Components.EditionList (shows all formats with per-edition prices), Components.ReviewSummary (stub in Phase 1), Components.PriceInfo (grouped by edition), Components.AuthorCard, Components.WritingLinks, Components.ShelfMover (all 5 shelves), Components.PartnerAvailability (Phase 3+). Messages: OpenBookDetail BookId, CloseBookDetail, MoveToShelf ShelfType, OpenExternalLink Url. Focus trapping within overlay for accessibility (US-19.1.1). |
| Backend (Phoenix) | Stacks.Books.get_book_detail/1 — aggregates work + editions + author + reviews + prices (per edition) + writing links + partner availability (Phase 3+) + community read count (for Looking for a Home). StacksWeb.BookController.show/2. |
| Database | Read: op.books (work), op.book_editions (all editions), op.authors, op.review_snapshots (per work), op.price_snapshots (per edition), op.bookshelf_placements. Phase 3+: op.partner_inventory (per edition via ISBN), op.partner_events (ISBN-linked), op.blog_posts, op.post_book_associations. wh.mart_community_read_count (for community wear on Looking for a Home). |
| Jobs (Oban) | None directly (data populated by enrichment jobs). |
| External Services | None at render time. |
| dbt Models | int_book_detail_view (pre-joined view for performance), mart_community_read_count. |
| Infrastructure | SSR rendering for public book pages (Phoenix templates) remains at /public/book/:isbn. The Elm overlay is the interactive path. |
| Dependencies | US-1.3.1 (spine must be clickable), US-1.1.1 (books exist). |
| Dimension | Detail |
|---|---|
| Summary | Search across all shelves or within a specific shelf. Sort by title, author, date added, rating. Filter by genre, format, price range. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Search module. Components.SearchBar (debounced input). Components.FilterPanel (genre tags, format checkboxes, price range slider). Components.SortSelector. Types: SearchQuery, SortField, FilterSet. |
| Backend (Phoenix) | Stacks.Books.search_books/2 -- Ecto query with full-text search (pg_trgm or tsvector), dynamic sort, filter composition. StacksWeb.SearchController.index/2. |
| Database | Read: op.books (with GIN index on title/author tsvector), op.authors, op.bookshelves, op.bookshelf_placements, op.price_snapshots (for price filter). |
| Jobs (Oban) | None. |
| External Services | None. |
| dbt Models | None directly. |
| Infrastructure | PostgreSQL GIN / GiST indexes for full-text search. |
| Dependencies | US-1.1.1, US-1.2.1 -- US-1.2.4. |
| Dimension | Detail |
|---|---|
| Summary | Searches beyond the user's own collection: other users' public shelves, marketplace listings, partner inventory, and Third Spaces events. Results surfaced in the search dropdown and enriched within the book detail overlay. |
| Phase | Phase 1 (late MVP) / Phase 3 (partner results) |
| Layer | Components |
|---|---|
| Frontend (Elm) | SearchScope type gains WholePlatform variant. Search dropdown splits into "Your Collection" (instant local) and "On the Platform" (async API). External results show contextual labels ("Listed by [user] for R120", "In stock at [partner]", "On [user]'s shelf"). Shimmer placeholders while loading. |
| Backend (Phoenix) | Stacks.Books.search_platform/2 (GET /api/search/platform) — queries across bookshelf_placements (public visibility), listings (active), partner_inventory (approved partners), partner_events (related ISBNs). Applies resolve_visibility/2 filtering and block filtering. Results grouped by type. |
| Database | Read: op.books, op.book_editions, op.bookshelf_placements (where visibility = 'platform'), op.listings, op.partner_inventory, op.partner_events, op.user_blocks. |
| Jobs (Oban) | None. |
| External Services | None. |
| dbt Models | None directly. |
| Infrastructure | PostgreSQL full-text indexes on books.title, authors.name. Rate limit: 30/min. |
| Dependencies | US-1.4.1 (basic search), US-10.1.1 (visibility model for filtering). |
| Dimension | Detail |
|---|---|
| Summary | Standard flow: WishList -> AntiLibrary -> Reading Pile -> Library. History of all movements preserved. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Components.ShelfMover -- dropdown or contextual menu showing valid target shelves. Animation of book sliding to new shelf. Confirmation toast. |
| Backend (Phoenix) | Stacks.Shelving.move_book/3 -- validates transition, updates placement, writes history. StacksWeb.BookshelfPlacementController.update/2. |
| Database | Write: op.bookshelf_placements (update shelf_id), op.bookshelf_placement_history (insert movement record). Read: op.bookshelf_placements (current location). |
| Jobs (Oban) | Stacks.Workers.RecalculateWearJob triggered on move (wear may change). |
| External Services | None. |
| dbt Models | stg_bookshelf_placement_history, int_book_journey. |
| Infrastructure | None additional. |
| Dependencies | US-1.2.1 -- US-1.2.4, US-1.3.2 (move action lives on detail page). |
| Dimension | Detail |
|---|---|
| Summary | Move from Reading Pile back to AntiLibrary. Optional note about why abandoned. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Components.AbandonModal -- optional textarea for note, confirm/cancel. Triggered from Reading Pile or Book Detail. |
| Backend (Phoenix) | Stacks.Shelving.abandon_book/2 -- special case of move with optional abandon_note. |
| Database | Write: op.bookshelf_placements, op.bookshelf_placement_history (with note field). |
| Jobs (Oban) | Wear recalculation. |
| External Services | None. |
| dbt Models | int_abandonment_rate (metrics). |
| Infrastructure | None additional. |
| Dependencies | US-1.5.1, US-1.2.4 (Reading Pile). |
| Dimension | Detail |
|---|---|
| Summary | Move from Library back to Reading Pile. Wear level increases. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Re-read button on Book Detail when book is on Library shelf. Uses Components.ShelfMover with pre-selected target. |
| Backend (Phoenix) | Stacks.Shelving.reread_book/1 -- moves to Reading Pile, triggers wear recalculation. Read count is derived from bookshelf_placement_history (no denormalised counter). |
| Database | Write: op.bookshelf_placements, op.bookshelf_placement_history. |
| Jobs (Oban) | RecalculateWearJob. |
| External Services | None. |
| dbt Models | int_book_engagement. |
| Infrastructure | None additional. |
| Dependencies | US-1.5.1, US-1.2.1 (Library). |
| Dimension | Detail |
|---|---|
| Summary | Track which formats the user owns per book. Formats are now derived from book_editions — each owned format is a row in book_editions under the same work. Adding a format means creating a new edition (with its own ISBN) or toggling is_primary. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Components.FormatPicker on Book Detail overlay. Shows all known editions as filled icons (owned) plus outlined icons for formats not yet owned. Clicking an un-owned format prompts: "Enter the ISBN for the [format] edition" (inline ISBN input), which triggers the multi-format merge flow (US-1.1.8). |
| Backend (Phoenix) | Stacks.Books.merge_edition/2 — creates a new book_editions row. No update_placement_formats — formats are no longer a column on placements. |
| Database | Write: op.book_editions (new edition row). Read: op.book_editions (existing editions for the work). |
| Jobs (Oban) | EnrichBookJob (fetch edition-specific metadata). |
| External Services | Open Library, Google Books (ISBN resolution for new format). |
| dbt Models | stg_book_editions, int_format_distribution. |
| Infrastructure | None additional. |
| Dependencies | US-1.3.2 (format picker lives on detail overlay), US-1.1.8 (merge flow). |
| Dimension | Detail |
|---|---|
| Summary | Soft delete via bookshelf_placements.removed_at. Book row preserved. Reading history preserved. Confirmation dialog warns this removes from all shelves. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Components.RemoveBookModal -- confirmation with book title, warning text ("This removes [title] from your collection. Your reading history is preserved."), confirm/cancel. Triggered from Book Detail page. |
| Backend (Phoenix) | Stacks.Shelving.remove_book/1 -- sets removed_at on all bookshelf_placements for the book. Does NOT delete the books row. Writes history record. |
| Database | Write: op.bookshelf_placements (set removed_at), op.bookshelf_placement_history (removal record). Read: op.bookshelf_placements (current placements). |
| Jobs (Oban) | RecalculateWearJob (no longer on shelf). |
| External Services | None. |
| dbt Models | int_collection_churn. |
| Infrastructure | None additional. |
| Dependencies | US-1.3.2 (remove action lives on detail page). |
| Dimension | Detail |
|---|---|
| Summary | Each shelf displays a themed empty state when no books are present: Library ("Your library awaits..."), AntiLibrary ("Unread worlds..."), WishList ("What will you read next?"), Reading Pile ("Pick up something new"), Third Spaces ("Discover reading spaces"), Metrics ("Add books to see insights"). |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Components.EmptyShelf -- per-shelf variant with themed illustration and message. Rendered when bookshelf_placements count is 0 for a shelf. Includes CTA button ("Add a book" → upload). |
| Backend (Phoenix) | None additional (empty state is client-side based on empty shelf data). |
| Database | Read: op.bookshelves, op.bookshelf_placements (count check). |
| Jobs (Oban) | None. |
| External Services | None. |
| dbt Models | None. |
| Infrastructure | None additional. |
| Dependencies | US-1.2.1 -- US-1.2.4 (shelves must exist). |
| Dimension | Detail |
|---|---|
| Summary | Aggregate reviews from GoodReads, Reddit, Storygraph. Show sentiment overview and source links. |
| Phase | Phase 2 (Enrichment) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Components.ReviewSummary -- sentiment bar (positive/mixed/negative), source cards with rating + link. Expandable detail. |
| Backend (Phoenix) | Stacks.Enrichment.Reviews context. ReviewAggregator Broadway pipeline for ingestion. Enrichment.Reviews.get_review_summary/1. |
| Database | Write: op.review_snapshots. Read: op.books (ISBN for lookup). |
| Jobs (Oban) | Stacks.Workers.FetchReviewsJob -- per-book, scheduled with adaptive staleness (popular books refresh more often). |
| External Services | GoodReads (scrape/API), Reddit API, Storygraph (scrape). |
| dbt Models | stg_review_snapshots, int_review_sentiment, mart_book_reviews. |
| Infrastructure | Broadway pipeline for backpressure on bulk review fetching. |
| Dependencies | US-1.1.1, US-1.3.2 (reviews displayed on detail page). |
| Dimension | Detail |
|---|---|
| Summary | Track prices from SA bookshops. Show price history with sparkline trends. |
| Phase | Phase 2 (Enrichment) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Components.PriceInfo -- current prices by store, sparkline chart (SVG or elm-charts), lowest price highlight. |
| Backend (Phoenix) | Stacks.Enrichment.Prices context. Receives scraped data from Rust scraper via internal API or message queue. Enrichment.Prices.get_price_history/1. |
| Database | Write: op.price_snapshots, op.bookstores. Read: op.books (ISBN). |
| Jobs (Oban) | Stacks.Workers.TriggerPriceScrapeJob -- signals Rust scraper for a batch of ISBNs. |
| External Services | Rust scraper microservice (internal). |
| dbt Models | stg_price_snapshots, int_price_trends, mart_book_prices. |
| Infrastructure | Rust scraper as separate Fly.io machine. Internal networking between Phoenix and Rust service. |
| Dependencies | US-1.1.1, US-2.2.2 (scraper config must exist). |
| Dimension | Detail |
|---|---|
| Summary | TOML configuration per bookstore per country. Defines scraping targets, selectors, rate limits. |
| Phase | Phase 2 (Enrichment) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Admin-only Page.Admin.ScraperConfig -- TOML editor with validation feedback. |
| Backend (Phoenix) | Stacks.Admin.ScraperConfig context -- CRUD for bookstore scraper configs. Validates TOML, pushes to Rust scraper. StacksWeb.Admin.ScraperConfigController. |
| Database | Write: op.bookstores (config column). |
| Jobs (Oban) | None (config is synchronous). |
| External Services | Rust scraper (config reload endpoint). |
| dbt Models | None. |
| Infrastructure | TOML files or DB-stored config synced to Rust service. |
| Dependencies | None (can be built independently, needed before US-2.2.1). |
| Dimension | Detail |
|---|---|
| Summary | Track author websites, RSS feeds, events, new releases. |
| Phase | Phase 2 (Enrichment) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Components.AuthorCard (expanded) -- website link, latest RSS posts, upcoming events, new releases list. |
| Backend (Phoenix) | Stacks.Enrichment.Authors context. Enrichment.Authors.get_author_intel/1. RSS feed parser (Elixir feeder_ex or similar). |
| Database | Write: op.authors (website, rss_url, last_fetched). op.discovered_sources (author-related sources). Read: op.books (by author). |
| Jobs (Oban) | Stacks.Workers.FetchAuthorRSSJob -- periodic RSS poll. Stacks.Workers.DiscoverAuthorSourcesJob -- uses Brave Search to find author info. |
| External Services | Brave Search API, author RSS feeds, publisher sites. |
| dbt Models | stg_authors, int_author_activity. |
| Infrastructure | None additional. |
| Dependencies | US-1.1.1, US-2.5.1 (source discovery feeds author intelligence). |
| Dimension | Detail |
|---|---|
| Summary | Discover book signings, readings at physical stores. Match to user's books/authors. |
| Phase | Phase 2 (Enrichment) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Events module. Components.EventCard -- store name, event type, date, matched book/author. Calendar-style or list view. |
| Backend (Phoenix) | Stacks.Enrichment.Events context. Enrichment.Events.get_matched_events/1 -- joins events with user's book/author graph. |
| Database | Write: op.bookstore_events. Read: op.bookstores, op.books, op.authors, op.bookshelf_placements. |
| Jobs (Oban) | Stacks.Workers.DiscoverBookstoreEventsJob -- periodic search for events. |
| External Services | Brave Search, SearXNG, bookstore websites. |
| dbt Models | stg_bookstore_events, int_event_matches. |
| Infrastructure | None additional. |
| Dependencies | US-2.2.2 (bookstores must exist), US-2.5.1 (discovery agent). |
| Dimension | Detail |
|---|---|
| Summary | Automatic + scheduled agent that uses Brave Search and SearXNG to discover review sources, author info, events. LLM confidence scoring. Human approval before activation. |
| Phase | Phase 2 (Enrichment) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Admin.SourceApproval -- queue of discovered sources with confidence scores. Approve/reject actions. |
| Backend (Phoenix) | Stacks.Discovery context. Discovery.Agent GenServer or Oban-driven. Discovery.search_and_score/1 -- calls search APIs, runs LLM scoring. Discovery.approve_source/1, Discovery.reject_source/1. |
| Database | Write: op.discovered_sources (url, type, confidence, status, approved_at). op.audit_log. Read: op.books, op.authors (to generate search queries). |
| Jobs (Oban) | Stacks.Workers.SourceDiscoveryJob -- scheduled (e.g., daily). Stacks.Workers.ScoreSourceJob -- LLM confidence scoring per discovered URL. |
| External Services | Brave Search API, SearXNG (self-hosted), Together AI (LLM scoring — future feature). |
| dbt Models | stg_discovered_sources, int_source_approval_rate. |
| Infrastructure | SearXNG instance on Fly.io. Rate limiting for Brave Search API. |
| Dependencies | US-1.1.1 (books/authors must exist to search for). |
| Dimension | Detail |
|---|---|
| Summary | Location-triggered discovery of local bookshops, reading groups, cafes, and literary events. Fires when user sets location (US-17.2.2) and on a quarterly cron. Populates Third Spaces independently of book-specific triggers. |
| Phase | Phase 2 (Enrichment) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Third Spaces page shows "Discovering spaces near [City]…" loading state after location set. Results appear as cards on the cork board. |
| Backend (Phoenix) | Stacks.Discovery.GeographicSweep — generates location-based search queries ("bookshop {city}", "reading group {city}", etc.), evaluates results with LLM scoring, checks exclusion list before suggesting. Discovery.geographic_sweep/2 accepts {city, country_code}. |
| Database | Write: op.discovered_sources (with discovered_via = 'geographic_sweep'), op.third_spaces. Read: op.discovered_sources (exclusion list — status = 'excluded'), op.users (location). |
| Jobs (Oban) | Stacks.Workers.GeographicDiscoveryJob — triggered by user.location_updated event and by quarterly Oban.Cron schedule. Queue: geographic_discovery, concurrency: 2. |
| External Services | Brave Search API, SearXNG. |
| dbt Models | Reuses stg_discovered_sources, stg_third_spaces. |
| Infrastructure | None additional (reuses discovery infrastructure). |
| Dependencies | US-2.5.1 (same scoring infrastructure), US-17.2.2 (location must be set). |
| Dimension | Detail |
|---|---|
| Summary | Businesses discovered and listed without their consent can request removal. "Is this your business?" link on every discovered listing. Exclusion list prevents re-discovery. |
| Phase | Phase 2 (Enrichment) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Subtle "Is this your business?" link on Components.SpaceCard and Components.PriceInfo for discovered (non-partner) sources. Links to a simple form: business name, contact email, choice between "Remove my listing" and "Become a partner". |
| Backend (Phoenix) | StacksWeb.OptOutController.create/2 (POST /api/opt-out) — unauthenticated endpoint. Creates an opt-out request. Platform owner is notified. For removal: sets discovered_sources.status = 'excluded' and/or third_spaces.opted_out = true. For partnership interest: routes to US-9.1.1 flow. Stacks.Discovery.OptOut context — request_removal/1, process_removal/1, add_to_exclusion_list/1. |
| Database | Write: op.discovered_sources (status = 'excluded', excluded_at, exclusion_email). op.third_spaces (opted_out = true, opted_out_at). op.audit_log. |
| Jobs (Oban) | Stacks.Workers.OptOutConfirmationJob — sends confirmation email to the business. |
| External Services | Resend / Postmark (confirmation email). |
| dbt Models | int_opt_out_rate. |
| Infrastructure | Rate limit: 5/min on /api/opt-out (unauthenticated). |
| Dependencies | US-2.5.1 or US-2.5.2 (discovered sources must exist). |
| Dimension | Detail |
|---|---|
| Summary | Cork board aesthetic displaying reading groups, cafes, festivals. Discovered via search. Users share off-platform links. Country-aware location settings. |
| Phase | Phase 4 (Polish) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.ThirdSpaces module. Components.CorkBoard -- pinned card layout. Components.SpaceCard -- name, type, location, external link. Components.LocationFilter -- country/city selector. |
| Backend (Phoenix) | Stacks.ThirdSpaces context. CRUD for spaces. StacksWeb.ThirdSpaceController. Location-based filtering with country config. |
| Database | Write: op.third_spaces, op.third_space_events. Read: same. |
| Jobs (Oban) | Stacks.Workers.DiscoverThirdSpacesJob -- periodic discovery of new spaces. |
| External Services | Brave Search, SearXNG. |
| dbt Models | stg_third_spaces. |
| Infrastructure | None additional. |
| Dependencies | US-2.5.1 (uses same discovery infrastructure). |
| Dimension | Detail |
|---|---|
| Summary | 4-step pipeline: (1) book check via vision, (2) ISBN resolution, (3) BISAC subject classification, (4) store with visibility tier. |
| Phase | Phase 1 (MVP) / Cross-cutting |
| Layer | Components |
|---|---|
| Frontend (Elm) | Status indicators on upload flow -- progress through pipeline steps. Error states per step. |
| Backend (Phoenix) | Stacks.Moderation context. Moderation.Pipeline -- orchestrates 4 steps as a state machine. Steps: classify_image/1 -> resolve_isbn/1 -> classify_subject/1 -> store_with_tier/1. |
| Database | Write: op.books (visibility_tier), op.audit_log (each step logged). Read: op.books (dedup). |
| Jobs (Oban) | Integrated into IdentifyBookJob as sub-steps, or Stacks.Workers.ModerationPipelineJob as separate orchestrator. |
| External Services | Modal vision service (step 1), Open Library / Google Books (step 2). |
| dbt Models | int_moderation_outcomes. |
| Infrastructure | None additional. |
| Dependencies | US-1.1.1 (runs as part of upload). |
| Dimension | Detail |
|---|---|
| Summary | KYC integration for age verification. Self-config for single-user. Proper verification for multi-user. |
| Phase | Cross-cutting (Phase 1 for self-config, Phase 3 for full KYC) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Settings.AgeVerification -- self-declaration toggle (single-user) or KYC upload flow (multi-user). Components.KYCWidget -- iframe or redirect to provider. |
| Backend (Phoenix) | Stacks.Accounts.Verification -- verify_age/1, self_declare_age/1. KYC provider integration module. StacksWeb.VerificationController. |
| Database | Write: op.audit_log (verification events). User account flags (age_verified, verification_method). |
| Jobs (Oban) | Stacks.Workers.KYCCallbackJob -- process async KYC provider callbacks. |
| External Services | Smile Identity, Yoti, or Sumsub (KYC provider). |
| dbt Models | None. |
| Infrastructure | Webhook endpoint for KYC provider callbacks. |
| Dependencies | US-4.1.1 (need content to gate). |
| Dimension | Detail |
|---|---|
| Summary | System health, job status, data freshness, source discovery stats, costs, GDPR compliance. Curator's desk aesthetic. NOT user reading analytics. |
| Phase | Phase 4 (Polish) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Admin.Metrics module. Components.MetricCard, Components.JobStatusTable, Components.FreshnessGauge, Components.CostTracker. Curator's desk visual theme. |
| Backend (Phoenix) | Stacks.Admin.Metrics context -- aggregates from Oban telemetry, dbt freshness, audit log. StacksWeb.Admin.MetricsController. |
| Database | Read: op.audit_log, wh.* (warehouse views), Oban job tables, op.discovered_sources. |
| Jobs (Oban) | Stacks.Workers.MetricsSnapshotJob -- periodic snapshot of system state. |
| External Services | None (all internal data). |
| dbt Models | mart_system_health, mart_job_stats, mart_data_freshness, mart_cost_tracking, mart_gdpr_compliance. |
| Infrastructure | dbt scheduled runs (could be Oban-triggered or cron). |
| Dependencies | US-8.1.5 (audit log must exist). Most useful after Phase 2 when enrichment jobs are running. |
| Dimension | Detail |
|---|---|
| Summary | Atom feed per public shelf. Friends can follow shelves. Enables IRL book borrowing. |
| Phase | Phase 4 (Polish) |
| Layer | Components |
|---|---|
| Frontend (Elm) | RSS icon/link on shelf pages. Components.RSSLink. No Elm rendering of feed itself (consumed by RSS readers). |
| Backend (Phoenix) | Stacks.Feeds context. Feeds.generate_atom/2 -- builds Atom XML for a shelf. StacksWeb.FeedController.show/2 -- serves XML with correct content type. Cache with ETag/Last-Modified. |
| Database | Read: op.bookshelves, op.bookshelf_placements, op.books, op.authors. |
| Jobs (Oban) | Stacks.Workers.RegenerateFeedJob -- regenerate cached feed when shelf changes (event-driven). |
| External Services | None. |
| dbt Models | None. |
| Infrastructure | CDN caching for feed XML. |
| Dependencies | US-1.2.1 -- US-1.2.4 (shelves must exist). |
See ADR 013 for the decision to ship as a classifieds board rather than a full e-commerce platform.
| Dimension | Detail |
|---|---|
| Summary | Classifieds listing: photos, condition grading (new / like_new / good / fair / poor), pricing, and seller contact info for off-platform communication. No on-platform payments or messaging. |
| Phase | Phase 5 (Marketplace) — Implemented |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Marketplace.CreateListing -- photo upload (reuse upload components), condition selector (enum), pricing mode toggle (fixed / offer), minimum price input, contact info field. Components.ConditionGrader. |
| Backend (Phoenix) | Stacks.Marketplace context. Marketplace.create_listing/1. StacksWeb.ListingController. Listing state machine: draft -> active -> sold -> removed -> expired. Denormalises listing_status to bookshelf_placements. |
| Database | Write: op.listings (including contact_info), op.uploaded_images (listing photos), op.bookshelf_placements (listing_status denorm). Read: op.books. |
| Jobs (Oban) | Stacks.Workers.ListingExpiryJob -- auto-expire listings past 30-day TTL. |
| External Services | None. |
| dbt Models | stg_listings, mart_marketplace_activity. |
| Infrastructure | Image storage for listing photos. |
| Dependencies | US-1.1.1 (book must exist). |
| Dimension | Detail |
|---|---|
| Summary | Deferred per ADR 013. On-platform payments (Stitch Money, #054b) and shipping (Pargo, #054c) are deferred indefinitely. Buyers contact sellers directly using the contact info on the listing. The transactions, offer_threads, and offer_messages tables exist in DB but are unused. |
| Phase | Future (was Phase 5) |
| Dimension | Detail |
|---|---|
| Summary | Deferred per ADR 013. Seller KYC becomes relevant when the platform facilitates financial transactions. Not needed for classifieds model. |
| Phase | Future (was Phase 5) |
| Dimension | Detail |
|---|---|
| Summary | User can export all personal data in JSON, CSV, or OPDS format. |
| Phase | Phase 2 (Enrichment) / Cross-cutting |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Settings.DataExport -- format selector (JSON/CSV/OPDS), download button, progress indicator. |
| Backend (Phoenix) | Stacks.GDPR.Export -- export_user_data/2. Collects from all user-related tables. Serialisers for JSON, CSV, OPDS. StacksWeb.GDPRController.export/2. |
| Database | Read: All tables with user data: op.books, op.bookshelves, op.bookshelf_placements, op.bookshelf_placement_history, op.blog_posts, op.audit_log (user's entries). |
| Jobs (Oban) | Stacks.Workers.DataExportJob -- async generation for large datasets. Notifies user when ready. |
| External Services | None. |
| dbt Models | None. |
| Infrastructure | Temporary file storage for export downloads. |
| Dependencies | US-8.1.3 (consent must be in place). |
| Dimension | Detail |
|---|---|
| Summary | Cascade delete all user data from operational DB. Anonymise records in warehouse. |
| Phase | Phase 2 (Enrichment) / Cross-cutting |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Settings.DeleteAccount -- confirmation flow with typed confirmation phrase. Explains consequences. |
| Backend (Phoenix) | Stacks.GDPR.Deletion -- delete_user_data/1. Ecto.Multi transaction for cascade delete across op schema. Separate call to anonymise wh schema records. StacksWeb.GDPRController.delete/2. |
| Database | Delete: All user rows in op.*. Update: wh.* (anonymise user_id, hash PII). |
| Jobs (Oban) | Stacks.Workers.AccountDeletionJob -- async cascade + warehouse anonymisation. Stacks.Workers.ConfirmDeletionJob -- verification email before execution. |
| External Services | None. |
| dbt Models | mart_gdpr_deletions (tracking). |
| Infrastructure | None additional. |
| Dependencies | US-8.1.3 (consent), US-8.1.5 (audit log records deletion event). |
| Dimension | Detail |
|---|---|
| Summary | Per-use consent with timestamps. User controls what data is collected and how it is used. |
| Phase | Phase 1 (MVP) / Cross-cutting |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Settings.Consent -- toggle switches per consent category. Components.ConsentBanner -- first-visit consent collection. Timestamps displayed per consent. |
| Backend (Phoenix) | Stacks.GDPR.Consent -- grant_consent/2, revoke_consent/2, check_consent/2. Plug StacksWeb.Plugs.ConsentCheck for gating features on consent. |
| Database | Write: Consent records (user_id, consent_type, granted_at, revoked_at) in op.audit_log or dedicated consent table. |
| Jobs (Oban) | None. |
| External Services | None. |
| dbt Models | mart_consent_status. |
| Infrastructure | None additional. |
| Dependencies | None (foundational -- built first). |
| Dimension | Detail |
|---|---|
| Summary | Uploaded images auto-deleted after 30 days. Only thumbnails retained permanently. |
| Phase | Phase 1 (MVP) / Cross-cutting |
| Layer | Components |
|---|---|
| Frontend (Elm) | Info text on upload page explaining retention policy. |
| Backend (Phoenix) | Stacks.GDPR.ImageRetention -- cleanup_expired_images/0. Generates thumbnail on upload, stores separately. |
| Database | Write: op.uploaded_images (delete originals, keep thumbnail_url). Read: op.uploaded_images (where uploaded_at < 30 days ago). |
| Jobs (Oban) | Stacks.Workers.ImageRetentionJob -- daily scheduled cleanup. |
| External Services | Object storage (Cloudflare R2) -- delete original files. |
| dbt Models | mart_image_retention_stats. |
| Infrastructure | Object storage lifecycle policies as backup. |
| Dependencies | US-1.1.1 (images must be uploaded). |
| Dimension | Detail |
|---|---|
| Summary | Immutable append-only log. Hashed IPs. Encrypted metadata. |
| Phase | Phase 1 (MVP) / Cross-cutting |
| Layer | Components |
|---|---|
| Frontend (Elm) | None (backend-only). Admin view in US-5.1.1. |
| Backend (Phoenix) | Stacks.Audit context -- Audit.log/3 (action, actor, metadata). IP hashing via :crypto.hash(:sha256, ip). Metadata encryption via Cloak or similar. Append-only (no UPDATE/DELETE on audit_log). |
| Database | Write: op.audit_log (action, actor_hash, ip_hash, encrypted_metadata, inserted_at). Table has no UPDATE/DELETE grants for app role. |
| Jobs (Oban) | None (synchronous writes, or async via Stacks.Workers.AuditLogJob if performance requires). |
| External Services | None. |
| dbt Models | stg_audit_log, mart_audit_summary. |
| Infrastructure | Database role with INSERT-only permission on audit_log. Partition by month for performance. |
| Dependencies | None (foundational -- built first). |
| Dimension | Detail |
|---|---|
| Summary | Bookshops, reading groups, cafés register to push data to the platform. Owner approves. |
| Phase | Phase 3 (Partner Integration) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Partner.Register -- registration form (name, type, location, description, links). Page.Metrics.PartnerRequests -- owner approval UI as pinned index cards. |
| Backend (Phoenix) | Stacks.Partners context -- register/1, approve/1, decline/1, suspend/1. StacksWeb.PartnerController.create/2. API key generation on approval (32-byte random hex, Argon2 hash stored). Event emission: partner.registered, partner.approved. |
| Database | Write: partners (name, type, country_code, city, api_key_hash, status). Write: audit_log, event_log. |
| Jobs (Oban) | Stacks.Workers.PartnerApprovalNotificationJob -- notify partner on approval/decline. |
| External Services | None. |
| dbt Models | stg_partners, int_partner_approval_rate. |
| Infrastructure | Partner API rate limiter (separate tier: 100/min, 10k/day). |
| Dependencies | US-8.1.5 (audit logging). EDA infrastructure (event_log, emit/subscribe). |
| Dimension | Detail |
|---|---|
| Summary | Partners rotate or revoke their API keys from the partner dashboard. |
| Phase | Phase 3 (Partner Integration) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Partner.Settings -- key management panel showing prefix, created date, last used. "Rotate Key" with confirmation dialog. |
| Backend (Phoenix) | Stacks.Partners.rotate_key/1 -- generates new key, hashes, invalidates old. StacksWeb.PartnerSettingsController. |
| Database | Update: partners.api_key_hash, partners.api_key_prefix. Write: audit_log. |
| Jobs (Oban) | None. |
| External Services | None. |
| dbt Models | None. |
| Infrastructure | None additional. |
| Dependencies | US-9.1.1 (partner exists). |
| Dimension | Detail |
|---|---|
| Summary | Partners push inventory (ISBN, price, condition, quantity) via API. Books with known ISBNs link immediately; unknown ISBNs queue for resolution. |
| Phase | Phase 3 (Partner Integration) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Book detail view: Components.PartnerAvailability -- "Available at [Shop] for R149" sidebar section. Shelf view: green dot on spine for partner-stocked books. |
| Backend (Phoenix) | Stacks.Partners.Inventory context -- sync/2 (upsert/remove). StacksWeb.PartnerAPI.InventoryController. Schema validation via Protobuf-generated JSON schema (InventorySyncRequest). Event emission: inventory.updated. ISBN resolution via existing Stacks.Books.ISBNResolver for unknown ISBNs. |
| Database | Write: partner_inventory (isbn, price_cents, currency, condition, quantity). Read: books (ISBN lookup). Write: event_log. |
| Jobs (Oban) | Stacks.Workers.PartnerISBNResolveJob -- resolve unknown ISBNs from partner inventory. Reuses existing ISBN pipeline. |
| External Services | Open Library, Google Books (for unknown ISBN resolution). |
| dbt Models | stg_partner_inventory, int_partner_availability, mart_partner_stock_coverage. |
| Infrastructure | Protobuf schema (proto/stacks/partner/inventory.proto). |
| Dependencies | US-9.1.1 (partner approved), US-9.6.2 (validation). US-1.1.1 (ISBN resolution pipeline). Protobuf schemas. |
| Dimension | Detail |
|---|---|
| Summary | Non-technical partners upload a CSV instead of using the API. Same ingest pipeline underneath. |
| Phase | Phase 3 (Partner Integration) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Partner.InventoryImport -- file upload area, template download link, preview table (matched/pending/invalid rows), confirm button. |
| Backend (Phoenix) | Stacks.Partners.Inventory.CSVImport -- parse, validate per-row, generate preview, then delegate to sync/2 on confirmation. StacksWeb.PartnerDashboard.InventoryController.import/2. |
| Database | Same as US-9.2.1. |
| Jobs (Oban) | Same as US-9.2.1 (ISBN resolution for unknowns). |
| External Services | Same as US-9.2.1. |
| dbt Models | Same as US-9.2.1. |
| Infrastructure | None additional. |
| Dependencies | US-9.2.1 (same ingest pipeline). |
| Dimension | Detail |
|---|---|
| Summary | Partners push events (signings, meetups, launches) via API. Events appear on the Third Spaces cork board and optionally on book detail views (if ISBN-linked). |
| Phase | Phase 3 (Partner Integration) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Third Spaces cork board: Components.PartnerEventCard -- hand-lettered flyer style. Book detail: "Upcoming event at [Shop]" in sidebar when ISBN matches. |
| Backend (Phoenix) | Stacks.Partners.Events context -- create/2, update/2, cancel/1. StacksWeb.PartnerAPI.EventController. Schema validation via Protobuf (PartnerEvent). Event emission: event.created. Auto-archive past events via scheduled job. |
| Database | Write: partner_events (title, event_type, event_date, location, related_isbns). Write: event_log. |
| Jobs (Oban) | Stacks.Workers.ArchivePartnerEventsJob -- scheduled daily, archives past events. |
| External Services | None. |
| dbt Models | stg_partner_events, int_partner_event_calendar. |
| Infrastructure | Protobuf schema (proto/stacks/partner/events.proto). |
| Dependencies | US-9.1.1 (partner approved), US-9.6.2 (validation). |
| Dimension | Detail |
|---|---|
| Summary | Non-technical partners create and manage events through a web form. |
| Phase | Phase 3 (Partner Integration) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Partner.Events -- event list (upcoming/past/cancelled). Page.Partner.EventForm -- date picker, location, ISBN autocomplete, description. |
| Backend (Phoenix) | Same context as US-9.3.1, different controller: StacksWeb.PartnerDashboard.EventController. |
| Database | Same as US-9.3.1. |
| Jobs (Oban) | Same as US-9.3.1. |
| External Services | None. |
| dbt Models | Same as US-9.3.1. |
| Infrastructure | None additional. |
| Dependencies | US-9.3.1 (same storage). |
| Dimension | Detail |
|---|---|
| Summary | Partners register their venue as a reader-friendly third space. Owner approves on first submission. |
| Phase | Phase 3 (Partner Integration) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Third Spaces cork board: Components.PartnerSpaceCard -- vintage postcard style, distinct from user-submitted. Shows name, type, distance, amenities, outbound links. |
| Backend (Phoenix) | Stacks.Partners.Spaces context -- register/2, update/2. StacksWeb.PartnerAPI.SpaceController. Schema validation via Protobuf (Space). Event emission: space.registered. Owner approval for first submission. |
| Database | Write: partner_spaces (name, type, address, amenities, opening_hours, links). Write: event_log. |
| Jobs (Oban) | None. |
| External Services | None. |
| dbt Models | stg_partner_spaces. |
| Infrastructure | Protobuf schema (proto/stacks/partner/spaces.proto). |
| Dependencies | US-9.1.1 (partner approved), US-9.6.2 (validation). |
| Dimension | Detail |
|---|---|
| Summary | Readers suggest third spaces they've discovered. Community-submitted, visually distinct from partner-verified. |
| Phase | Phase 3 (Partner Integration) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Third Spaces cork board: "Pin a new space" button. Page.ThirdSpaces.SuggestForm -- postcard-style form (name, type, city, link). Components.CommunitySpaceCard -- handwritten style, "suggested by a reader" note. |
| Backend (Phoenix) | Stacks.ThirdSpaces.suggest/2 -- creates a third_spaces record with verified: false, discovered_via: 'user_submission'. Owner approval required. |
| Database | Write: third_spaces (reuses existing table with discovered_via = 'user_submission'). Write: audit_log. |
| Jobs (Oban) | None. |
| External Services | None. |
| dbt Models | Reuses stg_third_spaces. |
| Infrastructure | None additional. |
| Dependencies | US-3.1.1 (Third Spaces cork board exists). |
| Dimension | Detail |
|---|---|
| Summary | Partners see aggregate, anonymised engagement data (impressions, clicks). Counts rounded to nearest 10. |
| Phase | Phase 3 (Partner Integration) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Partner.Metrics -- counters with 30-day sparklines. Inventory impressions, event views, space card views, outbound link clicks. |
| Backend (Phoenix) | Stacks.Partners.Metrics context -- aggregate queries on event_log filtered by partner. Counts rounded to nearest 10. StacksWeb.PartnerAPI.MetricsController. StacksWeb.PartnerDashboard.MetricsController. |
| Database | Read: event_log (filtered by aggregate_id matching partner's content). partner_inventory, partner_events, partner_spaces. |
| Jobs (Oban) | Stacks.Workers.PartnerMetricsSnapshotJob -- scheduled daily, pre-aggregates metrics for dashboard performance. |
| External Services | None. |
| dbt Models | int_partner_impressions, int_partner_clicks, mart_partner_engagement. |
| Infrastructure | None additional. |
| Dependencies | US-9.2.1, US-9.3.1, US-9.4.1 (content must exist). EDA (event_log for impression tracking). |
| Dimension | Detail |
|---|---|
| Summary | Owner approves/declines partner registrations and can flag/suspend partner content. |
| Phase | Phase 3 (Partner Integration) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Metrics.PartnerManagement -- partner table (name, type, status, content count, last sync). Approval queue as index cards. Content moderation queue for flagged items. |
| Backend (Phoenix) | Stacks.Partners.approve/1, decline/2, suspend/1, reinstate/1. Stacks.Partners.Content.flag/2, remove/2. Suspension hides all content and rejects API calls. |
| Database | Update: partners.status. Update: partner_events, partner_spaces (flag/remove). Write: audit_log. |
| Jobs (Oban) | None (synchronous actions). |
| External Services | None. |
| dbt Models | int_partner_moderation. |
| Infrastructure | None additional. |
| Dependencies | US-9.1.1 (partners exist). US-5.1.1 (Metrics Dashboard as host page). |
| Dimension | Detail |
|---|---|
| Summary | All partner payloads auto-validated before reaching the platform. Schema validation, ISBN checks, text blocklist, date validation. |
| Phase | Phase 3 (Partner Integration) |
| Layer | Components |
|---|---|
| Frontend (Elm) | None (API-level). Partner dashboard shows validation errors on CSV import. |
| Backend (Phoenix) | StacksWeb.Plugs.SchemaValidation -- validates request body against Protobuf-generated JSON schema. Stacks.Partners.Validation -- ISBN checksum, positive price, future date, text blocklist, URL domain check. Returns structured ValidationError list. |
| Database | Read: partners (status check). |
| Jobs (Oban) | None (synchronous validation). |
| External Services | None. |
| dbt Models | int_partner_validation_errors (tracking rejection patterns). |
| Infrastructure | Protobuf schemas (all partner .proto files). buf lint in CI. |
| Dependencies | Protobuf schema contracts. |
| Dimension | Detail |
|---|---|
| Summary | Partners check registration status via a link from their confirmation email. Shows Pending/Changes Requested/Approved/Declined with appropriate next steps. |
| Phase | Phase 3 (Partner Integration) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Partner.Status -- progress tracker (Applied → Under Review → Approved/Declined). Components.OwnerNotes callout for change requests. Editable resubmission form when changes requested. Welcome card on approval with dashboard/API setup CTAs. |
| Backend (Phoenix) | Stacks.Partners.get_status/1 -- returns partner record with status + owner notes. Stacks.Partners.resubmit/2 -- updates partner, resets status to pending. StacksWeb.PartnerStatusController. Token-based access (no login required — link from email). |
| Database | Read: partners (status, owner_notes). Write: partners (on resubmit). audit_log. |
| Jobs (Oban) | None (email sent by PartnerApprovalNotificationJob from US-9.1.1). |
| External Services | Resend / Postmark (confirmation email with status link). |
| dbt Models | int_partner_onboarding_funnel. |
| Infrastructure | Token-based URL for status checking (signed, time-limited). |
| Dependencies | US-9.1.1 (partner registration). |
| Dimension | Detail |
|---|---|
| Summary | Partners update their business details from the dashboard. Non-sensitive changes take effect immediately; name/address changes require owner re-approval. |
| Phase | Phase 3 (Partner Integration) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Partner.Profile -- form with current values, live preview of partner card (as shown on Third Spaces and book detail). "Pending Approval" badge on fields requiring re-approval. |
| Backend (Phoenix) | Stacks.Partners.update_profile/2 -- partitions fields into immediate-effect (description, hours, website, logo) and approval-required (name, address). Approval-required changes create a pending update visible to owner. StacksWeb.PartnerDashboard.ProfileController. |
| Database | Write: partners (immediate fields). partners pending fields stored separately (JSONB pending_changes column or separate table). audit_log. |
| Jobs (Oban) | None. |
| External Services | None. |
| dbt Models | None. |
| Infrastructure | None additional. |
| Dependencies | US-9.1.1 (partner exists), US-9.6.1 (owner approves name/address changes). |
| Dimension | Detail |
|---|---|
| Summary | Readers see which local partners stock a book. "Available at" section on book detail; green dot on spine in shelf views. |
| Phase | Phase 3 (Partner Integration) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Components.PartnerAvailability on Page.BookDetail -- partner cards (logo, name, price, condition badge). Components.Spine -- green dot indicator when partner_availability is non-empty. Sort by proximity (if location set) or alphabetically. Section hidden when no partners stock the book. |
| Backend (Phoenix) | Stacks.Partners.Inventory.available_for/1 -- queries partner_inventory by ISBN, joins partners for display data. Included in Stacks.Books.get_book_detail/1 response. |
| Database | Read: partner_inventory (by ISBN), partners (name, logo, type). |
| Jobs (Oban) | None (data populated by US-9.2.1 ingest). |
| External Services | None. |
| dbt Models | int_partner_availability (reuses model from US-9.2.1). |
| Infrastructure | None additional. |
| Dependencies | US-9.2.1 (partner inventory exists), US-1.3.2 (book detail page). |
| Dimension | Detail |
|---|---|
| Summary | Group pages show a reverse-chronological feed of blog posts and shelf activity from group members. Content respects visibility rules. Feed behaviour varies by group type (close_friends: all members contribute; broadcast/subscription: owner only). |
| Phase | Phase 6 (Social Graph & Visibility) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Group.Feed module — card-based feed. Components.FeedCard.BlogPost (title, author, first two lines, date). Components.FeedCard.ShelfActivity ("[Name] added [Title] to Reading Pile" with spine thumbnail). Strict chronological order, no algorithmic ranking. Pagination at 20 items. |
| Backend (Phoenix) | Stacks.Groups.Feed context — get_feed/2 (group_id, pagination). Queries blog_posts and bookshelf_placement_history for group members, filtered by visibility ceiling + block filtering. For broadcast/subscription groups, filters to owner's content only. StacksWeb.GroupController.feed/2 (GET /api/groups/:id/feed). |
| Database | Read: op.groups, op.group_members, op.blog_posts (where visibility allows), op.bookshelf_placements, op.bookshelf_placement_history, op.books, op.user_blocks. |
| Jobs (Oban) | None (query-time aggregation). |
| External Services | None. |
| dbt Models | int_group_activity. |
| Infrastructure | None additional. |
| Dependencies | US-11.1.1 (groups exist), US-10.1.1 (visibility infrastructure), US-12.1.1 (blog posts). |
| Dimension | Detail |
|---|---|
| Summary | First user becomes owner. Guardian JWT, Argon2 password hashing. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Login module (registration form). Types: User, RemoteData. |
| Backend (Phoenix) | Stacks.Accounts context -- Accounts.register/1. StacksWeb.AuthController.register/2. Guardian JWT token generation. |
| Database | Write: op.users, op.audit_log. |
| Dependencies | None (foundational). |
| Dimension | Detail |
|---|---|
| Summary | Email + password login, JWT token returned. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Login module (login form). |
| Backend (Phoenix) | Stacks.Accounts context -- credential verification with Argon2. StacksWeb.AuthController.login/2. Guardian JWT signing. |
| Database | Read: op.users. Write: op.audit_log. |
| Dependencies | US-14.1.1 (account must exist). |
| Dimension | Detail |
|---|---|
| Summary | 3-step guided flow after first registration: Welcome → Upload first book → Choose shelf. Dismissable. Sets onboarding_completed flag on completion or skip. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Components.OnboardingOverlay — fullscreen overlay with progress dots. OnboardingStep type: `Welcome |
| Backend (Phoenix) | Reuses StacksWeb.UploadController.identify/2 and StacksWeb.BookController.confirm/2 for the inline upload. Stacks.Accounts.complete_onboarding/1 — sets users.onboarding_completed = true. StacksWeb.AuthController.register/2 returns onboarding_completed: false to trigger the flow. |
| Database | Write: op.users (onboarding_completed). Plus all writes from US-1.1.1 if the user uploads a book. |
| Jobs (Oban) | Same as US-1.1.1 if a book is uploaded. |
| External Services | Same as US-1.1.1 if a book is uploaded. |
| dbt Models | int_onboarding_completion_rate. |
| Infrastructure | None additional. |
| Dependencies | US-14.1.1 (registration), US-1.1.1 (upload flow for step 2). |
| Dimension | Detail |
|---|---|
| Summary | Elm SPA stores JWT, shows user-specific navigation when authenticated. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Types.User, Api module (token storage), navigation state management. |
| Backend (Phoenix) | StacksWeb.AuthController.me/2 -- returns current user from JWT. StacksWeb.Plugs.AuthPipeline. |
| Database | Read: op.users. |
| Dependencies | US-14.2.1 (must be logged in). |
| Dimension | Detail |
|---|---|
| Summary | 24h access token, 7d refresh token. Graceful redirect to login on expiry. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Token expiry detection, automatic redirect to login page. |
| Backend (Phoenix) | Guardian token lifecycle. Refresh token handling. |
| Database | Read: op.users. |
| Dependencies | US-14.2.1. |
| Dimension | Detail |
|---|---|
| Summary | Display name in nav is a dropdown with "Settings" and "Sign Out". Sign out clears JWT, resets Elm state, redirects to sign-in page. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Components.UserMenu — dropdown toggled by clicking display name. Two items: "Settings" (navigates to /settings) and "Sign Out" (clears JWT via clearAuth port, resets model, redirects). userMenuOpen : Bool in model. |
| Backend (Phoenix) | None additional (sign out is client-side JWT removal). |
| Database | Write: op.audit_log (logout event). |
| Dependencies | US-14.3.1 (authenticated state), US-17.1.1 (settings page must exist). |
| Dimension | Detail |
|---|---|
| Summary | Authenticated users are immediately redirected to /antilibrary. Unauthenticated users see a static landing page with "Sign In" CTA. The home page is not a dashboard — it's a portal for unauthenticated visitors only. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Route handler checks auth state: if authenticated, redirect to /antilibrary; if not, render static landing with title, subtitle, and "Sign In" button. |
| Backend (Phoenix) | None additional (routing is client-side). |
| Database | None. |
| Dependencies | US-14.3.1 (auth state determines redirect). |
| Dimension | Detail |
|---|---|
| Summary | Persistent top navigation. Authenticated: Library, AntiLibrary, WishList, Reading Pile, Looking for a Home, Search, Add Book, plus display name dropdown (Settings + Sign Out). Unauthenticated: Costs, Sign In. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Navigation.Route module. Main.elm handles URL routing via Browser.application. Components.UserMenu dropdown on display name (US-14.3.3). Nav items conditionally rendered based on Maybe User in model. |
| Backend (Phoenix) | CoreWeb.Router catch-all route serves Elm SPA for all non-API paths. |
| Database | None. |
| Dependencies | US-14.3.1 (auth state determines nav items), US-14.3.3 (user menu dropdown). |
| Dimension | Detail |
|---|---|
| Summary | Touch swipe gestures to navigate between bookshelves on mobile. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Navigation.SwipeNavigation module. Touch event handling via ports or subscriptions. |
| Backend (Phoenix) | None (client-side only). |
| Database | None. |
| Dependencies | US-1.2.5 (shelf navigation). |
| Dimension | Detail |
|---|---|
| Summary | Footer with links to settings, GDPR pages, and project info. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Footer component in Main.elm view. |
| Backend (Phoenix) | None (static content). |
| Database | None. |
| Dependencies | None. |
| Dimension | Detail |
|---|---|
| Summary | Themed 404 page matching the dark-academic aesthetic. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | 404 variant in route handling. Themed error display. |
| Backend (Phoenix) | CoreWeb.ErrorJSON for API 404s. SPA catch-all handles client-side 404s. |
| Database | None. |
| Dependencies | US-15.2.1 (routing). |
| Dimension | Detail |
|---|---|
| Summary | Display helpful error messages when API calls fail. Retry where appropriate. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Types.RemoteData handles Loading/Success/Failure states. Error display components. |
| Backend (Phoenix) | Structured error responses from all controllers. |
| Database | None. |
| Dependencies | None. |
| Dimension | Detail |
|---|---|
| Summary | Redirect to login when accessing protected routes without valid JWT. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Route guard in Main.elm — redirects to login if no token. |
| Backend (Phoenix) | StacksWeb.Plugs.AuthPipeline returns 401 for unauthenticated API requests. |
| Database | None. |
| Dependencies | US-14.2.1, US-15.2.1. |
| Dimension | Detail |
|---|---|
| Summary | Central settings hub at /settings with sidebar navigation linking to all sub-pages: Profile, Password, Privacy & Consent, Age Verification, Data Export, Delete Account, Audit Log, Notifications. Accessible from display name dropdown. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Settings module with sidebar navigation. Sub-pages: Page.Settings.Profile (US-17.2.1), Page.Settings.Password (US-17.2.3), Page.Settings.Consent (US-8.3), Page.Settings.AgeVerification (US-4.2), Page.Settings.Export (US-8.1), Page.Settings.Delete (US-8.2), Page.Settings.AuditLog (US-8.5), Page.Settings.Notifications (US-17.3.1). Sidebar highlights active sub-page. On mobile, sidebar collapses to dropdown. |
| Backend (Phoenix) | StacksWeb.UserSettingsController with routes for each sub-page. |
| Database | Read: op.users. |
| Dependencies | US-14.3.1 (authenticated), US-14.3.3 (accessible from user menu dropdown). |
| Dimension | Detail |
|---|---|
| Summary | User edits display name, email, and website URL. Email changes require password confirmation. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Settings.Profile — form with Display Name, Email, Website URL fields. Inline validation. Save button with confirmation toast. |
| Backend (Phoenix) | Stacks.Accounts.update_profile/2. Email changes require current_password param. StacksWeb.UserSettingsController.update_profile/2 (PUT /api/settings/profile). |
| Database | Write: op.users (display_name, email, website_url), op.audit_log. |
| Dependencies | US-17.1.1 (settings hub). |
| Dimension | Detail |
|---|---|
| Summary | User sets country and city for Third Spaces, partner inventory, and event filtering. Explicit setting, not device geolocation. Saving triggers geographic discovery sweep. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Location section within Page.Settings.Profile — Country dropdown (ZA at top), City text input with autocomplete. Explanation: "We use your location to show nearby bookshops, events, and reading spaces." Post-save note: "We'll start looking for spaces near [City]." |
| Backend (Phoenix) | Stacks.Accounts.update_location/2. Emits user.location_updated event on change, triggering GeographicDiscoveryJob (US-2.5.2). StacksWeb.UserSettingsController.update_profile/2 (same endpoint as US-17.2.1). |
| Database | Write: op.users (country_code, city), op.event_log (user.location_updated). |
| Jobs (Oban) | GeographicDiscoveryJob triggered by event. |
| Dependencies | US-17.1.1 (settings hub), US-2.5.2 (geographic sweep). |
| Dimension | Detail |
|---|---|
| Summary | Current password + new password (twice) form. Argon2 hashing. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Settings.Password — three fields: Current Password, New Password, Confirm. Strength indicator. |
| Backend (Phoenix) | Stacks.Accounts.change_password/2. Verifies current password before applying. StacksWeb.UserSettingsController.change_password/2 (PUT /api/settings/password). Rate limit: 3/min. |
| Database | Write: op.users (password_hash), op.audit_log. |
| Dependencies | US-17.1.1 (settings hub). |
| Dimension | Detail |
|---|---|
| Summary | Toggle email notifications per category. Quiet by default. ToS changes always sent (non-toggleable). |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Settings.Notifications — list of notification categories with toggles. ToS toggle is locked (always on). Auto-save on toggle change. Philosophy note in italic serif. |
| Backend (Phoenix) | Stacks.Accounts.update_notification_preferences/2. StacksWeb.UserSettingsController.update_notifications/2 (PUT /api/settings/notifications). |
| Database | Write: op.users (notify_wishlist_availability, notify_marketplace, notify_group_invitations, notify_event_matches). |
| Jobs (Oban) | Stacks.Workers.EmailNotificationJob — checks user preferences before sending any email. Queue: notifications, concurrency: 3. Stacks.Workers.WishListAvailabilityJob — triggered by partner inventory ingestion or marketplace listing creation. Checks WishList ISBNs against new availability. |
| External Services | Resend / Postmark (transactional email). |
| dbt Models | int_notification_delivery_rate. |
| Dependencies | US-17.1.1 (settings hub). |
| Dimension | Detail |
|---|---|
| Summary | Fifth bookshelf with community-driven wear state. Spine wear reflects how many users across the platform have read each book (aggregate Library shelf placement count), not the individual user's engagement. Books can arrive from any shelf or directly from upload. Marketplace-ready in future phases. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Page.Bookshelf.LookingForHome module. Uses CommunityWear type (not SpineWear) for spine rendering. Warm, transitional aesthetic (window ledge / market stall). |
| Backend (Phoenix) | Stacks.Shelving context, bookshelf_name :looking_for_home. Stacks.Books.community_read_count/1 — reads from wh.mart_community_read_count to determine community wear level. StacksWeb.BookshelfController.show/2. |
| Database | Read: op.bookshelves, op.bookshelf_placements, op.books, op.book_editions. Read: wh.mart_community_read_count (aggregate read stats). |
| Jobs (Oban) | None directly. mart_community_read_count is refreshed by dbt on schedule. |
| dbt Models | mart_community_read_count — SELECT book_id, COUNT(DISTINCT user_id) AS read_count FROM bookshelf_placements bp JOIN bookshelves b ON bp.bookshelf_id = b.id WHERE b.name = 'library' AND bp.removed_at IS NULL GROUP BY book_id. |
| Infrastructure | None additional. |
| Dependencies | US-1.1.1 (books exist), US-1.2.1 (shelf infrastructure). |
| Dimension | Detail |
|---|---|
| Summary | Every spine, shelf, overlay, and interactive element gets meaningful ARIA labels. Upload states announced via aria-live regions. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Attributes added to existing components: Components.Spine gets aria-label ("Book: [Title] by [Author], [Pages] pages, [wear state]"). Components.Shelf gets role="list", aria-label ("[Shelf Name] — N books"). Book detail overlay gets role="dialog" with focus trapping. Upload states use aria-live="polite" for progress announcements. Components.UserMenu dropdown labelled "User menu". |
| Backend (Phoenix) | API responses must include all data needed for labels: shelf name, book count per shelf, wear state as text, page count. No new endpoints — data already present in existing responses. |
| Database | None additional. |
| Dependencies | US-1.2.1–4 (shelves), US-1.3.1 (spines), US-1.3.2 (detail overlay). |
| Dimension | Detail |
|---|---|
| Summary | Full keyboard navigation: Tab between nav items → shelf content → spines. Arrow keys within shelf grid. Enter opens detail overlay. Escape closes. Skip links for main content. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | Navigation.Keyboard module — keyboard event subscriptions. tabindex attributes on all interactive spine elements. Arrow key handlers for shelf grid navigation. Focus management: return focus to triggering spine when overlay closes. Skip link: hidden "Skip to main content" link before navigation. Visible focus indicators styled with platform aesthetic. |
| Backend (Phoenix) | None (purely client-side). |
| Database | None. |
| Dependencies | US-1.2.1–4 (shelves), US-1.3.2 (detail overlay), US-19.1.1 (ARIA labels). |
| Dimension | Detail |
|---|---|
| Summary | Toggle between spine view (visual bookshelf) and list view (sortable table of books with metadata). Preference persisted in localStorage. Better for accessibility, small screens, and information-dense browsing. |
| Phase | Phase 1 (MVP) |
| Layer | Components |
|---|---|
| Frontend (Elm) | ShelfViewMode type: `SpineView |
| Backend (Phoenix) | None additional (same API data, different rendering). |
| Database | None (preference in localStorage). |
| Dependencies | US-1.2.1–4 (shelves), US-1.3.2 (detail overlay). |
| Layer | Components |
|---|---|
| Summary | The data pipeline follows a contract-first derived data pattern (ADR 010, drawing from DDIA Ch. 4, 11, 12). Protobuf contracts enforce shape at the write boundary; the event log provides ordered history; all downstream views are purpose-built derivations that can be rebuilt from the systems of record (op.*, audit.*). This is neither star schema nor medallion — it is "one log, many derivations" with contract-enforced input. |
| Systems of record | op.* tables (OLTP, Ecto writes), op.event_log (append-only domain events), audit.audit_log (immutable, encrypted). |
| Derived data | wh.stg_* (structural projections, PII-excluded), wh.int_* (semantic aggregates — domain joins), wh.mart_* (consumer-optimised read models), ETS caches (BookDetailCache), search indexes (mart_platform_searchable), Atom feeds. |
| Key invariant | Every layer after op.* is derived and rebuildable. If the wh schema is dropped, dbt run reconstructs it. |
| Materialisation | Staging: VIEW. Intermediate: VIEW or incremental. Marts: incremental for hot-path (5-min), VIEW/table for cold-path (daily). Hot-path marts may use MATERIALIZED VIEW with REFRESH CONCURRENTLY. |
| Refresh trigger | Event-triggered selective dbt runs (event → specific model set) with daily cron as catch-all. Mapping in Stacks.Workers.DbtRefreshJob. |
| Design rules | (1) No mart without a named consumer. (2) No SELECT * in staging — explicit column lists. (3) Tier 3/4 data never enters wh. (4) Incremental models for unboundedly growing sources. |
| References | ADR 010, ADR 009 (proto codegen), ADR 007 (proto contracts), docs/data-quality.md. |
| Layer | Components |
|---|---|
| Summary | Oban-backed event bus. All significant state changes emit events to event_log table. Subscribers are registered at startup and notified via Oban jobs. The event log is both a system of record (immutable history) and the integration point that connects subsystems — enrichment workers, feed regeneration, dbt refresh, and caches are all event-driven (ADR 010). |
| Affects stories | All stories benefit. Primary consumers: US-9.2.1 (inventory events), US-9.3.1 (event events), US-9.5.1 (metrics from events), US-1.5.1 (shelf movement events), US-2.1.1–2.4.1 (enrichment fan-out). |
| Backend (Phoenix) | Stacks.Events -- emit/1, replay/3. Stacks.Events.Registry -- subscriber mapping. Stacks.Events.Upcaster -- schema version migration. Stacks.Events.SubscriberWorker -- Oban worker that dispatches to subscriber modules. |
| Database | event_log table (event_type, aggregate_type, aggregate_id, schema_version, payload JSONB, metadata JSONB, occurred_at, published_at). Index on (event_type, aggregate_id, occurred_at DESC). |
| dbt Models | stg_event_log, int_event_throughput, mart_event_bus_health. |
| dbt Integration | Events trigger selective dbt runs via DbtRefreshJob (e.g., shelf.book_placed → mart_community_read_count). See ADR 010 for the full event-to-model mapping. |
| Infrastructure | Oban queue :events with concurrency tuned to subscriber count. |
| Layer | Components |
|---|---|
| Summary | .proto files as single source of truth for all structured data contracts. buf for linting and breaking change detection in CI. JSON on the wire. |
| Affects stories | All partner stories (US-9.x), internal event bus, service-to-service contracts. |
| Proto files | proto/stacks/partner/ (inventory, events, spaces), proto/stacks/internal/ (event_bus, enrichment), proto/stacks/common/ (book, location). |
| Code generation | Elixir, Rust, Python: generated at build time, gitignored. Elm: generated JSON decoders gitignored and regenerated at build time via scripts/gen-elm-proto.sh. |
| Schema codegen (Issue #080) | mix proto.sync generates Ecto migrations, Ecto schemas, and dbt staging models from .proto messages tagged with (stacks.persisted) = true. mix proto.sync --check in CI catches drift. Covers raw ingestion tables only — domain tables remain hand-written. This is the "contract-enforced input" pillar of ADR 010 — the proto defines the shape, and the staging model is mechanically derived from that contract. See ADR 009. |
| CI | buf lint proto/ + buf breaking proto/ --against '.git#branch=main' + mix proto.sync --check in every PR. |
| Event upcasting | Stacks.Events.Upcaster -- pattern-matched version transforms for old events. Same pattern as Commanded (Elixir CQRS). |
| Layer | Components |
|---|---|
| Summary | Continuous quality monitoring per data product. Source health tracking, LLM faithfulness metrics, quality trend analysis. See docs/data-quality.md for the full framework. |
| Affects stories | US-5.1 (metrics dashboard — data quality section), US-2.1.1–2.4.1 (enrichment — source health), US-12.1.2 (blog — LLM faithfulness) |
| Backend (Phoenix) | Stacks.Enrichment.SourceHealth — record_success/2, record_failure/3. Instrumented in all enrichment workers. Stacks.Workers.RSSLivenessJob — weekly feed health check. |
| Database | op.source_health_checks — per-source operational health with consecutive failure tracking and computed status (healthy/degraded/broken). |
| dbt Models | stg_source_health_checks, int_source_health (per-source status), mart_data_quality_trend (12-week rollup), mart_enrichment_gaps (missing data by cause), mart_llm_faithfulness (LLM output quality). |
| Rust Scraper | Returns selector_match_rate in scrape response — percentage of CSS selectors that found matches. Low rate indicates HTML structure change. |
| Elm | Metrics dashboard: quality trend sparklines, source health table, enrichment gap cards, LLM faithfulness indicators. |
Which user stories touch each database table:
| Table | Stories |
|---|---|
books (works) |
US-1.1.1, US-1.1.2, US-1.1.4, US-1.1.5, US-1.1.6, US-1.1.8, US-1.2.1--4, US-1.3.1, US-1.3.2, US-1.4.1, US-1.5.1--3, US-1.5.3, US-2.1.1, US-2.3.1, US-2.4.1, US-4.1.1, US-7.1.1, US-7.2.1, US-8.1.1, US-11.1.5, US-18.1.1, US-19.2.1 |
book_editions |
US-1.1.1, US-1.1.5, US-1.1.6, US-1.1.8, US-1.3.2, US-1.5.4, US-2.2.1, US-9.2.1, US-9.8.1, US-14.1.2, US-19.1.1, US-19.2.1 |
authors |
US-1.1.1, US-1.2.1--4, US-1.3.2, US-1.4.1, US-2.3.1, US-2.4.1, US-6.1.1, US-8.1.1 |
bookshelves |
US-1.2.1--5, US-1.4.1, US-1.5.1, US-6.1.1, US-8.1.1 |
bookshelf_placements |
US-1.1.5, US-1.2.1--4, US-1.3.1, US-1.3.2, US-1.4.1, US-1.5.1--3, US-1.6.4, US-1.6.5, US-2.4.1, US-6.1.1, US-8.1.1 |
bookshelf_placement_history |
US-1.3.1, US-1.5.1--3, US-1.6.4, US-8.1.1 |
review_snapshots |
US-1.3.2, US-2.1.1 |
price_snapshots |
US-1.3.2, US-1.4.1, US-1.5.3, US-2.2.1 (references book_editions, not books) |
bookstores |
US-2.2.1, US-2.2.2, US-2.4.1 |
bookstore_events |
US-2.4.1 |
third_spaces |
US-3.1.1, US-2.5.2, US-2.5.3 |
third_space_events |
US-3.1.1, US-2.5.2 |
blog_posts |
US-12.1.1, US-12.1.2, US-12.1.3, US-10.2.3, US-8.1.1 |
post_book_associations |
US-12.1.2 |
comments |
US-13.1.1, US-13.1.2, US-13.2.1 |
user_blocks |
US-10.1.2, US-13.1.2 |
groups |
US-11.1.1, US-11.1.2, US-11.1.3, US-11.1.4, US-11.1.5, US-10.2.1 |
group_members |
US-11.1.2, US-11.1.3, US-11.1.4, US-11.1.5 |
group_invitations |
US-11.1.2, US-11.1.4 |
visibility_grants |
US-10.2.2 |
uploaded_images |
US-1.1.1, US-7.1.1, US-8.1.4 |
audit_log |
US-1.1.3, US-1.1.4, US-2.5.1, US-4.1.1, US-4.1.2, US-5.1.1, US-7.2.1, US-7.3.1, US-8.1.1, US-8.1.2, US-8.1.3, US-8.1.5 |
discovered_sources |
US-2.3.1, US-2.5.1, US-2.5.2, US-2.5.3 |
partners |
US-9.1.1, US-9.1.2, US-9.6.1, US-9.7.1, US-9.7.2, US-9.8.1 |
partner_inventory |
US-9.2.1, US-9.2.2, US-9.5.1, US-9.8.1 (references book_editions, not books) |
partner_events |
US-9.3.1, US-9.3.2, US-9.5.1, US-9.6.1 |
partner_spaces |
US-9.4.1, US-9.5.1, US-9.6.1 |
source_health_checks |
US-2.1.1, US-2.2.1, US-2.3.1, US-2.4.1, US-5.1 (quality dashboard) |
event_log |
US-9.2.1, US-9.3.1, US-9.4.1, US-9.5.1 (+ all stories via EDA) |
listings |
US-7.1.1, US-7.2.1 |
offers |
US-7.2.1 |
transactions |
US-7.2.1 |
| Job Worker | Triggered By | Schedule |
|---|---|---|
IdentifyBookJob |
US-1.1.1 (photo upload) | On-demand |
EnrichBookJob |
US-1.1.1 (after ISBN resolved) | On-demand |
RecalculateWearJob |
US-1.5.1, US-1.5.2, US-1.5.3 (shelf moves) | Event-driven |
FetchReviewsJob |
US-2.1.1 | Adaptive staleness (hours to days) |
TriggerPriceScrapeJob |
US-2.2.1 | Scheduled (daily) |
DiscoverAuthorSourcesJob |
US-2.3.1 | Scheduled (weekly) |
FetchAuthorRSSJob |
US-2.3.1 | Scheduled (hourly) |
DiscoverBookstoreEventsJob |
US-2.4.1 | Scheduled (daily) |
SourceDiscoveryJob |
US-2.5.1 | Scheduled (daily) |
ScoreSourceJob |
US-2.5.1 | On-demand (after discovery) |
DiscoverThirdSpacesJob |
US-3.1.1 | Scheduled (weekly) |
ModerationPipelineJob |
US-4.1.1 | On-demand (part of upload) |
KYCCallbackJob |
US-4.1.2 | Webhook-driven (US-7.3.1 deferred per ADR 013) |
MetricsSnapshotJob |
US-5.1.1 | Scheduled (every 5 min) |
RegenerateFeedJob |
US-6.1.1 | Event-driven (shelf change) |
ListingExpiryJob |
US-7.1.1 | Scheduled (daily) |
PaymentCallbackJob |
US-7.2.1 | Webhook-driven — DEFERRED (ADR 013) |
ShipmentTrackingJob |
US-7.2.1 | Scheduled (hourly) — DEFERRED (ADR 013) |
DataExportJob |
US-8.1.1 | On-demand |
AccountDeletionJob |
US-8.1.2 | On-demand |
ConfirmDeletionJob |
US-8.1.2 | On-demand |
ImageRetentionJob |
US-8.1.4 | Scheduled (daily) |
PartnerApprovalNotificationJob |
US-9.1.1 | Event-driven (partner.approved/declined) |
PartnerISBNResolveJob |
US-9.2.1, US-9.2.2 | Event-driven (unknown ISBN in partner inventory) |
ArchivePartnerEventsJob |
US-9.3.1 | Scheduled (daily) |
PartnerMetricsSnapshotJob |
US-9.5.1 | Scheduled (daily) |
GeographicDiscoveryJob |
US-2.5.2 | Event-driven (user.location_updated) + quarterly Oban.Cron |
OptOutConfirmationJob |
US-2.5.3 | On-demand (opt-out request) |
MarketplaceSaleWorker |
US-7.2 | Event-driven (book.sold — checks buyer WishList, prompts) — DEFERRED (ADR 013, relevant when payments are on-platform) |
WishListAvailabilityJob |
US-17.3.1 | Event-driven (partner inventory ingested, classifieds listing activated) |
EmailNotificationJob |
US-17.3.1 | On-demand (checks preferences before sending) |
EventSubscriberWorker |
EDA (cross-cutting) | Event-driven (dispatches to subscriber modules) |
| API / Service | Stories | Purpose |
|---|---|---|
| Modal | US-1.1.1, US-1.1.3, US-4.1.1 | Vision model (book identification, classification via Qwen2.5-VL-7B) |
| Open Library | US-1.1.1, US-1.1.2, US-4.1.1 | ISBN resolution, book metadata |
| Google Books | US-1.1.1, US-1.1.2, US-4.1.1 | ISBN resolution fallback, metadata |
| Brave Search | US-2.3.1, US-2.4.1, US-2.5.1, US-2.5.2, US-3.1.1 | Source discovery, author search, event search, geographic sweep |
| SearXNG (self-hosted) | US-2.4.1, US-2.5.1, US-2.5.2, US-3.1.1 | Meta-search for discovery and geographic sweep |
| GoodReads | US-2.1.1 | Review aggregation |
| Reddit API | US-2.1.1 | Review aggregation |
| Storygraph | US-2.1.1 | Review aggregation |
| Smile Identity / Yoti / Sumsub | US-4.1.2 | KYC / age verification (US-7.3.1 deferred per ADR 013) |
| Stitch Money | US-7.2.1 | Payment processing — DEFERRED (ADR 013) |
| Pargo | US-7.2.1 | Shipping / logistics — DEFERRED (ADR 013) |
| Resend / Postmark | US-2.5.3, US-8.1.2, US-9.1.1, US-9.7.1, US-17.3.1 | Transactional email (business opt-out confirmation, GDPR confirmation, partner notifications, status updates, WishList availability, event match, group invitation emails) |
| Model | Type | Fed By Stories | Consumed By Stories |
|---|---|---|---|
stg_books |
Staging | US-1.1.1 | Many |
stg_book_editions |
Staging | US-1.1.1, US-1.1.8 | US-1.3.2, US-1.5.4, US-2.2.1 |
stg_uploaded_images |
Staging | US-1.1.1 | US-8.1.4 |
stg_bookshelf_placements |
Staging | US-1.5.1--3 | US-1.2.1--4 |
stg_bookshelf_placement_history |
Staging | US-1.5.1--3 | US-1.3.1 |
stg_review_snapshots |
Staging | US-2.1.1 | US-1.3.2 |
stg_price_snapshots |
Staging | US-2.2.1 | US-1.3.2 |
stg_authors |
Staging | US-1.1.1 | US-2.3.1 |
stg_bookstore_events |
Staging | US-2.4.1 | US-2.4.1 |
stg_discovered_sources |
Staging | US-2.5.1 | US-2.5.1 |
stg_third_spaces |
Staging | US-3.1.1 | US-3.1.1 |
stg_audit_log |
Staging | US-8.1.5 | US-5.1.1 |
int_book_engagement |
Intermediate | US-1.3.1, US-1.5.1--3 | US-1.3.1 |
int_book_detail_view |
Intermediate | Multiple | US-1.3.2 |
int_upload_rejection_rate |
Intermediate | US-1.1.2, US-1.1.3 | US-5.1.1 |
int_rejection_categories |
Intermediate | US-1.1.3 | US-5.1.1 |
int_content_classification |
Intermediate | US-1.1.4 | US-5.1.1 |
int_shelf_composition |
Intermediate | US-1.2.1--4 | US-5.1.1 |
int_book_journey |
Intermediate | US-1.5.1 | US-5.1.1 |
int_abandonment_rate |
Intermediate | US-1.5.2 | US-5.1.1 |
int_format_distribution |
Intermediate | US-1.5.4 | US-5.1.1 |
int_review_sentiment |
Intermediate | US-2.1.1 | US-2.1.1 |
int_price_trends |
Intermediate | US-2.2.1 | US-2.2.1 |
int_author_activity |
Intermediate | US-2.3.1 | US-2.3.1 |
int_event_matches |
Intermediate | US-2.4.1 | US-2.4.1 |
int_source_approval_rate |
Intermediate | US-2.5.1 | US-5.1.1 |
int_moderation_outcomes |
Intermediate | US-4.1.1 | US-5.1.1 |
int_seller_verification_funnel |
Intermediate | US-7.3.1 | US-5.1.1 |
mart_book_reviews |
Mart | US-2.1.1 | US-1.3.2 |
mart_book_prices |
Mart | US-2.2.1 | US-1.3.2 |
mart_system_health |
Mart | Multiple | US-5.1.1 |
mart_job_stats |
Mart | Multiple | US-5.1.1 |
mart_data_freshness |
Mart | Multiple | US-5.1.1 |
mart_cost_tracking |
Mart | Multiple | US-5.1.1 |
mart_gdpr_compliance |
Mart | US-8.1.1--5 | US-5.1.1 |
mart_gdpr_deletions |
Mart | US-8.1.2 | US-5.1.1 |
mart_consent_status |
Mart | US-8.1.3 | US-5.1.1 |
mart_image_retention_stats |
Mart | US-8.1.4 | US-5.1.1 |
mart_audit_summary |
Mart | US-8.1.5 | US-5.1.1 |
mart_marketplace_activity |
Mart | US-7.1.1 | US-5.1.1 |
mart_transaction_volume |
Mart | US-7.2.1 | US-5.1.1 |
mart_marketplace_revenue |
Mart | US-7.2.1 | US-5.1.1 |
stg_partners |
Staging | US-9.1.1 | US-9.5.1, US-9.6.1 |
stg_partner_inventory |
Staging | US-9.2.1, US-9.2.2 | US-9.5.1 |
stg_partner_events |
Staging | US-9.3.1, US-9.3.2 | US-9.5.1 |
stg_partner_spaces |
Staging | US-9.4.1 | US-9.5.1 |
stg_event_log |
Staging | EDA (cross-cutting) | US-9.5.1, US-5.1.1 |
int_partner_availability |
Intermediate | US-9.2.1 | US-1.3.2 (book detail sidebar) |
int_partner_approval_rate |
Intermediate | US-9.1.1 | US-5.1.1 |
int_partner_event_calendar |
Intermediate | US-9.3.1 | US-3.1.1 (cork board) |
int_partner_impressions |
Intermediate | US-9.5.1 | US-9.5.1 |
int_partner_clicks |
Intermediate | US-9.5.1 | US-9.5.1 |
int_partner_validation_errors |
Intermediate | US-9.6.2 | US-5.1.1 |
int_partner_moderation |
Intermediate | US-9.6.1 | US-5.1.1 |
int_event_throughput |
Intermediate | EDA (cross-cutting) | US-5.1.1 |
mart_partner_engagement |
Mart | US-9.5.1 | US-9.5.1 |
mart_partner_stock_coverage |
Mart | US-9.2.1 | US-5.1.1 |
mart_event_bus_health |
Mart | EDA (cross-cutting) | US-5.1.1 |
stg_users |
Staging | US-10.1.1, US-11.1.1 | US-10.x, US-11.x |
stg_user_blocks |
Staging | US-11.2.1 | US-10.x, US-11.x |
stg_groups |
Staging | US-11.1.1 | US-11.1.x |
stg_group_members |
Staging | US-11.1.2 | US-11.1.x |
stg_bookshelves |
Staging | US-10.1.1 | US-10.x |
stg_blog_posts |
Staging | US-12.1.1 | US-12.x, US-13.x |
stg_comments |
Staging | US-13.1.1 | US-13.x |
stg_offer_threads |
Staging | US-7.1.1, US-13.2.2 | US-7.x |
stg_post_book_associations |
Staging | US-12.2.1 | US-12.2.1 |
int_visibility_resolution |
Intermediate | US-10.1.1--5 | US-10.x, US-12.x, US-13.x |
int_comment_threads |
Intermediate | US-13.1.1 | US-13.1.1 |
int_offer_activity |
Intermediate | US-7.1.1, US-13.2.2 | US-5.1.1 |
int_blog_engagement |
Intermediate | US-12.1.1 | US-5.1.1 |
int_group_activity |
Intermediate | US-11.1.1 | US-5.1.1 |
mart_social_graph_health |
Mart | US-11.x | US-5.1.1 |
mart_blog_activity |
Mart | US-12.x | US-5.1.1 |
mart_marketplace_offers |
Mart | US-7.1.1, US-13.2.2 | US-5.1.1 |
mart_community_read_count |
Mart | US-1.2.1 (Library placements) | US-18.1.1 (Looking for a Home wear) |
int_onboarding_completion_rate |
Intermediate | US-14.1.2 | US-5.1.1 |
int_notification_delivery_rate |
Intermediate | US-17.3.1 | US-5.1.1 |
int_opt_out_rate |
Intermediate | US-2.5.3 | US-5.1.1 |
stg_source_health_checks |
Staging | US-2.1.1–2.4.1 (enrichment workers) | US-5.1.1 |
int_source_health |
Intermediate | US-2.1.1–2.4.1 | US-5.1.1 (source health table) |
mart_data_quality_trend |
Mart | US-2.1.1–2.4.1 | US-5.1.1 (quality trend sparklines) |
mart_enrichment_gaps |
Mart | US-2.1.1–2.4.1 | US-5.1.1 (gap drill-down) |
mart_llm_faithfulness |
Mart | US-12.1.2, US-2.1.1 | US-5.1.1 (LLM quality metrics) |