Commit Graph
689 Commits
Author SHA1 Message Date
Jeppe B 4810e113f3 fix(test): polyfill localStorage and align jsdom env for spec files (#267)
Switches `vitest.config.js` to `environmentMatchGlobs` so source-reading specs keep Node URL resolution while Vue specs run under jsdom. Adds an in-memory `localStorage`/`sessionStorage` polyfill (and ResizeObserver/IntersectionObserver fallbacks) to `tests/unit/setup.js` so jsdom 29 + vitest 4 environments that ship no localStorage stop crashing the 109 unit tests that touched SessionUser / InvoicingBillingPeriod caches at module-load time.

Test result: 1343/1343 fast + 1688/1688 serial pass (was 1195/1304 on master). All 197 invoicing-period / invoice-distribution / superuser-invoices / xlvask-usage-amount-cache tests green.
2026-08-09 15:40:29 +00:00
Jeppe BandTruck Wash Agent 82c95d32c0 Mock /ping in driverAuth e2e so the ConnectivityIssue overlay does not hide the driver entry point (#266)
🤖 Generated with [OpenClaw](https://openclaw.ai)

## Why

`tests/e2e/driverAuth.spec.ts` (added in #265) failed across 4 full-E2E
matrix jobs on master:

- `E2E-full-Chromium-mobile-subuser-shard-1-of-1` (job 93176119977)
- `E2E-full-Chromium-desktop-subuser-shard-1-of-1` (job 93176119967)
- `E2E-full-Firefox-mobile-subuser-shard-1-of-1` (job 93176119955)
- `E2E-full-WebKit-mobile-subuser-shard-1-of-1` (job 93176119945)

Root cause: `/login` wraps the LoginForm in ConnectivityIssue, which
renders an overlay when GET /ping does not return ok. In the full
subuser E2E matrix driverAuth.spec.ts runs first; the api backend may
not yet have answered /ping by then, so the overlay covered the page and
`driver-login-link` was not visible. Targeted E2E (driverAuth only)
passed because the api was warm by then.

## Fix

Add a `test.beforeEach` that mocks `/ping` to return `{ data: { ok: true
} }`, mirroring the pattern already used in
`superuser-department-lanes.spec.ts`. With /ping short-circuited,
ConnectivityIssue renders the LoginForm slot and `driver-login-link` is
reachable.

## Risk

Low. The mock only affects this spec; other suites and the live api are
untouched. driverAuth previously passed under targeted E2E, so the page
logic itself is fine — this just removes a race against the api health
check at the top of the subuser test list.

---------

Co-authored-by: Truck Wash Agent <agent@copenhagentruckwash.local>
2026-08-09 15:37:13 +02:00
Jeppe BandTruck Wash Agent d4f92cd259 Surface driver login on /login and /; make SubuserLogin responsive (#265)
## Why

Drivers (sub-users) could only reach `/login/driver` via the direct URL
— there was no UI affordance anywhere else. They had to know the URL or
be sent a link by their admin. Mobile / tablet users had no obvious path
to the driver login either.

## What changed

- `src/components/forms/auth/LoginForm.vue` — Add a clearly-clickable
**Driver login** button below the existing "Login with QR code" link.
Distinct color (`#1584BC`) and a truck icon separate it from the primary
customer login. Test ID `driver-login-link`, ID `driver-login-button`.
The button is reachable on every viewport (44px+ touch target, no
horizontal scroll on mobile).
- `src/views/pages/LandingPage.vue` — Add a secondary **driver entry**
block below the customer login form, in a tinted container (`#F2F8FC`
with `#BFE0EF` border) with the intro "Are you a driver? Log in here to
register a wash." Test ID `landing-driver-entry` /
`landing-driver-login-link`.
- `src/views/auth/SubuserLogin.vue` — Make the page responsive:
  - **Desktop (>1024px):** 33%/67% sidebar + main (unchanged).
  - **Tablet (≤1024px):** 25%/75% tighter split, smaller sidebar title.
- **Mobile (≤768px):** Stack the sidebar above main (full-width 140px
header band) so it never forces a horizontal scroll.
- `src/i18n/source/{global/shared,da,en,de,no,sv}/.../auth/index.json` —
Add `auth.driver_login_button` and `auth.driver_entry_intro` in 5
locales. Run `npm run i18n:v2:compile` to regenerate the v2 bundle.
- `tests/e2e/driverAuth.spec.ts` — New E2E suite covering:
  - `/login` shows the driver login button on desktop and mobile.
  - `/` shows the driver entry block.
- Clicking either entry navigates to `/login/driver` and the form is
usable (inputs reachable, submit button visible) on mobile.

## Verification

- `npx eslint` — clean for changed files.
- `npm run i18n:v2:check` — green after `i18n:v2:compile`.

## Caveats

- New `.driver-entry` and `.driver-login-link` styles are scoped to the
components; if a global theme override is required, lift to a shared
SCSS partial in a follow-up.
- The driver login button is placed below the customer login in the
form. On very tall mobile viewports it may sit below the fold; in
practice the form fits in the first scroll, but worth watching in
production analytics.

🤖 Generated with [OpenClaw](https://openclaw.ai)

---------

Co-authored-by: Truck Wash Agent <agent@copenhagentruckwash.local>
2026-08-09 01:58:54 +02:00
Jeppe B 683196ddf5 Gate Fakturer nu on red flag count; expand customer card layout (#264)
## Why

1. The **Fakturer nu** button on the customer card in the superuser faktura-periode "Alle" view was firing even when the customer had multiple red flags — a footgun for superusers (the button shouldn't be one click away from a flagged customer).
2. Each customer card had a fixed `min-height: 68px` on its row and `overflow: hidden` on the identity block, so longer customer names were ellipsised and attribute chips were clipped. The user asked for taller cards with no internal scroll.

## What changed

### Original commit (`da35baa8`)

`src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/views/InvoicingBillingPeriodViewAll.vue`:

- New helper `hasMultipleRedFlags(customer)` — true when `getCustomerActiveFlagCounts(customer).manual >= 2`.
- Button `v-if` now requires `!hasMultipleRedFlags(customer)`.
- When gated, an `is-danger is-light` "Gennemgå flag" tag replaces it so superusers see why.

### Follow-up commit (`8370ba81`) — card layout + chip discoverability

- `.period-customer-card` — `min-height: 9rem`.
- `.period-customer-card__row` — dropped fixed `min-height: 68px`; added explicit `grid-template-rows: auto auto auto auto` + `row-gap: 0.35rem` so the grid stretches naturally.
- `.period-customer-card__identity` — `overflow: hidden → visible`.
- Customer name — added `overflow-wrap: anywhere` so long names wrap instead of clipping.
- Removed internal scroll; the outer list scroll still works.
- Sort billing-type chips deterministically (billing first, operational, review) so chip order is stable regardless of API response shape.
- Add view_friendly_name i18n key for `invoice_per_order`.
- Widen `invoicing-period.smoke.spec.js` mobile card-height tolerance from 3px → 32px (with explanatory comment) for the taller-cards-no-internal-scroll design.

### Follow-up commit (`4f5363fa`) — Playwright strict-mode collision

The chip-mirroring change in the review-detail header shared the same data-testid pattern (`invoicing-period-customer-attributes-{n}`) as the queue card, so the Playwright test failed with `strict mode violation: ... resolved to 2 elements` whenever a flagged customer was selected.

- Added a `scope` prop to `InvoicingBillingPeriodCustomerAttributes` (default `'queue'`, accepts `'review-detail'`). When scope is review-detail, the wrapper and per-chip test-ids are namespaced, so both instances coexist.

## Verification

- `npx eslint` — clean.
- `npm run i18n:v2:check` — pass.
- `vite build` — pass.

## Caveats / follow-ups (out of scope, not blocking)

- `invoicing_period.xlvask_autopilot` — fallback Danish strings ("Gennemgå flag") aren't yet in `invoicingPeriodTranslation.js`.
- Red-flag threshold `>= 2` is hard-coded; promote to a config ref if you want it tunable.
- `InvoicingBillingPeriodCustomerAttributes` still has internal `height: 2.45rem; overflow: hidden` on attribute chips — separate cleanup.

## Risk

- Surface-only CSS + 1 v-if guard; no data shape changes, no API changes, no permission changes. Behaviour change is strictly "Fakturer nu is hidden on multi-flag customers with an explanatory tag in its place".

🤖 Generated with [OpenClaw](https://openclaw.ai)
2026-08-09 00:43:30 +02:00
Jeppe BandJeppe Bundgaard 1548ae8cd5 Add multiple select customer product price recalculation (#261)
Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-08-04 16:13:23 +00:00
Jeppe B fd8b896c56 Add Superuser XL Vask AI automation controls (#260) 2026-08-04 11:46:20 +02:00
Jeppe B d393c8c175 Fix release version credential fallback (#258)
## Summary
- fall back to the existing scoped `RELEASE_MANAGER_GATE_TOKEN` when
`SERVER_UPDATE_TOKEN` is absent
- record the exact frontend SHA through the release-gate endpoint, then
independently read it back
- preserve the legacy dedicated-token path when it is configured
- carry the scoped credential and exact run-attempt build ID through
normal releases, rollback recovery, and restore-on-failure

## Dependency
Depends on backend PR copenhagentruckwash/api#342 being merged and
deployed before this PR is merged.

## Verification
- focused release-gate updater test: 1 passed
- direct exact-SHA update/readback execution passed
- ESLint passed for changed JavaScript/tests
- Prettier passed for both workflows and changed JavaScript/tests
- Node syntax and `git diff --check` passed

The existing broader cPanel release test is also updated; the local
cached dependency set cannot collect that file because `jszip` is
absent, so protected CI remains the full-suite authority.
2026-08-03 14:27:43 +00:00
Jeppe B 668e240e12 Surface XL-Vask autopilot in invoice period (#257)
Publish the revision-aware XL-Vask import status, certainty evidence, bounded run controls, and preview/apply workflow. Automatic production actions remain fail-closed behind backend readiness gates.
2026-08-03 15:46:03 +02:00
Jeppe B 0831d37d3c Stabilize self-serve loading skeleton release gate (#256)
Keep the mocked post-toggle image response pending long enough for every browser shard to observe the loading skeleton deterministically.
2026-08-03 14:31:57 +02:00
Jeppe B f995440098 Align invoicing period review workspace (#251)
Keep review navigation, customer cards, metadata, date labels, and direct order-item tables aligned across desktop and responsive layouts.
2026-08-03 13:45:11 +02:00
Jeppe B 7a5ee1aa5b Fix invoice period tree review findings (#253)
## Summary
- preserve complete snapshot item payloads during inline edits and
reject partial text-field payloads
- force snapshot refreshes after parent/mutation changes with one
bounded recovery retry
- make legacy tree-action fallback create, confirm, and apply a fresh
compatible preview
- keep collection labeling localized and report the correct changed
count

## Verification
- focused object-tree and snapshot suites: 30 tests passed
- focused ESLint and `git diff --check` clean
- production build and selected-customer mocked Playwright flow passed
before final review fixes
- App Store Readiness and Qodana green on exact head; Automated Tests in
progress
- independent QA and reviewer gates: GO

Resolves all inline review threads on the current head.
2026-08-03 13:04:19 +02:00
Jeppe B 3639527b0e Stabilize invoice-period responsive layout assertion (#254)
Wait for WebKit to settle responsive layout boxes before asserting tablet and mobile positioning.
2026-08-03 12:54:45 +02:00
Jeppe B f4816124c2 Complete selected-customer invoice period tree (#252)
Add the complete selected-customer invoice collection tree, revision-bound actions, fallback handling, and focused frontend coverage.
2026-08-03 12:15:55 +02:00
Jeppe B 664b50d4ef Clarify customer login and invoice emails (#250)
Separate Pleno login email from e-conomic invoice email, make the login-email update flow authoritative and cache-safe, clear stale economic profile state, and add focused frontend coverage.
2026-08-03 10:43:53 +02:00
Jeppe B 1768f5a38e Restore invoicing period right rail layout (#249)
## What changed

- restore the desktop invoicing-period category groups as a vertical
right-hand rail
- use a 3:1 content-to-navigation split at desktop widths with
responsive stacking below 1024px
- size the inline month selector to the available content width with a
readable 32rem cap
- adapt the invoice review workspace to the narrower content region
- add unit contracts and browser geometry coverage for desktop, tablet,
and mobile

## Why

The grouped period categories had moved into a wide horizontal row,
leaving unused space on the right and constraining the period content.
The inline month picker was also too small to read comfortably.

## User impact

Superusers again see period categories in the right rail on desktop.
Compact layouts retain responsive navigation, and the review/object-tree
content avoids field wrapping when the main region becomes narrow.

## Validation

- 60/60 focused unit tests
- 30/30 full Chromium desktop invoicing-period browser tests
- 9/9 priority browser tests across Chromium desktop, tablet, and mobile
- ESLint
- production Vite build
- focused Prettier checks
- git diff check
2026-08-03 07:54:53 +00:00
Jeppe B f2453ba0a3 Restore expand-all control in invoice period object tree (#248)
Add a persistent recursive expand/collapse control above collected invoices and keep order-item quantity and price in the canonical editable field layout.
2026-08-03 09:03:57 +02:00
Jeppe B 0149e06c42 Fix invoice period i18n release gate (#245)
Replace unreviewed dynamic invoice-period translation calls with a bounded literal-key contract and focused fallback coverage.
2026-08-03 07:04:48 +02:00
Jeppe B 832b362254 Redesign invoice period review workspace (#244)
Create an exception-first superuser invoice-period review workspace with responsive navigation, complete review states, and regression coverage.
2026-08-02 19:34:31 +02:00
Jeppe B eee9ba1c13 Fix localized forbidden assertions in full E2E (#243)
Use the stable localized forbidden-page contract in customer, subuser, and superuser denial tests while retaining the protected-action and zero-request assertions.
2026-08-02 12:24:36 +02:00
Jeppe B aaecffbdfa Finish Sæby demo registration and limited backoffice (#242)
Unify truckwash.dk Kundeoprettelse and QR traffic on the shared customer page, add protected registration UX, and complete the limited-backoffice demo flow.
2026-08-02 11:50:58 +02:00
Jeppe B 917c10c1d3 Fix mobile Stripe reader recovery action (#241)
## Summary
- keep the mobile Stripe terminal menu in normal document flow so it
cannot cover the reader recovery action
- preserve the existing desktop dropdown overlay behavior
- replace pre-existing dynamic subuser-access translations with direct
literal calls
- explicitly review Bird's bounded health-state translation signatures
in the full view-i18n gate

## Root causes
Exact master CI run `30484128661` exposed two deterministic failures:

1. All three attempts of `reader unavailable state is recoverable and
non-fatal` timed out because the open terminal dropdown intercepted
pointer events on `pos-stripe-no-readers`.
2. The full Chromium desktop/customer view-i18n gate rejected two
bounded Bird health-state signatures plus three pre-existing dynamic
subuser-access calls. The PR matrix did not run this full scanner.

## Verification
- exact Chromium mobile failing scenario: 1/1 passed
- Chromium mobile repeat stress: 5/5 passed
- exact full view-i18n scanner: 1/1 passed
- Bird configuration units: 5/5 passed
- Firefox mobile scenario: skipped by the existing project/browser gate
- ESLint: passed
- test Prettier check: passed
- `git diff --check`: passed
- Vite production build: passed
2026-08-02 09:42:47 +02:00
Jeppe B 14a0d65a01 fix(ci): unblock Bird frontend release (#239)
Link Bird status translations to existing aliases and modernize the POS automatic-capture visual contract with deterministic one-time completion assertions.
2026-07-29 21:22:34 +02:00
Jeppe B 468d436d3e Harden atomic cPanel release recovery (#238)
Adds exact-SHA cPanel release proof v2, atomic rollback restoration, and protected recovery gating.
2026-07-29 20:30:34 +02:00
Jeppe B c85a82b9ac feat(bird): add Control Plane configuration (#237)
Adds a safe Bird Control Plane configuration surface with read-only health, credential redaction, protected capability switches, canonical workspace support, and focused frontend coverage.

Required CI passed on head cc04117d6b against master 3de5215b5e.
2026-07-29 19:19:17 +02:00
Jeppe BandJeppe Bundgaard 3de5215b5e Fix Superuser invoicing registration field layout (#236)
## Summary
- Fixes Superuser invoicing period object-tree registration rows so
`reg_1`, `reg_2`, and `reg_3` labels/values no longer overlap in compact
rows.
- Lets composite registration fields auto-size, wraps long plate values
safely, and spans the full compact grid width on small containers.
- Adds a focused source-level regression contract and committed visual
before/after previews.

## Tests
- `npm run test:unit:single --
tests/unit/superuser-invoices-view.spec.js`
- `npm run build`

## Visual change previews

### View: Superuser invoicing period registration field
**Description:** Registration rows in the invoice-period object tree now
keep each registration label and value on distinct readable rows,
including compact mobile layout.

#### Mobile (390x844)
**Before:**
![Before mobile registration
overlap](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6a733a0023430929a3c130191c8015694f951a86/visual-previews/workboard-4505b9ab-registration-overlap/mobile-before.png)

**After:**
![After mobile registration
layout](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6a733a0023430929a3c130191c8015694f951a86/visual-previews/workboard-4505b9ab-registration-overlap/mobile-after.png)

#### Tablet (768x1024)
**Before:**
![Before tablet registration
overlap](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6a733a0023430929a3c130191c8015694f951a86/visual-previews/workboard-4505b9ab-registration-overlap/tablet-before.png)

**After:**
![After tablet registration
layout](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6a733a0023430929a3c130191c8015694f951a86/visual-previews/workboard-4505b9ab-registration-overlap/tablet-after.png)

#### Desktop (1440x900)
**Before:**
![Before desktop registration
overlap](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6a733a0023430929a3c130191c8015694f951a86/visual-previews/workboard-4505b9ab-registration-overlap/desktop-before.png)

**After:**
![After desktop registration
layout](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6a733a0023430929a3c130191c8015694f951a86/visual-previews/workboard-4505b9ab-registration-overlap/desktop-after.png)

Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-29 14:26:04 +02:00
Jeppe B 5ffd471a45 Exchange one-time login grants in QR flow (#235)
## Summary

Updates the QR login view to consume the short-lived, one-time employee
login grants created by approved Pleno Control Plane
Conversations/Suggestions actions.

- reads generated grants from the URL fragment
- scrubs the bearer from the address bar before exchange
- exchanges the grant for a normal session token, then uses the existing
secure session-storage path
- preserves legacy token QR links
- validates exact URL origin and removes raw credential/QR logging
- prevents repeated scanner exchange attempts while one is in progress

## Visual change previews

No layout or styling changes. The visible flow changes only after
opening or scanning a grant:

- Before: one-time grant links were rejected as unknown QR content.
- After: the existing loader appears during exchange; invalid/expired
grants use the existing localized error dialog; successful grants
redirect through the existing login path.

## Verification

- focused Vitest: 2 passed
- focused ESLint: passed
- production Vite build: passed (existing chunk-size warning only)
- `git diff --check`: passed

## Dependency

Pair with copenhagentruckwash/api (one-time limited-backoffice login
grants) and merge after that backend PR. Required by
copenhagentruckwash/pleno-control-plane#1.
2026-07-29 00:01:22 +02:00
Jeppe BandJeppe Bundgaard 1da6fbd1c4 Remove card payment moms selector (#232)
## Summary
- Removes the visible POS card-payment moms/tax selector from mobile and
desktop Stripe terminal payment flows.
- Keeps Stripe terminal payment intents at 25% moms by default and
updates the POS desktop E2E assertion for the removed selector.

## Testing
- `npx playwright test tests/e2e/pos-desktop-card-payments.spec.js
--project=chromium-desktop --grep "idle state renders grouped terminal
statuses"`
- Visual preview capture on `origin/master`:
`POS_CARD_PAYMENT_PREVIEW_DIR=.../before PLAYWRIGHT_WORKERS=1 npx
playwright test tests/e2e/pos-card-payment-visual-preview.spec.js
--project=chromium-desktop`
- Visual preview capture on this branch:
`POS_CARD_PAYMENT_PREVIEW_DIR=output/visual-previews/after
PLAYWRIGHT_WORKERS=1 npx playwright test
tests/e2e/pos-card-payment-visual-preview.spec.js
--project=chromium-desktop`

## Visual change previews

### View: POS card payment
**Description:** The POS card-payment step no longer shows the moms
selector. The terminal selector and card-payment action remain
available; 25% moms is still reflected in the payment summary where
shown.

#### Mobile (390x844)
**Before:** ![Before mobile POS card
payment](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/de780d857cddcc6f61f728662e21e3a6b73fa791/visual-previews/pos-card-payment-vat/before-mobile-390x844.png)

**After:** ![After mobile POS card
payment](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/de780d857cddcc6f61f728662e21e3a6b73fa791/visual-previews/pos-card-payment-vat/after-mobile-390x844.png)

#### Tablet (768x1024)
**Before:** ![Before tablet POS card
payment](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/de780d857cddcc6f61f728662e21e3a6b73fa791/visual-previews/pos-card-payment-vat/before-tablet-768x1024.png)

**After:** ![After tablet POS card
payment](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/de780d857cddcc6f61f728662e21e3a6b73fa791/visual-previews/pos-card-payment-vat/after-tablet-768x1024.png)

#### Desktop (1440x900)
**Before:** ![Before desktop POS card
payment](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/de780d857cddcc6f61f728662e21e3a6b73fa791/visual-previews/pos-card-payment-vat/before-desktop-1440x900.png)

**After:** ![After desktop POS card
payment](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/de780d857cddcc6f61f728662e21e3a6b73fa791/visual-previews/pos-card-payment-vat/after-desktop-1440x900.png)

Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-28 22:31:26 +02:00
Jeppe B 5204536f92 Fix self-serve settings state and contract (#229)
Ensure department-scoped self-serve settings load and save safely across route transitions, document the API contract, and cover stale in-flight state.
2026-07-28 18:34:35 +02:00
Jeppe BandJeppe Bundgaard 7a84cd9162 Align subuser self-service controls (#234)
## Summary
- Centralize subuser permission capability labels so the grant editor,
table header, and action settings wheel use the same self-service text.
- Add the self-service access control to each grant section in the
subuser action wheel.
- Add focused unit coverage for the shared label helper and subuser
action wheel payload.

## Verification
- `npm run test:unit:single --
tests/unit/subuser-management-labels.spec.js
tests/unit/subuser-grant-permission-nodes.spec.js`
- `npx eslint
src/components/displays/selectors/SubuserGrantPermissionNodes.vue
src/components/displays/superuser/tables/SubusersTable.vue
src/components/session/subuser/subuserPermissionLabels.js
tests/unit/subuser-management-labels.spec.js --quiet`
- `npm run build`

## Visual change previews
### View: Customer subuser management
**Description:** Shows the customer-facing chauffeur table and settings
wheel self-service label aligned to the same wording across table
header, row control, and wheel item.

#### Mobile (390x844)
**Before:** ![Before
mobile](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/wb-550fbd4a-subuser-module/visual-previews/subuser-management/mobile-before.png)
**After:** ![After
mobile](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/wb-550fbd4a-subuser-module/visual-previews/subuser-management/mobile-after.png)

#### Tablet (768x1024)
**Before:** ![Before
tablet](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/wb-550fbd4a-subuser-module/visual-previews/subuser-management/tablet-before.png)
**After:** ![After
tablet](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/wb-550fbd4a-subuser-module/visual-previews/subuser-management/tablet-after.png)

#### Desktop (1440x900)
**Before:** ![Before
desktop](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/wb-550fbd4a-subuser-module/visual-previews/subuser-management/desktop-before.png)
**After:** ![After
desktop](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/wb-550fbd4a-subuser-module/visual-previews/subuser-management/desktop-after.png)

---------

Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-28 17:06:36 +02:00
Jeppe B 4f26ddd2cc Remove manual Stripe payment actions (#233)
## Summary

- remove hosted email-payment/payment-link creation from POS and order
management
- automatically capture authorized card payments instead of exposing a
manual capture action
- replace provider-specific Stripe wording with card-terminal
terminology
- retain ordinary terminal payment, receipts, order completion, and
navigation
- add focused unit and Playwright regression coverage

Paired API change: https://github.com/copenhagentruckwash/api/pull/327

## Verification

- focused frontend unit tests: 8/8 passed
- desktop card-payment Playwright: 4/4 passed
- mobile card-payment Playwright: 15/15 passed
- admin order action rail Playwright: 3/3 passed
- ESLint, translation generation/checks, build, and diff checks passed
- all 18 visual states passed their relevant DOM assertions

## Visual change previews

### View: Regular POS card-payment view

**Description:** The hosted email-payment action is removed and
provider-specific wording becomes generic card-terminal wording.

#### Mobile

**Before:** ![POS card payment before
mobile](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6ea832ae507a81e010b599fb52bdb1b375b5431c/docs/pr-previews/stripe-cleanup/before-pos-card-payment-mobile.png)

**After:** ![POS card payment after
mobile](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6ea832ae507a81e010b599fb52bdb1b375b5431c/docs/pr-previews/stripe-cleanup/after-pos-card-payment-mobile.png)

#### Tablet

**Before:** ![POS card payment before
tablet](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6ea832ae507a81e010b599fb52bdb1b375b5431c/docs/pr-previews/stripe-cleanup/before-pos-card-payment-tablet.png)

**After:** ![POS card payment after
tablet](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6ea832ae507a81e010b599fb52bdb1b375b5431c/docs/pr-previews/stripe-cleanup/after-pos-card-payment-tablet.png)

#### Desktop

**Before:** ![POS card payment before
desktop](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6ea832ae507a81e010b599fb52bdb1b375b5431c/docs/pr-previews/stripe-cleanup/before-pos-card-payment-desktop.png)

**After:** ![POS card payment after
desktop](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6ea832ae507a81e010b599fb52bdb1b375b5431c/docs/pr-previews/stripe-cleanup/after-pos-card-payment-desktop.png)

### View: Authorized payment capture

**Description:** The manual capture action is replaced by an
automatically initiated capture and its in-progress state.

#### Mobile

**Before:** ![Payment capture before
mobile](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6ea832ae507a81e010b599fb52bdb1b375b5431c/docs/pr-previews/stripe-cleanup/before-payment-capture-mobile.png)

**After:** ![Payment capture after
mobile](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6ea832ae507a81e010b599fb52bdb1b375b5431c/docs/pr-previews/stripe-cleanup/after-payment-capture-mobile.png)

#### Tablet

**Before:** ![Payment capture before
tablet](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6ea832ae507a81e010b599fb52bdb1b375b5431c/docs/pr-previews/stripe-cleanup/before-payment-capture-tablet.png)

**After:** ![Payment capture after
tablet](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6ea832ae507a81e010b599fb52bdb1b375b5431c/docs/pr-previews/stripe-cleanup/after-payment-capture-tablet.png)

#### Desktop

**Before:** ![Payment capture before
desktop](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6ea832ae507a81e010b599fb52bdb1b375b5431c/docs/pr-previews/stripe-cleanup/before-payment-capture-desktop.png)

**After:** ![Payment capture after
desktop](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6ea832ae507a81e010b599fb52bdb1b375b5431c/docs/pr-previews/stripe-cleanup/after-payment-capture-desktop.png)

### View: Order-dashboard action rail

**Description:** The hosted Stripe invoice/payment-link action is
removed while normal order actions remain.

#### Mobile

**Before:** ![Order action rail before
mobile](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6ea832ae507a81e010b599fb52bdb1b375b5431c/docs/pr-previews/stripe-cleanup/before-order-dashboard-mobile.png)

**After:** ![Order action rail after
mobile](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6ea832ae507a81e010b599fb52bdb1b375b5431c/docs/pr-previews/stripe-cleanup/after-order-dashboard-mobile.png)

#### Tablet

**Before:** ![Order action rail before
tablet](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6ea832ae507a81e010b599fb52bdb1b375b5431c/docs/pr-previews/stripe-cleanup/before-order-dashboard-tablet.png)

**After:** ![Order action rail after
tablet](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6ea832ae507a81e010b599fb52bdb1b375b5431c/docs/pr-previews/stripe-cleanup/after-order-dashboard-tablet.png)

#### Desktop

**Before:** ![Order action rail before
desktop](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6ea832ae507a81e010b599fb52bdb1b375b5431c/docs/pr-previews/stripe-cleanup/before-order-dashboard-desktop.png)

**After:** ![Order action rail after
desktop](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6ea832ae507a81e010b599fb52bdb1b375b5431c/docs/pr-previews/stripe-cleanup/after-order-dashboard-desktop.png)

## Residual risk

No live Stripe Terminal hardware or production Stripe account was used.
Browser behavior is verified with repository-owned mocks; the API suite
verifies route and payment-intent lifecycle wiring.
2026-07-27 19:09:40 +02:00
Jeppe BandJeppe Bundgaard b7859d4ede Add subuser access and recovery flows (#231)
## Summary

- add chauffeur/subuser password recovery by SMS and authenticated
password changes
- add a read-only pre-authorized access-request preview with explicit
approve/deny actions
- replace duplicate customer grants with one deduplicated Buefy dropdown
- show colored vehicle, toolbox, calendar, order, and driver permission
indicators
- add localized copy across all supported locale sources and generated
catalogs

## Verification

- ESLint passed
- grant deduplication/icon unit tests: 2 passed
- authentication Playwright coverage: 3 passed
- authorized subuser management Playwright coverage: 1 passed
- mocked direct approval browser flow passed
- i18n source/runtime checks passed
- production build passed (2,034 modules transformed)
- `git diff --check` passed

## Paired delivery

Paired API PR: https://github.com/copenhagentruckwash/api/pull/325

## Visual change previews

### View: Forgot-password account selection

**Description:** Visitors can now choose customer or chauffeur recovery;
chauffeur recovery requests the country code and phone number used for
the SMS reset link.

#### Mobile (390 x 844)

**Before:**
![Password recovery mobile
before](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6c515817b934d838ae9d5e0192157c87e1e0a8d5/docs/pr-previews/customer-subuser-notifier/before-mobile.png)

**After:**
![Password recovery mobile
after](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6c515817b934d838ae9d5e0192157c87e1e0a8d5/docs/pr-previews/customer-subuser-notifier/after-mobile.png)

#### Tablet (768 x 1024)

**Before:**
![Password recovery tablet
before](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6c515817b934d838ae9d5e0192157c87e1e0a8d5/docs/pr-previews/customer-subuser-notifier/before-tablet.png)

**After:**
![Password recovery tablet
after](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6c515817b934d838ae9d5e0192157c87e1e0a8d5/docs/pr-previews/customer-subuser-notifier/after-tablet.png)

#### Desktop (1440 x 900)

**Before:**
![Password recovery desktop
before](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6c515817b934d838ae9d5e0192157c87e1e0a8d5/docs/pr-previews/customer-subuser-notifier/before-desktop.png)

**After:**
![Password recovery desktop
after](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6c515817b934d838ae9d5e0192157c87e1e0a8d5/docs/pr-previews/customer-subuser-notifier/after-desktop.png)

### View: Pre-authorized customer access decision

**Description:** The SMS destination now previews the exact chauffeur
and customer request and requires an explicit approve or deny action
before mutating access.

#### Mobile (390 x 844)

**Before:**
![Access decision mobile
before](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6c515817b934d838ae9d5e0192157c87e1e0a8d5/docs/pr-previews/customer-subuser-notifier/access-before-mobile.png)

**After:**
![Access decision mobile
after](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6c515817b934d838ae9d5e0192157c87e1e0a8d5/docs/pr-previews/customer-subuser-notifier/access-after-mobile.png)

#### Tablet (768 x 1024)

**Before:**
![Access decision tablet
before](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6c515817b934d838ae9d5e0192157c87e1e0a8d5/docs/pr-previews/customer-subuser-notifier/access-before-tablet.png)

**After:**
![Access decision tablet
after](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6c515817b934d838ae9d5e0192157c87e1e0a8d5/docs/pr-previews/customer-subuser-notifier/access-after-tablet.png)

#### Desktop (1440 x 900)

**Before:**
![Access decision desktop
before](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6c515817b934d838ae9d5e0192157c87e1e0a8d5/docs/pr-previews/customer-subuser-notifier/access-before-desktop.png)

**After:**
![Access decision desktop
after](https://raw.githubusercontent.com/copenhagentruckwash/pleno-vue/6c515817b934d838ae9d5e0192157c87e1e0a8d5/docs/pr-previews/customer-subuser-notifier/access-after-desktop.png)

---------

Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-27 18:40:43 +02:00
Jeppe B cbdca71e3f Show verified cron worker cadence (#230)
Expose responsive worker cadence proof in the Cron operations dashboard.
2026-07-27 18:16:12 +02:00
Jeppe B 9ff103d4d0 fix(ios): respect status area and persist location consent (#224)
Keep the iOS status bar outside the Capacitor web view and replace startup geolocation watching with silent permission checks plus an explicit location action.

Verified by full unit, App Store readiness, Qodana, production build, Capacitor sync, and Playwright mobile suites.
2026-07-23 19:29:54 +02:00
Jeppe B 32418b42a0 fix(ios): add required location purpose string (#223)
## What changed
- add `NSLocationAlwaysAndWhenInUseUsageDescription` to the iOS app
- localize the purpose string in Danish and English
- enforce the key in mobile permission validation and App Store
readiness tests

## Why
App Store Connect accepted builds 1 and 2 but emitted `ITMS-90683`,
stating that the final app references APIs requiring this purpose
string. Shipping a corrected binary avoids submitting a candidate with a
known Apple delivery warning.

## Validation
- `node scripts/mobile/check-permissions.mjs`
- `node scripts/mobile/validate-app-store.mjs --strict`
- `vitest run tests/unit/app-store-product-readiness.spec.js` (5 tests)
- Prettier check for changed JS files
- `git diff --check`
2026-07-23 16:36:35 +00:00
Jeppe B bf2208e77b fix(ios): use valid Danish TestFlight locale (#222)
Use Apple's supported `da` beta locale and cover the localization/distribution flow with a regression test.

The first signed upload already processed version 1.0.0 build 1 successfully; this fixes the post-processing localization failure before the controlled retry.
2026-07-23 16:50:37 +02:00
Jeppe B 5702d45bc6 fix(ios): harden App Store release automation (#221)
## Summary

- replace the unsupported top-level App Store version collection with
Apple's app-scoped version endpoint
- add tested release-policy and availability readback for exact
version/build, `AFTER_APPROVAL`, Denmark only, no preorder, and no
automatic future territories
- strengthen the stable `App Store Readiness` check and align
Fastlane/candidate handoff with the approved 1.0.0 release policy

## Task contract

`truckwash-ios-release-20260723` — R4 (`ci-policy`, `release-policy`,
`credential-handling`, `branch-protection-or-rules`,
`mobile-store-submission`). The user explicitly approved implementation,
protected-master delivery, and the App Store release path.

## Changed files

- App Store Connect client and dependency-free Node tests
- App Store readiness and candidate workflows
- Fastlane candidate release configuration
- Apple App Store release runbook

## Verification

- `node --test tests/node/app-store-connect.test.mjs` — 10 passed
- `node scripts/mobile/validate-app-store.mjs --strict` — passed
- `node scripts/mobile/check-permissions.mjs` — passed
- App Store product-readiness Vitest — 5 passed
- ESLint on changed Node files — passed
- workflow YAML parsing — passed
- `git diff --check` — passed
- local Fastlane validation unavailable because Ruby/Bundler is not
installed on this host; `App Store Readiness` runs it on GitHub

## Release target

- iOS App Store
- bundle `io.truckwash.app`
- version `1.0.0`
- App Store Connect app `6792777794`
- Denmark only
- automatic release after approval
- no preorder or phased release for 1.0.0

The repository App Store automation switch remains disabled until this
change is merged and credential health is reverified.
2026-07-23 12:59:17 +00:00
Jeppe BandJeppe Bundgaard 41b3be926a test(release): allow full live asset inventory (#218)
## Summary
- give the complete public asset inventory gate a five-minute test
budget
- keep the API and rendered guest-flow smoke tests on the existing
default timeout

## Rationale
The production manifest contains the full release inventory.
Sequentially downloading and hashing it takes longer than the global
60-second Playwright timeout from GitHub-hosted runners, causing false
failures even though release identity, API, and guest-flow checks pass.

## Verification
- ESLint on the changed spec
- Playwright live configuration test discovery

---------

Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-23 02:59:40 +02:00
Jeppe BandJeppe Bundgaard fc67e7cf0b Fix idempotent FTPS directory setup (#217)
The production FTPS deploy now authenticates, but lftp exits when cPanel
returns 550 File exists for pre-created deployment directories. Use lftp
mkdir -p -f for the archive and activation directories so retries remain
idempotent while subsequent upload operations still surface real access
failures.\n\nVerification:\n- vitest tests/unit/cpanel-deploy.spec.js
(26 passed)\n- eslint scripts/release/cpanel-deploy-lib.mjs
tests/unit/cpanel-deploy.spec.js\n- git diff --check

Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-23 00:00:48 +02:00
Jeppe B 9b3c06fc6f fix(release): stream lftp commands directly (#216)
## Summary
- feed the generated lftp command script directly over stdin
- avoid reopening `/dev/stdin`, which fails on the production hosted
runner
- keep FTP credentials out of process arguments

## Verification
- `vitest run tests/unit/cpanel-deploy.spec.js` (26/26)
- ESLint on changed files
- `git diff --check`

Supersedes the failed production release run 29948809036.
2026-07-22 21:24:04 +02:00
Jeppe B f0e3c4812b Require fresh passkeys for passwordless account deletion (#215)
## Summary
- Complete the frontend contract for hardened backend account deletion
(#319).
- For passwordless accounts, request a fresh deletion-specific WebAuthn
challenge and submit its serialized assertion.
- Reuse the existing passkey assertion serializer instead of duplicating
WebAuthn conversion logic.
- Accept the durable `manual_review` backend state while presenting the
existing safe failure copy.

## Verification
- Account deletion unit tests: 9/9.
- Focused ESLint passed for all four changed files.
- Node syntax checks and `git diff --check` passed.

Backend rollout flags remain default-off; this UI is inert until #319
schema checks and explicit API enablement are completed.
2026-07-22 20:05:37 +02:00
Jeppe B 74dd8e3691 Preserve redacted FTPS failure diagnostics (#213)
## Summary
- Preserve lftp stdout/stderr when the process exits non-zero.
- Surface bounded, whitespace-normalized diagnostics through the deploy
error.
- Redact FTPS host, username, password, path, URL userinfo, and encoded
secret forms.

## Verification
- `vitest run tests/unit/cpanel-deploy.spec.js` (25/25)
- `node --check scripts/release/cpanel-deploy-lib.mjs`
- `git diff --check`

This is the prerequisite diagnostic repair for failed Frontend Release
run 29854900889. Production was not switched during that failure.
2026-07-22 19:23:04 +02:00
Jeppe B 7782d93fe9 Gate mobile releases behind explicit phased rollout (#212)
Require explicit mobile-v* tags or manual dispatch, gate exact tested master SHAs, and default Google Play production submissions to an initial 1% in-progress rollout.
2026-07-22 18:51:40 +02:00
Jeppe BandJeppe Bundgaard fd26b0ee81 Package frontend releases without host zip tools (#211)
## Summary

- remove the release packager's undeclared dependency on host `zip` and
`unzip` executables
- create and round-trip validate ZIP artifacts in Node with explicit
paths, permissions, timestamps, CRC checks, and resource limits
- preserve the existing archive filename, checksum, inventory, and
top-level `dist/` contract

## Root cause

After the prebuilt-dist integrity repair passed on master, Frontend
Release reached packaging and failed with `spawn zip ENOENT` on the
self-hosted runner. The workflow never installed or checked either
archive executable.

## Verification

- focused release/deployment tests: 57/57 passed
- packager tests: 9/9 passed, including empty `PATH`, cross-timezone
determinism, exact archive entries, permissions, and oversized
sparse-file rejection
- real production build: 735 files packaged successfully with an empty
`PATH`
- repeated real packaging produced byte-identical archives
- Info-ZIP test/list/checksum validation passed
- extraction under `umask 077`: every directory is `0755`; all 735 files
extracted
- extracted inventory exactly matches the source inventory
- ESLint and Prettier passed

Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-21 16:43:19 +00:00
Jeppe B a01902356d Harden hosted releases and mobile store gates (#205) 2026-07-21 18:17:47 +02:00
Jeppe B a0c11e4bb7 Fix POS release-blocking customer rules and completion (#208)
Resolve direct-order product reconciliation context, preserve valid selections when restricted products are activated, remove the redundant certificate request, and add focused regression coverage.
2026-07-21 15:42:50 +02:00
Jeppe BandJeppe Bundgaard 6dc27a355f Fix privacy fallbacks and mobile safe-area test (#204)
Restore a green `master` baseline before processing the Dependabot
queue.

This PR:
- replaces the accidental Danish privacy-policy fallback in German,
Norwegian, and Swedish catalogs with an explicit English fallback;
- keeps the generated locale catalogs synchronized and preserves the
glossary de-duplication from #203;
- updates the mobile safe-area E2E setup from `/login` to public
`/guest/home`, which renders the header geometry the test asserts.

Verification on the final rebased tree:
- i18n compile/check and all catalog audits
- focused Vitest: 5/5
- Chromium mobile Playwright: 13/13
- Prettier, ESLint, encoding, and `git diff --check`

WebKit remains covered by GitHub CI because the local host lacks its
required runtime libraries.

---------

Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
2026-07-20 19:28:32 +00:00
Jeppe B ed2d67934c Unblock app icon validation in CI (#202)
Format the app icon background test and avoid per-pixel assertion overhead so the full unit suite remains bounded.
2026-07-20 19:20:26 +02:00
Jeppe B 1a959c2ce8 Activate cPanel releases with a pinned account runner (#193)
Replace unsafe legacy Fileman symlink activation with an authenticated, root-owned account runner and crash-safe pointer reconciliation.
2026-07-20 18:20:03 +02:00
Jeppe B 88eda43560 Automate signed iOS App Store releases (#192)
## What changed

- adds production iOS identity, localized storefront metadata, native
privacy declarations, App Store-safe artwork, and account-deletion UX
- mirrors the live Danish Google Play title, short description, and long
description in the App Store metadata source
- generates Android launcher/store icons from the opaque iOS marketing
master so both platforms use the same white background
- adds guarded GitHub Actions workflows for storefront readiness,
credential health, signed TestFlight uploads, and App Store candidate
preparation
- adds pinned Fastlane configuration with a committed dependency lock,
release manifest tooling, and an operational App Store runbook
- preserves the upstream iOS safe-area implementation while retaining
opaque App Store icon assets

## Why

The repository previously supported development-signed device bundles
but had no production App Store identity, reproducible storefront source
of truth, or protected signed-release pipeline. Apple also requires
in-app account deletion for apps that support account creation. The
Android icon master was transparent, which rendered as black on dark
store/device surfaces.

## Impact

Automation remains fail-closed behind
`APP_STORE_AUTOMATION_ENABLED=false`. No build can upload to TestFlight
or change App Store metadata until the switch is deliberately enabled
after merge and the remaining release gates are satisfied.

## Validation

- focused App Store, iOS icon, and cross-platform icon-background tests
pass
- every generated Android store/launcher icon is opaque with pure-white
corners; iOS marketing artwork is checked the same way
- Android icon drift check passes for all 19 generated files
- production Vite build and the broader focused release checks completed
successfully
- storefront metadata is valid; only the two expected screenshot-set
warnings remain
- App Store Readiness is green at head `4445fecc`
- Apple Distribution certificate and App Store profile were
independently verified for `HP3FJ4GVL7.io.truckwash.app`
- live App Store Connect API authentication succeeded for app
`6792777794`
- App Store record, free Denmark-only availability, and automatic
`Internal QA` TestFlight group are configured
- EU trader status, Content Rights, 4+ age rating, and the published App
Privacy label are completed in App Store Connect
- iPhone and iPad accessibility declarations are configured honestly as
pre-release drafts

## Remaining external gates

- reviewed iPhone and iPad screenshot sets are still required
- an App Review login must be supplied without creating or exposing
customer credentials
- the first signed TestFlight candidate must run after merge and
deliberate automation enablement
2026-07-20 17:59:43 +02:00
Jeppe B 133e53cfa6 Fix iPhone mobile header safe-area spacing
Add iOS safe-area viewport support and inset-aware mobile header/spacer
sizing so controls clear the notch or Dynamic Island. Desktop and tablet
headers remain unchanged. Includes focused unit and mobile browser
regression coverage.
2026-07-20 16:16:56 +02:00