Files
WRNexusJS/docs/superpowers/specs/2026-08-19-update-migration-design.md
T
ClintchizandClaude Opus 5 43652c14af docs: specs for apis blocks, migration, and editor tooling
Three specs completing the set, each depending on the one before it:

- apis {}: one container, mode-less declarations, api.<name>() callable
  anywhere with build-time dispatch, AsyncLocalStorage for server context,
  usage-driven emission, three render-binding forms. Replaces the ssr {} /
  client {} data blocks and the untypeable with($data) legacy body.
- update: migrations to the new syntax. The legacy bare-body rewrite is
  deliberately manual -- which free identifiers are payload fields is not
  knowable from the source, so an automatic guess would compile and be wrong.
- editor tooling: grammar, completions for api. and the api= attribute,
  diagnostics for removed constructs, and resolving by observation whether
  generated type errors surface inline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 22:52:31 +05:30

6.8 KiB

Carrying projects to the new syntax with wrnexus update — Design

Date: 2026-08-19 Status: Approved for implementation Scope: Migrations that take an existing project from today's syntax to the syntax left by the cleanup and apis { } specs.

Depends on: 2026-08-19-legacy-and-config-cleanup-design.md and 2026-08-19-apis-block-design.md. Both define the target this migrates to, so both must land first.

Goal

After the two preceding specs, every existing project is written in a syntax the framework no longer accepts. One wrnexus update should carry a project across — config keys removed, api blocks moved into apis { }, mode-scoped helpers relocated — and, where it cannot do that safely, say so precisely instead of guessing.

What already exists

This is an extension of working machinery, not a new subsystem:

  • Migration { version, id, description, apply(ctx) }, run when from < version <= to.
  • MigrationCtx carries appRoot, from, to, dryRun, a report, and a logger.
  • MigrationReport already separates changedAutomatically, needsReview, parseFailures, ambiguousFunctions, and unresolvedImports.
  • update.ts already imports parse and formatWrn from @wrnexus/syntax, so parsing a .wrn file, transforming it, and re-emitting formatted source is an established pattern here.

The report's shape matters: it was built around the idea that some changes are safe to make and others must be handed back to a human. That distinction is the backbone of this spec.

Non-goals

  • Migrating projects below 0.8.0. The cleanup spec removes those migrations; no such project exists.
  • Rewriting application logic. Only the constructs these specs changed.

The safety contract

A file is transformed correctly, or it is left untouched and reported. There is no third outcome. Concretely:

  1. Parse the file. A parse failure records the path in parseFailures and moves on — the file is never partially rewritten.
  2. Transform, then re-emit through formatWrn.
  3. If any part of a file's transform cannot be completed, the whole file is skipped and recorded in needsReview with the reason and the construct involved.

Running the migration twice must be a no-op: every transform detects already-migrated input and does nothing. Dry-run must report exactly what a real run would change.

The migrations

1. Remove the dead config keys

Delete compatibility, functions, compatibilityDate, and frameworkBehaviour from wrnexus.config.ts. Mechanical, fully automatic, and safe because none of them was ever read.

2. ssr { api … } / client { api … }apis { … }

Move each api entry into a page-level apis { } block, dropping the mode. Sectioned bodies — those already using request / response / error — carry across unchanged, because the payload is already bound to data.

Fully automatic. If a page has entries in both an ssr and a client block sharing a name, that is a duplicate under the new rules and the file is skipped and reported, since choosing which one survives is a decision about intent.

3. ssr { functions { … } }functions { shared function … }

Relocate mode-scoped helpers to the page-level functions { } block with the shared modifier. Automatic. If the page already has a function of the same name, the file is skipped and reported.

4. Legacy bare-body api blocks — needs review, not automatic

This is the one transform that cannot be done safely, and the spec is explicit about it rather than attempting a best effort.

A legacy bare body is evaluated inside with ($data ?? {}), so it references payload fields as bare identifiers:

api ssrUsers GET /api/users/ssr {
  return userNames(users)
}

The sectioned form binds the payload to data, so this must become data.users. But which free identifiers are payload fields is not knowable from the source. In the example, users comes from the response and userNames is a page helper — and nothing in the file distinguishes them. The response shape belongs to the route, and the route may not even be typed.

A migration that guessed would produce code that compiles and is wrong: data.userNames(...) or an untouched users that silently resolves to undefined. That is precisely the silent-wrong-answer failure this project keeps paying for.

So: legacy bare-body blocks are detected, reported in needsReview with the file, the block name, and the free identifiers found, and left untouched. The report tells the author exactly what to decide. wrnexus update prints a short explanation of why this one is manual.

5. Deprecated @wrnexus/auth options

Only if the cleanup spec's optional auth section is included. Rename call sites of the superseded options. Automatic where the rename is unambiguous; reported otherwise.

Version

All of these attach to the release that ships the breaking change. After the cleanup spec the migration floor is 0.8.0, so the list is short and every entry is reachable.

Output

At the end of a run the command prints, in this order: what it changed, what needs review and why, and what failed to parse. A run with anything in needsReview or parseFailures exits non-zero, so a scripted upgrade cannot appear to succeed while leaving a project half-migrated.

Testing

Each migration gets a fixture project and three assertions: the transform produces the expected source, running it a second time changes nothing, and a dry run reports the same set without writing.

  • Config removal — keys gone, rest of the config untouched.
  • api relocation — a page with both ssr and client api blocks lands in one apis { }; entries keep their names, methods, paths, and sections.
  • Name collision across modes — the file is skipped and reported, not silently merged.
  • Mode functions — relocated with the shared modifier; a name collision skips and reports.
  • Legacy bare body — reported in needsReview with the block name and free identifiers, and the file is byte-identical afterwards. This is the most important test in the spec: it pins that the migration does not attempt the rewrite.
  • Parse failure — a malformed .wrn is recorded in parseFailures and left untouched.
  • Exit code — non-zero when anything needs review.
  • End to endexamples/basic-app migrated by the command alone, then built and tested. If the framework's own example cannot be migrated by the tool, the tool is not finished.

What this does not promise

Automated source rewriting cannot be promised as "perfect". What is promised is bounded: every file is either correctly transformed or untouched and named in the report, with the reason. Nothing is half-rewritten, and nothing is guessed. The legacy bare-body case is deliberately manual because a correct automatic answer does not exist.