[Reading Journey][UX] Covers-only uniform shelf + responsive AVIF cover variants + book quick-view + blog-about-this-book (parent-gated) #719

Closed
opened 2026-07-19 03:40:56 +00:00 by spikerj · 2 comments
Owner

Approved UX pass on /reading-journey (plan reviewed with @spikerj 2026-07-19):

Shelf: drop title/author text from grid cards (the cover IS the title — kept in alt/aria/tooltip); uniform 2:3 cards; progress bar + action icons pinned in a consistent bottom bar; placeholder art for coverless books.

Cover standardization (backend): metadata-extractor generates letterboxed (black bars, baked) 2:3 AVIF variants at 240/480/960 widths alongside the untouched original; BookDto.CoverVariants (presigned) feeds <img srcset> so devices fetch the right size; CSS letterbox fallback keeps pre-backfill covers uniform; RabbitMQ-driven backfill consumer + staff POST api/book/admin/backfill-cover-variants.

Quick-view: card click opens an overlay with the ORIGINAL extracted cover + title/authors + Read/Quiz/Blog actions (reader nav unchanged underneath).

Blog-about-this-book: new card/overlay action → /blog/create pre-filled via router state (extends the linkedFact pattern); new BlogPost.LinkedBooks (server hydrates title/cover from the book id; validates the caller can READ the book — child accounts limited to parent-approved ApprovedBookIds); GET api/blog/posts/by-book/{id}. Child gating: button disabled-with-tooltip when FeaturePermissions.BlogPosting is off (server remains the hard gate via the existing DeterminePostStatus flow); children-tab summary gains the missing blogPosting row (parent-dashboard toggles already exist). Also fixes the existing multipart gap where tags/linkedFacts are dropped on /posts/upload.

Lands as two PRs: spikersoft-backend (contract first), spikersoft-angular.

Approved UX pass on /reading-journey (plan reviewed with @spikerj 2026-07-19): **Shelf**: drop title/author text from grid cards (the cover IS the title — kept in alt/aria/tooltip); uniform 2:3 cards; progress bar + action icons pinned in a consistent bottom bar; placeholder art for coverless books. **Cover standardization (backend)**: metadata-extractor generates letterboxed (black bars, baked) 2:3 AVIF variants at 240/480/960 widths alongside the untouched original; `BookDto.CoverVariants` (presigned) feeds `<img srcset>` so devices fetch the right size; CSS letterbox fallback keeps pre-backfill covers uniform; RabbitMQ-driven backfill consumer + staff `POST api/book/admin/backfill-cover-variants`. **Quick-view**: card click opens an overlay with the ORIGINAL extracted cover + title/authors + Read/Quiz/Blog actions (reader nav unchanged underneath). **Blog-about-this-book**: new card/overlay action → `/blog/create` pre-filled via router state (extends the linkedFact pattern); new `BlogPost.LinkedBooks` (server hydrates title/cover from the book id; validates the caller can READ the book — child accounts limited to parent-approved `ApprovedBookIds`); `GET api/blog/posts/by-book/{id}`. Child gating: button disabled-with-tooltip when `FeaturePermissions.BlogPosting` is off (server remains the hard gate via the existing DeterminePostStatus flow); children-tab summary gains the missing blogPosting row (parent-dashboard toggles already exist). Also fixes the existing multipart gap where tags/linkedFacts are dropped on /posts/upload. Lands as two PRs: spikersoft-backend (contract first), spikersoft-angular.
Author
Owner

Both halves are up: backend spikersoft-backend#427 (2:3 letterboxed AVIF variant ladder 240/480/960 + presigned CoverVariants on BookDto + RabbitMQ backfill consumer + staff backfill endpoint + BlogPost.LinkedBooks with server-side hydration/read-auth + by-book endpoint + multipart tags/linkedFacts gap fix) and frontend spikersoft-angular#424 (covers-only uniform shelf with srcset, quick-view overlay showing the original cover with Read/Quiz/Blog, parent-gated blog button with disabledInteractive tooltip, BlogCreate linked-book banner + blocked banner, children-tab blogPosting row). Frontend degrades gracefully pre-backend-deploy. After backend deploys, run POST api/book/admin/backfill-cover-variants once. Frontend PR also fixes master's broken spikersoft:test (photography epic aliases missing from tsconfig.spec.json) — the 2 remaining profile.component.spec failures pre-date both PRs.

Both halves are up: backend spikersoft-backend#427 (2:3 letterboxed AVIF variant ladder 240/480/960 + presigned CoverVariants on BookDto + RabbitMQ backfill consumer + staff backfill endpoint + BlogPost.LinkedBooks with server-side hydration/read-auth + by-book endpoint + multipart tags/linkedFacts gap fix) and frontend spikersoft-angular#424 (covers-only uniform shelf with srcset, quick-view overlay showing the original cover with Read/Quiz/Blog, parent-gated blog button with disabledInteractive tooltip, BlogCreate linked-book banner + blocked banner, children-tab blogPosting row). Frontend degrades gracefully pre-backend-deploy. After backend deploys, run POST api/book/admin/backfill-cover-variants once. Frontend PR also fixes master's broken spikersoft:test (photography epic aliases missing from tsconfig.spec.json) — the 2 remaining profile.component.spec failures pre-date both PRs.
Author
Owner

Verified implemented — landed as the two planned PRs, both merged to master:

  • spikersoft-backend PR #427 (bf5c009a, merged cb6e1d05): 2:3 letterboxed AVIF cover-variant ladder (240/480/960) in the metadata-extractor (CoverVariantGenerator), RabbitMQ backfill consumer (CoverVariantsConsumer + CoverVariantsMessaging), staff POST api/book/admin/backfill-cover-variants, Book/BookDto cover variants; BlogPost.LinkedBooks with server-side title/cover hydration and read-authorization (child accounts limited to parental ApprovedBookIds via BookAccess/BookReadContexts); GET api/blog/posts/by-book/{bookId}; multipart /posts/upload now honors tags/linkedFacts/linkedBooks (previously dropped).
  • spikersoft-angular PR #424 (0e9579e0, merged 2972ce0e): covers-only uniform 2:3 shelf with srcset cover variants and bottom action bar, book-quick-view-dialog overlay, blog-about-this-book action pre-filling /blog/create via router state (linkedBook, extending the linkedFact pattern), child gating (blogBlocked banner + disabled action when FeaturePermissions.BlogPosting is off), children-tab blogPosting summary row.

Follow-on hardening also merged since: #734 (Keycloak sub claim resolution — child read-context was never built on real tokens) and #736 (profile mapper dropped the permission fields the gating reads). Closing.

Verified implemented — landed as the two planned PRs, both merged to master: - **spikersoft-backend PR #427** (`bf5c009a`, merged `cb6e1d05`): 2:3 letterboxed AVIF cover-variant ladder (240/480/960) in the metadata-extractor (`CoverVariantGenerator`), RabbitMQ backfill consumer (`CoverVariantsConsumer` + `CoverVariantsMessaging`), staff `POST api/book/admin/backfill-cover-variants`, `Book`/`BookDto` cover variants; `BlogPost.LinkedBooks` with server-side title/cover hydration and read-authorization (child accounts limited to parental `ApprovedBookIds` via `BookAccess`/`BookReadContexts`); `GET api/blog/posts/by-book/{bookId}`; multipart `/posts/upload` now honors tags/linkedFacts/linkedBooks (previously dropped). - **spikersoft-angular PR #424** (`0e9579e0`, merged `2972ce0e`): covers-only uniform 2:3 shelf with `srcset` cover variants and bottom action bar, `book-quick-view-dialog` overlay, blog-about-this-book action pre-filling `/blog/create` via router state (`linkedBook`, extending the linkedFact pattern), child gating (`blogBlocked` banner + disabled action when `FeaturePermissions.BlogPosting` is off), children-tab `blogPosting` summary row. Follow-on hardening also merged since: #734 (Keycloak sub claim resolution — child read-context was never built on real tokens) and #736 (profile mapper dropped the permission fields the gating reads). Closing.
Sign in to join this conversation.