Skip to content

c23c74f4 - Gate Pay and Send behind a hidden insider unlock - #885

Open
TaprootFreak wants to merge 10 commits into
stagingfrom
feature/insider-gated-pay-send
Open

c23c74f4 - Gate Pay and Send behind a hidden insider unlock#885
TaprootFreak wants to merge 10 commits into
stagingfrom
feature/insider-gated-pay-send

Conversation

@TaprootFreak

@TaprootFreak TaprootFreak commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Soft launch for the two new phase-2 features: the Pay (OpenCryptoPay, #674) and Send (W2W transfer, #687) dashboard actions are hidden behind an invisible wall so only insiders can reach them.

  • The two dashboard buttons render only when a persisted insiderFeaturesUnlocked flag is set; Buy and Sell stay visible unconditionally.
  • Unlock: tap the version number in Settings seven times (developer-options pattern). A snackbar confirms the unlock; the flag persists across restarts (SharedPreferences, seeded into SettingsBloc).
  • The OpenCryptoPay payment deeplink intentionally keeps working regardless of the unlock state, so payment links handed to insiders resolve as before.

Deliberate deviation from the API-authority rule (reviewed, intentional)

CONTRIBUTING lists "feature visibility based on local state" as not OK and prefers an API capability flag. This gate deviates from that on purpose, as a product decision made with the API-capability alternative on the table:

  • The point of the soft launch is that outsiders must not even see the features, and the unlock must work offline/instantly for anyone told the gesture — an account-bound API capability would change the product (server-side insider bookkeeping, no gesture unlock).
  • No API truth is duplicated or contradicted: there is no server-side notion of this soft launch, and the API remains the sole decision authority for every actual transfer/payment the flows perform. The unlocked app renders exactly what the API-authorized app rendered before this PR; the locked app renders a subset.
  • Being a public repo, the mechanism is readable in source — the wall is a discoverability hurdle, not a security boundary.

Implementation notes

  • The Settings version row keeps its exact visuals; it moves into a new SettingsVersionUnlock widget that follows the page-local getIt bloc-access pattern (its test harness deliberately mirrors the settings golden harness structure).
  • DashboardActions gates Pay/Send with collection-ifs on context.watch<SettingsBloc>().
  • ActionButton now scales its icon/label column down (FittedBox) instead of overflowing its fixed 110x50 box — the new actions matrix exposed real overflows under Expanded width squeeze (German labels, 2px at 1.0x on narrow devices) and at large text scales (up to 258px at 3.0x). The tap area stays the full box (the InkWell wraps it, not the scaled content). Layouts that fit are visually unchanged; the four positive-balance dashboard goldens picked up sub-pixel antialiasing deltas (77-107 bytes each) from the new render path and were regenerated by the runner.
  • expectFullyTappable maps both rect corners through the render transform, so scaled targets measure their visual rect (transform-neutral for every existing call site).
  • The repository setter follows the established fire-and-forget persistence idiom; the repo-wide hardening idea is tracked in Settings persistence is fire-and-forget across SettingsRepository #886. Pre-existing positive-balance dashboard overflows (CashHoldingBox and siblings) are tracked in Positive-balance dashboard overflows on narrow devices from 1.0x text scale #887 and deliberately not part of this PR.

Handbook

Section 79 of the handbook (/de/#insider-unlock) explains the unlock step by step in German with three screenshots (settings version row, dashboard before, dashboard after) so the link can be shared directly with the people who should know. The new dashboard_insider_unlocked golden is mapped as handbook screenshot slot 269; the three updated dashboard baselines were already mapped and refresh automatically on the next handbook deploy.

Tests

  • New widget tests for the 7-tap unlock (6 taps inert, 7th dispatches exactly one event + snackbar, 9 rapid taps still dispatch exactly once, taps ignored once unlocked, version text still rendered)
  • DashboardActions locked/unlocked cases incl. the existing navigation assertions, plus a locked→unlocked transition test that pins the context.watch rebuild behaviour
  • New golden case dashboard_insider_unlocked (renders the same four-button dashboard the pre-PR baseline showed); existing dashboard goldens change to the 2-button locked default
  • New responsive-matrix group renders DashboardActions standalone (insider unlocked, all four buttons) across the full device/text-scale grid with overflow + tappability gates — scoped to the actions row this PR owns; the zero-balance page matrix is unchanged
  • Repository getter/setter covered against a real SharedPreferences backend (100% lines floor on lib/packages/*)
  • Full suite on the verification host: 4755 tests green, analyzer clean

TaprootFreak and others added 4 commits August 3, 2026 15:44
…r unlock

Pay and Send stay hidden on the dashboard until the app is unlocked by
tapping the version number in the settings seven times (the Android
developer-options pattern). The unlock is confirmed with a snackbar and
persisted via SharedPreferences, so it survives restarts. The
OpenCryptoPay payment deeplink intentionally keeps working regardless of
the unlock state.

The version row keeps its exact visuals; it is wrapped in a new
SettingsVersionUnlock widget that follows the page-local getIt bloc
access pattern. The dashboard golden baselines change (two buttons in
the locked default) and a dashboard_insider_unlocked case is added; the
responsive matrix renders the unlocked superset so all four actions stay
covered.
… fix analyzer findings

The SettingsBloc constructor now seeds insiderFeaturesUnlocked from the
repository unconditionally, so the four page/golden tests that build a
real bloc on a mocked repository need the new getter stubbed. Also apply
the analyzer findings on the new code: const on the settings version row
and an unused import in the unlock widget test.
The coverage floor scopes lib/packages/* at 100% lines; the new
insiderFeaturesUnlocked getter/setter only ran against a mocked
repository so far. Exercise both against a real SharedPreferences
backend like the neighbouring groups.
…rix and pin gate regressions

Review findings from the first pass: the zero-balance matrix case never
renders DashboardActions, so its insider stub suggested coverage that did
not exist — revert it and add a positive-balance insider-unlocked matrix
case that checks all four actions with expectFullyTappable across the
full device/text-scale matrix. Also pin two regression modes: a
locked-to-unlocked bloc emission must rebuild the mounted actions row
(context.watch), and nine rapid taps on the version row must still
dispatch exactly one unlock event (the == 7 comparison).
…atrix case renders

The positive-balance dashboard branch also builds
DashboardTransactionHistory, which resolves AppStore and friends via
getIt — mirror the golden harness registrations into the matrix test and
add the missing bloc_test import for whenListen.
TaprootFreak and others added 2 commits August 3, 2026 16:49
The four-button dashboard row overflowed its fixed 110x50 boxes once
Expanded call sites squeezed the width (German labels wrapped, 2px at
1.0x on narrow devices) or large text scales exceeded the height (up to
258px at 3.0x). Wrap the icon/label column in a scale-down FittedBox:
cells that fit keep rendering pixel-identically, the former overflow
cases shrink instead.

The responsive matrix now exercises the actions row standalone across
the full device/text-scale grid (the surrounding positive-balance
dashboard sections have pre-existing overflows, tracked in #887), and
expectFullyTappable maps both rect corners through the render transform
so scaled targets measure their visual rect instead of the untransformed
layout size. The locked-to-unlocked transition test double-pumps to let
the stream deliver before asserting the rebuild.
@TaprootFreak

Copy link
Copy Markdown
Contributor Author

Two full two-dimensional review passes (conformance + logic) were run on this PR, the second one covering the complete diff again after the fixes from the first.

Pass 1 surfaced four points: the responsive-matrix stub for the unlocked state never actually rendered the actions row (fixed — the matrix now exercises the four-button row standalone across the full device/text-scale grid, which in turn exposed and fixed a real ActionButton overflow and an over-wide rect measurement in expectFullyTappable), missing transition/rapid-tap regression tests (added), the fire-and-forget settings persistence idiom (pre-existing repo-wide convention — tracked in #886 instead), and the API-authority tension (an intentional, documented product decision — see the PR body).

Pass 2 came back clean on logic; the single conformance note (the widget-test harness registers a set-up-all placeholder that each test replaces) is the deliberate mirror of the existing settings golden harness structure and was kept for pattern consistency.

Pre-existing positive-balance dashboard overflows discovered by the new matrix are tracked in #887.

@TaprootFreak
TaprootFreak marked this pull request as ready for review August 3, 2026 15:09
@TaprootFreak TaprootFreak added the tier3:full Opt-in: run Tier 3 Maestro handbook flows on this PR label Aug 3, 2026
@TaprootFreak
TaprootFreak marked this pull request as draft August 3, 2026 15:57
…reable anchor

Section 79 documents how to unlock the hidden Pay and Send actions
(seven taps on the settings version number) with before/after
screenshots, reachable at /de/#insider-unlock for sharing with the
people who should know. The new dashboard_insider_unlocked golden joins
the screenshot mapping as slot 269, and the Dockerfile comment stops
hardcoding the screenshot count.
…image block

The build check still insisted on 278 assembled screenshots and would
have gone red with the new slot; bump the guard, the stale counts in the
docs, and add slot 269 to the smoke sample. The three-image unlock block
gets a wrapping flex container (the only multi-image block in the file)
and the source span the other catalog heads carry.
@TaprootFreak

Copy link
Copy Markdown
Contributor Author

Follow-up: the handbook addition (section 79, shareable /de/#insider-unlock anchor, screenshot slot 269) went through two further full review passes — four in total for this PR now.

Pass 3 confirmed every factual claim of the new instructions against the code (icon, tap count, snackbar wording, button labels, persistence, deeplink behaviour) and surfaced five conformance issues, most importantly the build check still guarding for 278 assembled screenshots; pass 4 verified all of them fixed (guard and docs at 279, smoke sample extended to the new slot, the three-image block got a wrapping container and a source span) and came back clean on both dimensions.

@TaprootFreak
TaprootFreak marked this pull request as ready for review August 3, 2026 16:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tier3:full Opt-in: run Tier 3 Maestro handbook flows on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant