Files
jobtrackingapp/docs/redesign/phase-5/p5-04b-readiness

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 private ReadJobsAsync; 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.ts only 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.

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.