@elizaos/ui 2.0.3-beta.3 → 2.0.3-beta.5

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 (86) hide show
  1. package/App.d.ts.map +1 -1
  2. package/App.js +3 -4
  3. package/app-navigate-view.d.ts.map +1 -1
  4. package/app-navigate-view.js +1 -1
  5. package/cloud/api-keys/ApiKeysView.d.ts +6 -3
  6. package/cloud/api-keys/ApiKeysView.d.ts.map +1 -1
  7. package/cloud/api-keys/ApiKeysView.js +21 -29
  8. package/cloud/api-keys/copy-api-key.d.ts +1 -8
  9. package/cloud/api-keys/copy-api-key.d.ts.map +1 -1
  10. package/cloud/api-keys/copy-api-key.js +1 -10
  11. package/cloud/api-keys/index.d.ts +1 -1
  12. package/cloud/api-keys/index.d.ts.map +1 -1
  13. package/cloud/api-keys/index.js +1 -1
  14. package/cloud/shell/CloudRouterShell.d.ts +12 -0
  15. package/cloud/shell/CloudRouterShell.d.ts.map +1 -1
  16. package/cloud/shell/CloudRouterShell.js +38 -2
  17. package/cloud-ui/components/data-list/api-keys-table.d.ts +1 -2
  18. package/cloud-ui/components/data-list/api-keys-table.d.ts.map +1 -1
  19. package/cloud-ui/components/data-list/api-keys-table.js +3 -9
  20. package/cloud-ui/components/docs/api-route-explorer-client.d.ts.map +1 -1
  21. package/cloud-ui/components/docs/api-route-explorer-client.js +2 -1
  22. package/cloud-ui/components/layout/dashboard-route-page.d.ts.map +1 -1
  23. package/cloud-ui/components/layout/dashboard-route-page.js +3 -1
  24. package/components/chat/widgets/home-widget-card.d.ts.map +1 -1
  25. package/components/chat/widgets/home-widget-card.js +1 -3
  26. package/components/config-ui/config-field.js +1 -1
  27. package/components/pages/Springboard.d.ts.map +1 -1
  28. package/components/pages/Springboard.js +37 -48
  29. package/components/pages/SpringboardSurface.d.ts.map +1 -1
  30. package/components/pages/SpringboardSurface.js +116 -6
  31. package/components/shell/ContinuousChatOverlay.d.ts.map +1 -1
  32. package/components/shell/ContinuousChatOverlay.js +207 -146
  33. package/components/shell/HomeSpringboardSurface.d.ts.map +1 -1
  34. package/components/shell/HomeSpringboardSurface.js +31 -76
  35. package/components/shell/use-pull-gesture.d.ts +6 -0
  36. package/components/shell/use-pull-gesture.d.ts.map +1 -1
  37. package/components/shell/use-pull-gesture.js +66 -27
  38. package/components/shell/useShellController.d.ts.map +1 -1
  39. package/components/shell/useShellController.js +13 -3
  40. package/components/views/ViewTileImage.d.ts +8 -8
  41. package/components/views/ViewTileImage.d.ts.map +1 -1
  42. package/components/views/ViewTileImage.js +38 -9
  43. package/hooks/useHorizontalPager.d.ts +33 -0
  44. package/hooks/useHorizontalPager.d.ts.map +1 -0
  45. package/hooks/useHorizontalPager.js +226 -0
  46. package/hooks/useLayoutShiftMonitor.d.ts +2 -0
  47. package/hooks/useLayoutShiftMonitor.d.ts.map +1 -1
  48. package/hooks/useLayoutShiftMonitor.js +23 -0
  49. package/hooks/view-catalog.d.ts +3 -4
  50. package/hooks/view-catalog.d.ts.map +1 -1
  51. package/hooks/view-catalog.js +4 -6
  52. package/package.json +10 -10
  53. package/state/startup-telemetry.d.ts +6 -0
  54. package/state/startup-telemetry.d.ts.map +1 -1
  55. package/state/startup-telemetry.js +23 -2
  56. package/styles/base.css +6 -6
  57. package/styles/brand-gold.css +6 -5
  58. package/testing/layout-stability.d.ts +11 -3
  59. package/testing/layout-stability.d.ts.map +1 -1
  60. package/testing/layout-stability.js +29 -4
  61. package/testing/run-animation-frames-immediately.d.ts +2 -0
  62. package/testing/run-animation-frames-immediately.d.ts.map +1 -0
  63. package/testing/run-animation-frames-immediately.js +8 -0
  64. package/themes/presets.js +14 -14
  65. package/utils/cloud-agent-base.d.ts.map +1 -1
  66. package/utils/cloud-agent-base.js +6 -0
  67. package/widgets/registry.d.ts.map +1 -1
  68. package/widgets/registry.js +7 -0
  69. package/components/pages/automation-conversations.d.ts +0 -16
  70. package/components/pages/automation-conversations.d.ts.map +0 -1
  71. package/components/pages/automation-conversations.js +0 -204
  72. package/components/pages/automations-missing-creds.d.ts +0 -25
  73. package/components/pages/automations-missing-creds.d.ts.map +0 -1
  74. package/components/pages/automations-missing-creds.js +0 -39
  75. package/components/settings/desktop-workspace-audit-config.d.ts +0 -3
  76. package/components/settings/desktop-workspace-audit-config.d.ts.map +0 -1
  77. package/components/settings/desktop-workspace-audit-config.js +0 -171
  78. package/components/shell/first-run-theme.d.ts +0 -6
  79. package/components/shell/first-run-theme.d.ts.map +0 -1
  80. package/components/shell/first-run-theme.js +0 -31
  81. package/genui/use-json-render-message.d.ts +0 -20
  82. package/genui/use-json-render-message.d.ts.map +0 -1
  83. package/genui/use-json-render-message.js +0 -38
  84. package/state/first-run-restart.d.ts +0 -8
  85. package/state/first-run-restart.d.ts.map +0 -1
  86. package/state/first-run-restart.js +0 -8
@@ -13,8 +13,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
13
  * the shared root `AppBackground` shows through, matching the home screen.
14
14
  */
15
15
  import { Pencil, Trash2 } from "lucide-react";
16
- import { motion, Reorder } from "motion/react";
16
+ import { Reorder } from "motion/react";
17
17
  import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
18
+ import { useHorizontalPager } from "../../hooks/useHorizontalPager.js";
18
19
  import { cn } from "../../lib/utils.js";
19
20
  import { moveIcon, readSpringboardLayout, reconcileLayout, SPRINGBOARD_DOCK_LIMIT, toggleFavorite, writeSpringboardLayout, } from "../../state/springboard-layout.js";
20
21
  import { emitViewInteraction } from "../../view-telemetry.js";
@@ -23,8 +24,6 @@ const LONG_PRESS_MS = 450;
23
24
  /** Finger travel (px) that aborts a long-press — a pan/swipe is not a press, so
24
25
  * a horizontal swipe-back can never ghost-fire edit mode mid-gesture. */
25
26
  const LONG_PRESS_MOVE_SLOP = 10;
26
- /** Horizontal drag distance (px) needed to flip to the adjacent page. */
27
- const SWIPE_THRESHOLD = 60;
28
27
  // Memoized so a layout reconcile (install/uninstall/sort) re-renders only the
29
28
  // tiles whose props actually changed, not all ~20 on a page.
30
29
  const IconTile = memo(function IconTile({ entry, editing, favorited, manageable, onLaunch, onToggleFavorite, onEdit, onDelete, onLongPress, }) {
@@ -62,20 +61,17 @@ const IconTile = memo(function IconTile({ entry, editing, favorited, manageable,
62
61
  // gesture interrupts the press — clear the timer so a long-press never
63
62
  // ghost-fires edit mode after the user finishes scrolling.
64
63
  onPointerCancel: clear, className: cn(
65
- // ViewTileImage handles image-vs-glyph internally, so the button is
66
- // one constant surface. The hero image (object-cover) covers the
67
- // tile when it loads; this dark neutral fill + white glyph is the
68
- // fallback so a tile reads as a real app icon (not a bare glyph
69
- // floating on the background) even before/without the image. Filter
70
- // effects (#9281) and focus rings (#9292) were removed on develop.
64
+ // ViewTileImage renders this surface as an app icon, not as a
65
+ // cropped catalog preview. The button stays one constant hit target
66
+ // and owns hover/focus chrome; the inner visual owns color/glyph.
71
67
  "h-16 w-16 overflow-hidden rounded-2xl border border-white/10 bg-black/35 text-white transition-colors hover:bg-black/45", editing && "animate-pulse"), children: _jsx(ViewTileImage, { entry: entry, source: "springboard", containerClassName: "grid h-full w-full place-items-center", glyphClassName: "h-7 w-7", imageTestId: `springboard-image-${entry.id}` }) }), editing ? (_jsx("button", { type: "button", "aria-label": favorited ? `Unpin ${entry.label}` : `Pin ${entry.label}`, "data-testid": `springboard-fav-${entry.id}`, onClick: (e) => {
72
68
  e.stopPropagation();
73
69
  onToggleFavorite(entry.id);
74
70
  }, className: cn(
75
71
  // Filled chips stay legible across image and dark tile backgrounds.
76
- "absolute -right-1.5 -top-1.5 grid h-5 w-5 place-items-center rounded-full text-[11px] font-bold shadow-md ring-1", favorited
77
- ? "bg-accent text-white ring-black/20"
78
- : "bg-white text-neutral-900 ring-black/15"), children: favorited ? "★" : "+" })) : null, editing && manageable ? (_jsxs("div", { className: "absolute -left-1.5 -top-1.5 flex gap-1", children: [onEdit ? (_jsx("button", { type: "button", "aria-label": `Edit ${entry.label}`, "data-testid": `springboard-edit-${entry.id}`, onClick: (e) => {
72
+ "absolute -right-1.5 -top-1.5 grid h-5 w-5 place-items-center rounded-full border text-[11px] font-bold shadow-md", favorited
73
+ ? "border-black/20 bg-accent text-white"
74
+ : "border-black/15 bg-white text-neutral-900"), children: favorited ? "★" : "+" })) : null, editing && manageable ? (_jsxs("div", { className: "absolute -left-1.5 -top-1.5 flex gap-1", children: [onEdit ? (_jsx("button", { type: "button", "aria-label": `Edit ${entry.label}`, "data-testid": `springboard-edit-${entry.id}`, onClick: (e) => {
79
75
  e.stopPropagation();
80
76
  onEdit(entry.id);
81
77
  }, className: "grid h-5 w-5 place-items-center rounded-full bg-bg-accent text-foreground", children: _jsx(Pencil, { className: "h-3 w-3", "aria-hidden": "true" }) })) : null, onDelete ? (_jsx("button", { type: "button", "aria-label": `Delete ${entry.label}`, "data-testid": `springboard-delete-${entry.id}`, onClick: (e) => {
@@ -199,40 +195,33 @@ export function Springboard({ entries, loading = false, onLaunch, onEdgeSwipeRig
199
195
  onDeleteView,
200
196
  toggleEditMode,
201
197
  ]);
202
- return (_jsxs("div", { className: cn("flex min-h-0 flex-1 flex-col", className), "data-testid": "springboard", children: [favoriteEntries.length > 0 ? (_jsx("div", { "data-testid": "springboard-dock", className: "mx-3 mt-2 mb-3 flex items-center justify-center gap-3 rounded-3xl border border-white/10 bg-black/30 px-3 py-3 backdrop-blur-md sm:mx-4 sm:gap-4 sm:px-6", children: favoriteEntries.map((entry) => (_jsx("div", { children: renderTile(entry, true) }, `dock-${entry.id}`))) })) : null, _jsxs("div", { className: "relative flex min-h-0 flex-1 flex-col overflow-hidden", children: [_jsx(motion.div
203
- // A stable identity (NOT `key={clampedPage}`): keying on the page
204
- // index forced a full unmount/remount of the page — Reorder.Group and
205
- // every tile on each swipe, which janks the paging. The page content
206
- // already swaps via `pages[clampedPage]`, and `dragSnapToOrigin`
207
- // animates the drag back to origin, so the swap is smooth without a
208
- // remount (#9304).
209
- , { drag: editing ? false : "x", dragConstraints: { left: 0, right: 0 }, dragElastic: 0.2, dragSnapToOrigin: true, onDragEnd: (_event, info) => {
210
- if (editing)
211
- return;
212
- if (info.offset.x < -SWIPE_THRESHOLD &&
213
- clampedPage < pages.length - 1) {
214
- setActivePage(clampedPage + 1);
215
- emitViewInteraction({
216
- source: "springboard",
217
- action: "page-swipe",
218
- count: clampedPage + 1,
219
- });
220
- }
221
- else if (info.offset.x > SWIPE_THRESHOLD && clampedPage > 0) {
222
- setActivePage(clampedPage - 1);
223
- emitViewInteraction({
224
- source: "springboard",
225
- action: "page-swipe",
226
- count: clampedPage - 1,
227
- });
228
- }
229
- else if (info.offset.x > SWIPE_THRESHOLD && clampedPage === 0) {
230
- onEdgeSwipeRight?.();
231
- }
232
- }, className: "flex min-h-0 flex-1 items-start justify-center overflow-y-auto px-6 pt-2 pb-8", children: loading && entries.length === 0 ? (_jsx("div", { className: "grid w-full max-w-2xl grid-cols-4 gap-x-4 gap-y-5 sm:grid-cols-5", children: ["a", "b", "c", "d", "e", "f", "g", "h"].map((id) => (_jsxs("div", { className: "flex flex-col items-center gap-1.5 opacity-60", children: [_jsx("div", { className: "h-16 w-16 rounded-2xl bg-bg-accent/50" }), _jsx("div", { className: "h-2.5 w-12 rounded-full bg-bg-accent/50" })] }, id))) })) : (_jsx(Reorder.Group, { axis: "y", values: pages[clampedPage] ?? [], onReorder: (next) => handleReorder(clampedPage, next), className: "grid w-full max-w-2xl grid-cols-4 gap-x-4 gap-y-5 sm:grid-cols-5", children: (pages[clampedPage] ?? []).map((id) => {
233
- const entry = byId.get(id);
234
- if (!entry)
235
- return null;
236
- return (_jsx(Reorder.Item, { value: id, drag: editing, dragListener: editing, className: "flex justify-center", children: renderTile(entry, favoriteSet.has(id)) }, id));
237
- }) })) }, "springboard-page"), showPageDots && pages.length > 1 ? (_jsx("div", { className: "flex items-center justify-center gap-2 pb-3", children: pages.map((pageIds, index) => (_jsx("button", { type: "button", "aria-label": `Page ${index + 1}`, "aria-current": index === clampedPage, onClick: () => setActivePage(index), className: cn("h-2 w-2 rounded-full transition-colors", index === clampedPage ? "bg-accent" : "bg-border") }, `dot-${index}-${pageIds[0] ?? "empty"}`))) })) : null] })] }));
198
+ const pager = useHorizontalPager({
199
+ page: clampedPage,
200
+ pageCount: pages.length,
201
+ enabled: !editing && pages.length > 1,
202
+ edgeSwipeRightEnabled: showPageDots && onEdgeSwipeRight != null,
203
+ onEdgeSwipeRight,
204
+ onPageChange: (nextPage) => {
205
+ setActivePage(nextPage);
206
+ emitViewInteraction({
207
+ source: "springboard",
208
+ action: "page-swipe",
209
+ count: nextPage,
210
+ });
211
+ },
212
+ });
213
+ return (_jsxs("div", { className: cn("flex min-h-0 flex-1 flex-col", className), "data-testid": "springboard", children: [favoriteEntries.length > 0 ? (_jsx("div", { "data-testid": "springboard-dock", className: "mx-3 mt-2 mb-3 flex items-center justify-center gap-3 rounded-3xl border border-white/10 bg-black/45 px-3 py-3 sm:mx-4 sm:gap-4 sm:px-6", children: favoriteEntries.map((entry) => (_jsx("div", { children: renderTile(entry, true) }, `dock-${entry.id}`))) })) : null, _jsxs("div", { className: "relative flex min-h-0 flex-1 flex-col overflow-hidden", children: [_jsx("div", { ref: pager.viewportRef, "data-testid": "springboard-page-window", className: "relative flex min-h-0 flex-1 overflow-hidden touch-pan-y", onPointerDown: pager.handlers.onPointerDown, onPointerMove: pager.handlers.onPointerMove, onPointerUp: pager.handlers.onPointerUp, onPointerCancel: pager.handlers.onPointerCancel, onLostPointerCapture: pager.handlers.onLostPointerCapture, children: _jsx("div", { ref: pager.railRef, "data-testid": "springboard-page-rail", className: "flex h-full min-h-0 w-full motion-reduce:transition-none", children: loading && entries.length === 0 ? (_jsx("div", { className: "flex h-full min-h-0 min-w-full items-start justify-center overflow-y-auto px-6 pt-2 pb-8", children: _jsx("div", { className: "grid w-full max-w-2xl grid-cols-4 gap-x-4 gap-y-5 sm:grid-cols-5", children: ["a", "b", "c", "d", "e", "f", "g", "h"].map((id) => (_jsxs("div", { className: "flex flex-col items-center gap-1.5 opacity-60", children: [_jsx("div", { className: "h-16 w-16 rounded-2xl bg-bg-accent/50" }), _jsx("div", { className: "h-2.5 w-12 rounded-full bg-bg-accent/50" })] }, id))) }) })) : (pages.map((pageIds, pageIndex) => {
214
+ const active = pageIndex === clampedPage;
215
+ return (_jsx("div", { "data-testid": `springboard-page-${pageIndex}`, "aria-hidden": !active, inert: !active || undefined, className: cn("flex h-full min-h-0 min-w-full items-start justify-center overflow-y-auto px-6 pt-2 pb-8", !active && "pointer-events-none"), children: editing && active ? (_jsx(Reorder.Group, { axis: "y", values: pageIds, onReorder: (next) => handleReorder(pageIndex, next), className: "grid w-full max-w-2xl grid-cols-4 gap-x-4 gap-y-5 sm:grid-cols-5", children: pageIds.map((id) => {
216
+ const entry = byId.get(id);
217
+ if (!entry)
218
+ return null;
219
+ return (_jsx(Reorder.Item, { value: id, drag: editing, dragListener: editing, className: "flex justify-center", children: renderTile(entry, favoriteSet.has(id)) }, id));
220
+ }) })) : (_jsx("div", { className: "grid w-full max-w-2xl grid-cols-4 gap-x-4 gap-y-5 sm:grid-cols-5", children: pageIds.map((id) => {
221
+ const entry = byId.get(id);
222
+ if (!entry)
223
+ return null;
224
+ return (_jsx("div", { className: "flex justify-center", children: renderTile(entry, favoriteSet.has(id)) }, id));
225
+ }) })) }, `springboard-page-${pageIndex}`));
226
+ })) }) }), showPageDots && pages.length > 1 ? (_jsx("div", { className: "flex items-center justify-center gap-2 pb-3", children: pages.map((pageIds, index) => (_jsx("button", { type: "button", "aria-label": `Page ${index + 1}`, "aria-current": index === clampedPage, onClick: () => setActivePage(index), className: cn("h-2 w-2 rounded-full transition-colors", index === clampedPage ? "bg-accent" : "bg-border") }, `dot-${index}-${pageIds[0] ?? "empty"}`))) })) : null] })] }));
238
227
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SpringboardSurface.d.ts","sourceRoot":"","sources":["../../../../../../src/components/pages/SpringboardSurface.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AA2F/B,MAAM,WAAW,uBAAuB;IACtC,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;CACrC;AAED,eAAO,MAAM,kBAAkB,0DAE5B,uBAAuB,KAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAgF5C,CAAC"}
1
+ {"version":3,"file":"SpringboardSurface.d.ts","sourceRoot":"","sources":["../../../../../../src/components/pages/SpringboardSurface.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAsI/B,MAAM,WAAW,uBAAuB;IACtC,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;CACrC;AAED,eAAO,MAAM,kBAAkB,0DAE5B,uBAAuB,KAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAmR5C,CAAC"}
@@ -1,11 +1,13 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { isViewVisible } from "@elizaos/core";
3
3
  import * as React from "react";
4
+ import { getElectrobunRendererRpc, registerDynamicView, unregisterDynamicView, } from "../../bridge/electrobun-rpc.js";
4
5
  import { useRoutableViews } from "../../hooks/useAvailableViews.js";
5
6
  import { useViewCatalog } from "../../hooks/useViewCatalog.js";
6
7
  import { viewToEntry } from "../../hooks/view-catalog.js";
7
8
  import { getActiveViewModality, } from "../../platform/platform-guards.js";
8
9
  import { setSpringboardEditing, setSpringboardPage, setSpringboardPageCount, useShellSurface, } from "../../state/shell-surface-store.js";
10
+ import { useIsDeveloperMode } from "../../state/useDeveloperMode.js";
9
11
  import { useEnabledViewKinds } from "../../state/useViewKinds.js";
10
12
  import { recordRecentViewId } from "../../view-recents.js";
11
13
  import { Springboard } from "./Springboard.js";
@@ -17,8 +19,14 @@ const HIDDEN_SPRINGBOARD_VIEW_IDS = new Set([
17
19
  "character",
18
20
  "character-select",
19
21
  "voice",
22
+ "background",
23
+ ]);
24
+ const HIDDEN_SPRINGBOARD_PATHS = new Set([
25
+ "/chat",
26
+ "/views",
27
+ "/apps",
28
+ "/background",
20
29
  ]);
21
- const HIDDEN_SPRINGBOARD_PATHS = new Set(["/chat", "/views", "/apps"]);
22
30
  const SPRINGBOARD_SYSTEM_ENTRY_IDS = new Set([
23
31
  "settings",
24
32
  "tasks",
@@ -37,7 +45,6 @@ const SPRINGBOARD_SYSTEM_ENTRY_IDS = new Set([
37
45
  "runtime",
38
46
  "database",
39
47
  "logs",
40
- "background",
41
48
  "stream",
42
49
  "desktop",
43
50
  ]);
@@ -74,15 +81,36 @@ function dedupeEntries(entries) {
74
81
  }
75
82
  return out;
76
83
  }
84
+ const EMPTY_DYNAMIC_VIEW_FORM = {
85
+ id: "",
86
+ title: "",
87
+ entrypoint: "",
88
+ description: "",
89
+ };
90
+ function dynamicViewBridgeAvailable() {
91
+ const request = getElectrobunRendererRpc()?.request;
92
+ return (typeof request?.dynamicViewRegister === "function" &&
93
+ typeof request?.dynamicViewUnregister === "function");
94
+ }
95
+ function editableEntrypointFor(entry) {
96
+ return (entry.view?.bundleUrl ??
97
+ entry.view?.path ??
98
+ entry.path ??
99
+ `/apps/${entry.id}`);
100
+ }
77
101
  export const SpringboardSurface = React.memo(function SpringboardSurface({ onNavigateHomeFromEdge, }) {
78
- const { views, loading } = useRoutableViews();
79
- const { entries: catalogEntries, get: getCatalogEntry } = useViewCatalog();
102
+ const { views, loading, refresh: refreshViews } = useRoutableViews();
103
+ const { entries: catalogEntries, get: getCatalogEntry, refresh: refreshCatalog, } = useViewCatalog();
80
104
  const enabledKinds = useEnabledViewKinds();
105
+ const developerMode = useIsDeveloperMode();
81
106
  const activeModality = React.useMemo(() => getActiveViewModality(), []);
107
+ const [dynamicViewForm, setDynamicViewForm] = React.useState(EMPTY_DYNAMIC_VIEW_FORM);
108
+ const [dynamicViewStatus, setDynamicViewStatus] = React.useState(null);
82
109
  // Page index + edit mode come from the single shell-surface store, so the
83
110
  // springboard, the rail, and its one indicator can never disagree (and edit
84
111
  // mode is auto-reset by the store when the user leaves the springboard).
85
112
  const { springboardPage, springboardEditing } = useShellSurface();
113
+ const showDynamicViewControls = developerMode && dynamicViewBridgeAvailable();
86
114
  const loadedEntries = React.useMemo(() => views
87
115
  .filter((view) => isVisibleSpringboardView(view, enabledKinds, activeModality))
88
116
  .map(viewToEntry), [activeModality, enabledKinds, views]);
@@ -92,6 +120,10 @@ export const SpringboardSurface = React.memo(function SpringboardSurface({ onNav
92
120
  .sort(compareEntryLabels), [activeModality, catalogEntries]);
93
121
  const entries = React.useMemo(() => dedupeEntries([...loadedEntries, ...availableEntries]), [availableEntries, loadedEntries]);
94
122
  const entryById = React.useMemo(() => new Map(entries.map((entry) => [entry.id, entry])), [entries]);
123
+ const refreshDynamicViews = React.useCallback(() => {
124
+ refreshViews();
125
+ refreshCatalog();
126
+ }, [refreshCatalog, refreshViews]);
95
127
  const handleLaunch = React.useCallback((entry) => {
96
128
  if (entry.state !== "loaded") {
97
129
  void getCatalogEntry(entry).catch(() => {
@@ -116,5 +148,83 @@ export const SpringboardSurface = React.memo(function SpringboardSurface({ onNav
116
148
  // Sandboxed navigation is best-effort.
117
149
  }
118
150
  }, [getCatalogEntry]);
119
- return (_jsx("div", { className: "absolute inset-0 flex min-h-0 flex-col px-0 pt-2 pb-[calc(var(--eliza-mobile-nav-offset,0px)+var(--safe-area-bottom,0px)+var(--eliza-continuous-chat-clearance,5.25rem)+1.75rem)]", children: _jsx(Springboard, { entries: entries, loading: loading, onLaunch: (entry) => handleLaunch(entryById.get(entry.id) ?? entry), onEdgeSwipeRight: onNavigateHomeFromEdge, page: springboardPage, onPageChange: setSpringboardPage, onPageCountChange: setSpringboardPageCount, editing: springboardEditing, onEditingChange: setSpringboardEditing, showPageDots: false }) }));
151
+ const canManageView = React.useCallback((id) => {
152
+ if (!showDynamicViewControls)
153
+ return false;
154
+ const entry = entryById.get(id);
155
+ return (entry?.state === "loaded" && entry.kind === "view" && !entry.builtin);
156
+ }, [entryById, showDynamicViewControls]);
157
+ const updateDynamicField = React.useCallback((field, value) => {
158
+ setDynamicViewForm((current) => ({ ...current, [field]: value }));
159
+ }, []);
160
+ const handleEditDynamicView = React.useCallback((id) => {
161
+ const entry = entryById.get(id);
162
+ if (!entry)
163
+ return;
164
+ setDynamicViewForm({
165
+ id: entry.id,
166
+ title: entry.label,
167
+ entrypoint: editableEntrypointFor(entry),
168
+ description: entry.description ?? "",
169
+ });
170
+ setDynamicViewStatus(null);
171
+ }, [entryById]);
172
+ const handleSaveDynamicView = React.useCallback(async (event) => {
173
+ event.preventDefault();
174
+ const id = dynamicViewForm.id.trim();
175
+ const title = dynamicViewForm.title.trim();
176
+ const entrypoint = dynamicViewForm.entrypoint.trim();
177
+ const description = dynamicViewForm.description.trim();
178
+ if (!id || !title || !entrypoint) {
179
+ setDynamicViewStatus("Dynamic view ID, title, and entrypoint are required.");
180
+ return;
181
+ }
182
+ const manifest = {
183
+ id,
184
+ title,
185
+ source: "developer",
186
+ entrypoint,
187
+ placement: "canvas",
188
+ };
189
+ if (description)
190
+ manifest.description = description;
191
+ try {
192
+ const saved = await registerDynamicView(manifest, { update: true });
193
+ if (!saved) {
194
+ setDynamicViewStatus("Dynamic view bridge is unavailable.");
195
+ return;
196
+ }
197
+ setDynamicViewStatus(`Saved ${saved.title}.`);
198
+ refreshDynamicViews();
199
+ }
200
+ catch (error) {
201
+ setDynamicViewStatus(error instanceof Error ? error.message : String(error));
202
+ }
203
+ }, [dynamicViewForm, refreshDynamicViews]);
204
+ const handleDeleteDynamicView = React.useCallback(async (id) => {
205
+ const entry = entryById.get(id);
206
+ if (!entry)
207
+ return;
208
+ try {
209
+ const result = await unregisterDynamicView(id);
210
+ if (!result) {
211
+ setDynamicViewStatus("Dynamic view bridge is unavailable.");
212
+ return;
213
+ }
214
+ if (!result.removed) {
215
+ setDynamicViewStatus(`${entry.label} was not registered.`);
216
+ return;
217
+ }
218
+ setDynamicViewStatus(`Deleted ${entry.label}.`);
219
+ setDynamicViewForm((current) => current.id === id ? { ...EMPTY_DYNAMIC_VIEW_FORM } : current);
220
+ refreshDynamicViews();
221
+ }
222
+ catch (error) {
223
+ setDynamicViewStatus(error instanceof Error ? error.message : String(error));
224
+ }
225
+ }, [entryById, refreshDynamicViews]);
226
+ return (_jsxs("div", { className: "absolute inset-0 flex min-h-0 flex-col px-0 pb-[calc(var(--eliza-mobile-nav-offset,0px)+var(--safe-area-bottom,0px)+var(--eliza-continuous-chat-clearance,5.25rem)+1.75rem)]", children: [showDynamicViewControls ? (_jsxs("form", { "aria-label": "Dynamic view management", onSubmit: handleSaveDynamicView, className: "mx-3 mb-2 grid flex-none gap-2 rounded-lg border border-white/10 bg-black/55 p-3 text-white sm:mx-4", children: [_jsxs("div", { className: "grid gap-2 sm:grid-cols-2", children: [_jsxs("label", { className: "grid gap-1 text-xs font-medium", children: ["Dynamic view ID", _jsx("input", { className: "h-9 rounded-md border border-white/15 bg-black/40 px-2 text-sm text-white outline-none", value: dynamicViewForm.id, onChange: (event) => updateDynamicField("id", event.currentTarget.value) })] }), _jsxs("label", { className: "grid gap-1 text-xs font-medium", children: ["Dynamic view title", _jsx("input", { className: "h-9 rounded-md border border-white/15 bg-black/40 px-2 text-sm text-white outline-none", value: dynamicViewForm.title, onChange: (event) => updateDynamicField("title", event.currentTarget.value) })] }), _jsxs("label", { className: "grid gap-1 text-xs font-medium", children: ["Dynamic view entrypoint", _jsx("input", { className: "h-9 rounded-md border border-white/15 bg-black/40 px-2 text-sm text-white outline-none", value: dynamicViewForm.entrypoint, onChange: (event) => updateDynamicField("entrypoint", event.currentTarget.value) })] }), _jsxs("label", { className: "grid gap-1 text-xs font-medium", children: ["Dynamic view description", _jsx("input", { className: "h-9 rounded-md border border-white/15 bg-black/40 px-2 text-sm text-white outline-none", value: dynamicViewForm.description, onChange: (event) => updateDynamicField("description", event.currentTarget.value) })] })] }), _jsxs("div", { className: "flex min-h-9 flex-wrap items-center gap-2", children: [_jsx("button", { type: "submit", className: "h-8 rounded-md bg-accent px-3 text-sm font-semibold text-white hover:bg-accent/85", children: "Save" }), _jsx("button", { type: "button", className: "h-8 rounded-md border border-white/15 px-3 text-sm font-semibold text-white hover:bg-white/10", onClick: () => {
227
+ setDynamicViewForm({ ...EMPTY_DYNAMIC_VIEW_FORM });
228
+ setDynamicViewStatus(null);
229
+ }, children: "Clear" }), dynamicViewStatus ? (_jsx("p", { role: "status", className: "text-sm text-white/80", children: dynamicViewStatus })) : null] })] })) : null, _jsx(Springboard, { entries: entries, loading: loading, onLaunch: (entry) => handleLaunch(entryById.get(entry.id) ?? entry), onEdgeSwipeRight: onNavigateHomeFromEdge, canManageView: showDynamicViewControls ? canManageView : undefined, onEditView: showDynamicViewControls ? handleEditDynamicView : undefined, onDeleteView: showDynamicViewControls ? handleDeleteDynamicView : undefined, page: springboardPage, onPageChange: setSpringboardPage, onPageCountChange: setSpringboardPageCount, editing: springboardEditing, onEditingChange: setSpringboardEditing, showPageDots: false })] }));
120
230
  });
@@ -1 +1 @@
1
- {"version":3,"file":"ContinuousChatOverlay.d.ts","sourceRoot":"","sources":["../../../../../../src/components/shell/ContinuousChatOverlay.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAc/B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAgCnF,OAAO,KAAK,EAAmB,eAAe,EAAE,MAAM,sBAAsB,CAAC;AA8D7E;;+CAE+C;AAC/C,MAAM,MAAM,SAAS,GACjB,QAAQ,GACR,OAAO,GACP,iBAAiB,GACjB,mBAAmB,GACnB,WAAW,CAAC;AAEhB;;;;;;;;GAQG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAu0B1D,wBAAgB,qBAAqB,CAAC,EACpC,UAAU,EACV,SAAmB,EACnB,KAAK,EAAE,SAAS,GACjB,EAAE;IACD,UAAU,EAAE,eAAe,CAAC;IAC5B,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,sBAAsB,CAAC;CAChC,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAqjFpB"}
1
+ {"version":3,"file":"ContinuousChatOverlay.d.ts","sourceRoot":"","sources":["../../../../../../src/components/shell/ContinuousChatOverlay.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAc/B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAqCnF,OAAO,KAAK,EAAmB,eAAe,EAAE,MAAM,sBAAsB,CAAC;AA8D7E;;+CAE+C;AAC/C,MAAM,MAAM,SAAS,GACjB,QAAQ,GACR,OAAO,GACP,iBAAiB,GACjB,mBAAmB,GACnB,WAAW,CAAC;AAEhB;;;;;;;;GAQG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAw0B1D,wBAAgB,qBAAqB,CAAC,EACpC,UAAU,EACV,SAAmB,EACnB,KAAK,EAAE,SAAS,GACjB,EAAE;IACD,UAAU,EAAE,eAAe,CAAC;IAC5B,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,sBAAsB,CAAC;CAChC,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAknFpB"}