@jami-studio/core 0.92.37 → 0.92.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/corpus/README.md CHANGED
@@ -28,4 +28,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
28
28
  ## Generated Counts
29
29
 
30
30
  - core files: 2159
31
- - template files: 5117
31
+ - template files: 5118
@@ -1,5 +1,17 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.92.39
4
+
5
+ ### Patch Changes
6
+
7
+ - 9390f7c: Edge runtimes mount an empty-list `/_agent-native/available-clis` fallback in the core-routes plugin: the real endpoint lives in the Node-only terminal plugin which never ships to workerd, so every page load logged a console 404 from the agent panel's CLI feature probe. Node runtimes keep the terminal plugin's real handler.
8
+
9
+ ## 0.92.38
10
+
11
+ ### Patch Changes
12
+
13
+ - 7e2841f: Cloudflare Pages static shell: render the real landing page instead of the hydration-mismatching manifest fallback — resolve framework-transitive packages (yjs, prosemirror) via module resolution hooks, follow same-origin index redirects with mount-prefix stripping, try both prefixed and unprefixed route shapes, and make the SSR heavy-lib stub callable/constructible so module-scope `new PluginKey(...)` in stubbed client code cannot crash the shell render. Manifest-fallback shells now emit `<html lang="en-US" dir="ltr" data-locale="en-US">` when the root route has a loader, matching the default hydration state. Fixes React #418 hydration errors on served workspace apps.
14
+
3
15
  ## 0.92.37
4
16
 
5
17
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.92.37",
3
+ "version": "0.92.39",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/studio-jami/jami-studio#readme",
6
6
  "bugs": {
@@ -1759,8 +1759,15 @@ export function generateCloudflarePagesStaticShellFromManifest(
1759
1759
  const encodedInitialState = rootRoute.hasLoader
1760
1760
  ? DEFAULT_ROOT_LOADER_REACT_ROUTER_TURBO_STREAM
1761
1761
  : EMPTY_REACT_ROUTER_TURBO_STREAM;
1762
-
1763
- return `<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/><link rel="manifest" href="/manifest.json"/><link rel="icon" type="image/svg+xml" href="/favicon.svg"/>${modulePreloads}${stylesheets}</head><body><div style="display:flex;align-items:center;justify-content:center;height:100vh;width:100%"><svg role="status" aria-label="Loading" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="animation:an-spin 1s linear infinite;opacity:0.7"><path d="M21 12a9 9 0 1 1-6.219-8.56"></path></svg><style>@keyframes an-spin { to { transform: rotate(360deg) } } @media (prefers-color-scheme: dark) { html { background: #09090b; color: #fafafa } }</style></div><script>window.__reactRouterContext = ${JSON.stringify(context)};window.__reactRouterContext.stream = new ReadableStream({start(controller){window.__reactRouterContext.streamController = controller;}}).pipeThrough(new TextEncoderStream());</script><script type="module" async="">${routeModuleScript}</script><!--$--><script>window.__reactRouterContext.streamController.enqueue(${JSON.stringify(encodedInitialState)});</script><!--$--><script>window.__reactRouterContext.streamController.close();</script><!--/$--><!--/$--></body></html>`;
1762
+ // Match the <html> attributes the root route renders during hydration with
1763
+ // the embedded default root-loader state (locale/dir), so recovering from
1764
+ // the fallback shell does not add an attribute mismatch on top of the
1765
+ // structural one.
1766
+ const htmlTag = rootRoute.hasLoader
1767
+ ? '<html lang="en-US" dir="ltr" data-locale="en-US">'
1768
+ : '<html lang="en">';
1769
+
1770
+ return `<!DOCTYPE html>${htmlTag}<head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/><link rel="manifest" href="/manifest.json"/><link rel="icon" type="image/svg+xml" href="/favicon.svg"/>${modulePreloads}${stylesheets}</head><body><div style="display:flex;align-items:center;justify-content:center;height:100vh;width:100%"><svg role="status" aria-label="Loading" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="animation:an-spin 1s linear infinite;opacity:0.7"><path d="M21 12a9 9 0 1 1-6.219-8.56"></path></svg><style>@keyframes an-spin { to { transform: rotate(360deg) } } @media (prefers-color-scheme: dark) { html { background: #09090b; color: #fafafa } }</style></div><script>window.__reactRouterContext = ${JSON.stringify(context)};window.__reactRouterContext.stream = new ReadableStream({start(controller){window.__reactRouterContext.streamController = controller;}}).pipeThrough(new TextEncoderStream());</script><script type="module" async="">${routeModuleScript}</script><!--$--><script>window.__reactRouterContext.streamController.enqueue(${JSON.stringify(encodedInitialState)});</script><!--$--><script>window.__reactRouterContext.streamController.close();</script><!--/$--><!--/$--></body></html>`;
1764
1771
  }
1765
1772
 
1766
1773
  function writeCloudflarePagesStaticShell({
@@ -1784,32 +1791,118 @@ function writeCloudflarePagesStaticShell({
1784
1791
  renderScript,
1785
1792
  `
1786
1793
  import fs from "node:fs";
1787
- import { createRequire } from "node:module";
1794
+ import Module, { createRequire } from "node:module";
1788
1795
  import { pathToFileURL } from "node:url";
1789
1796
 
1790
1797
  const cwd = ${JSON.stringify(cwd)};
1791
1798
  const serverEntry = ${JSON.stringify(serverEntry)};
1792
1799
  const outFile = ${JSON.stringify(outFile)};
1793
1800
  const basePath = ${JSON.stringify(basePath)};
1801
+ // The framework package's own module URL: bundled server chunks can import
1802
+ // packages (yjs, prosemirror-*) that are transitive framework dependencies
1803
+ // and not resolvable from the app directory under strict pnpm layouts. When
1804
+ // app-relative resolution fails, retry from the framework's own context.
1805
+ const frameworkParentUrl = ${JSON.stringify(import.meta.url)};
1806
+
1807
+ if (typeof Module.registerHooks === "function") {
1808
+ Module.registerHooks({
1809
+ resolve(specifier, context, nextResolve) {
1810
+ try {
1811
+ return nextResolve(specifier, context);
1812
+ } catch (error) {
1813
+ if (context?.parentURL && context.parentURL !== frameworkParentUrl) {
1814
+ try {
1815
+ return nextResolve(specifier, { ...context, parentURL: frameworkParentUrl });
1816
+ } catch {}
1817
+ }
1818
+ throw error;
1819
+ }
1820
+ },
1821
+ });
1822
+ }
1794
1823
 
1795
1824
  const requireFromApp = createRequire(cwd + "/package.json");
1796
1825
  const reactRouterEntry = requireFromApp.resolve("react-router");
1797
1826
  const { createRequestHandler } = await import(pathToFileURL(reactRouterEntry).href);
1798
1827
  const serverBuild = await import(pathToFileURL(serverEntry).href);
1799
1828
  const handler = createRequestHandler(serverBuild, "production");
1800
- const pathname = basePath ? basePath + "/" : "/";
1801
- const response = await handler(
1802
- new Request(new URL(pathname, "https://agent-native.local"), {
1803
- headers: { "X-React-Router-SPA-Mode": "yes" },
1804
- }),
1805
- );
1806
- const html = await response.text();
1807
1829
 
1808
- if (!html || !html.includes("__reactRouterContext") || !html.includes("entry.client")) {
1809
- throw new Error("React Router did not render a usable Cloudflare Pages static shell");
1830
+ const origin = "https://agent-native.local";
1831
+ const normalizedBase = basePath ? basePath.replace(/\\/+$/, "") : "";
1832
+
1833
+ function isUsable(html) {
1834
+ return Boolean(
1835
+ html && html.includes("__reactRouterContext") && html.includes("entry.client"),
1836
+ );
1810
1837
  }
1811
1838
 
1812
- fs.writeFileSync(outFile, html);
1839
+ // Render a path, following same-origin redirects (index routes commonly
1840
+ // redirect "/" to the real landing page). Server builds compiled with an
1841
+ // unprefixed router basename ("/") emit redirect targets that still carry
1842
+ // the mount prefix — strip it before re-requesting.
1843
+ async function renderPath(startPath) {
1844
+ let pathname = startPath;
1845
+ let response;
1846
+ for (let hop = 0; hop < 5; hop++) {
1847
+ response = await handler(
1848
+ new Request(new URL(pathname, origin), {
1849
+ headers: { "X-React-Router-SPA-Mode": "yes" },
1850
+ }),
1851
+ );
1852
+ if (response.status < 300 || response.status >= 400) break;
1853
+ const location = response.headers.get("location");
1854
+ if (!location) break;
1855
+ const target = new URL(location, new URL(pathname, origin));
1856
+ if (target.origin !== origin) break;
1857
+ let nextPath = target.pathname;
1858
+ if (
1859
+ normalizedBase &&
1860
+ (nextPath === normalizedBase || nextPath.startsWith(normalizedBase + "/")) &&
1861
+ !(startPath === normalizedBase + "/" || startPath.startsWith(normalizedBase + "/"))
1862
+ ) {
1863
+ nextPath = nextPath.slice(normalizedBase.length) || "/";
1864
+ }
1865
+ if (nextPath === pathname) break;
1866
+ pathname = nextPath;
1867
+ }
1868
+ const html = await response.text();
1869
+ return { pathname, status: response.status, html };
1870
+ }
1871
+
1872
+ // Route matching varies per template: some builds register routes under the
1873
+ // mount prefix, others compile with basename "/" and rely on the worker
1874
+ // entry stripping the prefix. Try both shapes and prefer a 2xx render;
1875
+ // otherwise keep the first usable render of any status (apps without an
1876
+ // index route ship their root-boundary render as the shell).
1877
+ const candidates = normalizedBase ? [normalizedBase + "/", "/"] : ["/"];
1878
+ let best = null;
1879
+ for (const candidate of candidates) {
1880
+ let result;
1881
+ try {
1882
+ result = await renderPath(candidate);
1883
+ } catch (error) {
1884
+ console.warn("[deploy] Static shell render threw at " + candidate + ": " + (error && error.message ? error.message : error));
1885
+ continue;
1886
+ }
1887
+ if (!isUsable(result.html)) continue;
1888
+ if (result.status >= 200 && result.status < 300) {
1889
+ best = result;
1890
+ break;
1891
+ }
1892
+ if (!best) best = result;
1893
+ }
1894
+
1895
+ if (!best) {
1896
+ throw new Error(
1897
+ "React Router did not render a usable Cloudflare Pages static shell for any of: " +
1898
+ candidates.join(", "),
1899
+ );
1900
+ }
1901
+
1902
+ console.log(
1903
+ "[deploy] Static shell rendered from " + best.pathname + " (status " + best.status + ").",
1904
+ );
1905
+ fs.writeFileSync(outFile, best.html);
1813
1906
  process.exit(0);
1814
1907
  `,
1815
1908
  );
@@ -93,6 +93,7 @@ import {
93
93
  MCP_EMBED_CORS_ALLOW_HEADERS,
94
94
  shouldAllowMcpEmbedCredentials,
95
95
  } from "../shared/mcp-embed-headers.js";
96
+ import { isNodeRuntime } from "../shared/runtime.js";
96
97
  import { track } from "../tracking/index.js";
97
98
  import { registerBuiltinProviders } from "../tracking/providers.js";
98
99
  import { validateTrackPayload } from "../tracking/route.js";
@@ -1233,6 +1234,19 @@ export function createCoreRoutesPlugin(
1233
1234
  );
1234
1235
  }
1235
1236
 
1237
+ // Available CLIs — the real endpoint lives in the terminal plugin,
1238
+ // which is Node-only and never ships to edge runtimes. Mount an
1239
+ // empty-list fallback there so the agent panel's feature probe gets a
1240
+ // clean 200 instead of console-noise 404s (issue: optional-endpoint
1241
+ // graceful feature detection). Node runtimes keep the terminal
1242
+ // plugin's real handler.
1243
+ if (!isNodeRuntime()) {
1244
+ getH3App(nitroApp).use(
1245
+ `${P}/available-clis`,
1246
+ defineEventHandler(() => []),
1247
+ );
1248
+ }
1249
+
1236
1250
  // ─── Durable sandbox execution processor ─────────────────────────
1237
1251
  // Self-fired by run-code's background queue (see
1238
1252
  // coding-tools/sandbox/background.ts): the enqueueing request POSTs here
@@ -1923,15 +1923,18 @@ function ssrStubPlugin(packages: string[]): Plugin | null {
1923
1923
  load(id) {
1924
1924
  if (id !== STUB_ID) return null;
1925
1925
  // Proxy that answers any property access with itself — lets dead
1926
- // import/re-export chains parse without blowing up, and still throws
1927
- // if code actually tries to call any of it on the server.
1926
+ // import/re-export chains parse without blowing up. Callable and
1927
+ // constructible (module-scope `new PluginKey(...)` in stubbed client
1928
+ // code must not crash server-side shell renders); every call returns
1929
+ // another stub.
1928
1930
  return (
1929
1931
  "const handler = { get(_, p) { " +
1930
1932
  "if (p === Symbol.toPrimitive) return () => ''; " +
1931
1933
  "if (p === 'then') return undefined; " +
1932
- "return new Proxy(() => {}, handler); " +
1933
- "} };" +
1934
- "const stub = new Proxy(() => {}, handler);" +
1934
+ "return new Proxy(function () {}, handler); " +
1935
+ "}, apply() { return new Proxy(function () {}, handler); }, " +
1936
+ "construct() { return new Proxy(function () {}, handler); } };" +
1937
+ "const stub = new Proxy(function () {}, handler);" +
1935
1938
  "export default stub;" +
1936
1939
  namedExports.map((name) => `export const ${name} = stub;`).join("")
1937
1940
  );
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-12
4
+ ---
5
+
6
+ Image uploads now work on serverless runtimes without native image processing - dimensions are read from file headers and thumbnails fall back to the original image
@@ -7,41 +7,201 @@ import type {
7
7
  } from "../../shared/api.js";
8
8
  import { aspectRatioValue } from "./preset-skeleton.js";
9
9
 
10
+ /**
11
+ * True when the error means the sharp native module cannot run in this
12
+ * runtime (e.g. the Cloudflare Pages worker build stubs `sharp` with a
13
+ * throwing proxy, and serverless Linux builds can fail to load the native
14
+ * binary). Used to degrade gracefully instead of failing the whole upload.
15
+ */
16
+ function isSharpUnavailableError(error: unknown): boolean {
17
+ const message = error instanceof Error ? error.message : String(error);
18
+ return /sharp unavailable|Could not load the "?sharp"? module/i.test(
19
+ message,
20
+ );
21
+ }
22
+
23
+ /**
24
+ * Pure-JS dimension sniffing for the upload formats the app accepts.
25
+ * Fallback path for runtimes where sharp is unavailable (workerd); parses
26
+ * container headers only — no decoding.
27
+ */
28
+ export function sniffImageDimensions(data: Uint8Array): {
29
+ width: number | null;
30
+ height: number | null;
31
+ mimeType: string | null;
32
+ } {
33
+ const none = { width: null, height: null, mimeType: null };
34
+ if (data.length < 16) return none;
35
+ // PNG: IHDR width/height are big-endian uint32 at offsets 16/20.
36
+ if (
37
+ data[0] === 0x89 &&
38
+ data[1] === 0x50 &&
39
+ data[2] === 0x4e &&
40
+ data[3] === 0x47
41
+ ) {
42
+ if (data.length < 24) return { ...none, mimeType: "image/png" };
43
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
44
+ return {
45
+ width: view.getUint32(16),
46
+ height: view.getUint32(20),
47
+ mimeType: "image/png",
48
+ };
49
+ }
50
+ // JPEG: walk segment markers to the first SOF frame header.
51
+ if (data[0] === 0xff && data[1] === 0xd8 && data[2] === 0xff) {
52
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
53
+ let offset = 2;
54
+ while (offset + 9 < data.length) {
55
+ if (data[offset] !== 0xff) {
56
+ offset += 1;
57
+ continue;
58
+ }
59
+ const marker = data[offset + 1];
60
+ // Standalone markers without length payloads.
61
+ if (marker === 0xd8 || (marker >= 0xd0 && marker <= 0xd7)) {
62
+ offset += 2;
63
+ continue;
64
+ }
65
+ const isSof =
66
+ marker >= 0xc0 &&
67
+ marker <= 0xcf &&
68
+ marker !== 0xc4 &&
69
+ marker !== 0xc8 &&
70
+ marker !== 0xcc;
71
+ if (isSof) {
72
+ return {
73
+ height: view.getUint16(offset + 5),
74
+ width: view.getUint16(offset + 7),
75
+ mimeType: "image/jpeg",
76
+ };
77
+ }
78
+ const length = view.getUint16(offset + 2);
79
+ if (length < 2) break;
80
+ offset += 2 + length;
81
+ }
82
+ return { ...none, mimeType: "image/jpeg" };
83
+ }
84
+ // WebP: RIFF container; VP8/VP8L/VP8X chunks carry dimensions.
85
+ if (
86
+ Buffer.from(data.subarray(0, 4)).toString("ascii") === "RIFF" &&
87
+ Buffer.from(data.subarray(8, 12)).toString("ascii") === "WEBP" &&
88
+ data.length >= 30
89
+ ) {
90
+ const chunk = Buffer.from(data.subarray(12, 16)).toString("ascii");
91
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
92
+ if (chunk === "VP8X") {
93
+ return {
94
+ width: 1 + (data[24] | (data[25] << 8) | (data[26] << 16)),
95
+ height: 1 + (data[27] | (data[28] << 8) | (data[29] << 16)),
96
+ mimeType: "image/webp",
97
+ };
98
+ }
99
+ if (chunk === "VP8L" && data[20] === 0x2f) {
100
+ const bits = view.getUint32(21, true);
101
+ return {
102
+ width: (bits & 0x3fff) + 1,
103
+ height: ((bits >> 14) & 0x3fff) + 1,
104
+ mimeType: "image/webp",
105
+ };
106
+ }
107
+ if (chunk === "VP8 ") {
108
+ // Lossy keyframe: 3-byte frame tag then 9d 01 2a start code.
109
+ if (data[23] === 0x9d && data[24] === 0x01 && data[25] === 0x2a) {
110
+ return {
111
+ width: view.getUint16(26, true) & 0x3fff,
112
+ height: view.getUint16(28, true) & 0x3fff,
113
+ mimeType: "image/webp",
114
+ };
115
+ }
116
+ return { ...none, mimeType: "image/webp" };
117
+ }
118
+ return { ...none, mimeType: "image/webp" };
119
+ }
120
+ // AVIF: scan for the `ispe` (image spatial extents) property box —
121
+ // version/flags(4) width(4) height(4) big-endian after the fourcc.
122
+ if (
123
+ Buffer.from(data.subarray(4, 12)).toString("ascii").includes("ftyp") &&
124
+ Buffer.from(data.subarray(8, 12)).toString("ascii") === "avif"
125
+ ) {
126
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
127
+ const limit = Math.min(data.length - 16, 65536);
128
+ for (let i = 12; i < limit; i++) {
129
+ if (
130
+ data[i] === 0x69 && // i
131
+ data[i + 1] === 0x73 && // s
132
+ data[i + 2] === 0x70 && // p
133
+ data[i + 3] === 0x65 // e
134
+ ) {
135
+ return {
136
+ width: view.getUint32(i + 8),
137
+ height: view.getUint32(i + 12),
138
+ mimeType: "image/avif",
139
+ };
140
+ }
141
+ }
142
+ return { ...none, mimeType: "image/avif" };
143
+ }
144
+ return none;
145
+ }
146
+
10
147
  export async function imageInfo(buffer: Buffer): Promise<{
11
148
  width: number | null;
12
149
  height: number | null;
13
150
  mimeType: string;
14
151
  sizeBytes: number;
15
152
  }> {
16
- const img = sharp(buffer, { failOn: "none" });
17
- const meta = await img.metadata();
18
- const format = meta.format === "jpeg" ? "jpeg" : meta.format || "png";
19
- return {
20
- width: meta.width ?? null,
21
- height: meta.height ?? null,
22
- mimeType:
23
- format === "jpg" || format === "jpeg" ? "image/jpeg" : `image/${format}`,
24
- sizeBytes: buffer.byteLength,
25
- };
153
+ try {
154
+ const img = sharp(buffer, { failOn: "none" });
155
+ const meta = await img.metadata();
156
+ const format = meta.format === "jpeg" ? "jpeg" : meta.format || "png";
157
+ return {
158
+ width: meta.width ?? null,
159
+ height: meta.height ?? null,
160
+ mimeType:
161
+ format === "jpg" || format === "jpeg"
162
+ ? "image/jpeg"
163
+ : `image/${format}`,
164
+ sizeBytes: buffer.byteLength,
165
+ };
166
+ } catch (error) {
167
+ if (!isSharpUnavailableError(error)) throw error;
168
+ // Passthrough-for-originals: header-parse dimensions so uploads keep
169
+ // working on runtimes without sharp. Callers fall back to the declared
170
+ // mime type when the sniffer returns null.
171
+ const sniffed = sniffImageDimensions(buffer);
172
+ return {
173
+ width: sniffed.width,
174
+ height: sniffed.height,
175
+ mimeType: sniffed.mimeType ?? "",
176
+ sizeBytes: buffer.byteLength,
177
+ };
178
+ }
26
179
  }
27
180
 
28
181
  export async function makeThumbnail(buffer: Buffer): Promise<{
29
182
  buffer: Buffer;
30
183
  mimeType: string;
31
- }> {
32
- return {
33
- buffer: await sharp(buffer, { failOn: "none" })
34
- .rotate()
35
- .resize({
36
- width: 640,
37
- height: 640,
38
- fit: "inside",
39
- withoutEnlargement: true,
40
- })
41
- .webp({ quality: 82 })
42
- .toBuffer(),
43
- mimeType: "image/webp",
44
- };
184
+ } | null> {
185
+ try {
186
+ return {
187
+ buffer: await sharp(buffer, { failOn: "none" })
188
+ .rotate()
189
+ .resize({
190
+ width: 640,
191
+ height: 640,
192
+ fit: "inside",
193
+ withoutEnlargement: true,
194
+ })
195
+ .webp({ quality: 82 })
196
+ .toBuffer(),
197
+ mimeType: "image/webp",
198
+ };
199
+ } catch (error) {
200
+ if (!isSharpUnavailableError(error)) throw error;
201
+ // No thumbnail on runtimes without sharp — readers already fall back to
202
+ // the original asset when `thumbnailObjectKey` is null.
203
+ return null;
204
+ }
45
205
  }
46
206
 
47
207
  export async function extractDominantColors(buffer: Buffer): Promise<string[]> {
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/deploy/build.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;GAYG;AAmCH,OAAO,EAML,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAI7B,eAAO,MAAM,6BAA6B,UAiBzC,CAAC;AAEF,eAAO,MAAM,mCAAmC,UAiB/C,CAAC;AACF,eAAO,MAAM,8BAA8B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAuIjE,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,UAUnC,CAAC;AAEF,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,MAAM,GACb,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAaxB;AAoCD,eAAO,MAAM,2CAA2C,EAAE,MAAM,CAC9D,MAAM,EACN,MAAM,CAmSP,CAAC;AAEF,MAAM,WAAW,0BAA0B;IACzC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;;;;;;;;OASG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,EAClB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,sBAAsB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9C,MAAM,CA+BR;AAED;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAC9C,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAcxB;AAED;;;GAGG;AACH,wBAAgB,8CAA8C,CAC5D,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAoBxB;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,MAAM,GAAG,IAAI,CAKf;AAED,UAAU,wBAAwB;IAChC,KAAK,EAAE,6BAA6B,CAAC;IACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,6BAA6B,CAAC,CAAC;IACtD,GAAG,EAAE,MAAM,CAAC;CACb;AAED,UAAU,6BAA6B;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,UAAU,6BAA6B;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAwBD,wBAAgB,wCAAwC,CACtD,WAAW,EAAE,MAAM,EAAE,GACpB,MAAM,CAaR;AAgBD,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC,CAAC;AAgBvE,wBAAgB,yCAAyC,CACvD,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,MAAM,EACjB,WAAW,SAAK,GACf,IAAI,CAQN;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,eAAe,EAAE,EACzB,WAAW,EAAE,MAAM,EAAE,EACrB,kBAAkB,GAAE,MAAM,EAAO,EACjC,OAAO,GAAE,gBAAgB,EAAO,EAChC,aAAa,GAAE,oBAAoB,GAAG,IAAW,EACjD,mBAAmB,GAAE,MAAM,EAAO,EAClC,gBAAgB,SAAmC,EACnD,OAAO,GAAE,0BAA+B,GACvC,MAAM,CA4rBR;AA4BD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sCAAsC,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAoC5E;AAkED,wBAAgB,8CAA8C,CAC5D,QAAQ,EAAE,wBAAwB,EAClC,QAAQ,SAAmC,GAC1C,MAAM,CAiCR;AAihBD,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAE9C;AAoED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EAAE,GACb;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAKlC;AA6DD,wBAAgB,OAAO,CACrB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,iBAAiB,cAAoB,QAoCtC;AAiCD,KAAK,0BAA0B,GAAG,OAAO,GAAG,KAAK,CAAC;AAQlD,wBAAgB,qCAAqC,CACnD,YAAY,GAAE,MAAM,CAAC,QAA2B,EAChD,QAAQ,GAAE,MAAM,CAAC,YAA2B,EAC5C,UAAU,GAAE,0BAA0B,GAAG,IAAgD,GACxF,OAAO,CAOT;AAqED,wBAAgB,gCAAgC,CAC9C,gBAAgB,EAAE,MAAM,EAAE,GACzB,MAAM,GAAG,IAAI,CA8Bf;AAED,wBAAgB,0BAA0B,CACxC,gBAAgB,EAAE,MAAM,EAAE,GACzB,KAAK,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAqCpD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gCAAgC,IAAI,OAAO,CAK1D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AACH,wBAAgB,2CAA2C,CACzD,UAAU,EAAE,MAAM,GACjB,IAAI,CA0GN;AA0DD;;;;;GAKG;AACH,wBAAgB,wCAAwC,CACtD,SAAS,EAAE,MAAM,GAChB,MAAM,EAAE,CA+CV;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA0BpE;AAED;;;;;;;;GAQG;AACH,wBAAgB,iCAAiC,CAAC,IAAI,EAAE;IACtD,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,MAAM,EAAE,CAwDX;AAED,wBAAgB,sCAAsC,CACpD,UAAU,EAAE,MAAM,GACjB,IAAI,CAwJN;AAED;;;;;GAKG;AACH,wBAAgB,mCAAmC,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAY5E;AA6GD;;;;;;GAMG;AACH,wBAAgB,yCAAyC,CACvD,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,IAAI,CAqDN;AA6ID;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,gBAAgB,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,UAAU,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,GAAG,CAAC;IACX,KAAK,EAAE,eAAe,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,yBAAyB,GAC9B,OAAO,CAAC,IAAI,CAAC,CA+Bf;AAkBD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iCAAiC,YAAI,KAAK,CAAU,CAAC;AAElE;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,MAAM,GACnB,IAAI,GAAG,SAAS,MAAM,EAAE,CAK1B"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/deploy/build.ts"],"names":[],"mappings":"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"}
@@ -1501,7 +1501,14 @@ export function generateCloudflarePagesStaticShellFromManifest(manifest, basePat
1501
1501
  const encodedInitialState = rootRoute.hasLoader
1502
1502
  ? DEFAULT_ROOT_LOADER_REACT_ROUTER_TURBO_STREAM
1503
1503
  : EMPTY_REACT_ROUTER_TURBO_STREAM;
1504
- return `<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/><link rel="manifest" href="/manifest.json"/><link rel="icon" type="image/svg+xml" href="/favicon.svg"/>${modulePreloads}${stylesheets}</head><body><div style="display:flex;align-items:center;justify-content:center;height:100vh;width:100%"><svg role="status" aria-label="Loading" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="animation:an-spin 1s linear infinite;opacity:0.7"><path d="M21 12a9 9 0 1 1-6.219-8.56"></path></svg><style>@keyframes an-spin { to { transform: rotate(360deg) } } @media (prefers-color-scheme: dark) { html { background: #09090b; color: #fafafa } }</style></div><script>window.__reactRouterContext = ${JSON.stringify(context)};window.__reactRouterContext.stream = new ReadableStream({start(controller){window.__reactRouterContext.streamController = controller;}}).pipeThrough(new TextEncoderStream());</script><script type="module" async="">${routeModuleScript}</script><!--$--><script>window.__reactRouterContext.streamController.enqueue(${JSON.stringify(encodedInitialState)});</script><!--$--><script>window.__reactRouterContext.streamController.close();</script><!--/$--><!--/$--></body></html>`;
1504
+ // Match the <html> attributes the root route renders during hydration with
1505
+ // the embedded default root-loader state (locale/dir), so recovering from
1506
+ // the fallback shell does not add an attribute mismatch on top of the
1507
+ // structural one.
1508
+ const htmlTag = rootRoute.hasLoader
1509
+ ? '<html lang="en-US" dir="ltr" data-locale="en-US">'
1510
+ : '<html lang="en">';
1511
+ return `<!DOCTYPE html>${htmlTag}<head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/><link rel="manifest" href="/manifest.json"/><link rel="icon" type="image/svg+xml" href="/favicon.svg"/>${modulePreloads}${stylesheets}</head><body><div style="display:flex;align-items:center;justify-content:center;height:100vh;width:100%"><svg role="status" aria-label="Loading" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="animation:an-spin 1s linear infinite;opacity:0.7"><path d="M21 12a9 9 0 1 1-6.219-8.56"></path></svg><style>@keyframes an-spin { to { transform: rotate(360deg) } } @media (prefers-color-scheme: dark) { html { background: #09090b; color: #fafafa } }</style></div><script>window.__reactRouterContext = ${JSON.stringify(context)};window.__reactRouterContext.stream = new ReadableStream({start(controller){window.__reactRouterContext.streamController = controller;}}).pipeThrough(new TextEncoderStream());</script><script type="module" async="">${routeModuleScript}</script><!--$--><script>window.__reactRouterContext.streamController.enqueue(${JSON.stringify(encodedInitialState)});</script><!--$--><script>window.__reactRouterContext.streamController.close();</script><!--/$--><!--/$--></body></html>`;
1505
1512
  }
1506
1513
  function writeCloudflarePagesStaticShell({ serverDir, distDir, tmpDir, }) {
1507
1514
  const serverEntry = path.join(serverDir, "index.js");
@@ -1513,32 +1520,118 @@ function writeCloudflarePagesStaticShell({ serverDir, distDir, tmpDir, }) {
1513
1520
  const basePath = normalizeConfiguredAppBasePath();
1514
1521
  fs.writeFileSync(renderScript, `
1515
1522
  import fs from "node:fs";
1516
- import { createRequire } from "node:module";
1523
+ import Module, { createRequire } from "node:module";
1517
1524
  import { pathToFileURL } from "node:url";
1518
1525
 
1519
1526
  const cwd = ${JSON.stringify(cwd)};
1520
1527
  const serverEntry = ${JSON.stringify(serverEntry)};
1521
1528
  const outFile = ${JSON.stringify(outFile)};
1522
1529
  const basePath = ${JSON.stringify(basePath)};
1530
+ // The framework package's own module URL: bundled server chunks can import
1531
+ // packages (yjs, prosemirror-*) that are transitive framework dependencies
1532
+ // and not resolvable from the app directory under strict pnpm layouts. When
1533
+ // app-relative resolution fails, retry from the framework's own context.
1534
+ const frameworkParentUrl = ${JSON.stringify(import.meta.url)};
1535
+
1536
+ if (typeof Module.registerHooks === "function") {
1537
+ Module.registerHooks({
1538
+ resolve(specifier, context, nextResolve) {
1539
+ try {
1540
+ return nextResolve(specifier, context);
1541
+ } catch (error) {
1542
+ if (context?.parentURL && context.parentURL !== frameworkParentUrl) {
1543
+ try {
1544
+ return nextResolve(specifier, { ...context, parentURL: frameworkParentUrl });
1545
+ } catch {}
1546
+ }
1547
+ throw error;
1548
+ }
1549
+ },
1550
+ });
1551
+ }
1523
1552
 
1524
1553
  const requireFromApp = createRequire(cwd + "/package.json");
1525
1554
  const reactRouterEntry = requireFromApp.resolve("react-router");
1526
1555
  const { createRequestHandler } = await import(pathToFileURL(reactRouterEntry).href);
1527
1556
  const serverBuild = await import(pathToFileURL(serverEntry).href);
1528
1557
  const handler = createRequestHandler(serverBuild, "production");
1529
- const pathname = basePath ? basePath + "/" : "/";
1530
- const response = await handler(
1531
- new Request(new URL(pathname, "https://agent-native.local"), {
1532
- headers: { "X-React-Router-SPA-Mode": "yes" },
1533
- }),
1534
- );
1535
- const html = await response.text();
1536
1558
 
1537
- if (!html || !html.includes("__reactRouterContext") || !html.includes("entry.client")) {
1538
- throw new Error("React Router did not render a usable Cloudflare Pages static shell");
1559
+ const origin = "https://agent-native.local";
1560
+ const normalizedBase = basePath ? basePath.replace(/\\/+$/, "") : "";
1561
+
1562
+ function isUsable(html) {
1563
+ return Boolean(
1564
+ html && html.includes("__reactRouterContext") && html.includes("entry.client"),
1565
+ );
1539
1566
  }
1540
1567
 
1541
- fs.writeFileSync(outFile, html);
1568
+ // Render a path, following same-origin redirects (index routes commonly
1569
+ // redirect "/" to the real landing page). Server builds compiled with an
1570
+ // unprefixed router basename ("/") emit redirect targets that still carry
1571
+ // the mount prefix — strip it before re-requesting.
1572
+ async function renderPath(startPath) {
1573
+ let pathname = startPath;
1574
+ let response;
1575
+ for (let hop = 0; hop < 5; hop++) {
1576
+ response = await handler(
1577
+ new Request(new URL(pathname, origin), {
1578
+ headers: { "X-React-Router-SPA-Mode": "yes" },
1579
+ }),
1580
+ );
1581
+ if (response.status < 300 || response.status >= 400) break;
1582
+ const location = response.headers.get("location");
1583
+ if (!location) break;
1584
+ const target = new URL(location, new URL(pathname, origin));
1585
+ if (target.origin !== origin) break;
1586
+ let nextPath = target.pathname;
1587
+ if (
1588
+ normalizedBase &&
1589
+ (nextPath === normalizedBase || nextPath.startsWith(normalizedBase + "/")) &&
1590
+ !(startPath === normalizedBase + "/" || startPath.startsWith(normalizedBase + "/"))
1591
+ ) {
1592
+ nextPath = nextPath.slice(normalizedBase.length) || "/";
1593
+ }
1594
+ if (nextPath === pathname) break;
1595
+ pathname = nextPath;
1596
+ }
1597
+ const html = await response.text();
1598
+ return { pathname, status: response.status, html };
1599
+ }
1600
+
1601
+ // Route matching varies per template: some builds register routes under the
1602
+ // mount prefix, others compile with basename "/" and rely on the worker
1603
+ // entry stripping the prefix. Try both shapes and prefer a 2xx render;
1604
+ // otherwise keep the first usable render of any status (apps without an
1605
+ // index route ship their root-boundary render as the shell).
1606
+ const candidates = normalizedBase ? [normalizedBase + "/", "/"] : ["/"];
1607
+ let best = null;
1608
+ for (const candidate of candidates) {
1609
+ let result;
1610
+ try {
1611
+ result = await renderPath(candidate);
1612
+ } catch (error) {
1613
+ console.warn("[deploy] Static shell render threw at " + candidate + ": " + (error && error.message ? error.message : error));
1614
+ continue;
1615
+ }
1616
+ if (!isUsable(result.html)) continue;
1617
+ if (result.status >= 200 && result.status < 300) {
1618
+ best = result;
1619
+ break;
1620
+ }
1621
+ if (!best) best = result;
1622
+ }
1623
+
1624
+ if (!best) {
1625
+ throw new Error(
1626
+ "React Router did not render a usable Cloudflare Pages static shell for any of: " +
1627
+ candidates.join(", "),
1628
+ );
1629
+ }
1630
+
1631
+ console.log(
1632
+ "[deploy] Static shell rendered from " + best.pathname + " (status " + best.status + ").",
1633
+ );
1634
+ fs.writeFileSync(outFile, best.html);
1542
1635
  process.exit(0);
1543
1636
  `);
1544
1637
  try {