Skip to content

feat(readlist): continue ordered read lists like series - #1095

Merged
everpcpc merged 9 commits into
kmworks:mainfrom
spawvn:feat/readlist-continuation
Sep 25, 2026
Merged

everpcpc merged 9 commits into
kmworks:mainfrom
spawvn:feat/readlist-continuation

Conversation

@spawvn

@spawvn spawvn commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1094

Problem

Reading through an ordered read list that spans several series only follows the read list while you stay on the read list page. From the dashboard, a series page, or book details, the reader falls back to series next/previous, and nothing on the dashboard points to the read list's next book. See #1094 for the reproduction.

Approach

  • Opt-in. A new setting, "Continue Read Lists Like Series" (Settings › Dashboard), off by default. While it's off, nothing is recorded, synced, resolved, or shown: no local reads or writes and no client-settings requests. Opening a book from a read list's page keeps following that list, as it does today. While the setting is off, ordered read list pages show a quiet hint row that opens the setting.
  • Reading state per read list. New read_list_reading_states table (migration 00009): one row per read list the user reads through (last book opened, when). A non-incognito reader session with a read-list context records it on open and on seamless cross-book moves. Only ordered read lists take part; "next book" means nothing in an unordered one.
  • Reader: resolve the context centrally. ReaderPresentationManager.present resolves a missing context through ReadListReadingService.ownerContext(forBookId:), so a book that belongs to a read list being read continues in read-list order from any entry point. An explicit context (opened from the read list page) always wins.
  • Dashboard: a "Read Lists in Progress" section of its own, first by default. One card per read list being read, most recently read first, driven directly by the service's published continuations (no pagination, no detail page). Per list, mirroring Komga's series rules: a book in progress, or once a book is finished the next unread one, searching forward from the last book opened. The card shows the next book's cover, the list's name, the book's title, the book's progress while it's in progress, and how many of the list's books are read ("88% • 1 of 3 read"). Tapping it opens that book in read-list order; the long-press menu offers View Details and Stop Reading. The library scope hides a card by its book's library without changing which book the list resolves to. On Deck and Keep Reading are untouched. The setting adds the section when turned on and removes it when turned off; Reset only offers it while it's on.
  • Same card look as Keep Reading. The cover-tinted background and its cover loading move out of BookHorizontalCardView into CoverTintedCardBackground and coverArtwork(instanceId:bookId:into:), used by both cards. Keep Reading renders exactly as before.
  • Cross-device sync through Komga's per-user client settings (/api/v1/client-settings/user, Komga 1.20.0+). One key per read list, kmreader.readlist.<lowercased id>: Komga's key pattern rejects uppercase, ids are case-insensitive base32, and the original id also travels in the value. It runs with the reading-progress catch-up (login, foreground, reconnect): it pulls first and keeps, per read list, whichever change happened last on any device (a read or a stop), then pushes the local changes that won. The app requires Komga 1.20.0+ (its minimum server version); sync failures are silent (debug log only).
  • Stop Reading in the card's long-press menu, the read list's context menu, and its detail page menu (one shared ReadListStopReadingButton), shown only for ordered read lists being read. A stop is a timestamped local tombstone until its server key is deleted: a pull doesn't undo it, and a read made on another device after it wins.
  • Startup load. The local state loads with each instance in ContentView's per-instance startup task, independent of the offline-gated, debounced network catch-up, so continuation works on offline launches and right after a relaunch.
  • Ownership. ReadListReadingService owns the derived snapshot. Refreshes run one at a time, after the service's own writes and on local book-progress / read-list projection changes; the section observes it directly. The service never posts dashboard reloads.
  • AGENTS.md documents the reader, sync, and dashboard rules.

Why this shape, not another

  • Opt-in: the feature changes how existing content behaves (navigation from every entry point), and recording starts on the first open. Off by default, only users who ask for it get it; everyone else pays nothing.
  • Its own section instead of merging into On Deck: On Deck stays Komga-native and nothing is hidden without a visible reason. The trade-off, as discussed in review: a list's next book can also be its series' On Deck pick and then shows in both places, which is arguably right, since the user is following both threads.
  • First by default: for users who opt in, the read list's next book is where reading continues, so it sits above the per-series suggestions.
  • Client settings, not a Komga change (yet): works with stock Komga today, per user, and follows the user across devices.
  • Central resolution at reader open: one rule covers every entry point (dashboard, series page, book details, search).

Validation

  • make build passes on iOS, macOS, and tvOS, up to date with current main.
  • Two simulators (iPad A16 + iPad Pro 11") against a local Komga with synthetic fixtures: Library A (Alpha 1–2, Gamma 1), Library B (Beta 1–2); read lists "Crossover Event" (ordered: Alpha 1 → Beta 1 → Alpha 2), "Annual Special" (ordered: Gamma 1 → Beta 2), "Unordered Mix" (unordered). Verified:
    1. Setting off (default): no section, no client-settings request at launch, On Deck is Komga's; Crossover Event's page shows the hint, and it opens the setting.
    2. Setting on: the section appears first, with Crossover Event (Beta 1, in progress) and Annual Special (Beta 2, next unread), while On Deck still shows Komga's Alpha 2.
    3. Tapping the Crossover Event card opens Beta 1 with /readlists/{id}/books/{bookId}/next|previous; so does opening Alpha 2 from its series page's Resume button.
    4. Book progress on the card: 50% → 88% after reading Beta 1 to page 7 (once feat(reader): delay progress recording until a few pages are turned #1103's three-page threshold is met), updating together with Keep Reading.
    5. Turning the setting off removes the section and empties the state; turning it back on re-adds the section at the top.
    6. Card long-press menu: View Details and Stop Reading; Stop Reading from the read list's menu shows the toast, deletes the server key, and the second device follows on its next catch-up.
    7. A second device picks the state up from client settings without opening the read list.
    8. Migrations: a database at main's latest migration upgrades by running 00009_add_read_list_reading_states after 00008_add_instance_api_key_id; a fresh install runs the baseline through 00009 in order.
    9. Cold start: on a relaunch inside the 30s catch-up debounce, and on a launch with the server unreachable, the snapshot loads from the local database; Stop Reading shows for the active read list.
    10. Stop vs. read across devices, with the other device's writes simulated through the client-settings API: an offline stop followed by a newer read → on reconnect the read wins and the server key survives; an online stop newer than the last read → the key is deleted.
    11. Unordered read lists: no hint, no Stop Reading, no state, including for a synced state written through the API.

Manual test plan

  1. Settings › Dashboard → turn on "Continue Read Lists Like Series" → "Read Lists in Progress" is added at the top.
  2. Create an ordered read list interleaving two series (A1 → B1 → A2). Open it, read A1 to the end, close.
  3. Dashboard → the list's card shows B1 ("1 of 3 read"); On Deck is unchanged.
  4. Tap the card → B1 opens and its end card shows A2 next; read a few pages → the card shows B1's percentage.
  5. Open A2 from series A's page → previous/next follow the read list.
  6. On a second device (same account, setting on), foreground the app → the same card appears.
  7. Long-press the card → Stop Reading → the card disappears on both devices after their next catch-up.
  8. Turn the setting off → the section disappears; ordered read list pages show the hint.

Cohort impact

  • Setting off (default): no change at all, no extra requests.
  • Setting on, one device: read-list order everywhere, and each list's next book at the top of the dashboard.
  • Several devices: the reading state follows the account (Komga 1.20.0+); the setting itself is per device.
  • Offline: recorded locally and synced on the next catch-up. Incognito sessions are not recorded.

Known limitations

  • Continuation is derived from locally cached progress, so progress made on another device shows after this device's next catch-up or SSE-triggered reload.
  • The library scope filters the dashboard only; the reader follows the read list regardless of scope.

🤖 Generated with Claude Code

Reading through a read list that spans several series fell back to
series order as soon as the user left the read list page: the reader
only used read-list navigation when the book was opened from that page,
and Keep Reading / On Deck are series-only (Komga's On Deck is the first
unread book of a series), so the dashboard suggested the series' next
book instead of the read list's, sometimes next to the read list's own.

KMReader now remembers the ordered read lists the user is reading and
treats them like series:

- ReaderPresentationManager.present resolves a missing read list context
  through ReadListReadingService.ownerContext, so a book owned by a read
  list being read continues in read-list order from any entry point
  (dashboard card, series page, book detail). An explicit context from
  the read list page always wins. Non-incognito sessions record the
  entry point, including seamless cross-book moves.
- On Deck (row and detail page) puts a read list's next unread book
  first and drops the series suggestion for any book owned by a visible
  read list, so one reading thread shows up once. Keep Reading is left
  to Komga, which already lists every book in progress. Resolution runs
  on the full read list; the library scope only hides the result.
- State lives in a new read_list_reading_states table (migration 00008)
  and syncs across devices through Komga's per-user client settings
  (1.20+), one key per read list, pushed before each pull and run with
  the reading-progress catch-up; older servers keep it local. Stop
  Reading (read list context menu and detail toolbar) clears it; a stop
  stays a tombstone until the server key is deleted.
- ReadListReadingService publishes the derived snapshot, re-deriving it
  after its own writes and on local book-progress / read-list projection
  changes. On Deck observes it and reloads when what it merged is out of
  date, so a sync or another device's change needs no manual refresh.

Alternatives dropped: passing a context per dashboard card only covered
the dashboard, not series pages or book details; merging read lists into
Keep Reading could show a book already finished on another device from
stale local progress; having the service post dashboard reloads on change
lost updates whenever another reader of the snapshot refreshed it first.

Only ordered read lists take part; "next book" has no meaning in an
unordered one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@everpcpc everpcpc left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — the design holds up well against the description: central context resolution, explicit-context-wins, observe-don't-push snapshot ownership, push-before-pull sync, and the new AGENTS.md rules all match the implementation. Spec check passes on the rest: On Deck suppression works on both the dashboard row and the detail page, tombstones survive pulls, incognito and unordered read lists are excluded.

Two blockers before merge (inline):

  1. Migration number collision — 00008 is already taken on main (00008_add_instance_api_key_id); this must be renumbered and reordered. Migrations are immutable once merged, so it has to happen now.
  2. Cold-start snapshot never loads — offline launch, restart within the 30s catch-up debounce, or a hidden On Deck section all leave the snapshot empty, so read-list continuation silently falls back to series order from every entry point except the read list page, despite the state sitting in GRDB.

Smaller notes:

  • mergingReadListContinuations is duplicated ~20 lines verbatim between the two dashboard views, and the onChange observers have already drifted (one extracted reloadIfReadListsChanged with logging, the other inlined it).
  • stopReading is duplicated between ReadListContextMenu and ReadListDetailView, and they behave differently — the menu fires onMutationCompleted?() (a browse reload) though stopping isn't a read-list mutation, the detail view doesn't.
  • Offline stop is unconditional last-writer-wins: if another device resumed the read list while this one was offline, the tombstone's DELETE still removes the fresher server key. If that's intentional, worth one line in AGENTS.md; otherwise compare readAt before deleting.
  • UserClientSettingDto (ClientSettingsService) and AuthService.ClientSetting are the same { value } DTO — could share one.
  • Theoretical: local recording is gated on readList.ordered, but applyRemoteReadListReadingStates isn't — a state synced from another device could surface Stop Reading for an unordered read list. Very low probability.

Once the migration is renumbered and the cold-start load is fixed, this looks good to go. The manual validation matrix in the description is impressive.

Comment thread KMReader/Core/Storage/GRDB/LocalDatabase.swift Outdated
Comment thread KMReader/Features/ReadList/Services/ReadListReadingService.swift
Comment thread KMReader/Features/Dashboard/Views/DashboardSectionView.swift Outdated
Comment thread KMReader/Features/ReadList/Views/ReadListContextMenu.swift Outdated
@everpcpc

Copy link
Copy Markdown
Member

One design suggestion on top of the review: please gate the whole feature behind a settings toggle, default OFF.

Why a toggle at all. The feature changes the behavior of existing content rather than adding something purely new: it reorders On Deck, suppresses series suggestions for owned books, and changes next/previous navigation from every entry point. Today the only escape is Stop Reading per read list — which a surprised user doesn't know exists, or why they'd need it. A single switch ("Continue read lists like series", e.g. in Settings › Dashboard alongside dashboardHorizontalBookCards) gives them a discoverable way out.

Why default OFF.

  • Recording starts implicitly on first open with no progress threshold, so with the feature on by default, one casual peek at a read list silently hijacks that book's navigation and On Deck everywhere — for users who never asked for this. Opt-in means only users who understand the semantics experience them.
  • It matches the PR's own "validate the behavior first" framing: ship opt-in, flip the default later once the activation semantics prove themselves. Much easier than walking back a default-on behavior after complaints.

What default OFF changes in the implementation. Gate everything, not just the visible effects: when off, don't record (recordReading), don't pull client settings in the catch-up, don't merge On Deck, don't resolve ownerContext, don't show Stop Reading. Users who never enable it then pay literally zero — no GRDB writes, no extra client-settings GET per catch-up (the "cohort impact" section's one small cost disappears). Opening a book from the read list page keeps following the list within that session as it does today; that's pre-existing explicit-context behavior, outside the toggle's scope.

Recovering discoverability. Since default OFF hides the feature, surface it where the need arises: when the toggle is off, ordered read list pages could show a quiet hint row ("Enable Continue like a series in Settings › Dashboard to read this list across series"), plus a changelog callout. Contextual discovery beats hoping users find the setting.

The two blockers from the review above still stand independently of this.

@everpcpc

Copy link
Copy Markdown
Member

A follow-up design suggestion that builds on the toggle comment above: surface read-list continuations as their own dashboard section instead of merging them into On Deck. This replaces the mergingOnDeck part of the PR; the reader-side half (context resolution, recording, sync, Stop Reading) stays exactly as is.

Why a dedicated section is the better shape:

  1. It deletes the most complex code in the PR. The merge needs mergingOnDeck, the ownership map, suppression, first-page-merge vs later-page-filter consistency against server-side pagination, and snapshot-compare reloads in both dashboard views (the ~20-line assembly is already duplicated and drifting between them). A standalone section's data source is snapshot.continuations — @Observable drives it directly. No merge, no suppression, no pagination interplay.
  2. It removes the silent suppression, which is the most confusing semantic in the merge design. Today a series suggestion vanishes from On Deck without any way for the user to attribute it. With a separate section, On Deck stays 100% Komga-native. The feared duplication barely materializes: in the canonical scenario (finish A1 → the list section shows B1, On Deck shows A2) those are two different books, each appearing once. A true duplicate only occurs when the list's next book is also its series' On Deck pick — and then the user is genuinely following both threads, so showing both is arguably correct. Visible duplication beats invisible hiding.
  3. It makes the feature visible. Merged into On Deck, there's no way to tell why B1 is there. A titled section gives the feature a legible presence — and pairs cleanly with the default-OFF toggle from the previous comment: enabled → section appears; disabled → nothing, anywhere.
  4. It plugs into the existing dashboard architecture. A new DashboardSection case with a cardKind (presumably horizontal, like pinned read lists) gets section reordering/hiding, the refresh coordinator, and row view models for free — instead of hard-coding read lists inside On Deck where users can't rearrange them.
  5. No pagination needed. One entry per read list being read, most-recently-read first (the snapshot's existing order) — a single horizontal row covers realistic counts, no "See all" page required.

Suggested card shape: read list name + next book's cover + progress (x/y); tapping opens the next book with an explicit read-list context (same as an On Deck card); context menu can carry Stop Reading.

The trade-off to be explicit about: this deviates from the issue's original expectation ("A2 isn't suggested separately while the read list is in progress"). I'd argue that expectation priced in the merge design's costs — complexity, invisibility, unattributable suppression — without considering the alternative. If a user reads A2 ahead of the list, the cursor simply follows the open, exactly as the PR already implements.

With this shape the whole feature becomes: default-OFF toggle gates everything; a dedicated section surfaces lists being read; On Deck is untouched.

- Renumber the migration to 00009_add_read_list_reading_states, after
  main's 00008_add_instance_api_key_id, instead of registering a second
  00008.
- Load the read list snapshot with each instance in ContentView's
  per-instance startup task. It was only loaded by the network catch-up
  (offline-gated and debounced) or by On Deck, so an offline launch, a
  relaunch within the debounce, or a hidden On Deck left continuation and
  Stop Reading disabled although the state was in the local database.
- Reconcile pulls first and keeps, per read list, whichever change
  happened last on any device, a read or a stop; a stop records its time
  on the tombstone. An offline stop no longer deletes a read another
  device made after it.
- Only ordered read lists count as being read, including for Stop
  Reading, whatever another device synced.
- Share the On Deck merge between the dashboard row and its detail page
  (OnDeckReadListMerge), Stop Reading between the context menu and the
  detail toolbar (ReadListStopReadingButton, no browse reload since
  stopping is not a read list mutation), and the client setting DTO
  between AuthService and ClientSettingsService.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@spawvn

spawvn commented Sep 25, 2026

Copy link
Copy Markdown
Contributor Author

Also addressed from the review summary:

  • The offline stop is no longer unconditional: a stop records its time on the tombstone, and reconcile now pulls first and keeps whichever change happened last for each read list, so an offline stop no longer deletes a read another device made after it (AGENTS.md updated).
  • AuthService and ClientSettingsService share one ClientSettingDto.
  • Only ordered read lists count as being read, so a state synced for an unordered one can't surface Stop Reading.

🤖 Addressed by Claude Code

spawvn and others added 3 commits September 25, 2026 10:54
Resolves the conflict with kmworks#1097, which moved dashboard row loading
into DashboardSectionViewModel: the On Deck read list merge now runs in
the view model's loadPage (after the fetch, re-checking the load id
after the merge) and the row view's snapshot observer calls
reloadIfReadListsOutdated, which supersedes through startReload.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adopts the design review of this PR: the feature changes how existing
content behaves, so it is now opt-in, and read lists being read surface
in a section of their own instead of being merged into On Deck.

- New setting "Continue Read Lists Like Series" in Settings > Dashboard,
  off by default. While off, nothing is recorded, synced, resolved, or
  shown: no local reads or writes and no client-settings requests.
  Opening a book from a read list's page keeps following that list, as
  before. Ordered read list pages show a hint row linking to the setting
  while it is off.
- New "Read Lists in Progress" dashboard section, first by default: one
  card per read list being read, most recently read first, driven
  directly by ReadListReadingService's published continuations (no
  pagination or detail page). A card shows the next book's cover, the
  list's name, the next book's title, the book's progress while it is in
  progress, and how many of the list's books are read; tapping opens
  the next book in read-list order, and the context menu offers View
  Details and Stop Reading. Turning the setting on adds the section,
  turning it off removes it, and Reset only offers it while on.
- On Deck is Komga's own again: the merge, the suppression of series
  suggestions, and OnDeckReadListMerge are removed, restoring the row,
  its detail page, and DashboardSectionViewModel to main.
- The card shares Keep Reading's cover-tinted look: the tinted background
  and its cover loading move out of BookHorizontalCardView into
  CoverTintedCardBackground and coverArtwork(instanceId:bookId:into:),
  which both cards use (no change for Keep Reading).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in kmworks#1106 and the book card metadata refresh fix; no overlap with
this branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@spawvn

spawvn commented Sep 25, 2026

Copy link
Copy Markdown
Contributor Author

Thanks! Adopted both suggestions in be2952a:

  • Default-off setting, "Continue Read Lists Like Series" in Settings › Dashboard. While it's off, nothing is recorded, synced, resolved, or shown: no GRDB reads or writes, and no client-settings request per catch-up. Opening a book from the read list page keeps its explicit context, as before. While it's off, ordered read list pages show a quiet hint row linking to the setting.
  • Dedicated "Read Lists in Progress" section instead of the On Deck merge. On Deck and its detail page are back to main's code, and OnDeckReadListMerge is gone. One horizontal card per read list being read, most recently read first, driven directly by the service's snapshot. Each card shows the next book's cover, the list name, the book title, the book's progress while in progress, and "x of y read". Tapping opens the next book with an explicit read-list context; the long-press menu has View Details and Stop Reading. The setting adds and removes the section, and Reset only offers it while the setting is on.

A few choices worth flagging:

  • The section defaults to the top of the dashboard (first enum case), above Keep Reading: for users who opt in, the list's next book is where reading continues. Existing layouts keep their order.
  • To match the Keep Reading card below it, the card uses the cover-tinted background. I moved that background and its cover loading out of BookHorizontalCardView into CoverTintedCardBackground and coverArtwork(instanceId:bookId:into:) rather than copying them; Keep Reading renders as before.
  • The setting is per device, like the other dashboard settings; the reading state itself still syncs through client settings.

Also merged current main. The PR description is updated, including the validation list.

🤖 Addressed by Claude Code

@spawvn
spawvn requested a review from everpcpc September 25, 2026 12:37
spawvn and others added 3 commits September 25, 2026 14:54
Resolves the conflict with the two-column read list page (kmworks#1108): the
continuation hint moves into the page's shared actions slot, so it renders
as the last row of the new action card in both the wide and single-column
layouts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Show the continuation book's download status icon in the card's bottom
  bar, per the horizontal-card contract; download-status projection
  changes now re-derive continuations instead of being skipped.
- Make the Read Lists in Progress header navigate to the read lists
  browse page, like the pinned sections' headers.
- Drop a reconcile round when the instance switched mid-flight, so a
  pull or push never applies the previous server's state to the new one.
@everpcpc

Copy link
Copy Markdown
Member

Pushed two commits to this branch (maintainer edit) covering the small follow-ups from the final review, to save another round trip — hope that's OK:

  • 969a18e3 — the continuation card now shows the book's download status icon in its bottom bar, per the horizontal-card contract (download-status projection changes now re-derive continuations instead of being skipped); the Read Lists in Progress header navigates to the read lists browse page, like the pinned sections' headers; and a reconcile round is dropped when the instance switched mid-flight, so a pull or push never applies the previous server's state to the new one.
  • 9f727772 — the continuation hint renders only for ordered, non-empty lists, ahead of the download actions.

Our main merges raced (0bca3ee6 vs mine) — I rebased onto yours, keeping your conflict resolution and only adding the non-empty guard on top. AGENTS.md updated for all of the above. Rebuilt cleanly on iOS, macOS, and tvOS.

Two leftovers from the final review are filed as follow-ups rather than blockers: orphaned state rows / server keys when a list is deleted on the server or fully read (no functional impact), and translator comments for the new localization keys.

Continuation syncs through the per-user client settings API (Komga
1.20.0+), so the app now targets 1.20.0+ overall instead of keeping a
local-only fallback for older servers. Bumps the declared minimum in
README, the App Store description, and the website docs.
@everpcpc

Copy link
Copy Markdown
Member

One more direction change, pushed as 7199590d (plus a small edit of the two outdated lines in the description): the app now requires Komga 1.20.0 as its minimum server version, so the local-only fallback for servers without the client-settings API is gone — no compatibility path to maintain or document. Since the repo baseline is 1.20.0+, the feature simply assumes the API exists (sync failures stay silent, as before). Declared minimum bumped in README, the App Store description, and the website docs. Verified kmrs already implements the per-user client-settings endpoints, so it stays compatible. Localization checked with make localize + translate.py list: all new strings complete in every language, nothing missing.

@everpcpc everpcpc left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. Both blockers from the review are fixed and verified (migration renumbered to 00009, snapshot loads at startup regardless of network), the offline-stop reconcile is now last-change-wins per list, and the opt-in setting + dedicated section design is in. We pushed the remaining polish directly (download icon, navigable header, hint gating, sync instance guard, Komga 1.20.0 minimum). CI green on all platforms; local builds pass on iOS/macOS/tvOS. Follow-ups noted in comments (orphaned state cleanup) are non-blocking.

@everpcpc
everpcpc merged commit 2c50246 into kmworks:main Sep 25, 2026
5 checks passed
@spawvn
spawvn deleted the feat/readlist-continuation branch September 25, 2026 14:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reading through a read list doesn't continue like a series (next book, On Deck)

2 participants