@popmelt.com/core 0.1.0 → 0.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.mjs CHANGED
@@ -35,7 +35,7 @@ var __objRest = (source, exclude) => {
35
35
  // src/components/PopmeltProvider.tsx
36
36
  import {
37
37
  createContext,
38
- useCallback as useCallback8,
38
+ useCallback as useCallback9,
39
39
  useContext,
40
40
  useEffect as useEffect20,
41
41
  useMemo as useMemo4,
@@ -71,13 +71,20 @@ async function checkBridgeHealth(bridgeUrl = DEFAULT_BRIDGE_URL) {
71
71
  return null;
72
72
  }
73
73
  }
74
- async function sendToBridge(screenshotBlob, feedbackJson, bridgeUrl = DEFAULT_BRIDGE_URL, color, provider, model) {
74
+ async function sendToBridge(screenshotBlob, feedbackJson, bridgeUrl = DEFAULT_BRIDGE_URL, color, provider, model, pastedImages) {
75
75
  const formData = new FormData();
76
76
  formData.append("screenshot", screenshotBlob, "screenshot.png");
77
77
  formData.append("feedback", feedbackJson);
78
78
  if (color) formData.append("color", color);
79
79
  if (provider) formData.append("provider", provider);
80
80
  if (model) formData.append("model", model);
81
+ if (pastedImages) {
82
+ for (const [annotationId, blobs] of pastedImages) {
83
+ for (let i = 0; i < blobs.length; i++) {
84
+ formData.append(`image-${annotationId}-${i}`, blobs[i], `image-${annotationId}-${i}.png`);
85
+ }
86
+ }
87
+ }
81
88
  const res = await fetch(`${bridgeUrl}/send`, {
82
89
  method: "POST",
83
90
  body: formData
@@ -153,12 +160,94 @@ async function sendPlanExecution(screenshotBlob, planId, tasks, bridgeUrl = DEFA
153
160
  }
154
161
  return res.json();
155
162
  }
156
- async function sendReplyToBridge(threadId, reply, bridgeUrl = DEFAULT_BRIDGE_URL, color, provider, model) {
157
- const res = await fetch(`${bridgeUrl}/reply`, {
158
- method: "POST",
159
- headers: { "Content-Type": "application/json" },
160
- body: JSON.stringify({ threadId, reply, color, provider, model })
161
- });
163
+ async function sendReplyToBridge(threadId, reply, bridgeUrl = DEFAULT_BRIDGE_URL, color, provider, model, images) {
164
+ let res;
165
+ if (images && images.length > 0) {
166
+ const formData = new FormData();
167
+ const placeholder = new Blob([new Uint8Array([
168
+ 137,
169
+ 80,
170
+ 78,
171
+ 71,
172
+ 13,
173
+ 10,
174
+ 26,
175
+ 10,
176
+ 0,
177
+ 0,
178
+ 0,
179
+ 13,
180
+ 73,
181
+ 72,
182
+ 68,
183
+ 82,
184
+ 0,
185
+ 0,
186
+ 0,
187
+ 1,
188
+ 0,
189
+ 0,
190
+ 0,
191
+ 1,
192
+ 8,
193
+ 6,
194
+ 0,
195
+ 0,
196
+ 0,
197
+ 31,
198
+ 21,
199
+ 196,
200
+ 137,
201
+ 0,
202
+ 0,
203
+ 0,
204
+ 10,
205
+ 73,
206
+ 68,
207
+ 65,
208
+ 84,
209
+ 120,
210
+ 156,
211
+ 98,
212
+ 0,
213
+ 0,
214
+ 0,
215
+ 2,
216
+ 0,
217
+ 1,
218
+ 229,
219
+ 39,
220
+ 222,
221
+ 252,
222
+ 0,
223
+ 0,
224
+ 0,
225
+ 0,
226
+ 73,
227
+ 69,
228
+ 78,
229
+ 68,
230
+ 174,
231
+ 66,
232
+ 96,
233
+ 130
234
+ ])], { type: "image/png" });
235
+ formData.append("screenshot", placeholder, "screenshot.png");
236
+ formData.append("feedback", JSON.stringify({ threadId, reply, color, provider, model }));
237
+ for (let i = 0; i < images.length; i++) {
238
+ formData.append(`image-reply-${i}`, images[i], `reply-image-${i}.png`);
239
+ }
240
+ res = await fetch(`${bridgeUrl}/reply`, {
241
+ method: "POST",
242
+ body: formData
243
+ });
244
+ } else {
245
+ res = await fetch(`${bridgeUrl}/reply`, {
246
+ method: "POST",
247
+ headers: { "Content-Type": "application/json" },
248
+ body: JSON.stringify({ threadId, reply, color, provider, model })
249
+ });
250
+ }
162
251
  if (!res.ok) {
163
252
  const body = await res.text();
164
253
  throw new Error(`Bridge returned ${res.status}: ${body}`);
@@ -487,7 +576,7 @@ function handleSetAnnotating(state, payload) {
487
576
  return __spreadProps(__spreadValues({}, state), { isAnnotating: payload });
488
577
  }
489
578
  function handleSetTool(state, payload) {
490
- return __spreadProps(__spreadValues({}, state), { activeTool: payload });
579
+ return __spreadProps(__spreadValues({}, state), { activeTool: payload, inspectedElement: null });
491
580
  }
492
581
  function handleSetColor(state, payload) {
493
582
  return __spreadProps(__spreadValues({}, state), { activeColor: payload });
@@ -526,8 +615,9 @@ function handleFinishPath(state, payload) {
526
615
  });
527
616
  }
528
617
  function handleAddText(state, payload) {
529
- const textAnnotation = {
530
- id: generateId(),
618
+ var _a;
619
+ const textAnnotation = __spreadValues({
620
+ id: (_a = payload.id) != null ? _a : generateId(),
531
621
  type: "text",
532
622
  points: [payload.point],
533
623
  text: payload.text,
@@ -540,7 +630,7 @@ function handleAddText(state, payload) {
540
630
  linkedSelector: payload.linkedSelector,
541
631
  linkedAnchor: payload.linkedAnchor,
542
632
  elements: payload.elements
543
- };
633
+ }, payload.imageCount ? { imageCount: payload.imageCount } : {});
544
634
  const baseState = payload.groupId ? state : pushToUndoStack(state);
545
635
  return __spreadProps(__spreadValues({}, baseState), {
546
636
  annotations: [...state.annotations, textAnnotation]
@@ -550,7 +640,7 @@ function handleUpdateText(state, payload) {
550
640
  const stateWithUndo = pushToUndoStack(state);
551
641
  return __spreadProps(__spreadValues({}, stateWithUndo), {
552
642
  annotations: state.annotations.map(
553
- (a) => a.id === payload.id ? __spreadProps(__spreadValues({}, a), { text: payload.text }) : a
643
+ (a) => a.id === payload.id ? __spreadValues(__spreadProps(__spreadValues({}, a), { text: payload.text }), payload.imageCount != null ? { imageCount: payload.imageCount } : {}) : a
554
644
  )
555
645
  });
556
646
  }
@@ -884,17 +974,25 @@ function handleSetAnnotationStatus(state, payload) {
884
974
  }
885
975
  function handleSetAnnotationThread(state, payload) {
886
976
  const idSet = new Set(payload.ids);
977
+ const groupIds = /* @__PURE__ */ new Set();
978
+ for (const a of state.annotations) {
979
+ if (idSet.has(a.id) && a.groupId) groupIds.add(a.groupId);
980
+ }
887
981
  return __spreadProps(__spreadValues({}, state), {
888
982
  annotations: state.annotations.map(
889
- (a) => idSet.has(a.id) ? __spreadProps(__spreadValues({}, a), { threadId: payload.threadId }) : a
983
+ (a) => idSet.has(a.id) || a.groupId && groupIds.has(a.groupId) ? __spreadProps(__spreadValues({}, a), { threadId: payload.threadId }) : a
890
984
  )
891
985
  });
892
986
  }
893
987
  function handleSetAnnotationQuestion(state, payload) {
894
988
  const idSet = new Set(payload.ids);
989
+ const groupIds = /* @__PURE__ */ new Set();
990
+ for (const a of state.annotations) {
991
+ if (idSet.has(a.id) && a.groupId) groupIds.add(a.groupId);
992
+ }
895
993
  return __spreadProps(__spreadValues({}, state), {
896
994
  annotations: state.annotations.map(
897
- (a) => idSet.has(a.id) ? __spreadProps(__spreadValues({}, a), { status: "waiting_input", question: payload.question, threadId: payload.threadId }) : a
995
+ (a) => idSet.has(a.id) || a.groupId && groupIds.has(a.groupId) ? __spreadProps(__spreadValues({}, a), { status: "waiting_input", question: payload.question, threadId: payload.threadId }) : a
898
996
  )
899
997
  });
900
998
  }
@@ -1917,25 +2015,26 @@ function buildFeedbackData(annotations, styleModifications = [], inspectedElemen
1917
2015
  const text = group.find((a) => a.type === "text");
1918
2016
  if (shape) {
1919
2017
  const linkedSelector = shape.linkedSelector || (text == null ? void 0 : text.linkedSelector);
1920
- annotationDataList.push(__spreadProps(__spreadValues({
2018
+ const imageCount = (text == null ? void 0 : text.imageCount) || shape.imageCount;
2019
+ annotationDataList.push(__spreadValues(__spreadProps(__spreadValues({
1921
2020
  id: shape.id,
1922
2021
  type: shape.type,
1923
2022
  instruction: text == null ? void 0 : text.text
1924
2023
  }, linkedSelector ? { linkedSelector } : {}), {
1925
2024
  // Use stored elements (captured at creation time) or empty array
1926
2025
  elements: shape.elements || []
1927
- }));
2026
+ }), imageCount ? { imageCount } : {}));
1928
2027
  }
1929
2028
  }
1930
2029
  for (const annotation of standaloneAnnotations) {
1931
- annotationDataList.push(__spreadProps(__spreadValues({
2030
+ annotationDataList.push(__spreadValues(__spreadProps(__spreadValues({
1932
2031
  id: annotation.id,
1933
2032
  type: annotation.type,
1934
2033
  instruction: annotation.type === "text" ? annotation.text : void 0
1935
2034
  }, annotation.linkedSelector ? { linkedSelector: annotation.linkedSelector } : {}), {
1936
2035
  // Use stored elements (captured at creation time) or empty array
1937
2036
  elements: annotation.elements || []
1938
- }));
2037
+ }), annotation.imageCount ? { imageCount: annotation.imageCount } : {}));
1939
2038
  }
1940
2039
  return __spreadValues({
1941
2040
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -2076,7 +2175,8 @@ function drawAnnotationsToCanvas(ctx, annotations, scrollY, dpr) {
2076
2175
  const fontSize = annotation.fontSize || 16;
2077
2176
  ctx.font = `${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`;
2078
2177
  ctx.fillStyle = annotation.color;
2079
- const lines = annotation.text.split("\n");
2178
+ const displayText = annotation.imageCount && annotation.imageCount > 0 ? `[${annotation.imageCount} image${annotation.imageCount > 1 ? "s" : ""}] ${annotation.text}` : annotation.text;
2179
+ const lines = displayText.split("\n");
2080
2180
  const lineHeight = fontSize * 1.2;
2081
2181
  const padding = 4;
2082
2182
  let maxWidth = 0;
@@ -3706,6 +3806,16 @@ function PaddingHandles({ element, padding, accentColor, hoveredSide, draggingSi
3706
3806
  // src/components/StylePanel.tsx
3707
3807
  import { useCallback as useCallback4, useEffect as useEffect12, useMemo, useRef as useRef4, useState as useState9 } from "react";
3708
3808
  import { AlignCenter, AlignHorizontalSpaceAround, AlignJustify, AlignLeft, AlignRight, AlignVerticalSpaceAround, Baseline, Check, ChevronDown, Columns3, Grid2x2, MoveHorizontal, Plus, RectangleHorizontal, RotateCcw, Rows3, Shrink, UnfoldHorizontal, UnfoldVertical, WholeWord, X } from "lucide-react";
3809
+
3810
+ // src/styles/border.ts
3811
+ var DIAG_SVG = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxMicgaGVpZ2h0PScxMic+PGRlZnM+PHBhdHRlcm4gaWQ9J2QnIHdpZHRoPSc0JyBoZWlnaHQ9JzQnIHBhdHRlcm5Vbml0cz0ndXNlclNwYWNlT25Vc2UnPjxwYXRoIGQ9J00tMSwxIGwyLC0yIE0wLDQgbDQsLTQgTTMsNSBsMiwtMicgc3Ryb2tlPSdibGFjaycgc3Ryb2tlLXdpZHRoPScuNScvPjwvcGF0dGVybj48L2RlZnM+PHJlY3Qgd2lkdGg9JzEyJyBoZWlnaHQ9JzEyJyBmaWxsPSd1cmwoI2QpJy8+PC9zdmc+";
3812
+ var POPMELT_BORDER = {
3813
+ borderWidth: 3,
3814
+ borderStyle: "solid",
3815
+ borderImage: `url("${DIAG_SVG}") 4 / 1.9 / 0 round`
3816
+ };
3817
+
3818
+ // src/components/StylePanel.tsx
3709
3819
  import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
3710
3820
  function colorWithAlpha6(color, alpha) {
3711
3821
  const oklchMatch = color.match(/^oklch\(([^)]+)\)$/i);
@@ -4675,10 +4785,16 @@ function UnitInput({
4675
4785
  style: inputStyle,
4676
4786
  placeholder,
4677
4787
  showUnit = true,
4678
- unitStyle: customUnitStyle
4788
+ unitStyle: customUnitStyle,
4789
+ preferredUnit,
4790
+ onUnitCycle
4679
4791
  }) {
4680
4792
  const parsed = parseValue(value);
4681
- const effectiveUnit = isModified ? parsed.unit || getDefaultUnit(property) : getDefaultUnit(property);
4793
+ const propertyDefault = getDefaultUnit(property);
4794
+ const units = PROPERTY_UNITS[property];
4795
+ const canUsePreference = preferredUnit && units && units.includes(preferredUnit);
4796
+ const defaultUnit = canUsePreference ? preferredUnit : propertyDefault;
4797
+ const effectiveUnit = isModified ? parsed.unit || defaultUnit : defaultUnit;
4682
4798
  const displayNum = !isModified && parsed.unit && parsed.unit !== effectiveUnit ? convertFromPx(parsed.num, effectiveUnit) : parsed.num;
4683
4799
  const [isFocused, setIsFocused] = useState9(false);
4684
4800
  const [editText, setEditText] = useState9("");
@@ -4737,12 +4853,17 @@ function UnitInput({
4737
4853
  const displayValue = isFocused ? editText : isNumericValue ? String(displayNum) : "";
4738
4854
  const hasTypedUnit = isFocused && /\s*(rem|em|px|%)\s*$/i.test(editText);
4739
4855
  const shownUnit = hasTypedUnit ? "" : effectiveUnit;
4856
+ const isUnitClickable = onUnitCycle && (shownUnit === "rem" || shownUnit === "px");
4740
4857
  const defaultUnitSuffix = {
4741
4858
  fontSize: 10,
4742
4859
  fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace',
4743
4860
  color: "#999",
4744
4861
  pointerEvents: "none"
4745
4862
  };
4863
+ const clickableUnitStyle = __spreadProps(__spreadValues({}, customUnitStyle != null ? customUnitStyle : defaultUnitSuffix), {
4864
+ pointerEvents: "auto",
4865
+ cursor: "pointer"
4866
+ });
4746
4867
  return /* @__PURE__ */ jsxs6(Fragment4, { children: [
4747
4868
  /* @__PURE__ */ jsx7(
4748
4869
  "input",
@@ -4758,7 +4879,15 @@ function UnitInput({
4758
4879
  style: inputStyle
4759
4880
  }
4760
4881
  ),
4761
- showUnit && shownUnit && /* @__PURE__ */ jsx7("span", { style: customUnitStyle != null ? customUnitStyle : defaultUnitSuffix, children: shownUnit })
4882
+ showUnit && shownUnit && /* @__PURE__ */ jsx7(
4883
+ "span",
4884
+ {
4885
+ style: isUnitClickable ? clickableUnitStyle : customUnitStyle != null ? customUnitStyle : defaultUnitSuffix,
4886
+ onClick: isUnitClickable ? onUnitCycle : void 0,
4887
+ title: isUnitClickable ? "Click to switch units" : void 0,
4888
+ children: shownUnit
4889
+ }
4890
+ )
4762
4891
  ] });
4763
4892
  }
4764
4893
  function ColorInput({
@@ -5179,7 +5308,10 @@ function LayoutSection({
5179
5308
  activeDropdown,
5180
5309
  onDropdownChange,
5181
5310
  panelContentRef,
5182
- accentColor
5311
+ accentColor,
5312
+ onFieldHover,
5313
+ preferredUnit,
5314
+ onUnitCycle
5183
5315
  }) {
5184
5316
  const setActiveDropdown = onDropdownChange;
5185
5317
  const display = getValue("display");
@@ -5207,7 +5339,10 @@ function LayoutSection({
5207
5339
  const overflow = getValue("overflow");
5208
5340
  const gridCols = gridTemplateCols.split(/\s+/).filter((v) => v && v !== "none").length || 1;
5209
5341
  const gridRows = gridTemplateRows.split(/\s+/).filter((v) => v && v !== "none").length || 1;
5342
+ const [gridHovered, setGridHovered] = useState9(false);
5210
5343
  const hasActiveDropdown = activeDropdown !== null;
5344
+ const dimSiblings = hasActiveDropdown || gridHovered;
5345
+ const siblingOpacity = hasActiveDropdown ? 0.3 : gridHovered ? 0.65 : 1;
5211
5346
  const DisplayModeButton = ({ mode, icon, active }) => /* @__PURE__ */ jsx7(
5212
5347
  "button",
5213
5348
  {
@@ -5246,84 +5381,159 @@ function LayoutSection({
5246
5381
  const v = value || "0";
5247
5382
  handleChange("padding", `${v} ${current.right} ${v} ${current.left}`);
5248
5383
  };
5249
- const AlignmentGrid = () => {
5250
- const justify = getValue("justify-content");
5251
- const align = getValue("align-items");
5252
- const isColumn = flexDirection === "column" || flexDirection === "column-reverse";
5253
- const getJustifyIndex = (val) => {
5254
- if (val === "flex-start" || val === "start") return 0;
5255
- if (val === "center") return 1;
5256
- if (val === "flex-end" || val === "end") return 2;
5257
- return 0;
5258
- };
5259
- const getAlignIndex = (val) => {
5260
- if (val === "flex-start" || val === "start") return 0;
5261
- if (val === "center") return 1;
5262
- if (val === "flex-end" || val === "end") return 2;
5263
- return 0;
5264
- };
5265
- const justifyIdx = getJustifyIndex(justify);
5266
- const alignIdx = getAlignIndex(align);
5267
- const activeCol = isColumn ? alignIdx : justifyIdx;
5268
- const activeRow = isColumn ? justifyIdx : alignIdx;
5269
- const handleClick = (col, row) => {
5270
- const justifyValues = ["flex-start", "center", "flex-end"];
5271
- const alignValues = ["flex-start", "center", "flex-end"];
5272
- if (isColumn) {
5273
- handleChange("justify-content", justifyValues[row]);
5274
- handleChange("align-items", alignValues[col]);
5275
- } else {
5276
- handleChange("justify-content", justifyValues[col]);
5277
- handleChange("align-items", alignValues[row]);
5384
+ const [scrubDisplay, setScrubDisplay] = useState9({});
5385
+ const scrubPreview = useCallback4((key, domUpdate) => (v) => {
5386
+ domUpdate(v);
5387
+ setScrubDisplay((prev) => __spreadProps(__spreadValues({}, prev), { [key]: v }));
5388
+ }, []);
5389
+ const clearScrub = useCallback4((key) => {
5390
+ setScrubDisplay((prev) => {
5391
+ const next = __spreadValues({}, prev);
5392
+ delete next[key];
5393
+ return next;
5394
+ });
5395
+ }, []);
5396
+ const previewPaddingHorizontal = useCallback4((v) => {
5397
+ const current = parseSpacing(getValue("padding"));
5398
+ applyInlineStyle(element, "padding", `${current.top} ${v} ${current.bottom} ${v}`);
5399
+ }, [element, getValue]);
5400
+ const previewPaddingVertical = useCallback4((v) => {
5401
+ const current = parseSpacing(getValue("padding"));
5402
+ applyInlineStyle(element, "padding", `${v} ${current.right} ${v} ${current.left}`);
5403
+ }, [element, getValue]);
5404
+ const previewProperty = useCallback4((property) => (v) => {
5405
+ applyInlineStyle(element, property, v);
5406
+ }, [element]);
5407
+ const isColumn = flexDirection === "column" || flexDirection === "column-reverse";
5408
+ const getAxisIndex = (val) => {
5409
+ if (val === "center") return 1;
5410
+ if (val === "flex-end" || val === "end") return 2;
5411
+ return 0;
5412
+ };
5413
+ const justifyIdx = getAxisIndex(getValue("justify-content"));
5414
+ const alignIdx = getAxisIndex(getValue("align-items"));
5415
+ const gridActiveCol = isColumn ? alignIdx : justifyIdx;
5416
+ const gridActiveRow = isColumn ? justifyIdx : alignIdx;
5417
+ const gridRef = useRef4(null);
5418
+ const gridSwipeAccum = useRef4({ x: 0, y: 0 });
5419
+ const gridPosRef = useRef4({ col: gridActiveCol, row: gridActiveRow });
5420
+ gridPosRef.current = { col: gridActiveCol, row: gridActiveRow };
5421
+ const applyGridPosition = useCallback4((col, row) => {
5422
+ const vals = ["flex-start", "center", "flex-end"];
5423
+ if (isColumn) {
5424
+ handleChange("justify-content", vals[row]);
5425
+ handleChange("align-items", vals[col]);
5426
+ } else {
5427
+ handleChange("justify-content", vals[col]);
5428
+ handleChange("align-items", vals[row]);
5429
+ }
5430
+ }, [isColumn, handleChange]);
5431
+ const applyGridRef = useRef4(applyGridPosition);
5432
+ applyGridRef.current = applyGridPosition;
5433
+ useEffect12(() => {
5434
+ const THRESHOLD = 30;
5435
+ const onWheel = (e) => {
5436
+ const grid = gridRef.current;
5437
+ if (!grid || !grid.contains(e.target)) return;
5438
+ e.preventDefault();
5439
+ e.stopPropagation();
5440
+ gridSwipeAccum.current.x += e.deltaX;
5441
+ gridSwipeAccum.current.y += e.deltaY;
5442
+ let { col, row } = gridPosRef.current;
5443
+ let moved = false;
5444
+ if (Math.abs(gridSwipeAccum.current.x) >= THRESHOLD) {
5445
+ col = Math.max(0, Math.min(2, col + (gridSwipeAccum.current.x > 0 ? 1 : -1)));
5446
+ gridSwipeAccum.current.x = 0;
5447
+ gridSwipeAccum.current.y = 0;
5448
+ moved = true;
5449
+ }
5450
+ if (!moved && Math.abs(gridSwipeAccum.current.y) >= THRESHOLD) {
5451
+ row = Math.max(0, Math.min(2, row + (gridSwipeAccum.current.y > 0 ? 1 : -1)));
5452
+ gridSwipeAccum.current.x = 0;
5453
+ gridSwipeAccum.current.y = 0;
5454
+ moved = true;
5455
+ }
5456
+ if (moved && (col !== gridPosRef.current.col || row !== gridPosRef.current.row)) {
5457
+ applyGridRef.current(col, row);
5278
5458
  }
5279
5459
  };
5280
- return /* @__PURE__ */ jsx7("div", { style: {
5281
- width: 56,
5282
- height: 56,
5283
- backgroundColor: FIELD_BG,
5284
- borderRadius: 2,
5285
- display: "grid",
5286
- gridTemplateColumns: "repeat(3, 1fr)",
5287
- gridTemplateRows: "repeat(3, 1fr)",
5288
- padding: 6,
5289
- gap: 2
5290
- }, children: [0, 1, 2].map(
5291
- (row) => [0, 1, 2].map((col) => {
5292
- const isActive = col === activeCol && row === activeRow;
5293
- return /* @__PURE__ */ jsx7(
5294
- "button",
5295
- {
5296
- type: "button",
5297
- onClick: () => handleClick(col, row),
5298
- style: {
5299
- width: "100%",
5300
- height: "100%",
5301
- display: "flex",
5302
- alignItems: "center",
5303
- justifyContent: "center",
5304
- border: "none",
5305
- backgroundColor: "transparent",
5306
- cursor: "pointer",
5307
- padding: 0
5460
+ document.addEventListener("wheel", onWheel, { passive: false, capture: true });
5461
+ return () => document.removeEventListener("wheel", onWheel, { capture: true });
5462
+ }, []);
5463
+ const renderAlignmentGrid = () => /* @__PURE__ */ jsx7(
5464
+ "div",
5465
+ {
5466
+ ref: gridRef,
5467
+ onMouseEnter: () => {
5468
+ setGridHovered(true);
5469
+ if (panelContentRef.current) panelContentRef.current.style.overflowY = "hidden";
5470
+ },
5471
+ onMouseLeave: () => {
5472
+ setGridHovered(false);
5473
+ if (panelContentRef.current) panelContentRef.current.style.overflowY = "auto";
5474
+ },
5475
+ style: {
5476
+ width: 56,
5477
+ height: 56,
5478
+ backgroundColor: FIELD_BG,
5479
+ borderRadius: 2,
5480
+ display: "grid",
5481
+ gridTemplateColumns: "repeat(3, 1fr)",
5482
+ gridTemplateRows: "repeat(3, 1fr)",
5483
+ padding: 6,
5484
+ gap: 2,
5485
+ touchAction: "none"
5486
+ },
5487
+ children: [0, 1, 2].map(
5488
+ (row) => [0, 1, 2].map((col) => {
5489
+ const isActive = col === gridActiveCol && row === gridActiveRow;
5490
+ return /* @__PURE__ */ jsx7(
5491
+ "button",
5492
+ {
5493
+ type: "button",
5494
+ onClick: () => applyGridPosition(col, row),
5495
+ style: {
5496
+ width: "100%",
5497
+ height: "100%",
5498
+ display: "flex",
5499
+ alignItems: "center",
5500
+ justifyContent: "center",
5501
+ border: "none",
5502
+ backgroundColor: "transparent",
5503
+ cursor: "pointer",
5504
+ padding: 0
5505
+ },
5506
+ children: isActive ? /* @__PURE__ */ jsx7("svg", { width: "10", height: "10", viewBox: "0 0 10 10", fill: "none", style: { flexShrink: 0 }, children: col === 0 ? /* @__PURE__ */ jsxs6(Fragment4, { children: [
5507
+ /* @__PURE__ */ jsx7("rect", { x: "1", y: "1.5", width: "8", height: "1.2", rx: "0.5", fill: accentColor }),
5508
+ /* @__PURE__ */ jsx7("rect", { x: "1", y: "4.4", width: "5", height: "1.2", rx: "0.5", fill: accentColor }),
5509
+ /* @__PURE__ */ jsx7("rect", { x: "1", y: "7.3", width: "7", height: "1.2", rx: "0.5", fill: accentColor })
5510
+ ] }) : col === 1 ? /* @__PURE__ */ jsxs6(Fragment4, { children: [
5511
+ /* @__PURE__ */ jsx7("rect", { x: "1", y: "1.5", width: "8", height: "1.2", rx: "0.5", fill: accentColor }),
5512
+ /* @__PURE__ */ jsx7("rect", { x: "2.5", y: "4.4", width: "5", height: "1.2", rx: "0.5", fill: accentColor }),
5513
+ /* @__PURE__ */ jsx7("rect", { x: "1.5", y: "7.3", width: "7", height: "1.2", rx: "0.5", fill: accentColor })
5514
+ ] }) : /* @__PURE__ */ jsxs6(Fragment4, { children: [
5515
+ /* @__PURE__ */ jsx7("rect", { x: "1", y: "1.5", width: "8", height: "1.2", rx: "0.5", fill: accentColor }),
5516
+ /* @__PURE__ */ jsx7("rect", { x: "4", y: "4.4", width: "5", height: "1.2", rx: "0.5", fill: accentColor }),
5517
+ /* @__PURE__ */ jsx7("rect", { x: "2", y: "7.3", width: "7", height: "1.2", rx: "0.5", fill: accentColor })
5518
+ ] }) }) : /* @__PURE__ */ jsx7("div", { style: {
5519
+ width: 5,
5520
+ height: 5,
5521
+ borderRadius: "50%",
5522
+ backgroundColor: "#aaa"
5523
+ } })
5308
5524
  },
5309
- children: /* @__PURE__ */ jsx7("div", { style: {
5310
- width: 6,
5311
- height: 6,
5312
- borderRadius: "50%",
5313
- backgroundColor: isActive ? accentColor : "#d1d5db"
5314
- } })
5315
- },
5316
- `${row}-${col}`
5317
- );
5318
- })
5319
- ) });
5320
- };
5525
+ `${row}-${col}`
5526
+ );
5527
+ })
5528
+ )
5529
+ }
5530
+ );
5321
5531
  const gridModified = isModified("grid-template-columns") || isModified("grid-template-rows");
5322
5532
  const sectionTitle = isFlexOrGrid2 ? "Auto layout" : "Layout";
5323
5533
  return /* @__PURE__ */ jsxs6("div", { style: { borderBottom: "1px solid rgba(0,0,0,0.08)" }, children: [
5324
5534
  /* @__PURE__ */ jsx7("div", { style: sectionHeaderStyle, children: /* @__PURE__ */ jsx7("span", { children: sectionTitle }) }),
5325
5535
  /* @__PURE__ */ jsxs6("div", { style: { padding: "8px 12px" }, children: [
5326
- /* @__PURE__ */ jsxs6("div", { style: { display: "flex", gap: 2, marginBottom: 8, backgroundColor: FIELD_BG, borderRadius: 2, padding: 2, opacity: hasActiveDropdown ? 0.3 : 1, transition: "opacity 150ms ease" }, children: [
5536
+ /* @__PURE__ */ jsxs6("div", { style: { display: "flex", gap: 2, marginBottom: 8, backgroundColor: FIELD_BG, borderRadius: 2, padding: 2, opacity: siblingOpacity, transition: "opacity 150ms ease" }, children: [
5327
5537
  /* @__PURE__ */ jsx7(DisplayModeButton, { mode: "block", icon: /* @__PURE__ */ jsx7(RectangleHorizontal, { size: 16 }), active: displayMode === "block" }),
5328
5538
  /* @__PURE__ */ jsx7(DisplayModeButton, { mode: "flex-col", icon: /* @__PURE__ */ jsx7(Rows3, { size: 16 }), active: displayMode === "flex-col" }),
5329
5539
  /* @__PURE__ */ jsx7(DisplayModeButton, { mode: "flex-row", icon: /* @__PURE__ */ jsx7(Columns3, { size: 16 }), active: displayMode === "flex-row" }),
@@ -5397,9 +5607,9 @@ function LayoutSection({
5397
5607
  }
5398
5608
  )
5399
5609
  ] }),
5400
- isFlex && /* @__PURE__ */ jsxs6("div", { style: { display: "flex", gap: 8, marginBottom: 8, opacity: hasActiveDropdown ? 0.3 : 1, transition: "opacity 150ms ease" }, children: [
5401
- /* @__PURE__ */ jsx7(AlignmentGrid, {}),
5402
- /* @__PURE__ */ jsxs6("div", { style: { flex: 1 }, children: [
5610
+ isFlex && /* @__PURE__ */ jsxs6("div", { onMouseEnter: () => onFieldHover == null ? void 0 : onFieldHover("gap"), onMouseLeave: () => onFieldHover == null ? void 0 : onFieldHover("element"), style: { display: "flex", gap: 8, marginBottom: 8 }, children: [
5611
+ /* @__PURE__ */ jsx7("div", { style: { opacity: hasActiveDropdown ? 0.3 : 1, transition: "opacity 150ms ease" }, children: renderAlignmentGrid() }),
5612
+ /* @__PURE__ */ jsxs6("div", { style: { flex: 1, opacity: siblingOpacity, transition: "opacity 150ms ease" }, children: [
5403
5613
  /* @__PURE__ */ jsx7(
5404
5614
  "div",
5405
5615
  {
@@ -5418,17 +5628,19 @@ function LayoutSection({
5418
5628
  ),
5419
5629
  /* @__PURE__ */ jsx7(FieldWrapper, { dimmed: hasActiveDropdown, children: /* @__PURE__ */ jsxs6("div", { style: { display: "flex", alignItems: "center" }, children: [
5420
5630
  /* @__PURE__ */ jsx7(
5421
- "span",
5631
+ ScrubLabel,
5422
5632
  {
5423
- onClick: isModified("gap") ? () => onResetProperty("gap") : void 0,
5424
- title: isModified("gap") ? "Click to reset" : void 0,
5425
- style: {
5426
- color: isModified("gap") ? accentColor : "#999",
5427
- padding: "0 4px",
5428
- display: "flex",
5429
- alignItems: "center",
5430
- cursor: isModified("gap") ? "pointer" : "default"
5633
+ value: gap,
5634
+ onChange: (v) => {
5635
+ clearScrub("gap");
5636
+ handleChange("gap", v);
5431
5637
  },
5638
+ onPreview: scrubPreview("gap", previewProperty("gap")),
5639
+ onScrubEnd: () => clearScrub("gap"),
5640
+ onReset: () => onResetProperty("gap"),
5641
+ isModified: isModified("gap"),
5642
+ accentColor,
5643
+ defaultUnit: preferredUnit,
5432
5644
  children: flexDirection === "column" || flexDirection === "column-reverse" ? /* @__PURE__ */ jsx7(UnfoldVertical, { size: 12, strokeWidth: isModified("gap") ? 2.5 : 1.5 }) : /* @__PURE__ */ jsx7(UnfoldHorizontal, { size: 12, strokeWidth: isModified("gap") ? 2.5 : 1.5 })
5433
5645
  }
5434
5646
  ),
@@ -5436,17 +5648,19 @@ function LayoutSection({
5436
5648
  UnitInput,
5437
5649
  {
5438
5650
  property: "gap",
5439
- value: gap,
5651
+ value: scrubDisplay["gap"] || gap,
5440
5652
  onChange: (v) => handleChange("gap", v),
5441
- isModified: isModified("gap"),
5653
+ isModified: isModified("gap") || "gap" in scrubDisplay,
5442
5654
  style: __spreadProps(__spreadValues({}, compactInputStyle), { flex: 1, minWidth: 0 }),
5443
- unitStyle: { fontSize: 10, fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace', color: "#999", padding: "0 8px" }
5655
+ unitStyle: { fontSize: 10, fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace', color: "#999", padding: "0 8px" },
5656
+ preferredUnit,
5657
+ onUnitCycle
5444
5658
  }
5445
5659
  )
5446
5660
  ] }) })
5447
5661
  ] })
5448
5662
  ] }),
5449
- isGrid && /* @__PURE__ */ jsxs6("div", { style: { display: "flex", gap: 8, marginBottom: 8, opacity: hasActiveDropdown ? 0.3 : 1, transition: "opacity 150ms ease" }, children: [
5663
+ isGrid && /* @__PURE__ */ jsxs6("div", { onMouseEnter: () => onFieldHover == null ? void 0 : onFieldHover("gap"), onMouseLeave: () => onFieldHover == null ? void 0 : onFieldHover("element"), style: { display: "flex", gap: 8, marginBottom: 8, opacity: siblingOpacity, transition: "opacity 150ms ease" }, children: [
5450
5664
  /* @__PURE__ */ jsx7(
5451
5665
  GridDimensions,
5452
5666
  {
@@ -5461,17 +5675,19 @@ function LayoutSection({
5461
5675
  /* @__PURE__ */ jsxs6("div", { style: { flex: 1, display: "flex", flexDirection: "column", gap: 4 }, children: [
5462
5676
  /* @__PURE__ */ jsx7(FieldWrapper, { dimmed: hasActiveDropdown, children: /* @__PURE__ */ jsxs6("div", { style: { display: "flex", alignItems: "center" }, children: [
5463
5677
  /* @__PURE__ */ jsx7(
5464
- "span",
5678
+ ScrubLabel,
5465
5679
  {
5466
- onClick: isModified("column-gap") ? () => onResetProperty("column-gap") : void 0,
5467
- title: isModified("column-gap") ? "Click to reset" : void 0,
5468
- style: {
5469
- color: isModified("column-gap") ? accentColor : "#999",
5470
- padding: "0 4px",
5471
- display: "flex",
5472
- alignItems: "center",
5473
- cursor: isModified("column-gap") ? "pointer" : "default"
5680
+ value: columnGap || gap,
5681
+ onChange: (v) => {
5682
+ clearScrub("column-gap");
5683
+ handleChange("column-gap", v);
5474
5684
  },
5685
+ onPreview: scrubPreview("column-gap", previewProperty("column-gap")),
5686
+ onScrubEnd: () => clearScrub("column-gap"),
5687
+ onReset: () => onResetProperty("column-gap"),
5688
+ isModified: isModified("column-gap"),
5689
+ accentColor,
5690
+ defaultUnit: preferredUnit,
5475
5691
  children: /* @__PURE__ */ jsx7(UnfoldHorizontal, { size: 12, strokeWidth: isModified("column-gap") ? 2.5 : 1.5 })
5476
5692
  }
5477
5693
  ),
@@ -5479,28 +5695,32 @@ function LayoutSection({
5479
5695
  UnitInput,
5480
5696
  {
5481
5697
  property: "column-gap",
5482
- value: columnGap || gap,
5698
+ value: scrubDisplay["column-gap"] || columnGap || gap,
5483
5699
  onChange: (v) => handleChange("column-gap", v),
5484
- isModified: isModified("column-gap"),
5700
+ isModified: isModified("column-gap") || "column-gap" in scrubDisplay,
5485
5701
  placeholder: "col",
5486
5702
  style: __spreadProps(__spreadValues({}, compactInputStyle), { flex: 1, minWidth: 0 }),
5487
- unitStyle: { fontSize: 10, fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace', color: "#999", padding: "0 8px" }
5703
+ unitStyle: { fontSize: 10, fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace', color: "#999", padding: "0 8px" },
5704
+ preferredUnit,
5705
+ onUnitCycle
5488
5706
  }
5489
5707
  )
5490
5708
  ] }) }),
5491
5709
  /* @__PURE__ */ jsx7(FieldWrapper, { dimmed: hasActiveDropdown, children: /* @__PURE__ */ jsxs6("div", { style: { display: "flex", alignItems: "center" }, children: [
5492
5710
  /* @__PURE__ */ jsx7(
5493
- "span",
5711
+ ScrubLabel,
5494
5712
  {
5495
- onClick: isModified("row-gap") ? () => onResetProperty("row-gap") : void 0,
5496
- title: isModified("row-gap") ? "Click to reset" : void 0,
5497
- style: {
5498
- color: isModified("row-gap") ? accentColor : "#999",
5499
- padding: "0 4px",
5500
- display: "flex",
5501
- alignItems: "center",
5502
- cursor: isModified("row-gap") ? "pointer" : "default"
5713
+ value: rowGap || gap,
5714
+ onChange: (v) => {
5715
+ clearScrub("row-gap");
5716
+ handleChange("row-gap", v);
5503
5717
  },
5718
+ onPreview: scrubPreview("row-gap", previewProperty("row-gap")),
5719
+ onScrubEnd: () => clearScrub("row-gap"),
5720
+ onReset: () => onResetProperty("row-gap"),
5721
+ isModified: isModified("row-gap"),
5722
+ accentColor,
5723
+ defaultUnit: preferredUnit,
5504
5724
  children: /* @__PURE__ */ jsx7(UnfoldVertical, { size: 12, strokeWidth: isModified("row-gap") ? 2.5 : 1.5 })
5505
5725
  }
5506
5726
  ),
@@ -5508,31 +5728,36 @@ function LayoutSection({
5508
5728
  UnitInput,
5509
5729
  {
5510
5730
  property: "row-gap",
5511
- value: rowGap || gap,
5731
+ value: scrubDisplay["row-gap"] || rowGap || gap,
5512
5732
  onChange: (v) => handleChange("row-gap", v),
5513
- isModified: isModified("row-gap"),
5733
+ isModified: isModified("row-gap") || "row-gap" in scrubDisplay,
5514
5734
  placeholder: "row",
5515
5735
  style: __spreadProps(__spreadValues({}, compactInputStyle), { flex: 1, minWidth: 0 }),
5516
- unitStyle: { fontSize: 10, fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace', color: "#999", padding: "0 8px" }
5736
+ unitStyle: { fontSize: 10, fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace', color: "#999", padding: "0 8px" },
5737
+ preferredUnit,
5738
+ onUnitCycle
5517
5739
  }
5518
5740
  )
5519
5741
  ] }) })
5520
5742
  ] })
5521
5743
  ] }),
5522
- isFlexOrGrid2 && /* @__PURE__ */ jsxs6("div", { style: { display: "flex", gap: 4, marginBottom: 8, opacity: hasActiveDropdown ? 0.3 : 1, transition: "opacity 150ms ease" }, children: [
5744
+ isFlexOrGrid2 && /* @__PURE__ */ jsxs6("div", { onMouseEnter: () => onFieldHover == null ? void 0 : onFieldHover("padding"), onMouseLeave: () => onFieldHover == null ? void 0 : onFieldHover("element"), style: { display: "flex", gap: 4, marginBottom: 8, opacity: siblingOpacity, transition: "opacity 150ms ease" }, children: [
5523
5745
  /* @__PURE__ */ jsx7(FieldWrapper, { style: { flex: 1 }, dimmed: hasActiveDropdown, children: /* @__PURE__ */ jsxs6("div", { style: { display: "flex", alignItems: "center" }, children: [
5524
5746
  /* @__PURE__ */ jsx7(
5525
- "span",
5747
+ ScrubLabel,
5526
5748
  {
5527
- onClick: isModified("padding") ? () => onResetProperty("padding") : void 0,
5528
- title: isModified("padding") ? "Click to reset" : void 0,
5529
- style: {
5530
- color: isModified("padding") ? accentColor : "#999",
5531
- padding: "0 4px",
5532
- display: "flex",
5533
- alignItems: "center",
5534
- cursor: isModified("padding") ? "pointer" : "default"
5749
+ value: padding.left,
5750
+ onChange: (v) => {
5751
+ clearScrub("padding-h");
5752
+ handlePaddingHorizontal(v);
5535
5753
  },
5754
+ onPreview: scrubPreview("padding-h", previewPaddingHorizontal),
5755
+ onScrubEnd: () => clearScrub("padding-h"),
5756
+ onReset: () => onResetProperty("padding"),
5757
+ isModified: isModified("padding"),
5758
+ accentColor,
5759
+ defaultUnit: preferredUnit,
5760
+ snapSteps: PADDING_SNAP_STEPS,
5536
5761
  children: /* @__PURE__ */ jsx7(AlignHorizontalSpaceAround, { size: 12, strokeWidth: isModified("padding") ? 2.5 : 1.5 })
5537
5762
  }
5538
5763
  ),
@@ -5540,28 +5765,33 @@ function LayoutSection({
5540
5765
  UnitInput,
5541
5766
  {
5542
5767
  property: "padding",
5543
- value: padding.left,
5768
+ value: scrubDisplay["padding-h"] || padding.left,
5544
5769
  onChange: (v) => handlePaddingHorizontal(v),
5545
- isModified: isModified("padding"),
5770
+ isModified: isModified("padding") || "padding-h" in scrubDisplay,
5546
5771
  placeholder: "H pad",
5547
5772
  style: __spreadProps(__spreadValues({}, compactInputStyle), { flex: 1, minWidth: 0 }),
5548
- unitStyle: { fontSize: 10, fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace', color: "#999", padding: "0 8px" }
5773
+ unitStyle: { fontSize: 10, fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace', color: "#999", padding: "0 8px" },
5774
+ preferredUnit,
5775
+ onUnitCycle
5549
5776
  }
5550
5777
  )
5551
5778
  ] }) }),
5552
5779
  /* @__PURE__ */ jsx7(FieldWrapper, { style: { flex: 1 }, dimmed: hasActiveDropdown, children: /* @__PURE__ */ jsxs6("div", { style: { display: "flex", alignItems: "center" }, children: [
5553
5780
  /* @__PURE__ */ jsx7(
5554
- "span",
5781
+ ScrubLabel,
5555
5782
  {
5556
- onClick: isModified("padding") ? () => onResetProperty("padding") : void 0,
5557
- title: isModified("padding") ? "Click to reset" : void 0,
5558
- style: {
5559
- color: isModified("padding") ? accentColor : "#999",
5560
- padding: "0 4px",
5561
- display: "flex",
5562
- alignItems: "center",
5563
- cursor: isModified("padding") ? "pointer" : "default"
5783
+ value: padding.top,
5784
+ onChange: (v) => {
5785
+ clearScrub("padding-v");
5786
+ handlePaddingVertical(v);
5564
5787
  },
5788
+ onPreview: scrubPreview("padding-v", previewPaddingVertical),
5789
+ onScrubEnd: () => clearScrub("padding-v"),
5790
+ onReset: () => onResetProperty("padding"),
5791
+ isModified: isModified("padding"),
5792
+ accentColor,
5793
+ defaultUnit: preferredUnit,
5794
+ snapSteps: PADDING_SNAP_STEPS,
5565
5795
  children: /* @__PURE__ */ jsx7(AlignVerticalSpaceAround, { size: 12, strokeWidth: isModified("padding") ? 2.5 : 1.5 })
5566
5796
  }
5567
5797
  ),
@@ -5569,17 +5799,19 @@ function LayoutSection({
5569
5799
  UnitInput,
5570
5800
  {
5571
5801
  property: "padding",
5572
- value: padding.top,
5802
+ value: scrubDisplay["padding-v"] || padding.top,
5573
5803
  onChange: (v) => handlePaddingVertical(v),
5574
- isModified: isModified("padding"),
5804
+ isModified: isModified("padding") || "padding-v" in scrubDisplay,
5575
5805
  placeholder: "V pad",
5576
5806
  style: __spreadProps(__spreadValues({}, compactInputStyle), { flex: 1, minWidth: 0 }),
5577
- unitStyle: { fontSize: 10, fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace', color: "#999", padding: "0 8px" }
5807
+ unitStyle: { fontSize: 10, fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace', color: "#999", padding: "0 8px" },
5808
+ preferredUnit,
5809
+ onUnitCycle
5578
5810
  }
5579
5811
  )
5580
5812
  ] }) })
5581
5813
  ] }),
5582
- /* @__PURE__ */ jsxs6("label", { style: { display: "flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 11, fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace', color: "#64748b", opacity: hasActiveDropdown ? 0.3 : 1, transition: "opacity 150ms ease" }, children: [
5814
+ /* @__PURE__ */ jsxs6("label", { style: { display: "flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 11, fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace', color: "#64748b", opacity: siblingOpacity, transition: "opacity 150ms ease" }, children: [
5583
5815
  /* @__PURE__ */ jsx7(
5584
5816
  "input",
5585
5817
  {
@@ -5618,7 +5850,9 @@ function TypographySection({
5618
5850
  colorVariables,
5619
5851
  activeColorDropdown,
5620
5852
  onColorDropdownChange,
5621
- panelContentRef
5853
+ panelContentRef,
5854
+ preferredUnit,
5855
+ onUnitCycle
5622
5856
  }) {
5623
5857
  var _a;
5624
5858
  const fontFamily = getValue("font-family");
@@ -5717,9 +5951,10 @@ function TypographySection({
5717
5951
  transform: "translateY(-50%)",
5718
5952
  fontSize: 10,
5719
5953
  fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace',
5720
- color: "#999",
5721
- pointerEvents: "none"
5722
- }
5954
+ color: "#999"
5955
+ },
5956
+ preferredUnit,
5957
+ onUnitCycle
5723
5958
  }
5724
5959
  ) }) })
5725
5960
  ] }),
@@ -5820,6 +6055,130 @@ function parseValue(value) {
5820
6055
  }
5821
6056
  return { num: 0, unit: "" };
5822
6057
  }
6058
+ var PADDING_SNAP_STEPS = [0, 1, 2, 4, 8, 12, 16, 20, 24, 28, 32];
6059
+ function ScrubLabel({
6060
+ value,
6061
+ onChange,
6062
+ onPreview,
6063
+ onScrubEnd,
6064
+ onReset,
6065
+ isModified,
6066
+ accentColor,
6067
+ defaultUnit = "rem",
6068
+ snapSteps,
6069
+ color,
6070
+ style,
6071
+ children
6072
+ }) {
6073
+ const ref = useRef4(null);
6074
+ const scrub = useRef4(null);
6075
+ const resetRef = useRef4(onReset);
6076
+ const modifiedRef = useRef4(isModified);
6077
+ const lastShiftRef = useRef4(false);
6078
+ resetRef.current = onReset;
6079
+ modifiedRef.current = isModified;
6080
+ useEffect12(() => {
6081
+ const onMove = (e) => {
6082
+ const s = scrub.current;
6083
+ if (!s) return;
6084
+ s.hasMoved = true;
6085
+ lastShiftRef.current = e.shiftKey;
6086
+ const sens = s.unit === "rem" || s.unit === "em" ? 0.1 : 1;
6087
+ s.accum += e.movementX * sens;
6088
+ let newVal = Math.max(0, Math.round((s.startValue + s.accum) * 10) / 10);
6089
+ if (e.shiftKey && snapSteps) {
6090
+ const rootFs = s.unit === "rem" || s.unit === "em" ? parseFloat(getComputedStyle(document.documentElement).fontSize) || 16 : 1;
6091
+ const pxVal = s.unit === "rem" || s.unit === "em" ? newVal * rootFs : newVal;
6092
+ let snapped = snapSteps[snapSteps.length - 1];
6093
+ for (let i = 0; i < snapSteps.length - 1; i++) {
6094
+ const lo = snapSteps[i];
6095
+ const hi = snapSteps[i + 1];
6096
+ if (pxVal <= (lo + hi) / 2) {
6097
+ snapped = lo;
6098
+ break;
6099
+ }
6100
+ if (pxVal < hi) {
6101
+ snapped = hi;
6102
+ break;
6103
+ }
6104
+ }
6105
+ if (pxVal > snapSteps[snapSteps.length - 1]) {
6106
+ snapped = Math.round(pxVal / 8) * 8;
6107
+ }
6108
+ newVal = s.unit === "rem" || s.unit === "em" ? Math.round(snapped / rootFs * 1e3) / 1e3 : snapped;
6109
+ }
6110
+ onPreview == null ? void 0 : onPreview(`${newVal}${s.unit}`);
6111
+ };
6112
+ const onUp = () => {
6113
+ const s = scrub.current;
6114
+ if (!s) return;
6115
+ let finalVal = Math.max(0, Math.round((s.startValue + s.accum) * 10) / 10);
6116
+ if (lastShiftRef.current && snapSteps) {
6117
+ const rootFs = s.unit === "rem" || s.unit === "em" ? parseFloat(getComputedStyle(document.documentElement).fontSize) || 16 : 1;
6118
+ const pxVal = s.unit === "rem" || s.unit === "em" ? finalVal * rootFs : finalVal;
6119
+ let snapped = snapSteps[snapSteps.length - 1];
6120
+ for (let i = 0; i < snapSteps.length - 1; i++) {
6121
+ const lo = snapSteps[i];
6122
+ const hi = snapSteps[i + 1];
6123
+ if (pxVal <= (lo + hi) / 2) {
6124
+ snapped = lo;
6125
+ break;
6126
+ }
6127
+ if (pxVal < hi) {
6128
+ snapped = hi;
6129
+ break;
6130
+ }
6131
+ }
6132
+ if (pxVal > snapSteps[snapSteps.length - 1]) {
6133
+ snapped = Math.round(pxVal / 8) * 8;
6134
+ }
6135
+ finalVal = s.unit === "rem" || s.unit === "em" ? Math.round(snapped / rootFs * 1e3) / 1e3 : snapped;
6136
+ }
6137
+ const changed = s.hasMoved && finalVal !== s.startValue;
6138
+ scrub.current = null;
6139
+ document.exitPointerLock();
6140
+ if (changed) {
6141
+ onChange(`${finalVal}${s.unit}`);
6142
+ } else if (s.hasMoved) {
6143
+ onPreview == null ? void 0 : onPreview(`${s.startValue}${s.unit}`);
6144
+ } else if (modifiedRef.current && resetRef.current) {
6145
+ resetRef.current();
6146
+ }
6147
+ onScrubEnd == null ? void 0 : onScrubEnd();
6148
+ };
6149
+ document.addEventListener("mousemove", onMove);
6150
+ document.addEventListener("mouseup", onUp);
6151
+ return () => {
6152
+ document.removeEventListener("mousemove", onMove);
6153
+ document.removeEventListener("mouseup", onUp);
6154
+ };
6155
+ }, [onChange, onPreview, onScrubEnd]);
6156
+ const onMouseDown = useCallback4((e) => {
6157
+ var _a;
6158
+ if (e.button !== 0) return;
6159
+ e.preventDefault();
6160
+ const parsed = parseValue(value);
6161
+ const unit = parsed.unit && parsed.unit !== "px" ? parsed.unit : defaultUnit;
6162
+ scrub.current = { startValue: parsed.num, unit, accum: 0, hasMoved: false };
6163
+ (_a = ref.current) == null ? void 0 : _a.requestPointerLock();
6164
+ }, [value, defaultUnit]);
6165
+ return /* @__PURE__ */ jsx7(
6166
+ "span",
6167
+ {
6168
+ ref,
6169
+ onMouseDown,
6170
+ title: isModified ? "Click to reset \xB7 Drag to scrub" : "Drag to scrub",
6171
+ style: __spreadValues({
6172
+ color: isModified ? accentColor || "#3b82f6" : color || "#999",
6173
+ padding: "0 4px",
6174
+ display: "flex",
6175
+ alignItems: "center",
6176
+ cursor: "ew-resize"
6177
+ }, style),
6178
+ children
6179
+ }
6180
+ );
6181
+ }
5823
6182
  function StylePanel({
5824
6183
  element,
5825
6184
  elementInfo,
@@ -5827,19 +6186,31 @@ function StylePanel({
5827
6186
  styleModifications,
5828
6187
  dispatch,
5829
6188
  onClose,
6189
+ onHover,
5830
6190
  accentColor = "#3b82f6"
5831
6191
  }) {
5832
6192
  var _a, _b;
5833
6193
  const panelRef = useRef4(null);
5834
6194
  const panelContentRef = useRef4(null);
5835
- const [visible, setVisible] = useState9(false);
6195
+ const [visible, setVisible] = useState9(() => {
6196
+ try {
6197
+ return !!localStorage.getItem("devtools-panel-position");
6198
+ } catch (e) {
6199
+ return false;
6200
+ }
6201
+ });
5836
6202
  useEffect12(() => {
6203
+ if (visible) return;
5837
6204
  const id = setTimeout(() => setVisible(true), 50);
5838
6205
  return () => clearTimeout(id);
5839
- }, []);
6206
+ }, [visible]);
5840
6207
  const [activeDropdown, setActiveDropdown] = useState9(null);
5841
6208
  const [activeColorDropdown, setActiveColorDropdown] = useState9(null);
5842
6209
  const hasActiveDropdown = activeDropdown !== null || activeColorDropdown !== null;
6210
+ const [preferredUnit, setPreferredUnit] = useState9("rem");
6211
+ const cycleUnit = useCallback4(() => {
6212
+ setPreferredUnit((u) => u === "rem" ? "px" : "rem");
6213
+ }, []);
5843
6214
  const originalValuesRef = useRef4(/* @__PURE__ */ new Map());
5844
6215
  const [rawCss, setRawCss] = useState9("");
5845
6216
  const colorVariables = useMemo(() => getColorVariables(), []);
@@ -5856,36 +6227,121 @@ function StylePanel({
5856
6227
  }, [onClose]);
5857
6228
  const positionRef = useRef4({ top: 0, left: 0, maxHeight: 400 });
5858
6229
  const [, forceUpdate] = useState9(0);
6230
+ const PANEL_POS_KEY = "devtools-panel-position";
6231
+ const dragOffset = useRef4({ x: 0, y: 0 });
6232
+ const dragState = useRef4(null);
6233
+ const hasSavedPosition = useRef4(false);
6234
+ useEffect12(() => {
6235
+ try {
6236
+ const stored = localStorage.getItem(PANEL_POS_KEY);
6237
+ if (stored) {
6238
+ const pos = JSON.parse(stored);
6239
+ if (typeof pos.top === "number" && typeof pos.left === "number") {
6240
+ hasSavedPosition.current = true;
6241
+ positionRef.current = __spreadProps(__spreadValues({}, positionRef.current), { top: pos.top, left: pos.left });
6242
+ }
6243
+ }
6244
+ } catch (e) {
6245
+ }
6246
+ }, []);
6247
+ useEffect12(() => {
6248
+ const onMouseMove = (e) => {
6249
+ const ds = dragState.current;
6250
+ if (!ds) return;
6251
+ const rawX = ds.startOffsetX + (e.clientX - ds.startX);
6252
+ const rawY = ds.startOffsetY + (e.clientY - ds.startY);
6253
+ const panelWidth = 280;
6254
+ const edgePad = 16;
6255
+ const clampedLeft = Math.max(edgePad, Math.min(window.innerWidth - panelWidth - edgePad, positionRef.current.left + rawX));
6256
+ const clampedTop = Math.max(edgePad, positionRef.current.top + rawY);
6257
+ dragOffset.current = {
6258
+ x: clampedLeft - positionRef.current.left,
6259
+ y: clampedTop - positionRef.current.top
6260
+ };
6261
+ const panel = panelRef.current;
6262
+ const wrapper = panel == null ? void 0 : panel.parentElement;
6263
+ if (!wrapper) return;
6264
+ wrapper.style.top = `${clampedTop}px`;
6265
+ wrapper.style.left = `${clampedLeft}px`;
6266
+ const toolbar = document.getElementById("devtools-toolbar");
6267
+ const toolbarRect = toolbar == null ? void 0 : toolbar.getBoundingClientRect();
6268
+ let bottomLimit = window.innerHeight - 16;
6269
+ if (toolbarRect && clampedLeft + panelWidth > toolbarRect.left) {
6270
+ bottomLimit = toolbarRect.top - 8;
6271
+ }
6272
+ const visibleTop = Math.max(0, clampedTop);
6273
+ const maxHeight = Math.max(200, bottomLimit - visibleTop);
6274
+ if (panel) panel.style.maxHeight = `${maxHeight}px`;
6275
+ };
6276
+ const onMouseUp = () => {
6277
+ if (!dragState.current) return;
6278
+ const finalTop = positionRef.current.top + dragOffset.current.y;
6279
+ const finalLeft = positionRef.current.left + dragOffset.current.x;
6280
+ positionRef.current = __spreadProps(__spreadValues({}, positionRef.current), { top: finalTop, left: finalLeft });
6281
+ dragOffset.current = { x: 0, y: 0 };
6282
+ hasSavedPosition.current = true;
6283
+ try {
6284
+ localStorage.setItem(PANEL_POS_KEY, JSON.stringify({ top: finalTop, left: finalLeft }));
6285
+ } catch (e) {
6286
+ }
6287
+ dragState.current = null;
6288
+ };
6289
+ window.addEventListener("mousemove", onMouseMove);
6290
+ window.addEventListener("mouseup", onMouseUp);
6291
+ return () => {
6292
+ window.removeEventListener("mousemove", onMouseMove);
6293
+ window.removeEventListener("mouseup", onMouseUp);
6294
+ };
6295
+ }, []);
6296
+ const handleHeaderMouseDown = useCallback4((e) => {
6297
+ if (e.button !== 0 || e.target.closest("button")) return;
6298
+ e.preventDefault();
6299
+ dragState.current = {
6300
+ startX: e.clientX,
6301
+ startY: e.clientY,
6302
+ startOffsetX: dragOffset.current.x,
6303
+ startOffsetY: dragOffset.current.y
6304
+ };
6305
+ }, []);
5859
6306
  useEffect12(() => {
6307
+ dragOffset.current = { x: 0, y: 0 };
5860
6308
  const updatePosition = (isScrollEvent = false) => {
5861
6309
  const panel = panelRef.current;
5862
- const rect = element.getBoundingClientRect();
5863
6310
  const panelWidth = 280;
5864
- const padding = 16;
5865
- const toolbarWidth = 300;
5866
- const toolbarHeight = 50;
5867
- const toolbarRight = padding;
5868
- const toolbarBottom = padding;
5869
- let left = rect.right + padding;
5870
- let top = rect.top;
5871
- if (left + panelWidth > window.innerWidth - padding) {
5872
- left = rect.left - panelWidth - padding;
5873
- }
5874
- if (left < padding) {
5875
- left = Math.max(padding, (window.innerWidth - panelWidth) / 2);
5876
- }
5877
- const panelRight = left + panelWidth;
5878
- const toolbarLeft = window.innerWidth - toolbarRight - toolbarWidth;
5879
- const overlapsToolbar = panelRight > toolbarLeft;
5880
- const bottomLimit = overlapsToolbar ? window.innerHeight - toolbarBottom - toolbarHeight - padding : window.innerHeight - padding;
6311
+ const gap = 8;
6312
+ let top;
6313
+ let left;
6314
+ if (hasSavedPosition.current) {
6315
+ top = positionRef.current.top;
6316
+ left = positionRef.current.left;
6317
+ } else {
6318
+ const rect = element.getBoundingClientRect();
6319
+ left = rect.right + gap;
6320
+ top = rect.top;
6321
+ if (left + panelWidth > window.innerWidth - gap) {
6322
+ left = rect.left - panelWidth - gap;
6323
+ }
6324
+ if (left < gap) {
6325
+ left = Math.max(gap, (window.innerWidth - panelWidth) / 2);
6326
+ }
6327
+ }
6328
+ const toolbar = document.getElementById("devtools-toolbar");
6329
+ const toolbarRect = toolbar == null ? void 0 : toolbar.getBoundingClientRect();
6330
+ let bottomLimit = window.innerHeight - 16;
6331
+ if (toolbarRect) {
6332
+ const panelRight = left + panelWidth;
6333
+ if (panelRight > toolbarRect.left) {
6334
+ bottomLimit = toolbarRect.top - gap;
6335
+ }
6336
+ }
5881
6337
  const visibleTop = Math.max(0, top);
5882
6338
  const maxHeight = Math.max(200, bottomLimit - visibleTop);
5883
6339
  positionRef.current = { top, left, maxHeight };
5884
6340
  if (isScrollEvent && panel) {
5885
6341
  const wrapper = panel.parentElement;
5886
- if (wrapper) {
5887
- wrapper.style.top = `${top}px`;
5888
- wrapper.style.left = `${left}px`;
6342
+ if (wrapper && !hasSavedPosition.current) {
6343
+ wrapper.style.top = `${top + dragOffset.current.y}px`;
6344
+ wrapper.style.left = `${left + dragOffset.current.x}px`;
5889
6345
  }
5890
6346
  panel.style.maxHeight = `${maxHeight}px`;
5891
6347
  } else {
@@ -6079,9 +6535,10 @@ function StylePanel({
6079
6535
  transform: "translateY(-50%)",
6080
6536
  fontSize: 10,
6081
6537
  fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace',
6082
- color: "#999",
6083
- pointerEvents: "none"
6084
- }
6538
+ color: "#999"
6539
+ },
6540
+ preferredUnit,
6541
+ onUnitCycle: cycleUnit
6085
6542
  }
6086
6543
  ) });
6087
6544
  }
@@ -6150,7 +6607,7 @@ function StylePanel({
6150
6607
  };
6151
6608
  const modificationCount = (_a = currentModification == null ? void 0 : currentModification.changes.length) != null ? _a : 0;
6152
6609
  const isCaptured = (_b = currentModification == null ? void 0 : currentModification.captured) != null ? _b : false;
6153
- const panelStyle2 = {
6610
+ const panelStyle2 = __spreadProps(__spreadValues({
6154
6611
  position: "fixed",
6155
6612
  top: positionRef.current.top,
6156
6613
  left: positionRef.current.left,
@@ -6158,9 +6615,8 @@ function StylePanel({
6158
6615
  maxHeight: positionRef.current.maxHeight,
6159
6616
  backgroundColor: "rgba(255, 255, 255, 0.85)",
6160
6617
  backdropFilter: "blur(32px)",
6161
- WebkitBackdropFilter: "blur(32px)",
6162
- border: "1px solid rgba(0,0,0,0.1)",
6163
- borderRadius: 0,
6618
+ WebkitBackdropFilter: "blur(32px)"
6619
+ }, POPMELT_BORDER), {
6164
6620
  zIndex: 1e4,
6165
6621
  fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace',
6166
6622
  fontSize: 12,
@@ -6169,22 +6625,16 @@ function StylePanel({
6169
6625
  flexDirection: "column",
6170
6626
  opacity: visible ? 1 : 0,
6171
6627
  transition: "opacity 150ms ease"
6172
- };
6173
- const cornerDotStyle = {
6174
- position: "absolute",
6175
- width: 2,
6176
- height: 2,
6177
- backgroundColor: "rgba(0, 0, 0, 0.25)",
6178
- pointerEvents: "none",
6179
- zIndex: 1
6180
- };
6628
+ });
6181
6629
  const headerStyle = {
6182
6630
  display: "flex",
6183
6631
  alignItems: "center",
6184
6632
  justifyContent: "space-between",
6633
+ margin: "3px 3px 0",
6185
6634
  padding: "8px 7px 8px 12px",
6186
6635
  borderBottom: "1px solid rgba(0,0,0,0.1)",
6187
- backgroundColor: "rgba(248, 250, 252, 0.6)"
6636
+ backgroundColor: "#f8fafc",
6637
+ cursor: dragState.current ? "grabbing" : "grab"
6188
6638
  };
6189
6639
  const sectionHeaderStyle = {
6190
6640
  display: "flex",
@@ -6210,198 +6660,193 @@ function StylePanel({
6210
6660
  color: "#64748b",
6211
6661
  flexShrink: 0
6212
6662
  };
6213
- return /* @__PURE__ */ jsxs6("div", { "data-devtools": "panel-wrapper", style: {
6663
+ return /* @__PURE__ */ jsx7("div", { "data-devtools": "panel-wrapper", style: {
6214
6664
  position: "fixed",
6215
- top: positionRef.current.top,
6216
- left: positionRef.current.left,
6665
+ top: positionRef.current.top + dragOffset.current.y,
6666
+ left: positionRef.current.left + dragOffset.current.x,
6217
6667
  zIndex: 1e4,
6218
6668
  pointerEvents: "none"
6219
- }, children: [
6220
- /* @__PURE__ */ jsxs6("div", { ref: panelRef, "data-devtools": "panel", style: __spreadProps(__spreadValues({}, panelStyle2), { position: "relative", top: 0, left: 0, zIndex: 0, pointerEvents: "auto" }), children: [
6221
- /* @__PURE__ */ jsxs6("div", { style: headerStyle, children: [
6222
- /* @__PURE__ */ jsxs6("div", { style: { display: "flex", alignItems: "center", gap: 8, overflow: "hidden" }, children: [
6223
- /* @__PURE__ */ jsxs6("span", { style: {
6224
- fontWeight: 600,
6225
- fontSize: 11,
6226
- overflow: "hidden",
6227
- textOverflow: "ellipsis",
6228
- whiteSpace: "nowrap"
6229
- }, children: [
6230
- /* @__PURE__ */ jsx7("span", { style: { color: accentColor, fontSize: 13 }, children: "\u22B9" }),
6231
- " ",
6232
- elementInfo.tagName
6233
- ] }),
6234
- modificationCount > 0 && /* @__PURE__ */ jsx7("span", { style: {
6235
- backgroundColor: isCaptured ? "#999999" : accentColor,
6236
- color: "#fff",
6237
- fontSize: 9,
6238
- padding: "1px 4px",
6239
- borderRadius: 2
6240
- }, children: modificationCount })
6241
- ] }),
6242
- /* @__PURE__ */ jsxs6("div", { style: { display: "flex", gap: 4 }, children: [
6243
- modificationCount > 0 && /* @__PURE__ */ jsx7(
6244
- "button",
6245
- {
6246
- type: "button",
6247
- onClick: handleReset,
6248
- title: "Reset all changes",
6249
- style: {
6250
- display: "flex",
6251
- alignItems: "center",
6252
- justifyContent: "center",
6253
- width: 24,
6254
- height: 24,
6255
- border: "none",
6256
- background: "none",
6257
- cursor: "pointer",
6258
- color: "#64748b",
6259
- borderRadius: 2
6260
- },
6261
- children: /* @__PURE__ */ jsx7(RotateCcw, { size: 14 })
6669
+ }, children: /* @__PURE__ */ jsxs6("div", { ref: panelRef, "data-devtools": "panel", style: __spreadProps(__spreadValues({}, panelStyle2), { position: "relative", top: 0, left: 0, zIndex: 0, pointerEvents: "auto" }), onMouseEnter: () => onHover == null ? void 0 : onHover("element"), onMouseLeave: () => onHover == null ? void 0 : onHover(null), children: [
6670
+ /* @__PURE__ */ jsxs6("div", { style: headerStyle, onMouseDown: handleHeaderMouseDown, children: [
6671
+ /* @__PURE__ */ jsxs6("div", { style: { display: "flex", alignItems: "center", gap: 8, overflow: "hidden" }, children: [
6672
+ /* @__PURE__ */ jsx7("span", { style: {
6673
+ fontWeight: 600,
6674
+ fontSize: 11,
6675
+ overflow: "hidden",
6676
+ textOverflow: "ellipsis",
6677
+ whiteSpace: "nowrap"
6678
+ }, children: elementInfo.tagName }),
6679
+ modificationCount > 0 && /* @__PURE__ */ jsx7("span", { style: {
6680
+ backgroundColor: isCaptured ? "#999999" : accentColor,
6681
+ color: "#fff",
6682
+ fontSize: 9,
6683
+ padding: "1px 4px",
6684
+ borderRadius: 2
6685
+ }, children: modificationCount })
6686
+ ] }),
6687
+ /* @__PURE__ */ jsxs6("div", { style: { display: "flex", gap: 4 }, children: [
6688
+ modificationCount > 0 && /* @__PURE__ */ jsx7(
6689
+ "button",
6690
+ {
6691
+ type: "button",
6692
+ onClick: handleReset,
6693
+ title: "Reset all changes",
6694
+ style: {
6695
+ display: "flex",
6696
+ alignItems: "center",
6697
+ justifyContent: "center",
6698
+ width: 24,
6699
+ height: 24,
6700
+ border: "none",
6701
+ background: "none",
6702
+ cursor: "pointer",
6703
+ color: "#64748b",
6704
+ borderRadius: 2
6705
+ },
6706
+ children: /* @__PURE__ */ jsx7(RotateCcw, { size: 14 })
6707
+ }
6708
+ ),
6709
+ /* @__PURE__ */ jsx7(
6710
+ "button",
6711
+ {
6712
+ type: "button",
6713
+ onClick: onClose,
6714
+ title: "Close",
6715
+ style: {
6716
+ display: "flex",
6717
+ alignItems: "center",
6718
+ justifyContent: "center",
6719
+ width: 24,
6720
+ height: 24,
6721
+ border: "none",
6722
+ background: "none",
6723
+ cursor: "pointer",
6724
+ color: "#64748b",
6725
+ borderRadius: 2
6726
+ },
6727
+ children: /* @__PURE__ */ jsx7(X, { size: 14 })
6728
+ }
6729
+ )
6730
+ ] })
6731
+ ] }),
6732
+ /* @__PURE__ */ jsxs6("div", { ref: panelContentRef, style: { flex: 1, overflowY: "auto", margin: "0 3px 3px" }, children: [
6733
+ /* @__PURE__ */ jsx7("div", { style: { opacity: activeColorDropdown ? 0.3 : 1, transition: "opacity 150ms ease" }, children: /* @__PURE__ */ jsx7(
6734
+ LayoutSection,
6735
+ {
6736
+ element,
6737
+ getValue,
6738
+ getOriginalValue,
6739
+ handleChange,
6740
+ isModified,
6741
+ onResetProperty: handleResetProperty,
6742
+ isCollapsed: false,
6743
+ onToggle: () => {
6744
+ },
6745
+ sectionHeaderStyle,
6746
+ activeDropdown,
6747
+ onDropdownChange: setActiveDropdown,
6748
+ panelContentRef,
6749
+ accentColor,
6750
+ onFieldHover: onHover,
6751
+ preferredUnit,
6752
+ onUnitCycle: cycleUnit
6753
+ }
6754
+ ) }),
6755
+ /* @__PURE__ */ jsx7("div", { style: { opacity: activeDropdown ? 0.3 : 1, transition: "opacity 150ms ease" }, children: /* @__PURE__ */ jsx7(
6756
+ TypographySection,
6757
+ {
6758
+ element,
6759
+ getValue,
6760
+ handleChange,
6761
+ isModified,
6762
+ onResetProperty: handleResetProperty,
6763
+ isCollapsed: false,
6764
+ onToggle: () => {
6765
+ },
6766
+ sectionHeaderStyle,
6767
+ accentColor,
6768
+ colorVariables,
6769
+ activeColorDropdown,
6770
+ onColorDropdownChange: setActiveColorDropdown,
6771
+ panelContentRef,
6772
+ preferredUnit,
6773
+ onUnitCycle: cycleUnit
6774
+ }
6775
+ ) }),
6776
+ SECTIONS.map((section, index) => {
6777
+ const isLast = index === SECTIONS.length - 1;
6778
+ const sectionHasActiveColorDropdown = activeColorDropdown && section.properties.some((p) => p.property === activeColorDropdown);
6779
+ const shouldFadeSection = hasActiveDropdown && !sectionHasActiveColorDropdown;
6780
+ return /* @__PURE__ */ jsxs6("div", { style: { borderBottom: isLast ? "none" : "1px solid rgba(0,0,0,0.08)", opacity: shouldFadeSection ? 0.3 : 1, transition: "opacity 150ms ease" }, children: [
6781
+ /* @__PURE__ */ jsx7("div", { style: sectionHeaderStyle, children: /* @__PURE__ */ jsx7("span", { children: section.name }) }),
6782
+ /* @__PURE__ */ jsx7("div", { style: { padding: "4px 0" }, children: section.properties.map((prop) => {
6783
+ const modified = isModified(prop.property);
6784
+ const shouldFadeRow = sectionHasActiveColorDropdown && prop.property !== activeColorDropdown;
6785
+ return /* @__PURE__ */ jsxs6("div", { style: __spreadProps(__spreadValues({}, propertyRowStyle), { opacity: shouldFadeRow ? 0.3 : 1, transition: "opacity 150ms ease" }), children: [
6786
+ /* @__PURE__ */ jsx7(
6787
+ "span",
6788
+ {
6789
+ onClick: modified ? () => handleResetProperty(prop.property) : void 0,
6790
+ title: modified ? "Click to reset" : void 0,
6791
+ style: __spreadProps(__spreadValues({}, labelStyle), {
6792
+ color: modified ? accentColor : "#64748b",
6793
+ fontWeight: modified ? 600 : 400,
6794
+ cursor: modified ? "pointer" : "default"
6795
+ }),
6796
+ children: prop.label
6797
+ }
6798
+ ),
6799
+ /* @__PURE__ */ jsx7("div", { style: { flex: 1, minWidth: 0, overflow: "hidden" }, children: renderInput(prop) })
6800
+ ] }, prop.property);
6801
+ }) })
6802
+ ] }, section.name);
6803
+ }),
6804
+ /* @__PURE__ */ jsxs6("div", { style: { opacity: hasActiveDropdown ? 0.3 : 1, transition: "opacity 150ms ease" }, children: [
6805
+ /* @__PURE__ */ jsx7("div", { style: sectionHeaderStyle, children: /* @__PURE__ */ jsx7("span", { children: "Raw CSS" }) }),
6806
+ /* @__PURE__ */ jsxs6("div", { style: { padding: "8px 12px" }, children: [
6807
+ /* @__PURE__ */ jsx7(
6808
+ "textarea",
6809
+ {
6810
+ value: rawCss,
6811
+ onChange: (e) => setRawCss(e.target.value),
6812
+ placeholder: "property: value; ...",
6813
+ style: {
6814
+ width: "100%",
6815
+ height: 60,
6816
+ padding: 8,
6817
+ fontSize: 11,
6818
+ fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace',
6819
+ border: "1px solid rgba(0,0,0,0.1)",
6820
+ borderRadius: 2,
6821
+ resize: "vertical",
6822
+ outline: "none"
6823
+ }
6262
6824
  }
6263
6825
  ),
6264
- /* @__PURE__ */ jsx7(
6826
+ rawCss.trim() && /* @__PURE__ */ jsx7(
6265
6827
  "button",
6266
6828
  {
6267
6829
  type: "button",
6268
- onClick: onClose,
6269
- title: "Close",
6830
+ onClick: handleApplyRawCss,
6270
6831
  style: {
6271
- display: "flex",
6272
- alignItems: "center",
6273
- justifyContent: "center",
6274
- width: 24,
6275
- height: 24,
6832
+ marginTop: 4,
6833
+ padding: "4px 8px",
6834
+ width: "100%",
6835
+ fontSize: 11,
6276
6836
  border: "none",
6277
- background: "none",
6837
+ borderRadius: 2,
6838
+ backgroundColor: accentColor,
6839
+ color: "#fff",
6278
6840
  cursor: "pointer",
6279
- color: "#64748b",
6280
- borderRadius: 2
6841
+ opacity: rawCss.trim() ? 1 : 0.5
6281
6842
  },
6282
- children: /* @__PURE__ */ jsx7(X, { size: 14 })
6843
+ children: "Apply"
6283
6844
  }
6284
6845
  )
6285
6846
  ] })
6286
- ] }),
6287
- /* @__PURE__ */ jsxs6("div", { ref: panelContentRef, style: { flex: 1, overflowY: "auto" }, children: [
6288
- /* @__PURE__ */ jsx7("div", { style: { opacity: activeColorDropdown ? 0.3 : 1, transition: "opacity 150ms ease" }, children: /* @__PURE__ */ jsx7(
6289
- LayoutSection,
6290
- {
6291
- element,
6292
- getValue,
6293
- getOriginalValue,
6294
- handleChange,
6295
- isModified,
6296
- onResetProperty: handleResetProperty,
6297
- isCollapsed: false,
6298
- onToggle: () => {
6299
- },
6300
- sectionHeaderStyle,
6301
- activeDropdown,
6302
- onDropdownChange: setActiveDropdown,
6303
- panelContentRef,
6304
- accentColor
6305
- }
6306
- ) }),
6307
- /* @__PURE__ */ jsx7("div", { style: { opacity: activeDropdown ? 0.3 : 1, transition: "opacity 150ms ease" }, children: /* @__PURE__ */ jsx7(
6308
- TypographySection,
6309
- {
6310
- element,
6311
- getValue,
6312
- handleChange,
6313
- isModified,
6314
- onResetProperty: handleResetProperty,
6315
- isCollapsed: false,
6316
- onToggle: () => {
6317
- },
6318
- sectionHeaderStyle,
6319
- accentColor,
6320
- colorVariables,
6321
- activeColorDropdown,
6322
- onColorDropdownChange: setActiveColorDropdown,
6323
- panelContentRef
6324
- }
6325
- ) }),
6326
- SECTIONS.map((section, index) => {
6327
- const isLast = index === SECTIONS.length - 1;
6328
- const sectionHasActiveColorDropdown = activeColorDropdown && section.properties.some((p) => p.property === activeColorDropdown);
6329
- const shouldFadeSection = hasActiveDropdown && !sectionHasActiveColorDropdown;
6330
- return /* @__PURE__ */ jsxs6("div", { style: { borderBottom: isLast ? "none" : "1px solid rgba(0,0,0,0.08)", opacity: shouldFadeSection ? 0.3 : 1, transition: "opacity 150ms ease" }, children: [
6331
- /* @__PURE__ */ jsx7("div", { style: sectionHeaderStyle, children: /* @__PURE__ */ jsx7("span", { children: section.name }) }),
6332
- /* @__PURE__ */ jsx7("div", { style: { padding: "4px 0" }, children: section.properties.map((prop) => {
6333
- const modified = isModified(prop.property);
6334
- const shouldFadeRow = sectionHasActiveColorDropdown && prop.property !== activeColorDropdown;
6335
- return /* @__PURE__ */ jsxs6("div", { style: __spreadProps(__spreadValues({}, propertyRowStyle), { opacity: shouldFadeRow ? 0.3 : 1, transition: "opacity 150ms ease" }), children: [
6336
- /* @__PURE__ */ jsx7(
6337
- "span",
6338
- {
6339
- onClick: modified ? () => handleResetProperty(prop.property) : void 0,
6340
- title: modified ? "Click to reset" : void 0,
6341
- style: __spreadProps(__spreadValues({}, labelStyle), {
6342
- color: modified ? accentColor : "#64748b",
6343
- fontWeight: modified ? 600 : 400,
6344
- cursor: modified ? "pointer" : "default"
6345
- }),
6346
- children: prop.label
6347
- }
6348
- ),
6349
- /* @__PURE__ */ jsx7("div", { style: { flex: 1, minWidth: 0, overflow: "hidden" }, children: renderInput(prop) })
6350
- ] }, prop.property);
6351
- }) })
6352
- ] }, section.name);
6353
- }),
6354
- /* @__PURE__ */ jsxs6("div", { style: { opacity: hasActiveDropdown ? 0.3 : 1, transition: "opacity 150ms ease" }, children: [
6355
- /* @__PURE__ */ jsx7("div", { style: sectionHeaderStyle, children: /* @__PURE__ */ jsx7("span", { children: "Raw CSS" }) }),
6356
- /* @__PURE__ */ jsxs6("div", { style: { padding: "8px 12px" }, children: [
6357
- /* @__PURE__ */ jsx7(
6358
- "textarea",
6359
- {
6360
- value: rawCss,
6361
- onChange: (e) => setRawCss(e.target.value),
6362
- placeholder: "property: value; ...",
6363
- style: {
6364
- width: "100%",
6365
- height: 60,
6366
- padding: 8,
6367
- fontSize: 11,
6368
- fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace',
6369
- border: "1px solid rgba(0,0,0,0.1)",
6370
- borderRadius: 2,
6371
- resize: "vertical",
6372
- outline: "none"
6373
- }
6374
- }
6375
- ),
6376
- rawCss.trim() && /* @__PURE__ */ jsx7(
6377
- "button",
6378
- {
6379
- type: "button",
6380
- onClick: handleApplyRawCss,
6381
- style: {
6382
- marginTop: 4,
6383
- padding: "4px 8px",
6384
- width: "100%",
6385
- fontSize: 11,
6386
- border: "none",
6387
- borderRadius: 2,
6388
- backgroundColor: accentColor,
6389
- color: "#fff",
6390
- cursor: "pointer",
6391
- opacity: rawCss.trim() ? 1 : 0.5
6392
- },
6393
- children: "Apply"
6394
- }
6395
- )
6396
- ] })
6397
- ] })
6398
6847
  ] })
6399
- ] }),
6400
- /* @__PURE__ */ jsx7("div", { style: __spreadProps(__spreadValues({}, cornerDotStyle), { top: -1, left: -1 }) }),
6401
- /* @__PURE__ */ jsx7("div", { style: __spreadProps(__spreadValues({}, cornerDotStyle), { top: -1, right: -1 }) }),
6402
- /* @__PURE__ */ jsx7("div", { style: __spreadProps(__spreadValues({}, cornerDotStyle), { bottom: -1, left: -1 }) }),
6403
- /* @__PURE__ */ jsx7("div", { style: __spreadProps(__spreadValues({}, cornerDotStyle), { bottom: -1, right: -1 }) })
6404
- ] });
6848
+ ] })
6849
+ ] }) });
6405
6850
  }
6406
6851
 
6407
6852
  // src/components/SwipeHints.tsx
@@ -6672,7 +7117,7 @@ function TextHandles({ element, fontSize, lineHeight, accentColor, hoveredProper
6672
7117
  // src/components/AnnotationCanvas.tsx
6673
7118
  import { Fragment as Fragment6, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
6674
7119
  var HANDLE_SIZE = 8;
6675
- var PADDING_SNAP_STEPS = [0, 1, 2, 4, 8, 12, 16, 20, 24, 28, 32];
7120
+ var PADDING_SNAP_STEPS2 = [0, 1, 2, 4, 8, 12, 16, 20, 24, 28, 32];
6676
7121
  var BADGE_HEIGHT = 22;
6677
7122
  var ACTIVE_TEXT_STORAGE_KEY = "devtools-active-text";
6678
7123
  function calculateLinkedPosition(rect, anchor, stackOffset = 0) {
@@ -6680,7 +7125,7 @@ function calculateLinkedPosition(rect, anchor, stackOffset = 0) {
6680
7125
  const y = anchor === "top-left" ? rect.top + window.scrollY - BADGE_HEIGHT - stackOffset * BADGE_HEIGHT + PADDING : rect.bottom + window.scrollY + PADDING - 1 + stackOffset * BADGE_HEIGHT;
6681
7126
  return { x, y };
6682
7127
  }
6683
- function AnnotationCanvas({ state, dispatch, onScreenshot, inFlightAnnotationIds, inFlightStyleSelectors, inFlightSelectorColors, onReply, onViewThread, isThreadPanelOpen, activePlan }) {
7128
+ function AnnotationCanvas({ state, dispatch, onScreenshot, inFlightAnnotationIds, inFlightStyleSelectors, inFlightSelectorColors, onAttachImages, onReply, onViewThread, activePlan }) {
6684
7129
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
6685
7130
  const { canvasRef, redrawAll, resizeCanvas } = useCanvasDrawing();
6686
7131
  const [isDrawing, setIsDrawing] = useState12(false);
@@ -6706,6 +7151,7 @@ function AnnotationCanvas({ state, dispatch, onScreenshot, inFlightAnnotationIds
6706
7151
  const handDragRef = useRef5({ isDragging: false, side: null, startX: 0, startY: 0, original: { top: 0, right: 0, bottom: 0, left: 0 }, element: null, elementInfo: null, selector: null, durableSelector: null });
6707
7152
  const [handHoveredElement, setHandHoveredElement] = useState12(null);
6708
7153
  const [handHoveredSide, setHandHoveredSide] = useState12(null);
7154
+ const [stylePanelHint, setStylePanelHint] = useState12(null);
6709
7155
  const [handDragging, setHandDragging] = useState12(null);
6710
7156
  const handCursorRef = useRef5({ x: 0, y: 0 });
6711
7157
  const [handCursorPos, setHandCursorPos] = useState12({ x: 0, y: 0 });
@@ -7376,26 +7822,39 @@ function AnnotationCanvas({ state, dispatch, onScreenshot, inFlightAnnotationIds
7376
7822
  return () => window.removeEventListener("mousemove", handleMouseMove);
7377
7823
  }, [activeText, findTextAtPoint]);
7378
7824
  const commitActiveText = useCallback5(() => {
7825
+ var _a2;
7379
7826
  if (!activeText) return;
7380
- if (activeText.text.trim()) {
7827
+ const imageCount = ((_a2 = activeText.images) == null ? void 0 : _a2.length) || 0;
7828
+ if (activeText.text.trim() || imageCount > 0) {
7381
7829
  if (activeText.isNew) {
7830
+ const annotationId = generateId();
7382
7831
  dispatch({
7383
7832
  type: "ADD_TEXT",
7384
- payload: {
7833
+ payload: __spreadValues({
7385
7834
  point: activeText.point,
7386
- text: activeText.text,
7835
+ text: activeText.text || (imageCount > 0 ? `[${imageCount} image${imageCount > 1 ? "s" : ""}]` : ""),
7387
7836
  fontSize: activeText.fontSize,
7837
+ id: annotationId,
7388
7838
  groupId: activeText.groupId,
7389
7839
  linkedSelector: activeText.linkedSelector,
7390
7840
  linkedAnchor: activeText.linkedAnchor,
7391
7841
  elements: activeText.elements
7392
- }
7842
+ }, imageCount > 0 ? { imageCount } : {})
7393
7843
  });
7844
+ if (imageCount > 0 && activeText.images && onAttachImages) {
7845
+ onAttachImages(annotationId, activeText.images);
7846
+ }
7394
7847
  } else {
7395
7848
  dispatch({
7396
7849
  type: "UPDATE_TEXT",
7397
- payload: { id: activeText.id, text: activeText.text }
7850
+ payload: __spreadValues({
7851
+ id: activeText.id,
7852
+ text: activeText.text || (imageCount > 0 ? `[${imageCount} image${imageCount > 1 ? "s" : ""}]` : "")
7853
+ }, imageCount > 0 ? { imageCount } : {})
7398
7854
  });
7855
+ if (imageCount > 0 && activeText.images && onAttachImages) {
7856
+ onAttachImages(activeText.id, activeText.images);
7857
+ }
7399
7858
  }
7400
7859
  } else if (!activeText.isNew) {
7401
7860
  dispatch({
@@ -7404,11 +7863,11 @@ function AnnotationCanvas({ state, dispatch, onScreenshot, inFlightAnnotationIds
7404
7863
  });
7405
7864
  }
7406
7865
  setActiveText(null);
7407
- }, [activeText, dispatch]);
7866
+ }, [activeText, dispatch, onAttachImages]);
7408
7867
  const snapPadding = useCallback5((value) => {
7409
- for (let i = 0; i < PADDING_SNAP_STEPS.length - 1; i++) {
7410
- const lo = PADDING_SNAP_STEPS[i];
7411
- const hi = PADDING_SNAP_STEPS[i + 1];
7868
+ for (let i = 0; i < PADDING_SNAP_STEPS2.length - 1; i++) {
7869
+ const lo = PADDING_SNAP_STEPS2[i];
7870
+ const hi = PADDING_SNAP_STEPS2[i + 1];
7412
7871
  if (value <= (lo + hi) / 2) return lo;
7413
7872
  if (value < hi) return hi;
7414
7873
  }
@@ -7438,6 +7897,12 @@ function AnnotationCanvas({ state, dispatch, onScreenshot, inFlightAnnotationIds
7438
7897
  (e) => {
7439
7898
  var _a2;
7440
7899
  if (!state.isAnnotating) return;
7900
+ if (state.inspectedElement && state.activeTool === "hand" && !("button" in e && e.button === 2)) {
7901
+ e.preventDefault();
7902
+ e.stopPropagation();
7903
+ dispatch({ type: "SELECT_ELEMENT", payload: null });
7904
+ return;
7905
+ }
7441
7906
  const point = getPoint(e);
7442
7907
  const isShiftClick = "shiftKey" in e && e.shiftKey;
7443
7908
  if (state.activeTool === "inspector") {
@@ -7663,7 +8128,7 @@ function AnnotationCanvas({ state, dispatch, onScreenshot, inFlightAnnotationIds
7663
8128
  setIsDrawing(true);
7664
8129
  dispatch({ type: "START_PATH", payload: point });
7665
8130
  },
7666
- [state.isAnnotating, state.activeTool, state.annotations, activeText, selectedAnnotationIds, hoveredElement, handHoveredElement, handHoveredSide, radiusHoveredElement, radiusHoveredCorner, gapHoveredElement, gapHoveredAxis, gapIsAuto, textHoveredElement, textHoveredProperty, getPoint, findAnnotationAtPoint, findHandleAtPoint, dispatch, selectAnnotation, clearSelection, commitActiveText]
8131
+ [state.isAnnotating, state.activeTool, state.inspectedElement, state.annotations, activeText, selectedAnnotationIds, hoveredElement, handHoveredElement, handHoveredSide, radiusHoveredElement, radiusHoveredCorner, gapHoveredElement, gapHoveredAxis, gapIsAuto, textHoveredElement, textHoveredProperty, getPoint, findAnnotationAtPoint, findHandleAtPoint, dispatch, selectAnnotation, clearSelection, commitActiveText]
7667
8132
  );
7668
8133
  const handlePointerMove = useCallback5(
7669
8134
  (e) => {
@@ -8268,7 +8733,8 @@ function AnnotationCanvas({ state, dispatch, onScreenshot, inFlightAnnotationIds
8268
8733
  if (gapDragRef.current.isDragging) {
8269
8734
  const drag = gapDragRef.current;
8270
8735
  const el = drag.element;
8271
- if (!drag.hasMoved && el && drag.selector && drag.elementInfo) {
8736
+ const isRightClick = "button" in e && e.button === 2;
8737
+ if (!drag.hasMoved && !isRightClick && el && drag.selector && drag.elementInfo) {
8272
8738
  if (el instanceof HTMLElement) {
8273
8739
  el.style.removeProperty("transition");
8274
8740
  }
@@ -8488,18 +8954,37 @@ function AnnotationCanvas({ state, dispatch, onScreenshot, inFlightAnnotationIds
8488
8954
  commitActiveText();
8489
8955
  }
8490
8956
  }, [commitActiveText]);
8957
+ const handleTextPaste = useCallback5((e) => {
8958
+ if (!activeText) return;
8959
+ const items = e.clipboardData.items;
8960
+ const imageBlobs = [];
8961
+ for (let i = 0; i < items.length; i++) {
8962
+ const item = items[i];
8963
+ if (item.type.startsWith("image/")) {
8964
+ const file = item.getAsFile();
8965
+ if (file) imageBlobs.push(file);
8966
+ }
8967
+ }
8968
+ if (imageBlobs.length > 0) {
8969
+ e.preventDefault();
8970
+ setActiveText((prev) => prev ? __spreadProps(__spreadValues({}, prev), {
8971
+ images: [...prev.images || [], ...imageBlobs]
8972
+ }) : null);
8973
+ }
8974
+ }, [activeText]);
8491
8975
  const handleContextMenu = useCallback5((e) => {
8492
- if (state.activeTool !== "inspector" || !state.isAnnotating) return;
8976
+ if (state.activeTool !== "hand" || !state.isAnnotating) return;
8493
8977
  e.preventDefault();
8494
- if (hoveredElement && !isElementInFlight(hoveredElement)) {
8495
- const info = extractElementInfo(hoveredElement);
8496
- const selector = getUniqueSelector(hoveredElement);
8978
+ const target = handHoveredElement || gapHoveredElement || radiusHoveredElement || textHoveredElement;
8979
+ if (target && !isElementInFlight(target)) {
8980
+ const info = extractElementInfo(target);
8981
+ const selector = getUniqueSelector(target);
8497
8982
  dispatch({
8498
8983
  type: "SELECT_ELEMENT",
8499
- payload: { el: hoveredElement, info: __spreadProps(__spreadValues({}, info), { selector }) }
8984
+ payload: { el: target, info: __spreadProps(__spreadValues({}, info), { selector }) }
8500
8985
  });
8501
8986
  }
8502
- }, [state.activeTool, state.isAnnotating, hoveredElement, dispatch, isElementInFlight]);
8987
+ }, [state.activeTool, state.isAnnotating, handHoveredElement, gapHoveredElement, radiusHoveredElement, textHoveredElement, dispatch, isElementInFlight]);
8503
8988
  useEffect15(() => {
8504
8989
  const linkedAnnotations = state.annotations.filter((a) => a.linkedSelector);
8505
8990
  if (linkedAnnotations.length === 0) return;
@@ -8676,10 +9161,36 @@ function AnnotationCanvas({ state, dispatch, onScreenshot, inFlightAnnotationIds
8676
9161
  value: activeText.text,
8677
9162
  onChange: handleTextChange,
8678
9163
  onKeyDown: handleTextKeyDown,
9164
+ onPaste: handleTextPaste,
8679
9165
  onBlur: commitActiveText,
8680
9166
  placeholder: "Type here...",
8681
9167
  style: textInputStyle
8682
9168
  }
9169
+ ),
9170
+ activeText.images && activeText.images.length > 0 && /* @__PURE__ */ jsxs9(
9171
+ "div",
9172
+ {
9173
+ "data-devtools": true,
9174
+ style: {
9175
+ position: "fixed",
9176
+ left: activeText.point.x - PADDING - scroll.x,
9177
+ top: activeText.point.y - PADDING - scroll.y - 20,
9178
+ zIndex: 1e4,
9179
+ fontSize: 11,
9180
+ fontFamily: "system-ui, sans-serif",
9181
+ color: "#fff",
9182
+ backgroundColor: "rgba(0,0,0,0.7)",
9183
+ padding: "2px 6px",
9184
+ borderRadius: 3,
9185
+ whiteSpace: "nowrap"
9186
+ },
9187
+ children: [
9188
+ activeText.images.length,
9189
+ " image",
9190
+ activeText.images.length > 1 ? "s" : "",
9191
+ " attached"
9192
+ ]
9193
+ }
8683
9194
  )
8684
9195
  ] }),
8685
9196
  state.isAnnotating && state.activeTool !== "hand" && state.styleModifications.length > 0 && /* @__PURE__ */ jsx10(
@@ -8700,29 +9211,16 @@ function AnnotationCanvas({ state, dispatch, onScreenshot, inFlightAnnotationIds
8700
9211
  accentColor: state.activeColor
8701
9212
  }
8702
9213
  ),
8703
- state.isAnnotating && inFlightAnnotationIds && inFlightAnnotationIds.size > 0 && /* @__PURE__ */ jsx10(
8704
- ThinkingSpinners,
8705
- {
8706
- annotations: state.annotations,
8707
- inFlightIds: inFlightAnnotationIds,
8708
- scrollX: scroll.x,
8709
- scrollY: scroll.y,
8710
- annotationGroupMap,
8711
- onViewThread,
8712
- onSelectAnnotation: selectAnnotation
8713
- }
8714
- ),
8715
9214
  state.isAnnotating && /* @__PURE__ */ jsx10(
8716
- ResolutionBadges,
9215
+ AnnotationBadges,
8717
9216
  {
8718
9217
  annotations: state.annotations,
8719
9218
  supersededAnnotations,
9219
+ inFlightIds: inFlightAnnotationIds,
8720
9220
  scrollX: scroll.x,
8721
9221
  scrollY: scroll.y,
8722
9222
  annotationGroupMap,
8723
- onReply,
8724
9223
  onViewThread,
8725
- isThreadPanelOpen,
8726
9224
  onSelectAnnotation: selectAnnotation
8727
9225
  }
8728
9226
  ),
@@ -8811,28 +9309,28 @@ function AnnotationCanvas({ state, dispatch, onScreenshot, inFlightAnnotationIds
8811
9309
  refreshKey: textDragging ? textDragging.fontSize + textDragging.lineHeight * 1e3 : 0
8812
9310
  }
8813
9311
  ),
8814
- state.activeTool === "inspector" && state.isAnnotating && /* @__PURE__ */ jsxs9(Fragment6, { children: [
8815
- hoveredElement && !state.inspectedElement && (() => {
8816
- const hasLinkedAnnotation = !!pendingLinkedText || !!(activeText == null ? void 0 : activeText.linkedSelector) || state.annotations.some((a) => {
8817
- if (!a.linkedSelector) return false;
8818
- try {
8819
- return hoveredElement.matches(a.linkedSelector);
8820
- } catch (e) {
8821
- return false;
8822
- }
8823
- });
8824
- return /* @__PURE__ */ jsx10(
8825
- ElementHighlight,
8826
- {
8827
- element: hoveredElement,
8828
- isSelected: false,
8829
- elementInfo: hoveredElementInfo,
8830
- color: state.activeColor,
8831
- hideTooltip: hasLinkedAnnotation
8832
- }
8833
- );
8834
- })(),
8835
- state.inspectedElement && (() => {
9312
+ state.activeTool === "inspector" && state.isAnnotating && /* @__PURE__ */ jsx10(Fragment6, { children: hoveredElement && !state.inspectedElement && (() => {
9313
+ const hasLinkedAnnotation = !!pendingLinkedText || !!(activeText == null ? void 0 : activeText.linkedSelector) || state.annotations.some((a) => {
9314
+ if (!a.linkedSelector) return false;
9315
+ try {
9316
+ return hoveredElement.matches(a.linkedSelector);
9317
+ } catch (e) {
9318
+ return false;
9319
+ }
9320
+ });
9321
+ return /* @__PURE__ */ jsx10(
9322
+ ElementHighlight,
9323
+ {
9324
+ element: hoveredElement,
9325
+ isSelected: false,
9326
+ elementInfo: hoveredElementInfo,
9327
+ color: state.activeColor,
9328
+ hideTooltip: hasLinkedAnnotation
9329
+ }
9330
+ );
9331
+ })() }),
9332
+ state.activeTool === "hand" && state.isAnnotating && state.inspectedElement && /* @__PURE__ */ jsxs9(Fragment6, { children: [
9333
+ stylePanelHint && stylePanelHint !== "padding" && stylePanelHint !== "gap" && (() => {
8836
9334
  var _a2;
8837
9335
  const annotationGroupCount = new Set(
8838
9336
  state.annotations.map((a) => a.groupId || a.id)
@@ -8855,7 +9353,27 @@ function AnnotationCanvas({ state, dispatch, onScreenshot, inFlightAnnotationIds
8855
9353
  }
8856
9354
  );
8857
9355
  })(),
8858
- state.inspectedElement && /* @__PURE__ */ jsx10(
9356
+ stylePanelHint === "padding" && /* @__PURE__ */ jsx10(
9357
+ PaddingHandles,
9358
+ {
9359
+ element: state.inspectedElement.el,
9360
+ padding: getComputedPadding(state.inspectedElement.el),
9361
+ accentColor: state.activeColor,
9362
+ hoveredSide: null,
9363
+ draggingSide: null
9364
+ }
9365
+ ),
9366
+ stylePanelHint === "gap" && /* @__PURE__ */ jsx10(
9367
+ GapHandles,
9368
+ {
9369
+ element: state.inspectedElement.el,
9370
+ gap: getComputedGap(state.inspectedElement.el),
9371
+ accentColor: state.activeColor,
9372
+ hoveredAxis: null,
9373
+ draggingAxis: null
9374
+ }
9375
+ ),
9376
+ /* @__PURE__ */ jsx10(
8859
9377
  StylePanel,
8860
9378
  {
8861
9379
  element: state.inspectedElement.el,
@@ -8864,6 +9382,7 @@ function AnnotationCanvas({ state, dispatch, onScreenshot, inFlightAnnotationIds
8864
9382
  styleModifications: state.styleModifications,
8865
9383
  dispatch,
8866
9384
  onClose: () => dispatch({ type: "SELECT_ELEMENT", payload: null }),
9385
+ onHover: setStylePanelHint,
8867
9386
  accentColor: state.activeColor
8868
9387
  }
8869
9388
  )
@@ -8887,8 +9406,9 @@ var THINKING_WORDS2 = [
8887
9406
  "riffing"
8888
9407
  ];
8889
9408
  var WORD_INTERVAL2 = 3e3;
8890
- function ThinkingSpinners({
9409
+ function AnnotationBadges({
8891
9410
  annotations,
9411
+ supersededAnnotations,
8892
9412
  inFlightIds,
8893
9413
  scrollX,
8894
9414
  scrollY,
@@ -8896,10 +9416,12 @@ function ThinkingSpinners({
8896
9416
  onViewThread,
8897
9417
  onSelectAnnotation
8898
9418
  }) {
8899
- var _a;
9419
+ var _a, _b;
8900
9420
  const [charIndex, setCharIndex] = useState12(0);
8901
9421
  const [wordIndex, setWordIndex] = useState12(() => Math.floor(Math.random() * THINKING_WORDS2.length));
9422
+ const hasInFlight = !!(inFlightIds && inFlightIds.size > 0);
8902
9423
  useEffect15(() => {
9424
+ if (!hasInFlight) return;
8903
9425
  const charTimer = setInterval(() => {
8904
9426
  setCharIndex((i) => (i + 1) % SPINNER_FRAME_COUNT2);
8905
9427
  }, SPINNER_INTERVAL2);
@@ -8910,14 +9432,25 @@ function ThinkingSpinners({
8910
9432
  clearInterval(charTimer);
8911
9433
  clearInterval(wordTimer);
8912
9434
  };
8913
- }, []);
8914
- const spinnerPositions = [];
9435
+ }, [hasInFlight]);
9436
+ const badges = [];
8915
9437
  for (const annotation of annotations) {
8916
9438
  if (annotation.type !== "text" || !annotation.text || !annotation.points[0]) continue;
8917
- const isInFlight = inFlightIds.has(annotation.id) || annotation.groupId && annotations.some(
8918
- (a) => a.groupId === annotation.groupId && inFlightIds.has(a.id)
9439
+ if (supersededAnnotations.has(annotation)) continue;
9440
+ const groupAnns = annotation.groupId ? annotations.filter((a) => a.groupId === annotation.groupId) : [annotation];
9441
+ const isInFlight = !!(inFlightIds && (inFlightIds.has(annotation.id) || groupAnns.some((a) => inFlightIds.has(a.id))));
9442
+ const status = (_a = annotation.status) != null ? _a : "pending";
9443
+ const groupMateResolved = groupAnns.some(
9444
+ (a) => a.status === "resolved" || a.status === "needs_review"
8919
9445
  );
8920
- if (!isInFlight) continue;
9446
+ const hasThread = groupAnns.some((a) => a.threadId);
9447
+ if (!isInFlight && status !== "resolved" && status !== "needs_review" && !groupMateResolved && !hasThread) continue;
9448
+ const threadId = annotation.threadId || ((_b = groupAnns.find((a) => a.threadId)) == null ? void 0 : _b.threadId);
9449
+ const isNeedsReview = status === "needs_review" || groupAnns.some((a) => a.status === "needs_review");
9450
+ const replyCount = groupAnns.reduce((n, a) => {
9451
+ var _a2;
9452
+ return n + ((_a2 = a.replyCount) != null ? _a2 : 0);
9453
+ }, 0) || 1;
8921
9454
  const point = annotation.points[0];
8922
9455
  const fontSize = annotation.fontSize || 12;
8923
9456
  const lineHeightPx = fontSize * LINE_HEIGHT;
@@ -8930,26 +9463,24 @@ function ThinkingSpinners({
8930
9463
  ctx.font = `${fontSize}px ${FONT_FAMILY}`;
8931
9464
  const maxWidth = Math.min(MAX_DISPLAY_WIDTH, Math.max(...displayLines.map((line) => ctx.measureText(line).width)));
8932
9465
  const totalHeight = displayLines.length * lineHeightPx;
8933
- const annotationHeight = totalHeight + PADDING * 2;
8934
- const groupAnns = annotation.groupId ? annotations.filter((a) => a.groupId === annotation.groupId) : [annotation];
8935
- const threadId = annotation.threadId || ((_a = groupAnns.find((a) => a.threadId)) == null ? void 0 : _a.threadId);
8936
- spinnerPositions.push({
9466
+ badges.push({
8937
9467
  id: annotation.id,
8938
9468
  threadId,
8939
9469
  x: point.x + maxWidth + PADDING,
8940
- // right edge of text bg
8941
9470
  y: point.y - PADDING,
8942
- // top of text bg
8943
- size: annotationHeight,
8944
- color: annotation.color
9471
+ size: totalHeight + PADDING * 2,
9472
+ color: annotation.color,
9473
+ isInFlight,
9474
+ isNeedsReview,
9475
+ replyCount
8945
9476
  });
8946
9477
  }
8947
- if (spinnerPositions.length === 0) return null;
9478
+ if (badges.length === 0) return null;
8948
9479
  const clickable = !!onViewThread;
8949
- return /* @__PURE__ */ jsx10(Fragment6, { children: spinnerPositions.map((pos, i) => /* @__PURE__ */ jsxs9(
9480
+ return /* @__PURE__ */ jsx10(Fragment6, { children: badges.map((pos) => /* @__PURE__ */ jsx10(
8950
9481
  "div",
8951
9482
  {
8952
- "data-devtools": true,
9483
+ "data-devtools": "annotation-badge",
8953
9484
  onClick: clickable && pos.threadId ? () => {
8954
9485
  onSelectAnnotation == null ? void 0 : onSelectAnnotation(pos.id);
8955
9486
  onViewThread(pos.threadId);
@@ -8962,7 +9493,7 @@ function ThinkingSpinners({
8962
9493
  display: "flex",
8963
9494
  alignItems: "center",
8964
9495
  pointerEvents: clickable ? "auto" : "none",
8965
- cursor: clickable ? "pointer" : void 0,
9496
+ cursor: clickable && pos.threadId ? "pointer" : void 0,
8966
9497
  zIndex: 9999,
8967
9498
  backgroundColor: pos.color,
8968
9499
  fontFamily: FONT_FAMILY,
@@ -8973,7 +9504,7 @@ function ThinkingSpinners({
8973
9504
  gap: 4,
8974
9505
  whiteSpace: "nowrap"
8975
9506
  },
8976
- children: [
9507
+ children: pos.isInFlight ? /* @__PURE__ */ jsxs9(Fragment6, { children: [
8977
9508
  /* @__PURE__ */ jsx10("svg", { width: "11", height: "11", viewBox: "0 0 24 24", fill: "currentColor", style: { verticalAlign: "middle" }, children: charIndex === 1 ? /* @__PURE__ */ jsxs9(Fragment6, { children: [
8978
9509
  /* @__PURE__ */ jsx10("circle", { cx: "7", cy: "7", r: "2" }),
8979
9510
  /* @__PURE__ */ jsx10("circle", { cx: "17", cy: "7", r: "2" }),
@@ -8986,287 +9517,23 @@ function ThinkingSpinners({
8986
9517
  /* @__PURE__ */ jsx10("circle", { cx: "12", cy: "18", r: "2" })
8987
9518
  ] }) }),
8988
9519
  /* @__PURE__ */ jsx10("span", { style: { opacity: 0.7 }, children: THINKING_WORDS2[wordIndex] })
8989
- ]
8990
- },
8991
- i
8992
- )) });
8993
- }
8994
- function ResolutionBadges({
8995
- annotations,
8996
- supersededAnnotations,
8997
- scrollX,
8998
- scrollY,
8999
- annotationGroupMap,
9000
- onReply,
9001
- onViewThread,
9002
- isThreadPanelOpen,
9003
- onSelectAnnotation
9004
- }) {
9005
- var _a;
9006
- const badgePositions = [];
9007
- for (const annotation of annotations) {
9008
- if (annotation.type !== "text" || !annotation.text || !annotation.points[0]) continue;
9009
- if (supersededAnnotations.has(annotation)) continue;
9010
- const status = (_a = annotation.status) != null ? _a : "pending";
9011
- const groupAnns = annotation.groupId ? annotations.filter((a) => a.groupId === annotation.groupId) : [annotation];
9012
- const groupMateResolved = groupAnns.some(
9013
- (a) => a.status === "resolved" || a.status === "needs_review"
9014
- );
9015
- const hasThread = groupAnns.some((a) => a.threadId);
9016
- if (status !== "resolved" && status !== "needs_review" && !groupMateResolved && !hasThread) continue;
9017
- const isNeedsReview = status === "needs_review" || groupAnns.some((a) => a.status === "needs_review");
9018
- const point = annotation.points[0];
9019
- const fontSize = annotation.fontSize || 12;
9020
- const lineHeightPx = fontSize * LINE_HEIGHT;
9021
- const lines = annotation.text.split("\n");
9022
- const groupNumber = annotationGroupMap.get(annotation.id);
9023
- const displayLines = groupNumber !== void 0 ? [groupNumber + ". " + (lines[0] || ""), ...lines.slice(1)] : lines;
9024
- const canvas = document.createElement("canvas");
9025
- const ctx = canvas.getContext("2d");
9026
- if (!ctx) continue;
9027
- ctx.font = `${fontSize}px ${FONT_FAMILY}`;
9028
- const maxWidth = Math.min(MAX_DISPLAY_WIDTH, Math.max(...displayLines.map((line) => ctx.measureText(line).width)));
9029
- const totalHeight = displayLines.length * lineHeightPx;
9030
- const annotationHeight = totalHeight + PADDING * 2;
9031
- badgePositions.push({
9032
- annotation,
9033
- x: point.x + maxWidth + PADDING,
9034
- y: point.y - PADDING,
9035
- size: annotationHeight,
9036
- isNeedsReview: !!isNeedsReview,
9037
- groupNumber
9038
- });
9039
- }
9040
- if (badgePositions.length === 0) return null;
9041
- return /* @__PURE__ */ jsx10(Fragment6, { children: badgePositions.map(({ annotation, x, y, size, isNeedsReview, groupNumber }) => /* @__PURE__ */ jsx10(
9042
- ResolutionBadge,
9043
- {
9044
- annotation,
9045
- annotations,
9046
- x: x - scrollX,
9047
- y: y - scrollY,
9048
- size,
9049
- isNeedsReview: !!isNeedsReview,
9050
- groupNumber,
9051
- onReply,
9052
- onViewThread,
9053
- isThreadPanelOpen,
9054
- onSelectAnnotation
9520
+ ] }) : /* @__PURE__ */ jsxs9(Fragment6, { children: [
9521
+ pos.isNeedsReview ? /* @__PURE__ */ jsx10("span", { style: { fontWeight: 700 }, children: "?" }) : /* @__PURE__ */ jsxs9("svg", { width: "11", height: "11", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [
9522
+ /* @__PURE__ */ jsx10("line", { x1: "12", y1: "3", x2: "12", y2: "9" }),
9523
+ /* @__PURE__ */ jsx10("line", { x1: "12", y1: "15", x2: "12", y2: "21" }),
9524
+ /* @__PURE__ */ jsx10("line", { x1: "3", y1: "12", x2: "9", y2: "12" }),
9525
+ /* @__PURE__ */ jsx10("line", { x1: "15", y1: "12", x2: "21", y2: "12" })
9526
+ ] }),
9527
+ /* @__PURE__ */ jsxs9("span", { style: { opacity: 0.7 }, children: [
9528
+ pos.replyCount,
9529
+ " ",
9530
+ pos.replyCount === 1 ? "reply" : "replies"
9531
+ ] })
9532
+ ] })
9055
9533
  },
9056
- `resolution-${annotation.id}`
9534
+ pos.id
9057
9535
  )) });
9058
9536
  }
9059
- function ResolutionBadge({
9060
- annotation,
9061
- annotations,
9062
- x,
9063
- y,
9064
- size,
9065
- isNeedsReview,
9066
- groupNumber,
9067
- onReply,
9068
- onViewThread,
9069
- isThreadPanelOpen,
9070
- onSelectAnnotation
9071
- }) {
9072
- var _a, _b;
9073
- const [expanded, setExpanded] = useState12(false);
9074
- const [replyText, setReplyText] = useState12("");
9075
- const textareaRef = useRef5(null);
9076
- const panelRef = useRef5(null);
9077
- useEffect15(() => {
9078
- if (expanded && textareaRef.current) {
9079
- textareaRef.current.focus();
9080
- }
9081
- }, [expanded]);
9082
- useEffect15(() => {
9083
- if (!expanded) return;
9084
- const handleClickOutside = (e) => {
9085
- if (panelRef.current && !panelRef.current.contains(e.target)) {
9086
- setExpanded(false);
9087
- }
9088
- };
9089
- const handleEscape = (e) => {
9090
- if (e.key === "Escape") setExpanded(false);
9091
- };
9092
- document.addEventListener("mousedown", handleClickOutside);
9093
- document.addEventListener("keydown", handleEscape);
9094
- return () => {
9095
- document.removeEventListener("mousedown", handleClickOutside);
9096
- document.removeEventListener("keydown", handleEscape);
9097
- };
9098
- }, [expanded]);
9099
- const groupAnnotations = annotation.groupId ? annotations.filter((a) => a.groupId === annotation.groupId) : [annotation];
9100
- const textAnn = groupAnnotations.find((a) => a.type === "text");
9101
- const userMessage = (textAnn == null ? void 0 : textAnn.text) || "(no text)";
9102
- const summary = (_a = groupAnnotations.find((a) => a.resolutionSummary)) == null ? void 0 : _a.resolutionSummary;
9103
- const threadId = (_b = groupAnnotations.find((a) => a.threadId)) == null ? void 0 : _b.threadId;
9104
- const handleSubmit = useCallback5(() => {
9105
- if (!replyText.trim() || !threadId || !onReply) return;
9106
- onReply(threadId, replyText.trim());
9107
- setReplyText("");
9108
- setExpanded(false);
9109
- }, [replyText, threadId, onReply]);
9110
- const handleKeyDown = useCallback5((e) => {
9111
- if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
9112
- e.preventDefault();
9113
- handleSubmit();
9114
- }
9115
- }, [handleSubmit]);
9116
- return /* @__PURE__ */ jsxs9(
9117
- "div",
9118
- {
9119
- ref: panelRef,
9120
- "data-devtools": "resolution-badge",
9121
- style: {
9122
- position: "fixed",
9123
- left: x,
9124
- top: y,
9125
- zIndex: expanded ? 10002 : 9999,
9126
- pointerEvents: "auto"
9127
- },
9128
- children: [
9129
- !expanded && /* @__PURE__ */ jsxs9(
9130
- "div",
9131
- {
9132
- onClick: () => {
9133
- onSelectAnnotation == null ? void 0 : onSelectAnnotation(annotation.id);
9134
- if (threadId && onViewThread) {
9135
- onViewThread(threadId);
9136
- }
9137
- },
9138
- style: {
9139
- height: size,
9140
- display: "flex",
9141
- alignItems: "center",
9142
- cursor: "pointer",
9143
- backgroundColor: annotation.color,
9144
- fontFamily: FONT_FAMILY,
9145
- fontSize: 12,
9146
- color: "#ffffff",
9147
- userSelect: "none",
9148
- padding: `0 ${PADDING}px`,
9149
- gap: 4,
9150
- whiteSpace: "nowrap"
9151
- },
9152
- children: [
9153
- isNeedsReview ? /* @__PURE__ */ jsx10("span", { style: { fontWeight: 700 }, children: "?" }) : /* @__PURE__ */ jsxs9("svg", { width: "11", height: "11", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [
9154
- /* @__PURE__ */ jsx10("line", { x1: "12", y1: "3", x2: "12", y2: "9" }),
9155
- /* @__PURE__ */ jsx10("line", { x1: "12", y1: "15", x2: "12", y2: "21" }),
9156
- /* @__PURE__ */ jsx10("line", { x1: "3", y1: "12", x2: "9", y2: "12" }),
9157
- /* @__PURE__ */ jsx10("line", { x1: "15", y1: "12", x2: "21", y2: "12" })
9158
- ] }),
9159
- /* @__PURE__ */ jsx10("span", { style: { opacity: 0.7 }, children: (() => {
9160
- const groupAnns = annotation.groupId ? annotations.filter((a) => a.groupId === annotation.groupId) : [annotation];
9161
- const count = groupAnns.reduce((n, a) => {
9162
- var _a2;
9163
- return n + ((_a2 = a.replyCount) != null ? _a2 : 0);
9164
- }, 0) || 1;
9165
- return `${count} ${count === 1 ? "reply" : "replies"}`;
9166
- })() })
9167
- ]
9168
- }
9169
- ),
9170
- expanded && /* @__PURE__ */ jsxs9(
9171
- "div",
9172
- {
9173
- style: {
9174
- minWidth: 280,
9175
- maxWidth: 400,
9176
- backgroundColor: "#ffffff",
9177
- fontFamily: FONT_FAMILY,
9178
- fontSize: 12,
9179
- color: "#1f2937",
9180
- border: "1px solid rgba(0, 0, 0, 0.1)"
9181
- },
9182
- children: [
9183
- /* @__PURE__ */ jsx10("div", { style: {
9184
- padding: `${PADDING + 2}px ${PADDING + 4}px`,
9185
- backgroundColor: annotation.color,
9186
- color: "#ffffff",
9187
- lineHeight: 1.4
9188
- }, children: userMessage }),
9189
- summary && /* @__PURE__ */ jsx10("div", { style: {
9190
- padding: `${PADDING + 2}px ${PADDING + 4}px`,
9191
- lineHeight: 1.4,
9192
- borderBottom: "1px solid rgba(0, 0, 0, 0.06)"
9193
- }, children: summary }),
9194
- threadId && /* @__PURE__ */ jsxs9("div", { style: { padding: PADDING }, children: [
9195
- onReply && /* @__PURE__ */ jsxs9(Fragment6, { children: [
9196
- /* @__PURE__ */ jsx10(
9197
- "textarea",
9198
- {
9199
- ref: textareaRef,
9200
- value: replyText,
9201
- onChange: (e) => setReplyText(e.target.value),
9202
- onKeyDown: handleKeyDown,
9203
- placeholder: "Reply...",
9204
- style: {
9205
- width: "100%",
9206
- minHeight: 40,
9207
- padding: PADDING,
9208
- fontSize: 12,
9209
- fontFamily: FONT_FAMILY,
9210
- backgroundColor: "rgba(0, 0, 0, 0.04)",
9211
- color: "#1f2937",
9212
- border: "1px solid rgba(0, 0, 0, 0.1)",
9213
- borderRadius: 0,
9214
- outline: "none",
9215
- resize: "vertical",
9216
- lineHeight: 1.4,
9217
- boxSizing: "border-box"
9218
- }
9219
- }
9220
- ),
9221
- /* @__PURE__ */ jsx10("div", { style: { display: "flex", justifyContent: "flex-end", marginTop: 4 }, children: /* @__PURE__ */ jsx10(
9222
- "button",
9223
- {
9224
- onClick: handleSubmit,
9225
- disabled: !replyText.trim(),
9226
- style: {
9227
- padding: "4px 12px",
9228
- fontSize: 11,
9229
- fontFamily: FONT_FAMILY,
9230
- fontWeight: 600,
9231
- backgroundColor: replyText.trim() ? annotation.color : "rgba(0,0,0,0.1)",
9232
- color: replyText.trim() ? "#ffffff" : "rgba(0,0,0,0.3)",
9233
- border: "none",
9234
- cursor: replyText.trim() ? "pointer" : "default"
9235
- },
9236
- children: "Send \u2318\u23CE"
9237
- }
9238
- ) })
9239
- ] }),
9240
- onViewThread && /* @__PURE__ */ jsx10(
9241
- "button",
9242
- {
9243
- onClick: () => {
9244
- onViewThread(threadId);
9245
- setExpanded(false);
9246
- },
9247
- style: {
9248
- width: "100%",
9249
- padding: "5px 0",
9250
- fontSize: 11,
9251
- fontFamily: FONT_FAMILY,
9252
- fontWeight: 500,
9253
- backgroundColor: "transparent",
9254
- color: "#6b7280",
9255
- border: "1px solid rgba(0, 0, 0, 0.1)",
9256
- cursor: "pointer",
9257
- marginTop: onReply ? 8 : 0
9258
- },
9259
- children: "View conversation \u2192"
9260
- }
9261
- )
9262
- ] })
9263
- ]
9264
- }
9265
- )
9266
- ]
9267
- }
9268
- );
9269
- }
9270
9537
  function PlanWaitingBadges({
9271
9538
  annotations,
9272
9539
  supersededAnnotations,
@@ -9792,13 +10059,6 @@ function useLocalStorageBatch(isExpanded, state, hasRestoredAnnotations, hasActi
9792
10059
  ]);
9793
10060
  }
9794
10061
 
9795
- // src/styles/border.ts
9796
- var POPMELT_BORDER = {
9797
- borderWidth: 4,
9798
- borderStyle: "solid",
9799
- borderImage: "repeating-linear-gradient(-45deg, rgba(0,0,0,1) 0, rgba(0,0,0,1) 1px, rgba(0,0,0,0.1) 1px, rgba(0,0,0,0.1) 4px) 4"
9800
- };
9801
-
9802
10062
  // src/components/ToolButton.tsx
9803
10063
  import { jsx as jsx11 } from "react/jsx-runtime";
9804
10064
  var colors = {
@@ -9919,13 +10179,15 @@ var TOOL_GUIDANCE = {
9919
10179
  "Corners \u2192 rounding",
9920
10180
  "Right of text \u2192 font size",
9921
10181
  "Below text \u2192 line height",
9922
- "Click a spacing handle to cycle distribution"
10182
+ "Click a spacing handle to cycle distribution",
10183
+ "Right-click \u2192 inspect element styles"
9923
10184
  ],
9924
10185
  keys: [
9925
10186
  { key: "H", desc: "Select tool" },
9926
10187
  { key: "Shift", desc: "Snap to scale" },
9927
10188
  { key: "\u2325 + swipe", desc: "Cycle justify / flip direction" },
9928
10189
  { key: "\u21E7 + swipe", desc: "Cycle align-items" },
10190
+ { key: "Right-click", desc: "Inspect styles" },
9929
10191
  { key: "Esc", desc: "Deselect" },
9930
10192
  { key: "\u2318 Enter", desc: "Hand off to your AI", accent: true }
9931
10193
  ]
@@ -10582,7 +10844,9 @@ function AnnotationToolbar({
10582
10844
  "div",
10583
10845
  {
10584
10846
  id: "devtools-toolbar",
10585
- style: __spreadProps(__spreadValues({}, toolbarStyle), { overflow: "visible" }),
10847
+ style: __spreadProps(__spreadValues({}, toolbarStyle), { overflow: "visible", opacity: 0.5, transition: "opacity 150ms ease" }),
10848
+ onMouseEnter: (e) => e.currentTarget.style.opacity = "1",
10849
+ onMouseLeave: (e) => e.currentTarget.style.opacity = "0.5",
10586
10850
  children: /* @__PURE__ */ jsx12(
10587
10851
  "button",
10588
10852
  {
@@ -10951,7 +11215,7 @@ function AnnotationToolbar({
10951
11215
  }
10952
11216
 
10953
11217
  // src/components/BridgeStatusPanel.tsx
10954
- import { useEffect as useEffect18, useRef as useRef8, useState as useState14 } from "react";
11218
+ import { useCallback as useCallback7, useEffect as useEffect18, useRef as useRef8, useState as useState14 } from "react";
10955
11219
  import { Fragment as Fragment8, jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
10956
11220
  var stackContainerStyle = {
10957
11221
  position: "fixed",
@@ -10960,21 +11224,22 @@ var stackContainerStyle = {
10960
11224
  zIndex: 9999,
10961
11225
  display: "flex",
10962
11226
  flexDirection: "column",
10963
- gap: 1
11227
+ gap: 10
10964
11228
  };
10965
- var rowStyle = {
11229
+ var rowStyle = __spreadProps(__spreadValues({
10966
11230
  display: "flex",
10967
11231
  alignItems: "center",
10968
11232
  gap: 6,
10969
11233
  padding: "4px 10px",
10970
- backgroundColor: "rgba(255, 255, 255, 0.85)",
10971
- border: "1px solid rgba(0, 0, 0, 0.1)",
11234
+ backgroundColor: "rgba(255, 255, 255, 0.85)"
11235
+ }, POPMELT_BORDER), {
10972
11236
  backdropFilter: "blur(32px)",
10973
11237
  fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
10974
11238
  fontSize: 11,
10975
11239
  color: "#1f2937",
10976
- whiteSpace: "nowrap"
10977
- };
11240
+ whiteSpace: "nowrap",
11241
+ transition: "transform 200ms ease, opacity 200ms ease"
11242
+ });
10978
11243
  function formatStepText(events) {
10979
11244
  var _a;
10980
11245
  const toolEvents = events.filter((e) => e.type === "tool_use");
@@ -11065,6 +11330,60 @@ function ErrorDot() {
11065
11330
  }
11066
11331
  );
11067
11332
  }
11333
+ function SwipeDismiss({ onDismiss, children }) {
11334
+ const ref = useRef8(null);
11335
+ const drag = useRef8(null);
11336
+ const [offset, setOffset] = useState14(0);
11337
+ const [dismissed, setDismissed] = useState14(false);
11338
+ const THRESHOLD = 60;
11339
+ const onPointerDown = useCallback7((e) => {
11340
+ var _a;
11341
+ drag.current = { startX: e.clientX, startY: e.clientY, locked: false };
11342
+ (_a = ref.current) == null ? void 0 : _a.setPointerCapture(e.pointerId);
11343
+ }, []);
11344
+ const onPointerMove = useCallback7((e) => {
11345
+ const d = drag.current;
11346
+ if (!d) return;
11347
+ const dx = e.clientX - d.startX;
11348
+ const dy = e.clientY - d.startY;
11349
+ if (!d.locked) {
11350
+ if (Math.abs(dx) < 4 && Math.abs(dy) < 4) return;
11351
+ d.locked = true;
11352
+ if (Math.abs(dy) > Math.abs(dx)) {
11353
+ drag.current = null;
11354
+ return;
11355
+ }
11356
+ }
11357
+ setOffset(Math.max(0, dx));
11358
+ }, []);
11359
+ const onPointerUp = useCallback7(() => {
11360
+ if (!drag.current) return;
11361
+ drag.current = null;
11362
+ if (offset >= THRESHOLD) {
11363
+ setDismissed(true);
11364
+ setTimeout(onDismiss, 200);
11365
+ } else {
11366
+ setOffset(0);
11367
+ }
11368
+ }, [offset, onDismiss]);
11369
+ return /* @__PURE__ */ jsx13(
11370
+ "div",
11371
+ {
11372
+ ref,
11373
+ onPointerDown,
11374
+ onPointerMove,
11375
+ onPointerUp,
11376
+ onPointerCancel: onPointerUp,
11377
+ style: {
11378
+ transform: dismissed ? "translateX(120%)" : `translateX(${offset}px)`,
11379
+ opacity: dismissed ? 0 : 1 - offset / (THRESHOLD * 3),
11380
+ transition: drag.current ? "none" : "transform 200ms ease, opacity 200ms ease",
11381
+ touchAction: "pan-y"
11382
+ },
11383
+ children
11384
+ }
11385
+ );
11386
+ }
11068
11387
  function BridgeEventStack({ bridge, inFlightJobs, isVisible: isVisible2, onHover, clearSignal }) {
11069
11388
  const [entries, setEntries] = useState14([]);
11070
11389
  useEffect18(() => {
@@ -11118,20 +11437,20 @@ function BridgeEventStack({ bridge, inFlightJobs, isVisible: isVisible2, onHover
11118
11437
  onMouseLeave: () => onHover(false),
11119
11438
  children: entries.map((entry) => {
11120
11439
  const label = entry.status === "working" ? formatStepText(bridge.events.filter((e) => e.data.jobId === entry.jobId)) : entry.status === "queued" ? "Queued" : entry.status === "done" ? entry.doneLabel || "Done" : "Error";
11121
- return /* @__PURE__ */ jsxs11("div", { style: rowStyle, children: [
11440
+ return /* @__PURE__ */ jsx13(SwipeDismiss, { onDismiss: () => setEntries((prev) => prev.filter((e) => e.jobId !== entry.jobId)), children: /* @__PURE__ */ jsxs11("div", { style: rowStyle, children: [
11122
11441
  entry.status === "working" && /* @__PURE__ */ jsx13(DotSpinner, { color: entry.color }),
11123
11442
  entry.status === "queued" && /* @__PURE__ */ jsx13(ColorSquare, { color: entry.color }),
11124
11443
  entry.status === "done" && /* @__PURE__ */ jsx13(Checkmark, { color: entry.color }),
11125
11444
  entry.status === "error" && /* @__PURE__ */ jsx13(ErrorDot, {}),
11126
11445
  /* @__PURE__ */ jsx13("span", { style: { color: entry.status === "queued" ? "#9ca3af" : "#1f2937" }, children: label })
11127
- ] }, entry.jobId);
11446
+ ] }) }, entry.jobId);
11128
11447
  })
11129
11448
  }
11130
11449
  );
11131
11450
  }
11132
11451
 
11133
11452
  // src/components/ThreadPanel.tsx
11134
- import { useCallback as useCallback7, useEffect as useEffect19, useRef as useRef9, useState as useState15 } from "react";
11453
+ import { useCallback as useCallback8, useEffect as useEffect19, useRef as useRef9, useState as useState15 } from "react";
11135
11454
 
11136
11455
  // src/utils/threadMarkdown.tsx
11137
11456
  import { Fragment as Fragment9, jsx as jsx14 } from "react/jsx-runtime";
@@ -11412,13 +11731,10 @@ function ThinkingBadge({ color }) {
11412
11731
  /* @__PURE__ */ jsx15("span", { style: { fontSize: 11, color: "#9ca3af", fontStyle: "italic" }, children: THINKING_WORDS3[wordIndex] })
11413
11732
  ] });
11414
11733
  }
11415
- function formatToolStep(events) {
11734
+ function formatToolEvent(event) {
11416
11735
  var _a;
11417
- const toolEvents = events.filter((e) => e.type === "tool_use");
11418
- if (toolEvents.length === 0) return null;
11419
- const last = toolEvents[toolEvents.length - 1];
11420
- const tool = String(last.data.tool || "");
11421
- const file = last.data.file ? String(last.data.file) : null;
11736
+ const tool = String(event.data.tool || "");
11737
+ const file = event.data.file ? String(event.data.file) : null;
11422
11738
  const basename = file ? (_a = file.split("/").pop()) != null ? _a : file : null;
11423
11739
  switch (tool) {
11424
11740
  case "Read":
@@ -11441,6 +11757,34 @@ function formatToolStep(events) {
11441
11757
  return tool ? `Using ${tool}` : null;
11442
11758
  }
11443
11759
  }
11760
+ function buildStreamSegments(events) {
11761
+ const segments = [];
11762
+ for (const e of events) {
11763
+ if (e.type === "tool_use") {
11764
+ const label = formatToolEvent(e);
11765
+ if (label) segments.push({ kind: "tool", label });
11766
+ } else if (e.type === "delta") {
11767
+ const text = String(e.data.text || "");
11768
+ if (!text) continue;
11769
+ const last = segments[segments.length - 1];
11770
+ if (last && last.kind === "text") {
11771
+ last.text += text;
11772
+ } else {
11773
+ segments.push({ kind: "text", text });
11774
+ }
11775
+ } else if (e.type === "thinking") {
11776
+ const text = String(e.data.text || "");
11777
+ if (!text) continue;
11778
+ const last = segments[segments.length - 1];
11779
+ if (last && last.kind === "thinking") {
11780
+ last.text += text;
11781
+ } else {
11782
+ segments.push({ kind: "thinking", text });
11783
+ }
11784
+ }
11785
+ }
11786
+ return segments;
11787
+ }
11444
11788
  function ThreadPanel({
11445
11789
  threadId,
11446
11790
  bridgeUrl,
@@ -11462,10 +11806,11 @@ function ThreadPanel({
11462
11806
  const [messages, setMessages] = useState15([]);
11463
11807
  const [loading, setLoading] = useState15(true);
11464
11808
  const [replyText, setReplyText] = useState15("");
11809
+ const [replyImages, setReplyImages] = useState15([]);
11465
11810
  const scrollRef = useRef9(null);
11466
11811
  const panelRef = useRef9(null);
11467
11812
  const prevStreamingRef = useRef9(isStreaming);
11468
- const fetchThread = useCallback7(() => {
11813
+ const fetchThread = useCallback8(() => {
11469
11814
  fetch(`${bridgeUrl}/thread/${threadId}`).then((r) => r.json()).then((data) => {
11470
11815
  var _a2;
11471
11816
  setMessages((_a2 = data.messages) != null ? _a2 : []);
@@ -11482,11 +11827,12 @@ function ThreadPanel({
11482
11827
  }
11483
11828
  prevStreamingRef.current = isStreaming;
11484
11829
  }, [isStreaming, fetchThread]);
11830
+ const streamSegments = streamingEvents ? buildStreamSegments(streamingEvents) : [];
11485
11831
  useEffect19(() => {
11486
11832
  if (scrollRef.current) {
11487
11833
  scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
11488
11834
  }
11489
- }, [messages, streamingResponse, streamingThinking, isStreaming]);
11835
+ }, [messages, streamSegments.length, isStreaming]);
11490
11836
  useEffect19(() => {
11491
11837
  const handleEscape = (e) => {
11492
11838
  if (e.key === "Escape") onClose();
@@ -11494,27 +11840,41 @@ function ThreadPanel({
11494
11840
  document.addEventListener("keydown", handleEscape);
11495
11841
  return () => document.removeEventListener("keydown", handleEscape);
11496
11842
  }, [onClose]);
11497
- const handleSubmit = useCallback7(() => {
11843
+ const handleSubmit = useCallback8(() => {
11498
11844
  if (!replyText.trim() || !onReply) return;
11499
11845
  const text = replyText.trim();
11846
+ const images = replyImages.length > 0 ? replyImages : void 0;
11500
11847
  setMessages((prev) => [...prev, {
11501
11848
  role: "human",
11502
11849
  timestamp: Date.now(),
11503
11850
  jobId: "pending",
11504
- replyToQuestion: text
11851
+ replyToQuestion: images ? `${text} [${images.length} image${images.length > 1 ? "s" : ""}]` : text
11505
11852
  }]);
11506
- onReply(threadId, text);
11853
+ onReply(threadId, text, images);
11507
11854
  setReplyText("");
11508
- }, [replyText, threadId, onReply]);
11509
- const handleKeyDown = useCallback7((e) => {
11855
+ setReplyImages([]);
11856
+ }, [replyText, replyImages, threadId, onReply]);
11857
+ const handlePaste = useCallback8((e) => {
11858
+ const items = e.clipboardData.items;
11859
+ const imageBlobs = [];
11860
+ for (let i = 0; i < items.length; i++) {
11861
+ const item = items[i];
11862
+ if (item.type.startsWith("image/")) {
11863
+ const file = item.getAsFile();
11864
+ if (file) imageBlobs.push(file);
11865
+ }
11866
+ }
11867
+ if (imageBlobs.length > 0) {
11868
+ e.preventDefault();
11869
+ setReplyImages((prev) => [...prev, ...imageBlobs]);
11870
+ }
11871
+ }, []);
11872
+ const handleKeyDown = useCallback8((e) => {
11510
11873
  if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
11511
11874
  e.preventDefault();
11512
11875
  handleSubmit();
11513
11876
  }
11514
11877
  }, [handleSubmit]);
11515
- const toolStep = streamingEvents ? formatToolStep(streamingEvents) : null;
11516
- const thinkingText = streamingThinking || "";
11517
- const responseText = streamingResponse ? stripInternalTags(streamingResponse) : "";
11518
11878
  return /* @__PURE__ */ jsxs12(Fragment10, { children: [
11519
11879
  /* @__PURE__ */ jsx15(
11520
11880
  "div",
@@ -11562,7 +11922,9 @@ function ThreadPanel({
11562
11922
  const isHuman = msg.role === "human";
11563
11923
  const text = isHuman ? msg.replyToQuestion || msg.feedbackSummary || "(annotation)" : stripInternalTags(msg.responseText || "");
11564
11924
  const questionText = !isHuman ? msg.question : void 0;
11565
- if (!text && !questionText) return null;
11925
+ const hasResolutions = !isHuman && msg.resolutions && msg.resolutions.length > 0;
11926
+ const hasToolsUsed = !isHuman && msg.toolsUsed && msg.toolsUsed.length > 0;
11927
+ if (!text && !questionText && !hasResolutions) return null;
11566
11928
  const isLatest = i === messages.length - 1;
11567
11929
  return /* @__PURE__ */ jsxs12(
11568
11930
  "div",
@@ -11595,7 +11957,21 @@ function ThreadPanel({
11595
11957
  marginTop: text ? 4 : 0,
11596
11958
  lineHeight: 1.5,
11597
11959
  wordBreak: "break-word"
11598
- }, children: renderMarkdown(questionText) })
11960
+ }, children: renderMarkdown(questionText) }),
11961
+ (hasToolsUsed || hasResolutions) && /* @__PURE__ */ jsxs12("div", { style: {
11962
+ marginTop: text || questionText ? 6 : 0,
11963
+ padding: "4px 8px",
11964
+ backgroundColor: "rgba(0, 0, 0, 0.03)",
11965
+ fontSize: 11,
11966
+ lineHeight: 1.5,
11967
+ color: "#6b7280"
11968
+ }, children: [
11969
+ hasToolsUsed && /* @__PURE__ */ jsx15("div", { style: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 10 }, children: msg.toolsUsed.map((t, j) => /* @__PURE__ */ jsx15("div", { children: t }, j)) }),
11970
+ hasResolutions && msg.resolutions.map((r, j) => /* @__PURE__ */ jsxs12("div", { style: { marginTop: hasToolsUsed ? 4 : 0 }, children: [
11971
+ /* @__PURE__ */ jsx15("span", { style: { color: r.status === "resolved" ? "#10b981" : "#f59e0b" }, children: r.status === "resolved" ? "Done" : "Needs review" }),
11972
+ r.summary ? ` \u2014 ${r.summary}` : ""
11973
+ ] }, j))
11974
+ ] })
11599
11975
  ]
11600
11976
  },
11601
11977
  `${msg.jobId}-${i}`
@@ -11692,39 +12068,102 @@ function ThreadPanel({
11692
12068
  /* @__PURE__ */ jsx15("span", { style: { fontWeight: 600, fontSize: 11, color: "#6b7280" }, children: "Claude Code" }),
11693
12069
  /* @__PURE__ */ jsx15("span", { style: { marginLeft: "auto" }, children: /* @__PURE__ */ jsx15(ThinkingBadge, { color: accentColor }) })
11694
12070
  ] }),
11695
- toolStep && /* @__PURE__ */ jsx15("div", { style: {
11696
- paddingLeft: 12,
11697
- marginBottom: 4,
11698
- fontSize: 11,
11699
- color: "#9ca3af",
11700
- fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace"
11701
- }, children: toolStep }),
11702
- thinkingText && /* @__PURE__ */ jsx15("div", { style: {
11703
- paddingLeft: 12,
11704
- marginBottom: 4,
11705
- fontSize: 11,
11706
- color: "#9ca3af",
11707
- fontStyle: "italic",
11708
- lineHeight: 1.4,
11709
- whiteSpace: "pre-wrap",
11710
- wordBreak: "break-word",
11711
- maxHeight: 120,
11712
- overflowY: "auto"
11713
- }, children: thinkingText }),
11714
- responseText && /* @__PURE__ */ jsx15("div", { style: {
11715
- paddingLeft: 12,
11716
- lineHeight: 1.5,
11717
- wordBreak: "break-word"
11718
- }, children: renderMarkdown(responseText) })
12071
+ streamSegments.map((seg, i) => {
12072
+ if (seg.kind === "tool") {
12073
+ return /* @__PURE__ */ jsx15(
12074
+ "div",
12075
+ {
12076
+ style: {
12077
+ paddingLeft: 12,
12078
+ fontSize: 11,
12079
+ color: "#9ca3af",
12080
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
12081
+ lineHeight: 1.6
12082
+ },
12083
+ children: seg.label
12084
+ },
12085
+ i
12086
+ );
12087
+ }
12088
+ if (seg.kind === "thinking") {
12089
+ return /* @__PURE__ */ jsx15(
12090
+ "div",
12091
+ {
12092
+ style: {
12093
+ paddingLeft: 12,
12094
+ marginTop: 2,
12095
+ marginBottom: 2,
12096
+ fontSize: 11,
12097
+ color: "#9ca3af",
12098
+ fontStyle: "italic",
12099
+ lineHeight: 1.4,
12100
+ whiteSpace: "pre-wrap",
12101
+ wordBreak: "break-word",
12102
+ maxHeight: 80,
12103
+ overflowY: "auto"
12104
+ },
12105
+ children: seg.text
12106
+ },
12107
+ i
12108
+ );
12109
+ }
12110
+ const stripped = stripInternalTags(seg.text);
12111
+ if (!stripped) return null;
12112
+ return /* @__PURE__ */ jsx15(
12113
+ "div",
12114
+ {
12115
+ style: {
12116
+ paddingLeft: 12,
12117
+ marginTop: 2,
12118
+ marginBottom: 2,
12119
+ lineHeight: 1.5,
12120
+ wordBreak: "break-word"
12121
+ },
12122
+ children: renderMarkdown(stripped)
12123
+ },
12124
+ i
12125
+ );
12126
+ })
11719
12127
  ] })
11720
12128
  ] }),
11721
12129
  onReply && /* @__PURE__ */ jsxs12("div", { style: replyAreaStyle, children: [
12130
+ replyImages.length > 0 && /* @__PURE__ */ jsxs12("div", { style: {
12131
+ fontSize: 11,
12132
+ color: "#6b7280",
12133
+ marginBottom: 4,
12134
+ display: "flex",
12135
+ alignItems: "center",
12136
+ gap: 4
12137
+ }, children: [
12138
+ /* @__PURE__ */ jsxs12("span", { children: [
12139
+ replyImages.length,
12140
+ " image",
12141
+ replyImages.length > 1 ? "s" : "",
12142
+ " attached"
12143
+ ] }),
12144
+ /* @__PURE__ */ jsx15(
12145
+ "button",
12146
+ {
12147
+ onClick: () => setReplyImages([]),
12148
+ style: {
12149
+ background: "none",
12150
+ border: "none",
12151
+ cursor: "pointer",
12152
+ fontSize: 11,
12153
+ color: "#9ca3af",
12154
+ padding: "0 2px"
12155
+ },
12156
+ children: "\xD7"
12157
+ }
12158
+ )
12159
+ ] }),
11722
12160
  /* @__PURE__ */ jsx15(
11723
12161
  "textarea",
11724
12162
  {
11725
12163
  value: replyText,
11726
12164
  onChange: (e) => setReplyText(e.target.value),
11727
12165
  onKeyDown: handleKeyDown,
12166
+ onPaste: handlePaste,
11728
12167
  placeholder: "Reply... (Cmd+Enter to send)",
11729
12168
  style: {
11730
12169
  width: "100%",
@@ -11782,6 +12221,7 @@ var CLAUDE_MODELS = [
11782
12221
  ];
11783
12222
  var CODEX_MODELS = [
11784
12223
  { id: "gpt-5.3-codex", label: "Codex 5.3" },
12224
+ { id: "gpt-5.3-codex-spark", label: "Spark 5.3" },
11785
12225
  { id: "gpt-5.1-codex-mini", label: "Mini 5.1" }
11786
12226
  ];
11787
12227
  function equivalentModelIndex(fromProvider, toProvider, currentIndex) {
@@ -11797,6 +12237,7 @@ function PopmeltProvider({
11797
12237
  var _a, _b, _c, _d, _e;
11798
12238
  const [state, dispatch] = useAnnotationState();
11799
12239
  const bridge = useBridgeConnection(bridgeUrl);
12240
+ const annotationImagesRef = useRef10(/* @__PURE__ */ new Map());
11800
12241
  const [provider, setProvider] = useState16(() => {
11801
12242
  if (typeof window === "undefined") return "claude";
11802
12243
  return localStorage.getItem(PROVIDER_STORAGE_KEY) || "claude";
@@ -11826,7 +12267,7 @@ function PopmeltProvider({
11826
12267
  }, [availableProviders, provider]);
11827
12268
  const models = provider === "codex" ? CODEX_MODELS : CLAUDE_MODELS;
11828
12269
  const currentModel = (_a = models[modelIndex]) != null ? _a : models[0];
11829
- const handleProviderChange = useCallback8((newProvider) => {
12270
+ const handleProviderChange = useCallback9((newProvider) => {
11830
12271
  const oldProvider = provider;
11831
12272
  setProvider(newProvider);
11832
12273
  localStorage.setItem(PROVIDER_STORAGE_KEY, newProvider);
@@ -11834,7 +12275,7 @@ function PopmeltProvider({
11834
12275
  setModelIndex(newIndex);
11835
12276
  localStorage.setItem(MODEL_STORAGE_KEY, String(newIndex));
11836
12277
  }, [provider, modelIndex]);
11837
- const handleModelChange = useCallback8((newIndex) => {
12278
+ const handleModelChange = useCallback9((newIndex) => {
11838
12279
  setModelIndex(newIndex);
11839
12280
  localStorage.setItem(MODEL_STORAGE_KEY, String(newIndex));
11840
12281
  }, []);
@@ -12026,7 +12467,7 @@ function PopmeltProvider({
12026
12467
  setInFlightJobs({});
12027
12468
  }
12028
12469
  }, [bridge.status]);
12029
- const handleScreenshot = useCallback8(async () => {
12470
+ const handleScreenshot = useCallback9(async () => {
12030
12471
  const canvas = document.getElementById("devtools-canvas");
12031
12472
  if (!canvas) return false;
12032
12473
  const blobs = await captureScreenshot(document.body, canvas, state.annotations);
@@ -12037,7 +12478,7 @@ function PopmeltProvider({
12037
12478
  }
12038
12479
  return success;
12039
12480
  }, [state.annotations, state.styleModifications, dispatch]);
12040
- const handlePlanGoal = useCallback8(async (goal) => {
12481
+ const handlePlanGoal = useCallback9(async (goal) => {
12041
12482
  var _a2;
12042
12483
  try {
12043
12484
  const activeAnnotations = state.annotations.filter((a) => {
@@ -12085,7 +12526,7 @@ function PopmeltProvider({
12085
12526
  return false;
12086
12527
  }
12087
12528
  }, [bridgeUrl, provider, currentModel.id, state.annotations, state.styleModifications, state.inspectedElement, dispatch]);
12088
- const materializePlan = useCallback8(async (tasks) => {
12529
+ const materializePlan = useCallback9(async (tasks) => {
12089
12530
  const planId = activePlan == null ? void 0 : activePlan.planId;
12090
12531
  if (!planId) return;
12091
12532
  const baseHueMatch = state.activeColor.match(/oklch\([^)]*\s+([\d.]+)\s*\)/);
@@ -12130,7 +12571,11 @@ function PopmeltProvider({
12130
12571
  }
12131
12572
  }
12132
12573
  }, [activePlan == null ? void 0 : activePlan.planId, state.activeColor, dispatch]);
12133
- const handleSendToClaude = useCallback8(async () => {
12574
+ const handleAttachImages = useCallback9((annotationId, images) => {
12575
+ const existing = annotationImagesRef.current.get(annotationId) || [];
12576
+ annotationImagesRef.current.set(annotationId, [...existing, ...images]);
12577
+ }, []);
12578
+ const handleSendToClaude = useCallback9(async () => {
12134
12579
  const canvas = document.getElementById("devtools-canvas");
12135
12580
  if (!canvas) return false;
12136
12581
  const activeAnnotations = state.annotations.filter((a) => {
@@ -12146,9 +12591,37 @@ function PopmeltProvider({
12146
12591
  if (!stitchedBlob) return false;
12147
12592
  const feedbackData = buildFeedbackData(activeAnnotations, state.styleModifications);
12148
12593
  const feedbackJson = JSON.stringify(feedbackData);
12594
+ const pastedImages = /* @__PURE__ */ new Map();
12595
+ for (const ann of activeAnnotations) {
12596
+ const blobs2 = annotationImagesRef.current.get(ann.id);
12597
+ if (blobs2 && blobs2.length > 0) {
12598
+ pastedImages.set(ann.id, blobs2);
12599
+ }
12600
+ if (ann.groupId) {
12601
+ for (const mate of activeAnnotations) {
12602
+ if (mate.groupId === ann.groupId && mate.id !== ann.id) {
12603
+ const mateBlobs = annotationImagesRef.current.get(mate.id);
12604
+ if (mateBlobs && mateBlobs.length > 0) {
12605
+ pastedImages.set(mate.id, mateBlobs);
12606
+ }
12607
+ }
12608
+ }
12609
+ }
12610
+ }
12149
12611
  try {
12150
12612
  const hexColor = cssColorToHex(state.activeColor);
12151
- const { jobId, threadId: assignedThreadId } = await sendToBridge(stitchedBlob, feedbackJson, bridgeUrl, hexColor, provider, currentModel.id);
12613
+ const { jobId, threadId: assignedThreadId } = await sendToBridge(
12614
+ stitchedBlob,
12615
+ feedbackJson,
12616
+ bridgeUrl,
12617
+ hexColor,
12618
+ provider,
12619
+ currentModel.id,
12620
+ pastedImages.size > 0 ? pastedImages : void 0
12621
+ );
12622
+ for (const annId of pastedImages.keys()) {
12623
+ annotationImagesRef.current.delete(annId);
12624
+ }
12152
12625
  const sentAnnotationIds = activeAnnotations.map((a) => a.id);
12153
12626
  const sentStyleSelectors = state.styleModifications.filter((m) => !m.captured).map((m) => m.selector);
12154
12627
  setInFlightJobs((prev) => __spreadProps(__spreadValues({}, prev), {
@@ -12160,6 +12633,9 @@ function PopmeltProvider({
12160
12633
  }
12161
12634
  }));
12162
12635
  dispatch({ type: "MARK_CAPTURED" });
12636
+ if (assignedThreadId && sentAnnotationIds.length > 0) {
12637
+ dispatch({ type: "SET_ANNOTATION_THREAD", payload: { ids: sentAnnotationIds, threadId: assignedThreadId } });
12638
+ }
12163
12639
  const hueMatch = state.activeColor.match(/oklch\([^)]*\s+([\d.]+)\s*\)/);
12164
12640
  const currentHue = (hueMatch == null ? void 0 : hueMatch[1]) ? parseFloat(hueMatch[1]) : 29;
12165
12641
  const nextHue = (currentHue + 60) % 360;
@@ -12170,10 +12646,10 @@ function PopmeltProvider({
12170
12646
  return false;
12171
12647
  }
12172
12648
  }, [state.annotations, state.styleModifications, state.activeColor, dispatch, bridgeUrl, provider, currentModel.id]);
12173
- const handleReply = useCallback8(async (threadId, reply) => {
12649
+ const handleReply = useCallback9(async (threadId, reply, images) => {
12174
12650
  try {
12175
12651
  const hexColor = cssColorToHex(state.activeColor);
12176
- const { jobId } = await sendReplyToBridge(threadId, reply, bridgeUrl, hexColor, provider, currentModel.id);
12652
+ const { jobId } = await sendReplyToBridge(threadId, reply, bridgeUrl, hexColor, provider, currentModel.id, images);
12177
12653
  setInFlightJobs((prev) => __spreadProps(__spreadValues({}, prev), {
12178
12654
  [jobId]: {
12179
12655
  annotationIds: [],
@@ -12205,7 +12681,7 @@ function PopmeltProvider({
12205
12681
  console.error("[Pare] Failed to send reply:", err);
12206
12682
  }
12207
12683
  }, [state.activeColor, state.annotations, bridgeUrl, bridge.dismissQuestion, dispatch, provider, currentModel.id]);
12208
- const handleApprovePlan = useCallback8(async () => {
12684
+ const handleApprovePlan = useCallback9(async () => {
12209
12685
  if (!activePlan || !activePlan.planId) return;
12210
12686
  try {
12211
12687
  await approvePlan(activePlan.planId, bridgeUrl);
@@ -12278,7 +12754,7 @@ function PopmeltProvider({
12278
12754
  console.error("[Pare] Failed to approve plan:", err);
12279
12755
  }
12280
12756
  }, [activePlan, state.annotations, state.activeColor, bridgeUrl, provider, currentModel.id, dispatch]);
12281
- const handleDismissPlan = useCallback8(() => {
12757
+ const handleDismissPlan = useCallback9(() => {
12282
12758
  setActivePlan(null);
12283
12759
  if (activePlan == null ? void 0 : activePlan.planId) {
12284
12760
  const planAnnotations = state.annotations.filter((a) => a.planId === activePlan.planId);
@@ -12376,7 +12852,7 @@ function PopmeltProvider({
12376
12852
  const values = Object.values(inFlightJobs);
12377
12853
  return values.length > 0 ? values[values.length - 1].color : void 0;
12378
12854
  }, [bridge.activeJobId, inFlightJobs]);
12379
- const handleViewThread = useCallback8((threadId) => {
12855
+ const handleViewThread = useCallback9((threadId) => {
12380
12856
  setOpenThreadId(threadId);
12381
12857
  }, []);
12382
12858
  const threadActiveJobId = useMemo4(() => {
@@ -12389,7 +12865,7 @@ function PopmeltProvider({
12389
12865
  const [eventStreamVisible, setEventStreamVisible] = useState16(false);
12390
12866
  const [clearSignal, setClearSignal] = useState16(0);
12391
12867
  const hideTimeoutRef = useRef10(null);
12392
- const handleEventStreamHover = useCallback8((hovering) => {
12868
+ const handleEventStreamHover = useCallback9((hovering) => {
12393
12869
  if (hovering) {
12394
12870
  if (hideTimeoutRef.current) {
12395
12871
  clearTimeout(hideTimeoutRef.current);
@@ -12403,7 +12879,7 @@ function PopmeltProvider({
12403
12879
  }, 150);
12404
12880
  }
12405
12881
  }, []);
12406
- const handleClearEventStream = useCallback8(() => {
12882
+ const handleClearEventStream = useCallback9(() => {
12407
12883
  setClearSignal((s) => s + 1);
12408
12884
  bridge.clearEvents();
12409
12885
  }, [bridge.clearEvents]);
@@ -12430,9 +12906,9 @@ function PopmeltProvider({
12430
12906
  inFlightAnnotationIds,
12431
12907
  inFlightStyleSelectors,
12432
12908
  inFlightSelectorColors,
12909
+ onAttachImages: handleAttachImages,
12433
12910
  onReply: bridge.isConnected ? handleReply : void 0,
12434
12911
  onViewThread: bridge.isConnected ? handleViewThread : void 0,
12435
- isThreadPanelOpen: openThreadId !== null,
12436
12912
  activePlan
12437
12913
  }
12438
12914
  ),