Adds a shared requireAuthenticated tRPC middleware (reads the server-
resolved ctx.user from createTrpcContext) and applies it to every
mutating feature procedure — blog.createArticle and media.deleteMedia
were anonymous-callable (audit finding B7). Read-only queries stay
public; features compose <x>ProtectedProcedure from their error-mapped
base procedure.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@trpc/client resolved to 11.16.0 in some packages while @trpc/server
resolved to 11.17.0 in others, tripping the exact-version peer check.
Update every @trpc/* range in-range so the whole workspace resolves to
one version (11.18.0); install is peer-warning-free again.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
core-trpc imported AppRouter (type-only) from core-api, which depends
on the features, which depend on core-trpc — a cycle that killed every
turbo graph walk (lint/build/CI) and an illegal core -> core-composition
boundary edge. The tRPC context is now router-agnostic (AnyTRPCRouter);
useTRPC takes the router type as a generic and each feature exports a
type-only app-slice (e.g. BlogAppSlice) mirroring its mount key, so UI
hooks keep full procedure typing without touching the composition layer.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Payload paginates by page, so floor(offset/limit)+1 alone returned the
wrong window whenever offset % limit != 0 (B9). Fetch the straddled
pages and slice the intra-page remainder. The test stub now honours
page like real Payload (it silently ignored it before), and the
contract pins aligned + non-aligned windows on mock and stub alike.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pnpm audit --prod --audit-level=critical (the new CI gate) failed on
vitest <3.2.6 (critical, arbitrary file read/execute via the UI
server). Bump vitest + @vitest/coverage-v8 in-range across the
workspace and add @vitest/coverage-v8 to every package that runs
vitest but lacked it (core-audit, core-cms, core-eslint, core-testing,
core-trpc, apps/cms, web-next, web-tanstack, turbo/generators) so
'pnpm test -- --coverage' works in every package. No compound test
scripts exist, so the vitest-last pass-through concern does not apply.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Server components (.server.tsx) resolve controllers from DI, prefetch
data, and wrap client components in HydrationBoundary. Client components
(.client.tsx) use hooks for hydration + background refetch. Barrel
exports server components under clean names — consumers never see the
server/client split.
Webpack (Next.js transpilePackages) resolves from .ts source directly
and cannot find .js files. The bind-production binders already used
extensionless imports; align bind-dev-seed to match. Also fixes the
turbo gen feature template so new features are consistent.
container.test.ts imported cross-directory modules with `../...` paths.
Test files use the `@/` alias for src imports per the repo convention;
this was the outlier vs auth/media/navigation.
Monthly purge schedule with 90-day post-deletion hard-delete window.
Part of compliance backfill (Story 05).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each feature's bind-production.ts already ends with
assertFeatureConformance(), but that check only fired on `pnpm dev`.
Adds a smoke test per feature that invokes the binder with a Noop ctx,
so missing __instrumented / __captured / __audited brands now fail
under `pnpm test` (and therefore in CI), closing the gap between the
TypeScript brand layer and the boot-time runtime assertion.
Replace inline withSpan + withCapture blocks for getArticles,
getArticleBySlug, and createArticle in both bind-production and
bind-dev-seed with wireUseCase calls.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Closes the user's ask: versioning + a changelog generated on merging
to main, building on the just-mandated Conventional Commits substrate
(CLAUDE.md Key Conventions).
Architecture: ADR-021. Cookbook: docs/guides/releasing.md.
Initial state — six tracked packages at v0.1.0:
- . -> template-vertical (tag: template-v...)
- packages/auth -> @repo/auth (tag: auth-v...)
- packages/blog -> @repo/blog (tag: blog-v...)
- packages/media -> @repo/media (tag: media-v...)
- packages/marketing-pages -> @repo/marketing-pages (tag: marketing-pages-v...)
- packages/navigation -> @repo/navigation (tag: navigation-v...)
Core packages, tooling, and apps are NOT independently versioned
(ADR-021 rationale: core bumps cascade; apps aren't consumables;
surfacing them would create noise without information).
Configuration:
- release-please-config.json - 6 tracked packages, hybrid scope,
pre-1.0 conservative bump policy
(feat: -> patch, feat!: -> minor),
conventional-commit type mapping
- .release-please-manifest.json - baseline 0.1.0 for all 6 packages
- .github/workflows/release-please.yml - googleapis/release-please-
action@v4 on push to main,
concurrency-gated, write
permissions for the rolling PR
Workflow: on every push to main, release-please scans commits since
the last release tag PER PACKAGE (using commit-path, not the
conventional-commit scope), updates a single rolling release PR with
version bumps + per-package CHANGELOG entries. Merging that PR cuts
per-package tags + GitHub releases.
CHANGELOG files seeded at v0.1.0 baseline:
- CHANGELOG.md (root)
- packages/<feature>/CHANGELOG.md (5 features)
Subsequent versions are appended by release-please from commit
history. Do not edit manually.
Visibility surfaces updated (every agent entry point):
- CLAUDE.md Read First + new "Versioning is hybrid" Key Conventions
bullet (with bump policy summary)
- AGENTS.md preamble - new "Releases:" callout alongside Commits
- docs/glossary.md - new Releasing section with 8 terms (Conventional
Commits, release-please, Hybrid versioning, Tag prefix, Rolling
release PR, Bump targeting, Pre-1.0 bump policy, Release-As trailer,
CHANGELOG.md)
- docs/README.md - guides tree updated with releasing.md
- .claude/hooks/session-start.sh - one-line release reminder
- .claude/hooks/prompt-context.sh - new keyword group for
release/version/bump/semver/tag prompts
Package.json version bumps:
- root: name "template" -> "template-vertical", version "0.1.0"
- packages/auth, blog, media, marketing-pages, navigation: "0.0.0" -> "0.1.0"
Root rename rationale: release-please tags use the package-name + the
component prefix; "template-vertical" matches the repo identity (and
the user's question preview).
First release-please PR after this lands will sweep all subsequent
post-baseline commits into 0.1.1 / 0.2.0 bumps as appropriate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Migrates blog and marketing-pages to the same pattern auth landed in
f7baa8b: vitest config consumes vitestThresholdsFromBands(
DEFAULT_COVERAGE_BANDS) instead of the duplicated hand-written
thresholds block, and each manifest declares its coverage section
explicitly.
Verified semantically identical to the previous hand-written
thresholds — the same numbers come out the other end of the helper.
No new regressions:
- blog: 89 tests, 96.33% overall, all bands green
- marketing-pages: 67 tests, 95.28% overall — controllers/ shows a
real 93.54% lines / 90.9% branches gap that has been there since
before this refactor (the previous hand-written threshold was the
same 100%/95%). This is one of the L0 unification work items
listed in the PRD's findings; capturing here as the third feature
with real test gaps (navigation, media, marketing-pages).
Three of five features now drive their vitest thresholds from the
manifest helper: auth ✓, blog ✓, marketing-pages ✓. Navigation and
media stay on the legacy config until L0 unification closes their
test gaps (touching them would expose the same failures and add
nothing).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Rename docs/decisions/adr-012-lazar-conformance.md → adr-012-feature-conventions.md
- Strip "Lazar", "Plan 8/9/10/11", "refactor-logs" refs from all ADRs,
architecture docs, HTML explainers, and feature/core AGENTS.md files
- Update all incoming links in docs/, packages/*/AGENTS.md, HTML explainers
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Six per-package AGENTS.md tables and the di-explainer wiring trace all
showed pre-ADR-014 binder shapes. Refreshed to the post-ADR-015
reality:
- Per-package "Public exports" tables now show the
(config, tracer, logger, bus, queue) production signature and the
(tracer, logger, bus, queue) dev-seed companion. auth and
marketing-pages also list their newly-public ./di/container,
./di/symbols, and (marketing-pages only) ./services/mailer +
./services/recording-mailer subpaths.
- di-explainer's wiring trace adds the resolveEventsAndJobs* step in
bindAll(), the (config, tracer, logger, bus, queue) binder
signature, and the // <gen:event-handlers> / // <gen:jobs>
injection sites.
No code change; pre-existing AGENTS.md staleness predates ADR-014 +
ADR-015 — this commit catches both up.
Plan 10 documented R44 (capture at originating-throw layer) but only the
R43 repo leg was wired. captureException had zero call sites in any
controller or use-case body. This commit closes the gap.
Mechanism:
- Extract __sentryReported flag helpers into core-shared/instrumentation/
reported-flag.ts. SentryLogger switches to importing them; RecordingLogger
carries an inlined copy (tooling → core boundary disallows the import).
- Add withCapture(logger, tags, fn) higher-order wrapper paralleling
withSpan. On throw: capture-with-tags, mark, re-throw. Bail if the flag
was already set — covers the bubbled-from-repo case so each error
surfaces in the logger exactly once with the inner-most layer's tags.
- Apply withSpan(withCapture(factory)) in every feature's bind-production
and bind-dev-seed: auth (3 use cases × 3 controllers), blog (3×3),
marketing-pages (2×2), navigation (1×1), media (3×3). Span is outermost
so the errored span timing reflects the capture-and-rethrow.
- RecordingLogger.captureException now also honours the flag — test
capture counts stay honest when both repo and outer layer wrap.
Tests:
- packages/core-shared/src/instrumentation/with-capture.test.ts —
4 cases covering success, capture-on-throw, mark-on-capture, no-double
via the flag.
- packages/blog/tests/r44-no-double-capture.test.ts — 3 cases: repo throw
→ 1 capture with repo tags; controller parse fail → 1 capture with
controller tags; success → 0 captures.
Verification: pnpm test 26/26, pnpm lint 15/15, pnpm typecheck 14/14.
Docs: ADR-014 and the refactor log gain a "Post-merge follow-up" section
recording the gap, the fix, and the underlying lesson (don't describe
intent as shipped state — grep first).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sets the canonical pattern for all 5 features. Three new files:
- src/__seeds__/dev.ts — buildDevArticles() function returning 3
realistic articles (welcome, vertical-feature-architecture,
wip-post) built from articleFactory with id/slug/title/status
overrides only.
- src/di/bind-dev-seed.ts — bindDevSeedBlog() async function that
unbinds IArticlesRepository, constructs MockArticlesRepository,
seeds it via buildDevArticles(), and rebinds via .toConstantValue.
- src/di/bind-dev-seed.test.ts — 3 tests: populated repo, welcome
article reachable by slug, idempotent (callable twice).
package.json adds the ./di/bind-dev-seed subpath export, parallel to
./di/bind-production.
Tests + use cases continue to construct MockArticlesRepository
directly — they never go through bindDevSeedBlog. The seed only attaches
when called explicitly at app boot.
Each per-feature AGENTS.md now reflects the post-Plan-9 layout:
entity/error paths, public-API split (./ui), use-case schemas, presenter
pattern, feature-scoped tRPC error map, and feature-specific
errors-to-codes table.
core-testing/AGENTS.md gains a Plan 9 test-patterns section documenting
R25 (output validation), R26 (router error mapping), R27/R28
(presenter shape) test obligations.
auth: documents real PayloadUsersRepository + AuthenticationService and
the deferred session methods.
media: documents the full Clean Architecture scaffold introduced in
Plan 8.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Spec reviewer caught a systemic gap during Task 6 (navigation) review:
Plan 8 left every feature's domain error class with a constructor that
didn't set this.name = '<ClassName>'. Plan 9 spec R6 requires it.
Fixed across all 10 error files:
- auth: AuthenticationError, UnauthenticatedError, UnauthorizedError, InputParseError
- blog: ArticleNotFoundError, InputParseError
- marketing-pages: PageNotFoundError, InputParseError
- navigation: HeaderNotFoundError, InputParseError
- media: MediaNotFoundError, InputParseError
Functionally a no-op — defineErrorMiddleware uses instanceof, not name —
but ensures correct serialization, stack traces, and JSON inspection.
Refactor log: §7
Spec: R6
Code-quality reviewer flagged stylistic divergence from auth's pattern:
- All 3 blog use cases now have // ── Input ──... / ── Output ──... /
── Use case ──... section dividers (consistent with auth Task 3 + plan
template §4.1).
- Removed inline comment in get-articles controller's presenter that
appeared in only 1 of 3 controllers; identity presenters are
self-explanatory.
All 5 features (auth, blog, marketing-pages, navigation; media has no
entities yet) now follow Lazar's pattern:
- entities/<x>.ts → entities/models/<x>.ts
- entities/errors.ts → entities/errors/<domain>.ts + errors/common.ts
Updates all import paths across factories, contracts, tests, use cases,
controllers, repositories, integrations, and src/index.ts exports.
navigation divergence: had no errors.ts; errors/header.ts +
errors/common.ts added as new forward-looking stubs.
Refactor log: docs/superpowers/refactor-logs/2026-05-05-lazar-pattern-conformance.md
Spec: §5, §9.3
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures the decision to add @repo/core-testing, factories, contract
suites, vitest safety defaults, coverage thresholds, Storybook
test-runner, and CI as one cohesive TDD foundation. Per-feature
AGENTS.md gains a Tests section pointing to factories, contract suite,
and the canonical test commands.
Spec: §7.4, §7.5
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Deprecate mockPayloadModule with throw guard (hoisting incompatible)
- Replace `as never` with stubPayloadConfig in payload-articles test (consistency)
- Tighten pages contract to use toHaveLength (exact assertions)
- Header contract: define CONTRACT_HEADER_SEED, assert item count + order
Reviewer: superpowers:code-reviewer (Task 4 of Plan 7).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each repository interface now has a contract suite under
src/__contracts__/. Both Mock and Payload implementations run the
same suite, eliminating mock-vs-real drift. Payload impls back the
contract with an in-memory stub via vi.mock('payload') + a small
buildPayloadStub helper.
Spec: §5.2, §6.4
- Add navItemFactory to navigation (spec §5.1 — was missing)
- Refactor blog/router.test.ts to use articleFactory (eliminate new Date())
- headerFactory uses sequence for logoId (deterministic buildList output)
- Align media/tsconfig.json with other features (jsx + tests/ include)
- Refactor auth/container.test.ts to use userFactory
Reviewer: superpowers:code-reviewer (Task 3 of Plan 7).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds src/__factories__/<entity>.factory.ts to auth, blog, marketing-pages,
navigation, media. Each factory uses defineFactory from @repo/core-testing
with stable date defaults (2026-01-01) so snapshot diffs reflect SUT
behavior only. Refactors mechanical inline-fixture tests to use factories.
Also adds vitest.config.ts and tsconfig path alias to @repo/media (lacked
both), and adds @repo/core-testing devDependency to @repo/media.
Spec: §5.1, §6.3
Adds vitest.base.node and vitest.base.jsdom with safety defaults
(clearMocks, restoreMocks, mockReset, unstubGlobals, sequence.shuffle)
and coverage thresholds (80/75/80/80). Migrates all feature configs
to the new base. Existing baseVitestConfig kept as backwards-compat
re-export of nodeVitestConfig.
Spec: §6.2
Aligns tooling packages with the core-* naming convention used by all
other foundation packages (core-shared, core-cms, core-api, core-trpc,
core-ui). Updates ~50 files: package.json names, devDependencies,
tsconfig extends, eslint.config imports, vitest.config imports, AGENTS.md
references, and the boundaries plugin patterns to match the new paths.
The tooling-specific patterns in boundaries/elements are now ordered BEFORE
the broader core-* pattern to ensure correct first-match-wins behavior.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>