Skip to content

Check the store listing's paid list against UNLOCK_BENEFITS mechanically #13

Description

@StaticHumStudio

The ask

A mechanical check that the store listing's paid-feature list still matches
UNLOCK_BENEFITS.

Why, and why a comment is not enough

UNLOCK_BENEFITS in ui/unlock/UnlockScreen.kt carries this comment:

Kept in one list so the unlock screen and the store listing cannot drift.

They drifted anyway. By 2026-08-23 the approved 2.1.0 store copy described six
unlock-only features as free (silence skipping, the equalizer, volume boost, the
motion-sensing sleep timer, shake-to-reset, auto-rewind on resume) plus the
Archive Shelf, which had its own section headline. Nobody noticed until a manual
audit on 08-24.

The 08-24 fix was to copy the constant verbatim into the listing. That made them
match. It did not make them stay matched, and docs/free-tier-variance.md
briefly claimed otherwise before a review caught it. A one-time copy is a
reconciliation. The next edit to either side re-drifts, and the only thing
standing in the way is somebody remembering a comment in a Kotlin file.

Shape

A script, run before any listing change is staged:

  1. Parse the UNLOCK_BENEFITS list out of UnlockScreen.kt. It is a plain
    listOf(...) of string literals, so this is a small parse and does not need
    the Kotlin toolchain.
  2. Parse the bullets under THE PAID UNLOCK, IN FULL out of the approved paste
    package.
  3. Diff. Non-zero exit and a readable report on any mismatch, in either
    direction.

Where it lives

The marketing repo, not this one. The canonical listing lives there, this repo
is public, and an app-repo test cannot read a path outside its own tree. The
script reads the app source by path, which works because both repos sit on the
same machine.

Sibling to tools/compose_shots.py, which already owns the "verify the assets
before they go to Console" job. Same idea, applied to words instead of pixels.

Requirements

  • Give it a deliberate-failure canary, the way compose_shots.py --canary
    has one. A gate nobody has watched fail is not a proven gate. Mutate one
    string, confirm the check reports it, restore.
  • Compare on normalized text, since the listing wraps at ~72 characters for
    human reading and the Kotlin strings do not. Whitespace and wrapping
    differences are not drift.
  • The two are allowed to word things differently in principle. If they should
    be, the check needs an explicit mapping file rather than a fuzzy match, so
    that any intentional divergence is written down instead of tolerated
    silently.

Origin

Codex review on PR #6, 2026-08-24, correctly pushing back on this document
calling a manual copy a structural fix. The finding was right and the honest
answer is to build the mechanism rather than reword the claim, so the wording
was fixed there and the mechanism is filed here.

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