@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.
- package/app-navigate-view.d.ts.map +1 -1
- package/app-navigate-view.js +1 -1
- package/cloud/api-keys/ApiKeysView.d.ts +6 -3
- package/cloud/api-keys/ApiKeysView.d.ts.map +1 -1
- package/cloud/api-keys/ApiKeysView.js +21 -29
- package/cloud/api-keys/copy-api-key.d.ts +1 -8
- package/cloud/api-keys/copy-api-key.d.ts.map +1 -1
- package/cloud/api-keys/copy-api-key.js +1 -10
- package/cloud/api-keys/index.d.ts +1 -1
- package/cloud/api-keys/index.d.ts.map +1 -1
- package/cloud/api-keys/index.js +1 -1
- package/cloud/shell/CloudRouterShell.d.ts +12 -0
- package/cloud/shell/CloudRouterShell.d.ts.map +1 -1
- package/cloud/shell/CloudRouterShell.js +38 -2
- package/cloud-ui/components/data-list/api-keys-table.d.ts +1 -2
- package/cloud-ui/components/data-list/api-keys-table.d.ts.map +1 -1
- package/cloud-ui/components/data-list/api-keys-table.js +3 -9
- package/cloud-ui/components/docs/api-route-explorer-client.d.ts.map +1 -1
- package/cloud-ui/components/docs/api-route-explorer-client.js +2 -1
- package/cloud-ui/components/layout/dashboard-route-page.d.ts.map +1 -1
- package/cloud-ui/components/layout/dashboard-route-page.js +3 -1
- package/components/config-ui/config-field.js +1 -1
- package/components/pages/Springboard.d.ts.map +1 -1
- package/components/pages/Springboard.js +34 -45
- package/components/pages/SpringboardSurface.d.ts.map +1 -1
- package/components/pages/SpringboardSurface.js +116 -6
- package/components/shell/ContinuousChatOverlay.d.ts.map +1 -1
- package/components/shell/ContinuousChatOverlay.js +207 -146
- package/components/shell/HomeSpringboardSurface.d.ts.map +1 -1
- package/components/shell/HomeSpringboardSurface.js +31 -76
- package/components/shell/use-pull-gesture.d.ts +6 -0
- package/components/shell/use-pull-gesture.d.ts.map +1 -1
- package/components/shell/use-pull-gesture.js +66 -27
- package/components/shell/useShellController.d.ts.map +1 -1
- package/components/shell/useShellController.js +13 -3
- package/components/views/ViewTileImage.d.ts +8 -8
- package/components/views/ViewTileImage.d.ts.map +1 -1
- package/components/views/ViewTileImage.js +38 -9
- package/hooks/useHorizontalPager.d.ts +33 -0
- package/hooks/useHorizontalPager.d.ts.map +1 -0
- package/hooks/useHorizontalPager.js +226 -0
- package/hooks/useLayoutShiftMonitor.d.ts +2 -0
- package/hooks/useLayoutShiftMonitor.d.ts.map +1 -1
- package/hooks/useLayoutShiftMonitor.js +23 -0
- package/hooks/view-catalog.d.ts +3 -4
- package/hooks/view-catalog.d.ts.map +1 -1
- package/hooks/view-catalog.js +4 -6
- package/package.json +10 -10
- package/state/startup-telemetry.d.ts +6 -0
- package/state/startup-telemetry.d.ts.map +1 -1
- package/state/startup-telemetry.js +23 -2
- package/styles/base.css +6 -6
- package/styles/brand-gold.css +6 -5
- package/testing/layout-stability.d.ts +11 -3
- package/testing/layout-stability.d.ts.map +1 -1
- package/testing/layout-stability.js +29 -4
- package/testing/run-animation-frames-immediately.d.ts +2 -0
- package/testing/run-animation-frames-immediately.d.ts.map +1 -0
- package/testing/run-animation-frames-immediately.js +8 -0
- package/themes/presets.js +14 -14
- package/utils/cloud-agent-base.d.ts.map +1 -1
- package/utils/cloud-agent-base.js +6 -0
- package/widgets/registry.d.ts.map +1 -1
- package/widgets/registry.js +7 -0
- package/components/pages/automation-conversations.d.ts +0 -16
- package/components/pages/automation-conversations.d.ts.map +0 -1
- package/components/pages/automation-conversations.js +0 -204
- package/components/pages/automations-missing-creds.d.ts +0 -25
- package/components/pages/automations-missing-creds.d.ts.map +0 -1
- package/components/pages/automations-missing-creds.js +0 -39
- package/components/settings/desktop-workspace-audit-config.d.ts +0 -3
- package/components/settings/desktop-workspace-audit-config.d.ts.map +0 -1
- package/components/settings/desktop-workspace-audit-config.js +0 -171
- package/components/shell/first-run-theme.d.ts +0 -6
- package/components/shell/first-run-theme.d.ts.map +0 -1
- package/components/shell/first-run-theme.js +0 -31
- package/genui/use-json-render-message.d.ts +0 -20
- package/genui/use-json-render-message.d.ts.map +0 -1
- package/genui/use-json-render-message.js +0 -38
- package/state/first-run-restart.d.ts +0 -8
- package/state/first-run-restart.d.ts.map +0 -1
- 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;
|
|
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;
|
package/hooks/view-catalog.d.ts
CHANGED
|
@@ -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
|
|
37
|
-
* otherwise this is a deterministic branded
|
|
38
|
-
*
|
|
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
|
|
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"}
|
package/hooks/view-catalog.js
CHANGED
|
@@ -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
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
|
|
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.
|
|
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.
|
|
283
|
-
"@elizaos/capacitor-camera": "2.0.3-beta.
|
|
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.
|
|
288
|
-
"@elizaos/cloud-shared": "2.0.3-beta.
|
|
289
|
-
"@elizaos/core": "2.0.3-beta.
|
|
290
|
-
"@elizaos/logger": "2.0.3-beta.
|
|
291
|
-
"@elizaos/shared": "2.0.3-beta.
|
|
292
|
-
"@elizaos/tui": "2.0.3-beta.
|
|
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": "
|
|
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;
|
|
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
|
-
|
|
43
|
-
|
|
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(
|
|
88
|
-
--scrollbar-thumb-hover-mid: rgba(
|
|
89
|
-
--scrollbar-thumb-hover-end: rgba(
|
|
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,
|
|
227
|
-
--scrollbar-thumb-hover-mid: rgba(255,
|
|
228
|
-
--scrollbar-thumb-hover-end: rgba(255,
|
|
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);
|
package/styles/brand-gold.css
CHANGED
|
@@ -406,11 +406,12 @@
|
|
|
406
406
|
* literals at the component layer.
|
|
407
407
|
*/
|
|
408
408
|
:root {
|
|
409
|
-
/* On-brand memory-type cues: gold (focal) +
|
|
410
|
-
No decorative indigo/purple/green/amber; the per-type icon carries the
|
|
411
|
-
of the distinction.
|
|
412
|
-
|
|
413
|
-
--memory-type-messages-
|
|
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
|
|
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
|
|
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
|
|
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 &&
|
|
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 @@
|
|
|
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
|
+
}
|