reflet-sdk 0.3.5 → 0.4.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/feedback.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use client";
2
+ import { a as REFLET_Z_INDEX, t as RefletContext } from "./react-context-3_IbhIFB.js";
2
3
  import { t as Reflet } from "./client-BYuSKJw5.js";
3
- import { t as RefletContext } from "./react-context-DZw_HHjj.js";
4
4
  import { useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
6
  import { Slider } from "@base-ui/react/slider";
@@ -533,6 +533,7 @@ const MAX_DEPTH = 12;
533
533
  const MIN_PATH_SEGMENTS = 2;
534
534
  const MAX_SELECTOR_LENGTH = 600;
535
535
  const MAX_SOURCE_LOCATION_LENGTH = 400;
536
+ const MAX_SELECTION_COMMENT_LENGTH = 2e3;
536
537
  const TEST_ATTRIBUTES = [
537
538
  "data-testid",
538
539
  "data-test-id",
@@ -809,24 +810,23 @@ function collectPageContext(options) {
809
810
  }
810
811
  //#endregion
811
812
  //#region src/feedback/types.ts
812
- const SDK_VERSION = "0.3.5";
813
+ const SDK_VERSION = "0.4.0";
813
814
  const DEFAULT_WIDGET_LABELS = {
814
815
  annotateHint: "Draw on the screenshot to point at the problem.",
816
+ attachElement: "Attach this element",
815
817
  attachmentUploadFailed: "Your feedback was saved, but some screenshots are still pending. Retry to attach them.",
816
818
  attachScreenshot: "Attach a screenshot",
817
819
  back: "Back",
818
820
  cancel: "Cancel",
819
821
  captureFailed: "Screenshot unavailable. Try again or send without it.",
820
822
  capturing: "Taking screenshot…",
821
- categoryBug: "Bug",
822
- categoryIdea: "Idea",
823
- categoryLabel: "Feedback type",
824
- categoryQuestion: "Question",
825
823
  clearAnnotations: "Clear drawing",
826
824
  clearSelection: "Remove selected element",
827
825
  descriptionPlaceholder: "What would you like to share?",
828
826
  dismissForDays: "Hide for {days} days",
829
827
  done: "Done",
828
+ elementNote: "Comment on the picked element",
829
+ elementNotePlaceholder: "What is wrong with this?",
830
830
  emailInvalid: "Enter a valid email address.",
831
831
  emailLabel: "Email",
832
832
  emailPlaceholder: "you@company.com",
@@ -835,8 +835,9 @@ const DEFAULT_WIDGET_LABELS = {
835
835
  minimize: "Minimize feedback",
836
836
  moreOptions: "More options",
837
837
  moveFeedback: "Move feedback",
838
+ pickAnother: "Pick another",
838
839
  pickElement: "Point at an element",
839
- pickElementHint: "Click the element you are talking about — cancel with",
840
+ pickElementHint: "Pick the element you are talking about",
840
841
  recapture: "Retake",
841
842
  removeScreenshot: "Remove",
842
843
  resume: "Resume feedback",
@@ -861,7 +862,6 @@ const BOX_TOOLS = [
861
862
  "blur"
862
863
  ];
863
864
  const MIN_GESTURE_LENGTH = 8;
864
- const ARROW_HEAD_ANGLE = Math.PI / 7;
865
865
  const DEFAULT_PATH_TOLERANCE = 2.5;
866
866
  function normalizeRect(start, end) {
867
867
  return {
@@ -871,17 +871,6 @@ function normalizeRect(start, end) {
871
871
  y: Math.min(start.y, end.y)
872
872
  };
873
873
  }
874
- /** The two barbs closing the arrow tip, both `size` px away from `end`. */
875
- function arrowHead(start, end, size) {
876
- const angle = Math.atan2(end.y - start.y, end.x - start.x);
877
- if (Math.hypot(end.x - start.x, end.y - start.y) === 0) return [end, end];
878
- const barb = (offset) => ({
879
- x: end.x - size * Math.cos(angle + offset),
880
- y: end.y - size * Math.sin(angle + offset)
881
- });
882
- return [barb(-ARROW_HEAD_ANGLE), barb(ARROW_HEAD_ANGLE)];
883
- }
884
- /** Drops points a user cannot perceive, keeping the payload small. */
885
874
  function simplifyPath(points, tolerance = DEFAULT_PATH_TOLERANCE) {
886
875
  if (points.length < 3) return points;
887
876
  const [first, ...rest] = points;
@@ -932,7 +921,6 @@ function toWire(annotation) {
932
921
  y: start.y
933
922
  };
934
923
  }
935
- /** Serializes to the shape the Reflet API stores next to the screenshot. */
936
924
  function toWireAnnotations(annotations) {
937
925
  return annotations.filter((item) => !isDegenerate(item)).map(toWire);
938
926
  }
@@ -1457,9 +1445,41 @@ function AnnotationToolbar({ editor, labels, drawing }) {
1457
1445
  });
1458
1446
  }
1459
1447
  //#endregion
1448
+ //#region src/feedback/core/arrow-geometry.ts
1449
+ function arrowOutline(start, end) {
1450
+ const dx = end.x - start.x;
1451
+ const dy = end.y - start.y;
1452
+ const length = Math.hypot(dx, dy);
1453
+ if (length === 0) return {
1454
+ points: [],
1455
+ strokeWidth: 0
1456
+ };
1457
+ const shaftWidth = Math.min(20, Math.max(6, length * .025), length * .2);
1458
+ const headLength = Math.min(length * .45, shaftWidth * 3);
1459
+ const headHalfWidth = headLength * .55;
1460
+ const shaftLength = length - headLength;
1461
+ const pointOnArrow = (along, across) => ({
1462
+ x: start.x + (dx * along - dy * across) / length,
1463
+ y: start.y + (dy * along + dx * across) / length
1464
+ });
1465
+ return {
1466
+ points: [
1467
+ pointOnArrow(0, 0),
1468
+ pointOnArrow(shaftLength, -shaftWidth / 2),
1469
+ pointOnArrow(shaftLength, -headHalfWidth),
1470
+ {
1471
+ x: end.x,
1472
+ y: end.y
1473
+ },
1474
+ pointOnArrow(shaftLength, headHalfWidth),
1475
+ pointOnArrow(shaftLength, shaftWidth / 2)
1476
+ ],
1477
+ strokeWidth: Math.min(4, length * .02)
1478
+ };
1479
+ }
1480
+ //#endregion
1460
1481
  //#region src/feedback/core/annotation-renderer.ts
1461
1482
  const STROKE_WIDTH = 3;
1462
- const ARROW_HEAD_SIZE = 16;
1463
1483
  const HIGHLIGHT_ALPHA = .3;
1464
1484
  const REDACTION_FILL = "#111827";
1465
1485
  const PIXEL_BLOCK = 10;
@@ -1480,10 +1500,6 @@ function scaleRect(rect, scale) {
1480
1500
  function isCanvasElement(value) {
1481
1501
  return typeof HTMLCanvasElement !== "undefined" && value instanceof HTMLCanvasElement;
1482
1502
  }
1483
- /**
1484
- * Downsamples the region and paints it back enlarged, so redacted pixels are
1485
- * unrecoverable. Falls back to an opaque block wherever 2d canvases are absent.
1486
- */
1487
1503
  function redact(context, rect) {
1488
1504
  const source = context.canvas;
1489
1505
  const width = Math.max(1, Math.round(rect.width / PIXEL_BLOCK));
@@ -1503,18 +1519,17 @@ function redact(context, rect) {
1503
1519
  context.fillStyle = REDACTION_FILL;
1504
1520
  context.fillRect(rect.x, rect.y, rect.width, rect.height);
1505
1521
  }
1506
- function drawArrow(context, start, end, scale) {
1507
- context.beginPath();
1508
- context.moveTo(start.x, start.y);
1509
- context.lineTo(end.x, end.y);
1510
- context.stroke();
1511
- const [left, right] = arrowHead(start, end, ARROW_HEAD_SIZE * scale);
1522
+ function drawArrow(context, annotation, scale) {
1523
+ const { points, strokeWidth } = arrowOutline(annotation.start, annotation.end);
1524
+ const [first, ...rest] = points.map((point) => scalePoint(point, scale));
1525
+ if (!first) return;
1512
1526
  context.beginPath();
1513
- context.moveTo(end.x, end.y);
1514
- context.lineTo(left.x, left.y);
1515
- context.lineTo(right.x, right.y);
1527
+ context.moveTo(first.x, first.y);
1528
+ for (const point of rest) context.lineTo(point.x, point.y);
1516
1529
  context.closePath();
1530
+ context.lineWidth = strokeWidth * scale;
1517
1531
  context.fill();
1532
+ context.stroke();
1518
1533
  }
1519
1534
  function drawPen(context, points, scale) {
1520
1535
  const [first, ...rest] = points;
@@ -1562,7 +1577,7 @@ function drawAnnotation(context, annotation, scale) {
1562
1577
  drawPen(context, annotation.points ?? [], scale);
1563
1578
  break;
1564
1579
  case "arrow":
1565
- drawArrow(context, scalePoint(annotation.start, scale), scalePoint(annotation.end, scale), scale);
1580
+ drawArrow(context, annotation, scale);
1566
1581
  break;
1567
1582
  case "spotlight":
1568
1583
  drawSpotlight(context, rect);
@@ -1590,10 +1605,6 @@ function loadImage(url) {
1590
1605
  image.src = url;
1591
1606
  });
1592
1607
  }
1593
- /**
1594
- * Burns the annotations into a copy of the capture so reviewers and agents see
1595
- * the drawing without needing to replay the vector data.
1596
- */
1597
1608
  async function renderAnnotatedImage(source, annotations) {
1598
1609
  if (annotations.length === 0) return null;
1599
1610
  const canvas = document.createElement("canvas");
@@ -1944,7 +1955,8 @@ function ScreenshotCard({ item, labels, state }) {
1944
1955
  //#region src/feedback/ui/composer/attachments.tsx
1945
1956
  function SelectedElement({ labels, state }) {
1946
1957
  if (!state.selection) return null;
1947
- const component = state.selection.componentStack[0];
1958
+ const { comment, componentStack, label, selector, sourceLocation } = state.selection;
1959
+ const component = componentStack[0];
1948
1960
  return /* @__PURE__ */ jsxs("div", {
1949
1961
  className: "selection-chip glass",
1950
1962
  children: [/* @__PURE__ */ jsxs("button", {
@@ -1952,17 +1964,24 @@ function SelectedElement({ labels, state }) {
1952
1964
  className: "selection-label",
1953
1965
  disabled: state.isEditingDisabled || state.isCapturing,
1954
1966
  onClick: () => state.setStep("picking"),
1955
- title: state.selection.sourceLocation ?? state.selection.selector,
1967
+ title: comment ?? sourceLocation ?? selector,
1956
1968
  type: "button",
1957
- children: [state.elementCapture ? /* @__PURE__ */ jsx("img", {
1958
- alt: "",
1959
- height: 32,
1960
- src: state.elementCapture.objectUrl,
1961
- width: 32
1962
- }) : /* @__PURE__ */ jsx(TargetIcon, {}), /* @__PURE__ */ jsx("span", {
1963
- className: "truncate",
1964
- children: component ? `<${component}>` : state.selection.label
1965
- })]
1969
+ children: [
1970
+ state.elementCapture ? /* @__PURE__ */ jsx("img", {
1971
+ alt: "",
1972
+ height: 32,
1973
+ src: state.elementCapture.objectUrl,
1974
+ width: 32
1975
+ }) : /* @__PURE__ */ jsx(TargetIcon, {}),
1976
+ /* @__PURE__ */ jsx("span", {
1977
+ className: "selection-name",
1978
+ children: component ? `<${component}>` : label
1979
+ }),
1980
+ comment && /* @__PURE__ */ jsx("span", {
1981
+ className: "truncate",
1982
+ children: comment
1983
+ })
1984
+ ]
1966
1985
  }), /* @__PURE__ */ jsx("button", {
1967
1986
  "aria-label": labels.clearSelection,
1968
1987
  className: "icon-btn",
@@ -2092,34 +2111,12 @@ function ComposerOptions({ labels, state }) {
2092
2111
  }
2093
2112
  //#endregion
2094
2113
  //#region src/feedback/ui/composer/controls.tsx
2095
- const CATEGORY_LABELS = {
2096
- bug: "categoryBug",
2097
- idea: "categoryIdea",
2098
- question: "categoryQuestion"
2099
- };
2100
- function ComposerControls({ options, labels, state }) {
2114
+ function ComposerControls({ floatingControls, labels, state }) {
2101
2115
  return /* @__PURE__ */ jsxs("div", {
2102
2116
  className: "composer-toolbar",
2103
- children: [options.floatingControls, /* @__PURE__ */ jsxs("div", {
2117
+ children: [floatingControls, /* @__PURE__ */ jsxs("div", {
2104
2118
  className: "composer-controls glass",
2105
2119
  children: [
2106
- options.categories.length > 1 && /* @__PURE__ */ jsxs("span", {
2107
- className: "select-control",
2108
- children: [/* @__PURE__ */ jsx("select", {
2109
- "aria-label": labels.categoryLabel,
2110
- className: "category-select",
2111
- disabled: state.isEditingDisabled,
2112
- onChange: (event) => {
2113
- const category = options.categories.find((value) => value === event.target.value);
2114
- if (category) state.setCategory(category);
2115
- },
2116
- value: state.category,
2117
- children: options.categories.map((value) => /* @__PURE__ */ jsx("option", {
2118
- value,
2119
- children: labels[CATEGORY_LABELS[value]]
2120
- }, value))
2121
- }), /* @__PURE__ */ jsx(ChevronDownIcon, {})]
2122
- }),
2123
2120
  /* @__PURE__ */ jsx("button", {
2124
2121
  "aria-label": labels.attachScreenshot,
2125
2122
  className: "icon-btn",
@@ -2167,7 +2164,7 @@ function MessageInput({ labels, state }) {
2167
2164
  "aria-busy": state.isSubmitting,
2168
2165
  "aria-label": submitLabel,
2169
2166
  className: "submit",
2170
- disabled: state.isSubmitting || state.isCapturing || !state.message.trim(),
2167
+ disabled: state.isSubmitting || state.isCapturing || !state.canSubmit,
2171
2168
  title: submitLabel,
2172
2169
  type: "submit",
2173
2170
  children: state.isSubmitting ? /* @__PURE__ */ jsx("span", { className: "spinner" }) : /* @__PURE__ */ jsx(ArrowIcon, {})
@@ -2176,7 +2173,7 @@ function MessageInput({ labels, state }) {
2176
2173
  }
2177
2174
  //#endregion
2178
2175
  //#region src/feedback/ui/panel.tsx
2179
- function FeedbackPanel({ options, labels, state }) {
2176
+ function FeedbackPanel({ floatingControls, labels, state }) {
2180
2177
  const panelRef = useRef(null);
2181
2178
  useEffect(() => {
2182
2179
  panelRef.current?.focus();
@@ -2193,7 +2190,7 @@ function FeedbackPanel({ options, labels, state }) {
2193
2190
  ]
2194
2191
  }), /* @__PURE__ */ jsx("div", {
2195
2192
  className: "composer-toolbar",
2196
- children: options.floatingControls
2193
+ children: floatingControls
2197
2194
  })]
2198
2195
  });
2199
2196
  return /* @__PURE__ */ jsxs("section", {
@@ -2216,8 +2213,8 @@ function FeedbackPanel({ options, labels, state }) {
2216
2213
  state
2217
2214
  }),
2218
2215
  /* @__PURE__ */ jsx(ComposerControls, {
2216
+ floatingControls,
2219
2217
  labels,
2220
- options,
2221
2218
  state
2222
2219
  }),
2223
2220
  /* @__PURE__ */ jsx("div", {
@@ -2241,8 +2238,86 @@ function FeedbackPanel({ options, labels, state }) {
2241
2238
  });
2242
2239
  }
2243
2240
  //#endregion
2241
+ //#region src/feedback/ui/selection-outline.tsx
2242
+ /**
2243
+ * Keeps the picked element ringed while the reporter writes about it, so the
2244
+ * composer parked next to it is visibly about that element.
2245
+ */
2246
+ function SelectionOutline({ node }) {
2247
+ const boxRef = useRef(null);
2248
+ useEffect(() => {
2249
+ const box = boxRef.current;
2250
+ if (!(box && node)) return;
2251
+ let frame = 0;
2252
+ const draw = () => {
2253
+ frame = 0;
2254
+ const rect = node.getBoundingClientRect();
2255
+ const hidden = rect.width === 0 || rect.height === 0 || rect.bottom < 0 || rect.top > window.innerHeight;
2256
+ box.style.opacity = hidden ? "0" : "1";
2257
+ box.style.width = `${rect.width}px`;
2258
+ box.style.height = `${rect.height}px`;
2259
+ box.style.transform = `translate(${rect.left}px, ${rect.top}px)`;
2260
+ };
2261
+ const schedule = () => {
2262
+ frame ||= requestAnimationFrame(draw);
2263
+ };
2264
+ draw();
2265
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(draw);
2266
+ observer?.observe(node);
2267
+ window.addEventListener("scroll", schedule, {
2268
+ capture: true,
2269
+ passive: true
2270
+ });
2271
+ window.addEventListener("resize", schedule);
2272
+ return () => {
2273
+ cancelAnimationFrame(frame);
2274
+ observer?.disconnect();
2275
+ window.removeEventListener("scroll", schedule, true);
2276
+ window.removeEventListener("resize", schedule);
2277
+ };
2278
+ }, [node]);
2279
+ if (!node) return null;
2280
+ return /* @__PURE__ */ jsx("div", {
2281
+ "aria-hidden": "true",
2282
+ className: "selection-outline",
2283
+ ref: boxRef
2284
+ });
2285
+ }
2286
+ //#endregion
2287
+ //#region src/feedback/ui/visible-viewport.ts
2288
+ /**
2289
+ * The part of the page the reporter can actually see, in fixed-position
2290
+ * coordinates. On mobile the keyboard shrinks this without touching
2291
+ * `window.innerHeight`, so anything that must stay reachable measures here.
2292
+ */
2293
+ function visibleViewport() {
2294
+ const viewport = window.visualViewport;
2295
+ const left = viewport?.offsetLeft ?? 0;
2296
+ const top = viewport?.offsetTop ?? 0;
2297
+ const width = viewport?.width ?? window.innerWidth;
2298
+ const height = viewport?.height ?? window.innerHeight;
2299
+ return {
2300
+ bottom: window.innerHeight - top - height,
2301
+ height,
2302
+ left,
2303
+ right: window.innerWidth - left - width,
2304
+ top,
2305
+ width
2306
+ };
2307
+ }
2308
+ function onViewportChange(update) {
2309
+ window.addEventListener("resize", update);
2310
+ window.visualViewport?.addEventListener("resize", update);
2311
+ window.visualViewport?.addEventListener("scroll", update);
2312
+ return () => {
2313
+ window.removeEventListener("resize", update);
2314
+ window.visualViewport?.removeEventListener("resize", update);
2315
+ window.visualViewport?.removeEventListener("scroll", update);
2316
+ };
2317
+ }
2318
+ //#endregion
2244
2319
  //#region src/feedback/ui/floating/use-floating-position.ts
2245
- const EDGE_GAP = 12;
2320
+ const EDGE_GAP$1 = 12;
2246
2321
  const KEYBOARD_STEP = 24;
2247
2322
  const KEY_DIRECTIONS = {
2248
2323
  ArrowDown: {
@@ -2262,25 +2337,10 @@ const KEY_DIRECTIONS = {
2262
2337
  y: -24
2263
2338
  }
2264
2339
  };
2265
- function visibleViewport() {
2266
- const viewport = window.visualViewport;
2267
- const left = viewport?.offsetLeft ?? 0;
2268
- const top = viewport?.offsetTop ?? 0;
2269
- const width = viewport?.width ?? window.innerWidth;
2270
- const height = viewport?.height ?? window.innerHeight;
2271
- return {
2272
- bottom: window.innerHeight - top - height,
2273
- height,
2274
- left,
2275
- right: window.innerWidth - left - width,
2276
- top,
2277
- width
2278
- };
2279
- }
2280
2340
  function clampPosition(point, bounds, viewport = visibleViewport()) {
2281
2341
  return {
2282
- x: Math.max(viewport.left + EDGE_GAP, Math.min(point.x, viewport.left + viewport.width - bounds.width - EDGE_GAP)),
2283
- y: Math.max(viewport.top + EDGE_GAP, Math.min(point.y, viewport.top + viewport.height - bounds.height - EDGE_GAP))
2342
+ x: Math.max(viewport.left + EDGE_GAP$1, Math.min(point.x, viewport.left + viewport.width - bounds.width - EDGE_GAP$1)),
2343
+ y: Math.max(viewport.top + EDGE_GAP$1, Math.min(point.y, viewport.top + viewport.height - bounds.height - EDGE_GAP$1))
2284
2344
  };
2285
2345
  }
2286
2346
  function useFloatingPosition(corner = "bottom-right") {
@@ -2309,17 +2369,12 @@ function useFloatingPosition(corner = "bottom-right") {
2309
2369
  keepInViewport();
2310
2370
  }, [keepInViewport]);
2311
2371
  useEffect(() => {
2312
- const resizeViewport = () => setViewport(visibleViewport());
2313
2372
  const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(keepInViewport);
2314
2373
  if (rootRef.current) observer?.observe(rootRef.current);
2315
- window.addEventListener("resize", resizeViewport);
2316
- window.visualViewport?.addEventListener("resize", resizeViewport);
2317
- window.visualViewport?.addEventListener("scroll", resizeViewport);
2374
+ const stopListening = onViewportChange(() => setViewport(visibleViewport()));
2318
2375
  return () => {
2319
2376
  observer?.disconnect();
2320
- window.removeEventListener("resize", resizeViewport);
2321
- window.visualViewport?.removeEventListener("resize", resizeViewport);
2322
- window.visualViewport?.removeEventListener("scroll", resizeViewport);
2377
+ stopListening();
2323
2378
  };
2324
2379
  }, [keepInViewport]);
2325
2380
  const onPointerDown = (event) => {
@@ -2369,10 +2424,10 @@ function useFloatingPosition(corner = "bottom-right") {
2369
2424
  }
2370
2425
  //#endregion
2371
2426
  //#region src/feedback/ui/floating/floating-widget.tsx
2372
- function FloatingWidget({ labels, options, state }) {
2427
+ function FloatingWidget({ labels, position, state }) {
2373
2428
  const [minimized, setMinimized] = useState(false);
2374
2429
  const launcherRef = useRef(null);
2375
- const floating = useFloatingPosition(options.position);
2430
+ const floating = useFloatingPosition(position);
2376
2431
  const showPanel = state.isOpen && !minimized;
2377
2432
  const minimize = () => {
2378
2433
  setMinimized(true);
@@ -2396,12 +2451,12 @@ function FloatingWidget({ labels, options, state }) {
2396
2451
  className: "root",
2397
2452
  "data-editing": state.isOpen && (state.step === "picking" || state.step === "annotate"),
2398
2453
  "data-moved": Boolean(floating.position),
2399
- "data-position": options.position,
2454
+ "data-position": position,
2400
2455
  ref: floating.rootRef,
2401
- children: [/* @__PURE__ */ jsx("style", { children: floating.styles }), showPanel ? /* @__PURE__ */ jsx(FeedbackPanel, {
2402
- labels,
2403
- options: {
2404
- categories: options.categories,
2456
+ children: [
2457
+ /* @__PURE__ */ jsx("style", { children: floating.styles }),
2458
+ showPanel && state.step === "compose" && /* @__PURE__ */ jsx(SelectionOutline, { node: state.selectedNode }),
2459
+ showPanel ? /* @__PURE__ */ jsx(FeedbackPanel, {
2405
2460
  floatingControls: /* @__PURE__ */ jsxs("div", {
2406
2461
  className: "floating-controls glass",
2407
2462
  children: [/* @__PURE__ */ jsx("button", {
@@ -2419,26 +2474,85 @@ function FloatingWidget({ labels, options, state }) {
2419
2474
  type: "button",
2420
2475
  children: /* @__PURE__ */ jsx(MinusIcon, {})
2421
2476
  })]
2422
- })
2423
- },
2424
- state
2425
- }) : /* @__PURE__ */ jsx(Launcher, {
2426
- buttonRef: launcherRef,
2427
- isOpen: false,
2428
- label: state.isOpen ? labels.resume : labels.trigger,
2429
- onClick: () => {
2430
- setMinimized(false);
2431
- if (!state.isOpen) state.open();
2432
- }
2433
- })]
2477
+ }),
2478
+ labels,
2479
+ state
2480
+ }) : /* @__PURE__ */ jsx(Launcher, {
2481
+ buttonRef: launcherRef,
2482
+ isOpen: false,
2483
+ label: state.isOpen ? labels.resume : labels.trigger,
2484
+ onClick: () => {
2485
+ setMinimized(false);
2486
+ if (!state.isOpen) state.open();
2487
+ }
2488
+ })
2489
+ ]
2434
2490
  });
2435
2491
  }
2436
2492
  //#endregion
2493
+ //#region src/feedback/ui/floating/element-walk.ts
2494
+ const ARROW_SIBLING = {
2495
+ ArrowLeft: "previousElementSibling",
2496
+ ArrowRight: "nextElementSibling"
2497
+ };
2498
+ const AIM_KEYS = [
2499
+ "Tab",
2500
+ "ArrowUp",
2501
+ "ArrowDown",
2502
+ "ArrowLeft",
2503
+ "ArrowRight"
2504
+ ];
2505
+ function isWidgetOwned(element) {
2506
+ return element.closest(`[${WIDGET_MARKER}]`) !== null;
2507
+ }
2508
+ function isPickable(element) {
2509
+ if (!element || element === document.documentElement || isWidgetOwned(element)) return false;
2510
+ const rect = element.getBoundingClientRect();
2511
+ return rect.width > 0 && rect.height > 0;
2512
+ }
2513
+ function pickableElements() {
2514
+ return Array.from(document.body.querySelectorAll("*")).filter(isPickable);
2515
+ }
2516
+ function documentStep(from, backwards) {
2517
+ const all = pickableElements();
2518
+ const index = all.indexOf(from);
2519
+ if (all.length === 0) return null;
2520
+ if (index === -1) return all[0] ?? null;
2521
+ return all[(index + (backwards ? -1 : 1) + all.length) % all.length] ?? null;
2522
+ }
2523
+ function climb(from) {
2524
+ let candidate = from.parentElement;
2525
+ while (candidate && !isPickable(candidate)) candidate = candidate.parentElement;
2526
+ return candidate;
2527
+ }
2528
+ function descend(from) {
2529
+ let candidate = from.firstElementChild;
2530
+ while (candidate && !isPickable(candidate)) candidate = candidate.nextElementSibling;
2531
+ return candidate;
2532
+ }
2533
+ function sibling(from, direction) {
2534
+ let candidate = from[direction];
2535
+ while (candidate && !isPickable(candidate)) candidate = candidate[direction];
2536
+ return candidate;
2537
+ }
2538
+ function stepAim(from, key, backwards) {
2539
+ if (!(from && isPickable(from))) return pickableElements()[0] ?? null;
2540
+ if (key === "Tab") return documentStep(from, backwards);
2541
+ if (key === "ArrowUp") return climb(from);
2542
+ if (key === "ArrowDown") return descend(from);
2543
+ const direction = ARROW_SIBLING[key];
2544
+ return direction ? sibling(from, direction) : null;
2545
+ }
2546
+ //#endregion
2437
2547
  //#region src/feedback/ui/picker.tsx
2438
2548
  const LABEL_HEIGHT = 24;
2439
2549
  const LABEL_GAP = 6;
2440
- function isWidgetOwned(element) {
2441
- return element.closest(`[${WIDGET_MARKER}]`) !== null;
2550
+ const NOTE_WIDTH = 300;
2551
+ const NOTE_GAP = 10;
2552
+ const EDGE_GAP = 12;
2553
+ /** Events raised by the picker's own note card must reach it, never be swallowed. */
2554
+ function targetsWidget(event) {
2555
+ return event.composedPath().some((node) => node instanceof Element && isWidgetOwned(node));
2442
2556
  }
2443
2557
  function elementUnder(x, y) {
2444
2558
  const element = document.elementFromPoint(x, y);
@@ -2455,233 +2569,298 @@ function describeTarget(element) {
2455
2569
  };
2456
2570
  }
2457
2571
  /**
2572
+ * Pins the note card to the element: below it, flipped above when the visible
2573
+ * viewport — what is left of the screen once a mobile keyboard is up — has no
2574
+ * room there. Measures against the part of the element actually on screen, so
2575
+ * picking a full-height section still leaves the card in view.
2576
+ */
2577
+ function noteStyle(rect, view) {
2578
+ const left = Math.min(Math.max(view.left + EDGE_GAP, rect.left), Math.max(view.left + EDGE_GAP, view.left + view.width - NOTE_WIDTH - EDGE_GAP));
2579
+ const visibleTop = Math.max(rect.top, view.top + EDGE_GAP);
2580
+ const visibleBottom = Math.min(rect.bottom, view.top + view.height - EDGE_GAP);
2581
+ if (view.top + view.height - visibleBottom > visibleTop - view.top) return {
2582
+ left,
2583
+ top: visibleBottom + NOTE_GAP
2584
+ };
2585
+ return {
2586
+ bottom: window.innerHeight - visibleTop + NOTE_GAP,
2587
+ left
2588
+ };
2589
+ }
2590
+ /**
2458
2591
  * Points at any element in the page the way a coding agent needs it: the React
2459
- * component that owns it plus the page region it lives in.
2592
+ * component that owns it plus the page region it lives in. Picking never
2593
+ * commits on its own — it pins a note card to the element, so the reporter
2594
+ * writes about that element right there and confirms. Aiming stays live until
2595
+ * then, which is what makes the flow usable without hover, on touch.
2460
2596
  */
2461
- function ElementPicker({ hint, onCancel, onPick }) {
2597
+ function ElementPicker({ labels, onCancel, onPick }) {
2462
2598
  const [target, setTarget] = useState(null);
2599
+ const [pinned, setPinned] = useState(null);
2600
+ const [note, setNote] = useState("");
2601
+ const noteRef = useRef(null);
2602
+ const pinnedRef = useRef(false);
2603
+ const [view, setView] = useState(visibleViewport);
2604
+ const aimRef = useRef(null);
2605
+ useEffect(() => onViewportChange(() => setView(visibleViewport())), []);
2606
+ useEffect(() => {
2607
+ pinnedRef.current = pinned !== null;
2608
+ if (pinned) noteRef.current?.focus({ preventScroll: true });
2609
+ }, [pinned]);
2610
+ useEffect(() => {
2611
+ aimRef.current = target?.element ?? null;
2612
+ }, [target]);
2463
2613
  useEffect(() => {
2464
2614
  const swallow = (event) => {
2615
+ if (targetsWidget(event)) return;
2465
2616
  event.preventDefault();
2466
2617
  event.stopPropagation();
2467
2618
  };
2468
- const onMove = (event) => {
2469
- const element = elementUnder(event.clientX, event.clientY);
2470
- if (!element) {
2471
- setTarget(null);
2472
- return;
2473
- }
2619
+ const aimAt = (x, y) => {
2620
+ const element = elementUnder(x, y);
2621
+ if (!element) return;
2474
2622
  setTarget((current) => current?.element === element ? {
2475
2623
  ...current,
2476
2624
  rect: element.getBoundingClientRect()
2477
2625
  } : describeTarget(element));
2478
2626
  };
2479
- const onClick = (event) => {
2627
+ const onMove = (event) => {
2628
+ if (pinnedRef.current || targetsWidget(event)) return;
2629
+ aimAt(event.clientX, event.clientY);
2630
+ };
2631
+ const onPointerDown = (event) => {
2632
+ if (targetsWidget(event)) return;
2633
+ swallow(event);
2634
+ if (!pinnedRef.current) aimAt(event.clientX, event.clientY);
2635
+ };
2636
+ const onPointerUp = (event) => {
2637
+ if (targetsWidget(event)) return;
2480
2638
  swallow(event);
2639
+ if (pinnedRef.current) return;
2481
2640
  const element = elementUnder(event.clientX, event.clientY);
2482
- if (element) onPick(element);
2641
+ if (element) setPinned(describeTarget(element));
2642
+ };
2643
+ const moveAim = (key, backwards) => {
2644
+ const next = stepAim(aimRef.current, key, backwards);
2645
+ if (!next) return;
2646
+ next.scrollIntoView({
2647
+ block: "nearest",
2648
+ inline: "nearest"
2649
+ });
2650
+ aimRef.current = next;
2651
+ setTarget(describeTarget(next));
2652
+ };
2653
+ const onEscape = () => {
2654
+ if (pinnedRef.current) {
2655
+ setPinned(null);
2656
+ setNote("");
2657
+ return;
2658
+ }
2659
+ onCancel();
2483
2660
  };
2484
2661
  const onKeyDown = (event) => {
2485
2662
  if (event.key === "Escape") {
2486
2663
  swallow(event);
2487
- onCancel();
2664
+ onEscape();
2665
+ return;
2666
+ }
2667
+ if (pinnedRef.current || targetsWidget(event)) return;
2668
+ if (event.key === "Enter") {
2669
+ swallow(event);
2670
+ if (aimRef.current) setPinned(describeTarget(aimRef.current));
2671
+ return;
2488
2672
  }
2673
+ if (!AIM_KEYS.includes(event.key)) return;
2674
+ swallow(event);
2675
+ moveAim(event.key, event.shiftKey);
2676
+ };
2677
+ const onScroll = () => {
2678
+ setTarget((current) => current ? {
2679
+ ...current,
2680
+ rect: current.element.getBoundingClientRect()
2681
+ } : current);
2682
+ setPinned((current) => current ? {
2683
+ ...current,
2684
+ rect: current.element.getBoundingClientRect()
2685
+ } : current);
2489
2686
  };
2490
2687
  document.addEventListener("pointermove", onMove, true);
2491
- document.addEventListener("pointerdown", swallow, true);
2688
+ document.addEventListener("pointerdown", onPointerDown, true);
2689
+ document.addEventListener("pointerup", onPointerUp, true);
2492
2690
  document.addEventListener("mousedown", swallow, true);
2493
2691
  document.addEventListener("mouseup", swallow, true);
2494
- document.addEventListener("click", onClick, true);
2692
+ document.addEventListener("click", swallow, true);
2495
2693
  document.addEventListener("keydown", onKeyDown, true);
2694
+ window.addEventListener("scroll", onScroll, {
2695
+ capture: true,
2696
+ passive: true
2697
+ });
2496
2698
  const previousCursor = document.body.style.cursor;
2497
2699
  document.body.style.cursor = "crosshair";
2498
2700
  return () => {
2499
2701
  document.removeEventListener("pointermove", onMove, true);
2500
- document.removeEventListener("pointerdown", swallow, true);
2702
+ document.removeEventListener("pointerdown", onPointerDown, true);
2703
+ document.removeEventListener("pointerup", onPointerUp, true);
2501
2704
  document.removeEventListener("mousedown", swallow, true);
2502
2705
  document.removeEventListener("mouseup", swallow, true);
2503
- document.removeEventListener("click", onClick, true);
2706
+ document.removeEventListener("click", swallow, true);
2504
2707
  document.removeEventListener("keydown", onKeyDown, true);
2708
+ window.removeEventListener("scroll", onScroll, true);
2505
2709
  document.body.style.cursor = previousCursor;
2506
2710
  };
2507
- }, [onCancel, onPick]);
2508
- const rect = target?.rect;
2711
+ }, [onCancel]);
2712
+ const rect = (pinned ?? target)?.rect;
2509
2713
  const labelAbove = rect ? rect.top > 30 : true;
2510
2714
  return /* @__PURE__ */ jsxs("div", { children: [
2511
2715
  rect && /* @__PURE__ */ jsx("div", {
2512
2716
  className: "picker-box",
2717
+ "data-pinned": pinned !== null,
2513
2718
  style: {
2514
2719
  height: rect.height,
2515
- left: rect.left,
2516
- top: rect.top,
2720
+ translate: `${rect.left}px ${rect.top}px`,
2517
2721
  width: rect.width
2518
2722
  }
2519
2723
  }),
2520
- rect && target && /* @__PURE__ */ jsxs("div", {
2724
+ rect && target && !pinned && /* @__PURE__ */ jsxs("div", {
2521
2725
  className: "picker-label",
2522
- style: {
2523
- left: Math.max(4, rect.left),
2524
- top: labelAbove ? rect.top - LABEL_HEIGHT - LABEL_GAP : rect.bottom + LABEL_GAP
2525
- },
2726
+ style: { translate: `${Math.max(4, rect.left)}px ${labelAbove ? rect.top - LABEL_HEIGHT - LABEL_GAP : rect.bottom + LABEL_GAP}px` },
2526
2727
  children: [/* @__PURE__ */ jsx("strong", { children: target.componentName ? `<${target.componentName}>` : target.label }), target.region && /* @__PURE__ */ jsx("span", { children: target.region })]
2527
2728
  }),
2528
- /* @__PURE__ */ jsxs("div", {
2529
- className: "picker-hint",
2729
+ pinned ? /* @__PURE__ */ jsxs("form", {
2730
+ className: "picker-note glass",
2731
+ onSubmit: (event) => {
2732
+ event.preventDefault();
2733
+ onPick(pinned.element, note.trim());
2734
+ },
2735
+ style: noteStyle(pinned.rect, view),
2530
2736
  children: [
2531
- hint,
2532
- " ",
2533
- /* @__PURE__ */ jsx("kbd", { children: "Esc" })
2737
+ /* @__PURE__ */ jsxs("p", {
2738
+ className: "picker-note-target",
2739
+ children: [/* @__PURE__ */ jsx("strong", { children: pinned.componentName ? `<${pinned.componentName}>` : pinned.label }), pinned.region && /* @__PURE__ */ jsx("span", { children: pinned.region })]
2740
+ }),
2741
+ /* @__PURE__ */ jsx("textarea", {
2742
+ "aria-label": labels.elementNote,
2743
+ maxLength: MAX_SELECTION_COMMENT_LENGTH,
2744
+ onChange: (event) => setNote(event.target.value),
2745
+ onKeyDown: (event) => {
2746
+ if (event.key === "Enter" && !event.shiftKey) {
2747
+ event.preventDefault();
2748
+ onPick(pinned.element, note.trim());
2749
+ }
2750
+ },
2751
+ placeholder: labels.elementNotePlaceholder,
2752
+ ref: noteRef,
2753
+ rows: 2,
2754
+ value: note
2755
+ }),
2756
+ /* @__PURE__ */ jsxs("div", {
2757
+ className: "picker-note-actions",
2758
+ children: [/* @__PURE__ */ jsx("button", {
2759
+ className: "picker-cancel",
2760
+ onClick: () => {
2761
+ setPinned(null);
2762
+ setNote("");
2763
+ },
2764
+ type: "button",
2765
+ children: labels.pickAnother
2766
+ }), /* @__PURE__ */ jsx("button", {
2767
+ "aria-label": labels.attachElement,
2768
+ className: "submit",
2769
+ title: labels.attachElement,
2770
+ type: "submit",
2771
+ children: /* @__PURE__ */ jsx(ArrowIcon, {})
2772
+ })]
2773
+ })
2534
2774
  ]
2775
+ }) : /* @__PURE__ */ jsxs("div", {
2776
+ className: "picker-hint",
2777
+ children: [/* @__PURE__ */ jsxs("span", {
2778
+ className: "picker-instruction",
2779
+ children: [
2780
+ labels.pickElementHint,
2781
+ " ",
2782
+ /* @__PURE__ */ jsx("kbd", { children: "Tab" }),
2783
+ /* @__PURE__ */ jsx("kbd", { children: "Enter" }),
2784
+ /* @__PURE__ */ jsx("kbd", { children: "Esc" })
2785
+ ]
2786
+ }), /* @__PURE__ */ jsx("button", {
2787
+ className: "picker-cancel",
2788
+ onClick: onCancel,
2789
+ type: "button",
2790
+ children: labels.cancel
2791
+ })]
2535
2792
  })
2536
2793
  ] });
2537
2794
  }
2538
2795
  //#endregion
2539
- //#region src/feedback/ui/styles.ts
2540
- const WIDGET_STYLES = `
2541
- :host {
2542
- --rf-offset: 20px;
2543
- --rf-bg: #ffffff;
2544
- --rf-bg-subtle: rgb(55 53 47 / 4%);
2545
- --rf-bg-hover: rgb(55 53 47 / 7%);
2546
- --rf-border: rgb(55 53 47 / 9%);
2547
- --rf-text: #37352f;
2548
- --rf-text-muted: rgb(55 53 47 / 68%);
2549
- --rf-primary: #37352f;
2550
- --rf-primary-text: #ffffff;
2551
- --rf-accent: #2383e2;
2552
- --rf-danger: #d44c47;
2553
- --rf-success: #448361;
2554
- --rf-radius: 20px;
2555
- --rf-pill: 999px;
2556
- --rf-width: 360px;
2557
- --rf-glass: rgb(255 255 255 / 72%);
2558
- --rf-glass-highlight: rgb(255 255 255 / 90%);
2559
- --rf-glass-stroke: rgb(255 255 255 / 70%);
2560
- --rf-radius-sm: 8px;
2561
- --rf-shadow-inset: inset 0 0 0 1px var(--rf-border);
2562
- --rf-shadow-low: 0 0 0 1px rgb(15 15 15 / 4%), 0 1px 2px rgb(15 15 15 / 6%), 0 4px 10px -4px rgb(15 15 15 / 10%);
2563
- --rf-shadow: 0 0 0 1px rgb(15 15 15 / 4%), 0 2px 5px -1px rgb(15 15 15 / 7%), 0 12px 32px -8px rgb(15 15 15 / 16%);
2564
- --rf-ease: cubic-bezier(0.16, 1, 0.3, 1);
2565
- color-scheme: light;
2566
- all: initial;
2567
- font-family: var(--rf-font, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif);
2568
- }
2796
+ //#region src/feedback/ui/annotation/styles.ts
2797
+ const ANNOTATOR_STYLES = `
2798
+ .overlay { margin: 0; border: 0; padding: 0; width: 100vw; max-width: none; height: 100dvh; max-height: none; position: fixed; inset: 0; z-index: ${REFLET_Z_INDEX.annotatorOverlay}; display: flex; flex-direction: column; background: transparent; color: var(--rf-text); font: 13px/1.45 ui-sans-serif, system-ui, sans-serif; }
2799
+ .overlay::before { content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none; background: color-mix(in srgb, var(--rf-bg) 94%, transparent); animation: rf-editor-in 260ms var(--rf-ease); transition: opacity 260ms var(--rf-ease); }
2800
+ .overlay::backdrop { background: transparent; }
2801
+ @keyframes rf-editor-in { from { opacity: 0; } }
2802
+ .editor { flex: 1; position: relative; display: flex; align-items: center; justify-content: center; padding: 20px; min-height: 0; container-type: size; }
2803
+ .editor-image { width: min(100cqw, calc(100cqh * var(--rf-capture-ratio))); aspect-ratio: var(--rf-capture-ratio); flex: none; position: relative; }
2804
+ .editor-visual { position: absolute; inset: 0; border-radius: var(--rf-radius); box-shadow: var(--rf-shadow); transform-origin: top left; overflow: hidden; }
2805
+ .editor-visual img, .editor-visual canvas { position: absolute; inset: 0; width: 100%; height: 100%; border-radius: inherit; object-fit: cover; object-position: top center; outline: 1px solid var(--rf-hairline); outline-offset: -1px; }
2806
+ .editor-visual canvas { cursor: crosshair; touch-action: none; }
2807
+ .overlay[data-opening="true"] .editor { pointer-events: none; }
2808
+ .overlay[data-closing="true"] { pointer-events: none; }
2809
+ .overlay[data-closing="true"]::before { opacity: 0; }
2810
+ .overlay[data-closing="true"] .toolbar { opacity: 0; transform: translateY(8px); }
2569
2811
 
2570
- :host([data-theme="dark"]) {
2571
- --rf-bg: #202020;
2572
- --rf-bg-subtle: rgb(255 255 255 / 5%);
2573
- --rf-bg-hover: rgb(255 255 255 / 9%);
2574
- --rf-border: rgb(255 255 255 / 9%);
2575
- --rf-text: #ebeae8;
2576
- --rf-text-muted: rgb(255 255 255 / 68%);
2577
- --rf-glass: rgb(32 32 32 / 78%);
2578
- --rf-glass-highlight: rgb(255 255 255 / 14%);
2579
- --rf-glass-stroke: rgb(255 255 255 / 12%);
2580
- --rf-primary: #f1efec;
2581
- --rf-primary-text: #1c1c1b;
2582
- --rf-accent: #529cca;
2583
- --rf-danger: #eb5757;
2584
- --rf-success: #4dab74;
2585
- --rf-shadow-low: 0 0 0 1px rgb(255 255 255 / 6%), 0 1px 2px rgb(0 0 0 / 30%), 0 4px 10px -4px rgb(0 0 0 / 40%);
2586
- --rf-shadow: 0 0 0 1px rgb(255 255 255 / 7%), 0 2px 5px -1px rgb(0 0 0 / 35%), 0 14px 36px -10px rgb(0 0 0 / 55%);
2587
- color-scheme: dark;
2812
+ .toolbar { display: flex; align-items: center; justify-content: center; gap: 8px; flex-wrap: wrap; transition: opacity 180ms var(--rf-ease), transform 180ms var(--rf-ease); padding: 4px 12px max(16px, env(safe-area-inset-bottom)); }
2813
+ .toolbar .group { display: flex; align-items: center; gap: 2px; padding: 4px; border-radius: var(--rf-pill); }
2814
+ .tool[aria-pressed="true"], .tool[data-selected="true"] { background: var(--rf-primary); color: var(--rf-primary-text); }
2815
+ .color-picker { position: relative; }
2816
+ .color-picker summary { display: flex; justify-content: center; align-items: center; }
2817
+ .color-preview { width: 18px; height: 18px; border-radius: 50%; background: var(--annotation-color); box-shadow: inset 0 0 0 1px var(--rf-border); }
2818
+ .color-popover { --rf-color-width: 200px; position: absolute; bottom: calc(100% + 14px); left: clamp(calc(12px - var(--rf-color-anchor)), 0px, calc(100vw - var(--rf-color-anchor) - var(--rf-color-width) - 12px)); display: flex; align-items: center; gap: 6px; width: var(--rf-color-width); padding: 4px 4px 4px 14px; border-radius: var(--rf-pill); animation: rf-in 180ms var(--rf-ease); }
2819
+ .color-picker[data-placement="below"] .color-popover { top: calc(100% + 14px); bottom: auto; }
2820
+ .color-slider { flex: 1; min-width: 0; }
2821
+ .color-slider-control { display: flex; align-items: center; height: 40px; width: 100%; touch-action: none; user-select: none; cursor: pointer; }
2822
+ .color-slider-track { height: 12px; width: 100%; border-radius: var(--rf-pill); background: linear-gradient(to right, var(--rf-swatch-base), var(--annotation-base-color)); box-shadow: inset 0 0 0 1px var(--rf-border); }
2823
+ .color-slider-thumb { width: 16px; height: 16px; border: 2px solid var(--rf-swatch-base); border-radius: 50%; background: var(--annotation-color); box-shadow: var(--rf-shadow-low); transition: scale 160ms var(--rf-ease); }
2824
+ .color-slider-thumb[data-dragging] { scale: 1.12; }
2825
+ .color-slider-thumb:has(:focus-visible) { outline: 2px solid var(--rf-accent); outline-offset: 3px; }
2826
+ .color-spectrum { position: relative; display: flex; align-items: center; justify-content: center; flex: none; width: 40px; height: 40px; border-radius: 50%; cursor: pointer; }
2827
+ .color-spectrum::before { content: ""; width: 16px; height: 16px; border-radius: 50%; background: var(--rf-spectrum); box-shadow: inset 0 0 0 1px var(--rf-glass-highlight); }
2828
+ .color-spectrum:hover { background: var(--rf-bg-hover); }
2829
+ .color-spectrum button { position: absolute; inset: 0; width: 100%; height: 100%; border-radius: inherit; }
2830
+ .color-spectrum button:focus-visible { outline: 2px solid var(--rf-accent); outline-offset: 3px; }
2831
+ .color-spectrum input { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; pointer-events: none; }
2832
+ .done-btn { display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; padding: 0; border-radius: var(--rf-pill); color: var(--rf-primary-text); background: var(--rf-primary); }
2833
+ .drawing-menu { position: relative; }
2834
+ .drawing-popover { position: absolute; z-index: 2; right: 0; bottom: calc(100% + 12px); width: 200px; max-height: calc(50dvh - 68px); overflow-y: auto; padding: 6px; border-radius: var(--rf-radius); }
2835
+ .drawing-menu[data-placement="below"] .drawing-popover { top: calc(100% + 12px); bottom: auto; }
2836
+ .drawing-option { display: flex; align-items: center; width: 100%; min-height: 40px; gap: 10px; padding: 0 10px; border-radius: 12px; text-align: left; }
2837
+ .drawing-option:hover, .drawing-option[aria-pressed="true"] { background: var(--rf-bg-hover); }
2838
+ .drawing-divider { height: 1px; margin: 5px; background: var(--rf-border); }
2839
+ .editor canvas[data-tool="text"] { cursor: text; }
2840
+ .text-annotation-editor { position: fixed; z-index: 3; width: 240px; padding: 8px; border-radius: var(--rf-radius); }
2841
+ .text-annotation-editor:focus-within { outline: 2px solid var(--rf-accent); outline-offset: 2px; }
2842
+ .text-annotation-editor textarea { display: block; width: 100%; min-height: 64px; max-height: 112px; padding: 6px; border: 0; background: transparent; resize: vertical; font-size: 16px; line-height: 24px; }
2843
+ .text-annotation-editor textarea:focus { outline: none; }
2844
+ .text-annotation-actions { display: flex; align-items: center; justify-content: flex-end; gap: 4px; }
2845
+ @media (pointer: coarse) {
2846
+ .drawing-option { min-height: 44px; }
2847
+ .done-btn { width: 44px; height: 44px; }
2848
+ .color-popover { --rf-color-width: 232px; padding-left: 16px; }
2849
+ .color-slider-control, .color-spectrum { height: 44px; }
2850
+ .color-spectrum { width: 44px; }
2851
+ .color-slider-thumb, .color-spectrum::before { width: 18px; height: 18px; }
2852
+ .editor { padding: 8px; }
2853
+ .toolbar { gap: 6px; }
2854
+ .toolbar .group { padding: 3px; gap: 0; }
2588
2855
  }
2589
-
2590
- *, *::before, *::after { box-sizing: border-box; }
2591
-
2592
-
2593
- .root {
2594
- position: fixed;
2595
- z-index: 2147483000;
2596
- display: flex;
2597
- align-items: flex-end;
2598
- gap: 8px;
2599
- font: 13px/1.45 var(--rf-font, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif);
2600
- color: var(--rf-text);
2856
+ @media (pointer: coarse) and (max-width: 360px) {
2857
+ .toolbar { gap: 4px; padding-inline: 2px; }
2858
+ .toolbar .group { padding: 0; }
2601
2859
  }
2602
- .root:has(.panel) { width: min(var(--rf-width), calc(var(--rf-viewport-width, 100vw) - 24px)); }
2603
- .root[data-position$="right"] { right: calc(var(--rf-viewport-right, 0px) + max(12px, min(var(--rf-offset), calc((var(--rf-viewport-width, 100vw) - var(--rf-width)) / 2)))); }
2604
- .root[data-position$="left"] { left: calc(var(--rf-viewport-left, 0px) + max(12px, min(var(--rf-offset), calc((var(--rf-viewport-width, 100vw) - var(--rf-width)) / 2)))); }
2605
- .root[data-position^="bottom"] { bottom: calc(var(--rf-viewport-bottom, 0px) + max(var(--rf-offset), env(safe-area-inset-bottom))); }
2606
- .root[data-position^="top"] { top: calc(var(--rf-viewport-top, 0px) + max(var(--rf-offset), env(safe-area-inset-top))); align-items: flex-start; }
2607
- .root[data-position="bottom-right"] .panel { transform-origin: bottom right; }
2608
- .root[data-position="bottom-left"] .panel { transform-origin: bottom left; }
2609
- .root[data-position^="top"] .panel { transform-origin: top center; }
2610
- .root[data-editing="true"] { visibility: hidden; pointer-events: none; }
2611
- button, select, input, textarea { font: inherit; color: inherit; }
2612
- button { margin: 0; cursor: pointer; border: 0; background: none; }
2613
- button:disabled { cursor: not-allowed; opacity: .4; }
2614
- svg { flex-shrink: 0; }
2615
- :focus-visible { outline: 2px solid var(--rf-accent); outline-offset: 3px; }
2616
- .icon-btn, .tool {
2617
- display: inline-flex; align-items: center; justify-content: center;
2618
- width: 32px; height: 32px; flex: none; border-radius: var(--rf-pill); color: var(--rf-text-muted);
2619
- transition: background 160ms var(--rf-ease), color 160ms var(--rf-ease);
2620
- }
2621
- .icon-btn:hover, .tool:hover { background: var(--rf-bg-hover); color: var(--rf-text); }
2622
- .launcher {
2623
- display: inline-flex; align-items: center; gap: 7px; height: 40px; padding: 0 15px;
2624
- border-radius: var(--rf-pill); color: var(--rf-text);
2625
- font-weight: 500;
2626
- transition: transform 200ms var(--rf-ease), box-shadow 200ms var(--rf-ease);
2627
- }
2628
- .launcher:hover { transform: translateY(-2px); box-shadow: var(--rf-shadow); }
2629
- .launcher-icon { display: flex; width: 15px; height: 15px; }
2630
- .launcher-icon .on-close { display: none; }
2631
- .floating-controls {
2632
- display: flex; align-items: center; padding: 3px; flex: none;
2633
- border-radius: var(--rf-pill);
2634
- animation: rf-in 220ms var(--rf-ease);
2635
- }
2636
- .drag-handle { touch-action: none; cursor: grab; }
2637
- .drag-handle:active { cursor: grabbing; }
2638
- .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2639
- .ghost-btn { display: flex; align-items: center; gap: 8px; min-height: 32px; padding: 0 8px; border-radius: var(--rf-radius-sm); }
2640
- .ghost-btn:hover { background: var(--rf-bg-hover); }
2641
- .error { margin: 0; padding: 10px 14px; border-radius: var(--rf-radius); font-size: 12px; color: var(--rf-danger); }
2642
- .done { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 24px; text-align: center; }
2643
- .done svg { color: var(--rf-success); }
2644
- .done h2 { margin: 0; font-size: 14px; font-weight: 600; }
2645
- .done p { margin: 0; font-size: 12px; color: var(--rf-text-muted); }
2646
- .spinner { width: 14px; height: 14px; border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: rf-spin 700ms linear infinite; }
2647
- .hp { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
2648
- @keyframes rf-spin { to { transform: rotate(360deg); } }
2649
- @keyframes rf-in { from { opacity: 0; transform: translateY(5px) scale(.98); } }
2650
- @keyframes rf-fade { from { opacity: 0; } }
2651
- @media (pointer: coarse) {
2652
- .icon-btn, .tool { width: 44px; height: 44px; }
2653
- .launcher, .ghost-btn { min-height: 44px; }
2654
- }
2655
- .glass {
2656
- position: relative;
2657
- isolation: isolate;
2658
- border: 1px solid var(--rf-glass-stroke);
2659
- box-shadow: inset 0 1px 0 var(--rf-glass-highlight), var(--rf-shadow);
2660
- }
2661
- .glass::before {
2662
- content: "";
2663
- position: absolute;
2664
- inset: 0;
2665
- z-index: -1;
2666
- border-radius: inherit;
2667
- background: var(--rf-bg);
2668
- pointer-events: none;
2669
- }
2670
- @supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
2671
- .glass::before {
2672
- background: var(--rf-glass);
2673
- -webkit-backdrop-filter: blur(20px) saturate(1.6);
2674
- backdrop-filter: blur(20px) saturate(1.6);
2675
- }
2676
- }
2677
- @media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
2678
- .glass::before { background: var(--rf-bg); backdrop-filter: none; -webkit-backdrop-filter: none; }
2679
- }
2680
- .capture-halo { position: fixed; inset: 0; z-index: 2147482999; pointer-events: none; opacity: 0; transition: opacity 500ms var(--rf-ease); }
2681
- .capture-halo[data-active="true"] { opacity: 1; transition-duration: 140ms; }
2682
- .capture-halo::before { content: ""; position: absolute; inset: 0; border-radius: 20px; box-shadow: inset 14px 0 36px -16px var(--rf-accent), inset -14px 0 36px -16px var(--rf-accent), inset 0 0 4px 1px color-mix(in srgb, var(--rf-accent) 35%, transparent); }
2683
- .capture-halo::after { content: ""; position: absolute; inset: 0; background: linear-gradient(90deg, color-mix(in srgb, var(--rf-accent) 20%, transparent), transparent 5%, transparent 95%, color-mix(in srgb, var(--rf-accent) 20%, transparent)); }
2684
-
2860
+ `;
2861
+ //#endregion
2862
+ //#region src/feedback/ui/composer/styles.ts
2863
+ const COMPOSER_STYLES = `
2685
2864
  .panel { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 8px; outline: none; animation: rf-in 240ms var(--rf-ease); }
2686
2865
  .success-panel { border-radius: var(--rf-radius); }
2687
2866
  .composer { position: relative; display: flex; flex-direction: column; gap: 8px; }
@@ -2689,7 +2868,7 @@ svg { flex-shrink: 0; }
2689
2868
  .message-row textarea {
2690
2869
  flex: 1; width: 0; min-height: 36px; height: 36px; max-height: min(140px, 30dvh);
2691
2870
  margin: 0; padding: 8px 10px 8px 12px; border: 0; border-radius: 20px; resize: none;
2692
- background: transparent; line-height: 20px; transition: height 220ms var(--rf-ease);
2871
+ background: transparent; line-height: 20px;
2693
2872
  }
2694
2873
  .panel:focus-within .message-row[data-expanded="true"] textarea { height: 76px; }
2695
2874
  .message-row textarea::placeholder { color: var(--rf-text-muted); }
@@ -2701,11 +2880,6 @@ svg { flex-shrink: 0; }
2701
2880
  .submit:disabled { background: var(--rf-bg-hover); color: var(--rf-text-muted); opacity: .65; }
2702
2881
  .composer-toolbar { display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex-wrap: wrap; }
2703
2882
  .composer-controls { display: flex; align-items: center; gap: 2px; padding: 3px; border-radius: var(--rf-pill); }
2704
- .select-control { position: relative; display: flex; align-items: center; min-width: 0; }
2705
- .select-control > svg { position: absolute; right: 9px; width: 12px; height: 12px; pointer-events: none; color: var(--rf-text-muted); }
2706
- .category-select { appearance: none; min-width: 0; max-width: 124px; height: 32px; padding: 0 26px 0 12px; border: 0; border-radius: var(--rf-pill); color: var(--rf-text); background: transparent; cursor: pointer; text-overflow: ellipsis; }
2707
- .category-select:hover:hover { background: var(--rf-bg-hover); }
2708
- option { background: var(--rf-bg); color: var(--rf-text); }
2709
2883
  .composer-options { position: relative; }
2710
2884
  summary { cursor: pointer; list-style: none; }
2711
2885
  summary::-webkit-details-marker { display: none; }
@@ -2714,7 +2888,7 @@ summary::-webkit-details-marker { display: none; }
2714
2888
  .email-field { display: flex; flex-direction: column; gap: 6px; color: var(--rf-text-muted); font-size: 12px; }
2715
2889
  .email-field [role="alert"] { color: var(--rf-danger); }
2716
2890
  .email-field input { width: 100%; padding: 9px; border: 1px solid var(--rf-border); border-radius: var(--rf-radius-sm); background: var(--rf-bg-subtle); color: var(--rf-text); }
2717
- .dismiss-btn { min-height: 32px; text-align: left; padding: 0 8px; border-radius: var(--rf-radius-sm); color: var(--rf-text-muted); }
2891
+ .dismiss-btn { min-height: 40px; text-align: left; padding: 0 8px; border-radius: var(--rf-radius-sm); color: var(--rf-text-muted); }
2718
2892
  .dismiss-btn:hover { background: var(--rf-bg-hover); }
2719
2893
  .version-info { font-size: 10px; color: var(--rf-text-muted); padding: 4px 8px 0; border-top: 1px solid var(--rf-border); text-decoration: none; }
2720
2894
  .version-info:hover { color: var(--rf-text); }
@@ -2733,9 +2907,9 @@ summary::-webkit-details-marker { display: none; }
2733
2907
  .attachments:empty { display: none; }
2734
2908
  .screenshot-attachment, .attachment-skeleton { position: relative; flex: none; margin: 0; min-width: 0; width: 128px; height: 78px; padding: 3px; border-radius: var(--rf-radius); animation: rf-in 260ms var(--rf-ease); }
2735
2909
  .screenshot-preview { display: block; position: relative; width: 100%; height: 100%; padding: 0; overflow: hidden; border-radius: 16px; }
2736
- .screenshot-preview img, .screenshot-preview canvas { width: 100%; height: 100%; display: block; object-fit: cover; object-position: top center; }
2910
+ .screenshot-preview img, .screenshot-preview canvas { width: 100%; height: 100%; display: block; object-fit: cover; object-position: top center; outline: 1px solid var(--rf-hairline); outline-offset: -1px; }
2737
2911
  .screenshot-preview canvas { position: absolute; inset: 0; pointer-events: none; }
2738
- .attachment-caption { position: absolute; left: 5px; bottom: 5px; display: flex; align-items: center; gap: 4px; padding: 3px 6px; font-size: 10px; border-radius: var(--rf-pill); color: var(--rf-text); }
2912
+ .attachment-caption { position: absolute; left: 5px; bottom: 5px; display: flex; align-items: center; gap: 4px; padding: 3px 6px; font-size: 10px; font-variant-numeric: tabular-nums; border-radius: var(--rf-pill); color: var(--rf-text); }
2739
2913
  .attachment-caption svg { width: 12px; height: 12px; }
2740
2914
  .annotation-count { color: var(--rf-text); font-weight: 600; }
2741
2915
  .attachment-actions { position: absolute; right: 4px; bottom: -4px; display: flex; gap: 1px; padding: 1px; border-radius: var(--rf-pill); opacity: 0; pointer-events: none; transform: translateY(3px); transition: opacity 150ms var(--rf-ease), transform 150ms var(--rf-ease); }
@@ -2745,9 +2919,9 @@ summary::-webkit-details-marker { display: none; }
2745
2919
  .attachment-skeleton::after { content: ""; position: absolute; inset: 0; border-radius: inherit; background: var(--rf-bg-subtle); animation: rf-fade 800ms alternate infinite; }
2746
2920
  .selection-chip { display: flex; align-items: center; max-width: 100%; padding: 3px; border-radius: var(--rf-pill); }
2747
2921
  .selection-label { display: flex; align-items: center; min-width: 0; gap: 6px; padding: 0 6px; font-size: 11px; }
2748
- .selection-label img { object-fit: contain; border-radius: var(--rf-pill); }
2922
+ .selection-name { flex: none; font-weight: 600; color: var(--rf-accent); }
2923
+ .selection-label img { object-fit: contain; border-radius: var(--rf-pill); outline: 1px solid var(--rf-hairline); outline-offset: -1px; }
2749
2924
  @media (pointer: coarse) {
2750
- .category-select { height: 44px; max-width: 96px; }
2751
2925
  .message-row { border-radius: 28px; }
2752
2926
  .message-row textarea { font-size: 16px; min-height: 44px; height: 44px; padding-top: 12px; }
2753
2927
  .submit { width: 44px; height: 44px; }
@@ -2759,79 +2933,26 @@ summary::-webkit-details-marker { display: none; }
2759
2933
  .email-field input { font-size: 16px; }
2760
2934
  .dismiss-btn, .selection-label { min-height: 44px; }
2761
2935
  }
2762
-
2763
-
2764
- .overlay { margin: 0; border: 0; padding: 0; width: 100vw; max-width: none; height: 100dvh; max-height: none; position: fixed; inset: 0; z-index: 2147483001; display: flex; flex-direction: column; background: color-mix(in srgb, var(--rf-bg) 94%, transparent); animation: rf-editor-in 260ms var(--rf-ease); transition: background 260ms var(--rf-ease); color: var(--rf-text); font: 13px/1.45 ui-sans-serif, system-ui, sans-serif; }
2765
- .overlay::backdrop { background: transparent; }
2766
- @keyframes rf-editor-in { from { background: transparent; } }
2767
- .editor { flex: 1; position: relative; display: flex; align-items: center; justify-content: center; padding: 20px; min-height: 0; container-type: size; }
2768
- .editor-image { width: min(100cqw, calc(100cqh * var(--rf-capture-ratio))); aspect-ratio: var(--rf-capture-ratio); flex: none; position: relative; }
2769
- .editor-visual { position: absolute; inset: 0; border-radius: var(--rf-radius); box-shadow: var(--rf-shadow); transform-origin: top left; overflow: hidden; }
2770
- .editor-visual img, .editor-visual canvas { position: absolute; inset: 0; width: 100%; height: 100%; border-radius: inherit; object-fit: cover; object-position: top center; }
2771
- .editor-visual canvas { cursor: crosshair; touch-action: none; }
2772
- .overlay[data-opening="true"] .editor { pointer-events: none; }
2773
- .overlay[data-closing="true"] { pointer-events: none; background: transparent; }
2774
- .overlay[data-closing="true"] .toolbar { opacity: 0; transform: translateY(8px); }
2775
-
2776
- .toolbar { display: flex; align-items: center; justify-content: center; gap: 8px; flex-wrap: wrap; transition: opacity 180ms var(--rf-ease), transform 180ms var(--rf-ease); padding: 4px 12px max(16px, env(safe-area-inset-bottom)); }
2777
- .toolbar .group { display: flex; align-items: center; gap: 2px; padding: 4px; border-radius: var(--rf-pill); }
2778
- .tool[aria-pressed="true"], .tool[data-selected="true"] { background: var(--rf-primary); color: var(--rf-primary-text); }
2779
- .color-picker { position: relative; }
2780
- .color-picker summary { display: flex; justify-content: center; align-items: center; }
2781
- .color-preview { width: 18px; height: 18px; border-radius: 50%; background: var(--annotation-color); box-shadow: inset 0 0 0 1px var(--rf-border); }
2782
- .color-popover { --rf-color-width: 200px; position: absolute; bottom: calc(100% + 14px); left: clamp(calc(12px - var(--rf-color-anchor)), 0px, calc(100vw - var(--rf-color-anchor) - var(--rf-color-width) - 12px)); display: flex; align-items: center; gap: 6px; width: var(--rf-color-width); padding: 4px 4px 4px 14px; border-radius: var(--rf-pill); animation: rf-in 180ms var(--rf-ease); }
2783
- .color-picker[data-placement="below"] .color-popover { top: calc(100% + 14px); bottom: auto; }
2784
- .color-slider { flex: 1; min-width: 0; }
2785
- .color-slider-control { display: flex; align-items: center; height: 32px; width: 100%; touch-action: none; user-select: none; cursor: pointer; }
2786
- .color-slider-track { height: 12px; width: 100%; border-radius: var(--rf-pill); background: linear-gradient(to right, white, var(--annotation-base-color)); box-shadow: inset 0 0 0 1px var(--rf-border); }
2787
- .color-slider-thumb { width: 16px; height: 16px; border: 2px solid white; border-radius: 50%; background: var(--annotation-color); box-shadow: var(--rf-shadow-low); transition: scale 160ms var(--rf-ease); }
2788
- .color-slider-thumb[data-dragging] { scale: 1.12; }
2789
- .color-slider-thumb:has(:focus-visible), .color-spectrum:has(:focus-visible) { outline: 2px solid var(--rf-accent); outline-offset: 3px; }
2790
- .color-spectrum { position: relative; display: flex; align-items: center; justify-content: center; flex: none; width: 32px; height: 32px; border-radius: 50%; cursor: pointer; }
2791
- .color-spectrum::before { content: ""; width: 16px; height: 16px; border-radius: 50%; background: conic-gradient(#ff5757, #ffde59, #6bdb78, #4ac9fa, #8b5cf6, #ef73dc, #ff5757); box-shadow: inset 0 0 0 1px var(--rf-glass-highlight); }
2792
- .color-spectrum:hover { background: var(--rf-bg-hover); }
2793
- .color-spectrum button { position: absolute; inset: 0; width: 100%; height: 100%; border-radius: inherit; }
2794
- .color-spectrum button:focus-visible { outline: none; }
2795
- .color-spectrum input { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; pointer-events: none; }
2796
- .done-btn { display: flex; align-items: center; justify-content: center; width: 34px; height: 34px; padding: 0; border-radius: var(--rf-pill); color: var(--rf-primary-text); background: var(--rf-primary); }
2797
- .drawing-menu { position: relative; }
2798
- .drawing-popover { position: absolute; z-index: 2; right: 0; bottom: calc(100% + 12px); width: 200px; max-height: calc(50dvh - 68px); overflow-y: auto; padding: 6px; border-radius: var(--rf-radius); }
2799
- .drawing-menu[data-placement="below"] .drawing-popover { top: calc(100% + 12px); bottom: auto; }
2800
- .drawing-option { display: flex; align-items: center; width: 100%; min-height: 36px; gap: 10px; padding: 0 10px; border-radius: 12px; text-align: left; }
2801
- .drawing-option:hover, .drawing-option[aria-pressed="true"] { background: var(--rf-bg-hover); }
2802
- .drawing-divider { height: 1px; margin: 5px; background: var(--rf-border); }
2803
- .editor canvas[data-tool="text"] { cursor: text; }
2804
- .text-annotation-editor { position: fixed; z-index: 3; width: 240px; padding: 8px; border-radius: var(--rf-radius); }
2805
- .text-annotation-editor textarea { display: block; width: 100%; min-height: 64px; max-height: 112px; padding: 6px; border: 0; background: transparent; resize: vertical; font-size: 16px; line-height: 24px; }
2806
- .text-annotation-editor textarea:focus { outline: none; }
2807
- .text-annotation-actions { display: flex; align-items: center; justify-content: flex-end; gap: 4px; }
2808
- @media (pointer: coarse) {
2809
- .drawing-option { min-height: 44px; }
2810
- .done-btn { width: 44px; height: 44px; }
2811
- .color-popover { --rf-color-width: 232px; padding-left: 16px; }
2812
- .color-slider-control, .color-spectrum { height: 44px; }
2813
- .color-spectrum { width: 44px; }
2814
- .color-slider-thumb, .color-spectrum::before { width: 18px; height: 18px; }
2815
- .editor { padding: 8px; }
2816
- .toolbar { gap: 6px; }
2817
- .toolbar .group { padding: 3px; gap: 0; }
2818
- }
2819
- @media (pointer: coarse) and (max-width: 360px) {
2820
- .toolbar { gap: 4px; padding-inline: 2px; }
2821
- .toolbar .group { padding: 0; }
2822
- }
2823
-
2824
-
2936
+ `;
2937
+ //#endregion
2938
+ //#region src/feedback/ui/floating/picker-styles.ts
2939
+ const PICKER_STYLES = `
2825
2940
  .picker-box {
2826
2941
  position: fixed;
2942
+ top: 0;
2943
+ left: 0;
2827
2944
  pointer-events: none;
2828
2945
  border-radius: 5px;
2829
2946
  background: color-mix(in srgb, var(--rf-accent) 10%, transparent);
2830
2947
  box-shadow: 0 0 0 2px var(--rf-accent), 0 0 0 5px color-mix(in srgb, var(--rf-accent) 18%, transparent);
2831
- transition: top 130ms var(--rf-ease), left 130ms var(--rf-ease), width 130ms var(--rf-ease), height 130ms var(--rf-ease);
2948
+ will-change: translate;
2949
+ transition: translate 130ms var(--rf-ease);
2832
2950
  }
2951
+ .picker-box[data-pinned="true"] { transition: none; }
2833
2952
  .picker-label {
2834
2953
  position: fixed;
2954
+ top: 0;
2955
+ left: 0;
2835
2956
  pointer-events: none;
2836
2957
  display: inline-flex;
2837
2958
  align-items: center;
@@ -2841,32 +2962,45 @@ summary::-webkit-details-marker { display: none; }
2841
2962
  padding: 0 8px;
2842
2963
  font-size: 11.5px;
2843
2964
  font-weight: 500;
2844
- color: #ffffff;
2965
+ color: var(--rf-accent-text);
2845
2966
  background: var(--rf-accent);
2846
2967
  border-radius: 6px;
2847
- box-shadow: 0 4px 12px -3px rgb(15 15 15 / 35%);
2968
+ box-shadow: var(--rf-shadow-float);
2848
2969
  white-space: nowrap;
2849
2970
  overflow: hidden;
2850
- transition: top 130ms var(--rf-ease), left 130ms var(--rf-ease);
2971
+ will-change: translate;
2972
+ transition: translate 130ms var(--rf-ease);
2851
2973
  }
2852
2974
  .picker-label strong { flex: none; font-weight: 600; }
2853
2975
  .picker-label span { min-width: 0; overflow: hidden; text-overflow: ellipsis; opacity: 0.75; }
2976
+ .selection-outline {
2977
+ position: fixed;
2978
+ top: 0;
2979
+ left: 0;
2980
+ pointer-events: none;
2981
+ border-radius: 5px;
2982
+ background: color-mix(in srgb, var(--rf-accent) 7%, transparent);
2983
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--rf-accent) 70%, transparent), 0 0 0 5px color-mix(in srgb, var(--rf-accent) 14%, transparent);
2984
+ transition: opacity 160ms var(--rf-ease);
2985
+ }
2854
2986
  .picker-hint {
2855
2987
  position: fixed;
2856
2988
  left: 50%;
2857
- bottom: 24px;
2989
+ bottom: calc(24px + env(safe-area-inset-bottom));
2858
2990
  transform: translateX(-50%);
2859
2991
  display: flex;
2860
2992
  align-items: center;
2861
2993
  gap: 6px;
2862
- padding: 7px 12px;
2994
+ max-width: calc(100vw - 24px);
2995
+ padding: 5px 6px 5px 12px;
2863
2996
  font-size: 12.5px;
2864
2997
  color: var(--rf-text);
2865
2998
  background: var(--rf-bg);
2866
- border-radius: 10px;
2999
+ border-radius: var(--rf-pill);
2867
3000
  box-shadow: var(--rf-shadow);
2868
3001
  animation: rf-fade 240ms var(--rf-ease);
2869
3002
  }
3003
+ .picker-instruction { display: flex; align-items: center; gap: 6px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2870
3004
  .picker-hint kbd {
2871
3005
  padding: 1px 5px;
2872
3006
  font: inherit;
@@ -2876,10 +3010,227 @@ summary::-webkit-details-marker { display: none; }
2876
3010
  background: var(--rf-bg-subtle);
2877
3011
  box-shadow: var(--rf-shadow-inset);
2878
3012
  }
3013
+ .picker-cancel {
3014
+ display: inline-flex;
3015
+ align-items: center;
3016
+ height: 40px;
3017
+ padding: 0 14px;
3018
+ border-radius: var(--rf-pill);
3019
+ font-size: 12.5px;
3020
+ font-weight: 500;
3021
+ white-space: nowrap;
3022
+ color: var(--rf-text-muted);
3023
+ }
3024
+ .picker-cancel:hover { background: var(--rf-bg-hover); color: var(--rf-text); }
3025
+ .picker-note {
3026
+ position: fixed;
3027
+ z-index: ${REFLET_Z_INDEX.annotatorOverlay};
3028
+ display: flex;
3029
+ flex-direction: column;
3030
+ gap: 6px;
3031
+ width: 300px;
3032
+ max-width: calc(100vw - 24px);
3033
+ padding: 6px 6px 6px 8px;
3034
+ border-radius: 18px;
3035
+ animation: rf-in 180ms var(--rf-ease);
3036
+ }
3037
+ .picker-note:focus-within { outline: 2px solid var(--rf-accent); outline-offset: 2px; }
3038
+ .picker-note-target {
3039
+ display: flex;
3040
+ align-items: center;
3041
+ gap: 6px;
3042
+ margin: 0;
3043
+ padding: 2px 4px 0;
3044
+ font-size: 11px;
3045
+ color: var(--rf-text-muted);
3046
+ white-space: nowrap;
3047
+ }
3048
+ .picker-note-target strong { flex: none; font-weight: 600; color: var(--rf-accent); }
3049
+ .picker-note-target span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
3050
+ .picker-note textarea {
3051
+ width: 100%;
3052
+ min-height: 46px;
3053
+ max-height: min(160px, 30dvh);
3054
+ margin: 0;
3055
+ padding: 7px 6px 0;
3056
+ border: 0;
3057
+ resize: none;
3058
+ background: transparent;
3059
+ color: var(--rf-text);
3060
+ font-size: 13px;
3061
+ line-height: 19px;
3062
+ }
3063
+ .picker-note textarea:focus { outline: none; }
3064
+ .picker-note textarea::placeholder { color: var(--rf-text-muted); }
3065
+ .picker-note-actions { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
3066
+ @media (pointer: coarse) {
3067
+ .picker-hint { padding: 6px 8px 6px 14px; }
3068
+ .picker-hint kbd { display: none; }
3069
+ .picker-cancel { height: 44px; padding: 0 18px; font-size: 14px; }
3070
+ .picker-note textarea { font-size: 16px; min-height: 56px; }
3071
+ }
2879
3072
 
3073
+ `;
3074
+ //#endregion
3075
+ //#region src/feedback/ui/styles.ts
3076
+ const WIDGET_STYLES = `
3077
+ :host {
3078
+ --rf-offset: 20px;
3079
+ --rf-bg: #ffffff;
3080
+ --rf-bg-subtle: rgb(55 53 47 / 4%);
3081
+ --rf-bg-hover: rgb(55 53 47 / 7%);
3082
+ --rf-border: rgb(55 53 47 / 9%);
3083
+ --rf-hairline: rgb(15 15 15 / 10%);
3084
+ --rf-text: #37352f;
3085
+ --rf-text-muted: rgb(55 53 47 / 68%);
3086
+ --rf-primary: #37352f;
3087
+ --rf-primary-text: #ffffff;
3088
+ --rf-accent: #2383e2;
3089
+ --rf-accent-text: #ffffff;
3090
+ --rf-danger: #d44c47;
3091
+ --rf-success: #448361;
3092
+ --rf-swatch-base: #ffffff;
3093
+ --rf-spectrum: conic-gradient(#ff5757, #ffde59, #6bdb78, #4ac9fa, #8b5cf6, #ef73dc, #ff5757);
3094
+ --rf-radius: 20px;
3095
+ --rf-pill: 999px;
3096
+ --rf-width: 360px;
3097
+ --rf-glass: rgb(255 255 255 / 72%);
3098
+ --rf-glass-highlight: rgb(255 255 255 / 90%);
3099
+ --rf-glass-stroke: rgb(255 255 255 / 70%);
3100
+ --rf-radius-sm: 8px;
3101
+ --rf-shadow-inset: inset 0 0 0 1px var(--rf-border);
3102
+ --rf-shadow-low: 0 0 0 1px rgb(15 15 15 / 4%), 0 1px 2px rgb(15 15 15 / 6%), 0 4px 10px -4px rgb(15 15 15 / 10%);
3103
+ --rf-shadow: 0 0 0 1px rgb(15 15 15 / 4%), 0 2px 5px -1px rgb(15 15 15 / 7%), 0 12px 32px -8px rgb(15 15 15 / 16%);
3104
+ --rf-shadow-float: 0 4px 12px -3px rgb(15 15 15 / 35%);
3105
+ --rf-ease: cubic-bezier(0.16, 1, 0.3, 1);
3106
+ color-scheme: light;
3107
+ all: initial;
3108
+ font-family: var(--rf-font, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif);
3109
+ }
2880
3110
 
3111
+ :host([data-theme="dark"]) {
3112
+ --rf-bg: #202020;
3113
+ --rf-bg-subtle: rgb(255 255 255 / 5%);
3114
+ --rf-bg-hover: rgb(255 255 255 / 9%);
3115
+ --rf-border: rgb(255 255 255 / 9%);
3116
+ --rf-hairline: rgb(255 255 255 / 12%);
3117
+ --rf-text: #ebeae8;
3118
+ --rf-text-muted: rgb(255 255 255 / 68%);
3119
+ --rf-glass: rgb(32 32 32 / 78%);
3120
+ --rf-glass-highlight: rgb(255 255 255 / 14%);
3121
+ --rf-glass-stroke: rgb(255 255 255 / 12%);
3122
+ --rf-primary: #f1efec;
3123
+ --rf-primary-text: #1c1c1b;
3124
+ --rf-accent: #529cca;
3125
+ --rf-danger: #eb5757;
3126
+ --rf-success: #4dab74;
3127
+ --rf-shadow-low: 0 0 0 1px rgb(255 255 255 / 6%), 0 1px 2px rgb(0 0 0 / 30%), 0 4px 10px -4px rgb(0 0 0 / 40%);
3128
+ --rf-shadow: 0 0 0 1px rgb(255 255 255 / 7%), 0 2px 5px -1px rgb(0 0 0 / 35%), 0 14px 36px -10px rgb(0 0 0 / 55%);
3129
+ --rf-shadow-float: 0 4px 14px -3px rgb(0 0 0 / 55%);
3130
+ color-scheme: dark;
3131
+ }
3132
+
3133
+ *, *::before, *::after { box-sizing: border-box; }
3134
+
3135
+
3136
+ .root {
3137
+ position: fixed;
3138
+ z-index: ${REFLET_Z_INDEX.widgetRoot};
3139
+ display: flex;
3140
+ align-items: flex-end;
3141
+ gap: 8px;
3142
+ font: 13px/1.45 var(--rf-font, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif);
3143
+ color: var(--rf-text);
3144
+ }
3145
+ .root:has(.panel) { width: min(var(--rf-width), calc(var(--rf-viewport-width, 100vw) - 24px)); }
3146
+ .root[data-position$="right"] { right: calc(var(--rf-viewport-right, 0px) + max(12px, min(var(--rf-offset), calc((var(--rf-viewport-width, 100vw) - var(--rf-width)) / 2)))); }
3147
+ .root[data-position$="left"] { left: calc(var(--rf-viewport-left, 0px) + max(12px, min(var(--rf-offset), calc((var(--rf-viewport-width, 100vw) - var(--rf-width)) / 2)))); }
3148
+ .root[data-position^="bottom"] { bottom: calc(var(--rf-viewport-bottom, 0px) + max(var(--rf-offset), env(safe-area-inset-bottom))); }
3149
+ .root[data-position^="top"] { top: calc(var(--rf-viewport-top, 0px) + max(var(--rf-offset), env(safe-area-inset-top))); align-items: flex-start; }
3150
+ .root[data-position="bottom-right"] .panel { transform-origin: bottom right; }
3151
+ .root[data-position="bottom-left"] .panel { transform-origin: bottom left; }
3152
+ .root[data-position^="top"] .panel { transform-origin: top center; }
3153
+ .root[data-editing="true"] { visibility: hidden; pointer-events: none; }
3154
+ button, select, input, textarea { font: inherit; color: inherit; }
3155
+ button { margin: 0; cursor: pointer; border: 0; background: none; transition: scale 120ms var(--rf-ease); }
3156
+ button:disabled { cursor: not-allowed; opacity: .4; }
3157
+ button:active:not(:disabled) { scale: .97; }
3158
+ svg { flex-shrink: 0; }
3159
+ :focus-visible { outline: 2px solid var(--rf-accent); outline-offset: 3px; }
3160
+ .icon-btn, .tool {
3161
+ display: inline-flex; align-items: center; justify-content: center;
3162
+ width: 40px; height: 40px; flex: none; border-radius: var(--rf-pill); color: var(--rf-text-muted);
3163
+ transition: background 160ms var(--rf-ease), color 160ms var(--rf-ease), scale 120ms var(--rf-ease);
3164
+ }
3165
+ .icon-btn:hover, .tool:hover { background: var(--rf-bg-hover); color: var(--rf-text); }
3166
+ .launcher {
3167
+ display: inline-flex; align-items: center; gap: 7px; height: 40px; padding: 0 15px;
3168
+ border-radius: var(--rf-pill); color: var(--rf-text);
3169
+ font-weight: 500;
3170
+ transition: transform 200ms var(--rf-ease), box-shadow 200ms var(--rf-ease), scale 120ms var(--rf-ease);
3171
+ }
3172
+ .launcher:hover { transform: translateY(-2px); box-shadow: var(--rf-shadow); }
3173
+ .launcher-icon { display: flex; width: 15px; height: 15px; }
3174
+ .launcher-icon .on-close { display: none; }
3175
+ .floating-controls {
3176
+ display: flex; align-items: center; padding: 3px; flex: none;
3177
+ border-radius: var(--rf-pill);
3178
+ animation: rf-in 220ms var(--rf-ease);
3179
+ }
3180
+ .drag-handle { touch-action: none; cursor: grab; }
3181
+ .drag-handle:active { cursor: grabbing; }
3182
+ .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
3183
+ .ghost-btn { display: flex; align-items: center; gap: 8px; min-height: 40px; padding: 0 8px; border-radius: var(--rf-radius-sm); }
3184
+ .ghost-btn:hover { background: var(--rf-bg-hover); }
3185
+ .error { margin: 0; padding: 10px 14px; border-radius: var(--rf-radius); font-size: 12px; color: var(--rf-danger); }
3186
+ .done { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 24px; text-align: center; }
3187
+ .done svg { color: var(--rf-success); }
3188
+ .done h2 { margin: 0; font-size: 14px; font-weight: 600; }
3189
+ .done p { margin: 0; font-size: 12px; color: var(--rf-text-muted); }
3190
+ .spinner { width: 14px; height: 14px; border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: rf-spin 700ms linear infinite; }
3191
+ .hp { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
3192
+ @keyframes rf-spin { to { transform: rotate(360deg); } }
3193
+ @keyframes rf-in { from { opacity: 0; transform: translateY(5px) scale(.98); } }
3194
+ @keyframes rf-fade { from { opacity: 0; } }
3195
+ @media (pointer: coarse) {
3196
+ .icon-btn, .tool { width: 44px; height: 44px; }
3197
+ .launcher, .ghost-btn { min-height: 44px; }
3198
+ }
3199
+ .glass {
3200
+ position: relative;
3201
+ isolation: isolate;
3202
+ border: 1px solid var(--rf-glass-stroke);
3203
+ box-shadow: inset 0 1px 0 var(--rf-glass-highlight), var(--rf-shadow);
3204
+ }
3205
+ .glass::before {
3206
+ content: "";
3207
+ position: absolute;
3208
+ inset: 0;
3209
+ z-index: -1;
3210
+ border-radius: inherit;
3211
+ background: var(--rf-bg);
3212
+ pointer-events: none;
3213
+ }
3214
+ @supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
3215
+ .glass::before {
3216
+ background: var(--rf-glass);
3217
+ -webkit-backdrop-filter: blur(20px) saturate(1.6);
3218
+ backdrop-filter: blur(20px) saturate(1.6);
3219
+ }
3220
+ }
3221
+ @media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
3222
+ .glass::before { background: var(--rf-bg); backdrop-filter: none; -webkit-backdrop-filter: none; }
3223
+ }
3224
+ .capture-halo { position: fixed; inset: 0; z-index: ${REFLET_Z_INDEX.captureHalo}; pointer-events: none; opacity: 0; transition: opacity 500ms var(--rf-ease); }
3225
+ .capture-halo[data-active="true"] { opacity: 1; transition-duration: 140ms; }
3226
+ .capture-halo::before { content: ""; position: absolute; inset: 0; border-radius: 20px; box-shadow: inset 14px 0 36px -16px var(--rf-accent), inset -14px 0 36px -16px var(--rf-accent), inset 0 0 4px 1px color-mix(in srgb, var(--rf-accent) 35%, transparent); }
3227
+ .capture-halo::after { content: ""; position: absolute; inset: 0; background: linear-gradient(90deg, color-mix(in srgb, var(--rf-accent) 20%, transparent), transparent 5%, transparent 95%, color-mix(in srgb, var(--rf-accent) 20%, transparent)); }
3228
+ ${COMPOSER_STYLES}
3229
+ ${ANNOTATOR_STYLES}
3230
+ ${PICKER_STYLES}
2881
3231
  @media (prefers-reduced-motion: reduce) {
2882
3232
  *, *::before, *::after { animation: none !important; transition: none !important; }
3233
+ button:active:not(:disabled) { scale: 1; }
2883
3234
  }
2884
3235
  `;
2885
3236
  //#endregion
@@ -3186,10 +3537,9 @@ function deriveTitle(message) {
3186
3537
  return `${(lastSpace > 0 ? clipped.slice(0, lastSpace) : clipped).trimEnd()}…`;
3187
3538
  }
3188
3539
  function buildDescription(params) {
3189
- const { category, email, isAnonymous, message } = params;
3190
- const label = category.charAt(0).toUpperCase() + category.slice(1);
3540
+ const { email, isAnonymous, message } = params;
3191
3541
  const contact = isAnonymous && email ? `\n\n---\nContact: ${email}` : "";
3192
- return `[${label}] ${message.trim()}${contact}`;
3542
+ return `${message.trim()}${contact}`;
3193
3543
  }
3194
3544
  async function attachPreparedScreenshots(transport, result) {
3195
3545
  const results = await Promise.allSettled(result.pendingScreenshots.map((screenshot) => transport.saveScreenshot({
@@ -3421,10 +3771,6 @@ function useScreenshotDrafts(captureFailed, onError) {
3421
3771
  }
3422
3772
  //#endregion
3423
3773
  //#region src/feedback/ui/state/use-widget-config.ts
3424
- function defaultCategory(props) {
3425
- const preferred = props.defaultCategory ?? "bug";
3426
- return props.categories?.find((category) => category === preferred) ?? props.categories?.[0] ?? preferred;
3427
- }
3428
3774
  function dismissalDuration(days) {
3429
3775
  return days !== void 0 && Number.isFinite(days) && days > 0 ? days : null;
3430
3776
  }
@@ -3446,7 +3792,6 @@ function useWidgetConfig(props) {
3446
3792
  user,
3447
3793
  userToken
3448
3794
  ]),
3449
- defaultCategory: defaultCategory(props),
3450
3795
  dismissalKey: `reflet-feedback-dismissed:${publicKey ?? "default"}:${user?.id ?? "anonymous"}`,
3451
3796
  dismissForDays: dismissalDuration(props.dismissForDays),
3452
3797
  isAnonymous: !(user || userToken)
@@ -3533,16 +3878,16 @@ function matchesHotkey(event, hotkey, platform = typeof navigator === "undefined
3533
3878
  return event.altKey === expected.alt && event.ctrlKey === expected.ctrl && event.metaKey === expected.meta && event.shiftKey === expected.shift;
3534
3879
  }
3535
3880
  function useWidgetState(props) {
3536
- const { client, defaultCategory, dismissalKey, dismissForDays, isAnonymous } = useWidgetConfig(props);
3881
+ const { client, dismissalKey, dismissForDays, isAnonymous } = useWidgetConfig(props);
3537
3882
  const [isOpen, setIsOpen] = useState(false);
3538
3883
  const [annotationTrigger, setAnnotationTrigger] = useState(null);
3539
3884
  const [step, setStep] = useState("compose");
3540
- const [category, setCategory] = useState(defaultCategory);
3541
3885
  const [message, setMessage] = useState("");
3542
3886
  const [email, setEmail] = useState("");
3543
3887
  const [honeypot, setHoneypot] = useState("");
3544
3888
  const [elementCapture, replaceElementCapture] = useOwnedCapture();
3545
3889
  const [selection, setSelection] = useState(null);
3890
+ const [selectedNode, setSelectedNode] = useState(null);
3546
3891
  const [isElementCapturing, setIsElementCapturing] = useState(false);
3547
3892
  const successTimer = useRef(null);
3548
3893
  const [error, setError] = useState(null);
@@ -3594,14 +3939,13 @@ function useWidgetState(props) {
3594
3939
  resetScreenshots();
3595
3940
  replaceElementCapture(null);
3596
3941
  setSelection(null);
3942
+ setSelectedNode(null);
3597
3943
  setMessage("");
3598
3944
  setEmail("");
3599
3945
  setHoneypot("");
3600
3946
  setError(null);
3601
3947
  setStep("compose");
3602
- setCategory(defaultCategory);
3603
3948
  }, [
3604
- defaultCategory,
3605
3949
  resetSubmission,
3606
3950
  resetScreenshots,
3607
3951
  replaceElementCapture
@@ -3635,11 +3979,15 @@ function useWidgetState(props) {
3635
3979
  props.onOpen,
3636
3980
  refreshAutomatic
3637
3981
  ]);
3638
- const selectElement = useCallback(async (element) => {
3982
+ const selectElement = useCallback(async (element, note = "") => {
3639
3983
  cancelPendingCapture();
3640
3984
  setIsElementCapturing(true);
3641
- const picked = buildElementSelection(element);
3985
+ const picked = note ? {
3986
+ ...buildElementSelection(element),
3987
+ comment: note.slice(0, MAX_SELECTION_COMMENT_LENGTH)
3988
+ } : buildElementSelection(element);
3642
3989
  setSelection(picked);
3990
+ setSelectedNode(element);
3643
3991
  setStep("compose");
3644
3992
  const pick = ++pickRef.current;
3645
3993
  const context = collectPageContext({ sdkVersion: SDK_VERSION });
@@ -3669,18 +4017,19 @@ function useWidgetState(props) {
3669
4017
  pickRef.current++;
3670
4018
  replaceElementCapture(null);
3671
4019
  setSelection(null);
4020
+ setSelectedNode(null);
3672
4021
  setIsElementCapturing(false);
3673
4022
  clearSelectionAnnotations();
3674
4023
  }, [clearSelectionAnnotations, replaceElementCapture]);
4024
+ const reportedMessage = message.trim() || selection?.comment || "";
3675
4025
  const submit = () => {
3676
4026
  if (honeypot) {
3677
4027
  setStep("success");
3678
4028
  return;
3679
4029
  }
3680
4030
  return submission.submit({
3681
- category,
3682
4031
  context: {
3683
- ...activeScreenshot?.context ?? collectPageContext({ sdkVersion: "0.3.5" }),
4032
+ ...activeScreenshot?.context ?? collectPageContext({ sdkVersion: "0.4.0" }),
3684
4033
  consoleEvents: consoleRef.current?.events(),
3685
4034
  metadata: props.metadata,
3686
4035
  selection: selection ?? void 0
@@ -3688,7 +4037,7 @@ function useWidgetState(props) {
3688
4037
  element: elementCapture,
3689
4038
  email,
3690
4039
  isAnonymous,
3691
- message,
4040
+ message: reportedMessage,
3692
4041
  screenshots: gallery.screenshots
3693
4042
  });
3694
4043
  };
@@ -3702,8 +4051,8 @@ function useWidgetState(props) {
3702
4051
  annotations,
3703
4052
  annotationTrigger,
3704
4053
  canDismiss: dismissForDays !== null,
4054
+ canSubmit: reportedMessage.length > 0,
3705
4055
  capture,
3706
- category,
3707
4056
  clearSelection,
3708
4057
  close,
3709
4058
  dismiss,
@@ -3726,9 +4075,9 @@ function useWidgetState(props) {
3726
4075
  retakeCapture: gallery.retakeCapture,
3727
4076
  screenshots: gallery.screenshots,
3728
4077
  selectElement,
4078
+ selectedNode,
3729
4079
  selection,
3730
4080
  setAnnotations: gallery.setAnnotations,
3731
- setCategory,
3732
4081
  setEmail,
3733
4082
  setHoneypot,
3734
4083
  setMessage,
@@ -3740,13 +4089,8 @@ function useWidgetState(props) {
3740
4089
  }
3741
4090
  //#endregion
3742
4091
  //#region src/feedback/widget.tsx
3743
- const DEFAULT_CATEGORIES = [
3744
- "bug",
3745
- "idea",
3746
- "question"
3747
- ];
3748
4092
  function RefletFeedback(props) {
3749
- const { categories = DEFAULT_CATEGORIES, enabled = true, hotkey = null, offset = 20, position = "bottom-right", theme = "auto" } = props;
4093
+ const { enabled = true, hotkey = null, offset = 20, position = "bottom-right", theme = "auto" } = props;
3750
4094
  const labels = {
3751
4095
  ...DEFAULT_WIDGET_LABELS,
3752
4096
  ...props.labels
@@ -3784,10 +4128,7 @@ function RefletFeedback(props) {
3784
4128
  }),
3785
4129
  /* @__PURE__ */ jsx(FloatingWidget, {
3786
4130
  labels,
3787
- options: {
3788
- categories,
3789
- position
3790
- },
4131
+ position,
3791
4132
  state
3792
4133
  }),
3793
4134
  isOpen && state.step === "annotate" && state.activeScreenshot && /* @__PURE__ */ jsx(Annotator, {
@@ -3805,7 +4146,7 @@ function RefletFeedback(props) {
3805
4146
  labels
3806
4147
  }),
3807
4148
  isOpen && state.step === "picking" && /* @__PURE__ */ jsx(ElementPicker, {
3808
- hint: labels.pickElementHint,
4149
+ labels,
3809
4150
  onCancel: () => state.setStep("compose"),
3810
4151
  onPick: state.selectElement
3811
4152
  })