# 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 { x }
}
`;
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)[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.