relay-companion 0.1.403 → 0.1.404
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/npm-shrinkwrap.json +2 -2
- package/overlay/inbox.html +147 -28
- package/package.json +1 -1
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "relay-companion",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.404",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "relay-companion",
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.404",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@anthropic-ai/claude-agent-sdk": "0.3.195",
|
package/overlay/inbox.html
CHANGED
|
@@ -94,6 +94,7 @@
|
|
|
94
94
|
Elevation is three tokens: the card's own drop shadow (this one), the
|
|
95
95
|
folded pill's lighter cousin, and one popover shadow — nothing else casts. */
|
|
96
96
|
.card {
|
|
97
|
+
--relay-loop-state:paused;
|
|
97
98
|
/* The native window preserves its current screen-space top-right point while
|
|
98
99
|
it changes size. Anchor the visible sheet to that same point so a folded
|
|
99
100
|
pill never lands at the old left edge and then jumps home when AppKit
|
|
@@ -109,6 +110,7 @@
|
|
|
109
110
|
transition:box-shadow var(--t-slow) var(--soft), border-radius var(--t-slow) var(--settle);
|
|
110
111
|
animation:appIn var(--t-slow) var(--soft) both;
|
|
111
112
|
}
|
|
113
|
+
.card[data-continuous-motion="running"] { --relay-loop-state:running; }
|
|
112
114
|
.card.collapsed {
|
|
113
115
|
border-radius:var(--r-fold);
|
|
114
116
|
box-shadow:var(--shadow-pill);
|
|
@@ -499,7 +501,7 @@
|
|
|
499
501
|
mask:radial-gradient(farthest-side, #0000 calc(100% - 1.6px), #000 0);
|
|
500
502
|
opacity:0; transition:opacity var(--t-base) var(--settle); pointer-events:none;
|
|
501
503
|
}
|
|
502
|
-
.row.opening .avatar::after, .th-item.opening .avatar::after { opacity:1; animation:dotSpin .7s linear infinite; }
|
|
504
|
+
.row.opening .avatar::after, .th-item.opening .avatar::after { opacity:1; animation:dotSpin .7s linear infinite; animation-play-state:var(--relay-loop-state); }
|
|
503
505
|
@keyframes dotSpin { to { transform:rotate(1turn); } }
|
|
504
506
|
.rk-top { display:flex; align-items:baseline; gap:7px; }
|
|
505
507
|
.rk-sender { font-size:13px; font-weight:600; color:var(--ink); letter-spacing:.1px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
@@ -1253,7 +1255,7 @@
|
|
|
1253
1255
|
}
|
|
1254
1256
|
.agent-working-indicator { display:inline-flex; align-items:center; gap:3px; margin-left:7px; vertical-align:2px; }
|
|
1255
1257
|
.agent-working-dot { width:5px; height:5px; border-radius:50%; background:currentColor; opacity:.35;
|
|
1256
|
-
animation:agentWorkingPulse 1.2s var(--settle) infinite; }
|
|
1258
|
+
animation:agentWorkingPulse 1.2s var(--settle) infinite; animation-play-state:var(--relay-loop-state); }
|
|
1257
1259
|
.agent-working-dot:nth-child(2) { animation-delay:.16s; }
|
|
1258
1260
|
.agent-working-dot:nth-child(3) { animation-delay:.32s; }
|
|
1259
1261
|
@keyframes agentWorkingPulse {
|
|
@@ -1308,7 +1310,7 @@
|
|
|
1308
1310
|
.th-host-context { overflow:hidden; color:var(--muted-2); font-family:var(--sans); font-size:10px;
|
|
1309
1311
|
text-overflow:ellipsis; white-space:nowrap; }
|
|
1310
1312
|
.th-host-arrow { color:var(--host-color); font-family:var(--sans); font-size:17px; line-height:1; }
|
|
1311
|
-
.th-msg.opening .th-host-logo { animation:hostOpenBreathe .8s ease-in-out infinite alternate; }
|
|
1313
|
+
.th-msg.opening .th-host-logo { animation:hostOpenBreathe .8s ease-in-out infinite alternate; animation-play-state:var(--relay-loop-state); }
|
|
1312
1314
|
@keyframes hostOpenBreathe { to { opacity:.42; transform:scale(.94); } }
|
|
1313
1315
|
@media (hover:none), (pointer:coarse) {
|
|
1314
1316
|
.th-host-disclosure { display:flex; }
|
|
@@ -1809,7 +1811,7 @@
|
|
|
1809
1811
|
.rd-act { display:flex; gap:6px; align-items:center; min-width:0; font-family:var(--sans); font-size:14px; line-height:21px;
|
|
1810
1812
|
font-weight:445; color:color-mix(in oklab, var(--ink) 60%, transparent); }
|
|
1811
1813
|
.rd-act .act-icon { width:16px; height:16px; flex:0 0 16px; color:inherit; }
|
|
1812
|
-
.rd-act.live .act-icon { animation:rdLivePulse 1.35s ease-in-out infinite; }
|
|
1814
|
+
.rd-act.live .act-icon { animation:rdLivePulse 1.35s ease-in-out infinite; animation-play-state:var(--relay-loop-state); }
|
|
1813
1815
|
.rd-act .act-stack { display:flex; flex-direction:column; min-width:0; max-width:100%; }
|
|
1814
1816
|
.rd-act .act-copy { display:inline-flex; align-items:baseline; gap:4px; min-width:0; max-width:100%; }
|
|
1815
1817
|
.rd-act .averb { flex:none; font-weight:400; white-space:nowrap; }
|
|
@@ -1822,7 +1824,7 @@
|
|
|
1822
1824
|
.rd-live-tail { display:flex; align-items:center; gap:7px; min-height:21px; font-family:var(--sans); font-size:14px; line-height:21px;
|
|
1823
1825
|
color:color-mix(in oklab, var(--ink) 64%, transparent); }
|
|
1824
1826
|
.rd-live-tail-dot { width:6px; height:6px; flex:0 0 6px; border-radius:999px; background:var(--provider, var(--accent));
|
|
1825
|
-
animation:rdLivePulse 1.35s ease-in-out infinite; }
|
|
1827
|
+
animation:rdLivePulse 1.35s ease-in-out infinite; animation-play-state:var(--relay-loop-state); }
|
|
1826
1828
|
@keyframes rdLivePulse { 0%,100% { opacity:.34; } 50% { opacity:1; } }
|
|
1827
1829
|
.rd-said { align-self:flex-start; width:100%; font-family:var(--sans); font-size:14px; line-height:21px; font-weight:445; color:var(--ink); overflow-wrap:anywhere; }
|
|
1828
1830
|
/* Codex conversation geometry: the agent and its work own the left edge;
|
|
@@ -2130,9 +2132,12 @@
|
|
|
2130
2132
|
.tb-delete:hover { color:var(--danger); background:var(--danger-soft); }
|
|
2131
2133
|
.tb-delete:disabled { opacity:.35; cursor:default; }
|
|
2132
2134
|
.tb-working { display:inline-flex; align-items:center; color:var(--accent); font-family:var(--sans); font-size:10.5px; font-weight:500; white-space:nowrap; }
|
|
2133
|
-
.tb-working-orb { position:relative; width:6px; height:6px; flex:0 0 6px; margin-right:7px; border-radius:50%; background:var(--accent); box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 11%, transparent); animation:taskWorkingBreathe 1.8s cubic-bezier(.4,0,.2,1) infinite; }
|
|
2135
|
+
.tb-working-orb { position:relative; width:6px; height:6px; flex:0 0 6px; margin-right:7px; border-radius:50%; background:var(--accent); box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 11%, transparent); animation:taskWorkingBreathe 1.8s cubic-bezier(.4,0,.2,1) infinite; animation-play-state:var(--relay-loop-state); }
|
|
2134
2136
|
.tb-working-orb::after { content:""; position:absolute; left:1px; top:1px; width:2px; height:2px; border-radius:50%; background:color-mix(in srgb, white 70%, var(--accent)); opacity:.7; }
|
|
2135
|
-
|
|
2137
|
+
/* Keep the live cue on Chromium's compositor. Animating box-shadow repaints
|
|
2138
|
+
the transparent always-on-top window and can monopolize Electron's main /
|
|
2139
|
+
renderer IPC path even after the card folds. */
|
|
2140
|
+
@keyframes taskWorkingBreathe { 0%,100% { opacity:.62; transform:scale(.88); } 50% { opacity:1; transform:scale(1); } }
|
|
2136
2141
|
@media (prefers-reduced-motion:reduce) {
|
|
2137
2142
|
.tb-working-orb, .task-zero.celebrate .tz-tile { animation:none; opacity:1; }
|
|
2138
2143
|
.task-zero, .tz-glow, .tz-mark, .tz-tile, .tz-title, .tz-sub, .tb-completed-reveal, .tb-completed-toggle svg { transition-duration:.001s; }
|
|
@@ -2457,10 +2462,10 @@
|
|
|
2457
2462
|
.su-checks { margin:20px 0 0; display:grid; gap:9px; color:var(--muted); font-size:12px; }
|
|
2458
2463
|
.su-check { display:flex; align-items:center; gap:8px; }
|
|
2459
2464
|
.su-check-mark { color:var(--accent); font-weight:700; }
|
|
2460
|
-
.su-check-active .su-check-mark { width:13px; height:13px; border:1.5px solid var(--hair); border-top-color:var(--accent); border-radius:50%; animation:suSpin .8s linear infinite; }
|
|
2465
|
+
.su-check-active .su-check-mark { width:13px; height:13px; border:1.5px solid var(--hair); border-top-color:var(--accent); border-radius:50%; animation:suSpin .8s linear infinite; animation-play-state:var(--relay-loop-state); }
|
|
2461
2466
|
.su-note { margin-top:12px; min-height:17px; color:var(--muted); font-size:11px; line-height:1.45; }
|
|
2462
2467
|
.su-error { color:var(--danger); }
|
|
2463
|
-
.su-spinner { width:24px; height:24px; margin-bottom:14px; border:2px solid var(--hair); border-top-color:var(--accent); border-radius:50%; animation:suSpin .8s linear infinite; }
|
|
2468
|
+
.su-spinner { width:24px; height:24px; margin-bottom:14px; border:2px solid var(--hair); border-top-color:var(--accent); border-radius:50%; animation:suSpin .8s linear infinite; animation-play-state:var(--relay-loop-state); }
|
|
2464
2469
|
@keyframes suSpin { to { transform:rotate(360deg); } }
|
|
2465
2470
|
@media (prefers-reduced-motion:reduce) { .su-spinner { animation:none; border-top-color:var(--hair); } }
|
|
2466
2471
|
@media (prefers-reduced-motion:reduce) { .su-check-active .su-check-mark { animation:none; border-top-color:var(--hair); } }
|
|
@@ -2964,6 +2969,34 @@
|
|
|
2964
2969
|
|
|
2965
2970
|
// ---------- collapse / expand / peek ----------
|
|
2966
2971
|
let collapsed = false;
|
|
2972
|
+
// Folding the transparent window hides its content visually, but opacity and
|
|
2973
|
+
// pointer-events do not stop animations, frame callbacks, or run polling.
|
|
2974
|
+
// This is the single lifecycle contract for all continuous renderer work;
|
|
2975
|
+
// finite fold/open transitions deliberately remain independent.
|
|
2976
|
+
const continuousMotionListeners = new Set();
|
|
2977
|
+
let surfaceRenderDeferred = false;
|
|
2978
|
+
let surfacePaintCount = 0;
|
|
2979
|
+
function rendererSurfaceActive() {
|
|
2980
|
+
return document.visibilityState === "visible"
|
|
2981
|
+
&& !collapsed
|
|
2982
|
+
&& !cardEl.classList.contains("offstage")
|
|
2983
|
+
&& !cardEl.classList.contains("bye");
|
|
2984
|
+
}
|
|
2985
|
+
function syncContinuousMotion() {
|
|
2986
|
+
const active = rendererSurfaceActive();
|
|
2987
|
+
const next = active ? "running" : "paused";
|
|
2988
|
+
const changed = cardEl.dataset.continuousMotion !== next;
|
|
2989
|
+
cardEl.dataset.continuousMotion = next;
|
|
2990
|
+
if (changed) for (const listener of continuousMotionListeners) listener(active);
|
|
2991
|
+
return active;
|
|
2992
|
+
}
|
|
2993
|
+
function reconcileSurfaceActivity() {
|
|
2994
|
+
const active = syncContinuousMotion();
|
|
2995
|
+
if (active) flushDeferredSurfaceRender();
|
|
2996
|
+
else syncRunMirrors();
|
|
2997
|
+
}
|
|
2998
|
+
new MutationObserver(reconcileSurfaceActivity).observe(cardEl, { attributes:true, attributeFilter:["class"] });
|
|
2999
|
+
document.addEventListener("visibilitychange", reconcileSurfaceActivity);
|
|
2967
3000
|
let readerOpenNow = false; // the reader holds the card at READER size
|
|
2968
3001
|
let readerMorphInFlight = false;
|
|
2969
3002
|
let readerMorphSnapshot = null;
|
|
@@ -3171,11 +3204,17 @@
|
|
|
3171
3204
|
// One face, one transition: the count and compact controls track the same
|
|
3172
3205
|
// state from the first shrinking frame through native-window settlement.
|
|
3173
3206
|
cardEl.classList.toggle("collapsed", collapsed);
|
|
3207
|
+
syncContinuousMotion();
|
|
3174
3208
|
const openSize = readerOpenNow ? READER : EXPANDED;
|
|
3175
3209
|
springTo(collapsed ? PILL.w : openSize.w, collapsed ? PILL.h : openSize.h);
|
|
3176
3210
|
playTink();
|
|
3177
3211
|
// List rebuilds are deferred while folded; catch up now that the card is visible.
|
|
3178
|
-
if (!collapsed)
|
|
3212
|
+
if (!collapsed) {
|
|
3213
|
+
flushPendingRelaysRender();
|
|
3214
|
+
flushDeferredSurfaceRender();
|
|
3215
|
+
} else {
|
|
3216
|
+
syncRunMirrors();
|
|
3217
|
+
}
|
|
3179
3218
|
}
|
|
3180
3219
|
// Park the card invisibly at expanded size while the OS window is hidden, so the
|
|
3181
3220
|
// next appearance (tray reopen / ghost banner) starts from a clean slate.
|
|
@@ -3195,6 +3234,8 @@
|
|
|
3195
3234
|
refreshCountLabel();
|
|
3196
3235
|
cardEl.classList.add("offstage");
|
|
3197
3236
|
cardEl.classList.remove("collapsed", "ghost", "bye");
|
|
3237
|
+
syncContinuousMotion();
|
|
3238
|
+
syncRunMirrors();
|
|
3198
3239
|
snapSize(EXPANDED.w, EXPANDED.h);
|
|
3199
3240
|
}
|
|
3200
3241
|
// ✕ — the card leaves gracefully, then the main process hides the window and keeps
|
|
@@ -3234,6 +3275,8 @@
|
|
|
3234
3275
|
cardEl.classList.remove("peek", "notifying");
|
|
3235
3276
|
collapsed = true;
|
|
3236
3277
|
cardEl.classList.add("collapsed");
|
|
3278
|
+
syncContinuousMotion();
|
|
3279
|
+
syncRunMirrors();
|
|
3237
3280
|
refreshCountLabel();
|
|
3238
3281
|
springTo(PILL.w, PILL.h);
|
|
3239
3282
|
renderAll();
|
|
@@ -5879,6 +5922,11 @@
|
|
|
5879
5922
|
// Every render rebuilds the stream element, so re-attach the poll to whatever
|
|
5880
5923
|
// is on screen now and drop the ones whose card just closed.
|
|
5881
5924
|
function syncRunMirrors() {
|
|
5925
|
+
if (!rendererSurfaceActive()) {
|
|
5926
|
+
for (const id of [...runFeedTimers.keys()]) stopRunFeed(id);
|
|
5927
|
+
for (const id of [...runScrollControllers.keys()]) destroyWorkScroller(id);
|
|
5928
|
+
return;
|
|
5929
|
+
}
|
|
5882
5930
|
const open = new Set();
|
|
5883
5931
|
for (const el of document.querySelectorAll("[data-run-stream]")) {
|
|
5884
5932
|
const id = el.getAttribute("data-run-stream");
|
|
@@ -13101,6 +13149,7 @@
|
|
|
13101
13149
|
// frame target. It removes the blank-frame and double-rebuild paths while
|
|
13102
13150
|
// preserving applyView as the sole visibility writer.
|
|
13103
13151
|
const enteringView = applyView._last !== activeView;
|
|
13152
|
+
const paintSurface = rendererSurfaceActive();
|
|
13104
13153
|
const credentialRecovery = ["unavailable", "missing", "corrupt"].includes(payload.account?.credentialStatus);
|
|
13105
13154
|
const tutorialPending = !credentialRecovery && payload.account?.paired !== false && payload.ui?.setupTutorialPending === true;
|
|
13106
13155
|
if (credentialRecovery) {
|
|
@@ -13114,13 +13163,20 @@
|
|
|
13114
13163
|
cardEl.classList.toggle("signup", needsSignup);
|
|
13115
13164
|
signupViewEl?.classList.toggle("gone", !needsSignup);
|
|
13116
13165
|
if (needsSignup) {
|
|
13117
|
-
|
|
13166
|
+
if (paintSurface) {
|
|
13167
|
+
renderSignup();
|
|
13168
|
+
surfacePaintCount += 1;
|
|
13169
|
+
surfaceRenderDeferred = false;
|
|
13170
|
+
} else {
|
|
13171
|
+
surfaceRenderDeferred = true;
|
|
13172
|
+
}
|
|
13118
13173
|
syncCardSize(false);
|
|
13119
13174
|
if (!credentialRecovery && payload.account?.paired === false && !signupStateLoaded && window.relay.installationAuthState) {
|
|
13120
13175
|
signupStateLoaded = true;
|
|
13121
13176
|
window.relay.installationAuthState().then((state) => {
|
|
13122
13177
|
applyInstallationState(state);
|
|
13123
|
-
renderSignup();
|
|
13178
|
+
if (rendererSurfaceActive()) renderSignup();
|
|
13179
|
+
else surfaceRenderDeferred = true;
|
|
13124
13180
|
if (["google", "approval", "finishing"].includes(signupStage)) pollInstallationState();
|
|
13125
13181
|
}).catch(() => {});
|
|
13126
13182
|
}
|
|
@@ -13156,23 +13212,35 @@
|
|
|
13156
13212
|
markAllReadEl.classList.toggle("gone", activeView !== "relays" || unread === 0);
|
|
13157
13213
|
markAllReadEl.disabled = unread === 0;
|
|
13158
13214
|
setBadge(tasksBadgeEl, requestsUnreadCount());
|
|
13159
|
-
if (
|
|
13160
|
-
|
|
13161
|
-
|
|
13162
|
-
|
|
13163
|
-
|
|
13164
|
-
|
|
13165
|
-
|
|
13166
|
-
|
|
13167
|
-
|
|
13215
|
+
if (paintSurface) {
|
|
13216
|
+
if (activeView === "chat") renderChat();
|
|
13217
|
+
if (activeView === "relays") renderRelays();
|
|
13218
|
+
else if (activeView === "slack") renderSlack();
|
|
13219
|
+
else if (activeView === "threads") renderThreads();
|
|
13220
|
+
else if (activeView === "sent") renderSent();
|
|
13221
|
+
else if (activeView === "tasks") renderTasksBoard();
|
|
13222
|
+
else if (activeView === "reader") renderReader();
|
|
13223
|
+
else if (activeView === "requestDetail") renderRequestDetail();
|
|
13224
|
+
else if (activeView === "settings" && enteringView) renderSettings();
|
|
13225
|
+
surfacePaintCount += 1;
|
|
13226
|
+
surfaceRenderDeferred = false;
|
|
13227
|
+
} else {
|
|
13228
|
+
surfaceRenderDeferred = true;
|
|
13229
|
+
}
|
|
13168
13230
|
applyView();
|
|
13169
13231
|
// Every render rebuilds the mirror's element, so re-attach the poll to
|
|
13170
13232
|
// whatever is on screen now and drop the ones whose card just closed.
|
|
13171
13233
|
syncRunMirrors();
|
|
13172
|
-
if (activeView === "tasks" || activeView === "reader") loadSchedules();
|
|
13234
|
+
if (paintSurface && (activeView === "tasks" || activeView === "reader")) loadSchedules();
|
|
13173
13235
|
// contacts renders from its own load path
|
|
13174
13236
|
}
|
|
13175
13237
|
|
|
13238
|
+
function flushDeferredSurfaceRender() {
|
|
13239
|
+
if (!surfaceRenderDeferred || !rendererSurfaceActive()) return;
|
|
13240
|
+
surfaceRenderDeferred = false;
|
|
13241
|
+
renderAll();
|
|
13242
|
+
}
|
|
13243
|
+
|
|
13176
13244
|
markAllReadEl.addEventListener("click", async () => {
|
|
13177
13245
|
if (markAllReadEl.disabled || unreadCount === 0) return;
|
|
13178
13246
|
markAllReadEl.disabled = true;
|
|
@@ -13299,7 +13367,11 @@
|
|
|
13299
13367
|
if (window.relay.onOpenFull) window.relay.onOpenFull(() => trayOpen());
|
|
13300
13368
|
// The OS window was just re-shown; reset notification dwell state until the
|
|
13301
13369
|
// pointer enters the card again.
|
|
13302
|
-
if (window.relay.onShown) window.relay.onShown(() => {
|
|
13370
|
+
if (window.relay.onShown) window.relay.onShown(() => {
|
|
13371
|
+
interactive = false;
|
|
13372
|
+
syncContinuousMotion();
|
|
13373
|
+
flushDeferredSurfaceRender();
|
|
13374
|
+
});
|
|
13303
13375
|
if (window.relay.onOpenDone) window.relay.onOpenDone((id) => { stopOpening(id); retireOpeningNote(id); });
|
|
13304
13376
|
// Visible confirmation for "Open in current chat". Two honest states:
|
|
13305
13377
|
// waiting — staged for an idle chat; ONLY that chat's next turn can ingest
|
|
@@ -13377,7 +13449,7 @@
|
|
|
13377
13449
|
// re-pushed. Skip while the pointer is on the card (a rebuild under the pointer wipes
|
|
13378
13450
|
// hover state) and while dragging; the next tick catches up.
|
|
13379
13451
|
setInterval(() => {
|
|
13380
|
-
if (dragging || interactive) return;
|
|
13452
|
+
if (dragging || interactive || !rendererSurfaceActive()) return;
|
|
13381
13453
|
if (activeView === "relays") renderRelays();
|
|
13382
13454
|
else if (activeView === "sent") renderSent();
|
|
13383
13455
|
}, 60000);
|
|
@@ -13386,13 +13458,20 @@
|
|
|
13386
13458
|
// A frame gap this long is not "slow", it is a visible freeze: the card stops
|
|
13387
13459
|
// moving and clicks appear dead. Record what the UI was doing so the next real
|
|
13388
13460
|
// occurrence on a real machine identifies its own cause (~/.relay/perf.log).
|
|
13461
|
+
// The watcher itself must obey the surface lifecycle: an always-running rAF
|
|
13462
|
+
// keeps Chromium producing frames for a folded transparent window.
|
|
13389
13463
|
const STALL_MS = 250;
|
|
13390
13464
|
let lastRenderCostMs = 0;
|
|
13391
|
-
|
|
13392
|
-
|
|
13465
|
+
let frameWatchStats = () => ({ active:false, ticks:0 });
|
|
13466
|
+
(function installFrameWatcher() {
|
|
13467
|
+
let watchRaf = null;
|
|
13468
|
+
let last = 0;
|
|
13469
|
+
let ticks = 0;
|
|
13393
13470
|
function tick(now) {
|
|
13471
|
+
if (!rendererSurfaceActive()) { watchRaf = null; return; }
|
|
13394
13472
|
const gap = now - last;
|
|
13395
13473
|
last = now;
|
|
13474
|
+
ticks += 1;
|
|
13396
13475
|
if (gap > STALL_MS && window.relay.reportStall) {
|
|
13397
13476
|
window.relay.reportStall({
|
|
13398
13477
|
ms: gap,
|
|
@@ -13403,9 +13482,21 @@
|
|
|
13403
13482
|
lastRenderMs: lastRenderCostMs,
|
|
13404
13483
|
});
|
|
13405
13484
|
}
|
|
13406
|
-
requestAnimationFrame(tick);
|
|
13485
|
+
watchRaf = requestAnimationFrame(tick);
|
|
13407
13486
|
}
|
|
13408
|
-
|
|
13487
|
+
function setActive(active) {
|
|
13488
|
+
if (!active) {
|
|
13489
|
+
if (watchRaf !== null) cancelAnimationFrame(watchRaf);
|
|
13490
|
+
watchRaf = null;
|
|
13491
|
+
return;
|
|
13492
|
+
}
|
|
13493
|
+
if (watchRaf !== null) return;
|
|
13494
|
+
last = performance.now();
|
|
13495
|
+
watchRaf = requestAnimationFrame(tick);
|
|
13496
|
+
}
|
|
13497
|
+
continuousMotionListeners.add(setActive);
|
|
13498
|
+
frameWatchStats = () => ({ active:watchRaf !== null, ticks });
|
|
13499
|
+
setActive(syncContinuousMotion());
|
|
13409
13500
|
})();
|
|
13410
13501
|
// Time the list rebuild itself, so a stall report says whether rendering caused it.
|
|
13411
13502
|
const __renderRelays = renderRelays;
|
|
@@ -13423,6 +13514,34 @@
|
|
|
13423
13514
|
// schema is deliberately allowed to migrate. Install this only after the
|
|
13424
13515
|
// complete renderer has initialized, and never expose it in production.
|
|
13425
13516
|
if (window.relay.isTestOverlay) {
|
|
13517
|
+
window.__relayMotionTest = {
|
|
13518
|
+
state: () => {
|
|
13519
|
+
const loopAnimations = document.getAnimations({ subtree:true }).filter((animation) => {
|
|
13520
|
+
try { return animation.effect && animation.effect.getTiming().iterations === Infinity; }
|
|
13521
|
+
catch { return false; }
|
|
13522
|
+
});
|
|
13523
|
+
return {
|
|
13524
|
+
active:rendererSurfaceActive(),
|
|
13525
|
+
motion:cardEl.dataset.continuousMotion,
|
|
13526
|
+
collapsed,
|
|
13527
|
+
activeView,
|
|
13528
|
+
deferred:surfaceRenderDeferred,
|
|
13529
|
+
surfacePaintCount,
|
|
13530
|
+
frameWatch:frameWatchStats(),
|
|
13531
|
+
loopAnimations:loopAnimations.map((animation) => animation.playState),
|
|
13532
|
+
};
|
|
13533
|
+
},
|
|
13534
|
+
setCollapsed: (value) => { setCollapsed(Boolean(value)); return collapsed; },
|
|
13535
|
+
setView: (view) => {
|
|
13536
|
+
activeView = String(view || "relays");
|
|
13537
|
+
commitNavigation();
|
|
13538
|
+
return activeView;
|
|
13539
|
+
},
|
|
13540
|
+
render: (count = 1) => {
|
|
13541
|
+
for (let i = 0; i < Math.max(1, Number(count) || 1); i += 1) renderAll();
|
|
13542
|
+
return surfacePaintCount;
|
|
13543
|
+
},
|
|
13544
|
+
};
|
|
13426
13545
|
window.__relayParityOpenRequest = (fixture) => {
|
|
13427
13546
|
if (!fixture?.id) throw new Error("A Task fixture id is required");
|
|
13428
13547
|
payload.relays = [...(payload.relays || []).filter((row) => row.id !== fixture.id), fixture];
|
package/package.json
CHANGED