docs(work): re-decompose binder-wrap-helper into vertical-slice stories
Re-decomposes the approved binder-wrap-helper PRD after the previous 8-story attempt (dropped in71c04f5) sliced horizontally — its "write test" / "write impl" splits would have left intermediate commits red. The new shape obeys slice = task = PR = commit: - 01-wire-use-case-helper — 1 task: ship the wireUseCase helper (factory composition + container bind + brand stack) with its unit tests and barrel export in one green commit. Blocks 02 + 03. - 02-migrate-feature-binders — 5 tasks, one per feature (auth, blog, media, marketing-pages, navigation). Each task migrates that feature's bind-production.ts + bind-dev-seed.ts together and keeps all gates green. - 03-update-generator-templates — 1 task: update the feature generator's bind-*.hbs so future scaffolds emit wireUseCase directly instead of inline withSpan + withCapture. Output of the now-fixed sandcastle decompose dispatch — depends on the completionSignal + maxIterations wiring (eadbb7e,26aa97f).
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
---
|
||||
id: 01-wire-use-case-helper
|
||||
epic: 2026-05-13-binder-wrap-helper
|
||||
title: Introduce wireUseCase helper in core-shared
|
||||
type: technical-story
|
||||
status: in-progress
|
||||
feature: core-shared
|
||||
depends-on: []
|
||||
blocks: [02-migrate-feature-binders, 03-update-generator-templates]
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Add `wire-use-case.ts` + `wire-use-case.test.ts` to `packages/core-shared/src/conformance/` and export the helper from the `@repo/core-shared/conformance` barrel. The helper encapsulates `withSpan(withCapture(withAudit?(factory(deps))))` composition and performs the container bind step, so callers pass options and get back a brand-stacked wired value.
|
||||
|
||||
## Why
|
||||
|
||||
Every feature binder currently inlines the same `withSpan + withCapture` wrapping per use case — 30–79 lines of mechanical boilerplate per binder pair. A single helper in core-shared eliminates the structural clone groups and makes adjusting the wrapping shape a one-file change.
|
||||
|
||||
## Done when
|
||||
|
||||
- `packages/core-shared/src/conformance/wire-use-case.ts` exists and is exported from `packages/core-shared/src/conformance/index.ts`.
|
||||
- `wire-use-case.test.ts` covers: no-audit path (Instrumented + Captured brands present), audit path (Instrumented + Captured + Audited brands present), span-name derivation (`<feature>.<name>`), capture-tag structure (`{ feature, layer, name }`), container binding (symbol resolves to wired value), idempotent re-bind (unbind + bind when symbol already bound).
|
||||
- `pnpm typecheck && pnpm test && pnpm lint && pnpm conformance && pnpm fallow:audit && pnpm coverage:diff` all pass.
|
||||
- `wire-use-case.ts` reaches 100% statement/branch coverage (file is small; exhaustive test coverage is achievable).
|
||||
|
||||
## In scope
|
||||
|
||||
- `packages/core-shared/src/conformance/wire-use-case.ts` — the helper and its exported types.
|
||||
- `packages/core-shared/src/conformance/wire-use-case.test.ts` — unit tests using `RecordingTracer` / `RecordingLogger` / `RecordingAuditLog` (or equivalent mocks from core-shared/core-audit).
|
||||
- Export line added to `packages/core-shared/src/conformance/index.ts`.
|
||||
- No changes to `withSpan`, `withCapture`, or `withAudit` internals — the helper composes them as-is.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Feature binder changes (Story 02).
|
||||
- Generator template changes (Story 03).
|
||||
- `wireController` peer — the `layer` discriminator in `wireUseCase` covers controllers too if the shapes converge; a separate peer is a follow-up if needed.
|
||||
- Repository or service binding patterns.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Add `wire-use-case.ts` + `wire-use-case.test.ts` to `packages/core-shared/src/conformance/` and export from the conformance index — implement the helper (options object: `container`, `symbol`, `factory`, `deps`, `feature`, `layer`, `name`, `tracer`, `logger`, optional `auditLog`) with full unit tests covering both audit and no-audit paths, brand presence assertions via `isInstrumented` / `isCaptured` / `isAudited`, container binding, and idempotent re-bind; all gates pass on this single commit.
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
id: 02-migrate-feature-binders
|
||||
epic: 2026-05-13-binder-wrap-helper
|
||||
title: Migrate all five feature binders to wireUseCase
|
||||
type: technical-story
|
||||
status: todo
|
||||
feature: auth, blog, media, marketing-pages, navigation
|
||||
depends-on: [01-wire-use-case-helper]
|
||||
blocks: []
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Replace every inline `withSpan + withCapture (+ withAudit)` block in each feature's `bind-production.ts` and `bind-dev-seed.ts` with a call to `wireUseCase`. Existing binder-level and integration tests must continue to pass unchanged. Each feature migrates in its own commit.
|
||||
|
||||
## Why
|
||||
|
||||
The five inline wrapping clone groups are the top source of duplication reported by `pnpm fallow`. Moving to `wireUseCase` eliminates all five clone groups and leaves each binder as decision content only (which adapter, which mode, which symbol).
|
||||
|
||||
## Done when
|
||||
|
||||
- All 10 binder files (`auth ×2`, `blog ×2`, `media ×2`, `marketing-pages ×2`, `navigation ×2`) call `wireUseCase` for every use case binding; no inline `withSpan + withCapture` blocks remain for use cases.
|
||||
- `assertFeatureConformance` at the tail of each `bind-production.ts` continues to accept all wired use cases (brands present, manifest entries match).
|
||||
- `pnpm typecheck && pnpm test && pnpm lint && pnpm conformance && pnpm fallow:audit && pnpm coverage:diff` pass after each per-feature commit.
|
||||
- `pnpm fallow dupes` no longer shows the five binder-pair clone groups in its output.
|
||||
|
||||
## In scope
|
||||
|
||||
- `packages/auth/src/di/bind-production.ts` + `bind-dev-seed.ts` — 3 use cases (signIn, signUp, signOut).
|
||||
- `packages/blog/src/di/bind-production.ts` + `bind-dev-seed.ts` — 3 use cases (getArticles, getArticleBySlug, createArticle).
|
||||
- `packages/media/src/di/bind-production.ts` + `bind-dev-seed.ts` — 3 use cases (getMedia, listMedia, deleteMedia).
|
||||
- `packages/marketing-pages/src/di/bind-production.ts` + `bind-dev-seed.ts` — 2 use cases (getPageBySlug, getSiteSettings).
|
||||
- `packages/navigation/src/di/bind-production.ts` + `bind-dev-seed.ts` — 1 use case (getHeader).
|
||||
- If any binder's existing tests assert on internal wrapping shape rather than observable behaviour, refactor those assertions to test through the helper's contract (same commit as the binder change).
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Controller bindings — covered by the helper per the PRD but deferred to a follow-up if scope is a concern.
|
||||
- Repository and service bindings — stay as direct `.toConstantValue()` calls.
|
||||
- Apps' `bindAll()` dispatcher — untouched.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Migrate `auth` binders (`bind-production.ts` + `bind-dev-seed.ts`) to `wireUseCase` for all 3 use cases (signIn, signUp, signOut); all gates pass on this commit.
|
||||
- [ ] Migrate `blog` binders to `wireUseCase` for all 3 use cases (getArticles, getArticleBySlug, createArticle); all gates pass on this commit.
|
||||
- [ ] Migrate `media` binders to `wireUseCase` for all 3 use cases (getMedia, listMedia, deleteMedia); all gates pass on this commit.
|
||||
- [ ] Migrate `marketing-pages` binders to `wireUseCase` for both use cases (getPageBySlug, getSiteSettings); all gates pass on this commit.
|
||||
- [ ] Migrate `navigation` binders to `wireUseCase` for the single use case (getHeader); all gates pass on this commit; verify `pnpm fallow dupes` no longer surfaces the five binder-pair clone groups.
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: 03-update-generator-templates
|
||||
epic: 2026-05-13-binder-wrap-helper
|
||||
title: Update feature generator templates to emit wireUseCase call shape
|
||||
type: technical-story
|
||||
status: todo
|
||||
feature: core-shared
|
||||
depends-on: [01-wire-use-case-helper]
|
||||
blocks: []
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Update `turbo/generators/templates/feature/src/di/bind-production.ts.hbs` and `bind-dev-seed.ts.hbs` so that `pnpm turbo gen feature` scaffolds new features with `wireUseCase` calls instead of the longhand inline `withSpan + withCapture` form.
|
||||
|
||||
## Why
|
||||
|
||||
New features scaffolded after the migration would otherwise drift back to the inline form, re-introducing the clone group. The generator is the canonical source for scaffolded binder shape; updating it closes the loop.
|
||||
|
||||
## Done when
|
||||
|
||||
- `turbo/generators/templates/feature/src/di/bind-production.ts.hbs` emits `wireUseCase(...)` calls (not inline `withSpan + withCapture`) for the placeholder use case.
|
||||
- `turbo/generators/templates/feature/src/di/bind-dev-seed.ts.hbs` emits `wireUseCase(...)` calls for the placeholder use case.
|
||||
- Running `pnpm turbo gen feature testfeature` produces binder files that call `wireUseCase` and pass `pnpm typecheck && pnpm lint && pnpm test && pnpm conformance && pnpm fallow:audit && pnpm coverage:diff` (or the generated feature is cleaned up after verification).
|
||||
|
||||
## In scope
|
||||
|
||||
- `turbo/generators/templates/feature/src/di/bind-production.ts.hbs`
|
||||
- `turbo/generators/templates/feature/src/di/bind-dev-seed.ts.hbs`
|
||||
- Any supporting type imports in the templates that change as a result (e.g. dropping `ProductionUseCase` intermediate type if the helper absorbs it).
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Generator templates for events, jobs, realtime, or core-packages — those do not produce binder files with use-case wrapping.
|
||||
- Runtime behaviour changes — the generated output must be semantically equivalent to the old inline form.
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Update `bind-production.ts.hbs` and `bind-dev-seed.ts.hbs` in `turbo/generators/templates/feature/src/di/` to emit `wireUseCase` calls; verify a test-scaffold of a new feature produces correct binder output and all gates pass on this commit.
|
||||
23
docs/work/2026-05-13-binder-wrap-helper/_epic.md
Normal file
23
docs/work/2026-05-13-binder-wrap-helper/_epic.md
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
id: 2026-05-13-binder-wrap-helper
|
||||
prd: docs/work/prds/2026-05-13-binder-wrap-helper.prd.md
|
||||
title: Collapse binder duplication via wireUseCase helper
|
||||
type: epic
|
||||
status: in-progress
|
||||
features: [core-shared, auth, blog, media, marketing-pages, navigation]
|
||||
created: 2026-05-13
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Introduce a `wireUseCase(...)` helper in `@repo/core-shared/conformance/` that encapsulates the `withSpan + withCapture (+ optional withAudit)` composition. Refactor all five features' binders to call the helper. Update the feature generator templates to emit the new call shape by default.
|
||||
|
||||
## Why
|
||||
|
||||
Five of `pnpm fallow`'s top-ten clone groups come from binder pairs across features. The inline wrapping runs 30–79 duplicated lines per binder pair. The helper becomes the single source of truth for the wrapping shape; per-feature binders shrink to their decision content plus a list of `wireUseCase` calls.
|
||||
|
||||
## Stories
|
||||
|
||||
- [ ] [01 — Introduce `wireUseCase` helper in core-shared](01-wire-use-case-helper/_story.md)
|
||||
- [ ] [02 — Migrate all five feature binders to `wireUseCase`](02-migrate-feature-binders/_story.md)
|
||||
- [ ] [03 — Update generator templates to emit `wireUseCase` call shape](03-update-generator-templates/_story.md)
|
||||
@@ -1,6 +1,44 @@
|
||||
{
|
||||
"updated_at": "2026-05-13T16:41:39.179Z",
|
||||
"updated_at": "2026-05-13T17:17:12.493Z",
|
||||
"epics": {
|
||||
"2026-05-13-binder-wrap-helper": {
|
||||
"status": "in-progress",
|
||||
"title": "Collapse binder duplication via wireUseCase helper",
|
||||
"prd": "docs/work/prds/2026-05-13-binder-wrap-helper.prd.md",
|
||||
"stories": {
|
||||
"01-wire-use-case-helper": {
|
||||
"status": "in-progress",
|
||||
"title": "Introduce wireUseCase helper in core-shared",
|
||||
"ac_total": 1,
|
||||
"ac_completed": 0,
|
||||
"depends_on": [],
|
||||
"blocks": [
|
||||
"02-migrate-feature-binders",
|
||||
"03-update-generator-templates"
|
||||
]
|
||||
},
|
||||
"02-migrate-feature-binders": {
|
||||
"status": "todo",
|
||||
"title": "Migrate all five feature binders to wireUseCase",
|
||||
"ac_total": 5,
|
||||
"ac_completed": 0,
|
||||
"depends_on": [
|
||||
"01-wire-use-case-helper"
|
||||
],
|
||||
"blocks": []
|
||||
},
|
||||
"03-update-generator-templates": {
|
||||
"status": "todo",
|
||||
"title": "Update feature generator templates to emit wireUseCase call shape",
|
||||
"ac_total": 1,
|
||||
"ac_completed": 0,
|
||||
"depends_on": [
|
||||
"01-wire-use-case-helper"
|
||||
],
|
||||
"blocks": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"template-reset-v1": {
|
||||
"status": "done",
|
||||
"title": "Template reset — strip setup-process noise + archive history",
|
||||
@@ -17,7 +55,30 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ready": [],
|
||||
"blocked": [],
|
||||
"ready": [
|
||||
{
|
||||
"epic": "2026-05-13-binder-wrap-helper",
|
||||
"story": "01-wire-use-case-helper",
|
||||
"title": "Introduce wireUseCase helper in core-shared"
|
||||
}
|
||||
],
|
||||
"blocked": [
|
||||
{
|
||||
"epic": "2026-05-13-binder-wrap-helper",
|
||||
"story": "02-migrate-feature-binders",
|
||||
"title": "Migrate all five feature binders to wireUseCase",
|
||||
"waiting_on": [
|
||||
"2026-05-13-binder-wrap-helper/01-wire-use-case-helper"
|
||||
]
|
||||
},
|
||||
{
|
||||
"epic": "2026-05-13-binder-wrap-helper",
|
||||
"story": "03-update-generator-templates",
|
||||
"title": "Update feature generator templates to emit wireUseCase call shape",
|
||||
"waiting_on": [
|
||||
"2026-05-13-binder-wrap-helper/01-wire-use-case-helper"
|
||||
]
|
||||
}
|
||||
],
|
||||
"needs_prd_ship": []
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user