# OpenLife v1.18.2 — Changelog (template hotfix)

**Branch:** `main`
**Predecessor:** v1.18.1 (tagged 2026-05-22)
**Released:** 2026-05-22

Hotfix release. The bundled `nextjs-supabase-clerk-stripe` template
shipped in v1.16.0 had **5 latent bugs** that only surfaced when a user
ran the full `npm install && npm run build && npm run dev` flow. The
v1.16/v1.17/v1.18.x test suites only verified file copy + token
substitution — never built or ran the scaffolded output.

This release fixes all 5 bugs in the template. Engine + CLI unchanged.

## What was broken

### 🔴 Bug 1 — Stripe apiVersion outdated (build-fatal)

`lib/stripe.ts` pinned `apiVersion: '2024-12-18.acacia'` but Stripe SDK
17.x (current) requires `'2025-02-24.acacia'`. The mismatch produced a
TypeScript type error that failed `npm run build` AFTER successful
compilation:

```
Type error: Type '"2024-12-18.acacia"' is not assignable to type '"2025-02-24.acacia"'.
```

**Fix:** bump apiVersion to `'2025-02-24.acacia'` in both the template
and the `stripe-billing-wire` SKILL.md.

### 🟡 Bug 2 — middleware.ts deprecation note missing (cosmetic)

Next.js 16.2+ deprecated the `middleware.ts` convention in favor of
`proxy.ts` (same file shape, just renamed). The deprecation is a
warning, not an error, but the template gave no guidance.

**Fix:** added a long-form comment in `middleware.ts` explaining the
deprecation + migration path. Clerk still recommends `middleware.ts`,
so we keep the original name.

### 🔴 Bug 3 — Route group `(dashboard)` conflicted with `/` (runtime-fatal)

The template structured dashboard pages under `app/(dashboard)/` —
parens make it a **route group** (no URL segment). So
`app/(dashboard)/page.tsx` was actually `/`, conflicting with
`app/page.tsx`. The build picked one, the URL `/dashboard` returned 404.

**Fix:** renamed `app/(dashboard)/` → `app/dashboard/`. Routes now
correctly resolve to:
- `/dashboard` (was: conflict with landing)
- `/dashboard/billing` (was: didn't exist)

Middleware matcher updated: `'/(dashboard)(.*)'` → `'/dashboard(.*)'`.

### 🔴 Bug 4 — Root layout needed `force-dynamic` (build-fatal)

`<ClerkProvider>` wraps EVERYTHING in `app/layout.tsx`, including the
auto-generated `_not-found` page. Next 16 tried to SSG `_not-found`,
needed Clerk's publishableKey at build time, failed with:

```
Error: @clerk/clerk-react: Missing publishableKey.
Export encountered an error on /_not-found/page: /_not-found, exiting the build.
```

**Fix:** added `export const dynamic = 'force-dynamic';` to
`app/layout.tsx`. SaaS templates are SSR-heavy anyway — every page
calls Clerk auth — so static optimization isn't a real loss.

### 🔴 Bug 5 — Middleware regex used negative lookahead (runtime-fatal)

The middleware matcher had:
```ts
const isProtectedRoute = createRouteMatcher([
  '/dashboard(.*)',
  '/api/(?!webhooks)(.*)',  // negative lookahead
]);
```

Clerk's `createRouteMatcher` uses `path-to-regexp` v6 internally, which
doesn't support negative lookaheads. At runtime:

```
⨯ Error: Invalid path: /api/(?!webhooks)(.*).
Pattern cannot start with "?" at 6
```

**Fix:** inverted the logic to an explicit public allowlist:
```ts
const isPublicRoute = createRouteMatcher([
  '/',
  '/sign-in(.*)',
  '/sign-up(.*)',
  '/api/webhooks/(.*)',
]);

export default clerkMiddleware(async (auth, req) => {
  if (!isPublicRoute(req)) {
    await auth.protect();
  }
});
```

This is the pattern Clerk's docs recommend, and it's regex-safe.

## How these bugs survived 4 releases

The scaffold engine test (v1.16) verified:
- File count matches expectation
- {{token}} substitution applied correctly
- Filenames with `__token__` renamed
- Manifest.exclude[] honored

It did NOT verify the scaffolded output ACTUALLY BUILDS. The bundled
template was treated as static content — never built in CI.

**v1.18.2 adds NO new tests** (the fixes are template content, not
engine). But the next minor release (v1.19.0) WILL add a CI step that
scaffolds the bundled template and runs `npm install && npm run build`
on it, to catch this class of regression early.

## Verification (manual smoke that caught these)

```bash
openlife scaffold smoke --template=nextjs-supabase-clerk-stripe
cd smoke
cp .env.example .env.local
# fill .env.local with fake values (pk_test_fake etc.)
npm install
npm run build          # MUST succeed (was failing on Stripe apiVersion)
npm run dev            # MUST start without errors
curl http://localhost:3000/                # MUST respond
curl http://localhost:3000/sign-in          # MUST respond (public)
curl http://localhost:3000/dashboard        # MUST respond (Clerk will 401/redirect with fake key)
```

All 5 bugs surface in this flow. None were caught by the v1.16-v1.18
test suite because that suite stopped at scaffold time.

## Files changed

- `templates/nextjs-supabase-clerk-stripe/app/layout.tsx` — `force-dynamic`
- `templates/nextjs-supabase-clerk-stripe/middleware.ts` — public-allowlist + deprecation note
- `templates/nextjs-supabase-clerk-stripe/lib/stripe.ts` — apiVersion bump
- `templates/nextjs-supabase-clerk-stripe/app/dashboard/` (renamed from `(dashboard)`)
- `.catalog/skills/stripe-billing-wire/SKILL.md` — apiVersion bump

## Tarball

No structural change. Same 1234 files, same ~2.0MB.

## Upgrade

```bash
openlife update --global

# Verify
openlife --version                                                    # 1.18.2
# Test the template
openlife scaffold test --template=nextjs-supabase-clerk-stripe
cd test && npm install && npm run build   # MUST succeed now
```

If you scaffolded with v1.16.0–v1.18.1, you have the broken template.
Either re-scaffold OR manually apply the 5 fixes documented above.
