# Legacy and Config Cleanup Implementation Plan

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

**Goal:** Delete the compatibility-flag surface, the `"legacy"` function runtime, dead migrations, and deprecated APIs before the framework's first public release.

**Architecture:** Almost all of this is deletion. Seven config keys are never read by any code, so removing them changes nothing. The one behaviour-sensitive item is the `"legacy"` function runtime, which is mapped to `"shared"` — an equivalent substitution, because an unmarked function is already emitted into both bundles.

**Tech Stack:** Bun, TypeScript, `bun:test`.

**Spec:** `docs/superpowers/specs/2026-08-19-legacy-and-config-cleanup-design.md`

## Global Constraints

- This plan removes configuration. It adds none.
- The legacy `api` block forms (bare-body `with ($data)`, and `api` inside `ssr {}` / `client {}`) are **out of scope** — they are replaced by the next plan, not deleted here.
- A removed config key must be **rejected loudly**, not silently ignored. Someone with a stale config must be told, not left believing a flag still applies.
- `bun run format` before every commit; the repo gate is `bun run check:production`.
- The editor bundles embed the compiler — rebuild with `bun run --cwd editors/vscode build` after any `packages/syntax` or `packages/compiler` change, or `check:editor-compiler` fails on a stale bundle.
- Do NOT use `node -e`, shell heredocs, or `sed` to write code into files; escaping mangles them silently. Use file editing tools.

---

### Task 1: Replace the `"legacy"` function runtime with `"shared"`

**Files:**

- Modify: `packages/syntax/src/v060.ts` (the `FunctionRuntime` type; the default at ~line 209)
- Modify: `packages/compiler/src/client-codegen.ts` (membership tests at ~lines 173 and 340)
- Modify: `packages/compiler/src/server-codegen.ts` (the `["legacy", "server", "shared"]` list)
- Modify: `packages/compiler/src/codegen.ts` (`targetFunctions`, ~line 1310)
- Test: `packages/compiler/test/legacy-runtime-removal.test.ts`

**Interfaces:**

- Produces: `FunctionRuntime` becomes `"client" | "server" | "shared"`. Later tasks and plans rely on `"legacy"` no longer existing.

**Why this is equivalent, not a behaviour change:** an unmarked `function foo()` currently parses as `"legacy"`, and both codegens include `"legacy"` in their membership tests — `["legacy", "client", "shared"]` for the browser and `["legacy", "server", "shared"]` for the server. So an unmarked function is already emitted into _both_ bundles, exactly like `shared`. `legacyDefaultRuntime` looks like it should modulate this but is never read.

- [ ] **Step 1: Write the failing test**

Create `packages/compiler/test/legacy-runtime-removal.test.ts`:

```ts
import { expect, test } from "bun:test";
import { parse } from "@wrnexus/syntax";
import { generateTargets } from "../src/targets.ts";

const SOURCE = `page Probe {
  functions {
    function unmarkedHelper() {
      return "both";
    }

    client function clientOnly() {
      return "browser";
    }

    server function serverOnly() {
      return "server";
    }
  }

  view { <main>x</main> }
}
`;

test("an unmarked function is emitted into both the browser and server modules", () => {
  // This is the property the "legacy" runtime provided. Removing the variant
  // must not change it.
  const targets = generateTargets(parse(SOURCE));

  expect(targets.browser).toContain("unmarkedHelper");
  expect(targets.server).toContain("unmarkedHelper");
});

test("marked functions still go only where they belong", () => {
  const targets = generateTargets(parse(SOURCE));

  expect(targets.browser).toContain("clientOnly");
  expect(targets.browser).not.toContain("serverOnly");
  expect(targets.server).toContain("serverOnly");
  expect(targets.server).not.toContain("clientOnly");
});

test("no emitted target mentions the removed legacy runtime", () => {
  const targets = generateTargets(parse(SOURCE));

  expect(targets.browser).not.toContain('"legacy"');
  expect(targets.server).not.toContain('"legacy"');
});
```

- [ ] **Step 2: Run the test and record the baseline**

Run: `bun test packages/compiler/test/legacy-runtime-removal.test.ts`
Expected: the first two tests PASS (they describe current behaviour and must keep passing), the third may already pass. This test file is a **regression guard written before the change**, so a green run here is correct — record the output.

- [ ] **Step 3: Remove the `"legacy"` variant from the type and parser**

In `packages/syntax/src/v060.ts`:

```ts
export type FunctionRuntime = "client" | "server" | "shared";
```

And at the parse site (~line 209), change the default:

```ts
let runtime: FunctionRuntime = "shared";
```

- [ ] **Step 4: Drop `"legacy"` from every membership test**

In `packages/compiler/src/client-codegen.ts`, both occurrences:

```ts
    ["client", "shared"].includes(fn.runtime),
```

In `packages/compiler/src/server-codegen.ts`:

```ts
const names = ast.runtimeFunctions
  .filter((fn) => ["server", "shared"].includes(fn.runtime))
  .map((fn) => fn.name);
```

In `packages/compiler/src/codegen.ts`, `targetFunctions`:

```ts
const runtimes =
  target === "browser" ? (["client", "shared"] as const) : (["server", "shared"] as const);
```

Search the repo for any remaining `"legacy"` in these packages and remove each — the string must not survive in `packages/syntax` or `packages/compiler`.

- [ ] **Step 5: Run the tests**

Run: `bun test packages/syntax packages/compiler`
Expected: PASS, including the three guards from Step 1. If the first two now fail, the substitution was not equivalent — stop and report rather than adjusting the test.

- [ ] **Step 6: Rebuild the editor bundles and commit**

```bash
bun run format
bun run --cwd editors/vscode build
git add packages/syntax packages/compiler editors/vscode/src
git commit -m "refactor: replace the legacy function runtime with shared"
```

---

### Task 2: Delete the compatibility surface

**Files:**

- Delete: `packages/styles/src/compatibility.ts`
- Delete: `packages/cli/src/compatibility-command.ts`
- Delete: `packages/cli/test/compatibility.test.ts`
- Modify: `packages/styles/src/config.ts` (`FunctionsConfig` ~233, `CompatibilityConfig` ~242, `AppConfig extends CompatibilityPolicy` ~249, the `functions?:` and `compatibility?:` members, the `resolveCompatibility` validation ~619, and the `CompatibilityPolicy` import ~23)
- Modify: `packages/styles/src/index.ts` (the `./compatibility.ts` exports at ~lines 39-45)
- Modify: `packages/cli/src/index.ts` (dispatch at ~line 295, help text at ~line 75)
- Modify: `packages/cli/src/create.ts` (~lines 263, 278-283)
- Modify: `packages/cli/src/update.ts` (the config insertion string at ~line 389)
- Modify: `packages/styles/test/config.test.ts` (assertions on the removed keys)
- Modify: `examples/basic-app/wrnexus.config.ts`
- Test: `packages/styles/test/removed-config-keys.test.ts`

**Interfaces:**

- Consumes: nothing from Task 1.
- Produces: `AppConfig` no longer extends `CompatibilityPolicy` and has no `compatibility` or `functions` members. `@wrnexus/styles` no longer exports `resolveCompatibility`, `isCompatibilityDate`, `CURRENT_COMPATIBILITY_DATE`, `CURRENT_FRAMEWORK_BEHAVIOUR`, `CompatibilityPolicy`, or `CompatibilityReport`.

**These seven keys are never read.** `legacyEmit`, `legacyEventProps`, `legacyComponentDiscovery`, `stringLayouts`, and `legacyDefaultRuntime` appear only in the type declaration, `create.ts`, and `update.ts`. `compatibilityDate` and `frameworkBehaviour` feed only a printed report and one validation. Removing them changes no behaviour.

- [ ] **Step 1: Write the failing test**

Create `packages/styles/test/removed-config-keys.test.ts`:

```ts
import { expect, test } from "bun:test";
import { validateConfig } from "../src/config.ts";

// A stale config must fail loudly. Silently ignoring a removed key leaves
// someone believing a flag still applies.
const REMOVED = [
  { key: "compatibilityDate", config: { compatibilityDate: "2026-08-02" } },
  { key: "frameworkBehaviour", config: { frameworkBehaviour: 1 } },
  { key: "functions", config: { functions: { legacyDefaultRuntime: "current" } } },
  { key: "compatibility", config: { compatibility: { legacyEmit: false } } },
];

for (const { key, config } of REMOVED) {
  test(`a config still setting "${key}" is rejected with a message naming it`, () => {
    const issues = validateConfig(config as never);
    const match = issues.find((issue) => issue.path === key || issue.path.startsWith(`${key}.`));

    expect(match).toBeDefined();
    expect(match!.severity).toBe("error");
    expect(match!.message.toLowerCase()).toContain("removed");
  });
}

test("a config without those keys is accepted", () => {
  const issues = validateConfig({} as never);

  expect(issues.filter((issue) => issue.severity === "error")).toEqual([]);
});
```

If `validateConfig` is not the exported name in `packages/styles/src/config.ts`, use whichever function that module exports for validation and keep the assertions identical.

- [ ] **Step 2: Run the test to verify it fails**

Run: `bun test packages/styles/test/removed-config-keys.test.ts`
Expected: FAIL — the keys are currently accepted, so no issue is produced.

- [ ] **Step 3: Delete the compatibility module and its command**

```bash
git rm packages/styles/src/compatibility.ts packages/cli/src/compatibility-command.ts packages/cli/test/compatibility.test.ts
```

In `packages/styles/src/index.ts`, remove the whole `./compatibility.ts` export block (both the value exports and the `export type` line).

In `packages/cli/src/index.ts`, remove the `case "compatibility":` dispatch and the `wrnexus compatibility …` line from the help text.

- [ ] **Step 4: Remove the config members and add the rejections**

In `packages/styles/src/config.ts`: delete the `CompatibilityPolicy` import, the `FunctionsConfig` and `CompatibilityConfig` interfaces, the `functions?:` and `compatibility?:` members of `AppConfig`, `extends CompatibilityPolicy` on `AppConfig`, and the `resolveCompatibility` validation block.

Then add the rejections so a stale config fails loudly:

```ts
const REMOVED_CONFIG_KEYS = [
  "compatibilityDate",
  "frameworkBehaviour",
  "functions",
  "compatibility",
] as const;

for (const key of REMOVED_CONFIG_KEYS) {
  if ((config as Record<string, unknown>)[key] !== undefined) {
    issues.push({
      path: key,
      severity: "error",
      message: "was removed; delete it from the configuration",
    });
  }
}
```

Place this beside the other validation pushes, using whatever local variable that function accumulates issues in.

- [ ] **Step 5: Stop scaffolding and inserting the keys**

In `packages/cli/src/create.ts`, delete the `compatibilityDate`, `frameworkBehaviour`, and `functions: { legacyDefaultRuntime: … }` lines from the generated config.

In `packages/cli/src/update.ts` (~line 389), remove `functions: { legacyDefaultRuntime: "current" },` and the whole `compatibility: { … },` fragment from the insertion string.

- [ ] **Step 6: Trim the example app config**

In `examples/basic-app/wrnexus.config.ts`, delete `compatibilityDate`, `frameworkBehaviour`, `functions`, and `compatibility`.

- [ ] **Step 7: Update the existing config tests**

`packages/styles/test/config.test.ts` asserts on the removed keys. Remove those assertions. Do not weaken any assertion that is still meaningful — if a test only existed to cover compatibility, delete the whole test.

- [ ] **Step 8: Run the tests**

Run: `bun test packages/styles packages/cli`
Expected: PASS, including the new rejection tests.

- [ ] **Step 9: Commit**

```bash
bun run format
git add -A packages/styles packages/cli examples/basic-app
git commit -m "refactor: delete the compatibility config surface"
```

---

### Task 3: Drop migrations below 0.8.0

**Files:**

- Modify: `packages/cli/src/update.ts` (all `Migration` entries with `version` below `"0.8.0"`)
- Test: `packages/cli/test/update-migration-floor.test.ts`

**Interfaces:**

- Consumes: nothing.
- Produces: the migration list starts at `0.8.0`.

`update.ts` holds 111 migrations reaching back to `0.2.8`. The framework is pre-public and the only projects run `0.8.x`, so everything below the floor is unreachable.

- [ ] **Step 1: Write the failing test**

Create `packages/cli/test/update-migration-floor.test.ts`:

```ts
import { expect, test } from "bun:test";
import { readFileSync } from "node:fs";
import { join } from "node:path";

test("no migration targets a version below 0.8.0", () => {
  const source = readFileSync(join(import.meta.dir, "../src/update.ts"), "utf8");
  const versions = [...source.matchAll(/version:\s*"([0-9.]+)"/g)].map((match) => match[1]!);

  expect(versions.length).toBeGreaterThan(0);

  const belowFloor = versions.filter((version) => {
    const [major, minor] = version.split(".").map(Number);
    return major! === 0 && minor! < 8;
  });

  expect(belowFloor).toEqual([]);
});
```

- [ ] **Step 2: Run the test to verify it fails**

Run: `bun test packages/cli/test/update-migration-floor.test.ts`
Expected: FAIL, listing the `0.2.x`–`0.7.x` versions.

- [ ] **Step 3: Delete the migrations below the floor**

Remove every `Migration` object whose `version` is below `"0.8.0"`, along with any helper function that becomes unused as a result. Keep every `0.8.x` entry.

After deleting, search for now-unreferenced helpers in the file and remove them too — an unused private helper is dead code, and the linter will flag it.

- [ ] **Step 4: Run the tests**

Run: `bun test packages/cli`
Expected: PASS. Existing update tests that exercised removed migrations should be deleted with them; do not keep a test that asserts nothing.

- [ ] **Step 5: Verify `update` still runs end to end**

```bash
bun run --cwd examples/basic-app wrnexus update --dry-run
```

Expected: completes without error and reports no pending migrations for an app already at the current version. Paste the output into the commit body if it is short.

- [ ] **Step 6: Commit**

```bash
bun run format
git add packages/cli
git commit -m "chore: drop update migrations below 0.8.0"
```

---

### Task 4: Remove the deprecated compiler re-export shims

**Files:**

- Modify: `packages/compiler/src/codegen.ts` (~line 20, the `./parser.ts` import)
- Modify: `packages/compiler/src/native-codegen.ts` (~line 1, the `./parser.ts` import)
- Delete: `packages/compiler/src/parser.ts`, `packages/compiler/src/tokenizer.ts`, `packages/compiler/src/types.ts`

**Interfaces:**

- Consumes: nothing.
- Produces: nothing new; imports move to `@wrnexus/syntax`.

**Order matters.** These three files are two-line re-exports marked deprecated, but `codegen.ts` and `native-codegen.ts` still import from them. Deleting the files first breaks the build.

- [ ] **Step 1: Repoint the imports**

In `packages/compiler/src/codegen.ts`, change:

```ts
import { VOID_ELEMENTS, type Attr, type DataMode, type PageAst, type ViewNode } from "./parser.ts";
```

to import the same names from `@wrnexus/syntax`. If the file already imports from `@wrnexus/syntax`, merge them into that one import rather than adding a second.

In `packages/compiler/src/native-codegen.ts`, change:

```ts
import type { Attr, PageAst, ViewNode } from "./parser.ts";
```

the same way.

- [ ] **Step 2: Verify nothing else imports the shims**

```bash
grep -rn "from \"./parser.ts\"\|from \"./tokenizer.ts\"\|from \"./types.ts\"" packages/compiler/src/
```

Expected: no output. If anything remains, repoint it before continuing.

- [ ] **Step 3: Delete the shims**

```bash
git rm packages/compiler/src/parser.ts packages/compiler/src/tokenizer.ts packages/compiler/src/types.ts
```

- [ ] **Step 4: Run the tests**

Run: `bun test packages/compiler && bun run typecheck`
Expected: PASS.

- [ ] **Step 5: Rebuild the editor bundles and commit**

```bash
bun run format
bun run --cwd editors/vscode build
git add -A packages/compiler editors/vscode/src
git commit -m "refactor: drop the deprecated compiler re-export shims"
```

---

### Task 5: Remove the deprecated `@wrnexus/auth` options

**Files:**

- Modify: `packages/auth/src/http/index.ts` (~lines 56-59)
- Modify: `packages/auth/src/plugin.ts` (~lines 55-58)
- Modify: `packages/auth/src/types.ts` (~line 423)
- Modify: `packages/auth/src/engine.ts` (~lines 163-165 and 179-181)
- Modify: `packages/auth/test/http.test.ts`, `packages/auth/test/plugin.test.ts`, `packages/auth/test/engine.test.ts`

**Interfaces:**

- Consumes: nothing.
- Produces: nothing new. Options are removed, not renamed.

**These are our own superseded options, not an out-of-date dependency.** The current form is already what `examples/auth-showcase/app/lib/auth.ts` uses — it passes `onSignedIn` / `onSignedOut` to `createAuthEngine`, which is correct and must not change. The deprecated members are the same names on _different_ option objects.

- [ ] **Step 1: Confirm the blast radius before deleting**

```bash
grep -rn "onSuccessfullSignUp" packages/ examples/ services/ | grep -v dist/
grep -rn "onSignedIn\|onSignedOut" packages/ examples/ --include=*.ts | grep -v "packages/auth/src" | grep -v dist/
```

Expected: `onSuccessfullSignUp` has zero references. The `onSignedIn` / `onSignedOut` hits are `packages/auth/test/http.test.ts`, `packages/auth/test/plugin.test.ts`, and `examples/auth-showcase/app/lib/auth.ts`. **The example is the correct `createAuthEngine` form and must be left alone.** Record what you found; if the results differ from this, stop and report before deleting anything.

- [ ] **Step 2: Remove the option declarations**

Delete `onSignedIn` and `onSignedOut` (and their `@deprecated` comments) from the options interface in `packages/auth/src/http/index.ts` and from `packages/auth/src/plugin.ts`. Delete `onSuccessfullSignUp` from `packages/auth/src/types.ts`. Delete the `rpId` and `origin` members from both verification signatures in `packages/auth/src/engine.ts`.

Then remove the code that reads them. The `rpId` / `origin` values are already ignored — verification uses the values bound to the issued challenge — so removing them changes no behaviour.

- [ ] **Step 3: Update the tests that exercised the deprecated paths**

`packages/auth/test/http.test.ts` and `plugin.test.ts` pass the deprecated options. Rewrite each to use the `createAuthEngine` form where the test is still meaningful, and delete the test where its only purpose was to cover the deprecated alias.

`packages/auth/test/engine.test.ts` passes `rpId` / `origin` to verification. Remove those arguments; the assertions on the verification result should be unchanged, which is the evidence that the options were inert.

- [ ] **Step 4: Run the tests**

Run: `bun test packages/auth && bun run typecheck`
Expected: PASS.

- [ ] **Step 5: Confirm no `@deprecated` markers remain in auth**

```bash
grep -rn "@deprecated" packages/auth/src/
```

Expected: no output.

- [ ] **Step 6: Commit**

```bash
bun run format
git add -A packages/auth
git commit -m "refactor: remove the deprecated auth options"
```

---

### Task 6: Full gate

**Files:**

- Modify: whatever the gate reports as stale (generated types, public API baseline, editor bundles)

**Interfaces:**

- Consumes: Tasks 1-5.
- Produces: a green `check:production`.

- [ ] **Step 1: Rebuild the editor bundles**

```bash
bun run --cwd editors/vscode build
```

The compiler and language server are embedded there and both changed.

- [ ] **Step 2: Run the full gate**

```bash
bun run format
bun test
bun run typecheck
bun run check:production
```

- [ ] **Step 3: Regenerate anything the gate reports as stale**

`check:public-api` fails when exports change — and this plan removed several from `@wrnexus/styles`. Run `bun run generate:public-api`, then **read the diff and confirm it is removals only**. An unexpected addition means something was exported by accident.

`check:generated-types` may need `bun run --cwd examples/basic-app wrnexus generate types`.

- [ ] **Step 4: Re-run the gate until green**

```bash
bun run check:production
```

Expected: exit 0.

- [ ] **Step 5: Commit**

```bash
git add -A
git commit -m "chore: regenerate baselines after the legacy cleanup"
```

---

## Notes for the executor

- **The seven config keys are dead.** If you find code that actually reads one, stop and report — the spec's central claim would be wrong and the plan needs revisiting.
- **Task 1 is the only behaviour-sensitive change.** Its first two tests describe current behaviour and must pass both before and after. If they fail after, the substitution was not equivalent; report rather than editing the test.
- **The auth example is already correct.** `examples/auth-showcase` uses `createAuthEngine({ onSignedIn })`, which is the current API, not the deprecated one.
- **If a test would still pass with the code it guards deleted, it is not a test.** Delete the implementation, watch it fail, restore it.
