169 lines
20 KiB
Markdown
169 lines
20 KiB
Markdown
# P4-05 — Table/list density contract
|
||
|
||
Implemented from `2cdcab9e2152f171a19cd0c9dd955bd16f5cc424` on `codex/ui-redesign-phase4`; final verification is recorded below. Scope: the [approved P4-05 slice](../../phase-3/PHASE4_IMPLEMENTATION_PLAN.md), not the Phase 5 Jobs redesign. F04 remains open; P4-06 and B07 are not started.
|
||
|
||
## Consumer and preservation analysis before implementation
|
||
|
||
Retain the existing `JobTable`: it already owns server sorting/paging/filtering, optional columns, selection, bulk and row actions, dedicated-workspace navigation and phone cards. Its existing Trash mode shares the same rendering and will receive the same density treatment, with restore semantics protected. No separate table framework, navigation or workflow change is required.
|
||
|
||
Density is absent from existing account preferences and saved-view contracts. Introduce a typed, component-local Comfortable/Compact choice with Comfortable as default. It survives filtering, selection, paging and viewport changes while mounted; it resets on unmount/reload. It is deliberately not saved to storage, URLs, saved views or the API. Existing page-size/column preferences and account-scoped saved views remain independent. CV document density is a different contract.
|
||
|
||
| PRES ID | Existing behavior retained | P4-05 change | Existing regression evidence | Added evidence |
|
||
|---|---|---|---|---|
|
||
| PRES-J05 | Search/filter, sort, page, columns, saved views, rows/cards | Opt-in shared spacing; local density selector | application-workspace-overlay, account-scoped-state, daily-control-loop; backend listing tests | Density changes retain query/selection/columns/page; both modes/locales/viewports |
|
||
| PRES-J06 | Status, edit, confirm delete, bulk, Trash/restore | Same controls with density-independent targets | confirm-flow; backend lifecycle/authorization tests | Row-action isolation, bulk/restore, native browser mutations |
|
||
| PRES-W01, PRES-W02 | Dedicated workspace and contextual workflow entry, return context | Preserve click/keyboard handlers; localize accessible row name | application-workspace-overlay, workflow-trust-signals, browser preservation | Link/action isolation; keyboard entry and focus |
|
||
| PRES-S03 | Independent page-size/column preferences | Density excluded from these settings | settings-view, page-layout preference case | Existing callbacks/paging unchanged |
|
||
| PRES-E08 | Language/theme/focus/error behavior | MUI semantic styling, EN/NB names | semantic-foundations, loaded-state and named-controls browser cases | Light/dark density/reflow/axe/focus evidence |
|
||
| PRES-E09 | Owner data and account-switch boundary | No storage or identity changes | account-scoped-state; B02 integration/browser regressions | Full inherited security regression |
|
||
|
||
PRES-E07 shell and PRES-J07 board behavior are regression-only dependencies; their implementation is not changed. Unknown/custom status strings retain existing labels/tones; no new status configuration is introduced. Quick preview is a future requirement, not a currently mounted workflow to rebuild here.
|
||
|
||
The existing App keeps the list mounted behind its dedicated workspace. Actual browser verification confirmed density survives opening a job and returning, along with the existing return focus and query. No route, router or application-shell code was changed to achieve this. Component unmount/reload still resets the provisional preference; the unit test explicitly proves the unmount boundary.
|
||
|
||
## Implementation and files
|
||
|
||
| File/component | Change |
|
||
|---|---|
|
||
| [theme.ts](../../../../job-tracker-ui/src/theme.ts) | Export the two-value `ContentDensity` type; opt-in spacing/target aliases under MUI `MuiCssBaseline`; existing `MuiTableCell` consumes cell padding with its original 10px fallback |
|
||
| [JobTable.tsx](../../../../job-tracker-ui/src/components/JobTable.tsx) | Local translated density select; opt-in table/card container; card spacing; MUI selected-row/surface treatment; accessible mobile/column checkboxes, localized row/paging names and focusable horizontal-scroll region; bounded long-content reflow |
|
||
| [translations.ts](../../../../job-tracker-ui/src/i18n/translations.ts) | Eleven EN/NB pairs for density, row/scroll/workflow names and pagination |
|
||
| [job-table-density.test.tsx](../../../../job-tracker-ui/src/job-table-density.test.tsx) | Six behavioral component tests, using existing providers and API mocks; only the unrelated edit-dialog body is stubbed |
|
||
| [table-density.spec.ts](../../../../job-tracker-ui/e2e/table-density.spec.ts) | Four real-API browser workflows, one per theme/language combination; synthetic owner data, dimensions, keyboard, mutations, axe and screenshots |
|
||
| This record, Phase 4 index, Phase 2 traceability and current TODO | Preservation links, verification, visual evidence and stop boundary |
|
||
|
||
Retained: MUI Table, TableSortLabel, TablePagination, cards, filter controls, SavedViewsMenu, lifecycle handlers, EditJobDialog, API queries and owner/session enforcement. Refactored: spacing, presentation/naming and a literal table-container shadow to the existing MUI semantic shadow. Added: one local preference/control and opt-in theme aliases. No library, package, CI, infrastructure, schema, backend, navigation or workflow architecture was added.
|
||
|
||
## Density contract
|
||
|
||
Opt in a MUI table/list container with `data-content-density` using the exported type. `MuiCssBaseline` owns the spacing values; consumers use the aliases through MUI `sx`/Stack. There is no parallel palette, table engine or generic DataTable wrapper.
|
||
|
||
| Property | Comfortable (default) | Compact |
|
||
|---|---|---|
|
||
| Table-cell vertical padding | 12px | 8px |
|
||
| Phone card padding | 16px | 12px |
|
||
| Phone inter-card/inner-stack gap | 12px | 8px |
|
||
| Text/font/icon sizes | Existing sizes | Identical |
|
||
| Fine-pointer table checkbox/icon targets | At least 32×32px | Identical |
|
||
| Phone/coarse-pointer checkbox/icon targets | At least 44×44px | Identical |
|
||
| Sort labels, clickable chips, buttons and links | Density-independent minimum height; 44px on phones/coarse pointers | Identical |
|
||
| Columns, values, actions, status labels and workflow signals | Existing content and user column choices | Identical |
|
||
|
||
Unmarked tables retain their original cell padding and control styles. Density does not enter fetch dependencies, route parameters, page-size callbacks, storage, saved-view fields or API requests. It therefore does not clear selection, refetch jobs or reset page/sort/filter state. Browser page-size/column persistence and workspace return remain independent.
|
||
|
||
Row height is content-driven rather than fixed: long text and wrapped actions can grow. The normal fixture row measures **59px Comfortable / 51px Compact** in all four theme/locale combinations; see [SCAN_RESULTS.json](SCAN_RESULTS.json). Typography, palette, focus tokens and status meanings remain those of P4-01/04. Selection uses existing MUI selection semantics plus the checked control, not color alone.
|
||
|
||
## Responsive and accessibility work
|
||
|
||
- Desktop/tablet retain the existing scrollable table, including all selected columns. The horizontal region now has an EN/NB name and native keyboard focus/scrolling; its inherited 3px focus outline is inset to survive the existing clipped Paper boundary. No page-wide overflow rule was introduced.
|
||
- Below the existing 768px boundary, the same records and selected IDs use existing cards. Comfortable/Compact changes spacing, not card fields or actions. Row checkbox names now match the desktop selection contract. No new mobile sorting workflow or filter architecture is introduced.
|
||
- Actual 320px visual review caught a custom status forcing the adjacent title into almost one-character lines. The bounded fix lets the card header wrap and its status label wrap without truncation. A minimum readable title-width assertion prevents geometry-only overflow checks from falsely passing this state. Desktop role cells receive a 260px minimum so long titles do not become similarly narrow columns; excess width remains deliberately scrollable.
|
||
- Column-menu checkboxes are named, without changing toggle callbacks. Native table headers/sort state, selection, row/action click isolation and Enter/Space row activation are retained. Row/workflow accessible names and page controls now use the existing localization system. No non-interactive card was made focusable.
|
||
- The new select uses P4-01 outlined-field focus and MUI keyboard choice/focus return. No animation or reduced-motion behavior was added; fixtures exercise the inherited reduced-motion path.
|
||
|
||
The limited header/column adjustment is owned by this slice's long-text/readability requirement. It does not reorganize Jobs filters, workspace tabs, table/board navigation, mobile cards, status configuration or business logic.
|
||
|
||
## Regression evidence
|
||
|
||
Three new behavior tests were run successfully against the original implementation before adding density: row-action isolation with edit/cancel/status; sort/page-size/columns; Trash selection/restore. The seven inherited workspace-overlay tests also passed during pre-change checking. Three further tests cover selected/query/page/column/custom-status preservation without refetch, no storage/API writes plus unmount reset, and failed-refresh/retry/empty compatibility.
|
||
|
||
The four native browser cases each create an ordinary synthetic user and 18 owned jobs, with Norwegian characters, a long title and a custom status. They prove:
|
||
|
||
- Comfortable default, keyboard density choice/focus return, unchanged visible row data/columns/query/selection and zero listing requests solely from density changes.
|
||
- Native sortable header semantics; table at 1440/1024/768px and cards at 390/320px; selected IDs and status labels survive resizing; card targets stay at least 44px in both densities, with containment and minimum title-width assertions.
|
||
- Keyboard horizontal scrolling and visible focus on the selection, scroll region and row-action button; desktop checkbox/icon targets at least 32px; native external-link isolation and keyboard workspace entry/back with focus restoration.
|
||
- Real status PATCH, paging 15→20, persisted column choice, confirm/soft-delete and Trash bulk restore. All writes await and require the actual success response before readback/continuation; existing B02 request headers remain required.
|
||
- Real empty Trash plus a deliberately held/503 list GET, followed by the existing retry and a successful native reload while keeping Compact. No mutation is mocked. Remote browser traffic is blocked; no provider or production load test is performed.
|
||
|
||
The new unit bulk tests use one selected record. Arbitrary multi-record partial-failure UI, saved-view CRUD browser coverage and dirty nested page-size changes remain explicit Phase 2 gaps. Existing account-scoped saved-view, server lifecycle/authorization and full workspace regression evidence is retained; this slice does not claim to close every gap in PRES-J05/J06/W02/S03.
|
||
|
||
## Rendered evidence
|
||
|
||
Actual screenshots were inspected, not only axe results. The long-status phone correction was prompted by that inspection. The final fixture produces **32 synthetic screenshots and 24 full tagged axe scans with zero violations**, across the two themes/locales/densities, empty and error states. Detailed scan results and incomplete checks are retained in [SCAN_RESULTS.json](SCAN_RESULTS.json): contrast is incomplete in 24 scans, `aria-hidden-focus` in four and `th-has-data-cells` in four. Incomplete checks are not passes.
|
||
|
||
| PRES → change → validation | Rendered evidence |
|
||
|---|---|
|
||
| J05/E08 → Comfortable/Compact spacing, identical data and semantic surfaces → row measurement and full axe | [Comfortable dark/NB](visual/desktop-comfortable-dark-nb.png), [Compact dark/NB](visual/desktop-compact-dark-nb.png), [Comfortable light/EN](visual/desktop-comfortable-light-en.png), [Compact light/EN](visual/desktop-compact-light-en.png) |
|
||
| J05/J06/E08 → unchanged phone actions, readable long custom status/title → target/width/containment checks | [320px Comfortable light/EN](visual/phone-320-comfortable-light-en.png), [320px Compact dark/NB](visual/phone-320-compact-dark-nb.png), [390px Comfortable dark/EN](visual/phone-390-comfortable-dark-en.png), [390px Compact light/NB](visual/phone-390-compact-light-nb.png) |
|
||
| J06/W01/W02 → visible row actions/focus and retained entry/status/delete/restore → native API and keyboard | [Focused action dark/EN](visual/row-action-focus-dark-en.png), [focused action light/NB](visual/row-action-focus-light-nb.png) |
|
||
| E08/J05 → existing loading/error/retry independent of density → held GET, failed GET, native retry | [Error light/NB](visual/list-error-light-nb.png), [error dark/EN](visual/list-error-dark-en.png) |
|
||
|
||
Horizontal clipping in a scrolled table screenshot represents its intentional viewport, not hidden/deleted columns: keyboard scroll and action focus expose the remaining columns. Card title/status wrapping, checkbox/action alignment, secondary text and selection are readable in the inspected phone renders. Existing bold typography, rounded cards and dense filter toolbar are retained for Phase 5 rather than redesigned here.
|
||
|
||
## Final validation
|
||
|
||
| Check | P4-04 inherited | P4-05 |
|
||
|---|---|---|
|
||
| Backend Release suite | 916 | **916 passed**, zero failed/skipped, including the rerun after the final application change |
|
||
| Frontend Jest | 348 in 74 suites | **354 passed in 75 suites**; six added tests, none removed |
|
||
| Full Chromium browser suite | 47 actual passes + P2-D01 expected failure | **51 actual passes + unchanged P2-D01 expected failure**: all 47 inherited passes plus four new cases |
|
||
| Focused new browser cases | — | **4 passed**, same four cases included in the full suite, not extra counts |
|
||
| Rate-limit HTTP | 9 | **9 passed** |
|
||
| Production-mode ingress HTTP | 6 | **6 passed**; local evidence cannot close deployed F04 |
|
||
| PDF admission HTTP | 1 | **1 passed** |
|
||
| Effective Compose/config checks | 3 | **3 passed** |
|
||
| Standalone frontend and scoped E2E TypeScript | Pass | **Pass** |
|
||
| Lint | Pass | **Pass**, zero warnings allowed |
|
||
| Frontend static production build | Pass | **Pass**, used by the browser tests |
|
||
| API Release build | Pass | **Pass**, zero warnings/errors, `-warnaserror` |
|
||
| Supply-chain / Ollama harness / extension tests | 3 / 5 / 3 | **3 / 5 / 3 passed** |
|
||
| Secret / whitespace / local Markdown-target checks | Pass | **Pass**, including staged files; **121** current/root Markdown documents, zero missing local targets, historical archive excluded |
|
||
|
||
No existing test or expected failure is renamed, suppressed or reclassified. The full backend suite retains B01–B05 protections. Complete frontend/browser/security counts are recorded independently; a reporter's expected-failure success outcome is not a successful P2-D01 workflow.
|
||
|
||
Final diff review confirmed that `App.tsx`/AppShell, navigation, backend code, dependencies and CI are unchanged. Density affects only opt-in presentation and does not alter requests or status values. All validation above ran after the final application change; subsequent changes only recorded the evidence. P4-05's bounded implementation and local validation are complete.
|
||
|
||
### Commands and reproducibility
|
||
|
||
From repository root unless noted. Existing installed runtimes/dependencies were used. Logs/TRX/Playwright JSON are under ignored `.artifacts/`; authentication traces are not committed. The committed scans/screenshots contain synthetic test data only.
|
||
|
||
```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=p4-05.trx' --results-directory .artifacts/redesign-p4-05 -- xUnit.parallelizeTestCollections=false xUnit.maxParallelThreads=1
|
||
# After the final application change, unchanged backend binaries:
|
||
& C:/Users/Connor/.dotnet/dotnet.exe test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --configuration Release --no-build --no-restore --logger 'trx;LogFileName=p4-05-final.trx' --results-directory .artifacts/redesign-p4-05 -- 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/test-supply-chain.py
|
||
python scripts/test-ollama-evaluation.py
|
||
node --test browser-extension/tests/*.test.mjs
|
||
Set-Location job-tracker-ui
|
||
npm test -- --watchAll=false --runInBand --runTestsByPath src/job-table-density.test.tsx src/application-workspace-overlay.test.tsx
|
||
npm test -- --watchAll=false --runInBand --json --outputFile=../.artifacts/p4-05-jest.json
|
||
npm run typecheck
|
||
node node_modules/typescript/bin/tsc --project ../.artifacts/p4-05-e2e-tsconfig.json
|
||
npm run lint
|
||
$env:NEXT_PUBLIC_API_BASE_URL='http://localhost:5302/api'
|
||
$env:NEXT_TELEMETRY_DISABLED='1'
|
||
npm run build
|
||
$env:DOTNET_HOST_PATH='C:/Users/Connor/.dotnet/dotnet.exe'
|
||
$env:PLAYWRIGHT_STATIC_EXPORT='true'
|
||
$env:PLAYWRIGHT_PREBUILT_EXPORT='true'
|
||
$env:P4_05_CAPTURE='true'
|
||
$env:PLAYWRIGHT_JSON_OUTPUT_FILE='../.artifacts/p4-05-browser-focused.json'
|
||
node node_modules/@playwright/test/cli.js test e2e/table-density.spec.ts --reporter=list,json
|
||
$env:PLAYWRIGHT_JSON_OUTPUT_FILE='../.artifacts/p4-05-browser.json'
|
||
node node_modules/@playwright/test/cli.js test --reporter=list,json
|
||
# Separate configurations run sequentially against their own disposable API instance.
|
||
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
|
||
Set-Location ..
|
||
python scripts/supply-chain.py scan-secrets
|
||
git diff --check
|
||
git diff --cached --check
|
||
```
|
||
|
||
The ignored scoped E2E type config extends the frontend tsconfig, includes `table-density.spec.ts` and existing MUI augmentation, resolves frontend Node types and disables incremental caching. There is no compiler exclusion, package or CI change.
|
||
|
||
## Deferred issues and Phase 5 implications
|
||
|
||
- P2-D01 remains the documented submitted-package expected failure. P4-04-O02 SQLite contention, P4-04-O01 toast overlap, P4-03-D01 Tooltip contrast, earlier modal/command defects and CV margin/template rendering work remain separately scoped. The new axe states do not certify transient tooltip contrast; row-action screenshots can still show the inherited tooltip. No accessibility rule is disabled in the new scans.
|
||
- **P4-05-O01:** existing status action menus/bulk buttons contain raw English status keys in Bokmål, and applied dates use the existing browser locale rather than the chosen application language. These are baseline localization issues; new density/pagination/accessible-name text is localized. Do not change lifecycle values or date semantics as density work.
|
||
- Existing mobile cards lack a sort picker; desktop/query ordering is retained across resize. Current status filters are a fixed list, while custom status labels still display. Custom-status management, new filters, saved-filter expansion and integrated Table/Board/preview architecture are Phase 5 requirements, not delivered here.
|
||
- Browser checks prove the stated Chromium dimensions and keyboard behavior, not physical touch, a coarse-pointer tablet hardware run, Narrator/NVDA, 200%/400% zoom, forced colors or every long input. Those manual gaps remain. Only this table/list is opted in; future consumers need their own data/action/responsive fixtures.
|
||
- Density is deliberately local/provisional. Cross-device or account-persisted density needs an explicit preferences decision; do not silently add it to CV document density or existing saved-view payloads.
|
||
- F04 remains open pending deployed Traefik/nginx/API, firewall, DNS and TLS evidence. No production environment was inspected or deployed by this slice.
|
||
|
||
Phase 5 can reuse the opt-in MUI density contract while separately reorganizing Jobs and other consumers. Preserve the current capabilities, own each migration's missing tests and keep touch/focus targets independent of density. Rollback is a presentation/test revert; no migration or stored-data transformation is required. Stop before P4-06.
|