Chapter dates: read date setting and tiered release dates (BL-0047)

- Chapter rows show when you first finished a chapter (setting: date + hour,
  date, off) from a new durable progress doneAt; older reads fall back to the
  reading clock's earliest remembered completion.
- chapter.json keeps added (arrival in this library, now kept on replace) plus
  x-dates.source (source upload, bulk imports flagged), x-dates.release
  (official, for a later metadata job) and x-dates.ecosystem (first entry into
  any Manga Circus library; carried over peering, earliest wins, clamped).
- Rows show one release date by tier: official > source upload > ecosystem >
  library, with all known dates in the tooltip (setting to hide).
- Fixes: WeebCentral dates came from the chapter page, which has none (every
  chapter got the download time); they now come from the full chapter list.
  MangaDex no longer writes a group's publishAt into added. Peering sends dates.
- tools/backfill-chapter-dates.js migrates an existing library (dry run default).
- Coverage dry run on the server: docs/chapter-dates-coverage-2026-10-24.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MaashkinoozheandClaude Opus 5.5 committed 2026-10-24 01:01:33 +02:00
1 parent b2bb34174c
commit e0dacd81c6
21 files changed
+470 -32

No files matched your search

+9
View File
@@ -180,6 +180,11 @@ main { max-width: 1200px; margin: 0 auto; padding: 16px; }
.ch { display: flex; width: 100%; max-width: 100%; align-items: center; gap: 6px; border-bottom: 1px solid var(--line); white-space: nowrap; }
.ch-main { flex: 1 1 auto; min-width: 0; display: grid; grid-template-columns: 76px 52px minmax(70px, 1fr) max-content; gap: 8px; align-items: baseline; padding: 6px 4px; text-decoration: none; }
.ch-main.has-time { grid-template-columns: 76px 52px 72px minmax(70px, 1fr) max-content; }
.ch-main.has-date { grid-template-columns: 76px 52px 104px minmax(70px, 1fr) max-content; }
.ch-main.has-time.has-date { grid-template-columns: 76px 52px 104px 72px minmax(70px, 1fr) max-content; }
.ch-date { min-width: 0; overflow: hidden; color: var(--muted); font-size: 13px; font-variant-numeric: tabular-nums; text-overflow: ellipsis; white-space: nowrap; }
.ch-date.tier-official { color: inherit; }
.ch-date.tier-ecosystem, .ch-date.tier-library { opacity: .75; }
.ch-main:hover .ch-title { text-decoration: underline; }
.ch-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; font-weight: 650; font-variant-numeric: tabular-nums; }
.ch-pages { min-width: 0; overflow: hidden; color: var(--muted); font-size: 13px; font-variant-numeric: tabular-nums; text-align: left; white-space: nowrap; }
@@ -205,6 +210,10 @@ main { max-width: 1200px; margin: 0 auto; padding: 16px; }
.ch { flex-wrap: nowrap; gap: 4px; overflow: hidden; }
.ch-main { grid-template-columns: 64px 44px minmax(0, 1fr) max-content; gap: 6px; }
.ch-main.has-time { grid-template-columns: 64px 44px 62px minmax(0, 1fr) max-content; }
/* Phones: the date takes the page-count column. */
.ch-main.has-date { grid-template-columns: 64px 64px minmax(0, 1fr) max-content; }
.ch-main.has-time.has-date { grid-template-columns: 64px 64px 62px minmax(0, 1fr) max-content; }
.ch-main.has-date .ch-pages { display: none; }
.ch > .btn.small { min-width: 0; padding-left: 5px; padding-right: 5px; }
.ch > .state { flex: none; }
}