Skip to content

Dossier: an unbounded All Time window after unlock, so January gets a real year look-back #7

Description

@StaticHumStudio

The ask

Add an unbounded "All Time" window to the Nightwatch Dossier, unlock-only. Free stays
at the rolling 30 days, which is settled and not reopened here.

Jeff, 2026-08-24: going forever in the history is the only way to get a true year
look-back in early January. Late December would be fine.

Why the calendar date matters

Every long window today is anchored to now, not to a calendar boundary:

app/src/main/java/com/ninelivesaudio/app/ui/dossier/NightwatchDossierViewModel.kt:58

THIRTY_DAYS   -> now - 30.days
THREE_MONTHS  -> now - 90.days
SIX_MONTHS    -> now - 180.days
ONE_YEAR      -> now - 365.days

So on Jan 3, "1 Year" covers Jan 3 of last year through today. The first days of the
year a person actually wants to review are already outside the window, and three days
of the new year are inside it. On Dec 28 the same rolling year covers essentially all
of the current calendar year, which is why late December reads fine and early January
does not. A year-in-review card that quietly drops the first week of the year is the
kind of thing one user notices and everybody else silently distrusts.

Check this before building it

DossierPeriod already has six entries, not four. Past 1 Year (which is where the
chip row runs off the right edge of the screen) there are two calendar-anchored ones:

THIS_YEAR  -> Jan 1 of this year  ->  now
LAST_YEAR  -> Jan 1 of last year  ->  Jan 1 of this year

LAST_YEAR is an exact prior-calendar-year look-back and it works correctly on Jan 3.
The chip row is a horizontalScroll (NightwatchDossierScreen.kt:197), so both are
reachable, just never on screen at rest. Which means the January problem may be a
discoverability bug rather than a missing window, and the fix might be a scroll hint,
a reorder, or a "Year in Review" affordance that surfaces itself in January.

Open question for Jeff: is the ask still All Time once you know Last Year exists, or
does Last Year cover the January case and All Time becomes a separate lifetime-totals
feature? Both are defensible. They are not the same work.

What All Time actually costs

Local mode: free, the data is already there. Nothing prunes LocalListeningSessions
by age. There is no retention job, no deleteOlderThan, no WorkManager cleanup. The
Dossier calls getAllSessions() and filters by window in memory
(NightwatchDossierViewModel.kt:301). An ALL_TIME entry with a cutoff of 0 is purely
additive.

Server mode: NOT forever, and this is the real work. In SERVER mode
getAllSessions() goes to Audiobookshelf instead
(ListeningSessionRepository.kt:30), and that path is hard-capped:

app/src/main/java/com/ninelivesaudio/app/data/remote/ApiService.kt:838

itemsPerPage: Int = 50
val maxPages = 20

1000 sessions, ceiling, silently. A daily listener hits that in under three years and
an "All Time" card would just stop being true with no indication. Whatever ships has to
either raise that bound, page until the server says it is done, or be honest in the UI
about how far back it actually reached. Shipping a forever window on top of a 1000-row
cap is worse than not shipping it.

Implementation notes

  • daysInPeriod (NightwatchDossierViewModel.kt:98) is the daily-average divisor and
    every other period has a fixed length. ALL_TIME does not, so the divisor has to come
    from the real span (earliest session to now) or the daily average is nonsense.
  • periodPhrase (:116) needs a non-"in the last X" wording, same special case
    THIS_YEAR and LAST_YEAR already take.
  • Gating is one line: keep it out of FreeTier.DOSSIER_PERIODS
    (FreeTier.kt:136) and effectiveDossierPeriod clamps it for free installs
    automatically. The clamp is applied at query time, not just at the chip, so there is
    no separate enforcement to add.
  • A seventh chip pushes the row further past the right edge. Worth checking the row does
    not become a place where two whole periods live invisibly.

Store listing consequence

The 2.1.0 listing copy and the slot 07 screenshot caption both describe this gate. The
current wording says "longer" spans, which stays true. Do not upgrade it to "your whole
record" or any superlative until the server-mode cap above is actually fixed, because in
server mode it would be false.

Post-2.1.0. Nothing here blocks the release or the price flip.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions