Files
WRNexusJS/docs/superpowers/specs/2026-08-19-update-migration-design.md
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

141 lines
6.8 KiB
Markdown

# 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:
```wrn
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 end** — `examples/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.