A handler expression is emitted as text and evaluated when the event fires, so
any loop variable it names has to travel with the element. Components emitted
`data-wrn-loop-locals` for this; pages did not. The same view worked inside a
component and threw ReferenceError inside a page -- with a green build and green
tests, since nothing renders the page in a browser during a build.
The CSR runtime already resolves locals generically via
closest("[data-wrn-loop-locals]"), so only codegen needed to change.
The marker is emitted only on elements that actually bind an event, and the
encoder only when a marker was produced -- but it MUST be emitted whenever one
is, or the render throws on an undefined function instead of the handler
throwing on an undefined variable, which is strictly worse. Covered by its own
test.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The brace scanner knew about strings and comments but had no case for regex
literals. A quote inside one opened a phantom string that swallowed every brace
until the next quote; a lone `{` or `}` inside one miscounted block depth. Both
failed the component with "Unbalanced braces" pointing at the block's first line.
`/-/g` parsed fine, which is why this went unnoticed -- it needs a quote or a
brace inside the pattern to bite.
Regex-vs-division is decided by scanning back to the last significant
character, erring towards division: mistaking division for a regex would
swallow code to the next `/` and lose any braces between. A regex cannot span a
newline, so an unterminated one on the line is treated as "not a regex", which
is what keeps a bare URL in view text intact.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A parent writing @focus on a component tag never heard that component's
own input or button take focus. The runtime bound every output-named DOM
fallback listener in the bubble phase, and focus and blur do not bubble,
so the event fired on the descendant and stopped there. Nothing errored --
the binding simply did nothing.
That made a whole class of declared outputs undeliverable: button.focus,
button.blur, TextLink.focus, TextLink.blur, WysiwygEditor.focus and
WysiwygEditor.blur all advertised events they could never send.
The ui ratchet for outputs nothing emits excluded natively-named outputs
on the grounds that a native event reaches the root anyway. That holds for
click and change, which bubble, and was wrong for focus and blur. Binding
those two in the capture phase makes the exclusion honest rather than
convenient; the ratchet's comment now says so.
Also documents WysiwygEditor as the chrome shell it is: it emits none of
its four outputs itself, it forwards whatever the slotted control raises.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An empty pattern compiles to a regex matching only the empty string, so
every typed value becomes invalid and the form silently refuses to submit
-- no error, no request. @wrnexus/ui's input declares pattern: string = ""
and renders pattern="{pattern}", so every input that did not opt into a
pattern shipped one that could never match. This broke sign-up in a real
app, and only became visible once the dev-server client-module fix let
form enhancements mount at all.
Attributes reach the output through two emitters and both needed it: a
component's interpolated value is baked at render time, so the whole
attribute is now emitted by __wrnOptionalAttr, while a page's static
element is dropped at compile time. Component mounts are excluded, where
the value is a prop being passed down rather than an attribute.
minlength/maxlength/min/max/step/inputmode/accept get the same treatment --
inert when empty, but meaningless too.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The dev server shipped two entries, index and serve-entry, bundled
independently because the publish build set splitting:false. They share
pipeline.ts, which holds mutable module state -- compileCacheDir, set once
at startup by the bootstrap, and browserArtifactPaths, populated during
compilation and read when serving /__wrnexus/client/*. Duplicating the
module duplicated the state, so the writer and the reader addressed
different copies: every component client module 404'd and .wrn compilation
wrote nothing. It works from source, where there is one module instance,
which is why it reached a release. Emitting a shared chunk fixes it for
every package at once.
resetDevCache also ran several hundred lines after the plugin virtual
modules were written into the same directory, deleting them at every boot.
An app with no plugins never noticed; an app with one lost them every time.
Separately, secureCookieOptions spread ...options after its path default,
and setSecureCookie always forwards an explicit path key -- so omitting
path emitted a cookie with no Path at all, which the browser then scoped to
the request's directory.
Verified end to end against a real app installing the published packages:
17 artifacts written, client modules 200, and the sign-in form submits from
the UI and reaches /dashboard.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ships the apis { } block, the legacy/config cleanup, the wrnexus update
migrations, and the editor tooling that understands all of it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Hover fired on whatever word was under the cursor, so a local variable
colliding with a declared block name reported the block's method and path
instead of its own hover info. Completion was already gated this way.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Task 6 Step 2 was already performed and confirmed the assertion error
from the apis-block type checks lands on the apis { } block itself
(WRN-TYPE-2344), not on the offending entry, so it already surfaces
usefully and needed no relocation mapping.
That observation surfaced a real pre-existing bug: the virtual
TypeScript document built for type checking declared `server` from
ast.dataApis-adjacent runtime functions but never declared `api`,
so every api.<name>(...) call raised a false 'Cannot find name apis'
plus a knock-on implicit-any on its result. Fixes it by declaring
`api` from ast.dataApis, mirroring the existing `server` declaration:
each entry gets an input parameter shaped from its request
parameters/body fields (optional when the entry declares none) and a
Promise<any> return. The binding is only emitted when the page has an
apis { } block, so pages without one keep the legitimate 'Cannot find
name api' diagnostic and 'state api' stays legal.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mode-functions writes without a parse check when a mode wrapper survives
holding api entries, since that intermediate state is unparseable until
move-api-blocks runs later in the same pass. Brace balance is the invariant
a bad splice offset would break, so check that instead; nothing downstream
could tell a corrupted wrapper from an untouched one.
Also records that Task 5's example-app migration ran against an already-
migrated target and so did not prove end-to-end behaviour.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- rebuild editors/vscode bundles, stale since the parser escape fix
- attach the caught ParseError as `cause` in both migration validators
- drop two unused test bindings flagged by eslint
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
readQuoted treated \X as an escape for any X, so a single literal
backslash in any quoted attribute value was silently dropped
(data-path="C:\Users" parsed as C:Users) and a doubled backslash
collapsed to one. Only the delimiter and the backslash itself are
escapes now; every other backslash is a literal character.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fix round 1: the deleted api-block-*.test.ts files were not fully superseded
by the apis-* siblings as claimed. Ports back, using apis {} fixtures:
- brace-inside-a-string-literal response-section scanner regression test
- type erasure of response/error bodies before browser emission
- client-side response-error-not-swallowed / transport-failure-fallback,
executed via dynamic import of a generated browser module
- the full SSR execution suite: response payload binding, error section
status/message/data binding, {#each} failure propagation, all executed
via dynamic import + a real load/api call chain (not string checks)
- the four real-tsc enforcement tests (matching/wrong-type/extra-field/
missing-field), plus the B1 cross-page collision guard and the B6
export-for-noUnusedLocals guard
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Parse api="name", api="name()", and api="name({ ... })" render bindings
for apis {} (mode "any") blocks, mirroring @click="fn()" syntax.
- Render-bind by calling Task 4's generated server `api` object directly
(api.<name>(args)) rather than re-implementing the fetch/response
transport, spliced into the SSR template via the existing loop/expression
sentinel mechanism so the call runs inside the async render function with
await support.
- A block that is both render-bound and called from code runs twice by
design (no dedup); pinned with a test.
- Fix packages/syntax's attribute-value lexer (readQuoted) to honor
backslash-escaped quotes, needed so an api="..." call expression can
itself contain a quoted string/object literal.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Usage-driven emission can only see api.<name> calls. api["name"]()
or passing api to a helper is invisible to it, silently drops the
block from the browser bundle, and fails at runtime instead of build
time. Detect that dynamic/indirect use (masking strings and comments
first, reusing the tokenizer's skipLiteralOrComment) and refuse to
compile instead, naming the offending function.
Root-cause fix for the fix-round-1 review: the inlined query/body
assembly in __wrnexusCallApi was a third, unguarded copy of
buildApiRequest's rules. Restore the import of buildApiRequest from
@wrnexus/core in the generated module and delete the inline copy.
The four api-block-ssr.test.ts tests (and three in compiler.test.ts)
that dynamically import a generated module from an OS tmpdir were
failing against a stale globally-installed @wrnexus/core (v0.8.8,
predates buildApiRequest) because that tmpdir has no node_modules of
its own and bare-specifier resolution walked out of the workspace.
Fixed at the source: symlink the workspace @wrnexus/core into each
tmpdir root before the dynamic import, the same way every in-repo
package already resolves it.
Server module now declares `const api = { ... }` for apis {} blocks in
mode "any", dispatching in-process via requireRequestContext + the
existing __wrnexusCallApi transport helper. The try wraps only the
transport call; the response body runs after it, outside the try, so
a bug in the author's response code surfaces rather than being
mistaken for a request failure. A block with no error {} section
rethrows instead of resolving undefined.
Also closes the pageCtx.__wrnexusCallApi wiring gap in
dev-server/runtime.ts: it now forwards input through to
callApiFromContext instead of dropping it.
The old assertions only searched REACTIVE_RUNTIME for substrings; they never
touched buildApiRequest and were not anchored to the content-type line they
claimed to guard, so they could not detect drift on either side. Replace
with a fixture-driven test that runs both implementations on the same
(path, method, input) cases and compares the actual request they produce.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
server.fn() posts to POST /__wrnexus/rpc. Dev intercepts that path before
handlers.fetch and routes it to a createRpcHandler instance built from
loadWrnServerModule; createProductionServer/createProductionHandlers had no
such route, so the request fell through to the internal-caller-gated
inter-app service RPC and 404'd.
Add resolveProdServerFunctions(), a synchronous equivalent of dev's resolve
that searches the already-statically-imported ProdManifest components/pages/
layouts for __wrnexusServerFunctions + __wrnexusRpcManifest, and wire it into
createProductionHandlers with the same validateCsrf + withServerFnRequestContext
wrapping dev uses. Move those two helpers into a new rpc-shared.ts so prod.ts
can use them without a circular import through index.ts.
Add packages/dev-server/test/prod-server-fn-rpc.test.ts covering a successful
call, CSRF rejection, and clean 404s for an unknown component/function.
Wraps the three additional entry points where user server code runs
outside fetchHandler's own context wrap:
- the server-function RPC path (/__wrnexus/rpc) intercepted before
handlers.fetch in the dev server (index.ts) - what server.fn() travels
- the service RPC path (isRpcPath) inside fetchHandler, which runs
implement()/implementStream() service code before ctx existed
- the HMR-sync handler, which runs real load blocks/actions via dispatch()
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bare bodies inside apis {} silently discarded their text with no error,
producing a do-nothing block. They now throw a ParseError naming the entry
and pointing at the response {} section. Duplicate-name detection for
dataApis moved from an incremental, order-dependent check (only saw prior
entries in the array) to a single post-parse pass over the whole ast.dataApis,
so it catches cross-mode duplicates (apis {} vs ssr { api }) regardless of
declaration order.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- generate-complete-framework-report.mjs no longer claims legacyEmit/
legacyEventProps/legacyComponentDiscovery/stringLayouts/
functions.legacyDefaultRuntime are usable compatibility flags; they were
removed before the first public release and a config setting them is now
rejected. Regenerated docs/WRNEXUS-COMPLETE-FEATURE-REPORT.md.
- Restored the update.test.ts coverage lost in the sub-0.8.0 migration
cleanup: a 0.8.x fixture now asserts refreshFrameworkFiles' still-live
behaviour (public/llms.txt and CLAUDE.md creation) and that
pkg.wrnexus.version stays at its old value after an unverified update.
The .gitignore refresh and build/start/production script backfill were
themselves removed as part of dropping the sub-0.8.0 migrations that
implemented them, so there is nothing left to cover for those two.
- updateApp now logs a clear warning when the detected project version is
below 0.8.0, naming the version and stating that automated migration from
below 0.8.0 is no longer supported, without failing the command (a
marker-less app, like examples/basic-app, is benign and must still
upgrade cleanly).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- regenerate docs/public-api-0.8.json (removals only: CompatibilityPolicy,
CompatibilityReport, CURRENT_COMPATIBILITY_DATE, CURRENT_FRAMEWORK_BEHAVIOUR,
isCompatibilityDate, resolveCompatibility from @wrnexus/styles)
- regenerate docs/WRNEXUS-COMPLETE-FEATURE-REPORT.md
- remove the deleted 'wrnexus compatibility' command row and its config-pinning
sentence from packages/cli/README.md
- drop compatibilityDate/frameworkBehaviour example fields from docs/GUIDE.md
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The single-branch 'shared'-only action fixture failed to infer through
defineStore's actions generic, typing store.increment as never and
failing bun run typecheck (TS2349) even though bun test passed.
Explicit type arguments fix the inference without weakening the test.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fix round 2 for task 1: store-codegen.ts stopped emitting runtime:
"legacy" actions in round 1, making the StoreRuntime variant and its
resolution fallback dead. Narrows StoreRuntime to three variants and
adds a regression test for the remaining options.runtime -> shared
fallback chain.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fix round 1 for task 1: packages/typecheck also branched on the
removed legacy runtime (componentContract's exclusion filter and the
runtime-namespace loop). Removes both, adds a regression test.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>