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