reflet-sdk 0.7.2 → 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-Dx892JVJ.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-BycJng5z.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,152 +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/route-hint.tsx
375
- function MissingRouteHint({ purpose }) {
376
- return /* @__PURE__ */ jsxs("div", {
377
- className: "dt-empty",
378
- children: [
379
- /* @__PURE__ */ jsxs("p", { children: [
380
- "Add the Reflet dev route to ",
381
- purpose,
382
- "."
383
- ] }),
384
- /* @__PURE__ */ jsxs("p", { children: [
385
- "Next.js: ",
386
- /* @__PURE__ */ jsx("code", { children: "app/api/reflet-devtools/[...path]/route.ts" }),
387
- /* @__PURE__ */ jsx("br", {}),
388
- /* @__PURE__ */ jsxs("code", { children: [
389
- "export ",
390
- "{ GET, POST }",
391
- " from \"reflet-sdk/devtools/next\";"
392
- ] })
393
- ] }),
394
- /* @__PURE__ */ jsxs("p", { children: [
395
- "Vite: ",
396
- /* @__PURE__ */ jsx("code", { children: "plugins: [refletDevtools()]" }),
397
- " from",
398
- " ",
399
- /* @__PURE__ */ jsx("code", { children: "reflet-sdk/devtools/vite" })
400
- ] })
401
- ]
402
518
  });
403
519
  }
404
- const MISSING_SECRET_KEY_HINT = "Set REFLET_SECRET_KEY (your fb_sec_ key) in the dev server environment to reach your board.";
405
520
  //#endregion
406
521
  //#region src/devtools/client/board/board-tab.tsx
407
- function ConnectPrompt({ publicKey }) {
408
- const [prompt, setPrompt] = useState({ kind: "idle" });
409
- const connect = async () => {
410
- const tab = window.open("", "_blank");
411
- if (tab) tab.opener = null;
412
- setPrompt({ kind: "starting" });
413
- try {
414
- const { authorizeUrl } = await startBoardConnect(publicKey);
415
- if (tab) tab.location.href = authorizeUrl;
416
- else window.open(authorizeUrl, "_blank", "noopener");
417
- setPrompt({
418
- authorizeUrl,
419
- kind: "waiting"
420
- });
421
- } catch (error) {
422
- tab?.close();
423
- setPrompt({ kind: "idle" });
424
- reportFailure("Could not connect to Reflet", error);
425
- }
426
- };
427
- if (prompt.kind === "waiting") return /* @__PURE__ */ jsxs("div", {
428
- className: "dt-empty",
429
- children: [
430
- /* @__PURE__ */ jsx("p", { children: "Finish connecting in the Reflet tab. This panel updates when you come back." }),
431
- /* @__PURE__ */ jsx("a", {
432
- className: "dt-btn",
433
- href: prompt.authorizeUrl,
434
- rel: "noopener noreferrer",
435
- target: "_blank",
436
- children: "Open Reflet again"
437
- }),
438
- /* @__PURE__ */ jsx("button", {
439
- className: "dt-btn",
440
- onClick: connect,
441
- type: "button",
442
- children: "Start again"
443
- })
444
- ]
445
- });
446
- return /* @__PURE__ */ jsxs("div", {
447
- className: "dt-empty",
448
- children: [/* @__PURE__ */ jsx("p", { children: "See this page's feedback and send notes to your board." }), /* @__PURE__ */ jsx("button", {
449
- className: "dt-btn",
450
- "data-variant": "primary",
451
- disabled: prompt.kind === "starting",
452
- onClick: connect,
453
- type: "button",
454
- children: "Connect to Reflet"
455
- })]
456
- });
457
- }
458
- function ConnectionBar({ organizationName, refresh }) {
459
- const [isDisconnecting, setIsDisconnecting] = useState(false);
460
- const disconnect = async () => {
461
- setIsDisconnecting(true);
462
- try {
463
- const { revoked } = await disconnectBoard();
464
- 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."));
465
- } catch (error) {
466
- reportFailure("The dev server did not disconnect", error);
467
- }
468
- setIsDisconnecting(false);
469
- refresh();
470
- };
471
- return /* @__PURE__ */ jsxs("div", {
472
- className: "dt-actions",
473
- children: [/* @__PURE__ */ jsxs("span", {
474
- className: "dt-status",
475
- children: ["Connected to ", organizationName]
476
- }), /* @__PURE__ */ jsx("button", {
477
- className: "dt-btn",
478
- "data-variant": "danger",
479
- disabled: isDisconnecting,
480
- onClick: disconnect,
481
- type: "button",
482
- children: "Disconnect"
483
- })]
484
- });
485
- }
486
- function BoardTab({ canConnect, onOpenCode, onShowSelector, publicKey, refresh, route }) {
522
+ function BoardTab({ canConnect, onOpenCode, onShowSelector, route }) {
487
523
  if (route.kind === "missing") return /* @__PURE__ */ jsx(MissingRouteHint, { purpose: "see your board's feedback for this page" });
488
524
  if (route.kind === "checking") return null;
489
- const { board } = route.status;
490
- if (board.kind === "disconnected") return canConnect && publicKey !== null ? /* @__PURE__ */ jsx(ConnectPrompt, { publicKey }) : /* @__PURE__ */ jsx("p", {
491
- className: "dt-hint",
492
- 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
493
528
  });
494
- const inbox = /* @__PURE__ */ jsx(InboxPanel, {
529
+ return /* @__PURE__ */ jsx(InboxPanel, {
495
530
  onOpenCode,
496
531
  onShowSelector
497
532
  });
498
- if (board.kind === "secretKey") return inbox;
499
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ConnectionBar, {
500
- organizationName: board.organizationName,
501
- refresh
502
- }), inbox] });
503
533
  }
504
534
  /**
505
535
  * @license bippy
@@ -3184,36 +3214,78 @@ function SendToBoard({ activity, sendBlockedReason, unsent }) {
3184
3214
  disabled: sendBlockedReason !== null || ready.length === 0,
3185
3215
  onClick: () => sendNotesToBoard(ready),
3186
3216
  type: "button",
3187
- 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…")]
3188
3218
  }),
3189
3219
  busyCount > 0 && ready.length > 0 && /* @__PURE__ */ jsxs("span", {
3190
3220
  className: "dt-status",
3191
- children: [busyCount, " still locating or sending"]
3221
+ children: [busyCount, " still locating or adding"]
3192
3222
  })
3193
3223
  ] });
3194
3224
  }
3195
- function NotesPanel({ notes: snapshot, onOpenCode, onShowSelector, sendBlockedReason }) {
3196
- const { activity, error, notes: allNotes, status } = snapshot;
3197
- if (status === "failed") return /* @__PURE__ */ jsxs("p", {
3198
- className: "dt-error",
3199
- 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
+ ]
3200
3242
  });
3201
- if (status === "loading") return /* @__PURE__ */ jsx("p", {
3202
- className: "dt-status",
3203
- 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
+ })]
3204
3258
  });
3205
- const notes = allNotes.filter(isActiveNote);
3206
- const archived = allNotes.filter((note) => !isActiveNote(note));
3207
- if (notes.length === 0) return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
3208
- className: "dt-empty",
3209
- 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." })]
3210
- }), /* @__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 }) {
3211
3285
  const { pathname } = window.location;
3212
3286
  const numbers = pinNumbers(notes, pathname);
3213
3287
  const onThisPage = notes.filter((note) => isNoteOnPage(note, pathname));
3214
3288
  const elsewhere = notes.filter((note) => !isNoteOnPage(note, pathname));
3215
- const unsent = notes.filter((note) => !note.sent);
3216
- const clearable = notes.filter((note) => activity[note.id] !== "sending").map((note) => note.id);
3217
3289
  const renderNote = (note, isHere) => /* @__PURE__ */ jsx(NoteCard, {
3218
3290
  isLocating: activity[note.id] === "locating",
3219
3291
  isSending: activity[note.id] === "sending",
@@ -3233,28 +3305,38 @@ function NotesPanel({ notes: snapshot, onOpenCode, onShowSelector, sendBlockedRe
3233
3305
  className: "dt-group",
3234
3306
  children: "Other pages"
3235
3307
  }),
3236
- 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
+ }),
3237
3335
  /* @__PURE__ */ jsx(ArchivedNotes, { archived }),
3238
- /* @__PURE__ */ jsxs("div", {
3239
- className: "dt-sheet-foot",
3240
- children: [
3241
- /* @__PURE__ */ jsx(CopyButton, {
3242
- label: `Copy ${notes.length} as prompt`,
3243
- text: notesToPrompt(notes),
3244
- variant: "primary"
3245
- }),
3246
- /* @__PURE__ */ jsx(SendToBoard, {
3247
- activity,
3248
- sendBlockedReason,
3249
- unsent
3250
- }),
3251
- clearable.length > 0 && /* @__PURE__ */ jsx("button", {
3252
- className: "dt-btn",
3253
- onClick: () => archiveNotes(clearable),
3254
- type: "button",
3255
- children: "Clear notes"
3256
- })
3257
- ]
3336
+ /* @__PURE__ */ jsx(NotesFooter, {
3337
+ activity,
3338
+ notes,
3339
+ sendBlockedReason
3258
3340
  })
3259
3341
  ] });
3260
3342
  }
@@ -3436,12 +3518,8 @@ function useDevtoolsSessions(options) {
3436
3518
  surface: "devtools",
3437
3519
  transport: isConnected ? DEVTOOLS_TRANSPORT : null
3438
3520
  });
3439
- const [open, setOpen] = useState(null);
3440
3521
  return {
3441
- navigation: {
3442
- onOpen: setOpen,
3443
- open
3444
- },
3522
+ navigation: useSessionNavigation(),
3445
3523
  state
3446
3524
  };
3447
3525
  }
@@ -3452,17 +3530,7 @@ function SessionsScope({ children, sessions }) {
3452
3530
  children
3453
3531
  });
3454
3532
  }
3455
- function ConnectHint({ onConnect }) {
3456
- return /* @__PURE__ */ jsxs("div", {
3457
- className: "dt-empty",
3458
- children: [/* @__PURE__ */ jsx("p", { children: "Sessions are saved under your Reflet account. Connect in the Board tab." }), /* @__PURE__ */ jsx("button", {
3459
- className: "dt-btn",
3460
- onClick: onConnect,
3461
- type: "button",
3462
- children: "Open the Board tab"
3463
- })]
3464
- });
3465
- }
3533
+ const SESSIONS_NEED_ACCOUNT = "Sessions are recorded under your Reflet account.";
3466
3534
  function WorkspaceGate({ actions, sessions }) {
3467
3535
  const { state } = sessions;
3468
3536
  if (state.kind === "ready") return /* @__PURE__ */ jsx(SessionsPanel, {
@@ -3485,17 +3553,24 @@ function WorkspaceGate({ actions, sessions }) {
3485
3553
  children: "Opening your sessions…"
3486
3554
  });
3487
3555
  }
3488
- function SessionsTab({ actions, route, sessions }) {
3556
+ function SessionsTab({ actions, canConnect, route, sessions }) {
3489
3557
  if (route.kind === "checking") return /* @__PURE__ */ jsx("p", {
3490
3558
  className: "dt-status",
3491
3559
  children: "Connecting to the Reflet dev route…"
3492
3560
  });
3493
3561
  if (route.kind === "missing") return /* @__PURE__ */ jsx(MissingRouteHint, { purpose: "record sessions under your Reflet account" });
3494
3562
  const { board } = route.status;
3495
- if (board.kind === "disconnected") return /* @__PURE__ */ jsx(ConnectHint, { onConnect: actions.onConnect });
3496
- if (board.kind === "secretKey") return /* @__PURE__ */ jsx("p", {
3563
+ if (board.kind === "disconnected") return /* @__PURE__ */ jsxs("p", {
3497
3564
  className: "dt-empty",
3498
- 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."]
3499
3574
  });
3500
3575
  return /* @__PURE__ */ jsx(WorkspaceGate, {
3501
3576
  actions,
@@ -3503,6 +3578,38 @@ function SessionsTab({ actions, route, sessions }) {
3503
3578
  });
3504
3579
  }
3505
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
3506
3613
  //#region src/devtools/client/devtools-layer.tsx
3507
3614
  const PICKER_LABELS = {
3508
3615
  ...DEFAULT_WIDGET_LABELS,
@@ -3513,9 +3620,9 @@ const PICKER_LABELS = {
3513
3620
  };
3514
3621
  function sendBlockedReason(route, canConnect) {
3515
3622
  if (route.kind === "checking") return "Connecting to the Reflet dev route…";
3516
- 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.";
3517
3624
  if (route.status.board.kind !== "disconnected") return null;
3518
- 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;
3519
3626
  }
3520
3627
  function canConnectBoard(route, publicKey) {
3521
3628
  return route.kind === "ready" && route.status.board.kind === "disconnected" && route.status.board.canConnect && publicKey !== null;
@@ -3547,6 +3654,37 @@ function useResumeLocating(notes, route) {
3547
3654
  }
3548
3655
  }, [notes, route.kind]);
3549
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
+ }
3550
3688
  function DevtoolsLayer({ isWidgetOpen, position, publicKey }) {
3551
3689
  const { refresh, route } = useDevRoute();
3552
3690
  const notes = useNotes();
@@ -3556,18 +3694,19 @@ function DevtoolsLayer({ isWidgetOpen, position, publicKey }) {
3556
3694
  route
3557
3695
  });
3558
3696
  const [isPicking, setIsPicking] = useState(false);
3559
- const [openTab, setOpenTab] = useState(null);
3697
+ const sheet = useSheetTab();
3698
+ const { openTab } = sheet;
3560
3699
  const [highlighted, setHighlighted] = useState(null);
3561
- const { codeLookup, inspectElement, openCode } = useCodeLookup(setOpenTab);
3700
+ const { codeLookup, inspectElement, openCode } = useCodeLookup(sheet.select);
3562
3701
  const edge = position.startsWith("top") ? "top" : "bottom";
3563
3702
  const side = position.endsWith("right") ? "right" : "left";
3564
3703
  const canReadSource = route.kind === "ready";
3565
3704
  const canConnect = canConnectBoard(route, publicKey);
3566
3705
  const isRecessed = isWidgetOpen || isPicking;
3567
- const closeSheet = useCallback(() => {
3568
- setOpenTab(null);
3706
+ const closeSheet = () => {
3707
+ sheet.close();
3569
3708
  setHighlighted(null);
3570
- }, []);
3709
+ };
3571
3710
  const startPick = useCallback(() => setIsPicking(true), []);
3572
3711
  const cancelPick = useCallback(() => setIsPicking(false), []);
3573
3712
  const showSelector = useCallback((selector) => {
@@ -3598,20 +3737,9 @@ function DevtoolsLayer({ isWidgetOpen, position, publicKey }) {
3598
3737
  copyToClipboard(draftLocatedNote(element, text, canReadSource).then(noteAsContext)).then(() => announce(`Copied ${title}`)).catch((error) => reportFailure(`${title} was not copied`, error));
3599
3738
  }
3600
3739
  }), [canReadSource]);
3601
- const tabs = codeLookup ? [
3602
- "notes",
3603
- "sessions",
3604
- "inbox",
3605
- "code"
3606
- ] : [
3607
- "notes",
3608
- "sessions",
3609
- "inbox"
3610
- ];
3611
- const writeNoteInstead = () => {
3612
- closeSheet();
3613
- startPick();
3614
- };
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;
3615
3743
  return /* @__PURE__ */ jsx(SessionsScope, {
3616
3744
  sessions,
3617
3745
  children: /* @__PURE__ */ jsxs("div", {
@@ -3625,58 +3753,53 @@ function DevtoolsLayer({ isWidgetOpen, position, publicKey }) {
3625
3753
  notes,
3626
3754
  onOpenCode: openCode
3627
3755
  }),
3628
- 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
+ },
3629
3769
  onClose: closeSheet,
3630
- onPick: startPick,
3631
- onSelectTab: setOpenTab,
3632
3770
  side,
3633
- tab: openTab,
3634
- tabs,
3635
- children: [
3636
- openTab === "notes" && /* @__PURE__ */ jsx(NotesPanel, {
3637
- notes,
3771
+ children: /* @__PURE__ */ jsx(SheetPanel, {
3772
+ canConnect,
3773
+ codeLookup,
3774
+ handlers: {
3775
+ onClose: closeSheet,
3638
3776
  onOpenCode: openCode,
3639
- onShowSelector: showSelector,
3640
- sendBlockedReason: sendBlockedReason(route, canConnect)
3641
- }),
3642
- openTab === "inbox" && /* @__PURE__ */ jsx(BoardTab, {
3643
- canConnect,
3644
- onOpenCode: openCode,
3645
- onShowSelector: showSelector,
3646
- publicKey,
3647
- refresh,
3648
- route
3649
- }),
3650
- openTab === "sessions" && /* @__PURE__ */ jsx(SessionsTab, {
3651
- actions: {
3652
- onConnect: () => setOpenTab("inbox"),
3653
- onStarted: closeSheet,
3654
- onWriteNote: writeNoteInstead
3655
- },
3656
- route,
3657
- sessions
3658
- }),
3659
- openTab === "code" && codeLookup && /* @__PURE__ */ jsx(CodeTab, {
3660
- lookup: codeLookup,
3661
- route
3662
- })
3663
- ]
3777
+ onPick: startPick,
3778
+ onShowSelector: showSelector
3779
+ },
3780
+ notes,
3781
+ openTab,
3782
+ route,
3783
+ sessions
3784
+ })
3664
3785
  }),
3665
3786
  /* @__PURE__ */ jsx(DevtoolsBar, {
3666
3787
  edge,
3667
3788
  side,
3668
3789
  children: /* @__PURE__ */ jsx(DevtoolsBarButtons, {
3669
- noteCount: unsentNoteCount(notes),
3790
+ onOpen: openPanel,
3670
3791
  onPick: startPick,
3671
- onToggleSheet: (tab) => setOpenTab((current) => current === tab ? null : tab),
3672
- openTab
3792
+ unsentNoteCount: unsentNoteCount(notes)
3673
3793
  })
3674
3794
  }),
3675
3795
  openTab && /* @__PURE__ */ jsx(SelectionOutline, { node: highlighted }),
3676
3796
  /* @__PURE__ */ jsx(Notices, {}),
3677
3797
  sessions.state.kind === "ready" && /* @__PURE__ */ jsx(RecordingLayer, { onFinished: (target) => {
3678
- sessions.navigation.onOpen(target);
3679
- setOpenTab("sessions");
3798
+ sessions.navigation.go({
3799
+ target,
3800
+ view: "review"
3801
+ });
3802
+ sheet.select("sessions");
3680
3803
  } }),
3681
3804
  isPicking && /* @__PURE__ */ jsx(ElementPicker, {
3682
3805
  copy,
@@ -3692,4 +3815,4 @@ function DevtoolsLayer({ isWidgetOpen, position, publicKey }) {
3692
3815
  //#endregion
3693
3816
  export { DevtoolsLayer };
3694
3817
 
3695
- //# sourceMappingURL=devtools-layer-DhQE3leI.js.map
3818
+ //# sourceMappingURL=devtools-layer-CcTmlikp.js.map