reflet-sdk 0.6.2 → 0.6.3
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/CHANGELOG.md +11 -0
- package/README.md +3 -1
- package/dist/{devtools-layer-DOECMj0S.js → devtools-layer-DtO89RlJ.js} +77 -19
- package/dist/devtools-layer-DtO89RlJ.js.map +1 -0
- package/dist/feedback.js +10 -8
- package/dist/feedback.js.map +1 -1
- package/dist/{picker-DCoAFX5y.js → picker-Rh-hsxyX.js} +75 -23
- package/dist/{picker-DCoAFX5y.js.map → picker-Rh-hsxyX.js.map} +1 -1
- package/dist/react.js +1 -1
- package/dist/{z-index-BL0SGZEP.js → z-index-CIb4-3Fy.js} +2 -1
- package/dist/{z-index-BL0SGZEP.js.map → z-index-CIb4-3Fy.js.map} +1 -1
- package/package.json +1 -1
- package/dist/devtools-layer-DOECMj0S.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,17 @@ All notable changes to `reflet-sdk` will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.6.3] - 2026-10-04
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Copy an element straight from the devtools picker, React Grab style: aim at it and press ⌘C (Ctrl+C), or press Copy on the note card to include your note. The clipboard gets the element, its source location, owner components, selector and HTML.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- The element picker and its hover outline stay above every layer of the page, so elements inside a fixed sidebar or header are outlined instead of hidden under it.
|
|
17
|
+
- The note card and hover label stay on screen when the picked element is taller than the window: the card tries below, above, then beside the element, and overlaps it only when nothing else fits.
|
|
18
|
+
|
|
8
19
|
## [0.6.2] - 2026-10-04
|
|
9
20
|
|
|
10
21
|
### Fixed
|
package/README.md
CHANGED
|
@@ -190,7 +190,9 @@ turn it off.
|
|
|
190
190
|
"View code" button, and Shift+click (or Shift+Enter) skips the note and opens
|
|
191
191
|
the source file with the JSX element highlighted, ready to jump to in your
|
|
192
192
|
editor. Locations come from React's debug stacks resolved through the dev
|
|
193
|
-
server's source maps, Server Components included.
|
|
193
|
+
server's source maps, Server Components included. Press ⌘C (Ctrl+C) on the
|
|
194
|
+
aimed element, or Copy on the note card, to put the element and its source on
|
|
195
|
+
the clipboard for a coding agent, without saving a note.
|
|
194
196
|
- **Notes** — a note appears the moment you press Enter, as a numbered pin on
|
|
195
197
|
its element, Figma-style; click the pin to read, edit or delete it, and find
|
|
196
198
|
the same number on its card in the panel. Its source location and a
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as EDITORS, n as DEVTOOLS_REQUEST_HEADER, r as DEVTOOLS_ROUTE_BASE, t as DEVTOOLS_ENDPOINTS } from "./protocol-ClqTFzwR.js";
|
|
2
|
-
import { A as TargetIcon, B as buildElementSelection, C as GripIcon, H as getFiberFromNode, I as DEFAULT_WIDGET_LABELS, J as releaseCapture, K as captureViewport, L as SDK_VERSION, R as collectPageContext, S as CloseIcon, U as resolveComponentStack, V as describeElement, W as shortenSourcePath, a as deriveTitle, c as useFloatingPosition, d as SelectionOutline, k as Svg, l as onViewportChange, m as listenToKeydown, n as isWidgetOwned, p as listenOnPageAndWidget, q as capturedFromCanvas, s as uploadImageBlob, t as ElementPicker, u as visibleViewport } from "./picker-
|
|
3
|
-
import { t as REFLET_Z_INDEX } from "./z-index-
|
|
2
|
+
import { A as TargetIcon, B as buildElementSelection, C as GripIcon, H as getFiberFromNode, I as DEFAULT_WIDGET_LABELS, J as releaseCapture, K as captureViewport, L as SDK_VERSION, R as collectPageContext, S as CloseIcon, U as resolveComponentStack, V as describeElement, W as shortenSourcePath, a as deriveTitle, c as useFloatingPosition, d as SelectionOutline, k as Svg, l as onViewportChange, m as listenToKeydown, n as isWidgetOwned, p as listenOnPageAndWidget, q as capturedFromCanvas, s as uploadImageBlob, t as ElementPicker, u as visibleViewport } from "./picker-Rh-hsxyX.js";
|
|
3
|
+
import { t as REFLET_Z_INDEX } from "./z-index-CIb4-3Fy.js";
|
|
4
4
|
import * as G$1 from "react";
|
|
5
5
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
6
6
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -142,16 +142,23 @@ function selectionLines(selection) {
|
|
|
142
142
|
if (selection.html) lines.push("HTML:", ...fenced("html", selection.html));
|
|
143
143
|
return lines;
|
|
144
144
|
}
|
|
145
|
+
function elementContext(note) {
|
|
146
|
+
return [`Page: ${note.capturedContext.url ?? "unknown"}`, ...selectionLines(note.selection)];
|
|
147
|
+
}
|
|
145
148
|
function notesToPrompt(notes) {
|
|
146
149
|
const sections = notes.map((note, index) => [
|
|
147
150
|
`## Note ${index + 1}`,
|
|
148
151
|
note.note || "(no comment)",
|
|
149
152
|
"",
|
|
150
|
-
|
|
151
|
-
...selectionLines(note.selection)
|
|
153
|
+
...elementContext(note)
|
|
152
154
|
].join("\n"));
|
|
153
155
|
return [NOTES_BRIEF, ...sections].join("\n\n");
|
|
154
156
|
}
|
|
157
|
+
/** No brief: pasted into a prompt the developer writes around it. */
|
|
158
|
+
function noteAsContext(note) {
|
|
159
|
+
const context = elementContext(note).join("\n");
|
|
160
|
+
return note.note ? `${note.note}\n\n${context}` : context;
|
|
161
|
+
}
|
|
155
162
|
function boardFeedbackToPrompt(item) {
|
|
156
163
|
const selections = item.context?.selections ?? [];
|
|
157
164
|
const comments = selections.map((selection) => selection.comment).filter(Boolean);
|
|
@@ -449,6 +456,7 @@ function InboxPanel({ onOpenCode, onShowSelector }) {
|
|
|
449
456
|
}
|
|
450
457
|
//#endregion
|
|
451
458
|
//#region src/devtools/client/notices.tsx
|
|
459
|
+
const STATUS_VISIBLE_MS = 2400;
|
|
452
460
|
let notices = [];
|
|
453
461
|
let nextNoticeId = 1;
|
|
454
462
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -464,10 +472,20 @@ function reportFailure(action, error, retry) {
|
|
|
464
472
|
const message = `${action}: ${error instanceof Error ? error.message : "Unknown error."}`;
|
|
465
473
|
publish([...notices.filter((notice) => notice.message !== message), {
|
|
466
474
|
id: nextNoticeId++,
|
|
475
|
+
kind: "failure",
|
|
467
476
|
message,
|
|
468
477
|
retry
|
|
469
478
|
}]);
|
|
470
479
|
}
|
|
480
|
+
function announce(message) {
|
|
481
|
+
const id = nextNoticeId++;
|
|
482
|
+
publish([...notices.filter((notice) => notice.message !== message), {
|
|
483
|
+
id,
|
|
484
|
+
kind: "status",
|
|
485
|
+
message
|
|
486
|
+
}]);
|
|
487
|
+
setTimeout(() => dismissNotice(id), STATUS_VISIBLE_MS);
|
|
488
|
+
}
|
|
471
489
|
function subscribe(listener) {
|
|
472
490
|
listeners.add(listener);
|
|
473
491
|
return () => listeners.delete(listener);
|
|
@@ -479,7 +497,8 @@ function Notices() {
|
|
|
479
497
|
className: "dt-notices",
|
|
480
498
|
children: current.map((notice) => /* @__PURE__ */ jsxs("div", {
|
|
481
499
|
className: "dt-notice glass",
|
|
482
|
-
|
|
500
|
+
"data-kind": notice.kind,
|
|
501
|
+
role: notice.kind === "failure" ? "alert" : "status",
|
|
483
502
|
children: [
|
|
484
503
|
/* @__PURE__ */ jsx("p", { children: notice.message }),
|
|
485
504
|
notice.retry && /* @__PURE__ */ jsx("button", {
|
|
@@ -634,6 +653,18 @@ function BoardTab({ canConnect, onOpenCode, onShowSelector, publicKey, refresh,
|
|
|
634
653
|
}), inbox] });
|
|
635
654
|
}
|
|
636
655
|
//#endregion
|
|
656
|
+
//#region src/devtools/client/clipboard.ts
|
|
657
|
+
/**
|
|
658
|
+
* Safari only lets a page write the clipboard inside the key press or click
|
|
659
|
+
* itself, while the text waits on a source-map lookup: the write starts now
|
|
660
|
+
* and the text follows.
|
|
661
|
+
*/
|
|
662
|
+
async function copyToClipboard(text) {
|
|
663
|
+
if (!window.isSecureContext) throw new Error("The clipboard needs HTTPS or localhost.");
|
|
664
|
+
const blob = text.then((value) => new Blob([value], { type: "text/plain" }));
|
|
665
|
+
await navigator.clipboard.write([new ClipboardItem({ "text/plain": blob })]);
|
|
666
|
+
}
|
|
667
|
+
//#endregion
|
|
637
668
|
//#region src/devtools/client/code/editor-url.ts
|
|
638
669
|
const fileSchemeLink = (scheme) => (absolutePath, line, column) => `${scheme}://file${encodeURI(absolutePath)}:${line}:${column}`;
|
|
639
670
|
const EDITOR_LINKS = {
|
|
@@ -758,6 +789,7 @@ function InboxIcon() {
|
|
|
758
789
|
//#endregion
|
|
759
790
|
//#region src/devtools/client/devtools-bar.tsx
|
|
760
791
|
const BAR_POSITION_KEY = "reflet-devtools-bar-position";
|
|
792
|
+
const PICK_BUTTON_LABEL = "Pick an element: note it, copy it, or Shift+click to open its code";
|
|
761
793
|
function BarButton({ children, label, onClick, pressed }) {
|
|
762
794
|
return /* @__PURE__ */ jsx("button", {
|
|
763
795
|
"aria-label": label,
|
|
@@ -794,7 +826,7 @@ function DevtoolsBar({ edge, noteCount, onPick, onToggleSheet, openTab, side })
|
|
|
794
826
|
children: /* @__PURE__ */ jsx(GripIcon, {})
|
|
795
827
|
}),
|
|
796
828
|
/* @__PURE__ */ jsx(BarButton, {
|
|
797
|
-
label:
|
|
829
|
+
label: PICK_BUTTON_LABEL,
|
|
798
830
|
onClick: onPick,
|
|
799
831
|
pressed: false,
|
|
800
832
|
children: /* @__PURE__ */ jsx(TargetIcon, {})
|
|
@@ -1000,7 +1032,7 @@ function DevtoolsSheet({ children, onClose, onPick, onSelectTab, side, tab, tabs
|
|
|
1000
1032
|
...floating.handleProps
|
|
1001
1033
|
}),
|
|
1002
1034
|
/* @__PURE__ */ jsx("button", {
|
|
1003
|
-
"aria-label":
|
|
1035
|
+
"aria-label": PICK_BUTTON_LABEL,
|
|
1004
1036
|
className: "icon-btn",
|
|
1005
1037
|
onClick: onPick,
|
|
1006
1038
|
title: "Pick an element",
|
|
@@ -1076,6 +1108,7 @@ const DEVTOOLS_STYLES = `
|
|
|
1076
1108
|
display: flex; align-items: center; gap: 6px; padding: 6px 6px 6px 14px; border-radius: 12px;
|
|
1077
1109
|
color: var(--rf-danger); font-size: 12.5px; animation: dt-notice-in 220ms var(--rf-ease);
|
|
1078
1110
|
}
|
|
1111
|
+
.dt-notice[data-kind="status"] { color: var(--rf-text); }
|
|
1079
1112
|
.dt-notice p { flex: 1; margin: 0; overflow-wrap: anywhere; }
|
|
1080
1113
|
.dt-notice .icon-btn { width: 30px; height: 30px; }
|
|
1081
1114
|
.dt-bar {
|
|
@@ -3301,6 +3334,24 @@ async function describeSource(source, canReadSource) {
|
|
|
3301
3334
|
}
|
|
3302
3335
|
return withPosition(shortenSourcePath(source.fileName), source);
|
|
3303
3336
|
}
|
|
3337
|
+
async function locateSource(element, canReadSource) {
|
|
3338
|
+
const source = await locateElementSource(element).catch(() => null);
|
|
3339
|
+
if (!source) return null;
|
|
3340
|
+
return {
|
|
3341
|
+
source,
|
|
3342
|
+
sourceLocation: await describeSource(source, canReadSource)
|
|
3343
|
+
};
|
|
3344
|
+
}
|
|
3345
|
+
function withSource(note, located) {
|
|
3346
|
+
return {
|
|
3347
|
+
...note,
|
|
3348
|
+
selection: {
|
|
3349
|
+
...note.selection,
|
|
3350
|
+
sourceLocation: located.sourceLocation
|
|
3351
|
+
},
|
|
3352
|
+
source: located.source
|
|
3353
|
+
};
|
|
3354
|
+
}
|
|
3304
3355
|
/** `crypto.randomUUID` only exists in secure contexts; a phone on the LAN IP is not one. */
|
|
3305
3356
|
function newNoteId() {
|
|
3306
3357
|
const randomBytes = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(8));
|
|
@@ -3322,6 +3373,12 @@ function draftNote(element, note) {
|
|
|
3322
3373
|
source: null
|
|
3323
3374
|
};
|
|
3324
3375
|
}
|
|
3376
|
+
/** The whole note in one go, source included: a copy has no stored note to fill in later. */
|
|
3377
|
+
async function draftLocatedNote(element, note, canReadSource) {
|
|
3378
|
+
const draft = draftNote(element, note);
|
|
3379
|
+
const located = await locateSource(element, canReadSource);
|
|
3380
|
+
return located ? withSource(draft, located) : draft;
|
|
3381
|
+
}
|
|
3325
3382
|
const locateAttempts = /* @__PURE__ */ new Set();
|
|
3326
3383
|
/** Once per note per page load: a note whose source cannot be found is not retried in a loop. */
|
|
3327
3384
|
function hasTriedToLocate(noteId) {
|
|
@@ -3336,17 +3393,9 @@ async function enrichNote(noteId, element, canReadSource) {
|
|
|
3336
3393
|
noteStore.setActivity(noteId, "locating");
|
|
3337
3394
|
let closeUp = null;
|
|
3338
3395
|
try {
|
|
3339
|
-
const [
|
|
3396
|
+
const [located, snapshot] = await Promise.all([locateSource(element, canReadSource), captureElementSnapshot(element).catch(() => null)]);
|
|
3340
3397
|
closeUp = snapshot;
|
|
3341
|
-
|
|
3342
|
-
if (source && sourceLocation) await noteStore.patch(noteId, (note) => ({
|
|
3343
|
-
...note,
|
|
3344
|
-
selection: {
|
|
3345
|
-
...note.selection,
|
|
3346
|
-
sourceLocation
|
|
3347
|
-
},
|
|
3348
|
-
source
|
|
3349
|
-
}));
|
|
3398
|
+
if (located) await noteStore.patch(noteId, (note) => withSource(note, located));
|
|
3350
3399
|
if (closeUp) await noteStore.attachCloseUp(noteId, {
|
|
3351
3400
|
blob: closeUp.blob,
|
|
3352
3401
|
height: closeUp.height,
|
|
@@ -3943,7 +3992,7 @@ const PICKER_LABELS = {
|
|
|
3943
3992
|
attachElement: "Save note",
|
|
3944
3993
|
elementNote: "Dev note",
|
|
3945
3994
|
elementNotePlaceholder: "What should change here?",
|
|
3946
|
-
pickElementHint: "Pick an element to note it"
|
|
3995
|
+
pickElementHint: "Pick an element to note or copy it"
|
|
3947
3996
|
};
|
|
3948
3997
|
function titleFor(element) {
|
|
3949
3998
|
const owner = resolveComponentStack(getFiberFromNode(element))[0];
|
|
@@ -4062,6 +4111,14 @@ function DevtoolsLayer({ isWidgetOpen, position, publicKey }) {
|
|
|
4062
4111
|
setIsPicking(false);
|
|
4063
4112
|
saveNote(draftNote(element, text), element, canReadSource);
|
|
4064
4113
|
}, [canReadSource]);
|
|
4114
|
+
const copy = useMemo(() => ({
|
|
4115
|
+
label: "Copy",
|
|
4116
|
+
onCopy: (element, text) => {
|
|
4117
|
+
setIsPicking(false);
|
|
4118
|
+
const title = titleFor(element);
|
|
4119
|
+
copyToClipboard(draftLocatedNote(element, text, canReadSource).then(noteAsContext)).then(() => announce(`Copied ${title}`)).catch((error) => reportFailure(`${title} was not copied`, error));
|
|
4120
|
+
}
|
|
4121
|
+
}), [canReadSource]);
|
|
4065
4122
|
return /* @__PURE__ */ jsxs("div", {
|
|
4066
4123
|
className: "dt",
|
|
4067
4124
|
"data-recessed": isRecessed,
|
|
@@ -4116,6 +4173,7 @@ function DevtoolsLayer({ isWidgetOpen, position, publicKey }) {
|
|
|
4116
4173
|
openTab && /* @__PURE__ */ jsx(SelectionOutline, { node: highlighted }),
|
|
4117
4174
|
/* @__PURE__ */ jsx(Notices, {}),
|
|
4118
4175
|
isPicking && /* @__PURE__ */ jsx(ElementPicker, {
|
|
4176
|
+
copy,
|
|
4119
4177
|
inspect,
|
|
4120
4178
|
labels: PICKER_LABELS,
|
|
4121
4179
|
onCancel: cancelPick,
|
|
@@ -4127,4 +4185,4 @@ function DevtoolsLayer({ isWidgetOpen, position, publicKey }) {
|
|
|
4127
4185
|
//#endregion
|
|
4128
4186
|
export { DevtoolsLayer };
|
|
4129
4187
|
|
|
4130
|
-
//# sourceMappingURL=devtools-layer-
|
|
4188
|
+
//# sourceMappingURL=devtools-layer-DtO89RlJ.js.map
|