findTEMP-webapp → Level 2 (Parallel)

Actionable roadmap to unlock Boris Cherny Step 2: several agents working concurrently, each self-verifying and landing its own GitHub PR. Evidence from live code · 2026-08-05. Deploy stays on Cloudflare Pages (build.sh); GitHub Actions is verification-only.

Step 1 → 2
Target unlock
~128
Tasks (indicative size)
3 days
Max branch age
Squash
Merge policy
Verdict

findTEMP-webapp is Assisted-capable today (strict TS, ESLint max-warnings 0, committed GraphQL schema). Parallel is blocked by zero tests, no CI, tracked secrets, no agent docs, and merge hotspots (jobs/new 2154 LOC · period_jobs/new 1732 LOC · single th.json). This page is the backlog to clear those blockers — including a modern Vitest 4 / Browser Mode / Playwright stack gated on a Node 22 + Vite 7 upgrade.

On this page

  1. Level 2 exit criteria
  2. Sizing & capacity tiers
  3. New findings from deep inspect
  4. Wave sequence
  5. Toolchain upgrade
  6. Test strategy (unit / integration / acceptance)
  7. Trunk-based · squash merge
  8. Agent-ready task rubric →
  9. Backlog (A–J)
  10. Out of scope
  11. Risks

Level 2 exit criteria

Sizing is indicative — not an estimate

S / M / L (1 / 2 / 3) are relative sizing hints for splitting and sequencing. They are not commitments and must not be summed into a delivery date. The team is ~3 engineers with a ~60 pt/month combined ceiling that is mostly product work. At a realistic 20–30% platform allocation (~12–18 pts/month), treat the tiers below as capacity guidance.

Tier 1
Minimum viable Parallel
~16 tasks · ~30–35 pts · modern toolchain, verify command, unit tests on money/date logic, PR gates, agent docs, worktrees. Roughly 2–3 months at 20–30% platform time.
Tier 2
Solid Parallel
Full Level 2 exit criteria: integration suite, first acceptance flows, type ratchet, squash/branch protection, conflict-surface cuts.
Tier 3
Hardening / L3 on-ramp
Remaining acceptance flows, god-file splits, strict type rules per directory, optional React 19 / MUI 7. Deliberately unscheduled.
Agent-executable now?

Roughly 60–70% of this backlog (unit tests for pure functions, AGENTS.md drafts, .env.example, doc fixes, mechanical extractions, most acceptance authoring once the harness exists) can be done by agents at Step 1. Human-only rows are flagged No (secrets rotation, branch protection, release ritual, Sentry major).

New findings from deep inspect

FindingEvidenceWhy it matters for L2
Sentry auth token committed under wrong name .env* has VITE_SENTRY_AUTH_TOKEN; vite.config.ts:42-53 never passes authToken (plugin reads SENTRY_AUTH_TOKEN) Secret exposed and sourcemap upload likely silently failing (errorHandler only warns).
RUN_ENV missing VITE_ prefix config.ts:1 · App.tsx:236 · no envPrefix in vite.config import.meta.env.RUN_ENV almost certainly undefined → Sentry enabled always true.
Holiday lists drive pricing and are stale config.ts:13 HOLIDAY_LIST ends 2024-12-31; SPECIAL ends 2025-04-15 dateClassifier feeds job wage tiers with no tests.
isMatchOrDoneHiring always true libs.ts:235 — status === 'match' || 'done' Hiring filters silently wrong; perfect first unit-test regression.
111 remote branches; release diverges from master master...release = 2/4; Merge branch master into release Blocks real trunk-based flow; promotion must become FF push.
Discord PR workflow written but never landed origin/feat/add-discord-pr-notification-workflow First CI artifact already exists — land it in Wave 0/1.
Modern Vitest blocked by Vite 4 + Node 18 vite@4.3.9 · .nvmrc 18.20.2 Vitest 4 needs Vite ≥6 + Node ≥20; Browser Mode needs Vitest 4.
Only 18 data-testids; App.tsx side effects on import App.tsx:36-91 · scattered testids in job create Acceptance needs flag to skip SW; testids scoped per flow task.

Wave sequence

Wave 0
Modern & runnable
Node 22 · Vite 7 · Corepack · Vitest 4 unit · env hygiene · AGENTS.md · first CI · task rubric · clear tsc debt
Wave 1
Self-verifying
Unit suite on money/date logic · Browser Mode integration bootstrap · type ratchet · full PR gates
Wave 2
Parallel-safe
Acceptance harness + first flows · worktrees · conflict-surface cuts · squash-merge & branch protection
Wave 3
Hardening
Remaining acceptance · god-file splits · strict type rules · optional React 19/MUI 7 · feature flags · metrics

Toolchain upgrade (prerequisite)

Verified: Vitest 3 supports Vite 5/6 but not Vite 4. Vitest 4 requires Vite ≥6 and Node ≥20. Browser Mode stabilized in Vitest 4; vitest-browser-react requires Vitest ≥4. This repo is Vite 4.3.9 on Node 18.20.2 — so the modern stack is gated behind an upgrade. Sequenced in three steps so testing is never blocked behind one giant PR.

StepTierChangeNotes
11 Node 18→22 LTS · Corepack packageManager · Vite 4→7 · plugin-react 5 · vite-plugin-pwa 1.x · Vitest 4 unit-only Ends Yarn Berry/Classic hybrid. Confirm CF Pages Node. PWA install+update on preview is acceptance.
22 Browser Mode + vitest-browser-react · ESLint 8→9 flat · drop airbnb-typescript → typescript-eslint v8 strictTypeChecked · Prettier standalone · Sentry 7→10 (human) Pairs lint migration with type-strictness. Sentry init API changed in App.tsx:36-62.
33 Optional: React 19 · MUI 5→7 · Apollo 4 · react-router 7 · codegen 5 Not required for Level 2. Defer.
Contingency

If Vite 7 stalls (especially vite-plugin-pwa injectManifest + src/sw.ts), pin Vitest 1.x on Vite 4 to unblock unit tests immediately, then revisit. Testing must not wait on the upgrade forever.

Test strategy

Three tiers with hard scope rules. Commands: yarn test:unit · yarn test:integration · yarn test:acceptance · yarn verify = typecheck + lint + unit + integration (fast pair). Acceptance is a separate slower CI job.

Unit — non-obvious / complex logic; irreducible components

Libs: Vitest 4, environment: 'node'; happy-dom only when a component cannot be broken down. No network, no providers, frozen clock.
Targets: 14 pure functions in libs.ts, dateClassifier + holiday refresh, OT wage math trapped in adjust_wage_dialog.tsx:167-170, scheduling constants, Yup schemas (Thai ID 13, Thai char, phone), token_manager with fake clock, notificationDataToRouteMap, errorsPairI18n.

Integration — complex components & pages

Libs: Vitest 4 Browser Mode (Playwright provider) + vitest-browser-react locators + MSW 2.
Why Browser Mode not jsdom: app is dialog/drawer/date-picker heavy (MUI portals); jsdom lies on visibility, focus, pointer.
Why MSW not MockedProvider: gql is inlined in ~40 files; document+variables matching breaks on refactors; MSW matches by operation name.
Targets: renderWithProviders (8 providers in App.tsx:243-264), 7-guard chain, jobs/new pricing+credit+confirm+idempotency, period_jobs/new, job_hirings_card_2 cache writes, profile/restaurant forms + upload, credit checkout.

Acceptance — full flows against a mock server

Libs: Playwright @playwright/test + webServer: vite preview. Mock backend: graphql-yoga + @graphql-tools/mock seeded from committed schema.graphql (2795 lines) + REST for /users/tokens/, upload, fast-track. .env.acceptance points API URLs at the mock.
Repo-specific: seed auth with unsigned JWT in localStorage.findtempWeb_accessToken (jwt-decode does not verify signatures) + per-role storageState; disable SW registration under a test flag (App.tsx:85-91) or PWA cache leaks across tests.
Flow order: fast-track (no auth) → sign-in through guards → create job → hiring lifecycle → onboarding → credit. data-testid backfill is scoped inside each flow task (only 18 exist today).

Do not use MSW browser worker for acceptance

MSW's browser integration installs its own service worker at scope / and collides with this app's PWA worker. MSW is safe in Browser Mode integration tests (components render directly; App.tsx registration never runs). Acceptance uses the separate yoga mock server instead.

Explicitly not doing: snapshot tests (5× dayjs() in render), testing MUI internals, Jest, Cypress, Enzyme.

Trunk-based development · squash merge only

Stay inside what GitHub enforces natively — no custom git guards. Agents get fewer ways to get git wrong.

SettingValueWhy
Allowed merge method Squash only (disable merge commit + rebase merge) Linear trunk; one commit per PR
Default squash message Pull request title and description Quality control lives on the PR title
Require linear history On Correct with squash (every merge is one commit)
Required checks Strict / up-to-date + reviews · no force-push Parallel agents cannot land on stale bases
Auto-delete head branch On + suggest updating PRs Cuts branch sprawl
PR title format Conventional: feat(scope): subject Becomes squash subject — formalizes existing (#257) style
release promotion git push origin master:release (fast-forward) Zero release-only commits; version bumps move to master
Branch age p95 < 3 days Load-bearing with squash (bisect/partial revert are coarser)
Agent ergonomics win

Intermediate commits are flattened, so agents can use GitHub's "Update branch" button freely without polluting trunk. Messy agent commit history stops mattering. No commitlint on individual commits — only semantic PR titles.

Accepted tradeoff

Squash makes git bisect coarser and partial reverts harder on large PRs. Mitigation: 3-day branch cap + per-task size fences so one squashed commit stays a sensible unit.

Agent-ready task rubric

The definition of a task good enough to hand to an AI — the 10-field spine, the four task types (story bug tech ops), the extra fields each type requires, and the role responsible for filling in every field — now lives on its own page.

It was split out because it is process work, not engineering work: it needs no code change, no CI job and no toolchain upgrade, so it can be adopted this sprint independently of everything else on this roadmap.

What makes a task good enough to hand to an AI

Includes the seven owner roles (PO, Design, Lead, Dev, Grooming, Ops owner, Reporter), the intake flow and its responsibilities, sizing and stop-and-split triggers, a smell list, worked before/after examples for each type, and copy-paste tracker templates.

Backlog

Ten categories in dependency order. Size = S/M/L indicative. Tier = 1/2/3. Agent? = executable at Step 1 today. Checkboxes persist in this browser (localStorage). Click a task ID/title to fetch its agent-ready markdown from /webapp-tasks/.

0 / 0 done

A. Test strategy & verification loop

Shared plumbing + unit + integration + acceptance. Unit first; acceptance after harness.

Shared plumbing

DoneIDTaskSizeTier WaveAgent?EvidenceAcceptance
S 1 0 Yes package.json yarn typecheck, yarn format:check, yarn verify exist; verify = typecheck+lint+test:unit
M 1 0 Partial place_autocomplete_input.tsx:38 · gtm.ts:1 yarn tsc exits 0 on a clean install
M 1 0 Yes vite.config.ts · new vitest.config.ts yarn test:unit runs; env stubs cover VITE_* + RUN_ENV
M 2 1 Yes src/graphql/graphql.ts factories for Job/JobHiring/Restaurant/User; no any
S 1 1 Yes new src/test/clock.ts documented helper; used by date/token tests
S 2 1 Yes new coverage-floor.json CI fails if coverage drops below floor
L 1 0 Partial src/App.tsx:36-91 Sentry/Firebase/GTM/SW behind bootstrap(); providers importable without side effects

Unit

DoneIDTaskSizeTier WaveAgent?EvidenceAcceptance
M 1 1 Yes libs.ts:127 · config.ts:13 tests cover weekday/weekend/holiday/special; 2026 holidays present
S 1 1 Yes libs.ts:235 status === 'match' || status === 'done'; failing test first
S 1 1 Yes libs.ts:20-59 edge cases: midnight, zero duration, locale th
S 1 1 Yes libs.ts:154-198 all known push types map to expected routes
S 1 1 Yes libs.ts table-driven cases for empty/null/mixed shapes
S 1 1 Yes libs.ts:233-250 composition tests; calJobsPositionCountMap
M 1 1 Yes adjust_wage_dialog.tsx:167-170 pure calcOvertimeWage(regular, ot, wage); 1.5× OT covered
M 2 1 Yes adjust_wage_dialog.tsx:132-138 urgent vs normal × day-type tiers
S 2 1 Yes fast_track/page.tsx:20-22 · period_jobs/new:98-100 min hours/days/max length enforced in pure validators
M 2 1 Yes profile/edit.tsx · register.tsx · fast_track valid/invalid fixtures; flag US-centric phone as known issue
M 2 1 Partial token_manager.ts expiry, refresh race, missing token; localStorage mocked
S 1 1 Yes idempotency.ts · stash.ts prefix+uuid format; stash round-trip with mock storage
S 2 1 Yes i18n/i18n.ts:70-99 known error codes map; unknown falls back safely

Integration

DoneIDTaskSizeTier WaveAgent?EvidenceAcceptance
L 2 1 Partial new vitest.integration.config.ts yarn test:integration boots Chromium; one smoke render
M 2 1 Yes App.tsx:243-264 helper exports; docs list required wrappers
L 2 1 Partial src/routes/* login/PDPA/force-password/info-complete/restaurant paths covered
L 2 2 Partial pages/jobs/new/page.tsx MSW createJob; idempotency header asserted
L 2 2 Partial pages/jobs/period_jobs/new/page.tsx batch pricing + createJobs mutation
L 2 2 Partial job_hirings_card_2.tsx start/get-off/review/ban; fragment writes asserted
L 2 2 Partial profile/edit.tsx · restaurants/* Yup fail paths + upload REST mocked
M 2 2 Partial pages/credit/* QR + card pending flows with MSW

Acceptance

DoneIDTaskSizeTier WaveAgent?EvidenceAcceptance
L 2 2 Partial new playwright.config.ts yarn test:acceptance boots preview on free port
L 2 2 Partial schema.graphql · new test/mock-server GraphQL + /users/tokens/ + upload + fast-track
M 2 2 Yes token_manager keys addInitScript writes findtempWeb_accessToken; per-role states
S 2 2 Yes App.tsx:85-91 no SW register when flag set; documented
L 2 2 Partial pages/fast_track/* end-to-end submit against mock; data-testid added as needed
L 2 2 Partial auth/sign_in · routes lands on jobs-page; guard redirects covered
L 2 2 Partial jobs/new happy path create; preview-equivalent
L 3 3 Partial job_hirings start → get-off → review
L 3 3 Partial routes guards first-login path completes
L 3 3 Partial pages/credit QR or card path against mock

B. Toolchain modernization

Node 22 · Vite 7 · Corepack · Vitest 4. Step 3 (React 19 etc.) is optional and not required for Level 2.

DoneIDTaskSizeTier WaveAgent?EvidenceAcceptance
M 1 0 Partial .nvmrc · package.json engines.node set; yarn install + lint on Node 22
S 1 0 No CF Pages settings / env preview deploy builds on Node 22
M 1 0 Yes .yarnrc.yml · yarn.lock · .yarn/ single Yarn path; install-state consistent
L 1 0 Partial vite.config.ts · sw.ts yarn build:staging green; PWA install+update on preview
M 1 0 Yes vitest.config.ts yarn test:unit green after B04
S 1 0 Yes docs note documented contingency; unblocks A* unit
M 2 1 Yes @vitest/browser · vitest-browser-react yarn test:integration starts
L 2 1 Partial .eslintrc.cjs → eslint.config.js lint green; prettier --check separate; no eslint-plugin-prettier
L 2 2 No App.tsx:36-62 init API migrated; sourcemaps still upload
L 3 3 No package.json NOT required for L2; tracked as Level 3 on-ramp

C. Type strictness & lint contracts

any is how an agent silently defeats typecheck — ratchet, don't big-bang.

DoneIDTaskSizeTier WaveAgent?EvidenceAcceptance
S 1 0 Yes src/** committed counts JSON with current totals
S 1 1 Yes new scripts/type-ratchet.ts CI fails on increase; decrease allowed
M 1 1 Partial eslint config no new any; existing tracked by ratchet
S 1 1 Yes eslint config @ts-ignore banned; descriptions required
M 1 1 Partial tsconfig.json tsc green with flag on
S 2 1 Yes eslint config ! assertions banned or ratchet-tracked
M 1 0 Yes src/vite-env.d.ts · config.ts:1 RUN_ENV/VITE_* typed; fix RUN_ENV prefix bug
S 2 2 Yes eslint / AGENTS.md documented; lint or review checklist
L 3 3 Partial typescript-eslint strictTypeChecked land dir-by-dir; gtm untyped any caught
L 3 3 Partial tsconfig would catch HOLIDAY_LIST lookups; gradual

D. CI gates & PR automation

Verification-only GitHub Actions. Deploy stays on Cloudflare Pages via build.sh.

DoneIDTaskSizeTier WaveAgent?EvidenceAcceptance
M 1 0 Yes new .github/workflows/pr.yml PR runs typecheck+lint+unit on push
M 2 2 Yes pr.yml or acceptance.yml separate job; browsers cached
M 1 1 Yes build.sh CI runs staging-mode build; no secret required
S 1 1 Yes src/graphql/* fails if generated files dirty
S 1 1 Yes C02 script wired as required check
S 1 1 Yes PR title feat|fix|chore|docs(scope): subject
S 1 1 Yes origin/feat/add-discord-pr-notification-workflow workflow on master; Discord fires
S 1 1 Yes .github/PULL_REQUEST_TEMPLATE.md required sections present
S 2 2 No new CODEOWNERS graphql/i18n/routes/App require review
S 2 2 Partial PR comment bot or CF integration preview link visible
M 1 1 No GitHub settings human; docs the required check names
S 1 1 No GitHub settings only squash enabled; linear history on
S 1 0 Yes pr.yml stale runs cancelled

E. Agent context & memory

Docs an agent can trust. Fix README drift first.

DoneIDTaskSizeTier WaveAgent?EvidenceAcceptance
M 1 0 Yes new AGENTS.md agent can bootstrap without human questions
M 1 1 Yes src/*/AGENTS.md 4 files; point to shared hotspots
S 1 0 Yes README.md gql from src/graphql/gql.ts; types from graphql.ts
S 1 1 Yes src/routes/AGENTS.md or docs table of guards + redirect targets
S 1 1 Yes AGENTS.md rule + examples from existing 18 ids
S 1 1 Yes AGENTS.md unit vs integration vs acceptance guidance
S 1 1 Yes new CONTRIBUTING.md links; 3-day branch age; squash

F. Secrets & env hygiene

VITE_* are client-public by design. Urgent: VITE_SENTRY_AUTH_TOKEN. Do not rewrite git history.

DoneIDTaskSizeTier WaveAgent?EvidenceAcceptance
S 1 0 Yes build output report yes/no with evidence
S 1 0 No Sentry org old token revoked; new in CF/GitHub only
M 1 0 Partial vite.config.ts:42-53 authToken passed; upload works without VITE_ prefix
M 1 0 Partial .env · .env.staging · .env.production git rm --cached; .gitignore; CF vars set
S 1 0 Yes new files keys only; no secrets; documented
S 2 1 No GCP console HTTP referrer allowlist
S 1 0 No GitHub settings enabled on repo

G. Parallel isolation

Three worktrees, no collisions.

DoneIDTaskSizeTier WaveAgent?EvidenceAcceptance
S 1 1 Yes AGENTS.md / docs/worktrees.md copy-paste recipe; no collisions
S 1 1 Yes docs PORT/env convention documented
S 1 1 Yes packageManager identical Yarn across trees
S 1 1 Yes docs YARN_CACHE_FOLDER or similar
S 2 2 Yes docs PLAYWRIGHT_BROWSERS_PATH
M 1 1 Partial manual/script documented run; no port/env clash

H. Merge-surface reduction

Cheap wins before god-file splits. Unit extractions in A already help.

DoneIDTaskSizeTier WaveAgent?EvidenceAcceptance
S 1 1 Yes .gitattributes gql.ts/graphql.ts marked
S 1 1 Yes docs rerere enabled locally; noted in AGENTS
S 1 1 Yes job_hirings_card.tsx removed; only Card2 remains
M 2 2 Yes jobs/new · period_jobs/new schemas importable; unit-tested
M 2 2 Yes jobs/new · period_jobs/new ops in .ts modules; codegen still works
S 2 2 Yes eslint threshold documented; god files grandfathered
L 2 2 Partial i18n/th.json fewer merge conflicts; load still works
L 2 2 Partial root_router.tsx lazy routes; smaller conflict surface
M 2 2 Yes components/index.ts AGENTS: no new barrel exports; migrate gradually
L 3 3 Partial jobs/new/page.tsx 2154 LOC ≤400 LOC files; behavior preserved
L 3 3 Partial period_jobs/new 1732 LOC shared pricing/validation; duplication cut
L 3 3 Partial job_hirings_card_2.tsx 875 LOC actions extracted; tests from A26
S 1 1 Yes AGENTS.md never hand-merge generated files
S 1 1 Yes docs/hotspots.md referenced by task scope fences

I. Trunk-based development · squash merge

Native GitHub enforcement. No custom merge-commit guards.

DoneIDTaskSizeTier WaveAgent?EvidenceAcceptance
S 1 1 No GitHub settings only squash allowed
S 1 1 No GitHub settings configured
M 1 1 No GitHub settings matches D11/D12; documented
S 1 1 No GitHub settings enabled
S 1 1 Yes D06 title format enforced
S 1 1 Yes CONTRIBUTING.md p95 target stated
M 2 2 Yes new workflow bot comments; no auto-close
M 1 1 No release ritual zero release-only commits; version bumps on master
L 1 1 No in-flight remotes plan per branch; land or archive
M 1 1 Partial origin/* script + human confirm; auto-delete on
M 2 2 Yes new src/flags.ts MODE gating pattern documented
S 2 2 Yes AGENTS.md how to stack; when to stop

J. Task prep & handoff

Definition of a task good enough to hand to an AI — operationalized.

DoneIDTaskSizeTier WaveAgent?EvidenceAcceptance
M 1 0 Yes new docs/agent_tasks.md 10-field rubric committed
M 1 0 Partial task tracker template live; required fields
S 1 1 No tracker + calendar label exists; 30-min weekly slot
S 1 1 Yes D08 enforced by template
M 2 2 Yes docs/design-tokens.md colors/spacing map to theme
S 1 1 Yes new .mcp.json Asana/Linear + Figma/Miro listed
M 2 2 Partial docs/staging-matrix.md covers 7-guard preconditions
M 1 1 Yes docs/examples/ isMatchOrDoneHiring + holidays + one UI
S 1 1 Yes AGENTS.md checklist agents must follow
M 3 3 Partial docs/metrics.md definitions + how to measure
S 1 0 Yes docs/agent_tasks.md two rewrites published

Out of scope

Deploy CI / replacing Cloudflare Pagesbuild.sh stays the deploy path
Rewriting git history to scrub old .env commitsRotate tokens instead
React 19 / MUI 7 / Apollo 4 / RR7Optional Tier 3; not required for L2
Jest / Cypress / Enzyme / snapshot testingModern stack only
Org seat counts, training, prompt librariesHarness readiness only

Risks

RiskMitigation
yarn tsc already red — typecheck gate can block Wave 0 A02 clears debt before making the gate required
Vite 4→7 + vite-plugin-pwa is the riskiest Tier 1 item PWA install/update on preview as AC; Vitest 1.x fallback (B06)
.nvmrc bump changes Cloudflare Pages Node too Preview-deploy check (B02) before merging to master
Type strictness big-bang is unmergeable on 28k LOC Ratchet + per-directory for no-unsafe / noUncheckedIndexedAccess
airbnb-typescript → flat ESLint changes lint output Pair with ESLint 9 move so churn happens once
Squash coarsens bisect / partial revert 3-day branch cap + size fences are load-bearing
release FF promotion changes ritual Human-only; release-day rehearsal
Sentry token rotation needs org access Sequence early; coordination-dependent
How to use this page

Start with Tier 1 rows in Waves 0–1. Filter the backlog, tick Done as you go (saved in this browser), and use Copy checklist report or per-category Copy as Markdown for Asana/Linear. Keep product work flowing — this roadmap is sized for a minority of engineering capacity. Related: findTEMP-webapp audit · org overview.