# Web FSD Migration Implementation Plan

> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Migrate `apps/web` from flat directory structure to Feature-Sliced Design (FSD) inside `src/`, preserving all existing behaviour and routes.

**Architecture:** Bottom-up incremental migration: infrastructure → shared → entities → features → widgets → views. App Router moves into `src/app/`. Each task ends with a working `pnpm build`. All work committed to the current branch; PR to main by the developer.

**Tech Stack:** Next.js 15 (App Router), React 19, TypeScript 5.6, next-intl 4.8.3, Tailwind CSS 3.4, pnpm workspaces / Turbo.

**Spec:** `docs/superpowers/specs/2026-03-16-web-fsd-migration-design.md`

---

## Chunk 1: Infrastructure + Shared

### Task 1: Infrastructure — move to `src/`, update aliases

**Files:**
- Move: `apps/web/app/` → `apps/web/src/app/`
- Move: `apps/web/components/` → `apps/web/src/components/` *(temporary)*
- Move: `apps/web/lib/` → `apps/web/src/lib/` *(temporary)*
- Modify: `apps/web/tsconfig.json`
- Modify: `apps/web/package.json`

> **Context:** Next.js 15 auto-detects `src/app/` — no `next.config.ts` change needed for routing. Changing `@/*` from `"./*"` to `"./src/*"` requires ALL existing `@/` imports to remain valid, so `lib/` and `components/` must also move to `src/` in this same step.

- [ ] **Step 1: Move directories into `src/`**

  Run from `apps/web/`:
  ```bash
  mkdir -p src
  mv app src/app
  mv components src/components
  mv lib src/lib
  ```

- [ ] **Step 2: Update `tsconfig.json` paths**

  Replace the `"paths"` section in `apps/web/tsconfig.json`:
  ```json
  {
    "extends": "@repo/typescript-config/nextjs.json",
    "compilerOptions": {
      "plugins": [{ "name": "next" }],
      "paths": {
        "@/shared/*": ["./src/shared/*"],
        "@/entities/*": ["./src/entities/*"],
        "@/features/*": ["./src/features/*"],
        "@/widgets/*": ["./src/widgets/*"],
        "@/views/*": ["./src/views/*"],
        "@/*": ["./src/*"]
      },
      "allowJs": true,
      "incremental": true
    },
    "include": [
      "next-env.d.ts",
      "**/*.ts",
      "**/*.tsx",
      ".next/types/**/*.ts"
    ],
    "exclude": ["node_modules"]
  }
  ```

  After this change:
  - `@/components/X` → resolves to `src/components/X` ✓
  - `@/lib/X` → resolves to `src/lib/X` ✓
  - No existing imports break.

- [ ] **Step 3: Fix `globals.css` import in layout**

  `src/app/layout.tsx` has `import './globals.css'`. After the move this still works (relative import). No change needed — verify the file exists at `src/app/globals.css`.

- [ ] **Step 4: Update lint scripts in `package.json`**

  In `apps/web/package.json`, change:
  ```json
  "lint": "eslint \"app/**/*.{ts,tsx}\" \"lib/**/*.ts\"",
  "lint:fix": "eslint \"app/**/*.{ts,tsx}\" \"lib/**/*.ts\" --fix"
  ```
  to:
  ```json
  "lint": "eslint \"src/**/*.{ts,tsx}\"",
  "lint:fix": "eslint \"src/**/*.{ts,tsx}\" --fix"
  ```

- [ ] **Step 5: Clear cache and verify build**

  Run from `apps/web/`:
  ```bash
  pnpm clean
  pnpm build
  ```
  Expected: build succeeds with no TypeScript errors.

- [ ] **Step 6: Commit**

  ```bash
  git add apps/web/src apps/web/tsconfig.json apps/web/package.json
  git commit -m "refactor(web): move app/, components/, lib/ into src/ and update path aliases"
  ```

---

### Task 2: Create `shared/` layer

**Files:**
- Create: `apps/web/src/shared/api/index.ts`
- Create: `apps/web/src/shared/lib/track.ts`
- Create: `apps/web/src/shared/i18n/request.ts`
- Modify: `apps/web/next.config.ts`
- Modify: all files in `apps/web/src/app/` that import from `@/lib/`

> **Context:** `i18n.config.ts` and `messages/` stay at the repo root of `apps/web/` — next-intl v4 requires them there. Only `i18n/request.ts` (the server config) moves into `shared/`.

- [ ] **Step 1: Create `src/shared/api/`**

  Copy `src/lib/api.ts` → `src/shared/api/api.ts` (no content changes).
  Copy `src/lib/api-types.ts` → `src/shared/api/api-types.ts` (no content changes).

  Create `src/shared/api/index.ts`:
  ```ts
  export { api } from './api';
  export type {
    CatalogItem,
    CatalogResponse,
    SellerProfile,
    ItemDetail,
    ItemDetailResponse,
    Subject,
    Location,
    ReviewResponse,
    ReviewsResponse,
    ModerationItem,
    ModerationItemDetail,
    ModerationResult,
    AdminStats,
    AdminLead,
    LeadResponse,
  } from './api-types';
  ```

- [ ] **Step 2: Create `src/shared/lib/track.ts`**

  Copy `src/lib/track.ts` to `src/shared/lib/track.ts`. No content changes.

- [ ] **Step 3: Create `src/shared/i18n/request.ts`**

  Create `apps/web/src/shared/i18n/request.ts` with updated relative paths. The file moves from `i18n/request.ts` (1 level from root) to `src/shared/i18n/request.ts` (3 levels from root), so paths gain two additional `../`:

  ```ts
  import { getRequestConfig } from 'next-intl/server';
  import { cookies } from 'next/headers';
  import { i18nConfig } from '../../../i18n.config';

  export default getRequestConfig(async () => {
    const cookieStore = await cookies();
    const savedLocale = cookieStore.get('locale')?.value;

    const locale =
      savedLocale && (i18nConfig.locales as readonly string[]).includes(savedLocale)
        ? savedLocale
        : i18nConfig.defaultLocale;

    return {
      locale,
      // Webpack resolves dynamic imports with static string prefix correctly
      // regardless of how many ../ are in the prefix — this is the standard pattern.
      messages: (await import(`../../../messages/${locale}.json`)).default,
    };
  });
  ```

- [ ] **Step 4: Create `shared/ui/` and `shared/config/` placeholder directories**

  These are part of the spec's target structure but have no files to populate in this migration. Create `.gitkeep` files so the directories exist in the repo:
  ```bash
  mkdir -p apps/web/src/shared/ui
  touch apps/web/src/shared/ui/.gitkeep
  mkdir -p apps/web/src/shared/config
  touch apps/web/src/shared/config/.gitkeep
  ```

- [ ] **Step 5: Update `next.config.ts` plugin path**

  In `apps/web/next.config.ts`, change line 5:
  ```ts
  // Before:
  const withNextIntl = createNextIntlPlugin('./i18n/request.ts');
  // After:
  const withNextIntl = createNextIntlPlugin('./src/shared/i18n/request.ts');
  ```

- [ ] **Step 6: Update `@/lib/*` imports in `src/app/` to use `@/shared/api`**

  First confirm the complete list of files that need updating:
  ```bash
  grep -r "@/lib/" apps/web/src --include="*.ts" --include="*.tsx" -l
  ```

  Files to update (replace `@/lib/api-types` → `@/shared/api` and `@/lib/api` → `@/shared/api`):

  | File | Old import | New import |
  |------|-----------|------------|
  | `src/app/page.tsx` | `@/lib/api` | `@/shared/api` |
  | `src/app/page.tsx` | `@/lib/api-types` | `@/shared/api` |
  | `src/app/item/[slug]/page.tsx` | `@/lib/api` | `@/shared/api` |
  | `src/app/item/[slug]/page.tsx` | `@/lib/api-types` | `@/shared/api` |
  | `src/app/seller/leads/page.tsx` | `@/lib/api-types` | `@/shared/api` |
  | `src/app/admin/leads/page.tsx` | `@/lib/api-types` | `@/shared/api` |
  | `src/app/admin/page.tsx` | `@/lib/api-types` | `@/shared/api` |
  | `src/app/admin/reference/subjects/page.tsx` | `@/lib/api-types` | `@/shared/api` |
  | `src/app/admin/reference/locations/page.tsx` | `@/lib/api-types` | `@/shared/api` |
  | `src/app/admin/moderation/items/page.tsx` | `@/lib/api-types` | `@/shared/api` |
  | `src/app/me/leads/page.tsx` | `@/lib/api-types` | `@/shared/api` |

  Also update `src/components/CourseFeed.tsx` and `src/components/CourseCard.tsx`:
  - `@/lib/api-types` → `@/shared/api`

  Example diff for `src/app/page.tsx`:
  ```ts
  // Before:
  import { CourseFeed } from '@/components/CourseFeed';
  import { api } from '@/lib/api';
  import type { CatalogResponse } from '@/lib/api-types';

  // After:
  import { CourseFeed } from '@/components/CourseFeed';
  import { api, type CatalogResponse } from '@/shared/api';
  ```

- [ ] **Step 7: Clear cache and verify build**

  ```bash
  pnpm clean
  pnpm build
  ```
  Expected: build succeeds.

- [ ] **Step 8: Commit**

  ```bash
  git add apps/web/src/shared apps/web/next.config.ts apps/web/src/app apps/web/src/components
  git commit -m "refactor(web): add shared/ layer — migrate lib/ and i18n/ into src/shared/"
  ```

---

## Chunk 2: Entities + Features + Widgets

### Task 3: Create `entities/` layer

**Files:**
- Create: `apps/web/src/entities/item/ui/ItemCard.tsx`
- Create: `apps/web/src/entities/item/index.ts`
- Create: `apps/web/src/entities/seller/index.ts`
- Create: `apps/web/src/entities/lead/index.ts`
- Create: `apps/web/src/entities/review/index.ts`
- Create: `apps/web/src/entities/subject/index.ts`

> **Context:** Entities hold domain types and basic display components. `CourseCard` becomes `ItemCard` — a pure display component for a `CatalogItem`. No logic changes, only relocation and renaming.

- [ ] **Step 1: Create `entities/item/` slice**

  Copy `src/components/CourseCard.tsx` to `src/entities/item/ui/ItemCard.tsx`.
  Rename the exported function from `CourseCard` to `ItemCard`.
  Keep all logic identical.

  Update the import inside the new file:
  ```ts
  // Before: import type { CatalogItem } from '@/lib/api-types';
  // After: import type { CatalogItem } from '@/shared/api';
  ```

  Create `src/entities/item/index.ts`:
  ```ts
  export { ItemCard } from './ui/ItemCard';
  export type {
    CatalogItem,
    ItemDetail,
    ItemDetailResponse,
    CatalogResponse,
  } from '@/shared/api';
  ```

- [ ] **Step 2: Create remaining entity index files**

  Create `src/entities/seller/index.ts`:
  ```ts
  export type { SellerProfile } from '@/shared/api';
  ```

  Create `src/entities/lead/index.ts`:
  ```ts
  export type { LeadResponse, AdminLead } from '@/shared/api';
  ```

  Create `src/entities/review/index.ts`:
  ```ts
  export type { ReviewResponse, ReviewsResponse } from '@/shared/api';
  ```

  Create `src/entities/subject/index.ts`:
  ```ts
  export type { Subject, Location } from '@/shared/api';
  ```

- [ ] **Step 3: Verify types check**

  ```bash
  pnpm check-types
  ```
  Expected: no errors.

- [ ] **Step 4: Commit**

  ```bash
  git add apps/web/src/entities
  git commit -m "refactor(web): add entities/ layer — item, seller, lead, review, subject"
  ```

---

### Task 4: Create `features/` layer

**Files:**
- Create: `apps/web/src/features/auth/ui/LoginForm.tsx`
- Create: `apps/web/src/features/auth/ui/RegisterForm.tsx`
- Create: `apps/web/src/features/auth/index.ts`
- Create: `apps/web/src/features/seller-item-form/ui/ItemForm.tsx`
- Create: `apps/web/src/features/seller-item-form/index.ts`
- Create: `apps/web/src/features/seller-onboarding/ui/SellerOnboardingForm.tsx`
- Create: `apps/web/src/features/seller-onboarding/index.ts`

> **Context:** Features are co-located client components that represent a user action. We copy them from their current location in `src/app/` or `src/components/` to `src/features/`. The originals stay for now and are removed in Task 7 once `views/` replaces them.
>
> **Note:** The spec also lists `features/catalog-filter/`, `features/submit-lead/`, and `features/admin-moderation/` — but those are currently embedded inside page components with no standalone files to migrate. They are intentionally deferred to a future extraction pass and are NOT created in this migration.

- [ ] **Step 1: Create `features/auth/`**

  Copy `src/app/login/LoginForm.tsx` → `src/features/auth/ui/LoginForm.tsx`. No content changes.
  Copy `src/app/register/RegisterForm.tsx` → `src/features/auth/ui/RegisterForm.tsx`. No content changes.

  Create `src/features/auth/index.ts`:
  ```ts
  export { LoginForm } from './ui/LoginForm';
  export { RegisterForm } from './ui/RegisterForm';
  ```

- [ ] **Step 2: Create `features/seller-item-form/`**

  Copy `src/app/seller/items/ItemForm.tsx` → `src/features/seller-item-form/ui/ItemForm.tsx`. No content changes.

  Create `src/features/seller-item-form/index.ts`:
  ```ts
  export { ItemForm } from './ui/ItemForm';
  ```

- [ ] **Step 3: Create `features/seller-onboarding/`**

  Read `src/app/seller/onboarding/` to find the form component file, then copy it to `src/features/seller-onboarding/ui/SellerOnboardingForm.tsx`. No content changes.

  Create `src/features/seller-onboarding/index.ts`:
  ```ts
  export { SellerOnboardingForm } from './ui/SellerOnboardingForm';
  ```

- [ ] **Step 4: Create `features/submit-review/`**

  Copy `src/components/ReviewForm.tsx` → `src/features/submit-review/ui/ReviewForm.tsx`. No content changes.

  Create `src/features/submit-review/index.ts`:
  ```ts
  export { ReviewForm } from './ui/ReviewForm';
  ```

- [ ] **Step 5: Verify types check**

  ```bash
  pnpm check-types
  ```
  Expected: no errors.

- [ ] **Step 6: Commit**

  ```bash
  git add apps/web/src/features
  git commit -m "refactor(web): add features/ layer — auth, seller-item-form, seller-onboarding, submit-review"
  ```

---

### Task 5: Create `widgets/` layer

**Files:**
- Create: `apps/web/src/widgets/course-feed/ui/CourseFeed.tsx`
- Create: `apps/web/src/widgets/course-feed/index.ts`
- Create: `apps/web/src/widgets/lead-modal/ui/LeadModal.tsx`
- Create: `apps/web/src/widgets/lead-modal/index.ts`
- Create: `apps/web/src/widgets/reviews-block/ui/ReviewsBlock.tsx`
- Create: `apps/web/src/widgets/reviews-block/index.ts`

> **Context:** Widgets are composed UI blocks that combine entities and features. `CourseFeed` uses `ItemCard` (from `entities/item`). `LeadModal` and `ReviewsBlock` move from `src/components/` unchanged. Internal imports update to use FSD aliases.

- [ ] **Step 1: Create `widgets/course-feed/`**

  Create `src/widgets/course-feed/ui/CourseFeed.tsx`. Copy content of `src/components/CourseFeed.tsx` but update the import:
  ```ts
  // Before: import { CourseCard } from '@/components/CourseCard';
  // After:  import { ItemCard } from '@/entities/item';
  // Also update usage: <CourseCard ... /> → <ItemCard ... />
  ```
  Import for type stays the same (via `@/entities/item`):
  ```ts
  import { ItemCard, type CatalogItem } from '@/entities/item';
  ```

  Full updated file:
  ```tsx
  import { ItemCard, type CatalogItem } from '@/entities/item';

  export function CourseFeed({
    items,
    emptyMessage,
  }: {
    items: CatalogItem[];
    emptyMessage: string;
  }) {
    if (items.length === 0) {
      return (
        <div className="flex flex-col items-center justify-center py-16 text-center">
          <svg className="mb-4 h-12 w-12 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path
              strokeLinecap="round"
              strokeLinejoin="round"
              strokeWidth={1.5}
              d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"
            />
          </svg>
          <p className="text-sm text-gray-500">{emptyMessage}</p>
        </div>
      );
    }

    return (
      <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
        {items.map((item) => (
          <ItemCard key={item.id} item={item} />
        ))}
      </div>
    );
  }
  ```

  Create `src/widgets/course-feed/index.ts`:
  ```ts
  export { CourseFeed } from './ui/CourseFeed';
  ```

- [ ] **Step 2: Create `widgets/lead-modal/`**

  Copy `src/components/LeadModal.tsx` → `src/widgets/lead-modal/ui/LeadModal.tsx`. No content changes needed (it uses only external packages and `NEXT_PUBLIC_API_URL`).

  Create `src/widgets/lead-modal/index.ts`:
  ```ts
  export { LeadModal } from './ui/LeadModal';
  ```

- [ ] **Step 3: Create `widgets/reviews-block/`**

  Copy `src/components/ReviewsBlock.tsx` → `src/widgets/reviews-block/ui/ReviewsBlock.tsx`.

  `ReviewsBlock` has two imports to update:
  1. `@/lib/api-types` → `@/shared/api`
  2. `./ReviewForm` → `@/features/submit-review` (the sibling import must become an FSD alias import)

  Updated import block in `src/widgets/reviews-block/ui/ReviewsBlock.tsx`:
  ```ts
  import type { ReviewsResponse } from '@/shared/api';
  import { ReviewForm } from '@/features/submit-review';
  ```

  Create `src/widgets/reviews-block/index.ts`:
  ```ts
  export { ReviewsBlock } from './ui/ReviewsBlock';
  ```

- [ ] **Step 4: Verify types check**

  ```bash
  pnpm check-types
  ```
  Expected: no errors.

- [ ] **Step 5: Commit**

  ```bash
  git add apps/web/src/widgets
  git commit -m "refactor(web): add widgets/ layer — course-feed, lead-modal, reviews-block"
  ```

---

## Chunk 3: Views + Cleanup

### Task 6: Create `views/` layer

**Files:** One view component per route (17 views total). Each view is the page body moved out of `src/app/*/page.tsx`. The `page.tsx` files become thin wrappers.

> **Context:** A view is a server or client component containing the full page logic. The corresponding `src/app/*/page.tsx` is updated to just import and render it. This makes `app/` a pure routing layer.
>
> Pattern for a **server component** page:
> ```tsx
> // src/app/page.tsx (after)
> import { CatalogPage } from '@/views/catalog';
> export default CatalogPage;
> // (or: export { CatalogPage as default } from '@/views/catalog')
> ```
>
> Pattern for a **client component** page (those with `'use client'` at the top):
> ```tsx
> // src/app/admin/page.tsx (after)
> import { AdminDashboardPage } from '@/views/admin/dashboard';
> export default AdminDashboardPage;
> ```

- [ ] **Step 1: Catalog view**

  Create `src/views/catalog/ui/CatalogPage.tsx` — copy full content of `src/app/page.tsx` and update imports:
  - `@/components/CourseFeed` → `@/widgets/course-feed`
  - `@/lib/api` / `@/shared/api` stays `@/shared/api`

  Create `src/views/catalog/index.ts`:
  ```ts
  export { CatalogPage } from './ui/CatalogPage';
  ```

  Update `src/app/page.tsx`:
  ```tsx
  export { CatalogPage as default } from '@/views/catalog';
  ```

- [ ] **Step 2: Item detail view**

  Create `src/views/item-detail/ui/ItemDetailPage.tsx` — copy full content of `src/app/item/[slug]/page.tsx` (including the `generateMetadata` export and the default export component) and update imports:
  - `@/components/CourseFeed` → `@/widgets/course-feed`
  - `@/components/LeadModal` → `@/widgets/lead-modal`
  - `@/components/ReviewsBlock` → `@/widgets/reviews-block`
  - `@/shared/api` stays as-is
  - `notFound`, `getTranslations` and other Next.js imports stay as-is (they work from any server component)

  Create `src/views/item-detail/index.ts` — re-export both the page component and `generateMetadata`:
  ```ts
  export { default as ItemDetailPage, generateMetadata } from './ui/ItemDetailPage';
  ```

  Update `src/app/item/[slug]/page.tsx` to be a thin wrapper:
  ```ts
  export { generateMetadata } from '@/views/item-detail';
  export { ItemDetailPage as default } from '@/views/item-detail';
  ```

- [ ] **Step 3: Auth views**

  Create `src/views/auth/login/ui/LoginPage.tsx` — copy content of `src/app/login/page.tsx`, update import:
  - `./LoginForm` → `@/features/auth`

  Create `src/views/auth/login/index.ts`:
  ```ts
  export { LoginPage } from './ui/LoginPage';
  ```

  Update `src/app/login/page.tsx`:
  ```ts
  export { LoginPage as default } from '@/views/auth/login';
  ```

  Repeat for register: create `src/views/auth/register/ui/RegisterPage.tsx` and `index.ts`.
  Update `src/app/register/page.tsx`:
  ```ts
  export { RegisterPage as default } from '@/views/auth/register';
  ```

- [ ] **Step 4: Seller dashboard views (items)**

  Create `src/views/seller-dashboard/items/list/ui/SellerItemsPage.tsx` — copy content of `src/app/seller/items/page.tsx`. No import changes needed (it uses `NEXT_PUBLIC_API_URL` directly).

  Create index file. Update `src/app/seller/items/page.tsx` to re-export.

  Repeat for:
  - `src/views/seller-dashboard/items/new/` ← `src/app/seller/items/new/page.tsx`
    - Update import: `../../ItemForm` → `@/features/seller-item-form`
  - `src/views/seller-dashboard/items/edit/` ← `src/app/seller/items/[id]/edit/page.tsx`
    - Update import: `../../ItemForm` → `@/features/seller-item-form`

- [ ] **Step 5: Seller dashboard views (leads, profile, staff)**

  Repeat the same pattern for:
  - `src/views/seller-dashboard/leads/` ← `src/app/seller/leads/page.tsx`
  - `src/views/seller-dashboard/profile/` ← `src/app/seller/profile/page.tsx`
  - `src/views/seller-dashboard/staff/` ← `src/app/seller/staff/page.tsx`

- [ ] **Step 6: Seller onboarding view**

  Create `src/views/seller-onboarding/ui/SellerOnboardingPage.tsx` — copy content of `src/app/seller/onboarding/page.tsx`.
  Update import of `SellerOnboardingForm` to use `@/features/seller-onboarding`.

  Create index. Update route wrapper.

- [ ] **Step 7: Admin views**

  Repeat for all admin routes:
  - `src/views/admin/dashboard/` ← `src/app/admin/page.tsx`
  - `src/views/admin/moderation/` ← `src/app/admin/moderation/items/page.tsx`
  - `src/views/admin/reference/subjects/` ← `src/app/admin/reference/subjects/page.tsx`
  - `src/views/admin/reference/locations/` ← `src/app/admin/reference/locations/page.tsx`
  - `src/views/admin/leads/` ← `src/app/admin/leads/page.tsx`

  Admin pages use `NEXT_PUBLIC_API_URL` directly for fetch calls. They also import types. By the time you copy these files (after Task 2), the source `page.tsx` files will already have been updated to use `@/shared/api` instead of `@/lib/api-types`. Copy the already-updated versions — verify there are no `@/lib/` imports remaining in the copied files.

- [ ] **Step 8: Me view**

  Create `src/views/me/leads/ui/MyLeadsPage.tsx` ← `src/app/me/leads/page.tsx`. No import changes.
  Create index. Update route wrapper.

- [ ] **Step 9: Verify build**

  ```bash
  pnpm clean
  pnpm build
  ```
  Expected: build succeeds with all 17 routes.

- [ ] **Step 10: Commit**

  ```bash
  git add apps/web/src/views apps/web/src/app
  git commit -m "refactor(web): add views/ layer — extract page components from app/ routes"
  ```

---

### Task 7: Cleanup

**Files:**
- Delete: `apps/web/src/components/` (now empty — all components migrated)
- Delete: `apps/web/src/lib/` (now empty — all files migrated to shared/)
- Delete: `apps/web/i18n/` (moved to src/shared/i18n/)

- [ ] **Step 1: Verify `src/components/` is no longer imported anywhere**

  ```bash
  grep -r "@/components" apps/web/src --include="*.ts" --include="*.tsx"
  ```
  Expected: no results. If any remain, update them to use the appropriate FSD layer.

- [ ] **Step 2: Verify `src/lib/` is no longer imported anywhere**

  ```bash
  grep -r "@/lib/" apps/web/src --include="*.ts" --include="*.tsx"
  ```
  Expected: no results. If any remain, update them to `@/shared/api` or `@/shared/lib`.

- [ ] **Step 3: Delete old directories**

  ```bash
  rm -rf apps/web/src/components
  rm -rf apps/web/src/lib
  rm -rf apps/web/i18n
  ```

  > **Note:** `apps/web/i18n.config.ts` (at the project root) is NOT deleted — it stays there as required by next-intl v4. Only the `i18n/` directory (containing the old `request.ts`) is removed.

- [ ] **Step 4: Final build and lint**

  ```bash
  pnpm clean
  pnpm build
  pnpm lint
  pnpm check-types
  ```
  Expected: all pass with no errors.

- [ ] **Step 5: Commit**

  ```bash
  git add -A
  git commit -m "refactor(web): cleanup — remove migrated src/components/, src/lib/, i18n/ directories"
  ```

---

## Summary

After all tasks the structure is:

```
apps/web/src/
├── app/           # Next.js routing — thin wrappers only
├── views/         # Page components (17 routes)
├── widgets/       # course-feed, lead-modal, reviews-block
├── features/      # auth, seller-item-form, seller-onboarding
├── entities/      # item, seller, lead, review, subject
└── shared/        # api, i18n, lib
```

All routes unchanged. All behaviour unchanged. FSD import rules enforced via directory structure and aliased imports.
