Wave 3 Report: Dev/Test Reliability + CVL Reorder Regression
Agent: sonnet-devtest-reliability
Date: 2026-04-25
Worktree: /home/richc/Documents/GitHub/careervector-wave3-sonnet-devtest-reliability
Branch: wave3-sonnet-devtest-reliability
Base: b9ee2d1 (Fix CVL reorder preview bugs and add e2e harness)
Commit: 15a88a6
Summary
Three bounded reliability improvements delivered, all within scope. Build and full test suite green. 5/5 e2e tests pass.
Files Changed
| File | Change |
|---|---|
src/components/ui/SortableList.tsx |
Added data-testid="section-drag-grip" to DragGrip button |
src/components/ui/Section.tsx |
Added data-testid="section-toggle" to section expand/collapse button |
e2e/helpers/cvl.ts |
Updated sectionGripAt() selector to use data-testid; added toggleSection() and isSectionExpanded() helpers |
e2e/cvl-reorder.spec.ts |
Added BUG-3 regression test; imported new helpers |
package.json |
Added db:bootstrap script |
playwright.config.ts |
Updated bootstrap documentation to use db:bootstrap |
git diff --stat: 6 files, 102 insertions, 13 deletions.
Deliverables
1. Stable test hooks for drag grips (SD-1)
Change: DragGrip in src/components/ui/SortableList.tsx now has data-testid="section-drag-grip". The section toggle button in src/components/ui/Section.tsx now has data-testid="section-toggle".
e2e update: sectionGripAt(page, index) in e2e/helpers/cvl.ts uses:
page.locator(`[data-index="${index}"] [data-testid="section-drag-grip"]`)
instead of the brittle button.cursor-grab CSS-class selector.
Two new helpers added:
toggleSection(page, index)— click the expand/collapse button for a sectionisSectionExpanded(page, index)— point-in-time snapshot; for test assertions use thespan.rotate-90locator withawait expect(locator).toBeVisible()directly (retry-based, see below)
2. Playwright regression for BUG-3 ("expanded section stays expanded after reorder")
New test: CVL section reorder — expand state › expanded section stays expanded after drag reorder
Location: e2e/cvl-reorder.spec.ts:123
What it covers:
- Expands a section (index 2 =
summary-tech, same "Summary" label group as index 1 =summary) - Drags it one position up (index 2 → index 1)
- This triggers
onSlugReorderwith a disambiguation rename (two adjacent "Summary" sections swap positions) - Asserts the section is still expanded at its new position (index 1) after the drag and PUT save
Why drag 2→1: Both sections have label "Summary", so the rename triggers the idMap path in handleDragEnd. Before the BUG-3 fix (setOpenSections(remap)), the expanded state tracked the old ID and the section would silently collapse. This test would fail on pre-b9ee2d1 code.
Assertion pattern used:
await expect(
page.locator('[data-index="1"] span.rotate-90'),
'Section must remain expanded after reorder',
).toBeVisible({ timeout: 5_000 });
await expect(locator).toBeVisible() retries until the DOM settles, which is correct after a React re-render triggered by drag. A point-in-time isVisible() call immediately after savePromise is unreliable because React's state flush may lag the network response.
3. Fresh local D1 bootstrap
Decision: Add db:bootstrap script that runs schema.sql only. Do NOT rename existing migration files.
Why schema.sql only: schema.sql is the complete current schema and already contains the connections column and telemetry_events table. Running add-connections-column.sql on a fresh DB fails with "duplicate column name: connections". The incremental migrations are for upgrading existing databases that predate those changes.
Script added:
"db:bootstrap": "wrangler d1 execute careervector --local --file=migrations/schema.sql"
Usage:
# Fresh local setup (replaces broken db:migrate on a clean clone):
npm run db:bootstrap
# Incremental upgrade on existing DB (unchanged):
npm run db:migrate
Verified: npm run db:bootstrap ran successfully on a clean worktree (19 commands in schema.sql, all succeeded). The local D1 was immediately usable by npm run dev and all e2e tests.
Important constraint: Do NOT run db:migrate after db:bootstrap. Wrangler's migrations apply tracks applied migrations in d1_migrations. After db:bootstrap (which uses execute, not migrations apply), db:migrate would try to apply all migrations alphabetically and fail on "duplicate column name". Bootstrap and migrate are mutually exclusive for a given local DB.
Docs updated: playwright.config.ts now documents npm run db:bootstrap as the correct setup command, replacing the broken four-step manual sequence.
Validation
All commands run in the worktree. node_modules symlinked from the main worktree (the worktree does not have its own install).
npx astro sync + npx tsc --noEmit
✓ astro sync: types generated in 121ms
✓ tsc: only pre-existing infra/ Pulumi errors (4 errors, @pulumi/* not installed locally)
All src/, tests/, and e2e/ files type-check cleanly.
npm run build
✓ Server built in 6.50s
✓ Complete
npm run test:unit
Test Files 50 passed (50)
Tests 892 passed (892)
No regressions. The unit test count is unchanged from wave-2 (892).
npm run test:e2e
Dev server started via npm run dev (wrangler local D1 bootstrapped with npm run db:bootstrap).
Running 5 tests using 1 worker
✓ 1 CVL section reorder — persist › reordering two sections persists after page refresh (2.2s)
✓ 2 CVL section reorder — expand state › expanded section stays expanded after drag reorder (1.8s)
✓ 3 CVL section reorder — preview renders › preview renders at least one page on initial load (2.6s)
✓ 4 CVL section reorder — preview renders › preview re-renders after section reorder without blank pages (6.5s)
✓ 5 CVL doc switch — no stale state › switching between CV and CL clears the section list (673ms)
5 passed (14.3s)
npm run test:e2e:known-bugs
✘ 1 CVL two-tab sync [KNOWN BUG] › Tab B sees section reorder from Tab A via realtime (5.1s)
1 passed (5.5s) ← expected-fail annotation, not a real failure
Residual Risks
isSectionExpanded()is point-in-time. The helper returnsawait locator.isVisible()which is a snapshot. It's fine for checking state that has already settled (e.g., after toggle click +waitForSelector). It should NOT be used directly in assertions after async operations (drag, save) — useawait expect(locator).toBeVisible()instead. The helper is still useful for conditional logic and is documented accordingly.db:bootstrapanddb:migrateare mutually exclusive. Running both on the same local DB will fail. This is documented inplaywright.config.tsand this report. If someone runs both, they need to delete.wrangler/state/v3/d1/and re-bootstrap. A more robust solution (numeric migration prefixes) was not implemented because renaming existing migration files risks breaking remote D1 migration history.data-testidon DragGrip not namespaced. The testid issection-drag-grip(notsection-drag-grip-{id}orsection-drag-grip-{index}). Since tests already scope by[data-index="N"], this is unambiguous. If DragGrip is ever used outside the CVL section list (e.g., in a header editor), a more specific testid would be needed.span.rotate-90as expand-state signal. This relies on Tailwind JIT not purgingrotate-90. The class IS present in source via${isOpen ? 'rotate-90' : ''}, so JIT will keep it. If Section.tsx is refactored to use a different CSS approach, the e2e expand-state tests would silently lose their assertions. Adata-testid="section-body"on the expanded content div would be more robust but was out of scope.Persist test flakiness at far drag distances. The original persist test uses
dragSectionToSection(page, summaryBefore, experienceBefore)= drag from index 1 to index 4. This worked in wave-2 and in repeated runs here but failed once in the first run of this session. Cause unknown — possibly a timing issue with the virtual list's absolute positioning and dnd-kit's collision detection during fast programmatic drags. The expand-state regression test avoids this by using index 2→1 (adjacent drag, proven reliable).
Recommendation
Merge.
All three deliverables are narrow, safe, and validated:
- Source changes: 2 single-line
data-testidadditions in production UI code (zero behavioral change) - Test changes: 1 new regression test (correct, passing, covers BUG-3 scenario) + selector hardening
- Script:
db:bootstrapis a safe addition (executenotmigrations apply, does not touch prod state) - No schema changes, no wrangler.toml changes, no dependency changes
Merge after opus-cvl-crdt-sync report is reviewed (no conflicts expected — no shared source files).