feat(book-detail): rebuild the highlights tab as a single-screen ledger

Replace the paginated card list with a three-column ledger: a marks rail, a
grouped stream that loads on scroll, an index that regroups by chapter, colour
or day, and a chapter position band. Add chapterBreakdown and activity to the
annotation stats so those panels describe the whole book without loading every
row, and move the metadata score colour onto a shared oklch ramp.
This commit is contained in:
neon
2026-08-26 17:04:48 +00:00
parent 4c42e0c73f
commit 75ccb8dd1a
32 changed files with 2874 additions and 308 deletions
+22
View File
@@ -81,6 +81,25 @@ export interface AnnotationItem {
createdAt: string;
}
/**
* One chapter's worth of highlights, aggregated server-side. The book detail
* Highlights tab draws its chapter index and its position band from this, so it
* never has to hold every annotation row in memory to describe the whole book.
*/
export interface AnnotationChapterStat {
/** null for highlights that carry no chapter title. */
title: string | null;
count: number;
/** Composition of the chapter, ordered by count descending. */
colors: { color: string; count: number }[];
/** Spine index when a position carries one, otherwise null. */
chapterIndex: number | null;
/** Sort key through the book. Null when no position could be resolved. */
order: number | null;
/** Oldest highlight in the chapter, the tiebreak when `order` is null. */
firstCreatedAt: string;
}
export interface AnnotationStats {
totalHighlights: number;
colorBreakdown: { color: string; count: number }[];
@@ -88,6 +107,9 @@ export interface AnnotationStats {
chaptersWithHighlights: number;
highlightsWithNotes: number;
chapters: string[];
chapterBreakdown: AnnotationChapterStat[];
/** Distinct days that carry at least one highlight, newest first. */
activity: { day: string; count: number; origins: { origin: AnnotationItem["origin"]; count: number }[] }[];
}
export interface AnnotationListResponse {