@reticlehq/browser 3.5.0 → 3.7.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/action-report.d.ts +54 -0
- package/dist/actions/action-report.js +60 -0
- package/dist/actions/actions-dom.d.ts +1 -0
- package/dist/actions/actions-dom.js +56 -43
- package/dist/actions/actions-press.d.ts +11 -4
- package/dist/actions/actions-press.js +132 -17
- package/dist/actions/actions.d.ts +1 -35
- package/dist/actions/actions.js +32 -65
- package/dist/actions/appeared-text.js +3 -0
- package/dist/actions/danger-context.d.ts +2 -0
- package/dist/actions/danger-context.js +29 -3
- package/dist/actions/value-input.d.ts +1 -0
- package/dist/actions/value-input.js +11 -0
- package/dist/commands/commands.js +4 -0
- package/dist/connect-options.d.ts +1 -0
- package/dist/dom/a11y.js +264 -14
- package/dist/dom/addressing/coverage-markers.d.ts +6 -0
- package/dist/dom/addressing/coverage-markers.js +13 -0
- 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/dom-ignore.d.ts +2 -0
- package/dist/dom/dom-ignore.js +15 -2
- package/dist/dom/query.d.ts +2 -1
- package/dist/dom/query.js +116 -27
- package/dist/dom/render-page.d.ts +12 -0
- package/dist/dom/render-page.js +135 -0
- package/dist/dom/snapshot.js +56 -7
- package/dist/observers/departure.js +51 -9
- package/dist/observers/dom.js +35 -18
- package/dist/observers/field.js +5 -21
- 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 +57 -8
- package/dist/observers/perf.js +53 -1
- package/dist/observers/route.js +6 -5
- package/dist/observers/storage.js +6 -1
- package/dist/presenter/carousel/carousel.d.ts +5 -2
- package/dist/presenter/carousel/carousel.js +30 -8
- package/dist/presenter/carousel/panel-slides.d.ts +3 -4
- package/dist/presenter/carousel/panel-slides.js +48 -9
- 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 +13 -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 +12 -4
- package/dist/presenter/chrome/presenter-log.js +100 -10
- package/dist/presenter/chrome/presenter-morph.d.ts +1 -0
- package/dist/presenter/chrome/presenter-morph.js +34 -0
- package/dist/presenter/chrome/presenter-report-copy.d.ts +28 -5
- package/dist/presenter/chrome/presenter-report-copy.js +40 -5
- package/dist/presenter/hud-telemetry.js +83 -3
- 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-styles.js +3 -3
- package/dist/presenter/presenter-account.d.ts +31 -8
- package/dist/presenter/presenter-account.js +105 -15
- package/dist/presenter/presenter-agent-link.d.ts +9 -0
- package/dist/presenter/presenter-agent-link.js +62 -0
- package/dist/presenter/presenter-chat-views.d.ts +43 -0
- package/dist/presenter/presenter-chat-views.js +622 -0
- package/dist/presenter/presenter-config.d.ts +5 -2
- package/dist/presenter/presenter-config.js +5 -3
- package/dist/presenter/presenter-controls.d.ts +3 -2
- package/dist/presenter/presenter-controls.js +163 -61
- 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 +187 -0
- package/dist/presenter/presenter-harness-row.d.ts +67 -0
- package/dist/presenter/presenter-harness-row.js +209 -0
- package/dist/presenter/presenter-personas.d.ts +35 -0
- package/dist/presenter/presenter-personas.js +95 -0
- package/dist/presenter/presenter-plan.d.ts +10 -0
- package/dist/presenter/presenter-plan.js +186 -0
- package/dist/presenter/presenter-report-styles.js +46 -21
- package/dist/presenter/presenter-report.d.ts +2 -1
- package/dist/presenter/presenter-report.js +123 -20
- package/dist/presenter/presenter-settings-styles.js +18 -4
- package/dist/presenter/presenter-settings.d.ts +8 -2
- package/dist/presenter/presenter-settings.js +134 -33
- package/dist/presenter/presenter-shell-styles.js +48 -42
- package/dist/presenter/presenter-shell.d.ts +17 -4
- package/dist/presenter/presenter-shell.js +139 -79
- package/dist/presenter/presenter-styles.js +9 -3
- package/dist/presenter/presenter-workspace.js +10 -3
- package/dist/presenter/presenter.d.ts +6 -4
- package/dist/presenter/presenter.js +171 -22
- 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 +4 -0
- package/dist/reticle-inject.js +938 -494
- package/dist/reticle.d.ts +10 -1
- package/dist/reticle.js +109 -11
- 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 +15 -0
- package/dist/storage-keys.js +21 -0
- package/dist/transport/transport.js +5 -3
- package/dist/transport/unreachable-message.d.ts +1 -1
- package/dist/transport/unreachable-message.js +3 -5
- package/package.json +3 -2
- package/dist/presenter/carousel/offer-card.d.ts +0 -21
- package/dist/presenter/carousel/offer-card.js +0 -55
package/dist/reticle.d.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import { EventType, RETICLE_URL_PARAM, type ReticleEvent } from '@reticlehq/core';
|
|
1
|
+
import { EventType, RETICLE_URL_PARAM, HudVisibility, type ReticleEvent } from '@reticlehq/core';
|
|
2
2
|
import { type CapabilitiesInput } from './registry/capabilities.js';
|
|
3
3
|
import type { ReticleConnectOptions } from './connect-options.js';
|
|
4
4
|
export type { ReticleConnectOptions };
|
|
5
5
|
export declare function shouldBlockProduction(nodeEnv: string | undefined, allowInProduction: boolean): boolean;
|
|
6
|
+
export declare function withBridgePath(bridgeUrl: string): {
|
|
7
|
+
url: string;
|
|
8
|
+
adjusted: boolean;
|
|
9
|
+
};
|
|
6
10
|
export declare function connectionPolicy(pageHostname: string, bridgeUrl: string, allowNonLocalhost: boolean, token: string | undefined, pageProtocol?: string): {
|
|
7
11
|
allowed: boolean;
|
|
8
12
|
reason?: string;
|
|
@@ -12,6 +16,11 @@ export { RETICLE_URL_PARAM };
|
|
|
12
16
|
export declare function reticleParamsFromSearch(search: string): {
|
|
13
17
|
session?: string;
|
|
14
18
|
projectId?: string;
|
|
19
|
+
hud?: HudVisibility;
|
|
20
|
+
};
|
|
21
|
+
export declare function hudWithoutPanel(hud: HudVisibility | undefined): {
|
|
22
|
+
remember?: HudVisibility;
|
|
23
|
+
placed: HudVisibility[];
|
|
15
24
|
};
|
|
16
25
|
export declare function resolveConnectIdentity(options: {
|
|
17
26
|
session?: string;
|
package/dist/reticle.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { setPresenterVisible } from './dom/dom-ignore.js';
|
|
2
|
-
import {
|
|
1
|
+
import { setPresenterViewer, setPresenterVisible } from './dom/dom-ignore.js';
|
|
2
|
+
import { setOutwardPolicy } from './actions/danger-context.js';
|
|
3
|
+
import { coverageMarkers, notApplicableFor } from './dom/addressing/coverage-markers.js';
|
|
4
|
+
import { EventType, RETICLE_DEFAULT_PORT, RETICLE_PROTOCOL_VERSION, RETICLE_URL_PARAM, RETICLE_WS_PATH, bridgeWsUrl, ReticleCommand, MessageKind, SESSION_AUTO, SessionState, TRANSPORT_LIMITS, buildRedactionPolicy, isLoopbackHostname, isLocalPage, setActiveRedactionPolicy, wireRedactionKeys, RETICLE_ROOT_GLOBAL, RETICLE_SDK_VERSION_GLOBAL, CONTRACT_FINGERPRINT, CONTRACT_PARTS, newDocumentId, NO_EDITS_OBSERVED, PresenterMode, CommandActor, HudCorner, HudVisibility, } from '@reticlehq/core';
|
|
3
5
|
import { rememberSessionLabel } from './session-continuity.js';
|
|
4
6
|
import { editEpoch } from './edit-epoch.js';
|
|
5
7
|
import { createCommandRegistry, RELOAD_CACHE_BUST_PARAM, } from './commands/commands.js';
|
|
@@ -10,9 +12,10 @@ import { declaredChannels } from './registry/declared-channels.js';
|
|
|
10
12
|
import { declaredCommands, declaredPlatform } from './registry/declared-platform.js';
|
|
11
13
|
import { registerCapabilities, setCapabilitiesListener, hasCapabilities, } from './registry/capabilities.js';
|
|
12
14
|
import { installAllObservers, runTeardowns } from './observers/install-all.js';
|
|
15
|
+
import { setTestIdAttr } from './dom/addressing/testid-attr.js';
|
|
13
16
|
import { setNetworkBodyMaxChars } from './observers/net-detail/network-body.js';
|
|
14
17
|
import { installOverlay } from './presenter/chrome/overlay.js';
|
|
15
|
-
import { LOG_KIND, LOG_RESULT } from './presenter/chrome/log-kinds.js';
|
|
18
|
+
import { LOG_ACTOR, LOG_KIND, LOG_RESULT } from './presenter/chrome/log-kinds.js';
|
|
16
19
|
import { actionVerb } from './presenter/chrome/presenter-verbs.js';
|
|
17
20
|
import { str, refLabel, modeForCommand, presentStatus } from './reticle-presenter-helpers.js';
|
|
18
21
|
import { resetClock } from './timers/clock.js';
|
|
@@ -22,6 +25,21 @@ import { Annotator } from './review/annotator.js';
|
|
|
22
25
|
export function shouldBlockProduction(nodeEnv, allowInProduction) {
|
|
23
26
|
return 'production' === nodeEnv && !allowInProduction;
|
|
24
27
|
}
|
|
28
|
+
export function withBridgePath(bridgeUrl) {
|
|
29
|
+
let parsed;
|
|
30
|
+
try {
|
|
31
|
+
parsed = new URL(bridgeUrl);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return { url: bridgeUrl, adjusted: false };
|
|
35
|
+
}
|
|
36
|
+
if ((parsed.protocol !== 'ws:' && parsed.protocol !== 'wss:') ||
|
|
37
|
+
(parsed.pathname !== '' && parsed.pathname !== '/')) {
|
|
38
|
+
return { url: bridgeUrl, adjusted: false };
|
|
39
|
+
}
|
|
40
|
+
parsed.pathname = RETICLE_WS_PATH;
|
|
41
|
+
return { url: parsed.toString(), adjusted: true };
|
|
42
|
+
}
|
|
25
43
|
export function connectionPolicy(pageHostname, bridgeUrl, allowNonLocalhost, token, pageProtocol = 'http:') {
|
|
26
44
|
let bridge;
|
|
27
45
|
try {
|
|
@@ -57,6 +75,8 @@ export function connectionPolicy(pageHostname, bridgeUrl, allowNonLocalhost, tok
|
|
|
57
75
|
}
|
|
58
76
|
return { allowed: true };
|
|
59
77
|
}
|
|
78
|
+
const MAX_PENDING_NARRATIONS = 20;
|
|
79
|
+
const KILLED_MESSAGE = '[Reticle] killed for this page. Restart your dev server or reload the page to bring it back.';
|
|
60
80
|
const BRIDGE_LOST_SUMMARY = 'Session ended - lost connection to Reticle (the agent is no longer running).';
|
|
61
81
|
export function resolveSessionLabel(option, gen) {
|
|
62
82
|
return option === undefined || option === SESSION_AUTO ? gen() : option;
|
|
@@ -78,12 +98,20 @@ export function reticleParamsFromSearch(search) {
|
|
|
78
98
|
const out = {};
|
|
79
99
|
const session = params.get(RETICLE_URL_PARAM.SESSION);
|
|
80
100
|
const projectId = params.get(RETICLE_URL_PARAM.PROJECT);
|
|
101
|
+
const hud = oneOf(HudVisibility, params.get(RETICLE_URL_PARAM.HUD));
|
|
81
102
|
if (session !== null && session.length > 0)
|
|
82
103
|
out.session = session;
|
|
83
104
|
if (projectId !== null && projectId.length > 0)
|
|
84
105
|
out.projectId = projectId;
|
|
106
|
+
if (hud !== undefined)
|
|
107
|
+
out.hud = hud;
|
|
85
108
|
return out;
|
|
86
109
|
}
|
|
110
|
+
export function hudWithoutPanel(hud) {
|
|
111
|
+
if (hud === undefined)
|
|
112
|
+
return { placed: [] };
|
|
113
|
+
return { remember: hud, placed: HudVisibility.SHOWN === hud ? [] : [hud] };
|
|
114
|
+
}
|
|
87
115
|
export function resolveConnectIdentity(options, search) {
|
|
88
116
|
const url = reticleParamsFromSearch(search);
|
|
89
117
|
const explicitSession = options.session !== undefined && options.session !== SESSION_AUTO ? options.session : undefined;
|
|
@@ -116,6 +144,8 @@ export class Reticle {
|
|
|
116
144
|
#overlay;
|
|
117
145
|
#presenter;
|
|
118
146
|
#pendingPushes = new Map();
|
|
147
|
+
#pendingNarrations = [];
|
|
148
|
+
#pendingHud;
|
|
119
149
|
#readPanelSettings;
|
|
120
150
|
#panelRequested = false;
|
|
121
151
|
#bridgeConnected = false;
|
|
@@ -142,7 +172,11 @@ export class Reticle {
|
|
|
142
172
|
return;
|
|
143
173
|
}
|
|
144
174
|
stripReloadCacheBustParam();
|
|
145
|
-
const
|
|
175
|
+
const bridge = withBridgePath(options.url ?? bridgeWsUrl(RETICLE_DEFAULT_PORT));
|
|
176
|
+
const url = bridge.url;
|
|
177
|
+
if (bridge.adjusted) {
|
|
178
|
+
globalThis.console.warn(`[Reticle] bridge URL has no path; using ${url} (the bridge only accepts WebSocket upgrades on ${RETICLE_WS_PATH}).`);
|
|
179
|
+
}
|
|
146
180
|
const policy = connectionPolicy(window.location.hostname, url, true === options.allowNonLocalhost, options.token, window.location.protocol);
|
|
147
181
|
if (!policy.allowed) {
|
|
148
182
|
globalThis.console.warn(`[Reticle] ${policy.reason ?? 'connection blocked'}`);
|
|
@@ -188,6 +222,7 @@ export class Reticle {
|
|
|
188
222
|
});
|
|
189
223
|
setCapabilitiesListener(() => this.#transport?.reannounce());
|
|
190
224
|
setPresenterVisible(true === options.exposePresenter);
|
|
225
|
+
setTestIdAttr(options.testIdAttribute);
|
|
191
226
|
if (options.networkBodyMaxChars !== undefined) {
|
|
192
227
|
setNetworkBodyMaxChars(options.networkBodyMaxChars);
|
|
193
228
|
}
|
|
@@ -205,7 +240,8 @@ export class Reticle {
|
|
|
205
240
|
this.#overlay = installOverlay();
|
|
206
241
|
this.#overlay.update({ connected: true, events: 0 });
|
|
207
242
|
}
|
|
208
|
-
|
|
243
|
+
this.#pendingHud = reticleParamsFromSearch(window.location.search).hud;
|
|
244
|
+
if (options.present !== false && HudVisibility.REMOVED !== this.#pendingHud)
|
|
209
245
|
this.#fetchAndShowPanel(options);
|
|
210
246
|
if (true === options.recorder) {
|
|
211
247
|
this.#recorder = installRecorder({ emit, now: () => Date.now() });
|
|
@@ -215,7 +251,7 @@ export class Reticle {
|
|
|
215
251
|
this.#annotator = new Annotator({
|
|
216
252
|
emit,
|
|
217
253
|
now: () => Date.now(),
|
|
218
|
-
onMark: (mark) => this.#presenter?.log(LOG_KIND.HUMAN, `🚩 #${String(mark.index)} ${mark.
|
|
254
|
+
onMark: (mark) => this.#presenter?.log(LOG_KIND.HUMAN, `🚩 #${String(mark.index)} ${mark.label}${mark.source !== undefined ? ` · ${mark.source}` : ''} — ${mark.note}`),
|
|
219
255
|
shouldBlock: () => this.#readPanelSettings?.().blockPageInteractions ?? true,
|
|
220
256
|
});
|
|
221
257
|
this.#annotator.mount();
|
|
@@ -249,16 +285,30 @@ export class Reticle {
|
|
|
249
285
|
if (options.idleEndMs !== undefined)
|
|
250
286
|
panelOptions.idleEndMs = options.idleEndMs;
|
|
251
287
|
panelOptions.sessionId = this.#session;
|
|
288
|
+
if (this.#projectId !== undefined)
|
|
289
|
+
panelOptions.projectId = this.#projectId;
|
|
252
290
|
panelOptions.onControl = (intent) => this.#emit(EventType.HUMAN_CONTROL, intent.text !== undefined
|
|
253
291
|
? { kind: intent.kind, text: intent.text }
|
|
254
292
|
: { kind: intent.kind });
|
|
255
293
|
panelOptions.onHudUse = (use) => this.#emit(EventType.HUD_USED, { ...use });
|
|
294
|
+
panelOptions.onKill = () => {
|
|
295
|
+
this.disconnect();
|
|
296
|
+
nativeWarn(KILLED_MESSAGE);
|
|
297
|
+
};
|
|
256
298
|
const panel = new Presenter(panelOptions);
|
|
257
299
|
this.#presenter = panel;
|
|
258
300
|
panel.mount();
|
|
301
|
+
panel.showSdkVersion(this.#sdkVersion);
|
|
259
302
|
for (const buffered of this.#pendingPushes.values())
|
|
260
303
|
panel.handlePush(buffered);
|
|
261
304
|
this.#pendingPushes.clear();
|
|
305
|
+
if (0 < this.#pendingNarrations.length)
|
|
306
|
+
panel.sessionStart();
|
|
307
|
+
for (const early of this.#pendingNarrations.splice(0))
|
|
308
|
+
panel.narrate(early.text, early.level, early.actor, early.summary);
|
|
309
|
+
if (this.#pendingHud !== undefined)
|
|
310
|
+
panel.placeHud(this.#pendingHud, undefined);
|
|
311
|
+
this.#pendingHud = undefined;
|
|
262
312
|
void import('./presenter/tour/tour.js').then((tour) => {
|
|
263
313
|
if (!this.#connected)
|
|
264
314
|
return;
|
|
@@ -384,19 +434,56 @@ export class Reticle {
|
|
|
384
434
|
};
|
|
385
435
|
}
|
|
386
436
|
async #handleCommand(command) {
|
|
437
|
+
if (command.name === ReticleCommand.MARKERS) {
|
|
438
|
+
const markers = coverageMarkers(document);
|
|
439
|
+
const buildPlugin = this.#sdkVersion !== undefined || this.#sourceMapping !== undefined;
|
|
440
|
+
const na = notApplicableFor({ buildPlugin, adapters: adapterNames(), markers });
|
|
441
|
+
return { ok: true, result: { markers, ...(0 === na.length ? {} : { notApplicable: na }) } };
|
|
442
|
+
}
|
|
387
443
|
if (command.name === ReticleCommand.NARRATE) {
|
|
444
|
+
if (this.#presenter === undefined && this.#panelRequested) {
|
|
445
|
+
if (this.#pendingNarrations.length < MAX_PENDING_NARRATIONS)
|
|
446
|
+
this.#pendingNarrations.push({
|
|
447
|
+
text: str(command.args['text']),
|
|
448
|
+
level: str(command.args['level'], 'info'),
|
|
449
|
+
actor: narrationActor(command.args),
|
|
450
|
+
summary: true === command.args['summary'],
|
|
451
|
+
});
|
|
452
|
+
return { ok: true, result: { shown: false, buffered: true } };
|
|
453
|
+
}
|
|
388
454
|
this.#presenter?.sessionStart();
|
|
389
|
-
this.#presenter?.narrate(str(command.args['text']), str(command.args['level'], 'info'));
|
|
455
|
+
this.#presenter?.narrate(str(command.args['text']), str(command.args['level'], 'info'), narrationActor(command.args), true === command.args['summary']);
|
|
390
456
|
return { ok: true, result: { shown: this.#presenter !== undefined } };
|
|
391
457
|
}
|
|
392
458
|
if (command.name === ReticleCommand.SESSION_CONFIG) {
|
|
393
459
|
const idleEndMs = command.args['idleEndMs'];
|
|
394
460
|
if ('number' === typeof idleEndMs)
|
|
395
461
|
this.#presenter?.setIdleEndMs(idleEndMs);
|
|
396
|
-
|
|
462
|
+
const hud = oneOf(HudVisibility, command.args['hud']);
|
|
463
|
+
const corner = oneOf(HudCorner, command.args['corner']);
|
|
464
|
+
const presenter = this.#presenter;
|
|
465
|
+
let placed;
|
|
466
|
+
if (presenter !== undefined)
|
|
467
|
+
placed = presenter.placeHud(hud, corner);
|
|
468
|
+
else {
|
|
469
|
+
const early = hudWithoutPanel(hud);
|
|
470
|
+
if (early.remember !== undefined)
|
|
471
|
+
this.#pendingHud = early.remember;
|
|
472
|
+
placed = early.placed;
|
|
473
|
+
}
|
|
474
|
+
return {
|
|
475
|
+
ok: true,
|
|
476
|
+
result: {
|
|
477
|
+
applied: this.#presenter !== undefined,
|
|
478
|
+
idleEndMs,
|
|
479
|
+
...(0 < placed.length ? { hud: placed } : {}),
|
|
480
|
+
},
|
|
481
|
+
};
|
|
397
482
|
}
|
|
398
483
|
if (command.name === ReticleCommand.PRESENTER ||
|
|
399
484
|
command.name === ReticleCommand.FLOWS ||
|
|
485
|
+
command.name === ReticleCommand.FLOW_PROGRESS ||
|
|
486
|
+
command.name === ReticleCommand.PLAN ||
|
|
400
487
|
command.name === ReticleCommand.IMPACT) {
|
|
401
488
|
if (this.#presenter === undefined) {
|
|
402
489
|
this.#pendingPushes.set(command.name, command);
|
|
@@ -411,6 +498,8 @@ export class Reticle {
|
|
|
411
498
|
}
|
|
412
499
|
this.#presenter?.sessionStart();
|
|
413
500
|
await this.#presentBefore(command);
|
|
501
|
+
setPresenterViewer(CommandActor.HARNESS === command.by);
|
|
502
|
+
setOutwardPolicy(command.outward?.allowed);
|
|
414
503
|
try {
|
|
415
504
|
const result = await handler(command.args);
|
|
416
505
|
this.#actHandle?.result(LOG_RESULT.PASS);
|
|
@@ -421,6 +510,8 @@ export class Reticle {
|
|
|
421
510
|
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
422
511
|
}
|
|
423
512
|
finally {
|
|
513
|
+
setPresenterViewer(false);
|
|
514
|
+
setOutwardPolicy(undefined);
|
|
424
515
|
this.#actHandle = undefined;
|
|
425
516
|
this.#presenter?.scheduleIdle();
|
|
426
517
|
}
|
|
@@ -431,10 +522,11 @@ export class Reticle {
|
|
|
431
522
|
return;
|
|
432
523
|
p.setMode(modeForCommand(command.name));
|
|
433
524
|
this.#actHandle = undefined;
|
|
525
|
+
const actor = CommandActor.HARNESS === command.by ? LOG_ACTOR.HARNESS : LOG_ACTOR.AGENT;
|
|
434
526
|
if (command.name === ReticleCommand.ACT) {
|
|
435
527
|
const ref = str(command.args['ref']);
|
|
436
528
|
const label = refLabel(ref);
|
|
437
|
-
this.#actHandle = p.log(LOG_KIND.ACT, `${actionVerb(str(command.args['action']))} ${label}
|
|
529
|
+
this.#actHandle = p.log(LOG_KIND.ACT, `${actionVerb(str(command.args['action']))} ${label}`, undefined, actor);
|
|
438
530
|
await p.beforeAct(ref, str(command.args['action']), label);
|
|
439
531
|
}
|
|
440
532
|
else if (command.name === ReticleCommand.ACT_SEQUENCE) {
|
|
@@ -443,14 +535,20 @@ export class Reticle {
|
|
|
443
535
|
const s = step;
|
|
444
536
|
const ref = str(s.ref);
|
|
445
537
|
const label = refLabel(ref);
|
|
446
|
-
this.#actHandle = p.log(LOG_KIND.ACT, `${actionVerb(str(s.action))} ${label}
|
|
538
|
+
this.#actHandle = p.log(LOG_KIND.ACT, `${actionVerb(str(s.action))} ${label}`, undefined, actor);
|
|
447
539
|
await p.beforeAct(ref, str(s.action), label);
|
|
448
540
|
}
|
|
449
541
|
}
|
|
450
542
|
else {
|
|
451
543
|
const label = presentStatus(command.name, command.args);
|
|
452
544
|
p.status(label);
|
|
453
|
-
p.log(LOG_KIND.READ, label);
|
|
545
|
+
p.log(LOG_KIND.READ, label, undefined, actor);
|
|
454
546
|
}
|
|
455
547
|
}
|
|
456
548
|
}
|
|
549
|
+
function narrationActor(args) {
|
|
550
|
+
return CommandActor.HARNESS === args['actor'] ? LOG_ACTOR.HARNESS : LOG_ACTOR.AGENT;
|
|
551
|
+
}
|
|
552
|
+
function oneOf(values, raw) {
|
|
553
|
+
return Object.values(values).includes(raw) ? raw : undefined;
|
|
554
|
+
}
|
|
@@ -3,4 +3,4 @@ export declare const MARK_SUBMIT = "Add";
|
|
|
3
3
|
export declare const MARK_CANCEL = "Cancel";
|
|
4
4
|
export declare const MARK_PENDING_GLYPH = "+";
|
|
5
5
|
export declare const ANNOTATOR_CSS: string;
|
|
6
|
-
export declare const ANNOTATOR_ROOT_HTML
|
|
6
|
+
export declare const ANNOTATOR_ROOT_HTML: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EventType } from '@reticlehq/core';
|
|
2
|
+
import type { MarkForAgent } from './marks-for-agent.js';
|
|
2
3
|
interface MarkReport {
|
|
3
4
|
note: string;
|
|
4
5
|
anchor: string;
|
|
@@ -19,12 +20,18 @@ export interface AnnotatorChrome {
|
|
|
19
20
|
copyRow?: HTMLElement;
|
|
20
21
|
countEl?: HTMLElement;
|
|
21
22
|
onCount?: (count: number) => void;
|
|
23
|
+
onMarksChange?: (marks: readonly AnnotationItem[]) => void;
|
|
24
|
+
}
|
|
25
|
+
export interface AnnotationItem extends MarkForAgent {
|
|
26
|
+
id: string;
|
|
27
|
+
createdAt: number;
|
|
22
28
|
}
|
|
23
29
|
export declare class Annotator {
|
|
24
30
|
#private;
|
|
25
31
|
constructor(deps: AnnotatorDeps);
|
|
26
32
|
get active(): boolean;
|
|
27
33
|
get markCount(): number;
|
|
34
|
+
annotations(): AnnotationItem[];
|
|
28
35
|
mount(): void;
|
|
29
36
|
setAccent(hex: string): void;
|
|
30
37
|
attachChrome(chrome: AnnotatorChrome): void;
|
package/dist/review/annotator.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readTestId } from '../dom/addressing/testid-attr.js';
|
|
1
2
|
import { isSyntheticInput } from '../actions/synthetic/synthetic-input.js';
|
|
2
3
|
import { EventType } from '@reticlehq/core';
|
|
3
4
|
import { isReticleUi, isReticleOverlay } from '../dom/dom-ignore.js';
|
|
@@ -36,6 +37,7 @@ export class Annotator {
|
|
|
36
37
|
#clearBtn;
|
|
37
38
|
#copyRow;
|
|
38
39
|
#onChromeCount;
|
|
40
|
+
#onMarksChange;
|
|
39
41
|
#countEl;
|
|
40
42
|
#accent;
|
|
41
43
|
#ac;
|
|
@@ -53,6 +55,17 @@ export class Annotator {
|
|
|
53
55
|
get markCount() {
|
|
54
56
|
return this.#marks.length;
|
|
55
57
|
}
|
|
58
|
+
annotations() {
|
|
59
|
+
return this.#marks.map(({ id, createdAt, note, label, anchor, route, source }) => ({
|
|
60
|
+
id,
|
|
61
|
+
createdAt,
|
|
62
|
+
note,
|
|
63
|
+
label,
|
|
64
|
+
anchor,
|
|
65
|
+
route,
|
|
66
|
+
...(source === undefined ? {} : { source }),
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
56
69
|
mount() {
|
|
57
70
|
if (this.#root !== undefined || 'undefined' === typeof document)
|
|
58
71
|
return;
|
|
@@ -99,6 +112,7 @@ export class Annotator {
|
|
|
99
112
|
this.#clearBtn = chrome.clearBtn;
|
|
100
113
|
this.#copyRow = chrome.copyRow;
|
|
101
114
|
this.#onChromeCount = chrome.onCount;
|
|
115
|
+
this.#onMarksChange = chrome.onMarksChange;
|
|
102
116
|
this.#countEl = chrome.countEl;
|
|
103
117
|
this.#markersBtn?.addEventListener('click', (e) => {
|
|
104
118
|
e.stopPropagation();
|
|
@@ -184,6 +198,7 @@ export class Annotator {
|
|
|
184
198
|
}
|
|
185
199
|
this.#clearBtn?.toggleAttribute('disabled', 0 === n);
|
|
186
200
|
this.#onChromeCount?.(n);
|
|
201
|
+
this.#onMarksChange?.(this.annotations());
|
|
187
202
|
if (this.#copyRow !== undefined) {
|
|
188
203
|
this.#copyRow.hidden = 0 === n;
|
|
189
204
|
const text = this.#copyRow.querySelector('[data-reticle-marks-text]');
|
|
@@ -406,6 +421,7 @@ export class Annotator {
|
|
|
406
421
|
route: editing.route,
|
|
407
422
|
});
|
|
408
423
|
this.#report(note, editing.anchor, editing.label, this.#marks.indexOf(editing) + 1, editing.source);
|
|
424
|
+
this.#syncChrome();
|
|
409
425
|
this.#closePopover();
|
|
410
426
|
return;
|
|
411
427
|
}
|
|
@@ -480,8 +496,10 @@ export class Annotator {
|
|
|
480
496
|
pin.style.left = `${String((x / window.innerWidth) * 100)}%`;
|
|
481
497
|
pin.style.top = `${String(isFixed ? y : yDoc - window.scrollY)}px`;
|
|
482
498
|
this.#root.appendChild(pin);
|
|
499
|
+
const createdAt = this.#now();
|
|
483
500
|
const mark = {
|
|
484
|
-
id: `${resolved.anchor}:${String(
|
|
501
|
+
id: `${resolved.anchor}:${String(createdAt)}:${String(n)}`,
|
|
502
|
+
createdAt,
|
|
485
503
|
note,
|
|
486
504
|
label: resolved.label,
|
|
487
505
|
anchor: resolved.anchor,
|
|
@@ -593,7 +611,7 @@ function clampPop(x, y) {
|
|
|
593
611
|
return { left, top };
|
|
594
612
|
}
|
|
595
613
|
function describeEl(el) {
|
|
596
|
-
const testid = el
|
|
614
|
+
const testid = readTestId(el);
|
|
597
615
|
if (testid !== null && testid.length > 0)
|
|
598
616
|
return testid;
|
|
599
617
|
const tag = el.tagName.toLowerCase();
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { readTestId } from '../dom/addressing/testid-attr.js';
|
|
1
2
|
import { MarkAnchorStrategy } from '@reticlehq/core';
|
|
2
3
|
import { AnchorStrategy, synthesizeAnchor, } from '../dom/addressing/auto-anchor.js';
|
|
3
4
|
import { getAccessibleName, getRole } from '../dom/a11y.js';
|
|
4
5
|
import { identifyComponent } from '../registry/stores/adapters.js';
|
|
5
6
|
import { sourceFor } from '../dom/addressing/source.js';
|
|
6
|
-
const TESTID_ATTR = 'data-testid';
|
|
7
7
|
const STRATEGY = {
|
|
8
8
|
[AnchorStrategy.TESTID]: MarkAnchorStrategy.TESTID,
|
|
9
9
|
[AnchorStrategy.COMPONENT]: MarkAnchorStrategy.COMPONENT,
|
|
@@ -16,7 +16,7 @@ function labelFor(el, role, name) {
|
|
|
16
16
|
return role.length > 0 ? role : el.tagName.toLowerCase();
|
|
17
17
|
}
|
|
18
18
|
export function resolveMarkAnchor(el) {
|
|
19
|
-
const testid = el
|
|
19
|
+
const testid = readTestId(el) ?? undefined;
|
|
20
20
|
const info = identifyComponent(el);
|
|
21
21
|
const component = info?.componentStack[0];
|
|
22
22
|
const source = sourceFor(el, info?.source);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const ReticleStorageKey: {
|
|
2
|
+
readonly SESSION: "__reticle_session";
|
|
3
|
+
readonly REF_BASE: "__reticle_ref_base";
|
|
4
|
+
readonly PRESENTER_LOG: "reticle-presenter-log";
|
|
5
|
+
readonly PRESENTER_LOG_OWNER: "reticle-presenter-log-owner";
|
|
6
|
+
readonly PRESENTER_SETTINGS: "reticle-presenter-settings";
|
|
7
|
+
readonly PRESENTER_MINIMISED: "reticle-presenter-minimised";
|
|
8
|
+
readonly PRESENTER_VIEW: "reticle-presenter-view";
|
|
9
|
+
readonly CAROUSEL_DISMISSED: "reticle.carousel.dismissed";
|
|
10
|
+
readonly ANNOTATION_HISTORY: "reticle.annotations.history.v1";
|
|
11
|
+
};
|
|
12
|
+
export declare const TOUR_SEEN_KEY_PREFIX = "reticle.tour.seen.";
|
|
13
|
+
export declare const PERSONA_KEY_PREFIX = "reticle.harness.persona.";
|
|
14
|
+
export declare const IMPACT_SEEN_KEY_PREFIX = "reticle.impact.seen.";
|
|
15
|
+
export declare function isReticleStorageKey(key: string): boolean;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const ReticleStorageKey = {
|
|
2
|
+
SESSION: '__reticle_session',
|
|
3
|
+
REF_BASE: '__reticle_ref_base',
|
|
4
|
+
PRESENTER_LOG: 'reticle-presenter-log',
|
|
5
|
+
PRESENTER_LOG_OWNER: 'reticle-presenter-log-owner',
|
|
6
|
+
PRESENTER_SETTINGS: 'reticle-presenter-settings',
|
|
7
|
+
PRESENTER_MINIMISED: 'reticle-presenter-minimised',
|
|
8
|
+
PRESENTER_VIEW: 'reticle-presenter-view',
|
|
9
|
+
CAROUSEL_DISMISSED: 'reticle.carousel.dismissed',
|
|
10
|
+
ANNOTATION_HISTORY: 'reticle.annotations.history.v1',
|
|
11
|
+
};
|
|
12
|
+
export const TOUR_SEEN_KEY_PREFIX = 'reticle.tour.seen.';
|
|
13
|
+
export const PERSONA_KEY_PREFIX = 'reticle.harness.persona.';
|
|
14
|
+
export const IMPACT_SEEN_KEY_PREFIX = 'reticle.impact.seen.';
|
|
15
|
+
const OWN_KEYS = new Set(Object.values(ReticleStorageKey));
|
|
16
|
+
export function isReticleStorageKey(key) {
|
|
17
|
+
return (OWN_KEYS.has(key) ||
|
|
18
|
+
key.startsWith(TOUR_SEEN_KEY_PREFIX) ||
|
|
19
|
+
key.startsWith(PERSONA_KEY_PREFIX) ||
|
|
20
|
+
key.startsWith(IMPACT_SEEN_KEY_PREFIX));
|
|
21
|
+
}
|
|
@@ -26,6 +26,7 @@ export class Transport {
|
|
|
26
26
|
#signalQueue = [];
|
|
27
27
|
#insertOrder = 0;
|
|
28
28
|
#closed = false;
|
|
29
|
+
#refused = false;
|
|
29
30
|
#disconnectedSince;
|
|
30
31
|
#reconnectDelay = RECONNECT_DELAY_MS;
|
|
31
32
|
#lost = false;
|
|
@@ -55,6 +56,7 @@ export class Transport {
|
|
|
55
56
|
this.#reconnectDelay = RECONNECT_DELAY_MS;
|
|
56
57
|
if (this.#ws !== undefined)
|
|
57
58
|
return;
|
|
59
|
+
this.#refused = false;
|
|
58
60
|
this.#open();
|
|
59
61
|
}
|
|
60
62
|
#open() {
|
|
@@ -92,9 +94,9 @@ export class Transport {
|
|
|
92
94
|
ws.onclose = (event) => {
|
|
93
95
|
this.#ws = undefined;
|
|
94
96
|
if (event.code === WS_POLICY_VIOLATION) {
|
|
95
|
-
this.#
|
|
97
|
+
this.#refused = true;
|
|
96
98
|
const reason = event.reason.length > 0 ? event.reason : 'policy violation';
|
|
97
|
-
nativeWarn(`[reticle] bridge refused the connection: ${reason} — not retrying.`);
|
|
99
|
+
nativeWarn(`[reticle] bridge refused the connection: ${reason} — not retrying until you come back to this tab.`);
|
|
98
100
|
this.#deps.onConnectionLost?.();
|
|
99
101
|
return;
|
|
100
102
|
}
|
|
@@ -107,7 +109,7 @@ export class Transport {
|
|
|
107
109
|
};
|
|
108
110
|
}
|
|
109
111
|
#scheduleReopen() {
|
|
110
|
-
if (this.#closed)
|
|
112
|
+
if (this.#closed || this.#refused)
|
|
111
113
|
return;
|
|
112
114
|
const delay = this.#reconnectDelay;
|
|
113
115
|
this.#reconnectDelay = nextReconnectDelay(delay);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export declare function unreachableMessage(url: string, attempts: number): string;
|
|
2
|
-
export declare
|
|
2
|
+
export declare const UNREACHABLE_HEADLINE = "Can't reach Reticle from this page. Run reticle status to check, or reticle serve to start it.";
|
|
@@ -5,9 +5,7 @@ export function unreachableMessage(url, attempts) {
|
|
|
5
5
|
`that is not there from one it is not allowed to reach, so this is not evidence about the ` +
|
|
6
6
|
`daemon. What answers it: run \`npx @reticlehq/server status\` to see whether one is listening ` +
|
|
7
7
|
`and on which port. If your app runs in a container, devcontainer or WSL, the daemon is on a ` +
|
|
8
|
-
`different host, so
|
|
9
|
-
`reticle.connect({ url })
|
|
10
|
-
}
|
|
11
|
-
export function unreachableStripText(url, attempts) {
|
|
12
|
-
return `no bridge at ${url} — ${String(attempts)} attempt${1 === attempts ? '' : 's'}`;
|
|
8
|
+
`different host, so point the page at it: RETICLE_PORT or reticle({ port }) with the Vite ` +
|
|
9
|
+
`plugin, reticle.connect({ url }) by hand. An https page cannot open a ws:// socket. Still retrying…`);
|
|
13
10
|
}
|
|
11
|
+
export const UNREACHABLE_HEADLINE = "Can't reach Reticle from this page. Run reticle status to check, or reticle serve to start it.";
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reticlehq/browser",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.0",
|
|
4
4
|
"description": "Reticle browser SDK — installs observers, builds semantic snapshots, executes actions, talks to the bridge.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/reticle-inject.js",
|
|
7
8
|
"types": "./dist/index.d.ts",
|
|
8
9
|
"exports": {
|
|
9
10
|
".": {
|
|
@@ -17,7 +18,7 @@
|
|
|
17
18
|
}
|
|
18
19
|
},
|
|
19
20
|
"dependencies": {
|
|
20
|
-
"@reticlehq/core": "3.
|
|
21
|
+
"@reticlehq/core": "3.7.0"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
23
24
|
"@iconify-json/heroicons-outline": "^1.2.1",
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export declare const OFFER_ATTR = "data-reticle-offer";
|
|
2
|
-
export declare const OFFER_CLAIM_ATTR = "data-reticle-offer-claim";
|
|
3
|
-
export declare const OFFER_DISMISSED_KEY = "reticle.harnessOffer.dismissed";
|
|
4
|
-
export declare const OFFER_TEXT: {
|
|
5
|
-
readonly HEADLINE: "Get Harness free for 3 months";
|
|
6
|
-
readonly BODY: "Explores your app and saves every journey as a free replay.";
|
|
7
|
-
readonly CLAIM: "Claim now";
|
|
8
|
-
readonly DISMISS: "Not now";
|
|
9
|
-
readonly claimed: (days: number) => string;
|
|
10
|
-
readonly ending: (days: number) => string;
|
|
11
|
-
};
|
|
12
|
-
export declare function offerDismissed(storage: Pick<Storage, 'getItem'> | undefined): boolean;
|
|
13
|
-
export interface OfferState {
|
|
14
|
-
claimed: boolean;
|
|
15
|
-
daysRemaining?: number | undefined;
|
|
16
|
-
claimUrl?: string | undefined;
|
|
17
|
-
eligible?: boolean | undefined;
|
|
18
|
-
drivable?: boolean | undefined;
|
|
19
|
-
}
|
|
20
|
-
export declare function offerHtml(offer: OfferState | undefined, dismissed: boolean): string;
|
|
21
|
-
export declare const OFFER_CSS = "\n.reticle-offer{margin:8px 10px;padding:10px 12px;border:1px solid var(--reticle-line,#2a2f3a);border-radius:10px;background:var(--reticle-surface-inset,rgba(255,255,255,.03));}\n.reticle-offer-title{display:block;font-weight:600;font-size:12px;padding-right:18px;}\n.reticle-offer-body{margin:6px 0 8px;font-size:11px;line-height:1.45;opacity:.8;}\n.reticle-offer-claim{display:inline-block;font-size:11px;font-weight:600;padding:4px 10px;border-radius:6px;background:var(--reticle-accent,#4f7cff);color:#fff;text-decoration:none;}\n.reticle-offer-claimed{margin:6px 10px;font-size:10px;opacity:.6;}\n";
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
export const OFFER_ATTR = 'data-reticle-offer';
|
|
2
|
-
export const OFFER_CLAIM_ATTR = 'data-reticle-offer-claim';
|
|
3
|
-
export const OFFER_DISMISSED_KEY = 'reticle.harnessOffer.dismissed';
|
|
4
|
-
export const OFFER_TEXT = {
|
|
5
|
-
HEADLINE: 'Get Harness free for 3 months',
|
|
6
|
-
BODY: 'Explores your app and saves every journey as a free replay.',
|
|
7
|
-
CLAIM: 'Claim now',
|
|
8
|
-
DISMISS: 'Not now',
|
|
9
|
-
claimed: (days) => `Harness active — ${String(days)} days left`,
|
|
10
|
-
ending: (days) => 1 >= days
|
|
11
|
-
? 'Harness free period ends today'
|
|
12
|
-
: `Harness free period ends in ${String(days)} days`,
|
|
13
|
-
};
|
|
14
|
-
const ENDING_SOON_DAYS = 14;
|
|
15
|
-
export function offerDismissed(storage) {
|
|
16
|
-
try {
|
|
17
|
-
return '1' === storage?.getItem(OFFER_DISMISSED_KEY);
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
const escape = (text) => text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
24
|
-
export function offerHtml(offer, dismissed) {
|
|
25
|
-
if (offer === undefined)
|
|
26
|
-
return '';
|
|
27
|
-
if (offer.claimed) {
|
|
28
|
-
const days = offer.daysRemaining;
|
|
29
|
-
if (days === undefined || 0 > days)
|
|
30
|
-
return '';
|
|
31
|
-
const text = ENDING_SOON_DAYS >= days ? OFFER_TEXT.ending(days) : OFFER_TEXT.claimed(days);
|
|
32
|
-
return `<div ${OFFER_ATTR} class="reticle-offer reticle-offer-claimed">${escape(text)}</div>`;
|
|
33
|
-
}
|
|
34
|
-
if (false === offer.eligible)
|
|
35
|
-
return '';
|
|
36
|
-
if (false === offer.drivable)
|
|
37
|
-
return '';
|
|
38
|
-
if (dismissed)
|
|
39
|
-
return '';
|
|
40
|
-
const url = offer.claimUrl;
|
|
41
|
-
if (url === undefined || 0 === url.length)
|
|
42
|
-
return '';
|
|
43
|
-
return `<div ${OFFER_ATTR} class="reticle-offer" role="note">
|
|
44
|
-
<span class="reticle-offer-title">${escape(OFFER_TEXT.HEADLINE)}</span>
|
|
45
|
-
<p class="reticle-offer-body">${escape(OFFER_TEXT.BODY)}</p>
|
|
46
|
-
<a ${OFFER_CLAIM_ATTR} class="reticle-offer-claim" href="${escape(url)}" target="_blank" rel="noopener noreferrer">${escape(OFFER_TEXT.CLAIM)}</a>
|
|
47
|
-
</div>`;
|
|
48
|
-
}
|
|
49
|
-
export const OFFER_CSS = `
|
|
50
|
-
.reticle-offer{margin:8px 10px;padding:10px 12px;border:1px solid var(--reticle-line,#2a2f3a);border-radius:10px;background:var(--reticle-surface-inset,rgba(255,255,255,.03));}
|
|
51
|
-
.reticle-offer-title{display:block;font-weight:600;font-size:12px;padding-right:18px;}
|
|
52
|
-
.reticle-offer-body{margin:6px 0 8px;font-size:11px;line-height:1.45;opacity:.8;}
|
|
53
|
-
.reticle-offer-claim{display:inline-block;font-size:11px;font-weight:600;padding:4px 10px;border-radius:6px;background:var(--reticle-accent,#4f7cff);color:#fff;text-decoration:none;}
|
|
54
|
-
.reticle-offer-claimed{margin:6px 10px;font-size:10px;opacity:.6;}
|
|
55
|
-
`;
|