Skip to content

feat(core): declarative headers, page redirects, image escape hatches, live dev config, and a foreign-safe navigation - #97

Merged
aralroca merged 3 commits into
mainfrom
feat/core-headers-redirects-foreign-morph
Aug 8, 2026
Merged

aralroca merged 3 commits into
mainfrom
feat/core-headers-redirects-foreign-morph

Conversation

@aralroca

@aralroca aralroca commented Aug 8, 2026

Copy link
Copy Markdown
Owner

A wave of core improvements that came out of building a large content+tools app on Janux — each one is something the framework claimed or implied but didn't fully deliver, now with tests and docs.

Routing & server

  • headers rules in janux.config.ts — same grammar and ordering as redirects: every matching rule contributes, later rules override a header both name, and except carves holes. The motivating case: Cross-Origin-Opener-Policy/Cross-Origin-Embedder-Policy everywhere (for SharedArrayBuffer) except the pages that embed third-party iframes. Docs: navigation → "Response headers".
  • redirect() from pages with typed 3xx statuses. Docs: core-api.
  • Declared rewrites hand HTTP handlers the resolved pathname — the request keeps the browser's URL, the handler's url.pathname is the rewritten one, the query survives.

Build

  • images: false opts out of the image variant pre-encode entirely (apps that serve their public/ images verbatim were paying the encode on every build); a corrupt source image is skipped with a warning instead of failing the whole build; public/sw.js survives builds (retireServiceWorker now takes the app root).

Interop & client runtime

  • Pages whose only client code is standalone foreign() hosts now ship the runtime. The server render registry tracks foreign hosts, the HTML shell gates its scripts on runtimeUrl rather than islands alone, island loaders register every def their module exports, and schema gains json() for pass-through payloads the app doesn't shape.
  • The first React commit of a foreign root lands synchronously (flushSync) over the SSR markup. A concurrent first commit left the host empty for at least a frame; after an SPA navigation that collapsed the page to header+footer, and Chrome's scroll anchoring then dragged a freshly-restored scroll offset to the bottom of the page — "back goes to the footer". Regression test uses MutationObserver callbacks as checkpoints: the host is never observed empty.
  • The navigation morph never diffs inside a live foreign host. React fibers keep node references: text rewritten behind their back makes later renders bail out against content they never wrote, and a same-position replacement by a different element re-parented React's children, so every unmount threw NotFoundError: removeChild. The morph now passes shouldSkipChildren for hosts with a live root — call-site props still flow through the synced data-jxf-props attribute push. This also turned a long-flaky interop test ("refreshes a preserved standalone foreign…") deterministic: the flake was this bug.
  • react-dom joins the runtime pre-bundle includes (the flushSync import), keeping the mid-session re-optimize away.

DX

  • janux dev picks up janux.config.ts edits live. Bun keys its module cache on the file path and ignores the ?mtime query the re-import used for cache busting, so every dev session was stuck with the config it booted with. The entry is now evicted explicitly (createRequire(...).cache) on every read. Docs: cli-and-deployment.

Tests

Full monorepo suite green (the only local failures are the pre-existing docs-playground e2e ones that run against a stale July apps/docs/dist — identical on main). New/extended: routing-rules (headers), server (declared headers, standalone-foreign runtime), app-config (config re-read mid-process), image-optimizer + service-worker + commands (escape hatches), registry (module def registration), interop/foreign (empty-host guard + the now-deterministic refresh test), deps (runtime includes census).

🤖 Generated with Claude Code

…, live dev config, and a foreign-safe navigation

- janux.config `headers` rules (from/except, later rules override): COOP/COEP
  for SharedArrayBuffer pages with carve-outs for third-party iframe embeds.
- `redirect()` from pages, with typed 3xx statuses.
- `images: false` opt-out of the variant pre-encode; a corrupt source image is
  skipped with a warning instead of failing the build; `public/sw.js` survives
  builds (retireServiceWorker takes the app root).
- Pages whose only client code is standalone foreign hosts now ship the
  runtime: the server render registry tracks foreign hosts, the shell gates
  scripts on runtimeUrl, island loaders register every def their module
  exports, and schema gains `json()` for payloads the app does not shape.
- Declared rewrites hand HTTP handlers the resolved pathname (the request
  still carries the browser URL; the query survives).
- `janux dev` picks up janux.config.ts edits live: Bun keys its module cache
  on the file path and ignores the ?mtime query, so the entry is evicted
  explicitly on every re-read.
- Foreign interop hardening: the first React commit lands synchronously over
  the SSR markup (a concurrent first commit left the host empty for a frame —
  after a navigation the page collapsed to header+footer and Chrome's scroll
  anchoring dragged a restored scroll position to the bottom), and the
  navigation morph never diffs inside a live foreign host (React fibers keep
  node references: rewritten text made later renders bail out, and re-parented
  children made unmount throw removeChild). Needs diff-dom-streaming ≥ 0.6.11
  for shouldSkipChildren; the option is ignored gracefully below that.
- react-dom joins the runtime pre-bundle includes (flushSync import).
- Docs: navigation (response headers), core-api (redirect), images (escape
  hatches), interop (the morph contract), cli-and-deployment (live config).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

aralroca and others added 2 commits August 8, 2026 13:34
…visories, update the test census

- `patchedDependencies` carries brisa-build/diff-dom-streaming#26 (build +
  types) until 0.6.11 is on npm: the registry's 0.6.10 has neither the
  behavior the foreign-safe morph relies on (the interop refresh test is racy
  without it) nor the option in its types (typecheck and the declaration emit
  both tripped on TS2353). Drop the patch and refresh the lockfile once the
  release lands.
- js-yaml 3.15.1 / 4.3.1 and nanoid 3.3.17 in the lockfile: the three High
  advisories osv-scan gates on, all in-range patch fixes.
- Test census: 11970 → 11992 (the suite grew by the empty-host guard, the
  skip-children coverage and the config re-read test).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The release carrying shouldSkipChildren is on npm, so the patched 0.6.10
goes away: the option now comes from the package itself, in the build and
in its types. `packages/conformance` moves to the same range so the
workspace resolves one copy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@aralroca
aralroca merged commit 7970c46 into main Aug 8, 2026
61 checks passed
@aralroca
aralroca deleted the feat/core-headers-redirects-foreign-morph branch August 8, 2026 12:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant