@loupekit/sdk 0.10.5 → 0.10.6-next.51

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
@@ -73,25 +73,74 @@ var STYLES = (
73
73
  .pin.free { background: var(--accent); border-radius: 50% 50% 2px 50%; }
74
74
  .pin.free.done { background: #10935a; }
75
75
  .pin.active { outline: 3px solid rgba(107,115,230,.45); }
76
+ /* The quick-action "Markers" toggle hides every pin without discarding it.
77
+ !important beats the inline display the pin positioner sets on each frame. */
78
+ .overlay.hide-pins .pin { display: none !important; }
76
79
 
77
- /* ------------------------------------------------------------------ launcher */
78
- /* The collapsed state: a small floating button that reopens the panel. */
79
- .launcher {
80
+ /* --------------------------------------------------------------- FAB cluster */
81
+ /* The collapsed state: a primary brand button (with the comment count) plus the
82
+ quick actions that expand out of it. Replaces the old single launcher. */
83
+ .fab-cluster {
80
84
  position: fixed; z-index: 2147483003; bottom: 20px; right: 20px;
81
- width: 46px; height: 46px; border-radius: 50%; padding: 0;
85
+ display: none; flex-direction: column; align-items: flex-end; gap: 10px;
86
+ }
87
+ .fab-cluster.show { display: flex; }
88
+
89
+ .fab-minis { display: none; flex-direction: column; align-items: flex-end; gap: 10px; }
90
+ .fab-cluster.expanded .fab-minis { display: flex; }
91
+ .fab-cluster.expanded .fab-minis .fab-mini { animation: loupe-fab-in 180ms cubic-bezier(.16, 1, .3, 1) both; }
92
+ .fab-cluster.expanded .fab-minis .fab-mini:nth-child(1) { animation-delay: 0ms; }
93
+ .fab-cluster.expanded .fab-minis .fab-mini:nth-child(2) { animation-delay: 30ms; }
94
+ .fab-cluster.expanded .fab-minis .fab-mini:nth-child(3) { animation-delay: 60ms; }
95
+ .fab-cluster.expanded .fab-minis .fab-mini:nth-child(4) { animation-delay: 90ms; }
96
+ @keyframes loupe-fab-in { from { opacity: 0; transform: translateY(8px) scale(.9); } to { opacity: 1; transform: none; } }
97
+
98
+ .fab-mini {
99
+ position: relative; width: 40px; height: 40px; border-radius: 50%; padding: 0;
100
+ border: 1px solid var(--line); background: var(--bg); color: var(--ink);
101
+ cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
102
+ box-shadow: var(--shadow);
103
+ }
104
+ .fab-mini:hover { border-color: var(--accent); color: var(--accent); }
105
+ .fab-mini.on { background: var(--accent); border-color: var(--accent); color: #fff; }
106
+ .fab-mini.on:hover { color: #fff; }
107
+ .fab-mini svg { display: block; width: 16px; height: 16px; }
108
+ /* The action's label, revealed on hover so the collapsed cluster stays calm. */
109
+ .fab-mini .fab-tip {
110
+ position: absolute; right: calc(100% + 8px); top: 50%; transform: translateY(-50%);
111
+ background: var(--bg); color: var(--ink); border: 1px solid var(--line);
112
+ border-radius: 7px; padding: 4px 8px; font-size: 11px; font-weight: 600; white-space: nowrap;
113
+ box-shadow: var(--shadow); opacity: 0; pointer-events: none; transition: opacity 120ms ease;
114
+ }
115
+ .fab-mini:hover .fab-tip { opacity: 1; }
116
+
117
+ .launcher {
118
+ position: relative; width: 46px; height: 46px; border-radius: 50%; padding: 0;
82
119
  border: 1px solid var(--line); background: var(--bg-2); color: var(--ink);
83
- cursor: pointer; display: none; align-items: center; justify-content: center;
120
+ cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
84
121
  box-shadow: var(--shadow);
85
122
  }
86
- .launcher.show { display: inline-flex; }
87
123
  .launcher:hover { border-color: var(--accent); }
88
124
  .launcher .logo { font-size: 24px; line-height: 1; color: var(--accent); }
125
+ /* Chevron pinned to the corner: up = actions are tucked away, down = they are out. */
126
+ .launcher .lchev {
127
+ position: absolute; right: -3px; bottom: -3px; width: 18px; height: 18px; border-radius: 50%;
128
+ background: var(--bg); border: 1px solid var(--line); color: var(--muted);
129
+ display: grid; place-items: center; transition: transform 160ms cubic-bezier(.16, 1, .3, 1);
130
+ }
131
+ .launcher .lchev svg { width: 11px; height: 11px; display: block; }
132
+ .fab-cluster.expanded .launcher .lchev { transform: rotate(180deg); }
89
133
  .launcher .lcount {
90
134
  position: absolute; top: -5px; right: -5px; background: var(--pin); color: #fff;
91
135
  font-size: 10px; font-weight: 700; line-height: 1; border-radius: 999px; padding: 3px 6px;
92
136
  border: 2px solid var(--bg);
93
137
  }
94
138
  .launcher .lcount:empty { display: none; }
139
+ @media (prefers-reduced-motion: reduce) {
140
+ .fab-cluster.expanded .fab-minis .fab-mini { animation: none; }
141
+ .launcher .lchev { transition: none; }
142
+ .fab-mini .fab-tip { transition: none; }
143
+ }
95
144
 
96
145
  /* --------------------------------------------------------------------- dock */
97
146
  /* The control panel. One container, four dock modes (left/right/bottom/float),
@@ -303,7 +352,7 @@ var STYLES = (
303
352
  .resize { display: none !important; }
304
353
  .tools button { flex: 1; justify-content: center; } /* full-width tap targets */
305
354
  .dock.mode-bottom .list { grid-template-columns: 1fr; }
306
- .launcher { bottom: 16px; right: 16px; }
355
+ .fab-cluster { bottom: 16px; right: 16px; }
307
356
  /* While a tool is active, shrink the sheet to just header + tools so most of the
308
357
  page stays visible and tappable; the list returns when the tool closes. */
309
358
  .dock.inspecting { height: auto !important; }
@@ -2428,7 +2477,7 @@ var HttpAdapter = class {
2428
2477
  };
2429
2478
 
2430
2479
  // src/app.ts
2431
- var SDK_VERSION = true ? "0.10.5" : "dev";
2480
+ var SDK_VERSION = true ? "0.10.6" : "dev";
2432
2481
  var DOCK_MODES = ["left", "right", "bottom", "float"];
2433
2482
  var RECORD_MAX_MS = 2e4;
2434
2483
  var MAX_FILES = 10;
@@ -2474,6 +2523,10 @@ function dataUrlToFile(dataUrl, name) {
2474
2523
  }
2475
2524
  var LoupeApp = class {
2476
2525
  constructor(cfg) {
2526
+ /** Whether the quick-action minis are expanded out of the primary FAB. */
2527
+ this.fabExpanded = false;
2528
+ /** Whether pin markers are hidden on the page (a quick-action toggle, persisted). */
2529
+ this.markersHidden = false;
2477
2530
  this.comments = [];
2478
2531
  /** Free-text filter over the list (title / body / author). */
2479
2532
  this.search = "";
@@ -2775,9 +2828,9 @@ var LoupeApp = class {
2775
2828
  this.composer = el("div", "composer");
2776
2829
  this.shadow.appendChild(this.composer);
2777
2830
  this.dock = this.buildDock();
2778
- this.launcher = this.buildLauncher();
2831
+ this.fabCluster = this.buildFabCluster();
2779
2832
  this.recBar = this.buildRecBar();
2780
- this.shadow.append(this.dock, this.launcher, this.recBar);
2833
+ this.shadow.append(this.dock, this.fabCluster, this.recBar);
2781
2834
  }
2782
2835
  /**
2783
2836
  * The dockable control panel:
@@ -2941,14 +2994,82 @@ var LoupeApp = class {
2941
2994
  bar.onclick = () => this.stopRecording?.();
2942
2995
  return bar;
2943
2996
  }
2944
- buildLauncher() {
2945
- const b = el("button", "launcher");
2946
- b.title = `Open ${this.cfg.label ?? "Loupe"}`;
2947
- b.setAttribute("aria-label", "Open Loupe");
2948
- b.innerHTML = `<span class="logo">\u25CE</span><span class="lcount"></span>`;
2949
- this.launchCount = b.querySelector(".lcount");
2950
- b.onclick = () => this.openDock();
2951
- return b;
2997
+ /**
2998
+ * The collapsed-state FAB cluster. The primary brand button carries the comment
2999
+ * count and toggles the quick actions out and back; the actions are the four
3000
+ * ways into the product without opening the full panel first:
3001
+ * pin a comment, drop a note, hide/show the markers already on the page, and
3002
+ * jump straight to the Claude/MCP setup.
3003
+ */
3004
+ buildFabCluster() {
3005
+ const cluster = el("div", "fab-cluster");
3006
+ const minis = el("div", "fab-minis");
3007
+ const mini = (role, icon, label, title) => {
3008
+ const b = el("button", "fab-mini");
3009
+ b.dataset.fab = role;
3010
+ b.title = title;
3011
+ b.setAttribute("aria-label", title);
3012
+ b.innerHTML = `${icon}<span class="fab-tip">${label}</span>`;
3013
+ return b;
3014
+ };
3015
+ const comment = mini("comment", I_COMMENT, "Pin comment", "Pin feedback on any element");
3016
+ comment.onclick = () => {
3017
+ this.collapseFab();
3018
+ this.openDock();
3019
+ this.setMode("inspect");
3020
+ };
3021
+ const note = mini("note", I_NOTE, "Note", "Drop a note anywhere on the page");
3022
+ note.onclick = () => {
3023
+ this.collapseFab();
3024
+ this.openDock();
3025
+ this.setMode("free");
3026
+ };
3027
+ const markers = mini("markers", I_EYE, "Markers", "Show or hide the markers on this page");
3028
+ markers.onclick = () => this.toggleMarkers();
3029
+ const connect = mini("connect", I_PLUG, "Connect Claude", "Set up the Claude/MCP connection");
3030
+ connect.onclick = () => {
3031
+ this.collapseFab();
3032
+ this.openDock();
3033
+ this.setTab("connect");
3034
+ };
3035
+ minis.append(comment, note, markers, connect);
3036
+ const primary = el("button", "launcher");
3037
+ primary.title = `Open ${this.cfg.label ?? "Loupe"}`;
3038
+ primary.setAttribute("aria-label", `${this.cfg.label ?? "Loupe"} \u2014 quick actions`);
3039
+ primary.setAttribute("aria-expanded", "false");
3040
+ primary.innerHTML = `<span class="logo">\u25CE</span>${I_FAB_CHEVRON}<span class="lcount"></span>`;
3041
+ this.fabBadge = primary.querySelector(".lcount");
3042
+ primary.onclick = () => this.toggleFab();
3043
+ cluster.append(minis, primary);
3044
+ this.fabMinis = minis;
3045
+ return cluster;
3046
+ }
3047
+ /** Expand/collapse the quick actions out of the primary FAB. */
3048
+ toggleFab() {
3049
+ this.fabExpanded = !this.fabExpanded;
3050
+ this.applyFab();
3051
+ }
3052
+ collapseFab() {
3053
+ if (!this.fabExpanded) return;
3054
+ this.fabExpanded = false;
3055
+ this.applyFab();
3056
+ }
3057
+ /** Hide/show every pin on the page without losing them (persisted). */
3058
+ toggleMarkers() {
3059
+ this.markersHidden = !this.markersHidden;
3060
+ this.saveState();
3061
+ this.applyFab();
3062
+ }
3063
+ /** Reflect cluster expansion + marker visibility into the DOM. */
3064
+ applyFab() {
3065
+ this.fabCluster.classList.toggle("expanded", this.fabExpanded);
3066
+ const primary = this.fabCluster.querySelector(".launcher");
3067
+ primary?.setAttribute("aria-expanded", String(this.fabExpanded));
3068
+ primary?.setAttribute("aria-label", this.fabExpanded ? "Close quick actions" : `Open ${this.cfg.label ?? "Loupe"} quick actions`);
3069
+ const markers = this.fabCluster.querySelector('[data-fab="markers"]');
3070
+ markers?.classList.toggle("on", this.markersHidden);
3071
+ markers?.setAttribute("aria-pressed", String(this.markersHidden));
3072
+ this.overlay.classList.toggle("hide-pins", this.markersHidden);
2952
3073
  }
2953
3074
  /** A tool button with a uniform icon + label layout. `icon` may be an SVG string. */
2954
3075
  toolBtn(icon, label, role) {
@@ -3370,7 +3491,7 @@ var LoupeApp = class {
3370
3491
  updateCount() {
3371
3492
  const n = this.comments.length;
3372
3493
  this.countEl.textContent = String(n);
3373
- this.launchCount.textContent = n ? String(n) : "";
3494
+ this.fabBadge.textContent = n ? String(n) : "";
3374
3495
  }
3375
3496
  /**
3376
3497
  * The current viewport rect for a region comment. Prefers the element-relative
@@ -3410,6 +3531,7 @@ var LoupeApp = class {
3410
3531
  // ---- dock: open / close / mode / theme ------------------------------------
3411
3532
  openDock() {
3412
3533
  this.open = true;
3534
+ this.fabExpanded = false;
3413
3535
  this.saveState();
3414
3536
  this.applyDockLayout();
3415
3537
  this.renderList();
@@ -3455,6 +3577,7 @@ var LoupeApp = class {
3455
3577
  if (p?.theme === "light" || p?.theme === "dark") this.theme = p.theme;
3456
3578
  if (p?.tab === "comments" || p?.tab === "connect") this.tab = p.tab;
3457
3579
  if (p?.float && typeof p.float.w === "number") this.floatRect = { ...this.floatRect, ...p.float };
3580
+ if (typeof p?.markersHidden === "boolean") this.markersHidden = p.markersHidden;
3458
3581
  } catch {
3459
3582
  }
3460
3583
  }
@@ -3465,7 +3588,8 @@ var LoupeApp = class {
3465
3588
  open: this.open,
3466
3589
  theme: this.theme,
3467
3590
  tab: this.tab,
3468
- float: this.floatRect
3591
+ float: this.floatRect,
3592
+ markersHidden: this.markersHidden
3469
3593
  }));
3470
3594
  } catch {
3471
3595
  }
@@ -3498,10 +3622,11 @@ var LoupeApp = class {
3498
3622
  for (const p of ["left", "top", "right", "bottom", "width", "height"]) d.style[p] = "";
3499
3623
  }
3500
3624
  d.querySelectorAll(".dctl [data-dock]").forEach((b) => b.classList.toggle("on", b.dataset.dock === this.dockMode));
3501
- this.launcher.classList.toggle("show", !this.open);
3625
+ this.fabCluster.classList.toggle("show", !this.open);
3502
3626
  const leftSide = this.dockMode === "left";
3503
- this.launcher.style.left = leftSide ? "20px" : "auto";
3504
- this.launcher.style.right = leftSide ? "auto" : "20px";
3627
+ this.fabCluster.style.left = leftSide ? "20px" : "auto";
3628
+ this.fabCluster.style.right = leftSide ? "auto" : "20px";
3629
+ this.applyFab();
3505
3630
  this.pushPage();
3506
3631
  }
3507
3632
  /**
@@ -3699,6 +3824,19 @@ var I_MOON = svg(
3699
3824
  var I_CLOSE = svg(
3700
3825
  `<path d="M4.2 4.2l7.6 7.6M11.8 4.2l-7.6 7.6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>`
3701
3826
  );
3827
+ var I_COMMENT = svg(
3828
+ `<path d="M2 3.1h12v7.4H6.5l-3.3 2.6v-2.6H2z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/><path d="M8 5.1v3.4M6.3 6.8h3.4" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>`
3829
+ );
3830
+ var I_NOTE = svg(
3831
+ `<path d="M2.6 2.6h10.8v7.2l-3.6 3.6H2.6z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/><path d="M13.4 9.8h-3.6v3.6" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/>`
3832
+ );
3833
+ var I_EYE = svg(
3834
+ `<path d="M1.4 8S3.9 4 8 4s6.6 4 6.6 4-2.5 4-6.6 4S1.4 8 1.4 8z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/><circle cx="8" cy="8" r="2" stroke="currentColor" stroke-width="1.3"/>`
3835
+ );
3836
+ var I_PLUG = svg(
3837
+ `<path d="M6 1.8v3.1M10 1.8v3.1" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><path d="M4.4 4.9h7.2v2.3a3.6 3.6 0 0 1-7.2 0z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/><path d="M8 10.8v3.4" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>`
3838
+ );
3839
+ var I_FAB_CHEVRON = `<span class="lchev">${svg(`<path d="M4.4 9.6 8 6l3.6 3.6" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/>`)}</span>`;
3702
3840
  var REGION_ICON = `<svg width="15" height="15" viewBox="0 0 15 15" fill="none" aria-hidden="true"><rect x="1.5" y="2.5" width="12" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4" stroke-dasharray="2.4 1.8"/></svg>`;
3703
3841
  var NOTE_ICON = `<svg width="15" height="15" viewBox="0 0 15 15" fill="none" aria-hidden="true"><path d="M2 2.5h11v7.5H6l-3 2.5v-2.5H2z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/></svg>`;
3704
3842
  var RECORD_ICON = `<svg width="15" height="15" viewBox="0 0 15 15" fill="none" aria-hidden="true"><rect x="1.5" y="2.5" width="12" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4" stroke-dasharray="2.4 1.8"/><circle cx="7.5" cy="7.5" r="2.4" fill="currentColor"/></svg>`;