@elizaos/ui 2.0.3-beta.4 → 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 (82) hide show
  1. package/app-navigate-view.d.ts.map +1 -1
  2. package/app-navigate-view.js +1 -1
  3. package/cloud/api-keys/ApiKeysView.d.ts +6 -3
  4. package/cloud/api-keys/ApiKeysView.d.ts.map +1 -1
  5. package/cloud/api-keys/ApiKeysView.js +21 -29
  6. package/cloud/api-keys/copy-api-key.d.ts +1 -8
  7. package/cloud/api-keys/copy-api-key.d.ts.map +1 -1
  8. package/cloud/api-keys/copy-api-key.js +1 -10
  9. package/cloud/api-keys/index.d.ts +1 -1
  10. package/cloud/api-keys/index.d.ts.map +1 -1
  11. package/cloud/api-keys/index.js +1 -1
  12. package/cloud/shell/CloudRouterShell.d.ts +12 -0
  13. package/cloud/shell/CloudRouterShell.d.ts.map +1 -1
  14. package/cloud/shell/CloudRouterShell.js +38 -2
  15. package/cloud-ui/components/data-list/api-keys-table.d.ts +1 -2
  16. package/cloud-ui/components/data-list/api-keys-table.d.ts.map +1 -1
  17. package/cloud-ui/components/data-list/api-keys-table.js +3 -9
  18. package/cloud-ui/components/docs/api-route-explorer-client.d.ts.map +1 -1
  19. package/cloud-ui/components/docs/api-route-explorer-client.js +2 -1
  20. package/cloud-ui/components/layout/dashboard-route-page.d.ts.map +1 -1
  21. package/cloud-ui/components/layout/dashboard-route-page.js +3 -1
  22. package/components/config-ui/config-field.js +1 -1
  23. package/components/pages/Springboard.d.ts.map +1 -1
  24. package/components/pages/Springboard.js +34 -45
  25. package/components/pages/SpringboardSurface.d.ts.map +1 -1
  26. package/components/pages/SpringboardSurface.js +116 -6
  27. package/components/shell/ContinuousChatOverlay.d.ts.map +1 -1
  28. package/components/shell/ContinuousChatOverlay.js +207 -146
  29. package/components/shell/HomeSpringboardSurface.d.ts.map +1 -1
  30. package/components/shell/HomeSpringboardSurface.js +31 -76
  31. package/components/shell/use-pull-gesture.d.ts +6 -0
  32. package/components/shell/use-pull-gesture.d.ts.map +1 -1
  33. package/components/shell/use-pull-gesture.js +66 -27
  34. package/components/shell/useShellController.d.ts.map +1 -1
  35. package/components/shell/useShellController.js +13 -3
  36. package/components/views/ViewTileImage.d.ts +8 -8
  37. package/components/views/ViewTileImage.d.ts.map +1 -1
  38. package/components/views/ViewTileImage.js +38 -9
  39. package/hooks/useHorizontalPager.d.ts +33 -0
  40. package/hooks/useHorizontalPager.d.ts.map +1 -0
  41. package/hooks/useHorizontalPager.js +226 -0
  42. package/hooks/useLayoutShiftMonitor.d.ts +2 -0
  43. package/hooks/useLayoutShiftMonitor.d.ts.map +1 -1
  44. package/hooks/useLayoutShiftMonitor.js +23 -0
  45. package/hooks/view-catalog.d.ts +3 -4
  46. package/hooks/view-catalog.d.ts.map +1 -1
  47. package/hooks/view-catalog.js +4 -6
  48. package/package.json +10 -10
  49. package/state/startup-telemetry.d.ts +6 -0
  50. package/state/startup-telemetry.d.ts.map +1 -1
  51. package/state/startup-telemetry.js +23 -2
  52. package/styles/base.css +6 -6
  53. package/styles/brand-gold.css +6 -5
  54. package/testing/layout-stability.d.ts +11 -3
  55. package/testing/layout-stability.d.ts.map +1 -1
  56. package/testing/layout-stability.js +29 -4
  57. package/testing/run-animation-frames-immediately.d.ts +2 -0
  58. package/testing/run-animation-frames-immediately.d.ts.map +1 -0
  59. package/testing/run-animation-frames-immediately.js +8 -0
  60. package/themes/presets.js +14 -14
  61. package/utils/cloud-agent-base.d.ts.map +1 -1
  62. package/utils/cloud-agent-base.js +6 -0
  63. package/widgets/registry.d.ts.map +1 -1
  64. package/widgets/registry.js +7 -0
  65. package/components/pages/automation-conversations.d.ts +0 -16
  66. package/components/pages/automation-conversations.d.ts.map +0 -1
  67. package/components/pages/automation-conversations.js +0 -204
  68. package/components/pages/automations-missing-creds.d.ts +0 -25
  69. package/components/pages/automations-missing-creds.d.ts.map +0 -1
  70. package/components/pages/automations-missing-creds.js +0 -39
  71. package/components/settings/desktop-workspace-audit-config.d.ts +0 -3
  72. package/components/settings/desktop-workspace-audit-config.d.ts.map +0 -1
  73. package/components/settings/desktop-workspace-audit-config.js +0 -171
  74. package/components/shell/first-run-theme.d.ts +0 -6
  75. package/components/shell/first-run-theme.d.ts.map +0 -1
  76. package/components/shell/first-run-theme.js +0 -31
  77. package/genui/use-json-render-message.d.ts +0 -20
  78. package/genui/use-json-render-message.d.ts.map +0 -1
  79. package/genui/use-json-render-message.js +0 -38
  80. package/state/first-run-restart.d.ts +0 -8
  81. package/state/first-run-restart.d.ts.map +0 -1
  82. package/state/first-run-restart.js +0 -8
@@ -0,0 +1,226 @@
1
+ import * as React from "react";
2
+ const AXIS_COMMIT_SLOP = 6;
3
+ const AXIS_DOMINANCE_RATIO = 1.15;
4
+ const MIN_DISTANCE_THRESHOLD = 64;
5
+ const DISTANCE_THRESHOLD_RATIO = 0.24;
6
+ const MIN_FLICK_DISTANCE = 48;
7
+ const FLICK_VELOCITY = 0.45;
8
+ const SETTLE_MS = 360;
9
+ const SNAP_BACK_MS = 280;
10
+ const SETTLE_EASING = "cubic-bezier(0.32, 0.72, 0, 1)";
11
+ function now() {
12
+ return typeof performance !== "undefined" ? performance.now() : Date.now();
13
+ }
14
+ function roundedPx(value) {
15
+ return `${Math.round(value * 1000) / 1000}px`;
16
+ }
17
+ function pageOffset(page, width) {
18
+ return -page * width;
19
+ }
20
+ function clampPage(page, pageCount) {
21
+ return Math.max(0, Math.min(Math.max(0, pageCount - 1), page));
22
+ }
23
+ /**
24
+ * Native-feeling horizontal pager for launcher surfaces.
25
+ *
26
+ * Pointer movement writes directly to the rail transform, paced by rAF, so a
27
+ * drag never waits on React render scheduling. React state is used only for the
28
+ * settled page index after release.
29
+ */
30
+ export function useHorizontalPager({ page, pageCount, enabled = true, edgeSwipeRightEnabled = false, onPageChange, onEdgeSwipeRight, }) {
31
+ const viewportRef = React.useRef(null);
32
+ const railRef = React.useRef(null);
33
+ const dragRef = React.useRef(null);
34
+ const rafRef = React.useRef(0);
35
+ const pendingOffsetRef = React.useRef(null);
36
+ const mountedRef = React.useRef(false);
37
+ const pageRef = React.useRef(page);
38
+ const pageCountRef = React.useRef(pageCount);
39
+ const enabledRef = React.useRef(enabled);
40
+ const edgeSwipeRightEnabledRef = React.useRef(edgeSwipeRightEnabled);
41
+ const onPageChangeRef = React.useRef(onPageChange);
42
+ const onEdgeSwipeRightRef = React.useRef(onEdgeSwipeRight);
43
+ pageRef.current = page;
44
+ pageCountRef.current = pageCount;
45
+ enabledRef.current = enabled;
46
+ edgeSwipeRightEnabledRef.current = edgeSwipeRightEnabled;
47
+ onPageChangeRef.current = onPageChange;
48
+ onEdgeSwipeRightRef.current = onEdgeSwipeRight;
49
+ const measureWidth = React.useCallback(() => {
50
+ const width = viewportRef.current?.clientWidth ||
51
+ (typeof window !== "undefined" ? window.innerWidth : 1);
52
+ return Math.max(1, width);
53
+ }, []);
54
+ const writeOffset = React.useCallback((offset, transitionMs) => {
55
+ const rail = railRef.current;
56
+ if (!rail)
57
+ return;
58
+ rail.style.transition =
59
+ transitionMs == null
60
+ ? "none"
61
+ : `transform ${transitionMs}ms ${SETTLE_EASING}`;
62
+ rail.style.transform = `translate3d(${roundedPx(offset)},0,0)`;
63
+ }, []);
64
+ const flushOffset = React.useCallback(() => {
65
+ rafRef.current = 0;
66
+ const offset = pendingOffsetRef.current;
67
+ pendingOffsetRef.current = null;
68
+ if (offset == null)
69
+ return;
70
+ writeOffset(offset, null);
71
+ }, [writeOffset]);
72
+ const scheduleOffset = React.useCallback((offset) => {
73
+ pendingOffsetRef.current = offset;
74
+ if (rafRef.current !== 0)
75
+ return;
76
+ if (typeof requestAnimationFrame === "function") {
77
+ rafRef.current = requestAnimationFrame(flushOffset);
78
+ return;
79
+ }
80
+ flushOffset();
81
+ }, [flushOffset]);
82
+ const cancelScheduledOffset = React.useCallback(() => {
83
+ if (rafRef.current !== 0 && typeof cancelAnimationFrame === "function") {
84
+ cancelAnimationFrame(rafRef.current);
85
+ }
86
+ rafRef.current = 0;
87
+ pendingOffsetRef.current = null;
88
+ }, []);
89
+ const canMove = React.useCallback((state, dx) => {
90
+ if (dx < 0)
91
+ return state.page < pageCountRef.current - 1;
92
+ if (dx > 0) {
93
+ return state.page > 0 || edgeSwipeRightEnabledRef.current;
94
+ }
95
+ return false;
96
+ }, []);
97
+ const releaseCapture = React.useCallback((event, state) => {
98
+ if (!state.captured)
99
+ return;
100
+ try {
101
+ event.currentTarget.releasePointerCapture?.(state.pointerId);
102
+ }
103
+ catch {
104
+ // The browser may already have revoked capture.
105
+ }
106
+ }, []);
107
+ React.useLayoutEffect(() => {
108
+ const width = measureWidth();
109
+ const nextPage = clampPage(page, pageCount);
110
+ writeOffset(pageOffset(nextPage, width), mountedRef.current && !dragRef.current ? SETTLE_MS : null);
111
+ mountedRef.current = true;
112
+ }, [measureWidth, page, pageCount, writeOffset]);
113
+ React.useEffect(() => {
114
+ if (typeof ResizeObserver === "undefined")
115
+ return;
116
+ const viewport = viewportRef.current;
117
+ if (!viewport)
118
+ return;
119
+ const observer = new ResizeObserver(() => {
120
+ if (dragRef.current)
121
+ return;
122
+ writeOffset(pageOffset(clampPage(pageRef.current, pageCountRef.current), measureWidth()), null);
123
+ });
124
+ observer.observe(viewport);
125
+ return () => observer.disconnect();
126
+ }, [measureWidth, writeOffset]);
127
+ React.useEffect(() => cancelScheduledOffset, [cancelScheduledOffset]);
128
+ const finish = React.useCallback((event, cancelled = false) => {
129
+ const state = dragRef.current;
130
+ if (!state || state.pointerId !== event.pointerId)
131
+ return;
132
+ cancelScheduledOffset();
133
+ dragRef.current = null;
134
+ releaseCapture(event, state);
135
+ const base = pageOffset(state.page, state.width);
136
+ const dx = event.clientX - state.startX;
137
+ const dy = event.clientY - state.startY;
138
+ if (cancelled || state.axis !== "horizontal" || !canMove(state, dx)) {
139
+ writeOffset(base, SNAP_BACK_MS);
140
+ return;
141
+ }
142
+ const elapsed = Math.max(1, now() - state.startTime);
143
+ const velocity = dx / elapsed;
144
+ const distanceThreshold = Math.max(MIN_DISTANCE_THRESHOLD, state.width * DISTANCE_THRESHOLD_RATIO);
145
+ const shouldAdvance = Math.abs(dx) >= distanceThreshold ||
146
+ (Math.abs(dx) >= MIN_FLICK_DISTANCE &&
147
+ Math.abs(velocity) >= FLICK_VELOCITY &&
148
+ Math.abs(dx) > Math.abs(dy) * AXIS_DOMINANCE_RATIO);
149
+ if (!shouldAdvance) {
150
+ writeOffset(base, SNAP_BACK_MS);
151
+ return;
152
+ }
153
+ if (dx > 0 && state.page === 0 && edgeSwipeRightEnabledRef.current) {
154
+ writeOffset(base, SNAP_BACK_MS);
155
+ onEdgeSwipeRightRef.current?.();
156
+ return;
157
+ }
158
+ const targetPage = clampPage(state.page + (dx < 0 ? 1 : -1), pageCountRef.current);
159
+ writeOffset(pageOffset(targetPage, state.width), SETTLE_MS);
160
+ if (targetPage !== pageRef.current) {
161
+ onPageChangeRef.current(targetPage);
162
+ }
163
+ }, [canMove, cancelScheduledOffset, releaseCapture, writeOffset]);
164
+ const onPointerDown = React.useCallback((event) => {
165
+ if (!enabledRef.current ||
166
+ pageCountRef.current <= 0 ||
167
+ event.isPrimary === false) {
168
+ return;
169
+ }
170
+ cancelScheduledOffset();
171
+ const currentPage = clampPage(pageRef.current, pageCountRef.current);
172
+ const width = measureWidth();
173
+ dragRef.current = {
174
+ pointerId: event.pointerId,
175
+ startX: event.clientX,
176
+ startY: event.clientY,
177
+ startTime: now(),
178
+ page: currentPage,
179
+ width,
180
+ captured: false,
181
+ axis: "pending",
182
+ };
183
+ writeOffset(pageOffset(currentPage, width), null);
184
+ }, [cancelScheduledOffset, measureWidth, writeOffset]);
185
+ const onPointerMove = React.useCallback((event) => {
186
+ const state = dragRef.current;
187
+ if (!state || state.pointerId !== event.pointerId)
188
+ return;
189
+ const dx = event.clientX - state.startX;
190
+ const dy = event.clientY - state.startY;
191
+ if (state.axis === "pending") {
192
+ const ax = Math.abs(dx);
193
+ const ay = Math.abs(dy);
194
+ if (Math.max(ax, ay) < AXIS_COMMIT_SLOP)
195
+ return;
196
+ state.axis = ax > ay * AXIS_DOMINANCE_RATIO ? "horizontal" : "vertical";
197
+ }
198
+ if (state.axis !== "horizontal")
199
+ return;
200
+ if (!canMove(state, dx))
201
+ return;
202
+ if (!state.captured) {
203
+ try {
204
+ event.currentTarget.setPointerCapture(event.pointerId);
205
+ state.captured = true;
206
+ }
207
+ catch {
208
+ // Capture is best-effort; the transform can still follow pointermove.
209
+ }
210
+ }
211
+ scheduleOffset(pageOffset(state.page, state.width) + dx);
212
+ }, [canMove, scheduleOffset]);
213
+ const onPointerUp = React.useCallback((event) => finish(event), [finish]);
214
+ const onPointerCancel = React.useCallback((event) => finish(event, true), [finish]);
215
+ return {
216
+ viewportRef,
217
+ railRef,
218
+ handlers: {
219
+ onPointerDown,
220
+ onPointerMove,
221
+ onPointerUp,
222
+ onPointerCancel,
223
+ onLostPointerCapture: onPointerCancel,
224
+ },
225
+ };
226
+ }
@@ -1,5 +1,7 @@
1
1
  /** Web Vitals "good" CLS budget: above this, a window of shifts is flagged. */
2
2
  export declare const DEFAULT_CLS_BUDGET = 0.1;
3
+ export declare const LAYOUT_SHIFT_INTENT_ATTR = "data-eliza-layout-shift-intent";
4
+ export declare const LAYOUT_SHIFT_INTENT_TRANSIENT = "transient";
3
5
  /** Telemetry payload emitted on the shared RENDER_TELEMETRY_EVENT channel. */
4
6
  export interface LayoutShiftTelemetryEvent {
5
7
  source: "layoutShift";
@@ -1 +1 @@
1
- {"version":3,"file":"useLayoutShiftMonitor.d.ts","sourceRoot":"","sources":["../../../../../src/hooks/useLayoutShiftMonitor.ts"],"names":[],"mappings":"AAkCA,+EAA+E;AAC/E,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC,8EAA8E;AAC9E,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,8EAA8E;IAC9E,GAAG,EAAE,MAAM,CAAC;IACZ,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;IACnB,gDAAgD;IAChD,YAAY,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,yBAAyB;IACxC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AA6BD;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,yBAA8B,GACtC,MAAM,IAAI,CA2EZ;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,GAAE,yBAA8B,GACtC,IAAI,CAGN"}
1
+ {"version":3,"file":"useLayoutShiftMonitor.d.ts","sourceRoot":"","sources":["../../../../../src/hooks/useLayoutShiftMonitor.ts"],"names":[],"mappings":"AAkCA,+EAA+E;AAC/E,eAAO,MAAM,kBAAkB,MAAM,CAAC;AACtC,eAAO,MAAM,wBAAwB,mCAAmC,CAAC;AACzE,eAAO,MAAM,6BAA6B,cAAc,CAAC;AAGzD,8EAA8E;AAC9E,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,8EAA8E;IAC9E,GAAG,EAAE,MAAM,CAAC;IACZ,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;IACnB,gDAAgD;IAChD,YAAY,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,yBAAyB;IACxC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAkDD;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,yBAA8B,GACtC,MAAM,IAAI,CA4EZ;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,GAAE,yBAA8B,GACtC,IAAI,CAGN"}
@@ -24,6 +24,9 @@ import { cumulativeLayoutShift, } from "../testing/layout-stability.js";
24
24
  import { currentRoute, isRenderTelemetryEnabled, nextRenderTelemetrySequence, RENDER_TELEMETRY_EVENT, } from "./useRenderGuard.js";
25
25
  /** Web Vitals "good" CLS budget: above this, a window of shifts is flagged. */
26
26
  export const DEFAULT_CLS_BUDGET = 0.1;
27
+ export const LAYOUT_SHIFT_INTENT_ATTR = "data-eliza-layout-shift-intent";
28
+ export const LAYOUT_SHIFT_INTENT_TRANSIENT = "transient";
29
+ const LAYOUT_SHIFT_INTENT_SELECTOR = `[${LAYOUT_SHIFT_INTENT_ATTR}="${LAYOUT_SHIFT_INTENT_TRANSIENT}"]`;
27
30
  function emitLayoutShift(event) {
28
31
  const globalObject = globalThis;
29
32
  if (Array.isArray(globalObject.__ELIZA_RENDER_TELEMETRY__)) {
@@ -40,6 +43,25 @@ function emitLayoutShift(event) {
40
43
  console.info(message, event);
41
44
  }
42
45
  }
46
+ function hasTransientLayoutShiftIntent(entry) {
47
+ const sources = Array.isArray(entry.sources) ? entry.sources : [];
48
+ let sawIntentionalSource = false;
49
+ for (const source of sources) {
50
+ const node = source.node;
51
+ const element = node instanceof Element ? node : (node?.parentElement ?? null);
52
+ // Browser pseudo-element sources can omit a DOM parent. Do not let an
53
+ // unattributed pseudo-source disqualify a shift whose concrete sources are
54
+ // all inside an intentional-motion surface.
55
+ if (!element)
56
+ continue;
57
+ if (element.closest(LAYOUT_SHIFT_INTENT_SELECTOR)) {
58
+ sawIntentionalSource = true;
59
+ continue;
60
+ }
61
+ return false;
62
+ }
63
+ return sawIntentionalSource;
64
+ }
43
65
  /**
44
66
  * Start observing layout shifts. Returns a stop function. No-op (returns a no-op
45
67
  * stop) when render telemetry is disabled or the engine lacks the layout-shift
@@ -91,6 +113,7 @@ export function startLayoutShiftMonitor(options = {}) {
91
113
  pending.push({
92
114
  value: entry.value,
93
115
  hadRecentInput: entry.hadRecentInput === true,
116
+ intentional: hasTransientLayoutShiftIntent(entry),
94
117
  });
95
118
  if (!entry.hadRecentInput && entry.value > largest) {
96
119
  largest = entry.value;
@@ -33,10 +33,9 @@ export interface ViewEntry {
33
33
  /** Real preview image URL, or undefined when only a generated fallback exists. */
34
34
  heroUrl?: string;
35
35
  /**
36
- * Always-available preview image URL. Real plugin art wins when present;
37
- * otherwise this is a deterministic branded SVG data URI. Use this (not
38
- * {@link heroUrl}) when every entry must show an image, e.g. Springboard
39
- * tiles.
36
+ * Always-available preview image URL. Real plugin art wins only when the
37
+ * registry says that asset exists; otherwise this is a deterministic branded
38
+ * SVG data URI, not a backend hero endpoint probe.
40
39
  */
41
40
  imageUrl?: string;
42
41
  /** Deterministic branded image used when a real preview image fails to load. */
@@ -1 +1 @@
1
- {"version":3,"file":"view-catalog.d.ts","sourceRoot":"","sources":["../../../../../src/hooks/view-catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAEL,KAAK,gBAAgB,EAErB,KAAK,QAAQ,EACd,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAoB7D,YAAY,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEhE,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,WAAW,GAAG,YAAY,GAAG,OAAO,CAAC;AAC7E,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,KAAK,CAAC;AAE3C,MAAM,WAAW,SAAS;IACxB,0EAA0E;IAC1E,GAAG,EAAE,MAAM,CAAC;IACZ,2DAA2D;IAC3D,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,QAAQ,EAAE,YAAY,CAAC;IACvB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC;IAC5B,KAAK,EAAE,cAAc,CAAC;IACtB,IAAI,EAAE,aAAa,CAAC;IACpB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,uDAAuD;IACvD,IAAI,CAAC,EAAE,iBAAiB,CAAC;IACzB,GAAG,CAAC,EAAE,eAAe,CAAC;CACvB;AAED,sEAAsE;AACtE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,iBAAiB,GAAG,SAAS,CAiC9D;AAoCD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,SAAS,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACvC,cAAc,EAAE,YAAY,CAAC;IAC7B,+EAA+E;IAC/E,YAAY,EAAE,gBAAgB,CAAC;CAChC,GAAG,SAAS,EAAE,CAgCd;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CAwBrE"}
1
+ {"version":3,"file":"view-catalog.d.ts","sourceRoot":"","sources":["../../../../../src/hooks/view-catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAEL,KAAK,gBAAgB,EAErB,KAAK,QAAQ,EACd,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAoB7D,YAAY,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEhE,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,WAAW,GAAG,YAAY,GAAG,OAAO,CAAC;AAC7E,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,KAAK,CAAC;AAE3C,MAAM,WAAW,SAAS;IACxB,0EAA0E;IAC1E,GAAG,EAAE,MAAM,CAAC;IACZ,2DAA2D;IAC3D,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,QAAQ,EAAE,YAAY,CAAC;IACvB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC;IAC5B,KAAK,EAAE,cAAc,CAAC;IACtB,IAAI,EAAE,aAAa,CAAC;IACpB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,uDAAuD;IACvD,IAAI,CAAC,EAAE,iBAAiB,CAAC;IACzB,GAAG,CAAC,EAAE,eAAe,CAAC;CACvB;AAED,sEAAsE;AACtE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,iBAAiB,GAAG,SAAS,CA+B9D;AAoCD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,SAAS,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACvC,cAAc,EAAE,YAAY,CAAC;IAC7B,+EAA+E;IAC/E,YAAY,EAAE,gBAAgB,CAAC;CAChC,GAAG,SAAS,EAAE,CAgCd;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,SAAS,EAAE,CAwBrE"}
@@ -41,12 +41,10 @@ export function viewToEntry(view) {
41
41
  description: view.description,
42
42
  icon: view.icon,
43
43
  heroUrl: hasHero ? view.heroImageUrl : undefined,
44
- // `imageUrl` ALWAYS resolves to a real/branded image so a Springboard tile
45
- // is never a bare glyph. A real plugin hero (`heroImageUrl`) wins; otherwise
46
- // we generate the branded hero CLIENT-SIDE (an inline data URI) rather than
47
- // pointing at the `/api/views/:id/hero` endpoint, which 404s offline / on
48
- // cloud builds — so the tiles show images everywhere, no agent required.
49
- imageUrl: view.heroImageUrl ?? generatedHero,
44
+ // Use a registry hero only when it is declared as real. Otherwise generate
45
+ // the branded fallback client-side so static/mobile shells avoid backend
46
+ // `/api/views/:id/hero` probes for icon-only views.
47
+ imageUrl: hasHero ? view.heroImageUrl : generatedHero,
50
48
  fallbackImageUrl: generatedHero,
51
49
  hasHero,
52
50
  modality: view.viewType ?? "gui",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elizaos/ui",
3
- "version": "2.0.3-beta.4",
3
+ "version": "2.0.3-beta.5",
4
4
  "description": "Shared UI primitives, composites, and layout utilities for elizaOS apps.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -279,17 +279,17 @@
279
279
  "react-dom": "19.2.5"
280
280
  },
281
281
  "optionalDependencies": {
282
- "@elizaos/capacitor-bun-runtime": "2.0.3-beta.4",
283
- "@elizaos/capacitor-camera": "2.0.3-beta.4"
282
+ "@elizaos/capacitor-bun-runtime": "2.0.3-beta.5",
283
+ "@elizaos/capacitor-camera": "2.0.3-beta.5"
284
284
  },
285
285
  "dependencies": {
286
286
  "@capacitor/core": "8.4.0",
287
- "@elizaos/cloud-sdk": "2.0.3-beta.4",
288
- "@elizaos/cloud-shared": "2.0.3-beta.4",
289
- "@elizaos/core": "2.0.3-beta.4",
290
- "@elizaos/logger": "2.0.3-beta.4",
291
- "@elizaos/shared": "2.0.3-beta.4",
292
- "@elizaos/tui": "2.0.3-beta.4",
287
+ "@elizaos/cloud-sdk": "2.0.3-beta.5",
288
+ "@elizaos/cloud-shared": "2.0.3-beta.5",
289
+ "@elizaos/core": "2.0.3-beta.5",
290
+ "@elizaos/logger": "2.0.3-beta.5",
291
+ "@elizaos/shared": "2.0.3-beta.5",
292
+ "@elizaos/tui": "2.0.3-beta.5",
293
293
  "@fontsource/poppins": "^5.2.7",
294
294
  "@json-render/core": "0.19.0",
295
295
  "@json-render/react": "0.19.0",
@@ -361,5 +361,5 @@
361
361
  "directory": "dist"
362
362
  },
363
363
  "types": "./index.d.ts",
364
- "gitHead": "f76f55793a0fb8d6b869dd8ddce03970de061e34"
364
+ "gitHead": "ff6157011c9459670021cc28a6797592a78b8817"
365
365
  }
@@ -37,11 +37,17 @@ export declare const STARTUP_TRACE_WINDOW_KEY = "__ELIZA_STARTUP_TRACE__";
37
37
  /** Window key a native host (Electrobun/Capacitor) may inject to share its id. */
38
38
  export declare const STARTUP_TRACE_ID_WINDOW_KEY = "__ELIZA_STARTUP_TRACE_ID__";
39
39
  declare global {
40
+ interface ElizaNativeStartupBridge {
41
+ /** Android-native synchronous startup trace id fast path. */
42
+ getStartupTraceId?: () => unknown;
43
+ }
40
44
  interface Window {
41
45
  /** Renderer startup trace mirrored for out-of-band harness capture. */
42
46
  [STARTUP_TRACE_WINDOW_KEY]?: StartupTrace;
43
47
  /** Native-host-injected trace id, shared across host ↔ renderer. */
44
48
  [STARTUP_TRACE_ID_WINDOW_KEY]?: string;
49
+ /** Android synchronous native bridge, when running in the Capacitor APK. */
50
+ ElizaNative?: ElizaNativeStartupBridge;
45
51
  }
46
52
  }
47
53
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"startup-telemetry.d.ts","sourceRoot":"","sources":["../../../../../src/state/startup-telemetry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,WAAW,WAAW;IAC1B,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,EAAE,EAAE,MAAM,CAAC;IACX,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,YAAY;IAC3B,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,UAAU,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAED,wEAAwE;AACxE,eAAO,MAAM,mBAAmB,mBAAmB,CAAC;AACpD,oCAAoC;AACpC,eAAO,MAAM,wBAAwB,4BAA4B,CAAC;AAClE,kFAAkF;AAClF,eAAO,MAAM,2BAA2B,+BAA+B,CAAC;AAExE,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,uEAAuE;QACvE,CAAC,wBAAwB,CAAC,CAAC,EAAE,YAAY,CAAC;QAC1C,oEAAoE;QACpE,CAAC,2BAA2B,CAAC,CAAC,EAAE,MAAM,CAAC;KACxC;CACF;AAoCD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAQ7D;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,IAAI,CAwBN;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,GACd,IAAI,CAgBN;AAED,8DAA8D;AAC9D,wBAAgB,eAAe,IAAI,YAAY,CAE9C;AAED,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,uCAAuC;AACvC,wBAAgB,2BAA2B,IAAI,IAAI,CAQlD"}
1
+ {"version":3,"file":"startup-telemetry.d.ts","sourceRoot":"","sources":["../../../../../src/state/startup-telemetry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,WAAW,WAAW;IAC1B,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,EAAE,EAAE,MAAM,CAAC;IACX,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,YAAY;IAC3B,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,UAAU,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAED,wEAAwE;AACxE,eAAO,MAAM,mBAAmB,mBAAmB,CAAC;AACpD,oCAAoC;AACpC,eAAO,MAAM,wBAAwB,4BAA4B,CAAC;AAClE,kFAAkF;AAClF,eAAO,MAAM,2BAA2B,+BAA+B,CAAC;AAExE,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,wBAAwB;QAChC,6DAA6D;QAC7D,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC;KACnC;IAED,UAAU,MAAM;QACd,uEAAuE;QACvE,CAAC,wBAAwB,CAAC,CAAC,EAAE,YAAY,CAAC;QAC1C,oEAAoE;QACpE,CAAC,2BAA2B,CAAC,CAAC,EAAE,MAAM,CAAC;QACvC,4EAA4E;QAC5E,WAAW,CAAC,EAAE,wBAAwB,CAAC;KACxC;CACF;AAyDD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAQ7D;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,IAAI,CAwBN;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,GACd,IAAI,CAgBN;AAED,8DAA8D;AAC9D,wBAAgB,eAAe,IAAI,YAAY,CAE9C;AAED,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,uCAAuC;AACvC,wBAAgB,2BAA2B,IAAI,IAAI,CASlD"}
@@ -36,11 +36,31 @@ function originMs() {
36
36
  ? performance.timeOrigin
37
37
  : 0;
38
38
  }
39
+ function normalizeTraceId(value) {
40
+ if (typeof value !== "string")
41
+ return undefined;
42
+ const trimmed = value.trim();
43
+ return trimmed.length > 0 ? trimmed : undefined;
44
+ }
45
+ function readAndroidBridgeTraceId() {
46
+ if (typeof window === "undefined")
47
+ return undefined;
48
+ const bridge = window.ElizaNative;
49
+ if (!bridge || typeof bridge.getStartupTraceId !== "function") {
50
+ return undefined;
51
+ }
52
+ try {
53
+ return normalizeTraceId(bridge.getStartupTraceId());
54
+ }
55
+ catch {
56
+ return undefined;
57
+ }
58
+ }
39
59
  function readInjectedTraceId() {
40
60
  if (typeof window === "undefined")
41
61
  return undefined;
42
- const value = window[STARTUP_TRACE_ID_WINDOW_KEY];
43
- return typeof value === "string" && value.length > 0 ? value : undefined;
62
+ return (normalizeTraceId(window[STARTUP_TRACE_ID_WINDOW_KEY]) ??
63
+ readAndroidBridgeTraceId());
44
64
  }
45
65
  function mirrorToWindow() {
46
66
  if (typeof window === "undefined")
@@ -135,5 +155,6 @@ export function __resetStartupTraceForTests() {
135
155
  if (typeof window !== "undefined") {
136
156
  delete window[STARTUP_TRACE_WINDOW_KEY];
137
157
  delete window[STARTUP_TRACE_ID_WINDOW_KEY];
158
+ delete window.ElizaNative;
138
159
  }
139
160
  }
package/styles/base.css CHANGED
@@ -84,9 +84,9 @@
84
84
  --scrollbar-thumb-start: rgba(0, 0, 0, 0.22);
85
85
  --scrollbar-thumb-mid: rgba(0, 0, 0, 0.3);
86
86
  --scrollbar-thumb-end: rgba(0, 0, 0, 0.36);
87
- --scrollbar-thumb-hover-start: rgba(255, 88, 0, 0.56);
88
- --scrollbar-thumb-hover-mid: rgba(255, 88, 0, 0.7);
89
- --scrollbar-thumb-hover-end: rgba(255, 88, 0, 0.78);
87
+ --scrollbar-thumb-hover-start: rgba(0, 0, 0, 0.34);
88
+ --scrollbar-thumb-hover-mid: rgba(0, 0, 0, 0.44);
89
+ --scrollbar-thumb-hover-end: rgba(0, 0, 0, 0.52);
90
90
  --scrollbar-thumb-edge: rgba(0, 0, 0, 0.1);
91
91
 
92
92
  /* Header */
@@ -223,9 +223,9 @@
223
223
  --scrollbar-thumb-start: rgba(255, 255, 255, 0.32);
224
224
  --scrollbar-thumb-mid: rgba(255, 255, 255, 0.24);
225
225
  --scrollbar-thumb-end: rgba(255, 255, 255, 0.28);
226
- --scrollbar-thumb-hover-start: rgba(255, 88, 0, 0.94);
227
- --scrollbar-thumb-hover-mid: rgba(255, 88, 0, 0.82);
228
- --scrollbar-thumb-hover-end: rgba(255, 88, 0, 0.88);
226
+ --scrollbar-thumb-hover-start: rgba(255, 255, 255, 0.46);
227
+ --scrollbar-thumb-hover-mid: rgba(255, 255, 255, 0.38);
228
+ --scrollbar-thumb-hover-end: rgba(255, 255, 255, 0.42);
229
229
  --scrollbar-thumb-edge: rgba(255, 255, 255, 0.12);
230
230
 
231
231
  --header-bar-bg: var(--brand-black);
@@ -406,11 +406,12 @@
406
406
  * literals at the component layer.
407
407
  */
408
408
  :root {
409
- /* On-brand memory-type cues: gold (focal) + blue (info) + neutral grays.
410
- No decorative indigo/purple/green/amber; the per-type icon carries the rest
411
- of the distinction. */
412
- --memory-type-messages-bg: rgba(29, 145, 232, 0.14);
413
- --memory-type-messages-fg: rgb(29, 145, 232);
409
+ /* On-brand memory-type cues: gold (focal) + neutral slate/stone grays.
410
+ No decorative blue/indigo/purple/green/amber; the per-type icon carries the
411
+ rest of the distinction. Messages use a darker slate so they read distinct
412
+ from facts (slate-500) without grabbing the gold primary accent. */
413
+ --memory-type-messages-bg: rgba(71, 85, 105, 0.14);
414
+ --memory-type-messages-fg: rgb(71, 85, 105);
414
415
  --memory-type-memories-bg: rgba(240, 185, 11, 0.16);
415
416
  --memory-type-memories-fg: rgb(240, 185, 11);
416
417
  --memory-type-facts-bg: rgba(100, 116, 139, 0.14);
@@ -5,7 +5,8 @@
5
5
  * 1. Layout shift — content jumps/reflows after paint (the ranked widget list
6
6
  * reordering, a card popping in and pushing others down). Quantified the
7
7
  * same way Chrome computes CLS: the sum of `layout-shift` PerformanceEntry
8
- * values that were NOT caused by recent user input.
8
+ * values that were NOT caused by recent user input or an explicitly marked
9
+ * transient motion surface.
9
10
  * 2. Opacity flash — an element fades out and back (or in then out), e.g. an
10
11
  * animation that re-triggers on re-render. Detected from a time series of
11
12
  * sampled opacity.
@@ -21,10 +22,17 @@ export interface LayoutShiftSample {
21
22
  value: number;
22
23
  /** True if a user interaction happened just before — excluded from CLS. */
23
24
  hadRecentInput: boolean;
25
+ /**
26
+ * True when every attributed source belongs to an explicitly marked transient
27
+ * motion surface. These are controlled overlay/sheet animations rather than
28
+ * page reflow.
29
+ */
30
+ intentional?: boolean;
24
31
  }
25
32
  /**
26
33
  * Cumulative Layout Shift: the sum of shift values not attributable to recent
27
- * user input (matching the Web Vitals CLS definition). 0 = perfectly stable.
34
+ * user input or an explicitly marked transient motion surface. 0 = perfectly
35
+ * stable.
28
36
  */
29
37
  export declare function cumulativeLayoutShift(samples: readonly LayoutShiftSample[]): number;
30
38
  /** A timestamped opacity reading of one element. */
@@ -63,5 +71,5 @@ export declare function summarizeStability(shifts: readonly LayoutShiftSample[],
63
71
  * array and feeds {@link summarizeStability}. Kept here so the contract lives
64
72
  * next to the math it feeds.
65
73
  */
66
- export declare const LAYOUT_SHIFT_OBSERVER_INIT = "\n(() => {\n const w = window;\n if (w.__ELIZA_LAYOUT_SHIFTS__) return;\n w.__ELIZA_LAYOUT_SHIFTS__ = [];\n try {\n const obs = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n w.__ELIZA_LAYOUT_SHIFTS__.push({\n value: entry.value,\n hadRecentInput: entry.hadRecentInput === true,\n });\n }\n });\n obs.observe({ type: 'layout-shift', buffered: true });\n } catch {\n /* layout-shift unsupported \u2014 caller treats absence as 0 CLS */\n }\n})();\n";
74
+ export declare const LAYOUT_SHIFT_OBSERVER_INIT = "\n(() => {\n const w = window;\n if (w.__ELIZA_LAYOUT_SHIFTS__) return;\n w.__ELIZA_LAYOUT_SHIFTS__ = [];\n try {\n const obs = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n w.__ELIZA_LAYOUT_SHIFTS__.push({\n value: entry.value,\n hadRecentInput: entry.hadRecentInput === true,\n intentional: (() => {\n const sources = Array.isArray(entry.sources) ? entry.sources : [];\n let sawIntentional = false;\n for (const source of sources) {\n const node = source?.node;\n const element =\n node instanceof Element ? node : (node?.parentElement ?? null);\n if (!element) continue;\n if (\n element.closest(\n '[data-eliza-layout-shift-intent=\"transient\"]',\n )\n ) {\n sawIntentional = true;\n continue;\n }\n return false;\n }\n return sawIntentional;\n })(),\n });\n }\n });\n obs.observe({ type: 'layout-shift', buffered: true });\n } catch {\n /* layout-shift unsupported \u2014 caller treats absence as 0 CLS */\n }\n})();\n";
67
75
  //# sourceMappingURL=layout-stability.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"layout-stability.d.ts","sourceRoot":"","sources":["../../../../../src/testing/layout-stability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,uEAAuE;AACvE,MAAM,WAAW,iBAAiB;IAChC,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,cAAc,EAAE,OAAO,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,SAAS,iBAAiB,EAAE,GACpC,MAAM,CAQR;AAED,oDAAoD;AACpD,MAAM,WAAW,aAAa;IAC5B,2BAA2B;IAC3B,CAAC,EAAE,MAAM,CAAC;IACV,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,QAAQ,SAAM,GACb,OAAO,CA2BT;AAED,MAAM,WAAW,eAAe;IAC9B,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,4DAA4D;IAC5D,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,uEAAuE;AACvE,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,iBAAiB,EAAE,EACpC,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,MAAM,EAAE,eAAe,GACtB,gBAAgB,CAYlB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,kiBAmBtC,CAAC"}
1
+ {"version":3,"file":"layout-stability.d.ts","sourceRoot":"","sources":["../../../../../src/testing/layout-stability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,uEAAuE;AACvE,MAAM,WAAW,iBAAiB;IAChC,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,cAAc,EAAE,OAAO,CAAC;IACxB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,SAAS,iBAAiB,EAAE,GACpC,MAAM,CAaR;AAED,oDAAoD;AACpD,MAAM,WAAW,aAAa;IAC5B,2BAA2B;IAC3B,CAAC,EAAE,MAAM,CAAC;IACV,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,QAAQ,SAAM,GACb,OAAO,CA2BT;AAED,MAAM,WAAW,eAAe;IAC9B,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,4DAA4D;IAC5D,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,uEAAuE;AACvE,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,iBAAiB,EAAE,EACpC,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,MAAM,EAAE,eAAe,GACtB,gBAAgB,CAYlB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,gwCAuCtC,CAAC"}
@@ -5,7 +5,8 @@
5
5
  * 1. Layout shift — content jumps/reflows after paint (the ranked widget list
6
6
  * reordering, a card popping in and pushing others down). Quantified the
7
7
  * same way Chrome computes CLS: the sum of `layout-shift` PerformanceEntry
8
- * values that were NOT caused by recent user input.
8
+ * values that were NOT caused by recent user input or an explicitly marked
9
+ * transient motion surface.
9
10
  * 2. Opacity flash — an element fades out and back (or in then out), e.g. an
10
11
  * animation that re-triggers on re-render. Detected from a time series of
11
12
  * sampled opacity.
@@ -17,12 +18,16 @@
17
18
  */
18
19
  /**
19
20
  * Cumulative Layout Shift: the sum of shift values not attributable to recent
20
- * user input (matching the Web Vitals CLS definition). 0 = perfectly stable.
21
+ * user input or an explicitly marked transient motion surface. 0 = perfectly
22
+ * stable.
21
23
  */
22
24
  export function cumulativeLayoutShift(samples) {
23
25
  let cls = 0;
24
26
  for (const s of samples) {
25
- if (!s.hadRecentInput && Number.isFinite(s.value) && s.value > 0) {
27
+ if (!s.hadRecentInput &&
28
+ !s.intentional &&
29
+ Number.isFinite(s.value) &&
30
+ s.value > 0) {
26
31
  cls += s.value;
27
32
  }
28
33
  }
@@ -68,7 +73,7 @@ export function detectOpacityFlash(samples, minDelta = 0.2) {
68
73
  /** Combine shift + opacity samples into a single pass/flag verdict. */
69
74
  export function summarizeStability(shifts, opacity, budget) {
70
75
  const cls = cumulativeLayoutShift(shifts);
71
- const shiftCount = shifts.filter((s) => !s.hadRecentInput && s.value > 0).length;
76
+ const shiftCount = shifts.filter((s) => !s.hadRecentInput && !s.intentional && s.value > 0).length;
72
77
  const flashed = detectOpacityFlash(opacity, budget.flashMinDelta ?? 0.2);
73
78
  return {
74
79
  cls,
@@ -95,6 +100,26 @@ export const LAYOUT_SHIFT_OBSERVER_INIT = `
95
100
  w.__ELIZA_LAYOUT_SHIFTS__.push({
96
101
  value: entry.value,
97
102
  hadRecentInput: entry.hadRecentInput === true,
103
+ intentional: (() => {
104
+ const sources = Array.isArray(entry.sources) ? entry.sources : [];
105
+ let sawIntentional = false;
106
+ for (const source of sources) {
107
+ const node = source?.node;
108
+ const element =
109
+ node instanceof Element ? node : (node?.parentElement ?? null);
110
+ if (!element) continue;
111
+ if (
112
+ element.closest(
113
+ '[data-eliza-layout-shift-intent="transient"]',
114
+ )
115
+ ) {
116
+ sawIntentional = true;
117
+ continue;
118
+ }
119
+ return false;
120
+ }
121
+ return sawIntentional;
122
+ })(),
98
123
  });
99
124
  }
100
125
  });
@@ -0,0 +1,2 @@
1
+ export declare function runAnimationFramesImmediately(): void;
2
+ //# sourceMappingURL=run-animation-frames-immediately.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-animation-frames-immediately.d.ts","sourceRoot":"","sources":["../../../../../src/testing/run-animation-frames-immediately.ts"],"names":[],"mappings":"AAEA,wBAAgB,6BAA6B,IAAI,IAAI,CAMpD"}
@@ -0,0 +1,8 @@
1
+ import { vi } from "vitest";
2
+ export function runAnimationFramesImmediately() {
3
+ vi.spyOn(window, "requestAnimationFrame").mockImplementation((callback) => {
4
+ callback(performance.now());
5
+ return 1;
6
+ });
7
+ vi.spyOn(window, "cancelAnimationFrame").mockImplementation(() => { });
8
+ }