Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions changelog/cli/0.10.52.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
package: "@webjsdev/cli"
version: 0.10.52
date: 2026-08-07T15:43:03.749Z
commit_count: 2
---
## Features

- **`webjs elision`, the per-module verdict and its differential** ([#1312](https://github.com/webjsdev/webjs/pull/1312)) [`51556a50`](https://github.com/webjsdev/webjs/commit/51556a50)
An app could not ask what elision dropped from its pages, and the SSR
differential that proves elision changes nothing was a framework test an app
could not run.

`webjs elision` prints the verdict per module with the evidence behind every
ship, `--json` emits it for a tool, and `--verify` boots two handlers with
`WEBJS_ELIDE` flipped and diffs the masked SSR bytes across the app's own
static route corpus. It exits non-zero on a divergence AND on a corpus where
nothing was compared, since a vacuous pass is the failure mode a verification
command must not have. The success output states its own boundary: it proves
the served bytes did not change, not post-hydration behaviour.

`webjs doctor` gains the component half of the verdict as its own check
(`ELISION_COMPONENTS`), reading the one shared report the carrier check was
rewired onto, so an app is told about its orphans in the ordinary health run.

Also corrects a claim four surfaces carried: `static interactive = true` does
NOT rescue a component whose own registration tag is computed, because the
scanner requires a literal tag, so that component gets no verdict for the
override to attach to. It does rescue the documented residuals (a computed
`whenDefined` observer, an external stylesheet's `:defined` rule), and those
are now pinned as measured rather than asserted in prose.

- **the scaffold teaches the self-sufficient submitter** ([#1317](https://github.com/webjsdev/webjs/pull/1317)) [`76afe0cd`](https://github.com/webjsdev/webjs/commit/76afe0cd)
The generated todo demo's guidance said a `formaction=${action}` button has to
sit inside a bound form. It no longer does, so a freshly scaffolded app now
teaches the rule that holds: a bound submitter carries its own `formmethod`
and enctype and works in any form, or none.
105 changes: 105 additions & 0 deletions changelog/core/0.7.48.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
---
package: "@webjsdev/core"
version: 0.7.48
date: 2026-08-07T15:43:03.612Z
commit_count: 5
---
## Features

- **a bound submitter carries its own submission** ([#1317](https://github.com/webjsdev/webjs/pull/1317)) [`76afe0cd`](https://github.com/webjsdev/webjs/commit/76afe0cd)
A `<button formaction=${action}>` used to be half a submission: it carried the
action identity in its own name and value pair, while `method="post"` and the
enctype were supplied on the enclosing form's start tag. So the button worked
only inside a form that was itself bound. That could not be enforced where it
mattered, since a component renders its own template in a separate SSR pass
with no view of the host page, so the scan bound anyway whenever it could not
tell, and the result was a form that submitted as a GET, put the identity in
the query string, and ran nothing: a silent 200 with no log.

Both renderers now supply the submission attributes at the level where the
action is bound, and never override what the author wrote at that same level.
A bound form still gains `method="post"` plus `enctype`; a bound submitter now
gains `formmethod="post"` plus `formenctype` on the button itself, which is
what React does for a function `formAction`. The button is self-sufficient
inside a bound form, an unbound form, a `method="get"` form, or no form at
all, so the failure class is deleted rather than reported.

Refusals narrow to same-element contradictions, the only ones with no correct
fallback. A cross-element rule always has one, namely whatever native HTML
would do, so a PLAIN button's own `formmethod` / `formenctype` is honoured
now rather than refused. That reverses part of #1207: the client router
already honoured a submitter's `formmethod` with native precedence, so both
paths lost the identity identically. No `formaction` url is emitted (an empty
one is a conformance error), so a bound submitter inside a form declaring
`action="/x"` posts to `/x` by native precedence and the action still runs,
because the identity travels in the body.

The client router also sends the enctype the author declared. It used to
build a `FormData` for every submission with no explicit content type, so
fetch always derived `multipart/form-data` and the authored enctype was never
read: a plain `<form method="post">` sent a urlencoded body with JS off and a
multipart body with JS on, one template making two different requests. The
effective enctype now resolves with native precedence (a submitter's
`formenctype` over the form's) and the body is encoded to match, with a
`File` in a urlencoded body serialized as its name, exactly as the platform's
own serializer does. `text/plain` gets no encoder and the router declines the
submission so the browser performs it natively, since the server answers such
a POST with a bare 405 before reading its body.

Invariant 12 in `AGENTS.md` carries the full rule set.

- **SSR action seeding is observable in dev** ([#1311](https://github.com/webjsdev/webjs/pull/1311)) [`a4eba9c1`](https://github.com/webjsdev/webjs/commit/a4eba9c1)
A seed miss was indistinguishable from a hit from the outside, so a refactor
that broke seeding for a whole app produced no error, no warning and no log
line: the app just quietly re-issued one RPC per async component on every
first load, which is the exact cost the feature exists to remove.

In dev the browser now logs one warning per page view when a hydration action
call missed its seed, naming the cause, and only causes it can prove (a page
that emitted no seeds is not evidence of anything, and that case is left to
the server's `X-Webjs-Seed` header, where `collected=0` is unambiguous). The
dev gate is a server-stamped `data-webjs-dev` marker rather than
`process.env.NODE_ENV`, which esbuild folds to a constant in the built browser
bundle. Production is byte-identical and silent.

Seed ingestion also flips to last-write-wins, and the accompanying fixes are
what make that safe: a navigation drains the outgoing page's seed block before
ingesting the incoming one and evicts the store, so a shared key holds the
render whose paint is on screen and a departed page's value can never be
handed to a component that outlived it. A `<webjs-frame>` swap is no longer
mistaken for a page navigation, and a frame response the router discards no
longer has its seeds ingested. Each scan closes the previous report window and
opens its own, so one page's misses are never charged to the next.

## Fixes

- **back-button restore survives late layout growth** ([#1313](https://github.com/webjsdev/webjs/pull/1313)) [`ddd68608`](https://github.com/webjsdev/webjs/commit/ddd68608)
A snapshot's `scrollY` is recorded against the page at its settled height, and
the restore replays that number onto a document that has only just been
swapped in and is still shorter, because the components in the restored markup
have not upgraded and re-rendered yet. When they do, content grows above the
viewport and the browser's scroll anchoring holds the visual position by
adding that growth to `scrollY`, so the recorded offset is counted twice and
the reader lands below where they left (763px on the gallery pages, exactly
the settled-minus-swapped height delta).

Anchoring is suppressed for the duration of the restore rather than the scroll
being re-asserted afterwards: the replayed number already accounts for the
growth, so withholding the browser's correction fixes the double count at its
source. Suppression never moves the viewport, so it cannot yank a reader who
has started scrolling, and it needs no settle detection, which a re-assert
would and which cannot be answered against a streaming `<webjs-suspense>`
boundary. The window closes on the first real input, on that restore's own
revalidation settling plus two frames, or on a 2s ceiling.

- **`removeBetween` leaves the end marker it was asked to remove** ([#1292](https://github.com/webjsdev/webjs/pull/1292)) [`93c00daf`](https://github.com/webjsdev/webjs/commit/93c00daf)
Every rendered template instance is bracketed by a `wjm-s` / `wjm-e` comment
pair, and the guard deciding whether to remove the end marker read
`start.parentNode` AFTER the walk had already detached `start`, so it compared
the live parent against `null`, could never fire, and every teardown left one
orphan comment behind. They accumulate for the life of the region and nothing
collects them, so a long-lived list that churns rows grows comment nodes
without limit: a 3-row `repeat()` cycled empty and back five times went from 3
end markers to 18. The parent is captured before the walk consumes `start`.
The leak is invisible to `textContent` and to `querySelectorAll`, which is why
nothing caught it, so the new tests count comment nodes directly.
27 changes: 27 additions & 0 deletions changelog/mcp/0.1.12.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
package: "@webjsdev/mcp"
version: 0.1.12
date: 2026-08-07T15:43:03.901Z
commit_count: 1
---
## Features

- **`list_elision`, and a component scan that sees an interpolated tag** ([#1312](https://github.com/webjsdev/webjs/pull/1312)) [`51556a50`](https://github.com/webjsdev/webjs/commit/51556a50)
The new `list_elision` tool returns the server's `analyzeAppElision` verbatim:
which component modules the browser drops, which ship and on what evidence,
which route modules over-ship, and which classes get no verdict at all. That
function already owns the app-level contract, so unlike `list_routes` there is
no projector leaf here and nothing to keep in sync, and a drift test asserts
it equals what `webjs elision --json` prints. `list_components` deliberately
stays a cheap lexical inventory: growing an elided flag onto it would make it
build a module graph.

The orphan scan carried a second, looser definition of a registered tag than
`extractComponents` did, so a class registered with an interpolated template
literal counted as registered for the orphan scan but not as a component for
the scanner, and then vanished from every surface: the dev warning, the report
orphans, the CLI section and the doctor check. That is the shape the docs most
loudly claim to cover, since an interpolated template is the idiomatic
computed tag, and every existing test used the concatenated-variable form,
which kept working. The second definition is removed rather than tightened, so
the two scans cannot disagree about what a literal tag is.
58 changes: 58 additions & 0 deletions changelog/server/0.8.60.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
package: "@webjsdev/server"
version: 0.8.60
date: 2026-08-07T15:43:03.677Z
commit_count: 3
---
## Features

- **the elision verdict is inspectable per app** ([#1312](https://github.com/webjsdev/webjs/pull/1312)) [`51556a50`](https://github.com/webjsdev/webjs/commit/51556a50)
The elision report told an app which pages ship when they could have been
elided, the benign over-ship direction. Nothing reported the other direction:
which components were DROPPED, and on what evidence. That is the direction
where a wrong verdict silently loses interactivity in production.

`analyzeElision` now records WHY each shipping component ships, alongside
every `mustShip` write, and returns it as `componentVerdicts`. The new
`analyzeAppElision` export projects the whole verdict (components, route
modules, orphans, summary) into one sorted, app-relative, JSON-serializable
object, which is what `webjs elision`, the new doctor check and the MCP
`list_elision` tool all read. Nothing is re-analysed: the data was already in
memory and was being discarded. `maskJsSet` and `staticPageRoutes` are
exported for the verification command, and moving `maskJsSet` into a shared
leaf keeps the framework's own differential guard and the app-facing one from
drifting apart on what the JS-loaded set even is.

The dev server prints one summary line per warm analysis, re-emitted after
each `fs.watch` rebuild. Deliberately not a browser push: an inert route ships
zero application JS, and opening the network tab on that route and seeing
nothing is the most useful manual check an author has, which a dev-only boot
script would corrupt on exactly the pages this feature exists to prove.

The orphan scan is fixed alongside. It read raw source, so every
`WebComponent` subclass written inside an `html` template as a CODE SAMPLE
counted as an unregistered component (this repo's own website reported 17
false orphans). It redacts strings and templates exactly as `extractComponents`
already did, so both dogfood apps go to zero false orphans while a real orphan
in the same tree still fires. Tolerable as dev-console noise, not tolerable as
a doctor warning.

- **a bound submitter carries its own submission** ([#1317](https://github.com/webjsdev/webjs/pull/1317)) [`76afe0cd`](https://github.com/webjsdev/webjs/commit/76afe0cd)
The server half of the submitter change. SSR supplies `formmethod="post"` plus
`formenctype` on a bound `<button formaction=${action}>` itself rather than
relying on the enclosing form's start tag, so the button is self-sufficient
wherever it sits, and the four-state form-scope tracking that existed only to
answer questions about the enclosing element is gone, including the Suspense
scope carried across the boundary drain. The form dispatcher reads a
urlencoded body as well as multipart, matching what the client router now
sends for a form whose declared enctype is the HTML default.

- **SSR action seeding reports itself in dev** ([#1311](https://github.com/webjsdev/webjs/pull/1311)) [`a4eba9c1`](https://github.com/webjsdev/webjs/commit/a4eba9c1)
Responses carry `X-Webjs-Seed` in dev: `off`, `html-cache`,
`collected=<m>, emitted=<n>`, with `... streamed` where the render streamed.
The same value is folded into the access log line as a `seed` field, and the
page carries a `data-webjs-dev` marker so the browser can gate its own
reporting on the server's judgement rather than on a bundled constant. A
serializer drop is excluded from the emitted count, since that is the one
failure the counts exist to expose and reporting `collected=N, emitted=N` for
it would falsify the client's warning text. Production emits none of it.
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@webjsdev/cli",
"version": "0.10.51",
"version": "0.10.52",
"type": "module",
"description": "The CLI for WebJs, a full-stack JavaScript framework built on web components with server-side rendering and no build step. Runs the dev and production servers, scaffolds apps, validates conventions, and drives the database. Node 24+ or Bun.",
"bin": {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@webjsdev/core",
"version": "0.7.47",
"version": "0.7.48",
"type": "module",
"description": "The runtime for WebJs, a full-stack JavaScript framework built on web components with server-side rendering and no build step. Ships the html and css template tags, the WebComponent base class, signals, directives, and the isomorphic renderers.",
"types": "./index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/mcp/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@webjsdev/mcp",
"version": "0.1.11",
"version": "0.1.12",
"type": "module",
"description": "The Model Context Protocol server for WebJs, a full-stack JavaScript framework built on web components with server-side rendering and no build step. Gives AI coding agents live app introspection over routes, actions, components, and convention checks, plus a knowledge layer of docs, recipes, and framework source.",
"bin": {
Expand Down
4 changes: 2 additions & 2 deletions packages/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@webjsdev/server",
"version": "0.8.59",
"version": "0.8.60",
"type": "module",
"description": "The server for WebJs, a full-stack JavaScript framework built on web components with server-side rendering and no build step. Provides the file-based router, SSR, server actions, route handlers, middleware, and live reload on Node 24+ or Bun.",
"main": "index.js",
Expand Down Expand Up @@ -28,7 +28,7 @@
"README.md"
],
"dependencies": {
"@webjsdev/core": "^0.7.47",
"@webjsdev/core": "^0.7.48",
"ws": "^8.20.0"
},
"publishConfig": {
Expand Down
Loading