@lotics/cli 0.55.0 → 0.56.0
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/dist/dev/wrapper_page.d.ts +5 -3
- package/dist/dev/wrapper_page.js +33 -27
- package/dist/src/cli.js +44 -37
- package/dist/starter_template.js +16 -13
- package/package.json +1 -1
|
@@ -24,9 +24,11 @@
|
|
|
24
24
|
* wrapper → iframe: { id, type: "result", data } | { id, type: "error", message }
|
|
25
25
|
* streaming (op "agentRun"): { id, type: "stream-chunk", chunk } * → { id, type: "stream-end" };
|
|
26
26
|
* the iframe aborts with { id, type: "abort" }.
|
|
27
|
-
* urlState (useUrlState
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* urlState (useUrlState): the iframe reads/writes the wrapper's address bar via
|
|
28
|
+
* urlState.get/set; set writes in place (replaceState), and browser
|
|
29
|
+
* back/forward broadcast { type: "url-state", params } back to the iframe.
|
|
30
|
+
* (In-app routing isn't here — the app owns the iframe's own url; see
|
|
31
|
+
* @lotics/app-sdk/router.)
|
|
30
32
|
*/
|
|
31
33
|
export interface WrapperPageArgs {
|
|
32
34
|
app_name: string;
|
package/dist/dev/wrapper_page.js
CHANGED
|
@@ -24,9 +24,11 @@
|
|
|
24
24
|
* wrapper → iframe: { id, type: "result", data } | { id, type: "error", message }
|
|
25
25
|
* streaming (op "agentRun"): { id, type: "stream-chunk", chunk } * → { id, type: "stream-end" };
|
|
26
26
|
* the iframe aborts with { id, type: "abort" }.
|
|
27
|
-
* urlState (useUrlState
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* urlState (useUrlState): the iframe reads/writes the wrapper's address bar via
|
|
28
|
+
* urlState.get/set; set writes in place (replaceState), and browser
|
|
29
|
+
* back/forward broadcast { type: "url-state", params } back to the iframe.
|
|
30
|
+
* (In-app routing isn't here — the app owns the iframe's own url; see
|
|
31
|
+
* @lotics/app-sdk/router.)
|
|
30
32
|
*/
|
|
31
33
|
export function buildWrapperPage(args) {
|
|
32
34
|
const { app_name, app_id, workspace_id, vite_url, api_url } = args;
|
|
@@ -78,9 +80,28 @@ export function buildWrapperPage(args) {
|
|
|
78
80
|
const iframe = document.getElementById("app");
|
|
79
81
|
|
|
80
82
|
// Pass the wrapper's own origin to the app via ?lotics_host= so the app
|
|
81
|
-
// SDK can origin-lock its postMessage bridge
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
// SDK can origin-lock its postMessage bridge, and bake the saved screen
|
|
84
|
+
// (the wrapper url's _loc, kept current by AppRouter's mirror) into the src
|
|
85
|
+
// path so a refresh boots the app at that screen — mirrors the production
|
|
86
|
+
// host. _loc is honoured only when it resolves same-origin to the Vite
|
|
87
|
+
// server (a url param flowing into an iframe src is a redirect vector);
|
|
88
|
+
// anything else falls back to the app root.
|
|
89
|
+
const appSrc = new URL(VITE_URL);
|
|
90
|
+
const savedLoc = new URLSearchParams(window.location.search).get("_loc");
|
|
91
|
+
if (savedLoc) {
|
|
92
|
+
try {
|
|
93
|
+
const resolved = new URL(savedLoc, VITE_ORIGIN);
|
|
94
|
+
if (resolved.origin === VITE_ORIGIN) {
|
|
95
|
+
appSrc.pathname = resolved.pathname;
|
|
96
|
+
appSrc.search = resolved.search;
|
|
97
|
+
appSrc.hash = resolved.hash;
|
|
98
|
+
}
|
|
99
|
+
} catch (e) {
|
|
100
|
+
// malformed _loc -> app root
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
appSrc.searchParams.set("lotics_host", window.location.origin);
|
|
104
|
+
iframe.src = appSrc.toString();
|
|
84
105
|
|
|
85
106
|
async function rpc(op, payload) {
|
|
86
107
|
const res = await fetch("/_rpc", {
|
|
@@ -185,7 +206,6 @@ export function buildWrapperPage(args) {
|
|
|
185
206
|
});
|
|
186
207
|
return out;
|
|
187
208
|
}
|
|
188
|
-
var lastPushAt = 0;
|
|
189
209
|
function handleUrlStateSet(payload) {
|
|
190
210
|
const params = (payload && payload.params) || {};
|
|
191
211
|
const sp = new URLSearchParams(window.location.search);
|
|
@@ -198,21 +218,9 @@ export function buildWrapperPage(args) {
|
|
|
198
218
|
});
|
|
199
219
|
const qs = sp.toString();
|
|
200
220
|
const url = window.location.pathname + (qs ? "?" + qs : "") + window.location.hash;
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
|
|
204
|
-
const now = Date.now();
|
|
205
|
-
if (payload && payload.push && now - lastPushAt > 100) {
|
|
206
|
-
lastPushAt = now;
|
|
207
|
-
window.history.pushState(null, "", url);
|
|
208
|
-
} else {
|
|
209
|
-
window.history.replaceState(null, "", url);
|
|
210
|
-
}
|
|
211
|
-
return undefined;
|
|
212
|
-
}
|
|
213
|
-
function handleUrlStateGo(payload) {
|
|
214
|
-
const delta = payload && payload.delta;
|
|
215
|
-
if (typeof delta === "number") window.history.go(delta);
|
|
221
|
+
// View-state writes in place — never a history entry. replaceState doesn't
|
|
222
|
+
// fire popstate, so no echo. Mirrors the production host.
|
|
223
|
+
window.history.replaceState(null, "", url);
|
|
216
224
|
return undefined;
|
|
217
225
|
}
|
|
218
226
|
|
|
@@ -281,8 +289,6 @@ export function buildWrapperPage(args) {
|
|
|
281
289
|
? readUrlParams()
|
|
282
290
|
: msg.op === "urlState.set"
|
|
283
291
|
? handleUrlStateSet(msg.payload)
|
|
284
|
-
: msg.op === "urlState.go"
|
|
285
|
-
? handleUrlStateGo(msg.payload)
|
|
286
292
|
: await rpc(msg.op, msg.payload);
|
|
287
293
|
const ms = Math.round(performance.now() - startedAt);
|
|
288
294
|
console.debug("[lotics-dev] " + msg.op + " " + ms + "ms", data);
|
|
@@ -300,9 +306,9 @@ export function buildWrapperPage(args) {
|
|
|
300
306
|
}
|
|
301
307
|
});
|
|
302
308
|
|
|
303
|
-
// Browser
|
|
304
|
-
//
|
|
305
|
-
//
|
|
309
|
+
// Browser back/forward → broadcast the new params so useUrlState re-hydrates
|
|
310
|
+
// (the app's own set writes use replaceState — no popstate — so there's no
|
|
311
|
+
// echo). Mirrors the production host.
|
|
306
312
|
window.addEventListener("popstate", function () {
|
|
307
313
|
iframe.contentWindow.postMessage(
|
|
308
314
|
{ type: "url-state", params: readUrlParams() },
|
package/dist/src/cli.js
CHANGED
|
@@ -30574,10 +30574,10 @@ mount(
|
|
|
30574
30574
|
path: "src/App.tsx",
|
|
30575
30575
|
// Default scaffold = a minimal in-app router example (a list screen and a
|
|
30576
30576
|
// detail screen), so a new app starts with the recommended routing shape:
|
|
30577
|
-
// - The app uses react-router via `AppRouter` (from @lotics/app-sdk/router)
|
|
30578
|
-
//
|
|
30579
|
-
// host
|
|
30580
|
-
//
|
|
30577
|
+
// - The app uses react-router via `AppRouter` (from @lotics/app-sdk/router).
|
|
30578
|
+
// The app owns its own url — embedded, that's the iframe's own url (the
|
|
30579
|
+
// host never sees it, so navigation never reloads the app); standalone,
|
|
30580
|
+
// real path URLs. Browser Back/Forward walk app screens in both.
|
|
30581
30581
|
// - The full-container layout pattern is preserved in `Screen`: an outer
|
|
30582
30582
|
// <View flex:1> claims the iframe height (index.html sets
|
|
30583
30583
|
// html/body/#root to 100% + #root is a flex column). Keep that chain
|
|
@@ -30592,11 +30592,13 @@ import { Card } from "@lotics/ui/card";
|
|
|
30592
30592
|
import { Text } from "@lotics/ui/text";
|
|
30593
30593
|
import { Button } from "@lotics/ui/button";
|
|
30594
30594
|
|
|
30595
|
-
// AppRouter makes the app's screens real
|
|
30596
|
-
//
|
|
30597
|
-
//
|
|
30598
|
-
//
|
|
30599
|
-
//
|
|
30595
|
+
// AppRouter makes the app's screens real URLs \u2014 write plain react-router
|
|
30596
|
+
// (useNavigate / useParams / <Link>) and it handles both modes. The app owns its
|
|
30597
|
+
// own url, so navigation never reloads the app:
|
|
30598
|
+
// - Embedded in the Lotics host: the app drives the iframe's OWN url (invisible
|
|
30599
|
+
// to the user, never seen by the host). Browser Back / Forward walk app
|
|
30600
|
+
// screens; the screen is also mirrored to the host url, so it's shareable and
|
|
30601
|
+
// survives a full refresh (handled by AppRouter \u2014 no extra code).
|
|
30600
30602
|
// - Standalone at <slug>.lotics.app: a normal browser router with real path URLs.
|
|
30601
30603
|
|
|
30602
30604
|
const ITEMS = [
|
|
@@ -30843,10 +30845,11 @@ the full export list at https://www.npmjs.com/package/@lotics/ui.
|
|
|
30843
30845
|
## Routing
|
|
30844
30846
|
|
|
30845
30847
|
\`src/App.tsx\` ships a minimal in-app router. Write plain react-router and wrap
|
|
30846
|
-
your routes in \`AppRouter\` from \`@lotics/app-sdk/router\` \u2014
|
|
30847
|
-
|
|
30848
|
-
|
|
30849
|
-
|
|
30848
|
+
your routes in \`AppRouter\` from \`@lotics/app-sdk/router\` \u2014 the app owns its own
|
|
30849
|
+
url, so navigation never reloads the app. Embedded in the Lotics host it drives
|
|
30850
|
+
the iframe's own url (invisible to the user, never seen by the host); browser
|
|
30851
|
+
Back/Forward walk app screens, and the screen is mirrored to the host url so it's
|
|
30852
|
+
shareable and survives a full refresh. Standalone (\`<slug>.lotics.app\`) it's a
|
|
30850
30853
|
normal browser router with real path URLs. A single-screen app can drop the
|
|
30851
30854
|
router and render one screen directly.
|
|
30852
30855
|
|
|
@@ -31056,9 +31059,28 @@ function buildWrapperPage(args) {
|
|
|
31056
31059
|
const iframe = document.getElementById("app");
|
|
31057
31060
|
|
|
31058
31061
|
// Pass the wrapper's own origin to the app via ?lotics_host= so the app
|
|
31059
|
-
// SDK can origin-lock its postMessage bridge
|
|
31060
|
-
|
|
31061
|
-
|
|
31062
|
+
// SDK can origin-lock its postMessage bridge, and bake the saved screen
|
|
31063
|
+
// (the wrapper url's _loc, kept current by AppRouter's mirror) into the src
|
|
31064
|
+
// path so a refresh boots the app at that screen \u2014 mirrors the production
|
|
31065
|
+
// host. _loc is honoured only when it resolves same-origin to the Vite
|
|
31066
|
+
// server (a url param flowing into an iframe src is a redirect vector);
|
|
31067
|
+
// anything else falls back to the app root.
|
|
31068
|
+
const appSrc = new URL(VITE_URL);
|
|
31069
|
+
const savedLoc = new URLSearchParams(window.location.search).get("_loc");
|
|
31070
|
+
if (savedLoc) {
|
|
31071
|
+
try {
|
|
31072
|
+
const resolved = new URL(savedLoc, VITE_ORIGIN);
|
|
31073
|
+
if (resolved.origin === VITE_ORIGIN) {
|
|
31074
|
+
appSrc.pathname = resolved.pathname;
|
|
31075
|
+
appSrc.search = resolved.search;
|
|
31076
|
+
appSrc.hash = resolved.hash;
|
|
31077
|
+
}
|
|
31078
|
+
} catch (e) {
|
|
31079
|
+
// malformed _loc -> app root
|
|
31080
|
+
}
|
|
31081
|
+
}
|
|
31082
|
+
appSrc.searchParams.set("lotics_host", window.location.origin);
|
|
31083
|
+
iframe.src = appSrc.toString();
|
|
31062
31084
|
|
|
31063
31085
|
async function rpc(op, payload) {
|
|
31064
31086
|
const res = await fetch("/_rpc", {
|
|
@@ -31163,7 +31185,6 @@ function buildWrapperPage(args) {
|
|
|
31163
31185
|
});
|
|
31164
31186
|
return out;
|
|
31165
31187
|
}
|
|
31166
|
-
var lastPushAt = 0;
|
|
31167
31188
|
function handleUrlStateSet(payload) {
|
|
31168
31189
|
const params = (payload && payload.params) || {};
|
|
31169
31190
|
const sp = new URLSearchParams(window.location.search);
|
|
@@ -31176,21 +31197,9 @@ function buildWrapperPage(args) {
|
|
|
31176
31197
|
});
|
|
31177
31198
|
const qs = sp.toString();
|
|
31178
31199
|
const url = window.location.pathname + (qs ? "?" + qs : "") + window.location.hash;
|
|
31179
|
-
//
|
|
31180
|
-
//
|
|
31181
|
-
|
|
31182
|
-
const now = Date.now();
|
|
31183
|
-
if (payload && payload.push && now - lastPushAt > 100) {
|
|
31184
|
-
lastPushAt = now;
|
|
31185
|
-
window.history.pushState(null, "", url);
|
|
31186
|
-
} else {
|
|
31187
|
-
window.history.replaceState(null, "", url);
|
|
31188
|
-
}
|
|
31189
|
-
return undefined;
|
|
31190
|
-
}
|
|
31191
|
-
function handleUrlStateGo(payload) {
|
|
31192
|
-
const delta = payload && payload.delta;
|
|
31193
|
-
if (typeof delta === "number") window.history.go(delta);
|
|
31200
|
+
// View-state writes in place \u2014 never a history entry. replaceState doesn't
|
|
31201
|
+
// fire popstate, so no echo. Mirrors the production host.
|
|
31202
|
+
window.history.replaceState(null, "", url);
|
|
31194
31203
|
return undefined;
|
|
31195
31204
|
}
|
|
31196
31205
|
|
|
@@ -31259,8 +31268,6 @@ function buildWrapperPage(args) {
|
|
|
31259
31268
|
? readUrlParams()
|
|
31260
31269
|
: msg.op === "urlState.set"
|
|
31261
31270
|
? handleUrlStateSet(msg.payload)
|
|
31262
|
-
: msg.op === "urlState.go"
|
|
31263
|
-
? handleUrlStateGo(msg.payload)
|
|
31264
31271
|
: await rpc(msg.op, msg.payload);
|
|
31265
31272
|
const ms = Math.round(performance.now() - startedAt);
|
|
31266
31273
|
console.debug("[lotics-dev] " + msg.op + " " + ms + "ms", data);
|
|
@@ -31278,9 +31285,9 @@ function buildWrapperPage(args) {
|
|
|
31278
31285
|
}
|
|
31279
31286
|
});
|
|
31280
31287
|
|
|
31281
|
-
// Browser
|
|
31282
|
-
//
|
|
31283
|
-
//
|
|
31288
|
+
// Browser back/forward \u2192 broadcast the new params so useUrlState re-hydrates
|
|
31289
|
+
// (the app's own set writes use replaceState \u2014 no popstate \u2014 so there's no
|
|
31290
|
+
// echo). Mirrors the production host.
|
|
31284
31291
|
window.addEventListener("popstate", function () {
|
|
31285
31292
|
iframe.contentWindow.postMessage(
|
|
31286
31293
|
{ type: "url-state", params: readUrlParams() },
|
package/dist/starter_template.js
CHANGED
|
@@ -379,10 +379,10 @@ mount(
|
|
|
379
379
|
path: "src/App.tsx",
|
|
380
380
|
// Default scaffold = a minimal in-app router example (a list screen and a
|
|
381
381
|
// detail screen), so a new app starts with the recommended routing shape:
|
|
382
|
-
// - The app uses react-router via `AppRouter` (from @lotics/app-sdk/router)
|
|
383
|
-
//
|
|
384
|
-
// host
|
|
385
|
-
//
|
|
382
|
+
// - The app uses react-router via `AppRouter` (from @lotics/app-sdk/router).
|
|
383
|
+
// The app owns its own url — embedded, that's the iframe's own url (the
|
|
384
|
+
// host never sees it, so navigation never reloads the app); standalone,
|
|
385
|
+
// real path URLs. Browser Back/Forward walk app screens in both.
|
|
386
386
|
// - The full-container layout pattern is preserved in `Screen`: an outer
|
|
387
387
|
// <View flex:1> claims the iframe height (index.html sets
|
|
388
388
|
// html/body/#root to 100% + #root is a flex column). Keep that chain
|
|
@@ -397,11 +397,13 @@ import { Card } from "@lotics/ui/card";
|
|
|
397
397
|
import { Text } from "@lotics/ui/text";
|
|
398
398
|
import { Button } from "@lotics/ui/button";
|
|
399
399
|
|
|
400
|
-
// AppRouter makes the app's screens real
|
|
401
|
-
//
|
|
402
|
-
//
|
|
403
|
-
//
|
|
404
|
-
//
|
|
400
|
+
// AppRouter makes the app's screens real URLs — write plain react-router
|
|
401
|
+
// (useNavigate / useParams / <Link>) and it handles both modes. The app owns its
|
|
402
|
+
// own url, so navigation never reloads the app:
|
|
403
|
+
// - Embedded in the Lotics host: the app drives the iframe's OWN url (invisible
|
|
404
|
+
// to the user, never seen by the host). Browser Back / Forward walk app
|
|
405
|
+
// screens; the screen is also mirrored to the host url, so it's shareable and
|
|
406
|
+
// survives a full refresh (handled by AppRouter — no extra code).
|
|
405
407
|
// - Standalone at <slug>.lotics.app: a normal browser router with real path URLs.
|
|
406
408
|
|
|
407
409
|
const ITEMS = [
|
|
@@ -648,10 +650,11 @@ the full export list at https://www.npmjs.com/package/@lotics/ui.
|
|
|
648
650
|
## Routing
|
|
649
651
|
|
|
650
652
|
\`src/App.tsx\` ships a minimal in-app router. Write plain react-router and wrap
|
|
651
|
-
your routes in \`AppRouter\` from \`@lotics/app-sdk/router\` —
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
653
|
+
your routes in \`AppRouter\` from \`@lotics/app-sdk/router\` — the app owns its own
|
|
654
|
+
url, so navigation never reloads the app. Embedded in the Lotics host it drives
|
|
655
|
+
the iframe's own url (invisible to the user, never seen by the host); browser
|
|
656
|
+
Back/Forward walk app screens, and the screen is mirrored to the host url so it's
|
|
657
|
+
shareable and survives a full refresh. Standalone (\`<slug>.lotics.app\`) it's a
|
|
655
658
|
normal browser router with real path URLs. A single-screen app can drop the
|
|
656
659
|
router and render one screen directly.
|
|
657
660
|
|