# 07 — Scaffolding Playbook

> Step-by-step instructions for AI coding agents to replicate this architecture from scratch or rescaffold an existing repo.

## Path A: Start from Scratch

Follow these steps in order. Each step has a **checkpoint** — verify it passes before continuing.

---

### Step 1: Initialize the Monorepo

```bash
mkdir my-dashboard && cd my-dashboard
git init

# Create monorepo structure
mkdir -p hub/frontend hub/api hub/config hub/deploy docs reference
```

**Checkpoint:** `ls hub/` shows `frontend/ api/ config/ deploy/`

---

### Step 2: Scaffold the Frontend (Vite + React + TypeScript)

```bash
cd hub/frontend
npm create vite@latest . -- --template react-ts

# Install core dependencies
npm install \
  react-router-dom@^6 \
  @tanstack/react-query@^5 \
  axios \
  lucide-react \
  clsx \
  tailwind-merge \
  framer-motion \
  sonner \
  dompurify

# Install dev dependencies
npm install -D \
  tailwindcss@^3 \
  postcss \
  autoprefixer \
  vitest \
  @testing-library/react \
  @testing-library/jest-dom \
  @testing-library/user-event \
  @vitest/coverage-v8 \
  jsdom \
  @playwright/test \
  @axe-core/playwright \
  @types/dompurify
```

**Checkpoint:** `npm run dev` starts Vite on `localhost:5173`

---

### Step 3: Configure Tailwind CSS

```bash
npx tailwindcss init -p --ts
```

Edit `tailwind.config.ts`:
```typescript
import type { Config } from 'tailwindcss'

export default {
  darkMode: ['class'],
  content: ['./index.html', './src/**/*.{ts,tsx}'],
  theme: {
    extend: {
      colors: {
        tenant: {
          // Replace with your brand colors
          primary: '#500711',
          secondary: '#c2410c',
        },
      },
      fontFamily: {
        sans: ['Inter', 'system-ui', 'sans-serif'],
      },
    },
  },
  plugins: [],
} satisfies Config
```

Edit `src/index.css`:
```css
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
  --brand-primary: #500711;
  --bg-primary: #FFFFFF;
  --bg-secondary: #F9FAFB;
  --text-primary: #111827;
  --text-secondary: #4B5563;
  --border-color: #E5E7EB;
}

.dark {
  --bg-primary: #0F0F0F;
  --bg-secondary: #171717;
  --text-primary: #F9FAFB;
  --text-secondary: #D1D5DB;
  --border-color: #374151;
}
```

**Checkpoint:** Components using Tailwind classes render correctly

---

### Step 4: Configure Strict TypeScript

Edit `tsconfig.json`:
```jsonc
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "jsx": "react-jsx",
    "strict": true,
    "exactOptionalPropertyTypes": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noUncheckedIndexedAccess": false,  // Optional: true for maximum safety
    "noPropertyAccessFromIndexSignature": true,
    "paths": { "@/*": ["./src/*"] }
  },
  "include": ["src"]
}
```

Edit `vite.config.ts`:
```typescript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: { '@': path.resolve(__dirname, './src') },
  },
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          'vendor-react': ['react', 'react-dom', 'react-router-dom'],
          'vendor-query': ['@tanstack/react-query'],
        },
      },
    },
  },
  server: {
    proxy: {
      '/api': { target: 'http://localhost:7071', changeOrigin: true },
      '/.auth': { target: 'http://localhost:7071', changeOrigin: true },
    },
  },
})
```

**Checkpoint:** `npx tsc --noEmit` passes with zero errors

---

### Step 5: Create the Frontend Skeleton

Create these files in order:

#### `src/lib/utils.ts`
```typescript
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}
```

#### `src/providers/ThemeProvider.tsx`
```typescript
import { createContext, useContext, useState, useEffect, type ReactNode } from 'react'

interface ThemeContextType {
  theme: 'light' | 'dark'
  toggleTheme: () => void
}

const ThemeContext = createContext<ThemeContextType>({ theme: 'light', toggleTheme: () => {} })

export function ThemeProvider({ children }: { children: ReactNode }) {
  const [theme, setTheme] = useState<'light' | 'dark'>(() => {
    const stored = localStorage.getItem('theme')
    if (stored === 'dark' || stored === 'light') return stored
    return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
  })

  useEffect(() => {
    document.documentElement.classList.toggle('dark', theme === 'dark')
    localStorage.setItem('theme', theme)
  }, [theme])

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme: () => setTheme(t => t === 'dark' ? 'light' : 'dark') }}>
      {children}
    </ThemeContext.Provider>
  )
}

export const useTheme = () => useContext(ThemeContext)
```

#### `src/providers/AuthProvider.tsx`
```typescript
import { createContext, useContext, useState, useEffect, type ReactNode } from 'react'

interface User {
  identityProvider: string
  userId: string
  userDetails: string
  userRoles: string[]
}

interface AuthContextType {
  user: User | null
  isLoading: boolean
  login: () => void
  logout: () => void
}

const AuthContext = createContext<AuthContextType>({
  user: null, isLoading: true,
  login: () => {}, logout: () => {},
})

export function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<User | null>(null)
  const [isLoading, setIsLoading] = useState(true)

  useEffect(() => {
    fetch('/.auth/me')
      .then(r => r.json())
      .then(data => setUser(data.clientPrincipal ?? null))
      .catch(() => setUser(null))
      .finally(() => setIsLoading(false))
  }, [])

  return (
    <AuthContext.Provider value={{
      user,
      isLoading,
      login: () => { window.location.href = '/.auth/login/aad' },
      // Two-step logout: clear SWA cookie → /logged-out → AAD SSO logout
      logout: () => { window.location.href = '/.auth/logout?post_logout_redirect_uri=/logged-out' },
    }}>
      {children}
    </AuthContext.Provider>
  )
}

export const useAuth = () => useContext(AuthContext)
```

#### `src/main.tsx`
```tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ThemeProvider } from '@/providers/ThemeProvider'
import { AuthProvider } from '@/providers/AuthProvider'
import { Toaster } from 'sonner'
import App from './App'
import './index.css'

const queryClient = new QueryClient({
  defaultOptions: {
    queries: { staleTime: 30_000, retry: 1 },
  },
})

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <BrowserRouter>
      <QueryClientProvider client={queryClient}>
        <ThemeProvider>
          <AuthProvider>
            <App />
            <Toaster position="top-right" richColors />
          </AuthProvider>
        </ThemeProvider>
      </QueryClientProvider>
    </BrowserRouter>
  </React.StrictMode>
)
```

#### `src/App.tsx`
```tsx
import { Routes, Route, Navigate } from 'react-router-dom'
import { MainLayout } from '@/components/layout/MainLayout'
import { Dashboard } from '@/pages/Dashboard'

export default function App() {
  return (
    <Routes>
      <Route element={<MainLayout />}>
        <Route path="/" element={<Navigate to="/dashboard" replace />} />
        <Route path="/dashboard" element={<Dashboard />} />
        {/* Add more routes as you build pages */}
      </Route>
    </Routes>
  )
}
```

**Checkpoint:** App renders in browser with theme toggle working

---

### Step 6: Scaffold the API (Azure Functions v4)

```bash
cd hub/api
npm init -y

# Install Azure Functions v4
npm install \
  @azure/functions@^4 \
  @azure/identity \
  @microsoft/microsoft-graph-client \
  applicationinsights \
  rate-limiter-flexible

npm install -D \
  typescript \
  vitest \
  @types/node
```

Create `tsconfig.json`:
```jsonc
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "node16",
    "outDir": "dist",
    "rootDir": "src",
    "strict": true,
    "exactOptionalPropertyTypes": true,
    "noUncheckedIndexedAccess": true,
    "esModuleInterop": true,
    "declaration": true,
    "sourceMap": true
  },
  "include": ["src"]
}
```

Create `host.json`:
```json
{
  "version": "2.0",
  "logging": { "logLevel": { "default": "Information" } },
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[4.*, 5.0.0)"
  }
}
```

Create the auth, RBAC, and CSRF files:

```
hub/api/src/
  lib/
    auth/
      swaAuth.ts          ← Copy from 05-AUTH-AND-RBAC.md
    middleware/
      rbac.ts             ← Copy from 05-AUTH-AND-RBAC.md
      rateLimiter.ts
      csrfProtection.ts   ← Stateless HMAC-signed tokens (see 04-API-PATTERNS.md)
    graph/
      client.ts           ← Graph API client with user filtering helpers
    healthChecks/
    auditLogger.ts
    auditQuery.ts           ← Kusto query builder for App Insights audit queries
    blobPersistence.ts      ← Azure Blob Storage persistence for user store
    logger.ts
    readOnlyMode.ts         ← Runtime read-only toggle for emergency kill switch
    telemetry.ts            ← App Insights init (33% sampling, audit events bypass sampling)
    userStore.ts            ← User CRUD with Blob Storage persistence + env var bootstrap
    writeFailureTelemetry.ts ← Categorised write failure tracking (hub.write_failure events)
  functions/               ← 21 files at v3.10 (19 HTTP + 1 timer + 1 pre-invocation hook)
    authRoles.ts          ← SWA rolesSource handler (Standard tier only — see 05-AUTH-AND-RBAC.md)
    startup.ts            ← Pre-invocation hook: ensures user store initialized before every call
    health.ts             ← First endpoint: GET /api/health
    userContext.ts        ← GET /api/user-context: returns globalPersona + accessible tenants
```

#### `src/functions/health.ts` (starter endpoint):
```typescript
import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions'

async function healthHandler(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
  return {
    status: 200,
    jsonBody: {
      status: 'healthy',
      timestamp: new Date().toISOString(),
      version: process.env['npm_package_version'] ?? '0.0.0',
    },
  }
}

app.http('health', {
  methods: ['GET'],
  authLevel: 'anonymous',
  route: 'health',
  handler: healthHandler,
})
```

**Checkpoint:** `cd hub/api && npm start` → `curl localhost:7071/api/health` returns `{"status":"healthy"}`

---

### Step 7: Add SWA Configuration

Create `hub/frontend/staticwebapp.config.json` (see 02-AZURE-SWA-SETUP.md for templates).

Create `hub/frontend/public/` and ensure `staticwebapp.config.json` is also placed there or at the root level so Vite copies it to `dist/`.

Create `hub/swa-cli.config.json` for local SWA CLI development:
```json
{
  "$schema": "https://aka.ms/azure/static-web-apps-cli/schema",
  "configurations": {
    "hub": {
      "appLocation": "frontend",
      "apiLocation": "api",
      "outputLocation": "dist",
      "appDevserverUrl": "http://localhost:5173",
      "apiPort": 7071,
      "run": "npm run dev -- --port 5173",
      "appBuildCommand": "npm run build",
      "apiBuildCommand": "npm run build"
    }
  }
}
```

Create `hub/api/.env.example` to document all required environment variables with placeholder values (see 01-ARCHITECTURE.md for the full list).

**Checkpoint:** Build succeeds — `cd hub/frontend && npm run build` produces `dist/` with `staticwebapp.config.json` inside

---

### Step 8: Add Testing Infrastructure

Create `hub/frontend/src/test-setup.ts` (see 06-TESTING-STRATEGY.md).

Create first test:
```typescript
// hub/frontend/src/__tests__/App.test.tsx
import { render, screen } from '@testing-library/react'
import { BrowserRouter } from 'react-router-dom'
import App from '@/App'

describe('App', () => {
  it('renders without crashing', () => {
    render(
      <BrowserRouter>
        <App />
      </BrowserRouter>
    )
    expect(document.body).toBeTruthy()
  })
})
```

Add scripts to `package.json`:
```json
{
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "test": "vitest run",
    "test:watch": "vitest",
    "test:coverage": "vitest run --coverage",
    "typecheck": "tsc --noEmit"
  }
}
```

**Checkpoint:** `npm test` passes, `npm run typecheck` passes

---

### Step 9: Set Up CI/CD

Create `.github/workflows/azure-static-web-apps.yml` (see 02-AZURE-SWA-SETUP.md for the full multi-job pipeline template).

The production pipeline uses separate jobs for API build, unit tests, E2E tests, and deploy — with `skip_api_build: true` to use the pre-built API artifact:

```yaml
jobs:
  build_api:     # Pre-build API and cache artifact
  unit_tests:    # Frontend + API unit tests
  e2e_tests:     # Playwright E2E (--project=chromium)
  deploy:        # SWA deploy with skip_api_build: true
    needs: [build_api, unit_tests, e2e_tests]
```

---

### Step 10: Deploy

1. Create the Azure Static Web App resource (Azure Portal or CLI)
2. Get the deployment token
3. Add `AZURE_STATIC_WEB_APPS_API_TOKEN` to GitHub secrets
4. Push to `main` → auto-deploys

**Final checkpoint:** Visit `https://your-app.azurestaticapps.net` → Azure AD login → Dashboard renders

---

## Path B: Rescaffold an Existing Repo

When you have an existing codebase and want to restructure it to follow this architecture.

### Phase 1: Audit

1. **Map the existing structure.** Run `find . -name '*.ts' -o -name '*.tsx' | head -100` to understand what exists.
2. **Identify the tech stack.** Read `package.json` files for framework, dependencies, and build tools.
3. **Find the entry point.** Look for `main.tsx`, `index.tsx`, or `App.tsx`.
4. **Find the API.** Look for Azure Functions (`host.json`, `@azure/functions`), Express, or Next.js API routes.

### Phase 2: Create Target Structure

Create the target directories if they don't exist:

```bash
mkdir -p hub/frontend/src/{api,components/{common,layout},hooks,lib,pages,providers,types,__tests__}
mkdir -p hub/api/src/{functions,lib/{auth,middleware,graph}}
mkdir -p hub/config hub/deploy hub/infrastructure
```

### Phase 3: Move Files

Move files to their new locations following these rules:

| Source pattern | Target |
|---------------|--------|
| Page-level components (routing targets) | `hub/frontend/src/pages/` |
| Reusable UI components | `hub/frontend/src/components/common/` |
| Domain-specific components | `hub/frontend/src/components/{domain}/` |
| API call functions/hooks | `hub/frontend/src/api/` |
| Custom React hooks | `hub/frontend/src/hooks/` |
| Context providers | `hub/frontend/src/providers/` |
| TypeScript interfaces/types | `hub/frontend/src/types/` |
| Utility functions | `hub/frontend/src/lib/` |
| API endpoints/handlers | `hub/api/src/functions/` |
| Auth logic | `hub/api/src/lib/auth/` |
| Middleware (RBAC, CSRF, etc.) | `hub/api/src/lib/middleware/` |
| Graph/external API clients | `hub/api/src/lib/graph/` |
| Config files (tenant defs, etc.) | `hub/config/` |
| Deployment scripts | `hub/deploy/` |

### Phase 4: Update Imports

After moving files, update all import paths. The `@/` alias is your friend:

```bash
# If using sed/ripgrep to bulk-update:
rg 'from.*\.\./\.\./components' --files-with-matches | xargs sed -i 's|from.*\.\./\.\./components|from "@/components|g'
```

### Phase 5: Add Missing Pieces

Compare the existing codebase against this reference and add:

- [ ] `staticwebapp.config.json` (if not present)
- [ ] `swa-cli.config.json` (SWA CLI project config)
- [ ] `.env.example` (API env var documentation)
- [ ] SWA auth extraction (`swaAuth.ts`)
- [ ] RBAC middleware (`rbac.ts`) — 4 personas, 30 operations
- [ ] Stateless HMAC-signed CSRF protection (`csrfProtection.ts`)
- [ ] SWA rolesSource handler (`authRoles.ts`) — assigns custom roles on login **(Standard tier only; on Free tier, skip and rely on ROLE_ASSIGNMENTS)**
- [ ] `usePermissions()` hook
- [ ] `useUserContext()` hook — calls `GET /api/user-context`, returns `globalPersona` + tenant access (Free-tier RBAC source of truth)
- [ ] `useAccessibleTenants()` hook — combines `useTenants()` + `useUserContext()`, admin sees all, non-admin filtered
- [ ] `LoggedOut.tsx` page — sign-out intermediate page, redirects to AAD `/oauth2/v2.0/logout`
- [ ] `startup.ts` — pre-invocation hook, calls `ensureInitialized()` before every Azure Function
- [ ] `recommendations.ts` — `analyzeTenantHealth()` + `aggregateRecommendations()` for dashboard insights
- [ ] `AdminRoute` component for admin-only pages
- [ ] Error boundary
- [ ] Provider stack (Auth, Theme, Notification, ReadOnly)
- [ ] CSS custom properties for brand tokens
- [ ] User data filtering (`MEMBER_USERS_FILTER` + `hasExchangeLicense()`)
- [ ] Test infrastructure (vitest config, test-utils, setup)
- [ ] GitHub Actions workflow

### Phase 6: Verify

```bash
# Type check
cd hub/frontend && npx tsc --noEmit
cd hub/api && npx tsc --noEmit

# Unit tests
cd hub/frontend && npm test
cd hub/api && npm test

# Build
cd hub/frontend && npm run build

# E2E (if applicable)
cd hub/frontend && npx playwright test
```

---

## File Creation Order (for AI agents)

When creating a project from scratch, create files in this priority order:

```
 1. package.json (both frontend + api)
 2. tsconfig.json (both)
 3. vite.config.ts
 4. tailwind.config.ts + postcss.config.js
 5. index.html + src/index.css + src/main.tsx
 6. src/lib/utils.ts (cn helper)
 7. src/providers/ThemeProvider.tsx
 8. src/providers/AuthProvider.tsx (with two-step AAD logout chain)
 9. src/pages/LoggedOut.tsx (sign-out intermediate → AAD SSO logout)
 9b. src/App.tsx (routes — /logged-out OUTSIDE MainLayout)
10. src/components/layout/MainLayout.tsx (sidebar + header shell)
11. src/pages/Dashboard.tsx (first page)
12. hub/api/host.json + src/functions/health.ts (first API endpoint)
13. hub/api/src/lib/auth/swaAuth.ts
14. hub/api/src/lib/middleware/rbac.ts
14b. hub/api/src/functions/authRoles.ts (SWA rolesSource handler — Standard tier only)
14c. hub/api/src/functions/startup.ts (pre-invocation hook — ensures user store initialized)
14d. hub/api/src/functions/userContext.ts (resolves globalPersona + tenant list per user)
15. hub/frontend/staticwebapp.config.json (on Standard tier: include rolesSource + /api/auth/roles route; on Free tier: omit both)
16. src/api/client.ts (axios instance)
16b. src/api/userContext.ts (useUserContext hook — Free-tier RBAC source of truth)
17. src/hooks/usePermissions.ts
17b. src/hooks/useAccessibleTenants.ts (combines useTenants + useUserContext)
17c. src/lib/recommendations.ts (analyzeTenantHealth + aggregateRecommendations)
18. Tests (test-setup.ts, first test file)
19. .github/workflows/azure-static-web-apps.yml
20. hub/deploy/DEPLOYMENT.md
```

This order ensures each file can import from previously created files, and checkpoints are reachable at every step.
