@useclickly/react 1.1.0 → 1.3.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/index.js CHANGED
@@ -3,6 +3,7 @@ import { useState, useEffect, useSyncExternalStore, useRef, useCallback } from '
3
3
  import { createRoot } from 'react-dom/client';
4
4
  import { isPlacementAnnotation, isRearrangeAnnotation, createShadowHost, SelectionEngine, Overlay, collectComputedStyles, collectMetadata, getReadableElementPath, identifyElement } from '@useclickly/core';
5
5
  import { create } from 'zustand';
6
+ import { persist, createJSONStorage } from 'zustand/middleware';
6
7
  import { useShallow } from 'zustand/react/shallow';
7
8
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
9
  import { nanoid } from 'nanoid';
@@ -144,52 +145,84 @@ async function loadDirHandle() {
144
145
  }
145
146
 
146
147
  // src/state/annotations.ts
147
- var useAnnotations = create((set, get) => ({
148
- byId: {},
149
- order: [],
150
- add: (a) => {
151
- if (a.strokes && a.strokes.length > 0) void saveStrokes(a.id, a.strokes);
152
- set((s) => ({
153
- byId: { ...s.byId, [a.id]: a },
154
- order: s.order.includes(a.id) ? s.order : [...s.order, a.id]
155
- }));
156
- },
157
- remove: (id) => {
158
- void deleteStrokes(id);
159
- set((s) => {
160
- if (!(id in s.byId)) return s;
161
- const next = { ...s.byId };
162
- delete next[id];
163
- return { byId: next, order: s.order.filter((x) => x !== id) };
164
- });
165
- },
166
- update: (id, patch) => {
167
- if (patch.strokes) void saveStrokes(id, patch.strokes);
168
- set((s) => {
169
- const cur = s.byId[id];
170
- if (!cur) return s;
171
- return { byId: { ...s.byId, [id]: { ...cur, ...patch } } };
172
- });
173
- },
174
- clear: () => {
175
- set({ byId: {}, order: [] });
176
- },
177
- list: () => {
178
- const { byId, order } = get();
179
- return order.map((id) => byId[id]).filter(Boolean);
180
- },
181
- hydrateStrokes: (strokesById) => set((s) => {
182
- let touched = false;
183
- const next = { ...s.byId };
184
- for (const [id, strokes] of Object.entries(strokesById)) {
185
- const cur = next[id];
186
- if (!cur) continue;
187
- next[id] = { ...cur, strokes };
188
- touched = true;
148
+ var STORAGE_KEY = "clickly:annotations";
149
+ var useAnnotations = create()(
150
+ persist(
151
+ (set, get) => ({
152
+ byId: {},
153
+ order: [],
154
+ add: (a) => {
155
+ if (a.strokes && a.strokes.length > 0) void saveStrokes(a.id, a.strokes);
156
+ set((s) => ({
157
+ byId: { ...s.byId, [a.id]: a },
158
+ order: s.order.includes(a.id) ? s.order : [...s.order, a.id]
159
+ }));
160
+ },
161
+ remove: (id) => {
162
+ void deleteStrokes(id);
163
+ set((s) => {
164
+ if (!(id in s.byId)) return s;
165
+ const next = { ...s.byId };
166
+ delete next[id];
167
+ return { byId: next, order: s.order.filter((x) => x !== id) };
168
+ });
169
+ },
170
+ update: (id, patch) => {
171
+ if (patch.strokes) void saveStrokes(id, patch.strokes);
172
+ set((s) => {
173
+ const cur = s.byId[id];
174
+ if (!cur) return s;
175
+ return { byId: { ...s.byId, [id]: { ...cur, ...patch } } };
176
+ });
177
+ },
178
+ clear: () => {
179
+ set({ byId: {}, order: [] });
180
+ },
181
+ list: () => {
182
+ const { byId, order } = get();
183
+ return order.map((id) => byId[id]).filter(Boolean);
184
+ },
185
+ hydrateStrokes: (strokesById) => set((s) => {
186
+ let touched = false;
187
+ const next = { ...s.byId };
188
+ for (const [id, strokes] of Object.entries(strokesById)) {
189
+ const cur = next[id];
190
+ if (!cur) continue;
191
+ next[id] = { ...cur, strokes };
192
+ touched = true;
193
+ }
194
+ return touched ? { byId: next } : s;
195
+ })
196
+ }),
197
+ {
198
+ name: STORAGE_KEY,
199
+ storage: createJSONStorage(() => {
200
+ if (typeof localStorage !== "undefined") return localStorage;
201
+ return {
202
+ getItem: () => null,
203
+ setItem: () => void 0,
204
+ removeItem: () => void 0
205
+ };
206
+ }),
207
+ // Skip strokes — they live in IndexedDB and are reattached by
208
+ // hydrateStrokes(). Persisting them in JSON would blow the
209
+ // localStorage quota (5MB) on a single freehand drawing.
210
+ partialize: (state) => ({
211
+ byId: Object.fromEntries(
212
+ Object.entries(state.byId).map(([id, ann]) => {
213
+ const { strokes: _strokes, ...rest } = ann;
214
+ return [id, rest];
215
+ })
216
+ ),
217
+ order: state.order
218
+ }),
219
+ // Bump this if the persisted shape changes; old payloads are
220
+ // ignored and the store starts empty rather than crashing on
221
+ // a mismatched schema.
222
+ version: 1
189
223
  }
190
- return touched ? { byId: next } : s;
191
- })
192
- }));
224
+ )
225
+ );
193
226
  function useAnnotationsList() {
194
227
  return useAnnotations(
195
228
  useShallow((s) => s.order.map((id) => s.byId[id]).filter(Boolean))
@@ -205,11 +238,11 @@ var DEFAULTS = {
205
238
  mcpEndpoint: "http://localhost:4747",
206
239
  mcpSessionId: null
207
240
  };
208
- var STORAGE_KEY = "clickly:settings";
241
+ var STORAGE_KEY2 = "clickly:settings";
209
242
  function load() {
210
243
  if (typeof localStorage === "undefined") return DEFAULTS;
211
244
  try {
212
- const raw = localStorage.getItem(STORAGE_KEY);
245
+ const raw = localStorage.getItem(STORAGE_KEY2);
213
246
  if (!raw) return DEFAULTS;
214
247
  const parsed = JSON.parse(raw);
215
248
  return { ...DEFAULTS, ...parsed };
@@ -217,10 +250,10 @@ function load() {
217
250
  return DEFAULTS;
218
251
  }
219
252
  }
220
- function persist(s) {
253
+ function persist2(s) {
221
254
  if (typeof localStorage === "undefined") return;
222
255
  try {
223
- localStorage.setItem(STORAGE_KEY, JSON.stringify(s));
256
+ localStorage.setItem(STORAGE_KEY2, JSON.stringify(s));
224
257
  } catch {
225
258
  }
226
259
  }
@@ -228,11 +261,11 @@ var useSettings = create((set) => ({
228
261
  ...load(),
229
262
  set: (patch) => set((cur) => {
230
263
  const next = { ...cur, ...patch };
231
- persist(next);
264
+ persist2(next);
232
265
  return next;
233
266
  }),
234
267
  reset: () => {
235
- persist(DEFAULTS);
268
+ persist2(DEFAULTS);
236
269
  set(DEFAULTS);
237
270
  }
238
271
  }));
@@ -497,9 +530,15 @@ async function safeReadText(res) {
497
530
  return "";
498
531
  }
499
532
  }
533
+ var PANEL_W = 284;
534
+ var PANEL_H = 340;
535
+ var PANEL_GAP = 12;
536
+ var VIEWPORT_PAD = 8;
537
+ var TOOLBAR_H = 46;
500
538
  function SettingsPopover({ anchor, width, onClose }) {
501
539
  const ref = useRef(null);
502
- const s = useSettings();
540
+ const [view, setView] = useState("main");
541
+ const [detailMenuOpen, setDetailMenuOpen] = useState(false);
503
542
  useEffect(() => {
504
543
  const onDown = (e) => {
505
544
  if (ref.current && e.composedPath().includes(ref.current)) return;
@@ -508,95 +547,162 @@ function SettingsPopover({ anchor, width, onClose }) {
508
547
  window.addEventListener("pointerdown", onDown, true);
509
548
  return () => window.removeEventListener("pointerdown", onDown, true);
510
549
  }, [onClose]);
511
- const PANEL_H = 330;
512
- const top = Math.max(8, anchor.y - PANEL_H - 12);
513
- const left = Math.min(window.innerWidth - 300, Math.max(8, anchor.x + width - 284));
514
- return /* @__PURE__ */ jsxs("div", { ref, className: "clickly-settings", style: { left, top }, children: [
550
+ useEffect(() => {
551
+ const onKey = (e) => {
552
+ if (e.key !== "Escape") return;
553
+ if (view !== "main") {
554
+ e.preventDefault();
555
+ setView("main");
556
+ }
557
+ };
558
+ window.addEventListener("keydown", onKey);
559
+ return () => window.removeEventListener("keydown", onKey);
560
+ }, [view]);
561
+ const vw = typeof window !== "undefined" ? window.innerWidth : 1024;
562
+ const vh = typeof window !== "undefined" ? window.innerHeight : 768;
563
+ const spaceAbove = anchor.y - VIEWPORT_PAD;
564
+ const spaceBelow = vh - anchor.y - TOOLBAR_H - VIEWPORT_PAD;
565
+ const placeAbove = spaceAbove >= PANEL_H + PANEL_GAP || spaceAbove >= spaceBelow;
566
+ const top = placeAbove ? Math.max(VIEWPORT_PAD, anchor.y - PANEL_H - PANEL_GAP) : Math.min(vh - PANEL_H - VIEWPORT_PAD, anchor.y + TOOLBAR_H + PANEL_GAP);
567
+ const desiredLeft = anchor.x + width - PANEL_W;
568
+ const left = Math.min(
569
+ vw - PANEL_W - VIEWPORT_PAD,
570
+ Math.max(VIEWPORT_PAD, desiredLeft)
571
+ );
572
+ return /* @__PURE__ */ jsx(
573
+ "div",
574
+ {
575
+ ref,
576
+ className: "clickly-settings",
577
+ style: { left, top, width: PANEL_W, height: PANEL_H },
578
+ children: view === "main" ? /* @__PURE__ */ jsx(
579
+ MainView,
580
+ {
581
+ onClose,
582
+ onOpenMcp: () => setView("mcp"),
583
+ detailMenuOpen,
584
+ setDetailMenuOpen
585
+ }
586
+ ) : /* @__PURE__ */ jsx(McpSubView, { onBack: () => setView("main"), onClose })
587
+ }
588
+ );
589
+ }
590
+ function MainView({
591
+ onClose,
592
+ onOpenMcp,
593
+ detailMenuOpen,
594
+ setDetailMenuOpen
595
+ }) {
596
+ const s = useSettings();
597
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
515
598
  /* @__PURE__ */ jsxs("div", { className: "settings-header", children: [
516
599
  /* @__PURE__ */ jsx("span", { className: "settings-title", children: "Settings" }),
517
600
  /* @__PURE__ */ jsx("button", { className: "settings-close", onClick: onClose, "aria-label": "Close settings", children: /* @__PURE__ */ jsx(IconClose, {}) })
518
601
  ] }),
519
- /* @__PURE__ */ jsxs("div", { className: "settings-section", children: [
520
- /* @__PURE__ */ jsxs("label", { className: "settings-label", htmlFor: "clickly-detail", children: [
521
- "Output detail",
522
- /* @__PURE__ */ jsx("span", { className: "settings-hint", children: "How much metadata is captured" })
523
- ] }),
524
- /* @__PURE__ */ jsxs(
525
- "select",
526
- {
527
- id: "clickly-detail",
528
- className: "settings-select",
529
- value: s.outputDetail,
530
- onChange: (e) => s.set({ outputDetail: e.target.value }),
531
- children: [
532
- /* @__PURE__ */ jsx("option", { value: "compact", children: "Compact" }),
533
- /* @__PURE__ */ jsx("option", { value: "standard", children: "Standard" }),
534
- /* @__PURE__ */ jsx("option", { value: "detailed", children: "Detailed" }),
535
- /* @__PURE__ */ jsx("option", { value: "forensic", children: "Forensic" })
536
- ]
537
- }
538
- )
539
- ] }),
540
- /* @__PURE__ */ jsx("div", { className: "settings-divider" }),
541
- /* @__PURE__ */ jsxs("div", { className: "settings-section", children: [
542
- /* @__PURE__ */ jsxs("div", { className: "settings-row", children: [
543
- /* @__PURE__ */ jsxs("div", { className: "settings-row-label", children: [
544
- "Copy on add",
545
- /* @__PURE__ */ jsx("span", { className: "settings-hint", children: "Auto-copy markdown when annotating" })
546
- ] }),
547
- /* @__PURE__ */ jsxs("label", { className: "clickly-toggle", children: [
548
- /* @__PURE__ */ jsx(
549
- "input",
602
+ /* @__PURE__ */ jsxs("div", { className: "settings-scroll", children: [
603
+ /* @__PURE__ */ jsxs("div", { className: "settings-row-compact", children: [
604
+ /* @__PURE__ */ jsx(RowLabel, { hint: "How much metadata the markdown output captures.", children: "Output Detail" }),
605
+ /* @__PURE__ */ jsxs("div", { className: "settings-inline-select", children: [
606
+ /* @__PURE__ */ jsxs(
607
+ "button",
550
608
  {
551
- type: "checkbox",
552
- checked: s.copyOnAdd,
553
- onChange: (e) => s.set({ copyOnAdd: e.target.checked })
609
+ type: "button",
610
+ className: "settings-inline-select-trigger",
611
+ onClick: () => setDetailMenuOpen((v) => !v),
612
+ "aria-haspopup": "menu",
613
+ "aria-expanded": detailMenuOpen,
614
+ children: [
615
+ /* @__PURE__ */ jsx("span", { children: labelForDetail(s.outputDetail) }),
616
+ /* @__PURE__ */ jsx("span", { className: "settings-inline-chev", "aria-hidden": true, children: "\u22EE" })
617
+ ]
554
618
  }
555
619
  ),
556
- /* @__PURE__ */ jsx("span", { className: "toggle-track" })
557
- ] })
558
- ] }),
559
- /* @__PURE__ */ jsxs("div", { className: "settings-row", children: [
560
- /* @__PURE__ */ jsxs("div", { className: "settings-row-label", children: [
561
- "React components",
562
- /* @__PURE__ */ jsx("span", { className: "settings-hint", children: "Include component tree in output" })
563
- ] }),
564
- /* @__PURE__ */ jsxs("label", { className: "clickly-toggle", children: [
565
- /* @__PURE__ */ jsx(
566
- "input",
620
+ detailMenuOpen ? /* @__PURE__ */ jsx("div", { className: "settings-inline-menu", role: "menu", children: ["compact", "standard", "detailed", "forensic"].map((v) => /* @__PURE__ */ jsx(
621
+ "button",
567
622
  {
568
- type: "checkbox",
569
- checked: s.showReactComponents,
570
- onChange: (e) => s.set({ showReactComponents: e.target.checked })
571
- }
572
- ),
573
- /* @__PURE__ */ jsx("span", { className: "toggle-track" })
623
+ type: "button",
624
+ role: "menuitemradio",
625
+ "aria-checked": s.outputDetail === v,
626
+ className: `settings-inline-menu-item${s.outputDetail === v ? " is-active" : ""}`,
627
+ onClick: () => {
628
+ s.set({ outputDetail: v });
629
+ setDetailMenuOpen(false);
630
+ },
631
+ children: labelForDetail(v)
632
+ },
633
+ v
634
+ )) }) : null
574
635
  ] })
575
- ] })
576
- ] }),
577
- /* @__PURE__ */ jsx("div", { className: "settings-divider" }),
578
- /* @__PURE__ */ jsx("div", { className: "settings-section", children: /* @__PURE__ */ jsxs("div", { className: "settings-row", children: [
579
- /* @__PURE__ */ jsxs("div", { className: "settings-row-label", children: [
580
- "Marker color",
581
- /* @__PURE__ */ jsx("span", { className: "settings-hint", children: "Color used for annotation pins" })
582
636
  ] }),
583
- /* @__PURE__ */ jsxs("label", { className: "settings-color-wrap", "aria-label": "Marker color", children: [
584
- /* @__PURE__ */ jsx("span", { className: "color-swatch", style: { background: s.markerColor } }),
637
+ /* @__PURE__ */ jsxs("div", { className: "settings-row-compact", children: [
638
+ /* @__PURE__ */ jsx(RowLabel, { hint: "Auto-copy markdown to the clipboard every time you submit an annotation.", children: "Copy on Add" }),
585
639
  /* @__PURE__ */ jsx(
586
- "input",
640
+ Toggle,
587
641
  {
588
- type: "color",
589
- value: s.markerColor,
590
- onChange: (e) => s.set({ markerColor: e.target.value })
642
+ checked: s.copyOnAdd,
643
+ onChange: (v) => s.set({ copyOnAdd: v })
591
644
  }
592
645
  )
593
- ] })
594
- ] }) }),
595
- /* @__PURE__ */ jsx("div", { className: "settings-divider" }),
596
- /* @__PURE__ */ jsx(McpSection, {})
646
+ ] }),
647
+ /* @__PURE__ */ jsxs("div", { className: "settings-row-compact", children: [
648
+ /* @__PURE__ */ jsx(RowLabel, { hint: "Include the React component tree (App \u203A Page \u203A Button) in the output.", children: "React Components" }),
649
+ /* @__PURE__ */ jsx(
650
+ Toggle,
651
+ {
652
+ checked: s.showReactComponents,
653
+ onChange: (v) => s.set({ showReactComponents: v })
654
+ }
655
+ )
656
+ ] }),
657
+ /* @__PURE__ */ jsxs("div", { className: "settings-row-compact settings-row-stacked", children: [
658
+ /* @__PURE__ */ jsx(RowLabel, { hint: "Color used for the floating annotation pins on the page.", children: "Marker Color" }),
659
+ /* @__PURE__ */ jsxs("div", { className: "settings-color-presets", role: "radiogroup", "aria-label": "Marker color", children: [
660
+ MARKER_PRESETS.map((color) => /* @__PURE__ */ jsx(
661
+ "button",
662
+ {
663
+ type: "button",
664
+ role: "radio",
665
+ "aria-checked": s.markerColor.toLowerCase() === color.toLowerCase(),
666
+ className: `settings-color-preset${s.markerColor.toLowerCase() === color.toLowerCase() ? " is-active" : ""}`,
667
+ style: { "--preset": color },
668
+ onClick: () => s.set({ markerColor: color }),
669
+ title: color,
670
+ "aria-label": `Use color ${color}`
671
+ },
672
+ color
673
+ )),
674
+ /* @__PURE__ */ jsxs("label", { className: "settings-color-preset is-custom", title: "Custom color", children: [
675
+ /* @__PURE__ */ jsx(
676
+ "input",
677
+ {
678
+ type: "color",
679
+ value: s.markerColor,
680
+ onChange: (e) => s.set({ markerColor: e.target.value }),
681
+ "aria-label": "Pick a custom marker color"
682
+ }
683
+ ),
684
+ /* @__PURE__ */ jsx("span", { className: "settings-color-custom-glyph", children: "+" })
685
+ ] })
686
+ ] })
687
+ ] }),
688
+ /* @__PURE__ */ jsxs(
689
+ "button",
690
+ {
691
+ type: "button",
692
+ className: "settings-disclosure",
693
+ onClick: onOpenMcp,
694
+ "aria-haspopup": "menu",
695
+ children: [
696
+ /* @__PURE__ */ jsx("span", { className: "settings-disclosure-label", children: "Manage MCP & Webhooks" }),
697
+ /* @__PURE__ */ jsx(McpStatusDot, {}),
698
+ /* @__PURE__ */ jsx("span", { className: "settings-disclosure-chev", "aria-hidden": true, children: "\u203A" })
699
+ ]
700
+ }
701
+ )
702
+ ] })
597
703
  ] });
598
704
  }
599
- function McpSection() {
705
+ function McpSubView({ onBack, onClose }) {
600
706
  const s = useSettings();
601
707
  const status = useMcpStatus((m) => m.status);
602
708
  const lastError = useMcpStatus((m) => m.lastError);
@@ -609,64 +715,177 @@ function McpSection() {
609
715
  error: { label: "Error", modifier: "is-error" }
610
716
  };
611
717
  const meta = statusMeta[status];
612
- return /* @__PURE__ */ jsxs("div", { className: "settings-section", children: [
613
- /* @__PURE__ */ jsxs("div", { className: "settings-row", children: [
614
- /* @__PURE__ */ jsxs("div", { className: "settings-row-label", children: [
615
- "MCP sync",
616
- /* @__PURE__ */ jsx("span", { className: "settings-hint", children: "Push annotations to a running mcp-server so the agent reads them live" })
617
- ] }),
618
- /* @__PURE__ */ jsxs("label", { className: "clickly-toggle", children: [
619
- /* @__PURE__ */ jsx(
620
- "input",
621
- {
622
- type: "checkbox",
623
- checked: s.mcpEnabled,
624
- onChange: (e) => s.set({ mcpEnabled: e.target.checked })
625
- }
626
- ),
627
- /* @__PURE__ */ jsx("span", { className: "toggle-track" })
628
- ] })
629
- ] }),
630
- /* @__PURE__ */ jsxs("div", { className: "settings-row", style: { marginTop: 8 }, children: [
631
- /* @__PURE__ */ jsx("div", { className: "settings-row-label", style: { flex: "0 0 auto", minWidth: 72 }, children: "Endpoint" }),
632
- /* @__PURE__ */ jsx(
633
- "input",
634
- {
635
- type: "text",
636
- className: "settings-input",
637
- value: s.mcpEndpoint,
638
- onChange: (e) => s.set({ mcpEndpoint: e.target.value }),
639
- placeholder: "http://localhost:4747",
640
- disabled: !s.mcpEnabled,
641
- spellCheck: false
642
- }
643
- )
644
- ] }),
645
- /* @__PURE__ */ jsxs("div", { className: `settings-mcp-status ${meta.modifier}`, children: [
646
- /* @__PURE__ */ jsx("span", { className: "mcp-dot", "aria-hidden": true }),
647
- /* @__PURE__ */ jsx("span", { className: "mcp-status-label", children: meta.label }),
648
- status === "connected" && /* @__PURE__ */ jsxs("span", { className: "mcp-status-detail", children: [
649
- serverVersion ? `v${serverVersion}` : "",
650
- lastPingAt ? ` \xB7 pinged ${formatRelative(Date.now() - lastPingAt)} ago` : ""
651
- ] }),
652
- status === "error" && lastError ? /* @__PURE__ */ jsx("span", { className: "mcp-status-detail mcp-status-error", children: lastError }) : null
653
- ] }),
654
- status === "connected" && s.mcpSessionId ? /* @__PURE__ */ jsxs("div", { className: "settings-mcp-session", children: [
655
- /* @__PURE__ */ jsx("span", { className: "mcp-status-label", style: { opacity: 0.7 }, children: "Session" }),
656
- /* @__PURE__ */ jsx("code", { className: "mcp-session-id", children: s.mcpSessionId }),
718
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
719
+ /* @__PURE__ */ jsxs("div", { className: "settings-header settings-subview-header", children: [
657
720
  /* @__PURE__ */ jsx(
658
721
  "button",
659
722
  {
660
723
  type: "button",
661
- className: "mcp-session-reset",
662
- onClick: () => s.set({ mcpSessionId: null }),
663
- title: "Start a fresh session on the next ping",
664
- children: "Reset"
724
+ className: "settings-back",
725
+ onClick: onBack,
726
+ "aria-label": "Back to settings",
727
+ title: "Back (Esc)",
728
+ children: "\u2039"
665
729
  }
666
- )
667
- ] }) : null
730
+ ),
731
+ /* @__PURE__ */ jsx("span", { className: "settings-title", children: "Manage MCP & Webhooks" }),
732
+ /* @__PURE__ */ jsx("button", { className: "settings-close", onClick: onClose, "aria-label": "Close settings", children: /* @__PURE__ */ jsx(IconClose, {}) })
733
+ ] }),
734
+ /* @__PURE__ */ jsxs("div", { className: "settings-scroll", children: [
735
+ /* @__PURE__ */ jsxs("div", { className: "settings-subview-section", children: [
736
+ /* @__PURE__ */ jsxs("div", { className: "settings-subview-section-head", children: [
737
+ /* @__PURE__ */ jsx("span", { className: "settings-subview-section-title", children: "MCP Connection" }),
738
+ /* @__PURE__ */ jsx(InfoTip, { children: "Lets a running mcp-server bridge annotations to your AI agent. Requires the @useclickly/mcp-server CLI running on your machine." }),
739
+ /* @__PURE__ */ jsx(
740
+ Toggle,
741
+ {
742
+ checked: s.mcpEnabled,
743
+ onChange: (v) => s.set({ mcpEnabled: v })
744
+ }
745
+ )
746
+ ] }),
747
+ /* @__PURE__ */ jsxs("p", { className: "settings-subview-desc", children: [
748
+ "MCP connection allows agents to receive and act on annotations.",
749
+ " ",
750
+ /* @__PURE__ */ jsx(
751
+ "a",
752
+ {
753
+ href: "https://www.useclickly.com/docs/mcp",
754
+ target: "_blank",
755
+ rel: "noreferrer",
756
+ className: "settings-subview-link",
757
+ children: "Learn more"
758
+ }
759
+ )
760
+ ] }),
761
+ /* @__PURE__ */ jsxs("div", { className: "settings-subview-field", children: [
762
+ /* @__PURE__ */ jsx("label", { className: "settings-subview-field-label", htmlFor: "clickly-mcp-endpoint", children: "Endpoint" }),
763
+ /* @__PURE__ */ jsx(
764
+ "input",
765
+ {
766
+ id: "clickly-mcp-endpoint",
767
+ type: "text",
768
+ className: "settings-input",
769
+ value: s.mcpEndpoint,
770
+ onChange: (e) => s.set({ mcpEndpoint: e.target.value }),
771
+ placeholder: "http://localhost:4747",
772
+ disabled: !s.mcpEnabled,
773
+ spellCheck: false
774
+ }
775
+ )
776
+ ] }),
777
+ /* @__PURE__ */ jsxs("div", { className: `settings-mcp-status ${meta.modifier}`, children: [
778
+ /* @__PURE__ */ jsx("span", { className: "mcp-dot", "aria-hidden": true }),
779
+ /* @__PURE__ */ jsx("span", { className: "mcp-status-label", children: meta.label }),
780
+ status === "connected" && /* @__PURE__ */ jsxs("span", { className: "mcp-status-detail", children: [
781
+ serverVersion ? `v${serverVersion}` : "",
782
+ lastPingAt ? ` \xB7 pinged ${formatRelative(Date.now() - lastPingAt)} ago` : ""
783
+ ] }),
784
+ status === "error" && lastError ? /* @__PURE__ */ jsx("span", { className: "mcp-status-detail mcp-status-error", children: lastError }) : null
785
+ ] }),
786
+ status === "connected" && s.mcpSessionId ? /* @__PURE__ */ jsxs("div", { className: "settings-mcp-session", children: [
787
+ /* @__PURE__ */ jsx("span", { className: "mcp-status-label", style: { opacity: 0.7 }, children: "Session" }),
788
+ /* @__PURE__ */ jsx("code", { className: "mcp-session-id", children: s.mcpSessionId }),
789
+ /* @__PURE__ */ jsx(
790
+ "button",
791
+ {
792
+ type: "button",
793
+ className: "mcp-session-reset",
794
+ onClick: () => s.set({ mcpSessionId: null }),
795
+ title: "Start a fresh session on the next ping",
796
+ children: "Reset"
797
+ }
798
+ )
799
+ ] }) : null
800
+ ] }),
801
+ /* @__PURE__ */ jsxs("div", { className: "settings-subview-section is-dim", children: [
802
+ /* @__PURE__ */ jsxs("div", { className: "settings-subview-section-head", children: [
803
+ /* @__PURE__ */ jsx("span", { className: "settings-subview-section-title", children: "Webhooks" }),
804
+ /* @__PURE__ */ jsx(InfoTip, { children: "Forward annotation events to your own server. Not yet available \u2014 open an issue if you want this." }),
805
+ /* @__PURE__ */ jsx("span", { className: "settings-subview-badge", children: "Soon" })
806
+ ] }),
807
+ /* @__PURE__ */ jsx("p", { className: "settings-subview-desc", children: "The webhook URL will receive live annotation changes." }),
808
+ /* @__PURE__ */ jsx(
809
+ "textarea",
810
+ {
811
+ className: "settings-input settings-subview-textarea",
812
+ placeholder: "Webhook URL",
813
+ disabled: true,
814
+ rows: 2
815
+ }
816
+ )
817
+ ] })
818
+ ] })
819
+ ] });
820
+ }
821
+ function RowLabel({ children, hint }) {
822
+ return /* @__PURE__ */ jsxs("div", { className: "settings-row-label-compact", children: [
823
+ /* @__PURE__ */ jsx("span", { children }),
824
+ hint ? /* @__PURE__ */ jsx(InfoTip, { children: hint }) : null
825
+ ] });
826
+ }
827
+ function InfoTip({ children }) {
828
+ return /* @__PURE__ */ jsxs("span", { className: "settings-infotip", tabIndex: 0, "aria-label": "More info", children: [
829
+ "?",
830
+ /* @__PURE__ */ jsx("span", { className: "settings-infotip-bubble", role: "tooltip", children })
668
831
  ] });
669
832
  }
833
+ function Toggle({
834
+ checked,
835
+ onChange
836
+ }) {
837
+ return /* @__PURE__ */ jsxs("label", { className: "clickly-toggle", children: [
838
+ /* @__PURE__ */ jsx(
839
+ "input",
840
+ {
841
+ type: "checkbox",
842
+ checked,
843
+ onChange: (e) => onChange(e.target.checked)
844
+ }
845
+ ),
846
+ /* @__PURE__ */ jsx("span", { className: "toggle-track" })
847
+ ] });
848
+ }
849
+ function McpStatusDot() {
850
+ const s = useSettings();
851
+ const status = useMcpStatus((m) => m.status);
852
+ if (!s.mcpEnabled) return null;
853
+ return /* @__PURE__ */ jsx(
854
+ "span",
855
+ {
856
+ className: `settings-mcp-mini-dot is-${status}`,
857
+ "aria-label": `MCP sync: ${status}`
858
+ }
859
+ );
860
+ }
861
+ function labelForDetail(d) {
862
+ switch (d) {
863
+ case "compact":
864
+ return "Compact";
865
+ case "standard":
866
+ return "Standard";
867
+ case "detailed":
868
+ return "Detailed";
869
+ case "forensic":
870
+ return "Forensic";
871
+ }
872
+ }
873
+ var MARKER_PRESETS = [
874
+ "#6366f1",
875
+ // indigo
876
+ "#0ea5e9",
877
+ // sky
878
+ "#06b6d4",
879
+ // cyan (current default)
880
+ "#10b981",
881
+ // emerald
882
+ "#f59e0b",
883
+ // amber
884
+ "#f97316",
885
+ // orange
886
+ "#ef4444"
887
+ // red
888
+ ];
670
889
  function formatRelative(ms) {
671
890
  if (ms < 1500) return "just now";
672
891
  const s = Math.round(ms / 1e3);
@@ -1180,6 +1399,11 @@ function downloadViaAnchor(dataUrl, filename) {
1180
1399
  return false;
1181
1400
  }
1182
1401
  }
1402
+ var PANEL_W2 = 336;
1403
+ var PANEL_H2 = 420;
1404
+ var PANEL_GAP2 = 12;
1405
+ var VIEWPORT_PAD2 = 8;
1406
+ var TOOLBAR_H2 = 46;
1183
1407
  function AnnotationList({ anchor, width, onClose }) {
1184
1408
  const items = useAnnotationsList();
1185
1409
  const remove = useAnnotations((s) => s.remove);
@@ -1193,9 +1417,17 @@ function AnnotationList({ anchor, width, onClose }) {
1193
1417
  window.addEventListener("pointerdown", onDown, true);
1194
1418
  return () => window.removeEventListener("pointerdown", onDown, true);
1195
1419
  }, [onClose]);
1196
- const estimatedHeight = Math.min(window.innerHeight * 0.55, items.length * 88 + 48);
1197
- const top = Math.max(8, anchor.y - estimatedHeight - 12);
1198
- const left = Math.min(window.innerWidth - 336, Math.max(8, anchor.x));
1420
+ const vw = typeof window !== "undefined" ? window.innerWidth : 1024;
1421
+ const vh = typeof window !== "undefined" ? window.innerHeight : 768;
1422
+ const spaceAbove = anchor.y - VIEWPORT_PAD2;
1423
+ const spaceBelow = vh - anchor.y - TOOLBAR_H2 - VIEWPORT_PAD2;
1424
+ const placeAbove = spaceAbove >= PANEL_H2 + PANEL_GAP2 || spaceAbove >= spaceBelow;
1425
+ const top = placeAbove ? Math.max(VIEWPORT_PAD2, anchor.y - PANEL_H2 - PANEL_GAP2) : Math.min(vh - PANEL_H2 - VIEWPORT_PAD2, anchor.y + TOOLBAR_H2 + PANEL_GAP2);
1426
+ const desiredLeft = anchor.x + width - PANEL_W2;
1427
+ const left = Math.min(
1428
+ vw - PANEL_W2 - VIEWPORT_PAD2,
1429
+ Math.max(VIEWPORT_PAD2, desiredLeft)
1430
+ );
1199
1431
  const [exporting, setExporting] = useState(false);
1200
1432
  const update = useAnnotations((s) => s.update);
1201
1433
  const exportComposite = async () => {
@@ -1219,36 +1451,55 @@ function AnnotationList({ anchor, width, onClose }) {
1219
1451
  setExporting(false);
1220
1452
  }
1221
1453
  };
1222
- return /* @__PURE__ */ jsxs("div", { ref, className: "clickly-list", style: { left, top }, children: [
1223
- /* @__PURE__ */ jsxs("div", { className: "list-header", children: [
1224
- /* @__PURE__ */ jsx("span", { className: "list-title", children: "Annotations" }),
1225
- /* @__PURE__ */ jsx("span", { className: "list-count", children: items.length }),
1226
- items.length > 0 && /* @__PURE__ */ jsx(
1227
- "button",
1228
- {
1229
- className: "list-action-btn",
1230
- onClick: exportComposite,
1231
- title: "Export all screenshots as a numbered image strip",
1232
- disabled: exporting,
1233
- style: { marginLeft: "auto" },
1234
- children: /* @__PURE__ */ jsx(IconDownload, {})
1235
- }
1236
- )
1237
- ] }),
1238
- items.length === 0 ? /* @__PURE__ */ jsx("div", { className: "list-empty", children: "No annotations yet." }) : /* @__PURE__ */ jsx("div", { className: "list-items", children: items.map((a, i) => /* @__PURE__ */ jsx(
1239
- AnnotationCard,
1240
- {
1241
- annotation: a,
1242
- index: i + 1,
1243
- outputDetail,
1244
- onRemove: () => remove(a.id),
1245
- onCaptured: (data) => update(a.id, {
1246
- screenshot: { mimeType: "image/jpeg", dataUrl: data, width: 0, height: 0 }
1247
- })
1248
- },
1249
- a.id
1250
- )) })
1251
- ] });
1454
+ return /* @__PURE__ */ jsxs(
1455
+ "div",
1456
+ {
1457
+ ref,
1458
+ className: "clickly-list",
1459
+ style: { left, top, width: PANEL_W2, height: PANEL_H2 },
1460
+ children: [
1461
+ /* @__PURE__ */ jsxs("div", { className: "list-header", children: [
1462
+ /* @__PURE__ */ jsx("span", { className: "list-title", children: "Annotations" }),
1463
+ /* @__PURE__ */ jsx("span", { className: "list-count", children: items.length }),
1464
+ items.length > 0 && /* @__PURE__ */ jsx(
1465
+ "button",
1466
+ {
1467
+ className: "list-action-btn",
1468
+ onClick: exportComposite,
1469
+ title: "Export all screenshots as a numbered image strip",
1470
+ disabled: exporting,
1471
+ style: { marginLeft: "auto" },
1472
+ children: /* @__PURE__ */ jsx(IconDownload, {})
1473
+ }
1474
+ ),
1475
+ /* @__PURE__ */ jsx(
1476
+ "button",
1477
+ {
1478
+ className: "list-action-btn",
1479
+ onClick: onClose,
1480
+ "aria-label": "Close annotation list",
1481
+ title: "Close",
1482
+ style: items.length > 0 ? void 0 : { marginLeft: "auto" },
1483
+ children: /* @__PURE__ */ jsx(IconClose, {})
1484
+ }
1485
+ )
1486
+ ] }),
1487
+ /* @__PURE__ */ jsx("div", { className: "list-scroll", children: items.length === 0 ? /* @__PURE__ */ jsx("div", { className: "list-empty", children: "No annotations yet." }) : /* @__PURE__ */ jsx("div", { className: "list-items", children: items.map((a, i) => /* @__PURE__ */ jsx(
1488
+ AnnotationCard,
1489
+ {
1490
+ annotation: a,
1491
+ index: i + 1,
1492
+ outputDetail,
1493
+ onRemove: () => remove(a.id),
1494
+ onCaptured: (data) => update(a.id, {
1495
+ screenshot: { mimeType: "image/jpeg", dataUrl: data, width: 0, height: 0 }
1496
+ })
1497
+ },
1498
+ a.id
1499
+ )) }) })
1500
+ ]
1501
+ }
1502
+ );
1252
1503
  }
1253
1504
  function AnnotationCard({
1254
1505
  annotation: a,
@@ -4425,8 +4676,12 @@ var REACT_UI_CSS = `
4425
4676
  /* \u2500\u2500\u2500 Settings panel (redesigned) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
4426
4677
 
4427
4678
  .clickly-settings {
4679
+ /* Fixed-size frame. Width/height are also set inline by SettingsPopover
4680
+ so the values stay in one place (TS source of truth). The flex
4681
+ column splits: header (fixed) on top, scroll body underneath. */
4428
4682
  position: fixed;
4429
- width: 284px;
4683
+ display: flex;
4684
+ flex-direction: column;
4430
4685
  background: #fff;
4431
4686
  border-radius: 14px;
4432
4687
  box-shadow: 0 16px 48px rgba(2,6,23,0.20), 0 0 0 1px rgba(15,23,42,0.07);
@@ -4438,12 +4693,48 @@ var REACT_UI_CSS = `
4438
4693
  overflow: hidden;
4439
4694
  }
4440
4695
 
4696
+ /* Scrollable body \u2014 wraps everything below the header. Header stays
4697
+ pinned; this region grows to fill the remaining height of the
4698
+ fixed-size .clickly-settings frame and scrolls internally when
4699
+ content overflows.
4700
+
4701
+ The overscroll-behavior:contain rule prevents wheel/touch scroll from
4702
+ bubbling into the host page once you reach the top/bottom of the
4703
+ panel \u2014 without this, scrolling past the end would scroll the user's
4704
+ actual app underneath, which is jarring. */
4705
+ .settings-scroll {
4706
+ flex: 1 1 auto;
4707
+ min-height: 0; /* required for flex children to allow overflow */
4708
+ overflow-y: auto;
4709
+ overscroll-behavior: contain;
4710
+ /* Thin scrollbar in both light + dark themes */
4711
+ scrollbar-width: thin;
4712
+ scrollbar-color: rgba(15,23,42,0.20) transparent;
4713
+ }
4714
+ .settings-scroll::-webkit-scrollbar { width: 6px; }
4715
+ .settings-scroll::-webkit-scrollbar-thumb {
4716
+ background: rgba(15,23,42,0.18);
4717
+ border-radius: 3px;
4718
+ }
4719
+ .settings-scroll::-webkit-scrollbar-thumb:hover { background: rgba(15,23,42,0.32); }
4720
+ :host([data-clickly-theme="dark"]) .settings-scroll {
4721
+ scrollbar-color: rgba(255,255,255,0.18) transparent;
4722
+ }
4723
+ :host([data-clickly-theme="dark"]) .settings-scroll::-webkit-scrollbar-thumb {
4724
+ background: rgba(255,255,255,0.18);
4725
+ }
4726
+ :host([data-clickly-theme="dark"]) .settings-scroll::-webkit-scrollbar-thumb:hover {
4727
+ background: rgba(255,255,255,0.32);
4728
+ }
4729
+
4441
4730
  .settings-header {
4442
4731
  display: flex;
4443
4732
  align-items: center;
4444
4733
  justify-content: space-between;
4445
4734
  padding: 14px 14px 12px;
4446
4735
  border-bottom: 1px solid #f1f5f9;
4736
+ /* Header never shrinks \u2014 body scrolls instead */
4737
+ flex-shrink: 0;
4447
4738
  }
4448
4739
 
4449
4740
  .settings-title {
@@ -4538,6 +4829,415 @@ var REACT_UI_CSS = `
4538
4829
  min-width: 0;
4539
4830
  }
4540
4831
 
4832
+ /* \u2500\u2500\u2500 Dense Agentation-style rows \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
4833
+ * One row per setting, single-line label on the left, control on the
4834
+ * right. Hints move into (?) hover tooltips so the row stays compact.
4835
+ * Used by the refactored SettingsPopover.
4836
+ */
4837
+
4838
+ .settings-row-compact {
4839
+ display: flex;
4840
+ align-items: center;
4841
+ justify-content: space-between;
4842
+ gap: 12px;
4843
+ padding: 10px 14px;
4844
+ border-top: 1px solid #f1f5f9;
4845
+ }
4846
+ .settings-row-compact:first-of-type { border-top: none; }
4847
+ .settings-row-compact.settings-row-stacked {
4848
+ flex-direction: column;
4849
+ align-items: stretch;
4850
+ gap: 8px;
4851
+ }
4852
+
4853
+ .settings-row-label-compact {
4854
+ display: inline-flex;
4855
+ align-items: center;
4856
+ gap: 6px;
4857
+ font-size: 13px;
4858
+ font-weight: 500;
4859
+ color: #1e293b;
4860
+ min-width: 0;
4861
+ }
4862
+
4863
+ /* Info tooltip \u2014 small (?) circle + hover bubble */
4864
+ .settings-infotip {
4865
+ display: inline-grid;
4866
+ place-items: center;
4867
+ width: 14px;
4868
+ height: 14px;
4869
+ border-radius: 50%;
4870
+ background: #e2e8f0;
4871
+ color: #64748b;
4872
+ font: 600 9px/1 -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
4873
+ cursor: help;
4874
+ position: relative;
4875
+ flex-shrink: 0;
4876
+ user-select: none;
4877
+ outline: none;
4878
+ }
4879
+ .settings-infotip:hover,
4880
+ .settings-infotip:focus-visible {
4881
+ background: #cbd5e1;
4882
+ color: #1e293b;
4883
+ }
4884
+ .settings-infotip-bubble {
4885
+ position: absolute;
4886
+ bottom: calc(100% + 8px);
4887
+ left: 50%;
4888
+ transform: translateX(-50%);
4889
+ background: rgba(15, 23, 42, 0.98);
4890
+ color: #f1f5f9;
4891
+ font: 400 12px/1.4 -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
4892
+ padding: 6px 10px;
4893
+ border-radius: 6px;
4894
+ width: max-content;
4895
+ max-width: 220px;
4896
+ white-space: normal;
4897
+ text-align: left;
4898
+ pointer-events: none;
4899
+ opacity: 0;
4900
+ transition: opacity 100ms ease 150ms;
4901
+ box-shadow: 0 8px 24px rgba(0,0,0,0.30);
4902
+ z-index: 10;
4903
+ }
4904
+ .settings-infotip-bubble::after {
4905
+ content: "";
4906
+ position: absolute;
4907
+ top: 100%;
4908
+ left: 50%;
4909
+ transform: translateX(-50%);
4910
+ border: 4px solid transparent;
4911
+ border-top-color: rgba(15,23,42,0.98);
4912
+ }
4913
+ .settings-infotip:hover .settings-infotip-bubble,
4914
+ .settings-infotip:focus-visible .settings-infotip-bubble { opacity: 1; }
4915
+
4916
+ /* Inline-value selector (Output Detail) */
4917
+ .settings-inline-select { position: relative; }
4918
+ .settings-inline-select-trigger {
4919
+ display: inline-flex;
4920
+ align-items: center;
4921
+ gap: 6px;
4922
+ background: transparent;
4923
+ border: none;
4924
+ font: inherit;
4925
+ font-size: 13px;
4926
+ color: #475569;
4927
+ cursor: pointer;
4928
+ padding: 4px 6px;
4929
+ border-radius: 6px;
4930
+ transition: background 100ms, color 100ms;
4931
+ }
4932
+ .settings-inline-select-trigger:hover { background: #f1f5f9; color: #0f172a; }
4933
+ .settings-inline-chev {
4934
+ font-size: 14px;
4935
+ line-height: 1;
4936
+ color: #94a3b8;
4937
+ transform: rotate(0deg);
4938
+ }
4939
+ .settings-inline-menu {
4940
+ position: absolute;
4941
+ right: 0;
4942
+ top: calc(100% + 6px);
4943
+ min-width: 140px;
4944
+ background: #fff;
4945
+ border-radius: 8px;
4946
+ box-shadow: 0 12px 32px rgba(2,6,23,0.22), 0 0 0 1px rgba(15,23,42,0.06);
4947
+ padding: 4px;
4948
+ z-index: 20;
4949
+ display: flex;
4950
+ flex-direction: column;
4951
+ gap: 1px;
4952
+ animation: clickly-fade-in 80ms ease;
4953
+ }
4954
+ .settings-inline-menu-item {
4955
+ display: flex;
4956
+ align-items: center;
4957
+ justify-content: flex-start;
4958
+ background: transparent;
4959
+ border: none;
4960
+ font: inherit;
4961
+ font-size: 13px;
4962
+ color: #1e293b;
4963
+ cursor: pointer;
4964
+ padding: 7px 10px;
4965
+ border-radius: 6px;
4966
+ text-align: left;
4967
+ transition: background 100ms, color 100ms;
4968
+ }
4969
+ .settings-inline-menu-item:hover { background: #f1f5f9; }
4970
+ .settings-inline-menu-item.is-active {
4971
+ background: rgba(14,165,233,0.10);
4972
+ color: #0284c7;
4973
+ font-weight: 600;
4974
+ }
4975
+
4976
+ /* Marker color preset circles */
4977
+ .settings-color-presets {
4978
+ display: flex;
4979
+ align-items: center;
4980
+ gap: 8px;
4981
+ flex-wrap: wrap;
4982
+ }
4983
+ .settings-color-preset {
4984
+ position: relative;
4985
+ width: 20px;
4986
+ height: 20px;
4987
+ border-radius: 50%;
4988
+ background: var(--preset, #94a3b8);
4989
+ border: none;
4990
+ cursor: pointer;
4991
+ padding: 0;
4992
+ transition: transform 100ms ease;
4993
+ box-shadow:
4994
+ 0 0 0 1.5px rgba(15,23,42,0.06),
4995
+ 0 1px 2px rgba(15,23,42,0.10);
4996
+ }
4997
+ .settings-color-preset:hover { transform: scale(1.08); }
4998
+ .settings-color-preset.is-active {
4999
+ /* Ring around the active preset \u2014 matches Agentation's selected style. */
5000
+ box-shadow:
5001
+ 0 0 0 2px #fff,
5002
+ 0 0 0 4px currentColor,
5003
+ 0 0 0 5px rgba(15,23,42,0.10);
5004
+ color: var(--preset, #0ea5e9);
5005
+ }
5006
+ .settings-color-preset.is-custom {
5007
+ display: inline-grid;
5008
+ place-items: center;
5009
+ background: #f1f5f9;
5010
+ border: 1.5px dashed #cbd5e1;
5011
+ }
5012
+ .settings-color-preset.is-custom input[type="color"] {
5013
+ position: absolute;
5014
+ inset: 0;
5015
+ opacity: 0;
5016
+ cursor: pointer;
5017
+ border: none;
5018
+ background: transparent;
5019
+ padding: 0;
5020
+ }
5021
+ .settings-color-custom-glyph {
5022
+ font: 600 13px/1 -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
5023
+ color: #64748b;
5024
+ }
5025
+
5026
+ /* MCP disclosure (collapsible footer row) */
5027
+ .settings-disclosure {
5028
+ display: flex;
5029
+ align-items: center;
5030
+ gap: 8px;
5031
+ width: 100%;
5032
+ background: transparent;
5033
+ border: none;
5034
+ border-top: 1px solid #f1f5f9;
5035
+ padding: 12px 14px;
5036
+ font: inherit;
5037
+ font-size: 13px;
5038
+ font-weight: 500;
5039
+ color: #1e293b;
5040
+ cursor: pointer;
5041
+ text-align: left;
5042
+ transition: background 100ms ease;
5043
+ }
5044
+ .settings-disclosure:hover { background: #f8fafc; }
5045
+ .settings-disclosure-label { flex: 1; }
5046
+ .settings-disclosure-chev {
5047
+ color: #94a3b8;
5048
+ font-size: 16px;
5049
+ line-height: 1;
5050
+ transition: transform 160ms ease;
5051
+ }
5052
+ .settings-disclosure.is-open .settings-disclosure-chev {
5053
+ transform: rotate(90deg);
5054
+ }
5055
+
5056
+ .settings-mcp-mini-dot {
5057
+ width: 7px;
5058
+ height: 7px;
5059
+ border-radius: 50%;
5060
+ background: #94a3b8;
5061
+ flex-shrink: 0;
5062
+ }
5063
+ .settings-mcp-mini-dot.is-connecting {
5064
+ background: #f59e0b;
5065
+ animation: mcp-dot-pulse 1.2s ease-in-out infinite;
5066
+ }
5067
+ .settings-mcp-mini-dot.is-connected { background: #10b981; }
5068
+ .settings-mcp-mini-dot.is-error { background: #ef4444; }
5069
+
5070
+ .settings-mcp-panel {
5071
+ background: rgba(15, 23, 42, 0.02);
5072
+ border-top: 1px solid #f1f5f9;
5073
+ padding: 4px 0 10px;
5074
+ }
5075
+
5076
+ /* \u2500\u2500\u2500 Sub-view (MCP / Webhooks page swap) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
5077
+
5078
+ /* Header gets a back arrow on the left + title centered.
5079
+ Re-uses .settings-header but with a 3-column layout. */
5080
+ .settings-header.settings-subview-header {
5081
+ display: grid;
5082
+ grid-template-columns: 24px 1fr 24px;
5083
+ align-items: center;
5084
+ gap: 8px;
5085
+ }
5086
+ .settings-header.settings-subview-header .settings-title {
5087
+ text-align: center;
5088
+ }
5089
+
5090
+ .settings-back {
5091
+ display: grid;
5092
+ place-items: center;
5093
+ width: 24px;
5094
+ height: 24px;
5095
+ background: transparent;
5096
+ border: none;
5097
+ color: #64748b;
5098
+ font: 600 18px/1 -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
5099
+ cursor: pointer;
5100
+ padding: 0;
5101
+ border-radius: 6px;
5102
+ transition: background 100ms, color 100ms;
5103
+ }
5104
+ .settings-back:hover { background: #f1f5f9; color: #0f172a; }
5105
+
5106
+ .settings-subview-section {
5107
+ padding: 14px 16px 16px;
5108
+ border-top: 1px solid #f1f5f9;
5109
+ }
5110
+ .settings-subview-section:first-of-type { border-top: none; }
5111
+ .settings-subview-section.is-dim { opacity: 0.65; }
5112
+
5113
+ .settings-subview-section-head {
5114
+ display: flex;
5115
+ align-items: center;
5116
+ gap: 6px;
5117
+ margin-bottom: 6px;
5118
+ }
5119
+ .settings-subview-section-title {
5120
+ font-size: 14px;
5121
+ font-weight: 600;
5122
+ color: #0f172a;
5123
+ flex: 1;
5124
+ }
5125
+ .settings-subview-section-head .clickly-toggle { margin-left: auto; }
5126
+
5127
+ .settings-subview-desc {
5128
+ margin: 0 0 12px;
5129
+ font-size: 12.5px;
5130
+ line-height: 1.5;
5131
+ color: #64748b;
5132
+ }
5133
+
5134
+ .settings-subview-link {
5135
+ color: #0ea5e9;
5136
+ text-decoration: none;
5137
+ font-weight: 500;
5138
+ }
5139
+ .settings-subview-link:hover {
5140
+ color: #0284c7;
5141
+ text-decoration: underline;
5142
+ }
5143
+
5144
+ .settings-subview-field {
5145
+ display: flex;
5146
+ flex-direction: column;
5147
+ gap: 6px;
5148
+ margin-bottom: 10px;
5149
+ }
5150
+ .settings-subview-field-label {
5151
+ font-size: 12px;
5152
+ font-weight: 600;
5153
+ color: #475569;
5154
+ text-transform: uppercase;
5155
+ letter-spacing: 0.04em;
5156
+ }
5157
+
5158
+ .settings-subview-textarea {
5159
+ resize: none;
5160
+ min-height: 56px;
5161
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
5162
+ font-size: 12px;
5163
+ }
5164
+
5165
+ .settings-subview-badge {
5166
+ margin-left: auto;
5167
+ font-size: 10px;
5168
+ font-weight: 600;
5169
+ text-transform: uppercase;
5170
+ letter-spacing: 0.05em;
5171
+ padding: 2px 6px;
5172
+ border-radius: 4px;
5173
+ background: #fef3c7;
5174
+ color: #b45309;
5175
+ }
5176
+
5177
+ :host([data-clickly-theme="dark"]) .settings-back { color: #94a3b8; }
5178
+ :host([data-clickly-theme="dark"]) .settings-back:hover {
5179
+ background: rgba(255,255,255,0.06);
5180
+ color: #f8fafc;
5181
+ }
5182
+ :host([data-clickly-theme="dark"]) .settings-subview-section {
5183
+ border-top-color: rgba(255,255,255,0.06);
5184
+ }
5185
+ :host([data-clickly-theme="dark"]) .settings-subview-section-title { color: #f1f5f9; }
5186
+ :host([data-clickly-theme="dark"]) .settings-subview-desc { color: #94a3b8; }
5187
+ :host([data-clickly-theme="dark"]) .settings-subview-field-label { color: #94a3b8; }
5188
+ :host([data-clickly-theme="dark"]) .settings-subview-link { color: #38bdf8; }
5189
+ :host([data-clickly-theme="dark"]) .settings-subview-link:hover { color: #7dd3fc; }
5190
+ :host([data-clickly-theme="dark"]) .settings-subview-badge {
5191
+ background: rgba(245, 158, 11, 0.18);
5192
+ color: #fbbf24;
5193
+ }
5194
+
5195
+ /* Dark-mode overrides for the new compact rows */
5196
+ :host([data-clickly-theme="dark"]) .settings-row-compact { border-top-color: rgba(255,255,255,0.06); }
5197
+ :host([data-clickly-theme="dark"]) .settings-row-label-compact { color: #e2e8f0; }
5198
+ :host([data-clickly-theme="dark"]) .settings-infotip { background: rgba(255,255,255,0.10); color: #94a3b8; }
5199
+ :host([data-clickly-theme="dark"]) .settings-infotip:hover { background: rgba(255,255,255,0.18); color: #f8fafc; }
5200
+ :host([data-clickly-theme="dark"]) .settings-inline-select-trigger { color: #cbd5e1; }
5201
+ :host([data-clickly-theme="dark"]) .settings-inline-select-trigger:hover {
5202
+ background: rgba(255,255,255,0.06);
5203
+ color: #f8fafc;
5204
+ }
5205
+ :host([data-clickly-theme="dark"]) .settings-inline-menu {
5206
+ background: #1e293b;
5207
+ box-shadow: 0 12px 32px rgba(0,0,0,0.45), 0 0 0 1px rgba(255,255,255,0.08);
5208
+ }
5209
+ :host([data-clickly-theme="dark"]) .settings-inline-menu-item { color: #e2e8f0; }
5210
+ :host([data-clickly-theme="dark"]) .settings-inline-menu-item:hover { background: rgba(255,255,255,0.06); }
5211
+ :host([data-clickly-theme="dark"]) .settings-inline-menu-item.is-active {
5212
+ background: rgba(14,165,233,0.20);
5213
+ color: #7dd3fc;
5214
+ }
5215
+ :host([data-clickly-theme="dark"]) .settings-color-preset {
5216
+ box-shadow:
5217
+ 0 0 0 1.5px rgba(255,255,255,0.10),
5218
+ 0 1px 2px rgba(0,0,0,0.40);
5219
+ }
5220
+ :host([data-clickly-theme="dark"]) .settings-color-preset.is-active {
5221
+ box-shadow:
5222
+ 0 0 0 2px #0f172a,
5223
+ 0 0 0 4px currentColor,
5224
+ 0 0 0 5px rgba(255,255,255,0.10);
5225
+ }
5226
+ :host([data-clickly-theme="dark"]) .settings-color-preset.is-custom {
5227
+ background: rgba(255,255,255,0.05);
5228
+ border-color: rgba(255,255,255,0.20);
5229
+ }
5230
+ :host([data-clickly-theme="dark"]) .settings-color-custom-glyph { color: #cbd5e1; }
5231
+ :host([data-clickly-theme="dark"]) .settings-disclosure {
5232
+ color: #e2e8f0;
5233
+ border-top-color: rgba(255,255,255,0.06);
5234
+ }
5235
+ :host([data-clickly-theme="dark"]) .settings-disclosure:hover { background: rgba(255,255,255,0.04); }
5236
+ :host([data-clickly-theme="dark"]) .settings-mcp-panel {
5237
+ background: rgba(255,255,255,0.02);
5238
+ border-top-color: rgba(255,255,255,0.06);
5239
+ }
5240
+
4541
5241
  /* Toggle switch */
4542
5242
  .clickly-toggle {
4543
5243
  position: relative;
@@ -4767,9 +5467,11 @@ var REACT_UI_CSS = `
4767
5467
  /* \u2500\u2500\u2500 Annotation list \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
4768
5468
 
4769
5469
  .clickly-list {
5470
+ /* Fixed-size frame. Width/height set inline by AnnotationList so the
5471
+ values stay in one place (TS source of truth). Header is pinned;
5472
+ .list-scroll fills the remainder and scrolls internally \u2014 matches
5473
+ the SettingsPopover layout. */
4770
5474
  position: fixed;
4771
- width: 320px;
4772
- max-height: 55vh;
4773
5475
  background: #fff;
4774
5476
  border-radius: 14px;
4775
5477
  box-shadow: 0 16px 48px rgba(2,6,23,0.20), 0 0 0 1px rgba(15,23,42,0.07);
@@ -4783,6 +5485,34 @@ var REACT_UI_CSS = `
4783
5485
  overflow: hidden;
4784
5486
  }
4785
5487
 
5488
+ /* Scrollable body \u2014 mirrors .settings-scroll. min-height:0 is the
5489
+ classic fix that lets a flex child shrink small enough to overflow
5490
+ inside its parent (otherwise the auto min-content size pushes the
5491
+ layout taller than the frame, defeating overflow:auto). */
5492
+ .list-scroll {
5493
+ flex: 1 1 auto;
5494
+ min-height: 0;
5495
+ overflow-y: auto;
5496
+ overscroll-behavior: contain;
5497
+ scrollbar-width: thin;
5498
+ scrollbar-color: rgba(15,23,42,0.20) transparent;
5499
+ }
5500
+ .list-scroll::-webkit-scrollbar { width: 6px; }
5501
+ .list-scroll::-webkit-scrollbar-thumb {
5502
+ background: rgba(15,23,42,0.18);
5503
+ border-radius: 3px;
5504
+ }
5505
+ .list-scroll::-webkit-scrollbar-thumb:hover { background: rgba(15,23,42,0.32); }
5506
+ :host([data-clickly-theme="dark"]) .list-scroll {
5507
+ scrollbar-color: rgba(255,255,255,0.18) transparent;
5508
+ }
5509
+ :host([data-clickly-theme="dark"]) .list-scroll::-webkit-scrollbar-thumb {
5510
+ background: rgba(255,255,255,0.18);
5511
+ }
5512
+ :host([data-clickly-theme="dark"]) .list-scroll::-webkit-scrollbar-thumb:hover {
5513
+ background: rgba(255,255,255,0.32);
5514
+ }
5515
+
4786
5516
  .list-header {
4787
5517
  display: flex;
4788
5518
  align-items: center;
@@ -4814,9 +5544,10 @@ var REACT_UI_CSS = `
4814
5544
  }
4815
5545
 
4816
5546
  .list-items {
4817
- overflow-y: auto;
4818
- overscroll-behavior: contain;
4819
- flex: 1;
5547
+ /* Cards container \u2014 scrolling moved up to .list-scroll so the
5548
+ empty state shares the same scroll region. */
5549
+ display: flex;
5550
+ flex-direction: column;
4820
5551
  }
4821
5552
 
4822
5553
  .list-empty {