waygraph 0.15.27 → 0.15.29
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/highlights/css.js +2 -44
- package/dist/pilot-overlay.d.ts +0 -18
- package/dist/pilot-overlay.js +27 -32
- package/dist/runner/inpage/banner.js +68 -0
- package/dist/runner/inpage/core.js +267 -0
- package/dist/runner/inpage/device.js +211 -0
- package/dist/runner/inpage/panel-chrome.js +152 -0
- package/dist/runner/inpage/stamp.js +46 -0
- package/dist/runner/inpage/swipe-zoom.js +220 -0
- package/dist/runner/inpage/todos.js +332 -0
- package/dist/runner/overlay-css.js +7 -18
- package/dist/runner/overlay-install.js +15 -1269
- package/dist/step-overlay.js +2 -46
- package/dist/ui/components/banner.d.ts +2 -0
- package/dist/ui/components/banner.js +10 -0
- package/dist/ui/components/cursor.d.ts +2 -0
- package/dist/ui/components/cursor.js +19 -0
- package/dist/ui/components/ring.d.ts +4 -0
- package/dist/ui/components/ring.js +44 -0
- package/dist/ui/compose.d.ts +4 -0
- package/dist/ui/compose.js +7 -0
- package/dist/ui/tokens.d.ts +31 -0
- package/dist/ui/tokens.js +21 -0
- package/examples/saucedemo/.waygraph-auto/2324269a.log +0 -0
- package/examples/saucedemo/.waygraph-auto/3f088138.log +0 -0
- package/examples/saucedemo/.waygraph-auto/42e014d1.log +0 -0
- package/examples/saucedemo/.waygraph-auto/6706ead4.log +0 -0
- package/examples/saucedemo/.waygraph-auto/7773a809.log +0 -0
- package/examples/saucedemo/.waygraph-auto/94221b2b.log +0 -0
- package/examples/saucedemo/.waygraph-auto/c60cc55e.log +0 -0
- package/examples/saucedemo/.waygraph-auto/da6ebb31.log +0 -0
- package/examples/saucedemo/.waygraph-auto/e03cfd8e.log +0 -0
- package/examples/saucedemo/.waygraph-auto/f2943898.log +0 -0
- package/package.json +1 -1
package/dist/highlights/css.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ringCss } from "../ui/components/ring.js";
|
|
1
2
|
// Split out of the former 2,100-line highlights.ts (see src/ARCHITECTURE.md). Behavior unchanged.
|
|
2
3
|
/**
|
|
3
4
|
* The `#wg-ring` / `#wg-ring-label` CSS (tone/size/weight variants) - the
|
|
@@ -12,47 +13,4 @@
|
|
|
12
13
|
* renders them belongs alongside it for the same reason. `cli.ts` imports
|
|
13
14
|
* this constant instead of keeping its own duplicate copy.
|
|
14
15
|
*/
|
|
15
|
-
export const WAYGRAPH_RING_CSS =
|
|
16
|
-
"border:2.5px solid #7C3AED;border-radius:10px;" +
|
|
17
|
-
// Opacity fade only - NEVER transition border-color/box-shadow. Tone swaps
|
|
18
|
-
// (auto gray -> planned purple -> warning yellow) must snap instantly;
|
|
19
|
-
// color transitions read as a muddy gray/purple/yellow morph on ring 2+.
|
|
20
|
-
"box-shadow:0 0 0 4px rgba(124,58,237,.16);transition:opacity .3s ease;}" +
|
|
21
|
-
// Planned (stubs / fixtures / YAP / instruction.highlights) = purple.
|
|
22
|
-
// Automation (verify fallback, unmatched fill/click) = gray - operators
|
|
23
|
-
// can ignore engine checks and watch purple + semantic tones.
|
|
24
|
-
"#wg-ring[data-tone=planned]{border-color:#7C3AED;box-shadow:0 0 0 4px rgba(124,58,237,.16);}" +
|
|
25
|
-
"#wg-ring[data-tone=auto]{border-color:#9CA3AF;box-shadow:0 0 0 4px rgba(156,163,175,.28);}" +
|
|
26
|
-
"#wg-ring[data-tone=info]{border-color:#3B82F6;box-shadow:0 0 0 4px rgba(59,130,246,.22);}" +
|
|
27
|
-
"#wg-ring[data-tone=warning]{border-color:#EAB308;box-shadow:0 0 0 4px rgba(234,179,8,.22);}" +
|
|
28
|
-
"#wg-ring[data-tone=danger]{border-color:#EF4444;box-shadow:0 0 0 4px rgba(239,68,68,.22);}" +
|
|
29
|
-
"#wg-ring[data-tone=success]{border-color:#22C55E;box-shadow:0 0 0 4px rgba(34,197,94,.22);}" +
|
|
30
|
-
// Real, direct user request: a visible color distinction between a real
|
|
31
|
-
// Playwright-driven action (auto gray, unchanged) and a pure DOM
|
|
32
|
-
// inspection (this - orange).
|
|
33
|
-
"#wg-ring[data-tone=orange]{border-color:#F97316;box-shadow:0 0 0 4px rgba(249,115,22,.22);}" +
|
|
34
|
-
// A real element, not a ::after pseudo-element - a pseudo-element's
|
|
35
|
-
// position is CSS-relative to the ring's own box (left:0 always meant
|
|
36
|
-
// "the ring's own left edge"), so it had no way to be clamped back onto
|
|
37
|
-
// screen when that box sat near a viewport edge - the label's text just
|
|
38
|
-
// ran off, invisibly, with no overflow guard at all. A real sibling can
|
|
39
|
-
// be measured (its actual rendered width) and repositioned in JS -
|
|
40
|
-
// pushed back onto screen, same width, never shrunk. See
|
|
41
|
-
// window.__wgPositionRing in cli.ts's own installOverlay.
|
|
42
|
-
"#wg-ring-label{position:fixed;z-index:2147483646;pointer-events:none;opacity:0;" +
|
|
43
|
-
"max-width:min(360px,70vw);white-space:normal;padding:6px 10px;border-radius:7px;background:#7C3AED;color:#fff;" +
|
|
44
|
-
"font:600 12px/1.35 system-ui,sans-serif;transition:opacity .3s ease;}" +
|
|
45
|
-
"#wg-ring-label[data-tone=planned]{background:#7C3AED;color:#fff;}" +
|
|
46
|
-
"#wg-ring-label[data-tone=auto]{background:#6B7280;color:#fff;}" +
|
|
47
|
-
"#wg-ring-label[data-tone=info]{background:#2563EB;color:#fff;}" +
|
|
48
|
-
"#wg-ring-label[data-tone=warning]{background:#EAB308;color:#1c1917;}" +
|
|
49
|
-
"#wg-ring-label[data-tone=danger]{background:#DC2626;color:#fff;}" +
|
|
50
|
-
"#wg-ring-label[data-tone=success]{background:#16A34A;color:#fff;}" +
|
|
51
|
-
"#wg-ring-label[data-tone=orange]{background:#EA580C;color:#fff;}" +
|
|
52
|
-
// size: ring pad + label font; weight: label boldness
|
|
53
|
-
"#wg-ring[data-size=sm]{border-width:1.5px;border-radius:8px;}" +
|
|
54
|
-
"#wg-ring[data-size=lg]{border-width:4px;border-radius:12px;}" +
|
|
55
|
-
"#wg-ring-label[data-size=sm]{font-size:10px;line-height:1.25;padding:4px 7px;border-radius:5px;}" +
|
|
56
|
-
"#wg-ring-label[data-size=lg]{font-size:16px;line-height:1.35;padding:8px 14px;border-radius:9px;max-width:min(480px,85vw);}" +
|
|
57
|
-
"#wg-ring-label[data-weight=bold]{font-weight:800;}" +
|
|
58
|
-
"#wg-ring-label[data-weight=normal]{font-weight:600;}";
|
|
16
|
+
export const WAYGRAPH_RING_CSS = ringCss();
|
package/dist/pilot-overlay.d.ts
CHANGED
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A small, self-contained on-page indicator for a live AutoSession -
|
|
3
|
-
* "this browser is being driven by Waygraph," plus an expandable panel
|
|
4
|
-
* showing the session's own current live menu. Real, direct user request:
|
|
5
|
-
* a headful Pilot session looks like an ordinary browser tab with no
|
|
6
|
-
* indication anything is driving it, which matters once a human might be
|
|
7
|
-
* co-driving the same visible session (see `AutoSession.resync()`'s own
|
|
8
|
-
* doc comment for the related state-desync problem that scenario causes).
|
|
9
|
-
*
|
|
10
|
-
* Deliberately NOT built on `src/cli.ts`'s own demo-panel/banner overlay
|
|
11
|
-
* system, for the same reason `pilot-v1`'s narrate-mode ring renderer
|
|
12
|
-
* wasn't either (see the git history at tag
|
|
13
|
-
* `waygraph-pilot-v1-logs-prettified`): `cli.ts` runs `main().catch(...)`
|
|
14
|
-
* unconditionally at module load with no `import.meta.url` guard, so
|
|
15
|
-
* importing anything from it here would trigger the whole CLI's argument
|
|
16
|
-
* dispatch as a side effect of loading a library module - unsafe. This
|
|
17
|
-
* ships its own small, purpose-built badge + panel instead.
|
|
18
|
-
*/
|
|
19
1
|
import type { BrowserContext, Page } from "@playwright/test";
|
|
20
2
|
import type { SessionSnapshot } from "./auto-session.js";
|
|
21
3
|
import { type HighlightTone, type DevicePreset, type DeviceState, type TodoDockUiOpts } from "./highlights.js";
|
package/dist/pilot-overlay.js
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small, self-contained on-page indicator for a live AutoSession -
|
|
3
|
+
* "this browser is being driven by Waygraph," plus an expandable panel
|
|
4
|
+
* showing the session's own current live menu. Real, direct user request:
|
|
5
|
+
* a headful Pilot session looks like an ordinary browser tab with no
|
|
6
|
+
* indication anything is driving it, which matters once a human might be
|
|
7
|
+
* co-driving the same visible session (see `AutoSession.resync()`'s own
|
|
8
|
+
* doc comment for the related state-desync problem that scenario causes).
|
|
9
|
+
*
|
|
10
|
+
* Deliberately NOT built on `src/cli.ts`'s own demo-panel/banner overlay
|
|
11
|
+
* system, for the same reason `pilot-v1`'s narrate-mode ring renderer
|
|
12
|
+
* wasn't either (see the git history at tag
|
|
13
|
+
* `waygraph-pilot-v1-logs-prettified`): `cli.ts` runs `main().catch(...)`
|
|
14
|
+
* unconditionally at module load with no `import.meta.url` guard, so
|
|
15
|
+
* importing anything from it here would trigger the whole CLI's argument
|
|
16
|
+
* dispatch as a side effect of loading a library module - unsafe. This
|
|
17
|
+
* ships its own small, purpose-built badge + panel instead.
|
|
18
|
+
*/
|
|
19
|
+
import { pilotFxRingCss } from "./ui/components/ring.js";
|
|
20
|
+
import { SURFACE } from "./ui/tokens.js";
|
|
1
21
|
import { WAYGRAPH_RING_CSS, normalizeHighlightTone, normalizeHighlightSize, normalizeHighlightWeight, formatHighlightCaption, resolveDeviceState, resolveTodoDockUi, } from "./highlights.js";
|
|
2
22
|
// Real, direct user request: this overlay's own colors were an improvised
|
|
3
23
|
// purple/white scheme (#a78bfa / #1a1033) that didn't match anything else -
|
|
@@ -22,7 +42,7 @@ const OVERLAY_CSS = `
|
|
|
22
42
|
}
|
|
23
43
|
#wg-pilot-panel {
|
|
24
44
|
display: none; margin-top: 6px; max-width: 420px; max-height: 320px;
|
|
25
|
-
overflow-y: auto; background:
|
|
45
|
+
overflow-y: auto; background: ${SURFACE}; color: #fff; border-radius: 8px;
|
|
26
46
|
box-shadow: 0 2px 8px rgba(0,0,0,0.25); padding: 8px 10px;
|
|
27
47
|
}
|
|
28
48
|
/* The graph tab renders a real Mermaid flowchart, which needs more room than
|
|
@@ -55,42 +75,17 @@ const OVERLAY_CSS = `
|
|
|
55
75
|
#wg-pilot-activity {
|
|
56
76
|
position: fixed; z-index: 2147483000; bottom: 12px; left: 12px;
|
|
57
77
|
font: 12px/1.4 ui-monospace, "SF Mono", Consolas, monospace;
|
|
58
|
-
background:
|
|
78
|
+
background: ${SURFACE}; color: #fff; padding: 6px 10px; border-radius: 8px;
|
|
59
79
|
box-shadow: 0 2px 8px rgba(0,0,0,0.25); white-space: nowrap; pointer-events: none;
|
|
60
80
|
opacity: 0; transform: translateY(4px); transition: opacity 0.15s, transform 0.15s;
|
|
61
81
|
}
|
|
62
82
|
#wg-pilot-activity.wg-pilot-toast-show { opacity: 1; transform: translateY(0); }
|
|
63
|
-
/* Agent-sent fixture rings
|
|
64
|
-
|
|
65
|
-
border:3px solid #7C3AED;border-radius:10px;box-sizing:border-box;
|
|
66
|
-
transition:opacity .12s ease,left .12s,top .12s,width .12s,height .12s;}
|
|
67
|
-
.wg-pilot-fx-ring[data-tone=planned]{border-color:#7C3AED;box-shadow:0 0 0 4px rgba(124,58,237,.16);}
|
|
68
|
-
.wg-pilot-fx-ring[data-tone=auto]{border-color:#9CA3AF;box-shadow:0 0 0 4px rgba(156,163,175,.28);}
|
|
69
|
-
.wg-pilot-fx-ring[data-tone=info]{border-color:#3B82F6;box-shadow:0 0 0 4px rgba(59,130,246,.22);}
|
|
70
|
-
.wg-pilot-fx-ring[data-tone=warning]{border-color:#EAB308;box-shadow:0 0 0 4px rgba(234,179,8,.22);}
|
|
71
|
-
.wg-pilot-fx-ring[data-tone=danger]{border-color:#EF4444;box-shadow:0 0 0 4px rgba(239,68,68,.22);}
|
|
72
|
-
.wg-pilot-fx-ring[data-tone=success]{border-color:#22C55E;box-shadow:0 0 0 4px rgba(34,197,94,.22);}
|
|
73
|
-
.wg-pilot-fx-ring[data-tone=orange]{border-color:#F97316;box-shadow:0 0 0 4px rgba(249,115,22,.22);}
|
|
74
|
-
.wg-pilot-fx-label{position:fixed;z-index:2147483645;pointer-events:none;opacity:0;
|
|
75
|
-
font:600 12px/1.3 ui-sans-serif,system-ui,sans-serif;padding:5px 9px;border-radius:6px;
|
|
76
|
-
max-width:min(360px,80vw);box-shadow:0 2px 8px rgba(0,0,0,.25);white-space:nowrap;
|
|
77
|
-
overflow:hidden;text-overflow:ellipsis;transition:opacity .12s ease;}
|
|
78
|
-
.wg-pilot-fx-label[data-tone=planned]{background:#7C3AED;color:#fff;}
|
|
79
|
-
.wg-pilot-fx-label[data-tone=auto]{background:#6B7280;color:#fff;}
|
|
80
|
-
.wg-pilot-fx-label[data-tone=info]{background:#2563EB;color:#fff;}
|
|
81
|
-
.wg-pilot-fx-label[data-tone=warning]{background:#EAB308;color:#1c1917;}
|
|
82
|
-
.wg-pilot-fx-label[data-tone=danger]{background:#DC2626;color:#fff;}
|
|
83
|
-
.wg-pilot-fx-label[data-tone=success]{background:#16A34A;color:#fff;}
|
|
84
|
-
.wg-pilot-fx-label[data-tone=orange]{background:#EA580C;color:#fff;}
|
|
85
|
-
.wg-pilot-fx-ring[data-size=sm]{border-width:1.5px;border-radius:8px;}
|
|
86
|
-
.wg-pilot-fx-ring[data-size=lg]{border-width:4px;border-radius:12px;}
|
|
87
|
-
.wg-pilot-fx-label[data-size=sm]{font-size:10px;padding:4px 7px;}
|
|
88
|
-
.wg-pilot-fx-label[data-size=lg]{font-size:16px;padding:8px 14px;}
|
|
89
|
-
.wg-pilot-fx-label[data-weight=bold]{font-weight:800;}
|
|
83
|
+
/* Agent-sent fixture rings: generated from ui/tokens (tones shared with #wg-ring). */
|
|
84
|
+
${pilotFxRingCss()}
|
|
90
85
|
#wg-pilot-fx-todos{
|
|
91
86
|
position:fixed;z-index:2147483645;top:72px;max-width:280px;max-height:calc(100vh - 100px);overflow:auto;
|
|
92
87
|
font:12px/1.4 ui-sans-serif,system-ui,sans-serif;color:#fff;
|
|
93
|
-
background
|
|
88
|
+
background:${SURFACE};border-radius:10px;padding:10px 12px;
|
|
94
89
|
box-shadow:0 2px 10px rgba(0,0,0,.3);pointer-events:auto;
|
|
95
90
|
transition:opacity .25s ease;
|
|
96
91
|
}
|
|
@@ -113,7 +108,7 @@ const OVERLAY_CSS = `
|
|
|
113
108
|
/* Zoom HUD (demo parity - badge only; no CSS scale of the page). */
|
|
114
109
|
#wg-pilot-fx-zoom{position:fixed;z-index:2147483646;bottom:14px;right:14px;
|
|
115
110
|
display:flex;align-items:center;gap:7px;padding:6px 11px 6px 8px;
|
|
116
|
-
border-radius:999px;background
|
|
111
|
+
border-radius:999px;background:${SURFACE};color:#f0e8ff;
|
|
117
112
|
border:1px solid rgba(250,204,21,.6);box-shadow:0 6px 18px rgba(0,0,0,.4);
|
|
118
113
|
font:700 12px/1.2 system-ui,sans-serif;pointer-events:none;}
|
|
119
114
|
#wg-pilot-fx-zoom .wg-pilot-fx-zoom-ico{width:18px;height:18px;display:flex;align-items:center;justify-content:center;
|
|
@@ -122,7 +117,7 @@ const OVERLAY_CSS = `
|
|
|
122
117
|
#wg-pilot-fx-zoom .wg-pilot-fx-zoom-val{color:#fde68a;font-variant-numeric:tabular-nums;font-weight:800;min-width:3.2em;}
|
|
123
118
|
#wg-pilot-fx-zoom[data-zoomed="1"]{border-color:#fbbf24;}
|
|
124
119
|
#wg-pilot-fx-device{position:fixed;z-index:2147483646;top:14px;right:14px;
|
|
125
|
-
padding:8px 12px;border-radius:10px;background
|
|
120
|
+
padding:8px 12px;border-radius:10px;background:${SURFACE};color:#f0e8ff;
|
|
126
121
|
border:1px solid rgba(124,58,237,.55);font:700 12px/1.2 system-ui,sans-serif;
|
|
127
122
|
pointer-events:none;box-shadow:0 6px 18px rgba(0,0,0,.35);}
|
|
128
123
|
#wg-pilot-fx-device[data-preset=mobile]{border-color:#3B82F6;}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// In-page installer (runs inside the browser via page.evaluate): must stay self-contained - no module-scope closure.
|
|
2
|
+
// Split out of the former single installOverlay evaluate (see src/ARCHITECTURE.md). Shares state only via window.__wg*.
|
|
3
|
+
/* eslint-disable no-unused-vars */
|
|
4
|
+
export function installBanner({ title, favicon, bannerPos, todoPos, envAutoplay, todoDockUi }) {
|
|
5
|
+
const POSITIONS = ["left", "center", "right"];
|
|
6
|
+
const applyPos = (el, pos) => {
|
|
7
|
+
el.dataset.pos = pos;
|
|
8
|
+
try {
|
|
9
|
+
localStorage.setItem("wg-banner-pos", pos);
|
|
10
|
+
} catch {
|
|
11
|
+
/* private mode / blocked storage - position still applies this page */
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
if (title) {
|
|
15
|
+
let banner = document.getElementById("wg-banner");
|
|
16
|
+
if (!banner) {
|
|
17
|
+
banner = document.createElement("div");
|
|
18
|
+
banner.id = "wg-banner";
|
|
19
|
+
let saved = null;
|
|
20
|
+
try {
|
|
21
|
+
saved = localStorage.getItem("wg-banner-pos");
|
|
22
|
+
} catch {
|
|
23
|
+
/* ignore */
|
|
24
|
+
}
|
|
25
|
+
const startPos =
|
|
26
|
+
saved && POSITIONS.includes(saved) ? saved : bannerPos;
|
|
27
|
+
applyPos(banner, startPos);
|
|
28
|
+
banner.title = "Click to move: top left / center / right";
|
|
29
|
+
banner.addEventListener("click", (e) => {
|
|
30
|
+
e.stopPropagation();
|
|
31
|
+
const i = POSITIONS.indexOf(banner.dataset.pos || "left");
|
|
32
|
+
applyPos(banner, POSITIONS[(i + 1) % POSITIONS.length]);
|
|
33
|
+
});
|
|
34
|
+
const tag = document.createElement("span");
|
|
35
|
+
tag.className = "wg-banner-tag";
|
|
36
|
+
tag.textContent = "waygraph demo";
|
|
37
|
+
const text = document.createElement("span");
|
|
38
|
+
text.className = "wg-banner-text";
|
|
39
|
+
text.textContent = title;
|
|
40
|
+
banner.appendChild(tag);
|
|
41
|
+
banner.appendChild(text);
|
|
42
|
+
document.documentElement.appendChild(banner);
|
|
43
|
+
} else {
|
|
44
|
+
// Fixture / episode title changes every step - update in place
|
|
45
|
+
// (banner is created once; do not leave the first step's text stuck).
|
|
46
|
+
let text = banner.querySelector(".wg-banner-text");
|
|
47
|
+
if (!text) {
|
|
48
|
+
text = document.createElement("span");
|
|
49
|
+
text.className = "wg-banner-text";
|
|
50
|
+
banner.appendChild(text);
|
|
51
|
+
}
|
|
52
|
+
text.textContent = title;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Tab title/favicon: a real navigation resets document.title and any
|
|
56
|
+
// <link rel="icon"> the new document brings, so re-check (not
|
|
57
|
+
// re-append) on every call instead of a one-time flag.
|
|
58
|
+
if (!document.title.startsWith("[waygraph] ")) {
|
|
59
|
+
document.title = "[waygraph] " + document.title;
|
|
60
|
+
}
|
|
61
|
+
let iconLink = document.querySelector("link[rel~='icon']");
|
|
62
|
+
if (!iconLink) {
|
|
63
|
+
iconLink = document.createElement("link");
|
|
64
|
+
iconLink.rel = "icon";
|
|
65
|
+
document.head.appendChild(iconLink);
|
|
66
|
+
}
|
|
67
|
+
if (iconLink.href !== favicon) iconLink.href = favicon;
|
|
68
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// In-page installer (runs inside the browser via page.evaluate): must stay self-contained - no module-scope closure.
|
|
2
|
+
// Split out of the former single installOverlay evaluate (see src/ARCHITECTURE.md). Shares state only via window.__wg*.
|
|
3
|
+
/* eslint-disable no-unused-vars */
|
|
4
|
+
export function installCore({ title, favicon, bannerPos, todoPos, envAutoplay, todoDockUi }) {
|
|
5
|
+
window.__wgTodoDockUi = todoDockUi || {
|
|
6
|
+
compact: true,
|
|
7
|
+
cap: 5,
|
|
8
|
+
expandCap: 14,
|
|
9
|
+
collision: true,
|
|
10
|
+
behindRing: true,
|
|
11
|
+
};
|
|
12
|
+
// Seed the live autoplay toggle from the env default on first ever
|
|
13
|
+
// load only - a real navigation re-runs this, and re-stamping here
|
|
14
|
+
// would silently undo a human's mid-run checkbox click.
|
|
15
|
+
try {
|
|
16
|
+
if (localStorage.getItem("wg-autoplay") === null) {
|
|
17
|
+
localStorage.setItem("wg-autoplay", envAutoplay ? "1" : "0");
|
|
18
|
+
}
|
|
19
|
+
} catch {
|
|
20
|
+
/* private mode / blocked storage - falls back to manual gating */
|
|
21
|
+
}
|
|
22
|
+
if (!document.getElementById("wg-ring")) {
|
|
23
|
+
const ring = document.createElement("div");
|
|
24
|
+
ring.id = "wg-ring";
|
|
25
|
+
document.documentElement.appendChild(ring);
|
|
26
|
+
}
|
|
27
|
+
if (!document.getElementById("wg-ring-label")) {
|
|
28
|
+
const ringLabel = document.createElement("div");
|
|
29
|
+
ringLabel.id = "wg-ring-label";
|
|
30
|
+
document.documentElement.appendChild(ringLabel);
|
|
31
|
+
}
|
|
32
|
+
if (!document.getElementById("wg-cursor")) {
|
|
33
|
+
const cursor = document.createElement("div");
|
|
34
|
+
cursor.id = "wg-cursor";
|
|
35
|
+
// Dark fill + white stroke, same as help-center-clip-engine's own
|
|
36
|
+
// #clip-cursor - visible against any page background, light or
|
|
37
|
+
// dark, unlike a plain solid-white shape.
|
|
38
|
+
cursor.innerHTML =
|
|
39
|
+
"<svg viewBox='0 0 32 32' width='24' height='24'>" +
|
|
40
|
+
"<path fill='#0C0C1A' stroke='#fff' stroke-width='1.4' stroke-linejoin='round' " +
|
|
41
|
+
"d='M6 3.5l1.4 22.5 5.8-5.4 4.2 9.4 3.6-1.6-4.2-9.2H26z'/></svg>";
|
|
42
|
+
document.documentElement.appendChild(cursor);
|
|
43
|
+
}
|
|
44
|
+
if (!document.getElementById("wg-click-pulse")) {
|
|
45
|
+
const pulse = document.createElement("div");
|
|
46
|
+
pulse.id = "wg-click-pulse";
|
|
47
|
+
document.documentElement.appendChild(pulse);
|
|
48
|
+
}
|
|
49
|
+
window.__wgMoveCursorTo = (x, y, ms, instant) => {
|
|
50
|
+
const cursor = document.getElementById("wg-cursor");
|
|
51
|
+
if (!cursor) return;
|
|
52
|
+
cursor.style.setProperty("--wg-cursor-ms", (ms || 600) + "ms");
|
|
53
|
+
if (instant) {
|
|
54
|
+
const prev = cursor.style.transition;
|
|
55
|
+
cursor.style.transition = "none";
|
|
56
|
+
cursor.style.transform = "translate(" + x + "px," + y + "px)";
|
|
57
|
+
void cursor.offsetWidth;
|
|
58
|
+
cursor.style.transition = prev || "";
|
|
59
|
+
} else {
|
|
60
|
+
cursor.style.transform = "translate(" + x + "px," + y + "px)";
|
|
61
|
+
}
|
|
62
|
+
cursor.style.opacity = "1";
|
|
63
|
+
};
|
|
64
|
+
window.__wgHideCursor = () => {
|
|
65
|
+
const cursor = document.getElementById("wg-cursor");
|
|
66
|
+
if (cursor) cursor.style.opacity = "0";
|
|
67
|
+
};
|
|
68
|
+
window.__wgPositionRing = (box, label, tone, style) => {
|
|
69
|
+
const ring = document.getElementById("wg-ring");
|
|
70
|
+
const ringLabel = document.getElementById("wg-ring-label");
|
|
71
|
+
if (!ring || !ringLabel || !box) return;
|
|
72
|
+
const raw = (tone || "planned") + "";
|
|
73
|
+
const t =
|
|
74
|
+
raw === "auto" || raw === "info" || raw === "warning" || raw === "danger" || raw === "success"
|
|
75
|
+
? raw
|
|
76
|
+
: "planned";
|
|
77
|
+
const st = style && typeof style === "object" ? style : {};
|
|
78
|
+
const sizeRaw = (st.size || "md") + "";
|
|
79
|
+
const size =
|
|
80
|
+
sizeRaw === "sm" || sizeRaw === "lg" ? sizeRaw : "md";
|
|
81
|
+
const weightRaw = (st.weight || "normal") + "";
|
|
82
|
+
const weight = weightRaw === "bold" ? "bold" : "normal";
|
|
83
|
+
ring.dataset.tone = t;
|
|
84
|
+
ringLabel.dataset.tone = t;
|
|
85
|
+
ring.dataset.size = size;
|
|
86
|
+
ringLabel.dataset.size = size;
|
|
87
|
+
ringLabel.dataset.weight = weight;
|
|
88
|
+
const pad = size === "sm" ? 3 : size === "lg" ? 10 : 6;
|
|
89
|
+
const margin = 6;
|
|
90
|
+
const vw = window.innerWidth;
|
|
91
|
+
const vh = window.innerHeight;
|
|
92
|
+
// Pin ring EXACTLY to the element - never clamp the ring away from
|
|
93
|
+
// the target (that looked "strapped on" the viewport).
|
|
94
|
+
const left = box.x - pad;
|
|
95
|
+
const top = box.y - pad;
|
|
96
|
+
const width = Math.max(4, box.width + pad * 2);
|
|
97
|
+
const height = Math.max(4, box.height + pad * 2);
|
|
98
|
+
ring.style.left = left + "px";
|
|
99
|
+
ring.style.top = top + "px";
|
|
100
|
+
ring.style.width = width + "px";
|
|
101
|
+
ring.style.height = height + "px";
|
|
102
|
+
ring.style.opacity = "1";
|
|
103
|
+
ringLabel.textContent = label || "";
|
|
104
|
+
ringLabel.style.opacity = "1";
|
|
105
|
+
const lw = ringLabel.offsetWidth;
|
|
106
|
+
const lh = ringLabel.offsetHeight;
|
|
107
|
+
let labelLeft = left;
|
|
108
|
+
let labelTop = top + height + 8;
|
|
109
|
+
if (labelTop + lh > vh - margin) labelTop = top - lh - 8;
|
|
110
|
+
if (labelTop < margin) labelTop = margin;
|
|
111
|
+
if (labelLeft + lw > vw - margin) labelLeft = Math.max(margin, vw - margin - lw);
|
|
112
|
+
if (labelLeft < margin) labelLeft = margin;
|
|
113
|
+
ringLabel.style.left = labelLeft + "px";
|
|
114
|
+
ringLabel.style.top = labelTop + "px";
|
|
115
|
+
if (window.__wgTodosSetBehind) window.__wgTodosSetBehind(true);
|
|
116
|
+
if (window.__wgTodosAvoidRingCollision) window.__wgTodosAvoidRingCollision(box);
|
|
117
|
+
};
|
|
118
|
+
/** Dim/lower floating todo docks while a ring caption is the focus. */
|
|
119
|
+
window.__wgTodosSetBehind = (on) => {
|
|
120
|
+
const ui = window.__wgTodoDockUi || {};
|
|
121
|
+
if (ui.behindRing === false) {
|
|
122
|
+
document.querySelectorAll(".wg-todo-dock, #wg-todo-dock").forEach((el) => {
|
|
123
|
+
el.classList.remove("wg-todo-behind");
|
|
124
|
+
});
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
document.querySelectorAll(".wg-todo-dock, #wg-todo-dock").forEach((el) => {
|
|
128
|
+
el.classList.toggle("wg-todo-behind", !!on);
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* If the active ring box intersects a todo dock, flip L/R once.
|
|
133
|
+
* Locked until hideRing so follow-rAF does not thrash.
|
|
134
|
+
*/
|
|
135
|
+
window.__wgTodosAvoidRingCollision = (box) => {
|
|
136
|
+
const ui = window.__wgTodoDockUi || {};
|
|
137
|
+
if (ui.collision === false) return;
|
|
138
|
+
if (!box || window.__wgTodoCollisionLocked) return;
|
|
139
|
+
const pad = 10;
|
|
140
|
+
const rx1 = box.x - pad;
|
|
141
|
+
const ry1 = box.y - pad;
|
|
142
|
+
const rx2 = box.x + box.width + pad;
|
|
143
|
+
const ry2 = box.y + box.height + pad;
|
|
144
|
+
const overlaps = (el) => {
|
|
145
|
+
const r = el.getBoundingClientRect();
|
|
146
|
+
return !(r.right < rx1 || r.left > rx2 || r.bottom < ry1 || r.top > ry2);
|
|
147
|
+
};
|
|
148
|
+
const docks = [...document.querySelectorAll(".wg-todo-dock, #wg-todo-dock")].filter(
|
|
149
|
+
(el, i, arr) => arr.indexOf(el) === i,
|
|
150
|
+
);
|
|
151
|
+
for (const dock of docks) {
|
|
152
|
+
if (!overlaps(dock)) continue;
|
|
153
|
+
const cur = dock.dataset.pos === "right" ? "right" : "left";
|
|
154
|
+
dock.dataset.pos = cur === "right" ? "left" : "right";
|
|
155
|
+
try {
|
|
156
|
+
localStorage.setItem("wg-todo-pos", dock.dataset.pos);
|
|
157
|
+
} catch {
|
|
158
|
+
/* ignore */
|
|
159
|
+
}
|
|
160
|
+
// Still colliding after flip - tuck (shorter max-height).
|
|
161
|
+
if (overlaps(dock)) dock.dataset.tucked = "1";
|
|
162
|
+
else delete dock.dataset.tucked;
|
|
163
|
+
window.__wgTodoCollisionLocked = true;
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
/** Live-follow a selector with rAF (accurate under device shell scale). */
|
|
168
|
+
window.__wgStopRingFollow = () => {
|
|
169
|
+
if (window.__wgRingFollowRaf) {
|
|
170
|
+
cancelAnimationFrame(window.__wgRingFollowRaf);
|
|
171
|
+
window.__wgRingFollowRaf = 0;
|
|
172
|
+
}
|
|
173
|
+
window.__wgRingFollowSel = "";
|
|
174
|
+
};
|
|
175
|
+
window.__wgFollowRing = (sel, label, tone, style, focus) => {
|
|
176
|
+
window.__wgStopRingFollow();
|
|
177
|
+
if (!sel) return;
|
|
178
|
+
window.__wgRingFollowSel = sel;
|
|
179
|
+
const tick = () => {
|
|
180
|
+
if (window.__wgRingFollowSel !== sel) return;
|
|
181
|
+
const el = document.querySelector(sel);
|
|
182
|
+
if (!el) {
|
|
183
|
+
window.__wgRingFollowRaf = requestAnimationFrame(tick);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const r = el.getBoundingClientRect();
|
|
187
|
+
const box = { x: r.x, y: r.y, width: r.width, height: r.height };
|
|
188
|
+
if (window.__wgPositionRing) {
|
|
189
|
+
window.__wgPositionRing(box, label, tone || "planned", style || {});
|
|
190
|
+
}
|
|
191
|
+
if (focus && window.__wgApplyFocus) window.__wgApplyFocus(box);
|
|
192
|
+
window.__wgRingFollowRaf = requestAnimationFrame(tick);
|
|
193
|
+
};
|
|
194
|
+
tick();
|
|
195
|
+
};
|
|
196
|
+
window.__wgClickPulse = (x, y, tone) => {
|
|
197
|
+
const pulse = document.getElementById("wg-click-pulse");
|
|
198
|
+
if (!pulse) return;
|
|
199
|
+
const raw = (tone || "planned") + "";
|
|
200
|
+
pulse.dataset.tone =
|
|
201
|
+
raw === "auto" || raw === "info" || raw === "warning" || raw === "danger" || raw === "success"
|
|
202
|
+
? raw
|
|
203
|
+
: "planned";
|
|
204
|
+
pulse.style.left = x + "px";
|
|
205
|
+
pulse.style.top = y + "px";
|
|
206
|
+
pulse.classList.remove("wg-pulse");
|
|
207
|
+
void pulse.offsetWidth;
|
|
208
|
+
pulse.classList.add("wg-pulse");
|
|
209
|
+
};
|
|
210
|
+
window.__wgHideRing = (opts) => {
|
|
211
|
+
// While a narrate() call owns the ring (mid multi-step action),
|
|
212
|
+
// the auto-highlight click/fill patches' own end-of-step hide is
|
|
213
|
+
// a no-op - narrate() itself clears it once the WHOLE wrapped
|
|
214
|
+
// action finishes, not just its first sub-step.
|
|
215
|
+
if (window.__wgNarrateOwnsRing) return;
|
|
216
|
+
if (window.__wgStopRingFollow) window.__wgStopRingFollow();
|
|
217
|
+
const ring = document.getElementById("wg-ring");
|
|
218
|
+
const ringLabel = document.getElementById("wg-ring-label");
|
|
219
|
+
if (ring) ring.style.opacity = "0";
|
|
220
|
+
if (ringLabel) ringLabel.style.opacity = "0";
|
|
221
|
+
if (window.__wgClearFocus) window.__wgClearFocus();
|
|
222
|
+
if (window.__wgTodosSetBehind) window.__wgTodosSetBehind(false);
|
|
223
|
+
window.__wgTodoCollisionLocked = false;
|
|
224
|
+
document.querySelectorAll(".wg-todo-dock[data-tucked], #wg-todo-dock[data-tucked]").forEach((el) => {
|
|
225
|
+
delete el.dataset.tucked;
|
|
226
|
+
});
|
|
227
|
+
// Honor zoomOut:false - keep camera until next zoom / desktop clear.
|
|
228
|
+
const forceClear = opts && opts.clearZoom === true;
|
|
229
|
+
const skipClear = opts && opts.clearZoom === false;
|
|
230
|
+
if (
|
|
231
|
+
!skipClear &&
|
|
232
|
+
(forceClear || window.__wgZoomOutOnHide !== false) &&
|
|
233
|
+
window.__wgClearZoom
|
|
234
|
+
) {
|
|
235
|
+
window.__wgClearZoom();
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
window.__wgClearFocus = () => {
|
|
239
|
+
const veil = document.getElementById("wg-focus-veil");
|
|
240
|
+
if (!veil) return;
|
|
241
|
+
veil.classList.remove("wg-in");
|
|
242
|
+
setTimeout(() => {
|
|
243
|
+
const v = document.getElementById("wg-focus-veil");
|
|
244
|
+
if (v && !v.classList.contains("wg-in")) v.remove();
|
|
245
|
+
}, 320);
|
|
246
|
+
};
|
|
247
|
+
window.__wgApplyFocus = (box) => {
|
|
248
|
+
if (!box) {
|
|
249
|
+
if (window.__wgClearFocus) window.__wgClearFocus();
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
let veil = document.getElementById("wg-focus-veil");
|
|
253
|
+
if (!veil) {
|
|
254
|
+
veil = document.createElement("div");
|
|
255
|
+
veil.id = "wg-focus-veil";
|
|
256
|
+
veil.setAttribute("data-wg-ui", "1");
|
|
257
|
+
document.documentElement.appendChild(veil);
|
|
258
|
+
}
|
|
259
|
+
const pad = 10;
|
|
260
|
+
veil.style.left = Math.max(0, box.x - pad) + "px";
|
|
261
|
+
veil.style.top = Math.max(0, box.y - pad) + "px";
|
|
262
|
+
veil.style.width = Math.max(8, box.width + pad * 2) + "px";
|
|
263
|
+
veil.style.height = Math.max(8, box.height + pad * 2) + "px";
|
|
264
|
+
void veil.offsetWidth;
|
|
265
|
+
veil.classList.add("wg-in");
|
|
266
|
+
};
|
|
267
|
+
}
|