16 KiB
P5-04b readiness — shared Jobs query
Base: 42a696bc866c3eae17afa618d5ae1290d58db076. This checkpoint changes only the read/query contract; Table/Board integration remains separate. Status: complete; GO for the P5-04b query-parity prerequisite.
Decision before implementation
The existing /api/jobapplications/board already returns the complete owned population in one request. Extend this read with Table's existing query semantics and extract the existing controller query into one private implementation. Keep the Board array response and Table paged DTO response compatible. No new endpoint, schema, browser page crawl, oversized page request, or query framework is needed.
Previous contracts and reproduction
| Concern | Table /api/jobapplications |
Board /api/jobapplications/board |
|---|---|---|
| Search | SQL LIKE over title/company/notes/correspondence content | Ignored |
| Filters | Exact status (including custom), company, location LIKE; rule-derived follow-up and readiness before pagination | None |
| Scope | Active default; includeDeleted; deletedOnly takes precedence | Active default; includeDeleted |
| Ownership | Authenticated controller and EF current-owner filters on jobs/company/correspondence | Same filters, never bypassed |
| Sort | Whitelisted company/title/status/location/dateApplied/daysSince; asc/desc | DateApplied descending; UI groups by stage then recency |
| Pagination | Page >= 1; pageSize 15/20/25; total before paging | Complete array, no paging |
| URL | q/status/companyId/location/needsFollowUp/readiness/includeDeleted/sortBy/sortDir/page; booleans encoded as 1 | Query retained in navigation but ignored by load |
The native P5-04a fixture had 27 jobs: 26 titles contained Board; the remaining Norwegian title did not. Table q=Board correctly matched 26 (15 on page 1). Board ignored q=table-only and returned 27. This was divergent read capability, not a pagination/counting defect. Original P5-04a evidence is preserved.
Shared logical contract
Both reads apply the same server-owned search, exact standard/custom status, company, location, rule-derived filters, deletion scope and tenant boundary. SQL LIKE wildcard/collation behavior is preserved. deletedOnly wins over includeDeleted. There is no independent archive flag/filter on these reads; submitted-package archives are unrelated. Invalid readiness remains 400. No supplied user/account identifier affects ownership. Unknown statuses remain exact strings, including in Other. Table's existing status-selector catalogue is unchanged; direct/custom status query values remain supported without inventing a custom-status editor or expanding filter controls here.
Table alone paginates, with its existing whitelist. Board's array length is its complete logical total; no silent truncation. Shared sorting adds ascending ID as a stable tie-breaker. Board's existing within-column stage/recency presentation remains intact: membership parity does not require identical visual ordering.
The existing Jobs URL vocabulary is accepted by Board; page position remains Table-only. Both clients default to descending dateApplied; historical direct API omitted-sort defaults remain Table ascending / Board descending for compatibility. An explicit sort has identical server ordering. Existing routes stay separate. The existing Board → Table button retains the query, including a carried Table page; no unified selector or Table → Board switch is introduced. Global shell destinations remain independent fresh routes; P5-04b owns the future two-way switch and compatibility alias. /trash still supplies Table's deletedOnly scope; there is no new Trash Board UI. Both APIs can read that same deletion scope.
Status mutations reload the server query so a moved job can leave a filtered result. The Board shows its existing loading/error/retry surface while refreshing, rather than actionable stale membership. A write failure retains the card; a committed write followed by read failure hides the outdated Board, and Retry repeats only the read. Focus returns to the moved card if still present, otherwise the first named column. No client-side approximation of rule filters.
Performance boundary
One Board HTTP read, not N page requests. Normal Table filters retain database pagination. Derived filters already materialize matching candidates before Table pagination; Board necessarily materializes all matching rows. This retains the existing full-array/O(N) Board contract and has no fixed result cap. Stable IDs remove tied-sort page ambiguity, but reads are not a cross-request snapshot under concurrent writes. Large-account Board rendering/response size remains a separate pagination/virtualization concern; do not claim unbounded scalability or silently limit results.
The shared read also obtains the cached account rule settings and latest-correspondence dictionary; on a warm settings cache it executes the correspondence aggregate plus the result query (Table additionally counts). One successful Board mutation costs one PATCH plus one GET; failed refresh Retry is one GET. API cancellation tokens still reach EF operations, and existing authentication/rate controls are unchanged. There is no dedicated Board result-size or account-job-count cap to rely on. The 62-owned-row SQLite corpus proves multi-page completeness, not a production capacity benchmark. No new full-dataset browser cache or persistent preferences are introduced.
Preservation impact
| Contracts | Existing behavior → parity requirement | Evidence planned |
|---|---|---|
| PRES-J05 | Table search/filter/sort/page/saved query → identical Board membership, unchanged Table pages | Native SQLite membership and browser query cases |
| PRES-J06 | Active/trash/restore → same deletion scopes without mutation changes | Deleted/includeDeleted membership cases; inherited lifecycle suites |
| PRES-J07, PRES-W02 | Grouped/custom statuses and movement → preserve grouping/actions; requery after successful mutation | Board keyboard/pointer/menu/failure cases; filtered-out move |
| PRES-W01 | Open/Back retains URL → retain query and focus | Existing native navigation and filtered Board case |
| PRES-E08, PRES-E09 | Localized errors/retry, account isolation → fail closed on reload; trusted owner only | Retry/ownership cases; inherited session tests |
| PRES-S03 (indirect) | Saved filters/rules/page size → reuse interpretation without preference/schema change | Rule-derived parity and full baseline |
Open acceptance items
P5-02 manual screen-reader verification remains mandatory for P5-15. P2-D01, F04 production ingress evidence and native touch-drag verification remain open. No integration, new visual design, P5-06 or B07 work is authorized here.
Implementation and file justification
JobApplicationsController.cs: extract its existing query into privateReadJobsAsync; both GETs use it. Board accepts the existing logical filters and retains the raw entity-array response; Table retains DTOs, total and page-size whitelist. Ascending ID breaks equal-sort ties. No ownership filters are bypassed, and no mutation endpoint changes.jobsQuery.ts/JobTable.tsx: extract existing URL interpretation, including invalid-value fallbacks, into a small shared parser. Table's debounce, form state, pagination, saved-view writes, density, selection and row actions remain intact.KanbanBoard.tsx: submit those query parameters, reload after successful status writes, preserve query in its existing Table link, and restore meaningful focus after reload. No styling, columns, grouping, backend status values, menu actions or route hierarchy changed.translations.ts: replace the now-false statement that Board ignores query filters, in EN/NB. No new UI workflow.JobQueryParityTests.cs,kanban-grouped-board.test.tsx,board-presentation.spec.ts: permanent membership, refresh/failure/focus and native regression evidence.kanban-theme.spec.tsonly changes its mock URL matcher to include query parameters.- This record, four synthetic screenshots, readiness/blocker index and TODO updates keep the gate's current status discoverable. The original mismatch evidence remains in P5-04a and history.
Regression evidence
JobQueryParityTests runs 16 native SQLite cases with 62 owned jobs plus a foreign owner's job: unfiltered, search, notes, correspondence, standard status, custom status, combined search/status, no results, company, foreign company, location, follow-up, needs-work, interview, deleted-only and include-deleted. Each compares the ordered Board IDs against every Table page, asserts no duplicates or foreign IDs, verifies nonempty positive cases and relevant exact counts, and preserves readiness validation. Tied dates span more than four Table pages; ordinary Table pages remain at most 15.
Three new frontend cases prove the URL query/ownership exclusion, removal plus focus after a filtered-out move, and committed-write/read-failure recovery without write replay. Existing keyboard, pointer, menu, custom-status, cancellation and failed-write tests remain.
The existing native Board browser case is expanded, not counted as a new case. Seven API query variants compare full ID membership. The 27-job corpus still yields 26 for q=Board; Table page 1 has 15. q=Board&status=Saved yields 24; moving a matching job to Applied yields 23 and removes exactly that card. The existing Table link keeps the same URL query and shows 15 matching rows; browser Back returns to the 24-card Board. An absent search gives three empty columns and zero cards. The remainder still exercises all four theme/language combinations, 1440/1024/768/390/320 widths, native pointer and keyboard movement, custom status, write failure and workspace Back focus.
Rendered UI and accessibility evidence
Reviewed the actual browser screenshots below. Query-scoped and empty results retain the P5-04a columns/cards, semantic colors, status text and responsive horizontal Board. Updated NB explanatory copy wraps at phone width without horizontal page overflow. No presentation migration was performed.
- Filtered query, light/English
- Zero-result query, light/English
- Existing full Board, dark/Bokmål
- Phone Board, dark/Bokmål
Native keyboard and pointer checks pass in the focused and final full runs. Board axe checks detect zero violations. Each phone-theme scan has one incomplete color-contrast node; the empty-state scan has one incomplete color-contrast and one aria-prohibited-attr node. These are not certified as passes. Sanitized final outcome/axe summary. Historical post-axe screenshot glyph omissions remain an evidence limitation, not a new typography fix. No spoken-name/live-announcement screen-reader acceptance or native touch certification is claimed.
Validation ledger
Commands run from repository root unless UI directory is specified. All used the required rtk wrapper (rtk proxy for unsupported commands). Local .NET executable: C:/Users/Connor/.dotnet/dotnet.exe; DOTNET_ROOT/DOTNET_HOST_PATH identify that installation.
| Command | Result |
|---|---|
dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj -c Release --no-restore --filter FullyQualifiedName~JobQueryParityTests --logger 'console;verbosity=minimal' |
16 passed |
dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj -c Release --no-restore --logger 'console;verbosity=minimal' |
934 passed: 918 inherited + 16 parity cases |
UI npm.cmd test -- --runInBand --silent |
384 passed in 77 suites: 381 + 3 query/refresh cases |
UI npm.cmd run typecheck |
Passed after final application change |
UI npm.cmd run lint |
Passed after final application change |
UI npm.cmd run build |
Passed; local browser build uses NEXT_PUBLIC_API_BASE_URL=http://localhost:5302/api, CI=false |
dotnet build JobTrackerApi/JobTrackerApi.csproj -c Release --no-restore -warnaserror |
Passed, zero warnings/errors |
UI npm.cmd run test:e2e -- e2e/board-presentation.spec.ts e2e/kanban-theme.spec.ts --reporter=line |
3 passed; also passed in final full run |
UI npm.cmd run test:e2e -- --reporter=line |
First full run: 74 actual passes + P2-D01 expected failure (75 reporter passes), 2 unexpected failures below, 12.1 minutes |
UI npm.cmd run test:e2e -- --reporter=line,json |
Final full run: 76 actual passes + unchanged P2-D01 expected failure, no unexpected failures (77 reporter passes), 10.6 minutes. PLAYWRIGHT_STATIC_EXPORT=true, PLAYWRIGHT_PREBUILT_EXPORT=true; JSON report written locally with PLAYWRIGHT_JSON_OUTPUT_NAME=test-results/query-readiness-full.json |
UI npm.cmd run test:rate-limits -- --reporter=line |
9 passed |
UI npm.cmd exec playwright test -- --config=playwright.ingress.config.ts --reporter=line |
6 passed |
UI npm.cmd run test:pdf-admission -- --reporter=line |
1 passed |
UI npm.cmd run test:e2e -- e2e/semantic-foundations.spec.ts e2e/smoke.spec.ts --grep 'dark loaded status|Code template' --repeat-each=3 --reporter=line |
6 passed: three unchanged repetitions of each unexpected failure; no retry mode or relaxed assertions |
python scripts/test-ingress.py |
3 passed |
python scripts/test-supply-chain.py |
3 passed |
python scripts/test-ollama-evaluation.py |
5 passed |
node --test browser-extension/tests/*.test.mjs |
3 passed |
python scripts/supply-chain.py scan-secrets |
Passed with new code/tests staged |
Local Markdown target existence check; git diff --check; git diff --cached --check |
Passed. Target check does not certify anchor fragments/external URLs |
During development the new URL parser needed an explicit readiness union type, and reload exposed a focus effect running before the refresh finished. Both were corrected and focused tests rerun. One new test used the wrong English menu label (Set status: Applied instead of the existing Set Applied); the fixture was corrected. No inherited expected failure was weakened.
Unrelated baseline observation: P5-04b-R01
The first full browser run failed the inherited dark loaded status/navigation/command focus case: toBeFocused passed, then the outline was 0px instead of 3px. Sanitized trace timing shows the command acquired Mui-focusVisible after Tab at +290ms, then lost it at +311ms. The unchanged QuickCommandDialog has a transition onEntered that focuses the search input; this is the leading explanation for the late focus loss. The component, theme and test are byte-equivalent (normalizing line endings) to the base checkpoint. This query patch neither changes nor disables their assertions. Three unchanged focused repetitions and the final full run passed; this does not close the timing-sensitive accessibility debt. Owner: shell keyboard/transition validation before P5-15, not the Jobs query contract.
Unrelated baseline observation: P5-04b-R02
The same full run failed the inherited long Code-template CV smoke case before PDF export: after clicking Expand Experience and Expand Education, Collapse Experience was absent. Sanitized observation. The CV editor and smoke test are unchanged from the base; the query patch has no CV route/state/rendering changes. Three unchanged focused repetitions and the final full run passed. Root cause is not established, so this is not claimed fixed or conclusively diagnosed. Owner: existing CV behavior/rendering readiness gate; keep separate from query parity. No assertion was skipped or relaxed.
Decision
GO for P5-04b's shared-read prerequisite. Search/filter/status/custom-status/deletion/owner membership is shared and proven by IDs, with complete Board population and independent Table pagination. No query-parity blocker remains. The existing full-array scaling ceiling and non-snapshot concurrent-data behavior remain explicit limitations, not fabricated guarantees.
The next slice may implement the approved two-way view integration using this contract; it must preserve query state and both route histories, re-run membership/focus checks, and must not replace the Board with one Table page. Rollback is this checkpoint's controller/parser/consumer/test changes together; no schema/data migration is involved. Integration, P5-06 and B07 have not begun. Existing production/provider/accessibility/CV gates and the two recorded baseline observations remain open for their owners.