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>
1096 lines
42 KiB
Markdown
1096 lines
42 KiB
Markdown
# Vertical Refactor — Plan 6: Cleanup + Enforcement + E2E + Docs
|
|
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans.
|
|
|
|
**Goal:** Land the vertical-refactor branch in a fully clean, enforced, tested, and documented state. Delete the six legacy packages; migrate `apps/storybook` from `@repo/ui` to `@repo/core-ui`; install `eslint-plugin-boundaries` with rules matching the spec's three-tag model; set up Playwright in both apps with smoke specs; rewrite the doc tree (AGENTS.md, ADRs, guides). End state: PR-ready branch where every package, app, lint pass, and test suite tells the same coherent vertical-feature story.
|
|
|
|
**Architecture:** Five sequential phases. Each phase ends green so a reviewer can land Plan 6 in chunks if desired. Phase A removes legacy packages (drops their `apps/cms` and `apps/storybook` references first, then deletes). Phase B is `eslint-plugin-boundaries` configuration. Phase C is Playwright. Phase D is full doc rewrite. Phase E is final repo-wide green check.
|
|
|
|
**Tech Stack:** `eslint-plugin-boundaries@^4`, `@playwright/test@^1.50`, all existing tooling.
|
|
|
|
**Plan position:** Plan 6 of 6 — final.
|
|
- Plans 1-5 ✅ Foundation, Blog, Auth+Media, Marketing-pages+Navigation, App+UI integration
|
|
- **Plan 6 (this doc):** Cleanup + enforcement + e2e + docs
|
|
|
|
**Spec reference:** `docs/superpowers/specs/2026-04-21-vertical-monorepo-refactor-design.md`
|
|
|
|
---
|
|
|
|
## Decisions taken in this plan
|
|
|
|
- **Delete six legacy packages:** `packages/api`, `packages/api-client`, `packages/cms-client`, `packages/cms-core`, `packages/core`, `packages/ui`. Their callers (apps/cms, apps/storybook) get repointed first.
|
|
- **`eslint-plugin-boundaries` v4** chosen — the v5 series has breaking config-file changes; v4 is stable, ESLint-9 compatible, well-documented. Configured in `packages/eslint-config` so all packages inherit.
|
|
- **Playwright `chromium` only** in initial setup — Firefox/WebKit deferred. Each app gets its own `playwright.config.ts` and starts its own `webServer` (web-next on 3000, web-tanstack pointed at 3000 since it shares the backend in dev).
|
|
- **Storybook stays alive** but switches to `@repo/core-ui`. If `packages/ui/src/atoms/*` had stories in Plan 1's snapshot, they're already in `core-ui` after Plan 1's `core-ui` migration — but Plan 1 actually only scaffolded `core-ui` empty; the real `ui` content was never migrated. **This plan does that migration in Phase A** as a prerequisite for deleting `packages/ui`.
|
|
- **Doc rewrite scope:** rewrite root `AGENTS.md` + `CLAUDE.md`, all per-package `AGENTS.md`, `docs/architecture/{overview,dependency-flow}.md`, `docs/guides/{adding-a-feature,testing-strategy}.md`. Add 4 new ADRs and update/supersede 2 existing ones. Delete the six stale 2026-04-06 plan docs (they describe the old architecture).
|
|
- **No CLAUDE.md changes outside the worktree** — the user's main repo has its own .git/.claude state; we only touch the in-worktree CLAUDE.md (project-level instructions).
|
|
|
|
---
|
|
|
|
## File Structure
|
|
|
|
**Delete (Phase A):**
|
|
- `packages/api/` (entire directory)
|
|
- `packages/api-client/`
|
|
- `packages/cms-client/`
|
|
- `packages/cms-core/`
|
|
- `packages/core/`
|
|
- `packages/ui/`
|
|
- `docs/superpowers/plans/2026-04-06-plan-{1..6}-*.md` (6 stale plan files)
|
|
- All per-package `AGENTS.md` files inside the deleted packages (handled by directory deletion)
|
|
|
|
**Modify (Phase A — pre-delete repointing):**
|
|
- `packages/core-ui/src/` — populate with content migrated from `packages/ui/src/` (atoms, molecules, lib/utils + stories)
|
|
- `packages/core-ui/package.json` — add storybook-related devDeps if any are needed
|
|
- `apps/storybook/package.json` — swap `@repo/ui` → `@repo/core-ui`
|
|
- `apps/storybook/.storybook/main.ts` (if it exists) — update component path
|
|
- `apps/cms/package.json` — drop `@repo/cms-core` dep (no longer used)
|
|
|
|
**Create/modify (Phase B):**
|
|
- `packages/eslint-config/index.js` (or `index.mjs`) — add `eslint-plugin-boundaries` with rules
|
|
- `packages/eslint-config/package.json` — add `eslint-plugin-boundaries` dep
|
|
- Each package/app `eslint.config.js|mjs` — verify it inherits properly (most should already)
|
|
|
|
**Create (Phase C):**
|
|
- `apps/web-next/playwright.config.ts`
|
|
- `apps/web-next/e2e/home.spec.ts`
|
|
- `apps/web-next/e2e/blog-post.spec.ts`
|
|
- `apps/web-next/e2e/marketing-page.spec.ts`
|
|
- `apps/web-next/package.json` — add `@playwright/test` devDep + `test:e2e` script
|
|
- `apps/web-tanstack/playwright.config.ts`
|
|
- `apps/web-tanstack/e2e/home.spec.ts`
|
|
- `apps/web-tanstack/package.json` — add `@playwright/test` devDep + `test:e2e` script
|
|
- Root `package.json` — add `test:e2e` script that runs Turbo task
|
|
- Root `turbo.json` — add `test:e2e` task
|
|
|
|
**Create/rewrite (Phase D):**
|
|
- `docs/architecture/vertical-feature-spec.md` — copy of source spec
|
|
- `docs/architecture/overview.md` — full rewrite
|
|
- `docs/architecture/dependency-flow.md` — full rewrite
|
|
- `docs/guides/adding-a-feature.md` — full rewrite
|
|
- `docs/guides/testing-strategy.md` — full rewrite
|
|
- `docs/decisions/adr-002-di-framework.md` — append note about per-feature containers
|
|
- `docs/decisions/adr-003-cms-separation.md` — mark v1 superseded; write v2 (or replace inline)
|
|
- `docs/decisions/adr-004-dual-mode-client.md` — mark superseded
|
|
- `docs/decisions/adr-005-atomic-design.md` — append scope note (applies to core-ui only)
|
|
- `docs/decisions/adr-006-vertical-feature-packages.md` — NEW
|
|
- `docs/decisions/adr-007-drop-cms-client-wrapper.md` — NEW
|
|
- `docs/decisions/adr-008-per-feature-di-containers.md` — NEW
|
|
- `docs/decisions/adr-009-integrations-folder-naming.md` — NEW
|
|
- Root `AGENTS.md` — full rewrite
|
|
- Root `CLAUDE.md` — update Read First + add boundary note
|
|
- `apps/cms/AGENTS.md` — rewrite (replace cms-core references with core-cms)
|
|
- `apps/web-next/AGENTS.md` — NEW (or rewrite if exists)
|
|
- `apps/web-tanstack/AGENTS.md` — NEW
|
|
- `apps/storybook/AGENTS.md` — update (replace ui with core-ui)
|
|
- `packages/<core-or-feature>/AGENTS.md` — one per remaining package (12 total: 5 core-* + 5 features + 2 tooling)
|
|
|
|
**Delete:**
|
|
- `docs/superpowers/plans/2026-04-06-plan-{1,2,3,4,5,6}-*.md` (6 stale plans)
|
|
|
|
---
|
|
|
|
## Phase A: Cleanup — populate core-ui, repoint apps, delete legacy packages
|
|
|
|
### Task 6.1: Migrate `packages/ui/src/` content into `packages/core-ui/src/`
|
|
|
|
**Files:**
|
|
- Read: `packages/ui/src/**/*` (currently has atoms/, molecules/, templates/, lib/utils.ts and stories)
|
|
- Write: copy into `packages/core-ui/src/` matching the same structure
|
|
- Modify: `packages/core-ui/package.json` — add any devDeps needed (likely none — `@types/react`, `react`, `clsx`, `tailwind-merge` already present)
|
|
|
|
- [ ] **Step 1: List what's in packages/ui/src**
|
|
|
|
Run: `find packages/ui/src -type f -name "*.ts" -o -name "*.tsx" | head -30`
|
|
|
|
- [ ] **Step 2: Copy entire src tree from ui to core-ui**
|
|
|
|
```bash
|
|
cp -R packages/ui/src/. packages/core-ui/src/
|
|
```
|
|
|
|
This preserves the existing `packages/core-ui/src/index.ts` (will overwrite if there's a name collision; verify with the diff before committing). After copy, `packages/core-ui/src/` should contain `atoms/`, `molecules/`, `templates/` (if they exist in ui), `lib/utils.ts`, and the index.ts (overwritten with ui's barrel).
|
|
|
|
- [ ] **Step 3: Verify the new core-ui index.ts barrel exports something meaningful**
|
|
|
|
Run: `cat packages/core-ui/src/index.ts`
|
|
|
|
If it's still the empty `export {};`, replace with the equivalent of `packages/ui/src/index.ts` (whatever that exports). The intent: `import { Button } from '@repo/core-ui'` should work from the storybook app.
|
|
|
|
- [ ] **Step 4: Verify core-ui typechecks**
|
|
|
|
Run: `pnpm typecheck --filter @repo/core-ui`
|
|
Expected: PASS. If not, the issue is likely missing devDeps like Storybook types — for components, that's fine (those types only matter in `*.stories.tsx`). If stories cause errors, see Step 5.
|
|
|
|
- [ ] **Step 5: If `*.stories.tsx` cause typecheck errors due to missing `@storybook/react`**
|
|
|
|
Storybook's React types come from the storybook app's deps, not the ui library's. To exclude stories from core-ui's typecheck, modify `packages/core-ui/tsconfig.json`:
|
|
|
|
```json
|
|
{
|
|
"extends": "@repo/core-typescript/base.json",
|
|
"compilerOptions": {
|
|
"outDir": "dist",
|
|
"rootDir": ".",
|
|
"lib": ["ES2022", "DOM"],
|
|
"jsx": "preserve"
|
|
},
|
|
"include": ["src/**/*"],
|
|
"exclude": ["node_modules", "dist", "**/*.stories.tsx", "**/*.stories.ts"]
|
|
}
|
|
```
|
|
|
|
- [ ] **Step 6: Verify core-ui typechecks again**
|
|
|
|
Run: `pnpm typecheck --filter @repo/core-ui`
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 7: Commit**
|
|
|
|
```bash
|
|
git add packages/core-ui
|
|
git commit -m "feat(core-ui): migrate atoms/molecules/templates from packages/ui"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 6.2: Repoint `apps/storybook` from `@repo/ui` to `@repo/core-ui`
|
|
|
|
- [ ] **Step 1: Update `apps/storybook/package.json`**
|
|
|
|
Change `"@repo/ui": "workspace:*"` to `"@repo/core-ui": "workspace:*"`.
|
|
|
|
- [ ] **Step 2: Find and update import references in storybook config + stories**
|
|
|
|
```bash
|
|
grep -rln "@repo/ui" apps/storybook
|
|
```
|
|
|
|
For each file: replace `@repo/ui` with `@repo/core-ui`. Common files: `apps/storybook/.storybook/main.ts` (story path globs), `apps/storybook/preview.ts` if exists.
|
|
|
|
- [ ] **Step 3: Install + verify storybook can boot**
|
|
|
|
Run: `pnpm install`
|
|
Run: `pnpm typecheck --filter @repo/storybook`
|
|
Expected: PASS.
|
|
|
|
> Optional: try `pnpm dev --filter @repo/storybook` in background; verify port 6006 responds. Skip if storybook setup is fragile and would require additional config beyond the repoint.
|
|
|
|
- [ ] **Step 4: Commit**
|
|
|
|
```bash
|
|
git add apps/storybook pnpm-lock.yaml
|
|
git commit -m "build(storybook): migrate from @repo/ui to @repo/core-ui"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 6.3: Drop `@repo/cms-core` dep from `apps/cms/package.json`
|
|
|
|
- [ ] **Step 1: Edit `apps/cms/package.json`**
|
|
|
|
Remove the line `"@repo/cms-core": "workspace:*",` from `dependencies`. Keep `@repo/core-cms` (added in Plan 1).
|
|
|
|
- [ ] **Step 2: Install + verify**
|
|
|
|
Run: `pnpm install`
|
|
Run: `pnpm typecheck --filter @repo/cms`
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 3: Commit**
|
|
|
|
```bash
|
|
git add apps/cms/package.json pnpm-lock.yaml
|
|
git commit -m "build(cms): drop legacy @repo/cms-core dep (now uses @repo/core-cms only)"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 6.4: Delete the six legacy packages
|
|
|
|
- [ ] **Step 1: Verify no remaining references**
|
|
|
|
Run: `grep -rln "@repo/api\b\|@repo/api-client\|@repo/cms-client\|@repo/cms-core\|@repo/core\b\|@repo/ui\b" apps/ packages/ 2>/dev/null | grep -v "^node_modules" | grep -v "^.next" | grep -v "^.turbo" | grep -v "^dist"`
|
|
|
|
Expected: empty output (no remaining imports). If anything appears, fix it before deleting.
|
|
|
|
> Note: the regex `@repo/core\b` matches `@repo/core` but not `@repo/core-shared`. Same for `@repo/ui\b` vs `@repo/core-ui`. The `\b` word boundary is important.
|
|
|
|
- [ ] **Step 2: Delete the six packages**
|
|
|
|
```bash
|
|
rm -rf packages/api packages/api-client packages/cms-client packages/cms-core packages/core packages/ui
|
|
```
|
|
|
|
- [ ] **Step 3: Install — pnpm prunes the now-orphaned workspace entries**
|
|
|
|
Run: `pnpm install`
|
|
Expected: lockfile updates, no errors. Old packages no longer in workspace.
|
|
|
|
- [ ] **Step 4: Repo-wide typecheck + tests to confirm nothing broke**
|
|
|
|
Run: `pnpm typecheck`
|
|
Expected: PASS for all packages we kept (core-shared, core-cms, core-api, core-trpc, core-ui, auth, blog, marketing-pages, media, navigation, eslint-config, typescript-config, web-next, web-tanstack, cms, storybook). NO pre-existing failures should remain — those were in `@repo/api` and `@repo/ui` which we just deleted.
|
|
|
|
Run: `pnpm test`
|
|
Expected: PASS for the 96 feature tests.
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
```bash
|
|
git add -A
|
|
git commit -m "chore: delete legacy packages (api, api-client, cms-client, cms-core, core, ui)"
|
|
```
|
|
|
|
---
|
|
|
|
## Phase B: ESLint boundary enforcement
|
|
|
|
### Task 6.5: Install + configure `eslint-plugin-boundaries`
|
|
|
|
- [ ] **Step 1: Add `eslint-plugin-boundaries` to `packages/eslint-config/package.json` deps**
|
|
|
|
Read the current file, then add to `dependencies`:
|
|
|
|
```json
|
|
"eslint-plugin-boundaries": "^4.2.2"
|
|
```
|
|
|
|
(If `dependencies` doesn't exist, add the block.)
|
|
|
|
- [ ] **Step 2: Inspect current eslint config**
|
|
|
|
Run: `cat packages/eslint-config/index.{js,mjs,ts} 2>/dev/null`
|
|
|
|
There may be one or multiple files (e.g., a base flat config + framework variants). Identify the file that's used by all packages (typically named `index.js` or `base.js` and exports a flat config array).
|
|
|
|
- [ ] **Step 3: Add `eslint-plugin-boundaries` rules to the base config**
|
|
|
|
Append to the base config's exports (assuming it's a flat config array):
|
|
|
|
```javascript
|
|
import boundaries from "eslint-plugin-boundaries";
|
|
|
|
export default [
|
|
// ... existing config ...
|
|
{
|
|
plugins: { boundaries },
|
|
settings: {
|
|
"boundaries/elements": [
|
|
// Apps — top tier
|
|
{ type: "app", pattern: "apps/*" },
|
|
// Core foundation packages
|
|
{ type: "core", pattern: "packages/core-*" },
|
|
// Composition core packages — special: may import feature subpaths
|
|
{ type: "core-composition", pattern: "packages/core-api" },
|
|
{ type: "core-composition", pattern: "packages/core-cms" },
|
|
// Business feature packages
|
|
{ type: "feature", pattern: "packages/!(core-*|eslint-config|typescript-config)" },
|
|
// Tooling — untagged for boundary purposes
|
|
{ type: "tooling", pattern: "packages/eslint-config" },
|
|
{ type: "tooling", pattern: "packages/typescript-config" },
|
|
],
|
|
},
|
|
rules: {
|
|
"boundaries/element-types": [
|
|
2,
|
|
{
|
|
default: "disallow",
|
|
rules: [
|
|
{ from: "app", allow: ["app", "core", "core-composition", "feature", "tooling"] },
|
|
{ from: "feature", allow: ["core", "tooling"] },
|
|
{ from: "core", allow: ["core", "tooling"] },
|
|
{ from: "core-composition", allow: ["core", "feature", "tooling"] },
|
|
{ from: "tooling", allow: ["tooling"] },
|
|
],
|
|
},
|
|
],
|
|
"boundaries/no-private": [2, { allowUncles: false }],
|
|
"boundaries/external": [
|
|
2,
|
|
{
|
|
default: "allow",
|
|
rules: [],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
];
|
|
```
|
|
|
|
> Notes:
|
|
> - `core-composition` is the special tier for `core-api` + `core-cms` — they're allowed to import `feature/*` subpath exports. Plain `core` packages cannot.
|
|
> - The disallow-by-default policy means any package combination not explicitly allowed errors. `app → app` is permitted in case apps share helpers.
|
|
> - `boundaries/no-private` blocks deep-imports into another package's internal source paths (only public `exports` allowed).
|
|
|
|
- [ ] **Step 4: Install + run lint across all packages**
|
|
|
|
Run: `pnpm install` then `pnpm lint`
|
|
|
|
Expected: PASS. If violations appear, they're real — usually from forgotten cross-feature imports or deep imports. Fix them or report DONE_WITH_CONCERNS with a list.
|
|
|
|
> Common false positives: import path patterns the plugin doesn't understand. If genuine violations are zero but the plugin misclassifies, file an entry under `boundaries/element-types` ignore lists or refine the `pattern` regexes.
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
```bash
|
|
git add packages/eslint-config pnpm-lock.yaml
|
|
git commit -m "feat(eslint-config): add boundaries plugin enforcing app→feature→core graph"
|
|
```
|
|
|
|
---
|
|
|
|
## Phase C: Playwright
|
|
|
|
### Task 6.6: Install Playwright in `apps/web-next` + initial config
|
|
|
|
- [ ] **Step 1: Add to `apps/web-next/package.json`**
|
|
|
|
Add to `devDependencies`:
|
|
```json
|
|
"@playwright/test": "^1.50.0"
|
|
```
|
|
|
|
Add to `scripts`:
|
|
```json
|
|
"test:e2e": "playwright test",
|
|
"test:e2e:install": "playwright install --with-deps chromium"
|
|
```
|
|
|
|
- [ ] **Step 2: Install + Playwright browser binaries**
|
|
|
|
Run: `pnpm install`
|
|
Run: `cd apps/web-next && pnpm playwright install --with-deps chromium`
|
|
|
|
(The `--with-deps` may require sudo on Linux to install OS-level browser deps. On macOS it should run without elevation.)
|
|
|
|
- [ ] **Step 3: Create `apps/web-next/playwright.config.ts`**
|
|
|
|
```typescript
|
|
import { defineConfig, devices } from "@playwright/test";
|
|
|
|
export default defineConfig({
|
|
testDir: "./e2e",
|
|
fullyParallel: true,
|
|
forbidOnly: !!process.env.CI,
|
|
retries: process.env.CI ? 2 : 0,
|
|
workers: process.env.CI ? 1 : undefined,
|
|
reporter: "list",
|
|
use: {
|
|
baseURL: "http://localhost:3000",
|
|
trace: "on-first-retry",
|
|
},
|
|
projects: [
|
|
{
|
|
name: "chromium",
|
|
use: { ...devices["Desktop Chrome"] },
|
|
},
|
|
],
|
|
webServer: {
|
|
command: "pnpm dev",
|
|
url: "http://localhost:3000",
|
|
reuseExistingServer: !process.env.CI,
|
|
timeout: 60_000,
|
|
},
|
|
});
|
|
```
|
|
|
|
- [ ] **Step 4: Create three smoke specs**
|
|
|
|
`apps/web-next/e2e/home.spec.ts`:
|
|
```typescript
|
|
import { test, expect } from "@playwright/test";
|
|
|
|
test("home page renders site name + nav + article list", async ({ page }) => {
|
|
await page.goto("/");
|
|
await expect(page.locator("h1").first()).toBeVisible();
|
|
// Site name from siteSettings (mock seed: "My App")
|
|
await expect(page.locator("body")).toContainText(/My App/i);
|
|
// At least one nav item (mock seed: Home/Blog/About)
|
|
await expect(page.locator("nav a").first()).toBeVisible();
|
|
});
|
|
```
|
|
|
|
`apps/web-next/e2e/marketing-page.spec.ts`:
|
|
```typescript
|
|
import { test, expect } from "@playwright/test";
|
|
|
|
test("/about renders the about marketing page", async ({ page }) => {
|
|
await page.goto("/about");
|
|
// Either renders the seeded page (h1 = "About us") or "not yet published" message
|
|
// — both are HTTP 200, so the test only checks it doesn't 500.
|
|
const status = (await page.context().request.get("/about")).status();
|
|
expect(status).toBe(200);
|
|
await expect(page.locator("body")).toBeVisible();
|
|
});
|
|
```
|
|
|
|
`apps/web-next/e2e/blog-post.spec.ts`:
|
|
```typescript
|
|
import { test, expect } from "@playwright/test";
|
|
|
|
test("/blog/[slug] returns 404 for non-existent slug", async ({ page }) => {
|
|
const response = await page.goto("/blog/this-slug-does-not-exist", {
|
|
waitUntil: "domcontentloaded",
|
|
});
|
|
expect(response?.status()).toBe(404);
|
|
});
|
|
|
|
test("/blog/[slug] for a real slug renders the article", async ({ page }) => {
|
|
// The mock blog repository is empty by default — so this test currently
|
|
// expects 404. When seeded data exists in Payload, replace 404 with 200
|
|
// and check for article.title in the page body.
|
|
test.skip(
|
|
true,
|
|
"Pending: seed a published article in Payload before enabling this test",
|
|
);
|
|
await page.goto("/blog/example-slug");
|
|
await expect(page.locator("h1").first()).toBeVisible();
|
|
});
|
|
```
|
|
|
|
- [ ] **Step 5: Run e2e**
|
|
|
|
Run: `cd apps/web-next && pnpm test:e2e`
|
|
Expected: PASS — 3 tests run, 1 skipped, all green. The webServer config auto-starts `pnpm dev` so you don't need a separate dev server.
|
|
|
|
> Note: Postgres must be running. If not: `docker compose up -d postgres` from repo root first.
|
|
|
|
- [ ] **Step 6: Commit**
|
|
|
|
```bash
|
|
git add apps/web-next pnpm-lock.yaml
|
|
git commit -m "test(web-next): add Playwright config + smoke specs (home, about, blog 404)"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 6.7: Install Playwright in `apps/web-tanstack` + initial config
|
|
|
|
> Note: `apps/web-tanstack` doesn't have a real dev server yet (its `dev` script is just `echo 'placeholder'`). For Playwright we need to either (a) skip web-tanstack e2e until TanStack Start runtime is wired, or (b) add a basic Vite dev server. Option (a) keeps Plan 6 focused. The e2e config is added but only one test runs against the shared web-next backend at port 3000 — proving the cross-framework consumption works at the data layer even without a real TanStack runtime.
|
|
|
|
- [ ] **Step 1: Add to `apps/web-tanstack/package.json`**
|
|
|
|
Add to `devDependencies`:
|
|
```json
|
|
"@playwright/test": "^1.50.0"
|
|
```
|
|
|
|
Add to `scripts`:
|
|
```json
|
|
"test:e2e": "playwright test"
|
|
```
|
|
|
|
- [ ] **Step 2: Install**
|
|
|
|
Run: `pnpm install`
|
|
(Browsers installed already by web-next task 6.6 step 2 — they're shared system-wide.)
|
|
|
|
- [ ] **Step 3: Create `apps/web-tanstack/playwright.config.ts`**
|
|
|
|
```typescript
|
|
import { defineConfig, devices } from "@playwright/test";
|
|
|
|
export default defineConfig({
|
|
testDir: "./e2e",
|
|
fullyParallel: true,
|
|
retries: process.env.CI ? 2 : 0,
|
|
reporter: "list",
|
|
use: {
|
|
baseURL: "http://localhost:3000",
|
|
trace: "on-first-retry",
|
|
},
|
|
projects: [
|
|
{
|
|
name: "chromium",
|
|
use: { ...devices["Desktop Chrome"] },
|
|
},
|
|
],
|
|
// No webServer: web-tanstack tests run against the shared web-next backend
|
|
// (port 3000). When TanStack Start runtime is wired in a future plan, add
|
|
// a webServer block here pointing at port 3002.
|
|
});
|
|
```
|
|
|
|
- [ ] **Step 4: Create `apps/web-tanstack/e2e/home.spec.ts`**
|
|
|
|
```typescript
|
|
import { test, expect } from "@playwright/test";
|
|
|
|
test.skip(
|
|
"TanStack home renders site name + nav (pending TanStack Start runtime)",
|
|
async ({ page }) => {
|
|
// Pending: web-tanstack has no dev server yet. When the TanStack Start
|
|
// runtime is wired (future plan), update the playwright.config.ts
|
|
// webServer to start it on port 3002 and remove this skip.
|
|
await page.goto("http://localhost:3002");
|
|
await expect(page.locator("h1").first()).toBeVisible();
|
|
},
|
|
);
|
|
```
|
|
|
|
- [ ] **Step 5: Verify config + skipped test pass**
|
|
|
|
Run: `cd apps/web-tanstack && pnpm test:e2e`
|
|
Expected: 1 test, skipped. Exit 0.
|
|
|
|
- [ ] **Step 6: Commit**
|
|
|
|
```bash
|
|
git add apps/web-tanstack pnpm-lock.yaml
|
|
git commit -m "test(web-tanstack): add Playwright scaffold + skipped home spec"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 6.8: Root `test:e2e` script + Turbo task
|
|
|
|
- [ ] **Step 1: Add `test:e2e` script to root `package.json`**
|
|
|
|
```json
|
|
"test:e2e": "turbo run test:e2e"
|
|
```
|
|
|
|
(Add inside the `scripts` block.)
|
|
|
|
- [ ] **Step 2: Add `test:e2e` task to root `turbo.json`**
|
|
|
|
```json
|
|
"test:e2e": {
|
|
"dependsOn": ["^build"],
|
|
"cache": false
|
|
}
|
|
```
|
|
|
|
(Add inside the `tasks` object alongside `test`, `lint`, etc.)
|
|
|
|
- [ ] **Step 3: Run from root**
|
|
|
|
Run: `pnpm test:e2e`
|
|
Expected: web-next runs 3 tests (1 skipped); web-tanstack runs 1 test (skipped). All green.
|
|
|
|
- [ ] **Step 4: Commit**
|
|
|
|
```bash
|
|
git add package.json turbo.json
|
|
git commit -m "build: add root test:e2e task aggregating per-app Playwright suites"
|
|
```
|
|
|
|
---
|
|
|
|
## Phase D: Docs rewrite
|
|
|
|
### Task 6.9: Copy spec into `docs/architecture/vertical-feature-spec.md`
|
|
|
|
- [ ] **Step 1: Find the source spec**
|
|
|
|
The spec was provided as `monorepo-architecture-spec-detailed-v5.md` from `/Users/danijel/Downloads/`. The user's worktree may not have a copy. The `docs/superpowers/specs/2026-04-21-vertical-monorepo-refactor-design.md` references it. For Plan 6, copy the design spec (not the source) into `docs/architecture/` since it's our authoritative interpretation.
|
|
|
|
```bash
|
|
cp docs/superpowers/specs/2026-04-21-vertical-monorepo-refactor-design.md docs/architecture/vertical-feature-spec.md
|
|
```
|
|
|
|
> Note: If the source spec from Downloads is required verbatim, that's a separate user-supplied step — for now we use our own design spec which already encodes our decisions.
|
|
|
|
- [ ] **Step 2: Add a brief preamble to the copied file**
|
|
|
|
Edit the top of `docs/architecture/vertical-feature-spec.md`:
|
|
|
|
```markdown
|
|
# Vertical Feature Architecture Spec
|
|
|
|
> **Source of truth.** Copied from `docs/superpowers/specs/2026-04-21-vertical-monorepo-refactor-design.md` for in-tree reference. Edits here should be backported to the design spec.
|
|
|
|
```
|
|
|
|
(Insert above the existing `# Vertical Feature Monorepo Refactor — Design Spec` heading; or replace the heading, your choice.)
|
|
|
|
- [ ] **Step 3: Commit**
|
|
|
|
```bash
|
|
git add docs/architecture/vertical-feature-spec.md
|
|
git commit -m "docs(architecture): copy refactor design spec into in-tree reference"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 6.10: Rewrite `docs/architecture/overview.md`
|
|
|
|
Replace the entire file with:
|
|
|
|
```markdown
|
|
# Architecture Overview
|
|
|
|
A vertical-feature monorepo. Business capabilities are top-level packages; non-business foundations are `core-*`.
|
|
|
|
## Package map
|
|
|
|
```
|
|
packages/
|
|
# Foundation (no business logic)
|
|
core-shared/ Generic primitives — Payload field/block helpers, tRPC init/context, lib utilities
|
|
core-cms/ Composition only: assembles feature CMS exports into one Payload config
|
|
core-api/ Composition only: aggregates feature tRPC routers into one appRouter
|
|
core-trpc/ Frontend tRPC client + per-framework providers (Next.js, TanStack)
|
|
core-ui/ Design-system primitives (atoms, molecules, generic organisms, templates)
|
|
|
|
# Business capabilities
|
|
auth/ Users + sign-in/sign-up/sign-out + session/cookie domain
|
|
blog/ Articles collection + publishing flow
|
|
media/ Media upload collection (skeleton; expand with optimization, CDN, etc.)
|
|
marketing-pages/ Pages collection + SiteSettings global
|
|
navigation/ Header global + menu items
|
|
|
|
# Tooling
|
|
eslint-config/ Shared ESLint flat config + boundary rules
|
|
typescript-config/ Shared tsconfig + vitest base
|
|
```
|
|
|
|
## Data flow
|
|
|
|
```
|
|
React component
|
|
↓ useQuery(trpc.blog.articleBySlug.queryOptions(...)) ← ui/query.ts (typed tRPC client)
|
|
HTTP /api/trpc
|
|
↓
|
|
tRPC procedure ← integrations/api/router.ts
|
|
↓ .input(zod).query(...)
|
|
Controller (Zod safeParse) ← interface-adapters/controllers/
|
|
↓
|
|
Use case ← application/use-cases/
|
|
↓ container.get(SYMBOL)
|
|
Repository implementation ← infrastructure/repositories/ (@injectable)
|
|
↓ getPayload({ config })
|
|
Payload Local API → Postgres
|
|
```
|
|
|
|
## Three enforcement layers
|
|
|
|
1. **`package.json` deps** — only declare allowed deps
|
|
2. **`exports` map** — each package exposes a small public surface (`.`, `./cms`, `./api`, `./di/bind-production`)
|
|
3. **ESLint `eslint-plugin-boundaries`** — three tags (`app`, `feature`, `core`); two composition exceptions (`core-api` may import `@repo/<feature>/api`; `core-cms` may import `@repo/<feature>/cms`)
|
|
|
|
## Per-feature DI containers
|
|
|
|
Each feature owns its own InversifyJS `Container` + symbol table. No shared symbols, no cross-feature DI coupling. Tests rebind per feature without touching others. Apps call `bindProduction*(config)` per feature at boot to swap the default mock implementations for Payload-backed ones.
|
|
|
|
## Spec reference
|
|
|
|
`docs/architecture/vertical-feature-spec.md` is the canonical design.
|
|
```
|
|
|
|
- [ ] **Step 1: Replace the file**
|
|
|
|
(Use the Write tool with the content above.)
|
|
|
|
- [ ] **Step 2: Commit**
|
|
|
|
```bash
|
|
git add docs/architecture/overview.md
|
|
git commit -m "docs(architecture): rewrite overview for vertical feature architecture"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 6.11: Rewrite `docs/architecture/dependency-flow.md`
|
|
|
|
Replace with:
|
|
|
|
```markdown
|
|
# Dependency Flow
|
|
|
|
```
|
|
+-------------+ +-----------------+ +-----------+
|
|
| apps/web- | | apps/web- | | apps/cms |
|
|
| next | | tanstack | | |
|
|
+------+------+ +--------+--------+ +-----+-----+
|
|
| | |
|
|
+------------------+--------------+ | |
|
|
| | | | |
|
|
+----v-----+ +-----v------+ +-----v----v---+ +-------v------+
|
|
| core-api | | core-trpc | | feature | | core-cms |
|
|
| | | | | packages | | |
|
|
+-----+----+ +-----+------+ +------+-------+ +-------+------+
|
|
| | | |
|
|
| | | |
|
|
+--+-------+------+---------------+----+ +-------------+
|
|
| | | |
|
|
+----v---+ +-v---------+ +-------v---v---+
|
|
| core- | | core-ui | | core-shared |
|
|
| shared | | | | |
|
|
+--------+ +-----------+ +----------------+
|
|
|
|
Boundary rules (enforced by eslint-plugin-boundaries):
|
|
app → app, core, feature, core-composition (any)
|
|
feature → core (any), but NOT other features, NOT app
|
|
core → core, but NOT feature, NOT app
|
|
core-composition → core, feature subpath exports only (`/cms`, `/api`)
|
|
core-api → @repo/<feature>/api
|
|
core-cms → @repo/<feature>/cms
|
|
```
|
|
|
|
## Concrete examples
|
|
|
|
Allowed:
|
|
```ts
|
|
// in apps/web-next
|
|
import { appRouter } from "@repo/core-api";
|
|
import { NextTrpcProvider } from "@repo/core-trpc/next";
|
|
import { bindProductionBlog } from "@repo/blog/di/bind-production";
|
|
|
|
// in packages/blog
|
|
import { slugifyIfMissing } from "@repo/core-shared/payload";
|
|
|
|
// in packages/core-api
|
|
import { blogRouter } from "@repo/blog/api"; // composition exception
|
|
import { router } from "@repo/core-shared/trpc/init"; // core → core fine
|
|
|
|
// in packages/core-cms
|
|
import { articles } from "@repo/blog/cms"; // composition exception
|
|
```
|
|
|
|
Disallowed:
|
|
```ts
|
|
// in packages/blog (cross-feature)
|
|
import { Article } from "@repo/marketing-pages"; // ❌ feature → feature
|
|
|
|
// in packages/blog (deep import past public exports)
|
|
import { articles } from "@repo/blog/src/integrations/cms/collections/articles"; // ❌ no-private
|
|
|
|
// in packages/core-shared
|
|
import { blogRouter } from "@repo/blog/api"; // ❌ core → feature
|
|
|
|
// in packages/core-trpc
|
|
import { someBlogThing } from "@repo/blog"; // ❌ core → feature (only core-api/core-cms have exception)
|
|
```
|
|
|
|
## Three-layer enforcement
|
|
|
|
ESLint catches accidental cross-package imports at lint time. The `package.json` `exports` map blocks deep imports at module-resolution time. Workspace `dependencies` declarations make the package graph itself the source of truth — if you didn't declare it, you can't import it.
|
|
```
|
|
|
|
- [ ] **Step 1: Replace the file**
|
|
|
|
- [ ] **Step 2: Commit**
|
|
|
|
```bash
|
|
git add docs/architecture/dependency-flow.md
|
|
git commit -m "docs(architecture): rewrite dependency-flow for vertical features + boundary rules"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 6.12: Rewrite `docs/guides/adding-a-feature.md`
|
|
|
|
Replace with a new walkthrough. The guide should cover:
|
|
1. Decide if the work is a new feature or extends an existing one
|
|
2. Scaffold a minimal feature (smallest viable shape — see addendum v5)
|
|
3. Add layers as needed (entities → application → infrastructure → di → integrations/cms → integrations/api → ui)
|
|
4. Add `bindProduction*(config)` if it has a payload-backed repo
|
|
5. Wire `/cms` into `core-cms`, `/api` into `core-api`
|
|
6. Add path aliases to `tsconfig.base.json`
|
|
7. Run `pnpm install`, typecheck, test
|
|
|
|
A second walkthrough should cover modifying an existing feature (e.g., adding a procedure to blog).
|
|
|
|
Use the existing `auth`, `blog`, `marketing-pages`, `navigation` packages as living examples. Keep the guide concise (~300-400 lines) and link to the spec for theoretical depth.
|
|
|
|
- [ ] **Step 1: Write the new guide**
|
|
|
|
Use the Write tool. Format the guide with clear `###` step headings, code blocks per file, and a "Done criteria" section at the end.
|
|
|
|
> The exact prose is at the implementer's discretion (within the structure above) — Plan 6 doesn't ship the verbatim guide text. The implementer should look at the existing `packages/blog/src/` for ground truth on what a feature looks like.
|
|
|
|
- [ ] **Step 2: Commit**
|
|
|
|
```bash
|
|
git add docs/guides/adding-a-feature.md
|
|
git commit -m "docs(guides): rewrite adding-a-feature for vertical canonical pattern"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 6.13: Rewrite `docs/guides/testing-strategy.md`
|
|
|
|
Replace with a guide covering:
|
|
1. **Test placement table** (colocated `*.test.ts` next to source; feature-level `tests/*.feature.test.ts`; e2e `apps/<app>/e2e/*.spec.ts`)
|
|
2. **Per-feature DI in tests** — show the `beforeEach` pattern of unbinding + rebinding the feature's container
|
|
3. **Vitest setup** — each package has its own `vitest.config.ts` with `resolve.alias` for `@/`
|
|
4. **Playwright setup** — apps have `playwright.config.ts` with `webServer` block; smoke specs initially
|
|
5. **Mocking strategy for Payload** — feature-test level uses Mock repos via DI rebind; the `payload` module can be mocked at vitest level for infrastructure tests (see `payload-articles.repository.test.ts`)
|
|
|
|
Keep concise (~150-200 lines).
|
|
|
|
- [ ] **Step 1: Write**
|
|
- [ ] **Step 2: Commit**
|
|
|
|
```bash
|
|
git add docs/guides/testing-strategy.md
|
|
git commit -m "docs(guides): rewrite testing-strategy for vertical features (per-feature DI, colocated tests, Playwright)"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 6.14: Update existing ADRs (002, 003, 005), supersede ADR-004
|
|
|
|
For each:
|
|
|
|
- [ ] **Step 1: ADR-002 (DI framework)** — append a section:
|
|
|
|
```markdown
|
|
|
|
## Update (2026-05-04)
|
|
|
|
The vertical-feature refactor preserved InversifyJS but moved from a single shared container in `packages/core/src/di/` to **per-feature containers** in each feature package (`packages/<feature>/src/di/container.ts`). See ADR-008.
|
|
```
|
|
|
|
- [ ] **Step 2: ADR-003 (CMS separation)** — mark v1 superseded, add v2 inline:
|
|
|
|
```markdown
|
|
|
|
## Status: Partially superseded by v2 (2026-05-04)
|
|
|
|
v1 advocated `@repo/cms-core` as a single CMS package. v2 splits this into:
|
|
- `@repo/core-cms` — composition only (assembles feature CMS schemas)
|
|
- Each feature owns its own collections/globals under `packages/<feature>/src/integrations/cms/`
|
|
|
|
Rationale: vertical-feature ownership scales better; CMS schema lives with the business code that needs it. See ADR-006.
|
|
```
|
|
|
|
- [ ] **Step 3: ADR-004 (dual-mode client)** — supersede entirely:
|
|
|
|
```markdown
|
|
|
|
## Status: Superseded by ADR-007 (2026-05-04)
|
|
|
|
The dual-mode client wrapper was deleted. Feature payload-backed repositories now call `getPayload({ config })` directly with the assembled config injected via constructor. See ADR-007 for rationale.
|
|
```
|
|
|
|
- [ ] **Step 4: ADR-005 (atomic design)** — append scope note:
|
|
|
|
```markdown
|
|
|
|
## Update (2026-05-04)
|
|
|
|
Atomic Design now applies to `@repo/core-ui/` only — generic primitives (atoms, molecules, generic organisms, templates). Feature-specific components (e.g., `ArticleCard`, `HeaderNavMenu`) live in the owning feature's `ui/` folder per the vertical-feature architecture. See ADR-006.
|
|
```
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
```bash
|
|
git add docs/decisions/adr-002 docs/decisions/adr-003 docs/decisions/adr-004 docs/decisions/adr-005
|
|
git commit -m "docs(adr): update 002/003/005 with vertical-refactor notes; supersede 004"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 6.15: Add four new ADRs (006-009)
|
|
|
|
Create each as a short ADR (~50-100 lines):
|
|
|
|
- [ ] **Step 1: `docs/decisions/adr-006-vertical-feature-packages.md`**
|
|
|
|
Title: "Vertical feature packages over horizontal layers"
|
|
Context: original Clean Architecture used one `packages/core` for all domains
|
|
Decision: split by business capability; each feature owns the full vertical slice (entities → ui)
|
|
Consequences: features evolve independently; cross-feature coupling explicit at the package-graph level; per-feature DI containers
|
|
|
|
- [ ] **Step 2: `docs/decisions/adr-007-drop-cms-client-wrapper.md`**
|
|
|
|
Title: "Drop the dual-mode CMS client wrapper"
|
|
Context: ADR-004 introduced `@repo/cms-client` with local + HTTP modes; never used in production
|
|
Decision: delete the wrapper; payload-backed repositories use `getPayload({ config })` directly with config passed via constructor
|
|
Consequences: one fewer abstraction; package graph stays acyclic (feature ↛ core-cms)
|
|
|
|
- [ ] **Step 3: `docs/decisions/adr-008-per-feature-di-containers.md`**
|
|
|
|
Title: "Per-feature InversifyJS containers"
|
|
Context: original architecture had one shared container in `packages/core/src/di/`
|
|
Decision: each feature owns its own `Container` + symbol table; tests rebind per-feature without coordination
|
|
Consequences: zero cross-feature DI coupling; symbol collisions impossible; cross-feature shared services need explicit per-container binding (rare in practice)
|
|
|
|
- [ ] **Step 4: `docs/decisions/adr-009-integrations-folder-naming.md`**
|
|
|
|
Title: "Rename spec's `adapters/` to `integrations/`"
|
|
Context: source spec used `adapters/cms` + `adapters/api`; conflicts with Clean Architecture's `interface-adapters/` folder we kept
|
|
Decision: rename to `integrations/cms` + `integrations/api` to avoid the collision
|
|
Consequences: source-spec deviation bounded to one naming choice; semantically equivalent (both describe role-based plug points); leaves `adapters` unambiguously meaning Clean Architecture's interface-adapter layer
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
```bash
|
|
git add docs/decisions/adr-006 docs/decisions/adr-007 docs/decisions/adr-008 docs/decisions/adr-009
|
|
git commit -m "docs(adr): add ADRs 006-009 for vertical refactor (vertical packages, drop wrapper, per-feature DI, integrations naming)"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 6.16: Delete stale 2026-04-06 plan files
|
|
|
|
- [ ] **Step 1: Delete the six files**
|
|
|
|
```bash
|
|
rm docs/superpowers/plans/2026-04-06-plan-1-monorepo-foundation.md
|
|
rm docs/superpowers/plans/2026-04-06-plan-2-core-package.md
|
|
rm docs/superpowers/plans/2026-04-06-plan-3-payload-cms.md
|
|
rm docs/superpowers/plans/2026-04-06-plan-4-api-layer-app-shells.md
|
|
rm docs/superpowers/plans/2026-04-06-plan-5-ui-system.md
|
|
rm docs/superpowers/plans/2026-04-06-plan-6-documentation.md
|
|
```
|
|
|
|
- [ ] **Step 2: Optionally delete the stale superseded design spec**
|
|
|
|
```bash
|
|
rm docs/superpowers/specs/2026-04-06-clean-architecture-monorepo-template-design.md
|
|
```
|
|
|
|
(Optional — keeping the old spec is acceptable as historical record. Default to deleting since the new spec supersedes it and the directory is for active specs.)
|
|
|
|
- [ ] **Step 3: Commit**
|
|
|
|
```bash
|
|
git add -A
|
|
git commit -m "docs(plans): delete six stale 2026-04-06 plan docs (superseded by 2026-05-04-plan-{1..6})"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 6.17: Rewrite root `AGENTS.md`
|
|
|
|
Full rewrite. Cover:
|
|
1. **What this repo is** — vertical-feature Turborepo + pnpm monorepo template
|
|
2. **Package map** — same as overview but in table form
|
|
3. **Boundary rules** — three tags, two composition exceptions, three enforcement layers
|
|
4. **Adding a feature** — link to `docs/guides/adding-a-feature.md`
|
|
5. **Key commands** — pnpm install, dev, typecheck, lint, test, test:e2e
|
|
6. **Per-package conventions** — relative imports in src, `@/` in tests, vitest config has resolve.alias, payload repos take SanitizedConfig via constructor
|
|
|
|
Aim for ~150 lines. Reference the spec + guides for depth.
|
|
|
|
- [ ] **Step 1: Replace `AGENTS.md` at repo root**
|
|
- [ ] **Step 2: Commit**
|
|
|
|
```bash
|
|
git add AGENTS.md
|
|
git commit -m "docs(agents): rewrite root AGENTS.md for vertical feature architecture"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 6.18: Update root `CLAUDE.md`
|
|
|
|
Surgical edit, don't full rewrite — preserve user's existing setup notes and ports table. Update:
|
|
- **Read First section** to point at the new `docs/architecture/overview.md`, `docs/architecture/vertical-feature-spec.md`, `docs/guides/adding-a-feature.md`
|
|
- **Add a "Key conventions" section** with the bullet points from Plans 2-5 lessons (relative imports in src, no rootDir warning fixed, vitest alias, payload repos via constructor, `bindProduction*` for app boot)
|
|
- **MCP Servers section** — Storybook still on :6006 — keep as-is
|
|
|
|
- [ ] **Step 1: Edit `CLAUDE.md`**
|
|
- [ ] **Step 2: Commit**
|
|
|
|
```bash
|
|
git add CLAUDE.md
|
|
git commit -m "docs(claude): update Read First + add vertical feature conventions"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 6.19: Per-package + per-app `AGENTS.md`
|
|
|
|
12 packages + 4 apps = 16 AGENTS.md files. Most can be 30-60 lines each. Cover for each package:
|
|
- **Purpose** (one paragraph)
|
|
- **What it owns** (bulleted)
|
|
- **What it must NOT import** (boundary rules)
|
|
- **Public exports** (the entries from `package.json` `exports`)
|
|
- **Test conventions** (where tests live, how to run them)
|
|
|
|
For apps:
|
|
- **What it does** + how to run dev
|
|
- **What it imports** (top-level deps)
|
|
- **Pages/routes** (if applicable)
|
|
- **e2e** location + how to run
|
|
|
|
Group commits by area to keep history readable:
|
|
|
|
- [ ] **Step 1: Write AGENTS.md for all 5 core packages** (core-shared, core-cms, core-api, core-trpc, core-ui). Commit:
|
|
|
|
```bash
|
|
git add packages/core-*/AGENTS.md
|
|
git commit -m "docs(agents): add per-package AGENTS.md for all core-* packages"
|
|
```
|
|
|
|
- [ ] **Step 2: Write AGENTS.md for all 5 feature packages** (auth, blog, media, marketing-pages, navigation). Commit:
|
|
|
|
```bash
|
|
git add packages/{auth,blog,media,marketing-pages,navigation}/AGENTS.md
|
|
git commit -m "docs(agents): add per-package AGENTS.md for all feature packages"
|
|
```
|
|
|
|
- [ ] **Step 3: Write AGENTS.md for tooling** (eslint-config, typescript-config — short, mostly "shared config"). Commit:
|
|
|
|
```bash
|
|
git add packages/{eslint-config,typescript-config}/AGENTS.md
|
|
git commit -m "docs(agents): add per-package AGENTS.md for eslint-config + typescript-config"
|
|
```
|
|
|
|
- [ ] **Step 4: Write/rewrite app AGENTS.md** (cms, web-next, web-tanstack, storybook). Commit:
|
|
|
|
```bash
|
|
git add apps/*/AGENTS.md
|
|
git commit -m "docs(agents): write per-app AGENTS.md for cms, web-next, web-tanstack, storybook"
|
|
```
|
|
|
|
> Note: `apps/cms/AGENTS.md` already exists and references the deleted `@repo/cms-core`. The rewrite replaces all `@repo/cms-core` mentions with `@repo/core-cms` and removes the "thin shell" text since core-cms now actually composes feature schemas.
|
|
|
|
---
|
|
|
|
## Phase E: Final repo-wide green check
|
|
|
|
### Task 6.20: All-green verification
|
|
|
|
- [ ] **Step 1: Run all checks in sequence**
|
|
|
|
```bash
|
|
pnpm install
|
|
pnpm typecheck
|
|
pnpm lint
|
|
pnpm test
|
|
pnpm test:e2e
|
|
```
|
|
|
|
Each must exit 0.
|
|
|
|
- [ ] **Step 2: Verify package counts**
|
|
|
|
Run: `ls packages/ | wc -l`
|
|
Expected: 12 (5 core + 5 feature + 2 tooling).
|
|
|
|
Run: `ls apps/ | wc -l`
|
|
Expected: 4 (cms, storybook, web-next, web-tanstack).
|
|
|
|
- [ ] **Step 3: Verify no remaining references to deleted packages**
|
|
|
|
Run: `grep -rln "@repo/api\b\|@repo/api-client\|@repo/cms-client\|@repo/cms-core\|@repo/core\b\|@repo/ui\b" apps/ packages/ docs/ 2>/dev/null | grep -v node_modules | grep -v ".turbo" | grep -v ".next" | grep -v "dist/"`
|
|
|
|
Expected: empty (any matches in `docs/` are likely in superseded ADRs and acceptable — verify each).
|
|
|
|
- [ ] **Step 4: No commit needed if everything is green** (any small fixes uncovered should be committed individually with descriptive messages)
|
|
|
|
---
|
|
|
|
## Plan 6 Done Criteria
|
|
|
|
- [ ] 6 legacy packages deleted (api, api-client, cms-client, cms-core, core, ui); workspace has exactly 12 packages
|
|
- [ ] `apps/cms` no longer depends on `@repo/cms-core`
|
|
- [ ] `apps/storybook` migrated to `@repo/core-ui` and typechecks
|
|
- [ ] `eslint-plugin-boundaries` configured and `pnpm lint` passes with zero violations
|
|
- [ ] Playwright installed in both apps; `pnpm test:e2e` from repo root passes (3 specs run + 2 skipped)
|
|
- [ ] Root + per-package + per-app AGENTS.md all rewritten for vertical features
|
|
- [ ] 4 new ADRs added (006-009); 4 existing ADRs updated/superseded
|
|
- [ ] `docs/architecture/{overview,dependency-flow,vertical-feature-spec}.md` rewritten/copied
|
|
- [ ] `docs/guides/{adding-a-feature,testing-strategy}.md` rewritten
|
|
- [ ] 6 stale 2026-04-06 plan docs deleted
|
|
- [ ] `pnpm install && pnpm typecheck && pnpm lint && pnpm test && pnpm test:e2e` all green
|
|
- [ ] Branch `refactor/vertical-features` ready to merge
|
|
|
|
**After this plan:** the branch is ready for PR + merge. Apps render features in browsers. Tests cover unit/feature/integration/e2e levels. Architecture is enforced by ESLint, package exports, and the workspace dependency graph. Documentation matches reality.
|