P5-07 — Existing-report Analytics destination
Source checkpoint: ec6baaeddd3e950b1eb20abb992182deb0747ca9, codex/ui-redesign-phase4. Scope: approved P5-07, Dashboard source definitions, P5-06. No P5-08a, Phase 8 or B07.
Inventory before implementation
There is no standalone Analytics route at the source checkpoint. All existing personal reports live in DashboardView; they are not separate backend capabilities to rebuild. Every read below uses authenticated local principal and the existing tenant-filtered context; non-deleted owner jobs only, except stats.total includes deleted. No admin permission or Pro entitlement is required. No new aggregate or full Jobs download is necessary.
| Existing report | Source / actual population | Filters and dates | Representation / actions / empty behavior |
|---|---|---|---|
| Monthly applications / dated responses | GET /jobapplications/analytics |
Existing UI 6/12/24 months, month-aligned submitted cohort; responses counted in displayed months only for that cohort | Shared-scale solid/dashed chart, exact monthly table; no date-cohort Jobs link because Jobs cannot represent the same cohort |
| Current pipeline / raw status distribution | overview.funnel + stats.byStatus | Current non-deleted snapshot; canonical normalization may omit custom stages; raw values retained separately | Bars/counts and raw status links to /jobs?status=...; custom names unchanged, empty canonical series distinct from no raw statuses |
| Current-stage age | overview.timeInStage | Current occupancy median, most recent matching transition or DateApplied/SavedAt fallback; not completed duration | Stage label, days and sample count; absent sample hides report as before |
| Source response samples | overview.responseRateBySource | Current Company.Source; top six, includes prospects; responses = flag OR date | All returned sources, response/total and server rate; no invented source drill-down |
| Company activity | overview.topCompanies | Current snapshot, top eight; same response rule | Counts/rate and supported /jobs?companyId=...; true empty state |
| Salary groups | overview.salaryInsights | Recorded non-deleted ranges; separate currency/pay period; sample required | Mean midpoint, minimum/maximum and count; missing sample hides report, no FX or inferred salary |
| Job tags | GET /jobapplications/tags?limit=10 |
All-time non-deleted tags; no months parameter consumed | Dashboard displays top eight; counts/bars, not verified user skills; no tag drill-down |
| Monthly tag trends | GET /jobapplications/tag-trends?months=...&limit=5 |
Selected applied cohort; counts per month per tag | Bars plus exact tables, no hover dependency; true no-series state |
| Snapshot / attention / onboarding | stats + reminders + existing setup reads | Tracked total, last30 dates, current interviews/offers; ranked reminder feed | Dashboard-specific immediate actions remain on Dashboard, not copied as Analytics widgets |
All existing report UI text uses EN/Bokmål translations and locale formatting; raw user/custom labels and returned source names remain raw. Backend reminder prose is not silently translated. Summary and trends are independent resource groups, each with genuine error and Retry; a failed read must not become an empty report. Tags/date-filter API support (from/to) exists but is not exposed by the current UI and is not expanded here: tag-trends/overview cannot share that arbitrary range safely. There are no existing status/source/role/location controls on these reports to consolidate.
Preservation and implementation decision
| PRES | Existing behavior → intended P5-07 change | Required evidence |
|---|---|---|
| PRES-S01 | All eight report families → separate detailed Analytics reading using one shared report owner/calculation | Dashboard regressions plus exact values, period/current scope, partial failures and report inventory tests |
| PRES-S02 | Five Dashboard owner-local flags/onboarding → unchanged Dashboard choices; Analytics details remain readable independently | Hidden Dashboard preferences do not hide Analytics; no Analytics preference writes |
| PRES-E07–E09 | Existing route/provider/auth/theme/locale owners → one authenticated /analytics, Insights item, honest page metadata |
Native direct entry, reload/Back, mobile/rail entry, account isolation, all four theme/locale pairs |
| PRES-J05/J09 | Supported status/company report links → same query contract | Native matching IDs after drill-down, browser Back restores Analytics period |
The approved plan explicitly permits initial reuse of the report presentation component while retaining Dashboard groups. Use a bounded reporting mode on the existing owner and a thin Analytics route. No parallel metric calculations, providers, requests within a mounted route, new charting library or backend contract. Analytics omits snapshot/attention/onboarding/customization, opens exact-value disclosures and provides more explicit period/snapshot/truncated-list context. Dashboard remains unchanged in functionality and retains access to every report and all five flags.
Only the existing 6/12/24 month control applies to activity/tag trends; labels explicitly exclude current snapshot and all-time tags. Analytics may persist that supported selection in its URL for direct entry/refresh/Back, with unsupported values safely defaulting to 12. No new filter is implied.
Metric definitions and unavailable work
- Window percentage = sum of returned dated-response monthly counts / sum of returned applied monthly counts × 100. Denominator zero means no percentage. It is a recorded application cohort/window, not eventual response probability or all employer replies in the window.
- Source/company percentages use returned response flag OR response date counts divided by current non-deleted jobs in each group, including prospects. Top lists are truncated and not population totals. Current mutable Company.Source is not original import provenance.
- Current-stage age is the server median occupancy age with fallback, not completed-stage duration. Salary average is arithmetic mean of each recorded range's midpoint (a single bound substitutes the missing bound), separated by currency/period.
- Raw status counts are not a historical conversion funnel. Never infer an Unknown count by subtracting canonical stages from another population.
- Roles, locations, sectors, historical conversions, average response time, AI/match/CV/document performance and aggregate admin statistics require separate defined reads/events. Do not activate previously unused DTO averages/medians with an unsupported interpretation.
Implementation and hierarchy
AnalyticsPage is a thin consumer of DashboardView's reporting mode. Activity comes first with exact monthly values initially open, then a two-column current pipeline/raw-status comparison, current-stage ages/source samples, separate salary groups, and company/tag reports. On phones these stack in the same DOM order; exact tables wrap within their width. Native tag-value disclosure also starts open for detailed reading. Every disclosure can still be collapsed/expanded by keyboard; no hover-only information.
The shared owner retains all Dashboard behavior by default. Analytics does not load its reminder feed or setup checklist, read/write Dashboard flags, show its snapshot strip, or offer Dashboard customization. Its five report calls are stats, overview, analytics, tags and tag-trends, once per initial mount across two existing resource groups. Period changes reload only the trends group. Dashboard still makes its six existing calls and retains all controlled groups. There is no duplicate report tree or parallel metric calculation in either mounted route.
Analytics displays all ten tags already returned by the existing bounded read (Dashboard remains top eight). The top-six source/top-eight company/top-five trend limits and exact denominators are visible. Tag sparklines normalize within each tag series, with totals and exact monthly values available; they are patterns within a tag, not an absolute cross-tag height comparison. No source/role/location/date-cohort drill-down is invented. Current company and raw status links retain their supported query fields; their Jobs result IDs are checked natively.
Deliberately unchanged: report calculations, shared-scale SVG plotting, source/company response samples, occupancy-age bars, separate salary summaries and supported Jobs links. No existing report is deleted or replaced with an unsupported percentage. Analytics differs through activity-first ordering, initially expanded exact-value tables/raw-status breakdown, URL-backed period selection, explicit scope and all ten returned tags; it does not duplicate Dashboard's daily attention/onboarding/snapshot widgets. Admin aggregate reporting remains in its authorized Admin owner and is neither queried nor migrated here.
/analytics uses the existing private Shell authentication gate, account-plan/provider tree and lazy route owner. It adds exactly one route and one Insights item; all original routes/aliases remain. Its page title/subtitle/breadcrumbs are localized. Dashboard's historical Home → Analytics → Overview breadcrumb is corrected to Home → Dashboard; its command still opens Dashboard and its hint now describes Dashboard instead of Analytics. All six existing commands remain: no new command catalogue or business action.
Related shared shell boundary
The first rendered review showed the new Insights/current row partially clipped below the expanded sidebar's initial scroll viewport, above the fixed Account group. This is a direct consequence of the new destination, not authorization to redesign the shell. AppShell now uses the appropriate existing permanent/temporary Drawer ref to reveal its actual current item with native scrollIntoView({block:'nearest',inline:'nearest'}) when route/collapse/breakpoint/open state or measured header height changes. It does not reset later manual scrolling, move focus, resize/group items or alter callbacks/permissions. Separate refs avoid targeting the hidden keep-mounted mobile copy. A native assertion checks the whole current row against every scrollable ancestor, and existing rail/drawer/focus regressions remain required.
Files and ownership
src/components/DashboardView.tsx: one existing report/data owner with bounded Analytics composition; same calculations/status semantics/error hook.src/views/AnalyticsPage.tsx: thin route consumer, no independent data owner.src/App.tsx: route, Insights item and page metadata only; authentication/providers/callbacks untouched.src/layout/AppShell.tsx: current-item visibility in the existing scrolling navigation only, justified above.src/components/QuickCommandDialog.tsx: Dashboard hint only; command actions/catalogue unchanged.src/i18n/translations.ts: nine localized UI labels/context/error messages per language, no raw status/source mutation.src/analytics-presentation.test.tsx,e2e/analytics-presentation.spec.ts: scoped regressions. No backend/schema/configuration/CI/dependency files changed.
Regression protection
Five new frontend cases cover complete report/value access independent of Dashboard hidden flags, URL period/Back and no snapshot refetch, genuine snapshot failure with independent trends/Retry, empty/unsupported-period safety without an invented denominator, and exact existing status/company destinations. Existing Dashboard/daily-control-loop/workflow-trust-signals, command and shell tests remain in force. Initial regression run intentionally failed because the new route consumer did not yet exist; a report-name fixture was then aligned to the actual existing translated headings, without changing report names to satisfy tests.
Two new browser cases use the actual production UI export and disposable local API:
- Native ordinary owner, six jobs including current/sparse/custom statuses, twelve raw tag categories, missing optional metadata, separate recorded salary data and an application outside the shorter window. Compare all six exact monthly rows, 1/4 dated responses versus 1/6 source response sample, and the 24-month 1/5 cohort; period Back/refresh, matching custom-status/company Jobs IDs, unchanged hidden Dashboard flags, expanded/current-row visibility, rail/drawer entry, keyboard disclosure/focus, EN/Bokmål × light/dark desktop/phone, intermediate/minimum/wide widths, and account switch to a fresh owner plus one application. Cross-owner direct job read returns 404.
- Controlled transport holds/503s for genuine loading and independent resource failures, Retry and zero-history recovery in the actual rendered app. These are synthetic failure fixtures, not production-data/load proof; native values and ownership come from case 1.
Locale fixture setup requires a real 204 preference save from a blank document before mounting the next Analytics view, following P5-06's documented preparation. It does not test or repair concurrent preference writes during Dashboard loading. No retry, forged identity, mocked success or permission bypass is used.
Validation observations retained
The initial full browser run found one unchanged light/English desktop attachment-confirmation case failing to focus Delete after Shift+Tab from Cancel. The trace proves the initial Cancel focus assertion passed and the keyboard event occurred shortly after opening; the unchanged ConfirmProvider also unconditionally focuses Cancel in its transition onEntered callback. A transition/focus race is a plausible explanation, not an instrumented proof of callback timing. Neither this provider nor the inherited test was changed, skipped or given a retry. This observation is P5-07-O01, separate from Analytics; a later passing run does not certify the race fixed. Preserve its sanitized initial summary and compare the final full run independently.
Rendered review also caught a reused Dashboard-specific snapshot error description on Analytics. The final localized Analytics description now identifies current job reports and independently loading activity/tag trends; both unit and browser assertions cover it. Final validation and error-state capture are repeated after that correction.
Accessibility and rendered evidence
The detailed destination uses the existing shared PageSection headings, native links/disclosures, row/column-header tables, semantic theme/status colors and ViewStateNotice error/Retry owners. Noninteractive charts and metric rows are not focusable. Canonical status color is supplemented by labels and values; raw custom status names remain available separately. Monthly and tag values do not require hover, SVG interpretation or color perception. Keyboard checks exercise disclosure toggles/focus, supported report navigation and mobile route-heading focus; unchanged shared dialog/command/shell tests remain part of the full suite.
Scoped axe scans cover the populated English phone page in light and dark mode using the existing WCAG A/AA tags, without disabling rules. The final sanitized summary records violations and incomplete checks separately. These scans do not certify all states/locales or full screen-reader operation. Spoken names, chart/table reading and live/state announcements remain an open manual P5-15 acceptance item; no manual screen-reader, native touch, forced-colors or exhaustive zoom session is claimed.
The final axe summary records zero detected violations in each scan and one incomplete color-contrast node in each: .MuiBadge-invisible, whose content was too short for the checker to determine whether it is text. That is an incomplete result, not an automated contrast pass or closure of the broader manual accessibility requirement. Separate keyboard/disclosure and rendered focus evidence pass within their tested scope.
Evidence is generated by the actual production UI export against a disposable local API. Native populated/sparse accounts contain synthetic data, not real user information. Loading/503 screenshots are explicitly controlled transport fixtures. No production request, credentials, cookies/storage snapshots, raw API bodies or traces are committed. The manifest records capture time, viewport, actual full-page dimensions, period, fixture type and individual rendered inspection. There was no pre-existing Analytics screen to manufacture a before comparison; P5-06's retained Dashboard is the prior report-layout reference.
| State / evidence | Rendered review scope |
|---|---|
| Desktop light EN, light Bokmål, dark EN, dark Bokmål | Activity-first hierarchy, exact six-month values, side-by-side current/canonical versus raw statuses, restrained semantic surfaces and readable labels |
| Phone light EN, light Bokmål, dark EN, dark Bokmål | 390×844; stacked reports, wrapping labels/actions and exact values, with no document overflow in the measured browser fixture |
| Minimum 320, tablet 768, wide 1920 | 320×740, 768×1024, 1920×1080; twelve-month default after fresh route entry, bounded content width and responsive navigation; no 1024-specific Analytics image is claimed |
| Keyboard exact values | Six monthly API values match the visible rows; Enter toggles disclosure and a shared ≥3px focus outline remains visible |
| New owner, one application | No invented percentage for an empty cohort; 0/1 is valid for a real application with no recorded response; missing optional metadata does not become fabricated reports |
| Loading, snapshot error, trends error, zero history | Progress is distinct from empty data; genuine 503 messages/Retry preserve the successful independent resource; no false conversion value |
The focused full-page screenshot includes fixed-shell stitching at its current scroll offset; it proves the data disclosure/focus, not that stitched header placement. Default captures and native current-item visibility assertions separately validate the shell. Detailed tag/month tables naturally make the page longer; disclosure controls retain the ability to condense them. The unequal company/tag column heights on desktop reflect actual detail data, not empty placeholder widgets. Rebalancing those groups is a future presentation decision, not permission to remove report values.
Performance, compatibility and rollback
Analytics performs five existing bounded aggregate reads, not a request per job or a full Jobs crawl; shared shell reminder polling remains unchanged. Its 6/12/24-month series and top-six/eight/ten/five source/company/tag/trend bounds remain the server contracts. Tags are re-read with the existing trends group when the period changes, although their totals stay all-time; optimizing that owner is not needed for this slice. Local fixtures prove rendering and behavior, not production load certification.
All 31 original route patterns are retained and /analytics adds the 32nd. Authentication/providers and Dashboard preference keys remain compatible. If presentation is rolled back, retain /analytics and its meaningful shared report consumer (or a direct compatibility entry to the retained reports); do not blindly remove the new route and produce a 404. No database migration, irreversible data write, dependency or deployment change is involved.
Validation commands and baseline
Inherited: backend 934; frontend 398; browser 79 actual passes + unchanged P2-D01; rate-limit 9, ingress 6, PDF 1, configuration 3. Commands below run through rtk proxy; frontend commands use job-tracker-ui. Native browser and API security suites use separate disposable local API lifetimes sequentially, never production. Browser export configuration is DOTNET_ROOT=C:/Users/Connor/.dotnet, DOTNET_HOST_PATH=C:/Users/Connor/.dotnet/dotnet.exe, PLAYWRIGHT_STATIC_EXPORT=true, PLAYWRIGHT_PREBUILT_EXPORT=true; production export is built with NEXT_PUBLIC_API_BASE_URL=http://localhost:5302/api, CI=false. The browser run has zero configured retries.
| Check | Command after rtk proxy |
Final result / inherited comparison |
|---|---|---|
| Backend | C:/Users/Connor/.dotnet/dotnet.exe test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj -c Release --no-restore --logger 'console;verbosity=minimal' |
934 passed, 0 failed/skipped; unchanged |
| Frontend | npm.cmd test -- --runInBand --silent |
403 passed in 80 suites; +5 Analytics cases, no removed tests |
| Browser | npm.cmd run test:e2e -- --reporter=line,json |
81 actual passes (+2 Analytics), unchanged P2-D01 expected failure; 0 unexpected/skipped/retries, 18m12s. Reporter says “82 passed” because it includes the expected failure; this record does not |
| TypeScript | npm.cmd run typecheck |
Passed; historical TS2571 is not current |
| New E2E fixture typing | npm.cmd exec tsc -- --noEmit --incremental false -p ../.artifacts/p5-07-e2e-tsconfig.json |
Passed; ignored local config extends UI tsconfig and includes the new fixture, no committed compiler/CI change |
| Lint | npm.cmd run lint |
Passed, zero warnings |
| Frontend production export | npm.cmd run build |
Passed compilation, TypeScript and static generation after final error-description change |
| API Release | C:/Users/Connor/.dotnet/dotnet.exe build JobTrackerApi/JobTrackerApi.csproj -c Release --no-restore -warnaserror |
Passed, 0 warnings/errors |
| Rate-limit | npm.cmd run test:rate-limits -- --reporter=line |
9 passed; unchanged |
| Ingress | npm.cmd exec playwright test -- --config=playwright.ingress.config.ts --reporter=line |
6 passed; unchanged; local application checks do not close production F04 |
| PDF admission | npm.cmd run test:pdf-admission -- --reporter=line |
1 passed; unchanged |
| Effective Compose/config | python scripts/test-ingress.py |
3 passed; unchanged |
| Supply-chain fixtures | python scripts/test-supply-chain.py |
3 passed; unchanged |
| Ollama evaluation fixtures | python scripts/test-ollama-evaluation.py |
5 passed; unchanged |
| Extension capture URL | node --test browser-extension/tests/capture-url.test.mjs |
3 passed; unchanged |
| Secret scan | python scripts/supply-chain.py scan-secrets |
Passed against staged final candidate; no high-confidence credentials |
| Whitespace | git diff --check and git diff --cached --check |
Passed against final candidate |
| Document/evidence validation | python .artifacts/p4-04-check-doc-links.py plus local screenshot/manifest and sanitized JSON assertions |
140 current/root Markdown documents resolve local targets; all 18 inspected PNGs/dimensions and 82 distinct outcomes validate, zero retries/skips; summaries exclude raw rendered HTML/credentials |
The initial full browser summary preserves 80 actual passes, unchanged P2-D01 expected failure and one unexpected attachment-focus failure, before the final error-copy correction. It is not reported as a pass. The final complete run is reported independently; passing reruns do not close P5-07-O01. No existing test was removed, skipped, renamed or given a weaker assertion.
The final browser summary records all 79 inherited successful cases still passing, both new Analytics cases passing and the frozen-package case still failing under its original P2-D01 annotation. Final screenshots were retained before the API-only suites replaced working test output; all 18 final files match the individually inspected bytes and their manifest records actual 2026-10-06 capture dates.
F04, P2-D01, P5-02 manual screen-reader acceptance, incomplete axe/manual checks, native Board touch/full-result scaling and P5-06-O01 preference-save 500 remain open. P5-07 does not repair the preference endpoint or certify concurrent locale writes during loading.
No new roles/locations/sectors, historical conversions, response-time averages, AI recommendations or CV/document performance are added. Those require reliable separately approved data contracts. Aggregate Admin reporting, new charts/frameworks, arbitrary analytical date filters and Phase 8 dashboard customization remain out of scope.
Acceptance and stopping point
P5-07 presentation/preservation acceptance is satisfied within its approved scope: 934 backend, 403 frontend (+5), 81 actual browser passes (+2) plus unchanged P2-D01 expected failure; rate-limit 9, ingress 6, PDF 1 and configuration 3. Existing reports, Dashboard controls, original routes and ownership boundaries remain protected. Both builds, TypeScript and lint pass after the final application change; staged repository checks are recorded above. No baseline count was reduced, unexpected failure suppressed or report silently removed.
Scoped keyboard and rendered light/dark EN/Bokmål/responsive checks pass; the two incomplete axe checks and broader manual screen-reader acceptance remain open. P5-07-O01's initial inherited focus failure remains a separate observation despite a passing final full run. This is a local feature-branch presentation checkpoint, not production or universal accessibility approval.
No P5-08a implementation has begun. The recommended next ticket is P5-08a only after its G-WORKSPACE mobile-progress/focus prerequisites are established; package/CV gates and production F04 remain separate. No B07, package/tailoring or CV work is authorized by this Analytics checkpoint.