@songsid/agend 2.3.0-beta.2 → 2.3.0-beta.4
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-whats-new.js +2 -0
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +30 -1
- package/dist/auth-api.js +42 -12
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/claude-code.d.ts +2 -2
- package/dist/backend/claude-code.js +5 -4
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/types.d.ts +6 -0
- package/dist/backend/types.js +15 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-availability.d.ts +120 -0
- package/dist/backend-availability.js +396 -0
- package/dist/backend-availability.js.map +1 -0
- package/dist/backend-command.d.ts +21 -3
- package/dist/backend-command.js +23 -4
- package/dist/backend-command.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +7 -0
- package/dist/channel/adapters/discord.js +35 -0
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/mcp-tools.js +8 -4
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/types.d.ts +6 -0
- package/dist/classic-channel-manager.d.ts +8 -3
- package/dist/classic-channel-manager.js +10 -3
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/config-validator.js +73 -2
- package/dist/config-validator.js.map +1 -1
- package/dist/delivery-outbox.js +5 -0
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/fleet-context.d.ts +10 -0
- package/dist/fleet-manager.d.ts +238 -5
- package/dist/fleet-manager.js +878 -71
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-traffic.d.ts +38 -0
- package/dist/fleet-traffic.js +96 -0
- package/dist/fleet-traffic.js.map +1 -0
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +13 -5
- package/dist/general-knowledge/skills/multi-channel/SKILL.md +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +56 -11
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +398 -0
- package/dist/human-delivery-receipts.js +2 -1
- package/dist/human-delivery-receipts.js.map +1 -1
- package/dist/locale.js +48 -8
- package/dist/locale.js.map +1 -1
- package/dist/memory-hogs.d.ts +27 -0
- package/dist/memory-hogs.js +124 -0
- package/dist/memory-hogs.js.map +1 -0
- package/dist/outbound-handlers.d.ts +2 -0
- package/dist/outbound-handlers.js +40 -1
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +92 -0
- package/dist/outbound-schemas.js +55 -1
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +1 -1
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +9 -0
- package/dist/public-web-link.js +8 -0
- package/dist/public-web-link.js.map +1 -1
- package/dist/quickstart-api.d.ts +10 -1
- package/dist/quickstart-api.js +12 -4
- package/dist/quickstart-api.js.map +1 -1
- package/dist/reply-buttons-controller.d.ts +2 -0
- package/dist/reply-buttons-controller.js +1 -1
- package/dist/reply-buttons-controller.js.map +1 -1
- package/dist/tmux-manager.d.ts +5 -0
- package/dist/tmux-manager.js +16 -0
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tool-permissions.js +4 -0
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +5 -0
- package/dist/topic-commands.js +16 -0
- package/dist/topic-commands.js.map +1 -1
- package/dist/types.d.ts +25 -1
- package/dist/ui/app-effects.js +33 -9
- package/dist/ui/chat-effects.js +62 -4
- package/dist/ui/chat-strings.js +40 -4
- package/dist/ui/chat-thread.js +17 -6
- package/dist/ui/effect-trial-panel.js +19 -16
- package/dist/ui/fleet-canvas-layout.js +111 -0
- package/dist/ui/fleet-canvas.js +489 -0
- package/dist/ui/fleet-org.js +27 -12
- package/dist/ui/fleet-traffic.js +224 -0
- package/dist/ui/panel-chat.js +12 -5
- package/dist/ui/panel-details.js +2 -1
- package/dist/ui/panel-fleet.js +32 -11
- package/dist/ui/panel-settings.js +6 -4
- package/dist/ui/preview.js +3 -2
- package/dist/ui/settings-appearance.js +34 -18
- package/dist/ui/settings-catalog.js +89 -0
- package/dist/ui/settings-dialogs.js +40 -12
- package/dist/ui/settings-effects.js +4 -3
- package/dist/ui/settings-learn.js +25 -0
- package/dist/ui/settings-model.js +6 -2
- package/dist/ui/settings-mods.js +4 -3
- package/dist/ui/settings-owner-effects.js +8 -8
- package/dist/ui/settings-strings.js +40 -38
- package/dist/ui/shared/app-i18n.js +2 -2
- package/dist/ui/shared/app-prefs.js +1 -1
- package/dist/ui/shared/app-shell.js +23 -1
- package/dist/ui/shared/app-store.js +12 -1
- package/dist/ui/shared/app-stream.js +2 -1
- package/dist/ui/shared/app.css +186 -11
- package/dist/ui/shared/card-strings.js +4 -0
- package/dist/ui/shared/effect-app-runtime.js +1 -1
- package/dist/ui/shared/effect-frame.js +10 -5
- package/dist/ui/shared/effect-geometry.js +24 -9
- package/dist/ui/shared/effect-policy.js +2 -1
- package/dist/ui/shared/effect-registry.js +71 -17
- package/dist/ui/shared/effect-stock-trust.js +23 -2
- package/dist/ui/shared/effect-trial-shadow.js +62 -0
- package/dist/ui/shared/effect-trial-stage.js +15 -9
- package/dist/ui/shared/effect-trial.css +40 -0
- package/dist/ui/shared/identity-card.js +75 -4
- package/dist/ui/shared/identity-color.js +3 -3
- package/dist/ui/shared/instance-nav.js +20 -4
- package/dist/ui/shared/panel-view.js +24 -4
- package/dist/ui/shared/view-strings.js +22 -0
- package/dist/view-api.d.ts +32 -0
- package/dist/view-api.js +55 -4
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +19 -4
- package/dist/web-api.js +178 -20
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-policy.d.ts +4 -6
- package/dist/web-appearance-policy.js +5 -12
- package/dist/web-appearance-policy.js.map +1 -1
- package/dist/web-appearance-tools.js +5 -1
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance-view.d.ts +14 -1
- package/dist/web-appearance-view.js +98 -10
- package/dist/web-appearance-view.js.map +1 -1
- package/dist/web-appearance.d.ts +2 -2
- package/dist/web-appearance.js +5 -6
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-auth.js +1 -1
- package/dist/web-auth.js.map +1 -1
- package/dist/web-card.d.ts +14 -0
- package/dist/web-card.js +40 -6
- package/dist/web-card.js.map +1 -1
- package/dist/web-channel-echo.d.ts +7 -1
- package/dist/web-channel-echo.js +6 -2
- package/dist/web-channel-echo.js.map +1 -1
- package/dist/web-chat-history.d.ts +40 -0
- package/dist/web-chat-history.js +66 -3
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +1 -0
- package/dist/web-effect-tools.js +2 -3
- package/dist/web-effect-tools.js.map +1 -1
- package/dist/web-effect-trials.d.ts +2 -2
- package/dist/web-effect-trials.js +3 -3
- package/dist/web-effect-trials.js.map +1 -1
- package/dist/web-guest-access.d.ts +125 -0
- package/dist/web-guest-access.js +232 -0
- package/dist/web-guest-access.js.map +1 -0
- package/dist/web-guest-rooms.d.ts +82 -0
- package/dist/web-guest-rooms.js +135 -0
- package/dist/web-guest-rooms.js.map +1 -0
- package/dist/web-identity-palette.d.ts +1 -1
- package/dist/web-identity-palette.js +2 -2
- package/dist/web-identity-palette.js.map +1 -1
- package/dist/web-login.d.ts +47 -0
- package/dist/web-login.js +115 -0
- package/dist/web-login.js.map +1 -1
- package/dist/web-mod-agent.d.ts +1 -1
- package/dist/web-mod-agent.js +2 -4
- package/dist/web-mod-agent.js.map +1 -1
- package/dist/web-mod-store.d.ts +5 -0
- package/dist/web-mod-store.js +6 -0
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-prefs.d.ts +8 -0
- package/dist/web-prefs.js +9 -0
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-preview.d.ts +6 -2
- package/dist/web-preview.js +4 -2
- package/dist/web-preview.js.map +1 -1
- package/dist/web-returning-device.d.ts +39 -0
- package/dist/web-returning-device.js +46 -5
- package/dist/web-returning-device.js.map +1 -1
- package/dist/web-session.d.ts +58 -10
- package/dist/web-session.js +104 -22
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-tools.d.ts +81 -0
- package/dist/web-settings-tools.js +257 -0
- package/dist/web-settings-tools.js.map +1 -0
- package/dist/web-theme.d.ts +2 -0
- package/dist/web-theme.js +10 -1
- package/dist/web-theme.js.map +1 -1
- package/package.json +1 -1
package/dist/ui/shared/app.css
CHANGED
|
@@ -119,7 +119,10 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
|
|
|
119
119
|
@media (prefers-color-scheme: light) { :root:not([data-theme="dark"]) .idc { --id: var(--id-light); --id-name: var(--id-name-light); } }
|
|
120
120
|
:root[data-theme="light"] .idc { --id: var(--id-light); --id-name: var(--id-name-light); }
|
|
121
121
|
.idc.id-ring .av { box-shadow: 0 0 0 2px var(--id); }
|
|
122
|
-
|
|
122
|
+
/* #2056: the bar is a plain straight line inside the row: no caps following the row's rounded corners (an inset shadow
|
|
123
|
+
did), and short of the corners, so the hover and selected highlight's radius is never crossed. */
|
|
124
|
+
.idc.id-bar { position: relative; }
|
|
125
|
+
.idc.id-bar::before { content: ""; position: absolute; left: 0; top: var(--s-2); bottom: var(--s-2); width: 3px; background: var(--id); pointer-events: none; }
|
|
123
126
|
.idc.id-name .inst-name { color: var(--id-name); }
|
|
124
127
|
.idc-swatches, .idc-places { border: 0; padding: 0; margin: 0 0 var(--s-3); display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
|
125
128
|
.idc-swatches legend, .idc-places legend { font-weight: var(--fw-semibold); margin-bottom: 6px; width: 100%; }
|
|
@@ -132,6 +135,20 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
|
|
|
132
135
|
.idc-hex { width: 9ch; font-family: var(--font-mono, monospace); }
|
|
133
136
|
.dlg-actions .dlg-spacer { flex: 1 1 auto; }
|
|
134
137
|
.idc-preview { padding: 6px; border-radius: 10px; background: var(--bg-sidebar); max-width: 260px; }
|
|
138
|
+
/* #2056 B: Settings → Web appearance — plain group headings, a "?" per card to its docs section, and the colour card's sample row. */
|
|
139
|
+
.s-ap-group { font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--text); margin: var(--s-4) 0 var(--s-2); }
|
|
140
|
+
.s-ap-group:first-child { margin-top: 0; }
|
|
141
|
+
.s-ap-title { display: flex; align-items: center; gap: var(--s-2); }
|
|
142
|
+
/* The fields' placeholders show the defaults: readable, not the browser's faint grey (dark mode). */
|
|
143
|
+
.s-appearance input::placeholder { color: var(--text-3); opacity: 1; }
|
|
144
|
+
/* A card title in Chinese is never upper-cased ("AGENT 的主題"): the small caps are a Latin convention. */
|
|
145
|
+
:lang(zh-Hant) .card h3 { text-transform: none; letter-spacing: 0; }
|
|
146
|
+
.s-learn { display: inline-grid; place-items: center; width: 18px; height: 18px; border-radius: var(--r-full); border: 1px solid var(--border);
|
|
147
|
+
color: var(--text-3); font-size: 11px; text-decoration: none; text-transform: none; letter-spacing: 0; line-height: 1; }
|
|
148
|
+
.s-learn:hover, .s-learn:focus-visible { color: var(--text); border-color: var(--text-3); }
|
|
149
|
+
.s-colors-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3); margin-bottom: var(--s-2); }
|
|
150
|
+
.s-colors-row .idc-places { margin-bottom: 0; flex: 1 1 220px; }
|
|
151
|
+
.s-colors-sample { flex: 0 1 220px; min-width: 0; }
|
|
135
152
|
.av-text { overflow: hidden; }
|
|
136
153
|
.av .av-dot { position: absolute; right: -2px; bottom: -2px; margin: 0; box-shadow: 0 0 0 2px var(--bg-sidebar); }
|
|
137
154
|
/* #1722: the CLI as a letter on the avatar's top-left corner — monochrome (the #1630 ring keeps the colour), cut out like the dot */
|
|
@@ -222,11 +239,15 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
|
|
|
222
239
|
}
|
|
223
240
|
.panel-head .sb-open { display: none; }
|
|
224
241
|
.sb-collapsed .panel-head .sb-open { display: inline-flex; }
|
|
242
|
+
/* #2074: a collapsed sidebar's "New agent", beside its ☰ (a phone opens the drawer, which has the row). */
|
|
243
|
+
.panel-head .sb-new { margin-left: calc(-1 * var(--s-1)); }
|
|
225
244
|
/* One row of --header-h on every page (a phone's instance tabs wrap below it): the title, its status and badges on one centre line. */
|
|
226
245
|
.panel-title { flex: 1; min-width: 0; display: flex; align-items: center; gap: var(--s-3); min-height: calc(var(--header-h) - 2 * var(--s-1)); }
|
|
227
246
|
.panel-title h1 { font-size: var(--fs-lg); line-height: var(--lh-lg); font-weight: var(--fw-semibold); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; outline: none; }
|
|
228
247
|
.panel-sub { color: var(--text-2); font-size: var(--fs-sm); white-space: nowrap; }
|
|
229
248
|
.status { display: inline-flex; align-items: center; gap: 6px; }
|
|
249
|
+
/* #2015: the largest processes, after the host memory chip's text. */
|
|
250
|
+
.host-mem-top { margin-left: var(--s-2); opacity: 0.8; overflow-wrap: anywhere; }
|
|
230
251
|
.panel-actions { display: flex; align-items: center; gap: var(--s-1); }
|
|
231
252
|
/* #1523 N2: the instance view switch — beside the title on a wide screen; on a phone, a row of tabs under the header. */
|
|
232
253
|
.inst-switch.seg { padding: 0; margin: 0; width: auto; max-width: none; flex-shrink: 0; }
|
|
@@ -345,6 +366,9 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
|
|
|
345
366
|
.field input:focus, .field select:focus, .field textarea:focus, .field-row input:focus { border-color: var(--focus); box-shadow: 0 0 0 3px var(--accent-dim); }
|
|
346
367
|
.field input:disabled { opacity: 0.6; }
|
|
347
368
|
.field-row { display: flex; gap: var(--s-1); align-items: center; }
|
|
369
|
+
/* #2072: a model field — the list, and the text box "Other…" opens under it. */
|
|
370
|
+
.model-pick { display: flex; flex-direction: column; gap: var(--s-1); min-width: 0; }
|
|
371
|
+
.model-pick > select, .model-pick > input { width: 100%; }
|
|
348
372
|
.note { font-size: var(--fs-xs); color: var(--text-3); }
|
|
349
373
|
.checks { display: flex; flex-direction: column; gap: var(--s-1); max-height: 180px; overflow-y: auto; }
|
|
350
374
|
.check { display: flex; gap: var(--s-2); align-items: center; font-size: var(--fs-sm); }
|
|
@@ -593,8 +617,12 @@ html.lb-open .scroller { overflow: hidden; }
|
|
|
593
617
|
.pv-menu[open] { z-index: 6; background: var(--surface); border-radius: var(--r-sm); padding: var(--s-1); box-shadow: var(--shadow-2); }
|
|
594
618
|
.pv-menu[open] > .chip-btn { display: block; text-align: left; width: max-content; max-width: 260px; white-space: normal; }
|
|
595
619
|
|
|
620
|
+
/* #2087: the pill takes no room in the thread — it was a sticky box in the flow, 38 px at the end of the scroll, so
|
|
621
|
+
hiding it 48 px from the bottom took that room away under a reader still scrolling — the wheel's last step was cut
|
|
622
|
+
short, or the view clamped back. Its dock is sticky and 0 px tall; the pill floats above it. */
|
|
623
|
+
.jump-dock { position: sticky; bottom: 0; height: 0; z-index: 5; }
|
|
596
624
|
.jump-latest {
|
|
597
|
-
position:
|
|
625
|
+
position: absolute; bottom: var(--s-3); left: 50%; transform: translateX(-50%); white-space: nowrap; display: inline-flex; align-items: center; gap: var(--s-1);
|
|
598
626
|
background: var(--bg); color: var(--text); border: 1px solid var(--border); border-radius: var(--r-full); box-shadow: var(--shadow-2);
|
|
599
627
|
padding: 6px var(--s-4); font-size: var(--fs-xs); font-weight: var(--fw-semibold); cursor: pointer;
|
|
600
628
|
}
|
|
@@ -725,6 +753,7 @@ html.lb-open .scroller { overflow: hidden; }
|
|
|
725
753
|
.sb-open .sidebar { transform: none; visibility: visible; }
|
|
726
754
|
.sb-open .scrim { display: block; position: fixed; inset: 0; background: var(--scrim); z-index: 39; }
|
|
727
755
|
.panel-head .sb-open { display: inline-flex; }
|
|
756
|
+
.panel-head .sb-new { display: none; }
|
|
728
757
|
.only-narrow { display: inline-flex; }
|
|
729
758
|
.thread { padding: var(--s-3) var(--s-3) var(--s-2); gap: var(--s-5); }
|
|
730
759
|
.col { padding: var(--s-3) var(--s-3) var(--s-8); }
|
|
@@ -1122,6 +1151,114 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
|
|
|
1122
1151
|
content: ""; position: absolute; top: var(--s-5); left: calc(-1 * var(--s-4) - 2px); width: var(--s-4); height: 2px; background: var(--border-strong); }
|
|
1123
1152
|
.org-link { min-height: var(--tap); }
|
|
1124
1153
|
}
|
|
1154
|
+
/* ── #1918: the org chart as a canvas (fleet-canvas.js). SVG with classes only: per-card values are SVG attributes, the
|
|
1155
|
+
view's transform one attribute on .oc-world (#1300). The stage fills the panel's height; touch gestures are the
|
|
1156
|
+
canvas's own (touch-action: none on it only), the page around it still scrolls. ── */
|
|
1157
|
+
.oc-bar { flex-wrap: wrap; row-gap: var(--s-2); }
|
|
1158
|
+
.oc-tools { margin-left: auto; display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); }
|
|
1159
|
+
.oc-find { flex: 0 1 260px; min-width: 150px; position: relative; }
|
|
1160
|
+
.oc-find input { height: 32px; padding-right: 72px; }
|
|
1161
|
+
.oc-found { position: absolute; right: var(--s-3); top: 50%; transform: translateY(-50%); font-size: var(--fs-xs); color: var(--text-3); white-space: nowrap; pointer-events: none; }
|
|
1162
|
+
.oc-stage { position: relative; height: clamp(420px, calc(100dvh - 240px), 960px); border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden; background: var(--bg); }
|
|
1163
|
+
.oc-svg { display: block; width: 100%; height: 100%; touch-action: none; cursor: grab; user-select: none; -webkit-user-select: none; }
|
|
1164
|
+
.oc-svg:active { cursor: grabbing; }
|
|
1165
|
+
.oc-svg:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
|
|
1166
|
+
.oc-tree path { fill: none; stroke: var(--border-strong); stroke-width: 1.25; }
|
|
1167
|
+
.oc-box { fill: var(--surface); stroke: var(--border); stroke-width: 1; }
|
|
1168
|
+
.oc-team.loose .oc-box { stroke-dasharray: 5 4; }
|
|
1169
|
+
.oc-box.match { stroke: var(--focus); stroke-width: 2.5; }
|
|
1170
|
+
.oc-head { fill: var(--surface-2); }
|
|
1171
|
+
.oc-hit { fill: transparent; }
|
|
1172
|
+
.oc-toggle:not(.empty) { cursor: pointer; }
|
|
1173
|
+
.oc-toggle:focus { outline: none; }
|
|
1174
|
+
.oc-toggle:focus-visible .oc-hit { stroke: var(--focus); stroke-width: 2; }
|
|
1175
|
+
.oc-caret { fill: var(--text-2); font-size: 12px; }
|
|
1176
|
+
.oc-team-name { fill: var(--text); font-size: 13px; font-weight: 600; }
|
|
1177
|
+
.oc-team-meta { fill: var(--text-3); font-size: 11px; }
|
|
1178
|
+
.oc-team-sum { fill: var(--text-2); font-size: 11.5px; }
|
|
1179
|
+
.oc-team.dim { opacity: 0.55; }
|
|
1180
|
+
.oc-node.dim { opacity: 0.4; }
|
|
1181
|
+
.oc-node { cursor: pointer; }
|
|
1182
|
+
.oc-node[role="img"] { cursor: default; }
|
|
1183
|
+
.oc-node:focus { outline: none; }
|
|
1184
|
+
.oc-card { fill: var(--bg); stroke: var(--border); stroke-width: 1; }
|
|
1185
|
+
.oc-node.lead .oc-card { fill: var(--surface); stroke: var(--accent); stroke-width: 1.5; }
|
|
1186
|
+
.oc-node:hover .oc-card { stroke: var(--border-strong); }
|
|
1187
|
+
.oc-node:focus-visible .oc-card, .oc-node.match .oc-card, .oc-node.match:hover .oc-card { stroke: var(--focus); stroke-width: 2.5; }
|
|
1188
|
+
.oc-node.st-missing { opacity: 0.7; }
|
|
1189
|
+
.oc-av { fill: var(--surface-2); stroke: var(--surface); stroke-width: 2; }
|
|
1190
|
+
.oc-node.idc.id-ring .oc-av { stroke: var(--id); stroke-width: 2.5; }
|
|
1191
|
+
.oc-idbar { display: none; }
|
|
1192
|
+
.oc-node.idc.id-bar .oc-idbar { display: inline; fill: var(--id); }
|
|
1193
|
+
.oc-face.emoji .oc-av { fill: transparent; }
|
|
1194
|
+
.oc-av-t { fill: var(--text-2); font-size: 12px; font-weight: 600; }
|
|
1195
|
+
.oc-face.emoji .oc-av-t { font-size: 18px; font-weight: normal; }
|
|
1196
|
+
.oc-name { fill: var(--text); font-size: 13.5px; font-weight: 600; }
|
|
1197
|
+
.oc-node.lead .oc-name { font-size: 15px; }
|
|
1198
|
+
.oc-node.idc.id-name .oc-name { fill: var(--id-name); }
|
|
1199
|
+
.oc-sub { font-size: 12px; }
|
|
1200
|
+
.oc-st { fill: var(--text-2); }
|
|
1201
|
+
.oc-st.st-working { fill: var(--ok); } .oc-st.st-needs, .oc-st.st-stuck { fill: var(--warn); } .oc-st.st-crashed { fill: var(--danger); }
|
|
1202
|
+
.oc-dot { fill: var(--text-3); stroke: var(--bg); stroke-width: 2; }
|
|
1203
|
+
.oc-dot.st-working, .oc-dot.st-idle { fill: var(--ok); } .oc-dot.st-needs, .oc-dot.st-stuck { fill: var(--warn); } .oc-dot.st-crashed { fill: var(--danger); }
|
|
1204
|
+
.oc-dot.st-paused { fill: var(--bg); stroke: var(--text-3); stroke-width: 1.5; }
|
|
1205
|
+
.oc-zoom { position: absolute; right: var(--s-3); top: var(--s-3); display: flex; flex-direction: column; border: 1px solid var(--border); border-radius: var(--r-md); overflow: hidden; background: var(--surface); }
|
|
1206
|
+
.oc-zoom .icon-btn { width: 36px; height: 34px; border-radius: 0; font-size: 18px; }
|
|
1207
|
+
.oc-zoom .icon-btn + .icon-btn { border-top: 1px solid var(--border); }
|
|
1208
|
+
.oc-mini { position: absolute; right: var(--s-3); bottom: var(--s-3); width: 180px; height: 120px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md); cursor: pointer; }
|
|
1209
|
+
.oc-mini-team { fill: var(--surface-2); stroke: var(--border-strong); stroke-width: 6; }
|
|
1210
|
+
.oc-mini-team.needs { fill: var(--warn-bg); stroke: var(--warn); }
|
|
1211
|
+
.oc-mini-top { fill: var(--accent-dim); stroke: var(--accent); stroke-width: 6; }
|
|
1212
|
+
.oc-mini-team.match { stroke: var(--focus); stroke-width: 14; }
|
|
1213
|
+
.oc-mini-hit { fill: var(--focus); }
|
|
1214
|
+
.oc-mini-view { fill: none; stroke: var(--focus); stroke-width: 10; }
|
|
1215
|
+
/* #1918 phase 3: live traffic. Kind hues kept apart from the state colours (ok = working/idle, warn = needs you, danger). */
|
|
1216
|
+
.oc { --fx-task: #b794f6; --fx-send: #2dd4bf; --fx-report: #f472b6; --fx-broadcast: #fb923c; --fx-team: #cbd5e1; }
|
|
1217
|
+
@media (prefers-color-scheme: light) { :root:not([data-theme="dark"]) .oc { --fx-task: #7c3aed; --fx-send: #0f766e; --fx-report: #be185d; --fx-broadcast: #c2410c; --fx-team: #475569; } }
|
|
1218
|
+
:root[data-theme="light"] .oc { --fx-task: #7c3aed; --fx-send: #0f766e; --fx-report: #be185d; --fx-broadcast: #c2410c; --fx-team: #475569; }
|
|
1219
|
+
.oc-flow { fill: none; stroke-width: 2.5; stroke-linecap: round; }
|
|
1220
|
+
.oc-flow-halo { fill: none; stroke: var(--bg); stroke-width: 6; stroke-linecap: round; opacity: 0.85; }
|
|
1221
|
+
.oc-flow.h-task { stroke: var(--fx-task); } .oc-flow.h-send { stroke: var(--fx-send); } .oc-flow.h-report { stroke: var(--fx-report); }
|
|
1222
|
+
.oc-flow.h-broadcast { stroke: var(--fx-broadcast); } .oc-flow.h-team { stroke: var(--fx-team); stroke-width: 3.5; }
|
|
1223
|
+
.oc-flow.ph-live { stroke-dasharray: 7 6; animation: oc-dash 0.9s linear infinite; }
|
|
1224
|
+
.oc-flow.ph-queued { stroke-dasharray: 2 5; opacity: 0.8; }
|
|
1225
|
+
.oc-flow.ph-fading { stroke: var(--text-3); animation: oc-fade 3s ease-out forwards; }
|
|
1226
|
+
.oc-ah.h-task { fill: var(--fx-task); } .oc-ah.h-send { fill: var(--fx-send); } .oc-ah.h-report { fill: var(--fx-report); }
|
|
1227
|
+
.oc-ah.h-broadcast { fill: var(--fx-broadcast); } .oc-ah.h-team { fill: var(--fx-team); } .oc-ah.h-fading { fill: var(--text-3); }
|
|
1228
|
+
.oc-pulse.h-task { fill: var(--fx-task); } .oc-pulse.h-send { fill: var(--fx-send); } .oc-pulse.h-report { fill: var(--fx-report); }
|
|
1229
|
+
.oc-pulse.h-broadcast { fill: var(--fx-broadcast); } .oc-pulse.h-team { fill: var(--fx-team); }
|
|
1230
|
+
.oc-fl.dim { opacity: 0.18; }
|
|
1231
|
+
.oc-badge rect { fill: var(--surface-2); stroke: var(--border); } .oc-badge text { fill: var(--text-2); font-size: 10.5px; }
|
|
1232
|
+
@keyframes oc-dash { to { stroke-dashoffset: -26; } }
|
|
1233
|
+
@keyframes oc-fade { from { opacity: 0.8; } to { opacity: 0.15; } }
|
|
1234
|
+
.oc-legend { position: absolute; left: var(--s-3); bottom: var(--s-3); max-width: calc(100% - 220px);
|
|
1235
|
+
font-size: var(--fs-xs); color: var(--text-2); background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md); padding: 6px 10px; }
|
|
1236
|
+
.oc-legend summary { cursor: pointer; font-weight: var(--fw-semibold); color: var(--text); }
|
|
1237
|
+
.oc-legend[open] summary { margin-bottom: 4px; }
|
|
1238
|
+
.oc-lg-rows { display: flex; flex-wrap: wrap; gap: var(--s-1) var(--s-3); }
|
|
1239
|
+
.oc-lg { display: inline-flex; align-items: center; gap: 6px; }
|
|
1240
|
+
.oc-sw { width: 18px; height: 3px; border-radius: 2px; display: inline-block; background: var(--text-3); }
|
|
1241
|
+
.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); }
|
|
1242
|
+
.oc-sw.ph-queued { background: repeating-linear-gradient(90deg, var(--text-2) 0 3px, transparent 3px 7px); }
|
|
1243
|
+
.oc-sw.ph-fading { opacity: 0.5; }
|
|
1244
|
+
.oc-live { gap: 6px; }
|
|
1245
|
+
.oc-live-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--text-3); }
|
|
1246
|
+
.oc-live.on .oc-live-dot { background: var(--ok); }
|
|
1247
|
+
.org-sending { font-size: var(--fs-xs); color: var(--text-2); overflow-wrap: anywhere; }
|
|
1248
|
+
.org-send.k-task { color: var(--fx-task); } .org-send.k-report { color: var(--fx-report); } .org-send.k-broadcast { color: var(--fx-broadcast); }
|
|
1249
|
+
.org-send.k-send, .org-send.k-query, .org-send.k-update { color: var(--fx-send); }
|
|
1250
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1251
|
+
.oc-flow.ph-live { animation: none; stroke-dasharray: none; }
|
|
1252
|
+
.oc-flow.ph-fading { animation: none; opacity: 0.4; }
|
|
1253
|
+
.oc-pulse { display: none; }
|
|
1254
|
+
}
|
|
1255
|
+
@media (max-width: 640px) {
|
|
1256
|
+
.oc-stage { height: calc(100dvh - 248px); min-height: 340px; }
|
|
1257
|
+
.oc-find { flex: 1 1 150px; }
|
|
1258
|
+
.oc-tools { margin-left: 0; width: 100%; flex-wrap: nowrap; }
|
|
1259
|
+
.oc-mini { display: none; }
|
|
1260
|
+
.oc-legend { max-width: calc(100% - 24px); gap: 2px var(--s-2); }
|
|
1261
|
+
}
|
|
1125
1262
|
/* A phone: Fleet's five tabs fit when only the open one shows its label (the others keep theirs for screen readers). */
|
|
1126
1263
|
@media (max-width: 479px) {
|
|
1127
1264
|
.p-fleet .seg-item:not(.active) > span, .p-settings .seg-item:not(.active) > span { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
|
|
@@ -1200,12 +1337,43 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
|
|
|
1200
1337
|
font-weight: var(--fw-medium); white-space: nowrap; }
|
|
1201
1338
|
|
|
1202
1339
|
/* #1629 r4 §1 / r4.1 Q3 (a): the identity card (identity-card.js). The size is a class token; its heights are here
|
|
1203
|
-
(S 160 / M 240 / L 360 px, on a phone 120 / 180 / 240)
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
.id-card
|
|
1208
|
-
.id-
|
|
1340
|
+
(S 160 / M 240 / L 360 px, on a phone 120 / 180 / 240). Never a style attribute.
|
|
1341
|
+
#2038: the banner's height follows the card's width — S 4:1, M 3:1, L 2.25:1 — between a floor and a ceiling per size,
|
|
1342
|
+
so it is never a strip across a wide chat and never a wall on a phone; the picture fills it, cropped about its middle.
|
|
1343
|
+
The chat header's card (card-c) follows the same size choice (card-c bs-<size>). */
|
|
1344
|
+
.id-card { --id-feather: linear-gradient(to right, transparent 0, #000 7%, #000 93%, transparent 100%); --id-h: 240px; --id-ar: 3 / 1; --id-bmin: 140px; --id-bmax: 240px; margin: 0 0 var(--s-4); border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface); overflow: hidden; }
|
|
1345
|
+
.id-card.card-s { --id-h: 160px; --id-ar: 4 / 1; --id-bmin: 100px; --id-bmax: 160px; }
|
|
1346
|
+
.id-card.card-l { --id-h: 360px; --id-ar: 9 / 4; --id-bmin: 200px; --id-bmax: 360px; }
|
|
1347
|
+
.id-card.card-c { --id-h: 96px; --id-bmin: 120px; --id-bmax: 200px; margin: 0; border: 0; border-radius: 0; background: none; }
|
|
1348
|
+
.id-card.card-c.bs-s { --id-ar: 4 / 1; --id-bmin: 96px; --id-bmax: 150px; }
|
|
1349
|
+
.id-card.card-c.bs-l { --id-ar: 9 / 4; --id-bmin: 150px; --id-bmax: 220px; }
|
|
1350
|
+
/* #2086: the banner at its focus (--id-focus, set per card), never wider than 5 : 1 — its width is capped at five times its
|
|
1351
|
+
size's tallest height, so the band shown everywhere is (picture aspect ÷ 5) of its height. Beyond that it is centred
|
|
1352
|
+
over a blurred copy of itself. */
|
|
1353
|
+
.id-banner-box { position: relative; overflow: hidden; container: idbanner / inline-size; }
|
|
1354
|
+
/* #2089 review: and never narrower than 9 : 4 — its height never more than its box's width ÷ 2.25 (a size's floor yields
|
|
1355
|
+
to it on a narrow column: a wide sidebar, the usage rail), so the promised safe area holds on every layout. */
|
|
1356
|
+
.id-banner { position: relative; display: block; width: 100%; max-width: calc(var(--id-bmax) * 5); margin-inline: auto; height: auto; aspect-ratio: var(--id-ar);
|
|
1357
|
+
min-height: min(var(--id-bmin), calc(100cqw / 2.25)); max-height: min(var(--id-bmax), calc(100cqw / 2.25)); object-fit: cover; object-position: 50% var(--id-focus, 50%); }
|
|
1358
|
+
/* #2086: Edit profile's banner focus — the picture whole, the band every screen shows (dashed, outside it shaded) and a phone's. */
|
|
1359
|
+
.bf-wrap { position: relative; max-width: 440px; overflow: hidden; border-radius: var(--r-sm); }
|
|
1360
|
+
.bf-pic { display: block; width: 100%; height: auto; }
|
|
1361
|
+
.bf-band { position: absolute; pointer-events: none; box-sizing: border-box; }
|
|
1362
|
+
.bf-band span { position: absolute; right: 4px; top: 2px; font-size: var(--fs-xs); font-weight: var(--fw-semibold); color: #fff; text-shadow: 0 1px 2px #000, 0 0 4px #000; }
|
|
1363
|
+
.bf-safe { border: 2px dashed #fff; box-shadow: 0 0 0 999px rgba(0, 0, 0, .45); }
|
|
1364
|
+
/* The phone's band over the shade, in its own colour (its label at its own top-left, never in the shaded part). */
|
|
1365
|
+
.bf-phone { z-index: 1; border: 2px solid #ffd54a; }
|
|
1366
|
+
.bf-phone span { right: auto; left: 8px; top: 5px; color: #ffd54a; }
|
|
1367
|
+
.bf-field input[type=range] { width: 100%; max-width: 440px; }
|
|
1368
|
+
.id-banner-fill { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 50% var(--id-focus, 50%); filter: blur(24px) saturate(0.75) brightness(0.7); transform: scale(1.4); opacity: .9; pointer-events: none; }
|
|
1369
|
+
/* Wider than the banner's cap (5 × the size's tallest height): its sides feather into the fill, so the sharp centre
|
|
1370
|
+
leads without a seam. One threshold per size (a container query cannot read a custom property). */
|
|
1371
|
+
@container idbanner (min-width: 750px) { .id-card.card-c.bs-s .id-banner { -webkit-mask-image: var(--id-feather); mask-image: var(--id-feather); } }
|
|
1372
|
+
@container idbanner (min-width: 800px) { .id-card.card-s .id-banner { -webkit-mask-image: var(--id-feather); mask-image: var(--id-feather); } }
|
|
1373
|
+
@container idbanner (min-width: 1000px) { .id-card.card-c:not(.bs-s):not(.bs-l) .id-banner { -webkit-mask-image: var(--id-feather); mask-image: var(--id-feather); } }
|
|
1374
|
+
@container idbanner (min-width: 1100px) { .id-card.card-c.bs-l .id-banner { -webkit-mask-image: var(--id-feather); mask-image: var(--id-feather); } }
|
|
1375
|
+
@container idbanner (min-width: 1200px) { .id-card:not(.card-s):not(.card-l):not(.card-c) .id-banner { -webkit-mask-image: var(--id-feather); mask-image: var(--id-feather); } }
|
|
1376
|
+
@container idbanner (min-width: 1800px) { .id-card.card-l .id-banner { -webkit-mask-image: var(--id-feather); mask-image: var(--id-feather); } }
|
|
1209
1377
|
.id-row { display: flex; align-items: flex-start; gap: var(--s-3); padding: var(--s-3) var(--s-4); }
|
|
1210
1378
|
.id-portrait { display: block; flex-shrink: 0; height: var(--id-h); width: auto; max-width: min(calc(var(--id-h) * 0.75), 100%); object-fit: cover; border-radius: var(--r-sm); }
|
|
1211
1379
|
.id-text { flex: 1; min-width: 0; }
|
|
@@ -1263,9 +1431,12 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
|
|
|
1263
1431
|
.chat-card-toggle { display: flex; align-items: center; gap: var(--s-2); width: 100%; min-height: 32px; padding: 0 var(--s-4); border: 0; background: none; color: var(--text-2); font: inherit; font-size: var(--fs-xs); text-align: left; cursor: pointer; }
|
|
1264
1432
|
.chat-card-peek { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-3); }
|
|
1265
1433
|
@media (max-width: 479px) {
|
|
1266
|
-
.id-card.card-s { --id-h: 120px; }
|
|
1267
|
-
.id-card.card-m { --id-h: 180px; }
|
|
1268
|
-
.id-card.card-l { --id-h: 240px; }
|
|
1434
|
+
.id-card.card-s { --id-h: 120px; --id-bmin: 80px; --id-bmax: 120px; }
|
|
1435
|
+
.id-card.card-m { --id-h: 180px; --id-bmin: 100px; --id-bmax: 150px; }
|
|
1436
|
+
.id-card.card-l { --id-h: 240px; --id-bmin: 120px; --id-bmax: 200px; }
|
|
1437
|
+
.id-card.card-c { --id-bmin: 100px; --id-bmax: 140px; }
|
|
1438
|
+
.id-card.card-c.bs-s { --id-bmin: 80px; --id-bmax: 110px; }
|
|
1439
|
+
.id-card.card-c.bs-l { --id-bmin: 110px; --id-bmax: 150px; }
|
|
1269
1440
|
.id-row { flex-wrap: wrap; }
|
|
1270
1441
|
.id-text { flex-basis: 100%; }
|
|
1271
1442
|
}
|
|
@@ -1304,6 +1475,10 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
|
|
|
1304
1475
|
.s-fx-owner-row{display:flex;align-items:end;flex-wrap:wrap;gap:var(--s-2);margin-top:var(--s-2)}
|
|
1305
1476
|
.s-fx-owner-row>label{display:flex;flex-direction:column;gap:var(--s-1);min-width:0;flex:1}
|
|
1306
1477
|
|
|
1478
|
+
/* #2044: the effect frame's document declares no colour scheme (light). An iframe whose own colour scheme differs
|
|
1479
|
+
from its document's gets an opaque backdrop, so under the dark theme the frame hid the whole chat: keep them equal. */
|
|
1480
|
+
.agend-effect-frame { color-scheme: normal !important; }
|
|
1481
|
+
|
|
1307
1482
|
/* #1629: host-created static poster IMG; geometry and visibility belong to the trusted CSSOM binding. */
|
|
1308
1483
|
.agend-effect-poster { object-fit: cover !important; object-position: center !important; user-select: none; }
|
|
1309
1484
|
|
|
@@ -13,6 +13,8 @@ register("card", {
|
|
|
13
13
|
byAgent: "set by the agent",
|
|
14
14
|
show: "Show card",
|
|
15
15
|
hide: "Hide card",
|
|
16
|
+
bannerSafe: "Every screen",
|
|
17
|
+
bannerPhone: "Phone · M card",
|
|
16
18
|
},
|
|
17
19
|
"zh-TW": {
|
|
18
20
|
label: "{0} 的身分卡",
|
|
@@ -24,5 +26,7 @@ register("card", {
|
|
|
24
26
|
byAgent: "由 agent 設定",
|
|
25
27
|
show: "顯示卡片",
|
|
26
28
|
hide: "隱藏卡片",
|
|
29
|
+
bannerSafe: "所有螢幕",
|
|
30
|
+
bannerPhone: "手機 · 中卡片",
|
|
27
31
|
},
|
|
28
32
|
});
|
|
@@ -98,7 +98,7 @@ export function createAppEffectRuntime({ window: host, current, observe, onChang
|
|
|
98
98
|
if (!registry.ok) throw Error("runtime_unavailable");
|
|
99
99
|
trial = make("trialSource", createEffectTrialSource)({ window: host, current: () => {
|
|
100
100
|
if (!alive()) return false; const v = observe(), c = v.context;
|
|
101
|
-
return alive() &&
|
|
101
|
+
return alive() && c.enabled === true && c.show === true && c.trialReady === true
|
|
102
102
|
&& c.safe === false && c.publicLink === false && c.reducedMotion === false && c.saveData === false && c.hidden === false;
|
|
103
103
|
}, ownerCurrent: (s, name) => sourceAlive(s) && s.name === name,
|
|
104
104
|
stock: () => registry.frameRoot ? make("stock", stockEffectTrust)(host, { frameRoot: registry.frameRoot, ownerCss: observe().ownerCss }) : null,
|
|
@@ -30,16 +30,21 @@ function snapshotRender(raw) {
|
|
|
30
30
|
return { code: raw.code, params: { ...params }, instance: raw.instance, width: raw.width, height: raw.height, dpr: raw.dpr, assets, wants: [...raw.wants] };
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/** Listener transport only; this does not grant HTML-preview or ordinary-effect consent. */
|
|
34
|
+
export function effectTransportReady(host, origin, boot) {
|
|
35
|
+
try {
|
|
36
|
+
const target = new URL(origin), page = new URL(host.location.origin);
|
|
37
|
+
return origin === target.origin && ["http:", "https:"].includes(target.protocol) && ["http:", "https:"].includes(page.protocol)
|
|
38
|
+
&& target.origin !== page.origin && !(page.protocol === "https:" && target.protocol !== "https:") && typeof boot === "string" && /^[0-9a-f]{32}$/.test(boot);
|
|
39
|
+
} catch { return false; }
|
|
40
|
+
}
|
|
41
|
+
|
|
33
42
|
/**
|
|
34
43
|
* All options come from the host, never from an agent event or manifest origin. The registry supplies an admitted
|
|
35
44
|
* root and geometry in a later integration. This primitive alone does not implement caps, clipping or profiling.
|
|
36
45
|
*/
|
|
37
46
|
export function createEffectFrame({ window: host, root, origin, boot, render, clock = () => performance.now(), onReady = () => {}, onRemove = () => {} } = {}) {
|
|
38
|
-
|
|
39
|
-
try { url = new URL(origin); } catch { return { ok: false, reason: "preview_unknown" }; }
|
|
40
|
-
if (origin !== url.origin || !["http:", "https:"].includes(url.protocol) || typeof boot !== "string" || !/^[0-9a-f]{32}$/.test(boot)) return { ok: false, reason: "preview_unknown" };
|
|
41
|
-
let hostOrigin; try { hostOrigin = new URL(host.location.origin); } catch { return { ok: false, reason: "preview_unknown" }; }
|
|
42
|
-
if (!["http:", "https:"].includes(hostOrigin.protocol) || origin === hostOrigin.origin || hostOrigin.protocol === "https:" && url.protocol !== "https:") return { ok: false, reason: "preview_unknown" };
|
|
47
|
+
if (!effectTransportReady(host, origin, boot)) return { ok: false, reason: "preview_unknown" };
|
|
43
48
|
const payload = snapshotRender(render);
|
|
44
49
|
if (!payload || !host || !root || root.ownerDocument !== host.document || typeof root.append !== "function" || typeof host.MessageChannel !== "function" || typeof clock !== "function" || typeof onReady !== "function" || typeof onRemove !== "function") return { ok: false, reason: "unsupported" };
|
|
45
50
|
let mounted;
|
|
@@ -70,36 +70,51 @@ export function createEffectGeometry({ window: host } = {}) {
|
|
|
70
70
|
frame.remove();
|
|
71
71
|
return { ok: false, reason: "geometry_unknown" };
|
|
72
72
|
}
|
|
73
|
-
let dead = false;
|
|
73
|
+
let dead = false, shown = false;
|
|
74
74
|
const hide = () => {
|
|
75
|
+
shown = false;
|
|
75
76
|
try {
|
|
76
77
|
rule.style.setProperty("visibility", "hidden", "important");
|
|
77
|
-
if (frame.isConnected === true && host.getComputedStyle(frame).visibility !== "hidden") frame.remove();
|
|
78
|
-
|
|
78
|
+
if (frame.isConnected === true && host.getComputedStyle(frame).visibility !== "hidden") { frame.remove(); return false; }
|
|
79
|
+
return true;
|
|
80
|
+
} catch { frame.remove(); return false; }
|
|
79
81
|
};
|
|
80
82
|
const handle = { ok: true, hide,
|
|
81
83
|
/** Same-turn host paint snapshot is mandatory, including behind/human placements. No DOM fallback. */
|
|
82
84
|
sync(rect, stock) {
|
|
83
|
-
hide();
|
|
85
|
+
const refuse = () => { hide(); return false; };
|
|
84
86
|
const proof = effectStockSnapshot(stock);
|
|
85
|
-
if (!effectStockAllows(proof, { layer, by })) return
|
|
86
|
-
if (dead || disposed || !hostEffectSpace(host, frame) || !rect || ![rect.x, rect.y, rect.width, rect.height].every(number) || ![rect.x, rect.y, rect.width, rect.height].every(Number.isInteger) || rect.width < 1 || rect.height < 1 || rect.width > 4096 || rect.height > 4096 || rect.width * rect.height > 8388608)
|
|
87
|
+
if (!effectStockAllows(proof, { layer, by })) return refuse();
|
|
88
|
+
if (dead || disposed || !hostEffectSpace(host, frame) || !rect || ![rect.x, rect.y, rect.width, rect.height].every(number) || ![rect.x, rect.y, rect.width, rect.height].every(Number.isInteger) || rect.width < 1 || rect.height < 1 || rect.width > 4096 || rect.height > 4096 || rect.width * rect.height > 8388608) return refuse();
|
|
87
89
|
let path = "none";
|
|
88
90
|
if (layer === "front" && by !== "human") {
|
|
89
91
|
const clipped = effectTrustClip(rect, proof.rects);
|
|
90
|
-
if (!clipped.ok) return
|
|
92
|
+
if (!clipped.ok) return refuse();
|
|
91
93
|
path = clipped.path;
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
try {
|
|
97
|
+
const matches = visibility => {
|
|
98
|
+
const style = host.getComputedStyle(frame), actual = frame.getBoundingClientRect();
|
|
99
|
+
return style.position === "fixed" && style.pointerEvents === "none" && style.visibility === visibility
|
|
100
|
+
&& style.opacity === (layer === "front" && by !== "human" ? "0.85" : "1") && style.zIndex === (layer === "front" ? "20" : "0")
|
|
101
|
+
&& sameClip(path, rule.style.getPropertyValue("clip-path")) && sameClip(path, style.clipPath)
|
|
102
|
+
&& ["x", "y", "width", "height"].every(key => number(actual[key]) && Math.abs(actual[key] - rect[key]) < 0.02)
|
|
103
|
+
&& frame.isConnected === true && frame.ownerDocument === host.document;
|
|
104
|
+
};
|
|
105
|
+
// A fresh proof and actual viewport/clip are still checked every frame. Rewriting an
|
|
106
|
+
// already verified presentation makes WebKit repeatedly invalidate the iframe's paint.
|
|
107
|
+
if (shown && matches("visible")) return true;
|
|
108
|
+
// Changed or unproven geometry is hidden before writes and verified before reveal.
|
|
109
|
+
if (!hide()) return false;
|
|
95
110
|
for (const key of ["x", "y", "width", "height"]) rule.style.setProperty(key === "x" ? "left" : key === "y" ? "top" : key, `${rect[key]}px`, "important");
|
|
96
111
|
rule.style.setProperty("clip-path", path, "important");
|
|
97
112
|
if (!sameClip(path, rule.style.getPropertyValue("clip-path"))) return false;
|
|
98
113
|
// The stylesheet can lose the cascade to another host rule. Verify the actual viewport and clip
|
|
99
114
|
// before revealing: holes expressed in planned coordinates cannot protect a transformed frame.
|
|
100
|
-
|
|
101
|
-
if (style.position !== "fixed" || style.pointerEvents !== "none" || style.visibility !== "hidden" || style.opacity !== (layer === "front" && by !== "human" ? "0.85" : "1") || style.zIndex !== (layer === "front" ? "20" : "0") || !sameClip(path, style.clipPath) || !["x", "y", "width", "height"].every(key => number(actual[key]) && Math.abs(actual[key] - rect[key]) < 0.02)) return false;
|
|
115
|
+
if (!matches("hidden")) return false;
|
|
102
116
|
rule.style.setProperty("visibility", "visible", "important");
|
|
117
|
+
shown = true;
|
|
103
118
|
return true;
|
|
104
119
|
} catch { hide(); return false; }
|
|
105
120
|
},
|
|
@@ -74,7 +74,7 @@ export function allocateEffectFrames(descriptors, context) {
|
|
|
74
74
|
else if (context.saveData === true) global = "save_data";
|
|
75
75
|
else if (context.hidden === true) global = "hidden";
|
|
76
76
|
else if (context.enabled !== true || context.show !== true) global = "disabled";
|
|
77
|
-
|
|
77
|
+
|
|
78
78
|
const candidates = [];
|
|
79
79
|
for (let i = 0; i < descriptors.length; i++) {
|
|
80
80
|
const d = descriptors[i], r = result[i];
|
|
@@ -87,6 +87,7 @@ export function allocateEffectFrames(descriptors, context) {
|
|
|
87
87
|
if (!owner && d.region !== "chat") { r.reason = "scope"; continue; }
|
|
88
88
|
if (d.region === "chat" && (!instanceName(d.instance) || d.instance !== context.openInstance)) { r.reason = "closed_chat"; continue; }
|
|
89
89
|
if (global) { r.reason = global; continue; }
|
|
90
|
+
if (trial ? context.trialReady !== true : context.previewReady !== true || context.optIn !== true) { r.reason = "preview_unavailable"; continue; }
|
|
90
91
|
if (d.paused === true) { r.reason = "paused"; continue; }
|
|
91
92
|
if (d.visible !== true) { r.reason = "offscreen"; continue; }
|
|
92
93
|
if (d.layer === "front" && context.modal === true && !trial) { r.reason = "modal"; continue; }
|
|
@@ -65,7 +65,7 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
|
|
|
65
65
|
finally { readingStages.delete(d.trialStage); }
|
|
66
66
|
return !dead && !blocked && revision === before && stage?.root === d.element ? stage : null;
|
|
67
67
|
};
|
|
68
|
-
const permits = (d, proof) => d.trialStage ? !!localStage(d, proof) : effectStockAllows(proof, d);
|
|
68
|
+
const permits = (d, proof) => eligible(d) && (d.trialStage ? !!localStage(d, proof) : effectStockAllows(proof, d));
|
|
69
69
|
const coverage = (d = null) => {
|
|
70
70
|
if (dead || blocked || readingStock) return null;
|
|
71
71
|
const before = revision; let proof = null; readingStock = true;
|
|
@@ -91,6 +91,26 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
|
|
|
91
91
|
const current = s => !dead && !blocked && slots.get(s.d.key) === s && s.live;
|
|
92
92
|
const tell = () => { if (dead) return; const state = view(), stamp = JSON.stringify(state); if (stamp === lastView) return; lastView = stamp; try { onChange(state); } catch { /* a display callback cannot admit code */ } };
|
|
93
93
|
const receipt = (fn, ...args) => { try { return Reflect.apply(fn, safety, args); } catch { return null; } };
|
|
94
|
+
// #2055: the history's own read names a denial; any other answer stays unknown (and denied).
|
|
95
|
+
const SAFETY_REASON = Object.freeze({ __proto__: null, off: "safety_off", paused: "paused", interrupted: "interrupted" });
|
|
96
|
+
// Display only: the sticky history state per descriptor, which outranks a waiting reason (a queue, a dialog, typing)
|
|
97
|
+
// because it is what Resume must answer. Admission, caps, latches and safety policy never read it.
|
|
98
|
+
const named = new Map();
|
|
99
|
+
// The read can re-enter (an update from a callback): only a pass still current, for the same device, may write.
|
|
100
|
+
const nameSafety = (d, dev = device()) => {
|
|
101
|
+
const proposed = revision;
|
|
102
|
+
const reason = d.trialStage ? null : SAFETY_REASON[receipt(safety.status, d.id, dev)?.state];
|
|
103
|
+
if (dead || proposed !== revision || dev !== device()) return false;
|
|
104
|
+
if (reason) named.set(d.key, { identity: signature(d), reason }); else named.delete(d.key);
|
|
105
|
+
return true;
|
|
106
|
+
};
|
|
107
|
+
// Display only: the closed global gate a row is waiting on, when the host knows it (else the caller's unknown).
|
|
108
|
+
const gateReason = () => {
|
|
109
|
+
if (!gatesKnown()) return null;
|
|
110
|
+
const c = context;
|
|
111
|
+
return c.safe ? "safe_mode" : c.publicLink ? "public_link" : c.reducedMotion ? "reduced_motion" : c.saveData ? "save_data"
|
|
112
|
+
: !(c.enabled && c.show) ? "disabled" : !(c.previewReady && c.optIn) ? "preview_unavailable" : null;
|
|
113
|
+
};
|
|
94
114
|
const clearMarker = (s, why) => {
|
|
95
115
|
if (!s.token) return true;
|
|
96
116
|
const token = s.token; s.token = null;
|
|
@@ -110,6 +130,7 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
|
|
|
110
130
|
paused.set(s.d.key, signature(s.d));
|
|
111
131
|
if (receipt(safety.pause, { id: s.d.id, device: s.device, reason: why, metrics: s.budget?.snapshot().metrics ?? null }) !== true) blocked = "safety_unknown";
|
|
112
132
|
}
|
|
133
|
+
if (count || why === "safety_revoked") nameSafety(s.d, s.device); // #2055: the third pause, or another tab's, says off now
|
|
113
134
|
clearMarker(s, why);
|
|
114
135
|
if (s.owner) { try { onRetire(s.owner, why); } catch { /* an observation cannot prevent physical cleanup */ } }
|
|
115
136
|
if (blocked) for (const other of [...slots.values()]) retire(other, blocked);
|
|
@@ -124,14 +145,25 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
|
|
|
124
145
|
return true;
|
|
125
146
|
}
|
|
126
147
|
function gatesKnown() { return context && FLAGS.every(k => typeof context[k] === "boolean") && (context.openInstance === null || typeof context.openInstance === "string" && context.openInstance.length >= 1 && context.openInstance.length <= 128); }
|
|
127
|
-
function
|
|
128
|
-
|
|
148
|
+
function consent(d) {
|
|
149
|
+
const trial = d?.trialStage && effectTrialStageIdentity(d.trialStage);
|
|
150
|
+
return trial ? context.trialReady === true : context.previewReady === true && context.optIn === true;
|
|
151
|
+
}
|
|
152
|
+
function eligible(d = null) {
|
|
153
|
+
return gatesKnown() && !context.safe && !context.publicLink && !context.reducedMotion && !context.saveData && context.enabled && context.show
|
|
154
|
+
&& (d ? consent(d) : context.previewReady && context.optIn || descriptors.some(x => x.trialStage && consent(x)));
|
|
155
|
+
}
|
|
156
|
+
const globalAllowed = (d = null) => eligible(d) && host.document.visibilityState === "visible";
|
|
129
157
|
const signature = d => d.identity;
|
|
158
|
+
// A frame stopped because its gates closed is recorded as gates_unknown; when the host knows which gate, say it.
|
|
159
|
+
const shown = why => (why === "gates_unknown" && !eligible() && gateReason()) || why;
|
|
130
160
|
function view() {
|
|
131
161
|
return descriptors.map(d => {
|
|
132
|
-
const r = results.get(d.key) ?? { verdict: "off", reason: blocked ?? "unknown", queuePos: null }, s = slots.get(d.key);
|
|
133
|
-
|
|
134
|
-
|
|
162
|
+
const r = results.get(d.key) ?? { verdict: "off", reason: blocked ?? (!eligible() && gateReason()) ?? "unknown", queuePos: null }, s = slots.get(d.key);
|
|
163
|
+
// #2055 one display precedence: a global latch, then the sticky safety history, then this page's own pause/failure.
|
|
164
|
+
const n = named.get(d.key), sticky = n && n.identity === signature(d) ? n.reason : null;
|
|
165
|
+
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,
|
|
166
|
+
quality: s?.budget?.snapshot().quality ?? null, reason: blocked ?? sticky ?? ((paused.get(d.key) === signature(d)) ? "paused" : failures.get(d.key)?.identity === signature(d) ? shown(failures.get(d.key).reason) : s && context?.hidden ? "hidden" : r.reason) };
|
|
135
167
|
});
|
|
136
168
|
}
|
|
137
169
|
function schedule() {
|
|
@@ -165,7 +197,9 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
|
|
|
165
197
|
const stock = eligible() ? coverage() : null;
|
|
166
198
|
if (blocked || !eligible() || !stock) {
|
|
167
199
|
if (!stopAll(blocked ?? "gates_unknown")) return;
|
|
168
|
-
results.clear();
|
|
200
|
+
results.clear();
|
|
201
|
+
if (!blocked) for (const d of descriptors) if (!nameSafety(d) || dead || proposal !== revision) return;
|
|
202
|
+
tell(); return;
|
|
169
203
|
}
|
|
170
204
|
// Hidden frames already owned may survive for <=10 s, but no new frame or load is admitted while hidden.
|
|
171
205
|
const ctx = { ...context, hidden: false };
|
|
@@ -176,6 +210,7 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
|
|
|
176
210
|
if (dead || proposal !== revision) return;
|
|
177
211
|
ds.push({ ...d, paused: !allowed || d.paused === true || paused.get(d.key) === signature(d) || failures.get(d.key)?.identity === signature(d) });
|
|
178
212
|
}
|
|
213
|
+
for (const d of descriptors) if (!nameSafety(d) || dead || proposal !== revision) return;
|
|
179
214
|
const allocation = allocateEffectFrames(ds, ctx); results.clear();
|
|
180
215
|
for (const r of allocation) {
|
|
181
216
|
const d = descriptors.find(d => d.key === r.key);
|
|
@@ -230,8 +265,14 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
|
|
|
230
265
|
if (slots.size >= max || loads.size >= max) break;
|
|
231
266
|
const permitted = receipt(safety.allowed, d.id, device());
|
|
232
267
|
if (proposal !== revision) return;
|
|
233
|
-
if (permitted !== true) {
|
|
234
|
-
|
|
268
|
+
if (permitted !== true) {
|
|
269
|
+
// #2055: say which denial it is. Only the safety history's own read names it; anything else stays unknown (still denied).
|
|
270
|
+
const st = receipt(safety.status, d.id, device());
|
|
271
|
+
if (proposal !== revision) return;
|
|
272
|
+
const why = SAFETY_REASON[st?.state] ?? "safety_unknown";
|
|
273
|
+
results.set(d.key, { verdict: "off", reason: why, queuePos: null }); continue;
|
|
274
|
+
}
|
|
275
|
+
if (slots.has(d.key) || slots.size >= max || loads.size >= max || !globalAllowed(d) || context.hidden || !coverage(d) || proposal !== revision || !descriptors.some(x => x.key === d.key && signature(x) === signature(d) && x.element === d.element)) break;
|
|
235
276
|
const rect = position(d, coverage(d));
|
|
236
277
|
if (dead || proposal !== revision) return;
|
|
237
278
|
if (!rect || d.element.ownerDocument !== host.document) { results.set(d.key, { verdict: "off", reason: "geometry_unknown", queuePos: null }); continue; }
|
|
@@ -254,7 +295,7 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
|
|
|
254
295
|
const acceptCompletion = () => {
|
|
255
296
|
if (!current(s)) { clearMarker(s, "superseded"); return false; }
|
|
256
297
|
if (!withinDeadline()) return false;
|
|
257
|
-
if (request.controller.signal.aborted || !globalAllowed() || context.hidden || !coverage(d)) {
|
|
298
|
+
if (request.controller.signal.aborted || !globalAllowed(d) || context.hidden || !coverage(d)) {
|
|
258
299
|
if (current(s)) retire(s, "admission_revoked"); else clearMarker(s, "superseded");
|
|
259
300
|
return false;
|
|
260
301
|
}
|
|
@@ -290,7 +331,7 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
|
|
|
290
331
|
s.budget = new EffectBudget({ device: s.device, baselineFps: baseline, clock: nativeClock, longTasksSupported });
|
|
291
332
|
const frame = createEffectFrame({ window: host, root: local?.root ?? root, origin, boot, clock: nativeClock,
|
|
292
333
|
render: { code: resource.code, params: resource.params, assets: resource.assets, wants: resource.wants, instance: d.instance, width: fresh.width, height: fresh.height, dpr: dpr(s.device) },
|
|
293
|
-
onReady() { if (!current(s)) return; if (!globalAllowed() || context.hidden || !coverage(d)) { retire(s, "hidden"); tell(); return; } const n = time(); if (n === null) { blocked = "clock_unavailable"; stopAll(blocked); tell(); return; } s.ready = true; s.readyAt = n; s.healthySince = n; publishEnv(s); schedule(); tell(); },
|
|
334
|
+
onReady() { if (!current(s)) return; if (!globalAllowed(d) || context.hidden || !coverage(d)) { retire(s, "hidden"); tell(); return; } const n = time(); if (n === null) { blocked = "clock_unavailable"; stopAll(blocked); tell(); return; } s.ready = true; s.readyAt = n; s.healthySince = n; publishEnv(s); schedule(); tell(); },
|
|
294
335
|
onRemove(why) { if (current(s)) { retire(s, why); tell(); } },
|
|
295
336
|
});
|
|
296
337
|
s.frame = frame;
|
|
@@ -316,7 +357,7 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
|
|
|
316
357
|
schedule(); tell();
|
|
317
358
|
}
|
|
318
359
|
function publishEnv(s, proof = coverage(s.d)) {
|
|
319
|
-
if (!s.ready || !current(s) || context.hidden || !globalAllowed() || !permits(s.d, proof)) return;
|
|
360
|
+
if (!s.ready || !current(s) || context.hidden || !globalAllowed(s.d) || !permits(s.d, proof)) return;
|
|
320
361
|
const e = effectEvent("env", { width: s.rect.width, height: s.rect.height, dpr: dpr(s.device), quality: s.budget.snapshot().quality }, null, time());
|
|
321
362
|
if (e) s.frame.send(e);
|
|
322
363
|
}
|
|
@@ -339,12 +380,17 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
|
|
|
339
380
|
if (changed) publishEnv(s, paint);
|
|
340
381
|
if (s.ready) s.budget.frame();
|
|
341
382
|
if (s.pointer) {
|
|
342
|
-
const packet = s.pointer;
|
|
383
|
+
const packet = s.pointer;
|
|
343
384
|
const n = time(), b = s.budget.snapshot();
|
|
344
|
-
if (n
|
|
385
|
+
if (n === null || n - packet.at > 100 || !permits(s.d, paint) || !pointerGate(paint) || !current(s) || !b.forward) {
|
|
386
|
+
if (s.pointer === packet) s.pointer = null;
|
|
387
|
+
} else if (s.lastPointer === null || n - s.lastPointer >= 1000 / b.pointerHz) {
|
|
388
|
+
if (s.pointer === packet) s.pointer = null;
|
|
345
389
|
const event = effectEvent("pointer", packet.fields, s.d.instance, packet.at);
|
|
346
390
|
if (event && s.frame.send(event)) s.lastPointer = n;
|
|
347
391
|
}
|
|
392
|
+
// Keep the latest fresh sample until the next eligible frame. An early rAF must
|
|
393
|
+
// not consume a click just because the preceding move used this rate interval.
|
|
348
394
|
}
|
|
349
395
|
}
|
|
350
396
|
if (dead || proposal !== revision) return;
|
|
@@ -401,7 +447,11 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
|
|
|
401
447
|
if (!globalAllowed() || context.hidden || context.modal || !proof || proof.front !== true) return false;
|
|
402
448
|
try {
|
|
403
449
|
const focused = host.document.activeElement;
|
|
404
|
-
|
|
450
|
+
// Native clicks on ordinary stock content focus its tabindex=-1 main ancestor.
|
|
451
|
+
// Recognize only that unique host root; unknown/editor focus remains a refusal.
|
|
452
|
+
const mains = focused?.tagName === "MAIN" ? host.document.querySelectorAll("#app > .shell > main#main.main[tabindex='-1']") : [];
|
|
453
|
+
const stockMain = focused?.isConnected === true && focused.ownerDocument === host.document && mains.length === 1 && mains[0] === focused;
|
|
454
|
+
return host.document.designMode !== "on" && !!focused && (["BODY", "BUTTON", "A"].includes(focused.tagName) || stockMain) && focused.isContentEditable === false && typeof focused.matches === "function" && !focused.matches("input,textarea,select,[contenteditable]:not([contenteditable='false'])");
|
|
405
455
|
} catch { return false; }
|
|
406
456
|
}
|
|
407
457
|
function pointer(event) {
|
|
@@ -416,7 +466,11 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
|
|
|
416
466
|
const fields = { kind, x: event.clientX - r.x, y: event.clientY - r.y, buttons: event.buttons };
|
|
417
467
|
if (fields.x < 0 || fields.y < 0 || fields.x > r.width || fields.y > r.height) continue;
|
|
418
468
|
if (kind === "wheel") { fields.dx = event.deltaX; fields.dy = event.deltaY; }
|
|
419
|
-
if (effectEvent("pointer", fields, s.d.instance, at))
|
|
469
|
+
if (effectEvent("pointer", fields, s.d.instance, at)) {
|
|
470
|
+
// One slot, with discrete input ahead of motion. A later move must not erase
|
|
471
|
+
// a waiting click; down/up/click may still coalesce to the latest discrete event.
|
|
472
|
+
if (kind !== "move" || !s.pointer || s.pointer.fields.kind === "move" || at - s.pointer.at > 100) s.pointer = { fields, at };
|
|
473
|
+
}
|
|
420
474
|
}
|
|
421
475
|
schedule();
|
|
422
476
|
}
|
|
@@ -456,7 +510,7 @@ export function createEffectRegistry({ window: host, origin, boot, load, safety,
|
|
|
456
510
|
descriptors = ds.map(snapshotDescriptor).filter(Boolean);
|
|
457
511
|
for (const [key, identity] of paused) if (!descriptors.some(d => d.key === key && signature(d) === identity)) paused.delete(key);
|
|
458
512
|
for (const [key, f] of failures) if (!descriptors.some(d => d.key === key && signature(d) === f.identity)) failures.delete(key);
|
|
459
|
-
context = Object.fromEntries([...FLAGS, "openInstance"].map(k => [k, ctx[k]]));
|
|
513
|
+
context = Object.fromEntries([...FLAGS, "trialReady", "openInstance"].map(k => [k, ctx[k]]));
|
|
460
514
|
reconcile(); return view();
|
|
461
515
|
},
|
|
462
516
|
snapshot: view,
|