Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 149 additions & 34 deletions docs/free-tier-variance.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,30 @@ Produced 2026-08-15 by reading all three sources directly. Sources:
- **Store copy** ... the 2.1.0 full description and release notes drafted
2026-08-15 in `/projects/Marketing/nine-lives-marketing/store-listing.md`.

## Status, 2026-08-24

**Every listing conflict in this document is closed IN THE APPROVED 2.1.0
COPY.** The store copy was rewritten on 2026-08-24 and re-approved the same
day, and the paid-feature list in it was copied verbatim from
`UNLOCK_BENEFITS` in `ui/unlock/UnlockScreen.kt` rather than written
alongside it.

**That copy is a one-time reconciliation, not a mechanism, and this document
should not have called it structural.** `UNLOCK_BENEFITS` is a private Kotlin
value in this repo, the canonical listing lives in the marketing repo, and
nothing generates one from the other or checks them against each other. The
next edit to either side can re-drift exactly as before. A real check is
filed separately.

**The listing LIVE on Play is a different question and it is still open.** It
carries the pre-rewrite text until the flip-week refresh replaces it
wholesale, so the grouping conflict in row 9 is closed in the approved copy
and open on the live listing. Where this document says a row is resolved, it
means the approved copy unless it says otherwise.

Read section 1 for what changed, and 1b for the class of error this chart was
never built to catch and therefore missed.

## The headline

**The code is not drifting from the plan. The plan is drifting from itself.**
Expand All @@ -33,37 +57,70 @@ Legend: **Y** gated / specified · **n** not mentioned · **--** not applicable
| 1 | Downloads capped at one offline book | **Y** | **Y** | **Y** `DownloadSlotStore.slotApplies` | states it | **aligned** |
| 2 | Playback speed pinned 1.0x | **Y** | **Y** | **Y** `FreeTier.allowsSpeed` | states it | **aligned** |
| 3 | Sleep timer, 30-minute preset only | **Y** | **Y** | **Y** `FreeTier.SLEEP_TIMER_MINUTES` | states it | **aligned** |
| 4 | Sleep timer motion grace, shake-to-reset, rewind-on-expire | n | **Y** | **Y** normalized off | n | summary gap · **listing silent** |
| 5 | Auto-rewind on resume | n | **Y** | **Y** `autoRewindEnabled=false` | n | summary gap · **listing silent** |
| 6 | Equalizer and volume boost | n | **Y** | **Y** normalized off, clamped in `PlaybackManager` | n | summary gap · **listing silent** |
| 7 | Silence skipping | n | **Y** | **Y** `skipSilenceEnabled=false` | **advertises it as new, no gate note** | **listing conflict** |
| 8 | Sorts: 8 of 11 gated | n | **Y** | **Y** `FreeTier.SORT_MODES` | n | summary gap · **listing silent** |
| 9 | Grouping: series, author, genre | n | **Y** | **Y** `VIEW_MODES = {ALL}` | n | summary gap · **listing silent** |
| 10 | Archive Shelf browsing and manual restore | n | **Y** (auto-restore and deletion stay free) | **Y** `LibraryScreen` tab lock | **describes it as a feature, no gate note** | **listing conflict** |
| 11 | Nightwatch Dossier and share card | n | **Y** | **30-day window FREE**, longer periods gated, as of 2026-08-16 | **describes it as a feature, no gate note** | **resolved in code**, listing still needs the period note |
| 12 | Themes | n | **Y** NOIR free, other three gated | **NOIR + BRIGHT free** as of 2026-08-15 | n | **code ahead of plan, deliberate** |
| 13 | Advanced server settings (self-signed cert, TOFU) | n | **Y** "gated in the UI" | **NO GATE** ... bare `Switch`, no `GatedControl` | n | **code gap** |
| 14 | Android Auto browse gating and command grants | n | **Y** | **NO GATE** ... zero entitlement refs in `PlaybackService`, `MediaBrowseTree`, `RemoteMediaAccessPolicy` | n | **code gap** |
| 4 | Sleep timer motion grace, shake-to-reset, rewind-on-expire | n | **Y** | **Y** normalized off | covered by "full sleep timer control" in the unlock list | summary gap · **listing aligned 2026-08-24** |
| 5 | Auto-rewind on resume | n | **Y** | **Y** `autoRewindEnabled=false` | named in the unlock list | summary gap · **listing aligned 2026-08-24** |
| 6 | Equalizer and volume boost | n | **Y** | **Y** normalized off, clamped in `PlaybackManager` | named in the unlock list | summary gap · **listing aligned 2026-08-24** |
| 7 | Silence skipping | n | **Y** | **Y** `skipSilenceEnabled=false` | named in the unlock list, and the release notes now mark it "(new)" inside the unlock sentence | **resolved 2026-08-24** |
| 8 | Sorts: 8 of 11 gated | n | **Y** | **all 11 FREE** as of 2026-08-15, `FreeTier.SORT_MODES` = every entry, mechanism retained | n | **code ahead of plan, deliberate** |
| 9 | Grouping: series, author, genre | n | **Y** | **Y** `VIEW_MODES = {ALL}` | named in the unlock list | gated, ships gated · **approved copy resolved 2026-08-24, LIVE listing still open until the refresh** |
| 10 | Archive Shelf browsing and manual restore | n | **Y** (auto-restore and deletion stay free) | **Y** `LibraryScreen` tab lock | **names browsing as a paid unlock feature**, and points at the full list | **resolved 2026-08-24** |
| 11 | Nightwatch Dossier and share card | n | **Y** | **30-day window FREE**, longer periods gated, as of 2026-08-16 | **names the free window as permanent and the longer ones as paid** | **resolved 2026-08-24** |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Remove the stale Dossier action item

This row now marks the Dossier listing conflict resolved because the rewritten copy names the free and paid windows, but lines 145–146 still say the listing needs a period note and remains open. Anyone using this as the decision record is therefore given contradictory instructions about whether copy work is still required; update or mark that older action as superseded.

Useful? React with 👍 / 👎.

| 12 | Themes | n | **Y** NOIR free, other three gated | **NOIR + BRIGHT free** as of 2026-08-15 | AMOLED and Candlelight named in the unlock list | **code ahead of plan, deliberate** |
| 13 | Advanced server settings (self-signed cert, TOFU) | n | **Y** "gated in the UI" | **NO GATE** ... bare `Switch`, no `GatedControl` | n | ships ungated in 2.1.0, deliberate · see reconciliation |
| 14 | Android Auto browse gating and command grants | n | **Y** | **NO GATE** ... zero entitlement refs in `PlaybackService`, `MediaBrowseTree`, `RemoteMediaAccessPolicy` | n | **never gated, decided 2026-08-16** · see reconciliation |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Update the superseded Android Auto decision section

Row 14 now records Android Auto as a locked decision to remain ungated, but the current What actually needs a decision section at lines 151–163 still labels it one of two code gaps and recommends deciding the gate rather than leaving it omitted. That stale section reverses the newly recorded decision, so it should be rewritten as historical material or reduced to the still-open row 13 question.

Useful? React with 👍 / 👎.

| 15 | Local folder cap | dropped 2026-08-15 | marked superseded | never built | correctly silent | **aligned** |
| 16 | ABS library cap | dropped 2026-08-15 | marked superseded | never built | correctly silent | **aligned** |

## What actually needs a decision

### 1. The three listing conflicts (rows 7, 10, 11)
### 1. The listing conflicts (rows 7, 10, 11) ... RESOLVED 2026-08-24

This was the row set that produces angry reviews, because the listing promised
something the app then charges for. It was also the cheapest to fix, being copy
rather than code.

The recommendation below was to move the gated features into the unlock
paragraph rather than tag each mention. Jeff took a version of that on
2026-08-24 and went further:

This is the row set that produces angry reviews, because the listing promises
something the app then charges for. It is also the cheapest to fix: it is copy,
not code.
- `BUILT FOR LONG LISTENS` is now free features only, and ends with a line
pointing at the paid list.
- The Archive Shelf and Dossier sections each name their gate inline and point
at the same list.
- The old `FREE AND UNLOCKED` section split into `WHAT FREE GETS` and
`THE PAID UNLOCK, IN FULL`, so the pointers land on a header that says what
it is.
- The paid list was copied **verbatim** from `UNLOCK_BENEFITS` in
`ui/unlock/UnlockScreen.kt`, whose own comment says it exists so the unlock
screen and the store listing cannot drift. It had drifted anyway, which is
the point: a comment asking people to remember is not a mechanism. Copying
beats paraphrasing and it makes rows 4 through 11 true today, but it is a
one-time reconciliation and the next edit to either artifact can undo it.
The mechanical check is issue #13, not this paste. See the status block.

- **Silence skipping** is in the 2.1.0 release notes as "Also new: silence
skipping" with no indication it is an unlock feature. A free user reads the
release note, updates, and finds a locked toggle.
- **Archive Shelf** and **Nightwatch Dossier** each get their own section in the
full description, written as capabilities the app has. Both are gated.
Approved copy is the paste package in the marketing repo, re-approved
2026-08-24 09:57 after three review passes.

Fix: either add the gate to each mention, or move all three into the unlock
paragraph. Recommend the second, since it makes the unlock paragraph a real
value proposition instead of three limits phrased as subtractions.
### 1b. What this chart was never built to catch

Every row here is a **free-versus-paid variance**: is a gated thing described as
free. That framing is correct and it found five real conflicts. It is also
blind to a second class of error, and the 2026-08-24 audit found three of those
in copy this document had already reviewed:

- `Widget and full media-notification controls` promised a home-screen widget
that **does not exist in this repo at all**. No `AppWidgetProvider`, no
Glance, no appwidget receiver in the manifest, no widget XML. The
notification half is real, which is how the bullet survived a read.
- `Sleep timer that fades out` ... it does not fade. `SleepTimerManager` pauses
immediately at zero when motion sensing is off, which is the free path.
- `Run both modes side by side ... in one app` ... `AppMode` is an exclusive
enum read as either/or in `LibraryViewModel` and `SettingsViewModel`. You
configure both and switch.

None of those are variance. A feature that does not exist has no tier. So when
this chart is next run, run a second pass that asks a different question of
every claim: **not "is this gated" but "is this true."**

### 2. The Dossier strategy conflict (row 11) ... DECIDED 2026-08-16

Expand Down Expand Up @@ -95,25 +152,33 @@ Rejected alternatives, for the record: share card without the Dossier screen
(splits one feature into two half-features), and leaving it gated while dropping
H1 (throws away the only user-distribution mechanism in the plan).

Still open: the listing copy needs a period note, since the full description
describes the Dossier without saying the long windows are an unlock feature.
~~Still open: the listing copy needs a period note, since the full description
describes the Dossier without saying the long windows are an unlock feature.~~
**CLOSED 2026-08-24.** The approved copy now says the thirty-day window is
free and never expires, and that longer retrospectives are a paid unlock
feature, with a pointer to the full list. No copy work remains on this row.

Recommend the first. The card is an acquisition asset, and gating your own
acquisition asset behind a purchase is backwards.

### 3. The two code gaps (rows 13, 14)
### 3. The one open gate question (row 13), and one that is decided (row 14)

Both are planned gates that were never built. Neither is a correctness bug and
Both were planned gates that were never built. Neither is a correctness bug and
neither leaks entitlement in the wrong direction, so free users currently get
*more* than intended.

- **Advanced server settings**: this one is arguably better left ungated.
Self-signed cert trust is how a self-hoster connects to their own box at all.
Gating it is close to gating access to a user's own data, which is the line
the paywall philosophy says not to cross.
- **Android Auto browse gating**: worth deciding deliberately rather than by
omission, especially since PRs #88, #90, and #92 all touched Auto and none
added it.
- **Advanced server settings (row 13), still open.** This one is arguably
better left ungated. Self-signed cert trust is how a self-hoster connects to
their own box at all. Gating it is close to gating access to a user's own
data, which is the line the paywall philosophy says not to cross. No formal
lock, and the code ships ungated with the listing silent, so nothing is
promised either way.
- **~~Android Auto browse gating~~ (row 14), DECIDED 2026-08-16: never gated.**
This section previously called it a gap and asked for a deliberate decision.
That decision was made and Android Auto is free, so the absence of
entitlement references in `PlaybackService`, `MediaBrowseTree` and
`RemoteMediaAccessPolicy` is the intended end state rather than an omission.
Kept here rather than deleted because the old framing was cited elsewhere.

### 4. The plan doc sentence

Expand Down Expand Up @@ -143,3 +208,53 @@ the default, not the first free entry`).
The plan doc's locked decision on themes and the `FreeTier` lore line ("the
vault is what you get, lighting is a privilege you buy") are both now superseded
and need updating in place.

## Reconciliation, 2026-08-23

Written the night 2.1.0 (212) was finalized for production, to close the rows
still marked open above. Facts and decisions, with provenance:

- **Row 14, Android Auto: never gated. Locked decision, 2026-08-16.** Auto
ships free in the category king's Basic tier, and a gate here would punish
the exact commute use case the listing sells. The absence of entitlement
references in `PlaybackService`, `MediaBrowseTree`, and
`RemoteMediaAccessPolicy` is the intended end state, not a gap. The old
"code gap" verdict is superseded. One live artifact still contradicts the
decision: the listing currently on Play (mirrored in `store-listing.md`
at the repo root, PLAYBACK section) sells the unlock as adding Android
Auto. The approved 2.1.0 copy drops that claim, and the flip-week listing
refresh replaces the live text wholesale. Until then the live listing
oversells the unlock in Auto's favor ... known, accepted, and it dies at
the refresh. When the refresh publishes, re-sync the root mirror and
`reference/play-store-listing.txt` in the marketing package from the
approved paste source, per the mirror's own header rule.
- **Row 13, advanced server settings: ships ungated in 2.1.0.** The standing
recommendation (self-signed cert trust is how a self-hoster reaches their
own box, and gating it sits next door to gating access to a user's own
data) has not been formally locked, but the code ships ungated and the
listing is silent, so nothing is promised either way. If a future release
wants this gate, that is a new decision, not a regression fix.
- **Row 9, grouping: gated, and 2.1.0 ships it gated.** Whether it stays
gated long-term is formally still open, marked low priority 2026-08-16.
As of the 2026-08-24 rewrite the approved copy names grouping in the paid
list, as "Series, author and genre grouping", which is the honest place
for it. Earlier drafts of this section said the copy never mentioned
grouping at all, which was true of the 08-23 version and is not any more.
The listing live today is another story: its LIBRARY section advertises
"grouping by series, author, or genre" with no gate note
(`store-listing.md` mirror, same section). That line is one more thing the
flip-week refresh retires, and it stays a listing conflict, not a closed
row, until the refresh is live.
Comment on lines +246 to +247

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Scope the closed status to the approved copy

Before the flip-week refresh, the live listing still advertises grouping without a gate note, and this sentence explicitly says that conflict remains open until the refresh. That contradicts the new top-level claim that every listing conflict is closed and row 9's closed verdict. Distinguish the approved-copy status from the live-listing status so the document has one answer for whether this conflict is currently open.

Useful? React with 👍 / 👎.

- **Row 8, sorts: all 11 free since 2026-08-15.** Chart cell updated. The
gate mechanism is retained deliberately ... a lever not pulled is not a
lever removed.
- **Rows 4, 5, 6, 7, 10, 11, the listing gaps and conflicts: CLOSED
2026-08-24.** The 2026-08-23 approval left every one of them open, because
that approval had only ever checked the free-tier claims and not the
feature claims. Jeff reopened it on 08-24 and the rewrite closed the set
rather than patching the worst case. See section 1 above for what changed
and why the paid list is now copied from `UNLOCK_BENEFITS` instead of
written alongside it.
- **The 30-day trial is not in 2.1.0.** Decided 2026-08-16, cut 2026-08-22:
v212 ships without it and the trial moves to 2.2 as a release beat. No
listing or doc text may mention a trial until it ships.
Loading