@songsid/agend 2.3.0-beta.2 → 2.3.0-beta.3
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.js +10 -5
- 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 +5 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/mcp-tools.js +4 -0
- package/dist/channel/mcp-tools.js.map +1 -1
- 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/delivery-outbox.js +5 -0
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/fleet-manager.d.ts +68 -3
- package/dist/fleet-manager.js +247 -41
- 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 +39 -4
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +398 -0
- package/dist/locale.js +6 -4
- 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 +88 -0
- package/dist/outbound-schemas.js +50 -0
- 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/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/types.d.ts +1 -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 +13 -5
- 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 +8 -2
- package/dist/ui/panel-details.js +2 -1
- package/dist/ui/panel-fleet.js +17 -5
- package/dist/ui/panel-settings.js +3 -2
- package/dist/ui/preview.js +3 -2
- package/dist/ui/settings-appearance.js +34 -18
- package/dist/ui/settings-dialogs.js +3 -6
- 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 +34 -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 +152 -10
- 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-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 +5 -2
- 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 +2 -2
- package/dist/ui/shared/view-strings.js +16 -0
- package/dist/view-api.d.ts +21 -0
- package/dist/view-api.js +28 -0
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +5 -6
- package/dist/web-api.js +57 -10
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-view.d.ts +10 -0
- package/dist/web-appearance-view.js +77 -8
- package/dist/web-appearance-view.js.map +1 -1
- package/dist/web-auth.js +1 -1
- package/dist/web-auth.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 +67 -0
- package/dist/web-guest-access.js +126 -0
- package/dist/web-guest-access.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-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-session.d.ts +15 -7
- package/dist/web-session.js +25 -8
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-tools.d.ts +69 -0
- package/dist/web-settings-tools.js +247 -0
- package/dist/web-settings-tools.js.map +1 -0
- 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; }
|
|
@@ -725,6 +746,7 @@ html.lb-open .scroller { overflow: hidden; }
|
|
|
725
746
|
.sb-open .sidebar { transform: none; visibility: visible; }
|
|
726
747
|
.sb-open .scrim { display: block; position: fixed; inset: 0; background: var(--scrim); z-index: 39; }
|
|
727
748
|
.panel-head .sb-open { display: inline-flex; }
|
|
749
|
+
.panel-head .sb-new { display: none; }
|
|
728
750
|
.only-narrow { display: inline-flex; }
|
|
729
751
|
.thread { padding: var(--s-3) var(--s-3) var(--s-2); gap: var(--s-5); }
|
|
730
752
|
.col { padding: var(--s-3) var(--s-3) var(--s-8); }
|
|
@@ -1122,6 +1144,114 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
|
|
|
1122
1144
|
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
1145
|
.org-link { min-height: var(--tap); }
|
|
1124
1146
|
}
|
|
1147
|
+
/* ── #1918: the org chart as a canvas (fleet-canvas.js). SVG with classes only: per-card values are SVG attributes, the
|
|
1148
|
+
view's transform one attribute on .oc-world (#1300). The stage fills the panel's height; touch gestures are the
|
|
1149
|
+
canvas's own (touch-action: none on it only), the page around it still scrolls. ── */
|
|
1150
|
+
.oc-bar { flex-wrap: wrap; row-gap: var(--s-2); }
|
|
1151
|
+
.oc-tools { margin-left: auto; display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); }
|
|
1152
|
+
.oc-find { flex: 0 1 260px; min-width: 150px; position: relative; }
|
|
1153
|
+
.oc-find input { height: 32px; padding-right: 72px; }
|
|
1154
|
+
.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; }
|
|
1155
|
+
.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); }
|
|
1156
|
+
.oc-svg { display: block; width: 100%; height: 100%; touch-action: none; cursor: grab; user-select: none; -webkit-user-select: none; }
|
|
1157
|
+
.oc-svg:active { cursor: grabbing; }
|
|
1158
|
+
.oc-svg:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
|
|
1159
|
+
.oc-tree path { fill: none; stroke: var(--border-strong); stroke-width: 1.25; }
|
|
1160
|
+
.oc-box { fill: var(--surface); stroke: var(--border); stroke-width: 1; }
|
|
1161
|
+
.oc-team.loose .oc-box { stroke-dasharray: 5 4; }
|
|
1162
|
+
.oc-box.match { stroke: var(--focus); stroke-width: 2.5; }
|
|
1163
|
+
.oc-head { fill: var(--surface-2); }
|
|
1164
|
+
.oc-hit { fill: transparent; }
|
|
1165
|
+
.oc-toggle:not(.empty) { cursor: pointer; }
|
|
1166
|
+
.oc-toggle:focus { outline: none; }
|
|
1167
|
+
.oc-toggle:focus-visible .oc-hit { stroke: var(--focus); stroke-width: 2; }
|
|
1168
|
+
.oc-caret { fill: var(--text-2); font-size: 12px; }
|
|
1169
|
+
.oc-team-name { fill: var(--text); font-size: 13px; font-weight: 600; }
|
|
1170
|
+
.oc-team-meta { fill: var(--text-3); font-size: 11px; }
|
|
1171
|
+
.oc-team-sum { fill: var(--text-2); font-size: 11.5px; }
|
|
1172
|
+
.oc-team.dim { opacity: 0.55; }
|
|
1173
|
+
.oc-node.dim { opacity: 0.4; }
|
|
1174
|
+
.oc-node { cursor: pointer; }
|
|
1175
|
+
.oc-node[role="img"] { cursor: default; }
|
|
1176
|
+
.oc-node:focus { outline: none; }
|
|
1177
|
+
.oc-card { fill: var(--bg); stroke: var(--border); stroke-width: 1; }
|
|
1178
|
+
.oc-node.lead .oc-card { fill: var(--surface); stroke: var(--accent); stroke-width: 1.5; }
|
|
1179
|
+
.oc-node:hover .oc-card { stroke: var(--border-strong); }
|
|
1180
|
+
.oc-node:focus-visible .oc-card, .oc-node.match .oc-card, .oc-node.match:hover .oc-card { stroke: var(--focus); stroke-width: 2.5; }
|
|
1181
|
+
.oc-node.st-missing { opacity: 0.7; }
|
|
1182
|
+
.oc-av { fill: var(--surface-2); stroke: var(--surface); stroke-width: 2; }
|
|
1183
|
+
.oc-node.idc.id-ring .oc-av { stroke: var(--id); stroke-width: 2.5; }
|
|
1184
|
+
.oc-idbar { display: none; }
|
|
1185
|
+
.oc-node.idc.id-bar .oc-idbar { display: inline; fill: var(--id); }
|
|
1186
|
+
.oc-face.emoji .oc-av { fill: transparent; }
|
|
1187
|
+
.oc-av-t { fill: var(--text-2); font-size: 12px; font-weight: 600; }
|
|
1188
|
+
.oc-face.emoji .oc-av-t { font-size: 18px; font-weight: normal; }
|
|
1189
|
+
.oc-name { fill: var(--text); font-size: 13.5px; font-weight: 600; }
|
|
1190
|
+
.oc-node.lead .oc-name { font-size: 15px; }
|
|
1191
|
+
.oc-node.idc.id-name .oc-name { fill: var(--id-name); }
|
|
1192
|
+
.oc-sub { font-size: 12px; }
|
|
1193
|
+
.oc-st { fill: var(--text-2); }
|
|
1194
|
+
.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); }
|
|
1195
|
+
.oc-dot { fill: var(--text-3); stroke: var(--bg); stroke-width: 2; }
|
|
1196
|
+
.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); }
|
|
1197
|
+
.oc-dot.st-paused { fill: var(--bg); stroke: var(--text-3); stroke-width: 1.5; }
|
|
1198
|
+
.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); }
|
|
1199
|
+
.oc-zoom .icon-btn { width: 36px; height: 34px; border-radius: 0; font-size: 18px; }
|
|
1200
|
+
.oc-zoom .icon-btn + .icon-btn { border-top: 1px solid var(--border); }
|
|
1201
|
+
.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; }
|
|
1202
|
+
.oc-mini-team { fill: var(--surface-2); stroke: var(--border-strong); stroke-width: 6; }
|
|
1203
|
+
.oc-mini-team.needs { fill: var(--warn-bg); stroke: var(--warn); }
|
|
1204
|
+
.oc-mini-top { fill: var(--accent-dim); stroke: var(--accent); stroke-width: 6; }
|
|
1205
|
+
.oc-mini-team.match { stroke: var(--focus); stroke-width: 14; }
|
|
1206
|
+
.oc-mini-hit { fill: var(--focus); }
|
|
1207
|
+
.oc-mini-view { fill: none; stroke: var(--focus); stroke-width: 10; }
|
|
1208
|
+
/* #1918 phase 3: live traffic. Kind hues kept apart from the state colours (ok = working/idle, warn = needs you, danger). */
|
|
1209
|
+
.oc { --fx-task: #b794f6; --fx-send: #2dd4bf; --fx-report: #f472b6; --fx-broadcast: #fb923c; --fx-team: #cbd5e1; }
|
|
1210
|
+
@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; } }
|
|
1211
|
+
:root[data-theme="light"] .oc { --fx-task: #7c3aed; --fx-send: #0f766e; --fx-report: #be185d; --fx-broadcast: #c2410c; --fx-team: #475569; }
|
|
1212
|
+
.oc-flow { fill: none; stroke-width: 2.5; stroke-linecap: round; }
|
|
1213
|
+
.oc-flow-halo { fill: none; stroke: var(--bg); stroke-width: 6; stroke-linecap: round; opacity: 0.85; }
|
|
1214
|
+
.oc-flow.h-task { stroke: var(--fx-task); } .oc-flow.h-send { stroke: var(--fx-send); } .oc-flow.h-report { stroke: var(--fx-report); }
|
|
1215
|
+
.oc-flow.h-broadcast { stroke: var(--fx-broadcast); } .oc-flow.h-team { stroke: var(--fx-team); stroke-width: 3.5; }
|
|
1216
|
+
.oc-flow.ph-live { stroke-dasharray: 7 6; animation: oc-dash 0.9s linear infinite; }
|
|
1217
|
+
.oc-flow.ph-queued { stroke-dasharray: 2 5; opacity: 0.8; }
|
|
1218
|
+
.oc-flow.ph-fading { stroke: var(--text-3); animation: oc-fade 3s ease-out forwards; }
|
|
1219
|
+
.oc-ah.h-task { fill: var(--fx-task); } .oc-ah.h-send { fill: var(--fx-send); } .oc-ah.h-report { fill: var(--fx-report); }
|
|
1220
|
+
.oc-ah.h-broadcast { fill: var(--fx-broadcast); } .oc-ah.h-team { fill: var(--fx-team); } .oc-ah.h-fading { fill: var(--text-3); }
|
|
1221
|
+
.oc-pulse.h-task { fill: var(--fx-task); } .oc-pulse.h-send { fill: var(--fx-send); } .oc-pulse.h-report { fill: var(--fx-report); }
|
|
1222
|
+
.oc-pulse.h-broadcast { fill: var(--fx-broadcast); } .oc-pulse.h-team { fill: var(--fx-team); }
|
|
1223
|
+
.oc-fl.dim { opacity: 0.18; }
|
|
1224
|
+
.oc-badge rect { fill: var(--surface-2); stroke: var(--border); } .oc-badge text { fill: var(--text-2); font-size: 10.5px; }
|
|
1225
|
+
@keyframes oc-dash { to { stroke-dashoffset: -26; } }
|
|
1226
|
+
@keyframes oc-fade { from { opacity: 0.8; } to { opacity: 0.15; } }
|
|
1227
|
+
.oc-legend { position: absolute; left: var(--s-3); bottom: var(--s-3); max-width: calc(100% - 220px);
|
|
1228
|
+
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; }
|
|
1229
|
+
.oc-legend summary { cursor: pointer; font-weight: var(--fw-semibold); color: var(--text); }
|
|
1230
|
+
.oc-legend[open] summary { margin-bottom: 4px; }
|
|
1231
|
+
.oc-lg-rows { display: flex; flex-wrap: wrap; gap: var(--s-1) var(--s-3); }
|
|
1232
|
+
.oc-lg { display: inline-flex; align-items: center; gap: 6px; }
|
|
1233
|
+
.oc-sw { width: 18px; height: 3px; border-radius: 2px; display: inline-block; background: var(--text-3); }
|
|
1234
|
+
.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); }
|
|
1235
|
+
.oc-sw.ph-queued { background: repeating-linear-gradient(90deg, var(--text-2) 0 3px, transparent 3px 7px); }
|
|
1236
|
+
.oc-sw.ph-fading { opacity: 0.5; }
|
|
1237
|
+
.oc-live { gap: 6px; }
|
|
1238
|
+
.oc-live-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--text-3); }
|
|
1239
|
+
.oc-live.on .oc-live-dot { background: var(--ok); }
|
|
1240
|
+
.org-sending { font-size: var(--fs-xs); color: var(--text-2); overflow-wrap: anywhere; }
|
|
1241
|
+
.org-send.k-task { color: var(--fx-task); } .org-send.k-report { color: var(--fx-report); } .org-send.k-broadcast { color: var(--fx-broadcast); }
|
|
1242
|
+
.org-send.k-send, .org-send.k-query, .org-send.k-update { color: var(--fx-send); }
|
|
1243
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1244
|
+
.oc-flow.ph-live { animation: none; stroke-dasharray: none; }
|
|
1245
|
+
.oc-flow.ph-fading { animation: none; opacity: 0.4; }
|
|
1246
|
+
.oc-pulse { display: none; }
|
|
1247
|
+
}
|
|
1248
|
+
@media (max-width: 640px) {
|
|
1249
|
+
.oc-stage { height: calc(100dvh - 248px); min-height: 340px; }
|
|
1250
|
+
.oc-find { flex: 1 1 150px; }
|
|
1251
|
+
.oc-tools { margin-left: 0; width: 100%; flex-wrap: nowrap; }
|
|
1252
|
+
.oc-mini { display: none; }
|
|
1253
|
+
.oc-legend { max-width: calc(100% - 24px); gap: 2px var(--s-2); }
|
|
1254
|
+
}
|
|
1125
1255
|
/* A phone: Fleet's five tabs fit when only the open one shows its label (the others keep theirs for screen readers). */
|
|
1126
1256
|
@media (max-width: 479px) {
|
|
1127
1257
|
.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 +1330,17 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
|
|
|
1200
1330
|
font-weight: var(--fw-medium); white-space: nowrap; }
|
|
1201
1331
|
|
|
1202
1332
|
/* #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-
|
|
1333
|
+
(S 160 / M 240 / L 360 px, on a phone 120 / 180 / 240). Never a style attribute.
|
|
1334
|
+
#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,
|
|
1335
|
+
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.
|
|
1336
|
+
The chat header's card (card-c) follows the same size choice (card-c bs-<size>). */
|
|
1337
|
+
.id-card { --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; }
|
|
1338
|
+
.id-card.card-s { --id-h: 160px; --id-ar: 4 / 1; --id-bmin: 100px; --id-bmax: 160px; }
|
|
1339
|
+
.id-card.card-l { --id-h: 360px; --id-ar: 9 / 4; --id-bmin: 200px; --id-bmax: 360px; }
|
|
1340
|
+
.id-card.card-c { --id-h: 96px; --id-bmin: 120px; --id-bmax: 200px; margin: 0; border: 0; border-radius: 0; background: none; }
|
|
1341
|
+
.id-card.card-c.bs-s { --id-ar: 4 / 1; --id-bmin: 96px; --id-bmax: 150px; }
|
|
1342
|
+
.id-card.card-c.bs-l { --id-ar: 9 / 4; --id-bmin: 150px; --id-bmax: 220px; }
|
|
1343
|
+
.id-banner { display: block; width: 100%; height: auto; aspect-ratio: var(--id-ar); min-height: var(--id-bmin); max-height: var(--id-bmax); object-fit: cover; object-position: 50% 50%; }
|
|
1209
1344
|
.id-row { display: flex; align-items: flex-start; gap: var(--s-3); padding: var(--s-3) var(--s-4); }
|
|
1210
1345
|
.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
1346
|
.id-text { flex: 1; min-width: 0; }
|
|
@@ -1263,9 +1398,12 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
|
|
|
1263
1398
|
.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
1399
|
.chat-card-peek { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-3); }
|
|
1265
1400
|
@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; }
|
|
1401
|
+
.id-card.card-s { --id-h: 120px; --id-bmin: 80px; --id-bmax: 120px; }
|
|
1402
|
+
.id-card.card-m { --id-h: 180px; --id-bmin: 100px; --id-bmax: 150px; }
|
|
1403
|
+
.id-card.card-l { --id-h: 240px; --id-bmin: 120px; --id-bmax: 200px; }
|
|
1404
|
+
.id-card.card-c { --id-bmin: 100px; --id-bmax: 140px; }
|
|
1405
|
+
.id-card.card-c.bs-s { --id-bmin: 80px; --id-bmax: 110px; }
|
|
1406
|
+
.id-card.card-c.bs-l { --id-bmin: 110px; --id-bmax: 150px; }
|
|
1269
1407
|
.id-row { flex-wrap: wrap; }
|
|
1270
1408
|
.id-text { flex-basis: 100%; }
|
|
1271
1409
|
}
|
|
@@ -1304,6 +1442,10 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
|
|
|
1304
1442
|
.s-fx-owner-row{display:flex;align-items:end;flex-wrap:wrap;gap:var(--s-2);margin-top:var(--s-2)}
|
|
1305
1443
|
.s-fx-owner-row>label{display:flex;flex-direction:column;gap:var(--s-1);min-width:0;flex:1}
|
|
1306
1444
|
|
|
1445
|
+
/* #2044: the effect frame's document declares no colour scheme (light). An iframe whose own colour scheme differs
|
|
1446
|
+
from its document's gets an opaque backdrop, so under the dark theme the frame hid the whole chat: keep them equal. */
|
|
1447
|
+
.agend-effect-frame { color-scheme: normal !important; }
|
|
1448
|
+
|
|
1307
1449
|
/* #1629: host-created static poster IMG; geometry and visibility belong to the trusted CSSOM binding. */
|
|
1308
1450
|
.agend-effect-poster { object-fit: cover !important; object-position: center !important; user-select: none; }
|
|
1309
1451
|
|
|
@@ -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,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** #2039: fixed host-only closed shadow shell. Owner CSS has no exported parts or slots into it. */
|
|
2
|
+
const roots = new WeakMap();
|
|
3
|
+
const clear = root => { while (root.firstChild) root.removeChild(root.firstChild); };
|
|
4
|
+
function sheetOf(host, record) {
|
|
5
|
+
try {
|
|
6
|
+
const link = record.link, sheet = link?.sheet;
|
|
7
|
+
return record.ready && !record.dead && roots.get(record.panel)?.current === record && record.root.mode === "closed" && record.root.host === record.panel
|
|
8
|
+
&& link.getRootNode() === record.root && link.isConnected && link.disabled === false && sheet?.ownerNode === link && sheet.disabled === false
|
|
9
|
+
&& sheet.href === new URL("/assets/effect-trial.css", host.location.origin).href && typeof sheet.insertRule === "function" && typeof sheet.deleteRule === "function" && sheet.cssRules ? sheet : null;
|
|
10
|
+
} catch { return null; }
|
|
11
|
+
}
|
|
12
|
+
/** Reflect only a shell issued here, after its fixed same-origin stylesheet loaded. Never a serialized capability. */
|
|
13
|
+
export function effectTrialShadow(host, panel) {
|
|
14
|
+
const record = roots.get(panel)?.current;
|
|
15
|
+
return record && sheetOf(host, record) === record.proof?.sheet ? record.proof : null;
|
|
16
|
+
}
|
|
17
|
+
export function createEffectTrialShadow({ window: host, panel, title, labels, onStop, onClose, onReady, onError } = {}) {
|
|
18
|
+
if (!host?.document || panel?.ownerDocument !== host.document || typeof panel.attachShadow !== "function" || typeof onReady !== "function" || typeof onError !== "function") return null;
|
|
19
|
+
let slot = roots.get(panel), cleanup = null;
|
|
20
|
+
const clock = () => (host.performance ?? globalThis.performance).now(), later = host.setTimeout?.bind(host) ?? globalThis.setTimeout, cancel = host.clearTimeout?.bind(host) ?? globalThis.clearTimeout;
|
|
21
|
+
try {
|
|
22
|
+
if (slot?.current && !slot.current.dead) return null;
|
|
23
|
+
if (!slot) { slot = { root: panel.attachShadow({ mode: "closed" }), current: null }; roots.set(panel, slot); }
|
|
24
|
+
const root = slot.root; clear(root);
|
|
25
|
+
const doc = host.document, element = (tag, cls) => { const el = doc.createElement(tag); el.className = cls; return el; };
|
|
26
|
+
const link = element("link", "effect-trial-style"); link.setAttribute("rel", "stylesheet"); link.setAttribute("href", "/assets/effect-trial.css");
|
|
27
|
+
const head = element("header", "effect-trial-head"), label = element("strong", "effect-trial-label"), name = element("span", "effect-trial-title"), close = element("button", "effect-trial-close"), controls = element("div", "effect-trial-controls"), stop = element("button", "effect-trial-stop"), body = element("div", "effect-trial-body"), stage = element("div", "effect-trial-stage"), note = element("p", "effect-trial-note");
|
|
28
|
+
close.setAttribute("type", "button"); stop.setAttribute("type", "button"); stage.setAttribute("aria-hidden", "true");
|
|
29
|
+
head.append(label, name, close); controls.append(stop); body.append(stage);
|
|
30
|
+
const nodes = [head, controls, body, note]; for (const el of nodes) el.setAttribute("hidden", "");
|
|
31
|
+
const began = clock(); if (!Number.isFinite(began) || began < 0) return null;
|
|
32
|
+
let timer = null;
|
|
33
|
+
const record = { panel, root, link, stage, head, controls, body, note, close, stop, ready: false, dead: false, proof: null }; slot.current = record;
|
|
34
|
+
const text = (nextTitle, nextLabels) => {
|
|
35
|
+
if (record.dead || !nextLabels) return;
|
|
36
|
+
label.textContent = nextLabels.label; name.textContent = typeof nextTitle === "string" ? nextTitle : "";
|
|
37
|
+
close.textContent = nextLabels.close; stop.textContent = nextLabels.stop; note.textContent = nextLabels.note;
|
|
38
|
+
panel.setAttribute("data-effect-trial-theme", doc.documentElement.dataset.theme === "dark" ? "dark" : "light");
|
|
39
|
+
};
|
|
40
|
+
const failed = () => { if (!record.dead) { dispose(); try { onError("stage_unavailable"); } catch { /* cleanup already owns failure */ } } };
|
|
41
|
+
const loaded = () => {
|
|
42
|
+
if (record.dead || record.ready || slot.current !== record) return;
|
|
43
|
+
const at = clock(); if (!Number.isFinite(at) || at < began || at - began >= 10000) { failed(); return; }
|
|
44
|
+
record.ready = true; const sheet = sheetOf(host, record);
|
|
45
|
+
if (!sheet) { failed(); return; }
|
|
46
|
+
cancel(timer); timer = null;
|
|
47
|
+
record.proof = Object.freeze({ panel, root, link, sheet, stage, head, controls, body, note, close, stop });
|
|
48
|
+
for (const el of nodes) el.removeAttribute("hidden");
|
|
49
|
+
try { onReady(record.proof); } catch { failed(); }
|
|
50
|
+
};
|
|
51
|
+
const stopClick = () => { if (!record.dead) onStop?.(); }, closeClick = () => { if (!record.dead) onClose?.(); };
|
|
52
|
+
function dispose() {
|
|
53
|
+
if (record.dead) return; record.dead = true; cancel(timer); timer = null;
|
|
54
|
+
link.removeEventListener("load", loaded); link.removeEventListener("error", failed); stop.removeEventListener("click", stopClick); close.removeEventListener("click", closeClick);
|
|
55
|
+
if (slot.current === record) { slot.current = null; clear(root); }
|
|
56
|
+
}
|
|
57
|
+
cleanup = dispose;
|
|
58
|
+
link.addEventListener("load", loaded); link.addEventListener("error", failed); stop.addEventListener("click", stopClick); close.addEventListener("click", closeClick);
|
|
59
|
+
text(title, labels); timer = later(failed, 10000); root.append(link, ...nodes);
|
|
60
|
+
return Object.freeze({ dispose, text });
|
|
61
|
+
} catch { cleanup?.(); return null; }
|
|
62
|
+
}
|