@crossworks/app-build 0.232.244 → 0.232.246

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.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/src/kit.ts +33 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crossworks/app-build",
3
- "version": "0.232.244",
3
+ "version": "0.232.246",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -11,7 +11,7 @@
11
11
  "src"
12
12
  ],
13
13
  "dependencies": {
14
- "@mantle/share-ui": "npm:@crossworks/share-ui@0.232.244",
14
+ "@mantle/share-ui": "npm:@crossworks/share-ui@0.232.246",
15
15
  "@tailwindcss/oxide": "^4.3.3",
16
16
  "esbuild": "^0.28.2",
17
17
  "lucide-react": "^1.41.0",
package/src/kit.ts CHANGED
@@ -212,6 +212,13 @@ export const host = {
212
212
  resize: (h) => window.parent.postMessage({ v: 1, kind: 'resize', height: h }, '*'),
213
213
  notifyError: (msg) => window.parent.postMessage({ v: 1, kind: 'error', message: String(msg) }, '*'),
214
214
  onAnnotate: (fn) => { annotateListeners.add(fn); return () => annotateListeners.delete(fn); },
215
+ // The host shows a loader until the app is ready to be seen: mounted,
216
+ // painted, and its first bridge requests settled. An app loading what the
217
+ // host can't see (a heavy computation, a big client-side parse) calls
218
+ // holdReady() while it first renders and ready() when it's done. The host
219
+ // reveals anyway after a few seconds, so a forgotten ready() can't hang it.
220
+ holdReady: () => window.parent.postMessage({ v: 1, kind: 'ready.hold' }, '*'),
221
+ ready: () => window.parent.postMessage({ v: 1, kind: 'ready.release' }, '*'),
215
222
  },
216
223
  // Team-hub surface only. get() rejects everywhere else (owner editor,
217
224
  // ordinary shares) — hub apps catch that and render a local preview. The
@@ -245,13 +252,37 @@ class ErrorBoundary extends React.Component {
245
252
  }
246
253
  }
247
254
 
255
+ // Posts \`ready\` once the first commit has painted. It used to be posted in
256
+ // the same tick as root.render(), before React had rendered anything, so the
257
+ // host revealed a blank frame. Children's mount effects run before this one,
258
+ // so an app's first bridge requests (and any holdReady) reach the host first.
259
+ //
260
+ // The next frame OR a short timer, whichever comes first: a background tab
261
+ // runs no animation frames, and waiting on one alone left an app opened in a
262
+ // background tab unready until it was shown (and tripped the host watchdog).
263
+ function ReadySignal(props) {
264
+ React.useEffect(() => {
265
+ let sent = false;
266
+ const send = () => {
267
+ if (sent) return;
268
+ sent = true;
269
+ window.parent.postMessage({ v: 1, kind: 'ready' }, '*');
270
+ };
271
+ requestAnimationFrame(send);
272
+ const t = setTimeout(send, 100);
273
+ return () => clearTimeout(t);
274
+ }, []);
275
+ return props.children;
276
+ }
277
+
248
278
  export function __mount(App) {
249
279
  const el = document.getElementById('root');
250
280
  if (!el) return;
251
281
  const root = createRoot(el);
252
- root.render(React.createElement(ErrorBoundary, null, React.createElement(App)));
282
+ root.render(
283
+ React.createElement(ReadySignal, null, React.createElement(ErrorBoundary, null, React.createElement(App))),
284
+ );
253
285
  const send = () => host.ui.resize(Math.ceil(document.documentElement.scrollHeight));
254
- window.parent.postMessage({ v: 1, kind: 'ready' }, '*');
255
286
  requestAnimationFrame(send);
256
287
  try { new ResizeObserver(send).observe(document.body); } catch {}
257
288
  }