diff --git a/docs/superpowers/specs/2026-04-21-vertical-monorepo-refactor-design.md b/docs/superpowers/specs/2026-04-21-vertical-monorepo-refactor-design.md new file mode 100644 index 0000000..934c51d --- /dev/null +++ b/docs/superpowers/specs/2026-04-21-vertical-monorepo-refactor-design.md @@ -0,0 +1,604 @@ +# Vertical Feature Monorepo Refactor — Design Spec + +**Date:** 2026-04-21 +**Status:** Approved for implementation planning +**Supersedes (partially):** 2026-04-06-clean-architecture-monorepo-template-design.md +**Source spec:** `monorepo-architecture-spec-detailed-v5.md` (v1 + addenda v3/v4/v5) + +--- + +## 1. Goal + +Refactor the template from a horizontal Clean Architecture monorepo (single `packages/core`, single `packages/api`, etc.) into a vertical feature-package monorepo where business capabilities (`auth`, `blog`, `media`, `marketing-pages`, `navigation`) are the top-level organizing principle, supported by `core-*` foundation packages for non-business concerns. + +The refactor preserves Clean Architecture layering *inside* each feature (the existing rigor) while reorganizing *between* packages by business capability. + +--- + +## 2. Current state (summary) + +- `packages/core` — all domains (auth, content) share one Clean Architecture layout with InversifyJS DI container +- `packages/api` — single tRPC aggregator + per-domain routers +- `packages/api-client` — React Query hooks + `ApiProvider` + `useTRPC` +- `packages/cms-core` — Payload config + all collections (Users, Articles, Media, SiteSettings global) +- `packages/cms-client` — dual-mode Payload client wrapper; defined but unused +- `packages/ui` — atomic-design component library +- `apps/web-next` (empty shell), `apps/web-tanstack` (empty shell), `apps/cms` (just stabilized), `apps/storybook` +- Mock repositories only — no Payload-backed infrastructure +- 9 Vitest unit tests under `packages/core/tests/unit/` +- Extensive per-directory AGENTS.md; 5 ADRs; 2 guides; 6 dated superpower plans + +--- + +## 3. Target state (summary) + +- Five `core-*` packages: `core-shared`, `core-cms`, `core-api`, `core-trpc`, `core-ui` +- Five feature packages: `auth`, `blog`, `media`, `marketing-pages`, `navigation` +- Two tooling packages unchanged: `eslint-config`, `typescript-config` +- Three apps unchanged in name: `apps/web-next`, `apps/web-tanstack`, `apps/cms` +- Packages deleted: `packages/core`, `packages/api`, `packages/api-client`, `packages/cms-core`, `packages/cms-client`, `packages/ui` +- Per-feature InversifyJS containers (no shared container) +- Clean Architecture controllers retained inside each feature (`interface-adapters/controllers/`) +- Spec's `adapters/` renamed to `integrations/` to avoid collision with `interface-adapters/` +- Boundary enforcement via `eslint-plugin-boundaries` + `package.json` deps + `exports` maps + Turborepo tags +- Playwright e2e set up from day one in `web-next` and `web-tanstack` + +--- + +## 4. Decision log + +All decisions captured from the brainstorming conversation: + +| # | Decision | Rationale | +|---|---|---| +| 1 | **Big-bang migration** (not incremental) | Template has empty reference apps and no external consumers; incremental dual-maintenance is overhead without benefit | +| 2 | **Feature scope:** `auth` + `blog` + `media` + `marketing-pages` + `navigation` (all real, none as empty skeletons) | Template value is in worked examples; reference app needs something to render; spec §15A.2 forbids empty folders | +| 3 | **Keep InversifyJS** | User wants to preserve existing DI pattern rather than move to plain function injection | +| 4 | **Per-feature DI containers** (not a shared container) | Each feature owns its own `Container`, symbols, `getInjection()`. Perfect vertical ownership; no composition package needed for DI; tests unbind/rebind their own container | +| 5 | **`media` is a feature package**, not `core-media` | Application can live without media; it's a business capability per spec §3. Site-wide concerns (SiteSettings) fold into `marketing-pages`, not a separate `site` package | +| 6 | **Keep all three apps** (`web-next`, `web-tanstack`, `cms`) with existing names | `web-tanstack` proves features are framework-agnostic; no rename avoids churn | +| 7 | **Keep `interface-adapters/controllers/` layer** | Transport-agnostic controllers enable CLI/cron reuse; template should demonstrate growth room for presenters/gateways | +| 8 | **Rename spec's `adapters/` → `integrations/`** | Avoids collision with Clean Architecture's `interface-adapters/`; captures spec's "role not implementation" intent equally well; bounded deviation from spec | +| 9 | **Delete existing tests, rewrite fresh**; rewrite AGENTS.md, add ADRs, mark old ADRs superseded where relevant | DI pattern change + file layout change make porting more work than rewriting; ADRs preserve architectural history | +| 10 | **Include `eslint-plugin-boundaries`** from day one | Spec §13A.7 explicitly requires three-layer enforcement; package.json deps + exports + lint rules | +| 11 | **Playwright set up immediately** in `web-next` and `web-tanstack` | Not deferred; demonstrates framework portability end-to-end | +| 12 | **Keep `articles` collection/entity naming** (not rename to `posts`) | Simpler migration; collapses CMS-doc vs domain distinction which is fine for a template | + +Deviations from source spec (document explicitly as new ADRs): +- Keep InversifyJS (spec examples use plain function injection) +- Keep controller layer between tRPC and use-case (spec goes tRPC→use-case direct) +- Rename spec's `adapters/` to `integrations/` +- Omit `core-payload-client` wrapper (aligned with spec §10) + +--- + +## 5. Target package layout + +``` +repo/ + apps/ + web-next/ # Next.js 15 App Router (port 3000) + app/ + layout.tsx # from @repo/core-trpc/next + trpc/[trpc]/route.ts # tRPC fetch adapter → @repo/core-api appRouter + blog/[slug]/page.tsx + about/page.tsx + page.tsx # home — navigation + marketing-pages + e2e/ + playwright.config.ts + blog-post.spec.ts + marketing-page.spec.ts + home-nav.spec.ts + package.json next.config.mjs tsconfig.json turbo.json + + web-tanstack/ # TanStack Start (port 3002) + ... # parallel tRPC wiring; own providers from @repo/core-trpc/tanstack + e2e/ + playwright.config.ts + blog-post.spec.ts + + cms/ # Payload admin host (port 3001) + app/(payload)/ # unchanged from current stabilized state + package.json next.config.mjs tsconfig.json turbo.json + + storybook/ # unchanged; updates imports from @repo/ui → @repo/core-ui + + packages/ + # ─── CORE (foundation, tagged "core") ─── + core-shared/ # generic primitives (no business knowledge) + core-cms/ # Payload composition only (aggregates feature cms exports) + core-api/ # tRPC composition only (aggregates feature api exports) + core-trpc/ # frontend tRPC platform (client, providers per framework) + core-ui/ # design-system primitives (atoms/molecules/templates) + + # ─── FEATURES (business capabilities, tagged "feature") ─── + auth/ # Users collection + sign-in/up/out + blog/ # Articles collection + article use-cases + media/ # Media collection + upload helpers + marketing-pages/ # pages collection + SiteSettings global + navigation/ # header global + + # ─── TOOLING (untagged) ─── + eslint-config/ + typescript-config/ + + docs/ + architecture/ + overview.md # rewritten + dependency-flow.md # rewritten + vertical-feature-spec.md # copy of source spec + decisions/ + adr-001 … adr-009 # five existing + four new (see §10) + guides/ + adding-a-feature.md # rewritten + testing-strategy.md # rewritten + superpowers/specs/ # this file + implementation plan + + CLAUDE.md AGENTS.md docker-compose.yml package.json pnpm-lock.yaml + pnpm-workspace.yaml tsconfig.base.json turbo.json +``` + +**Package count:** 3 apps + 5 core + 5 feature + 2 tooling = **15 packages** (was 12). + +--- + +## 6. Feature package internal shape + +Canonical mature shape (e.g., `packages/blog/`): + +``` +packages/blog/ + src/ + entities/ + article.ts # Zod schema + Article type + article.test.ts + errors.ts + + application/ + repositories/ + articles-repository.interface.ts # IArticlesRepository + use-cases/ + get-article.use-case.ts + get-article.use-case.test.ts + create-article.use-case.ts + + infrastructure/ + repositories/ + payload-articles.repository.ts # @injectable, calls getPayload({ config }) from core-cms + mock-articles.repository.ts # @injectable, for tests + payload-articles.repository.test.ts + + interface-adapters/ # Clean Arch grouping (controllers now; presenters/gateways later) + controllers/ + articles.controller.ts # Zod safeParse → InputParseError → use case + articles.controller.test.ts + + di/ # feature-local InversifyJS container + symbols.ts # BLOG_SYMBOLS + module.ts # ContainerModule + container.ts # blogContainer + getInjection() + container.test.ts + + integrations/ # renamed from spec's adapters/ + cms/ + collections/ + articles.ts # Payload CollectionConfig + hooks/ + after-post-change.ts # Payload lifecycle adapter → calls effects + index.ts # exports: articles (for core-cms composition) + api/ + router.ts # tRPC procedures → controllers + router.test.ts + + effects/ # only when needed + revalidate-post.ts + sync-post-search.ts + + jobs/ # only when needed + publish-scheduled-posts.ts + + events/ # only when needed + post-updated.ts + + ui/ + query.ts # trpc.blog.articleBySlug.queryOptions(...) + query.test.ts + article-client.tsx + article-client.test.tsx + page.tsx + + index.ts # re-exports ui components + public types + + tests/ + article-by-slug.feature.test.ts # cross-layer feature test + + package.json # exports: ".", "./cms", "./api" + tsconfig.json + turbo.json # tags: ["feature"] +``` + +Small-feature variant (e.g., `packages/navigation/`) omits folders without meaningful code per spec §15 / addendum v5 ("create folders only when needed"): + +``` +packages/navigation/ + src/ + entities/ nav.ts + infrastructure/repositories/ payload-navigation.repository.ts + di/ symbols.ts module.ts container.ts + interface-adapters/controllers/ navigation.controller.ts + integrations/ + cms/ globals/ header.ts + index.ts + api/ router.ts + ui/ query.ts + index.ts +``` + +No `application/use-cases/`, `effects/`, `jobs/`, `events/` unless the feature grows them. + +**Request flow:** + +``` +useQuery(articleQuery(slug)) ui/query.ts (typed tRPC client) + ↓ +tRPC router.articleBySlug integrations/api/router.ts + ↓ .input(zod).query(...) +articlesController.getBySlug(input) interface-adapters/controllers/ + ↓ safeParse → delegate +getArticleUseCase(slug) application/use-cases/ + ↓ getInjection(BLOG_SYMBOLS.IArticlesRepository) +PayloadArticlesRepository.getBySlug infrastructure/repositories/ (@injectable) + ↓ getPayload({ config }) from @repo/core-cms +Payload Local API → PostgreSQL +``` + +**DI placement rationale:** `di/` sits at feature root (not under `infrastructure/`) because the container wires `application/` interfaces to `infrastructure/` implementations — it has knowledge of both layers and is a sibling to them, not a sub-layer. + +--- + +## 7. Core package responsibilities + +### `core-shared/` + +Generic reusable primitives. Zero business knowledge. + +``` +src/ + lib/ + env.ts date.ts + payload/ + access/ is-admin.ts + fields/ slug-field.ts seo-fields.ts + blocks/ cta.ts + hooks/ set-published-at.ts slugify-if-missing.ts + index.ts + trpc/ + init.ts # initTRPC.create + router/publicProcedure + context.ts # createTrpcContext + TrpcContext type + index.ts +``` + +Exports: `.`, `./payload`, `./trpc/init`, `./trpc/context`. + +Forbidden: importing any feature package. + +### `core-cms/` + +Payload composition only. + +``` +src/ + payload.config.ts # imports feature /cms exports, composes buildConfig + generated-types.ts # Payload type generator output + index.ts # re-exports config as default +``` + +Exports: `.`, `./generated-types`. + +Allowed exception: may import `@repo//cms` subpath exports only. + +### `core-api/` + +tRPC composition only. + +``` +src/ + root.ts # aggregates feature routers → appRouter + index.ts # re-exports appRouter + AppRouter type +``` + +Allowed exception: may import `@repo//api` subpath exports only. + +### `core-trpc/` + +Frontend tRPC platform with framework-specific provider shims. + +``` +src/ + client.ts # createTRPCReact() + query-client.ts # makeQueryClient() + providers/ + next-provider.tsx # 'use client' — Next.js App Router pattern + tanstack-provider.tsx # TanStack Start pattern + index.ts +``` + +Exports: `.`, `./next`, `./tanstack`. + +Forbidden: importing any feature package. + +### `core-ui/` + +Design-system primitives only. + +``` +src/ + atoms/ button/ input/ label/ + molecules/ form-field/ + organisms/ (generic only — e.g., modal, tabs, navigation-menu) + templates/ auth-layout/ dashboard-layout/ + lib/ utils.ts + index.ts +``` + +Generic organisms (Modal, Tabs, NavigationMenu, Command) live here. Feature-specific organisms (e.g., `ArticleCard`, `PricingSection`, `HeaderNavMenu`) live in the owning feature's `ui/`. Spec §6.5 boundary. + +Forbidden: importing any feature package. + +--- + +## 8. Payload collection & global ownership + +| Current | → New location | Slug / type | Notes | +|---|---|---|---| +| `cms-core/src/collections/users/` | `packages/auth/src/integrations/cms/collections/users.ts` | `users` | Authenticated collection | +| `cms-core/src/collections/articles/` | `packages/blog/src/integrations/cms/collections/articles.ts` | `articles` | Name preserved per decision #12 | +| `cms-core/src/collections/media/` | `packages/media/src/integrations/cms/collections/media.ts` | `media` | Upload collection | +| `cms-core/src/globals/site-settings/` | `packages/marketing-pages/src/integrations/cms/globals/site-settings.ts` | `site-settings` | Site-wide metadata | +| (new) | `packages/marketing-pages/src/integrations/cms/collections/pages.ts` | `pages` | | +| (new) | `packages/navigation/src/integrations/cms/globals/header.ts` | `header` | | + +Composition in `core-cms/src/payload.config.ts`: + +```ts +import { buildConfig } from 'payload' +import { users } from '@repo/auth/cms' +import { articles } from '@repo/blog/cms' +import { pages, siteSettings } from '@repo/marketing-pages/cms' +import { header } from '@repo/navigation/cms' +import { media } from '@repo/media/cms' + +export default buildConfig({ + collections: [users, articles, pages, media], + globals: [header, siteSettings], + typescript: { + outputFile: new URL('./generated-types.ts', import.meta.url).pathname, + declare: false, + }, + // db, admin config unchanged from current cms-core +}) +``` + +**Hook routing policy:** +- Generic hooks (e.g., `slugify-if-missing`, `set-published-at`) live in `core-shared/src/payload/hooks/` and are imported by any collection that needs them. +- Business-specific hooks (e.g., "revalidate blog post page when published") live in the feature's `integrations/cms/hooks/`, which call the feature's `effects/` for reusable side effects. + +--- + +## 9. Boundaries + enforcement + +### 9.1 Tags + +Package-level `turbo.json` tags: + +| Tag | Packages | +|---|---| +| `app` | `apps/web-next`, `apps/web-tanstack`, `apps/cms` | +| `feature` | `packages/auth`, `blog`, `media`, `marketing-pages`, `navigation` | +| `core` | `packages/core-shared`, `core-cms`, `core-api`, `core-trpc`, `core-ui` | +| (untagged) | `packages/eslint-config`, `packages/typescript-config` | + +### 9.2 Allowed dependency directions + +``` +app → feature, core +feature → core +core → core (restricted; see exceptions) +``` + +Disallowed: `core → feature`, `core → app`, `feature → app`, `feature → feature`. + +### 9.3 Composition exceptions + +- `core-cms` may import `@repo//cms` subpath exports only. +- `core-api` may import `@repo//api` subpath exports only. +- No other `core-*` package may import any feature package under any export. + +### 9.4 Three enforcement layers + +1. **`package.json` dependencies** — only allowed deps declared. +2. **`exports` maps** — feature packages expose `.`, `./cms`, `./api` only (no deep source paths). +3. **ESLint `eslint-plugin-boundaries`** — configured in `packages/eslint-config/` flat config: + - Feature packages may import other packages only through public subpath exports. + - `core-shared`, `core-trpc`, `core-ui` may not import feature packages. + - `core-api` restricted to `@repo//api`. + - `core-cms` restricted to `@repo//cms`. + - No `../../../` cross-package source imports. +4. **TypeScript path aliases** (`tsconfig.base.json`) — only `@repo/`, `@repo//cms`, `@repo//api`; no `@repo//src/...` paths exist, blocking deep imports at editor level. + +### 9.5 Root `turbo.json` (unchanged concept) + +```json +{ + "tasks": { + "build": { "dependsOn": ["^build"], "outputs": [".next/**", "dist/**"] }, + "lint": { "dependsOn": ["^lint"] }, + "typecheck": { "dependsOn": ["^typecheck"] }, + "test": { "dependsOn": ["^build"] }, + "test:e2e": { "dependsOn": ["^build"], "cache": false } + } +} +``` + +Tags govern architectural boundaries; `dependsOn: ["^build"]` governs task execution order — separate concerns per spec §13A.6. + +--- + +## 10. Test placement + tooling + +### 10.1 Placement + +| Scope | Location | Suffix | +|---|---|---| +| Entity / value-object | colocated | `*.test.ts` | +| Use-case (with fake repo) | colocated | `*.test.ts` | +| Controller (Zod validation) | colocated | `*.test.ts` | +| Infrastructure repository | colocated | `*.test.ts` | +| DI container bindings | colocated | `*.test.ts` | +| React component | colocated | `*.test.tsx` | +| Query helper | colocated | `*.test.ts` | +| Core-shared primitive | colocated in `core-shared` | `*.test.ts` | +| Feature-level cross-layer | `packages//tests/` | `*.feature.test.ts` | +| Browser e2e | `apps//e2e/` | `*.spec.ts` | + +### 10.2 Vitest + +- Each package has its own `vitest.config.ts`. +- Shared base in `packages/typescript-config/vitest.base.ts`; packages extend it and pick `environment: 'jsdom' | 'node'` per need. +- Turbo `test` task runs `vitest run` per package. + +### 10.3 DI in tests (per-feature container) + +Each feature's tests import the feature's own container and rebind per test: + +```ts +import { blogContainer, BLOG_SYMBOLS } from '../../di/container' +import { MockArticlesRepository } from '../../infrastructure/repositories/mock-articles.repository' + +beforeEach(() => { + blogContainer.unbindAll() + blogContainer.bind(BLOG_SYMBOLS.IArticlesRepository).to(MockArticlesRepository) +}) +``` + +No shared `initializeContainer()` / `destroyContainer()`. + +### 10.4 Starter test coverage (end of refactor) + +- `core-shared`: 3 tests (slug-field, set-published-at, is-admin) +- `auth`: 6 tests (3 controllers + 3 use-cases) — replaces current auth unit tests +- `blog`: 3 tests (2 use-cases + 1 feature test for `articleBySlug`) +- `marketing-pages`, `navigation`, `media`: minimum one feature test each + +Total ~15 unit/integration tests + 4 e2e = replaces current 9 tests with full new layout. + +### 10.5 Playwright (included from day one) + +- `apps/web-next/e2e/`: + - `playwright.config.ts` — starts dev server on port 3000 via `webServer`, chromium only initially + - `blog-post.spec.ts`, `marketing-page.spec.ts`, `home-nav.spec.ts` +- `apps/web-tanstack/e2e/`: + - Parallel config (port 3002) + - `blog-post.spec.ts` (validates framework-agnostic feature claim) +- `apps/cms/` — no e2e (Payload has its own admin tests) +- Root script: `pnpm test:e2e` via Turbo +- ESLint config adds `eslint-plugin-playwright` for e2e folders +- Playwright's `globalSetup` verifies Postgres is running; fails fast with a helpful message otherwise + +--- + +## 11. Docs + ADR strategy + +### 11.1 Existing ADRs + +| File | Action | Notes | +|---|---|---| +| `adr-001-monorepo-tool.md` | Keep unchanged | Turborepo + pnpm still accurate | +| `adr-002-di-framework.md` | Keep; append note | InversifyJS kept, but now per-feature containers | +| `adr-003-cms-separation.md` | Mark v1 superseded, write v2 | New architecture splits `cms-core` into `core-cms` + feature-owned collections | +| `adr-004-dual-mode-client.md` | Mark superseded | `cms-client` deleted per spec §10 | +| `adr-005-atomic-design.md` | Keep; append scope note | Applies to `core-ui/` only | + +### 11.2 New ADRs + +- `adr-006-vertical-feature-packages.md` — the main architectural pivot; references source spec +- `adr-007-drop-cms-client-wrapper.md` — rationale for removing `packages/cms-client` +- `adr-008-per-feature-di-containers.md` — why each feature owns its InversifyJS container +- `adr-009-integrations-folder-naming.md` — why spec's `adapters/` is renamed `integrations/` + +### 11.3 Rewritten docs + +- `docs/architecture/overview.md` — new diagram, new flow, vertical package organization +- `docs/architecture/dependency-flow.md` — new graph, three-tag boundary model +- `docs/architecture/vertical-feature-spec.md` — copy of source spec for offline reference +- `docs/guides/adding-a-feature.md` — new recipe (small feature + mature feature, per addendum v5) +- `docs/guides/testing-strategy.md` — new placement table + +### 11.4 Deleted + +- `docs/superpowers/plans/*` — 6 stale plan files from previous architecture + +### 11.5 AGENTS.md + +All rewritten: + +- Root `AGENTS.md` — new package map, new data flow, new rules, new boundary model +- Per-core-package: responsibilities + forbidden imports (~60 lines each) +- Per-feature-package: layer rules, addendum v5 folder-creation checklist, test placement +- Per-layer inside features: local import rules, test patterns (short) +- Per-app: purpose, imports, port, dev commands, e2e commands + +Root `CLAUDE.md` — updated "Read First" pointers, unchanged port table, added boundary-enforcement note. + +--- + +## 12. Migration sequencing + +Big-bang refactor executed as 11 internal phases; each phase ends with a verification gate (`pnpm typecheck && pnpm test`) before proceeding. Commit per phase (or per feature within Phase 5) so `git bisect` works. + +| # | Phase | Key artifact | Gate | +|---|---|---|---| +| 1 | Scaffold core packages (empty shells) | `packages/core-shared/`, `core-cms/`, `core-api/`, `core-trpc/`, `core-ui/` with stubs | `pnpm install` + `pnpm typecheck` green | +| 2 | Populate `core-shared` | Fields, blocks, access helpers, hooks, tRPC init/context | `pnpm test --filter @repo/core-shared` passes | +| 3 | Populate `core-cms` stub **and repoint `apps/cms`** | `payload.config.ts` lifted from `cms-core` to `core-cms`; `collections: []`, `globals: []` initially; `apps/cms` updates its import from `@repo/cms-core` to `@repo/core-cms` | `pnpm dev --filter @repo/cms` boots admin UI; `pnpm generate:types` succeeds | +| 4 | Migrate `blog` feature end-to-end (first vertical — proves pattern) | Full canonical shape; Articles collection; per-feature DI container; tRPC router; UI | `pnpm typecheck && pnpm test --filter @repo/blog` green | +| 5 | Migrate remaining features: `auth`, `marketing-pages`, `navigation`, `media` | Each follows the blog template | Per-feature typecheck + tests + `core-cms` regenerates | +| 6 | Populate `core-trpc` + wire apps | Client, per-framework providers; route handlers in `web-next` + `web-tanstack`; example pages | `pnpm dev` serves pages; tRPC returns Payload data | +| 7 | Migrate `core-ui` | Move `packages/ui/` contents; relocate feature-shaped organisms into features; update Storybook imports | Storybook builds; `pnpm test` green | +| 8 | Delete old packages | `core/`, `api/`, `api-client/`, `cms-core/`, `cms-client/`, `ui/` | `pnpm install && pnpm typecheck && pnpm test` green | +| 9 | Boundary enforcement | Install `eslint-plugin-boundaries`; add package-level `turbo.json` tags; write lint rules | `pnpm lint` zero violations | +| 10 | Playwright setup | Configs, initial specs in both frontends; root `test:e2e` script | `pnpm test:e2e` green | +| 11 | Docs rewrite | Copy spec; rewrite overview, dependency-flow, guides; new ADRs; all AGENTS.md; delete stale plans | Human review | + +**Commit strategy:** one commit per phase. Phase 5 may be multiple commits (one per feature). Every commit builds + tests green. + +--- + +## 13. Out of scope (deferred) + +- Real Payload integration tests with a test database (stub with mock repos; write real integration tests later) +- Coverage reporting aggregation across packages (initial vitest setup per-package; aggregation is a follow-up) +- Multi-browser Playwright matrix (chromium only initially; add firefox/webkit later) +- Queue workers / CLI scripts (no `core-events` package yet; spec addendum v4's optional `core-events` stays deferred until a feature actually needs an event bus) +- Payload subscriptions / realtime (spec addendum v4); no feature requires it yet +- CMS app Next.js 15.5 + Payload 3.81 stabilization concerns (recently patched; monitor; no specific action in this refactor) + +--- + +## 14. Success criteria + +- `pnpm install && pnpm typecheck && pnpm lint && pnpm test && pnpm build` all green +- `pnpm test:e2e` green against running dev servers +- `pnpm dev --filter @repo/web-next` serves a home page with navigation + marketing content + a blog index, and `/blog/[slug]` shows an article — all fed by tRPC → feature controllers → use-cases → Payload Local API +- `pnpm dev --filter @repo/web-tanstack` renders the same blog post using the same feature packages +- Storybook builds showing `core-ui` primitives +- Any deep import (e.g., `import x from '@repo/blog/src/...'`) fails `pnpm lint` +- Any cross-feature import fails `pnpm lint` +- Root `AGENTS.md` and one-per-package AGENTS.md reflect the new architecture +- 9 new ADRs (5 existing maintained/appended/superseded + 4 new) +- Zero references to deleted packages anywhere in the codebase + +--- + +## 15. Post-approval next step + +Invoke the `superpowers:writing-plans` skill to produce a detailed, executable implementation plan based on the 11-phase sequencing in §12. Each phase becomes a plan section with concrete file-by-file steps, verification commands, and commit-message templates.