When a flagged wash has no metadata in the api flag-context payload, the
user previously saw 'No XL Vask details available.' with no indication
of why. This adds a clearer fallback message and adds unit coverage for
that scenario.
Resolves TRU-8 (AUT-4).
Co-authored-by: Jeppe <jeppe@copenhagentruckwash.io>
Co-authored-by: openhands <openhands@all-hands.dev>
## Summary
TRU-19 reports that the wash programs **FF Uvs** and **10min** are
unresponsive on the Pleno bay while **SF** works. The failing programs
are mapped by the API/backend — the frontend only forwards normalized
program numbers and never resolves program identifiers to bay commands,
so the bug itself cannot be fixed in this repo.
This PR adds two frontend-side smoke checks that lock the contract
with the wash bay so the next reader (or the next agent) cannot go
down the same dead end and so any future contract drift surfaces as a
deliberate test failure rather than a silent UI regression:
1. **Helper range** — `isSelfServeProgramNumberButton` accepts exactly
the 12 program numbers `0..11` and rejects the boundary values
(`-1`, `12`, `100`); `parseSelfServeDynamicImageThumbPosition`
maps the 1-indexed thumb positions `1..12` to the same range and
rejects `0`, `13`, and non-numeric / nullish input.
2. **Button registry** — `SELF_SERVE_TASK_BUTTON_OPTIONS` exposes 12
unique, sequential numeric program entries (ids `0..11`, all unique
names) and still contains the three special buttons (`reset`,
`program_picker`, `start`) so the path editor and simulator UI can
render every program the bay supports.
## Why this is the right frontend-side fix
- The frontend never sees the program identifiers (`FF Uvs`, `10min`,
`SF`) — those live in the API repo's machine mapping table. Trying
to "fix" the unresponsiveness here would only mask the bug.
- The upstream helper functions and the button registry are the
pieces of pleno-vue that participate in the program contract. Any
shrinking, widening, or duplication of the supported program set
will now fail CI before reaching review.
- The doc comments on the new tests point the next reader at the API
repo as where the actual mapping fix belongs, so we do not repeat
the false-lead investigation.
## Commits
- `743d4e4` test(self-serve): smoke check program number range (0-11)
and thumb position (1-12)
- `0f3b125` test(self-serve): smoke check button registry has 12
unique sequential programs
## Tests
- `npx vitest run tests/unit/self-serve-dynamic-image.spec.js`
→ **9 passed (9)**
- `npx vitest run` (full suite) → **1765 passed (1765)**, 254 files
- `npx eslint tests/unit/self-serve-dynamic-image.spec.js` → clean
- `git diff --check HEAD~2..HEAD` → no whitespace/conflict-marker
issues
## Issue
- Linear: TRU-19 (`AUT-15: pleno-vue + api — wash programs "FF Uvs"
and "10min" are unresponsive; "SF" works`)
- This PR closes the frontend-side portion of the bug. The actual
program mapping fix must be applied in the `api` repo.
> _This PR description was generated by an AI agent (OpenHands) on
> behalf of the user._
---------
Co-authored-by: Jeppe <jeppe@copenhagentruckwash.io>
## Summary
- `XLVaskUsagePagination` now accepts a `departmentId` prop and, when
set (or when the route's `departmentId` param is set), constrains the
`/modules/xlvask/services/usage/orders` request to that department via a
`HallId` filter.
- Previously the request went out unfiltered, so the Selvvask tab could
return washes for unrelated departments (or none, depending on the
endpoint contract).
- `DepartmentPosSync` forwards the URL department id to the pagination
via the new prop. The superuser `InvoicingBillingPeriodViewSelfWash`
view is unaffected because it does not pass `departmentId`.
## Test plan
- [x] `tests/unit/xlvask-usage-department-filter.spec.js` locks the prop
declaration, the HallId filter wiring, the route-param fallback, the
negative path, and the consumer contract.
- [x] `npm run lint` is clean.
- [x] `npm run format:tests:check` is clean.
- [x] `npm run text:check-encoding` is clean.
- [x] Full vitest suite: 255 files / 1768 tests pass.
Refs: TRU-11 (AUT-7)
Co-authored-by: openhands <openhands@all-hands.dev>
## Summary
The `invoicing_period.xlvask_review.*` and
`invoice_period.flags.preview.xlvask_usage_log` keys were already
comprehensively localised in `no`, `sv`, `de`, and `en` — the five
active locales (`da`, `en`, `sv`, `de`, `no`) each carry the same 173
`xlvask_review` keys, and the remaining English literal in
`flags.automatic.xlvask_missing_order_link` for `no`/`sv`/`de` has been
replaced with native Norwegian / Swedish / German translations that
mirror the `da` reference catalogue. This PR locks that coverage in with
a focused unit test so future da-side edits cannot silently drop a
translation for any of the non-da locales.
## Commits
- `49ff30f` — TRU-13 / AUT-9: translate xlvask_missing_order_link to
no/sv/de
- `550d32c` — TRU-13 / AUT-9: add xlvask_review translation coverage
regression test
## Verification
- `npm run i18n:v2:check` — clean across source-check,
global-template-audit, template-dedupe-audit, and word-audit.
- `npx vitest run tests/unit/i18n-xlvask-review-coverage.spec.js` — 1 /
1 pass (5 locales × 173 keys verified).
- `npx vitest run tests/unit/i18n-*` — 11 / 11 pass.
- `npx playwright test tests/e2e/i18n-v2-integrity.spec.ts` — 126 / 126
pass across all 9 browser projects.
The new test in `tests/unit/i18n-xlvask-review-coverage.spec.js`
enumerates every leaf string under
`compat.invoicing_period.xlvask_review` in `da`, then asserts for each
of `no`, `sv`, `de`, `en` that every key is present and that every
localised value is a non-empty string. Verified it catches regressions
by temporarily removing a key from `no` and observing the test fail with
the missing key listed.
Closes TRU-13.
---------
Co-authored-by: Jeppe <jeppe@copenhagentruckwash.io>
Co-authored-by: openhands <openhands@all-hands.dev>
## Summary
Mirror `xlvask_review` keys (under `invoicing_period.xlvask_review.*`)
and `invoice_period.flags.preview.xlvask_usage_log` keys from the `da`
source into the `global` i18n fallback so other locales (no, sv, de, en)
resolve them instead of falling through to raw key paths.
The implementation was already correct in the source tree:
- `src/i18n/source/global/shared/invoicing_period/xlvask_review.json`
mirrors all 173 da keys via `@:{}:` linked references.
- `src/i18n/source/global/shared/invoice_period/flags.json` mirrors
`flags.preview.xlvask_usage_log` keys (and adds a shared
`no_xlvask_usage_log` alias).
- All 5 locales (da, en, sv, de, no) carry the same 173 `xlvask_review`
keys, so the global fallback resolves cleanly for every active locale.
This PR adds two regression tests in
`tests/e2e/i18n-v2-integrity.spec.ts` that lock in the mirror so future
da-side edits cannot silently drop the fallback for non-da locales.
## Commits
- `0c0bd7c` — i18n(test): lock in xlvask_review mirroring to the global
v2 fallback
- `6ed2f8d` — i18n(test): lock in xlvask_usage_log flag mirroring to the
global v2 fallback
## Verification
- `npm run i18n:v2:check` — clean across source-check,
global-template-audit, template-dedupe-audit, and word-audit.
- `npx playwright test tests/e2e/i18n-v2-integrity.spec.ts` — 126 / 126
pass across all 9 browser projects, including the two new regression
tests.
Closes TRU-9.
---------
Co-authored-by: Jeppe <jeppe@copenhagentruckwash.io>
Co-authored-by: openhands <openhands@all-hands.dev>
Fixes AUT-8 (TRU-12).
## Problem
The Selvvask (XLVask) usage pagination in `XLVaskUsagePagination.vue`
calls `/modules/xlvask/services/usage/orders`, an endpoint that ships in
the frontend ahead of the backend implementation. While the backend is
still being built, the route responds with HTTP 404 — which
`paginatedList` was treating identically to any other error and
surfacing through the generic `ShowErrorField` popper. Operators hitting
the Selvvask tab during that window saw an alarming failure message
instead of an honest "not implemented yet" notice.
## Fix
- **`src/components/pagination/paginatedList.vue`**: introduce a
reactive `lastError` ref on the composable that captures `{ status,
endpoint, message }` from the most recent failed `paginatedGetRequest`
call. The ref is reset on success, left untouched for cancelled
requests, and exposed both on the composable's return and on the global
instance so other surfaces can opt into status-aware error handling.
Non-cancelled failures continue to populate the existing global error
store via `parseError`.
-
**`src/components/displays/pagination/models/DepartmentPos/XLVaskUsagePagination.vue`**:
derive an `apiEndpointNotImplemented` computed that is `true` only when
`lastError.status === 404` *and* the failing endpoint matches this
component's own `/modules/xlvask/services/usage/orders`. A watcher on
that computed clears the matching `paginatedGetRequest` entry from the
global error store, and the template renders a friendly, info-styled
Bulma notice between `PaginationDisplay` and `ShowErrorField`. Non-404
errors (500, network failures, etc.) still fall through to the generic
popper unchanged.
## i18n
New `errors.api_endpoint_not_implemented` key under
`invoicing_period.xlvask_review`, added to all five source locales (en,
da, de, no, sv) plus the global shared template alias, then regenerated
into the i18n v2 runtime files via `npm run i18n:v2:compile`. Each
locale now reads as a real translation rather than a raw key in the UI.
## Tests
- `tests/unit/superuser-invoices-view.spec.js`: two new contract
assertions verify that `XLVaskUsagePagination.vue` references
`apiEndpointNotImplemented`, `lastError`, the `404` literal, the new
i18n key path, the `xlvask-api-not-implemented` test id, and the
`removeError("paginatedGetRequest")` call; the locale assertion confirms
the key resolves in every supported locale.
- `tests/unit/xlvask-usage-pagination-404.spec.js` (new): four runtime
cases against `usePaginatedList` cover the success path, 404 capture,
reset on the next request, and skip-on-cancel.
- Full unit suite: **1768/1768 pass across 255 files**.
- ESLint clean on all four modified files plus the new spec.
- `npm run i18n:v2:check` (source check, global-template audit, template
dedupe, word audit) all green.
- No new dependencies; uses existing `vue` (`ref`, `computed`, `watch`),
`axios` response shape, and `vue-i18n`.
## Files changed (16 files, +176 / -16)
- `src/components/pagination/paginatedList.vue` (+14 / -3)
-
`src/components/displays/pagination/models/DepartmentPos/XLVaskUsagePagination.vue`
(+23 / -3)
-
`src/i18n/source/{en,da,de,no,sv}/phrases/compat/invoicing_period/xlvask_review.json`
(+3 each / -2 each, net +1 per locale)
- `src/i18n/source/global/shared/invoicing_period/xlvask_review.json`
(+1)
- `src/i18n/generated/{en,da,de,no,sv}-v2.json`, `global-v2.json`
(regenerated)
- `tests/unit/superuser-invoices-view.spec.js` (+24)
- `tests/unit/xlvask-usage-pagination-404.spec.js` (new, +90)
Logic-only frontend change; no visual diff beyond a single info notice
(which is the entire point of the fix). No backend API endpoint touched.
_This PR was created by an AI agent (OpenHands) on behalf of the
OpenSymphony autonomous workflow._
Co-authored-by: Jeppe <jeppe@copenhagentruckwash.io>
Co-authored-by: openhands <openhands@all-hands.dev>
Fixes AUT-6 (TRU-10).
## Problem
When a user clicked an `xlvask_usage_log` token from a flag in the
InvoicingPeriod flag list, `openXlVaskUsageLog(flag)` navigated to:
```
/invoices?activeTab=period&periodView=self_wash&xlvaskUsageLogId=<id>
```
`InvoicingBillingPeriodViewSelfWash.vue` already read
`route.query.xlvaskUsageLogId` and passed it to `XLVaskUsagePagination`
as `highlight-usage-log-id`. However, the date range was hardcoded to
`dates.computed.formattedStartDate.value` /
`dates.computed.formattedEndDate.value` — the parent period view's
range. If the flag was emitted for a wash **outside** that range (e.g. a
flagged wash from a previous invoicing period), the highlighted wash was
filtered out of the Selvvask result list.
## Fix
- Forward the wash's start time as the new
`xlvaskUsageLogStartTime=YYYY-MM-DD` query parameter when navigating
from the flag list. The `extractDateOnly` helper in
`InvoicingPeriodFlagList.vue` accepts both `flag.context.start_time` and
`flag.start_time`, and tolerates the full ISO-8601 form (e.g.
`2026-04-28T08:15:00`).
- `InvoicingBillingPeriodViewSelfWash.vue` reads
`route.query.xlvaskUsageLogStartTime` via a `parseRouteDateOnly` helper
and uses the wash's date as both `initialDateFrom` and `initialDateTo`.
When the parameter is absent (e.g. direct navigation to Selvvask), the
view falls back to the parent period's range, preserving existing
behaviour.
## Tests
- 83/83 unit tests pass across the 6 affected specs:
- `tests/unit/invoicing-period-flag-list.spec.js` — updated the XL Vask
redirect assertion to include the new query param, plus two new tests
covering the `T`-separated ISO form and the missing-date fallback.
- `tests/unit/superuser-invoices-view.spec.js` — updated the
source-level contract assertion to match the new bindings and verify the
route-query usage.
- `tests/unit/invoicing-period-flag-badge.spec.js`,
`invoicing-period-flag-badge-buefy.spec.js`,
`invoicing-period-view-totals.spec.js`,
`order-content-table-flags.spec.js` — all unchanged, still green.
- ESLint clean on all four modified files.
- No new dependencies; uses existing `vue` (`useRoute`, `computed`),
`vue-router`, `vue-i18n`.
## Files changed (4 files, +87 / -5)
-
`src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/components/InvoicingPeriodFlagList.vue`
(+14)
-
`src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/views/InvoicingBillingPeriodViewSelfWash.vue`
(+18 / -2)
- `tests/unit/invoicing-period-flag-list.spec.js` (+50 / -1)
- `tests/unit/superuser-invoices-view.spec.js` (+5 / -2)
Logic-only frontend filter adjustment; no visual change (so no
before/after screenshots required) and no backend API endpoint touched.
_This PR was created by an AI agent (OpenHands) on behalf of the
OpenSymphony autonomous workflow._
Co-authored-by: Jeppe <jeppe@copenhagentruckwash.io>
Co-authored-by: openhands <openhands@all-hands.dev>
## Summary
Adds a desktop-only Playwright `@smoke` test that proves the
click-to-navigate
contract for the `xlvask_missing_order_link` flag token: clicking the
`xlvask_usage_log` token in a flagged customer row opens a popup
targeting the
Selvvash (`self_wash`) view of the period dashboard with the usage-log
ID
preserved as a query parameter.
## Test scope
`tests/e2e/xlvask-flag-to-selvvash-navigation.spec.ts` (172 lines, 1
commit):
- Builds an `xlvask_missing_order_link` flag fixture pointing at
usage-log `55`
on customer `4001` inside the `2026-07-01..2026-07-31` period.
- Loads
`/superuser/invoices?activeTab=period&startDate=2026-07-01&endDate=2026-07-31&periodView=all`
with a primed `GET /superuser/invoicing/period**` route so the flag row
is
visible.
- Clicks the flag's `.invoice-period-flag-token` and waits for the
`page.waitForEvent("popup", …)` promise.
- Asserts the popup URL matches
`/superuser/invoices?activeTab=period&periodView=self_wash&xlvaskUsageLogId=55`
(the URL actually built by `openXlVaskUsageLog` in
`InvoicingPeriodFlagList.vue` via `redirectTo.superUser(..., true)`).
- Skips on non-desktop projects via `isDesktopProject(testInfo)`.
- Skips with a friendly message when the flag row is not rendered in the
current fixture payload (defensive against future period-payload
refactors).
## Acceptance-criteria mapping
| Acceptance criterion | Coverage |
| --- | --- |
| Click xlvask usage-log flag opens Selvvash tab | Popup URL asserted to
carry `periodView=self_wash` and `xlvaskUsageLogId=55` |
| Correct date range | Test runs inside the `2026-07-01..2026-07-31`
period so the source page is in the correct date context (the popup URL
itself does not currently preserve dates — see note below) |
| Highlighted wash | Wash `55` is the `xlvask_usage_log_id` propagated
via the popup URL (`highlightOrderItem` is appended inside the view, not
at the navigation entry point, so it is asserted at the URL level rather
than the DOM level) |
### Note on the source-code contract
`openXlVaskUsageLog` builds the destination URL with
`activeTab=period&periodView=self_wash[&xlvaskUsageLogId=…]` only — it
does
not propagate `startDate`/`endDate` from the originating page. The
Selvvash
view rendering depends on those dates (see
`Right.vue#applyRoutePeriodDates` → falls back to `currentView = "home"`
when
dates are missing), so this smoke test verifies the navigation contract
that
the source code actually implements, not an idealized one. Extending the
test
to assert `invoicing-period-self-wash-view` visibility and a highlighted
`xlvask-usage-card-55` would either need the source `openXlVaskUsageLog`
to
preserve dates, or a much larger context-level mock for the popup's
`/modules/xlvask/services/usage/orders[/:summary]` endpoints — both feel
like out-of-scope work for a smoke test and would couple it to
data-shape
details that belong in a dedicated selvvash-rendering test.
## Validation
- `npx playwright test
tests/e2e/xlvask-flag-to-selvvash-navigation.spec.ts
--project=chromium-desktop` → **1 passed (3.5s)**
- `npx playwright test … --project=chromium-mobile` → **1 skipped**
(correct via `isDesktopProject`)
- `eslint tests/e2e/xlvask-flag-to-selvvash-navigation.spec.ts` → clean
- `prettier --check
tests/e2e/xlvask-flag-to-selvvash-navigation.spec.ts` → "All matched
files use Prettier code style!"
- `git diff --check HEAD~1 HEAD` → clean
## How to run
```sh
npx playwright test tests/e2e/xlvask-flag-to-selvvash-navigation.spec.ts --project=chromium-desktop
```
The test is tagged `@smoke`, so it is also picked up by the existing
smoke
slice in `scripts/run-playwright-full-slice.mjs`.
## Related
- Source page:
`src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/components/InvoicingPeriodFlagList.vue`
- Source trigger: `openXlVaskUsageLog()` →
`SessionUser.functions.redirectTo.superUser(...)` (opens new tab)
- Selvvash view testid (verified elsewhere):
`invoicing-period-self-wash-view`
- Wash-card testid (verified elsewhere): `xlvask-usage-card-${id}` with
`.is-highlighted`
---------
Co-authored-by: Jeppe <jeppe@copenhagentruckwash.io>
Co-authored-by: openhands <openhands@all-hands.dev>
2026-08-15 20:51:20 +02:00
12 changed files with 6 additions and 16 deletions
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.