reflet-sdk 0.7.3 → 0.7.4

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.
@@ -1,9 +1,134 @@
1
- import { $ as shortenSourcePath, G as describeElement, Q as resolveComponentStack, R as DEFAULT_WIDGET_LABELS, U as buildElementSelection, V as collectPageContext, X as getFiberFromNode, a as uploadBlob, f as listenOnPageAndWidget, g as isSubmitEnter, it as releaseCapture, nt as capturedFromCanvas, o as ElementPicker, p as listenToKeydown, r as deriveTitle, rt as capturedViewportSize, s as isWidgetOwned, tt as captureViewport, z as SDK_VERSION } from "./submit-Dl0rm6TV.js";
1
+ import { $ as shortenSourcePath, G as describeElement, M as TargetIcon, Q as resolveComponentStack, R as DEFAULT_WIDGET_LABELS, U as buildElementSelection, V as collectPageContext, X as getFiberFromNode, a as uploadBlob, f as listenOnPageAndWidget, g as isSubmitEnter, it as releaseCapture, nt as capturedFromCanvas, o as ElementPicker, p as listenToKeydown, r as deriveTitle, rt as capturedViewportSize, s as isWidgetOwned, tt as captureViewport, z as SDK_VERSION } from "./submit-CG4LXioK.js";
2
2
  import { t as SelectionOutline } from "./selection-outline-NzAAAZXR.js";
3
- import { A as Notices, C as searchCode, D as openDatabase, E as describeStorageFailure, M as reportFailure, O as settle, S as probeDevRoute, T as committed, a as DevtoolsBar, b as fetchBoardFeedback, c as copyToClipboard, f as SessionWorkspaceProvider, g as devtoolsTransport, j as announce, l as SESSIONS_STYLES, m as useWorkspaceLoader, n as DEVTOOLS_STYLES, o as DevtoolsBarButtons, r as DevtoolsSheet, t as SessionsPanel, u as RecordingLayer, v as callReflet, w as startBoardConnect, x as fetchSourceFile, y as disconnectBoard } from "./sessions-panel-BYM0UUgT.js";
3
+ import { A as searchCode, C as devtoolsTransport, D as fetchBoardFeedback, E as disconnectBoard, F as settle, L as Notices, M as committed, N as describeStorageFailure, O as fetchSourceFile, P as openDatabase, R as announce, T as callReflet, _ as RecordingLayer, a as SHEET_TABS, c as AccountNote, d as sessionSheetPlace, f as useSessionNavigation, g as copyToClipboard, h as ViewOptionsIcon, i as DevtoolsBarButtons, j as startBoardConnect, k as probeDevRoute, l as SESSIONS_STYLES, m as RefreshIcon, n as DevtoolsSheet, p as Menu, r as DevtoolsBar, s as AccountFooter, t as DEVTOOLS_STYLES, u as SessionsPanel, v as countLabel, x as useWorkspaceLoader, y as SessionWorkspaceProvider, z as reportFailure } from "./devtools-styles-teRMfSzW.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";
7
+ //#region src/devtools/client/route/route-hint.tsx
8
+ function MissingRouteHint({ purpose }) {
9
+ return /* @__PURE__ */ jsxs("div", {
10
+ className: "dt-empty",
11
+ children: [
12
+ /* @__PURE__ */ jsxs("p", { children: [
13
+ "Add the Reflet dev route to ",
14
+ purpose,
15
+ "."
16
+ ] }),
17
+ /* @__PURE__ */ jsxs("p", { children: [
18
+ "Next.js: ",
19
+ /* @__PURE__ */ jsx("code", { children: "app/api/reflet-devtools/[...path]/route.ts" }),
20
+ /* @__PURE__ */ jsx("br", {}),
21
+ /* @__PURE__ */ jsxs("code", { children: [
22
+ "export ",
23
+ "{ GET, POST }",
24
+ " from \"reflet-sdk/devtools/next\";"
25
+ ] })
26
+ ] }),
27
+ /* @__PURE__ */ jsxs("p", { children: [
28
+ "Vite: ",
29
+ /* @__PURE__ */ jsx("code", { children: "plugins: [refletDevtools()]" }),
30
+ " from",
31
+ " ",
32
+ /* @__PURE__ */ jsx("code", { children: "reflet-sdk/devtools/vite" })
33
+ ] })
34
+ ]
35
+ });
36
+ }
37
+ const CONNECT_REQUIREMENT = "Connecting to Reflet needs a public key and a local address (localhost, *.localhost or *.test).";
38
+ const MISSING_SECRET_KEY_HINT = `${CONNECT_REQUIREMENT} Or set REFLET_SECRET_KEY (your fb_sec_ key) in the dev server environment to reach your board.`;
39
+ //#endregion
40
+ //#region src/devtools/client/account/connect-footer.tsx
41
+ function useBoardConnect(publicKey) {
42
+ const [state, setState] = useState({ kind: "idle" });
43
+ const connect = async () => {
44
+ const tab = window.open("", "_blank");
45
+ if (tab) tab.opener = null;
46
+ setState({ kind: "starting" });
47
+ try {
48
+ const { authorizeUrl } = await startBoardConnect(publicKey);
49
+ if (tab) tab.location.href = authorizeUrl;
50
+ else window.open(authorizeUrl, "_blank", "noopener");
51
+ setState({
52
+ authorizeUrl,
53
+ kind: "waiting"
54
+ });
55
+ } catch (error) {
56
+ tab?.close();
57
+ setState({ kind: "idle" });
58
+ reportFailure("Could not connect to Reflet", error);
59
+ }
60
+ };
61
+ return {
62
+ connect,
63
+ state
64
+ };
65
+ }
66
+ function ConnectFooter({ publicKey }) {
67
+ const { connect, state } = useBoardConnect(publicKey);
68
+ if (state.kind === "waiting") return /* @__PURE__ */ jsxs("div", {
69
+ className: "dt-sheet-account",
70
+ role: "status",
71
+ children: [/* @__PURE__ */ jsx("p", {
72
+ className: "dt-meta",
73
+ children: "Finish connecting in the Reflet tab. This panel updates when you come back."
74
+ }), /* @__PURE__ */ jsxs("div", {
75
+ className: "dt-actions",
76
+ children: [/* @__PURE__ */ jsx("a", {
77
+ className: "dt-btn",
78
+ href: state.authorizeUrl,
79
+ rel: "noopener noreferrer",
80
+ target: "_blank",
81
+ children: "Open Reflet again"
82
+ }), /* @__PURE__ */ jsx("button", {
83
+ className: "dt-btn",
84
+ onClick: connect,
85
+ type: "button",
86
+ children: "Start again"
87
+ })]
88
+ })]
89
+ });
90
+ return /* @__PURE__ */ jsxs("div", {
91
+ className: "dt-sheet-account",
92
+ children: [/* @__PURE__ */ jsx("p", {
93
+ className: "dt-meta",
94
+ children: "Not connected"
95
+ }), /* @__PURE__ */ jsx("button", {
96
+ className: "dt-btn",
97
+ "data-variant": "primary",
98
+ disabled: state.kind === "starting",
99
+ onClick: connect,
100
+ type: "button",
101
+ children: "Connect to Reflet"
102
+ })]
103
+ });
104
+ }
105
+ //#endregion
106
+ //#region src/devtools/client/account/devtools-account.tsx
107
+ async function disconnect(refresh) {
108
+ try {
109
+ const { revoked } = await disconnectBoard();
110
+ if (!revoked) reportFailure("The Reflet token was not revoked", /* @__PURE__ */ new Error("Disconnected here, but Reflet could not be reached. Revoke it from Account → Devtools on reflet.app."));
111
+ } catch (error) {
112
+ reportFailure("The dev server did not disconnect", error);
113
+ }
114
+ refresh();
115
+ }
116
+ function DevtoolsAccount({ connectKey, refresh, route, workspace }) {
117
+ if (route.kind !== "ready") return null;
118
+ const { board } = route.status;
119
+ if (board.kind === "secretKey") return /* @__PURE__ */ jsx(AccountNote, { children: "Reaching the board with REFLET_SECRET_KEY" });
120
+ if (board.kind === "disconnected") return connectKey === null ? /* @__PURE__ */ jsxs(AccountNote, { children: ["Not connected. ", CONNECT_REQUIREMENT] }) : /* @__PURE__ */ jsx(ConnectFooter, { publicKey: connectKey });
121
+ return /* @__PURE__ */ jsx(AccountFooter, {
122
+ actions: [{
123
+ label: "Disconnect",
124
+ onSelect: () => disconnect(refresh),
125
+ variant: "danger"
126
+ }],
127
+ organizationName: board.organizationName,
128
+ person: workspace.kind === "ready" ? workspace.workspace.identity.user : null
129
+ });
130
+ }
131
+ //#endregion
7
132
  //#region src/devtools/client/copy-button.tsx
8
133
  const COPIED_FEEDBACK_MS = 1600;
9
134
  function CopyButton({ label, text, variant }) {
@@ -281,24 +406,29 @@ const SCOPE_LABELS = {
281
406
  board: "Whole board",
282
407
  page: "This page"
283
408
  };
409
+ const SCOPE_PLACES = {
410
+ board: "on the board",
411
+ page: "on this page"
412
+ };
284
413
  const EMPTY_SCOPE_MESSAGES = {
285
414
  board: "The board has no feedback yet.",
286
415
  page: "Nothing on the board points at this page yet."
287
416
  };
288
417
  function useBoardFeedback(scope) {
289
418
  const [state, setState] = useState({ kind: "loading" });
419
+ const latestRequest = useRef(0);
290
420
  const load = useCallback(async () => {
291
- const pagePath = scope === "page" ? window.location.pathname : null;
421
+ latestRequest.current += 1;
422
+ const request = latestRequest.current;
292
423
  setState({ kind: "loading" });
293
424
  try {
294
- const page = await fetchBoardFeedback(pagePath);
295
- setState({
425
+ const page = await fetchBoardFeedback(scope === "page" ? window.location.pathname : null);
426
+ if (request === latestRequest.current) setState({
296
427
  kind: "ready",
297
- page,
298
- place: pagePath ?? "the board"
428
+ page
299
429
  });
300
430
  } catch (error) {
301
- setState({
431
+ if (request === latestRequest.current) setState({
302
432
  kind: "failed",
303
433
  message: error instanceof Error ? error.message : "Could not reach the board."
304
434
  });
@@ -312,21 +442,46 @@ function useBoardFeedback(scope) {
312
442
  state
313
443
  };
314
444
  }
315
- function BoardFeedbackList({ onOpenCode, onShowSelector, onShowWholeBoard, scope }) {
316
- const { load, state } = useBoardFeedback(scope);
317
- return /* @__PURE__ */ jsxs(Fragment, { children: [
318
- /* @__PURE__ */ jsxs("div", {
319
- className: "dt-card-head",
320
- children: [/* @__PURE__ */ jsx("p", {
445
+ function InboxToolbar({ onRefresh, onSelectScope, scope, state }) {
446
+ return /* @__PURE__ */ jsxs("div", {
447
+ className: "dt-toolbar",
448
+ children: [
449
+ /* @__PURE__ */ jsx("span", {
321
450
  className: "dt-status",
322
- children: state.kind === "ready" ? `${state.page.total} on ${state.place}` : "Board feedback"
323
- }), /* @__PURE__ */ jsx("button", {
324
- className: "dt-btn",
451
+ children: state.kind === "ready" ? `${state.page.total} ${SCOPE_PLACES[scope]}` : SCOPE_LABELS[scope]
452
+ }),
453
+ /* @__PURE__ */ jsx(Menu, {
454
+ items: INBOX_SCOPES.map((option) => ({
455
+ checked: option === scope,
456
+ label: SCOPE_LABELS[option],
457
+ onSelect: () => onSelectScope(option)
458
+ })),
459
+ trigger: {
460
+ className: "icon-btn",
461
+ content: /* @__PURE__ */ jsx(ViewOptionsIcon, {}),
462
+ label: "Show feedback from"
463
+ }
464
+ }),
465
+ /* @__PURE__ */ jsx("button", {
466
+ "aria-label": "Refresh",
467
+ className: "icon-btn",
325
468
  disabled: state.kind === "loading",
326
- onClick: load,
469
+ onClick: onRefresh,
470
+ title: "Refresh",
327
471
  type: "button",
328
- children: "Refresh"
329
- })]
472
+ children: /* @__PURE__ */ jsx(RefreshIcon, {})
473
+ })
474
+ ]
475
+ });
476
+ }
477
+ function BoardFeedbackList({ onOpenCode, onSelectScope, onShowSelector, scope }) {
478
+ const { load, state } = useBoardFeedback(scope);
479
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
480
+ /* @__PURE__ */ jsx(InboxToolbar, {
481
+ onRefresh: load,
482
+ onSelectScope,
483
+ scope,
484
+ state
330
485
  }),
331
486
  state.kind === "loading" && /* @__PURE__ */ jsx("p", {
332
487
  className: "dt-status",
@@ -334,13 +489,14 @@ function BoardFeedbackList({ onOpenCode, onShowSelector, onShowWholeBoard, scope
334
489
  }),
335
490
  state.kind === "failed" && /* @__PURE__ */ jsx("p", {
336
491
  className: "dt-error",
492
+ role: "alert",
337
493
  children: state.message
338
494
  }),
339
495
  state.kind === "ready" && state.page.items.length === 0 && /* @__PURE__ */ jsxs("div", {
340
496
  className: "dt-empty",
341
497
  children: [/* @__PURE__ */ jsx("p", { children: EMPTY_SCOPE_MESSAGES[scope] }), scope === "page" && /* @__PURE__ */ jsx("button", {
342
498
  className: "dt-btn",
343
- onClick: onShowWholeBoard,
499
+ onClick: () => onSelectScope("board"),
344
500
  type: "button",
345
501
  children: "Show the whole board"
346
502
  })]
@@ -354,157 +510,26 @@ function BoardFeedbackList({ onOpenCode, onShowSelector, onShowWholeBoard, scope
354
510
  }
355
511
  function InboxPanel({ onOpenCode, onShowSelector }) {
356
512
  const [scope, setScope] = useState("page");
357
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
358
- className: "dt-tabs",
359
- children: INBOX_SCOPES.map((option) => /* @__PURE__ */ jsx("button", {
360
- "aria-pressed": option === scope,
361
- className: "dt-tab",
362
- onClick: () => setScope(option),
363
- type: "button",
364
- children: SCOPE_LABELS[option]
365
- }, option))
366
- }), /* @__PURE__ */ jsx(BoardFeedbackList, {
513
+ return /* @__PURE__ */ jsx(BoardFeedbackList, {
367
514
  onOpenCode,
515
+ onSelectScope: setScope,
368
516
  onShowSelector,
369
- onShowWholeBoard: () => setScope("board"),
370
517
  scope
371
- }, scope)] });
372
- }
373
- //#endregion
374
- //#region src/devtools/client/route/connect-prompt.tsx
375
- function ConnectPrompt({ publicKey, purpose }) {
376
- const [prompt, setPrompt] = useState({ kind: "idle" });
377
- const connect = async () => {
378
- const tab = window.open("", "_blank");
379
- if (tab) tab.opener = null;
380
- setPrompt({ kind: "starting" });
381
- try {
382
- const { authorizeUrl } = await startBoardConnect(publicKey);
383
- if (tab) tab.location.href = authorizeUrl;
384
- else window.open(authorizeUrl, "_blank", "noopener");
385
- setPrompt({
386
- authorizeUrl,
387
- kind: "waiting"
388
- });
389
- } catch (error) {
390
- tab?.close();
391
- setPrompt({ kind: "idle" });
392
- reportFailure("Could not connect to Reflet", error);
393
- }
394
- };
395
- if (prompt.kind === "waiting") return /* @__PURE__ */ jsxs("div", {
396
- className: "dt-empty",
397
- children: [
398
- /* @__PURE__ */ jsx("p", { children: "Finish connecting in the Reflet tab. This panel updates when you come back." }),
399
- /* @__PURE__ */ jsx("a", {
400
- className: "dt-btn",
401
- href: prompt.authorizeUrl,
402
- rel: "noopener noreferrer",
403
- target: "_blank",
404
- children: "Open Reflet again"
405
- }),
406
- /* @__PURE__ */ jsx("button", {
407
- className: "dt-btn",
408
- onClick: connect,
409
- type: "button",
410
- children: "Start again"
411
- })
412
- ]
413
- });
414
- return /* @__PURE__ */ jsxs("div", {
415
- className: "dt-empty",
416
- children: [/* @__PURE__ */ jsx("p", { children: purpose }), /* @__PURE__ */ jsx("button", {
417
- className: "dt-btn",
418
- "data-variant": "primary",
419
- disabled: prompt.kind === "starting",
420
- onClick: connect,
421
- type: "button",
422
- children: "Connect to Reflet"
423
- })]
424
518
  });
425
519
  }
426
520
  //#endregion
427
- //#region src/devtools/client/route/route-hint.tsx
428
- function MissingRouteHint({ purpose }) {
429
- return /* @__PURE__ */ jsxs("div", {
430
- className: "dt-empty",
431
- children: [
432
- /* @__PURE__ */ jsxs("p", { children: [
433
- "Add the Reflet dev route to ",
434
- purpose,
435
- "."
436
- ] }),
437
- /* @__PURE__ */ jsxs("p", { children: [
438
- "Next.js: ",
439
- /* @__PURE__ */ jsx("code", { children: "app/api/reflet-devtools/[...path]/route.ts" }),
440
- /* @__PURE__ */ jsx("br", {}),
441
- /* @__PURE__ */ jsxs("code", { children: [
442
- "export ",
443
- "{ GET, POST }",
444
- " from \"reflet-sdk/devtools/next\";"
445
- ] })
446
- ] }),
447
- /* @__PURE__ */ jsxs("p", { children: [
448
- "Vite: ",
449
- /* @__PURE__ */ jsx("code", { children: "plugins: [refletDevtools()]" }),
450
- " from",
451
- " ",
452
- /* @__PURE__ */ jsx("code", { children: "reflet-sdk/devtools/vite" })
453
- ] })
454
- ]
455
- });
456
- }
457
- const MISSING_SECRET_KEY_HINT = "Set REFLET_SECRET_KEY (your fb_sec_ key) in the dev server environment to reach your board.";
458
- //#endregion
459
521
  //#region src/devtools/client/board/board-tab.tsx
460
- function ConnectionBar({ organizationName, refresh }) {
461
- const [isDisconnecting, setIsDisconnecting] = useState(false);
462
- const disconnect = async () => {
463
- setIsDisconnecting(true);
464
- try {
465
- const { revoked } = await disconnectBoard();
466
- if (!revoked) reportFailure("The Reflet token was not revoked", /* @__PURE__ */ new Error("Disconnected here, but Reflet could not be reached. Revoke it from Account → Devtools on reflet.app."));
467
- } catch (error) {
468
- reportFailure("The dev server did not disconnect", error);
469
- }
470
- setIsDisconnecting(false);
471
- refresh();
472
- };
473
- return /* @__PURE__ */ jsxs("div", {
474
- className: "dt-actions",
475
- children: [/* @__PURE__ */ jsxs("span", {
476
- className: "dt-status",
477
- children: ["Connected to ", organizationName]
478
- }), /* @__PURE__ */ jsx("button", {
479
- className: "dt-btn",
480
- "data-variant": "danger",
481
- disabled: isDisconnecting,
482
- onClick: disconnect,
483
- type: "button",
484
- children: "Disconnect"
485
- })]
486
- });
487
- }
488
- function BoardTab({ canConnect, onOpenCode, onShowSelector, publicKey, refresh, route }) {
522
+ function BoardTab({ canConnect, onOpenCode, onShowSelector, route }) {
489
523
  if (route.kind === "missing") return /* @__PURE__ */ jsx(MissingRouteHint, { purpose: "see your board's feedback for this page" });
490
524
  if (route.kind === "checking") return null;
491
- const { board } = route.status;
492
- if (board.kind === "disconnected") return canConnect && publicKey !== null ? /* @__PURE__ */ jsx(ConnectPrompt, {
493
- publicKey,
494
- purpose: "See this page's feedback and send notes to your board."
495
- }) : /* @__PURE__ */ jsx("p", {
496
- className: "dt-hint",
497
- children: MISSING_SECRET_KEY_HINT
525
+ if (route.status.board.kind === "disconnected") return /* @__PURE__ */ jsx("p", {
526
+ className: "dt-empty",
527
+ children: canConnect ? "Connect to Reflet below to see this page's feedback and add notes to your board." : MISSING_SECRET_KEY_HINT
498
528
  });
499
- const inbox = /* @__PURE__ */ jsx(InboxPanel, {
529
+ return /* @__PURE__ */ jsx(InboxPanel, {
500
530
  onOpenCode,
501
531
  onShowSelector
502
532
  });
503
- if (board.kind === "secretKey") return inbox;
504
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ConnectionBar, {
505
- organizationName: board.organizationName,
506
- refresh
507
- }), inbox] });
508
533
  }
509
534
  /**
510
535
  * @license bippy
@@ -3189,36 +3214,78 @@ function SendToBoard({ activity, sendBlockedReason, unsent }) {
3189
3214
  disabled: sendBlockedReason !== null || ready.length === 0,
3190
3215
  onClick: () => sendNotesToBoard(ready),
3191
3216
  type: "button",
3192
- children: [ready.length > 0 && `Send ${ready.length} to board`, ready.length === 0 && (unsent.some((note) => activity[note.id] === "locating") ? "Locating…" : "Sending…")]
3217
+ children: [ready.length > 0 && `Add ${ready.length} to board`, ready.length === 0 && (unsent.some((note) => activity[note.id] === "locating") ? "Locating…" : "Adding…")]
3193
3218
  }),
3194
3219
  busyCount > 0 && ready.length > 0 && /* @__PURE__ */ jsxs("span", {
3195
3220
  className: "dt-status",
3196
- children: [busyCount, " still locating or sending"]
3221
+ children: [busyCount, " still locating or adding"]
3197
3222
  })
3198
3223
  ] });
3199
3224
  }
3200
- function NotesPanel({ notes: snapshot, onOpenCode, onShowSelector, sendBlockedReason }) {
3201
- const { activity, error, notes: allNotes, status } = snapshot;
3202
- if (status === "failed") return /* @__PURE__ */ jsxs("p", {
3203
- className: "dt-error",
3204
- children: ["Notes cannot be stored: ", error]
3225
+ function NotesEmpty({ onPick }) {
3226
+ return /* @__PURE__ */ jsxs("div", {
3227
+ className: "dt-empty",
3228
+ children: [
3229
+ /* @__PURE__ */ jsx("h2", {
3230
+ className: "dt-empty-title",
3231
+ children: "No notes yet"
3232
+ }),
3233
+ /* @__PURE__ */ jsx("p", { children: "Pick an element on the page and write what should change. Shift-click it to open its code instead." }),
3234
+ /* @__PURE__ */ jsxs("button", {
3235
+ className: "dt-btn",
3236
+ "data-variant": "primary",
3237
+ onClick: onPick,
3238
+ type: "button",
3239
+ children: [/* @__PURE__ */ jsx(TargetIcon, {}), "Pick an element"]
3240
+ })
3241
+ ]
3205
3242
  });
3206
- if (status === "loading") return /* @__PURE__ */ jsx("p", {
3207
- className: "dt-status",
3208
- children: "Loading notes…"
3243
+ }
3244
+ function NotesToolbar({ count, onPick }) {
3245
+ return /* @__PURE__ */ jsxs("div", {
3246
+ className: "dt-toolbar",
3247
+ children: [/* @__PURE__ */ jsx("span", {
3248
+ className: "dt-status",
3249
+ children: countLabel(count, "note")
3250
+ }), /* @__PURE__ */ jsx("button", {
3251
+ "aria-label": "Pick an element to note",
3252
+ className: "icon-btn",
3253
+ onClick: onPick,
3254
+ title: "Pick an element to note",
3255
+ type: "button",
3256
+ children: /* @__PURE__ */ jsx(TargetIcon, {})
3257
+ })]
3209
3258
  });
3210
- const notes = allNotes.filter(isActiveNote);
3211
- const archived = allNotes.filter((note) => !isActiveNote(note));
3212
- if (notes.length === 0) return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
3213
- className: "dt-empty",
3214
- children: [/* @__PURE__ */ jsx("p", { children: "No notes yet." }), /* @__PURE__ */ jsx("p", { children: "Use the pick button, then click an element to write one — or Shift+click it to open its code." })]
3215
- }), /* @__PURE__ */ jsx(ArchivedNotes, { archived })] });
3259
+ }
3260
+ function NotesFooter({ activity, notes, sendBlockedReason }) {
3261
+ const clearable = notes.filter((note) => activity[note.id] !== "sending").map((note) => note.id);
3262
+ return /* @__PURE__ */ jsxs("div", {
3263
+ className: "dt-sheet-foot",
3264
+ children: [
3265
+ /* @__PURE__ */ jsx(CopyButton, {
3266
+ label: `Copy ${notes.length} as prompt`,
3267
+ text: notesToPrompt(notes),
3268
+ variant: "primary"
3269
+ }),
3270
+ /* @__PURE__ */ jsx(SendToBoard, {
3271
+ activity,
3272
+ sendBlockedReason,
3273
+ unsent: notes.filter((note) => !note.sent)
3274
+ }),
3275
+ clearable.length > 0 && /* @__PURE__ */ jsx("button", {
3276
+ className: "dt-btn",
3277
+ onClick: () => archiveNotes(clearable),
3278
+ type: "button",
3279
+ children: "Clear notes"
3280
+ })
3281
+ ]
3282
+ });
3283
+ }
3284
+ function NoteGroups({ activity, notes, onOpenCode, onShowSelector }) {
3216
3285
  const { pathname } = window.location;
3217
3286
  const numbers = pinNumbers(notes, pathname);
3218
3287
  const onThisPage = notes.filter((note) => isNoteOnPage(note, pathname));
3219
3288
  const elsewhere = notes.filter((note) => !isNoteOnPage(note, pathname));
3220
- const unsent = notes.filter((note) => !note.sent);
3221
- const clearable = notes.filter((note) => activity[note.id] !== "sending").map((note) => note.id);
3222
3289
  const renderNote = (note, isHere) => /* @__PURE__ */ jsx(NoteCard, {
3223
3290
  isLocating: activity[note.id] === "locating",
3224
3291
  isSending: activity[note.id] === "sending",
@@ -3238,28 +3305,38 @@ function NotesPanel({ notes: snapshot, onOpenCode, onShowSelector, sendBlockedRe
3238
3305
  className: "dt-group",
3239
3306
  children: "Other pages"
3240
3307
  }),
3241
- elsewhere.map((note) => renderNote(note, false)),
3308
+ elsewhere.map((note) => renderNote(note, false))
3309
+ ] });
3310
+ }
3311
+ function NotesPanel({ notes: snapshot, onOpenCode, onPick, onShowSelector, sendBlockedReason }) {
3312
+ const { activity, error, notes: allNotes, status } = snapshot;
3313
+ if (status === "failed") return /* @__PURE__ */ jsxs("p", {
3314
+ className: "dt-error",
3315
+ children: ["Notes cannot be stored: ", error]
3316
+ });
3317
+ if (status === "loading") return /* @__PURE__ */ jsx("p", {
3318
+ className: "dt-status",
3319
+ children: "Loading notes…"
3320
+ });
3321
+ const notes = allNotes.filter(isActiveNote);
3322
+ const archived = allNotes.filter((note) => !isActiveNote(note));
3323
+ if (notes.length === 0) return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(NotesEmpty, { onPick }), /* @__PURE__ */ jsx(ArchivedNotes, { archived })] });
3324
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
3325
+ /* @__PURE__ */ jsx(NotesToolbar, {
3326
+ count: notes.length,
3327
+ onPick
3328
+ }),
3329
+ /* @__PURE__ */ jsx(NoteGroups, {
3330
+ activity,
3331
+ notes,
3332
+ onOpenCode,
3333
+ onShowSelector
3334
+ }),
3242
3335
  /* @__PURE__ */ jsx(ArchivedNotes, { archived }),
3243
- /* @__PURE__ */ jsxs("div", {
3244
- className: "dt-sheet-foot",
3245
- children: [
3246
- /* @__PURE__ */ jsx(CopyButton, {
3247
- label: `Copy ${notes.length} as prompt`,
3248
- text: notesToPrompt(notes),
3249
- variant: "primary"
3250
- }),
3251
- /* @__PURE__ */ jsx(SendToBoard, {
3252
- activity,
3253
- sendBlockedReason,
3254
- unsent
3255
- }),
3256
- clearable.length > 0 && /* @__PURE__ */ jsx("button", {
3257
- className: "dt-btn",
3258
- onClick: () => archiveNotes(clearable),
3259
- type: "button",
3260
- children: "Clear notes"
3261
- })
3262
- ]
3336
+ /* @__PURE__ */ jsx(NotesFooter, {
3337
+ activity,
3338
+ notes,
3339
+ sendBlockedReason
3263
3340
  })
3264
3341
  ] });
3265
3342
  }
@@ -3441,12 +3518,8 @@ function useDevtoolsSessions(options) {
3441
3518
  surface: "devtools",
3442
3519
  transport: isConnected ? DEVTOOLS_TRANSPORT : null
3443
3520
  });
3444
- const [open, setOpen] = useState(null);
3445
3521
  return {
3446
- navigation: {
3447
- onOpen: setOpen,
3448
- open
3449
- },
3522
+ navigation: useSessionNavigation(),
3450
3523
  state
3451
3524
  };
3452
3525
  }
@@ -3457,16 +3530,7 @@ function SessionsScope({ children, sessions }) {
3457
3530
  children
3458
3531
  });
3459
3532
  }
3460
- function SessionsConnect({ connectKey }) {
3461
- if (connectKey === null) return /* @__PURE__ */ jsx("p", {
3462
- className: "dt-empty",
3463
- children: "Sessions are recorded under your Reflet account. Connecting needs this app on localhost with a Reflet public key."
3464
- });
3465
- return /* @__PURE__ */ jsx(ConnectPrompt, {
3466
- publicKey: connectKey,
3467
- purpose: "Sessions are recorded under your Reflet account."
3468
- });
3469
- }
3533
+ const SESSIONS_NEED_ACCOUNT = "Sessions are recorded under your Reflet account.";
3470
3534
  function WorkspaceGate({ actions, sessions }) {
3471
3535
  const { state } = sessions;
3472
3536
  if (state.kind === "ready") return /* @__PURE__ */ jsx(SessionsPanel, {
@@ -3489,17 +3553,24 @@ function WorkspaceGate({ actions, sessions }) {
3489
3553
  children: "Opening your sessions…"
3490
3554
  });
3491
3555
  }
3492
- function SessionsTab({ actions, connectKey, route, sessions }) {
3556
+ function SessionsTab({ actions, canConnect, route, sessions }) {
3493
3557
  if (route.kind === "checking") return /* @__PURE__ */ jsx("p", {
3494
3558
  className: "dt-status",
3495
3559
  children: "Connecting to the Reflet dev route…"
3496
3560
  });
3497
3561
  if (route.kind === "missing") return /* @__PURE__ */ jsx(MissingRouteHint, { purpose: "record sessions under your Reflet account" });
3498
3562
  const { board } = route.status;
3499
- if (board.kind === "disconnected") return /* @__PURE__ */ jsx(SessionsConnect, { connectKey });
3500
- if (board.kind === "secretKey") return /* @__PURE__ */ jsx("p", {
3563
+ if (board.kind === "disconnected") return /* @__PURE__ */ jsxs("p", {
3501
3564
  className: "dt-empty",
3502
- children: "Sessions are recorded under your own name, so they need Connect in the Board tab rather than REFLET_SECRET_KEY."
3565
+ children: [
3566
+ SESSIONS_NEED_ACCOUNT,
3567
+ " ",
3568
+ canConnect ? "Connect to Reflet below to start one." : CONNECT_REQUIREMENT
3569
+ ]
3570
+ });
3571
+ if (board.kind === "secretKey") return /* @__PURE__ */ jsxs("p", {
3572
+ className: "dt-empty",
3573
+ children: [SESSIONS_NEED_ACCOUNT, " REFLET_SECRET_KEY can't stand for you: remove it from the dev server environment, then connect to Reflet."]
3503
3574
  });
3504
3575
  return /* @__PURE__ */ jsx(WorkspaceGate, {
3505
3576
  actions,
@@ -3507,6 +3578,38 @@ function SessionsTab({ actions, connectKey, route, sessions }) {
3507
3578
  });
3508
3579
  }
3509
3580
  //#endregion
3581
+ //#region src/devtools/client/use-sheet-tab.ts
3582
+ const TAB_KEY = "reflet-devtools-sheet-tab";
3583
+ const FIRST_SHEET_TAB = "inbox";
3584
+ function readStoredTab() {
3585
+ try {
3586
+ const stored = localStorage.getItem(TAB_KEY);
3587
+ return SHEET_TABS.find((tab) => tab === stored) ?? "inbox";
3588
+ } catch {
3589
+ return FIRST_SHEET_TAB;
3590
+ }
3591
+ }
3592
+ function storeTab(tab) {
3593
+ try {
3594
+ localStorage.setItem(TAB_KEY, tab);
3595
+ } catch {}
3596
+ }
3597
+ /** The panel reopens where it was left, across reloads. */
3598
+ function useSheetTab() {
3599
+ const [openTab, setOpenTab] = useState(null);
3600
+ const [lastTab, setLastTab] = useState(readStoredTab);
3601
+ return {
3602
+ close: () => setOpenTab(null),
3603
+ lastTab,
3604
+ openTab,
3605
+ select: (tab) => {
3606
+ setOpenTab(tab);
3607
+ setLastTab(tab);
3608
+ storeTab(tab);
3609
+ }
3610
+ };
3611
+ }
3612
+ //#endregion
3510
3613
  //#region src/devtools/client/devtools-layer.tsx
3511
3614
  const PICKER_LABELS = {
3512
3615
  ...DEFAULT_WIDGET_LABELS,
@@ -3517,9 +3620,9 @@ const PICKER_LABELS = {
3517
3620
  };
3518
3621
  function sendBlockedReason(route, canConnect) {
3519
3622
  if (route.kind === "checking") return "Connecting to the Reflet dev route…";
3520
- if (route.kind === "missing") return "Add the Reflet dev route to send notes to your board.";
3623
+ if (route.kind === "missing") return "Add the Reflet dev route to add notes to your board.";
3521
3624
  if (route.status.board.kind !== "disconnected") return null;
3522
- return canConnect ? "Connect to Reflet in the Board tab to send notes." : MISSING_SECRET_KEY_HINT;
3625
+ return canConnect ? "Connect to Reflet below to add notes to your board." : MISSING_SECRET_KEY_HINT;
3523
3626
  }
3524
3627
  function canConnectBoard(route, publicKey) {
3525
3628
  return route.kind === "ready" && route.status.board.kind === "disconnected" && route.status.board.canConnect && publicKey !== null;
@@ -3551,6 +3654,37 @@ function useResumeLocating(notes, route) {
3551
3654
  }
3552
3655
  }, [notes, route.kind]);
3553
3656
  }
3657
+ function SheetPanel({ canConnect, codeLookup, handlers, notes, openTab, route, sessions }) {
3658
+ if (openTab === "notes") return /* @__PURE__ */ jsx(NotesPanel, {
3659
+ notes,
3660
+ onOpenCode: handlers.onOpenCode,
3661
+ onPick: handlers.onPick,
3662
+ onShowSelector: handlers.onShowSelector,
3663
+ sendBlockedReason: sendBlockedReason(route, canConnect)
3664
+ });
3665
+ if (openTab === "inbox") return /* @__PURE__ */ jsx(BoardTab, {
3666
+ canConnect,
3667
+ onOpenCode: handlers.onOpenCode,
3668
+ onShowSelector: handlers.onShowSelector,
3669
+ route
3670
+ });
3671
+ if (openTab === "sessions") return /* @__PURE__ */ jsx(SessionsTab, {
3672
+ actions: {
3673
+ onStarted: handlers.onClose,
3674
+ onWriteNote: () => {
3675
+ handlers.onClose();
3676
+ handlers.onPick();
3677
+ }
3678
+ },
3679
+ canConnect,
3680
+ route,
3681
+ sessions
3682
+ });
3683
+ return codeLookup && /* @__PURE__ */ jsx(CodeTab, {
3684
+ lookup: codeLookup,
3685
+ route
3686
+ });
3687
+ }
3554
3688
  function DevtoolsLayer({ isWidgetOpen, position, publicKey }) {
3555
3689
  const { refresh, route } = useDevRoute();
3556
3690
  const notes = useNotes();
@@ -3560,18 +3694,19 @@ function DevtoolsLayer({ isWidgetOpen, position, publicKey }) {
3560
3694
  route
3561
3695
  });
3562
3696
  const [isPicking, setIsPicking] = useState(false);
3563
- const [openTab, setOpenTab] = useState(null);
3697
+ const sheet = useSheetTab();
3698
+ const { openTab } = sheet;
3564
3699
  const [highlighted, setHighlighted] = useState(null);
3565
- const { codeLookup, inspectElement, openCode } = useCodeLookup(setOpenTab);
3700
+ const { codeLookup, inspectElement, openCode } = useCodeLookup(sheet.select);
3566
3701
  const edge = position.startsWith("top") ? "top" : "bottom";
3567
3702
  const side = position.endsWith("right") ? "right" : "left";
3568
3703
  const canReadSource = route.kind === "ready";
3569
3704
  const canConnect = canConnectBoard(route, publicKey);
3570
3705
  const isRecessed = isWidgetOpen || isPicking;
3571
- const closeSheet = useCallback(() => {
3572
- setOpenTab(null);
3706
+ const closeSheet = () => {
3707
+ sheet.close();
3573
3708
  setHighlighted(null);
3574
- }, []);
3709
+ };
3575
3710
  const startPick = useCallback(() => setIsPicking(true), []);
3576
3711
  const cancelPick = useCallback(() => setIsPicking(false), []);
3577
3712
  const showSelector = useCallback((selector) => {
@@ -3602,20 +3737,9 @@ function DevtoolsLayer({ isWidgetOpen, position, publicKey }) {
3602
3737
  copyToClipboard(draftLocatedNote(element, text, canReadSource).then(noteAsContext)).then(() => announce(`Copied ${title}`)).catch((error) => reportFailure(`${title} was not copied`, error));
3603
3738
  }
3604
3739
  }), [canReadSource]);
3605
- const tabs = codeLookup ? [
3606
- "notes",
3607
- "sessions",
3608
- "inbox",
3609
- "code"
3610
- ] : [
3611
- "notes",
3612
- "sessions",
3613
- "inbox"
3614
- ];
3615
- const writeNoteInstead = () => {
3616
- closeSheet();
3617
- startPick();
3618
- };
3740
+ const tabs = SHEET_TABS.filter((tab) => tab !== "code" || codeLookup);
3741
+ const openPanel = (tab) => sheet.select(tab ?? (tabs.includes(sheet.lastTab) ? sheet.lastTab : "inbox"));
3742
+ const sheetPlace = openTab === "sessions" && sessions.state.kind === "ready" ? sessionSheetPlace(sessions.navigation) : null;
3619
3743
  return /* @__PURE__ */ jsx(SessionsScope, {
3620
3744
  sessions,
3621
3745
  children: /* @__PURE__ */ jsxs("div", {
@@ -3629,58 +3753,53 @@ function DevtoolsLayer({ isWidgetOpen, position, publicKey }) {
3629
3753
  notes,
3630
3754
  onOpenCode: openCode
3631
3755
  }),
3632
- openTab && /* @__PURE__ */ jsxs(DevtoolsSheet, {
3756
+ openTab && /* @__PURE__ */ jsx(DevtoolsSheet, {
3757
+ footer: sheetPlace === null && /* @__PURE__ */ jsx(DevtoolsAccount, {
3758
+ connectKey: canConnect ? publicKey : null,
3759
+ refresh,
3760
+ route,
3761
+ workspace: sessions.state
3762
+ }),
3763
+ navigation: {
3764
+ onSelectTab: sheet.select,
3765
+ place: sheetPlace,
3766
+ tab: openTab,
3767
+ tabs
3768
+ },
3633
3769
  onClose: closeSheet,
3634
- onPick: startPick,
3635
- onSelectTab: setOpenTab,
3636
3770
  side,
3637
- tab: openTab,
3638
- tabs,
3639
- children: [
3640
- openTab === "notes" && /* @__PURE__ */ jsx(NotesPanel, {
3641
- notes,
3771
+ children: /* @__PURE__ */ jsx(SheetPanel, {
3772
+ canConnect,
3773
+ codeLookup,
3774
+ handlers: {
3775
+ onClose: closeSheet,
3642
3776
  onOpenCode: openCode,
3643
- onShowSelector: showSelector,
3644
- sendBlockedReason: sendBlockedReason(route, canConnect)
3645
- }),
3646
- openTab === "inbox" && /* @__PURE__ */ jsx(BoardTab, {
3647
- canConnect,
3648
- onOpenCode: openCode,
3649
- onShowSelector: showSelector,
3650
- publicKey,
3651
- refresh,
3652
- route
3653
- }),
3654
- openTab === "sessions" && /* @__PURE__ */ jsx(SessionsTab, {
3655
- actions: {
3656
- onStarted: closeSheet,
3657
- onWriteNote: writeNoteInstead
3658
- },
3659
- connectKey: canConnect ? publicKey : null,
3660
- route,
3661
- sessions
3662
- }),
3663
- openTab === "code" && codeLookup && /* @__PURE__ */ jsx(CodeTab, {
3664
- lookup: codeLookup,
3665
- route
3666
- })
3667
- ]
3777
+ onPick: startPick,
3778
+ onShowSelector: showSelector
3779
+ },
3780
+ notes,
3781
+ openTab,
3782
+ route,
3783
+ sessions
3784
+ })
3668
3785
  }),
3669
3786
  /* @__PURE__ */ jsx(DevtoolsBar, {
3670
3787
  edge,
3671
3788
  side,
3672
3789
  children: /* @__PURE__ */ jsx(DevtoolsBarButtons, {
3673
- noteCount: unsentNoteCount(notes),
3790
+ onOpen: openPanel,
3674
3791
  onPick: startPick,
3675
- onToggleSheet: (tab) => setOpenTab((current) => current === tab ? null : tab),
3676
- openTab
3792
+ unsentNoteCount: unsentNoteCount(notes)
3677
3793
  })
3678
3794
  }),
3679
3795
  openTab && /* @__PURE__ */ jsx(SelectionOutline, { node: highlighted }),
3680
3796
  /* @__PURE__ */ jsx(Notices, {}),
3681
3797
  sessions.state.kind === "ready" && /* @__PURE__ */ jsx(RecordingLayer, { onFinished: (target) => {
3682
- sessions.navigation.onOpen(target);
3683
- setOpenTab("sessions");
3798
+ sessions.navigation.go({
3799
+ target,
3800
+ view: "review"
3801
+ });
3802
+ sheet.select("sessions");
3684
3803
  } }),
3685
3804
  isPicking && /* @__PURE__ */ jsx(ElementPicker, {
3686
3805
  copy,
@@ -3696,4 +3815,4 @@ function DevtoolsLayer({ isWidgetOpen, position, publicKey }) {
3696
3815
  //#endregion
3697
3816
  export { DevtoolsLayer };
3698
3817
 
3699
- //# sourceMappingURL=devtools-layer-CsIcpepI.js.map
3818
+ //# sourceMappingURL=devtools-layer-CcTmlikp.js.map