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>
132 lines
6.4 KiB
Markdown
132 lines
6.4 KiB
Markdown
# Editor support for the new syntax — Design
|
|
|
|
**Date:** 2026-08-19
|
|
**Status:** Approved for implementation
|
|
**Scope:** Language server and VS Code extension updated for `apis { }`, the `api.<name>()` call, and
|
|
the removal of the mode data blocks.
|
|
|
|
**Depends on:** `2026-08-19-apis-block-design.md`. The syntax must exist before the editor can
|
|
describe it.
|
|
|
|
## Goal
|
|
|
|
A syntax change that the editor does not know about is worse than no change: valid code is
|
|
red-underlined, removed constructs still autocomplete, and the new block gets no highlighting. This
|
|
spec keeps the tooling level with the language.
|
|
|
|
It also settles a question left open by earlier work: whether type errors from the generated
|
|
assertions actually appear inside the `.wrn` file, or only in the generated file. That was never
|
|
verified — it was inferred from reading source — and inference is not good enough for the thing
|
|
developers rely on to tell them their code is wrong.
|
|
|
|
### Non-goals
|
|
|
|
- New editor features unrelated to this syntax change.
|
|
- Editors other than VS Code beyond what standard LSP provides.
|
|
- HTML formatting — `formatWrn` still owns markup.
|
|
|
|
## What exists
|
|
|
|
- **Grammar:** `editors/vscode/syntaxes/wrn.tmLanguage.json` names block keywords directly —
|
|
`api` appears 4 times, `client` 6, `server` 5, `ssr` once.
|
|
- **Keyword list:** `WRN_KEYWORDS` in `packages/language-server/src/index.ts` drives completion and
|
|
includes `api` but not `apis`.
|
|
- **Extension completions:** `editors/vscode/src/completion.js` carries block snippets.
|
|
- **Server features:** completion, hover, folding, linked editing, tag completion, and TypeScript
|
|
diagnostics over a virtual document.
|
|
|
|
## Changes by surface
|
|
|
|
### Grammar
|
|
|
|
Add `apis` as a block keyword. Remove the `ssr` / `client` **data block** patterns, keeping `client`
|
|
and `server` where they mean other things — `client state { }`, the `runtime` values, and the
|
|
function modifiers in `functions { }`. This is the change most likely to over-reach: `client` is one
|
|
word with several jobs in this language, and blanket removal would un-highlight constructs that
|
|
still exist.
|
|
|
|
Highlight an `apis` entry's shape — name, method, path — so a declaration reads as a declaration
|
|
rather than as loose identifiers.
|
|
|
|
### Keyword and block completion
|
|
|
|
- `apis` joins `WRN_KEYWORDS`.
|
|
- A snippet for the container and a snippet for an entry, including the `request` / `response` /
|
|
`error` sections, so the shape is discoverable without the docs.
|
|
- `ssr` and `client` data-block snippets are removed from `completion.js`. Offering a construct the
|
|
compiler rejects is worse than offering nothing.
|
|
|
|
### Call completion — the feature worth building
|
|
|
|
Inside a function body, `api.` should complete to the names declared in that page's `apis { }`
|
|
block, with the method and path as detail. The server already indexes the document to build
|
|
completions, and the block names are in the AST.
|
|
|
|
This is where the syntax pays off in the editor: the set of legal calls is knowable, so the editor
|
|
should know it. Without it, `api.` is an empty namespace and every call is typed from memory.
|
|
|
|
Hovering a name inside `api.<name>()` shows its method, path, and declared request fields.
|
|
|
|
### The `api=` attribute in markup
|
|
|
|
`api="searchUsers({ name: nameFilter })"` is an attribute whose value is a call expression. The HTML
|
|
service must not flag it as an unknown attribute, and the expression must not be treated as plain
|
|
text. Completion inside the quotes offers the page's block names, matching the `api.` behaviour.
|
|
|
|
### Diagnostics for removed constructs
|
|
|
|
An `ssr { api … }` or `client { api … }` block gets a diagnostic naming `apis { }` as the
|
|
replacement, positioned on the block keyword. The compiler already rejects these; the editor should
|
|
say so while typing rather than at build time, and it should say what to do instead.
|
|
|
|
## Inline type errors — verifying, not assuming
|
|
|
|
The `apis` spec generates assertions into `app/types/wrnexus.generated.api-checks.ts`, and `tsc`
|
|
fails when a block declares a field its endpoint rejects. Whether that failure surfaces **inside the
|
|
`.wrn` file** has never been confirmed.
|
|
|
|
This spec resolves it in two steps, in order:
|
|
|
|
1. **Observe the current behaviour.** With a deliberately wrong field in place, open the page in VS
|
|
Code and record where the error appears: on the block, only in the generated file, or nowhere.
|
|
2. **Act on what is observed.** If the error already surfaces usefully, document it and stop. If it
|
|
appears only in the generated file, map the diagnostic back to the block that produced it — the
|
|
generator knows which page and block each assertion came from, so the mapping is available if it
|
|
is recorded rather than discarded.
|
|
|
|
If step 2 proves larger than this spec can hold, it becomes its own work, and the spec says so
|
|
plainly rather than leaving an unfinished feature implied. **Nothing here should claim inline
|
|
diagnostics work until someone has seen them work.**
|
|
|
|
## Testing
|
|
|
|
**Grammar** — a fixture page using `apis { }`, `client state { }`, `functions { shared function }`,
|
|
and `runtime = "client"` tokenizes correctly; `client` keeps its highlighting everywhere it is still
|
|
valid. This is the guard against over-reaching removal.
|
|
|
|
**Completion**
|
|
|
|
- `apis` is offered at page level; `ssr` / `client` data blocks are not.
|
|
- `api.` inside a function body offers the page's declared names with method and path.
|
|
- Inside `api="…"` in markup, the same names are offered.
|
|
- Outside those contexts, completion is unchanged — the guard that non-API editing is undisturbed.
|
|
|
|
**Hover** — a name inside `api.<name>()` reports its method, path, and request fields.
|
|
|
|
**Diagnostics** — an `ssr { api … }` block produces a diagnostic naming `apis { }`, positioned on
|
|
the block keyword.
|
|
|
|
**Bundles** — `check:editor-compiler`, `check:editor-language-server`, and
|
|
`check:editor-extension` pass. These embed the compiler and language server, so they must be rebuilt
|
|
after the syntax change; a stale bundle fails the gate.
|
|
|
|
**Manual, and recorded in the implementation notes** — open the migrated `examples/basic-app` in VS
|
|
Code: `apis { }` highlights, `api.` completes, a removed construct is flagged, and the inline
|
|
type-error question above is answered by observation.
|
|
|
|
## Deferred
|
|
|
|
- Mapping generated assertion diagnostics back into `.wrn`, if step 2 above proves too large.
|
|
- Moving the remaining component intelligence out of `completion.js` and into the server.
|
|
- Editors other than VS Code.
|