# 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.