Compare commits

..

11 Commits

Author SHA1 Message Date
cesnimda d6d3dd977d test(redesign): establish preservation baseline
Map 57 capability groups before restructuring. Add behavioral regression coverage and a standalone TypeScript gate; retain the native SQLite package failure as separate baseline debt.
2026-09-10 12:53:06 +02:00
cesnimda 2d0f131a99 docs(redesign): complete Phase 1 audit checkpoint 2026-09-10 07:22:09 +02:00
cesnimda fc749b1a30 docs(release): record browser crash and operator handoff 2026-09-10 07:09:16 +02:00
cesnimda 32adac74f3 chore(audit): retain release retry evidence 2026-09-10 00:25:22 +02:00
cesnimda 27657f3113 docs(release): record verified runner dependency corruption
CI and Deploy / test (push) Failing after 7m25s
CI and Deploy / deploy (push) Has been skipped
2026-09-10 00:24:05 +02:00
cesnimda 957fb5740f docs(audit): reconcile baseline with verified release repair 2026-09-10 00:19:23 +02:00
cesnimda 68a371c8ed chore(audit): retain verified release dependency correction 2026-09-10 00:18:19 +02:00
cesnimda a861ca4275 fix(deps): patch release-blocking frontend advisories
CI and Deploy / test (push) Failing after 3m11s
CI and Deploy / deploy (push) Has been skipped
2026-09-10 00:18:10 +02:00
cesnimda 0856ea64d6 docs(audit): record full Linux backend verification 2026-09-10 00:07:31 +02:00
cesnimda 0e3eab08ec Merge verified release portability fixes 2026-09-09 19:35:49 +02:00
cesnimda 2024c374ac fix(test): preserve TLS and PDF checks across Linux runners
CI and Deploy / test (push) Failing after 3m8s
CI and Deploy / deploy (push) Has been skipped
2026-09-09 19:35:47 +02:00
25 changed files with 1338 additions and 195 deletions
+7
View File
@@ -48,6 +48,8 @@ jobs:
&& install https://dotnetcli.azureedge.net/dotnet )
fi
echo "$DOTNET_ROOT" >> "$GITHUB_PATH"
# Native test apphosts need the custom runtime location in later steps too.
echo "DOTNET_ROOT=$DOTNET_ROOT" >> "$GITHUB_ENV"
"$DOTNET_ROOT/dotnet" --info
- name: Setup Node
@@ -215,6 +217,11 @@ jobs:
echo "Frontend lint process crashed with SIGSEGV — retrying once..."
npm run lint
- name: Typecheck frontend
working-directory: job-tracker-ui
# Include application and test sources; Jest transpilation is not type checking.
run: npm run typecheck
- name: Test frontend
working-directory: job-tracker-ui
# Run the WHOLE suite. Never whitelist test files here again: the previous
@@ -335,7 +335,9 @@ public sealed class JobImportHttpTransportTests
{
var error = await Assert.ThrowsAsync<HttpRequestException>(request);
Assert.IsType<AuthenticationException>(error.InnerException);
Assert.Null(server.Request); // No HTTP data sent before TLS authenticates the origin.
// TLS rejection can surface as EOF after the server handshake on Linux.
// Both no read and a zero-byte read mean no HTTP data reached this origin.
Assert.True(string.IsNullOrEmpty(server.Request));
}
Assert.Equal("jobs.example.test", sni);
Assert.Null(handler.SslOptions.RemoteCertificateValidationCallback);
@@ -326,3 +326,79 @@ Local verification: `C:/Users/Connor/.dotnet/dotnet.exe test JobTrackerApi.Tests
[Run 739](https://git.cesnimda.uk/cesnimda/jobtrackingapp/actions/runs/739/jobs/1613), for `23cc250`, failed earlier in the unchanged API compiler: `csc.dll` exited 139 on both the ordinary and existing single-process retry. This is the previously documented runner crash signature, not a compile diagnostic or test assertion. It prevented Linux confirmation of the packaging correction. No retries/gates were weakened. Fresh local broader verification with the explicit SDK passed **915/915** selected backend tests, no selected skips, with only the same documented external-DNS-dependent IMAP exclusion; `.artifacts/redesign-audit/backend.trx` records the run.
Pushing this evidence provides one further complete CI attempt with unchanged application/test logic. If the existing bounded retry is exhausted again, stop release attempts and obtain runner-host diagnostics under the [runner investigation](../../../infrastructure/runner-investigation.md); do not increase retry loops indefinitely or deploy past a red test gate. Phase 1 redesign audit is separately committed on `codex/ui-redesign-audit`, with no redesign implementation. F04 remains open regardless of CI outcome until its runtime/production checklist is proven.
[Run 740](https://git.cesnimda.uk/cesnimda/jobtrackingapp/actions/runs/740/jobs/1615) recovered from the compiler issue, confirmed the Linux fixture **build** fix, and executed all 916 backend cases: **903 passed, 13 failed**. These were actionable portability failures rather than another compiler crash: two TLS-rejection cases recorded an empty HTTP read instead of null, and eleven PDF process cases could not start the .NET fixture (exit 131 or ensuing start-marker timeout). The workflow installed .NET in `/root/.dotnet`, but exported `DOTNET_ROOT` only inside the setup step; PATH alone does not supply the runtime location to a native apphost ([Microsoft runtime-location documentation](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-environment-variables#dotnet_root-dotnet_rootx86-dotnet_root_x86-dotnet_root_x64)).
The correction persists the already-validated runtime path through `GITHUB_ENV`. The TLS assertion accepts null **or an empty string**, while still requiring `HttpRequestException` with `AuthenticationException`, original-host SNI, no custom validation callback and zero HTTP request content. It does not accept whitespace or any HTTP bytes, trust a bad certificate or alter the transport. Fresh explicit-SDK Release verification with `DOTNET_ROOT` set: **98/98** `PdfRenderAdmissionTests|JobImportHttpTransportTests` pass; `.artifacts/redesign-audit/portability.trx`. No application code or test filters changed. The earlier stop rule applies to repeated compiler crashes, not evidence-backed correction of these now-observed test/setup defects.
### Release dependency gate — 2026-09-10 local / 2026-09-09 UTC
[Run 741](https://git.cesnimda.uk/cesnimda/jobtrackingapp/actions/runs/741/jobs/1617), `2024c37`, confirmed **916/916 Linux backend tests passed, zero skipped**. Compose/safety/parser/build checks also passed. Deployment was then skipped because `npm audit --audit-level=high` found three vulnerable package entries: Next critical, sharp high and js-yaml high. A fresh local audit reproduced the same report. These advisories were updated in the public database on September 8; earlier audit results are historical, not proof of the current dependency gate.
The targeted patch raises the Next and matching lint-config floor to 16.3.3 (lock resolves 16.3.4), updates the existing sharp override from 0.35.3 to 0.35.4, and updates the transitive js-yaml 3.x lock to 3.15.2. Its 4.x copy already uses patched 4.3.2. Lockfile review shows only these dependency families and their required platform/compiler artifacts changed. React, MUI, routing, API and schema contracts were not changed.
Primary advisory evidence: [Next Windows-hosted server issue](https://github.com/advisories/GHSA-p293-qw3h-jr36), [Next AVIF optimization issue](https://github.com/advisories/GHSA-2xp9-vwfh-vxw4), [sharp/libheif](https://github.com/advisories/GHSA-rgj7-g3m4-5g8c), [js-yaml merge budget](https://github.com/advisories/GHSA-2883-xcg3-v3hh). The intended production frontend is a static nginx export, not a Windows Next server or a running Next image optimizer; the advisories alone do not establish production exploitation. Build/development dependencies still fall within the existing release policy, so the gate is retained and patched instead of suppressed.
Initial commands: `npm install --package-lock-only --ignore-scripts --no-audit --no-fund`, then `npm update js-yaml --package-lock-only --ignore-scripts --no-audit --no-fund`; `npm audit --package-lock-only --audit-level=high` reports **zero vulnerabilities**. Actual installation, frontend regression/build and new CI results follow separately; a lockfile-only audit is not runtime verification.
Fresh installation with `npm ci --no-audit --no-fund` succeeded (820 packages); installed-tree `npm audit --audit-level=high` also reports **zero vulnerabilities**. Next's updated lint preset introduced `no-location-assign-relative-destination`, which assumes Next Router. This static SPA uses React Router and deliberately performs full navigation after account deletion to clear private state. That one inapplicable navigation rule is disabled with a configuration comment; both existing full-navigation calls and B02 cleanup remain unchanged.
The patched Next build compiled application code but then exposed the historical `interview-prep.test.tsx:120` TS2571 as a build blocker. The smallest test-only correction uses `objectContaining({ content: expect.not.stringMatching(...) })` on the unknown Axios request argument. The existing positive assertion still requires the exact endpoint/category/title and a string containing the debrief prompt; the negative assertion still rejects invented company/achievement/metric content. No cast, type exclusion, strictness relaxation or production behavior change was introduced. R01a's explicit standalone CI command remains outstanding; its original reproduction is retained.
Fresh local release verification after these changes:
| Command (UI directory unless noted) | Result |
|---|---|
| `npm test -- --watchAll=false --runInBand` | **67 suites / 298 tests passed**, 102.468s, after the dependency update |
| `npm test -- --watchAll=false --runInBand src/interview-prep.test.tsx` | **13/13 passed** after the assertion correction |
| `node node_modules/typescript/bin/tsc --noEmit --incremental false` | **Exit 0** after correction; original error reproduced before it |
| `npm run build` with `NEXT_PUBLIC_API_BASE_URL=http://localhost:5302/api` and telemetry disabled | **Exit 0**, Next 16.3.4 static export, including TypeScript |
| `npm run test:e2e` with `PLAYWRIGHT_STATIC_EXPORT=true`, `PLAYWRIGHT_PREBUILT_EXPORT=true`, explicit installed `DOTNET_HOST_PATH`/`DOTNET_ROOT` | **18/18 passed**, 1.4m; Release API build zero warnings/errors. Includes B02 account isolation, B05 exact CSV bytes, anonymous public CV and long searchable PDF |
| `npm run lint` | **Exit 0**, zero warnings after the rule compatibility adjustment; one redundant concurrent run raced Playwright's temporary `test-results` cleanup (ENOENT), so final lint is run serially |
| Root: `python scripts/supply-chain.py scan-secrets`; `git diff --check` | **Pass**; line-ending notices only |
Logs are under `.artifacts/redesign-audit/`. The latest full Linux backend run remains **916/916 at `2024c37`**; no backend source changed in this dependency correction. Deployment still requires the next full CI run and production preflight. This is authorized release repair, not Phase 2 redesign or B07; **F04 remains open**.
### Runner dependency corruption after the patch
[Run 742](https://git.cesnimda.uk/cesnimda/jobtrackingapp/actions/runs/742/jobs/1619), `a861ca4`, again passed **916/916 backend tests**, no skips (16s), repository/parser/build checks, locked frontend installation and the dependency audit. Lint stopped before analyzing application code with exit 2: installed `es-abstract/2025/RegExpCreate.js:19` contained `var"flags`, causing `SyntaxError: Invalid or unexpected token`. Subsequent gates and deployment were skipped.
Independent verification downloaded the exact `es-abstract` **1.24.2** tarball URL from the lockfile, verified its SHA-512 integrity against that lock and read the member without extracting it. The official member and locally installed file match byte-for-byte; line 19 is `var flags = typeof F === 'undefined' ? '' : ToString(F);`. This is evidence of different dependency bytes in the runner's failure, not a source lint violation or a need to edit the dependency. The corruption mechanism is unproven; the existing [runner investigation](../../../infrastructure/runner-investigation.md) documents analogous failures.
This evidence-only push makes **one further unchanged-code CI attempt** with the workflow's existing isolated install cache. No new retry loop, lint suppression or gate bypass is added. If corrupted dependency bytes or assertion-free process crashes recur, stop release retries and request runner-host diagnostics; do not generate repeated empty commits. Public `/health` still reports version **299**, `/ready` reports ready before this attempt; that is the existing deployment, not proof that `a861ca4` is live. The completed Phase 1 audit is pushed separately at `957fb57` on `codex/ui-redesign-audit`.
### Final release retry — 2026-09-10
[Run 743, test job 1621](https://git.cesnimda.uk/cesnimda/jobtrackingapp/actions/runs/743/jobs/1621), for `27657f31131cd95d42f0e1290853ec9c9b3959a4`, completed in **7m25s**; Gitea recorded the failed status at **2026-09-09 22:32:25 UTC** (September 10 locally). Repository safety/Compose, extension, parser, secret/SBOM, backend build and dependency checks passed. The full backend suite passed **916/916**, zero skipped (15s test duration); frontend passed **67/67 suites, 298/298 tests** (82.867s). Locked installation, npm audit, lint and the production frontend build all passed. The previous malformed `es-abstract` source did not recur at lint.
The browser gate failed: **6 passed, 12 failed** (1.4m). The first failure was `worker process exited unexpectedly (code=null, signal=SIGSEGV)` in the authenticated accessibility case; its existing retry timed out in `beforeEach` after 30 seconds. The remaining eleven failures report `page.goto: Target page, context or browser has been closed`, including after their existing retries. There is no reported accessibility violation or application assertion mismatch in these failures. This establishes a process/browser execution failure, but does **not** establish its underlying host, runtime or hardware cause. The local **18/18** browser result above remains separate evidence and cannot replace the failed CI gate.
[Deploy job 1622](https://git.cesnimda.uk/cesnimda/jobtrackingapp/actions/runs/743/jobs/1622) was **skipped**. At **2026-09-10 05:07:21 UTC**, read-only HTTPS requests still returned `/health` → `{"status":"ok","version":"299"}` and `/ready` → `{"status":"ready"}`. The requested commits are pushed to main, but this release has **not deployed**. No production service was replaced by this run.
The documented stop condition is now reached: **no further unchanged-code release retries or gate bypasses**. Capture runner diagnostics before another attempt. This result is recorded on `codex/ui-redesign-audit` so that documenting the block does not itself start another main deployment. Phase 1 is complete; Phase 2 and B07 have not started. **B06 implementation complete, but F04 remains open**, for the runtime/production reasons in the closure checklist above as well as the unshipped release.
#### Runner evidence to collect before another attempt
Run these read-only commands on the actual Ubuntu **runner host**, which must be identified independently of the application host. No SSH alias or credentials are assumed. Use the run's timestamps for a bounded kernel-log window; the example covers this observed attempt. Do not reset Docker, delete caches, alter limits or restart production services just to obtain evidence.
```bash
date -u
uname -srmo
free -h
df -h / /tmp /dev/shm
df -i / /tmp
docker ps -a --no-trunc --format '{{.ID}} {{.Names}} {{.Image}} {{.Status}}'
docker stats --no-stream --format '{{.Name}} memory={{.MemUsage}} cpu={{.CPUPerc}} pids={{.PIDs}}'
sudo journalctl -k --utc --since '2026-09-09 22:20:00 UTC' --until '2026-09-09 22:40:00 UTC' --no-pager \
| grep -Ei 'segfault|oom|out of memory|killed process|I/O error|machine check|hardware error|EDAC'
# If systemd-coredump is installed: list metadata only, not memory dumps.
command -v coredumpctl >/dev/null && \
coredumpctl --since '2026-09-09 22:20:00 UTC' --until '2026-09-09 22:40:00 UTC' --no-pager list
# Select a verified runner/job container ID from the inventory, if it still exists.
read -r -p 'Observed runner/job container ID: ' runner_container_id
test -n "$runner_container_id" && docker inspect --format \
'{{.Name}} image={{.Image}} state={{json .State}} memory={{.HostConfig.Memory}} memorySwap={{.HostConfig.MemorySwap}} pidsLimit={{.HostConfig.PidsLimit}} shmSize={{.HostConfig.ShmSize}} ulimits={{json .HostConfig.Ulimits}}' \
"$runner_container_id"
```
Return selected, sanitized output plus the job-container identity/image and exact failure window. **Do not provide environment dumps, runner registration/config files, tokens, core files, full Docker inspection or unrelated logs.** A removed job container or unavailable kernel/core records is an evidence gap, not a clean bill of health. Host free memory after completion does not prove the earlier job's cgroup or shared-memory capacity. OOM/cgroup failures, segfault records, or storage/hardware errors would direct the next investigation; absent records do not justify guessing a cause. Retain the existing failed-job browser trace/error-context artifacts if available, keeping synthetic test data separate from production information. A successful full CI run, actual deploy completion and live version/health verification are still required before calling the push live; F04 then additionally requires its independent ingress evidence.
@@ -32,7 +32,7 @@ The numbered order is the default integration order. Independent work may start
| Order / ticket | Size and ownership | Concrete scope and acceptance | Dependencies / rollback risk |
|---|---|---|---|
| **R01a Standalone type gate** | S. `interview-prep.test.tsx`, UI package script, CI workflow. | Narrowly type/narrow the mocked request payload, add explicit standalone type checking to CI. Prove 13 interview tests, standalone tsc, and the changed workflow command. Keep strictness and test coverage. | Can run in parallel with Batch 0. No production-code behavior or schema. Do not weaken the gate to preserve a green build. |
| **R01a Standalone type gate — partially resolved during authorized release repair, 2026-09-10** | S. `interview-prep.test.tsx`, UI package script, CI workflow. | The patched Next build exposed the historical TS2571. A test-only object matcher now preserves the positive/negative content assertions without dereferencing an unknown Axios payload; 13 interview tests, standalone tsc and Next build pass. An explicit standalone CI command remains outstanding; keep strictness and test coverage. See [release evidence](b06-completion.md#release-dependency-gate--2026-09-10-local--2026-09-09-utc). | Can run in parallel with Batch 0. No production-code behavior or schema. Do not weaken the gate to preserve a green build. |
| **R01b Production DB configuration validation** | S. Provider-selection code in `Program.cs` and startup tests. | Fail startup for an unknown production provider or missing required configured connection. Preserve deliberate dev SQLite defaults and valid explicit SQLite/MariaDB. Assert no fallback database is created on rejected startup. | Integrate after Batch 0 `Program.cs` mutations. No schema; rejecting an existing invalid environment can prevent boot, so validate deployed inputs first. |
| **R02 Consistent recovery-set creation** | M. `deploy.sh`, existing backup runner/scripts, isolated fixture and operator documentation. | Replace live SQLite tar as the DB consistency mechanism; coordinate file inventory/checkpoint or a short write pause, manifest/checksums and incomplete-set marking. Preserve keys/tombstone recovery. For MariaDB, coordinate the file set with the consistent SQL dump. | No new app schema. Can develop synthetic fixture in parallel with Batch 0. A backup that is only present is not yet verified. Keep previous verified sets. |
| **R03 Restore drill and schema-compatible rollback** | M. Deploy rollback logic, isolated restore harness and runbook; no production restore in the implementation session. | Restore latest/previous synthetic sets under active-source writes; integrity, FK owner boundaries, attachment hashes, provider-token decryptability and deletion replay must hold. Run previous/new binaries against the intended schema; allow automatic image rollback only when compatible. Record measured RPO/RTO, required key custody and operator steps. | Depends on R02 and Connor's recovery objectives. **Blocks every schema-changing ticket below.** Failed restore is a stop, not permission to reset the source DB. Do not run downgrade migrations automatically. |
+2
View File
@@ -40,3 +40,5 @@ AI should support this flow naturally and contextually. It must never invent fac
These documents define the intended target state. They should be updated as the remaining functional work is completed. The full redesign implementation should begin only after a stable pre-redesign baseline has been created and protected by tests.
2026-09-09: [Phase 1 source audit](audit/README.md) completed on `codex/ui-redesign-audit`: current routes, protected functionality, component/test/API inventories, identity/storage boundaries and proposed migration gates. No redesign implementation or B07 started. Required release checks and F04 production verification remain separate from this audit's completion.
2026-09-10: [Final Phase 1 summary and scope classification](audit/PHASE1_SUMMARY.md) completes the audit checkpoint: 57 protected capability groups, 31 route patterns, existing-versus-new distinctions, preservation contract, baseline defects/tests and the recommended Phase 2 baseline session. This finalization changes documentation only. B06/F04 runtime and production closure remain open; no Phase 2+ work has started.
@@ -2,6 +2,8 @@
This is the current source-backed preservation baseline. **All rows are Preserve** until a later approved migration explicitly records relocation, merger or deprecation. Target-only ideas are separated at the end. A component's existence proves implemented behavior, not live provider availability or usability quality. The [API census](API_CALL_SITES.md) and [component census](COMPONENT_INVENTORY.md) supply the individual call sites/consumers behind these groups.
There are **57 protected capability groups**. The [completion summary](PHASE1_SUMMARY.md) classifies existing behavior, UX improvements, new requirements and known defects separately. A preserve-and-improve classification never removes a capability from this contract; known defective behavior is not the intended behavior to preserve.
## Entry, navigation and account
| ID | Current user-facing function | Source / important contract |
@@ -57,8 +59,8 @@ This is the current source-backed preservation baseline. **All rows are Preserve
| C03 | CV/file upload and extraction, rebuild/improve/reprocess, progress/history, diff, explicit low-confidence selection, accept/discard, retry/cancel | `CareerProfilePage`, `/profile-cv/*`; uploaded proposal is not silently canonical truth |
| C04 | Evidence bank: sourced statement/title/category/tags, add/list/delete and truthfulness guidance | `CareerEvidenceBank`; evidence belongs to profile owner and is not AI-invented experience |
| C05 | CV library create/name/copy/delete, template gallery and real sample thumbnails, publish/unpublish/share and PDF download | `CvBuilderPage`, `cvBuilder`; separate public slug from numeric private ID |
| C06 | Studio Template/Content/Customize/AI Tools/History navigation; debounced preview and save state | `CvBuilderEditor`; existing mobile edit/preview and desktop split must retain parity |
| C07 | Built-in item visibility/order, custom sections, preset/free-text section catalog, item/content overrides, hide/remove, keyboard reorder | `CvBuilderEditor`, `components/cv/*`; selected profile source and overrides remain distinguishable |
| C06 | Studio Template/Content/Customize/AI Tools/History navigation; debounced preview, page boundaries/automatic fit and save state | `CvBuilderEditor`; existing mobile edit/preview and desktop split must retain parity; automatic fit is not a manual zoom control |
| C07 | Built-in item visibility/order, custom sections, preset/free-text section catalog, item/content overrides, hide/remove, native section drag and keyboard reorder | `CvBuilderEditor`, `components/cv/*`, `useDragReorder`; selected profile source and overrides remain distinguishable |
| C08 | Template-capability-aware typography/colors/header/headings, skills presentation/groups, one/two-column and sidebar settings, photo/icons | `CvCustomizeTab`, theme registry; retain unsupported-setting handling and server render equivalence |
| C09 | A4/Letter, density, margins/spacing, date format, document language, per-section rows/grid/compact/bubbles and columns | `cvBuilder`, `CvCustomizeTab`, content controls; do not drop advanced controls merely to simplify layout |
| C10 | Undo/redo of editor settings, save queue, unsaved guard, history/version restore and preview lifecycle | `CvBuilderEditor`; current queue serializes one mounted editor only, not concurrent browsers |
@@ -89,10 +91,12 @@ This is the current source-backed preservation baseline. **All rows are Preserve
| A02 | Admin audit event list/detail, supported undo and job restore | `AdminAuditPage`; preserve event/ownership semantics rather than generic mutation buttons |
| A03 | Separate system health/build/database/AI/email status, SMTP settings/test, AI probe, CV corpus benchmark and maintenance run controls | `AdminSystemPage`; sensitive config values/logs must not move into personal dashboard |
## Target brief items that are not current features
## Relationship to the target brief
The target grouped navigation would reorganize an existing collapsible sidebar. Jobs/Table and Kanban can become view choices over the existing APIs. Dedicated Analytics/Documents/Notifications destinations would relocate embedded capabilities. They are not permission to delete offers, reminders, correspondence review, operations or legacy URLs.
Dashboard drag/order/resize/reset and cross-device persistence are new work beyond the five local booleans. A general assistant/chat, goals system, new brand/name and additional email/calendar providers are not implied by existing contextual AI or integrations. Retain implemented Google/Outlook calendar actions even though the product brief describes broader calendar ambitions as future work.
Dashboard drag/order/resize/reset are new work beyond the five local booleans; cross-device persistence is an unresolved scope choice. Optional goals, a new logo and expanded contextual access are separately identified additions; a general chat platform, product rename or additional providers are not implied by existing AI/integrations. Retain implemented Google/Outlook calendar actions even though the product brief describes broader calendar ambitions as future work.
The [new-requirements matrix](PHASE1_SUMMARY.md#3-genuinely-new-requirements-and-conditional-additions) distinguishes required versus suggested/future additions: expanded command search/actions, application density, configurable status mappings, per-task scheduling, intent-led onboarding, contextual-assistant access and aggregate admin metrics. Existing raw custom status values, job reminders, onboarding checklist, operational admin dashboard and CV document density must remain intact; none alone establishes the corresponding new contract. The brief asks for contextual assistant access, not necessarily a general chat platform; a new logo is required, while a name change remains undecided.
During migration, extend this table with destination, regression test and review evidence per ID. Do not treat a screen screenshot or a count of passing tests as proof that all rows survived.
+117
View File
@@ -0,0 +1,117 @@
# Phase 1 completion summary
Completed **2026-09-10** on `codex/ui-redesign-audit`. This is an audit checkpoint, not a verified production release. **No visual redesign, component-library migration, route restructuring, Phase 2 implementation or B07 work has begun. B06/F04 closure remains open.**
The detailed [architecture assessment](README.md), [functionality ledger](FUNCTIONALITY_INVENTORY.md), [route/workflow map](ROUTE_MAP.md), [API/identity map](API_AND_IDENTITY.md), [component inventory](COMPONENT_INVENTORY.md), [test assessment](TEST_INVENTORY.md) and [migration risks](RISKS_AND_MIGRATION.md) form this completed audit. The source census remains valid: the only `src`/`app` change since its checkpoint is the separately documented interview test assertion. This final completion commit changes documentation only.
## 1. Existing functionality to preserve
All **57 preservation IDs** in the full ledger remain protected, including features that already satisfy the brief. These are capability groups, not a claim of 57 individual controls or complete runtime test coverage.
| Ledger IDs | Complete functional coverage by area |
|---|---|
| E01–E09 | Public plans; local and provider authentication; registration/verification/reset/email change; 2FA/recovery/trusted devices; sessions/logout; account identity/avatar/password; sidebar/command search; theme/localisation/shared feedback; cross-account state isolation |
| J01–J10 | NAV discovery and saved alerts; reviewed manual/URL/paste capture; extension/bookmarklet capture; table/mobile cards/filter/sort/pagination/columns/saved views; edit/bulk actions/trash/restore; Kanban; companies/contacts; offer comparison; JSON/CSV export and import/conversion |
| W01–W12 | Full job workspace and quick overlay/deep links; progression/timeline; analysis/match/strategy; checklist; CV selection/tailoring; cover letters/review/version restore; application drafts/frozen submitted packages; attachments; interview prep/debrief; reminders/calendar/notifications; asynchronous operations |
| C01–C12 | Canonical Career Profile and long-tail sections; overview/completeness/history; file extraction/diff/accept/discard; evidence bank; CV library/copy/publish/share; responsive editor/preview; item and section visibility/order/overrides; template-aware customization; page/document settings; undo/redo/save/history; section/document AI review; private/public PDF |
| M01–M06 | Job correspondence records; Gmail search/import/thread linking; global inbox/filter/attachment information; provider compose/reply/drafts/review/send/delivery uncertainty; Gmail review decisions; Google/Microsoft/IMAP connections |
| S01–S05 | Personal analytics/next actions; owner-local widget visibility/onboarding/notifications; follow-up/email/table/theme/language preferences; AI privacy/usage/Free–Pro/billing capabilities; account export/encrypted backup/lifecycle/deletion |
| A01–A03 | Admin user/role/reset/deletion guards; audit/undo/restore; separate operational diagnostics/email settings/AI probes/CV benchmarks/maintenance |
The route map accounts for **31 concrete patterns: 29 page patterns and two compatibility aliases**. It also records not-found/error boundaries and non-route entry points: capture query strings, command search, notification links, workspace section/tab aliases, OAuth popups and public CV slugs. Embedded Documents, Analytics and Notifications functionality is inventoried even though these are not current standalone routes.
Protected workflows include discover/capture → review/create → analyse/match → select/tailor CV → prepare material → submit/freeze package → track/remind → interview/outcome; import CV → review canonical profile → create/edit/publish/export variant; inbox/import → link → draft/review/send; and account/session/admin lifecycle. Exact sub-actions and source owners are in the ledger; a redesigned page must not silently omit its nested functions.
## 2. Existing functionality needing UX improvement
These are **preserve-and-improve**, not missing features or authorization to implement them now. Assessment concerns coherence/discoverability unless a defect is explicitly identified in section 4. No new user study or visual/manual accessibility session is claimed.
| Existing capability / IDs | Already satisfies the brief | Remaining UX work |
|---|---|---|
| Navigation and command launcher, E07 | Collapsible sidebar, section support, mobile drawer, active child-route matching, Ctrl/Cmd+K job/company search | Main features currently share one broad Manage group. Improve grouping and discoverability; retain account/admin separation. Extra command capabilities are new scope below |
| Job views and workspace, J05–J07, W01–W12 | Desktop table/mobile cards, saved views, keyboard Kanban, full job page and shared quick overlay, next actions and artifact relationships | Present table/board as views of one workspace; clarify section hierarchy and full-page/preview transitions. Preserve every alias, status and nested workflow |
| Career and CV, C01–C12 | Canonical profile, document overrides, real template previews, page boundaries/automatic fit, drag and keyboard reorder, undo/history, desktop split/mobile edit-preview, PDF | Improve source-versus-override clarity, advanced-control discovery and preview focus. Keep advanced existing controls despite the brief's shorter suggested list |
| Contextual AI, W03/W05/W06/W09, C03/C11 | Context selection, source material, preview/diff and accept/reject already exist | Standardize current/suggested/evidence presentation and locked/error states. Presence of review UI is not proof that every AI output is factually correct |
| Dashboard/onboarding, S01–S02 | Metrics/trends, next actions, five owner-local visibility switches, guided checklist/dismissal | Improve hierarchy and empty/loaded states; additional widget/layout contracts are new work |
| Mail/documents/notifications, M01–M06, W06–W08/W11–W12 | Drafts, explicit send/review, delivery states, job-linked files, inbox/review, reminders and operations | Make destinations and status distinctions easier to find. Unified navigation relocates these capabilities; it does not replace their persistence contracts |
| Calendar and account/settings/admin, W11, E02–E06, S03–S05, A01–A03 | Follow-up/deadline Google/Outlook event creation and ICS; account controls; separate admin tools | Preserve provider availability/permission handling and clarify account versus career versus provider identity; keep operational diagnostics separate from personal data |
| Themes/localisation/responsiveness, E08 and all flows | MUI theme, light/dark/system, EN/Bokmål and existing workflow adaptations | Standardize tokens, layout/focus/error states and test populated EN/NB/mobile surfaces. New density and brand choices are separate requirements |
## 3. Genuinely new requirements and conditional additions
These distinctions come from comparing the brief to the current handlers/models, not from treating every desired noun as a missing page. Proposed defaults are not permission to change stored data.
| Brief requirement | Current boundary / incremental work | Scope decision |
|---|---|---|
| Dashboard ordering, resizing, reset and remembered layout | [DashboardView](../../../job-tracker-ui/src/components/DashboardView.tsx) persists only `cards/activity/funnel/companies/skills` visibility flags | New layout behavior. Decide device-local versus cross-device persistence before backend/schema work; Phase 8 |
| Expanded command palette | [QuickCommandDialog](../../../job-tracker-ui/src/components/QuickCommandDialog.tsx) searches jobs/companies and has six static commands | Add CV/document search, CV creation, Career Profile, appearance/language and contextual commands where specified; reuse existing actions |
| Comfortable/compact application density | Theme/table display preferences exist; CV **document** density exists | New application preference affecting chosen dense surfaces; do not confuse it with document density |
| Configurable statuses and category mapping | [JobPipeline](../../../JobTrackerApi/Services/JobPipeline.cs) preserves free-text/custom values; [pipeline.ts](../../../job-tracker-ui/src/pipeline.ts) defines fixed groups and an Other fallback | New configuration/mapping workflow. Preserve Waiting/Ghosted, raw values, analytics and rules; never replace statuses with the brief's suggested list |
| Per-task due dates and notifications | [Checklist model](../../../JobTrackerApi/Models/ApplicationChecklistItem.cs) stores status/order/completion; follow-up and deadline dates belong to the job | New task scheduling contract if adopted. Existing job reminders do not prove per-task scheduling |
| Optional editable weekly goals | Existing analytics and onboarding do not store a weekly target | New optional capability with supportive defaults; no universal application quota |
| Intent-led onboarding and suggested profile preferences | [OnboardingChecklist](../../../job-tracker-ui/src/components/OnboardingChecklist.tsx) links existing account/profile/job/provider steps; it does not collect the brief's initial job-seeker intent | New intake/personalization scope if adopted. Suggested desired-role/remote/salary preferences need an explicit data contract; do not infer them from job salary/location |
| Shared contextual-assistant entry and command actions | Domain-specific AI controls/wrappers exist; no persistent shell assistant is wired into the current route graph | New interaction surface/reuse work. A general-purpose chat platform is not required to satisfy contextual access |
| Aggregate product/admin dashboard and candidate metrics | [AdminSystemPage](../../../job-tracker-ui/src/views/AdminSystemPage.tsx) is operational; personal dashboard analytics already exist | Cohort/adoption/conversion and CV-performance additions require defined measures, permitted aggregate data and availability. Candidate widget lists are not a promise every metric exists or must ship; correlation is not causation |
| Manual CV preview zoom | [CvBuilderEditor](../../../job-tracker-ui/src/views/CvBuilderEditor.tsx) automatically fits page width; drag reorder, pagination and history already exist | Manual zoom is a recommended incremental control, not a reason to rebuild the preview or declare all builder capabilities missing |
| Logo and brand treatment | Existing mark/theme remain functional | New logo is required by the brief; exact accent/name decisions are deferred. No automatic product rename |
| Broader event/provider support, priority/favourite and optional quick-drawer form | Existing calendar actions cover follow-up/deadline; current quick overlay is functional; suggested fields/providers are not all current contracts | Conditional product additions or presentation choices. Broader calendar is explicitly future scope. Do not remove existing integration or introduce schema merely to match illustrative examples |
Grouped navigation, a Jobs view switch, and clearer Documents/Analytics/Notifications destinations belong to **section 2's reorganization of existing behavior**; they do not automatically require new APIs or a new resource model. New analytical measures or cross-job queries must be scoped separately from relocating existing views.
## 4. Known baseline defects and technical/UX debt
The existing validated queue owns these defects; this final audit pass records them and does not fix or re-audit them. Prior independently reproduced findings remain distinct from source-only risks and unresolved production evidence.
| Status / owner | Known issue relevant to redesign | Required treatment |
|---|---|---|
| Open, F01 / D01–D03 | CV/profile/job lost-update and atomic-history/projection contracts | Preserve existing history; a mounted editor's save queue does not solve concurrent writers. Coordinate before changing persistence/conflict UX |
| Open, D04–D07 | Duplicate creation intent, concurrent/omitted byte accounting and export/history growth | Preserve current warnings/quotas; do not silently add idempotency, retention or storage redesign |
| Open, F14 / A01–A03 | Previously validated shared contrast and preview keyboard-scroll defect; thin loaded-state accessibility coverage | Track structural fixes separately; no compliance claim from shell-only axe samples |
| Open, W01/W05 | Calendar date/timezone correctness and private unsaved-draft recovery scope | Preserve current semantics while documenting defects. Do not persist private drafts across accounts or invent new storage |
| Open, R01b/R02/R03/R05 and paid-lifecycle queue | Production DB fallback validation, consistent backup/restore/rollback proof, worker survival, billing entitlement/deletion | Separate owners and release gates. No schema work before recovery proof; billing UI cannot establish entitlement correctness |
| Historical error fixed; R01a partly open | `interview-prep.test.tsx:120` TS2571 reproduced, then corrected with a test-only matcher during authorized release repair | Standalone tsc now passes. Explicit standalone CI gate is still missing; do not continue calling TS2571 an active failure |
| Current CI block | Run 743 browser worker SIGSEGV, then timeout/closed-browser errors; deploy skipped | Obtain runner diagnostics before another release attempt. Do not weaken gates or label the crash an application assertion failure |
| Source-backed maintainability/UX debt | Two router layers; large stateful forms; mixed breakpoints; hardcoded shell colors alongside theme; hardcoded English mobile accessible name | Small, tested future changes. Module size is not performance evidence; cleanup candidates are not approved deletions |
| Unverified risk | Router identity changes with display preferences; optional labels/select patterns; loaded EN/NB/theme/mobile/manual accessibility gaps | Targeted regression/manual checks needed. Do not promote these to reproduced security defects |
| Open, B06/F04 | Actual deployed ingress trust and bypass protection remain unproven | Separate production verification below; repository checks cannot close it |
See [validated ticket boundaries](../../audits/2026-09-07/validation/implementation-queue.md), [test limitations](TEST_INVENTORY.md) and [regression-risk evidence](RISKS_AND_MIGRATION.md). Identity isolation, record concurrency and creation idempotency remain three different contracts.
## Functionality preservation contract
Every ledger ID defaults to **preserve**. Before migrating a workflow, record its destination, retained actions/state transitions, exact API/data meaning, permission/session boundary and regression evidence. Relocation/merger must retain capability; deprecation requires an explicit documented decision. No deletion is approved in this audit.
Preserve direct links/aliases/capture/OAuth flows; owner/session/role/entitlement checks; canonical Career Profile versus document overrides; AI preview/reject/apply and factual provenance; dirty/conflict recovery and private-state disposal; server drafts/history/frozen packages and uncertain-send behavior; publication versus private downloads; B01–B05 safety contracts; typed date/JSON/CSV/PDF formats and Norwegian text. Existing backend defects are recorded for their own tickets, not redefined as acceptable intended behavior.
A migrated workflow needs same-account success and cross-account denial, populated/empty/error/locked states, EN/NB, light/dark, desktop/mobile, keyboard/focus and artifact checks appropriate to its behavior. A screenshot, module presence or aggregate test count alone is insufficient preservation evidence.
## Frontend architecture and stack assessment
**Retain the current stack.** React 19/strict TypeScript is a client-rendered SPA packaged by Next 16 static export, with React Router 7 owning application routes and nginx handling static/deep-link/API routing. MUI 7, MUI X, Emotion and MUI icons already provide the foundation; styling combines central theme/global CSS and substantial local `sx`. TanStack Table is used, not TanStack Query. No Redux/Zustand/cache-platform replacement is warranted.
Local React state and focused contexts own forms/auth/theme/language/plan/feedback; shared Axios owns cookies/XSRF/B02 request provenance/cancellation and blob downloads. The API census contains **266 call expressions**; ownership is server-enforced. Improve shared component contracts and existing tokens before page migration. No demonstrated limitation justifies replacing the component library, routing or backend.
## Test and CI baseline
| Evidence | Result / limit |
|---|---|
| Latest completed CI, run 743 at main `27657f3` | **916/916 backend**, zero skips; **67 suites / 298 frontend tests**; lint, dependency audits, Compose/safety/parser and production builds passed |
| Same CI browser gate | **6 passed / 12 failed**; initial worker SIGSEGV, retry timeout and closed-browser errors. Deploy skipped; not a green release |
| Local release checks at `a861ca4`, unchanged source since | **18/18 browser**, standalone tsc, lint and static build passed; installed npm audit zero vulnerabilities. Local success does not replace CI |
| Prior B06 focused tests | **166 backend, 3 Compose, 6 native Production ingress, 9 native B04 HTTP** checks passed; no actual Traefik/nginx topology |
| Accessibility/test census | 76 frontend test files, 106 backend test-source files; general axe samples eight routes in English, not all loaded interactions/manual accessibility |
| Final audit-only validation | **621 local links valid, 57 unique ledger IDs, 31 route patterns, 266 API rows, all 120 production source files match the census**; only intervening `src` change is the documented test assertion. Secret scan and diff checks pass; application suites were not rerun for prose edits |
Exact commands, historical failures and classification are preserved in [TEST_INVENTORY](TEST_INVENTORY.md) and the [release report](../../audits/2026-09-07/validation/b06-completion.md#final-release-retry--2026-09-10). Previous CI/build changes were separate authorized release/baseline repairs: Compose availability, Linux fixture/runtime/TLS-test portability, blocking dependency advisories and the test-only TS2571 correction. They are not redesign implementation, and this final pass adds none.
## Unresolved production validation
**B06 implementation complete, but B06 runtime/production closure and F04 remain open.** Intended path: Traefik → host 3000 → nginx → internal API 8080. Docker Desktop's inference-socket failure prevented the real local container chain. No verified production SSH alias was available; no credentials or CI secrets were obtained.
Actual nginx startup/fail-closed configuration/restart, Traefik socket peer and trusted CIDR, backend client IP/scheme, real-chain B02/B04 behavior, host/container listeners, firewall/NAT/IPv6 and independent direct-port denial still need evidence. Public DNS/valid HTTPS were observed, but intended-address inventory and the HTTP redirect to unreachable HTTPS port 444 remain unresolved. Live `/health` last observed **2026-09-10 05:07 UTC**, version **299**, ready; this is not deployment of the pushed audit repairs. Exact safe operator commands and expected evidence remain in the [B06 report](../../audits/2026-09-07/validation/b06-completion.md).
## Recommended Phase 2 starting point
Start only when separately authorized: **a bounded functional-baseline session, with no layout or route changes**. Add the explicit standalone type-check script/CI step while coordinating with runner repair; map the 57 preservation IDs to existing assertions and add the highest-risk missing tests for route/overlay/capture parity, populated CV/profile overrides and AI accept/reject. Preserve real API/B02 identity tests and B01/B03/B04/B05 suites.
Exit requires a documented pass/fail/unavailable baseline, meaningful loaded-workflow proof and a preservation checklist tied to tests. Existing defects stay separately ticketed; failed tests are not made green by weakening expectations. Record-integrity and recovery prerequisites govern later persistence/schema work. Phase 3 navigation proposals and Phase 4 visuals follow their own checkpoints; no stable/live tag or F04 closure is warranted while their evidence is missing. Branding, product additions and cross-device layout decisions do not block this audit's completion.
+8 -1
View File
@@ -1,15 +1,22 @@
# Phase 1 — Frontend audit
Successor checkpoint: [Phase 2 preservation matrix, regression results and traceability](../phase-2/README.md), based on the completed Phase 1 commit `2d0f131`. The historical audit below is retained; no visual redesign has started.
Date: 2026-09-09. Source checkpoint: `97fce89c002fe86e737ca7387170321d464b80d7` (application changes through `0954876`; subsequent commits contain the brief and CI prerequisite corrections). Audit branch: `codex/ui-redesign-audit`. Test-only packaging correction `23cc250` leaves the frontend census unchanged; its fresh 22 PDF and 915 broader backend passes are recorded in the test assessment.
**Phase 1 source audit complete; redesign implementation has not started.** The repository already implements much of the proposed workflow. Retain its MUI/React foundation and domain contracts, improve shared semantics, then migrate representative screens in small steps. A replacement frontend framework or component library is not justified by the observed problems.
Finalized 2026-09-10: [Phase 1 completion summary](PHASE1_SUMMARY.md) explicitly separates existing functionality to preserve, existing functionality needing UX improvement, new/conditional requirements and baseline defects/debt. It includes the preservation contract, validation limits and bounded Phase 2 starting point. All 57 ledger IDs remain protected; this is a separate documentation checkpoint, not a stable/live release tag.
This is a source and regression-evidence audit, not a new visual usability study, production certification, or execution of security ticket B07. The existing security queue still governs unresolved integrity/recovery work. F04 remains open. A pushed revision is not proof of a successful deployment; see [release evidence](../../audits/2026-09-07/validation/b06-completion.md#authorized-release-attempt-and-ci-prerequisite-correction).
Release status, 2026-09-10: main `27657f3` is pushed. Run 743 passed 916 backend and 298 frontend tests, lint, audits and production builds, but the browser gate failed with a worker SIGSEGV and subsequent browser-closed errors; deployment was skipped. Further release attempts await runner diagnostics. The live endpoint still reports version 299. This does not block delivering the Phase 1 findings, but it does block claiming a verified live checkpoint for subsequent work.
## Deliverables and method
| Handoff requirement | Evidence |
|---|---|
| Final summary and four-way scope classification | [Phase 1 completion summary](PHASE1_SUMMARY.md) |
| Architecture, styling, state | This document |
| Every declared route/page | [Route map](ROUTE_MAP.md): 31 concrete route patterns, including two redirect aliases; 29 page patterns |
| Existing user functionality | [Preservation ledger](FUNCTIONALITY_INVENTORY.md), including nested screens and non-navigation entry points |
@@ -87,6 +94,6 @@ The automated axe suite samples eight routes and largely waits for `body`/`main`
2. The full job workspace, canonical Career Profile, contextual AI review, CV history and adaptive mobile surfaces already exist. The redesign should improve coherence and discoverability.
3. Shared visual semantics and loaded-state accessibility coverage are weaker than the broad test count suggests. Fix foundations before whole-page migration.
4. Five local dashboard visibility switches are not the proposed configurable dashboard. Ordering, resizing and cross-device persistence are future scope requiring a bounded contract.
5. The known standalone TypeScript failure reproduces. The source baseline is documented, but release/production and unresolved integrity gates must not be called green by implication.
5. The known standalone TypeScript failure reproduced during the audit; subsequent authorized release repair `a861ca4` corrected its test-only assertion after patched Next made it build-blocking. Standalone tsc now passes; an explicit standalone CI gate remains outstanding. Release/production and unresolved integrity gates must not be called green by implication.
Next work is **Phase 2 — Functional baseline**, subject to the gates in [RISKS_AND_MIGRATION](RISKS_AND_MIGRATION.md). No Phase 2 code, B07, or redesign component changes were made here.
+3 -3
View File
@@ -11,7 +11,7 @@ This report proposes the next steps; it does not authorize or implement Phase 2,
| Creation replay becomes easier through capture/dialog rerenders | Preview/duplicate warning and subsequent create are separate requests | Preserve current review flow; existing D04 creation-intent ticket is distinct from identity and record revision. No idempotency work in this audit |
| Route consolidation loses entry contracts | Two router layers, legacy `/applications/:id`, old section mappings, `?add`, notification/company deep links | Route manifest + redirect tests before moving navigation; keep capture and OAuth callbacks functional |
| New styling repeats existing contrast/focus defects | Validated F14 theme pair contrast; builder preview scroll-region transition | A01 semantic foreground/background/state colors, A02 preview structure, A03 loaded/manual verification before broad page work |
| Thin initial-state tests create false confidence | Eight-route axe sample; component API mocks; standalone TS2571 | Phase 2 loaded-interaction and type baseline; require feature-ledger evidence per migrated workflow |
| Thin initial-state tests create false confidence | Eight-route axe sample; component API mocks; historical TS2571 now corrected during release repair | Phase 2 loaded-interaction and explicit standalone type gate; require feature-ledger evidence per migrated workflow |
| CV/profile semantics blur in a unified editor | Canonical profile save versus CV override/settings and published slug | Clearly display source versus document override; test master unchanged, source selection, history, publish/unpublish and long PDF |
| AI presentation implies facts or silently applies changes | Contextual suggestions and extraction accept/discard already exist | Preserve preview/reject/apply and source/context metadata; prompt instructions alone do not prove factual accuracy |
| Draft/history/mail/package behavior lost in visual simplification | `ApplicationAssets` 769 lines, inbox 750, correspondence 747; separate persistence/attempt APIs | Extract by cohesive responsibility, not one generic form; keep conflict/send uncertainty, frozen package and unsaved guards |
@@ -28,7 +28,7 @@ Line counts come from the [source census](source-inventory.json); they are prior
## Recommended order
1. **Finish the authorized release attempt and retain the checkpoint.** Required CI and deploy preflight must pass before calling the changes live. Production F04 runtime/firewall/DNS/peer checks remain a separate closure gate. Audit/planning can continue without claiming Phase 0 stability.
2. **Phase 2 — Functional baseline.** Narrow TS2571 and add full type checking; trace preservation IDs to existing tests; add missing loaded workflow/route/AI-review tests. Preserve B01–B06 regression suites. Coordinate outstanding record-integrity and recovery tickets through their existing queue, without silently starting them here.
2. **Phase 2 — Functional baseline.** Add explicit standalone CI type checking (the TS2571 assertion was corrected separately in release repair `a861ca4`); trace preservation IDs to existing tests; add missing loaded workflow/route/AI-review tests. Preserve B01–B06 regression suites. Coordinate outstanding record-integrity and recovery tickets through their existing queue, without silently starting them here.
3. **Phase 3 — UX architecture.** Define grouped navigation, Jobs view switch, destination for embedded Documents/Analytics/Notifications, full-page/preview contract, contextual AI pattern and mobile workflow rules. Map every current route/feature to its destination and backward-compatible entry. Keep API contracts.
4. **Phase 4 — Shared foundations.** Use existing MUI/Emotion/theme system. Resolve proven semantic contrast and focus defects, then unify tokens, page/layout/state primitives, labelled fields, buttons, dialogs/drawers and tables. Avoid a speculative new design-system framework.
5. **Phase 5 — Representative migration.** Shell → Jobs table/board entry → full job workspace → CV editor; dashboard presentation can join after shell tokens stabilize. Preserve a data-rich populated example, empty/error/locked case, mobile and keyboard path for each. Do not modify editor save semantics concurrently with integrity work.
@@ -44,7 +44,7 @@ No router/state/styling dependency replacement is currently recommended. If a la
| Suggested future scope | Boundaries / expected proof | Parallel constraints |
|---|---|---|
| Type and baseline bookkeeping | One test payload narrowing, explicit type script/CI gate, known-failure classification | Independent of visual work; coordinate CI edits with deployment repair |
| Type and baseline bookkeeping | Preserve the corrected test assertion; explicit type script/CI gate, known-failure classification | Independent of visual work; coordinate CI edits with deployment repair |
| Route/preservation regression | Existing route helpers, full/overlay/capture navigation tests | Do not change router structure at the same time |
| Theme semantic contract | Central theme/MUI states + representative loaded contrast fixtures | Separate from backend; integrate before page styling |
| Builder focus contract | Actual preview container, viewport transition and keyboard scroll tests | Must not run concurrently with D01/editor save refactor |
+5 -2
View File
@@ -31,15 +31,18 @@ The default browser suite currently contains **18 tests**. The nine special brow
| Fresh release-prerequisite checks | `python scripts/test-ingress.py` → 3/3 pass; real standalone Compose path also tested; official downloaded Linux binary digest verified; tracked-secret scan and diff check pass |
| Fresh packaging regression at `23cc250` | Explicit installed .NET 9.0.317 SDK: Release build + **22/22 PDF admission tests** pass, then **915/915 selected backend tests** pass (same documented external-DNS IMAP exclusion). Results: `.artifacts/redesign-audit/backend.trx` |
| Actual CI push verification | Runs 736/737 exposed missing Compose/token prerequisites; corrected. Run 738 passed Compose/safety/extension/parser/API build, then exposed Linux PDF test-fixture packaging collision. Follow-up result belongs in [release log](../../audits/2026-09-07/validation/b06-completion.md#authorized-release-attempt-and-ci-prerequisite-correction) |
| Fresh Linux backend confirmation | Run 741, `2024c37`, passed **916/916 backend cases**, zero skipped, after test packaging/runtime-location and TLS EOF-or-no-read portability corrections. This includes the locally excluded DNS-dependent case. Application behavior and all security assertions remain protected; deployment has separate gates |
| Subsequent authorized release correction, `a861ca4` | Next 16.3.4/sharp 0.35.4/js-yaml 3.15.2 resolve three blocking dependency entries: installed npm audit **zero vulnerabilities**. Fresh **298 frontend / 18 browser** pass, plus **13 interview tests** after the test-only TS2571 correction. Standalone tsc, lint and production static build pass. Exact commands and failed-before evidence remain in the B06 release log; this is release repair, not a redesign implementation |
| Final CI retry, run 743 at `27657f3` | **916 backend / 298 frontend** pass, zero backend skips; Compose/safety/parser, dependency audits, lint and production builds pass. Browser gate: **6 passed / 12 failed**, beginning with worker SIGSEGV, then retry timeout and closed-browser navigation errors. Deploy skipped. Repeated process crashes reached the bounded stop condition; runner-host evidence is required before another release attempt. See [exact result and diagnostic handoff](../../audits/2026-09-07/validation/b06-completion.md#final-release-retry--2026-09-10). Local browser success is not a replacement for CI |
| Live/container boundary | Local Docker engine unavailable; F04 production evidence remains incomplete. No audit claim of real proxy-chain success |
The selected broader backend suite was repeated for the PDF fixture packaging change; the frontend suite was not repeated merely for documentation edits. Preserve the distinction between prior unchanged-source evidence, newly executed checks and actual CI failures. Run 739 subsequently failed earlier in the unchanged API compiler with `csc.dll` exit 139 on both existing attempts; it did not reach the corrected test-project build. No test gate was bypassed.
## Exact TypeScript root cause
[Interview test:120](../../../job-tracker-ui/src/interview-prep.test.tsx#L120) reads `.content` from `mockedApi.post.mock.calls[0][1]`. Axios's mocked generic request-data argument is typed `unknown`; the test dereferences it without narrowing. The runtime assertion can pass because Babel removes types. The production component's emitted behavior is not shown broken by this test diagnostic.
[Interview test:120](../../../job-tracker-ui/src/interview-prep.test.tsx#L120) originally read `.content` from `mockedApi.post.mock.calls[0][1]`. Axios's mocked generic request-data argument is typed `unknown`; the test dereferenced it without narrowing. The runtime assertion passed because Babel removes types. The production component's emitted behavior was not shown broken by this test diagnostic.
[tsconfig](../../../job-tracker-ui/tsconfig.json) includes `src`, including tests. CI runs Jest/lint/Next build but has no explicit full standalone `tsc --noEmit` gate. Next's successful build is not evidence that this exact standalone test-inclusive program passed. Phase 2 should narrow the test payload appropriately and introduce a deliberate full type gate. Do not weaken strictness, blanket-exclude tests or call this a P1 production outage. No type fix was made in Phase 1.
[tsconfig](../../../job-tracker-ui/tsconfig.json) includes `src`, including tests. CI runs Jest/lint/Next build but has no explicit full standalone `tsc --noEmit` gate. The patched Next 16.3.4 release build independently failed on this exact diagnostic. Authorized release repair `a861ca4` replaced the property dereference with an object matcher, retaining both positive prompt and negative invented-content assertions. Fresh standalone tsc, 13 interview tests and Next build now pass. The original Phase 1 reproduction above remains historical evidence. Phase 2 still needs a deliberate standalone CI gate; no strictness or test coverage was weakened.
## Missing regression proof before redesign
+68
View File
@@ -0,0 +1,68 @@
# Phase 2 baseline defects and intentional gaps
Application source is unchanged from `2d0f131`. No defect below was fixed as part of the preservation checkpoint.
## P2-D01 — SQLite tailoring and submitted-package capture fail
**Confirmed in the disposable running local API, not production.** The new [browser regression](../../../job-tracker-ui/e2e/redesign-preservation.spec.ts) creates a real non-admin account, canonical profile, company/job, attached CV, cover letter and UTF-8 attachment. It opens the existing legacy job URL, then clicks **Freeze current package** in the **CV section** (not the cover-letter section).
Observed:
| Request | Result |
|---|---|
| Profile/job/CV/letter/file fixture writes | Successful, including B02 session preconditions |
| GET job submitted-packages | 200, empty list |
| GET job tailoring | 500 |
| POST job submitted-packages | 500; no successful package row shown |
| API exception | `System.NotSupportedException: SQLite does not support expressions of type 'DateTimeOffset' in ORDER BY clauses.` |
Stack evidence points to [ApplicationAssetsService](../../../JobTrackerApi/Services/ApplicationAssetsService.cs), `GetTailoringPlanAsync` (ordering verified CareerEvidence by UpdatedAtUtc at line 182), and [SubmittedApplicationPackageService](../../../JobTrackerApi/Services/SubmittedApplicationPackageService.cs), `CaptureAsync` (ordering variants by UpdatedAtUtc at line 67). These paths do not use the SQLite-safe ordering used in some nearby services. Existing [SqliteDateTimeOffsetCompatibilityTests](../../../JobTrackerApi.Tests/SqliteDateTimeOffsetCompatibilityTests.cs) exercise other ordering paths; they do not certify these two. Existing [SubmittedApplicationPackageTests](../../../JobTrackerApi.Tests/SubmittedApplicationPackageTests.cs) prove snapshot/owner/failure contracts with test seams, not successful native SQLite execution of this query.
The new desired success test is retained with an explicit `test.fail` annotation activated **after fixture and navigation setup, only for the observed capture HTTP 500**. Playwright reports an expected failure as expected execution; this is **not** a successfully frozen package. Post-capture checks for unchanged archived HTML/letter and exact attachment bytes after deleting the current source are present but **not reached**. Do not count them as achieved coverage. A successful capture runs all later assertions as ordinary tests; setup, non-500 responses and subsequent archive failures cannot be masked by the annotation. Remove the narrow annotation and update this classification only with the separately scoped fix and successful native evidence.
Initial fixture error: the first draft looked for package capture under the cover-letter tab; inspection showed the existing capture lives under CV, and the test was corrected. That locator error is not an application defect.
Scope/next owner: a separate bounded provider-compatibility repair and native storage verification before any package/tailoring redesign. Preserve timestamps/ownership and MariaDB behavior; no schema change is presumed. Phase 2 neither changes ordering nor claims the same failure in MariaDB/production. All original 916 backend and 298 frontend cases remain retained.
Local diagnostic command (PowerShell, from job-tracker-ui, after the documented build):
```powershell
$env:DEBUG='pw:webserver'
node node_modules/@playwright/test/cli.js test e2e/redesign-preservation.spec.ts
```
The full debug log is local ignored evidence, `.artifacts/phase2-browser-focused.log`; it is not committed wholesale. It contains synthetic accounts only. The exception/request facts above are the durable evidence. Use the normal commands in [README](README.md) to reproduce with an isolated data directory; never substitute production URLs.
## P2-O01 — one rules-save HTTP 500, not independently reproduced
The first browser run sent the valid rule object with appliedFollowUpDays=5 and received 500; a subsequent GET still returned 14. The visible draft retained 5. Response contained a generic problem and trace ID; that run did not retain the server exception, so the cause is unknown.
An isolated authenticated direct PUT then returned 204 and persisted 5. A repeat of the original mobile→desktop browser save with explicit response observation also returned 204 and persisted 5. **No rules regression is expected-failed or skipped.** Both successful browser/direct saves remain ordinary permanent tests. Do not claim a confirmed root cause from one transient result. If it recurs, collect the server exception/trace before proposing a fix. Failed notification-save UI retention is independently covered with a deterministic transport failure.
## Audit precision: workspace overlay and admin route
The current source has a reusable ApplicationWorkspace with override/onClose/full-page-link props, but the production call-site search finds only its normal full-page wrapper. Preserve the reusable contract; do not claim the test named application-workspace-overlay exercises a mounted production quick-preview host. A future overlay needs its own close/dirty/focus tests before adoption.
Non-admin navigation hides admin destinations. A crafted admin URL can render the page shell; the API returns 403 and no user list is exposed. The browser test verifies this established boundary, not a nonexistent frontend route-denial screen.
## Existing debt remains separate
The [Phase 1 risks](../audit/RISKS_AND_MIGRATION.md), [baseline summary](../audit/PHASE1_SUMMARY.md) and [validated queue](../../audits/2026-09-07/validation/implementation-queue.md) remain authoritative for prior findings:
- Stale CV revision overwrites, creation idempotency, calendar/timezone correctness, restore/rollback readiness, billing/deletion and operational failures are not resolved by redesign tests.
- Shared contrast/focus, builder preview scrolling and incomplete loaded accessibility/manual testing remain open. No axe/WCAG certification is added.
- Historical TS2571 was corrected before Phase 2; it does not recur. This checkpoint adds the explicit full src/app standalone type gate without relaxing strictness. Its existing tsconfig scope includes Jest files, not all Playwright/config files.
- Main CI run 743's worker SIGSEGV/retry timeout/closed-browser failures and skipped deployment remain unresolved runner evidence. A local passing suite and feature-branch push do not close this release blocker.
- **B06 implementation complete; F04 remains open.** Actual Traefik peer, nginx/API runtime listeners, firewall/NAT/IPv6 exposure, forwarded client IP, DNS/TLS/redirect and restart behavior still need the [operator handoff](../../audits/2026-09-07/validation/b06-completion.md). No production credentials accessed or live tests run here.
## Intentional gaps by deadline
| Deadline | Evidence still required |
|---|---|
| Before shell/Jobs restructuring | Mobile drawer focus/return, saved view CRUD, bulk partial failures/trash restore, capture/legacy entries; matrix E07/J04/J05/J06/W01 |
| Before CV/package restructuring | Native capture/tailoring success, item-specific override reset/source edit, browser history restore, loaded preview keyboard and template/control combinations; C07–C10/W05/W07 |
| Before career import restructuring | Low-confidence selection/discard, real unusual document extraction and error recovery; C03 |
| Before mail/settings/admin restructuring | Provider sandbox reconnect/delivery; persisted draft conflict/uncertain-send retained; session list UI, audit undo/restore, dashboard toggle ownership; E05/M01–M06/S02/A02 |
| Before public release | Manual assistive technology/touch/zoom/reflow; EN/NB and both themes; provider/DNS/TLS/firewall/backup restoration; original CI runner remediation |
These gaps permit Phase 3 planning, not unguarded production restructuring of the affected capability.
@@ -0,0 +1,223 @@
# Phase 2 preservation matrix
Baseline: **`2d0f131`**, the [57-group Phase 1 ledger](../audit/FUNCTIONALITY_INVENTORY.md). `PRES-E01` is a stable alias of `E01`, and similarly for all rows; original IDs retain their meaning. No capability has been removed, merged or deprecated. Each row includes all sub-capabilities in its linked baseline group, even when its short label abbreviates them.
## Measurement and limits
**57/57 groups (100%) have mapped automated behavioral evidence for at least one core behavior.** This is group-level preservation coverage, not statement coverage, exhaustive sub-workflow coverage, visual parity or 100% readiness. Every row's remaining gaps are explicit. A passing mock/service test does not prove a real provider, actual browser persistence, or production. In particular W07 retains passing service/component evidence but its new native SQLite capture test is an **expected failure**, not successful end-to-end preservation proof. See [baseline defects](BASELINE_DEFECTS.md).
`H` = identity/data loss/irreversible action or a high-risk shared navigation/editing surface; `M` = important reversible presentation/workflow risk. These are redesign regression risks, not security severity labels. “Touch” is expected later-phase impact, not implementation authorization. `F` links are Jest component/helper tests; `B` links are backend tests. Named behaviors describe assertions, not mere file existence. The existing App.test harness is deliberately not counted as route proof.
New browser tests live in [redesign-preservation.spec.ts](../../../job-tracker-ui/e2e/redesign-preservation.spec.ts); original [smoke](../../../job-tracker-ui/e2e/smoke.spec.ts), [account isolation](../../../job-tracker-ui/e2e/account-isolation.spec.ts), and special B01–B06 suites stay unchanged. The [full original test census](../audit/TEST_FILES.md) supplies secondary coverage beyond the representative tests mapped below.
| Preservation ID / capability | Route / entry | Frontend; backend owners | Automated tests and protected behavior | Missing evidence / deliberate gap | Risk | Expected redesign touch |
|---|---|---|---|---|---|---|
| **PRES-E01** — Public product/plans; honest unavailable billing | `/` | LandingPage, planCatalog; AccountPlans | [F:landing-page][F:landing-page.test.tsx], [F:planCatalog][F:planCatalog.test.ts], [F:LandingPage.authRedirect][F:LandingPage.authRedirect.test.tsx]; [B:AccountPlans][B:AccountPlansTests]. Free/Pro copy, unavailable checkout and authenticated redirect | Actual checkout/provider availability; public mobile/keyboard review | M | Yes — public entry |
| **PRES-E02** — Local login/register/remember, challenge, reset and verification | `/login; /register; /forgot-password; /reset-password; /verify-email` | LoginPage, TurnstileWidget, VerifyEmailPage; AuthController | [F:login-page][F:login-page.test.tsx], [F:verify-email-page][F:verify-email-page.test.tsx], [F:turnstile-widget][F:turnstile-widget.test.tsx]; [B:LocalAuthIdentity][B:LocalAuthIdentityTests], [B:AuthAndSystemController][B:AuthAndSystemControllerTests]. Configuration gates, two-factor branch, safe redirect, verification outcomes; smoke login | Real reset-email delivery, Turnstile widget/browser configuration, complete reset submit journey | H | Yes — entry forms |
| **PRES-E03** — Google/Microsoft login, link, legacy relink and popup results | `/login; /microsoft-legacy-relink; provider callbacks` | GoogleAuthCard, MicrosoftAuthCard, MicrosoftLegacyRelinkPage; token validators | [F:login-page][F:login-page.test.tsx], [F:microsoft-legacy-relink-page][F:microsoft-legacy-relink-page.test.tsx]; [B:GoogleTokenValidator][B:GoogleTokenValidatorTests], [B:MicrosoftTokenValidator][B:MicrosoftTokenValidatorTests], [B:LocalAuthIdentity][B:LocalAuthIdentityTests]. Provider failure/relink presentation and token validation | Real consent/popups, revoked grants, provider sandbox; callbacks must retain origin/state | H | Yes — account entry |
| **PRES-E04** — 2FA challenge/recovery/setup/disable; trusted-device revocation | `/profile; /login` | TwoFactorSettingsCard, TwoFactorChallenge; TwoFactorController | [F:two-factor-settings][F:two-factor-settings.test.tsx], [F:login-page][F:login-page.test.tsx]; [B:TwoFactorController][B:TwoFactorControllerTests], [B:SessionsController][B:SessionsControllerTests]. Setup/recovery presentation, disable failure/retry; server trusted-device/session checks | Real mobile authenticator/recovery and trusted-device UI traversal | H | Yes — security forms |
| **PRES-E05** — Session list/current device, revoke one/others, logout | `/profile; shell user menu` | SessionsSettingsCard, AppShell, auth; SessionsController | [F:session-boundary][F:session-boundary.test.ts], [F:account-scoped-state][F:account-scoped-state.test.tsx]; [B:SessionsController][B:SessionsControllerTests], [B:AuthSessionRevocation][B:AuthSessionRevocationTests]. Own active sessions only, revoke ownership and next-request invalidation; B02 browser logout | Session-list card UI selection/refresh after remote revoke | H | Yes — shell/account |
| **PRES-E06** — Avatar, account names/headline, email change and password | `/profile; /confirm-email-change` | ProfilePage; AuthController, AvatarStorage | [F:profile-page][F:profile-page.test.tsx], [F:verify-email-page][F:verify-email-page.test.tsx]; [B:AvatarStorage][B:AvatarStorageTests], [B:LocalAuthIdentity][B:LocalAuthIdentityTests]. Email change password/confirmation contract; safe avatar storage and identity endpoints | Avatar upload/remove and password-change browser journeys | H | Yes — account/profile separation |
| **PRES-E07** — Desktop collapse/mobile drawer, active nav and command search/actions | `Shell; Ctrl/Cmd+K` | AppShell, QuickCommandDialog; job/company search APIs | [F:sidebar-active-nav][F:sidebar-active-nav.test.ts], [F:app-shell-notifications][F:app-shell-notifications.test.tsx], [F:quick-command-preservation][F:quick-command-preservation.test.tsx]; [B:JobApplicationsAuthorization][B:JobApplicationsAuthorizationTests], [B:CompanyRelationship][B:CompanyRelationshipTests]. Active matching and notification entry; NEW job/company destinations, reviewed add and search reset; NEW browser keyboard navigation | Drawer focus return and collapse persistence in EN/NB; all command destinations before shell migration | H | Yes — first restructuring target |
| **PRES-E08** — Theme/system, EN/NB hydration, toast/confirm/prompt and errors | `Shell; preferences; fallback routes` | themePrefs, I18nProvider, confirm, prompt; auth preference | [F:theme-state][F:theme-state.test.tsx], [F:i18n-provider][F:i18n-provider.test.tsx], [F:confirm][F:confirm.test.tsx], [F:prompt][F:prompt.test.tsx], [F:dialog-flow][F:dialog-flow.test.tsx]; [B:LocalAuthIdentity][B:LocalAuthIdentityTests]. Hydrated theme/language, confirmation/cancellation and dialog behavior | Not-found/error-boundary retry route; full locale/theme/state cross-product and OS theme changes | M | Yes — shared foundations |
| **PRES-E09** — Account-switch private-state disposal and stale request rejection | `All private routes; tabs/BFCache/focus` | App, api, sessionBoundary; SessionContextMiddleware | [F:session-boundary][F:session-boundary.test.ts], [F:account-scoped-state][F:account-scoped-state.test.tsx]; [B:SessionContextMiddleware][B:SessionContextMiddlewareTests], [B:CurrentUserIdLiveEvaluation][B:CurrentUserIdLiveEvaluationTests], [B:OwnershipGuard][B:OwnershipGuardTests]. B02 stale mutation/read abort, account-scoped state; four real-API/two-tab account-isolation browser cases | Real BFCache variations and long offline retry; preserve server precondition even with old bundles | H | Yes — all shared state |
| **PRES-J01** — NAV discovery/filter/sort/source/deadline/details | `/discover` | JobDiscoveryPage; JobDiscoveryController | [F:job-discovery][F:job-discovery.test.tsx]; [B:JobDiscoveryController][B:JobDiscoveryControllerTests], [B:JobOpportunitySync][B:JobOpportunitySyncTests]. Search/sort/source details, empty versus failure/retry; responsive discovery browser | Live NAV freshness/API availability; long result keyboard traversal | M | Yes — jobs entry |
| **PRES-J02** — Saved searches, run/delete/alerts, result changes/dismissal | `/discover` | JobDiscoveryPage; saved-search/alert services | [F:job-discovery][F:job-discovery.test.tsx]; [B:SavedJobSearchAlert][B:SavedJobSearchAlertTests], [B:JobDiscoveryController][B:JobDiscoveryControllerTests]. Saved search run, pause and dismiss controls; owner-scoped alert results | Scheduler-to-notification browser journey, delete confirmation on mobile | M | Yes — discovery workspace |
| **PRES-J03** — Reviewed manual/URL/paste job create, company/tags/dates, duplicates | `Shell AddJobModal` | AddJobModal; JobImportService, JobApplicationsController | [F:quick-capture][F:quick-capture.test.tsx]; [B:JobImportService][B:JobImportServiceTests], [B:JobImportHttpTransport][B:JobImportHttpTransportTests], [B:JobApplicationsController][B:JobApplicationsControllerTests]. Preview before write, URL prefills, manual browser create; B01 real connection boundary | Duplicate warning and company/date edge UI; creation-intent idempotency remains separate unresolved contract | H | Yes — capture/navigation |
| **PRES-J04** — Extension/bookmarklet/share capture; post-create attachment/CV | `?add= / ?addtext=; QuickCaptureCard; browser-extension` | App, captureUrl, QuickCaptureCard; job/CV APIs | [F:captureUrl][F:captureUrl.test.ts], [F:quick-capture][F:quick-capture.test.tsx], [F:quick-command-preservation][F:quick-command-preservation.test.tsx]; [B:JobImportService][B:JobImportServiceTests], [B:CvBuilder][B:CvBuilderTests]. Capture URL encoding/review/no auto-create, post-create CV; extension node tests; NEW keyboard add opens review only | Installed extension permissions/browser-share flow and optional attachment failure recovery | H | Yes — entry contracts |
| **PRES-J05** — Job filters/sort/page/columns, saved views and row/mobile actions | `/jobs` | JobTable, SavedViewsMenu; JobApplicationsController | [F:application-workspace-overlay][F:application-workspace-overlay.test.tsx], [F:account-scoped-state][F:account-scoped-state.test.tsx], [F:daily-control-loop][F:daily-control-loop.test.tsx]; [B:JobApplicationsController][B:JobApplicationsControllerTests], [B:JobApplicationsEndpointBehavior][B:JobApplicationsEndpointBehaviorTests]. URL hydration, selection does not open row, list return/focus; browser dirty/full workspace | Saved-view CRUD browser journey, page-size changes with dirty nested state | H | Yes — jobs view consolidation |
| **PRES-J06** — Bulk status/delete, soft delete/trash/restore and job edit | `/jobs; /trash; EditJobDialog` | JobTable, EditJobDialog; job lifecycle/controller | [F:application-workspace-overlay][F:application-workspace-overlay.test.tsx], [F:confirm-flow][F:confirm-flow.test.tsx]; [B:JobApplicationsController][B:JobApplicationsControllerTests], [B:JobApplicationsAuthorization][B:JobApplicationsAuthorizationTests], [B:JobLifecycleEvent][B:JobLifecycleEventTests]. Selection and destructive confirmation primitive; server CRUD/ownership/lifecycle behavior | Bulk partial failure and trash→restore UI journey; shared confirm tests alone do not prove bulk flow | H | Yes — table/actions |
| **PRES-J07** — Kanban grouped statuses, pointer/keyboard move and precise menu | `/kanban` | KanbanBoard, pipeline; status endpoint | [F:kanban-grouped-board][F:kanban-grouped-board.test.tsx], [F:pipeline][F:pipeline.test.ts]; [B:JobPipeline][B:JobPipelineTests]. Keyboard pickup/drop, failed move keeps card, custom group and precise stages; dark browser test | Touch drag, keyboard cancel/focus after responsive view switching | H | Yes — table/board unified entry |
| **PRES-J08** — Company/contact edits, pipeline dates and linked-job history | `/companies; ?edit=id` | CompaniesTable; company relationship APIs | [F:companies-relationship][F:companies-relationship.test.tsx], [F:quick-command-preservation][F:quick-command-preservation.test.tsx]; [B:CompanyRelationship][B:CompanyRelationshipTests]. Job history/context and NEW command company destination | All contact/date edits persisted through browser; source URL safety | M | Yes — directory/navigation |
| **PRES-J09** — Recorded offers/compensation/deadlines/notes and source links | `/offers` | OfferComparisonPage; job API | [F:offer-comparison][F:offer-comparison.test.tsx]; [B:AnalyticsSalary][B:AnalyticsSalaryTests], [B:JobApplicationsEndpointBehavior][B:JobApplicationsEndpointBehaviorTests]. Recorded values/empty states and salary normalization | Mobile many-offer comparison; localized currency/date visual review | M | Yes — jobs/analytics placement |
| **PRES-J10** — CSV/JSON export, JSON import and CSV-to-JSON conversion | `/settings; ImportExportJobs` | ImportExportJobs; CSV/job controller | [F:csv-export][F:csv-export.test.tsx]; [B:CsvExport][B:CsvExportTests], [B:JobApplicationsController][B:JobApplicationsControllerTests]. B05 formula/quotes/UTF-8/typed dates and numeric behavior; CSV browser byte download | Full browser JSON import/count/roundtrip; Excel/LibreOffice compatibility remains separately bounded | H | Yes — settings/download placement |
| **PRES-W01** — Full workspace, sections, legacy links and optional reusable overlay | `/jobs/:id; /applications/:id; section/followMode` | ApplicationWorkspacePage, jobWorkspaceRoute; workspace API | [F:application-workspace-overlay][F:application-workspace-overlay.test.tsx], [F:applicationWorkspace][F:applicationWorkspace.test.ts], [F:application-route-contracts][F:application-route-contracts.test.ts]; [B:ApplicationWorkspace][B:ApplicationWorkspaceTests]. Deep links, dirty cancel/discard, missing job and back/filter/focus; NEW actual App command/legacy entry | No active production caller of override/onClose overlay props found; full overlay parity needs a host fixture before adoption | H | Yes — primary workflow |
| **PRES-W02** — Detail/status/progression, recruiter/dates/description/timeline | `/jobs/:id overview` | WorkspaceHeader, JobDetailsSection, ApplicationProgress/Timeline; workflow APIs | [F:application-intelligence][F:application-intelligence.test.tsx], [F:workflow-trust-signals][F:workflow-trust-signals.test.tsx], [F:daily-control-loop][F:daily-control-loop.test.tsx]; [B:JobApplicationsWorkflowSignals][B:JobApplicationsWorkflowSignalsTests], [B:JobApplicationsAppliedDateHistory][B:JobApplicationsAppliedDateHistoryTests]. Timeline source labels, next-action routing and applied-date history | Edit/status→timeline real-browser sequence across locale/timezone | H | Yes — workspace |
| **PRES-W03** — Analysis, match/gaps, strategy and reviewed status/workflow suggestions | `/jobs/:id?section=analysis` | ApplicationAnalysis/Match/StrategySnapshot/StatusSuggestion; intelligence/AI services | [F:application-intelligence][F:application-intelligence.test.tsx], [F:application-workflow-assist][F:application-workflow-assist.test.tsx]; [B:ApplicationIntelligence][B:ApplicationIntelligenceTests], [B:StrategySnapshotOperation][B:StrategySnapshotOperationTests], [B:JobApplicationsEndpointBehavior][B:JobApplicationsEndpointBehaviorTests]. Loading/error/empty states, match and explicit suggested action behavior | Actual model truthfulness/evaluation; source freshness and cancelled generation browser states | H | Yes — contextual AI |
| **PRES-W04** — Checklist create/edit/complete/delete/order and next action | `Job overview/checklist entry` | ApplicationChecklist; checklist service/controller | [F:application-checklist][F:application-checklist.test.tsx]; [B:ApplicationChecklist][B:ApplicationChecklistTests]. Persisted item actions and keyboard reorder; localized readiness | Real-API mobile edit/delete and interruption recovery | H | Yes — workspace detail |
| **PRES-W05** — Attach/select/create/tailor CV and source context | `Job CV section; builder deep link` | ApplicationCvSection/TailoringSection; ApplicationAssetsService | [F:application-assets][F:application-assets.test.tsx], [F:cv-builder-deep-link][F:cv-builder-deep-link.test.tsx]; [B:ApplicationAssets][B:ApplicationAssetsTests], [B:CvBuilder][B:CvBuilderTests]. Attach re-points only, detach keeps variant, foreign owner denied, tailoring never writes source; B02 API browser | P2-D01 SQLite tailoring ordering blocks successful real-browser guidance; actual attach→render relationship beyond existing service coverage | H | Yes — document relationships |
| **PRES-W06** — Cover-letter manual/AI review, versions/restore and dirty protection | `Job cover-letter section` | ApplicationCoverLetterSection, CoverLetterAiAssistant; assets/history | [F:application-assets][F:application-assets.test.tsx], [F:application-workspace-overlay][F:application-workspace-overlay.test.tsx]; [B:ApplicationAssets][B:ApplicationAssetsTests]. Manual save/discard/restore, linked-CV AI explicit apply then save, version history owner scope | Whole-browser AI reject/provider success and cover-letter history persistence; no actual model called here | H | Yes — workspace/content editing |
| **PRES-W07** — Application drafts and immutable submitted package/archived binaries | `Job cover-letter drafts; CV section package history` | ApplicationPackageDraftsSection, SubmittedPackagesSection; package service/storage | [F:application-assets][F:application-assets.test.tsx], [F:application-drafts][F:application-drafts.test.ts]; [B:SubmittedApplicationPackage][B:SubmittedApplicationPackageTests], [B:JobApplicationsApplicationPackage][B:JobApplicationsApplicationPackageTests], [B:JobApplicationsEndpointBehavior][B:JobApplicationsEndpointBehaviorTests]. Drafts keep ordinary notes; service frozen document/hash/copy-failure/tenant assertions; NEW native browser capture oracle | P2-D01 expected failure: SQLite capture 500. NEW post-capture mutation/archive assertions are NOT reached. Require real storage/provider success before package restructuring | H | Yes — documents/submission |
| **PRES-W08** — Attachments upload/category/name/AI consent/preview/download/delete | `Job overview Documents accordion; package archive` | Attachments; AttachmentsController, AttachmentStorage | [F:attachments][F:attachments.test.tsx]; [B:AttachmentsController][B:AttachmentsControllerTests], [B:AttachmentConsistency][B:AttachmentConsistencyTests], [B:AttachmentFlagsRecompute][B:AttachmentFlagsRecomputeTests]. Empty/flag controls, storage consistency and owned download; NEW browser fixture uploads real UTF-8 binary | Archived bytes after live delete blocked by W07; drag/drop, rename and download browser journey | H | Yes — document surfaces |
| **PRES-W09** — Interview questions/answers/ready/manual removal/AI review | `Job interview section` | InterviewPrep; interview persistence/AI endpoints | [F:interview-prep][F:interview-prep.test.tsx]; [B:InterviewPrepPersistence][B:InterviewPrepPersistenceTests], [B:InterviewAiContext][B:InterviewAiContextTests], [B:InterviewPrep][B:InterviewPrepTests]. Explicit AI add, manual/answer readiness, relevant prompt context without invented defaults | Populated mobile keyboard editing and real reload; actual model quality separate | H | Yes — workspace AI |
| **PRES-W10** — Debrief/follow-up draft/date/readiness | `Job interview section; follow-up entry` | InterviewPrep; follow-up draft services | [F:interview-prep][F:interview-prep.test.tsx]; [B:JobApplicationsFollowUpDraft][B:JobApplicationsFollowUpDraftTests], [B:InterviewPrep][B:InterviewPrepTests]. Neutral debrief template and follow-up content handling | Timezone/reload browser chain; calendar defects remain outside Phase 2 | M | Yes — workspace workflow |
| **PRES-W11** — Reminder rules/actions/notifications, ICS and Google/Outlook events | `/reminders; job calendar actions; /settings` | RemindersView, RulesSettingsCard, ApplicationCalendarActions; calendar/rules APIs | [F:daily-control-loop][F:daily-control-loop.test.tsx], [F:application-calendar][F:application-calendar.test.tsx], [F:settings-view][F:settings-view.test.tsx]; [B:ApplicationCalendarController][B:ApplicationCalendarControllerTests], [B:RulesEngineProspect][B:RulesEngineProspectTests], [B:NotificationSettingsController][B:NotificationSettingsControllerTests]. Provider write confirmation, ICS and permission; NEW per-scenario rule payload and real saved preference checks | DST/date-only correctness and real provider timezone must be resolved separately; initial rules 500 not reproduced | H | Yes — scheduling/navigation |
| **PRES-W12** — Async progress/failure/retry/cancel/notifications | `/operations; profile processing` | OperationsPage, CareerProfilePage; operation store/worker | [F:operations-page][F:operations-page.test.tsx], [F:profile-page][F:profile-page.test.tsx]; [B:OperationsController][B:OperationsControllerTests], [B:UserOperationStore][B:UserOperationStoreTests], [B:CvProcessingOperation][B:CvProcessingOperationTests]. Failure retry/cancel, durable operation IDs; extraction polling keeps dirty fields | Long-lived disconnect/reconnect and worker fault/recovery operational gaps | H | Yes — notifications/import |
| **PRES-C01** — Canonical structured career data and long-tail/custom sections | `/career?section=profile` | CareerProfilePage, career views; CareerProfileService/controller | [F:profile-page][F:profile-page.test.tsx]; [B:CareerProfileController][B:CareerProfileControllerTests], [B:CareerProfileService][B:CareerProfileServiceTests]. Structured field changes propagate to saved singleton, stable child IDs and relational roundtrip; NEW CV edit leaves master unchanged | Every long-tail section/mobile keyboard save; concurrent canonical saves outside scope | H | Yes — career workflow |
| **PRES-C02** — Profile overview/completeness/import guidance/raw copy/history/restore | `/career` | CareerWorkspacePage, CareerProfilePage; version service | [F:career-workspace-page][F:career-workspace-page.test.tsx], [F:profile-page][F:profile-page.test.tsx]; [B:CareerProfileService][B:CareerProfileServiceTests]. First/returning state, completeness/errors and non-destructive history restore | Clipboard/avatar and full browser historical restore; real extraction quality separate | M | Yes — career entry |
| **PRES-C03** — Upload/extract/rebuild/reprocess/diff/select/accept/discard/retry/cancel | `Career import/review` | CareerProfilePage; profile-CV/processing/diff services | [F:profile-page][F:profile-page.test.tsx]; [B:ProfileCvController][B:ProfileCvControllerTests], [B:CvProfileDiffService][B:CvProfileDiffServiceTests], [B:CvProcessingOperation][B:CvProcessingOperationTests], [B:CvExtractionCoverage][B:CvExtractionCoverageTests]. Queue/upload, operation retry, pending explicit apply and polling retains edits; extraction/diff service checks | Manual low-confidence deselection/discard UI; OCR/scanned/unusual real-document quality | H | Yes — career import |
| **PRES-C04** — Evidence bank source/category/tags/add/list/delete | `Career overview/evidence` | CareerEvidenceBank; CareerEvidenceController | [F:career-evidence][F:career-evidence.test.tsx]; [B:CareerEvidenceController][B:CareerEvidenceControllerTests]. Sourced evidence creation, tags and owner boundaries | Delete and many-item search/keyboard browser flow | M | Yes — canonical evidence |
| **PRES-C05** — CV library create/copy/delete/templates/share/publication/PDF | `/career/builder` | CvBuilderPage, thumbnails; CvVariantService/PublicCvController | [F:cv-builder-page][F:cv-builder-page.test.tsx], [F:cv-template-thumbnail][F:cv-template-thumbnail.test.tsx]; [B:CvBuilder][B:CvBuilderTests], [B:PublicCvController][B:PublicCvControllerTests]. General/job-specific creation, delete cancel, duplication fresh slug, public revoke/republish; existing public browser PDF | Complete library→publish/unpublish/revoke old link browser chain | H | Yes — CV workspace |
| **PRES-C06** — Studio tabs, preview debounce/pages/fit/save state/mobile panes | `/career/builder/:id` | CvBuilderEditor, CvPreviewPane; preview API | [F:cv-builder-deep-link][F:cv-builder-deep-link.test.tsx]; [B:CvBuilder][B:CvBuilderTests]. Five tabs, deep-link/error/save state and preview retry; NEW real mobile Edit/Preview, reload and desktop split | Keyboard preview scroll/focus (known F15), all theme/page interactions | H | Yes — representative editor |
| **PRES-C07** — Visibility/order/custom sections and item/document overrides | `Builder Content` | CvBuilderEditor, CV controls/useDragReorder; resolver/settings | [F:cv-builder-deep-link][F:cv-builder-deep-link.test.tsx], [F:cvBuilder][F:cvBuilder.test.ts]; [B:CvBuilder][B:CvBuilderTests]. Add/edit/delete/order/hide, override renders without master change; NEW persisted headline and sibling isolation | Native drag/touch and override reset-to-source browser flow; item-level real saved overrides beyond headline | H | Yes — editing controls |
| **PRES-C08** — Capability-aware templates/typography/color/header/skills/sidebar/photo | `Builder Template/Customize` | CvCustomizeTab, registry; ThemedCvRenderer | [F:cv-builder-deep-link][F:cv-builder-deep-link.test.tsx], [F:cvBuilder][F:cvBuilder.test.ts], [F:cv-template-thumbnail][F:cv-template-thumbnail.test.tsx]; [B:CvBuilder][B:CvBuilderTests]. Template change clears incompatible defaults; normalized overrides reach shared renderer, safe color/markdown | Complete template×advanced control visual/render parity; inaccessible color controls baseline | M | Yes — component foundation |
| **PRES-C09** — A4/Letter/density/margins/dates/document locale/section presentation | `Builder Customize/Content` | cvBuilder, CvCustomizeTab; settings normalization/render model | [F:cvBuilder][F:cvBuilder.test.ts], [F:cv-builder-deep-link][F:cv-builder-deep-link.test.tsx]; [B:CvBuilder][B:CvBuilderTests]. Page sizes, NB controls/default labels/dates, section grid/columns; existing long searchable PDF browser | All advanced combinations, print margins and long localized text manual review | M | Yes — controls must remain |
| **PRES-C10** — Undo/redo, save queue, dirty guard, history restore/preview lifecycle | `Builder editor/history` | CvBuilderEditor; CvVariantService version history | [F:cv-builder-deep-link][F:cv-builder-deep-link.test.tsx]; [B:CvBuilder][B:CvBuilderTests]. Undo/redo, failed save retry, guard cancel, restore-as-new-version; NEW saved override reload | Multi-browser stale-CV revision remains separate defect; history-tab browser restore and offline navigation | H | Yes — do not alter persistence contract |
| **PRES-C11** — Section and whole-document AI improve/translate/instruction/grammar review | `Builder AI Tools/content assistant` | AiSectionAssistant, DocumentAiTab; CV AI assist | [F:ai-section-assistant][F:ai-section-assistant.test.tsx], [F:cv-builder-deep-link][F:cv-builder-deep-link.test.tsx]; [B:AiWorkspace][B:AiWorkspaceTests], [B:AiPrivacyPolicy][B:AiPrivacyPolicyTests], [B:ProEntitlementAuthorization][B:ProEntitlementAuthorizationTests]. NEW reject/close, edited acceptance, failure retry, whole-document apply/reject and malformed/missing/duplicate IDs rejected; no implicit PUT | Real model factual validation and section→whole review interactions; Free-disabled controls proven in new browser | H | Yes — AI interaction pattern |
| **PRES-C12** — Private PDF and published anonymous CV/PDF, long searchable pages | `Builder export; /cv/:slug` | PublicCvPage, cvBuilder; HtmlPdfGenerator/shared admission | [F:public-cv-page][F:public-cv-page.test.tsx]; [B:PublicCvController][B:PublicCvControllerTests], [B:PdfRenderAdmission][B:PdfRenderAdmissionTests], [B:CvBuilder][B:CvBuilderTests]. Anonymous public rendering versus private refusal; B03 shared capacity/failure; real multipage/public PDF browser | All templates/print engines; deployed Chromium/font behavior and PDF manual inspection | H | Yes — export/preview links |
| **PRES-M01** — Job-scoped manual correspondence/search/delete/paste import | `Workspace communication accordion` | Correspondence; CorrespondenceController | [F:end-to-end-trust-loop][F:end-to-end-trust-loop.test.tsx], [F:correspondence-search-context][F:correspondence-search-context.test.ts]; [B:CorrespondenceController][B:CorrespondenceControllerTests]. Canonical communication entry does not send mail, job context and CRUD ownership | Manual note edit/delete/paste-import real browser; preserve send/log distinction | H | Yes — workspace/correspondence |
| **PRES-M02** — Gmail candidate/thread import/refresh/sync/unlink/relink/move | `Job communication Gmail tools` | Correspondence; GmailController/provider | [F:correspondence-gmail-import][F:correspondence-gmail-import.test.tsx]; [B:GmailController][B:GmailControllerTests], [B:EmailOwnershipIntegration][B:EmailOwnershipIntegrationTests]. Candidates, selected thread import/move/refresh and diagnostics; owner-bound relationships | Real provider/sandbox threads, multi-account reconnect and sync failure | H | Yes — relationship controls |
| **PRES-M03** — Global inbox filters/page/provider/link/direction/detail/source links | `/correspondence` | CorrespondenceInboxPage; EmailController | [F:correspondence-inbox-page][F:correspondence-inbox-page.test.tsx]; [B:EmailController][B:EmailControllerTests], [B:EmailOwnershipIntegration][B:EmailOwnershipIntegrationTests]. Filter/pagination/detail/provider read failure distinct from empty | Real large mailbox and attachment binary browser flows | H | Yes — inbox/navigation |
| **PRES-M04** — Compose/reply/provider/recipient/job; draft conflicts; confirm send/uncertainty | `/correspondence drafts/compose` | CorrespondenceInboxPage; EmailDrafts/Send/attempt store | [F:correspondence-inbox-page][F:correspondence-inbox-page.test.tsx]; [B:EmailDraftsController][B:EmailDraftsControllerTests], [B:EmailDraftPersistence][B:EmailDraftPersistenceTests], [B:EmailSendController][B:EmailSendControllerTests], [B:EmailSendAttemptStore][B:EmailSendAttemptStoreTests], [B:EmailProviderDelivery][B:EmailProviderDeliveryTests]. Revision conflicts, explicit send confirm, uncertain state has no automatic retry, deliberate new attempt | Actual sandbox mail delivery and browser reconnect; do not loosen idempotency/conflict guards | H | Yes — high-risk drafts/navigation |
| **PRES-M05** — Review proposed jobs/link/reject/keep/notes/sync/create | `/correspondence?view=review; /correspondence/review` | GmailReviewPage, CorrespondenceInboxPage; Gmail review | [F:gmail-review-page][F:gmail-review-page.test.tsx], [F:correspondence-inbox-page][F:correspondence-inbox-page.test.tsx]; [B:GmailController][B:GmailControllerTests]. Review decisions/localization and inbox review entry | Real App legacy alias and suggested-job create browser workflow | H | Yes — merged review entry |
| **PRES-M06** — Gmail/Graph/IMAP connect/status/disconnect separate from sign-in | `/settings/connected-accounts` | EmailProviderConnections; Google/Graph/IMAP controllers | [F:email-provider-connections][F:email-provider-connections.test.tsx]; [B:MicrosoftGraphController][B:MicrosoftGraphControllerTests], [B:ImapController][B:ImapControllerTests], [B:ImapServiceSsrfGuard][B:ImapServiceSsrfGuardTests], [B:GmailController][B:GmailControllerTests]. Connection state/NB/IMAP form and provider ownership; outbound IMAP safety | Actual provider authorization/revocation/token expiry; no credentials contacted in new tests | H | Yes — settings/account |
| **PRES-S01** — Personal metrics/activity/funnel/company/skills/stage/salary/next actions | `/dashboard` | DashboardView; analytics/controllers | [F:daily-control-loop][F:daily-control-loop.test.tsx], [F:workflow-trust-signals][F:workflow-trust-signals.test.tsx]; [B:StageAnalytics][B:StageAnalyticsTests], [B:AnalyticsSalary][B:AnalyticsSalaryTests]. Empty dashboard prioritizes onboarding, attention deep links; server analytics facts | Chart transformation fidelity and full populated/timezone browser dashboard | M | Yes — dashboard/analytics |
| **PRES-S02** — Owner-local five visibility preferences/onboarding dismiss/notifications | `/dashboard; notification bell` | DashboardView, OnboardingChecklist, NotificationsPopover; notification APIs | [F:onboarding-checklist][F:onboarding-checklist.test.tsx], [F:notifications-popover][F:notifications-popover.test.tsx], [F:app-shell-notifications][F:app-shell-notifications.test.tsx], [F:account-scoped-state][F:account-scoped-state.test.tsx]; [B:JobLifecycleEvent][B:JobLifecycleEventTests]. Onboarding actions; unread/read/dismiss UI and owner-scoped storage primitive | Five dashboard toggles/reload per account not fully tested at mounted dashboard; ordering/resizing is new work | M | Yes — preserve current preferences |
| **PRES-S03** — Rules/reminder/table/theme/language preferences and account links | `/settings tabs/mobile selector` | SettingsView, RulesSettingsCard, theme/i18n; rules/notification/auth | [F:settings-view][F:settings-view.test.tsx], [F:theme-state][F:theme-state.test.tsx], [F:i18n-provider][F:i18n-provider.test.tsx]; [B:NotificationSettingsController][B:NotificationSettingsControllerTests], [B:RulesEngineProspect][B:RulesEngineProspectTests]. NEW failed notification retry, rule scenario preservation; NEW mobile→desktop edit + native notification/rule persistence | Initial single rules 500 unconfirmed; unsaved rule drafts are not persisted across tab unmount; table preference/router identity change gap | H | Yes — shared preferences/navigation |
| **PRES-S04** — AI privacy/usage/limits/Free-Pro/server entitlement/conditional billing | `/settings; locked AI controls; plans` | AiPrivacySettingsCard, AiUsageCard, AccountPlanProvider; billing/entitlement | [F:settings-view][F:settings-view.test.tsx], [F:ai-usage-card][F:ai-usage-card.test.tsx], [F:pro-feature-notice][F:pro-feature-notice.test.tsx], [F:planCatalog][F:planCatalog.test.ts]; [B:AiPrivacyPolicy][B:AiPrivacyPolicyTests], [B:AiUsageMeter][B:AiUsageMeterTests], [B:ProEntitlementAuthorization][B:ProEntitlementAuthorizationTests], [B:BillingController][B:BillingControllerTests]. Local-only AI setting, limits/locks, billing readiness; NEW real Free CV AI denied UI and API | Real checkout/webhooks/portal; billing/account deletion issues retain independent queue | H | Yes — settings/AI states |
| **PRES-S05** — Account ZIP/encrypted backup/lifecycle/deletion confirmations | `Settings Backup tab` | BackupCard; export/backup/deletion services | [F:account-data-export][F:account-data-export.test.tsx]; [B:AccountDataExport][B:AccountDataExportTests], [B:BackupController][B:BackupControllerTests], [B:AccountDeletion][B:AccountDeletionTests], [B:DatabaseBackupRunner][B:DatabaseBackupRunnerTests]. ZIP protected download, recent auth, server enable flag and exact deletion phrase; service deletion/backup | Real restored account/database and paid deletion/provider cancellation operational proof; not safe to run against production | H | Yes — account lifecycle |
| **PRES-A01** — Admin user/create/roles/reset/delete/last-admin/self guards | `/admin/users` | AdminUsersPage; UsersController | [F:admin-users-page][F:admin-users-page.test.tsx]; [B:UsersController][B:UsersControllerTests], [B:ApiAuthorizationPosture][B:ApiAuthorizationPostureTests]. Demotion/delete confirm, last-admin server checks; NEW non-admin crafted create/read denied | Role revocation during mounted admin view and actual reset delivery; route can render empty shell without granting privilege | H | Yes — separate admin area |
| **PRES-A02** — Audit list/detail/supported undo/job restore | `/admin/audit` | AdminAuditPage; AdminAuditController | [F:admin-audit-page][F:admin-audit-page.test.tsx]; [B:AuthAndSystemController][B:AuthAndSystemControllerTests], [B:ApiAuthorizationPosture][B:ApiAuthorizationPostureTests]. Unavailable-versus-empty retry and NB stored event text; server admin-only posture | Successful undo and job-restore UI against persisted API; role posture test is not undo behavior proof | H | Yes — admin navigation |
| **PRES-A03** — System/build/DB/AI/mail/SMTP/test/probe/benchmark/maintenance | `/admin/system` | AdminSystemPage; AdminSystemController/maintenance/benchmark | [F:admin-system-page][F:admin-system-page.test.tsx]; [B:AuthAndSystemController][B:AuthAndSystemControllerTests], [B:MaintenanceJobDispatcher][B:MaintenanceJobDispatcherTests], [B:CvCorpusHarness][B:CvCorpusHarnessTests]. Health/error/NB, SMTP submit and benchmark controls; dispatcher authorization and modes | Production SMTP/model/corpus/maintenance and sensitive-data presentation; no live jobs launched | H | Yes — admin hierarchy |
## Before changing a mapped surface
Run its mapped tests and the shared identity/route regressions. Add the missing behavioral fixture needed by the proposed change before editing that workflow. A gap can remain intentional for Phase 3 documentation; it cannot silently become permission to restructure an unprotected workflow. Keep [traceability](TRACEABILITY.md) updated per preservation ID and require real EN/NB, light/dark, desktop/mobile and keyboard evidence for each later migration.
[F:landing-page.test.tsx]: ../../../job-tracker-ui/src/landing-page.test.tsx
[F:planCatalog.test.ts]: ../../../job-tracker-ui/src/planCatalog.test.ts
[F:LandingPage.authRedirect.test.tsx]: ../../../job-tracker-ui/src/LandingPage.authRedirect.test.tsx
[B:AccountPlansTests]: ../../../JobTrackerApi.Tests/AccountPlansTests.cs
[F:login-page.test.tsx]: ../../../job-tracker-ui/src/login-page.test.tsx
[F:verify-email-page.test.tsx]: ../../../job-tracker-ui/src/verify-email-page.test.tsx
[F:turnstile-widget.test.tsx]: ../../../job-tracker-ui/src/turnstile-widget.test.tsx
[B:LocalAuthIdentityTests]: ../../../JobTrackerApi.Tests/LocalAuthIdentityTests.cs
[B:AuthAndSystemControllerTests]: ../../../JobTrackerApi.Tests/AuthAndSystemControllerTests.cs
[F:microsoft-legacy-relink-page.test.tsx]: ../../../job-tracker-ui/src/microsoft-legacy-relink-page.test.tsx
[B:GoogleTokenValidatorTests]: ../../../JobTrackerApi.Tests/GoogleTokenValidatorTests.cs
[B:MicrosoftTokenValidatorTests]: ../../../JobTrackerApi.Tests/MicrosoftTokenValidatorTests.cs
[F:two-factor-settings.test.tsx]: ../../../job-tracker-ui/src/two-factor-settings.test.tsx
[B:TwoFactorControllerTests]: ../../../JobTrackerApi.Tests/TwoFactorControllerTests.cs
[B:SessionsControllerTests]: ../../../JobTrackerApi.Tests/SessionsControllerTests.cs
[F:session-boundary.test.ts]: ../../../job-tracker-ui/src/session-boundary.test.ts
[F:account-scoped-state.test.tsx]: ../../../job-tracker-ui/src/account-scoped-state.test.tsx
[B:AuthSessionRevocationTests]: ../../../JobTrackerApi.Tests/AuthSessionRevocationTests.cs
[F:profile-page.test.tsx]: ../../../job-tracker-ui/src/profile-page.test.tsx
[B:AvatarStorageTests]: ../../../JobTrackerApi.Tests/AvatarStorageTests.cs
[F:sidebar-active-nav.test.ts]: ../../../job-tracker-ui/src/sidebar-active-nav.test.ts
[F:app-shell-notifications.test.tsx]: ../../../job-tracker-ui/src/app-shell-notifications.test.tsx
[F:quick-command-preservation.test.tsx]: ../../../job-tracker-ui/src/quick-command-preservation.test.tsx
[B:JobApplicationsAuthorizationTests]: ../../../JobTrackerApi.Tests/JobApplicationsAuthorizationTests.cs
[B:CompanyRelationshipTests]: ../../../JobTrackerApi.Tests/CompanyRelationshipTests.cs
[F:theme-state.test.tsx]: ../../../job-tracker-ui/src/theme-state.test.tsx
[F:i18n-provider.test.tsx]: ../../../job-tracker-ui/src/i18n-provider.test.tsx
[F:confirm.test.tsx]: ../../../job-tracker-ui/src/confirm.test.tsx
[F:prompt.test.tsx]: ../../../job-tracker-ui/src/prompt.test.tsx
[F:dialog-flow.test.tsx]: ../../../job-tracker-ui/src/dialog-flow.test.tsx
[B:SessionContextMiddlewareTests]: ../../../JobTrackerApi.Tests/SessionContextMiddlewareTests.cs
[B:CurrentUserIdLiveEvaluationTests]: ../../../JobTrackerApi.Tests/CurrentUserIdLiveEvaluationTests.cs
[B:OwnershipGuardTests]: ../../../JobTrackerApi.Tests/OwnershipGuardTests.cs
[F:job-discovery.test.tsx]: ../../../job-tracker-ui/src/job-discovery.test.tsx
[B:JobDiscoveryControllerTests]: ../../../JobTrackerApi.Tests/JobDiscoveryControllerTests.cs
[B:JobOpportunitySyncTests]: ../../../JobTrackerApi.Tests/JobOpportunitySyncTests.cs
[B:SavedJobSearchAlertTests]: ../../../JobTrackerApi.Tests/SavedJobSearchAlertTests.cs
[F:quick-capture.test.tsx]: ../../../job-tracker-ui/src/quick-capture.test.tsx
[B:JobImportServiceTests]: ../../../JobTrackerApi.Tests/JobImportServiceTests.cs
[B:JobImportHttpTransportTests]: ../../../JobTrackerApi.Tests/JobImportHttpTransportTests.cs
[B:JobApplicationsControllerTests]: ../../../JobTrackerApi.Tests/JobApplicationsControllerTests.cs
[F:captureUrl.test.ts]: ../../../job-tracker-ui/src/captureUrl.test.ts
[B:CvBuilderTests]: ../../../JobTrackerApi.Tests/CvBuilderTests.cs
[F:application-workspace-overlay.test.tsx]: ../../../job-tracker-ui/src/application-workspace-overlay.test.tsx
[F:daily-control-loop.test.tsx]: ../../../job-tracker-ui/src/daily-control-loop.test.tsx
[B:JobApplicationsEndpointBehaviorTests]: ../../../JobTrackerApi.Tests/JobApplicationsEndpointBehaviorTests.cs
[F:confirm-flow.test.tsx]: ../../../job-tracker-ui/src/confirm-flow.test.tsx
[B:JobLifecycleEventTests]: ../../../JobTrackerApi.Tests/JobLifecycleEventTests.cs
[F:kanban-grouped-board.test.tsx]: ../../../job-tracker-ui/src/kanban-grouped-board.test.tsx
[F:pipeline.test.ts]: ../../../job-tracker-ui/src/pipeline.test.ts
[B:JobPipelineTests]: ../../../JobTrackerApi.Tests/JobPipelineTests.cs
[F:companies-relationship.test.tsx]: ../../../job-tracker-ui/src/companies-relationship.test.tsx
[F:offer-comparison.test.tsx]: ../../../job-tracker-ui/src/offer-comparison.test.tsx
[B:AnalyticsSalaryTests]: ../../../JobTrackerApi.Tests/AnalyticsSalaryTests.cs
[F:csv-export.test.tsx]: ../../../job-tracker-ui/src/csv-export.test.tsx
[B:CsvExportTests]: ../../../JobTrackerApi.Tests/CsvExportTests.cs
[F:applicationWorkspace.test.ts]: ../../../job-tracker-ui/src/applicationWorkspace.test.ts
[F:application-route-contracts.test.ts]: ../../../job-tracker-ui/src/application-route-contracts.test.ts
[B:ApplicationWorkspaceTests]: ../../../JobTrackerApi.Tests/ApplicationWorkspaceTests.cs
[F:application-intelligence.test.tsx]: ../../../job-tracker-ui/src/application-intelligence.test.tsx
[F:workflow-trust-signals.test.tsx]: ../../../job-tracker-ui/src/workflow-trust-signals.test.tsx
[B:JobApplicationsWorkflowSignalsTests]: ../../../JobTrackerApi.Tests/JobApplicationsWorkflowSignalsTests.cs
[B:JobApplicationsAppliedDateHistoryTests]: ../../../JobTrackerApi.Tests/JobApplicationsAppliedDateHistoryTests.cs
[F:application-workflow-assist.test.tsx]: ../../../job-tracker-ui/src/application-workflow-assist.test.tsx
[B:ApplicationIntelligenceTests]: ../../../JobTrackerApi.Tests/ApplicationIntelligenceTests.cs
[B:StrategySnapshotOperationTests]: ../../../JobTrackerApi.Tests/StrategySnapshotOperationTests.cs
[F:application-checklist.test.tsx]: ../../../job-tracker-ui/src/application-checklist.test.tsx
[B:ApplicationChecklistTests]: ../../../JobTrackerApi.Tests/ApplicationChecklistTests.cs
[F:application-assets.test.tsx]: ../../../job-tracker-ui/src/application-assets.test.tsx
[F:cv-builder-deep-link.test.tsx]: ../../../job-tracker-ui/src/cv-builder-deep-link.test.tsx
[B:ApplicationAssetsTests]: ../../../JobTrackerApi.Tests/ApplicationAssetsTests.cs
[F:application-drafts.test.ts]: ../../../job-tracker-ui/src/application-drafts.test.ts
[B:SubmittedApplicationPackageTests]: ../../../JobTrackerApi.Tests/SubmittedApplicationPackageTests.cs
[B:JobApplicationsApplicationPackageTests]: ../../../JobTrackerApi.Tests/JobApplicationsApplicationPackageTests.cs
[F:attachments.test.tsx]: ../../../job-tracker-ui/src/attachments.test.tsx
[B:AttachmentsControllerTests]: ../../../JobTrackerApi.Tests/AttachmentsControllerTests.cs
[B:AttachmentConsistencyTests]: ../../../JobTrackerApi.Tests/AttachmentConsistencyTests.cs
[B:AttachmentFlagsRecomputeTests]: ../../../JobTrackerApi.Tests/AttachmentFlagsRecomputeTests.cs
[F:interview-prep.test.tsx]: ../../../job-tracker-ui/src/interview-prep.test.tsx
[B:InterviewPrepPersistenceTests]: ../../../JobTrackerApi.Tests/InterviewPrepPersistenceTests.cs
[B:InterviewAiContextTests]: ../../../JobTrackerApi.Tests/InterviewAiContextTests.cs
[B:InterviewPrepTests]: ../../../JobTrackerApi.Tests/InterviewPrepTests.cs
[B:JobApplicationsFollowUpDraftTests]: ../../../JobTrackerApi.Tests/JobApplicationsFollowUpDraftTests.cs
[F:application-calendar.test.tsx]: ../../../job-tracker-ui/src/application-calendar.test.tsx
[F:settings-view.test.tsx]: ../../../job-tracker-ui/src/settings-view.test.tsx
[B:ApplicationCalendarControllerTests]: ../../../JobTrackerApi.Tests/ApplicationCalendarControllerTests.cs
[B:RulesEngineProspectTests]: ../../../JobTrackerApi.Tests/RulesEngineProspectTests.cs
[B:NotificationSettingsControllerTests]: ../../../JobTrackerApi.Tests/NotificationSettingsControllerTests.cs
[F:operations-page.test.tsx]: ../../../job-tracker-ui/src/operations-page.test.tsx
[B:OperationsControllerTests]: ../../../JobTrackerApi.Tests/OperationsControllerTests.cs
[B:UserOperationStoreTests]: ../../../JobTrackerApi.Tests/UserOperationStoreTests.cs
[B:CvProcessingOperationTests]: ../../../JobTrackerApi.Tests/CvProcessingOperationTests.cs
[B:CareerProfileControllerTests]: ../../../JobTrackerApi.Tests/CareerProfileControllerTests.cs
[B:CareerProfileServiceTests]: ../../../JobTrackerApi.Tests/CareerProfileServiceTests.cs
[F:career-workspace-page.test.tsx]: ../../../job-tracker-ui/src/career-workspace-page.test.tsx
[B:ProfileCvControllerTests]: ../../../JobTrackerApi.Tests/ProfileCvControllerTests.cs
[B:CvProfileDiffServiceTests]: ../../../JobTrackerApi.Tests/CvProfileDiffServiceTests.cs
[B:CvExtractionCoverageTests]: ../../../JobTrackerApi.Tests/CvExtractionCoverageTests.cs
[F:career-evidence.test.tsx]: ../../../job-tracker-ui/src/career-evidence.test.tsx
[B:CareerEvidenceControllerTests]: ../../../JobTrackerApi.Tests/CareerEvidenceControllerTests.cs
[F:cv-builder-page.test.tsx]: ../../../job-tracker-ui/src/cv-builder-page.test.tsx
[F:cv-template-thumbnail.test.tsx]: ../../../job-tracker-ui/src/cv-template-thumbnail.test.tsx
[B:PublicCvControllerTests]: ../../../JobTrackerApi.Tests/PublicCvControllerTests.cs
[F:cvBuilder.test.ts]: ../../../job-tracker-ui/src/cvBuilder.test.ts
[F:ai-section-assistant.test.tsx]: ../../../job-tracker-ui/src/ai-section-assistant.test.tsx
[B:AiWorkspaceTests]: ../../../JobTrackerApi.Tests/AiWorkspaceTests.cs
[B:AiPrivacyPolicyTests]: ../../../JobTrackerApi.Tests/AiPrivacyPolicyTests.cs
[B:ProEntitlementAuthorizationTests]: ../../../JobTrackerApi.Tests/ProEntitlementAuthorizationTests.cs
[F:public-cv-page.test.tsx]: ../../../job-tracker-ui/src/public-cv-page.test.tsx
[B:PdfRenderAdmissionTests]: ../../../JobTrackerApi.Tests/PdfRenderAdmissionTests.cs
[F:end-to-end-trust-loop.test.tsx]: ../../../job-tracker-ui/src/end-to-end-trust-loop.test.tsx
[F:correspondence-search-context.test.ts]: ../../../job-tracker-ui/src/correspondence-search-context.test.ts
[B:CorrespondenceControllerTests]: ../../../JobTrackerApi.Tests/CorrespondenceControllerTests.cs
[F:correspondence-gmail-import.test.tsx]: ../../../job-tracker-ui/src/correspondence-gmail-import.test.tsx
[B:GmailControllerTests]: ../../../JobTrackerApi.Tests/GmailControllerTests.cs
[B:EmailOwnershipIntegrationTests]: ../../../JobTrackerApi.Tests/EmailOwnershipIntegrationTests.cs
[F:correspondence-inbox-page.test.tsx]: ../../../job-tracker-ui/src/correspondence-inbox-page.test.tsx
[B:EmailControllerTests]: ../../../JobTrackerApi.Tests/EmailControllerTests.cs
[B:EmailDraftsControllerTests]: ../../../JobTrackerApi.Tests/EmailDraftsControllerTests.cs
[B:EmailDraftPersistenceTests]: ../../../JobTrackerApi.Tests/EmailDraftPersistenceTests.cs
[B:EmailSendControllerTests]: ../../../JobTrackerApi.Tests/EmailSendControllerTests.cs
[B:EmailSendAttemptStoreTests]: ../../../JobTrackerApi.Tests/EmailSendAttemptStoreTests.cs
[B:EmailProviderDeliveryTests]: ../../../JobTrackerApi.Tests/EmailProviderDeliveryTests.cs
[F:gmail-review-page.test.tsx]: ../../../job-tracker-ui/src/gmail-review-page.test.tsx
[F:email-provider-connections.test.tsx]: ../../../job-tracker-ui/src/email-provider-connections.test.tsx
[B:MicrosoftGraphControllerTests]: ../../../JobTrackerApi.Tests/MicrosoftGraphControllerTests.cs
[B:ImapControllerTests]: ../../../JobTrackerApi.Tests/ImapControllerTests.cs
[B:ImapServiceSsrfGuardTests]: ../../../JobTrackerApi.Tests/ImapServiceSsrfGuardTests.cs
[B:StageAnalyticsTests]: ../../../JobTrackerApi.Tests/StageAnalyticsTests.cs
[F:onboarding-checklist.test.tsx]: ../../../job-tracker-ui/src/onboarding-checklist.test.tsx
[F:notifications-popover.test.tsx]: ../../../job-tracker-ui/src/notifications-popover.test.tsx
[F:ai-usage-card.test.tsx]: ../../../job-tracker-ui/src/ai-usage-card.test.tsx
[F:pro-feature-notice.test.tsx]: ../../../job-tracker-ui/src/pro-feature-notice.test.tsx
[B:AiUsageMeterTests]: ../../../JobTrackerApi.Tests/AiUsageMeterTests.cs
[B:BillingControllerTests]: ../../../JobTrackerApi.Tests/BillingControllerTests.cs
[F:account-data-export.test.tsx]: ../../../job-tracker-ui/src/account-data-export.test.tsx
[B:AccountDataExportTests]: ../../../JobTrackerApi.Tests/AccountDataExportTests.cs
[B:BackupControllerTests]: ../../../JobTrackerApi.Tests/BackupControllerTests.cs
[B:AccountDeletionTests]: ../../../JobTrackerApi.Tests/AccountDeletionTests.cs
[B:DatabaseBackupRunnerTests]: ../../../JobTrackerApi.Tests/DatabaseBackupRunnerTests.cs
[F:admin-users-page.test.tsx]: ../../../job-tracker-ui/src/admin-users-page.test.tsx
[B:UsersControllerTests]: ../../../JobTrackerApi.Tests/UsersControllerTests.cs
[B:ApiAuthorizationPostureTests]: ../../../JobTrackerApi.Tests/ApiAuthorizationPostureTests.cs
[F:admin-audit-page.test.tsx]: ../../../job-tracker-ui/src/admin-audit-page.test.tsx
[F:admin-system-page.test.tsx]: ../../../job-tracker-ui/src/admin-system-page.test.tsx
[B:MaintenanceJobDispatcherTests]: ../../../JobTrackerApi.Tests/MaintenanceJobDispatcherTests.cs
[B:CvCorpusHarnessTests]: ../../../JobTrackerApi.Tests/CvCorpusHarnessTests.cs
+107
View File
@@ -0,0 +1,107 @@
# Phase 2 — Preservation regression checkpoint
**Completed 2026-09-10 from `2d0f131` on `codex/ui-redesign-phase2`.** This checkpoint adds regression protection and an explicit TypeScript CI gate. **No visual redesign, application behavior change, route/layout/component restructuring, schema change or B07 work.** The Phase 1 source/audit remains intact.
## Preservation coverage
- **57/57 protected capability groups mapped (100%)**, with stable `PRES-` aliases of the original E/J/W/C/M/S/A IDs.
- **44 high-risk groups**, all mapped to existing behavioral assertions. High risk includes private identity, data edits/history/relationships, irreversible actions and shared navigation likely to be restructured.
- **147 referenced existing/new automated test files** across frontend and backend. Each row records route/entry, component/service owners, protected behavior, missing proof and expected later redesign impact.
- This percentage means **at least one core automated behavioral assertion per capability group**. It is not exhaustive sub-workflow, code, provider, mobile/accessibility or visual coverage. The matrix explicitly retains partial coverage and one native package-capture blocker.
Read the [complete preservation matrix](PRESERVATION_MATRIX.md), [traceability contract and Phase 2 change ledger](TRACEABILITY.md), and [baseline defects/intentional gaps](BASELINE_DEFECTS.md).
## High-risk additions and preserved evidence
| Workflow | New meaningful regression |
|---|---|
| Command/navigation/capture | Three component cases: job/company destination, closing clears query, add opens review without creating; real browser Ctrl+K search, legacy match link, reviewed add |
| CV section AI | Four cases: Reject/Close clear proposal without applying, edited proposal applies only on acceptance, failure retry leaves content untouched |
| Whole-CV AI | Five cases: explicit Apply/Reject; invalid JSON, missing IDs and duplicate IDs cannot become edits; save targets only selected CV |
| Cover-letter AI | One case: Reject preserves saved draft, disabled Save/no dirty state/no PUT |
| Settings | Two component cases: retry failed notification save with retained draft; changing one rule keeps other scenarios |
| Canonical profile → derived CV | Browser mobile edit/preview → reload → desktop preview; stored headline changes only selected CV, complete canonical profile and sibling remain unchanged |
| Documents/submission | Native profile/job/CV/letter/file fixture + legacy entry and capture; **expected failure P2-D01**, with desired immutable HTML/letter/archive-byte checks retained but not reached |
| Responsive preferences | Browser mobile→desktop rule draft stays, native save persists; notification false survives reload; separate direct API rule save |
| Permissions | Non-admin admin-menu entries absent, direct users GET/POST forbidden, normal CV create allowed, Free AI actions disabled and API 403 |
Existing B01 connection-time outbound protections, B02 ownership/session/cache checks, B03 rendering admission, B04 trusted principal/IP limits, B05 CSV bytes, and B06 application/configuration tests are unchanged. Mail draft revisions/uncertain sends, profile extraction acceptance, CV history, long PDF, job dirty navigation and other existing tests were retained.
## Baseline and fresh verification
Fresh local execution uses installed .NET **9.0.317**, Node dependencies already locked in the checkout, Windows/Chromium and Playwright's disposable SQLite API. There were no production requests or provider credentials. Test fixtures use only synthetic accounts and local URLs.
| Gate | Recorded Phase 1 baseline | Fresh Phase 2 result |
|---|---|---|
| Backend Release suite | 916 passing | **916 passed, 0 failed, 0 skipped**; no exclusions, count unchanged |
| Frontend Jest | 298 tests / 67 suites | **313 passed / 68 suites**; +15 cases, +1 test file; no existing test removed |
| General browser | 18 passing | **23 actually passed + 1 expected failure**, 0 unexpected failures/skips/flaky; original 18 still pass, +6 cases |
| B04 real HTTP | 9 cases | **9 passed** |
| B06 production-mode API/forwarding HTTP | 6 cases | **6 passed**; not nginx/Traefik runtime proof |
| B03 PDF burst/admission | 1 case | **1 passed**, multi-route capacity/recovery oracle unchanged |
| Compose ingress/configuration | 3 tests/17 hostile mutations | **3 passed** |
| Backend build | Release | **Pass, 0 warnings/errors**, warnings-as-errors |
| Frontend build | Next production static export | **Pass** |
| Standalone type check | Historical TS2571 repaired before Phase 2 | **Pass**; new explicit CI command, unchanged strictness |
| New browser-spec TypeScript | Previously no whole-Playwright type gate | **Pass** with temporary Node-aware config for this new spec only |
| ESLint | Zero-warning gate | **Pass**, no suppressions added |
| Tracked secret scan; diff whitespace | Required | **Pass** |
Playwright's list reporter says “24 passed” because expected failures count as expected outcomes. Its JSON records **23 `expectedStatus=passed/actual=passed` and one `expectedStatus=failed/actual=failed`**. Do not present this as 24 successful workflows. The failed native package test is not a regression introduced by application changes: there are no such changes in this branch. See P2-D01.
Intermediate test-authoring corrections: one missing test-fixture brace, Testing Library's unsupported `exact` role options, a Playwright callback named `use` misidentified as a React hook, and package capture's actual CV-tab location. Corrected test code rather than weakening lint/types or changing UI. The temporary isolated TypeScript config initially lacked Node type roots; setting the existing installed Node declarations resolved Buffer. No new dependency installed.
One initial rules-save 500 did not reproduce in the direct or repeated browser path; retained as observation P2-O01, not asserted as a diagnosed defect. The reproducible SQLite package/tailoring failure is recorded separately. No blanket skipping or gate bypass was added.
## Exact commands
From the repository root (PowerShell):
```powershell
$env:DOTNET_ROOT='C:/Users/Connor/.dotnet'
& C:/Users/Connor/.dotnet/dotnet.exe test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --configuration Release --no-restore --logger 'trx;LogFileName=phase2.trx' --results-directory .artifacts/redesign-phase2 -- xUnit.parallelizeTestCollections=false xUnit.maxParallelThreads=1
& C:/Users/Connor/.dotnet/dotnet.exe build JobTrackerApi/JobTrackerApi.csproj --configuration Release --no-restore -warnaserror
python scripts/test-ingress.py
python scripts/supply-chain.py scan-secrets
git diff --check
```
From `job-tracker-ui`:
```powershell
npm test -- --watchAll=false --runInBand
npm run typecheck
npm run lint
$env:NEXT_PUBLIC_API_BASE_URL='http://localhost:5302/api'
$env:NEXT_TELEMETRY_DISABLED='1'
npm run build
$env:DOTNET_ROOT='C:/Users/Connor/.dotnet'
$env:DOTNET_HOST_PATH='C:/Users/Connor/.dotnet/dotnet.exe'
$env:PLAYWRIGHT_STATIC_EXPORT='true'
$env:PLAYWRIGHT_PREBUILT_EXPORT='true'
$env:PLAYWRIGHT_JSON_OUTPUT_FILE='../.artifacts/phase2-browser-results.json'
node node_modules/@playwright/test/cli.js test --reporter=list,json
node node_modules/@playwright/test/cli.js test --config=playwright.rate-limits.config.ts
node node_modules/@playwright/test/cli.js test --config=playwright.ingress.config.ts
node node_modules/@playwright/test/cli.js test --config=playwright.pdf.config.ts
```
Initial focused command: `npm test -- --watchAll=false --runInBand --runTestsByPath src/quick-command-preservation.test.tsx src/ai-section-assistant.test.tsx src/cv-builder-deep-link.test.tsx src/settings-view.test.tsx` → 31 passed. Final full Jest includes the additional cover-letter reject case.
Final browser rerun after narrowing the known-failure annotation: `node node_modules/@playwright/test/cli.js test e2e/redesign-preservation.spec.ts` → five actual passes and the same one expected HTTP-500 capture failure (23.8s); no unexpected failures. Final lint and isolated browser-spec typing also pass.
Additional isolated browser-spec typing used `node node_modules/typescript/bin/tsc --project ../.artifacts/phase2-browser-tsconfig.json --noEmit`. Temporary config extends the frontend tsconfig, includes only `../job-tracker-ui/e2e/redesign-preservation.spec.ts`, sets `incremental:false`, `typeRoots:["../job-tracker-ui/node_modules/@types"]`, `types:["node"]`. This is a local check, not a claim that every pre-existing Playwright/config source is covered by the new CI type gate.
Local logs: `.artifacts/phase2-{backend,jest,build,api-build,typecheck,browser-typecheck,lint,browser,rate-limits,ingress,pdf,compose,secrets}.log`; backend TRX under `.artifacts/redesign-phase2`; browser JSON and sanitized outcome summary under `.artifacts`. Durable conclusions and reproduction remain in these committed documents/tests; raw traces with synthetic session material are not published.
The matrix was checked against the original ledger: 57 rows, 57 unique IDs, no missing/extra IDs, and all 147 test-reference paths exist. No production source file changed. Full baseline execution was not repeated for documentation-only edits.
## Remaining gaps and next phase
**Phase 2 is complete as a regression/planning checkpoint, with its documented baseline failure.** Phase 3 may define UX architecture: grouped sidebar, page/route destination and backwards compatibility, one Jobs table/board entry, canonical Career Profile/derived-document workflows, contextual AI review, and explicit mobile task rules. Keep the existing MUI/Emotion/React Router/API stack while planning. Do not implement those layouts or components in this checkpoint.
Before a later code migration, close that surface's matrix gaps or establish a deliberately scoped additional fixture. W07 package/tailoring needs native-provider success first; shared shell needs drawer/focus and bulk/saved-view cases; career/mail/admin need their specified interactive and provider proof. Screen-reader/touch/reflow and the full EN/NB × light/dark × desktop/mobile matrix remain visual/UX gates.
**F04 remains open.** The B06 repository/application changes are complete, but actual deployed proxy peer/listeners, firewall/NAT/IPv6, client IP, DNS/TLS/redirect and restart behavior remain unverified; follow the existing [operator commands](../../audits/2026-09-07/validation/b06-completion.md). Prior main CI run 743 failed in the browser worker and did not deploy; this branch's local success does not repair or certify that runner. Pushing this feature branch is a preservation checkpoint, not a production deployment or a release-readiness claim.
No Phase 3 implementation, visual redesign, component-library migration, B07 or later security ticket has begun.
+56
View File
@@ -0,0 +1,56 @@
# Redesign traceability
Phase 2 starts at `2d0f131`. The [preservation matrix](PRESERVATION_MATRIX.md) is the stable baseline: **PRES-E01…PRES-A03**, in the original ledger's groups (not one contiguous numeric range). Old references such as C07 remain valid aliases. Subdivide a later workflow with a suffix, for example `PRES-C07.override-reset`; do not reuse an ID or silently change its meaning.
## Required chain for each later change
`PRES-ID → requirement/source contract → implementation commit + changed owner → regression test + executed result → visual/UX evidence`
The requirement for every current PRES-ID is its **complete original row** in [FUNCTIONALITY_INVENTORY.md](../audit/FUNCTIONALITY_INVENTORY.md), including the source contract. A proposed new requirement uses a separate `NEW-xxx` ID referencing the product brief; it cannot overwrite a PRES requirement. Defects use their original audit IDs or the separate P2-D IDs below, not “preserve the broken behavior.”
For every implementation PR/session, add a row to this file (or link its scoped trace sheet here) with:
| Field | Required evidence |
|---|---|
| PRES-ID / requirement | Original ledger group plus the precise sub-workflow being changed; target-brief requirement if applicable |
| Baseline → destination | Existing route, capture/query/alias/keyboard entry and intended location; preserved/relocated/merged/explicitly deprecated |
| Implementation | Commit SHA, affected components/API owners, reason for the change; “unchanged” is valid but not “done” |
| Automated regression | Named behavioral test, file, command/result and provider/test layer; include negative/dirty/error cases |
| Visual/UX validation | Screenshot/video or review artifact with route, populated/empty/error/locked state, viewport, theme, locale, keyboard/focus result and date |
| Gaps / decision / reviewer | Known baseline defects and intentional missing proof; approval reference only when semantics/deprecation need it |
No screenshot alone proves data preservation. No mocked API test alone proves ownership. No successful localhost run proves F04. A shifted feature remains protected after moving to another page.
## Phase 2 changes (test/CI checkpoint only)
All application implementations and routes remain at their Phase 1 behavior. The commit containing this document identifies the Phase 2 implementation of **regression protection**, not a product migration. Use `git log -- docs/redesign/phase-2` to resolve its SHA without a self-referential commit placeholder.
| PRES IDs → existing requirement | Phase 2 change → automated oracle | Visual/UX evidence |
|---|---|---|
| E07, J04 → command job/company/add entry and reviewed capture | [quick-command-preservation](../../../job-tracker-ui/src/quick-command-preservation.test.tsx): three cases; destinations, callback and no-create boundary | No visual redesign. Native browser keyboard search/add evidence below |
| C11 → per-section review never implicitly applies | [ai-section-assistant](../../../job-tracker-ui/src/ai-section-assistant.test.tsx): reject/close, edit then accept, failed suggestion retry | jsdom interaction only; no model or visual certification |
| C07, C11 → whole-document review changes only accepted CV settings | [cv-builder-deep-link](../../../job-tracker-ui/src/cv-builder-deep-link.test.tsx): apply/reject and invalid/missing/duplicate block responses | jsdom current/proposed comparison; real canonical isolation below |
| W06 → rejected letter does not replace saved content | [application-assets](../../../job-tracker-ui/src/application-assets.test.tsx): reject clears proposal, original remains, no dirty/save | No provider contacted; real delivery remains outside this workflow |
| S03, W11 → scenario-specific rules and retryable preferences | [settings-view](../../../job-tracker-ui/src/settings-view.test.tsx): unchanged scenarios in payload; failed notification save retains draft and retries | Responsive browser preference evidence below |
| C01, C06, C07, C10 → canonical/sibling isolation and responsive editor persistence | [redesign-preservation browser](../../../job-tracker-ui/e2e/redesign-preservation.spec.ts): mobile override, preview/edit, reload, desktop preview; API compares master and sibling before/after | Current Chromium UI at 390×844 and 1440×900, EN/default theme. Behavioral viewport check, not visual approval |
| W01, W05–W08 → source relationships and frozen submission | Same browser spec: fixture creates actual job/CV/letter/file; captures via legacy CV workspace entry | **Expected failure P2-D01:** capture fails on SQLite. Later source-mutation/archive checks not reached; do not mark this migration ready |
| E07, J04, W01 → real command launcher and old links | Same browser spec: Ctrl+K job selection, actual App legacy match link, reviewed add leaves job count unchanged | Current desktop route/keyboard behavior only |
| S03, W11 → responsive rules/notification controls | Same browser spec: edit rule mobile→desktop, save, native API check; notification false survives reload; independent direct rule save | 390×844/1440×900 EN/default theme; no screenshot-based redesign |
| A01, S04, E07 → hidden admin navigation is not authorization; Free AI locks | Same browser spec: hidden admin menu, direct users read/create 403; ordinary CV creation succeeds, all three AI actions disabled, API AI 403 | Current non-admin browser + real API. Admin route shell may render, but cannot retrieve privileged data |
| All 57 → reliable test type baseline | [package script](../../../job-tracker-ui/package.json), [CI gate](../../../.gitea/workflows/ci-deploy.yml): `npm run typecheck` before Jest | Static TypeScript for existing src/app scope; no visual or app change |
## Phase 3 handoff and later migration gates
Phase 3 is **UX architecture documentation**, not a new implementation batch: grouped sidebar/page hierarchy; Jobs table/board view entry; canonical Career Profile versus derived CV/document relationships; full-page versus optional preview; contextual AI review pattern; mobile task flows. Map all 31 existing routes, both aliases, capture/notification/provider entries and all 57 PRES rows to intended destinations.
Keep current MUI/Emotion/React Router/API and ownership contracts while designing. Brand/name/accent and cross-device dashboard customization remain genuine product decisions for the dependent work; do not ask about facts already in the audit.
Before Phase 4/5 code, require an approved route/preservation plan, targeted regression fixtures for that surface's gaps, a shared accessibility/state contract, and coordinated integrity/recovery dependencies. In particular:
- Do not restructure package/tailoring workflows while P2-D01 blocks native success proof.
- Do not conflate B02 identity, stale-CV revision and create-intent idempotency. Those queue items remain distinct; B07 was not started.
- Do not drop mail draft revisions, explicit send/new-attempt controls or owner-bound references.
- Before migrating shared controls, validate EN/NB × light/dark × desktop/mobile, keyboard focus, error/empty/dirty states and representative loaded data. Real screen reader/touch/reflow tests remain manual.
- Do not label a release healthy until the previously failed CI browser runner is diagnosed and the required pipeline passes. F04 requires actual production ingress evidence.
Future trace sheets must link here. There are **no Phase 3+ implementation commits or visual sign-offs** in this checkpoint.
+7 -2
View File
@@ -1,11 +1,16 @@
# Redesign — 2026-09-09
- [x] Phase 2 from `2d0f131`, on `codex/ui-redesign-phase2`: [preservation checkpoint](../redesign/phase-2/README.md), 57 stable PRES groups mapped, 15 added component cases, six added browser cases, explicit standalone CI type gate and traceability. Fresh 916 backend / 313 frontend pass; browser 23 actual passes + one documented SQLite expected failure, B04/B06/PDF/config regressions pass. No application/visual redesign, B07 or F04 closure.
- [ ] P2-D01 separate baseline repair before package/tailoring restructuring: SQLite DateTimeOffset ordering fails in native tailoring/capture. Retain the expected-failure browser oracle; prove native capture and unchanged historical binaries after source edits/deletion before removing annotation. P2-O01 initial rules 500 remains unconfirmed; direct and browser save controls pass. See [baseline evidence](../redesign/phase-2/BASELINE_DEFECTS.md).
- [x] Read the Astra handoff and required product documents; complete Phase 1 source audit with 31 concrete route patterns, functionality preservation ledger, 266 API call sites, component/test inventory, identity/storage map, responsive/localisation assessment and migration gates. See `docs/redesign/audit/README.md`. No redesign implementation or B07 started.
- [ ] Complete the authorized `main` release through required CI/deploy gates; commits are pushed, but a push alone is not live confirmation. Compose runner prerequisite and Linux PDF fixture packaging failures were fixed without weakening tests; current outcome is recorded in the B06 completion report.
- [ ] Next redesign work: Phase 2 functional baseline, subject to the audit's integrity/recovery and release gates. No automatic implementation in Phase 1.
- [x] Finalize Phase 1 separately, 2026-09-10: classify all 57 preservation groups, UX improvements, genuinely new/conditional requirements and baseline defects; retain already-implemented CV drag/fit, custom status values, calendar and other protected workflows. Final summary/contract/test limits/Phase 2 starting point: `docs/redesign/audit/PHASE1_SUMMARY.md`. Documentation only; B06/F04 closure remains open.
- [ ] Complete the authorized `main` release through required CI/deploy gates. Main `27657f3` is pushed; run 743 passed 916 backend/298 frontend tests, lint, dependency audits and production builds, then the browser gate failed (worker SIGSEGV, retry timeout and closed-browser errors; 6 passed/12 failed). Deploy skipped; live remains version 299. Stop further release retries pending runner-host diagnostics; exact read-only handoff and evidence are in `docs/audits/2026-09-07/validation/b06-completion.md`. No gate was bypassed.
- [ ] Next redesign work: Phase 3 UX architecture/route and workflow plan using the [traceability contract](../redesign/phase-2/TRACEABILITY.md), subject to documented gaps and integrity/recovery/release gates. Not started automatically; no visual implementation authorized by this checkpoint.
# Expanded production audit — 2026-09-07
- [x] Authorized release repair, 2026-09-10: patch Next/sharp/js-yaml dependency advisories; preserve deliberate React Router full navigation and correct the historical TS2571 test assertion when the patched Next build makes it release-blocking. Local standalone tsc/build and 13 interview tests pass. R01a's explicit standalone CI command is now added in the separate Phase 2 checkpoint; deployed CI execution and F04 closure remain separate gates in the B06 report.
- [ ] B06 implementation complete; **F04 open, runtime/production verification incomplete**. Fresh 166 focused backend, 3 Compose, 6 Production HTTP and 9 B04 HTTP checks pass; prior broader/build evidence retained. Public DNS/valid HTTPS observed; ports 3000/5202/8080 unreachable from this workstation. HTTP redirects to unreachable HTTPS port 444. Docker inference-socket failure blocks real Compose/Traefik/nginx tests; actual peers/backend client IP and production host/firewall evidence require operator handoff (no documented SSH alias). See `docs/audits/2026-09-07/validation/b06-completion.md`. B01–B05 preserved; B07 not started.
- [x] B05 only: shared spreadsheet-safe text policy across all 13 CSV text columns; typed fields/storage/JSON unchanged. Original Excel `=1+1` evaluation now literal text; UTF-8 BOM and byte-preserving download retain Norwegian text. 25 focused CSV cases, 902 broader backend, 298 frontend, 18 browser tests, nine unchanged B04 HTTP scenarios and B03 admission probe pass; Release builds/lint pass. Existing R01a TS2571 remains. Evidence: `docs/audits/2026-09-07/validation/b05-completion.md`. B06 recommended next, not started.
@@ -0,0 +1,188 @@
import { expect, test as base, type APIRequestContext } from "@playwright/test";
const apiUrl = "http://localhost:5302/api";
const password = "E2ePassword123!";
const canonical = {
version: "1", contact: { fullName: "Åse Eksempel", headline: "Canonical engineer" },
jobs: [{ title: "Engineer", company: "Nordlys AS", bullets: ["Built reliable APIs"] }],
skills: ["C#", "Norsk"],
};
async function login(api: APIRequestContext, email: string) {
expect((await api.post(`${apiUrl}/auth/login`, { data: { email, password } })).status()).toBe(200);
const me = await (await api.get(`${apiUrl}/auth/me`)).json();
const csrf = (await api.storageState()).cookies.find(cookie => cookie.name === "XSRF-TOKEN")!.value;
return { "X-CSRF-TOKEN": csrf, "X-Session-Context": me.sessionContext as string };
}
// Every case gets a real non-admin account in Playwright's disposable local API database.
// No API responses are mocked; browser traffic cannot leave the local test environment.
const test = base.extend<{ headers: Record<string, string> }>({
headers: async ({ playwright, page, context }, provide) => {
const admin = await playwright.request.newContext();
try {
const adminHeaders = await login(admin, "e2e@example.test");
const email = `pres-${Date.now()}-${Math.random().toString(36).slice(2)}@example.test`;
expect((await admin.post(`${apiUrl}/users`, { headers: adminHeaders, data: { email, password, roles: [] } })).status()).toBe(200);
const headers = await login(page.request, email);
await context.route("**/*", route => {
const url = new URL(route.request().url());
return ["localhost", "127.0.0.1"].includes(url.hostname) || ["data:", "blob:", "about:"].includes(url.protocol)
? route.continue() : route.abort();
});
await context.addInitScript(() => localStorage.setItem("uiLanguage", "en"));
expect((await page.request.put(`${apiUrl}/career/profile`, { headers, data: { profile: canonical } })).status()).toBe(200);
await provide(headers);
} finally { await admin.dispose(); }
},
});
test("[PRES-C01 PRES-C06 PRES-C07 PRES-C10] mobile CV override persists without changing the canonical profile or sibling CV", async ({ page, headers }) => {
const create = async (name: string) => {
const response = await page.request.post(`${apiUrl}/cv/variants`, { headers, data: { name } });
expect(response.status()).toBe(200);
return response.json();
};
const selected = await create("Selected CV");
const sibling = await create("Sibling CV");
const profileBefore = await (await page.request.get(`${apiUrl}/career/profile`)).json();
await page.setViewportSize({ width: 390, height: 844 });
await page.goto(`/career/builder/${selected.id}`);
await page.getByRole("tab", { name: "Content", exact: true }).click();
await page.getByLabel("Headline override", { exact: true }).fill("Reviewed CV-only headline");
await page.getByRole("button", { name: "Save now", exact: true }).click();
await expect(page.getByText("Saved", { exact: true })).toBeVisible();
await page.getByRole("button", { name: "Preview", exact: true }).click();
await expect(page.getByTitle("CV preview page 1")).toBeVisible();
await page.getByRole("button", { name: "Edit", exact: true }).click();
await expect(page.getByLabel("Headline override", { exact: true })).toHaveValue("Reviewed CV-only headline");
await page.reload();
await page.getByRole("tab", { name: "Content", exact: true }).click();
await expect(page.getByLabel("Headline override", { exact: true })).toHaveValue("Reviewed CV-only headline");
await page.setViewportSize({ width: 1440, height: 900 });
await expect(page.getByTitle("CV preview page 1")).toBeVisible();
expect((await (await page.request.get(`${apiUrl}/cv/variants/${selected.id}`)).json()).settings.headline).toBe("Reviewed CV-only headline");
expect(await (await page.request.get(`${apiUrl}/cv/variants/${sibling.id}`)).json()).toEqual(sibling);
expect((await (await page.request.get(`${apiUrl}/career/profile`)).json()).profile).toEqual(profileBefore.profile);
});
test("[PRES-W01 PRES-W05 PRES-W06 PRES-W07 PRES-W08] legacy workspace preserves frozen documents after current sources change", async ({ page, headers }) => {
const companyResponse = await page.request.post(`${apiUrl}/companies`, { headers, data: { name: "Preservation AS" } });
expect(companyResponse.status()).toBe(201);
const company = await companyResponse.json();
const jobResponse = await page.request.post(`${apiUrl}/jobapplications`, { headers, data: { companyId: company.id, jobTitle: "Preserved workspace", status: "Saved" } });
expect(jobResponse.ok()).toBe(true);
const job = await jobResponse.json();
const variantResponse = await page.request.post(`${apiUrl}/cv/variants`, { headers, data: { name: "Submitted CV", jobApplicationId: job.id } });
expect(variantResponse.status()).toBe(200);
const variant = await variantResponse.json();
const jobUrl = `${apiUrl}/jobapplications/${job.id}`;
expect((await page.request.put(`${jobUrl}/cv`, { headers, data: { variantId: variant.id } })).status()).toBe(200);
expect((await page.request.put(`${jobUrl}/cover-letter`, { headers, data: { text: "Original reviewed letter", source: "manual" } })).status()).toBe(200);
const bytes = Buffer.from("Original attachment — æøå\n", "utf8");
const upload = await page.request.post(`${apiUrl}/attachments`, { headers, multipart: { jobId: String(job.id), files: { name: "evidence.txt", mimeType: "text/plain", buffer: bytes } } });
expect(upload.ok()).toBe(true);
const [attachment] = await (await page.request.get(`${apiUrl}/attachments/${job.id}`)).json();
await page.goto(`/applications/${job.id}?section=cv`);
await expect(page).toHaveURL(new RegExp(`/jobs/${job.id}\\?section=cv$`));
const captured = page.waitForResponse(response => response.url() === `${jobUrl}/submitted-packages` && response.request().method() === 'POST');
await page.getByRole("button", { name: "Freeze current package", exact: true }).click();
const captureResponse = await captured;
// P2-D01: native SQLite rejects the service's DateTimeOffset ORDER BY. Only
// this known response is expected; setup failures and later archive failures
// after a successful capture must remain ordinary failing assertions.
if (captureResponse.status() === 500) {
test.fail(true, 'P2-D01: SQLite submitted-package capture; phase-2/BASELINE_DEFECTS.md');
}
expect(captureResponse.status()).toBe(200);
await expect(page.getByText("Submission package v1", { exact: true })).toBeVisible();
const [snapshot] = await (await page.request.get(`${jobUrl}/submitted-packages`)).json();
const before = await (await page.request.get(`${jobUrl}/submitted-packages/${snapshot.id}`)).json();
expect(before.coverLetterText).toBe("Original reviewed letter");
expect(before.renderedCvHtml).toContain("Canonical engineer");
expect(snapshot.cvVariantId).toBe(variant.id);
expect(snapshot.attachments).toHaveLength(1);
expect((await page.request.put(`${jobUrl}/cover-letter`, { headers, data: { text: "Later letter", source: "manual" } })).status()).toBe(200);
expect((await page.request.put(`${apiUrl}/cv/variants/${variant.id}`, { headers, data: { name: "Later CV", settings: { ...variant.settings, headline: "Later headline" } } })).status()).toBe(200);
expect((await page.request.delete(`${apiUrl}/attachments/${attachment.id}`, { headers })).status()).toBe(204);
expect(await (await page.request.get(`${jobUrl}/submitted-packages/${snapshot.id}`)).json()).toEqual(before);
const archived = await page.request.get(`${jobUrl}/submitted-packages/${snapshot.id}/attachments/${snapshot.attachments[0].id}`);
expect(archived.status()).toBe(200);
expect(await archived.body()).toEqual(bytes);
await page.reload();
await page.getByRole("button", { name: "View preserved package", exact: true }).click();
await expect(page.getByRole("dialog").getByText("Original reviewed letter", { exact: true })).toBeVisible();
});
test("[PRES-S03 PRES-W11] responsive settings navigation keeps rule edits and persists notification preferences", async ({ page, headers }) => {
expect((await page.request.get(`${apiUrl}/auth/me`, { headers })).status()).toBe(200);
await page.setViewportSize({ width: 390, height: 844 });
await page.goto("/settings");
await page.getByLabel("Settings section", { exact: true }).click();
await page.getByRole("option", { name: "Follow-ups", exact: true }).click();
const field = page.getByLabel("Applied: follow-up days", { exact: true });
await field.fill("5");
await page.setViewportSize({ width: 1440, height: 900 });
await expect(field).toHaveValue("5");
const saveRules = page.waitForResponse(response => response.url() === `${apiUrl}/rules` && response.request().method() === 'PUT');
await page.getByRole("button", { name: "Save Rules", exact: true }).click();
expect((await saveRules).status()).toBe(204);
await expect.poll(async () => (await (await page.request.get(`${apiUrl}/rules`)).json()).appliedFollowUpDays).toBe(5);
await page.getByRole("tab", { name: "Notifications", exact: true }).click();
await page.getByLabel("Email reminders for follow-ups", { exact: true }).uncheck();
await page.getByRole("button", { name: "Save notification settings", exact: true }).click();
await expect.poll(async () => (await (await page.request.get(`${apiUrl}/notification-settings`)).json()).emailFollowUpRemindersEnabled).toBe(false);
await page.reload();
await page.getByRole("tab", { name: "Notifications", exact: true }).click();
await expect(page.getByLabel("Email reminders for follow-ups", { exact: true })).not.toBeChecked();
});
test("[PRES-S03] rule changes persist through the real API", async ({ page, headers }) => {
const settings = await (await page.request.get(`${apiUrl}/rules`)).json();
const saved = await page.request.put(`${apiUrl}/rules`, { headers, data: { ...settings, appliedFollowUpDays: 5 } });
expect(saved.status()).toBe(204);
expect((await (await page.request.get(`${apiUrl}/rules`)).json()).appliedFollowUpDays).toBe(5);
});
test("[PRES-E07 PRES-W01 PRES-J04] keyboard command navigation and legacy workspace entry preserve the selected job", async ({ page, headers }) => {
const companyResponse = await page.request.post(`${apiUrl}/companies`, { headers, data: { name: "Command AS" } });
expect(companyResponse.status()).toBe(201);
const jobResponse = await page.request.post(`${apiUrl}/jobapplications`, { headers,
data: { companyId: (await companyResponse.json()).id, jobTitle: "Command engineer", status: "Saved" } });
expect(jobResponse.ok()).toBe(true);
const job = await jobResponse.json();
await page.goto("/dashboard");
await expect(page.getByRole("heading", { name: "Dashboard", exact: true })).toBeVisible();
await page.keyboard.press("Control+k");
await page.getByRole("dialog").getByRole("textbox").fill("Command engineer");
await page.getByText("Command AS - Command engineer", { exact: true }).click();
await expect(page).toHaveURL(new RegExp(`/jobs/${job.id}$`));
await expect(page.getByRole("heading", { name: "Command engineer", exact: true })).toBeVisible();
await page.goto(`/applications/${job.id}?section=match`);
await expect(page).toHaveURL(new RegExp(`/jobs/${job.id}\\?section=match$`));
await expect(page.getByRole("tab", { name: "Analysis", exact: true })).toHaveAttribute("aria-selected", "true");
await page.keyboard.press("Control+k");
await page.getByRole("dialog").getByText("Add new job", { exact: true }).click();
await expect(page.getByRole("dialog")).toBeVisible();
expect((await (await page.request.get(`${apiUrl}/jobapplications`)).json()).total).toBe(1);
});
test("[PRES-A01 PRES-S04 PRES-E07] non-admin navigation cannot grant API privileges or unlock CV AI", async ({ page, headers }) => {
await page.goto("/dashboard");
await expect(page.getByRole("heading", { name: "Dashboard", exact: true })).toBeVisible();
for (const name of ["Users", "Audit log", "System"]) await expect(page.getByRole("button", { name, exact: true })).toHaveCount(0);
const denied = page.waitForResponse(response => response.url() === `${apiUrl}/users`);
await page.goto("/admin/users");
expect((await denied).status()).toBe(403);
await expect(page.getByText("e2e@example.test", { exact: true })).toHaveCount(0);
expect((await page.request.post(`${apiUrl}/users`, { headers, data: { email: "must-not-exist@example.test", password, roles: ["Admin"] } })).status()).toBe(403);
const created = await page.request.post(`${apiUrl}/cv/variants`, { headers, data: { name: "Free CV" } });
expect(created.status()).toBe(200);
await page.goto(`/career/builder/${(await created.json()).id}`);
await page.getByRole("tab", { name: "AI Tools", exact: true }).click();
const actions = page.getByRole("button", { name: "Review suggestion", exact: true });
await expect(actions).toHaveCount(3);
for (const action of await actions.all()) await expect(action).toBeDisabled();
expect((await page.request.post(`${apiUrl}/cv/ai/assist`, { headers, data: { action: "improve", text: "Safe test text" } })).status()).toBe(403);
expect((await (await page.request.get(`${apiUrl}/auth/me`)).json()).roles).toEqual([]);
});
+2
View File
@@ -15,6 +15,8 @@ export default defineConfig([
"react-hooks/purity": "off",
"@next/next/no-page-custom-font": "off",
"@next/next/no-img-element": "off",
// React Router owns this static SPA; full navigation also clears private session state.
"@next/next/no-location-assign-relative-destination": "off",
},
},
{
+269 -176
View File
@@ -26,7 +26,7 @@
"@types/react-dom": "^19.2.3",
"axios": "^1.19.0",
"date-fns": "^4.1.0",
"next": "^16.2.12",
"next": "^16.3.3",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-router-dom": "^7.18.2",
@@ -42,7 +42,7 @@
"@types/jest": "^30.0.0",
"babel-jest": "^30.4.1",
"eslint": "^9.39.5",
"eslint-config-next": "^16.2.12",
"eslint-config-next": "^16.3.3",
"identity-obj-proxy": "^3.0.0",
"jest": "^30.4.2",
"jest-environment-jsdom": "^30.4.1"
@@ -2610,9 +2610,9 @@
}
},
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz",
"integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.4.tgz",
"integrity": "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==",
"cpu": [
"arm64"
],
@@ -2628,13 +2628,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.3.2"
"@img/sharp-libvips-darwin-arm64": "1.3.3"
}
},
"node_modules/@img/sharp-darwin-x64": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz",
"integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.4.tgz",
"integrity": "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==",
"cpu": [
"x64"
],
@@ -2650,20 +2650,20 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.3.2"
"@img/sharp-libvips-darwin-x64": "1.3.3"
}
},
"node_modules/@img/sharp-freebsd-wasm32": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz",
"integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.4.tgz",
"integrity": "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==",
"license": "Apache-2.0",
"optional": true,
"os": [
"freebsd"
],
"dependencies": {
"@img/sharp-wasm32": "0.35.3"
"@img/sharp-wasm32": "0.35.4"
},
"engines": {
"node": ">=20.9.0"
@@ -2673,9 +2673,9 @@
}
},
"node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz",
"integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.3.tgz",
"integrity": "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==",
"cpu": [
"arm64"
],
@@ -2689,9 +2689,9 @@
}
},
"node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz",
"integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.3.tgz",
"integrity": "sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==",
"cpu": [
"x64"
],
@@ -2705,12 +2705,15 @@
}
},
"node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz",
"integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.3.tgz",
"integrity": "sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==",
"cpu": [
"arm"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -2721,12 +2724,15 @@
}
},
"node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz",
"integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.3.tgz",
"integrity": "sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==",
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -2737,12 +2743,15 @@
}
},
"node_modules/@img/sharp-libvips-linux-ppc64": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz",
"integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.3.tgz",
"integrity": "sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==",
"cpu": [
"ppc64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -2753,12 +2762,15 @@
}
},
"node_modules/@img/sharp-libvips-linux-riscv64": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz",
"integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.3.tgz",
"integrity": "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==",
"cpu": [
"riscv64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -2769,12 +2781,15 @@
}
},
"node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz",
"integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.3.tgz",
"integrity": "sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==",
"cpu": [
"s390x"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -2785,12 +2800,15 @@
}
},
"node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz",
"integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.3.tgz",
"integrity": "sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==",
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -2801,12 +2819,15 @@
}
},
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz",
"integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.3.tgz",
"integrity": "sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==",
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -2817,12 +2838,15 @@
}
},
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz",
"integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.3.tgz",
"integrity": "sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==",
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@@ -2833,12 +2857,15 @@
}
},
"node_modules/@img/sharp-linux-arm": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz",
"integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.4.tgz",
"integrity": "sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==",
"cpu": [
"arm"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -2851,16 +2878,19 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.3.2"
"@img/sharp-libvips-linux-arm": "1.3.3"
}
},
"node_modules/@img/sharp-linux-arm64": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz",
"integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.4.tgz",
"integrity": "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==",
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -2873,16 +2903,19 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.3.2"
"@img/sharp-libvips-linux-arm64": "1.3.3"
}
},
"node_modules/@img/sharp-linux-ppc64": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz",
"integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.4.tgz",
"integrity": "sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==",
"cpu": [
"ppc64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -2895,16 +2928,19 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-ppc64": "1.3.2"
"@img/sharp-libvips-linux-ppc64": "1.3.3"
}
},
"node_modules/@img/sharp-linux-riscv64": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz",
"integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.4.tgz",
"integrity": "sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==",
"cpu": [
"riscv64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -2917,16 +2953,19 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-riscv64": "1.3.2"
"@img/sharp-libvips-linux-riscv64": "1.3.3"
}
},
"node_modules/@img/sharp-linux-s390x": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz",
"integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.4.tgz",
"integrity": "sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==",
"cpu": [
"s390x"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -2939,16 +2978,19 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.3.2"
"@img/sharp-libvips-linux-s390x": "1.3.3"
}
},
"node_modules/@img/sharp-linux-x64": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz",
"integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.4.tgz",
"integrity": "sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==",
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -2961,16 +3003,19 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.3.2"
"@img/sharp-libvips-linux-x64": "1.3.3"
}
},
"node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz",
"integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.4.tgz",
"integrity": "sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==",
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -2983,16 +3028,19 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.3.2"
"@img/sharp-libvips-linuxmusl-arm64": "1.3.3"
}
},
"node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz",
"integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.4.tgz",
"integrity": "sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==",
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -3005,17 +3053,17 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.3.2"
"@img/sharp-libvips-linuxmusl-x64": "1.3.3"
}
},
"node_modules/@img/sharp-wasm32": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz",
"integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.4.tgz",
"integrity": "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==",
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": {
"@emnapi/runtime": "^1.11.1"
"@emnapi/runtime": "^1.11.3"
},
"engines": {
"node": ">=20.9.0"
@@ -3025,16 +3073,16 @@
}
},
"node_modules/@img/sharp-webcontainers-wasm32": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz",
"integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.4.tgz",
"integrity": "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==",
"cpu": [
"wasm32"
],
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"@img/sharp-wasm32": "0.35.3"
"@img/sharp-wasm32": "0.35.4"
},
"engines": {
"node": ">=20.9.0"
@@ -3044,9 +3092,9 @@
}
},
"node_modules/@img/sharp-win32-arm64": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz",
"integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.4.tgz",
"integrity": "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==",
"cpu": [
"arm64"
],
@@ -3063,9 +3111,9 @@
}
},
"node_modules/@img/sharp-win32-ia32": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz",
"integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.4.tgz",
"integrity": "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==",
"cpu": [
"ia32"
],
@@ -3082,9 +3130,9 @@
}
},
"node_modules/@img/sharp-win32-x64": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz",
"integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.4.tgz",
"integrity": "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==",
"cpu": [
"x64"
],
@@ -4054,25 +4102,58 @@
}
},
"node_modules/@next/env": {
"version": "16.2.12",
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.12.tgz",
"integrity": "sha512-d0Z5Bc13Fa4nR8pFAKx2jay2yhJM16vlfHbTzYnUQAxlNb6B6lmn4hjt69lYNt4kRtyYP6gEM49lPRHNbIyneg==",
"version": "16.3.4",
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.4.tgz",
"integrity": "sha512-cjWZnUUa6jZq2kFaNe/ZyJdZonOZ/QoN0Zka2nz/FLOrfx14pQuM9c5RaSVkWMqgdt4ksgPAMWPyHSs/CyV48Q==",
"license": "MIT"
},
"node_modules/@next/eslint-plugin-next": {
"version": "16.2.12",
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.2.12.tgz",
"integrity": "sha512-uF2z/qAK2q7B5/6CpnFcBRX6jOq5iCO+Uqh1UkJhXljX1JwLarLYhhoJadO6dPb6moTprOKewMXheBcbIoSbug==",
"version": "16.3.4",
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.3.4.tgz",
"integrity": "sha512-szW9y2Aumu4z88YXfTzcFsgUAg2k64uzbtcO5L9f1AKS4w/GUKJcbFllRflROVyNPgJtGOnvNxiyp3v6b+prIA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "4.9.1",
"fast-glob": "3.3.1"
}
},
"node_modules/@next/eslint-plugin-next/node_modules/@eslint-community/eslint-utils": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
"integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"eslint-visitor-keys": "^3.4.3"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
},
"peerDependencies": {
"eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
}
},
"node_modules/@next/eslint-plugin-next/node_modules/eslint-visitor-keys": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@next/swc-darwin-arm64": {
"version": "16.2.12",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.12.tgz",
"integrity": "sha512-0W1R0teHWJrqKX0FH20IzzIWAOuGtBxPGuObrxy1lE8hQvCFj49KE8a3WUg0D7sq6rn6zkM4c7YGUnhudBS6oA==",
"version": "16.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.4.tgz",
"integrity": "sha512-iBr3I5LZNk5/bgl5//iTgD2tcym14MX0Xo7fD//u9dYAEgGzza1y9oywluPtf74YnOswVdH1908aK9xVz7zQTw==",
"cpu": [
"arm64"
],
@@ -4086,9 +4167,9 @@
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "16.2.12",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.12.tgz",
"integrity": "sha512-Hy5Ls099+aFUmOLmIgPfLqNi6iCwhL3uQCssz5rWk+5Nkc6TUKCE83DY5BbNylfm3+mfwcSFnLRfrZDJhVxdtw==",
"version": "16.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.4.tgz",
"integrity": "sha512-2dpiSyl2Jw/NrBPaU2MAKGSa+2MR82pJIn4Sm5Rjr+gxAeuh0z158Su3Z2O8zn7UNNq+ej4bToed6RcRN/Lydg==",
"cpu": [
"x64"
],
@@ -4102,12 +4183,15 @@
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "16.2.12",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.12.tgz",
"integrity": "sha512-+YqU2h1cQkHsGfvjAsrSmst8UIFBibBGm5x3Xgel8NLMiDQtNOM4sM2GOEMvG5YiOBNeN/Ykk8cQC2S0Xrqljg==",
"version": "16.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.4.tgz",
"integrity": "sha512-+t+U8HZT+fApePCS5h89CSH3datz29MkzyfCn+6fpsZBG/oiEOhINcb9rtkv6sdpToLGFn2e6146NzaKCXkqrA==",
"cpu": [
"arm64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -4118,12 +4202,15 @@
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "16.2.12",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.12.tgz",
"integrity": "sha512-0qjhiYBaKAqF63LA1ZWAAnKTzFUguAaZiRa5etMLGGPj/B6uEVjtIZldIzFEp3wHlB0koK6aTzqPtSdplTCjoA==",
"version": "16.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.4.tgz",
"integrity": "sha512-mx03GNs1ocQA5JQ4FxDMmIsNkdrZh8cuezKCrId28e5/gIPU/l7Kcy2+vmCCzdjnnmXJy+iOAu+7K0QppO6Urg==",
"cpu": [
"arm64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -4134,12 +4221,15 @@
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "16.2.12",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.12.tgz",
"integrity": "sha512-7A3q26W+h7gnA15uqBToNuDqBEFZZcqh0mW2mn4AJh/G5pdg2RVE3n4slzLEliASZFG3NmsbEzng/x2Sh09mBg==",
"version": "16.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.4.tgz",
"integrity": "sha512-YIhGY6fSMfha52bnVxnzc9zaVBzJg+cqQTOD8tXIBSx4fuv0pVMxQTE0PaS59YhnMOiYiG09IMwxJAf/CFm/Dw==",
"cpu": [
"x64"
],
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -4150,12 +4240,15 @@
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "16.2.12",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.12.tgz",
"integrity": "sha512-qSjL/uppm+cbh21s72Ss8gkiOhQ4dExWHNGOWy6eZV7STj5WsKehgxT61beSsOj+YYQuTplL376lOCdMQU5T8w==",
"version": "16.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.4.tgz",
"integrity": "sha512-+eaaX6axpDb0yF1GCpiERe6njplvdC+nks/fKfcHu3XPGRrald8P3/X7yv7QLdjA51knnxwl9pxdIJsg+w1L+Q==",
"cpu": [
"x64"
],
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -4166,9 +4259,9 @@
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "16.2.12",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.12.tgz",
"integrity": "sha512-X6hzsOUJac/e7AWSbn9gQ9nzHld1xWP5iyjHpYWvud8pufB679O1xg4JDyKr8Xd69Jvd+kM2Der6uftiZCmjYA==",
"version": "16.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.4.tgz",
"integrity": "sha512-0jcXW7Xs/uzICrmgV3MhDYDeRy++1CqnpDIerlPIqYO4bhzB4WNbX/aRnQclustsAyTkFKB0z6rbcjmNg5tR8A==",
"cpu": [
"arm64"
],
@@ -4182,9 +4275,9 @@
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "16.2.12",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.12.tgz",
"integrity": "sha512-F6fakeHuFTLOPt0bslQJdf+xtT+WIP9DVn/m4y1w1mRnVPyh3D/cNvzlRkxM444xfm+IvvYNSOrKiA2CDJ0Uxw==",
"version": "16.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.4.tgz",
"integrity": "sha512-vvBzwu1pYQCp92maZCFCIw/XgOTMR5tur9GjakwIo2cmwRTMKajRZZDS9+e4KsUZWKu1E007WUeAFXRRjZeuzw==",
"cpu": [
"x64"
],
@@ -4330,9 +4423,9 @@
}
},
"node_modules/@swc/helpers": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
"integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
"version": "0.5.23",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz",
"integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.8.0"
@@ -6856,13 +6949,13 @@
}
},
"node_modules/eslint-config-next": {
"version": "16.2.12",
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.2.12.tgz",
"integrity": "sha512-iaaf4vvKo5h2LBdGt0JuRv7t0Ysqr9FMCiFxbptDg8LqOE//mIKR80DdpOnSVM7qjLH3jT8P0aFiwXxBEGZRXw==",
"version": "16.3.4",
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.3.4.tgz",
"integrity": "sha512-35/8RM10huEL9vlr8hUZMERMENHBrnyHN3ZZkF9efSgzGaqK34jIqry44A956//zriUhUAUW0XSkcolhrryqAA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@next/eslint-plugin-next": "16.2.12",
"@next/eslint-plugin-next": "16.3.4",
"eslint-import-resolver-node": "^0.3.6",
"eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-import": "^2.32.0",
@@ -9749,9 +9842,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
"version": "3.15.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz",
"integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==",
"version": "3.15.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.2.tgz",
"integrity": "sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -10215,16 +10308,16 @@
"license": "MIT"
},
"node_modules/next": {
"version": "16.2.12",
"resolved": "https://registry.npmjs.org/next/-/next-16.2.12.tgz",
"integrity": "sha512-iD59eYQWmbFcEbX7v/acG5DRym9iw1DdaPoD0WTA920naWsE25wShzJW4+UvAs8MK9EC2kBfIH6vtto1H1PHGw==",
"version": "16.3.4",
"resolved": "https://registry.npmjs.org/next/-/next-16.3.4.tgz",
"integrity": "sha512-/Ztf6CeRH+ejEXUrYtqI4gkS66eFIHuSwqi60RgcpWKodxFZx2/dqVCMKBwILfAHXQ+F1b1vAudgj3mnxqtoIA==",
"license": "MIT",
"dependencies": {
"@next/env": "16.2.12",
"@swc/helpers": "0.5.15",
"@next/env": "16.3.4",
"@swc/helpers": "0.5.23",
"baseline-browser-mapping": "^2.9.19",
"caniuse-lite": "^1.0.30001579",
"postcss": "8.4.31",
"postcss": "8.5.23",
"styled-jsx": "5.1.6"
},
"bin": {
@@ -10234,15 +10327,15 @@
"node": ">=20.9.0"
},
"optionalDependencies": {
"@next/swc-darwin-arm64": "16.2.12",
"@next/swc-darwin-x64": "16.2.12",
"@next/swc-linux-arm64-gnu": "16.2.12",
"@next/swc-linux-arm64-musl": "16.2.12",
"@next/swc-linux-x64-gnu": "16.2.12",
"@next/swc-linux-x64-musl": "16.2.12",
"@next/swc-win32-arm64-msvc": "16.2.12",
"@next/swc-win32-x64-msvc": "16.2.12",
"sharp": "^0.34.5"
"@next/swc-darwin-arm64": "16.3.4",
"@next/swc-darwin-x64": "16.3.4",
"@next/swc-linux-arm64-gnu": "16.3.4",
"@next/swc-linux-arm64-musl": "16.3.4",
"@next/swc-linux-x64-gnu": "16.3.4",
"@next/swc-linux-x64-musl": "16.3.4",
"@next/swc-win32-arm64-msvc": "16.3.4",
"@next/swc-win32-x64-msvc": "16.3.4",
"sharp": "^0.35.4"
},
"peerDependencies": {
"@opentelemetry/api": "^1.1.0",
@@ -11426,9 +11519,9 @@
}
},
"node_modules/sharp": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz",
"integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz",
"integrity": "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==",
"license": "Apache-2.0",
"optional": true,
"dependencies": {
@@ -11443,31 +11536,31 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.35.3",
"@img/sharp-darwin-x64": "0.35.3",
"@img/sharp-freebsd-wasm32": "0.35.3",
"@img/sharp-libvips-darwin-arm64": "1.3.2",
"@img/sharp-libvips-darwin-x64": "1.3.2",
"@img/sharp-libvips-linux-arm": "1.3.2",
"@img/sharp-libvips-linux-arm64": "1.3.2",
"@img/sharp-libvips-linux-ppc64": "1.3.2",
"@img/sharp-libvips-linux-riscv64": "1.3.2",
"@img/sharp-libvips-linux-s390x": "1.3.2",
"@img/sharp-libvips-linux-x64": "1.3.2",
"@img/sharp-libvips-linuxmusl-arm64": "1.3.2",
"@img/sharp-libvips-linuxmusl-x64": "1.3.2",
"@img/sharp-linux-arm": "0.35.3",
"@img/sharp-linux-arm64": "0.35.3",
"@img/sharp-linux-ppc64": "0.35.3",
"@img/sharp-linux-riscv64": "0.35.3",
"@img/sharp-linux-s390x": "0.35.3",
"@img/sharp-linux-x64": "0.35.3",
"@img/sharp-linuxmusl-arm64": "0.35.3",
"@img/sharp-linuxmusl-x64": "0.35.3",
"@img/sharp-webcontainers-wasm32": "0.35.3",
"@img/sharp-win32-arm64": "0.35.3",
"@img/sharp-win32-ia32": "0.35.3",
"@img/sharp-win32-x64": "0.35.3"
"@img/sharp-darwin-arm64": "0.35.4",
"@img/sharp-darwin-x64": "0.35.4",
"@img/sharp-freebsd-wasm32": "0.35.4",
"@img/sharp-libvips-darwin-arm64": "1.3.3",
"@img/sharp-libvips-darwin-x64": "1.3.3",
"@img/sharp-libvips-linux-arm": "1.3.3",
"@img/sharp-libvips-linux-arm64": "1.3.3",
"@img/sharp-libvips-linux-ppc64": "1.3.3",
"@img/sharp-libvips-linux-riscv64": "1.3.3",
"@img/sharp-libvips-linux-s390x": "1.3.3",
"@img/sharp-libvips-linux-x64": "1.3.3",
"@img/sharp-libvips-linuxmusl-arm64": "1.3.3",
"@img/sharp-libvips-linuxmusl-x64": "1.3.3",
"@img/sharp-linux-arm": "0.35.4",
"@img/sharp-linux-arm64": "0.35.4",
"@img/sharp-linux-ppc64": "0.35.4",
"@img/sharp-linux-riscv64": "0.35.4",
"@img/sharp-linux-s390x": "0.35.4",
"@img/sharp-linux-x64": "0.35.4",
"@img/sharp-linuxmusl-arm64": "0.35.4",
"@img/sharp-linuxmusl-x64": "0.35.4",
"@img/sharp-webcontainers-wasm32": "0.35.4",
"@img/sharp-win32-arm64": "0.35.4",
"@img/sharp-win32-ia32": "0.35.4",
"@img/sharp-win32-x64": "0.35.4"
},
"peerDependenciesMeta": {
"@types/node": {
+4 -3
View File
@@ -21,7 +21,7 @@
"@types/react-dom": "^19.2.3",
"axios": "^1.19.0",
"date-fns": "^4.1.0",
"next": "^16.2.12",
"next": "^16.3.3",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-router-dom": "^7.18.2",
@@ -35,6 +35,7 @@
"build": "node --max-old-space-size=4096 ./node_modules/next/dist/bin/next build",
"lint": "eslint . --max-warnings=0",
"lint:fix": "eslint . --fix",
"typecheck": "tsc --noEmit --incremental false",
"test": "jest",
"test:pdf-admission": "playwright test --config=playwright.pdf.config.ts",
"test:rate-limits": "playwright test --config=playwright.rate-limits.config.ts",
@@ -61,7 +62,7 @@
"@types/jest": "^30.0.0",
"babel-jest": "^30.4.1",
"eslint": "^9.39.5",
"eslint-config-next": "^16.2.12",
"eslint-config-next": "^16.3.3",
"identity-obj-proxy": "^3.0.0",
"jest": "^30.4.2",
"jest-environment-jsdom": "^30.4.1"
@@ -69,6 +70,6 @@
"overrides": {
"brace-expansion": "2.1.4",
"postcss": "8.5.25",
"sharp": "0.35.3"
"sharp": "0.35.4"
}
}
@@ -69,3 +69,44 @@ test("section AI controls render in Bokmål without changing the document langua
expect(screen.getByLabelText("Språk for resultat")).toHaveTextContent("Norsk bokmål");
expect(screen.getByRole("button", { name: "Foreslå" })).toBeInTheDocument();
});
test.each(["Reject", "Close"])("[PRES-C11] %s leaves the source unchanged and removes the suggestion", async (action) => {
mockedApi.post.mockResolvedValue({ data: { result: "Unaccepted draft" } } as never);
const onApply = renderAssistant();
fireEvent.click(screen.getByRole("button", { name: "AI suggestions" }));
fireEvent.click(screen.getByRole("button", { name: "Suggest" }));
expect(await screen.findByDisplayValue("Unaccepted draft")).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: action }));
expect(screen.queryByDisplayValue("Unaccepted draft")).not.toBeInTheDocument();
expect(onApply).not.toHaveBeenCalled();
expect(mockedApi.put).not.toHaveBeenCalled();
if (action === "Close") fireEvent.click(screen.getByRole("button", { name: "AI suggestions" }));
expect(screen.queryByRole("button", { name: "Apply" })).not.toBeInTheDocument();
});
test("[PRES-C11] applies the user's edited suggestion only after acceptance", async () => {
mockedApi.post.mockResolvedValue({ data: { result: "Proposed wording" } } as never);
const onApply = renderAssistant();
fireEvent.click(screen.getByRole("button", { name: "AI suggestions" }));
fireEvent.click(screen.getByRole("button", { name: "Suggest" }));
fireEvent.change(await screen.findByDisplayValue("Proposed wording"), { target: { value: "My reviewed wording" } });
expect(onApply).not.toHaveBeenCalled();
fireEvent.click(screen.getByRole("button", { name: "Apply" }));
expect(onApply).toHaveBeenCalledTimes(1);
expect(onApply).toHaveBeenCalledWith("My reviewed wording");
});
test("[PRES-C11] a failed suggestion can be retried without applying or saving content", async () => {
mockedApi.post.mockRejectedValueOnce(new Error("offline"))
.mockResolvedValueOnce({ data: { result: "Recovered suggestion" } } as never);
const onApply = renderAssistant();
fireEvent.click(screen.getByRole("button", { name: "AI suggestions" }));
fireEvent.click(screen.getByRole("button", { name: "Suggest" }));
expect(await screen.findByRole("alert")).toBeInTheDocument();
await waitFor(() => expect(screen.getByRole("button", { name: "Suggest" })).toBeEnabled());
expect(onApply).not.toHaveBeenCalled();
fireEvent.click(screen.getByRole("button", { name: "Suggest" }));
expect(await screen.findByDisplayValue("Recovered suggestion")).toBeInTheDocument();
expect(onApply).not.toHaveBeenCalled();
expect(mockedApi.put).not.toHaveBeenCalled();
});
@@ -261,6 +261,23 @@ test("AI cover letter suggestions use the linked CV and require explicit apply",
));
});
test("[PRES-W06] rejecting an AI cover letter preserves the saved draft without a write", async () => {
routeGet();
mockedApi.post.mockResolvedValue({ data: { result: { text: "Unaccepted letter" } } } as never);
const onDirtyChange = jest.fn();
render(<ApplicationCoverLetterSection jobId={7} onDirtyChange={onDirtyChange} />);
await screen.findByText(/application's full job advert and analysis/i);
fireEvent.click(screen.getByRole("button", { name: "Generate" }));
await screen.findByText("Unaccepted letter");
expect(screen.getByLabelText("Cover letter")).toHaveValue("Dear team");
fireEvent.click(screen.getByRole("button", { name: "Reject" }));
expect(screen.queryByText("Unaccepted letter")).not.toBeInTheDocument();
expect(screen.getByLabelText("Cover letter")).toHaveValue("Dear team");
expect(screen.getByRole("button", { name: "Save" })).toBeDisabled();
expect(onDirtyChange).not.toHaveBeenCalledWith(true);
expect(mockedApi.put).not.toHaveBeenCalled();
});
test("a failed load surfaces an error", async () => {
mockedApi.get.mockRejectedValue(new Error("boom"));
@@ -345,3 +345,52 @@ test('preview failure is visible and retryable without leaving the editor', asyn
expect(screen.getByTitle('CV preview')).toHaveAttribute('sandbox', 'allow-same-origin allow-scripts');
await waitFor(() => expect(screen.queryByText('Preview unavailable')).not.toBeInTheDocument());
});
async function reviewDocumentSuggestion(result: string) {
routeGet(() => Promise.resolve({ data: variant }), { headline: 'Canonical headline', sections: [] });
mockedApi.post.mockImplementation((url: string) => Promise.resolve({ data: url === '/cv/ai/assist'
? { result } : { html: '<p>Preview</p>' } } as never));
mockedApi.put.mockImplementation((_url, body) => Promise.resolve({ data: { ...variant, ...(body as object) } } as never));
renderAt(3);
await screen.findByDisplayValue('Backend CV');
fireEvent.click(screen.getByRole('tab', { name: 'AI Tools' }));
fireEvent.click(screen.getAllByRole('button', { name: 'Review suggestion' })[0]);
await waitFor(() => expect(mockedApi.post).toHaveBeenCalledWith('/cv/ai/assist', expect.objectContaining({
action: 'document-translate', text: JSON.stringify([{ id: 'headline', items: ['Canonical headline'] }]),
})));
}
test.each(['Apply', 'Reject'])('[PRES-C11 PRES-C07] whole-document AI %s changes only an explicitly accepted CV override', async action => {
await reviewDocumentSuggestion(JSON.stringify([{ id: 'headline', items: ['Reviewed headline'] }]));
expect(await screen.findByText('Review 1 proposed change(s)')).toBeInTheDocument();
expect(screen.getByText('Canonical headline')).toBeInTheDocument();
expect(screen.getByText('Reviewed headline')).toBeInTheDocument();
expect(mockedApi.put).not.toHaveBeenCalled();
fireEvent.click(screen.getByRole('button', { name: action }));
expect(screen.queryByText('Review 1 proposed change(s)')).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('tab', { name: 'Content' }));
expect(await screen.findByLabelText('Headline override')).toHaveValue(action === 'Apply' ? 'Reviewed headline' : '');
if (action === 'Apply') {
fireEvent.click(screen.getByRole('button', { name: 'Save now' }));
await waitFor(() => expect(mockedApi.put).toHaveBeenCalledWith('/cv/variants/3', expect.objectContaining({
settings: expect.objectContaining({ headline: 'Reviewed headline' }),
})));
expect(mockedApi.put.mock.calls.every(([url]) => url === '/cv/variants/3')).toBe(true);
} else {
expect(screen.getByText('Saved')).toBeInTheDocument();
expect(mockedApi.put).not.toHaveBeenCalled();
}
});
test.each([
['invalid JSON', 'not JSON'],
['missing original ID', JSON.stringify([{ id: 'unknown', items: ['Wrong document'] }])],
['duplicate original ID', JSON.stringify([{ id: 'headline', items: ['First'] }, { id: 'headline', items: ['Second'] }])],
])('[PRES-C11] unsafe whole-document response (%s) cannot be applied', async (_case, result) => {
await reviewDocumentSuggestion(result);
expect(await screen.findByText('AI suggestion failed.')).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Apply' })).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('tab', { name: 'Content' }));
expect(await screen.findByLabelText('Headline override')).toHaveValue('');
expect(mockedApi.put).not.toHaveBeenCalled();
});
+3 -1
View File
@@ -117,7 +117,9 @@ test("starts a structured interview debrief without inventing answers", async ()
content: expect.stringContaining("Questions asked:"),
},
));
expect(mockedApi.post.mock.calls[0][1].content).not.toMatch(/Acme|achievement|metric/i);
expect(mockedApi.post.mock.calls[0][1]).toEqual(expect.objectContaining({
content: expect.not.stringMatching(/Acme|achievement|metric/i),
}));
});
test("marking an item ready patches it", async () => {
@@ -0,0 +1,46 @@
import React from "react";
import "@testing-library/jest-dom";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { api } from "./api";
import { I18nProvider } from "./i18n/I18nProvider";
import QuickCommandDialog from "./components/QuickCommandDialog";
const mockedApi = api as jest.Mocked<typeof api>;
beforeEach(() => {
jest.clearAllMocks();
localStorage.removeItem("uiLanguage");
mockedApi.get.mockImplementation((url: string) => Promise.resolve({ data: url === "/companies"
? [{ id: 7, name: "Nordlys AS" }]
: { items: [{ id: 42, jobTitle: "Platform engineer", company: { name: "Nordlys AS" } }] } } as never));
});
test.each([
["Nordlys AS - Platform engineer", "/jobs/42"],
["Nordlys AS", "/companies?edit=7"],
])("[PRES-E07] command search opens %s at its existing destination", async (label, destination) => {
const onNavigate = jest.fn();
const onClose = jest.fn();
render(<I18nProvider><QuickCommandDialog open onClose={onClose} onNavigate={onNavigate} onOpenAddJob={jest.fn()} /></I18nProvider>);
fireEvent.change(screen.getByRole("textbox"), { target: { value: "Nordlys" } });
fireEvent.click(await screen.findByText(label, { exact: true }));
expect(onNavigate).toHaveBeenCalledWith(destination);
expect(onClose).toHaveBeenCalledTimes(1);
expect(mockedApi.post).not.toHaveBeenCalled();
});
test("[PRES-E07 PRES-J04] the capture command opens review without creating a job, and closing clears search", async () => {
const onOpenAddJob = jest.fn();
const props = { onClose: jest.fn(), onNavigate: jest.fn(), onOpenAddJob };
const view = (open: boolean) => <I18nProvider><QuickCommandDialog open={open} {...props} /></I18nProvider>;
const { rerender } = render(view(true));
fireEvent.click(screen.getByRole("button", { name: /add new job/i }));
expect(onOpenAddJob).toHaveBeenCalledTimes(1);
expect(mockedApi.post).not.toHaveBeenCalled();
fireEvent.change(screen.getByRole("textbox"), { target: { value: "Nordlys" } });
await screen.findByText("Nordlys AS - Platform engineer");
rerender(view(false));
rerender(view(true));
await waitFor(() => expect(screen.getByRole("textbox")).toHaveValue(""));
expect(screen.queryByText("Nordlys AS - Platform engineer")).not.toBeInTheDocument();
});
+28 -1
View File
@@ -1,6 +1,6 @@
import React from 'react';
import '@testing-library/jest-dom';
import { fireEvent, render, screen } from '@testing-library/react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import SettingsView from './components/SettingsView';
@@ -136,3 +136,30 @@ test('AI privacy settings are server-backed and external processing is local-onl
externalProcessingAllowed: false,
});
});
test('[PRES-S03] failed notification save preserves the draft for an explicit retry', async () => {
mockedApi.put.mockRejectedValueOnce(new Error('offline'));
renderView();
fireEvent.click(screen.getByRole('tab', { name: /notifications/i }));
const preference = await screen.findByLabelText(/email reminders for follow-ups/i);
await waitFor(() => expect(preference).toBeChecked());
fireEvent.click(preference);
fireEvent.click(screen.getByRole('button', { name: /save notification settings/i }));
expect(await screen.findByRole('alert')).toHaveTextContent(/could not|failed/i);
expect(preference).not.toBeChecked();
fireEvent.click(screen.getByRole('button', { name: /save notification settings/i }));
await waitFor(() => expect(mockedApi.put).toHaveBeenCalledTimes(2));
expect(mockedApi.put).toHaveBeenLastCalledWith('/notification-settings', { emailFollowUpRemindersEnabled: false });
await waitFor(() => expect(screen.queryByRole('alert')).not.toBeInTheDocument());
});
test('[PRES-S03 PRES-W11] saving one follow-up rule preserves the other scenarios', async () => {
renderView();
fireEvent.click(screen.getByRole('tab', { name: /follow-ups/i }));
fireEvent.change(await screen.findByLabelText('Applied: follow-up days'), { target: { value: '5' } });
fireEvent.click(screen.getByRole('button', { name: /save rules/i }));
await waitFor(() => expect(mockedApi.put).toHaveBeenCalledWith('/rules', {
id: 1, appliedFollowUpDays: 5, appliedGhostDays: 30,
offerFollowUpDays: 7, offerGhostDays: 14, feedbackFollowUpDays: 7, feedbackGhostDays: 14,
}));
});