PR #144 (`31da384`, 2026-06-13) shipped a runtime `ReferenceError: useFocusTrap is not defined` to production because the component called the hook without importing it. The sibling `enable-no-undef-eslint-rule` convoy closes that bug class at LINT time. This PR locks the same regression at RENDER time so the bug would still fail CI even if the lint rule were dropped or disabled. ## What changes - `test/components/ScanDisambiguationDialog.test.js` — 8 tests: 1. `renders without crashing (PR #144 regression-lock)` — the direct lock-in. Mutation-tested: commenting out the `useFocusTrap` import causes all 8 tests to fail with the same `ReferenceError` shape that hit prod. 2. `returns null when disambiguation is falsy` 3. ARIA shape (`role`, `aria-modal`, `aria-labelledby`) 4. One button per candidate with accessible labels 5. `onPick` callback receives the selected candidate 6. Vision-hint branch renders when provided 7. Submitting state disables the "send for review" button 8. `onCancel` callback fires on Cancel click ## Why vitest + jsdom and not Playwright smoke | Path | Catches PR #144 | Setup | Runtime | |------|-----------------|-------|---------| | Playwright smoke | ✓ if disambiguation mounts in the smoke run | High (auth bypass, stable multi-candidate fixture image) | ~10s + browser | | Vitest render | ✓ directly — render-throw → test fail | Low | <100ms | Re-scoped the queued `scanner-disambiguation-smoke-test` task to the vitest shape because a render test catches the exact same bug class at 1/100th the cost and matches the existing `test/components/*.test.js` pattern (`Modal.test.js`, `ScannedCardItem.test.js`, etc.). A Playwright disambiguation smoke is still useful as integration-layer coverage and is queued as `scanner-disambiguation-playwright-smoke`. ## Verification - [x] `npm run test:run` — 26 files / 131 tests pass (up from 25/123) - [x] Mutation test: with `useFocusTrap` import commented out, all 8 tests fail with `ReferenceError`. With import restored, all pass. ## Test plan - [ ] CI on this PR green - [ ] Squash + merge - [ ] Smoke test post-merge: scan a card that triggers disambiguation in prod and confirm no console errors (the original PR #144 bug shape) ## Convoy doc `.convoys/scanner-disambiguation-render-test.md` documents D1 (cover the early-return branch explicitly), D2 (`fireEvent` not `userEvent`), D3 (do NOT mock `useFocusTrap` — the missing-hook is exactly what we're locking), and the two queued follow-ups (`add-component-render-smoke-pattern`, `scanner-disambiguation-playwright-smoke`). Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|---|---|---|
| .. | ||
| add-rate-limiting | ||
| adopt-playwright-smoke | ||
| bump-next-js | ||
| cors-tighten | ||
| drop-public-setup | ||
| fix-auth-bypass | ||
| fix-layout-default-user | ||
| fix-vercel-deployment-protection-in-ci | ||
| liquid-glass-design-tokens | ||
| pick-a-name | ||
| redesign-scanner-flow | ||
| unify-glass-panel-surfaces | ||
| .metrics.jsonl | ||
| add-rate-limiting.md | ||
| add-real-ocr-layer.md | ||
| adopt-playwright-smoke.md | ||
| bump-next-js.md | ||
| catalog-sync-vercel-cron.md | ||
| cleanup-card-item-list-and-share-modal-palette.md | ||
| cleanup-legacy-design-css.md | ||
| cleanup-mobile-nav-dead-props.md | ||
| cors-tighten.md | ||
| drop-public-setup.md | ||
| enable-no-undef-eslint-rule.md | ||
| fix-auth-bypass.md | ||
| fix-layout-default-user.md | ||
| fix-reset-db-script.md | ||
| fix-vercel-deployment-protection-in-ci.md | ||
| harden-visual-diff-gate.md | ||
| lint-against-cjs-in-esm-scripts.md | ||
| liquid-glass-card-surfaces.md | ||
| liquid-glass-design-tokens.md | ||
| liquid-glass-form-primitives.md | ||
| liquid-glass-layout-shell.md | ||
| liquid-glass-modal-and-surface-primitive.md | ||
| liquid-glass-public-and-auth.md | ||
| liquid-glass-redesign.md | ||
| migrate-button-input-mobilenav-to-glass-primitive.md | ||
| migrate-ci-to-self-hosted.md | ||
| migration-tool.md | ||
| motion-system-pass.md | ||
| pick-a-name.md | ||
| purge-quick-login-from-loginpage.md | ||
| purge-weak-creds-from-helpers.md | ||
| README.md | ||
| redesign-scanner-flow.md | ||
| redesign-v2-from-mockups.md | ||
| rename-collections-vocabulary.md | ||
| scanner-correctness-polish.md | ||
| scanner-disambiguation-render-test.md | ||
| scanner-redesign-a11y-fixes.md | ||
| scanner-user-cards-quantity-guard.md | ||
| secure-scanner-gemini-key.md | ||
| server-side-scan-pipeline.md | ||
| ship-readiness.md | ||
| single-auth-provider.md | ||
| single-sql-client.md | ||
| test-scanner-redesign-surfaces.md | ||
| tighten-visual-diff-path-filter.md | ||
| unify-glass-panel-surfaces.md | ||
Convoys
A convoy is a multi-PR work-stream coordinated by an agent pipeline. One convoy = one feature, bug fix, or epic. Each convoy is a Markdown file in this directory plus an optional sub-directory of implementer briefs.
File layout
.convoys/
├── README.md (this file)
├── <slug>.md (the convoy file — written by role-conductor)
└── <slug>/
├── brief-1-<kebab-title>.md (written by role-architect)
├── brief-2-<kebab-title>.md
└── ...
Convoy file format
Frontmatter (set by role-conductor, then appended-to by other roles):
---
name: <kebab-slug>
classification: feature | hotfix | docs-only | infra-only | server-only | config-only
success_metric: <one sentence>
skip:
- <flag1>
status: open | in-progress | merged | shipped | abandoned
created: <YYYY-MM-DD>
---
Body sections (added in order by the pipeline roles):
## Why(Conductor)## Scope(Conductor)## Roles invoked(Conductor)## Todos(Conductor → refined by Architect)## IA(IA Architect)## UX(UX Reviewer)## Architecture(Architect)
After Architect, briefs live in .convoys/<slug>/brief-N-*.md. Implementers read only their brief, not the whole convoy.
Skip flags
The Conductor sets skip: based on classification. These flags map to pipeline stages that no-op when set:
| Flag | Skips |
|---|---|
ia |
IA Architect |
ux |
UX Reviewer |
arch |
Architect |
test |
Component tests |
review |
Reviewer |
visual |
Visual diff |
a11y |
A11y auditor |
design |
Design-system auditor |
smoke |
Staging smoke |
qa |
Manual QA |
docs |
Doc Writer |
flag |
Flag rollout |
Never skipped (mandatory human gates): plan-approval, pr-merge, prod-promote.
Status lifecycle
open— Conductor created the convoy; no work started.in-progress— At least one brief has an open or merged PR.merged— All briefs merged to umbrella; release PR to develop pending.shipped— Release to main complete; flag rollout (if any) underway.abandoned— Convoy closed without shipping; reason in convoy body.
Update status by editing the convoy frontmatter as you progress.
Adding a new convoy
- Open Cursor in this repo.
- Prompt: "Start a new convoy: . Success = ."
- The
role-conductorsubagent writes.convoys/<slug>.md. - Run subsequent roles in order per the convoy's
Roles invokedlist.
See .cursor/agents/role-conductor.md for the Conductor's full spec.
Multitask + worktrees (Cursor 3.2+)
Cursor 3.2 (Apr 24, 2026) added /multitask async subagents and native worktree management in the Agents Window. The pipeline uses both:
Audit fan-out — after an implementer ships a PR draft:
/multitask role-reviewer + role-design-system-auditor + role-a11y-auditor
All three read the same diff and emit independent comments. Use group id audit-<convoy>-<pr> so analytics can compute wall-clock savings.
Implementer fleet — after architect's plan is approved (gate 1), if slice_dependencies: declares parallel-safe briefs (depends_on: [], disjoint files:):
/multitask role-implementer briefs 1, 2, 3
Use Cursor's Agents Window to create a worktree per brief — one click each. The legacy scripts/wt.sh is now a deprecation stub.
See the multitask playbook for the full guardrail set.
Self-analytics
Each L2 role appends one event to .convoys/.metrics.jsonl via scripts/log-convoy-event.sh. The file is gitignored by default — events stay local. To opt-in to commit team-shared metrics, remove .convoys/.metrics.jsonl from .gitignore.
Aggregate across repos and render a dashboard with the agent-pipeline analytics scripts:
cd ~/code/agent-pipeline/analytics
npx tsx analyze-convoys.ts <repo-path> [<repo-path>...]
npx tsx render-dashboard.ts
open ~/agent-pipeline-data/dashboard.html
Schema: analytics/schemas/convoy-event.json.