feat(accounts): capability state machine + headline status nomenclature (ENG-516) - #452
Conversation
Review — capability state machine (ENG-516)Domain derivation module + the 15-test matrix are solid. Four items to action before merge: 1. 🔴 CI is red — new mutation not registered in 2. 🟠 An unverified (L0) account can mint an L2 upgrade request. 3. 🟡 4. 🟡 Minor: the read-model derived level can exceed the still-exposed stored |
…re (ENG-516)
Retire Pro/International as user-facing tiers; rename Merchant to Business.
Model the account as capability flags {verified, bankPayout, business,
usdAccount} and derive the internal level (L1-L3) from them. Expose
capabilities + statusHeadline (light headline status: Trial / Verified /
Business) on ConsumerAccount and AuditedAccount, and add the
accountCapabilityUpgradeRequest mutation so clients request a single
capability instead of a whole tier. Spec in docs/account-capabilities.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…unt typing (ENG-516) Review follow-ups on the capability state machine: - Register accountCapabilityUpgradeRequest in the API-key scope map (BLOCKED, like businessAccountUpgradeRequest). The deny-by-default completeness test was red because the new mutation was unmapped. - requestCapabilityUpgrade now rejects unverified accounts: the state machine derives L0 for !verified, so a request from one would otherwise become a level-skipping L2 ERPNext request (validate() only guards requested<=current). - Type the L3 upgrade's bankAccount as optional and drop the `as BankAccount` cast — a business upgrade legitimately reuses a bank already on file, and validate() already reads it via optional chaining. - Add unit tests for requestCapabilityUpgrade (unverified, already-has, missing-bank, L2 add-bank, L3 reuse-on-file). - Doc note on derived-vs-stored level divergence. Verified: tsc --noEmit clean; full unit suite 134 suites / 1057 passed (api-key-scope-enforcement now green). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eral (ENG-516) Review follow-ups: - getAccountCapabilities memoizes per account object (WeakMap): the capabilities and statusHeadline GraphQL fields each resolved it separately, costing two serial ERPNext round-trips per query — and N×2 on admin views that list accounts. One lookup now serves every field resolution for the request; no cross-request staleness since each request fetches a fresh account object. - BRIDGE_KYC_APPROVED constant typed against Account["bridgeKycStatus"] replaces the bare "approved" magic string in the derivation, so an upstream status rename is a compile error instead of a silently-false usdAccount. - getAccountCapabilities unit tests: ERPNext-failure fallback (the branch that runs during an ERPNext incident), no-erpParty skip, bank-on-file, memoization semantics. - requestCapabilityUpgrade return annotation was Promise<Promise<...>> via ReturnType of an async fn; unwrapped. tsc clean; full unit suite 135 suites / 1064 passed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
7a8e960 to
37fc096
Compare
…, Merchant → Business (ENG-516) (#55) * feat(admin): adopt capability nomenclature — retire Pro/International, Merchant → Business (ENG-516) Account Hub and Account Management adopt the light-headline-status naming: accounts lead with Trial / Verified / Business, with capability badges (Bank payout, USD account) as supporting detail. Upgrade-request levels read as the capability being requested. The GraphQL account fragment now fetches statusHeadline + capabilities from the flash backend (requires lnflash/flash#452), with a level-based fallback for older backends. The change-level dialog uses disambiguated internal labels since L1 and L2 share the Verified headline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(admin): prettier format + disambiguate change-level dialog messages (ENG-516) Review follow-ups on #55: - prettier: format account_hub.js (was failing the pre-commit CI hook — the new capabilityBadgesHtml chain wasn't prettier-clean). - Change-level dialog now uses the disambiguated ADMIN_LEVEL_OPTIONS labels in its ERP-party and success messages (was ACCOUNT_LEVEL_LABELS, which renders "Verified" for both L1 and L2 — misleading, since L1 needs no ERP party). Verified: prettier --check clean; node --check OK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Patoo <262265744+patoo0x@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Dread <dread@example.com>
…G-516) (#667) * feat(account): light headline status + capability nomenclature (ENG-516) Retire Pro and International as user-facing labels; Merchant is now Business. The account leads with one headline word (Trial / Verified / Business) with capability badges (Bank payout, USD account) as detail, per the light-headline-status product decision. New useAccountStatus hook queries statusHeadline + capabilities from the backend (lnflash/flash#452) with a level-based fallback for older backends. Settings row and Account screen show the headline instead of the raw level; upgrade-flow tier cards adopt the new names. Synced public-schema.graphql from the backend (additive only) and reran codegen + typesafe-i18n. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(account): hide Business upgrade card for accounts already at L3 (ENG-516) The Business (L3) card rendered unconditionally, so a business account reaching the picker (e.g. via Bank accounts → Upgrade your account when Bridge KYC isn't approved) was offered Business again. Guard it like the other tier cards; a business account now only sees the remaining capability (USD Account). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(bank-accounts): launch Bridge KYC directly from the verify-identity CTA (ENG-516) The locked card on the Bank accounts screen gates on the usdAccount capability, so its CTA now opens the Bridge KYC flow (details modal → KYC webview) instead of the tier picker. KYC initiation is extracted from AccountType into a shared useBridgeKyc hook used by both screens; the CTA falls back to the picker when the Bridge feature flag is off. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(account): capabilities hub replaces tier picker (ENG-513) Rebuild AccountType into the "Do more with Flash" capabilities hub: a light status pill (Trial/Verified/Business) from useAccountStatus, a Verify hero card, and independent capability rows (Bank cash-out, US-dollar account, Business) grouped under "Ways to get paid" / "Grow". Each row routes into its existing setup flow — no guided interview. Rows key off the capabilities object (level-derived fallback for older backends); the internal L1/L2/L3 ladder stays hidden. Drops the linear ProgressSteps, which no longer models a non-linear capability menu. Adds hub i18n strings under AccountUpgrade + regenerated i18n types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(account): review fixes — Bridge KYC L1 floor, locked USD row, string sweep (ENG-513/516) - Bank accounts verify CTA now enforces the L1 floor: unverified users route through the verify wizard; verified users launch Bridge KYC directly. The fallback to the upgrade picker is gone. - Hub USD row is always visible: locked (not hidden) when Bridge is remotely disabled or the account is unverified; an "In review" state restores the pending-KYC affordance the old card's orange border gave. - useAccountStatus is the single source of capability truth: the level+KYC fallback moved to account-status-derivation.ts (unit-tested) and AccountType's duplicate derivation is removed. - String sweep: dead tier keys deleted (personal/pro/merchant/ international + descs, accountType, successUpgrade/successRequest); success + validation copy speaks capability language instead of "upgraded to BANK PAYOUT"; account screens and Bridge KYC alerts are localized; the hub's nav header is untitled (the screen self-titles). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(i18n): translate capability-hub strings into all locales (ENG-513/516) The translation-drift CI check requires every locale to carry all keys in en.json. Adds the 24 new AccountUpgrade hub keys and 3 BridgeKyc alert keys to all 23 locale files, refreshes the renamed BridgeKyc title/description (was untranslated "International Transfer" everywhere), and prunes the 11 tier keys deleted from en — including long-corrupted values like es "pro": "Pro Pro Pro …". Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(account): sharpen hub copy from device user-test (ENG-513) - Verify: "Phone + ID" was wrong — verification is phone + OTP only. Now "Just your phone number · unlocks higher limits in a minute". - USD row: title "US-Dollar Virtual Bank Account"; description leads with what you get — "Your own US account & routing number — receive ACH, wires & payroll". - Business: adds the rewards program — "Get on the Flash map & reward your customers". All 23 locales updated in step. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(account): explain the locked USD row + decrowd it (ENG-513) When the USD row is locked by the L1 floor, its description now reads "Verify your account first to unlock" instead of leaving the lock unexplained (kill-switch locks keep the normal description). The row also decrowds: title shortened to "USD Virtual Bank Account" and the locked state shows only a lock icon, so the title no longer wraps. All 23 locales updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(account): drop unused affordances from the KYC + status hooks startBridgeKyc's boolean return had no remaining consumers once the picker fallback was removed — make it void and fix the stale comment. use-account-status re-exported the derivation helpers nobody imports from it; the derivation module is their canonical home. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(account): cover useAccountStatus source selection + drop dead Screen prop Two review items: a renderHook test asserting backend statusHeadline/capabilities beat the level+KYC fallback (and that the fallback engages when the fields are absent), and removal of keyboardShouldPersistTaps from the hub Screen — it has no text inputs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Vandana <forge@getflash.io> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Dread <dread@example.com>
Summary
Backend half of ENG-516 (Account Upgrade Revamp). Retires Pro and International as user-facing tiers, renames Merchant → Business, and models the account as a set of capability flags from which the internal level is derived. Companion frappe-flash-admin PR adopts the new names in the admin panel; mobile presentation lands with ENG-513.
Product decision (2026-07-14): light headline status — the account leads with one word (Trial → Verified → Business) with capability badges as supporting detail.
The state machine
capabilities/statusHeadlineresolve from one memoized derivation per account per request (WeakMap keyed on the account object) — one ERPNext lookup no matter how many fields ask; the Bridge-KYC "approved" literal is a typed domain constant (BRIDGE_KYC_APPROVED).src/domain/accounts/capabilities.ts— pure derivation:deriveLevelFromCapabilities,deriveStatusHeadline,deriveCapabilitiesForAccount(read model over stored level + ERPNext bank accounts +bridgeKycStatus, with grandfathering for existing L2/L3 accounts).capabilities: AccountCapabilities!andstatusHeadline: AccountStatusHeadline!onConsumerAccount(public) andAuditedAccount(admin).levelstays exposed but documented as internal.accountCapabilityUpgradeRequestmutation — the client asks for one capability (BANK_PAYOUTorBUSINESS); the target level is derived server-side and the request flows down the existing ERPNext Account Upgrade Request pipeline unchanged.businessAccountUpgradeRequest(whole-tier) remains for existing clients.docs/account-capabilities.md.Testing
TEST="capabilities|request-capability-upgrade|get-account-capabilities" yarn test:unit— 27 tests: derivation matrix, headline mapping, grandfathering, round-trip;requestCapabilityUpgraderouting (unverified / already-has / missing-bank / L2 add-bank / L3 reuse-on-file);getAccountCapabilitiesERPNext-failure fallback, no-erpParty skip, and memoization semantics.yarn tsc-checkclean; SDL regenerated viayarn write-sdl(public + admin + supergraph).Local testing
🤖 Generated with Claude Code