@harness-fe/runtime 3.4.0 → 3.4.1
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/overlay.js +60 -14
- package/package.json +3 -3
- package/src/overlay.test.ts +21 -37
- package/src/overlay.ts +57 -15
package/dist/overlay.js
CHANGED
|
@@ -249,6 +249,12 @@ export function installOverlay(client) {
|
|
|
249
249
|
let pendingAttachment = null;
|
|
250
250
|
/** Set while the picker is collecting an element for a `requiresElement` plugin. */
|
|
251
251
|
let pluginAwaitingElement = null;
|
|
252
|
+
/**
|
|
253
|
+
* Purpose of the current picker session:
|
|
254
|
+
* - 'copy': copy element info to clipboard for use with an agent (default)
|
|
255
|
+
* - 'report': legacy report-a-problem flow (still used internally by plugins)
|
|
256
|
+
*/
|
|
257
|
+
let pickerPurpose = 'copy';
|
|
252
258
|
const setState = (next) => {
|
|
253
259
|
state = next;
|
|
254
260
|
infoCard.style.display = next === 'info' ? 'flex' : 'none';
|
|
@@ -322,7 +328,7 @@ export function installOverlay(client) {
|
|
|
322
328
|
lockedEl = hoveredEl;
|
|
323
329
|
setHighlight(lockedEl);
|
|
324
330
|
// A plugin requested the element — hand it straight to its onClick and
|
|
325
|
-
// skip
|
|
331
|
+
// skip all other flows.
|
|
326
332
|
if (pluginAwaitingElement) {
|
|
327
333
|
const plugin = pluginAwaitingElement;
|
|
328
334
|
pluginAwaitingElement = null;
|
|
@@ -332,9 +338,18 @@ export function installOverlay(client) {
|
|
|
332
338
|
void invokePlugin(plugin, el);
|
|
333
339
|
return;
|
|
334
340
|
}
|
|
335
|
-
//
|
|
336
|
-
|
|
337
|
-
|
|
341
|
+
// Copy mode: build element info and copy to clipboard for agent use.
|
|
342
|
+
if (pickerPurpose === 'copy') {
|
|
343
|
+
const el = lockedEl;
|
|
344
|
+
lockedEl = null;
|
|
345
|
+
const text = buildElementCopyText(el);
|
|
346
|
+
void copyText(text).then(() => {
|
|
347
|
+
showToast('✓ Element info copied');
|
|
348
|
+
});
|
|
349
|
+
setState('idle');
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
// Report mode (legacy, no longer exposed in UI but kept for plugin compatibility).
|
|
338
353
|
pendingAttachment = null;
|
|
339
354
|
const info = questionPanel.querySelector('[data-role=info]');
|
|
340
355
|
info.textContent = describeElement(lockedEl);
|
|
@@ -358,6 +373,7 @@ export function installOverlay(client) {
|
|
|
358
373
|
lockedEl = null;
|
|
359
374
|
pendingAttachment = null;
|
|
360
375
|
pluginAwaitingElement = null;
|
|
376
|
+
pickerPurpose = 'copy';
|
|
361
377
|
setState('info');
|
|
362
378
|
}
|
|
363
379
|
else if (state === 'info') {
|
|
@@ -428,6 +444,31 @@ export function installOverlay(client) {
|
|
|
428
444
|
}, 1200);
|
|
429
445
|
}
|
|
430
446
|
};
|
|
447
|
+
/**
|
|
448
|
+
* Build a compact element-info block for pasting into an agent prompt.
|
|
449
|
+
* Omits HTML (too verbose); includes source location, component name, css
|
|
450
|
+
* path, and session context — enough for the agent to locate and fix the
|
|
451
|
+
* element without any further investigation.
|
|
452
|
+
*/
|
|
453
|
+
const buildElementCopyText = (el) => {
|
|
454
|
+
const tag = el.tagName.toLowerCase();
|
|
455
|
+
const comp = el.getAttribute('data-morphix-comp');
|
|
456
|
+
const loc = el.getAttribute('data-morphix-loc');
|
|
457
|
+
const css = buildCssPath(el);
|
|
458
|
+
const lines = [];
|
|
459
|
+
lines.push(`### Element context`);
|
|
460
|
+
lines.push('');
|
|
461
|
+
if (comp)
|
|
462
|
+
lines.push(`- component: \`${comp}\``);
|
|
463
|
+
if (loc)
|
|
464
|
+
lines.push(`- source: \`${loc}\``);
|
|
465
|
+
lines.push(`- tag: \`${tag}\``);
|
|
466
|
+
lines.push(`- css: \`${css}\``);
|
|
467
|
+
lines.push(`- project: \`${client.projectId}\`${client.displayName ? ` (${client.displayName})` : ''}`);
|
|
468
|
+
lines.push(`- session: \`${client.sessionId}\``);
|
|
469
|
+
lines.push(`- url: ${location.href}`);
|
|
470
|
+
return lines.join('\n') + '\n';
|
|
471
|
+
};
|
|
431
472
|
const buildSnapshot = () => {
|
|
432
473
|
const lines = [];
|
|
433
474
|
lines.push(`### Harness-FE snapshot`);
|
|
@@ -525,6 +566,10 @@ export function installOverlay(client) {
|
|
|
525
566
|
btn.addEventListener('click', () => {
|
|
526
567
|
if (plugin.requiresElement) {
|
|
527
568
|
pluginAwaitingElement = plugin;
|
|
569
|
+
pickerPurpose = 'report'; // plugins use the legacy element-selection flow
|
|
570
|
+
const label = pickerBar.querySelector('[data-role=picker-label]');
|
|
571
|
+
if (label)
|
|
572
|
+
label.textContent = '🎯 Click an element';
|
|
528
573
|
setState('picker');
|
|
529
574
|
}
|
|
530
575
|
else {
|
|
@@ -753,16 +798,17 @@ export function installOverlay(client) {
|
|
|
753
798
|
setState(state === 'idle' ? 'info' : 'idle');
|
|
754
799
|
});
|
|
755
800
|
infoCard.querySelector('[data-role=close]').addEventListener('click', () => setState('idle'));
|
|
756
|
-
infoCard.querySelector('[data-role=
|
|
801
|
+
infoCard.querySelector('[data-role=pick-element]').addEventListener('click', () => {
|
|
802
|
+
pickerPurpose = 'copy';
|
|
803
|
+
const label = pickerBar.querySelector('[data-role=picker-label]');
|
|
804
|
+
if (label)
|
|
805
|
+
label.textContent = '🔍 Click element to copy info';
|
|
757
806
|
setState('picker');
|
|
758
807
|
});
|
|
759
808
|
infoCard.querySelector('[data-role=copy-snapshot]').addEventListener('click', (ev) => {
|
|
760
809
|
const btn = ev.currentTarget;
|
|
761
810
|
void copyText(buildSnapshot(), btn);
|
|
762
811
|
});
|
|
763
|
-
infoCard.querySelector('[data-role=view-reports]').addEventListener('click', () => {
|
|
764
|
-
setState('reports');
|
|
765
|
-
});
|
|
766
812
|
// "Open dashboard" — derive the daemon's dashboard URL from mcpUrl and
|
|
767
813
|
// pop it in a new tab, deep-linked to this session. Show the button
|
|
768
814
|
// only when we actually know the daemon address (mcpUrl was supplied by
|
|
@@ -815,6 +861,7 @@ export function installOverlay(client) {
|
|
|
815
861
|
pickerBar.querySelector('[data-role=cancel]').addEventListener('click', () => {
|
|
816
862
|
lockedEl = null;
|
|
817
863
|
pluginAwaitingElement = null;
|
|
864
|
+
pickerPurpose = 'copy';
|
|
818
865
|
setState('info');
|
|
819
866
|
});
|
|
820
867
|
questionPanel.querySelector('[data-role=cancel]').addEventListener('click', () => {
|
|
@@ -1939,16 +1986,15 @@ function buildInfoCard() {
|
|
|
1939
1986
|
<div class="row"><span class="key">url</span><span class="pill url" data-role="url"></span></div>
|
|
1940
1987
|
</div>
|
|
1941
1988
|
<div class="actions">
|
|
1942
|
-
<button class="primary" data-role="
|
|
1943
|
-
<span class="icon"
|
|
1944
|
-
<span class="label">
|
|
1945
|
-
<span class="hint">
|
|
1989
|
+
<button class="primary" data-role="pick-element" type="button">
|
|
1990
|
+
<span class="icon">🔍</span>
|
|
1991
|
+
<span class="label">Copy element info</span>
|
|
1992
|
+
<span class="hint">pick element →</span>
|
|
1946
1993
|
</button>
|
|
1947
1994
|
<button class="secondary" data-role="open-dashboard" type="button" style="display:none">
|
|
1948
1995
|
<span class="icon">↗</span>
|
|
1949
1996
|
<span>Open dashboard</span>
|
|
1950
1997
|
</button>
|
|
1951
|
-
<button class="secondary" data-role="view-reports" type="button">📁 My reports</button>
|
|
1952
1998
|
<button class="secondary" data-role="copy-snapshot" type="button">📋 Copy snapshot</button>
|
|
1953
1999
|
</div>
|
|
1954
2000
|
<div class="plugin-actions" data-role="plugin-actions" style="display:none"></div>
|
|
@@ -1979,7 +2025,7 @@ function buildPickerBar() {
|
|
|
1979
2025
|
const bar = document.createElement('div');
|
|
1980
2026
|
bar.className = 'picker-bar';
|
|
1981
2027
|
bar.innerHTML = `
|
|
1982
|
-
<span class="label"
|
|
2028
|
+
<span class="label" data-role="picker-label">🔍 Click element to copy info</span>
|
|
1983
2029
|
<span class="hint">esc to cancel</span>
|
|
1984
2030
|
<button data-role="cancel" type="button">Cancel</button>
|
|
1985
2031
|
`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@harness-fe/runtime",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.1",
|
|
4
4
|
"description": "Browser-side SDK injected into the dev page. Connects to the MCP server via WebSocket and executes commands.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@zumer/snapdom": "^2.12.0",
|
|
32
32
|
"rrweb": "2.0.0-alpha.4",
|
|
33
|
-
"@harness-fe/
|
|
34
|
-
"@harness-fe/
|
|
33
|
+
"@harness-fe/protocol": "3.2.0",
|
|
34
|
+
"@harness-fe/sandbox": "^3.2.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"happy-dom": "^20.9.0",
|
package/src/overlay.test.ts
CHANGED
|
@@ -73,20 +73,20 @@ describe('installOverlay', () => {
|
|
|
73
73
|
expect(root.querySelector('[data-role=build]')!.textContent).toBe('—');
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
it('"
|
|
76
|
+
it('"Copy element info" enters picker mode (FAB turns active, info card hidden)', () => {
|
|
77
77
|
setupDom();
|
|
78
78
|
const client = makeFakeClient();
|
|
79
79
|
installOverlay(client);
|
|
80
80
|
const root = document.getElementById('__harness_fe_overlay__')!.shadowRoot!;
|
|
81
81
|
(root.querySelector('.fab') as HTMLButtonElement).click();
|
|
82
|
-
(root.querySelector('[data-role=
|
|
82
|
+
(root.querySelector('[data-role=pick-element]') as HTMLButtonElement).click();
|
|
83
83
|
const fab = root.querySelector('.fab') as HTMLButtonElement;
|
|
84
84
|
expect(fab.dataset.state).toBe('active');
|
|
85
85
|
expect((root.querySelector('.info-card') as HTMLElement).style.display).toBe('none');
|
|
86
86
|
expect((root.querySelector('.picker-bar') as HTMLElement).style.display).toBe('flex');
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
-
it('
|
|
89
|
+
it('"Copy element info" returns to idle after element click (no task.submit fired)', () => {
|
|
90
90
|
const { doc } = setupDom();
|
|
91
91
|
const target = doc.createElement('button');
|
|
92
92
|
target.setAttribute('data-morphix-loc', 'app/cart/CartBadge.tsx:18:5');
|
|
@@ -94,47 +94,31 @@ describe('installOverlay', () => {
|
|
|
94
94
|
target.textContent = 'Cart (3)';
|
|
95
95
|
doc.body.appendChild(target);
|
|
96
96
|
|
|
97
|
+
// Stub clipboard so copyText does not throw in happy-dom.
|
|
98
|
+
const written: string[] = [];
|
|
99
|
+
Object.defineProperty(globalThis.navigator, 'clipboard', {
|
|
100
|
+
value: { writeText: (t: string) => { written.push(t); return Promise.resolve(); } },
|
|
101
|
+
configurable: true,
|
|
102
|
+
});
|
|
103
|
+
|
|
97
104
|
const client = makeFakeClient();
|
|
98
105
|
installOverlay(client);
|
|
99
106
|
const root = document.getElementById('__harness_fe_overlay__')!.shadowRoot!;
|
|
100
107
|
|
|
101
|
-
// Open
|
|
108
|
+
// Open info card → enter pick-element picker mode.
|
|
102
109
|
(root.querySelector('.fab') as HTMLButtonElement).click();
|
|
103
|
-
(root.querySelector('[data-role=
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
// and submit a payload — overlay.ts's submit handler reads lockedEl
|
|
109
|
-
// from a closure, so we go through a synthesized click instead.
|
|
110
|
-
// Trick: dispatch a capture-phase click on the body with the target.
|
|
111
|
-
// overlay's onClickCapture relies on `hoveredEl` set by mousemove.
|
|
112
|
-
// To avoid coupling to mousemove geometry, we test the submit handler
|
|
113
|
-
// is wired by inspecting the question textarea wiring instead.
|
|
114
|
-
|
|
115
|
-
// Force the panel into "question" state by clicking the target via
|
|
116
|
-
// the document; we first set hoveredEl by dispatching mousemove with
|
|
117
|
-
// matching screen coords.
|
|
118
|
-
target.dispatchEvent(new MouseEvent('mousemove', {
|
|
119
|
-
bubbles: true, clientX: 0, clientY: 0,
|
|
120
|
-
}));
|
|
121
|
-
// Direct click on the picker target triggers the capture handler.
|
|
110
|
+
(root.querySelector('[data-role=pick-element]') as HTMLButtonElement).click();
|
|
111
|
+
expect((root.querySelector('.picker-bar') as HTMLElement).style.display).toBe('flex');
|
|
112
|
+
|
|
113
|
+
// Simulate hover + click on the target element.
|
|
114
|
+
target.dispatchEvent(new MouseEvent('mousemove', { bubbles: true, clientX: 0, clientY: 0 }));
|
|
122
115
|
target.click();
|
|
123
116
|
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
expect(client.sent).toHaveLength(1);
|
|
130
|
-
expect(client.sent[0].name).toBe('task.submit');
|
|
131
|
-
const payload = client.sent[0].payload as { selector: { loc?: string }; question: string };
|
|
132
|
-
expect(payload.question).toBe('broken');
|
|
133
|
-
expect(payload.selector.loc).toBe('app/cart/CartBadge.tsx:18:5');
|
|
134
|
-
}
|
|
135
|
-
// If happy-dom's elementFromPoint didn't cooperate, the test still
|
|
136
|
-
// exercises mount/open/copy paths above — submit path is asserted
|
|
137
|
-
// separately by buildCssPath unit + bridge.test integration.
|
|
117
|
+
// After the pick: overlay should be idle (picker bar hidden, no question panel).
|
|
118
|
+
// The question panel must NOT open — copy mode skips the report flow.
|
|
119
|
+
expect((root.querySelector('.question') as HTMLElement).style.display).toBe('none');
|
|
120
|
+
// No task.submit event should be sent — copy mode never fires a report.
|
|
121
|
+
expect(client.sent).toHaveLength(0);
|
|
138
122
|
});
|
|
139
123
|
|
|
140
124
|
it('Esc closes the info card when open', () => {
|
package/src/overlay.ts
CHANGED
|
@@ -309,6 +309,12 @@ export function installOverlay(client: OverlayClient): void {
|
|
|
309
309
|
let pendingAttachment: TaskAttachment | null = null;
|
|
310
310
|
/** Set while the picker is collecting an element for a `requiresElement` plugin. */
|
|
311
311
|
let pluginAwaitingElement: OverlayPlugin | null = null;
|
|
312
|
+
/**
|
|
313
|
+
* Purpose of the current picker session:
|
|
314
|
+
* - 'copy': copy element info to clipboard for use with an agent (default)
|
|
315
|
+
* - 'report': legacy report-a-problem flow (still used internally by plugins)
|
|
316
|
+
*/
|
|
317
|
+
let pickerPurpose: 'copy' | 'report' = 'copy';
|
|
312
318
|
|
|
313
319
|
const setState = (next: State) => {
|
|
314
320
|
state = next;
|
|
@@ -381,7 +387,7 @@ export function installOverlay(client: OverlayClient): void {
|
|
|
381
387
|
lockedEl = hoveredEl;
|
|
382
388
|
setHighlight(lockedEl);
|
|
383
389
|
// A plugin requested the element — hand it straight to its onClick and
|
|
384
|
-
// skip
|
|
390
|
+
// skip all other flows.
|
|
385
391
|
if (pluginAwaitingElement) {
|
|
386
392
|
const plugin = pluginAwaitingElement;
|
|
387
393
|
pluginAwaitingElement = null;
|
|
@@ -391,9 +397,18 @@ export function installOverlay(client: OverlayClient): void {
|
|
|
391
397
|
void invokePlugin(plugin, el);
|
|
392
398
|
return;
|
|
393
399
|
}
|
|
394
|
-
//
|
|
395
|
-
|
|
396
|
-
|
|
400
|
+
// Copy mode: build element info and copy to clipboard for agent use.
|
|
401
|
+
if (pickerPurpose === 'copy') {
|
|
402
|
+
const el = lockedEl;
|
|
403
|
+
lockedEl = null;
|
|
404
|
+
const text = buildElementCopyText(el);
|
|
405
|
+
void copyText(text).then(() => {
|
|
406
|
+
showToast('✓ Element info copied');
|
|
407
|
+
});
|
|
408
|
+
setState('idle');
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
// Report mode (legacy, no longer exposed in UI but kept for plugin compatibility).
|
|
397
412
|
pendingAttachment = null;
|
|
398
413
|
const info = questionPanel.querySelector<HTMLElement>('[data-role=info]')!;
|
|
399
414
|
info.textContent = describeElement(lockedEl);
|
|
@@ -417,6 +432,7 @@ export function installOverlay(client: OverlayClient): void {
|
|
|
417
432
|
lockedEl = null;
|
|
418
433
|
pendingAttachment = null;
|
|
419
434
|
pluginAwaitingElement = null;
|
|
435
|
+
pickerPurpose = 'copy';
|
|
420
436
|
setState('info');
|
|
421
437
|
} else if (state === 'info') {
|
|
422
438
|
setState('idle');
|
|
@@ -486,6 +502,30 @@ export function installOverlay(client: OverlayClient): void {
|
|
|
486
502
|
}
|
|
487
503
|
};
|
|
488
504
|
|
|
505
|
+
/**
|
|
506
|
+
* Build a compact element-info block for pasting into an agent prompt.
|
|
507
|
+
* Omits HTML (too verbose); includes source location, component name, css
|
|
508
|
+
* path, and session context — enough for the agent to locate and fix the
|
|
509
|
+
* element without any further investigation.
|
|
510
|
+
*/
|
|
511
|
+
const buildElementCopyText = (el: Element): string => {
|
|
512
|
+
const tag = el.tagName.toLowerCase();
|
|
513
|
+
const comp = el.getAttribute('data-morphix-comp');
|
|
514
|
+
const loc = el.getAttribute('data-morphix-loc');
|
|
515
|
+
const css = buildCssPath(el);
|
|
516
|
+
const lines: string[] = [];
|
|
517
|
+
lines.push(`### Element context`);
|
|
518
|
+
lines.push('');
|
|
519
|
+
if (comp) lines.push(`- component: \`${comp}\``);
|
|
520
|
+
if (loc) lines.push(`- source: \`${loc}\``);
|
|
521
|
+
lines.push(`- tag: \`${tag}\``);
|
|
522
|
+
lines.push(`- css: \`${css}\``);
|
|
523
|
+
lines.push(`- project: \`${client.projectId}\`${client.displayName ? ` (${client.displayName})` : ''}`);
|
|
524
|
+
lines.push(`- session: \`${client.sessionId}\``);
|
|
525
|
+
lines.push(`- url: ${location.href}`);
|
|
526
|
+
return lines.join('\n') + '\n';
|
|
527
|
+
};
|
|
528
|
+
|
|
489
529
|
const buildSnapshot = (): string => {
|
|
490
530
|
const lines: string[] = [];
|
|
491
531
|
lines.push(`### Harness-FE snapshot`);
|
|
@@ -584,6 +624,9 @@ export function installOverlay(client: OverlayClient): void {
|
|
|
584
624
|
btn.addEventListener('click', () => {
|
|
585
625
|
if (plugin.requiresElement) {
|
|
586
626
|
pluginAwaitingElement = plugin;
|
|
627
|
+
pickerPurpose = 'report'; // plugins use the legacy element-selection flow
|
|
628
|
+
const label = pickerBar.querySelector<HTMLElement>('[data-role=picker-label]');
|
|
629
|
+
if (label) label.textContent = '🎯 Click an element';
|
|
587
630
|
setState('picker');
|
|
588
631
|
} else {
|
|
589
632
|
setState('idle');
|
|
@@ -803,7 +846,10 @@ export function installOverlay(client: OverlayClient): void {
|
|
|
803
846
|
|
|
804
847
|
infoCard.querySelector('[data-role=close]')!.addEventListener('click', () => setState('idle'));
|
|
805
848
|
|
|
806
|
-
infoCard.querySelector('[data-role=
|
|
849
|
+
infoCard.querySelector('[data-role=pick-element]')!.addEventListener('click', () => {
|
|
850
|
+
pickerPurpose = 'copy';
|
|
851
|
+
const label = pickerBar.querySelector<HTMLElement>('[data-role=picker-label]');
|
|
852
|
+
if (label) label.textContent = '🔍 Click element to copy info';
|
|
807
853
|
setState('picker');
|
|
808
854
|
});
|
|
809
855
|
|
|
@@ -812,10 +858,6 @@ export function installOverlay(client: OverlayClient): void {
|
|
|
812
858
|
void copyText(buildSnapshot(), btn);
|
|
813
859
|
});
|
|
814
860
|
|
|
815
|
-
infoCard.querySelector('[data-role=view-reports]')!.addEventListener('click', () => {
|
|
816
|
-
setState('reports');
|
|
817
|
-
});
|
|
818
|
-
|
|
819
861
|
// "Open dashboard" — derive the daemon's dashboard URL from mcpUrl and
|
|
820
862
|
// pop it in a new tab, deep-linked to this session. Show the button
|
|
821
863
|
// only when we actually know the daemon address (mcpUrl was supplied by
|
|
@@ -869,6 +911,7 @@ export function installOverlay(client: OverlayClient): void {
|
|
|
869
911
|
pickerBar.querySelector('[data-role=cancel]')!.addEventListener('click', () => {
|
|
870
912
|
lockedEl = null;
|
|
871
913
|
pluginAwaitingElement = null;
|
|
914
|
+
pickerPurpose = 'copy';
|
|
872
915
|
setState('info');
|
|
873
916
|
});
|
|
874
917
|
|
|
@@ -2071,16 +2114,15 @@ function buildInfoCard(): HTMLDivElement {
|
|
|
2071
2114
|
<div class="row"><span class="key">url</span><span class="pill url" data-role="url"></span></div>
|
|
2072
2115
|
</div>
|
|
2073
2116
|
<div class="actions">
|
|
2074
|
-
<button class="primary" data-role="
|
|
2075
|
-
<span class="icon"
|
|
2076
|
-
<span class="label">
|
|
2077
|
-
<span class="hint">
|
|
2117
|
+
<button class="primary" data-role="pick-element" type="button">
|
|
2118
|
+
<span class="icon">🔍</span>
|
|
2119
|
+
<span class="label">Copy element info</span>
|
|
2120
|
+
<span class="hint">pick element →</span>
|
|
2078
2121
|
</button>
|
|
2079
2122
|
<button class="secondary" data-role="open-dashboard" type="button" style="display:none">
|
|
2080
2123
|
<span class="icon">↗</span>
|
|
2081
2124
|
<span>Open dashboard</span>
|
|
2082
2125
|
</button>
|
|
2083
|
-
<button class="secondary" data-role="view-reports" type="button">📁 My reports</button>
|
|
2084
2126
|
<button class="secondary" data-role="copy-snapshot" type="button">📋 Copy snapshot</button>
|
|
2085
2127
|
</div>
|
|
2086
2128
|
<div class="plugin-actions" data-role="plugin-actions" style="display:none"></div>
|
|
@@ -2113,7 +2155,7 @@ function buildPickerBar(): HTMLDivElement {
|
|
|
2113
2155
|
const bar = document.createElement('div');
|
|
2114
2156
|
bar.className = 'picker-bar';
|
|
2115
2157
|
bar.innerHTML = `
|
|
2116
|
-
<span class="label"
|
|
2158
|
+
<span class="label" data-role="picker-label">🔍 Click element to copy info</span>
|
|
2117
2159
|
<span class="hint">esc to cancel</span>
|
|
2118
2160
|
<button data-role="cancel" type="button">Cancel</button>
|
|
2119
2161
|
`;
|