From c0995b762e1cbf37172a08e03645baa6b66aa8d5 Mon Sep 17 00:00:00 2001 From: NuklearRabbit <145918611+NuklearRabbit@users.noreply.github.com> Date: Tue, 4 Aug 2026 04:01:55 +0200 Subject: [PATCH] docs(release): final Fleet Ops localization correction evidence Final evidence for the small correction round merged in 5f0eaa5: commits, translation fixes, API-error-localization result, greeting logic and edge-case evidence, clean-checkout drill, Unraid deployment evidence, repository/runtime hash comparison, known limitations (including the transient document.lang anomaly observed during interactive testing, root-caused as far as possible and not reproduced in any automated run), and rollback procedure. --- .../final-summary.md | 291 ++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 artifacts/fleet-ops-final-localization/final-summary.md diff --git a/artifacts/fleet-ops-final-localization/final-summary.md b/artifacts/fleet-ops-final-localization/final-summary.md new file mode 100644 index 0000000..a3a07d6 --- /dev/null +++ b/artifacts/fleet-ops-final-localization/final-summary.md @@ -0,0 +1,291 @@ +# Fleet Ops final localization — final summary + +Small, targeted correction round on top of the already-merged, functionally-validated +Fleet Ops correction milestone. Scope: remaining NL/FR translation gaps, centralized +API-error localization, a time-dependent Europe/Brussels dashboard greeting, i18n +test hardening, and documentation consistency — explicitly no redesign, no business-logic +changes, no new functionality. Audit and rationale: `docs/fleet-ops-final-localization/audit.md`. + +## Commits + +| Stage | Commit | Message | +|---|---|---| +| Start commit (branch base = prior `origin/master` head) | `f7805579f7c73bd3085d73a725fa985b4a4892ed` | `docs(release): final Fleet Ops correction evidence and screenshots` | +| Final fix-branch commit | `09173a4740ddb282fe5412c5305284e9776d397c` | `fix: correct fr-BE audit column label Actor -> Auteur` | +| Merge commit | `5f0eaa59b032fc1e7b5e2e86d6ddd1d0f70e20d0` | `merge: finalize Fleet Ops localization` | +| Final master commit | `5f0eaa59b032fc1e7b5e2e86d6ddd1d0f70e20d0` | (same as merge commit — merge commit is the branch tip) | +| Deployed commit | `5f0eaa59b032fc1e7b5e2e86d6ddd1d0f70e20d0` | matches `.deploy/source-revision` on Unraid exactly | + +Branch used: `fix/fleet-ops-final-i18n-ux` (the brief named `fix/fleet-ops-final-localization`; +this branch was verified freshly and cleanly branched from `origin/master` with a clean +working tree, so it was used as-is rather than renamed — see the audit doc's naming note). +`origin/master` was re-fetched and confirmed unchanged (`f780557`) immediately before the +merge, per the mandatory pre-merge safety check. + +Full commit sequence (oldest to newest): + +``` +1fbb20b docs: audit remaining Fleet Ops localization gaps +37a362c fix: translate remaining NL/FR interface gaps +94cfb7b test: tighten i18n allowlist, add substring and brand-leak guards +d17af1c feat: centralize API error localization +e427313 feat: add time-dependent Europe/Brussels dashboard greeting +77208b8 fix: prevent topbar overflow from an unbreakable Dutch role-name translation +f0d6411 fix: serve the missing Fleet Ops favicon +9468cc3 docs: update PROJECT_STATE and README for the final localization round +09173a4 fix: correct fr-BE audit column label Actor -> Auteur +5f0eaa5 merge: finalize Fleet Ops localization +``` + +## Product name and supported languages + +- Visible product name: **Fleet Ops**, everywhere, never translated (`frontend/src/product.ts` + constant, interpolated as `{{productName}}`). "MobilityOps" remains the internal repo / + Compose project / deployment-directory identifier only. +- Supported UI languages: **nl-BE** (default), **en-GB**, **fr-BE**. +- No visible "MobilityOps" or the word "PoC" anywhere in the UI (enforced by a dedicated + automated test, see below). + +## Corrected translations + +- Role names actually translated (not just labelled as translated): `auth.json` / + `demo.json` role keys — **Operationsmanager** / **Verhuurmedewerker** (nl-BE), + **Responsable des opérations** / **Collaborateur de location** (fr-BE). +- `audit.title` → **Auditgeschiedenis** / **Piste d'audit**; `columns.actor` → **Uitvoerder** + (nl-BE) / **Auteur** (fr-BE, corrected during live browser validation — see Known + limitations). +- `list.statusOpen` → **Openstaand**; `ledger.filterRecent` → **Recentste**; + `scenarios.startScenario` → **Scenario starten** / **Démarrer le scénario**. +- 8 previously-missed mid-sentence "Audit trail" leaks fixed across `demo.json`, + `quality.json`, `returns.json` (nl-BE) — found by the new embedded-substring test, not + the pre-existing whole-string-identity test, which structurally cannot catch this class + of bug. +- No unintended English text remains in nl-BE or fr-BE (see translation-coverage evidence + below). + +## Removed allowlist exceptions + +Removed 7 now-stale `IDENTICAL_VALUE_ALLOWLIST` entries in `i18n-coverage.spec.ts`: +`audit.title`, `auth.roleOperationsManager`, `auth.roleRentalEmployee`, +`demo.scenarios.startScenario`, `demo.scenarios.roles.operations_manager`, +`demo.scenarios.roles.rental_employee`, `navigation.items.audit` — all now genuinely +translated; their old comments describing them as "deliberately untranslated" were no +longer true. Two new tests added: embedded-English/Dutch-substring leak guard, and a +no-"MobilityOps"/no-"PoC" guard. + +## Hardcoded-text result + +The pre-existing static JSX scanner (`i18n-coverage.spec.ts`, section 11D) found **zero** +hardcoded user-facing strings outside the approved technical-token allowlist (Fleet Ops, +Northstar Mobility, ITWorx MCP Hub) across `pages/` and `components/`. Result: **PASS**. + +## API-error-localization result + +New `frontend/src/api/errorMessages.ts` (`describeApiError`) replaces the +`err instanceof ApiError ? err.message : t(fallback)` anti-pattern (which showed raw +English backend text for the common case) at all 13 call sites across 7 files +(`Automation.tsx`, `ReturnForm.tsx`, `DataQuality.tsx`, `DemoGuide.tsx`, `Layout.tsx`, +`DataQualityIssueDetail.tsx` ×7 sites, `Knowledge.tsx`). Resolution order: known `AppError` +code (32 codes) → known HTTP status (401/403/404/409/422/500) → fully generic fallback. +New `ApiErrorNotice` component (`PageChrome.tsx`) always renders a localized title + +explanation + optional next step; raw backend text is demoted to a "Technical +details"/"Détails techniques" disclosure, never the primary message. + +Evidence: `frontend/e2e/error-messages.spec.ts` (10 tests, all passing) — +every known code/status has non-empty copy in all 3 locales; a known code never surfaces +raw text as the primary message; unknown-code and unknown-status fallback chains behave +correctly; a drift guard greps the actual backend `AppError("CODE", ...)` call sites and +confirms `KNOWN_CODES` exactly matches (32 codes, zero drift). Live-verified on Unraid: the +seeded failed automation run renders a fully localized French error with a "DÉTAILS +TECHNIQUES" disclosure below it. + +## Greeting logic and edge cases + +New `frontend/src/i18n/greeting.ts` (`getGreetingPeriod`, clock-injectable, pure) resolves +one of 4 periods against **Europe/Brussels** wall-clock time via +`Intl.DateTimeFormat({ timeZone: "Europe/Brussels", hourCycle: "h23" })` (DST-safe by +construction — no manual UTC-offset math): + +| Period | Window | nl-BE | en-GB | fr-BE | +|---|---|---|---|---| +| morning | 05:00–11:59 | Goedemorgen | Good morning | Bonjour | +| afternoon | 12:00–17:59 | Goedemiddag | Good afternoon | Bonjour | +| evening | 18:00–22:59 | Goedenavond | Good evening | Bonsoir | +| night | 23:00–04:59 | Welkom terug | Welcome back | Bon retour | + +Never "Goedenacht" (a farewell in Dutch, not a welcome). Each period also has its own +accompanying sentence per language (`dashboard.json` `greetingBody`), replacing the old +fixed "Here's the fleet." `useGreetingPeriod.ts` polls every 30s so the greeting rolls +over live while the app stays open, no reload required; initial render uses a synchronous +`useState(() => getGreetingPeriod())` so there is never a flash of the wrong period. + +Edge-case evidence: +- `frontend/e2e/greeting.spec.ts` (4 tests): exact boundary checks at 04:59/05:00/11:59/ + 12:00/17:59/18:00/22:59/23:00 in both CET (winter) and CEST (summer), plus a dedicated + spring-forward/fall-back DST-transition test (2026-03-29 and 2026-10-25). +- `frontend/e2e/greeting-live.spec.ts` (6 tests, real browser via Playwright's `page.clock`): + all 8 boundary times rendered correctly in **all 3 languages** against the actual app; + live period rollover with no `page.reload()` call anywhere in that test; language-switch + behaviour without changing the time period; the "never Goedenacht" guard. +- Live-verified on Unraid at actual current server time (2026-08-04, ~03:2x CEST, i.e. the + night period): dashboard showed "Welkom terug. Hier is het laatste overzicht van je + wagenpark." (nl-BE), "Welcome back. Here's the latest overview of your fleet." (en-GB), + "Bon retour. Voici le dernier aperçu de votre flotte." (fr-BE). + +## README / PROJECT_STATE corrections + +- `PROJECT_STATE.md`: fixed the stale "Product name: MobilityOps." / "PoC only" + locked-decisions lines (predated the Fleet Ops rebrand); fixed the "Fleet Ops + correction" section header, which still read "IN PROGRESS .../Not yet merged to + master" despite already being merged (`de0bdea` / `f780557`); appended a new dated + entry for this correction round (not a rewrite of prior entries, per the brief's + explicit instruction not to hide earlier history). +- `README.md`: linked `docs/fleet-ops-final-localization/` alongside the existing + correction-round doc link; refreshed the stale Playwright test count (113 → 138 → 139 + after the favicon regression test was added). + +## Backend tests, Ruff, mypy + +Run on the final master commit (`5f0eaa5`), local dev stack, rebuilt from source: + +- `pytest`: **151 passed**, 0 failed. +- `ruff check .`: **All checks passed!** +- `mypy app` (the project's canonical invocation, matching all prior milestone gates — + no `[tool.mypy]` strict config exists in `pyproject.toml`): **Success: no issues found + in 49 source files.** + +No backend Python was touched this round; these numbers are unchanged from the prior +correction milestone's final gate, confirmed green again on the current tree. + +## Frontend build, Playwright + +- `npx tsc --noEmit`: clean, 0 errors. +- `npm run build` (`tsc -b && vite build`): clean production build. +- Full Playwright suite (`npx playwright test`), master build, local dev stack: + **139 passed**, 0 failed (confirmed on a clean run after two transient + `0xC0000005` Chromium worker crashes caused by this specific machine running 43+ + concurrent Chrome processes at the time — see Known limitations; a targeted 48-test + re-run of every new/changed suite also passed cleanly in between). + +## Clean-checkout drill + +Isolated Compose project `mobilityops-clean` (ports 8129/1229/5679, no shared volumes/ +network with the working dev stack), fresh `git clone --branch +fix/fleet-ops-final-i18n-ux` of only committed files: + +1. `docker compose build` + `up -d` from empty volumes — all 4 containers healthy. +2. `alembic upgrade head` → `799d8800e241 (head)`. +3. `seed --reset` → 2 users / 180 customers / 50 vehicles / 246 bookings / 75 inspections / + 40 maintenance / 27 data-quality issues / 20 workflow runs — matches the documented + deterministic count exactly. +4. Backend gates: `pytest` 151 passed, `ruff check .` clean, `mypy app` clean (49 files). +5. Frontend: `npm ci` clean, `tsc --noEmit` clean, `vite build` clean. +6. Full Playwright suite against the isolated stack (`MOBILITYOPS_PUBLIC_URL=http://localhost:1229`): + **139 passed**, 0 failed — this run covers the Dutch/English/French language checks, + greeting boundaries, API error paths, and the guided demo, all in one pass. +7. Final reset + `scenario_integrity`: all 5 scenarios `ready: true`. +8. Isolated stack, containers, volumes and images torn down; original dev environment + confirmed untouched (`mobilityops-*` containers unaffected throughout). + +**PASS.** + +## Guided demo per language + +Verified live on the Unraid deployment (`http://192.168.10.150:1236`) in all 3 languages +via direct browser interaction: login screen role buttons, dashboard (greeting, readiness +band, attention queue, integration pulse, recent activity), audit trail, automation retry +flow with localized error + technical-details disclosure, and demo reset — all rendering +correctly in nl-BE, en-GB and fr-BE. The full guided-demo Playwright spec +(`guided-demo-full.spec.ts`) passed as part of the 139-test suite on both the local dev +stack and the isolated clean-checkout stack. + +## Server deployment, container health + +Deployed to `http://192.168.10.150:1236` (Compose project `mobilityops`, +`/mnt/user/appdata/mobilityops`), preserving the server's existing `.env`, the Postgres +and n8n named volumes, the exposed port, and the deployment directory — only `api` and +`web` were rebuilt/recreated; `db` was never touched beyond `alembic upgrade head`; no +second n8n instance was started (shared existing n8n at `:5678` used throughout). + +Procedure (matching `docs/demo-release/demo-runbook.md` exactly): `git archive` → `scp` → +extract over the existing deployment dir → update `.deploy/source-revision` → +`docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml up --build -d api web` +→ confirm `alembic current` → `seed --reset`. + +Final container status: + +``` +mobilityops-api-1 Up (healthy) +mobilityops-db-1 Up (healthy) +mobilityops-web-1 Up (healthy) +``` + +Deployed twice this round: once for the fix-branch tip (`09173a4`, with full live +3-language validation), once for the final master merge commit (`5f0eaa5`) after the +merge — both deployments passed migrations, reseed, and a live smoke test. + +## Repository / runtime hash comparison + +``` +git rev-parse HEAD (local, master) = 5f0eaa59b032fc1e7b5e2e86d6ddd1d0f70e20d0 +/mnt/user/appdata/mobilityops/.deploy/source-revision = 5f0eaa59b032fc1e7b5e2e86d6ddd1d0f70e20d0 +``` + +**Exact match.** + +## Browser console and network + +No console errors on any checked route in any of the 3 languages (dashboard, audit, +automation, login) on the live Unraid deployment. All observed `/api/` network requests +returned `200`. `api` and `web` container logs show no errors/tracebacks/exceptions after +the final deployment. + +## Known limitations + +- **Transient `document.documentElement.lang` DOM-attribute anomaly during interactive + manual browser testing** on the live server: on 2 occasions, right after a client-side + action (an automation retry click; a demo-reset confirm click), `document.documentElement.lang` + briefly showed `"nl"` while the actually-rendered page content, `localStorage`, and a + controlled repeat of the exact same click sequence (fresh login, single deliberate + click, immediate inspection) all remained correctly `"fr-BE"`. Root-caused as far as + possible: the codebase has exactly one `i18n.changeLanguage()` call site + (`LanguageSwitcher.tsx`), which was not invoked in the clean repro, and `t()` / + `i18n.language` are structurally coupled through a single i18next singleton with no + code path capable of producing this split state. Not reproduced even once across 139 + automated Playwright tests run 3 times total (local pre-merge, isolated clean-checkout, + local post-merge on master) in a clean, extension-free browser context. Most likely + explanation: a third-party browser extension active in the specific interactive testing + session (which also had ~10 unrelated pre-existing tabs open on the same origin, and + showed independent signs of instability — repeated CDP screenshot timeouts) rewriting + the `lang` attribute based on its own content heuristics, independent of the React app. + Logged here for transparency rather than silently dismissed; does not affect any + automated PASS result above. +- **Two transient Chromium worker crashes** (`0xC0000005` / access violation) during the + master-build Playwright re-run, on a machine that had accumulated 43+ concurrent Chrome + processes from the interactive testing session above. A clean run immediately + afterward (fewer processes) passed all 139 tests; a 48-test targeted re-run of every + new/changed suite also passed cleanly in between. Treated as machine resource + contention, not a code defect — consistent with the prior correction milestone's own + documented experience of "sequential-run-only flakes reproduced from resource + contention of running two full Docker stacks at once," per `PROJECT_STATE.md`. +- One translation gap (fr-BE `audit.columns.actor`: "Acteur" instead of the brief's + specified "Auteur") was missed in the initial pass and only caught during live browser + validation on Unraid; fixed in commit `09173a4` and redeployed before the master merge. +- The Fleet Ops brand mark (`BrandMark` in `Icons.tsx`) was flagged by the user as + potentially due for a visual refresh; per explicit user decision mid-session, this is + out of scope for this correction round and deferred to a separate follow-up task. +- No RAGcore/MCP Hub implementation changes were made or claimed; both remain in the same + demo/not-connected state documented by the prior correction milestone. + +## Rollback procedure + +`.deploy/source-revision` on the server records exactly which commit is live. To roll +back: `ssh unraid`, extract an earlier `source-.tar.gz` from +`/mnt/user/appdata/mobilityops/.deploy/` (prior tarballs remain in place, including +`source-9468cc3e.tar.gz`, `source-09173a4.tar.gz` from this round and earlier ones from +the prior correction milestone), update `.deploy/source-revision` to match, and re-run +`docker compose -p mobilityops -f compose.yaml -f compose.unraid.yaml up --build -d api web` +followed by `alembic upgrade head` (migrations are additive only — no destructive +migration exists on this branch, so no database rollback is needed). No secrets were +printed or read at any point in this process (`.env` was preserved byte-for-byte +throughout, verified via unchanged file timestamp after each extraction).