> ## Documentation Index
> Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Vercel Microfrontends — activation runbook (Phase 3)

> Status: Inert scaffolding committed; activation requires project-owner action Owner: Platform Architecture (technical) + Jeremy Bloom (Vercel dashboard owner)…

**Status:** Inert scaffolding committed; activation requires project-owner action
**Owner:** Platform Architecture (technical) + Jeremy Bloom (Vercel dashboard owner)
**Phase:** 3 of `docs/recommendations/DEV_VELOCITY_AND_ARCHITECTURE_PLAN_2026-04-22.md` (lands via PR #137)
**ADRs:** [`ADR-008`](../architecture/decisions/ADR-008-vite-spa-vs-ssr.md) (preserved), [`ADR-013`](../architecture/decisions/ADR-013-pwa-strategy.md) (amendment proposed in [`ADR-017`](../architecture/decisions/ADR-017-microfrontends-pwa-scoping.md))

***

## What this PR ships

* **`microfrontends.json`** at the repo root — the proposed split. Two applications:
  * `encoreos-app` (default; receives every un-routed path; **today this is the entire app**)
  * `encoreos-public` (peeled off: `/auth`, `/employee-setup`, `/pending-activation`, `/pm/kiosk/:siteId/*`, `/portal/*`, `/p/:token`, `/packet/:token`, `/gr/whistleblower-report`, `/chatbot/widget`)
* **Turborepo workspace mapping:** Turborepo resolves `microfrontends.json` application keys to **npm workspace packages** by matching the application key to `package.json` `name`. The keys `encoreos-app` / `encoreos-public` therefore have minimal placeholder packages at [`packages/encoreos-app`](../../packages/encoreos-app/package.json) and [`packages/encoreos-public`](../../packages/encoreos-public/package.json) (private, no source — the real app remains the root Vite SPA). When Phase 3 splits into real deployables, replace these placeholders with actual package roots or add `packageName` per the [Turborepo microfrontends schema](https://turborepo.dev/microfrontends/schema.json).
* **`scripts/audit/validate-microfrontends-config.ts`** — fast structural validator (kebab-case names, single default app, paths start with `/`, literal-overlap detection, soft check that paths exist in `src/App.tsx` / `src/routes/`). Wired to `npm run audit:microfrontends`.
* **`docs/architecture/decisions/ADR-017-microfrontends-pwa-scoping.md`** — formal ADR amending ADR-013 to handle per-zone PWA scopes.
* **This runbook** — step-by-step activation instructions, env-var layout, CI wiring, Playwright proxy invocation, rollback plan.

**What is intentionally NOT shipped:**

* The `@vercel/microfrontends` Vite plugin is **not** installed and **not** wired into `vite.config.ts`. Adding it without a corresponding Vercel project + microfrontends group does nothing useful and would only add a transitive dep. The activation steps below cover the install.
* No new Vercel projects exist yet. Creation is a dashboard action only a project owner can perform.
* The `microfrontends.json` file is **inert** until a Vercel Microfrontends Group exists in the Vercel team and the application names there match the names in this file.

This is intentional: by the time someone is ready to flip the switch, the design choices, validation, and PWA strategy are all already in code review.

> ⚠️ **Editing `microfrontends.json` — strict-keys gotcha**
>
> Both Turborepo (`turbo` 2.9+) and the Vercel runtime parse `microfrontends.json` and **reject any unknown keys** — including JSON-comment-style fields like `_comment`, `_comment_*`, etc. If an unknown key slips in, `turbo run …` aborts with `Unable to parse JSON: Found an unknown key …` and **every Turborepo task fails before it starts**. This regression was introduced once already (PR #145, fixed in the verification PR) when this runbook's author shipped Vercel-style `_comment_*` keys.
>
> Allowed top-level keys: `$schema`, `applications`, `options`. Allowed per-application keys: `routing`, `development`, `packageName`, `assetPrefix`. Allowed per-routing-rule keys: `group`, `paths`, `flag`. Allowed `development` keys: `fallback`, `local`, `task`.
>
> Keep narrative notes here (in this runbook), not in `microfrontends.json`. The `npm run audit:microfrontends` validator now hard-fails on unknown keys to prevent this from ever shipping again.

***

## Activation overview (project-owner steps)

Activation is sequenced in five phases. Steps 1–2 are dashboard / infrastructure actions; steps 3–5 are code PRs.

| Step                                                              | Who                      | Time                       | Reversible?                         |
| ----------------------------------------------------------------- | ------------------------ | -------------------------- | ----------------------------------- |
| 1. Create the second Vercel project (`encoreos-public`)           | Project owner            | \~5 min                    | trivially (delete project)          |
| 2. Create the Microfrontends Group + add both projects            | Project owner            | \~5 min                    | trivially (delete group)            |
| 3. Land the wiring PR (Vite plugin install + plugin registration) | Engineer + project owner | \~half-day                 | revert PR                           |
| 4. Promote a preview build with the new routing live              | Project owner            | \~10 min                   | Vercel Instant Rollback (one-click) |
| 5. Production cutover                                             | Project owner            | \~5 min after preview soak | Vercel Instant Rollback             |

If anything looks off at any step, **stop**. The `microfrontends.json` file in `prod` does nothing until the Vercel Group is wired up; rolling forward in code is decoupled from rolling forward in production routing.

***

## Step 1 — Create the second Vercel project

In the Vercel dashboard:

1. **Add New… → Project → Continue with this Repository** (`Encore-OS/encoreos`).
2. Project name: `encoreos-public`.
3. **Framework preset:** Vite. **Root directory:** `.` (same monorepo root).
4. **Build & Development Settings:**
   * Build command: `npm run build` (same as the main project; the Vite plugin handles asset prefixing once installed).
   * Output directory: `dist`.
   * Install command: `npm ci --legacy-peer-deps`.
5. **Environment variables:** none for now. Build-time Supabase URL/key are statically defined in `vite.config.ts` per `VERCEL_PROJECT_ID` (see `AGENTS.md` § Cursor Cloud caveats and `docs/development/VERCEL_ENV_REMOVAL_EXECUTION_2026-04-20.md`).
6. **Important:** before adding `encoreos-public`'s `prj_*` ID to `vite.config.ts`'s `VERCEL_PROJECT_TO_SUPABASE` map, decide which Supabase target it points to. Recommend **same as the parent project's environment** so `encoreos_dev`'s public zone hits dev Supabase, and `encoreos_prod`'s public zone hits prod Supabase.
7. Do **not** deploy yet. Pause auto-deploy under **Settings → Git → Deploy Hooks** if the project tries to build immediately.

Repeat for any additional zones beyond the recommended split (the recommendations doc § 5.5 suggests a possible second wave: `encoreos-clinical` for `/cl/*` + `/pm/*` — only do this if Phase 1+2 measurements show it's still needed).

***

## Step 2 — Create the Microfrontends Group

In the Vercel dashboard:

1. Team **Settings → Microfrontends → Create Group**.
2. Group name: `encoreos`.
3. **Default application:** `encoreos-app` (the original project).
4. Add `encoreos-public` to the group.
5. **Fallback environment:** `Same Environment` (so preview builds without a matching `encoreos-public` build fall back to the production deployment of `encoreos-public` rather than the production deployment of `encoreos-app`).
6. Confirm pricing acknowledgement: 2 microfrontend projects per group are included on Pro; the proposed split fits in the free tier as long as we never add a third zone.

The group will not change behavior until the next deployment of the **default application** that contains a `microfrontends.json` file. Since this PR commits `microfrontends.json` already, the next merge to `prod` after activation will start routing.

> ⚠️ Sequence matters. Create the group **before** merging the wiring PR (step 3). If the wiring PR merges first while the group does not exist, Vercel logs a deployment warning but does no harm — the file is treated as inert.

***

## Step 3 — Land the wiring PR

This is a small follow-up PR — open it once steps 1 and 2 are complete:

```bash theme={null}
# Install the Vite plugin (one new dep)
npm install --save @vercel/microfrontends --legacy-peer-deps
```

Add to `vite.config.ts`:

```ts theme={null}
import { microfrontends } from '@vercel/microfrontends/experimental/vite';

// inside the plugins array:
microfrontends({
  // The Vite plugin auto-detects the application name from VERCEL_PROJECT_ID
  // matched against the Vercel project names in microfrontends.json.
  // No options needed for the basic case.
}),
```

Update `package.json`:

```json theme={null}
{
  "scripts": {
    "mf:proxy": "microfrontends proxy --local-apps encoreos-app encoreos-public",
    "mf:port": "microfrontends port"
  }
}
```

Verify locally:

```bash theme={null}
npm run audit:microfrontends    # validate the config
npm run build                   # confirm Vite plugin doesn't break the build
npm run mf:proxy                # in one terminal
npm run dev                     # in another
# Visit the proxy URL printed by `mf:proxy` (typically http://localhost:3024)
# Try /, /auth, /pm/kiosk/test, /portal/login — all should resolve
```

The wiring PR should also:

* Update `.github/workflows/build.yml` to run `npm run audit:microfrontends` as a blocking step.
* Update `.github/workflows/build.yml` to set `MFE_DISABLE_LOCAL_PROXY_REWRITE=1` so existing E2E tests that hit individual project URLs continue to work.
* Add `TURBO_TOKEN` / `TURBO_TEAM` GitHub Actions secrets if you also want Vercel Remote Cache active for the `encoreos-public` project (see `docs/development/TURBOREPO_USAGE.md`).

***

## Step 4 — Preview soak

1. After the wiring PR merges, open a fresh PR (any small change). Vercel will build BOTH `encoreos-app` AND `encoreos-public` for the preview commit.
2. Visit the preview URL of `encoreos-app`. You should see:
   * `/` → main app (HR, etc.) — same as today.
   * `/auth` → routed to `encoreos-public`. Look for the small "M" badge on the deployment page (Vercel Microfrontends indicator).
   * `/pm/kiosk/test` → routed to `encoreos-public`.
3. Open the Vercel Toolbar on the preview, expand the **Microfrontends Panel**, and confirm the routing table matches `microfrontends.json`.
4. Run the smoke E2E suite against the preview:
   ```bash theme={null}
   npm run test:e2e:smoke
   ```
   If any test asserts a hard navigation between `/auth` and any authenticated route, expect it to *still pass* but to take an extra \~200 ms (the cross-zone hard-navigation cost). If any test relies on shared client state across zones, it will fail — those tests need to be rewritten or marked skip-on-microfrontends.

Soak the preview for **at least 24 hours** before proceeding. Watch the Vercel Speed Insights dashboard for the preview to confirm INP/LCP are not regressing on routes that cross zones.

***

## Step 5 — Production cutover

1. Promote the soaked preview deployment to production via the Vercel dashboard's "Promote to Production" button.
2. Watch Sentry for new error volume in the first 30 minutes — particularly:
   * Hydration mismatches (none expected; Vite SPA, no SSR).
   * Auth-redirect loops (the `/auth` zone now lives behind microfrontends path routing; verify `useCurrentUser` still works after the hard-nav from `/auth` → `/`).
   * PWA service worker registration errors (covered by ADR-017; both zones must agree on SW scope).
3. If anything looks off, hit Vercel Instant Rollback on the **default application** (`encoreos-app`). Microfrontends routing reverts to the previous `microfrontends.json` (which, before this whole rollout, was implicit — single app — so the old behavior is fully restored).

***

## Local development

Once steps 1–3 are complete, the local DX is:

```bash theme={null}
# Terminal 1: run the local proxy that knows about the routing config
npm run mf:proxy

# Terminal 2: run the dev server for the zone you're working on
npm run dev    # encoreos-app
# OR
npm run dev    # if you're working on encoreos-public, run the same; the
               # plugin auto-targets based on VERCEL_PROJECT_ID, defaulting to encoreos-app

# Visit the proxy URL printed in terminal 1 (typically http://localhost:3024)
```

The proxy routes requests for zones you are NOT running locally to the production fallback — so you only need to run one zone at a time.

To hit the local zone directly, bypassing the proxy, set `MFE_DISABLE_LOCAL_PROXY_REWRITE=1`.

***

## Environment variable layout

| Variable                            | Where set                                 | Used for                                                                                                                                                                                                             |
| ----------------------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `VERCEL_PROJECT_ID`                 | Set automatically by Vercel at build time | `vite.config.ts` uses this to pick `dev` vs `prod` Supabase target. **Must be added to `VERCEL_PROJECT_TO_SUPABASE` for the new `encoreos-public` project IDs (one for dev, one for prod) before the first deploy.** |
| `MFE_DISABLE_LOCAL_PROXY_REWRITE`   | Local `.env` only                         | Bypasses the local proxy when set to `1`. Useful for E2E tests that hit a specific zone directly.                                                                                                                    |
| `MFE_DEBUG`                         | Local `.env` only                         | Verbose logging from the proxy and the Vite plugin.                                                                                                                                                                  |
| `TURBO_TOKEN` / `TURBO_TEAM`        | GitHub Actions secrets + dashboard        | Vercel Remote Cache for both `encoreos-app` and `encoreos-public`. Optional but recommended.                                                                                                                         |
| `AUTOMATION_BYPASS_ENCOREOS_PUBLIC` | GitHub Actions secrets                    | If `encoreos-public` ends up behind Vercel Deployment Protection, this is the Protection-Bypass-for-Automation token. Documented in Vercel's microfrontends local-development docs.                                  |

Do **not** add new project-level environment variables in the Vercel dashboard for the public client config (Supabase URL, etc.). The constitution / `AGENTS.md` enforces that those stay in `vite.config.ts` per `VERCEL_PROJECT_ID`.

***

## Rollback plan

| Scenario                                           | Action                                                                                                                                                                                   |
| -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Bug in production routing                          | Vercel Instant Rollback on `encoreos-app` (or `encoreos-public`) — restores the routing of the previous deployment.                                                                      |
| Need to roll back the entire microfrontends scheme | Vercel dashboard → Microfrontends → delete the group. The default app reverts to receiving every path, exactly as today. The `microfrontends.json` file in the repo becomes inert.       |
| Need to revert the wiring PR                       | `git revert <wiring-PR-merge-commit>`; the Vite plugin disappears; subsequent builds of `encoreos-app` ignore `microfrontends.json`. The microfrontends group can be deleted at leisure. |

The whole stack is designed so each layer can be rolled back independently. The riskiest operation is the **first promotion to production** (step 5); after that, every change is incremental and well-contained.

***

## What this scaffolding does NOT solve

The recommendations doc (§ 3.5–3.7) calls out four hard problems with microfrontends on a Vite SPA. They remain unsolved by this PR — they're handled at activation time:

1. **Cross-zone shared state.** `OrganizationProvider`, `QueryClientProvider`, Sentry, Sonner, `nuqs`, and the PWA service worker all bootstrap per zone. A user moving between `/auth` and the authenticated app pays a cold cache penalty. Mitigations to evaluate at step 4: `BroadcastChannel` for cross-zone state sync, or accept the cold cache (the public→authenticated transition only happens at sign-in time anyway).
2. **PWA scope.** Each Vercel project generates its own service worker. Only one can install at scope `/`. ADR-017 (companion to this PR) defines the per-zone scope strategy: `encoreos-app` keeps `scope: '/'`; `encoreos-public` either uses sub-scopes (`scope: '/portal/'`, etc.) or disables the SW for that zone entirely.
3. **Dependency duplication.** Each zone bundles its own React, Radix, Supabase client. For a 2-zone split this adds \~300 KB of duplicate vendor code on first hard-nav between zones. Acceptable for the proposed split (the public zone is a thin set of pages); not acceptable for a 12-zone split.
4. **Cross-zone link prefetching.** Vercel's `PrefetchCrossZoneLinks` is **Next.js only**. Any prefetch-on-hover behavior we want has to be implemented manually using Chromium Speculation Rules, the same way Vercel.com does it.

***

## See also

* [`MICROFRONTENDS_ACTIVATION_CHECKLIST.md`](./MICROFRONTENDS_ACTIVATION_CHECKLIST.md) — short pre-flight checklist (this runbook remains canonical)
* [`PERFORMANCE_BASELINE.md`](./PERFORMANCE_BASELINE.md) — bundle / Vitals baselines before cutover
* `microfrontends.json` (repo root) — the proposed routing
* [`scripts/audit/validate-microfrontends-config.ts`](../../scripts/audit/validate-microfrontends-config.ts)
* [`docs/architecture/decisions/ADR-017-microfrontends-pwa-scoping.md`](../architecture/decisions/ADR-017-microfrontends-pwa-scoping.md)
* `docs/recommendations/DEV_VELOCITY_AND_ARCHITECTURE_PLAN_2026-04-22.md` § 3 (deep review of Vercel Microfrontends) and § 5.5 (the proposed split) — lands via PR #137
* [Vercel Microfrontends docs](https://vercel.com/docs/microfrontends)
* [Vercel Microfrontends local dev](https://vercel.com/docs/microfrontends/local-development)
* [`vite.config.ts`](../../vite.config.ts) — the file the wiring PR will modify
* [`vercel.json`](../../vercel.json) — the file the wiring PR may modify (`ignoreCommand` may need to consider `microfrontends.json` changes)
