Files
WRNexusJS/docs/superpowers/plans/2026-08-19-legacy-and-config-cleanup.md
T

538 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.