Skip to content

Commit 93ec6db

Browse files
committed
Implement compiler-owned client boundaries
1 parent bd40aef commit 93ec6db

72 files changed

Lines changed: 6377 additions & 265 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

bun.lock

Lines changed: 9 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bunfig.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ linker = "isolated"
88

99
[test]
1010
root = "./packages"
11+
pathIgnorePatterns = ["packages/cli/templates/**/tests/e2e/**"]

demo/auth-starter/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# mandu-auth-starter
22

3+
## 0.1.69
4+
5+
### Patch Changes
6+
7+
- Updated dependencies []:
8+
- @mandujs/core@0.54.18
9+
310
## 0.1.68
411

512
### Patch Changes

demo/auth-starter/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mandu-auth-starter",
3-
"version": "0.1.68",
3+
"version": "0.1.69",
44
"private": true,
55
"type": "module",
66
"scripts": {

demo/desktop-starter/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# mandu-desktop-starter
22

3+
## 0.1.68
4+
5+
### Patch Changes
6+
7+
- Updated dependencies []:
8+
- @mandujs/core@0.54.18
9+
310
## 0.1.67
411

512
### Patch Changes

demo/desktop-starter/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mandu-desktop-starter",
3-
"version": "0.1.67",
3+
"version": "0.1.68",
44
"private": true,
55
"type": "module",
66
"description": "Minimal Mandu desktop app (Phase 9c). Opens a native WebView pointing at a local Bun.serve() instance.",

demo/edge-workers-starter/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# mandu-edge-workers-starter
22

3+
## 0.1.69
4+
5+
### Patch Changes
6+
7+
- Updated dependencies []:
8+
- @mandujs/core@0.54.18
9+
- @mandujs/edge@0.4.66
10+
311
## 0.1.68
412

513
### Patch Changes

demo/edge-workers-starter/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mandu-edge-workers-starter",
3-
"version": "0.1.68",
3+
"version": "0.1.69",
44
"private": true,
55
"type": "module",
66
"scripts": {

docs/architect/island-hydration.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,79 @@ onto the wrapper element:
7373
The client runtime reads `data-hydrate` and dispatches via
7474
`scheduleHydration()`.
7575

76+
## Compiler-owned client boundaries
77+
78+
F42 adds a separate path for server routes that directly import `.client` or
79+
`.island` components. App code stays React-like:
80+
81+
```tsx
82+
import { CommentsSection } from "../client/CommentsSection.client";
83+
84+
export default async function Page({ comments }) {
85+
return <CommentsSection initialComments={comments} />;
86+
}
87+
```
88+
89+
The server build rewrites that JSX to Mandu's internal boundary component
90+
before SSR imports the route. The client component module is not executed
91+
during boundary discovery, and the route manifest becomes the source of truth
92+
for the boundary id, module, export name, hydration priority, and source
93+
location.
94+
95+
SSR emits a marker plus a boundary-local props payload:
96+
97+
```html
98+
<div
99+
data-mandu-island="pledges-$id--0"
100+
data-mandu-boundary-id="pledges-$id--0"
101+
data-mandu-route-id="pledges-$id"
102+
data-mandu-client-module="src/client/CommentsSection.client.tsx"
103+
data-mandu-client-export="CommentsSection"
104+
data-hydrate="visible"
105+
></div>
106+
<script type="application/json" data-mandu-props="pledges-$id--0">
107+
{"initialComments":[]}
108+
</script>
109+
```
110+
111+
The actual `data-mandu-src` value is filled from `.mandu/manifest.json`
112+
when the boundary bundle is present. The JSON payload uses Mandu's route data
113+
serializer and escapes script-closing sequences so it is safe inside an HTML
114+
`application/json` script.
115+
116+
Hydration props resolve in this order:
117+
118+
1. `script[data-mandu-props="<boundary-id>"]`
119+
2. Route-level `__MANDU_DATA__` fallback
120+
3. Empty props with a development warning
121+
122+
Named and default client exports both resolve from explicit boundary metadata
123+
before any generated-entry fallback. Repeated renders of the same boundary id
124+
keep the manifest id for the first instance and append an instance suffix for
125+
later props payloads.
126+
127+
Streaming SSR uses the same contract in F42: each compiler-owned boundary
128+
emits its props script immediately next to the marker, and route-filtered
129+
boundary chunks may be preloaded from the manifest. Delayed or out-of-order
130+
props delivery is reserved for the F44 hydration scheduler work.
131+
132+
### Boundary restrictions
133+
134+
F42 intentionally rejects ambiguous boundary shapes at build/dev time:
135+
136+
- Non-empty `children` on transformed client components are not serialized.
137+
- Function props, refs, React elements, symbols, and visible non-plain objects
138+
fail with stable `MANDU_BOUNDARY_*` diagnostics.
139+
- Client boundary modules may not import server-only modules such as Node/Bun
140+
built-ins, `server-only`, non-client `@mandujs/core` paths, or `*.server`
141+
files.
142+
- Compiler-owned boundaries cannot be emitted directly inside known invalid
143+
HTML host contexts such as `table`, `thead`, `tbody`, `tfoot`, `tr`,
144+
`select`, `optgroup`, `option`, `ul`, `ol`, `dl`, or `p`. The current marker
145+
uses sibling `<div>` and `<script>` nodes, so those contexts need a server
146+
wrapper, valid host restructuring, or an explicit island API until
147+
context-safe markers are implemented.
148+
76149
## Island vs partial
77150

78151
An island is a page-level client bundle. Do not render a compiled island as

docs/cli/diagnose.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Every check returns the unified shape:
4646
| `cloneelement_warnings` | `info` (1-10), `warning` (>10) | `Each child in a list should have a unique "key" prop` occurrences in `.mandu/build.log` or `.mandu/dev-server.stderr.log`. Closes #212 (fixed in `@mandujs/core >= 0.32.0`). |
4747
| `dev_artifacts_in_prod` | `error` | `_devtools.js` present when `manifest.env === 'production'` OR `mandu.config.ts` sets `dev.devtools: false`; prerendered HTML referencing devtools scripts. |
4848
| `package_export_gaps` | `error` | User imports of `@mandujs/core/<subpath>` where `<subpath>` is NOT declared in the installed core's `exports` map. Catches the #194 / #202 / #210 pattern. |
49+
| `client_boundary_manifests` | `error` | F42 compiler-owned client boundary records that are missing a route id, bundle manifest entry, module, export name, or route/bundle correlation. Blocks shipping SSR HTML that references a boundary the client cannot hydrate. |
4950

5051
### Legacy checks (MCP composite only)
5152

0 commit comments

Comments
 (0)