# TorbatYar Frontend — Migration Plan

**Date:** 2026-07-26  
**Status:** Planning document — no code changes applied  
**Prerequisite:** Read [frontend-architecture-audit.md](./frontend-architecture-audit.md)

This plan prioritizes **stabilization before restructuring**. Each phase is independently shippable.

---

## Guiding Principles

1. **Do not refactor working modules mid-feature** — changes are incremental and gated
2. **Enforce boundaries with tooling**, not convention alone
3. **Accounting follows beauty/healthcare route patterns** over time, not immediately
4. **Shared abstractions emerge from deduplication**, not upfront framework design
5. **Keep the monolith** until build times or team size force extraction

---

## Phase 0 — Stabilize (Week 1)

**Goal:** Green build, remove dead weight, zero functional changes.

| Task | Files | Effort | Risk |
|------|-------|--------|------|
| Fix DS `Button` type error OR remove `loading` prop usage | `app/accounting/purchase/returns/page.tsx`, possibly `components/ds/Button.tsx` | S | Low |
| Remove unused files (5 confirmed dead) | `lib/api-client.ts`, `lib/optional-sso.ts`, `lib/beauty-business-nav.ts`, `components/HealthStatus.tsx`, `components/beauty/pages/mobile.tsx` | S | Low |
| Remove or use unused npm deps | `@tanstack/react-table`, `recharts` from `package.json` | S | Low |
| Add `npm run typecheck` script | `package.json` | S | None |
| Verify `npm run build` passes in CI | CI config | S | None |

**Exit criteria:** `npm run build` succeeds; no files with zero imports (except intentional barrels).

---

## Phase 1 — Boundary Enforcement (Weeks 2–3)

**Goal:** Prevent architectural regression as new modules are added.

| Task | Description | Effort |
|------|-------------|--------|
| Add ESLint `no-restricted-imports` rules | Block `@/components/accounting/*` from beauty/healthcare and vice versa | M |
| Add ESLint rule: DS must not import domain code | Already clean — add rule to keep it that way | S |
| Document allowed import matrix | See [module-boundaries.md](./module-boundaries.md) | S |
| Add path aliases (optional) | `@/ds/*`, `@/platform/*` — reduce `@/` ambiguity | M |
| Consolidate beauty owner routes | Merge `beauty/(owner)/*` CRUD into `components/beauty/pages/owner.tsx` pattern OR merge `owner/` into `(owner)/` | L |

**Exit criteria:** Lint fails on cross-domain imports; beauty owner has single URL tree.

---

## Phase 2 — Shared Portal Infrastructure (Weeks 4–6)

**Goal:** Extract beauty/healthcare duplication into reusable platform layer.

### 2.1 Create `components/portals/` (shared)

Extract from duplicated files:

| Shared abstraction | Source files |
|--------------------|--------------|
| `createPortalLayout()` | `beauty/createPortalLayout.tsx`, `healthcare/createPortalLayout.tsx` |
| `PortalShell` base | `BeautyPortalShell`, `HealthcarePortalShell`, `AccountingShell` (~90% overlap) |
| `pages/shared.tsx` helpers | `beauty/pages/shared.tsx`, `healthcare/pages/shared.tsx` |
| `PublicSiteLayout` | `PublicBeautyLayout`, `PublicHealthcareLayout` |
| Accent-token cards/chips | `BeautyCards`, `HealthcareCards`, `BeautyStatusChip`, `MedicalStatusChip` |

**Parameterize by:**
- Portal ID type (generic)
- Nav config (`lib/*-portals.ts` stays domain-specific)
- CSS accent variable (`--beauty-accent` vs `--health-accent`)

### 2.2 Normalize module DS folder naming

Pick one convention:
- **Option A (recommended):** `components/{module}/design-system/` for both beauty and healthcare
- **Option B:** Move module-specific tokens to `components/portals/tokens.ts` with domain presets

**Exit criteria:** Single `createPortalLayout` factory; portal shells share one base component; healthcare `ui/` renamed to `design-system/`.

---

## Phase 3 — API Client Decomposition (Weeks 7–10)

**Goal:** Split monolithic API clients into maintainable modules.

### Current state

| File | Lines | Methods (approx) |
|------|------:|------------------|
| `lib/accounting-api.ts` | 1,856 | 200+ |
| `lib/beauty-business-api.ts` | 1,446 | 150+ |
| `lib/healthcare-api.ts` | 1,322 | 120+ |

### Target structure

```
lib/
├── api/
│   ├── core.ts              # Shared fetch wrapper, error class, token refresh
│   ├── accounting/
│   │   ├── index.ts         # Re-export barrel
│   │   ├── vouchers.ts
│   │   ├── treasury.ts
│   │   ├── inventory.ts
│   │   └── ...
│   ├── beauty/
│   │   ├── index.ts
│   │   ├── appointments.ts
│   │   ├── catalog.ts
│   │   └── ...
│   └── healthcare/
│       ├── index.ts
│       └── ...
├── accounting-api.ts        # Deprecated barrel → re-exports from api/accounting/
```

**Migration strategy:**
1. Extract shared `createApiClient(baseUrl, serviceName)` helper
2. Move one domain subdomain at a time (e.g. `accounting/vouchers`)
3. Keep deprecated barrel exports until all imports updated
4. Run grep after each subdomain to verify zero old imports

**Exit criteria:** No API file exceeds 400 lines; shared fetch logic in one place.

---

## Phase 4 — Accounting Route Extraction (Weeks 11–16)

**Goal:** Align accounting with beauty/healthcare thin-route pattern.

### Priority extractions (largest route files)

| Route file | Lines | Target component |
|------------|------:|------------------|
| `app/accounting/chart-of-accounts/page.tsx` | 644 | `components/accounting/ChartOfAccountsPage.tsx` |
| `app/accounting/reports/page.tsx` | 663 | `components/accounting/ReportsPage.tsx` |
| `app/accounting/fiscal/page.tsx` | 579 | `components/accounting/FiscalPage.tsx` |
| `app/accounting/vouchers/[id]/page.tsx` | 550 | `components/accounting/VoucherDetailPage.tsx` |
| `app/accounting/treasury/page.tsx` | 490 | `components/accounting/TreasuryPage.tsx` |
| `app/accounting/payroll/page.tsx` | 457 | `components/accounting/PayrollPage.tsx` |

### Screen aggregator split

| Current file | Lines | Action |
|--------------|------:|--------|
| `BusinessDocsPage.tsx` | 1,328 | Split by document type |
| `WorkflowScreens.tsx` | 1,132 | Split by workflow domain |
| `OperationalDocumentPage.tsx` | 996 | Keep; already reusable |
| `DomainScreens.tsx` | 652 | Split by domain section |

**Pattern:**
```tsx
// app/accounting/reports/page.tsx (after)
export { ReportsPage as default } from "@/components/accounting/ReportsPage";
```

**Exit criteria:** No accounting `page.tsx` exceeds 50 lines except dynamic route params; no component exceeds 500 lines.

---

## Phase 5 — Platform Hardening (Weeks 17–20)

**Goal:** Production-grade resilience and auth.

| Task | Description |
|------|-------------|
| Add `middleware.ts` | Edge auth check, tenant header injection, public route allowlist |
| Add segment `error.tsx` | Per-module error boundaries (`accounting/`, `beauty/`, `healthcare/`) |
| Add segment `loading.tsx` | Skeleton states using DS `LoadingState` / `Skeleton` |
| Conditional SiteHeader | Hide platform header on `/beauty/site/*`, `/healthcare/site/*` |
| BFF for Core API | `/api/core/[...path]` proxy for admin/dashboard |
| Bundle analysis | `@next/bundle-analyzer` baseline before module #4 |

---

## Phase 6 — New Module Template (Ongoing)

When adding module #4 (e.g. Delivery, CRM, Hospitality):

### Required scaffold

```
app/{module}/
├── layout.tsx                    # pass-through or module shell
├── hub/page.tsx                  # portal picker
├── {portal}/layout.tsx           # createPortalLayout("{portal}")
├── {portal}/.../page.tsx         # thin re-exports
└── site/layout.tsx               # public site (if applicable)

components/{module}/
├── design-system/
│   ├── tokens.ts
│   └── index.ts
├── pages/
│   ├── shared.tsx
│   └── {portal}.tsx
├── {Module}PortalShell.tsx       # extends shared PortalShell
└── createPortalLayout.tsx        # OR import from components/portals/

lib/
├── {module}-api.ts               # OR lib/api/{module}/
└── {module}-portals.ts

app/api/{module}/[...path]/route.ts
```

### Checklist before merge

- [ ] Zero imports from other business modules
- [ ] All pages use `@/components/ds` (not `components/ui`)
- [ ] BFF proxy route exists
- [ ] Nav config in `lib/{module}-portals.ts`
- [ ] Query keys prefixed `["{module}", tenantId, ...]`
- [ ] ESLint boundary rules pass

---

## Phase Summary Timeline

```
Phase 0  ████                          Stabilize (Week 1)
Phase 1  ░░░░████                      Boundaries (Weeks 2–3)
Phase 2  ░░░░░░░░████████              Portal infra (Weeks 4–6)
Phase 3  ░░░░░░░░░░░░░░████████████    API clients (Weeks 7–10)
Phase 4  ░░░░░░░░░░░░░░░░░░░░████████████████  Accounting routes (Weeks 11–16)
Phase 5  ░░░░░░░░░░░░░░░░░░░░░░░░░░░░████████  Hardening (Weeks 17–20)
Phase 6  ────────────────────────────────────►  Module template (ongoing)
```

---

## What NOT to do

| Anti-pattern | Reason |
|--------------|--------|
| Split into npm packages prematurely | Team size doesn't justify overhead yet |
| Introduce Redux/Zustand | TanStack Query + local state is sufficient |
| Rewrite accounting in beauty page-bundle style in one PR | Too risky; extract file-by-file |
| Move public sites to separate Next.js app | Shared auth/tenant infra makes monolith correct for now |
| Adopt TanStack Table without requirement | Custom DataTable works; add features when needed |

---

## Success Metrics

| Metric | Current | Phase 0 | Phase 4 | Phase 5 |
|--------|---------|---------|---------|---------|
| Build passes | ❌ | ✅ | ✅ | ✅ |
| Max page.tsx lines (accounting) | 663 | 663 | ≤50 | ≤50 |
| Max component file lines | 1,856 | 1,856 | ≤500 | ≤500 |
| Cross-domain imports | 0 | 0 | 0 | 0 (enforced) |
| Dead files | 5 | 0 | 0 | 0 |
| Unused npm deps | 2 | 0 | 0 | 0 |
| Portal factory implementations | 2 | 2 | 1 | 1 |
| Middleware coverage | 0% | 0% | 0% | 100% public/private |

---

## Related Documents

- [frontend-architecture-audit.md](./frontend-architecture-audit.md)
- [module-boundaries.md](./module-boundaries.md)
- [refactor-risk-analysis.md](./refactor-risk-analysis.md)
