From 5200f712b9da793cb5b6a4db3f65f8f3065f9b0f Mon Sep 17 00:00:00 2001 From: t Date: Thu, 8 Oct 2026 01:23:26 +0530 Subject: [PATCH 1/2] chore: release server 0.8.88 and cli 0.10.70 Claude-Session: https://claude.ai/code/session_01SZ72LSPAo4NvYvBDSD6RLo --- changelog/cli/0.10.70.md | 29 +++++++++++++++++++++++++++++ changelog/server/0.8.88.md | 13 +++++++++++++ package-lock.json | 6 +++--- packages/cli/package.json | 4 ++-- packages/server/package.json | 2 +- 5 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 changelog/cli/0.10.70.md create mode 100644 changelog/server/0.8.88.md diff --git a/changelog/cli/0.10.70.md b/changelog/cli/0.10.70.md new file mode 100644 index 000000000..ebb2915d2 --- /dev/null +++ b/changelog/cli/0.10.70.md @@ -0,0 +1,29 @@ +--- +package: "@webjsdev/cli" +version: 0.10.70 +date: 2026-10-07T19:53:26.375Z +commit_count: 3 +--- +## Reverts + +- **restore the read-the-skill, study-the-gallery agent flow** ([#1614](https://github.com/webjsdev/webjs/pull/1614)) [`1b65f46d`](https://github.com/webjsdev/webjs/commit/1b65f46d) + * The scaffold's AGENTS.md is the original flow again: read the skill, study + the gallery, then clear it. This withdraws the AGENTS.md primer that + shipped in 0.10.69 (#1592); its follow-ups (#1605, #1612) were reverted + before any release. + +## Performance + +- **build a production-only image from the scaffold Dockerfile** ([#1607](https://github.com/webjsdev/webjs/pull/1607)) [`687ccd0c`](https://github.com/webjsdev/webjs/commit/687ccd0c) + * perf: build a production-only image from the scaffold Dockerfile + + The image a scaffolded app built carried every devDependency and its + package manager's cache: 435 MiB on Bun and 719 MiB on Node, while + +## Fixes + +- **never reload the dev page for files nothing serves** ([#1610](https://github.com/webjsdev/webjs/pull/1610)) [`b7a14ad1`](https://github.com/webjsdev/webjs/commit/b7a14ad1) + * fix: never reload the dev page for files nothing serves + + `npm run dev > dev.log` in the app folder made every log line a rebuild + and a reload on both runtimes, which wiped streamed and live UI in the diff --git a/changelog/server/0.8.88.md b/changelog/server/0.8.88.md new file mode 100644 index 000000000..686f6d6c0 --- /dev/null +++ b/changelog/server/0.8.88.md @@ -0,0 +1,13 @@ +--- +package: "@webjsdev/server" +version: 0.8.88 +date: 2026-10-07T19:53:26.312Z +commit_count: 1 +--- +## Fixes + +- **never reload the dev page for files nothing serves** ([#1610](https://github.com/webjsdev/webjs/pull/1610)) [`b7a14ad1`](https://github.com/webjsdev/webjs/commit/b7a14ad1) + * fix: never reload the dev page for files nothing serves + + `npm run dev > dev.log` in the app folder made every log line a rebuild + and a reload on both runtimes, which wiped streamed and live UI in the diff --git a/package-lock.json b/package-lock.json index 94bb1421f..8d9f34816 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6980,11 +6980,11 @@ }, "packages/cli": { "name": "@webjsdev/cli", - "version": "0.10.69", + "version": "0.10.70", "license": "MIT", "dependencies": { "@webjsdev/mcp": "^0.1.0", - "@webjsdev/server": "^0.8.87", + "@webjsdev/server": "^0.8.88", "@webjsdev/ui": "^0.3.15" }, "bin": { @@ -7045,7 +7045,7 @@ }, "packages/server": { "name": "@webjsdev/server", - "version": "0.8.87", + "version": "0.8.88", "license": "MIT", "dependencies": { "ws": "^8.20.0" diff --git a/packages/cli/package.json b/packages/cli/package.json index 0a0af1dd9..e9af9043f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@webjsdev/cli", - "version": "0.10.69", + "version": "0.10.70", "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": { @@ -18,7 +18,7 @@ ], "dependencies": { "@webjsdev/mcp": "^0.1.0", - "@webjsdev/server": "^0.8.87", + "@webjsdev/server": "^0.8.88", "@webjsdev/ui": "^0.3.15" }, "publishConfig": { diff --git a/packages/server/package.json b/packages/server/package.json index e4e582c42..951de5fce 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@webjsdev/server", - "version": "0.8.87", + "version": "0.8.88", "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", From 0d32d222c7dcf008935b0cf996a6ca3bc55756bf Mon Sep 17 00:00:00 2001 From: t Date: Thu, 8 Oct 2026 01:23:42 +0530 Subject: [PATCH 2/2] docs: note the two limits of the dev watcher ignore Claude-Session: https://claude.ai/code/session_01SZ72LSPAo4NvYvBDSD6RLo --- packages/server/AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/AGENTS.md b/packages/server/AGENTS.md index ceebefb25..7b18e4e3c 100644 --- a/packages/server/AGENTS.md +++ b/packages/server/AGENTS.md @@ -38,7 +38,7 @@ with metadata, Suspense, streaming) for HTML, or `api.js` / | `dev-styles.js` | The stylesheet re-request for the in-place dev refresh (#1398, #1535), the BROWSER half, a browser-safe module (no node imports) that `reloadClientJs` inlines verbatim after an `export`-keyword strip, so the browser test drives the exact shipping code. `preloadStyles(root?, now?, timeoutMs?)` inserts a cache-busted replacement BESIDE every same-origin stylesheet and resolves once each has loaded or failed (or after `timeoutMs`, default 10s); the reload client swaps the markup in only THEN and calls the returned `commit()`, which drops each old link whose replacement loaded plus any same-identity copy the head merge appended. Swapping first (the old `refreshStyles` order) painted a new utility class with no rule for the whole `webjs.dev.regenerate` compile (56 frames for an 800ms compile, #1535). A replacement that fails removes itself and the working sheet stays (#936, #1400). De-dupes by every attribute except the href query, so a `media="print"` sibling is kept. Tests: `test/dev/browser/refresh-styles.test.js`, `test/e2e/dev-css-flash.test.mjs` (frame sampler, Node and `WEBJS_E2E_RUNTIME=bun`) | | `port-in-use.js` | A taken port fails fast (#1527). `reusePortRequested()` reads `WEBJS_REUSE_PORT` (the only way either listener sets `SO_REUSEPORT`; Bun's own default with `development: false` is on, which let a second server bind silently). `portInUseError(port, cause)` builds the `EADDRINUSE` error both listeners throw, its message naming the holder found by `findPortHolder` (`/proc/net/tcp{,6}` LISTEN inode, then `/proc/*/fd`; best effort, Linux only, null otherwise). `startNodeListener` now awaits the bind, so a failure rejects `startServer`; the CLI prints it and exits 98, which the dev supervisor treats as final. Tests: `test/listener/port-in-use.test.js`, `test/bun/port-in-use.mjs`. | | `dev/watch-tree.js` | The dev live-reload watcher (#1521). `watchTree(root, { ignore, onEvent, onError })` watches the root non-recursively plus each non-ignored top-level dir recursively through `dev/watch-recursive.js` (#1529: on Linux under Node one non-recursive watch per directory, because Node 24's per-file recursive watcher goes deaf to a file once `sed -i` or an atomic save replaces it; a byte-identical copy lives in `@webjsdev/cli` `lib/`) (a dir created later is followed), so `node_modules` is never walked (about 275ms and 10k per-file watches per boot on Node 24 Linux before), and every `fs.watch` error goes to `onError` instead of becoming an uncaughtException. `isWatchError(err)` (`syscall === 'watch'`) is what `installProcessHandlers` (`listener-core.js`) checks in dev to log a stray watcher error as a warning instead of shutting down; prod keeps every uncaught exception fatal. Tests: `test/dev/watch-tree.test.js`, `test/dev/watch-error-guard.test.js`, `test/bun/dev-watch-error.mjs`. | -| `dev/watch-ignore.js` | What the dev watcher never turns into a rebuild or a reload beyond the framework's own artefacts: tool output nothing serves (`*.log`, `coverage/`, `.cache/`, `.nyc_output/`, `test-results/`, `playwright-report/`, `.turbo/`, swap and backup files, `.DS_Store`) and whatever the watched root's `.gitignore` ignores (the common subset: comments, `!`, trailing `/`, anchoring `/`, `*` `**` `?`; `.env*` excluded since the server reads it). `npm run dev > dev.log` used to reload the page on every log line. The CLI supervisor's `shouldIgnoreRestartPath` skips the same output. Tests: `test/dev/watch-ignore.test.js`, cross-runtime `test/bun/dev-ignore-unserved.mjs`; the release gate writes a `dev.log` and a `coverage/` file after its edits and asserts no reload frame. | +| `dev/watch-ignore.js` | What the dev watcher never turns into a rebuild or a reload beyond the framework's own artefacts: tool output nothing serves (`*.log`, `coverage/`, `.cache/`, `.nyc_output/`, `test-results/`, `playwright-report/`, `.turbo/`, swap and backup files, `.DS_Store`) and whatever the watched root's `.gitignore` ignores (the common subset: comments, `!`, trailing `/`, anchoring `/`, `*` `**` `?`; `.env*` excluded since the server reads it). `npm run dev > dev.log` used to reload the page on every log line. The CLI supervisor's `shouldIgnoreRestartPath` skips the same fixed output list but not `.gitignore` (it only watches source dirs, so a gitignored file there still restarts Node). `.gitignore` is read once per watcher start, so an edited `.gitignore` takes effect after a dev restart. Tests: `test/dev/watch-ignore.test.js`, cross-runtime `test/bun/dev-ignore-unserved.mjs`; the release gate writes a `dev.log` and a `coverage/` file after its edits and asserts no reload frame. | | `dev-classify.js` | The dev live-reload classifier (#1398). Pure, state-free, and import-free so the ladder is unit-testable without booting a server. `classifyChangedPath(abs, ctx)` returns `{ v, by, why }` off a first-match ladder: a cold analysis reloads (Vite's pessimistic seed), a path outside `appDir` is an extra `webjs.dev.watch` root and takes `shell`, a path in the SHIPPED closure reloads (the transitive answer, and the rung that catches a page which ships whole and an action a shipping component imports), a page file morphs, anything else the graph walked is server-only and takes `shell`, and everything else reloads (a new file, a deleted file, and every `public/` asset, which reloads because `public/` is outside the module graph so the server cannot tell what the file is or what depends on it). `strongerVerdict(a, b)` compares by index into `RELOAD_VERDICTS` (`['reload', 'shell', 'page']`, strongest first), treating `null` as weakest and an unrecognised name as strongest, so a batch can only ever over-reload | | `dev-overlay.js` | The BROWSER half of the dev error overlay (#264), a browser-safe module (no node imports) that `reloadClientJs` inlines verbatim after an `export`-keyword strip, so the browser test drives the exact shipping code. `renderDevOverlay(frame, currentPath?)` builds the card with `createElement` + `textContent` ONLY (never `innerHTML`, so a hostile message / path / code frame is inert text); `dismissDevOverlay()` takes it down and forgets any held frame, which is what the Dismiss button calls. **URL scope (#1047):** a url-stamped `render` frame for a different path than `currentPath` (default `location.pathname + location.search`, compared encoding-tolerantly) is NOT rendered. The gate runs BEFORE any removal, so a refused frame neither paints nor wipes a live `rebuild` / `ts-strip` overlay. It cannot simply drop the frame either, because the SSE frame is pushed during the render, before the navigation response is sent, so it arrives while `location` is still the old page: a refused frame goes to a PENDING slot that `syncDevOverlayToLocation(currentPath?)` re-evaluates on the next navigation, which renders it only if the url matches AND it arrived during the navigation now finishing, and drops it otherwise (consume-once, so retention is bounded with no timer). The second half of that condition is what stops a frame held from an IDLE render (another tab's page, a background fetch of some other url, never a link prefetch since that reports nothing) painting on a later visit to that url, when the page may render fine; `markDevOverlayNavStart()` is the nav-start marker it counts. `installDevOverlayNavSync(opts?)` wires all of it to the client router's `webjs:navigate` (applied nav) + `popstate` (a snapshot-cache restore returns before `webjs:navigate`) + `webjs:before-cache` (the nav START, since `snapshotCurrent` runs at the top of every navigation and form submission), and returns an uninstall thunk. Two things keep a snapshot copy of the overlay from becoming an undismissable card (the router's tier-4 popstate restore `replaceChildren`s the body from cached HTML, and a parsed copy is not the node this module holds, so nothing could remove it and its Dismiss button has no listener). `before-cache` DETACHES the overlay across the router's synchronous `outerHTML` read and re-attaches it a microtask later, so the cached HTML carries none. That is what covers the hard ordering: under an opt-in view transition the router defers the body swap PAST the sync, so a copy the swap inserts would arrive after any sweep had already run. The sync additionally SWEEPS any `[data-webjs-error-overlay]` element it does not own, as the backstop for a copy from anywhere else (a snapshot cached before this client installed its listeners, or any other wholesale body replacement). What `before-cache` must NOT do is strip the overlay for good, which the event's own contract invites: it fires on EVERY navigation, so that would tear a `rebuild` / `ts-strip` overlay off the page on any link click while the build was still broken. `packages/core` is untouched: the fix rides existing events | | `frame-render.js` | Server-side `` subtree extraction (#253). `requestedFrameId(req)` reads the `x-webjs-frame` header (null when absent, the normal full-page path); `extractFrameSubtree(html, id)` returns the `...` slice from rendered HTML verbatim (so byte-equivalent by construction), balancing nested `` tags and reading the `id` attribute (not a substring match), or null when the id is absent. Used by `ssr.js`'s frame-render branch |