## 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.
Ensure department-scoped self-serve settings load and save safely across route transitions, document the API contract, and cover stale in-flight state.
Fix the location-permission source catalog nesting and regenerate all active v2 locale files so the App Store release matrix resolves the new compatibility aliases correctly.
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.
## 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`
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.
## 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.
Scopes rollback to actual public or credentialed live-gate failure.
Release Manager recording becomes non-blocking post-deploy observability
and records only the API check because the mandatory Playwright gate
already verifies the exact full static inventory. This avoids the
duplicate synchronous static verification that exceeded the API proxy
timeout and rolled back a verified release.\n\nVerification:\n- git diff
--check\n- release.yml parsed with the checked-in YAML dependency\n-
credentialed role gate without secrets: 2 skipped, exit 0
Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
Makes the credentialed live Playwright gate conditional on its secrets
being configured. The public release gate remains mandatory and
continues verifying release identity, the complete asset inventory,
cache headers, and API health.\n\nVerification:\n- git diff --check\n-
PLAYWRIGHT_BASE_URL=https://truckwash.io Playwright @role-live gate: 2
skipped, exit 0
Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
## 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>
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>
## 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.
## 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.
## 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.
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.
## 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>
## Summary
- serve the release workflow's already-built `dist` from the production
Playwright gate
- preserve the existing auto-build behavior for standalone local
production tests
- keep the pre/post `dist` inventory guard strict and unchanged
## Root cause
The release workflow built and fingerprinted `dist`, but Playwright then
launched `preview:prod`, which ran a second Vite build. Timestamped
build metadata changed hashed chunks and caused the integrity comparison
to fail after all 26 production browser tests had passed.
## Verification
- production Playwright gate: 26/26 passed
- pre/post inventory: 735 files, zero changes
- release package validation: 735 files passed
- ESLint passed
- Prettier passed
- release package unit tests: 7/7 passed
- workflow YAML parsed successfully
- no test files changed
Co-authored-by: Jeppe Bundgaard <jb@truckwash.dk>
Replace finite dynamic view translations with explicit literal-key mappings so the i18n integrity gate can verify locale coverage without changing rendered copy or tests.
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>
## Summary
- add the privacy-policy compatibility source to German, Norwegian, and
Swedish so every active locale has the same runtime key set
- reuse existing English and German glossary tokens so the raw v2
catalog keeps its word-deduplication invariant
- regenerate the affected runtime locale catalogs
## Verification
- `playwright test tests/e2e/i18n-v2-integrity.spec.ts
--project=chromium-mobile` (12 passed)
- `vitest run tests/unit/app-store-product-readiness.spec.js` (5 passed)
- i18n source compile check and all three catalog audits
- Prettier, encoding, and `git diff --check`
Align generated artwork with the published Truck Wash storefront, add strict iPhone and iPad App Store screenshots, and complete signed iOS release automation.
## 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
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.
Replace the iOS AppIcon variants with the exact Android launcher artwork
from `public/favicons/web-app-manifest-512x512.png`, so the physical
iPhone home-screen icon matches Android. Verified all declared icon
dimensions and focused iOS icon test.
## What changed
- load customer product rules for the booking customer and keep
selection fail-closed while they resolve
- prevent restricted products, add-ons, pickup, and wash certificates
from remaining selected or being submitted
- improve product-grid containment, selected add-on styling, unavailable
messaging, and tablet/mobile rendering
- preserve booking details when the API rejects stale restricted items
- add unit and Playwright coverage for loading, retry, exact
restrictions, automatic products, layout, and stale backend recovery
## Why
The customer booking flow could use stale or unrelated POS customer-rule
state, expose restricted items, and lose useful form state after a
server rejection. The desktop product area also overflowed and switched
to an inconsistent compact layout after selection.
## Validation
- desktop booking Playwright suite: 12 passed
- mobile booking Playwright suite: 4 passed, 8 expected desktop-only
skips
- relevant unit tests: 35 passed
- ESLint and i18n checks
- production Vite build
## Related backend PR
The coordinated API PR enforces the same rule at the write boundary.
Align restricted desktop and mobile POS controls, scope the Danish primary registration label to POS, and preserve generic registration wording across shared surfaces and other locales.
## Purpose
After-activation canary for repository ruleset `19051697`.
## Evidence before activation
- preparation PR #172 established stable `Required CI`
- strict-current-base remediation PR #174 passed `Required CI` and
Qodana
- merged master run `29501928124` completed with 43 successful jobs and
one intentional skip
- effective master rules were audited after activation
## Canary acceptance
- merge is blocked while `Required CI` is pending
- the exact `Required CI` check comes from GitHub Actions integration
`15368`
- only squash merge is accepted
- the branch is deleted automatically after merge
Keep expanded period rows on InvoicingPeriodObjectTree while rendering order-scoped flags in a compact expanded flag list. Update the invoicing-period E2E fixtures and source contract for the object-tree path.
Target the system status heading in the delayed session bootstrap test so Playwright strict mode does not match both the navigation link and page heading.
Add locale glossary entries for the cron due label and link the source alias through terms.glossary.due so generated runtime links resolve across active locales.
Redirect legacy QR signup routes for new customers and drivers to the shared /kundeoprettelse customer creation page.
Validated with focused eslint and QR Playwright coverage for mobile and desktop.