@songsid/agend 2.3.0-beta.1 → 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-cli.js +5 -5
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-file-read.d.ts +11 -6
- package/dist/agent-file-read.js +32 -17
- package/dist/agent-file-read.js.map +1 -1
- package/dist/agent-whats-new.js +10 -13
- 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 +10 -5
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/types.d.ts +13 -0
- package/dist/backend/types.js +10 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/cache-analysis.d.ts +47 -1
- package/dist/cache-analysis.js +68 -8
- package/dist/cache-analysis.js.map +1 -1
- package/dist/cache-report-tool.d.ts +17 -0
- package/dist/cache-report-tool.js +40 -0
- package/dist/cache-report-tool.js.map +1 -0
- package/dist/cache-service.d.ts +15 -0
- package/dist/cache-service.js +26 -1
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/mcp-tools.js +9 -3
- 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/cli.js +7 -3
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +5 -1
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.js +5 -5
- package/dist/daemon.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 +71 -3
- package/dist/fleet-manager.js +256 -45
- 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 +20 -5
- package/dist/general-knowledge/skills/multi-channel/SKILL.md +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +149 -5
- 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 +4 -0
- package/dist/outbound-handlers.js +74 -1
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +98 -0
- package/dist/outbound-schemas.js +56 -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/scheduler/scheduler.d.ts +12 -0
- package/dist/scheduler/scheduler.js +83 -10
- package/dist/scheduler/scheduler.js.map +1 -1
- package/dist/service-installer.d.ts +2 -1
- package/dist/service-installer.js +18 -3
- package/dist/service-installer.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.d.ts +5 -0
- package/dist/tool-permissions.js +15 -2
- 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-cache.js +23 -1
- 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 +163 -12
- 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 +35 -9
- package/dist/ui/shared/panel-view.js +2 -2
- package/dist/ui/shared/usage-dialog.js +14 -2
- package/dist/ui/shared/view-strings.js +22 -2
- package/dist/ui/signin.html +1 -1
- package/dist/usage/providers.d.ts +3 -0
- package/dist/usage/providers.js +4 -3
- package/dist/usage/providers.js.map +1 -1
- 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 +60 -11
- 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-asset-parts.d.ts +3 -8
- package/dist/web-asset-parts.js.map +1 -1
- package/dist/web-auth.js +1 -1
- package/dist/web-auth.js.map +1 -1
- package/dist/web-effect-service.js +3 -1
- package/dist/web-effect-service.js.map +1 -1
- package/dist/web-effect-tools.js +4 -1
- package/dist/web-effect-tools.js.map +1 -1
- package/dist/web-effect-trials.d.ts +4 -4
- package/dist/web-effect-trials.js +6 -6
- package/dist/web-effect-trials.js.map +1 -1
- package/dist/web-effect-upload.d.ts +2 -4
- package/dist/web-effect-upload.js +3 -8
- package/dist/web-effect-upload.js.map +1 -1
- package/dist/web-effect.d.ts +14 -0
- package/dist/web-effect.js +21 -0
- package/dist/web-effect.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-agent.js +5 -3
- package/dist/web-mod-agent.js.map +1 -1
- package/dist/web-mod-bundle.d.ts +5 -0
- package/dist/web-mod-bundle.js +6 -0
- package/dist/web-mod-bundle.js.map +1 -1
- package/dist/web-mod-store.d.ts +5 -0
- package/dist/web-mod-store.js +8 -1
- 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/templates/systemd.service.ejs +8 -0
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 */
|
|
@@ -157,7 +174,9 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
|
|
|
157
174
|
}
|
|
158
175
|
/* #1604: one row — Settings and Session (the anonymous View reader: Sign in and the theme/language popover). Their
|
|
159
176
|
pop-ups open over the whole footer's width. */
|
|
160
|
-
|
|
177
|
+
/* #1922: a divider above the footer, with the filter block's bottom padding and the footer's top padding trimmed to
|
|
178
|
+
--s-1: the gap reads as a section break, not an empty ~20 px band (the two --s-2 paddings used to stack). */
|
|
179
|
+
.side-foot { display: flex; align-items: center; gap: var(--s-1); padding: var(--s-1) var(--s-2) var(--s-2); border-top: 1px solid var(--border); position: relative; }
|
|
161
180
|
.side-foot > .side-row, .side-foot > .session { flex: 1; min-width: 0; }
|
|
162
181
|
.side-foot > .session { position: static; }
|
|
163
182
|
.side-foot .side-row span:not(.dot):not(.meta) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
@@ -220,11 +239,15 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
|
|
|
220
239
|
}
|
|
221
240
|
.panel-head .sb-open { display: none; }
|
|
222
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)); }
|
|
223
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. */
|
|
224
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)); }
|
|
225
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; }
|
|
226
247
|
.panel-sub { color: var(--text-2); font-size: var(--fs-sm); white-space: nowrap; }
|
|
227
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; }
|
|
228
251
|
.panel-actions { display: flex; align-items: center; gap: var(--s-1); }
|
|
229
252
|
/* #1523 N2: the instance view switch — beside the title on a wide screen; on a phone, a row of tabs under the header. */
|
|
230
253
|
.inst-switch.seg { padding: 0; margin: 0; width: auto; max-width: none; flex-shrink: 0; }
|
|
@@ -723,6 +746,7 @@ html.lb-open .scroller { overflow: hidden; }
|
|
|
723
746
|
.sb-open .sidebar { transform: none; visibility: visible; }
|
|
724
747
|
.sb-open .scrim { display: block; position: fixed; inset: 0; background: var(--scrim); z-index: 39; }
|
|
725
748
|
.panel-head .sb-open { display: inline-flex; }
|
|
749
|
+
.panel-head .sb-new { display: none; }
|
|
726
750
|
.only-narrow { display: inline-flex; }
|
|
727
751
|
.thread { padding: var(--s-3) var(--s-3) var(--s-2); gap: var(--s-5); }
|
|
728
752
|
.col { padding: var(--s-3) var(--s-3) var(--s-8); }
|
|
@@ -781,6 +805,9 @@ html.lb-open .scroller { overflow: hidden; }
|
|
|
781
805
|
.inst-star.on { color: var(--warn); }
|
|
782
806
|
.inst-star.on svg, .fav-mark { fill: currentColor; }
|
|
783
807
|
.fav-head { cursor: default; }
|
|
808
|
+
/* #1936: the open instance, pinned on top while a filter is on — its header reads "Current", a line under it. */
|
|
809
|
+
.pinned-head { cursor: default; }
|
|
810
|
+
.v-pinned { border-bottom: 1px solid var(--border); padding-bottom: var(--s-2); margin-bottom: var(--s-1); }
|
|
784
811
|
.fav-mark { color: var(--warn); }
|
|
785
812
|
.inst-nav:not(.fav-editing) .inst-star:not(.on) { visibility: hidden; }
|
|
786
813
|
/* As specific as the rule that hides it (and later), so hover and focus win. */
|
|
@@ -800,7 +827,7 @@ body.side-resizing, body.side-resizing * { cursor: col-resize !important; user-s
|
|
|
800
827
|
.dragging { opacity: 0.4; }
|
|
801
828
|
.drop-before { box-shadow: inset 0 2px 0 var(--accent); }
|
|
802
829
|
.drop-after { box-shadow: inset 0 -2px 0 var(--accent); }
|
|
803
|
-
.v-filter { flex: none; padding: var(--s-2) var(--s-3) var(--s-
|
|
830
|
+
.v-filter { flex: none; padding: var(--s-2) var(--s-3) var(--s-1); border-top: 1px solid var(--border); }
|
|
804
831
|
.v-filter-box { display: flex; align-items: center; gap: var(--s-2); padding: 0 var(--s-1) 0 var(--s-3); border-radius: var(--r-sm); background: var(--surface); color: var(--text-3); }
|
|
805
832
|
.v-filter-box:focus-within { box-shadow: 0 0 0 2px var(--focus); }
|
|
806
833
|
.v-filter-box input { flex: 1; min-width: 0; height: 34px; border: 0; background: transparent; color: var(--text); font-size: var(--fs-sm); outline: none; }
|
|
@@ -863,6 +890,10 @@ body.side-resizing, body.side-resizing * { cursor: col-resize !important; user-s
|
|
|
863
890
|
/* #1749: one line beside ↑↓ — the name gives way first, then the plan badge (each with an ellipsis); ↑↓ never wrap. */
|
|
864
891
|
.u-head strong { min-width: 0; flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
865
892
|
.u-plan { min-width: 0; flex: none; max-width: 55%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
893
|
+
/* #2014: a credential profile's row — the provider name beside the plan badge, and the profile on its own full-width
|
|
894
|
+
muted line under the head (one line in the rail, whole in the dialog). */
|
|
895
|
+
.u-profile { margin-top: calc(-1 * var(--s-2) + 2px); color: var(--text-3); font-size: var(--fs-xs); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
896
|
+
.dlg .u-profile { white-space: normal; overflow-wrap: anywhere; }
|
|
866
897
|
.u-order { margin-left: auto; display: flex; gap: 2px; flex: none; }
|
|
867
898
|
.u-order .icon-btn { width: 28px; height: 28px; }
|
|
868
899
|
.u-order .icon-btn:disabled { opacity: 0.3; }
|
|
@@ -1113,6 +1144,114 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
|
|
|
1113
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); }
|
|
1114
1145
|
.org-link { min-height: var(--tap); }
|
|
1115
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
|
+
}
|
|
1116
1255
|
/* A phone: Fleet's five tabs fit when only the open one shows its label (the others keep theirs for screen readers). */
|
|
1117
1256
|
@media (max-width: 479px) {
|
|
1118
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; }
|
|
@@ -1191,12 +1330,17 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
|
|
|
1191
1330
|
font-weight: var(--fw-medium); white-space: nowrap; }
|
|
1192
1331
|
|
|
1193
1332
|
/* #1629 r4 §1 / r4.1 Q3 (a): the identity card (identity-card.js). The size is a class token; its heights are here
|
|
1194
|
-
(S 160 / M 240 / L 360 px, on a phone 120 / 180 / 240)
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
.id-card
|
|
1199
|
-
.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%; }
|
|
1200
1344
|
.id-row { display: flex; align-items: flex-start; gap: var(--s-3); padding: var(--s-3) var(--s-4); }
|
|
1201
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); }
|
|
1202
1346
|
.id-text { flex: 1; min-width: 0; }
|
|
@@ -1254,9 +1398,12 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
|
|
|
1254
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; }
|
|
1255
1399
|
.chat-card-peek { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-3); }
|
|
1256
1400
|
@media (max-width: 479px) {
|
|
1257
|
-
.id-card.card-s { --id-h: 120px; }
|
|
1258
|
-
.id-card.card-m { --id-h: 180px; }
|
|
1259
|
-
.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; }
|
|
1260
1407
|
.id-row { flex-wrap: wrap; }
|
|
1261
1408
|
.id-text { flex-basis: 100%; }
|
|
1262
1409
|
}
|
|
@@ -1295,6 +1442,10 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
|
|
|
1295
1442
|
.s-fx-owner-row{display:flex;align-items:end;flex-wrap:wrap;gap:var(--s-2);margin-top:var(--s-2)}
|
|
1296
1443
|
.s-fx-owner-row>label{display:flex;flex-direction:column;gap:var(--s-1);min-width:0;flex:1}
|
|
1297
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
|
+
|
|
1298
1449
|
/* #1629: host-created static poster IMG; geometry and visibility belong to the trusted CSSOM binding. */
|
|
1299
1450
|
.agend-effect-poster { object-fit: cover !important; object-position: center !important; user-select: none; }
|
|
1300
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,
|