@reticlehq/browser 3.4.0 → 3.6.0
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/actions/actions-dom.js +44 -43
- package/dist/actions/actions-press.d.ts +10 -4
- package/dist/actions/actions-press.js +108 -17
- package/dist/actions/actions.d.ts +2 -1
- package/dist/actions/actions.js +25 -26
- package/dist/actions/danger-context.js +1 -1
- package/dist/commands/commands.js +2 -6
- package/dist/connect-options.d.ts +1 -0
- package/dist/dom/a11y.js +43 -11
- package/dist/dom/addressing/refs.js +2 -1
- package/dist/dom/addressing/testid-attr.d.ts +4 -0
- package/dist/dom/addressing/testid-attr.js +14 -0
- package/dist/dom/occlusion.js +5 -1
- package/dist/dom/query.d.ts +1 -0
- package/dist/dom/query.js +42 -15
- package/dist/dom/snapshot.js +1 -0
- package/dist/observers/departure.js +51 -9
- package/dist/observers/dom.js +3 -2
- package/dist/observers/field.js +5 -3
- package/dist/observers/health.js +63 -2
- package/dist/observers/network-stream.d.ts +1 -1
- package/dist/observers/network-stream.js +3 -3
- package/dist/observers/network.js +1 -1
- package/dist/observers/storage.js +6 -1
- package/dist/presenter/carousel/carousel.d.ts +5 -2
- package/dist/presenter/carousel/carousel.js +34 -7
- package/dist/presenter/carousel/offer-card.d.ts +2 -2
- package/dist/presenter/carousel/offer-card.js +2 -1
- package/dist/presenter/carousel/panel-slides.d.ts +4 -3
- package/dist/presenter/carousel/panel-slides.js +43 -6
- package/dist/presenter/carousel/talk-card.d.ts +1 -1
- package/dist/presenter/chrome/log-kinds.d.ts +5 -0
- package/dist/presenter/chrome/log-kinds.js +1 -0
- package/dist/presenter/chrome/log-memory.d.ts +12 -0
- package/dist/presenter/chrome/log-memory.js +39 -0
- package/dist/presenter/chrome/presenter-hud-chrome.d.ts +1 -0
- package/dist/presenter/chrome/presenter-hud-chrome.js +7 -1
- package/dist/presenter/chrome/presenter-log.d.ts +5 -4
- package/dist/presenter/chrome/presenter-log.js +55 -5
- package/dist/presenter/chrome/presenter-report-copy.d.ts +11 -1
- package/dist/presenter/chrome/presenter-report-copy.js +14 -1
- package/dist/presenter/hud-telemetry.js +31 -2
- package/dist/presenter/icons/presenter-heroicons-data.d.ts +1 -0
- package/dist/presenter/icons/presenter-heroicons-data.js +4 -3
- package/dist/presenter/icons/presenter-icons.d.ts +2 -0
- package/dist/presenter/icons/presenter-icons.js +2 -0
- package/dist/presenter/presenter-account.d.ts +5 -4
- package/dist/presenter/presenter-account.js +23 -10
- package/dist/presenter/presenter-chat-views.d.ts +21 -0
- package/dist/presenter/presenter-chat-views.js +382 -0
- package/dist/presenter/presenter-config.d.ts +3 -2
- package/dist/presenter/presenter-config.js +4 -3
- package/dist/presenter/presenter-controls.d.ts +4 -2
- package/dist/presenter/presenter-controls.js +231 -51
- package/dist/presenter/presenter-design-tokens.d.ts +1 -0
- package/dist/presenter/presenter-design-tokens.js +34 -0
- package/dist/presenter/presenter-dock-layout.js +1 -1
- package/dist/presenter/presenter-frame.d.ts +12 -0
- package/dist/presenter/presenter-frame.js +189 -0
- package/dist/presenter/presenter-plan.d.ts +10 -0
- package/dist/presenter/presenter-plan.js +185 -0
- package/dist/presenter/presenter-report-styles.js +7 -0
- package/dist/presenter/presenter-report.d.ts +1 -1
- package/dist/presenter/presenter-report.js +53 -16
- package/dist/presenter/presenter-settings-styles.js +10 -0
- package/dist/presenter/presenter-settings.d.ts +5 -0
- package/dist/presenter/presenter-settings.js +71 -11
- package/dist/presenter/presenter-shell-styles.js +39 -42
- package/dist/presenter/presenter-shell.d.ts +8 -1
- package/dist/presenter/presenter-shell.js +124 -72
- package/dist/presenter/presenter-styles.js +9 -1
- package/dist/presenter/presenter.d.ts +4 -3
- package/dist/presenter/presenter.js +101 -13
- package/dist/presenter/tour/tour.d.ts +4 -1
- package/dist/presenter/tour/tour.js +6 -4
- package/dist/recorder/recorder.js +2 -2
- package/dist/registry/auto-testids.js +3 -4
- package/dist/registry/declared-platform.js +2 -0
- package/dist/reticle-inject.js +880 -513
- package/dist/reticle.d.ts +10 -1
- package/dist/reticle.js +73 -9
- package/dist/review/annotator-styles.d.ts +1 -1
- package/dist/review/annotator.d.ts +7 -0
- package/dist/review/annotator.js +20 -2
- package/dist/review/mark-anchor.js +2 -2
- package/dist/session-continuity.js +2 -1
- package/dist/storage-keys.d.ts +14 -0
- package/dist/storage-keys.js +17 -0
- package/package.json +3 -2
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { ACCOUNT_SIGNIN_ATTR, ACCOUNT_TEXT } from './presenter-account.js';
|
|
2
|
+
import { CHAT_ATTR, CHAT_PILL_ATTR, CHAT_PLACEMENT_ATTR, DOCK_ALIGN_ATTR, DOCK_ATTR, MIN_ATTR, REPORT_ATTR, REPORT_PANEL_ATTR, SETTINGS_ATTR, SETTINGS_PANEL_ATTR, SETTINGS_PLACEMENT_ATTR, CHAT_PANEL_ATTR, } from './presenter-config.js';
|
|
3
|
+
import { HUD_DROP_SHADOW, HUD_GLASS_PAINT } from './chrome/presenter-hud-chrome.js';
|
|
4
|
+
export const RAIL_ATTR = 'data-reticle-rail';
|
|
5
|
+
export const RAIL_SIGNIN_ATTR = 'data-reticle-rail-signin';
|
|
6
|
+
export const RAIL_PROMO_ATTR = 'data-reticle-rail-promo';
|
|
7
|
+
const PAGE_OPEN_ATTR = 'data-reticle-page-open';
|
|
8
|
+
const PAGE_PANEL_ATTR = 'data-reticle-page-panel';
|
|
9
|
+
const OVERLAY = 'data-reticle-overlay';
|
|
10
|
+
export const RAIL_TEXT = {
|
|
11
|
+
TITLE: 'Sign in to try Reticle Harness',
|
|
12
|
+
DETAIL: 'Free credits every month for autonomous end-to-end checks',
|
|
13
|
+
CTA: 'Sign in',
|
|
14
|
+
};
|
|
15
|
+
export const RAIL_HTML = `<div ${RAIL_ATTR} class="reticle-rail" role="complementary" aria-label="Reticle">
|
|
16
|
+
<div ${RAIL_SIGNIN_ATTR} class="reticle-rail-signin" hidden>
|
|
17
|
+
<span class="reticle-rail-copy"><strong>${RAIL_TEXT.TITLE}</strong><span>${RAIL_TEXT.DETAIL}</span></span>
|
|
18
|
+
<button type="button" ${ACCOUNT_SIGNIN_ATTR} class="reticle-rail-cta" title="${ACCOUNT_TEXT.SIGNIN_TITLE}">${RAIL_TEXT.CTA}</button>
|
|
19
|
+
</div>
|
|
20
|
+
<div ${RAIL_PROMO_ATTR} class="reticle-rail-promo"></div>
|
|
21
|
+
</div>`;
|
|
22
|
+
export function paintRail(root, account) {
|
|
23
|
+
const signin = root.querySelector(`[${RAIL_SIGNIN_ATTR}]`);
|
|
24
|
+
const promo = root.querySelector(`[${RAIL_PROMO_ATTR}]`);
|
|
25
|
+
const signedOut = account !== undefined && !account.signedIn;
|
|
26
|
+
if (signin !== null)
|
|
27
|
+
signin.hidden = !signedOut;
|
|
28
|
+
if (promo !== null)
|
|
29
|
+
promo.hidden = signedOut;
|
|
30
|
+
}
|
|
31
|
+
const PAGES = `:is([${CHAT_PANEL_ATTR}],[${PAGE_PANEL_ATTR}],[${REPORT_PANEL_ATTR}],[${SETTINGS_PANEL_ATTR}])`;
|
|
32
|
+
const ANY_OPEN = `[${OVERLAY}][${MIN_ATTR}="0"]:is([${CHAT_ATTR}="1"],[${PAGE_OPEN_ATTR}],[${REPORT_ATTR}="1"],[${SETTINGS_ATTR}="1"])`;
|
|
33
|
+
const BELOW = `[${DOCK_ATTR}]:is([${CHAT_PLACEMENT_ATTR}="below"],[${SETTINGS_PLACEMENT_ATTR}="below"])`;
|
|
34
|
+
export const FRAME_CSS = `
|
|
35
|
+
[${DOCK_ATTR}]{
|
|
36
|
+
/* Short enough to leave most of a laptop screen to the app; the log scrolls inside it. */
|
|
37
|
+
--reticle-frame-h:min(440px,60vh,calc(100vh - 190px));
|
|
38
|
+
--reticle-rail-h:52px;--reticle-frame-gap:6px;}
|
|
39
|
+
[${DOCK_ATTR}] ${PAGES}{
|
|
40
|
+
right:0;left:auto;top:auto;
|
|
41
|
+
bottom:calc(100% + 8px + var(--reticle-rail-h) + var(--reticle-frame-gap));
|
|
42
|
+
width:var(--reticle-dock-w);max-width:min(var(--reticle-dock-w),calc(100vw - 24px));
|
|
43
|
+
height:var(--reticle-frame-h);max-height:var(--reticle-frame-h);
|
|
44
|
+
border-radius:var(--reticle-hud-radius-lg);
|
|
45
|
+
${HUD_GLASS_PAINT}}
|
|
46
|
+
[${DOCK_ATTR}] [${SETTINGS_PANEL_ATTR}]::after{display:none;}
|
|
47
|
+
[${DOCK_ATTR}] [${SETTINGS_PANEL_ATTR}] .reticle-settings-inner{height:100%;}
|
|
48
|
+
[${DOCK_ATTR}] [${SETTINGS_PANEL_ATTR}] :is(.reticle-settings-body,.reticle-settings-foot){background:transparent;}
|
|
49
|
+
[${DOCK_ATTR}] [${REPORT_PANEL_ATTR}] .reticle-report-inner{height:100%;}
|
|
50
|
+
/* The Agent Log section fills the frame, so the footer sits on the bottom edge, not mid-panel. */
|
|
51
|
+
[${CHAT_PANEL_ATTR}] .reticle-chat-view{flex:1;min-height:0;display:flex;flex-direction:column;}
|
|
52
|
+
[${CHAT_PANEL_ATTR}] .reticle-chat-view > .reticle-hud-log-well{flex:1;min-height:0;}
|
|
53
|
+
/*
|
|
54
|
+
* Two rows above the log in every state: the header and the status row. "Session ended" (or the
|
|
55
|
+
* handoff notice) takes the status text's place instead of arriving as a row of its own, and the
|
|
56
|
+
* Harness control sits in the footer beside the project capsule.
|
|
57
|
+
*/
|
|
58
|
+
[${OVERLAY}] [${CHAT_PANEL_ATTR}] .reticle-act-strip .reticle-banner{display:none;flex:1;min-width:0;min-height:0;padding:0;border:0;background:none;
|
|
59
|
+
font-size:11px;font-weight:600;line-height:1.3;color:var(--reticle-hud-text);
|
|
60
|
+
overflow:hidden;-webkit-box-orient:vertical;-webkit-line-clamp:2;}
|
|
61
|
+
[${OVERLAY}][data-reticle-state="ended"] [${CHAT_PANEL_ATTR}] .reticle-act-strip .reticle-banner{display:-webkit-box;}
|
|
62
|
+
[${OVERLAY}][data-reticle-state="ended"] [${CHAT_PANEL_ATTR}] .reticle-act-strip .reticle-act{display:none;}
|
|
63
|
+
[${CHAT_PANEL_ATTR}] .reticle-foot-workspace-row{gap:var(--reticle-hud-space-2);}
|
|
64
|
+
[${CHAT_PANEL_ATTR}] [data-reticle-foot] .reticle-harness-spot{flex:none;margin-left:auto;padding:0;border:0;}
|
|
65
|
+
[${CHAT_PANEL_ATTR}] [data-reticle-foot] .reticle-harness-row{min-height:0;gap:var(--reticle-hud-space-2);}
|
|
66
|
+
[${CHAT_PANEL_ATTR}] [data-reticle-foot] .reticle-harness-copy strong{font-size:var(--reticle-hud-size-xs);color:var(--reticle-hud-text-muted);white-space:nowrap;}
|
|
67
|
+
/* The explanation stays for screen readers; sighted users get it from the link it sits beside. */
|
|
68
|
+
[${CHAT_PANEL_ATTR}] [data-reticle-foot] .reticle-harness-copy span{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;}
|
|
69
|
+
${BELOW} ${PAGES}{bottom:auto;top:calc(100% + 8px + var(--reticle-rail-h) + var(--reticle-frame-gap));}
|
|
70
|
+
[${DOCK_ATTR}][${DOCK_ALIGN_ATTR}="start"] :is(${PAGES},[${RAIL_ATTR}]){right:auto;left:0;}
|
|
71
|
+
|
|
72
|
+
/* ── Every page's header: title over a one-line subtitle, its actions, then close. ────────────── */
|
|
73
|
+
[${DOCK_ATTR}] :is(.reticle-page-heading,.reticle-report-head,.reticle-settings-head){
|
|
74
|
+
flex:none;display:flex;align-items:center;gap:var(--reticle-hud-space-2);min-height:52px;box-sizing:border-box;
|
|
75
|
+
padding:var(--reticle-hud-space-2) var(--reticle-hud-space-2) var(--reticle-hud-space-2) var(--reticle-hud-space-3);
|
|
76
|
+
border-bottom:1px solid var(--reticle-hud-border);background:none;}
|
|
77
|
+
[${DOCK_ATTR}] .reticle-page-titles{display:flex;flex:1;min-width:0;flex-direction:column;gap:1px;}
|
|
78
|
+
[${DOCK_ATTR}] :is(.reticle-page-heading strong,.reticle-report-title,.reticle-settings-title){
|
|
79
|
+
font-size:var(--reticle-hud-size-base);font-weight:600;letter-spacing:0;text-transform:none;color:var(--reticle-hud-text);}
|
|
80
|
+
[${DOCK_ATTR}] .reticle-settings-title{flex:1;}
|
|
81
|
+
[${DOCK_ATTR}] .reticle-page-heading .reticle-page-subtitle{padding:0;border:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
|
|
82
|
+
font-size:var(--reticle-hud-size-xs);color:var(--reticle-hud-text-muted);}
|
|
83
|
+
[${DOCK_ATTR}] .reticle-primary-btn{flex:none;display:inline-flex;align-items:center;gap:5px;height:28px;padding:0 var(--reticle-hud-space-3);
|
|
84
|
+
border:0;border-radius:var(--reticle-hud-radius-pill);background:var(--reticle-hud-brand);color:#160b02;
|
|
85
|
+
font:inherit;font-size:var(--reticle-hud-size-sm);font-weight:700;white-space:nowrap;cursor:pointer;}
|
|
86
|
+
[${DOCK_ATTR}] .reticle-primary-btn:hover{filter:brightness(1.08);}
|
|
87
|
+
[${DOCK_ATTR}] .reticle-page-heading .reticle-primary-btn{width:auto;height:28px;border-radius:var(--reticle-hud-radius-pill);background:var(--reticle-hud-brand);color:#160b02;font-size:var(--reticle-hud-size-sm);}
|
|
88
|
+
/* One close control everywhere: a quiet 28px square, the same glyph weight on every page. */
|
|
89
|
+
[${DOCK_ATTR}] :is([data-reticle-page-close],.reticle-report-close,.reticle-settings-close){flex:none;width:28px;height:28px;
|
|
90
|
+
display:inline-flex;align-items:center;justify-content:center;padding:0;border:0;border-radius:var(--reticle-hud-radius-sm);
|
|
91
|
+
background:transparent;color:var(--reticle-hud-text-muted);font-size:18px;line-height:1;cursor:pointer;}
|
|
92
|
+
[${DOCK_ATTR}] :is([data-reticle-page-close],.reticle-report-close,.reticle-settings-close):hover{background:var(--reticle-hud-hover);color:var(--reticle-hud-text);}
|
|
93
|
+
[${DOCK_ATTR}] .reticle-primary-btn[data-active="1"]{background:var(--reticle-hud-text);color:var(--reticle-hud-ground);}
|
|
94
|
+
[${DOCK_ATTR}] .reticle-primary-btn svg{display:block;fill:none;stroke:currentColor;stroke-width:1.8;}
|
|
95
|
+
/* One empty state for every page: what is missing, then how it gets filled. */
|
|
96
|
+
[${DOCK_ATTR}] :is(.reticle-flows-empty,.reticle-annotation-empty){display:flex;flex-direction:column;align-items:center;gap:6px;
|
|
97
|
+
margin:auto 0;padding:40px var(--reticle-hud-space-4);text-align:center;}
|
|
98
|
+
[${DOCK_ATTR}] :is(.reticle-flows-empty,.reticle-annotation-empty) strong{font-size:var(--reticle-hud-size-base);font-weight:600;color:var(--reticle-hud-text);}
|
|
99
|
+
[${DOCK_ATTR}] :is(.reticle-flows-empty,.reticle-annotation-empty) span{max-width:260px;font-size:var(--reticle-hud-size-sm);line-height:1.45;color:var(--reticle-hud-text-muted);}
|
|
100
|
+
|
|
101
|
+
/* ── Saved flows: one row per journey. ───────────────────────────────────────────────────────── */
|
|
102
|
+
[${PAGE_PANEL_ATTR}="flows"] .reticle-all-flows{display:flex;flex-direction:column;gap:var(--reticle-hud-space-1);padding:var(--reticle-hud-space-2);}
|
|
103
|
+
[${PAGE_PANEL_ATTR}="flows"] .reticle-flow-row{display:flex;align-items:center;gap:var(--reticle-hud-space-3);width:100%;min-height:48px;
|
|
104
|
+
padding:var(--reticle-hud-space-2) var(--reticle-hud-space-3);border:1px solid transparent;border-radius:var(--reticle-hud-radius-md);
|
|
105
|
+
background:transparent;color:var(--reticle-hud-text);font:inherit;text-align:left;cursor:pointer;}
|
|
106
|
+
[${PAGE_PANEL_ATTR}="flows"] .reticle-flow-row:hover:not(:disabled){background:var(--reticle-hud-hover);border-color:var(--reticle-hud-border);}
|
|
107
|
+
[${PAGE_PANEL_ATTR}="flows"] .reticle-flow-row:focus-visible{outline:2px solid var(--reticle-hud-brand);outline-offset:1px;}
|
|
108
|
+
[${PAGE_PANEL_ATTR}="flows"] .reticle-flow-row:disabled{cursor:not-allowed;opacity:.5;}
|
|
109
|
+
[${PAGE_PANEL_ATTR}="flows"] .reticle-flow-play{flex:none;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;
|
|
110
|
+
border-radius:50%;background:var(--reticle-hud-brand-soft);color:var(--reticle-hud-brand);font-size:10px;}
|
|
111
|
+
[${PAGE_PANEL_ATTR}="flows"] .reticle-flow-row:disabled .reticle-flow-play{background:var(--reticle-hud-inset);color:var(--reticle-hud-text-faint);}
|
|
112
|
+
[${PAGE_PANEL_ATTR}="flows"] .reticle-flow-text{display:flex;flex:1;min-width:0;flex-direction:column;gap:1px;}
|
|
113
|
+
[${PAGE_PANEL_ATTR}="flows"] .reticle-flow-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--reticle-hud-size-sm);font-weight:600;}
|
|
114
|
+
[${PAGE_PANEL_ATTR}="flows"] .reticle-flow-meta{font-size:var(--reticle-hud-size-xs);color:var(--reticle-hud-text-muted);}
|
|
115
|
+
|
|
116
|
+
/* ── Notes: a segmented list switch, then the page's tools on the right. ─────────────────────── */
|
|
117
|
+
[${PAGE_PANEL_ATTR}="annotations"] .reticle-annotations-tabs{gap:var(--reticle-hud-space-1);padding:var(--reticle-hud-space-2) var(--reticle-hud-space-3);}
|
|
118
|
+
/* One segmented switch for every "show me this or that": Notes' list and Impact's scope. */
|
|
119
|
+
[${DOCK_ATTR}] .reticle-segmented{display:inline-flex;flex:none;padding:2px;border-radius:var(--reticle-hud-radius-sm);background:var(--reticle-hud-inset);}
|
|
120
|
+
[${PAGE_PANEL_ATTR}="annotations"] .reticle-segmented{margin-right:auto;}
|
|
121
|
+
[${DOCK_ATTR}] .reticle-segmented button{height:24px;padding:0 var(--reticle-hud-space-3);border:0;border-radius:4px;background:transparent;
|
|
122
|
+
color:var(--reticle-hud-text-muted);font:inherit;font-size:var(--reticle-hud-size-xs);white-space:nowrap;cursor:pointer;}
|
|
123
|
+
[${DOCK_ATTR}] .reticle-segmented button:hover{color:var(--reticle-hud-text);}
|
|
124
|
+
[${DOCK_ATTR}] .reticle-segmented button[aria-pressed="true"]{background:var(--reticle-hud-border-strong);color:var(--reticle-hud-text);}
|
|
125
|
+
[${DOCK_ATTR}] .reticle-report-head .reticle-segmented{margin-left:auto;}
|
|
126
|
+
[${PAGE_PANEL_ATTR}="annotations"] .reticle-annotations-tabs .reticle-copy-all{margin-left:0;color:var(--reticle-hud-text-muted);}
|
|
127
|
+
[${PAGE_PANEL_ATTR}="annotations"] .reticle-annotation-list{display:flex;flex-direction:column;}
|
|
128
|
+
[${PAGE_PANEL_ATTR}="annotations"] .reticle-annotation-item{padding:var(--reticle-hud-space-3) 2px;}
|
|
129
|
+
|
|
130
|
+
/* ── Impact: the streak and the account share the first line instead of a row each. ─────────── */
|
|
131
|
+
[${REPORT_PANEL_ATTR}] .reticle-report-top{display:flex;align-items:center;justify-content:space-between;gap:var(--reticle-hud-space-2);min-height:28px;margin-bottom:var(--reticle-hud-space-2);}
|
|
132
|
+
[${REPORT_PANEL_ATTR}] .reticle-report-top .reticle-report-streak{margin-bottom:0;}
|
|
133
|
+
[${REPORT_PANEL_ATTR}] .reticle-report-top .reticle-report-identity{margin:0 0 0 auto;padding:0;}
|
|
134
|
+
[${REPORT_PANEL_ATTR}] .reticle-report-hero-value{color:var(--reticle-hud-text);}
|
|
135
|
+
[${REPORT_PANEL_ATTR}] .reticle-report-card{background:var(--reticle-hud-inset);border-radius:var(--reticle-hud-radius-md);}
|
|
136
|
+
[${REPORT_PANEL_ATTR}] :is(.reticle-report-foot,.reticle-report-links){padding-left:var(--reticle-hud-space-3);padding-right:var(--reticle-hud-space-3);}
|
|
137
|
+
|
|
138
|
+
/* ── Settings: the help links scroll with the rest instead of pinning a footer over the options. */
|
|
139
|
+
[${SETTINGS_PANEL_ATTR}] .reticle-settings-body .reticle-settings-foot{margin-top:0;border-top:0;}
|
|
140
|
+
[${SETTINGS_PANEL_ATTR}] .reticle-settings-row{flex-wrap:wrap;}
|
|
141
|
+
[${SETTINGS_PANEL_ATTR}] .reticle-settings-helptext{flex-basis:100%;margin:6px 0 2px;font-size:var(--reticle-hud-size-xs);line-height:1.45;color:var(--reticle-hud-text-muted);}
|
|
142
|
+
[${SETTINGS_PANEL_ATTR}] .reticle-settings-help[aria-expanded="true"]{color:var(--reticle-hud-brand);}
|
|
143
|
+
[${SETTINGS_PANEL_ATTR}] .reticle-settings-reset{background:none;text-align:left;padding-left:14px;font-weight:500;}
|
|
144
|
+
|
|
145
|
+
/* ── Toolbar: Settings and Exit are tabs like the four beside them, so they light the same way. ── */
|
|
146
|
+
[data-reticle-hud] .reticle-toolbar-chrome .reticle-tb-btn{width:var(--reticle-hud-control-size);height:var(--reticle-hud-control-size);
|
|
147
|
+
border-radius:var(--reticle-hud-radius-md);background:transparent;color:var(--reticle-hud-text-muted);}
|
|
148
|
+
[data-reticle-hud] .reticle-toolbar-chrome .reticle-tb-btn:hover{background:var(--reticle-hud-hover);color:var(--reticle-hud-text);}
|
|
149
|
+
[data-reticle-hud] .reticle-toolbar-chrome .reticle-tb-btn[data-active="1"],
|
|
150
|
+
[data-reticle-hud] .reticle-toolbar-chrome .reticle-tb-btn[data-active="1"]:hover{background:var(--reticle-hud-inset);color:var(--reticle-hud-text);}
|
|
151
|
+
|
|
152
|
+
[${RAIL_ATTR}]{
|
|
153
|
+
display:none;position:absolute;right:0;left:auto;bottom:calc(100% + 8px);z-index:7;
|
|
154
|
+
box-sizing:border-box;width:var(--reticle-dock-w);max-width:min(var(--reticle-dock-w),calc(100vw - 24px));
|
|
155
|
+
height:var(--reticle-rail-h);padding:0 var(--reticle-hud-space-3);align-items:center;
|
|
156
|
+
border:1px solid color-mix(in srgb,var(--reticle-hud-brand) 30%,var(--reticle-hud-border));
|
|
157
|
+
border-radius:var(--reticle-hud-radius-lg);
|
|
158
|
+
background:linear-gradient(135deg,var(--reticle-hud-brand-soft),var(--reticle-hud-surface) 65%);
|
|
159
|
+
box-shadow:${HUD_DROP_SHADOW};color:var(--reticle-hud-text);font-family:var(--reticle-font);
|
|
160
|
+
pointer-events:auto;}
|
|
161
|
+
${BELOW} [${RAIL_ATTR}]{bottom:auto;top:calc(100% + 8px);}
|
|
162
|
+
${ANY_OPEN} [${RAIL_ATTR}]{display:flex;}
|
|
163
|
+
/* The capsule and the rail share a slot: the capsule is for "nothing open", the rail for "a page is". */
|
|
164
|
+
${ANY_OPEN} [${CHAT_PILL_ATTR}]{display:none !important;}
|
|
165
|
+
[${RAIL_ATTR}] > [hidden]{display:none !important;}
|
|
166
|
+
[${RAIL_ATTR}] .reticle-rail-signin,[${RAIL_ATTR}] .reticle-rail-promo{flex:1;min-width:0;}
|
|
167
|
+
[${RAIL_ATTR}] .reticle-rail-signin{display:flex;align-items:center;gap:var(--reticle-hud-space-3);}
|
|
168
|
+
[${RAIL_ATTR}] .reticle-rail-copy{display:flex;flex:1;min-width:0;flex-direction:column;line-height:1.3;}
|
|
169
|
+
[${RAIL_ATTR}] .reticle-rail-copy strong{font-size:var(--reticle-hud-size-sm);font-weight:600;}
|
|
170
|
+
[${RAIL_ATTR}] .reticle-rail-copy span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:var(--reticle-hud-size-xs);color:var(--reticle-hud-text-muted);}
|
|
171
|
+
[${RAIL_ATTR}] .reticle-rail-cta{flex:none;height:28px;padding:0 var(--reticle-hud-space-3);border:0;border-radius:var(--reticle-hud-radius-pill);
|
|
172
|
+
background:var(--reticle-hud-brand);color:#160b02;font:inherit;font-size:var(--reticle-hud-size-sm);font-weight:700;cursor:pointer;}
|
|
173
|
+
[${RAIL_ATTR}] .reticle-rail-cta:hover{filter:brightness(1.08);}
|
|
174
|
+
[${RAIL_ATTR}] .reticle-rail-cta:focus-visible{outline:2px solid var(--reticle-hud-text);outline-offset:2px;}
|
|
175
|
+
/* The promo: one line of copy, its link, and dots. The carousel draws no frame of its own here. */
|
|
176
|
+
[${RAIL_ATTR}] .reticle-carousel{margin:0;padding:0;border:0;background:none;}
|
|
177
|
+
[${RAIL_ATTR}] .reticle-carousel-track{height:32px;overflow:hidden;}
|
|
178
|
+
[${RAIL_ATTR}] :is(.reticle-carousel-close,.reticle-carousel-arrow,[data-reticle-carousel-position]){display:none;}
|
|
179
|
+
[${RAIL_ATTR}] .reticle-carousel-controls{position:absolute;right:var(--reticle-hud-space-3);top:6px;height:auto;margin:0;}
|
|
180
|
+
[${RAIL_ATTR}] .reticle-carousel-dots{position:static;display:flex;gap:4px;margin:0;}
|
|
181
|
+
[${RAIL_ATTR}] .reticle-carousel-dot{width:10px;height:3px;padding:0;border:0;border-radius:99px;background:var(--reticle-hud-text-muted);opacity:.35;cursor:pointer;}
|
|
182
|
+
[${RAIL_ATTR}] .reticle-carousel-dot[aria-current="true"]{background:var(--reticle-hud-brand);opacity:1;}
|
|
183
|
+
[${RAIL_ATTR}] .reticle-promo-copy{display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:8px;padding-right:0;}
|
|
184
|
+
[${RAIL_ATTR}] .reticle-promo-kicker{display:none;}
|
|
185
|
+
[${RAIL_ATTR}] .reticle-promo-title{grid-column:1/-1;overflow:hidden;padding-right:40px;font-size:var(--reticle-hud-size-sm);font-weight:600;line-height:1.3;text-overflow:ellipsis;white-space:nowrap;}
|
|
186
|
+
[${RAIL_ATTR}] .reticle-promo-detail{grid-column:1;overflow:hidden;color:var(--reticle-hud-text-muted);font-size:var(--reticle-hud-size-xs);line-height:1.3;text-overflow:ellipsis;white-space:nowrap;}
|
|
187
|
+
[${RAIL_ATTR}] .reticle-promo-link{position:static;grid-column:2;color:var(--reticle-hud-brand);font-size:var(--reticle-hud-size-xs);font-weight:600;text-decoration:none;white-space:nowrap;}
|
|
188
|
+
[${RAIL_ATTR}] .reticle-promo-link:hover{text-decoration:underline;}
|
|
189
|
+
`;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type PlanView } from '@reticlehq/core';
|
|
2
|
+
export declare const PLAN_ATTR = "data-reticle-plan";
|
|
3
|
+
export declare const PLAN_HTML = "<div data-reticle-plan class=\"reticle-plan\" hidden></div>";
|
|
4
|
+
export declare const PLAN_CSS: string;
|
|
5
|
+
export declare function parsePlanView(args: Record<string, unknown>): PlanView | undefined;
|
|
6
|
+
export declare class PlanBoard {
|
|
7
|
+
#private;
|
|
8
|
+
mount(root: HTMLElement): void;
|
|
9
|
+
paint(view: PlanView): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { ScriptStatus } from '@reticlehq/core';
|
|
2
|
+
export const PLAN_ATTR = 'data-reticle-plan';
|
|
3
|
+
const CLOSE_ATTR = 'data-reticle-plan-close';
|
|
4
|
+
const JOURNEY_ATTR = 'data-reticle-plan-journey';
|
|
5
|
+
export const PLAN_HTML = `<div ${PLAN_ATTR} class="reticle-plan" hidden></div>`;
|
|
6
|
+
const TEXT = {
|
|
7
|
+
TITLE: 'Harness plan',
|
|
8
|
+
CLOSE: 'Hide the plan',
|
|
9
|
+
WAITS: 'waits on',
|
|
10
|
+
AT_ONCE: 'at once',
|
|
11
|
+
LANE: 'Lane',
|
|
12
|
+
};
|
|
13
|
+
const GLYPH = {
|
|
14
|
+
[ScriptStatus.PENDING]: '○',
|
|
15
|
+
[ScriptStatus.RUNNING]: '◐',
|
|
16
|
+
[ScriptStatus.PASSED]: '✓',
|
|
17
|
+
[ScriptStatus.FAILED]: '✗',
|
|
18
|
+
[ScriptStatus.BLOCKED]: '⊘',
|
|
19
|
+
[ScriptStatus.NOT_TAKEN]: '–',
|
|
20
|
+
};
|
|
21
|
+
export const PLAN_CSS = `
|
|
22
|
+
[data-reticle-chat-panel] .reticle-plan{flex:none;margin:0 10px 8px;padding:8px 10px;border:1px solid var(--reticle-hud-border);border-radius:10px;background:rgba(255,255,255,.03);}
|
|
23
|
+
[data-reticle-chat-panel] .reticle-plan[hidden]{display:none;}
|
|
24
|
+
[data-reticle-chat-panel] .reticle-plan-head{display:flex;align-items:center;gap:8px;margin-bottom:6px;font-size:var(--reticle-hud-size-xs);color:var(--reticle-hud-text-muted);}
|
|
25
|
+
[data-reticle-chat-panel] .reticle-plan-head strong{color:var(--reticle-hud-text);font-size:var(--reticle-hud-size-sm);font-weight:600;}
|
|
26
|
+
[data-reticle-chat-panel] .reticle-plan-head span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
27
|
+
[data-reticle-chat-panel] .reticle-plan-close{flex:none;border:0;background:none;color:inherit;cursor:pointer;font-size:14px;line-height:1;padding:2px 4px;}
|
|
28
|
+
[data-reticle-chat-panel] .reticle-plan-lanes{display:flex;flex-direction:column;gap:6px;max-height:240px;overflow:auto;scrollbar-width:none;}
|
|
29
|
+
[data-reticle-chat-panel] .reticle-plan-lanes::-webkit-scrollbar{display:none;}
|
|
30
|
+
[data-reticle-chat-panel] .reticle-plan-lane{display:flex;gap:6px;align-items:flex-start;min-width:0;}
|
|
31
|
+
[data-reticle-chat-panel] .reticle-plan-lane-name{flex:none;width:16px;padding-top:6px;font-size:10px;font-weight:600;text-align:center;color:var(--reticle-hud-text-muted);}
|
|
32
|
+
[data-reticle-chat-panel] .reticle-plan-lane-journeys{display:flex;flex:1;min-width:0;flex-direction:column;gap:4px;}
|
|
33
|
+
[data-reticle-chat-panel] .reticle-plan-journey{display:block;width:100%;text-align:left;border:1px solid transparent;border-radius:8px;padding:5px 7px;background:rgba(255,255,255,.04);color:var(--reticle-hud-text);font:inherit;font-size:var(--reticle-hud-size-xs);cursor:pointer;}
|
|
34
|
+
[data-reticle-chat-panel] .reticle-plan-journey[data-status="running"]{border-color:var(--reticle-c-active);box-shadow:0 0 12px -4px var(--reticle-c-active);}
|
|
35
|
+
[data-reticle-chat-panel] .reticle-plan-journey[data-status="blocked"],[data-reticle-chat-panel] .reticle-plan-journey[data-status="not-taken"]{opacity:.55;}
|
|
36
|
+
[data-reticle-chat-panel] .reticle-plan-row{display:flex;gap:6px;align-items:baseline;min-width:0;}
|
|
37
|
+
[data-reticle-chat-panel] .reticle-plan-row b{flex:1;min-width:0;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
38
|
+
[data-reticle-chat-panel] .reticle-plan-glyph{flex:none;width:12px;text-align:center;}
|
|
39
|
+
[data-reticle-chat-panel] [data-status="running"]>.reticle-plan-row>.reticle-plan-glyph,[data-reticle-chat-panel] .reticle-plan-step[data-status="running"]>.reticle-plan-glyph{color:var(--reticle-c-active);animation:reticle-flow-pulse 1.2s ease-in-out infinite;}
|
|
40
|
+
[data-reticle-chat-panel] [data-status="passed"]>.reticle-plan-row>.reticle-plan-glyph,[data-reticle-chat-panel] .reticle-plan-step[data-status="passed"]>.reticle-plan-glyph{color:#4ade80;}
|
|
41
|
+
[data-reticle-chat-panel] [data-status="failed"]>.reticle-plan-row>.reticle-plan-glyph,[data-reticle-chat-panel] .reticle-plan-step[data-status="failed"]>.reticle-plan-glyph{color:#f87171;}
|
|
42
|
+
[data-reticle-chat-panel] .reticle-plan-meta{display:block;margin:2px 0 0 18px;color:var(--reticle-hud-text-muted);font-size:10px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
43
|
+
[data-reticle-chat-panel] .reticle-plan-steps{margin:4px 0 0 18px;display:flex;flex-direction:column;gap:2px;}
|
|
44
|
+
[data-reticle-chat-panel] .reticle-plan-step{display:flex;gap:6px;color:var(--reticle-hud-text-muted);font-size:10px;min-width:0;}
|
|
45
|
+
[data-reticle-chat-panel] .reticle-plan-step span:last-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
46
|
+
`;
|
|
47
|
+
const STATUSES = new Set(Object.values(ScriptStatus));
|
|
48
|
+
function statusOf(value) {
|
|
49
|
+
return 'string' === typeof value && STATUSES.has(value)
|
|
50
|
+
? value
|
|
51
|
+
: ScriptStatus.PENDING;
|
|
52
|
+
}
|
|
53
|
+
function textOf(value) {
|
|
54
|
+
return 'string' === typeof value ? value : '';
|
|
55
|
+
}
|
|
56
|
+
function listOf(value) {
|
|
57
|
+
return Array.isArray(value)
|
|
58
|
+
? value.filter((v) => 'object' === typeof v && null !== v)
|
|
59
|
+
: [];
|
|
60
|
+
}
|
|
61
|
+
export function parsePlanView(args) {
|
|
62
|
+
const lanes = listOf(args['lanes']).map((lane) => ({
|
|
63
|
+
id: textOf(lane['id']),
|
|
64
|
+
journeys: listOf(lane['journeys']).map((j) => ({
|
|
65
|
+
id: textOf(j['id']),
|
|
66
|
+
title: textOf(j['title']),
|
|
67
|
+
...('string' === typeof j['persona'] ? { persona: j['persona'] } : {}),
|
|
68
|
+
waitsOn: Array.isArray(j['waitsOn']) ? j['waitsOn'].map(textOf) : [],
|
|
69
|
+
status: statusOf(j['status']),
|
|
70
|
+
steps: listOf(j['steps']).map((s) => ({
|
|
71
|
+
label: textOf(s['label']),
|
|
72
|
+
status: statusOf(s['status']),
|
|
73
|
+
})),
|
|
74
|
+
})),
|
|
75
|
+
}));
|
|
76
|
+
if (0 === lanes.length)
|
|
77
|
+
return undefined;
|
|
78
|
+
const parallel = 'number' === typeof args['parallel'] ? args['parallel'] : 1;
|
|
79
|
+
return { parallel, lanes };
|
|
80
|
+
}
|
|
81
|
+
function el(tag, className, text) {
|
|
82
|
+
const node = document.createElement(tag);
|
|
83
|
+
node.className = className;
|
|
84
|
+
if (text !== undefined)
|
|
85
|
+
node.textContent = text;
|
|
86
|
+
return node;
|
|
87
|
+
}
|
|
88
|
+
export class PlanBoard {
|
|
89
|
+
#host;
|
|
90
|
+
#view;
|
|
91
|
+
#dismissed = false;
|
|
92
|
+
#toggled = new Map();
|
|
93
|
+
mount(root) {
|
|
94
|
+
this.#host = root.querySelector(`[${PLAN_ATTR}]`) ?? undefined;
|
|
95
|
+
this.#host?.addEventListener('click', (event) => {
|
|
96
|
+
const target = event.target;
|
|
97
|
+
if (!(target instanceof Element))
|
|
98
|
+
return;
|
|
99
|
+
if (null !== target.closest(`[${CLOSE_ATTR}]`)) {
|
|
100
|
+
this.#dismissed = true;
|
|
101
|
+
this.#paint();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const card = target.closest(`[${JOURNEY_ATTR}]`);
|
|
105
|
+
const key = card?.getAttribute(JOURNEY_ATTR);
|
|
106
|
+
if (null === key || key === undefined)
|
|
107
|
+
return;
|
|
108
|
+
this.#toggled.set(key, 'true' !== card?.getAttribute('aria-expanded'));
|
|
109
|
+
this.#paint();
|
|
110
|
+
});
|
|
111
|
+
this.#paint();
|
|
112
|
+
}
|
|
113
|
+
paint(view) {
|
|
114
|
+
const fresh = view.lanes.every((lane) => lane.journeys.every((j) => ScriptStatus.PENDING === j.status));
|
|
115
|
+
if (fresh) {
|
|
116
|
+
this.#dismissed = false;
|
|
117
|
+
this.#toggled.clear();
|
|
118
|
+
}
|
|
119
|
+
this.#view = view;
|
|
120
|
+
this.#paint();
|
|
121
|
+
}
|
|
122
|
+
#paint() {
|
|
123
|
+
const host = this.#host;
|
|
124
|
+
const view = this.#view;
|
|
125
|
+
if (host === undefined)
|
|
126
|
+
return;
|
|
127
|
+
host.replaceChildren();
|
|
128
|
+
host.hidden = view === undefined || this.#dismissed;
|
|
129
|
+
if (view === undefined || this.#dismissed)
|
|
130
|
+
return;
|
|
131
|
+
const journeys = view.lanes.flatMap((lane) => lane.journeys);
|
|
132
|
+
const done = journeys.filter((j) => ScriptStatus.PASSED === j.status).length;
|
|
133
|
+
const head = el('div', 'reticle-plan-head');
|
|
134
|
+
head.append(el('strong', '', TEXT.TITLE), el('span', '', `${String(done)} of ${String(journeys.length)} passed · ${String(view.lanes.length)} lane(s)` +
|
|
135
|
+
(1 < view.parallel ? `, ${String(view.parallel)} ${TEXT.AT_ONCE}` : '')));
|
|
136
|
+
const close = el('button', 'reticle-plan-close', '×');
|
|
137
|
+
close.setAttribute('type', 'button');
|
|
138
|
+
close.setAttribute(CLOSE_ATTR, '');
|
|
139
|
+
close.setAttribute('aria-label', TEXT.CLOSE);
|
|
140
|
+
close.setAttribute('title', TEXT.CLOSE);
|
|
141
|
+
head.append(close);
|
|
142
|
+
const lanes = el('div', 'reticle-plan-lanes');
|
|
143
|
+
for (const lane of view.lanes) {
|
|
144
|
+
const row = el('div', 'reticle-plan-lane');
|
|
145
|
+
const name = el('span', 'reticle-plan-lane-name', lane.id);
|
|
146
|
+
name.setAttribute('title', `${TEXT.LANE} ${lane.id}`);
|
|
147
|
+
const journeys = el('div', 'reticle-plan-lane-journeys');
|
|
148
|
+
for (const journey of lane.journeys)
|
|
149
|
+
journeys.append(this.#card(lane.id, journey));
|
|
150
|
+
row.append(name, journeys);
|
|
151
|
+
lanes.append(row);
|
|
152
|
+
}
|
|
153
|
+
host.append(head, lanes);
|
|
154
|
+
}
|
|
155
|
+
#card(laneId, journey) {
|
|
156
|
+
const key = `${laneId}/${journey.id}`;
|
|
157
|
+
const open = this.#toggled.get(key) ??
|
|
158
|
+
(ScriptStatus.RUNNING === journey.status || ScriptStatus.FAILED === journey.status);
|
|
159
|
+
const card = el('button', 'reticle-plan-journey');
|
|
160
|
+
card.setAttribute('type', 'button');
|
|
161
|
+
card.setAttribute(JOURNEY_ATTR, key);
|
|
162
|
+
card.setAttribute('data-status', journey.status);
|
|
163
|
+
card.setAttribute('aria-expanded', String(open));
|
|
164
|
+
const row = el('span', 'reticle-plan-row');
|
|
165
|
+
row.append(el('span', 'reticle-plan-glyph', GLYPH[journey.status]), el('b', '', journey.title));
|
|
166
|
+
card.append(row);
|
|
167
|
+
const meta = [
|
|
168
|
+
journey.persona,
|
|
169
|
+
0 < journey.waitsOn.length ? `${TEXT.WAITS} ${journey.waitsOn.join(', ')}` : undefined,
|
|
170
|
+
].filter((part) => part !== undefined && 0 < part.length);
|
|
171
|
+
if (0 < meta.length)
|
|
172
|
+
card.append(el('span', 'reticle-plan-meta', meta.join(' · ')));
|
|
173
|
+
if (open && 0 < journey.steps.length) {
|
|
174
|
+
const steps = el('span', 'reticle-plan-steps');
|
|
175
|
+
for (const step of journey.steps) {
|
|
176
|
+
const line = el('span', 'reticle-plan-step');
|
|
177
|
+
line.setAttribute('data-status', step.status);
|
|
178
|
+
line.append(el('span', 'reticle-plan-glyph', GLYPH[step.status]), el('span', '', step.label));
|
|
179
|
+
steps.append(line);
|
|
180
|
+
}
|
|
181
|
+
card.append(steps);
|
|
182
|
+
}
|
|
183
|
+
return card;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -51,6 +51,11 @@ export const REPORT_CSS = `
|
|
|
51
51
|
font-size:34px;font-weight:700;line-height:1;letter-spacing:-.02em;color:var(--reticle-c-active);}
|
|
52
52
|
[${REPORT_PANEL_ATTR}] .reticle-report-hero-label{color:var(--reticle-muted);font-size:11.5px;}
|
|
53
53
|
[${REPORT_PANEL_ATTR}] .reticle-report-verdicts{display:flex;gap:6px;margin-bottom:12px;}
|
|
54
|
+
[${REPORT_PANEL_ATTR}] .reticle-sync-status{font-size:11px;color:var(--reticle-faint);font-variant-numeric:tabular-nums;white-space:nowrap;}
|
|
55
|
+
[${REPORT_PANEL_ATTR}] .reticle-sync-status[data-reticle-sync-status="refused"]{color:var(--reticle-c-error,#f87171);}
|
|
56
|
+
[${REPORT_PANEL_ATTR}] .reticle-report-coverage{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 10px;margin-bottom:12px;padding:8px 10px;border:1px solid var(--reticle-hud-border);border-radius:var(--reticle-hud-radius-sm,8px);}
|
|
57
|
+
[${REPORT_PANEL_ATTR}] .reticle-report-coverage-value{font-size:18px;font-weight:600;font-variant-numeric:tabular-nums;color:var(--reticle-hud-text);}
|
|
58
|
+
[${REPORT_PANEL_ATTR}] .reticle-report-coverage-levels{display:flex;flex-wrap:wrap;gap:4px 10px;width:100%;font-size:11px;color:var(--reticle-faint);font-variant-numeric:tabular-nums;}
|
|
54
59
|
[${REPORT_PANEL_ATTR}] .reticle-report-verdict{
|
|
55
60
|
flex:1;padding:5px 8px;border-radius:8px;font-size:10.5px;text-align:center;
|
|
56
61
|
background:rgba(255,255,255,.05);color:var(--reticle-muted);}
|
|
@@ -96,6 +101,8 @@ export const REPORT_CSS = `
|
|
|
96
101
|
display:inline-block;margin-top:8px;font-size:10px;color:var(--reticle-c-active);
|
|
97
102
|
text-decoration:none;}
|
|
98
103
|
[${REPORT_PANEL_ATTR}] .reticle-report-defects-more:hover{text-decoration:underline;}
|
|
104
|
+
[${REPORT_PANEL_ATTR}] .reticle-report-defects-all{display:inline-block;margin-top:8px;border:0;padding:0;background:none;cursor:pointer;font:inherit;font-size:10.5px;font-weight:500;color:var(--reticle-c-active);}
|
|
105
|
+
[${REPORT_PANEL_ATTR}] .reticle-report-defects-all:hover{text-decoration:underline;}
|
|
99
106
|
[${REPORT_PANEL_ATTR}] .reticle-report-local-only{
|
|
100
107
|
margin:14px 0 0;padding-top:10px;border-top:1px solid var(--reticle-line);
|
|
101
108
|
color:var(--reticle-faint);font-size:10px;line-height:1.45;}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type AccountState, type ImpactScope, type ImpactSnapshot } from '@reticlehq/core';
|
|
2
2
|
export declare function reportPanelHtml(): string;
|
|
3
|
-
export declare function reportBodyHtml(scope: ImpactScope, dashboardUrl?: string, account?: AccountState, projectName?: string): string;
|
|
3
|
+
export declare function reportBodyHtml(scope: ImpactScope, dashboardUrl?: string, account?: AccountState, projectName?: string, coverage?: Readonly<Record<string, number>>, sync?: Readonly<Record<string, unknown>>): string;
|
|
4
4
|
interface ReportHost {
|
|
5
5
|
onBeforeOpen?: () => void;
|
|
6
6
|
onSyncNow?: () => void;
|
|
@@ -4,8 +4,9 @@ import { HUD_SURFACE_CLASS } from './chrome/presenter-hud-chrome.js';
|
|
|
4
4
|
import { esc, isSafeDashboardUrl } from './chrome/presenter-safe-html.js';
|
|
5
5
|
import { ACCOUNT_SIGNIN_ATTR, ACCOUNT_TEXT, SYNC_BTN_ATTR, accountControlHtml, syncButtonHtml, } from './presenter-account.js';
|
|
6
6
|
import { REPORT_PANEL_ATTR, REPORT_ATTR, REPORT_CLOSE_ATTR } from './presenter-config.js';
|
|
7
|
-
import { REPORT_LINKS, REPORT_TEXT, buildLinkedInShareUrl, buildReferralText, buildShareText, buildXShareUrl, compactDuration, compactNumber, } from './chrome/presenter-report-copy.js';
|
|
7
|
+
import { REPORT_LINKS, REPORT_TEXT, buildLinkedInShareUrl, buildReferralText, buildShareText, buildXShareUrl, compactDuration, compactNumber, COVERAGE_LEVELS, } from './chrome/presenter-report-copy.js';
|
|
8
8
|
const SCOPE_ATTR = 'data-reticle-report-scope';
|
|
9
|
+
const SCOPE = { PROJECT: 'project', MACHINE: 'machine' };
|
|
9
10
|
const SHARE_X_ATTR = 'data-reticle-share-x';
|
|
10
11
|
const SHARE_IN_ATTR = 'data-reticle-share-in';
|
|
11
12
|
const SHARE_COPY_ATTR = 'data-reticle-share-copy';
|
|
@@ -17,7 +18,7 @@ export function reportPanelHtml() {
|
|
|
17
18
|
<div class="reticle-report-inner">
|
|
18
19
|
<div class="reticle-report-head">
|
|
19
20
|
<span class="reticle-report-title">${REPORT_TEXT.TITLE}</span>
|
|
20
|
-
<button type="button" ${SCOPE_ATTR}
|
|
21
|
+
<span class="reticle-segmented reticle-report-scopes" role="group" aria-label="${REPORT_TEXT.SCOPE_LABEL}"><button type="button" ${SCOPE_ATTR}="${SCOPE.PROJECT}" aria-pressed="true">${REPORT_TEXT.PROJECT}</button><button type="button" ${SCOPE_ATTR}="${SCOPE.MACHINE}" aria-pressed="false" title="${REPORT_TEXT.GLOBAL_TITLE}">${REPORT_TEXT.GLOBAL}</button></span>
|
|
21
22
|
<button type="button" ${REPORT_CLOSE_ATTR} class="reticle-report-close" title="Close" aria-label="Close impact">${close}</button>
|
|
22
23
|
</div>
|
|
23
24
|
<div class="reticle-report-body" data-reticle-report-body></div>
|
|
@@ -76,13 +77,46 @@ function defects(scope, dashboardUrl) {
|
|
|
76
77
|
return `<li class="reticle-report-defect"><span class="reticle-report-defect-title">${esc(d.title)}</span>${detail}${source}${rowLink}</li>`;
|
|
77
78
|
})
|
|
78
79
|
.join('');
|
|
80
|
+
const hidden = scope.counts.failed > list.length;
|
|
79
81
|
const more = !linked
|
|
80
|
-
?
|
|
81
|
-
|
|
82
|
+
? !hidden
|
|
83
|
+
? ''
|
|
84
|
+
: `<button type="button" data-reticle-defects-all ${ACCOUNT_SIGNIN_ATTR} class="reticle-report-defects-all" title="${REPORT_TEXT.DEFECTS_SEE_ALL_TITLE}">${REPORT_TEXT.DEFECTS_SEE_ALL} (${String(scope.counts.failed)}) →</button>`
|
|
85
|
+
: `<a class="reticle-report-defects-more" data-reticle-link="dashboard" href="${esc(dashboardUrl)}" target="_blank" rel="noreferrer noopener">${REPORT_TEXT.DEFECTS_MORE}${hidden ? ` (${String(scope.counts.failed)})` : ''}</a>`;
|
|
82
86
|
const sync = syncButtonHtml(dashboardUrl);
|
|
83
87
|
return `<div class="reticle-report-defects-wrap"><div class="reticle-report-defects-head"><span class="reticle-report-section">${REPORT_TEXT.DEFECTS}</span>${sync}</div><ul class="reticle-report-defects">${rows}</ul>${more}</div>`;
|
|
84
88
|
}
|
|
85
|
-
|
|
89
|
+
function syncStatusHtml(sync) {
|
|
90
|
+
if (sync === undefined)
|
|
91
|
+
return '';
|
|
92
|
+
const num = (key) => ('number' === typeof sync[key] ? sync[key] : 0);
|
|
93
|
+
const status = sync['status'];
|
|
94
|
+
const label = 'refused' === status
|
|
95
|
+
? `${String(num('refused'))} refused`
|
|
96
|
+
: 'pending' === status
|
|
97
|
+
? `${String(num('pending'))} waiting`
|
|
98
|
+
: 'on-platform' === status
|
|
99
|
+
? `Synced · ${String(num('onPlatform'))}`
|
|
100
|
+
: undefined;
|
|
101
|
+
if (label === undefined)
|
|
102
|
+
return '';
|
|
103
|
+
const said = 'string' === typeof sync['said'] ? sync['said'] : '';
|
|
104
|
+
return `<span class="reticle-sync-status" data-reticle-sync-status="${status}" title="${esc(said)}">${label}</span>`;
|
|
105
|
+
}
|
|
106
|
+
function coverageHtml(coverage) {
|
|
107
|
+
if (coverage === undefined)
|
|
108
|
+
return '';
|
|
109
|
+
const known = COVERAGE_LEVELS.filter((l) => 'number' === typeof coverage[l.key]);
|
|
110
|
+
if (0 === known.length)
|
|
111
|
+
return '';
|
|
112
|
+
const pct = (key) => `${String(Math.round(coverage[key] ?? 0))}%`;
|
|
113
|
+
const head = 'number' === typeof coverage['proved'] ? pct('proved') : pct(known[0]?.key ?? '');
|
|
114
|
+
const rows = known
|
|
115
|
+
.map((l) => `<span class="reticle-report-coverage-level">${pct(l.key)} ${l.label}</span>`)
|
|
116
|
+
.join('');
|
|
117
|
+
return `<div class="reticle-report-coverage" title="${REPORT_TEXT.COVERAGE_HELP}"><span class="reticle-report-section">${REPORT_TEXT.COVERAGE}</span><span class="reticle-report-coverage-value">${head}</span><div class="reticle-report-coverage-levels">${rows}</div></div>`;
|
|
118
|
+
}
|
|
119
|
+
export function reportBodyHtml(scope, dashboardUrl, account, projectName, coverage, sync) {
|
|
86
120
|
const c = scope.counts;
|
|
87
121
|
if (0 === c.calls)
|
|
88
122
|
return `<p class="reticle-report-empty">${REPORT_TEXT.EMPTY}</p>`;
|
|
@@ -105,8 +139,8 @@ export function reportBodyHtml(scope, dashboardUrl, account, projectName) {
|
|
|
105
139
|
card(compactNumber(scope.savings.tokens.value), REPORT_TEXT.SAVED_TOKENS, scope.savings.tokens.basis),
|
|
106
140
|
card(compactDuration(scope.savings.minutes.value * 60_000), REPORT_TEXT.SAVED_MINUTES, scope.savings.minutes.basis),
|
|
107
141
|
].join('');
|
|
108
|
-
const identity = `<div class="reticle-report-identity">${accountControlHtml(account, { dashboardUrl, projectName, verdicts: c.verdicts, defects: c.failed })}${syncButtonHtml(dashboardUrl)}</div>`;
|
|
109
|
-
return
|
|
142
|
+
const identity = `<div class="reticle-report-identity">${accountControlHtml(account, { dashboardUrl, projectName, verdicts: c.verdicts, defects: c.failed })}${syncButtonHtml(dashboardUrl)}${syncStatusHtml(sync)}</div>`;
|
|
143
|
+
return `<div class="reticle-report-top">${streak}${identity}</div>${hero}${verdicts}${coverageHtml(coverage)}<div class="reticle-report-grid">${cards}</div>${defects(scope, dashboardUrl)}${chart(scope)}${localOnly(scope, dashboardUrl, account)}`;
|
|
110
144
|
}
|
|
111
145
|
function localOnly(scope, dashboardUrl, account) {
|
|
112
146
|
if (dashboardUrl !== undefined)
|
|
@@ -140,14 +174,17 @@ export class PresenterReport {
|
|
|
140
174
|
e.stopPropagation();
|
|
141
175
|
this.close();
|
|
142
176
|
});
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
177
|
+
const scopeBtns = root.querySelectorAll(`[${SCOPE_ATTR}]`);
|
|
178
|
+
for (const btn of scopeBtns) {
|
|
179
|
+
btn.addEventListener('click', (e) => {
|
|
180
|
+
e.stopPropagation();
|
|
181
|
+
this.#global = SCOPE.MACHINE === btn.getAttribute(SCOPE_ATTR);
|
|
182
|
+
for (const other of scopeBtns) {
|
|
183
|
+
other.setAttribute('aria-pressed', other === btn ? 'true' : 'false');
|
|
184
|
+
}
|
|
185
|
+
this.#paint();
|
|
186
|
+
});
|
|
187
|
+
}
|
|
151
188
|
root.querySelector(`[${SHARE_X_ATTR}]`)?.addEventListener('click', (e) => {
|
|
152
189
|
e.stopPropagation();
|
|
153
190
|
this.#openShare(buildXShareUrl(this.shareText()));
|
|
@@ -237,7 +274,7 @@ export class PresenterReport {
|
|
|
237
274
|
this.#body.innerHTML =
|
|
238
275
|
scope === undefined
|
|
239
276
|
? `<p class="reticle-report-empty">${REPORT_TEXT.EMPTY}</p>`
|
|
240
|
-
: reportBodyHtml(scope, this.#snapshot?.dashboardUrl, this.#snapshot?.account, this.#snapshot?.projectName);
|
|
277
|
+
: reportBodyHtml(scope, this.#snapshot?.dashboardUrl, this.#snapshot?.account, this.#snapshot?.projectName, this.#global ? undefined : this.#snapshot?.coverage, this.#global ? undefined : this.#snapshot?.sync);
|
|
241
278
|
}
|
|
242
279
|
#openShare(url) {
|
|
243
280
|
window.open(url, '_blank', 'noopener,noreferrer');
|
|
@@ -125,6 +125,16 @@ export const SETTINGS_CSS = `
|
|
|
125
125
|
[${SETTINGS_PANEL_ATTR}] .reticle-settings-link-caret{display:inline-flex;align-items:center;flex:none;opacity:.55;}
|
|
126
126
|
[${SETTINGS_PANEL_ATTR}] .reticle-settings-link:hover{background:rgba(255,255,255,.04);color:#fff;}
|
|
127
127
|
[${SETTINGS_PANEL_ATTR}] .reticle-settings-link:focus-visible{outline:2px solid rgba(59,130,246,.55);outline-offset:-2px;}
|
|
128
|
+
[${SETTINGS_PANEL_ATTR}] .reticle-settings-kill-row{padding:12px 14px 14px;border-top:1px solid rgba(255,255,255,.08);}
|
|
129
|
+
[${SETTINGS_PANEL_ATTR}] .reticle-settings-kill{
|
|
130
|
+
width:100%;display:flex;align-items:center;justify-content:center;
|
|
131
|
+
border:1px solid rgba(239,68,68,.55);border-radius:8px;background:rgba(239,68,68,.12);color:#f87171;
|
|
132
|
+
font:inherit;font-size:13px;font-weight:600;padding:8px 12px;cursor:pointer;
|
|
133
|
+
transition:background .12s,border-color .12s,color .12s;}
|
|
134
|
+
[${SETTINGS_PANEL_ATTR}] .reticle-settings-kill:hover{background:rgba(239,68,68,.22);border-color:#ef4444;color:#fca5a5;}
|
|
135
|
+
[${SETTINGS_PANEL_ATTR}] .reticle-settings-kill[data-armed="1"]{background:#ef4444;border-color:#ef4444;color:#fff;}
|
|
136
|
+
[${SETTINGS_PANEL_ATTR}] .reticle-settings-kill:focus-visible{outline:2px solid rgba(239,68,68,.6);outline-offset:2px;}
|
|
137
|
+
[${SETTINGS_PANEL_ATTR}] .reticle-settings-kill-sub{margin:8px 0 0;font-size:11px;line-height:1.45;color:rgba(255,255,255,.55);}
|
|
128
138
|
[${'data-reticle-overlay'}][${SETTINGS_ATTR}="1"] [${'data-reticle-hud'}] .reticle-tb-tip{
|
|
129
139
|
opacity:0;visibility:hidden;transition-delay:0s;}
|
|
130
140
|
/* The dock's accent IS the theme's active colour, published as --reticle-accent on the overlay by
|
|
@@ -41,11 +41,16 @@ export interface PresenterSettings {
|
|
|
41
41
|
export declare function getPresenterSettings(): PresenterSettings;
|
|
42
42
|
export declare function loadPresenterSettings(): PresenterSettings;
|
|
43
43
|
export declare const SETTINGS_ACCOUNT_ATTR = "data-reticle-settings-account";
|
|
44
|
+
export declare function creditsLeft(credits: {
|
|
45
|
+
used: number;
|
|
46
|
+
limit: number;
|
|
47
|
+
} | undefined): string;
|
|
44
48
|
export declare function paintHarnessRow(root: ParentNode, config: HarnessConfig | undefined): void;
|
|
45
49
|
export declare function paintSettingsAccount(root: HTMLElement, account: AccountState | undefined, dashboardUrl: string | undefined, details?: AccountDetails): void;
|
|
46
50
|
export declare function settingsPanelHtml(): string;
|
|
47
51
|
export interface SettingsHost {
|
|
48
52
|
onHideUntilRestart?: () => void;
|
|
53
|
+
onKill?: () => void;
|
|
49
54
|
onHarness?: (enabled: boolean) => void;
|
|
50
55
|
onSettingsChange?: (settings: PresenterSettings) => void;
|
|
51
56
|
onBeforeOpen?: () => void;
|