deckhearth/.convoys
Randall Stillwell 237870c17e convoy: render-test regression-lock for ScanDisambiguationDialog (PR #144)
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>
2026-06-13 01:20:21 -05:00
..
add-rate-limiting feat(security): rate-limit search/upload/import + gate import routes (P0 #6 - closes last P0) 2026-05-24 22:59:59 -05:00
adopt-playwright-smoke feat(test): adopt @playwright/test + ship playwright.config.js + visual scaffold (P1 #10 step 2) 2026-05-24 19:25:18 -05:00
bump-next-js convoy(bump-next-js): plan + brief 1 (Decisions A-D) 2026-05-23 02:31:26 -05:00
cors-tighten fix(security): drop wildcard CORS + redundant OPTIONS from 24 API routes (P0 #5) 2026-05-24 20:41:38 -05:00
drop-public-setup architect(drop-public-setup): expand scope with brief 2 (CJS→ESM) 2026-05-23 17:02:44 -05:00
fix-auth-bypass docs: post-convoy cleanup for fix-auth-bypass 2026-05-23 12:22:50 -05:00
fix-layout-default-user fix(layout+pages): default user=null + page audit sweep (P0 #7) (#15) 2026-05-24 14:31:37 -05:00
fix-vercel-deployment-protection-in-ci fix(ci): plumb VERCEL_AUTOMATION_BYPASS_SECRET into preview-smoke + visual-diff (#17) 2026-05-24 16:26:22 -05:00
liquid-glass-design-tokens feat(design-system): Liquid Glass redesign portfolio — foundation + primitives + Layout (#95) 2026-06-03 20:12:33 -05:00
pick-a-name feat(brand): unify on Deck Hearth across in-repo strings + infra (P1 brand decision) 2026-05-25 02:28:29 -05:00
redesign-scanner-flow test(scanner): cover redesign API and component surfaces (#47) 2026-05-27 14:28:04 -05:00
unify-glass-panel-surfaces docs(convoys): unify-glass-panel-surfaces + cleanup palette — conductor seed + architect plan (#119) 2026-06-04 14:09:40 -05:00
.metrics.jsonl convoy: render-test regression-lock for ScanDisambiguationDialog (PR #144) 2026-06-13 01:20:21 -05:00
add-rate-limiting.md docs: post-convoy cleanup for add-rate-limiting — MILESTONE, last P0 closed 2026-05-24 23:09:07 -05:00
add-real-ocr-layer.md chore(convoys): mark shipped convoys and refresh ship-readiness (#66) 2026-06-02 11:01:27 -05:00
adopt-playwright-smoke.md docs: post-convoy cleanup for adopt-playwright-smoke 2026-05-24 19:33:43 -05:00
bump-next-js.md chore(convoys): mark shipped convoys and refresh ship-readiness (#66) 2026-06-02 11:01:27 -05:00
catalog-sync-vercel-cron.md chore(convoys): mark shipped convoys and refresh ship-readiness (#66) 2026-06-02 11:01:27 -05:00
cleanup-card-item-list-and-share-modal-palette.md docs(convoys): close unify-glass-panel-surfaces + cleanup-card-item-list-and-share-modal-palette (#130) 2026-06-05 06:18:30 -05:00
cleanup-legacy-design-css.md feat(design-system): Liquid Glass redesign portfolio — foundation + primitives + Layout (#95) 2026-06-03 20:12:33 -05:00
cleanup-mobile-nav-dead-props.md docs: post-convoy cleanup for 7-convoy 2026-05-26 wave (#33) 2026-05-26 23:15:21 -05:00
cors-tighten.md docs: post-convoy cleanup for cors-tighten 2026-05-24 20:49:30 -05:00
drop-public-setup.md chore(convoys): mark shipped convoys and refresh ship-readiness (#66) 2026-06-02 11:01:27 -05:00
enable-no-undef-eslint-rule.md convoy: enable no-undef ESLint rule + fix 3 latent bugs it surfaced 2026-06-13 01:17:18 -05:00
fix-auth-bypass.md docs: post-convoy cleanup for fix-auth-bypass 2026-05-23 12:22:50 -05:00
fix-layout-default-user.md chore(convoys): mark shipped convoys and refresh ship-readiness (#66) 2026-06-02 11:01:27 -05:00
fix-reset-db-script.md docs: post-convoy cleanup for fix-reset-db-script 2026-05-26 22:10:13 -05:00
fix-vercel-deployment-protection-in-ci.md docs: post-convoy cleanup for fix-vercel-deployment-protection-in-ci 2026-05-24 16:32:45 -05:00
harden-visual-diff-gate.md convoy: flip visual-diff to a hard merge gate (harden-visual-diff-gate brief 2/2) (#140) 2026-06-12 22:03:17 -05:00
lint-against-cjs-in-esm-scripts.md chore(convoys): mark shipped convoys and refresh ship-readiness (#66) 2026-06-02 11:01:27 -05:00
liquid-glass-card-surfaces.md feat(design-system): Liquid Glass redesign portfolio — foundation + primitives + Layout (#95) 2026-06-03 20:12:33 -05:00
liquid-glass-design-tokens.md feat(design-system): Liquid Glass redesign portfolio — foundation + primitives + Layout (#95) 2026-06-03 20:12:33 -05:00
liquid-glass-form-primitives.md feat(design-system): Liquid Glass redesign portfolio — foundation + primitives + Layout (#95) 2026-06-03 20:12:33 -05:00
liquid-glass-layout-shell.md feat(design-system): Liquid Glass redesign portfolio — foundation + primitives + Layout (#95) 2026-06-03 20:12:33 -05:00
liquid-glass-modal-and-surface-primitive.md feat(design-system): Liquid Glass redesign portfolio — foundation + primitives + Layout (#95) 2026-06-03 20:12:33 -05:00
liquid-glass-public-and-auth.md feat(design-system): Liquid Glass redesign portfolio — foundation + primitives + Layout (#95) 2026-06-03 20:12:33 -05:00
liquid-glass-redesign.md feat(design-system): Liquid Glass redesign portfolio — foundation + primitives + Layout (#95) 2026-06-03 20:12:33 -05:00
migrate-button-input-mobilenav-to-glass-primitive.md refactor(glass): migrate Button.secondary + Input + MobileNav off bespoke glass-surface (#131) 2026-06-05 06:23:55 -05:00
migrate-ci-to-self-hosted.md convoy: forbidden-pattern gate + AGENTS.md docs (briefs 3+4) (#133) 2026-06-06 00:18:21 -05:00
migration-tool.md docs: post-convoy cleanup for 7-convoy 2026-05-26 wave (#33) 2026-05-26 23:15:21 -05:00
motion-system-pass.md feat(design-system): Liquid Glass redesign portfolio — foundation + primitives + Layout (#95) 2026-06-03 20:12:33 -05:00
pick-a-name.md docs: post-convoy cleanup for pick-a-name — first post-P0 P1 convoy 2026-05-25 04:10:12 -05:00
purge-quick-login-from-loginpage.md chore(convoys): mark shipped convoys and refresh ship-readiness (#66) 2026-06-02 11:01:27 -05:00
purge-weak-creds-from-helpers.md docs: post-convoy cleanup for 7-convoy 2026-05-26 wave (#33) 2026-05-26 23:15:21 -05:00
README.md bootstrap: agent pipeline v0.5.0 + ship-readiness review 2026-05-23 02:31:26 -05:00
redesign-scanner-flow.md docs(convoy): close redesign-scanner-flow post-PR audit 2026-05-27 14:09:50 -05:00
redesign-v2-from-mockups.md docs(convoys): mark redesign-v2-from-mockups epic shipped (8/8 sub-convoys merged) (#108) 2026-06-04 11:24:34 -05:00
rename-collections-vocabulary.md chore(convoys): mark shipped convoys and refresh ship-readiness (#66) 2026-06-02 11:01:27 -05:00
scanner-correctness-polish.md chore(convoys): mark shipped convoys and refresh ship-readiness (#66) 2026-06-02 11:01:27 -05:00
scanner-disambiguation-render-test.md convoy: render-test regression-lock for ScanDisambiguationDialog (PR #144) 2026-06-13 01:20:21 -05:00
scanner-redesign-a11y-fixes.md Remove Quick Login + scanner a11y polish (#56) 2026-05-29 22:58:41 -05:00
scanner-user-cards-quantity-guard.md test(scanner): cover redesign API and component surfaces (#47) 2026-05-27 14:28:04 -05:00
secure-scanner-gemini-key.md fix(security): stop leaking Gemini API key to browsers (#34) 2026-05-27 08:41:48 -05:00
server-side-scan-pipeline.md chore(convoys): mark shipped convoys and refresh ship-readiness (#66) 2026-06-02 11:01:27 -05:00
ship-readiness.md convoy: scripts/rotate-admin-password.js — one-shot admin rotation (rotate-default-admin) (#141) 2026-06-12 23:23:44 -05:00
single-auth-provider.md docs: post-convoy cleanup for 7-convoy 2026-05-26 wave (#33) 2026-05-26 23:15:21 -05:00
single-sql-client.md chore(convoys): mark shipped convoys and refresh ship-readiness (#66) 2026-06-02 11:01:27 -05:00
test-scanner-redesign-surfaces.md chore(convoys): mark shipped convoys and refresh ship-readiness (#66) 2026-06-02 11:01:27 -05:00
tighten-visual-diff-path-filter.md docs: post-convoy cleanup for 7-convoy 2026-05-26 wave (#33) 2026-05-26 23:15:21 -05:00
unify-glass-panel-surfaces.md docs(convoys): close unify-glass-panel-surfaces + cleanup-card-item-list-and-share-modal-palette (#130) 2026-06-05 06:18:30 -05:00

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):

  1. ## Why (Conductor)
  2. ## Scope (Conductor)
  3. ## Roles invoked (Conductor)
  4. ## Todos (Conductor → refined by Architect)
  5. ## IA (IA Architect)
  6. ## UX (UX Reviewer)
  7. ## 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

  1. Open Cursor in this repo.
  2. Prompt: "Start a new convoy: . Success = ."
  3. The role-conductor subagent writes .convoys/<slug>.md.
  4. Run subsequent roles in order per the convoy's Roles invoked list.

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.