@songsid/agend 2.3.0-beta.8 → 2.3.0-beta.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-cli-instructions.md +2 -1
- package/dist/agent-cli.js +3 -0
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-whats-new.js +2 -0
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/channel/mcp-tools.js +2 -2
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/decision-list.d.ts +25 -0
- package/dist/decision-list.js +30 -0
- package/dist/decision-list.js.map +1 -0
- package/dist/fleet-manager.d.ts +9 -2
- package/dist/fleet-manager.js +44 -12
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-traffic.d.ts +36 -0
- package/dist/fleet-traffic.js +100 -0
- package/dist/fleet-traffic.js.map +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +4 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +17 -5
- package/dist/outbound-schemas.d.ts +6 -4
- package/dist/outbound-schemas.js +6 -2
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/tool-permissions.js +9 -42
- package/dist/tool-permissions.js.map +1 -1
- package/dist/ui/app-effects.js +4 -0
- package/dist/ui/chat-effects.js +5 -4
- package/dist/ui/chat-render.js +5 -2
- package/dist/ui/chat-strings.js +2 -2
- package/dist/ui/chat-thread.js +133 -16
- package/dist/ui/effect-trial-controller.js +13 -8
- package/dist/ui/fleet-canvas.js +54 -18
- package/dist/ui/fleet-org.js +3 -3
- package/dist/ui/fleet-traffic.js +156 -37
- package/dist/ui/panel-chat.js +94 -45
- package/dist/ui/shared/agend-auth.js +1 -1
- package/dist/ui/shared/app-stream.js +3 -2
- package/dist/ui/shared/app.css +22 -9
- package/dist/ui/shared/effect-policy.js +2 -2
- package/dist/ui/shared/effect-registry.js +16 -7
- package/dist/ui/shared/effect-stock-snapshot.js +9 -0
- package/dist/ui/shared/effect-stock-trust.js +35 -4
- package/dist/ui/shared/effect-trial-source.js +1 -1
- package/dist/ui/shared/effect-trial-stage.js +27 -21
- package/dist/ui/shared/effect-trial.css +1 -1
- package/dist/ui/shared/tokens.css +9 -3
- package/dist/ui/web-terminal/terminal.css +4 -4
- package/dist/web-ambient.js +9 -2
- package/dist/web-ambient.js.map +1 -1
- package/dist/web-api.d.ts +1 -0
- package/dist/web-api.js +1 -1
- package/dist/web-api.js.map +1 -1
- package/dist/workflow-templates/default.md +1 -1
- package/package.json +1 -1
package/dist/ui/panel-chat.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// The instance's own actions (details, start, stop, restart, delete) are in the header's ⋯ menu, and only the ones
|
|
8
8
|
// that apply to its state are offered (#1408 rough edges 4 and 7).
|
|
9
9
|
import { html, useCallback, useEffect, useLayoutEffect, useRef, useState } from "/assets/app-html.js";
|
|
10
|
-
import { t } from "/assets/app-i18n.js";
|
|
10
|
+
import { lang, t } from "/assets/app-i18n.js";
|
|
11
11
|
import { appStore, guestPage, useStore } from "/assets/app-store.js";
|
|
12
12
|
import { AuthorizeChip } from "/assets/web-authorize.js";
|
|
13
13
|
import { useLease } from "/assets/app-ctx.js";
|
|
@@ -317,66 +317,115 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
|
|
|
317
317
|
</div>`;
|
|
318
318
|
}
|
|
319
319
|
|
|
320
|
-
/**
|
|
320
|
+
/**
|
|
321
|
+
* #2053: the last THREAD_KEEP chats' threads are kept when their view leaves (the chat view is keyed by instance, so
|
|
322
|
+
* each switch built the thread again: a revisit was no faster than a first visit). A kept thread is its list element
|
|
323
|
+
* and its renderer, and nothing more: it leaves as a chat always did (previews stop), with no subscription or timer
|
|
324
|
+
* left; shown again, one render draws only what changed meanwhile, where the reader was. A new language, another
|
|
325
|
+
* document, or more than THREAD_KEEP lets one go.
|
|
326
|
+
*/
|
|
327
|
+
export const THREAD_KEEP = 4;
|
|
328
|
+
const keptThreads = new Map(); // name → { thread, list, sink, lang }
|
|
329
|
+
/** Tests only: let every kept thread go. */
|
|
330
|
+
export function forgetKeptThreads() { for (const e of keptThreads.values()) letGo(e, true); keptThreads.clear(); }
|
|
331
|
+
function letGo(e, own) { e.thread.dispose(own); e.list.remove(); }
|
|
332
|
+
|
|
333
|
+
/** A chat's thread: its own list element and renderer, and a sink for the view that shows it. */
|
|
334
|
+
function threadEntry(name, doc) {
|
|
335
|
+
const list = doc.createElement("div");
|
|
336
|
+
list.className = "thread";
|
|
337
|
+
list.setAttribute("role", "log");
|
|
338
|
+
list.setAttribute("aria-live", "off");
|
|
339
|
+
list.setAttribute("aria-label", t("chat.conversationWith", name));
|
|
340
|
+
const e = { list, sink: null, lang: lang(), thread: null };
|
|
341
|
+
const thread = createThread(list, doc.createElement("div"), {
|
|
342
|
+
t, tf: t, isUser: (x) => store.isUser(x, name), onJump: (j) => e.sink?.setJump(j), onEmpty: (v) => e.sink?.setEmpty(v),
|
|
343
|
+
setPreviewOptIn, copyText, download: downloadHtml, toggleWrap,
|
|
344
|
+
effectTrialStatus: x => getAppEffects()?.trialStatus(x) ?? "localAdmin",
|
|
345
|
+
effectTrialFailure: x => getAppEffects()?.trialFailure(x),
|
|
346
|
+
fixEffectTrial: (x, reason) => {
|
|
347
|
+
if (reason === "reducedMotion") setFollowsReducedMotion(false);
|
|
348
|
+
else if (reason === "busy") getAppEffects()?.stopTrial();
|
|
349
|
+
else if (reason === "transport") location.reload();
|
|
350
|
+
else if (reason === "localAdmin") location.href = "/signin?next=" + encodeURIComponent(location.pathname + location.search);
|
|
351
|
+
else navigate("/settings/appearance");
|
|
352
|
+
},
|
|
353
|
+
canTryEffect: x => getAppEffects()?.canTry(x) === true,
|
|
354
|
+
startEffectTrial: x => { const seq = navStore.get().seq; void getAppEffects()?.startTrial(x).then(ok => { if (!ok && e.sink && navStore.get().seq === seq && document.hasFocus?.() === true && document.visibilityState === "visible") toast(t("chat.effectTrialUnavailable")); }); },
|
|
355
|
+
clickReplyButton: (id, index) => store.clickReplyButton(name, id, index), // #1266
|
|
356
|
+
replyButtonBusy: (id) => store.state.rbBusy.has(id),
|
|
357
|
+
pollMine: (id) => store.state.pollMine[id], // #1823: this device's own vote
|
|
358
|
+
serverNow: () => Date.now() + store.state.clockSkew, // #1823: the deadline is the server's
|
|
359
|
+
panel: { shown: shownKey, open: (spec, o) => openPanel(spec, o), version: panelVersion, claim: claimPanel, claims: panelClaims }, // #1481, #1704
|
|
360
|
+
openImage: openLightbox, // an image, full size, in the page
|
|
361
|
+
emojis: (id) => !!emojiIndex && emojiIndex.known(id), // #1630: the fleet's server emoji
|
|
362
|
+
mentions: (kind, id) => (mentionIndex ? mentionIndex.name(kind, id) : null), // #1682: mention names
|
|
363
|
+
});
|
|
364
|
+
e.thread = thread;
|
|
365
|
+
return e;
|
|
366
|
+
}
|
|
367
|
+
/** While a thread is shown: what it follows. Returns what lets it all go. */
|
|
368
|
+
function follow(name, thread) {
|
|
369
|
+
// #1630: the server-emoji list is read once a message shows a tag; when it arrives, those messages are drawn again.
|
|
370
|
+
// #1682: the same for mention names — asked for the ids the messages carry; when names arrive, drawn again.
|
|
371
|
+
const wantEmojis = (msgs) => { if (emojiIndex && emojiIndex.wants(msgs)) emojiIndex.ensure(); if (mentionIndex) mentionIndex.ensure(msgs); };
|
|
372
|
+
const offs = [];
|
|
373
|
+
if (emojiIndex) offs.push(emojiIndex.subscribe(() => thread.render(store.state.msgs[name] || [])));
|
|
374
|
+
if (mentionIndex) offs.push(mentionIndex.subscribe(() => thread.render(store.state.msgs[name] || [])));
|
|
375
|
+
wantEmojis(store.state.msgs[name]);
|
|
376
|
+
offs.push(store.subscribe((instance, kind) => {
|
|
377
|
+
if (instance !== name) return;
|
|
378
|
+
if (kind === "msgs") { thread.render(store.state.msgs[name] || []); wantEmojis(store.state.msgs[name]); }
|
|
379
|
+
else if (kind === "sent") thread.jumpLatest();
|
|
380
|
+
}));
|
|
381
|
+
offs.push(P() ? P().onChange(() => thread.refreshCards()) : () => {});
|
|
382
|
+
offs.push(getAppEffects()?.subscribeTrial(() => thread.render(store.state.msgs[name] || [])) ?? (() => {}));
|
|
383
|
+
offs.push(panelStore.subscribe(() => thread.refreshCards())); // a card shows whether the panel has its block
|
|
384
|
+
return () => { for (const off of offs) off(); };
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** The thread's view: Preact owns the scroller and the jump pill; the kept list (chat-thread.js) is put inside it. */
|
|
321
388
|
function Thread({ name, th }) {
|
|
322
|
-
const scroller = useRef(null),
|
|
389
|
+
const scroller = useRef(null), host = useRef(null);
|
|
323
390
|
const [jump, setJump] = useState({ show: false, unseen: 0 });
|
|
324
391
|
const [empty, setEmpty] = useState(false);
|
|
325
392
|
useLayoutEffect(() => {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
pollMine: (id) => store.state.pollMine[id], // #1823: this device's own vote
|
|
343
|
-
serverNow: () => Date.now() + store.state.clockSkew, // #1823: the deadline is the server's
|
|
344
|
-
panel: { shown: shownKey, open: (spec, o) => openPanel(spec, o), version: panelVersion, claim: claimPanel, claims: panelClaims }, // #1481, #1704
|
|
345
|
-
openImage: openLightbox, // an image, full size, in the page
|
|
346
|
-
emojis: (id) => !!emojiIndex && emojiIndex.known(id), // #1630: the fleet's server emoji
|
|
347
|
-
mentions: (kind, id) => (mentionIndex ? mentionIndex.name(kind, id) : null), // #1682: mention names
|
|
348
|
-
});
|
|
393
|
+
const doc = host.current.ownerDocument;
|
|
394
|
+
let e = keptThreads.get(name);
|
|
395
|
+
// Kept only while it can be shown as it is: the same language, and this page's own document.
|
|
396
|
+
if (e && (e.lang !== lang() || e.list.ownerDocument !== doc)) { letGo(e, true); keptThreads.delete(name); e = undefined; }
|
|
397
|
+
const kept = !!e;
|
|
398
|
+
if (e) keptThreads.delete(name);
|
|
399
|
+
else e = threadEntry(name, doc);
|
|
400
|
+
keptThreads.set(name, e); // the most recent last
|
|
401
|
+
for (const [k, old] of keptThreads) {
|
|
402
|
+
if (keptThreads.size <= THREAD_KEEP) break;
|
|
403
|
+
if (k !== name) { letGo(old, true); keptThreads.delete(k); }
|
|
404
|
+
}
|
|
405
|
+
const thread = e.thread, sc = scroller.current, ls = e.list;
|
|
406
|
+
e.sink = { setJump, setEmpty };
|
|
407
|
+
host.current.append(ls);
|
|
408
|
+
thread.attach(sc);
|
|
349
409
|
th.current = thread;
|
|
350
|
-
//
|
|
351
|
-
// #1682: the same for mention names — asked for the ids the messages carry; when names arrive, drawn again.
|
|
352
|
-
const wantEmojis = (msgs) => { if (emojiIndex && emojiIndex.wants(msgs)) emojiIndex.ensure(); if (mentionIndex) mentionIndex.ensure(msgs); };
|
|
353
|
-
const offEmojis = emojiIndex ? emojiIndex.subscribe(() => thread.render(store.state.msgs[name] || [])) : () => {};
|
|
354
|
-
const offMentions = mentionIndex ? mentionIndex.subscribe(() => thread.render(store.state.msgs[name] || [])) : () => {};
|
|
410
|
+
// Up to date, at the reader's place (the bottom unless they had scrolled up); a kept one's cards say what they can do.
|
|
355
411
|
thread.render(store.state.msgs[name] || [], { restore: store.state.scrollMemo[name] ?? null });
|
|
356
|
-
|
|
357
|
-
const
|
|
358
|
-
if (instance !== name) return;
|
|
359
|
-
if (kind === "msgs") { thread.render(store.state.msgs[name] || []); wantEmojis(store.state.msgs[name]); }
|
|
360
|
-
else if (kind === "sent") thread.jumpLatest();
|
|
361
|
-
});
|
|
362
|
-
const offPv = P() ? P().onChange(() => thread.refreshCards()) : () => {};
|
|
363
|
-
const offTrials = getAppEffects()?.subscribeTrial(() => thread.render(store.state.msgs[name] || [])) ?? (() => {});
|
|
364
|
-
const offPanel = panelStore.subscribe(() => thread.refreshCards()); // a card shows whether the panel has its block
|
|
412
|
+
if (kept) thread.refreshCards();
|
|
413
|
+
const unfollow = follow(name, thread);
|
|
365
414
|
const onScroll = () => { store.state.scrollMemo[name] = thread.onScroll(); };
|
|
366
|
-
const sc = scroller.current, ls = list.current;
|
|
367
415
|
sc.addEventListener("scroll", onScroll, { passive: true });
|
|
368
416
|
ls.addEventListener("click", thread.onClick);
|
|
369
417
|
return () => {
|
|
370
|
-
|
|
371
|
-
off(); offPv(); offPanel(); offTrials(); offEmojis(); offMentions();
|
|
418
|
+
unfollow();
|
|
372
419
|
sc.removeEventListener("scroll", onScroll);
|
|
373
420
|
ls.removeEventListener("click", thread.onClick);
|
|
374
|
-
|
|
421
|
+
e.sink = null;
|
|
375
422
|
th.current = null;
|
|
423
|
+
if (keptThreads.get(name) === e) { thread.park(); ls.remove(); } // kept for a later visit: the chat left
|
|
424
|
+
else letGo(e, false); // let go meanwhile (a new language)
|
|
376
425
|
};
|
|
377
426
|
}, [name]);
|
|
378
427
|
return html`<div class="scroller" ref=${scroller}>
|
|
379
|
-
<div class="thread" ref=${
|
|
428
|
+
<div class="thread-host" ref=${host}></div>
|
|
380
429
|
${empty ? html`<${Empty} icon="chat" title=${t("chat.noMessages")} />` : null}
|
|
381
430
|
<div class="jump-dock">${jump.show ? html`<button data-trust="thread-control" type="button" class="jump-latest" onClick=${() => th.current && th.current.jumpLatest()}>
|
|
382
431
|
<${Icon} name="down" size=${16} />${jump.unseen > 0 ? t("chat.newBelow", jump.unseen) : t("chat.jumpLatest")}</button>` : null}</div>
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
bar.setAttribute("role", "alert");
|
|
68
68
|
bar.setAttribute("data-trust", "session-ended");
|
|
69
69
|
bar.className = "agend-session-ended";
|
|
70
|
-
bar.style.cssText = "position:fixed;top:0;left:0;right:0;z-index:2147483647;padding:10px 16px;background:#f85149;color:#fff;font:14px system-ui,sans-serif;text-align:center";
|
|
70
|
+
bar.style.cssText = "position:fixed;top:0;left:0;right:0;z-index:2147483647;padding:10px 16px;background:#f85149;color:#fff;font:14px system-ui,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",\"Segoe UI Symbol\",\"Noto Color Emoji\",emoji;text-align:center";
|
|
71
71
|
const zh = /^zh/i.test(navigator.language || "");
|
|
72
72
|
bar.append(document.createTextNode(zh ? "登入已結束。" : "Your session has ended. "));
|
|
73
73
|
const link = document.createElement("a");
|
|
@@ -30,8 +30,9 @@
|
|
|
30
30
|
// - suspend()/resume(): the page is leaving (pagehide) / came back from the back-forward cache.
|
|
31
31
|
|
|
32
32
|
// #1218: steps_tick — an instance's transcript moved while a Log watches it (SSE only: a poll page asks the Log itself).
|
|
33
|
-
// #1918: traffic — a message between instances moved
|
|
34
|
-
|
|
33
|
+
// #1918: traffic — a message between instances moved; delegation — a task opened, or its report closed it (SSE only: the
|
|
34
|
+
// org canvas reads /ui/traffic when it has no stream).
|
|
35
|
+
const EVENTS = ["status", "message", "agent_reply_live", "delivery", "deliveries", "prompt", "prompts", "prompt_resolved", "activity", "needs", "reply_buttons", "steps_tick", "clock", "traffic", "delegation"];
|
|
35
36
|
const MAX_HELD = 1000; // live events held during one catch-up read; more → read again afterwards
|
|
36
37
|
const RETRY_DELAYS = [1000, 2000, 4000]; // after the first failed catch-up read; then it waits for Retry
|
|
37
38
|
const POLL_LIMIT_MS = 10000; // a poll that has not answered by then counts as failed
|
package/dist/ui/shared/app.css
CHANGED
|
@@ -453,6 +453,9 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
|
|
|
453
453
|
.msg.status-line .st-more { border: 0; background: none; color: var(--text-2); cursor: pointer; font-size: var(--fs-xs); padding: var(--s-1) 0 0 var(--s-3); }
|
|
454
454
|
.msg.status-line .st-more:hover { color: var(--text); }
|
|
455
455
|
.msg-tools { display: flex; gap: var(--s-1); margin-top: var(--s-1); opacity: 0; transition: opacity var(--t-fast); }
|
|
456
|
+
/* #2147: bounded stock toolbar paint, including fixed descendants. Padding preserves focus outlines. */
|
|
457
|
+
.shell .thread .msg-tools { contain: paint; overflow: clip; overflow-clip-margin: 0px; padding: 4px; margin: 0 -4px -4px; }
|
|
458
|
+
.shell .thread .codeblock > .cb-head { contain: paint; overflow: clip; overflow-clip-margin: 0px; }
|
|
456
459
|
.msg:hover .msg-tools, .msg:focus-within .msg-tools { opacity: 1; }
|
|
457
460
|
@media (hover: none) { .msg-tools { opacity: 1; } }
|
|
458
461
|
/* Delivery ticks: always visible, in colour, with their label on hover and for screen readers (rough edge 10). */
|
|
@@ -559,7 +562,7 @@ html.lb-open .scroller { overflow: hidden; }
|
|
|
559
562
|
these tokens. The surface paints its own background (the page's body is not its). */
|
|
560
563
|
.p-chat.th { background: var(--bg); color: var(--text); }
|
|
561
564
|
.p-chat.th .msg.user .body { border-radius: var(--bubble-radius, var(--r-xl)); }
|
|
562
|
-
.p-chat.th-font-system .thread, .p-chat.th-font-system .composer textarea { font-family: system-ui, -apple-system, "Segoe UI", sans-serif; }
|
|
565
|
+
.p-chat.th-font-system .thread, .p-chat.th-font-system .composer textarea { font-family: system-ui, -apple-system, "Segoe UI", sans-serif, var(--font-emoji); }
|
|
563
566
|
.p-chat.th-font-mono .thread, .p-chat.th-font-mono .composer textarea { font-family: var(--font-mono); }
|
|
564
567
|
.p-chat.th-bubble-thin .msg.user .body { box-shadow: inset 0 0 0 1px var(--border); }
|
|
565
568
|
.p-chat.th-bubble-accent .msg.user .body { box-shadow: inset 0 0 0 1px var(--accent); }
|
|
@@ -1217,7 +1220,7 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
|
|
|
1217
1220
|
.oc-team-name { fill: var(--text); font-size: 13px; font-weight: 600; }
|
|
1218
1221
|
.oc-team-meta { fill: var(--text-3); font-size: 11px; }
|
|
1219
1222
|
.oc-team-sum { fill: var(--text-2); font-size: 11.5px; }
|
|
1220
|
-
.oc-team.dim { opacity: 0.55; }
|
|
1223
|
+
.oc-team.dim, .oc-team-head.dim { opacity: 0.55; }
|
|
1221
1224
|
.oc-node.dim { opacity: 0.4; }
|
|
1222
1225
|
.oc-node { cursor: pointer; }
|
|
1223
1226
|
.oc-node[role="img"] { cursor: default; }
|
|
@@ -1263,7 +1266,11 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
|
|
|
1263
1266
|
.oc-flow.h-broadcast { stroke: var(--fx-broadcast); } .oc-flow.h-team { stroke: var(--fx-team); stroke-width: 3.5; }
|
|
1264
1267
|
.oc-flow.ph-live { stroke-dasharray: 7 6; animation: oc-dash 0.9s linear infinite; }
|
|
1265
1268
|
.oc-flow.ph-queued { stroke-dasharray: 2 5; opacity: 0.8; }
|
|
1266
|
-
.oc-flow.ph-fading { stroke: var(--text-3); animation: oc-fade
|
|
1269
|
+
.oc-flow.ph-fading { stroke: var(--text-3); animation: oc-fade 12s ease-out forwards; }
|
|
1270
|
+
/* #1918 review: the work — a delegation in progress (a slow dash, kept until its report), its report coming back, none. */
|
|
1271
|
+
.oc-flow.ph-work { stroke-dasharray: 10 7; stroke-width: 3; animation: oc-dash 2.4s linear infinite; }
|
|
1272
|
+
.oc-flow.ph-return { stroke-width: 3; animation: oc-fade 2.4s ease-out forwards; }
|
|
1273
|
+
.oc-flow.ph-stale { stroke: var(--text-3); stroke-dasharray: 3 6; animation: oc-fade 12s ease-out forwards; }
|
|
1267
1274
|
.oc-ah.h-task { fill: var(--fx-task); } .oc-ah.h-send { fill: var(--fx-send); } .oc-ah.h-report { fill: var(--fx-report); }
|
|
1268
1275
|
.oc-ah.h-broadcast { fill: var(--fx-broadcast); } .oc-ah.h-team { fill: var(--fx-team); } .oc-ah.h-fading { fill: var(--text-3); }
|
|
1269
1276
|
.oc-pulse.h-task { fill: var(--fx-task); } .oc-pulse.h-send { fill: var(--fx-send); } .oc-pulse.h-report { fill: var(--fx-report); }
|
|
@@ -1272,16 +1279,19 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
|
|
|
1272
1279
|
.oc-badge rect { fill: var(--surface-2); stroke: var(--border); } .oc-badge text { fill: var(--text-2); font-size: 10.5px; }
|
|
1273
1280
|
@keyframes oc-dash { to { stroke-dashoffset: -26; } }
|
|
1274
1281
|
@keyframes oc-fade { from { opacity: 0.8; } to { opacity: 0.15; } }
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
.oc-
|
|
1282
|
+
/* #2052: the key is a strip under the toolbar (its toggle is in the toolbar), never over the canvas. */
|
|
1283
|
+
.oc-legend { margin: 0 0 var(--s-2); font-size: var(--fs-xs); color: var(--text-2); background: var(--surface); border: 1px solid var(--border);
|
|
1284
|
+
border-radius: var(--r-md); padding: 6px 10px; }
|
|
1285
|
+
.oc-key.on { border-color: var(--accent); }
|
|
1279
1286
|
.oc-lg-rows { display: flex; flex-wrap: wrap; gap: var(--s-1) var(--s-3); }
|
|
1280
1287
|
.oc-lg { display: inline-flex; align-items: center; gap: 6px; }
|
|
1281
1288
|
.oc-sw { width: 18px; height: 3px; border-radius: 2px; display: inline-block; background: var(--text-3); }
|
|
1282
1289
|
.oc-sw.h-task { background: var(--fx-task); } .oc-sw.h-send { background: var(--fx-send); } .oc-sw.h-report { background: var(--fx-report); } .oc-sw.h-broadcast { background: var(--fx-broadcast); }
|
|
1283
1290
|
.oc-sw.ph-queued { background: repeating-linear-gradient(90deg, var(--text-2) 0 3px, transparent 3px 7px); }
|
|
1284
1291
|
.oc-sw.ph-fading { opacity: 0.5; }
|
|
1292
|
+
.oc-sw.ph-work { background: repeating-linear-gradient(90deg, var(--fx-task) 0 6px, transparent 6px 9px); height: 4px; }
|
|
1293
|
+
.oc-sw.ph-return { height: 4px; }
|
|
1294
|
+
.oc-sw.ph-stale { background: repeating-linear-gradient(90deg, var(--text-3) 0 2px, transparent 2px 6px); }
|
|
1285
1295
|
.oc-live { gap: 6px; }
|
|
1286
1296
|
.oc-live-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--text-3); }
|
|
1287
1297
|
.oc-live.on .oc-live-dot { background: var(--ok); }
|
|
@@ -1291,14 +1301,16 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
|
|
|
1291
1301
|
@media (prefers-reduced-motion: reduce) {
|
|
1292
1302
|
.oc-flow.ph-live { animation: none; stroke-dasharray: none; }
|
|
1293
1303
|
.oc-flow.ph-fading { animation: none; opacity: 0.4; }
|
|
1304
|
+
.oc-flow.ph-work { animation: none; }
|
|
1305
|
+
.oc-flow.ph-return, .oc-flow.ph-stale { animation: none; opacity: 0.5; }
|
|
1294
1306
|
.oc-pulse { display: none; }
|
|
1295
1307
|
}
|
|
1296
1308
|
@media (max-width: 640px) {
|
|
1297
1309
|
.oc-stage { height: calc(100dvh - 248px); min-height: 340px; }
|
|
1298
1310
|
.oc-find { flex: 1 1 150px; }
|
|
1299
|
-
|
|
1311
|
+
/* #2052: Find, Key, Live and Canvas/List are wider than a phone together: the row wraps (nothing is cut off). */
|
|
1312
|
+
.oc-tools { margin-left: 0; width: 100%; flex-wrap: wrap; row-gap: var(--s-2); }
|
|
1300
1313
|
.oc-mini { display: none; }
|
|
1301
|
-
.oc-legend { max-width: calc(100% - 24px); gap: 2px var(--s-2); }
|
|
1302
1314
|
}
|
|
1303
1315
|
/* A phone: Fleet's five tabs fit when only the open one shows its label (the others keep theirs for screen readers). */
|
|
1304
1316
|
@media (max-width: 479px) {
|
|
@@ -1440,6 +1452,7 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
|
|
|
1440
1452
|
}
|
|
1441
1453
|
/* #1630: a Discord server emoji in a chat message, the size of the text around it. */
|
|
1442
1454
|
.md img.emoji { display: inline-block; width: 1.375em; height: 1.375em; object-fit: contain; vertical-align: -0.3em; margin: 0 0.05em; }
|
|
1455
|
+
.md .emoji-text { color: var(--text-2); }
|
|
1443
1456
|
/* #1682: a mention, as Discord draws one — accent text on a tint of it; the id (or the Telegram handle) on hover. */
|
|
1444
1457
|
.md .mention { display: inline; padding: 0 0.2em; border-radius: var(--r-sm); color: var(--accent); background: var(--accent-dim);
|
|
1445
1458
|
font-weight: var(--fw-medium); white-space: nowrap; }
|
|
@@ -77,9 +77,9 @@ export function allocateEffectFrames(descriptors, context) {
|
|
|
77
77
|
if (!Array.isArray(descriptors) || descriptors.length > 128 || !plain(context)) return [];
|
|
78
78
|
const keys = new Set(), duplicate = new Set();
|
|
79
79
|
for (const d of descriptors) if (d && typeof d.key === "string") { if (keys.has(d.key)) duplicate.add(d.key); keys.add(d.key); }
|
|
80
|
-
const result = descriptors.map(d => ({ key: d?.key, verdict: "off", reason: "
|
|
80
|
+
const result = descriptors.map(d => ({ key: d?.key, verdict: "off", reason: "descriptor_invalid", queuePos: null }));
|
|
81
81
|
const gates = ["safe", "publicLink", "reducedMotion", "hidden", "enabled", "show", "transportReady", "constrained", "modal"];
|
|
82
|
-
if (gates.some(k => typeof context[k] !== "boolean") || context.openInstance !== null && !instanceName(context.openInstance)) return result;
|
|
82
|
+
if (gates.some(k => typeof context[k] !== "boolean") || context.openInstance !== null && !instanceName(context.openInstance)) return result.map(r => ({ ...r, reason: "gates_unknown" }));
|
|
83
83
|
let global = null;
|
|
84
84
|
if (context.safe === true) global = "safe_mode";
|
|
85
85
|
else if (context.reducedMotion === true) global = "reduced_motion";
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { allocateEffectFrames, effectEvent, effectPlaybackReason } from "./effect-policy.js";
|
|
3
3
|
import { createOpaqueEffectFrame } from "./effect-frame.js";
|
|
4
4
|
import { createEffectGeometry, hostEffectRect } from "./effect-geometry.js";
|
|
5
|
-
import { effectStockSnapshot, effectStockAllows } from "./effect-stock-snapshot.js";
|
|
5
|
+
import { effectStockSnapshot, effectStockAllows, effectStockRefusal } from "./effect-stock-snapshot.js";
|
|
6
6
|
import { effectTrialStage, effectTrialStageIdentity, watchEffectTrialStage } from "./effect-trial-stage.js";
|
|
7
7
|
import { EffectBudget } from "./effect-profiler.js";
|
|
8
8
|
|
|
@@ -42,7 +42,7 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
|
|
|
42
42
|
try { if (!host?.document || !host.document.body || registries.has(host) || typeof load !== "function" || typeof stockSnapshot !== "function" || typeof onChange !== "function" || typeof onProfile !== "function" || typeof onMount !== "function" || typeof onRetire !== "function" || typeof clock !== "function" || !safety || ["allowed", "begin", "finish", "pause", "resume"].some(k => typeof safety[k] !== "function") || ["requestAnimationFrame", "cancelAnimationFrame", "setTimeout", "clearTimeout", "MutationObserver", "ResizeObserver", "AbortController"].some(k => typeof host[k] !== "function")) return { ok: false, reason: "registry_unsupported" }; } catch { return { ok: false, reason: "registry_unsupported" }; }
|
|
43
43
|
let dead = false, blocked = null, revision = 0, last = null, lastView = null, frameSerial = 0, context = null, descriptors = [], raf = null, timer = null;
|
|
44
44
|
const slots = new Map(), loads = new Set(), paused = new Map(), offs = [], observed = new Set(), failures = new Map(), results = new Map();
|
|
45
|
-
let readingStock = false;
|
|
45
|
+
let readingStock = false, stockFailure = "stock_evidence_invalid", warned = null;
|
|
46
46
|
let root, geometry, observer, resize, longTasks = null, longTasksSupported = false;
|
|
47
47
|
const time = () => {
|
|
48
48
|
let n; try { n = clock(); } catch { return null; }
|
|
@@ -68,10 +68,14 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
|
|
|
68
68
|
const coverage = (d = null) => {
|
|
69
69
|
if (dead || blocked || readingStock) return null;
|
|
70
70
|
const before = revision; let proof = null; readingStock = true;
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
let failure = "stock_provider_failed";
|
|
72
|
+
try { const raw = stockSnapshot(Object.freeze({ frameRoot: root })); proof = effectStockSnapshot(raw); failure = proof ? null : effectStockRefusal(raw); }
|
|
73
|
+
catch { /* unavailable stays denied, with a local diagnostic */ }
|
|
73
74
|
finally { readingStock = false; }
|
|
74
75
|
if (dead || blocked || before !== revision) return null;
|
|
76
|
+
stockFailure = failure;
|
|
77
|
+
if (failure !== warned) { warned = failure; if (failure) { try { host.console?.warn("[AgEnD effects] Playback blocked:", failure); } catch { /* display only */ } } }
|
|
78
|
+
if (dead || blocked || before !== revision) return null;
|
|
75
79
|
return d === null || permits(d, proof) ? proof : null;
|
|
76
80
|
};
|
|
77
81
|
const position = (d, proof) => d.trialStage ? localStage(d, proof)?.rect ?? null : hostEffectRect(d.element);
|
|
@@ -150,7 +154,7 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
|
|
|
150
154
|
const shown = why => (why === "gates_unknown" && !eligible() && gateReason()) || why;
|
|
151
155
|
function view() {
|
|
152
156
|
return descriptors.map(d => {
|
|
153
|
-
const r = results.get(d.key) ?? { verdict: "off", reason: blocked ?? (
|
|
157
|
+
const r = results.get(d.key) ?? { verdict: "off", reason: blocked ?? (eligible() ? stockFailure : gateReason()) ?? "runtime_unavailable", queuePos: null }, s = slots.get(d.key);
|
|
154
158
|
// #2055 one display precedence: a global latch, then the sticky safety history, then this page's own pause/failure.
|
|
155
159
|
const n = named.get(d.key), sticky = n && n.identity === signature(d) ? n.reason : null;
|
|
156
160
|
return { key: d.key, id: d.id, ...r, ...(sticky && r.verdict === "queued" ? { verdict: "off", queuePos: null } : {}), phase: s?.frame ? s.ready ? "running" : "starting" : s ? "loading" : null,
|
|
@@ -173,7 +177,7 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
|
|
|
173
177
|
};
|
|
174
178
|
function observe() {
|
|
175
179
|
const nodes = host.document.querySelectorAll("[data-trust]");
|
|
176
|
-
if (nodes.length >
|
|
180
|
+
if (nodes.length > 4096) throw Error("trust coverage unavailable");
|
|
177
181
|
const wanted = new Set([host.document.documentElement, ...nodes]);
|
|
178
182
|
for (const d of descriptors) if (d.element?.ownerDocument === host.document && d.element.isConnected === true) wanted.add(d.element);
|
|
179
183
|
for (const n of observed) if (!wanted.has(n)) { resize.unobserve(n); observed.delete(n); }
|
|
@@ -186,6 +190,7 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
|
|
|
186
190
|
if (time() === null) blocked = "clock_unavailable";
|
|
187
191
|
const hidden = context?.hidden === true || host.document.visibilityState !== "visible";
|
|
188
192
|
const stock = eligible() ? coverage() : null;
|
|
193
|
+
if (dead || proposal !== revision) return;
|
|
189
194
|
if (blocked || !eligible() || !stock) {
|
|
190
195
|
if (!stopAll(blocked ?? "gates_unknown")) return;
|
|
191
196
|
results.clear();
|
|
@@ -234,7 +239,11 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
|
|
|
234
239
|
if (s.frame && s.hiddenSince !== null) { s.hiddenSince = null; s.healthySince = last; if (s.budget.gates({ hidden: false, reducedMotion: false, saveData: false, show: true }).remove) retire(s, "hidden"); }
|
|
235
240
|
if (dead || proposal !== revision) return;
|
|
236
241
|
}
|
|
237
|
-
try { observe(); } catch {
|
|
242
|
+
try { observe(); } catch {
|
|
243
|
+
if (dead || proposal !== revision || !stopAll("trust_geometry_unknown")) return;
|
|
244
|
+
for (const d of descriptors) results.set(d.key, { verdict: "off", reason: "trust_geometry_unknown", queuePos: null });
|
|
245
|
+
tell(); return;
|
|
246
|
+
}
|
|
238
247
|
const max = context.constrained ? 1 : 3;
|
|
239
248
|
for (const d of descriptors) {
|
|
240
249
|
if (!chosen.has(d.key) || slots.has(d.key)) continue;
|
|
@@ -32,3 +32,12 @@ export function effectStockAllows(snapshot, { layer, by } = {}) {
|
|
|
32
32
|
const proof = effectStockSnapshot(snapshot);
|
|
33
33
|
return !!proof && (layer === "behind" || layer === "front" && proof.front && (by === "human" || proof.agentFront));
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
/** Display only. Refused provider metadata never becomes paint evidence or authority. */
|
|
37
|
+
export function effectStockRefusal(value) {
|
|
38
|
+
try {
|
|
39
|
+
if (!plain(value)) return "stock_evidence_invalid";
|
|
40
|
+
const d = Object.getOwnPropertyDescriptor(value, "reason");
|
|
41
|
+
return d && Object.hasOwn(d, "value") && typeof d.value === "string" && /^[a-z][a-z0-9_]{0,79}$/.test(d.value) ? d.value : "stock_evidence_invalid";
|
|
42
|
+
} catch { return "stock_evidence_invalid"; }
|
|
43
|
+
}
|
|
@@ -115,13 +115,36 @@ function stockBannerFill(host, el) {
|
|
|
115
115
|
if (b.display !== "block" || el.offsetParent !== box) return null;
|
|
116
116
|
return rect(box);
|
|
117
117
|
}
|
|
118
|
+
function zeroClipMargin(host, style) {
|
|
119
|
+
const margin = style.overflowClipMargin ?? style.getPropertyValue?.("overflow-clip-margin");
|
|
120
|
+
// WebKit versions without this property still implement paint containment. An absent
|
|
121
|
+
// computed value is usable only when the engine explicitly reports it unsupported;
|
|
122
|
+
// unknown capability or a supported but unreadable/expanded margin cannot prove a box.
|
|
123
|
+
return margin === "0px" || [undefined, ""].includes(margin)
|
|
124
|
+
&& host.CSS?.supports?.("overflow-clip-margin", "1px") === false;
|
|
125
|
+
}
|
|
118
126
|
function protectedPaint(host, root, budget) {
|
|
127
|
+
// These two stock toolbar boxes physically clip ALL descendants, including fixed-position
|
|
128
|
+
// children. Their entire box is a hole; descendant ink cannot escape it. Inline, fragmented,
|
|
129
|
+
// expanded-clip or CSS-overridden boxes take the ordinary bounded walk instead.
|
|
130
|
+
const s = host.getComputedStyle(root);
|
|
131
|
+
if (root.matches?.(".shell .thread .msg-tools, .shell .thread .codeblock > .cb-head") && s.contain === "paint"
|
|
132
|
+
&& s.display === "flex" && s.overflowX === "clip" && s.overflowY === "clip" && zeroClipMargin(host, s)
|
|
133
|
+
&& none(s.filter) && none(s.backdropFilter) && s.borderImageSource === "none" && [undefined, "", "visible"].includes(s.contentVisibility) && root.getClientRects().length === 1) {
|
|
134
|
+
if (++budget.n > 4096) { budget.exceeded = true; return null; }
|
|
135
|
+
const r = rect(root), pad = outset(s);
|
|
136
|
+
// Containment clips descendants, not the root's own ink. Do not infer a world-space
|
|
137
|
+
// outset from local CSS lengths under a potentially transformed content ancestor.
|
|
138
|
+
if (!r || pad !== 0) return null;
|
|
139
|
+
return r;
|
|
140
|
+
}
|
|
119
141
|
const nodes = [root, ...root.querySelectorAll("*")]; budget.n += nodes.length;
|
|
120
|
-
if (budget.n > 4096) return null;
|
|
142
|
+
if (budget.n > 4096) { budget.exceeded = true; return null; }
|
|
121
143
|
let union = null;
|
|
122
144
|
for (const el of nodes) {
|
|
123
145
|
if (!visible(host, el)) continue;
|
|
124
146
|
const style = host.getComputedStyle(el), pad = outset(style);
|
|
147
|
+
if (style.borderImageSource !== undefined && style.borderImageSource !== "none" || ![undefined, "", "visible"].includes(style.contentVisibility)) return null;
|
|
125
148
|
const r = none(style.filter) ? rect(el) : stockBannerFill(host, el);
|
|
126
149
|
if (!r || pad === null || !none(style.backdropFilter)) return null;
|
|
127
150
|
const a = { x: r.x - pad, y: r.y - pad, width: r.width + pad * 2, height: r.height + pad * 2 };
|
|
@@ -165,15 +188,23 @@ export function stockEffectTrust(host, { frameRoot = null, ownerCss = "unknown"
|
|
|
165
188
|
if (s.position !== "relative" || s.zIndex !== "1" || !neutral(s)) return fail("stock_order_unknown");
|
|
166
189
|
}
|
|
167
190
|
const marked = [...doc.querySelectorAll("[data-trust]")], expected = new Set(), rects = [], budget = { n: 0 };
|
|
168
|
-
if (marked.length >
|
|
191
|
+
if (marked.length > 4096) return fail("stock_marker_limit");
|
|
169
192
|
for (const { kind, selector } of STOCK_TRUST_INVENTORY) for (const el of doc.querySelectorAll(selector)) {
|
|
170
193
|
if (el.ownerDocument !== doc || el.isConnected !== true || el.getAttribute("data-trust") !== kind) return fail("stock_trust_unknown");
|
|
171
194
|
expected.add(el);
|
|
172
195
|
}
|
|
173
196
|
if (marked.some(el => !expected.has(el)) || surface.kind === "chat" && !chat.querySelector('.dock[data-trust="composer"]') || !surface.panel.querySelector('.panel-head[data-trust="header"]')) return fail("stock_trust_unknown");
|
|
174
197
|
for (const el of marked) {
|
|
175
|
-
const bounds = protectedPaint(host, el, budget); if (!bounds) return fail("stock_paint_unknown");
|
|
176
|
-
|
|
198
|
+
const bounds = protectedPaint(host, el, budget); if (!bounds) return fail(budget.exceeded ? "stock_paint_limit" : "stock_paint_unknown");
|
|
199
|
+
// Off-screen history remains inventoried and paint-checked; it need not consume a
|
|
200
|
+
// viewport hole. Include the clip algorithm's extra 8px protection margin.
|
|
201
|
+
if (bounds.width && bounds.height && (!finite(host.innerHeight) || bounds.x + bounds.width + 8 >= 0 && bounds.y + bounds.height + 8 >= 0 && bounds.x - 8 <= host.innerWidth && bounds.y - 8 <= host.innerHeight)) rects.push(bounds);
|
|
202
|
+
}
|
|
203
|
+
// Keep the existing 128-hole transport bound. Conservative union may hide extra
|
|
204
|
+
// effect pixels; it can never reveal protected pixels, even for a dense viewport.
|
|
205
|
+
if (rects.length > 128) {
|
|
206
|
+
const rest = rects.splice(127), x = Math.min(...rest.map(r => r.x)), y = Math.min(...rest.map(r => r.y));
|
|
207
|
+
rects.push({ x, y, width: Math.max(...rest.map(r => r.x + r.width)) - x, height: Math.max(...rest.map(r => r.y + r.height)) - y });
|
|
177
208
|
}
|
|
178
209
|
if (doc.querySelector(".agend-session-ended")) return fail("session_ended");
|
|
179
210
|
let barrier = [...doc.querySelectorAll(BARRIERS)].some(el => visible(host, el));
|
|
@@ -64,7 +64,7 @@ export function createEffectTrialSource({ window: host, current, ownerCurrent, s
|
|
|
64
64
|
const lease = { identity, reply, stage, owner, current: x.current, onStop: x.onStop, startedAt: x.startedAt, until, dead: false, checking: false, pending: new Set(), timer: null, off: null, binding: null };
|
|
65
65
|
lease.binding = Object.freeze({ kind: "trial", key: identity.key, id: identity.id, source: lease, generation: identity.generation });
|
|
66
66
|
active = lease; used.add(stage.receipt);
|
|
67
|
-
lease.off = watchEffectTrialStage(stage.receipt,
|
|
67
|
+
lease.off = watchEffectTrialStage(stage.receipt, reason => stop(lease, typeof reason === "string" ? reason : "stage_ended"));
|
|
68
68
|
if (typeof lease.off !== "function" || constrainEffectTrialStage(stage.receipt, () => checked(lease)) !== true || !checked(lease)) { stop(lease, "stage_unavailable"); return null; }
|
|
69
69
|
try { arm(lease); } catch { stop(lease, "trial_unavailable"); return null; }
|
|
70
70
|
if (!checked(lease)) return null;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** #1629: host-issued LOCAL trial paint receipt. No authority, loader, frame factory or pool is created here. */
|
|
2
2
|
import { effectTrialShadow } from "./effect-trial-shadow.js";
|
|
3
3
|
import { effectHostStyle } from "./effect-host-style.js";
|
|
4
|
-
import { effectStockSnapshot } from "./effect-stock-snapshot.js";
|
|
4
|
+
import { effectStockSnapshot, effectStockRefusal } from "./effect-stock-snapshot.js";
|
|
5
5
|
import { STOCK_EFFECT_BARRIERS } from "./effect-stock-trust.js";
|
|
6
6
|
const receipts = new WeakMap(), owners = new WeakMap();
|
|
7
7
|
let serial = 0;
|
|
@@ -64,7 +64,8 @@ export function createEffectTrialStage({ window: host, panel, stage, binding: ra
|
|
|
64
64
|
if (!own || typeof current !== "function" || typeof onStop !== "function" || !host?.document || !panel || !stage || owners.has(stage)) return null;
|
|
65
65
|
const shell = effectTrialShadow(host, panel), hostSheet = effectHostStyle(host);
|
|
66
66
|
let sheet; try { sheet = shell?.sheet; if (!shell || shell.stage !== stage || !hostSheet || !sheet || host.CSS?.supports("contain", "paint") !== true || host.CSS.supports("overflow", "clip") !== true) return null; } catch { return null; }
|
|
67
|
-
const receipt = Object.freeze(Object.create(null)), frames = new Set(), listeners = new Set(); let dead = false, constraint = null;
|
|
67
|
+
const receipt = Object.freeze(Object.create(null)), frames = new Set(), listeners = new Set(); let dead = false, constraint = null, failure = "stage_unavailable";
|
|
68
|
+
const reject = reason => { failure = reason; return null; };
|
|
68
69
|
const identity = Object.freeze({ key: own.key, id: own.id, instance: own.instance, generation: own.generation, serial: ++serial });
|
|
69
70
|
const invalidate = reason => {
|
|
70
71
|
if (dead) return; dead = true; receipts.delete(receipt); if (owners.get(stage) === receipt) owners.delete(stage);
|
|
@@ -76,45 +77,49 @@ export function createEffectTrialStage({ window: host, panel, stage, binding: ra
|
|
|
76
77
|
};
|
|
77
78
|
const blur = event => { if (event.target === host) invalidate("blur"); };
|
|
78
79
|
function check(stock) {
|
|
80
|
+
failure = "trial_layout_unverified";
|
|
79
81
|
try {
|
|
80
82
|
// This callback may reenter disposal or replace the owner. Recheck identity after it returns.
|
|
81
|
-
if (dead || current(own) !== true || constraint !== null && constraint() !== true || dead || owners.get(stage) !== receipt || dead) return
|
|
83
|
+
if (dead || current(own) !== true || constraint !== null && constraint() !== true || dead || owners.get(stage) !== receipt || dead) return reject("trial_current_changed");
|
|
82
84
|
const proof = effectStockSnapshot(stock), doc = host.document;
|
|
83
|
-
if (
|
|
85
|
+
if (!proof) return reject(effectStockRefusal(stock));
|
|
86
|
+
if (effectHostStyle(host) !== hostSheet || effectTrialShadow(host, panel) !== shell) return reject("trial_style_unavailable");
|
|
84
87
|
// The own panel MUST still suppress ordinary foreground effects. Never synthesize front:true.
|
|
85
|
-
if (
|
|
88
|
+
if (doc.hasFocus?.() !== true) return reject("trial_window_unfocused");
|
|
89
|
+
if (doc.visibilityState !== "visible") return reject("hidden");
|
|
90
|
+
if (proof.front !== false || doc.fullscreenElement || doc.pictureInPictureElement) return reject("trial_overlay_unverified");
|
|
86
91
|
const split = doc.querySelector(".main > .p-chat > .chat-split");
|
|
87
92
|
if (!split || doc.querySelectorAll(".main > .p-chat").length !== 1 || doc.querySelectorAll(".chat-split > .pv-panel").length !== 1
|
|
88
|
-
|| panel.parentElement !== split || panel.ownerDocument !== doc || panel.isConnected !== true || panel.className !== "pv-panel effect-trial-panel" || panel.getAttribute("data-trust") !== "preview") return
|
|
93
|
+
|| panel.parentElement !== split || panel.ownerDocument !== doc || panel.isConnected !== true || panel.className !== "pv-panel effect-trial-panel" || panel.getAttribute("data-trust") !== "preview") return reject("trial_panel_unavailable");
|
|
89
94
|
const children = [...shell.root.children], names = ["effect-trial-style", "effect-trial-head", "effect-trial-controls", "effect-trial-body", "effect-trial-note"];
|
|
90
95
|
if (panel.children.length !== 0 || children.length !== names.length || children.some((el, i) => el.className !== names[i]) || children[3].children.length !== 1 || children[3].firstElementChild !== stage
|
|
91
|
-
|| stage.className !== "effect-trial-stage" || stage.ownerDocument !== doc || stage.isConnected !== true || stage.hasAttribute("tabindex") || stage.hasAttribute("contenteditable")) return
|
|
96
|
+
|| stage.className !== "effect-trial-stage" || stage.ownerDocument !== doc || stage.isConnected !== true || stage.hasAttribute("tabindex") || stage.hasAttribute("contenteditable")) return reject("trial_shadow_unverified");
|
|
92
97
|
for (const control of [shell.head, shell.controls, shell.close, shell.stop, shell.note]) {
|
|
93
98
|
const style = host.getComputedStyle(control);
|
|
94
|
-
if (style.display === "none" || style.visibility !== "visible" || style.opacity !== "1" || !rect(control)) return
|
|
99
|
+
if (style.display === "none" || style.visibility !== "visible" || style.opacity !== "1" || !rect(control)) return reject("trial_controls_unavailable");
|
|
95
100
|
}
|
|
96
101
|
if ([...doc.querySelectorAll(STOCK_EFFECT_BARRIERS)].some(el => el !== panel && visible(host, el))
|
|
97
|
-
|| [...doc.querySelectorAll(".pv-panel")].some(el => el !== panel && visible(host, el))) return
|
|
102
|
+
|| [...doc.querySelectorAll(".pv-panel")].some(el => el !== panel && visible(host, el))) return reject("trial_other_overlay");
|
|
98
103
|
const active = doc.activeElement;
|
|
99
|
-
if (active && active !== doc.body && active !== doc.documentElement && (active.matches("input,select,textarea,[contenteditable]") || active.isContentEditable)) return
|
|
104
|
+
if (active && active !== doc.body && active !== doc.documentElement && (active.matches("input,select,textarea,[contenteditable]") || active.isContentEditable)) return reject("trial_editable_focus");
|
|
100
105
|
let el = stage, depth = 0;
|
|
101
|
-
while (el) { const s = host.getComputedStyle(el); if (++depth > 128 || el.ownerDocument !== doc || !neutral(s) || s.opacity !== "1" || s.visibility !== "visible" || s.display === "none" || s.contain !== (el === stage ? "paint" : "none")) return
|
|
102
|
-
if (el !== doc.documentElement) return
|
|
106
|
+
while (el) { const s = host.getComputedStyle(el); if (++depth > 128 || el.ownerDocument !== doc || !neutral(s) || s.opacity !== "1" || s.visibility !== "visible" || s.display === "none" || s.contain !== (el === stage ? "paint" : "none")) return reject("trial_ancestor_paint"); if (el === doc.documentElement) break; el = el.parentElement ?? (el.getRootNode?.() === shell.root ? panel : null); }
|
|
107
|
+
if (el !== doc.documentElement) return reject("trial_ancestor_paint");
|
|
103
108
|
const s = host.getComputedStyle(stage), r = rect(stage), p = rect(panel);
|
|
104
109
|
if (!r || !p || s.position !== "relative" || s.zIndex !== "0" || s.overflowX !== "clip" || s.overflowY !== "clip"
|
|
105
110
|
|| s.paddingLeft !== "0px" || s.paddingRight !== "0px" || s.paddingTop !== "0px" || s.paddingBottom !== "0px"
|
|
106
111
|
|| ["borderLeftWidth", "borderRightWidth", "borderTopWidth", "borderBottomWidth"].some(k => s[k] !== "0px") || !none(s.boxShadow) || s.outlineStyle !== "none"
|
|
107
112
|
|| r.x < p.x || r.y < p.y || r.x + r.width > p.x + p.width || r.y + r.height > p.y + p.height
|
|
108
|
-
|| r.x < 0 || r.y < 0 || r.x + r.width > host.innerWidth || r.y + r.height > host.innerHeight) return
|
|
109
|
-
if ([...stage.children].some(child => ![...frames].some(f => f.element === child))) return
|
|
113
|
+
|| r.x < 0 || r.y < 0 || r.x + r.width > host.innerWidth || r.y + r.height > host.innerHeight) return reject("trial_clip_unverified");
|
|
114
|
+
if ([...stage.children].some(child => ![...frames].some(f => f.element === child))) return reject("trial_frame_unknown");
|
|
110
115
|
const width = Math.floor(r.width), height = Math.floor(r.height);
|
|
111
|
-
if (width < 1 || height < 1 || width > 4096 || height > 4096 || width * height > 8388608 || dead || owners.get(stage) !== receipt) return
|
|
112
|
-
if (current(own) !== true || constraint !== null && constraint() !== true || dead || owners.get(stage) !== receipt || dead || stage.isConnected !== true || panel.parentElement !== split) return
|
|
116
|
+
if (width < 1 || height < 1 || width > 4096 || height > 4096 || width * height > 8388608 || dead || owners.get(stage) !== receipt) return reject("trial_size_limit");
|
|
117
|
+
if (current(own) !== true || constraint !== null && constraint() !== true || dead || owners.get(stage) !== receipt || dead || stage.isConnected !== true || panel.parentElement !== split) return reject("trial_current_changed");
|
|
113
118
|
return { x: r.x, y: r.y, width, height };
|
|
114
|
-
} catch { return
|
|
119
|
+
} catch { return reject("trial_check_failed"); }
|
|
115
120
|
}
|
|
116
121
|
function project(stock) {
|
|
117
|
-
const r = check(stock); if (!r) { invalidate(
|
|
122
|
+
const r = check(stock); if (!r) { invalidate(failure); return null; }
|
|
118
123
|
return Object.freeze({ root: stage, rect: Object.freeze(r), identity, bind });
|
|
119
124
|
}
|
|
120
125
|
function bind(frame, { layer } = {}, stock) {
|
|
@@ -131,7 +136,7 @@ export function createEffectTrialStage({ window: host, panel, stage, binding: ra
|
|
|
131
136
|
};
|
|
132
137
|
const handle = { ok: true, element: frame, remove, hide,
|
|
133
138
|
sync(proof) {
|
|
134
|
-
const r = check(proof); if (!r || removed) { invalidate(
|
|
139
|
+
const r = check(proof); if (!r || removed) { invalidate(failure); return false; }
|
|
135
140
|
try {
|
|
136
141
|
const matches = visibility => {
|
|
137
142
|
const s = host.getComputedStyle(frame), actual = rect(frame);
|
|
@@ -146,7 +151,7 @@ export function createEffectTrialStage({ window: host, panel, stage, binding: ra
|
|
|
146
151
|
for (const [k, v] of Object.entries({ left: "0px", top: "0px", width: `${r.width}px`, height: `${r.height}px` })) rule.style.setProperty(k, v, "important");
|
|
147
152
|
if (!matches("hidden")) throw Error("local geometry lost");
|
|
148
153
|
rule.style.setProperty("visibility", "visible", "important"); shown = true; return true;
|
|
149
|
-
} catch { invalidate("
|
|
154
|
+
} catch { invalidate("trial_frame_geometry"); return false; }
|
|
150
155
|
},
|
|
151
156
|
};
|
|
152
157
|
frames.add(handle);
|
|
@@ -154,7 +159,8 @@ export function createEffectTrialStage({ window: host, panel, stage, binding: ra
|
|
|
154
159
|
const i = sheet.insertRule(`.${token}{}`, sheet.cssRules.length); rule = sheet.cssRules[i];
|
|
155
160
|
for (const [k, v] of Object.entries({ position: "absolute", visibility: "hidden", "pointer-events": "none", "z-index": "0", opacity: layer === "front" ? "0.85" : "1", border: "0px", margin: "0px", padding: "0px", "box-sizing": "border-box", "min-width": "0px", "min-height": "0px", "max-width": "none", "max-height": "none", outline: "none", "box-shadow": "none", "background-color": "transparent", animation: "none", transition: "none" })) rule.style.setProperty(k, v, "important");
|
|
156
161
|
frame.classList.add(token);
|
|
157
|
-
if (host.getComputedStyle(frame).visibility !== "hidden"
|
|
162
|
+
if (host.getComputedStyle(frame).visibility !== "hidden") throw Error("initial local hiding lost");
|
|
163
|
+
if (!check(stock)) { remove(); invalidate(failure); return { ok: false }; }
|
|
158
164
|
} catch { remove(); invalidate("stage_unavailable"); return { ok: false }; }
|
|
159
165
|
return handle;
|
|
160
166
|
}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
overflow: clip !important;
|
|
17
17
|
background: #ffffff !important;
|
|
18
18
|
color: #1b1b1f !important;
|
|
19
|
-
font: 400 16px/1.4 system-ui, sans-serif !important;
|
|
19
|
+
font: 400 16px/1.4 system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", emoji !important;
|
|
20
20
|
direction: ltr !important;
|
|
21
21
|
unicode-bidi: isolate !important;
|
|
22
22
|
text-align: start !important;
|