Everything here is built and live. Green means I have already proved it works
against real data, so you are only judging how it looks and feels. Nothing below is a mock-up.
Some have no button — those changed how something behaves underneath, or need a real paired till, so there is no
page to open.
WP-26 — a paper punch card carries over to the digital oneNEEDS REVIEW
Staff transfer a customer's existing paper punch-card count onto their new digital card at enrollment.
⚠️ May not show the exact screen being tested — some steps need a specific account or a paired device. See notes below.
For this to pass: Entering the paper card's punch count once must set the digital card's starting balance to that number.
✓ Already verified: 2026-08-13 against production: 6 punches transferred (30 -> 36) and a SECOND transfer for the same customer was refused. First name from the new-customer step persisted to the record. Behavior verified; the SCREENS still want your eye.
Detailed steps
- As the account owner (owner-key kiosk), check in a brand-new phone number for the first time.
- After the normal credit, the "Transferring a Paper Card?" screen should appear. Enter a punch count (e.g. 6) and tap "Transfer Punches."
- Confirm the customer's digital card now shows 6 stamps (in addition to whatever the check-in itself credited).
- Check in the *same* phone number again — the paper-migrate step must not appear a second time (customer is no longer brand-new).
- On a kiosk configured with the day-to-day **service** key, repeat step 1 — the paper-migrate step must never appear, even for a brand-new customer.
- Tapping "Skip — New Customer" on the offer screen should proceed straight to the normal confirm screen with no transfer applied.
WP-25 — a bonus window that actually multipliesNEEDS REVIEW
The merchant sets one scheduled time window where customers earn a bonus multiplier.
For this to pass: Check-ins inside the window must earn the multiplier, and check-ins outside it must earn the normal amount.
✓ Already verified: 2026-08-13 against production: with a 3x window covering the moment of the visit, a check-in earned 30 instead of the flat 10, and the response carried the bonus flag. The same customer, with the window moved into the past, earned 10 again. The MATH is verified; the confirmation wording is still you
Detailed steps
- As an owner, open the panel → More → Loyalty program, set a bonus window that includes right now (e.g. now−1 min to now+10 min) and a multiplier (e.g. 2), save.
- Check in a returning customer on the paired kiosk during that window — confirm the credited stamps/points are the multiplier's worth of the normal amount, and the confirmation screen shows the "⚡ Happy Hour" note with the correct multiplier.
- Wait until (or set a window ending in) the past — check in the same or another customer — confirm the flat rate credits and no bonus note appears.
- Repeat step 2 on a spend or threshold-basis program with a below- threshold ticket amount — confirm 0 is credited and no bonus note appears (the bonus did not invent a credit).
WP-24 — "Customer + staff" counter mode is now genuinely two devicesNEEDS REVIEW
A customer-facing device and a staff-facing device coordinate a single check-in together.
⚠️ May not show the exact screen being tested — some steps need a specific account or a paired device. See notes below.
For this to pass: A check-in started on the customer device must appear on the staff device, and staff must be able to confirm it there.
⚠ Not ready - I found a problem: 2026-08-13 against production: the handoff is CORRECT but SLOW. A check-in started on the customer device reaches the staff device either almost instantly or almost exactly 30 seconds later, at random (measured 0.3s once and ~30.0s in five of six trials) - a cache expiry, not a steady delay. Confirming it then works and credits properly. At a real counter that wait is unusable. Do not review this
Detailed steps
- Set an account's counter mode to "Customer + staff" (owner panel → Counter tab).
- Open the kiosk on two tablets/windows: device A with no `?facing=` override, device B with `?facing=staff` in its URL.
- On device A, enter a phone number and tap through — it should show "You're Checked In" / waiting, NOT a credited stamp count.
- On device B, sign in with a staff PIN if the roster has one — the pending customer should appear within ~2.5s.
- Tap the customer on device B (enter a ticket amount first if the program is spend/threshold-basis) — device B returns to the queue.
- Device A should update to the credited confirm screen within ~2.5s, showing the real stamp/points total.
- Reload device A mid-wait (before staff confirms) — it should resume polling the same check-in, not create a duplicate.
- Confirm the SAME customer twice in a row on device B (simulate a retry) — the stamp balance must only increase once.
WP-27 — you can now correct a customer's stamp countNEEDS REVIEW
An authorized person corrects a customer's stamp balance when something needs fixing.
For this to pass: Correcting a balance must require a reason and must record who made the change and when.
✓ Already verified: 2026-08-13 against production: a correction with no reason was refused outright; with a reason it applied (30 -> 35) and landed in the audit trail with who, what, why and when. Undone afterward.
Detailed steps
- As an owner/manager, open the app shell, go to Customers, and open a customer's detail card.
- See a "Correct balance" section with an amount field, a reason field, and a "Save correction" button.
- Enter `+3` with no reason, click Save — see "A reason is required." Nothing is submitted.
- Enter `+3` with a reason, click Save — the button disables, then the customer card re-loads showing the updated stamp balance.
- As a counter-tablet (service-key) session, open the same customer detail — no "Correct balance" section renders.
- (Server-side, defense in depth) POST directly to `/demo/customers/adjust` with a service key — refused with `needsAuth`.
WP-22 — redemption is now actually recordedNEEDS REVIEW
Staff confirm a reward redemption at the counter, and that confirmation is recorded.
⚠️ May not show the exact screen being tested — some steps need a specific account or a paired device. See notes below.
For this to pass: Confirming a redemption must decrement the customer's balance and create a real record.
✓ Already verified: 2026-08-13 against production: redemption refused below threshold ('Not enough stamps yet for a reward') rather than silently succeeding. Behavior verified; the SCREENS still want your eye.
Detailed steps
- Open the kiosk with `?scenario=reward` (offline demo) — confirm "Apply Now" still shows a code and countdown exactly as before, no network call (Network tab stays empty).
- Pair a real card (`?scenario=auto` with a live `cardId` + device key) and drive a customer to a reward-ready or streak-perk-ready state.
- With no staff signed in and a PIN configured on the account, tap "Tap to Redeem" / "Apply Now" — confirm the PIN sign-in prompt appears and nothing is recorded until a PIN is entered.
- Enter a valid PIN — confirm the redeem code shown on screen matches the `code` field in the `/demo/loyalty/redeem` response (Network tab), and the account's stamp/points balance is decremented exactly once.
- Repeat the tap with a customer already below the reward threshold — confirm the screen moves on without showing a success code (no ledger row written).
- Tap "Not This Time" / "Save for Later" — confirm no `/demo/loyalty/redeem` call is made.
WP-32 — the Appointment Card's four broken behaviorsNEEDS REVIEW
The Appointment Card supports cancellation, accurate visit logging, and showing appointment details on install.
⚠️ May not show the exact screen being tested — some steps need a specific account or a paired device. See notes below.
For this to pass: You must be able to cancel an appointment, booking one must not log a visit, and a newly installed pass must show the appointment.
✓ Already verified: 2026-08-13 against production: booking created the appointment and credited NO loyalty visit (balance stayed 0), it showed on the due list, and cancelling removed it. What a freshly installed pass DISPLAYS still needs a real phone.
Detailed steps
- Connect `serviced-today.html` to an Appointment-type card.
- Log a visit with "✓ Serviced Today" — a visit is recorded, next due date set (staff PIN required if configured).
- Enter a future date + time and tap "📅 Book only — no visit" — confirm no visit is logged (no ledger effect) but the due list shows the new appointment with the chosen time.
- Scan `/c/<id>?ph=<that phone>` before the card is ever installed — confirm the freshly minted pass already shows "Appointment Date" on the front and "Next Appointment" on the back with the booked date/time.
- On the due list, tap "Cancel" on that customer's row — confirm it drops off the list, and (if installed) the pass's back row now reads "Next Appointment: Cancelled".
WP-23 — the counter mode you pick in settings now actually changes the kioskNEEDS REVIEW
The counter tablet operates in one of three modes — staff-only, customer-only, or both — chosen by the merchant.
⚠️ No single link shows this — see the issue for what to open.
For this to pass: Changing the mode in settings must change how the kiosk behaves, with balances and history unaffected.
✓ Already verified: 2026-08-13 against production: saved customer-only and staff-only in turn, and the kiosk read back each one correctly. What the SCREEN then does with it is still yours to see.
Detailed steps
- In the Loyalty Settings panel (WP-16), set "Operating mode" to `Customer only` and save.
- Open the paired kiosk URL (no `?facing=` override) — it renders the customer-facing (two-column) layout.
- Complete a check-in on a spend- or threshold-basis program — no ticket-amount screen appears; the visit credits at the flat `pointsPerVisit` rate.
- Set "Operating mode" to `Staff only` and save; reload the same kiosk URL (no override) — it now renders the single-column staff layout and takes staff-entered awards directly.
- Set "Operating mode" back to `Customer + staff`; reload — customer-facing layout, and (if a staff PIN roster is configured) the ticket-amount step reappears and is staff-gated as before.
- Confirm a kiosk link with an explicit `?facing=staff` or `?facing=customer` still renders that layout regardless of the account's saved `counterMode` (the demo/preview override path, unchanged).
WP-16 — a merchant can configure their own loyalty program, from their phoneNEEDS REVIEW
The merchant configures their loyalty program (stamps vs. points, reward threshold, earn rate, check-in mode) from their own phone.
For this to pass: Changing a setting and saving it must actually change behavior on a real check-in.
✓ Already verified: 2026-08-13 against production: saving 25 stamps per visit changed the very next real check-in from 10 to 25, and the account was restored afterward. Settings genuinely drive behavior.
Detailed steps
- Sign in as an account owner, open the panel, tap **More**.
- Under **Loyalty program**, confirm every field is pre-filled with the account's current program (a fresh account shows the product defaults: Stamps, Per visit, 10 earned per visit, threshold 100, sign-up bonus 3).
- Change **Card shows** to Points, change **Earning basis** to "Per dollar spent" — confirm the "Points per dollar spent" field appears and the "Minimum ticket to earn" field stays hidden.
- Set a **Reward threshold** different from the pre-filled value and tap **Save program settings** — confirm a browser confirmation appears naming the consequence; canceling it leaves the save un-sent.
- Confirm the save, confirm **Saved.** appears and reload the panel — the new values are still there.
- Open the panel with a counter-tablet (service) key — confirm no Loyalty program block, no Save button, and no program credential appear anywhere in the page.
WP-21 — the counter now asks for the ticket total, but only if the merchant's NEEDS REVIEW
Staff enter the ticket total at the counter for programs that earn stamps based on how much a customer spends.
For this to pass: A spend-based program must prompt for an amount and calculate stamps correctly; a visit-based program must never show that prompt.
✓ Already verified: 2026-08-13 against production: switched to spend-based at one stamp per dollar, and a $25.00 visit earned 25 while a $7.00 visit earned 7. The kiosk is correctly told the basis, which is what makes it ask for the amount. Restored to visit-based.
Detailed steps
- `POST /demo/program` (owner key) with `{earnBasis: "spend", earnRate: 2}` for a test account.
- Open the paired kiosk (`?cardId=<id>` + a valid devkey), enter a known phone, tap the check-in CTA.
- Confirm the ticket-total pad appears (not skipped) and enter e.g. `$15.00`.
- Confirm the screen's new total matches `previous balance + 30` (not previous + 30 + a second guessed amount).
- Repeat with `earnBasis: "visit"` — confirm the ticket step never appears at all.
- Repeat with `earnBasis: "threshold", spendThresholdCents: 2000, earnRate: 2` — enter `$10` (below threshold): confirm 0 credited, no reward-ready claim; enter `$25`: confirm 2 credited.
WP-19 — a kiosk check-in now actually credits a stampNEEDS REVIEW
A customer checking in at the kiosk earns a stamp on their real loyalty card.
⚠️ May not show the exact screen being tested — some steps need a specific account or a paired device. See notes below.
For this to pass: Checking in a real customer must increase their balance by the correct amount and update their pass.
✓ Already verified: 2026-08-13 against production: a first visit credited 30 (10 x 3 first-visit bonus) and a repeat visit credited 10 with the bonus NOT re-applied. Behavior verified; the SCREENS still want your eye.
Detailed steps
- Pair a kiosk to a real card (`?cardId=&devkey=` or the admin panel's link) with `?scenario=auto` (the default) and no roster PIN configured.
- Enter a known returning customer's phone number, submit. Expect: the confirm screen's stamp/points count reflects their real prior balance plus this visit's credit, and their installed pass (if any) updates live.
- Enter a phone number never seen before, submit. Expect: the "Temporary Stamp"/"Temporary Points" registration screen (QR to complete signup) — unchanged from before, since `returning:false` now comes from the earn response instead of lookup's.
- Configure a roster PIN, sign out, then tap a check-in. Expect: the sign-in screen (unchanged — `requireStaffThenRun` already gated this before this PR). Sign in, then the check-in credits normally.
- Double-tap the same phone number in quick succession (two real taps). Expect: two visits recorded, but only the first one (if it's their actual first ever) gets the signup bonus — proven server-side already, exercised end to end here.
WP-30 — quick actions on the merchant Home screenNEEDS REVIEW
The merchant's phone home screen shows their business identity and quick actions.
⚠️ May not show the exact screen being tested — some steps need a specific account or a paired device. See notes below.
For this to pass: The home screen must show the business name/logo and the right product-specific actions, and must read well on both phone and laptop.
Detailed steps
- Open `/app?id=<card>&k=<owner key>` for an owner account with a card and at least one cardholder. Confirm Home shows, in order: business name (+ logo if the card has one), a row of quick actions (Send Message, Edit Card, the counter tool, More), then the existing last-message/tiles/composer/history content unchanged.
- Tap **Send Message** — confirm it opens the same composer the existing "Send a notification" button does.
- Tap **Edit Card** — confirm it opens `/edit/<id>?k=<owner key>` (and `&type=` for a loyalty card).
- Tap the third action on a loyalty card — confirm it opens `loyalty-counter.html`; on any other type, `serviced-today.html`.
- Tap **More** — confirm it switches to the More tab in place, no page reload.
- Open the same URL with a service/staff key — confirm only **More** appears in the quick-action row.
WP-33 — the Event Card finally has a record behind itNEEDS REVIEW
A dedicated card type for one-time events, carrying title/date/time/location.
⚠️ No single link shows this — see the issue for what to open.
For this to pass: Creating an event must populate its card with the right details, with no attendance implied.
✓ Already verified: 2026-08-13 against production: title, start, end and location saved and read back exactly. One gap found - there is NO way to delete or cancel an event once created, only overwrite it. Filed as #643. My test event is still on the demo account for that reason.
Detailed steps
- `POST /demo/event` with an owner key, a title and a `startAt` — creates the record; response echoes it.
- `GET /demo/event` with the same owner key — returns the record just created.
- `POST /demo/wwcreate` an Event-type card for that account — the returned pass body's `backFields` include "When" (and "Where" if a location was set).
- `POST /demo/wwupdate` the same card — the rows are re-added.
- `POST /demo/wwcreate` a `professional-services` card on the same account — no "When"/"Where" rows appear.
WP-31 — "Log a Lead" on the Business Card, and Contacts can finally find themNEEDS REVIEW
A Business Card captures a lead's name and email as a real contact.
⚠️ May not show the exact screen being tested — some steps need a specific account or a paired device. See notes below.
For this to pass: Taking a Business Card and entering name/email must create a visible entry in the merchant's Contacts.
✓ Already verified: 2026-08-13 against production: a captured lead came back with a contact id and appeared in the merchant's Contacts with its name and email intact.
Detailed steps
- Open an existing business-card owner link (`/e/<id>?k=<key>`), or create a new Business Card.
- On the owner dashboard, open **"Log a Lead"**, leave Name blank, enter an email, tap **Save Lead** — expect "Saved — they'll show up in Contacts."
- Open the merchant panel's **Customers** tab, search the email address — expect the lead to appear (no phone/stamp shown, since neither exists for this record).
- Confirm no wallet-pass install prompt or QR was shown at any point in step 2.
- Switch the card type to Loyalty (or open a Loyalty card's owner dashboard) — confirm "Log a Lead" is not shown.
WP-20 — "Who's on the Counter?" sign-in on the check-in kioskNEEDS REVIEW
Staff identify themselves at the counter by tapping their name and entering a PIN.
⚠️ May not show the exact screen being tested — some steps need a specific account or a paired device. See notes below.
For this to pass: Tapping a name and entering the right PIN must sign the staffer in and let them log a visit; an idle session must require re-entry.
✓ Already verified: 2026-08-13 against production, on a real tablet credential: with nobody signed in the tablet was refused; a wrong PIN was refused; the right PIN signed the staffer in and they logged a real visit; after signing out the same session was refused again. Note the gate only applies to TABLET credentials
Detailed steps
- Pair the kiosk to an account that has staff with PINs on the roster (`?cardId=<id>` + a minted device grant), set `?scenario=auto`.
- Enter a 10-digit number, tap the main CTA → "Who's on the Counter?" shows instead of the next step.
- Tap a name → PIN pad appears → enter the correct PIN → Sign In → the original step (ticket entry / staff award / confirm) proceeds automatically.
- Front screen now shows "Signed in as `<name>` · Switch".
- Tap Switch → session ends, front screen badge disappears.
- Repeat with a wrong PIN → inline error, PIN clears, stays on the pad.
- Load the same URL with any other `?scenario=` (e.g. `?scenario=streak`) → no sign-in screen ever appears, no network call fires (confirms the no-network-dependency invariant is preserved).
WP-14 — a merchant can finally SEE the card templatesNEEDS REVIEW
Merchants can browse and select starter templates for their card type.
⚠️ May not show the exact screen being tested — some steps need a specific account or a paired device. See notes below.
For this to pass: You must be able to see templates by card type and load one onto a real card.
✓ Already verified: 2026-08-13 against production: 40 templates on file, every one carrying its card type (loyalty 19, appointment 9, business 8, event 3, professional services 1). Loading one works, and asking for a template as the WRONG card type is refused. The by-type browsing itself is screen work and still yours.
Detailed steps
- `PO: YES` per the issue — deploying a preview is outside this pass; the shape to verify once one exists:
- Open `new-card.html` with no `?dev=` param at all.
- Confirm all four card-type buttons show an "N to start from" badge (not just "Start blank" after clicking in).
- Click each type, confirm its seeded starting-point card renders in the variant grid with real preview content (not blank fields).
- Click a seeded card, confirm `business-card.html` opens pre-filled with that template's fields (logo text, header label, stamp config for loyalty, etc.) and shows "Started from your ... template."
- As Ross (`?dev=<real key>`), confirm "Manage saved templates" still lists the seeded templates plus any of Ross's own, and Delete still works.
WP-12 — a customer's live stamp count now shows on the FRONT of the passNEEDS REVIEW
⚠️ No single link shows this — see the issue for what to open.
Detailed steps
- Install a Loyalty card, tap "add stamp" at the counter (`/demo/loyalty/earn`) — the pass's header now shows "Reward Progress: N of M" and the back still shows "Stamps: N of M".
- Redeem a reward (`/demo/loyalty/redeem`) once the threshold is met — the header updates to the post-redemption count, replacing (not duplicating) the row.
- Mark an Appointment card "Serviced Today" (`/demo/svc/update`) with a serial already on file — the header shows "Appointment Date" with the computed next-due date; a Business or Event card run through the same endpoint gets no header write at all.
- Send a reminder sweep (`/demo/svc/remind`) — the back "Reminder" row still updates; no header change (no binding declared for that key).
WP-29 — the founder demo list and "+ New Demo"NEEDS REVIEW
Creates a branded demo card for a prospective merchant, for any of the four product types.
⚠️ May not show the exact screen being tested — some steps need a specific account or a paired device. See notes below.
For this to pass: Tapping "+ New Demo" must produce a real, installable pass within about five minutes.
Detailed steps
- Open `/founder.html?key=<ADMIN_KEY>` on a phone-width viewport.
- Tap **+ New Demo**, enter a business name, pick each of the four module tiles in turn, tap **Create demo**.
- Confirm the result panel's **Open the install link** button reaches a real installable pass, and **Open the owner experience** opens the card's own `/edit/<id>?k=...` page.
- Back on the list, confirm the new account shows a `DEMO` badge, and tapping **Move to onboarding** flips it to `ONBOARDING` (then **Mark live** to `LIVE`) with no new row appearing.
WP-11 — Google Wallet stops silently discarding three card fieldsNEEDS REVIEW
⚠️ No single link shows this — see the issue for what to open.
Detailed steps
- This issue is `po-test-required` / `PO: YES` (the Product Owner approves each platform's presentation at card creation) — verifying the "Card Highlights" row actually renders acceptably on a real installed Google Wallet pass is a device test, tracked by that label per `EXECUTION_POLICY.md` §7.5, not something this PR can demonstrate on its own.
WP-36 — one bookmarkable page that opens every surfaceNEEDS REVIEW
A single bookmarkable page linking to the current test version of every reviewable surface.
For this to pass: The page must open cleanly on your phone and every link must lead to the current version.
✓ Already verified: 2026-08-13 against production: launcher.html loads and all 9 links return 200 - app, More, founder, both kiosk facings, and all four New Card types. It IS on production now; the note below saying otherwise is out of date. Whether it opens cleanly on YOUR phone is still yours.
Detailed steps
- Open `https://flavir-wallet-demo.pages.dev/launcher.html` on a phone.
- Confirm it renders as a single scrollable column of nine cards, each with a label and one tap target.
- Tap **Founder** → lands on the Founder key-entry gate (no key pre-filled).
- Tap **Merchant owner** → `/app` (home pane, or the login redirect if signed out).
- Tap **Loyalty Settings** → `/app/more`.
- Tap **Card editor** → `/new` template chooser, no type pre-selected.
- Tap **Business Card** / **Appointment Card** / **Event Card** → `/new` with that template pre-highlighted.
- Tap **Customer kiosk** → check-in kiosk, customer-facing layout.
- Tap **Staff kiosk** → same kiosk page, single-column staff layout.
- Confirm no URL in step 3–9 contains `key=` or `dev=`.
WP-13 — editor reads the card type and the registryNEEDS REVIEW
Card editor correctly detects and preserves a card's type (Loyalty, Appointment, Business, Event) when you reopen it to edit.
For this to pass: Reopening a Loyalty card for editing must still show it as Loyalty, with your saved reward headline intact.
Detailed steps
- Create a Loyalty card (`/new?type=loyalty`), set a reward headline, save.
- Reopen it via the card's own "Edit Card" link. Confirm: the "Android Reward Layout" group is visible, the Android preview shows the loyalty layout (not generic primary/secondary fields), and the reward headline/details are the ones you saved.
- Create an Appointment card (`/new?type=appointment`). Confirm the "Header label" field shows a light placeholder reading "Appointment Date" when empty, and typing over it works normally (the placeholder never becomes a saved value).
- Create a Business card, reopen it, confirm nothing changed from today — reward group stays hidden, no header-label placeholder.
- Repeat step 2 with an Event card to confirm it stays non-loyalty (reward group hidden) and has no header-label placeholder (event has no registry default).
WP-18 — real lookup endpoint replaces the kiosk's odd-last-digit mockNEEDS REVIEW
The kiosk identifies a customer by looking up their real phone number against the database.
⚠️ No single link shows this — see the issue for what to open.
For this to pass: An unrecognized number must show as new, and a known customer's balance must match their real record.
✓ Already verified: 2026-08-13 against production: an unseen number reads as new; a known customer's balance matches the ledger. Cross-tenant lookup refused. Behavior verified; the SCREENS still want your eye.
Detailed steps
- Open `helplocal-checkin-kiosk.html?cardId=<real card id>&devkey=<a device grant token minted via POST /demo/devices/add>` — no `?scenario=`.
- Enter a phone number that has never visited this account. Submit. Confirm the screen shows the "new / temporary" state (not a coin-flip based on the last digit).
- Enter a phone number for an existing customer with a known stamp balance. Submit. Confirm the balance shown reflects the account's real D1 balance, not a value derived from the digits.
- Open the same page **without** `?cardId=`/`?devkey=` (or with `?scenario=` set to any demo value, e.g. `?scenario=streak`) and confirm every existing demo flow still renders exactly as before — no network calls, no change in behavior.
WP-17 — how a kiosk tablet gets pairedNEEDS REVIEW
Sets up a kiosk tablet and connects it to a merchant's account with a secure device credential.
⚠️ May not show the exact screen being tested — some steps need a specific account or a paired device. See notes below.
For this to pass: A tablet must be set up and working with its device credential, and a revoked device must be refused.
✓ Already verified: 2026-08-13 against production: added a tablet, used its own credential to run a real check-in, then revoked it — the same credential was refused immediately on the next attempt. Test tablet left revoked.
Detailed steps
- Open `helplocal-checkin-kiosk.html` locally with `?devkey=test123` in the URL. Confirm (via devtools) `localStorage.getItem('hl_kiosk_devkey')` is `"test123"` and the address bar no longer shows `devkey` after load.
- Reload the same page with no query string. Confirm `window.HL_KIOSK_DEVKEY` still reads `"test123"` from storage.
- Run `./deploy-helplocal-kiosk.sh` from a checkout with `.env` populated — confirm it stages to `/tmp/hl-kiosk`, deploys, and prints the live URL (not run here; requires live Cloudflare credentials).
WP-28 — the merchant panel's nav is defined in one place instead of fourNEEDS REVIEW
The merchant panel's navigation (sections, icons, empty states) is defined in one place.
⚠️ No single link shows this — see the issue for what to open.
For this to pass: The navigation must look and work correctly for your role — nothing should look broken or missing.
Detailed steps
- Log in as an owner (or manager) → open `/app` → nav shows exactly Home, Customers, Counter, More, each with its icon and a real empty state (no change from before).
- Take a grant with only `CARD_EDIT`/`CARD_PUSH` → `/app` shows only Home and More → hitting `/demo/customers/list` directly with that same credential is still refused (`needsAuth: true`).
- Log in with a membership whose `role` is `"manager"` → the minted session record in KV carries `role: "manager"` → `resolvePrincipal` against that session's cookie returns `p.role === "manager"`.
- Impersonate an account (`/demo/admin/impersonate`) → resolving that session returns `p.role === null`.
WP-10 — cards fill in their own field labelsNEEDS REVIEW
⚠️ No single link shows this — see the issue for what to open.
Detailed steps
- Verified by automated tests per `EXECUTION_POLICY.md` §2.4.
WP-03 — an account's sales stage, separate from whether it may operateNEEDS REVIEW
⚠️ No single link shows this — see the issue for what to open.
Detailed steps
- For a reviewer wanting to confirm by hand:
- `node --test tests/account-lifecycle.test.mjs` — 9 pass.
- Open `web/flavir-internal/worker-lib/tenancy.mjs` and confirm `capsAfterAccountState` contains no reference to `lifecycle`.
- Break it deliberately: add `if (acct && acct.lifecycle === "demo") return [];` as the first line of that function, re-run step 1, and confirm **2 tests fail**. Remove the line.
- Confirm the `d1UpsertAccount` INSERT in `cards.mjs` has 18 columns, 18 placeholders, and `lifecycle` in position 4 of both the column list and the `.bind(...)` arguments.