reflet-sdk 0.7.7 → 0.7.9

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,5 +1,5 @@
1
- import { a as EDITORS, n as DEVTOOLS_REQUEST_HEADER, r as DEVTOOLS_ROUTE_BASE, t as DEVTOOLS_ENDPOINTS } from "./protocol-BnurX0CF.js";
2
- import { $ as inspectReactElement, A as PencilIcon, B as DEFAULT_WIDGET_LABELS, C as CheckIcon, D as GripIcon, E as CodeIcon, G as buildElementSelection, H as clip, J as getElementRect, K as buildSelector, N as Svg, P as TargetIcon, Q as getFiberFromNode, T as CloseIcon, U as collectPageContext, V as SDK_VERSION, Y as REDACT_ATTRIBUTE, Z as redactUrl, _ as isSubmitEnter, a as uploadBlob, at as capturedViewportSize, c as useFloatingPosition, d as SelectionOutline, et as resolveComponentStack, h as startsInWidget, it as capturedFromCanvas, k as MoreIcon, l as onViewportChange, m as listenToKeydown, o as ElementPicker, ot as releaseCapture, p as listenOnPageAndWidget, q as describeElement, r as deriveTitle, rt as captureViewport, s as isWidgetOwned, tt as shortenSourcePath, u as visibleViewport, v as matchesHotkey } from "./submit-r3_M1ITo.js";
1
+ import { a as EDITORS, n as DEVTOOLS_REQUEST_HEADER, r as DEVTOOLS_ROUTE_BASE, t as DEVTOOLS_ENDPOINTS } from "./protocol-ClxYWmhu.js";
2
+ import { $ as inspectReactElement, A as PencilIcon, B as DEFAULT_WIDGET_LABELS, C as CheckIcon, D as GripIcon, E as CodeIcon, G as buildElementSelection, H as clip, I as TrashIcon, J as getElementRect, K as buildSelector, N as Svg, P as TargetIcon, Q as getFiberFromNode, T as CloseIcon, U as collectPageContext, V as SDK_VERSION, Y as REDACT_ATTRIBUTE, Z as redactUrl, _ as isSubmitEnter, a as uploadBlob, at as capturedViewportSize, c as useFloatingPosition, d as SelectionOutline, et as resolveComponentStack, h as startsInWidget, it as capturedFromCanvas, k as MoreIcon, l as onViewportChange, m as listenToKeydown, o as ElementPicker, ot as releaseCapture, p as listenOnPageAndWidget, q as describeElement, r as deriveTitle, rt as captureViewport, s as isWidgetOwned, tt as shortenSourcePath, u as visibleViewport, v as matchesHotkey } from "./submit-CQ5nAuwm.js";
3
3
  import { t as REFLET_Z_INDEX } from "./z-index-CwokqVq_.js";
4
4
  import { z } from "zod";
5
5
  import * as G$1 from "react";
@@ -630,7 +630,6 @@ function createProposalEndpoints(requests) {
630
630
  files: z.array(handoffFileSchema),
631
631
  handoff: sessionHandoffSchema
632
632
  })),
633
- createProposal: (body) => postProposal("/api/v1/sessions/proposals/create", body),
634
633
  decideProposal: (body) => postProposal("/api/v1/sessions/proposals/decide", body),
635
634
  editProposal: (body) => postProposal("/api/v1/sessions/proposals/edit", body),
636
635
  markHandoffDelivered: async (body) => (await post("/api/v1/sessions/handoffs/delivered", body, z.object({ handoff: sessionHandoffSchema }))).handoff,
@@ -920,6 +919,21 @@ function operationMethods(database) {
920
919
  id: request.result
921
920
  };
922
921
  },
922
+ cancelDraft: async (deletion) => {
923
+ const row = await packOperation(deletion);
924
+ await database.write([DRAFTS, OPERATIONS], (transaction) => {
925
+ const drafts = transaction.objectStore(DRAFTS);
926
+ const request = drafts.get(deletion.sessionKey);
927
+ request.onsuccess = () => {
928
+ const current = request.result;
929
+ if (current) drafts.put({
930
+ ...current,
931
+ state: "cancelled"
932
+ });
933
+ };
934
+ transaction.objectStore(OPERATIONS).add(row);
935
+ });
936
+ },
923
937
  deleteOperations: (ids) => database.write([OPERATIONS], (transaction) => {
924
938
  for (const id of ids) transaction.objectStore(OPERATIONS).delete(id);
925
939
  }),
@@ -1231,6 +1245,10 @@ async function sendOperation(context) {
1231
1245
  kind: "started",
1232
1246
  sessionId: (await context.api.start(payload.body)).id
1233
1247
  };
1248
+ if (payload.kind === "delete") {
1249
+ if (context.sessionId !== null) await context.api.deleteSession(context.sessionId);
1250
+ return { kind: "done" };
1251
+ }
1234
1252
  if (context.sessionId === null) return {
1235
1253
  kind: "drop",
1236
1254
  message: "This session was never created on Reflet."
@@ -1297,6 +1315,17 @@ var SessionOutbox = class {
1297
1315
  }
1298
1316
  });
1299
1317
  };
1318
+ async cancelSession(sessionKey) {
1319
+ const payload = { kind: "delete" };
1320
+ await this.options.store.cancelDraft({
1321
+ accountKey: this.options.accountKey,
1322
+ bytes: operationBytes(payload),
1323
+ payload,
1324
+ sessionKey
1325
+ });
1326
+ this.options.onDraftsChanged();
1327
+ this.drain();
1328
+ }
1300
1329
  async enqueue(sessionKey, payload) {
1301
1330
  const bytes = operationBytes(payload);
1302
1331
  if (this.snapshot.pendingBytes + bytes > this.options.maxLocalBufferBytes) this.failStorage(BUFFER_FULL);
@@ -1357,11 +1386,12 @@ var SessionOutbox = class {
1357
1386
  }
1358
1387
  async drainPass() {
1359
1388
  const { accountKey, store } = this.options;
1389
+ const drafts = await store.readDrafts(accountKey);
1360
1390
  const pass = {
1361
1391
  outcomes: /* @__PURE__ */ new Map(),
1362
- sessionIds: sessionIdsOf(await store.readDrafts(accountKey))
1392
+ sessionIds: sessionIdsOf(drafts)
1363
1393
  };
1364
- for (const operation of await store.readOperations(accountKey)) {
1394
+ for (const operation of await this.dropAbandonedUploads(drafts)) {
1365
1395
  if (this.isHalted()) return;
1366
1396
  if (pass.outcomes.get(operation.sessionKey)?.kind !== "retry") await this.runOperation(operation, pass);
1367
1397
  }
@@ -1373,6 +1403,16 @@ var SessionOutbox = class {
1373
1403
  await this.forgetFinishedDrafts(remaining);
1374
1404
  this.afterPass(pass);
1375
1405
  }
1406
+ async dropAbandonedUploads(drafts) {
1407
+ const { accountKey, store } = this.options;
1408
+ const cancelledSessionKeys = new Set(drafts.filter((draft) => draft.state === "cancelled").map((draft) => draft.sessionKey));
1409
+ const abandonedIds = [];
1410
+ const toSend = [];
1411
+ for (const operation of await store.readOperations(accountKey)) if (cancelledSessionKeys.has(operation.sessionKey) && operation.payload.kind !== "delete") abandonedIds.push(operation.id);
1412
+ else toSend.push(operation);
1413
+ await store.deleteOperations(abandonedIds);
1414
+ return toSend;
1415
+ }
1376
1416
  afterPass(pass) {
1377
1417
  if (![...pass.outcomes.values()].some((outcome) => outcome.kind === "retry")) {
1378
1418
  this.failedPasses = 0;
@@ -1442,7 +1482,7 @@ var SessionOutbox = class {
1442
1482
  async forgetFinishedDrafts(operations) {
1443
1483
  const { accountKey, store } = this.options;
1444
1484
  const waiting = new Set(operations.map((operation) => operation.sessionKey));
1445
- const finished = (await store.readDrafts(accountKey)).filter((draft) => draft.state === "ended" && !waiting.has(draft.sessionKey));
1485
+ const finished = (await store.readDrafts(accountKey)).filter((draft) => (draft.state === "ended" || draft.state === "cancelled") && !waiting.has(draft.sessionKey));
1446
1486
  await Promise.all(finished.map((draft) => store.deleteDraft(draft.sessionKey)));
1447
1487
  if (finished.length > 0) this.options.onDraftsChanged();
1448
1488
  }
@@ -1463,15 +1503,16 @@ const IDLE_SYNC = {
1463
1503
  pendingOperations: 0
1464
1504
  };
1465
1505
  function visibleDraft(stored, isLive) {
1506
+ if (stored.state === "cancelled") return [];
1466
1507
  const leftOpen = stored.state === "recording" || stored.state === "paused";
1467
- return {
1508
+ return [{
1468
1509
  lastActiveAt: stored.lastActiveAt,
1469
1510
  sessionId: stored.sessionId,
1470
1511
  sessionKey: stored.sessionKey,
1471
1512
  startedAt: stored.startedAt,
1472
1513
  state: leftOpen && !isLive ? "interrupted" : stored.state,
1473
1514
  title: stored.title
1474
- };
1515
+ }];
1475
1516
  }
1476
1517
  var LocalSessionsRegistry = class {
1477
1518
  accounts = /* @__PURE__ */ new Map();
@@ -1508,7 +1549,8 @@ var LocalSessionsRegistry = class {
1508
1549
  async discardDraft(options) {
1509
1550
  const { accountKey, sessionKey } = options;
1510
1551
  const stored = (await this.store.readDrafts(accountKey)).find((candidate) => candidate.sessionKey === sessionKey);
1511
- if (!stored || this.live.has(sessionKey) || stored.state === "ended") return;
1552
+ const isClosed = stored?.state === "ended" || stored?.state === "cancelled";
1553
+ if (!stored || this.live.has(sessionKey) || isClosed) return;
1512
1554
  const { outbox } = this.ensureOutbox({
1513
1555
  ...options,
1514
1556
  maxLocalBufferBytes: null
@@ -1544,7 +1586,7 @@ var LocalSessionsRegistry = class {
1544
1586
  }
1545
1587
  refreshDrafts(accountKey) {
1546
1588
  this.store.readDrafts(accountKey).then((stored) => {
1547
- const visible = stored.map((item) => visibleDraft(item, this.live.has(item.sessionKey))).sort((a, b) => b.startedAt - a.startedAt);
1589
+ const visible = stored.flatMap((item) => visibleDraft(item, this.live.has(item.sessionKey))).sort((a, b) => b.startedAt - a.startedAt);
1548
1590
  this.drafts.set(accountKey, visible);
1549
1591
  this.notify();
1550
1592
  }).catch(() => {
@@ -2866,6 +2908,13 @@ var RecordingController = class {
2866
2908
  annotateMark(key, note) {
2867
2909
  this.live?.annotate(key, cleanText(note, MAX_NOTE_CHARS));
2868
2910
  }
2911
+ async cancel() {
2912
+ const live = this.live;
2913
+ if (!live) return;
2914
+ const cancelling = this.account.outbox.cancelSession(live.sessionKey);
2915
+ this.close(live, IDLE);
2916
+ await cancelling;
2917
+ }
2869
2918
  dispose() {
2870
2919
  this.interrupt();
2871
2920
  this.stopIssues();
@@ -2921,7 +2970,7 @@ var RecordingController = class {
2921
2970
  });
2922
2971
  }
2923
2972
  async resumeDraft(sessionKey) {
2924
- const draft = (await this.account.store.readDrafts(this.options.account.key)).find((candidate) => candidate.sessionKey === sessionKey && candidate.state !== "ended");
2973
+ const draft = (await this.account.store.readDrafts(this.options.account.key)).find((candidate) => candidate.sessionKey === sessionKey && candidate.state !== "ended" && candidate.state !== "cancelled");
2925
2974
  if (!draft || this.isBusy()) return;
2926
2975
  const voice = await this.requestVoice();
2927
2976
  if (!voice) return;
@@ -3349,6 +3398,191 @@ function isDetailProcessing(detail) {
3349
3398
  return transcribing || refining || comparing || isAnalysisBusy(detail.session) || detail.session.collection !== "ended";
3350
3399
  }
3351
3400
  //#endregion
3401
+ //#region src/sessions/ui/review/use-session-detail.ts
3402
+ const POLL_INTERVAL_MS = 2e3;
3403
+ function nextRequest(current) {
3404
+ return {
3405
+ ...current,
3406
+ count: current.count + 1
3407
+ };
3408
+ }
3409
+ function withProposal(detail, proposal) {
3410
+ const isKnown = detail.proposals.some((item) => item.id === proposal.id);
3411
+ return {
3412
+ ...detail,
3413
+ proposals: isKnown ? detail.proposals.map((item) => item.id === proposal.id ? proposal : item) : [...detail.proposals, proposal]
3414
+ };
3415
+ }
3416
+ function withSegment(detail, segment) {
3417
+ const others = detail.segments.filter((item) => item.id !== segment.id);
3418
+ return {
3419
+ ...detail,
3420
+ segments: [...others, segment].sort((a, b) => a.startMs - b.startMs)
3421
+ };
3422
+ }
3423
+ function withEvidence(detail, evidence) {
3424
+ return {
3425
+ ...detail,
3426
+ evidence: detail.evidence.map((item) => item.id === evidence.id ? evidence : item)
3427
+ };
3428
+ }
3429
+ function isHidden() {
3430
+ return typeof document !== "undefined" && document.visibilityState === "hidden";
3431
+ }
3432
+ function usePollTick(lastAnswer) {
3433
+ const [tick, setTick] = useState(0);
3434
+ useEffect(() => {
3435
+ if (lastAnswer.kind !== "ready" || !isDetailProcessing(lastAnswer.detail)) return;
3436
+ let timer = 0;
3437
+ const schedule = () => {
3438
+ window.clearTimeout(timer);
3439
+ if (!isHidden()) timer = window.setTimeout(() => setTick((current) => current + 1), POLL_INTERVAL_MS);
3440
+ };
3441
+ schedule();
3442
+ document.addEventListener("visibilitychange", schedule);
3443
+ return () => {
3444
+ window.clearTimeout(timer);
3445
+ document.removeEventListener("visibilitychange", schedule);
3446
+ };
3447
+ }, [lastAnswer]);
3448
+ return tick;
3449
+ }
3450
+ function failedState(error, current) {
3451
+ if (error instanceof SessionApiError && error.status === 404) return { kind: "missing" };
3452
+ if (current.kind === "ready") return current;
3453
+ return {
3454
+ kind: "failed",
3455
+ message: failureMessage(error)
3456
+ };
3457
+ }
3458
+ function useLandedUploadTick() {
3459
+ const { account } = useSessionWorkspace();
3460
+ const { pendingOperations } = useSyncStatus(account.key);
3461
+ const [seen, setSeen] = useState({
3462
+ pending: pendingOperations,
3463
+ tick: 0
3464
+ });
3465
+ if (seen.pending !== pendingOperations) setSeen({
3466
+ pending: pendingOperations,
3467
+ tick: pendingOperations < seen.pending ? seen.tick + 1 : seen.tick
3468
+ });
3469
+ return seen.tick;
3470
+ }
3471
+ function useSessionDetail(sessionId) {
3472
+ const { api, onUnauthorized } = useSessionWorkspace();
3473
+ const [state, setState] = useState({ kind: "loading" });
3474
+ const [request, setRequest] = useState({
3475
+ count: 0,
3476
+ sessionId
3477
+ });
3478
+ const pollTick = usePollTick(state);
3479
+ const uploadTick = useLandedUploadTick();
3480
+ useEffect(() => {
3481
+ let isCurrent = true;
3482
+ api.detail(request.sessionId).then((detail) => isCurrent && setState({
3483
+ detail,
3484
+ kind: "ready"
3485
+ }), (error) => {
3486
+ if (isUnauthorized(error)) {
3487
+ onUnauthorized();
3488
+ return;
3489
+ }
3490
+ if (isCurrent) setState((current) => failedState(error, current));
3491
+ const isRefresh = request.count + pollTick + uploadTick > 0;
3492
+ if (isCurrent && isRefresh) reportFailure("The session did not refresh", error);
3493
+ });
3494
+ return () => {
3495
+ isCurrent = false;
3496
+ };
3497
+ }, [
3498
+ api,
3499
+ onUnauthorized,
3500
+ request,
3501
+ pollTick,
3502
+ uploadTick
3503
+ ]);
3504
+ return {
3505
+ patch: (update) => setState((current) => current.kind === "ready" ? {
3506
+ detail: update(current.detail),
3507
+ kind: "ready"
3508
+ } : current),
3509
+ reload: () => setRequest(nextRequest),
3510
+ state
3511
+ };
3512
+ }
3513
+ //#endregion
3514
+ //#region src/sessions/ui/review/transcript/remove-evidence.tsx
3515
+ const REMOVE_COPY = "Remove this screenshot? The image is deleted for good. Briefs you already downloaded keep their copy.";
3516
+ function useFocusWhen(shouldFocus) {
3517
+ const ref = useRef(null);
3518
+ useEffect(() => {
3519
+ if (shouldFocus) ref.current?.focus();
3520
+ }, [shouldFocus]);
3521
+ return ref;
3522
+ }
3523
+ function RemoveConfirmation({ evidence, onCancel, removal }) {
3524
+ const { api } = useSessionWorkspace();
3525
+ const run = useSessionAction();
3526
+ const cancelRef = useFocusWhen(true);
3527
+ const remove = async () => {
3528
+ const removed = await run("The screenshot was not removed", () => api.removeEvidence(evidence.id));
3529
+ if (removed !== null) {
3530
+ removal.review.patch((detail) => withEvidence(detail, removed));
3531
+ removal.review.reload();
3532
+ removal.onRemoved(removed.id);
3533
+ announce("Screenshot removed");
3534
+ }
3535
+ };
3536
+ return /* @__PURE__ */ jsxs("div", {
3537
+ className: "dt-ses-banner",
3538
+ children: [/* @__PURE__ */ jsx("p", { children: REMOVE_COPY }), /* @__PURE__ */ jsxs("div", {
3539
+ className: "dt-actions",
3540
+ children: [/* @__PURE__ */ jsx("button", {
3541
+ className: "dt-btn",
3542
+ "data-variant": "danger",
3543
+ onClick: remove,
3544
+ type: "button",
3545
+ children: "Remove"
3546
+ }), /* @__PURE__ */ jsx("button", {
3547
+ className: "dt-btn",
3548
+ onClick: onCancel,
3549
+ ref: cancelRef,
3550
+ type: "button",
3551
+ children: "Cancel"
3552
+ })]
3553
+ })]
3554
+ });
3555
+ }
3556
+ function RemoveEvidence({ evidence, removal }) {
3557
+ const [isConfirming, setIsConfirming] = useState(false);
3558
+ const [hasCancelled, setHasCancelled] = useState(false);
3559
+ const triggerRef = useFocusWhen(hasCancelled);
3560
+ if (isConfirming) return /* @__PURE__ */ jsx(RemoveConfirmation, {
3561
+ evidence,
3562
+ onCancel: () => {
3563
+ setIsConfirming(false);
3564
+ setHasCancelled(true);
3565
+ },
3566
+ removal
3567
+ });
3568
+ const label = `Remove screenshot at ${formatClock(evidence.atMs)}`;
3569
+ return /* @__PURE__ */ jsx("div", {
3570
+ className: "dt-hover-actions",
3571
+ children: /* @__PURE__ */ jsx("button", {
3572
+ "aria-label": label,
3573
+ className: "icon-btn glass",
3574
+ onClick: () => {
3575
+ setIsConfirming(true);
3576
+ setHasCancelled(false);
3577
+ },
3578
+ ref: triggerRef,
3579
+ title: label,
3580
+ type: "button",
3581
+ children: /* @__PURE__ */ jsx(TrashIcon, {})
3582
+ })
3583
+ });
3584
+ }
3585
+ //#endregion
3352
3586
  //#region src/sessions/ui/session-icons.tsx
3353
3587
  function MicIcon() {
3354
3588
  return /* @__PURE__ */ jsxs(Svg, { children: [/* @__PURE__ */ jsx("rect", {
@@ -3375,6 +3609,22 @@ function ClockIcon() {
3375
3609
  r: "8.5"
3376
3610
  }), /* @__PURE__ */ jsx("path", { d: "M12 7.5V12l3 2" })] });
3377
3611
  }
3612
+ function LockIcon() {
3613
+ return /* @__PURE__ */ jsxs(Svg, { children: [/* @__PURE__ */ jsx("rect", {
3614
+ height: "10",
3615
+ rx: "2.5",
3616
+ width: "15",
3617
+ x: "4.5",
3618
+ y: "10.5"
3619
+ }), /* @__PURE__ */ jsx("path", { d: "M8 10.5V8a4 4 0 0 1 8 0v2.5" })] });
3620
+ }
3621
+ function TeamIcon() {
3622
+ return /* @__PURE__ */ jsxs(Svg, { children: [/* @__PURE__ */ jsx("circle", {
3623
+ cx: "9",
3624
+ cy: "8.5",
3625
+ r: "3.5"
3626
+ }), /* @__PURE__ */ jsx("path", { d: "M2.5 20a6.5 6.5 0 0 1 13 0M16 5.2a3.5 3.5 0 0 1 0 6.6M18.5 14.4a6.5 6.5 0 0 1 3 5.6" })] });
3627
+ }
3378
3628
  function IntentionIcon() {
3379
3629
  return /* @__PURE__ */ jsx(Svg, { children: /* @__PURE__ */ jsx("path", { d: "M9 18h6M10 21h4M12 3a6 6 0 0 0-3.6 10.8c.6.5 1.1 1.3 1.1 2.2h5c0-.9.5-1.7 1.1-2.2A6 6 0 0 0 12 3z" }) });
3380
3630
  }
@@ -3399,6 +3649,50 @@ function ImageIcon() {
3399
3649
  ] });
3400
3650
  }
3401
3651
  //#endregion
3652
+ //#region src/sessions/ui/recording/cancel-confirmation.tsx
3653
+ function CancelConfirmation({ onCancelRecording, onKeepRecording }) {
3654
+ const confirmationRef = useRef(null);
3655
+ const keepRef = useFocusWhen(true);
3656
+ useEffect(() => {
3657
+ const confirmation = confirmationRef.current;
3658
+ if (!confirmation) return;
3659
+ const keepOnEscape = (event) => {
3660
+ if (event.key === "Escape" && !event.isComposing) {
3661
+ event.stopPropagation();
3662
+ onKeepRecording();
3663
+ }
3664
+ };
3665
+ confirmation.addEventListener("keydown", keepOnEscape);
3666
+ return () => confirmation.removeEventListener("keydown", keepOnEscape);
3667
+ }, [onKeepRecording]);
3668
+ return /* @__PURE__ */ jsxs("div", {
3669
+ "aria-labelledby": "rf-rec-cancel-copy",
3670
+ className: "dt-rec-note",
3671
+ ref: confirmationRef,
3672
+ role: "alertdialog",
3673
+ children: [
3674
+ /* @__PURE__ */ jsx("span", {
3675
+ id: "rf-rec-cancel-copy",
3676
+ children: "Cancel this recording? Nothing from it is kept."
3677
+ }),
3678
+ /* @__PURE__ */ jsx("button", {
3679
+ className: "dt-btn",
3680
+ "data-variant": "danger",
3681
+ onClick: onCancelRecording,
3682
+ type: "button",
3683
+ children: "Discard"
3684
+ }),
3685
+ /* @__PURE__ */ jsx("button", {
3686
+ className: "dt-btn",
3687
+ onClick: onKeepRecording,
3688
+ ref: keepRef,
3689
+ type: "button",
3690
+ children: "Keep recording"
3691
+ })
3692
+ ]
3693
+ });
3694
+ }
3695
+ //#endregion
3402
3696
  //#region src/sessions/ui/recording/level-meter.tsx
3403
3697
  const REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
3404
3698
  /** The level changes many times a second: it goes straight to a CSS variable, never through React state. */
@@ -3461,25 +3755,19 @@ function MarkNote({ focusOnOpen, onClose }) {
3461
3755
  //#endregion
3462
3756
  //#region src/sessions/ui/recording/recording-bar.tsx
3463
3757
  const MARK_HOTKEY = "alt+shift+m";
3464
- function SyncChip() {
3465
- const { account } = useSessionWorkspace();
3466
- const sync = useSyncStatus(account.key);
3467
- let label = "Saved";
3468
- if (sync.blockedBy === "storage") label = "Not saved on this device";
3469
- else if (!sync.isOnline) label = "Offline — kept on this device";
3470
- else if (sync.pendingOperations > 0) label = `${sync.pendingOperations} waiting to upload`;
3471
- return /* @__PURE__ */ jsx("span", {
3472
- className: "dt-chip",
3473
- children: label
3474
- });
3758
+ function syncProblem(sync) {
3759
+ if (sync.blockedBy === "storage") return "Not saved on this device";
3760
+ if (!sync.isOnline) return "Offline — kept on this device";
3761
+ return null;
3475
3762
  }
3476
- function statusText(snapshot, maxDurationMs) {
3763
+ function statusText(snapshot, sync, maxDurationMs) {
3477
3764
  if (snapshot.phase === "failed") return snapshot.failure?.message ?? "Recording stopped.";
3478
3765
  if (snapshot.phase === "finishing") return "Finishing…";
3479
3766
  if (snapshot.pauseReason === "limit") return "Time limit reached · Finish to review the session";
3480
3767
  if (snapshot.pauseReason === "hidden") return "Paused because the tab was hidden";
3481
- if (snapshot.phase === "paused") return "Paused · nothing is recorded";
3482
3768
  if (snapshot.limitsReached.length > 0) return `Limit reached for ${snapshot.limitsReached.join(", ")}`;
3769
+ const problem = syncProblem(sync);
3770
+ if (problem) return problem;
3483
3771
  return snapshot.nearLimit ? `${minutesLeft(maxDurationMs - snapshot.activeMs)} min left` : "";
3484
3772
  }
3485
3773
  function markMoment(recorder, focus, setOpenMark) {
@@ -3513,42 +3801,67 @@ function useMarkMoment(snapshot, barRef) {
3513
3801
  };
3514
3802
  }
3515
3803
  function CaptureButtons({ canCapture, onMark, onPointAt }) {
3516
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("button", {
3804
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
3517
3805
  "aria-keyshortcuts": "Alt+Shift+M",
3806
+ "aria-label": "Mark moment",
3518
3807
  className: "dt-btn",
3808
+ "data-icon-only": true,
3519
3809
  disabled: !canCapture,
3520
3810
  onClick: onMark,
3521
3811
  title: "Mark moment (Alt+Shift+M)",
3522
3812
  type: "button",
3523
- children: [/* @__PURE__ */ jsx(MarkIcon, {}), "Mark moment"]
3813
+ children: /* @__PURE__ */ jsx(MarkIcon, {})
3524
3814
  }), /* @__PURE__ */ jsx("button", {
3815
+ "aria-label": "Point at element",
3525
3816
  className: "dt-btn",
3817
+ "data-icon-only": true,
3526
3818
  disabled: !canCapture,
3527
3819
  onClick: onPointAt,
3820
+ title: "Point at element",
3528
3821
  type: "button",
3529
- children: "Point at element"
3822
+ children: /* @__PURE__ */ jsx(TargetIcon, {})
3530
3823
  })] });
3531
3824
  }
3532
3825
  function PauseToggle({ snapshot }) {
3533
3826
  const { recorder } = useSessionWorkspace();
3534
- if (snapshot.phase === "recording") return /* @__PURE__ */ jsxs("button", {
3827
+ if (snapshot.phase === "recording") return /* @__PURE__ */ jsx("button", {
3828
+ "aria-label": "Pause",
3535
3829
  className: "dt-btn",
3830
+ "data-icon-only": true,
3536
3831
  onClick: () => recorder.pause("paused"),
3832
+ title: "Pause",
3537
3833
  type: "button",
3538
- children: [/* @__PURE__ */ jsx(PauseIcon, {}), "Pause"]
3834
+ children: /* @__PURE__ */ jsx(PauseIcon, {})
3539
3835
  });
3540
- if (snapshot.phase === "paused" && snapshot.pauseReason !== "limit") return /* @__PURE__ */ jsxs("button", {
3836
+ if (snapshot.phase === "paused" && snapshot.pauseReason !== "limit") return /* @__PURE__ */ jsx("button", {
3837
+ "aria-label": "Resume",
3541
3838
  className: "dt-btn",
3839
+ "data-icon-only": true,
3542
3840
  onClick: () => recorder.resume(),
3841
+ title: "Resume",
3543
3842
  type: "button",
3544
- children: [/* @__PURE__ */ jsx(PlayIcon, {}), "Resume"]
3843
+ children: /* @__PURE__ */ jsx(PlayIcon, {})
3545
3844
  });
3546
3845
  return null;
3547
3846
  }
3548
- function RecordingControls({ onFinish, onMark, onPointAt, snapshot }) {
3847
+ function CancelButton({ hasKeptRecording, onAskToCancel }) {
3848
+ const buttonRef = useFocusWhen(hasKeptRecording);
3849
+ return /* @__PURE__ */ jsx("button", {
3850
+ "aria-label": "Cancel recording",
3851
+ className: "dt-btn",
3852
+ "data-icon-only": true,
3853
+ onClick: onAskToCancel,
3854
+ ref: buttonRef,
3855
+ title: "Cancel recording",
3856
+ type: "button",
3857
+ children: /* @__PURE__ */ jsx(CloseIcon, {})
3858
+ });
3859
+ }
3860
+ function RecordingControls({ hasKeptRecording, onAskToCancel, onFinish, onMark, onPointAt, snapshot }) {
3549
3861
  const { phase } = snapshot;
3862
+ const isActive = phase === "recording" || phase === "paused";
3550
3863
  return /* @__PURE__ */ jsxs(Fragment, { children: [
3551
- (phase === "recording" || phase === "paused") && /* @__PURE__ */ jsx(CaptureButtons, {
3864
+ isActive && /* @__PURE__ */ jsx(CaptureButtons, {
3552
3865
  canCapture: phase === "recording",
3553
3866
  onMark,
3554
3867
  onPointAt
@@ -3561,6 +3874,10 @@ function RecordingControls({ onFinish, onMark, onPointAt, snapshot }) {
3561
3874
  onClick: onFinish,
3562
3875
  type: "button",
3563
3876
  children: "Finish"
3877
+ }),
3878
+ isActive && /* @__PURE__ */ jsx(CancelButton, {
3879
+ hasKeptRecording,
3880
+ onAskToCancel
3564
3881
  })
3565
3882
  ] });
3566
3883
  }
@@ -3573,10 +3890,14 @@ function RecordingClock({ activeMs }) {
3573
3890
  children: formatClock(activeMs)
3574
3891
  });
3575
3892
  }
3576
- function RecordingBar({ onFinish, onPointAt, snapshot }) {
3577
- const { identity, recorder } = useSessionWorkspace();
3893
+ function RecordingBar({ onCancel, onFinish, onPointAt, snapshot }) {
3894
+ const { account, identity, recorder } = useSessionWorkspace();
3895
+ const sync = useSyncStatus(account.key);
3578
3896
  const barRef = useRef(null);
3579
3897
  const { markWithNote, openMark } = useMarkMoment(snapshot, barRef);
3898
+ const [cancelStep, setCancelStep] = useState("idle");
3899
+ const keepRecording = useCallback(() => setCancelStep("kept"), []);
3900
+ const showsMeter = snapshot.phase === "recording" || snapshot.phase === "paused";
3580
3901
  return /* @__PURE__ */ jsxs("div", {
3581
3902
  "aria-label": "Session recording",
3582
3903
  className: "dt-rec glass",
@@ -3588,24 +3909,29 @@ function RecordingBar({ onFinish, onPointAt, snapshot }) {
3588
3909
  "aria-hidden": "true",
3589
3910
  className: "dt-rec-dot"
3590
3911
  }),
3591
- snapshot.phase === "recording" && /* @__PURE__ */ jsx(LevelMeter, { recorder }),
3912
+ showsMeter && /* @__PURE__ */ jsx(LevelMeter, { recorder }),
3592
3913
  /* @__PURE__ */ jsx(RecordingClock, { activeMs: snapshot.activeMs }),
3593
3914
  /* @__PURE__ */ jsx("span", {
3594
3915
  "aria-live": "polite",
3595
3916
  className: "dt-rec-status",
3596
- children: statusText(snapshot, identity.limits.maxDurationMs)
3917
+ children: statusText(snapshot, sync, identity.limits.maxDurationMs)
3597
3918
  }),
3598
3919
  /* @__PURE__ */ jsx(RecordingControls, {
3920
+ hasKeptRecording: cancelStep === "kept",
3921
+ onAskToCancel: () => setCancelStep("confirming"),
3599
3922
  onFinish,
3600
3923
  onMark: markWithNote,
3601
3924
  onPointAt,
3602
3925
  snapshot
3603
3926
  }),
3604
- /* @__PURE__ */ jsx(SyncChip, {}),
3605
3927
  openMark && /* @__PURE__ */ jsx(MarkNote, {
3606
3928
  focusOnOpen: openMark.focus,
3607
3929
  onClose: openMark.closeNote
3608
- }, openMark.key)
3930
+ }, openMark.key),
3931
+ cancelStep === "confirming" && /* @__PURE__ */ jsx(CancelConfirmation, {
3932
+ onCancelRecording: onCancel,
3933
+ onKeepRecording: keepRecording
3934
+ })
3609
3935
  ]
3610
3936
  });
3611
3937
  }
@@ -3641,7 +3967,16 @@ function RecordingLayer({ onFinished }) {
3641
3967
  reportFailure("The session did not finish", error, finish);
3642
3968
  }
3643
3969
  };
3970
+ const cancel = async () => {
3971
+ setIsPointing(false);
3972
+ try {
3973
+ await recorder.cancel();
3974
+ } catch (error) {
3975
+ reportFailure("The recording was not deleted", error);
3976
+ }
3977
+ };
3644
3978
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(RecordingBar, {
3979
+ onCancel: cancel,
3645
3980
  onFinish: finish,
3646
3981
  onPointAt: () => setIsPointing(true),
3647
3982
  snapshot
@@ -3824,6 +4159,15 @@ function evidenceCaption(evidence) {
3824
4159
  const element = evidence.element ? ` · ${evidence.element.label}` : "";
3825
4160
  return `${TRIGGER_LABELS[evidence.trigger]}${element}`;
3826
4161
  }
4162
+ function ShotCaption({ atMs, children }) {
4163
+ return /* @__PURE__ */ jsxs("span", {
4164
+ className: "dt-ses-caption",
4165
+ children: [/* @__PURE__ */ jsxs("span", {
4166
+ className: "dt-ses-caption-time",
4167
+ children: [/* @__PURE__ */ jsx(ClockIcon, {}), formatClock(atMs)]
4168
+ }), children]
4169
+ });
4170
+ }
3827
4171
  const hasViewport = (frame) => frame.viewportWidth > 0 && frame.viewportHeight > 0;
3828
4172
  const percent = (value, total) => `${value / total * 100}%`;
3829
4173
  function percentBox(rect, frame) {
@@ -3931,12 +4275,18 @@ function HistoryEntry({ context, entry }) {
3931
4275
  const evidenceIds = new Set(entry.evidenceIds);
3932
4276
  const evidence = context.detail.contributedEvidence.filter((item) => evidenceIds.has(item.id));
3933
4277
  return /* @__PURE__ */ jsxs("li", {
3934
- className: "dt-ses-history",
4278
+ className: "dt-ses-history dt-openable",
3935
4279
  "data-removed": entry.detachedAt !== null,
3936
4280
  children: [
3937
4281
  /* @__PURE__ */ jsx("p", {
3938
4282
  className: "dt-ses-subhead",
3939
- children: entryTitle(entry)
4283
+ children: entry.session.canOpen ? /* @__PURE__ */ jsx("button", {
4284
+ className: "dt-open",
4285
+ onClick: () => context.navigation.onOpenSession(entry.session.id),
4286
+ title: "Open session",
4287
+ type: "button",
4288
+ children: entryTitle(entry)
4289
+ }) : entryTitle(entry)
3940
4290
  }),
3941
4291
  entry.detachedAt !== null && /* @__PURE__ */ jsxs("p", {
3942
4292
  className: "dt-meta",
@@ -3953,12 +4303,6 @@ function HistoryEntry({ context, entry }) {
3953
4303
  }) : /* @__PURE__ */ jsx(HistoryExtracts, {
3954
4304
  evidence,
3955
4305
  quotes: entry.quotes
3956
- }),
3957
- entry.session.canOpen && /* @__PURE__ */ jsx("button", {
3958
- className: "dt-ses-link",
3959
- onClick: () => context.navigation.onOpenSession(entry.session.id),
3960
- type: "button",
3961
- children: "Open session"
3962
4306
  })
3963
4307
  ]
3964
4308
  });
@@ -4363,119 +4707,6 @@ function SaveProblemMessage({ onReload, problem }) {
4363
4707
  });
4364
4708
  }
4365
4709
  //#endregion
4366
- //#region src/sessions/ui/review/use-session-detail.ts
4367
- const POLL_INTERVAL_MS = 2e3;
4368
- function nextRequest(current) {
4369
- return {
4370
- ...current,
4371
- count: current.count + 1
4372
- };
4373
- }
4374
- function withProposal(detail, proposal) {
4375
- const isKnown = detail.proposals.some((item) => item.id === proposal.id);
4376
- return {
4377
- ...detail,
4378
- proposals: isKnown ? detail.proposals.map((item) => item.id === proposal.id ? proposal : item) : [...detail.proposals, proposal]
4379
- };
4380
- }
4381
- function withSegment(detail, segment) {
4382
- const others = detail.segments.filter((item) => item.id !== segment.id);
4383
- return {
4384
- ...detail,
4385
- segments: [...others, segment].sort((a, b) => a.startMs - b.startMs)
4386
- };
4387
- }
4388
- function withEvidence(detail, evidence) {
4389
- return {
4390
- ...detail,
4391
- evidence: detail.evidence.map((item) => item.id === evidence.id ? evidence : item)
4392
- };
4393
- }
4394
- function isHidden() {
4395
- return typeof document !== "undefined" && document.visibilityState === "hidden";
4396
- }
4397
- function usePollTick(lastAnswer) {
4398
- const [tick, setTick] = useState(0);
4399
- useEffect(() => {
4400
- if (lastAnswer.kind !== "ready" || !isDetailProcessing(lastAnswer.detail)) return;
4401
- let timer = 0;
4402
- const schedule = () => {
4403
- window.clearTimeout(timer);
4404
- if (!isHidden()) timer = window.setTimeout(() => setTick((current) => current + 1), POLL_INTERVAL_MS);
4405
- };
4406
- schedule();
4407
- document.addEventListener("visibilitychange", schedule);
4408
- return () => {
4409
- window.clearTimeout(timer);
4410
- document.removeEventListener("visibilitychange", schedule);
4411
- };
4412
- }, [lastAnswer]);
4413
- return tick;
4414
- }
4415
- function failedState(error, current) {
4416
- if (error instanceof SessionApiError && error.status === 404) return { kind: "missing" };
4417
- if (current.kind === "ready") return current;
4418
- return {
4419
- kind: "failed",
4420
- message: failureMessage(error)
4421
- };
4422
- }
4423
- function useLandedUploadTick() {
4424
- const { account } = useSessionWorkspace();
4425
- const { pendingOperations } = useSyncStatus(account.key);
4426
- const [seen, setSeen] = useState({
4427
- pending: pendingOperations,
4428
- tick: 0
4429
- });
4430
- if (seen.pending !== pendingOperations) setSeen({
4431
- pending: pendingOperations,
4432
- tick: pendingOperations < seen.pending ? seen.tick + 1 : seen.tick
4433
- });
4434
- return seen.tick;
4435
- }
4436
- function useSessionDetail(sessionId) {
4437
- const { api, onUnauthorized } = useSessionWorkspace();
4438
- const [state, setState] = useState({ kind: "loading" });
4439
- const [request, setRequest] = useState({
4440
- count: 0,
4441
- sessionId
4442
- });
4443
- const pollTick = usePollTick(state);
4444
- const uploadTick = useLandedUploadTick();
4445
- useEffect(() => {
4446
- let isCurrent = true;
4447
- api.detail(request.sessionId).then((detail) => isCurrent && setState({
4448
- detail,
4449
- kind: "ready"
4450
- }), (error) => {
4451
- if (isUnauthorized(error)) {
4452
- onUnauthorized();
4453
- return;
4454
- }
4455
- if (isCurrent) setState((current) => failedState(error, current));
4456
- const isRefresh = request.count + pollTick + uploadTick > 0;
4457
- if (isCurrent && isRefresh) reportFailure("The session did not refresh", error);
4458
- });
4459
- return () => {
4460
- isCurrent = false;
4461
- };
4462
- }, [
4463
- api,
4464
- onUnauthorized,
4465
- request,
4466
- pollTick,
4467
- uploadTick
4468
- ]);
4469
- return {
4470
- patch: (update) => setState((current) => current.kind === "ready" ? {
4471
- detail: update(current.detail),
4472
- kind: "ready"
4473
- } : current),
4474
- reload: () => setRequest(nextRequest),
4475
- state
4476
- };
4477
- }
4478
- //#endregion
4479
4710
  //#region src/sessions/ui/review/proposals/proposal-editor.tsx
4480
4711
  const MAX_LIST_ITEMS = 8;
4481
4712
  function lines(text) {
@@ -5252,73 +5483,6 @@ function UpdateCard({ context, continuation }) {
5252
5483
  });
5253
5484
  }
5254
5485
  //#endregion
5255
- //#region src/sessions/ui/review/transcript/remove-evidence.tsx
5256
- const REMOVE_COPY = "Remove this screenshot? The image is deleted for good. Briefs you already downloaded keep their copy.";
5257
- function useFocusWhen(shouldFocus) {
5258
- const ref = useRef(null);
5259
- useEffect(() => {
5260
- if (shouldFocus) ref.current?.focus();
5261
- }, [shouldFocus]);
5262
- return ref;
5263
- }
5264
- function RemoveConfirmation({ evidence, onCancel, removal }) {
5265
- const { api } = useSessionWorkspace();
5266
- const run = useSessionAction();
5267
- const cancelRef = useFocusWhen(true);
5268
- const remove = async () => {
5269
- const removed = await run("The screenshot was not removed", () => api.removeEvidence(evidence.id));
5270
- if (removed !== null) {
5271
- removal.review.patch((detail) => withEvidence(detail, removed));
5272
- removal.review.reload();
5273
- removal.onRemoved(removed.id);
5274
- announce("Screenshot removed");
5275
- }
5276
- };
5277
- return /* @__PURE__ */ jsxs("div", {
5278
- className: "dt-ses-banner",
5279
- children: [/* @__PURE__ */ jsx("p", { children: REMOVE_COPY }), /* @__PURE__ */ jsxs("div", {
5280
- className: "dt-actions",
5281
- children: [/* @__PURE__ */ jsx("button", {
5282
- className: "dt-btn",
5283
- "data-variant": "danger",
5284
- onClick: remove,
5285
- type: "button",
5286
- children: "Remove"
5287
- }), /* @__PURE__ */ jsx("button", {
5288
- className: "dt-btn",
5289
- onClick: onCancel,
5290
- ref: cancelRef,
5291
- type: "button",
5292
- children: "Cancel"
5293
- })]
5294
- })]
5295
- });
5296
- }
5297
- function RemoveEvidence({ evidence, removal }) {
5298
- const [isConfirming, setIsConfirming] = useState(false);
5299
- const [hasCancelled, setHasCancelled] = useState(false);
5300
- const triggerRef = useFocusWhen(hasCancelled);
5301
- if (isConfirming) return /* @__PURE__ */ jsx(RemoveConfirmation, {
5302
- evidence,
5303
- onCancel: () => {
5304
- setIsConfirming(false);
5305
- setHasCancelled(true);
5306
- },
5307
- removal
5308
- });
5309
- return /* @__PURE__ */ jsx("button", {
5310
- "aria-label": `Remove screenshot at ${formatClock(evidence.atMs)}`,
5311
- className: "dt-ses-link",
5312
- onClick: () => {
5313
- setIsConfirming(true);
5314
- setHasCancelled(false);
5315
- },
5316
- ref: triggerRef,
5317
- type: "button",
5318
- children: "Remove"
5319
- });
5320
- }
5321
- //#endregion
5322
5486
  //#region src/sessions/ui/review/moment/moment-labels.ts
5323
5487
  const URL_PATH = /^[a-z][a-z\d+.-]*:\/\/[^/?#]*([^?#]*)/i;
5324
5488
  const KIND_LABELS = {
@@ -5338,7 +5502,7 @@ function contextKindLabel(item) {
5338
5502
  return KIND_LABELS[item.kind];
5339
5503
  }
5340
5504
  function pagePath(url) {
5341
- if (!url) return null;
5505
+ if (!url || url.startsWith("file:")) return null;
5342
5506
  return URL_PATH.exec(url)?.[1] || "/";
5343
5507
  }
5344
5508
  function contextLine$1(item) {
@@ -5852,69 +6016,262 @@ function MomentView({ handlers, segmentId, session }) {
5852
6016
  });
5853
6017
  }
5854
6018
  //#endregion
5855
- //#region src/sessions/ui/review/screens/callout-report.ts
5856
- const clampFraction = (value) => Math.min(1, Math.max(0, value));
5857
- function visibleFraction(rect, frame) {
5858
- if (frame.viewportWidth <= 0 || frame.viewportHeight <= 0) return null;
5859
- const left = clampFraction(rect.x / frame.viewportWidth);
5860
- const top = clampFraction(rect.y / frame.viewportHeight);
5861
- const right = clampFraction((rect.x + rect.width) / frame.viewportWidth);
5862
- const bottom = clampFraction((rect.y + rect.height) / frame.viewportHeight);
5863
- if (right <= left || bottom <= top) return null;
5864
- return {
5865
- height: bottom - top,
5866
- width: right - left,
5867
- x: left,
5868
- y: top
6019
+ //#region src/sessions/ui/review/processing-steps.ts
6020
+ function uploadStep(pendingUploads) {
6021
+ return pendingUploads > 0 ? {
6022
+ analyzeLabel: null,
6023
+ key: "upload",
6024
+ label: "Uploading",
6025
+ state: "active"
6026
+ } : {
6027
+ analyzeLabel: null,
6028
+ key: "upload",
6029
+ label: "Uploaded",
6030
+ state: "done"
5869
6031
  };
5870
6032
  }
5871
- function shownScreenshots(detail) {
5872
- const shots = /* @__PURE__ */ new Map();
5873
- for (const evidence of detail.evidence) {
5874
- const url = evidence.status === "available" ? evidence.image?.url : null;
5875
- if (evidence.image && url) shots.set(evidence.id, {
5876
- evidence,
5877
- image: {
5878
- ...evidence.image,
5879
- url
5880
- }
5881
- });
5882
- }
5883
- return shots;
5884
- }
5885
- const readingOrderY = (callout) => callout.outline?.y ?? 0;
5886
- function calloutReport(detail) {
5887
- const shots = shownScreenshots(detail);
5888
- const screensById = /* @__PURE__ */ new Map();
5889
- const unplaced = [];
5890
- const reported = detail.proposals.filter((proposal) => proposal.mergedIntoId === null && proposal.attachment === null);
5891
- for (const proposal of reported) {
5892
- const label = proposal.callout ?? proposal.need;
5893
- const target = proposal.calloutTarget;
5894
- const shot = target ? shots.get(target.evidenceId) : void 0;
5895
- if (!(target && shot)) {
5896
- unplaced.push({
5897
- label,
5898
- outline: null,
5899
- proposal
5900
- });
5901
- continue;
5902
- }
5903
- const { frame } = shot.evidence;
5904
- const outline = target.element && frame ? visibleFraction(target.element.rect, frame) : null;
5905
- const screen = screensById.get(shot.evidence.id) ?? {
5906
- ...shot,
5907
- callouts: []
6033
+ function transcriptionStep(detail, isAnalysisQueued) {
6034
+ const voice = detail.segments.filter((segment) => segment.source === "voice");
6035
+ const transcribing = voice.filter(({ transcription }) => transcription.state === "pending" || transcription.state === "running").length;
6036
+ const step = {
6037
+ analyzeLabel: null,
6038
+ key: "transcription"
6039
+ };
6040
+ if (transcribing > 0) {
6041
+ const transcribed = voice.length - transcribing;
6042
+ return {
6043
+ ...step,
6044
+ label: "Transcribing your voice",
6045
+ progress: {
6046
+ done: transcribed,
6047
+ total: voice.length,
6048
+ valueText: `${transcribed} of ${countLabel(voice.length, "passage")}`
6049
+ },
6050
+ state: "active"
5908
6051
  };
5909
- screen.callouts.push({
5910
- label,
5911
- outline,
5912
- proposal
5913
- });
5914
- screensById.set(shot.evidence.id, screen);
5915
6052
  }
5916
- let number = 0;
5917
- const numbered = (callout) => {
6053
+ if (voice.length > 0) return {
6054
+ ...step,
6055
+ label: "Voice transcribed",
6056
+ state: "done"
6057
+ };
6058
+ return isAnalysisQueued ? {
6059
+ ...step,
6060
+ label: "Transcribe your voice",
6061
+ state: "waiting"
6062
+ } : null;
6063
+ }
6064
+ function comparedRevision(detail) {
6065
+ const { continuation } = detail;
6066
+ return continuation?.intention && continuation.contribution.detachedAt === null ? continuation.contribution.baseRevision : null;
6067
+ }
6068
+ function intentionStep(detail, isAnalysisQueued) {
6069
+ const { session } = detail;
6070
+ const { analysis } = session;
6071
+ const revision = comparedRevision(detail);
6072
+ const isComparing = revision !== null;
6073
+ const step = {
6074
+ analyzeLabel: null,
6075
+ key: "intentions"
6076
+ };
6077
+ if (analysis.state === "running") {
6078
+ const label = isComparing ? `Comparing with revision ${revision}…` : "Finding intentions with AI…";
6079
+ return {
6080
+ ...step,
6081
+ label,
6082
+ state: "active"
6083
+ };
6084
+ }
6085
+ if (analysis.state === "waiting" || isAnalysisQueued) {
6086
+ const label = isComparing ? `Compare with revision ${revision}` : "Find intentions with AI";
6087
+ return {
6088
+ ...step,
6089
+ label,
6090
+ state: "waiting"
6091
+ };
6092
+ }
6093
+ if (analysis.state === "failed") {
6094
+ const failure = isComparing ? "The comparison failed" : "Analysis failed";
6095
+ const reason = analysis.error ?? "Unknown error.";
6096
+ return {
6097
+ ...step,
6098
+ analyzeLabel: "Try again",
6099
+ label: `${failure}: ${reason}`,
6100
+ state: "failed"
6101
+ };
6102
+ }
6103
+ if (isAnalysisOutdated(session)) return {
6104
+ ...step,
6105
+ analyzeLabel: "Analyze again",
6106
+ label: "The transcript changed since the analysis.",
6107
+ state: "waiting"
6108
+ };
6109
+ if (analysis.state === "idle") return detail.segments.length > 0 ? {
6110
+ ...step,
6111
+ analyzeLabel: "Analyze",
6112
+ label: "Not analyzed yet.",
6113
+ state: "waiting"
6114
+ } : null;
6115
+ const label = isComparing ? `Compared with revision ${revision}` : `Found ${countLabel(analysis.proposalCount ?? 0, "intention")}`;
6116
+ return {
6117
+ ...step,
6118
+ label,
6119
+ state: "done"
6120
+ };
6121
+ }
6122
+ function processingSteps(detail, pendingUploads) {
6123
+ if (detail.session.collection !== "ended") return [];
6124
+ const isAnalysisQueued = pendingUploads > 0 && detail.session.analysis.state === "idle";
6125
+ return [
6126
+ detail.session.isMine ? uploadStep(pendingUploads) : null,
6127
+ transcriptionStep(detail, isAnalysisQueued),
6128
+ intentionStep(detail, isAnalysisQueued)
6129
+ ].filter((step) => step !== null);
6130
+ }
6131
+ //#endregion
6132
+ //#region src/sessions/ui/review/processing-progress.tsx
6133
+ function useProcessingSteps(detail) {
6134
+ const { account } = useSessionWorkspace();
6135
+ const sync = useSyncStatus(account.key);
6136
+ return processingSteps(detail, detail.session.isMine ? sync.pendingOperations : 0);
6137
+ }
6138
+ function AnalyzeButton({ detail, label, review }) {
6139
+ const { api } = useSessionWorkspace();
6140
+ const run = useSessionAction();
6141
+ const analyze = async () => {
6142
+ const status = await run("The analysis did not start", () => api.analyze(detail.session.id));
6143
+ if (status) review.patch((current) => ({
6144
+ ...current,
6145
+ session: {
6146
+ ...current.session,
6147
+ analysis: status
6148
+ }
6149
+ }));
6150
+ };
6151
+ return /* @__PURE__ */ jsx("button", {
6152
+ className: "dt-btn",
6153
+ onClick: analyze,
6154
+ type: "button",
6155
+ children: label
6156
+ });
6157
+ }
6158
+ function ProgressBar({ label, progress }) {
6159
+ if (!progress) return /* @__PURE__ */ jsx("span", {
6160
+ "aria-label": label,
6161
+ className: "dt-ses-bar",
6162
+ role: "progressbar",
6163
+ children: /* @__PURE__ */ jsx("span", { "data-indeterminate": "true" })
6164
+ });
6165
+ const { done, total, valueText } = progress;
6166
+ return /* @__PURE__ */ jsx("span", {
6167
+ "aria-label": label,
6168
+ "aria-valuemax": total,
6169
+ "aria-valuemin": 0,
6170
+ "aria-valuenow": done,
6171
+ "aria-valuetext": valueText,
6172
+ className: "dt-ses-bar",
6173
+ role: "progressbar",
6174
+ title: valueText,
6175
+ children: /* @__PURE__ */ jsx("span", { style: { width: `${done / total * 100}%` } })
6176
+ });
6177
+ }
6178
+ function StepRow({ detail, review, step }) {
6179
+ return /* @__PURE__ */ jsxs("li", {
6180
+ "data-state": step.state,
6181
+ children: [
6182
+ /* @__PURE__ */ jsx("span", { children: step.label }),
6183
+ step.analyzeLabel && detail.session.canEdit && /* @__PURE__ */ jsx(AnalyzeButton, {
6184
+ detail,
6185
+ label: step.analyzeLabel,
6186
+ review
6187
+ }),
6188
+ step.state === "active" && /* @__PURE__ */ jsx(ProgressBar, {
6189
+ label: step.label,
6190
+ progress: step.progress
6191
+ })
6192
+ ]
6193
+ });
6194
+ }
6195
+ function ProcessingProgress({ detail, fillsPanel, isProcessing, review, steps }) {
6196
+ return /* @__PURE__ */ jsx("div", {
6197
+ "aria-live": "polite",
6198
+ className: "dt-ses-progress",
6199
+ "data-fills-panel": fillsPanel,
6200
+ children: isProcessing && /* @__PURE__ */ jsx("ol", {
6201
+ "aria-label": "Processing",
6202
+ className: "dt-ses-steps",
6203
+ children: steps.map((step) => /* @__PURE__ */ jsx(StepRow, {
6204
+ detail,
6205
+ review,
6206
+ step
6207
+ }, step.key))
6208
+ })
6209
+ });
6210
+ }
6211
+ //#endregion
6212
+ //#region src/sessions/ui/review/screens/callout-report.ts
6213
+ const clampFraction = (value) => Math.min(1, Math.max(0, value));
6214
+ function visibleFraction(rect, frame) {
6215
+ if (frame.viewportWidth <= 0 || frame.viewportHeight <= 0) return null;
6216
+ const left = clampFraction(rect.x / frame.viewportWidth);
6217
+ const top = clampFraction(rect.y / frame.viewportHeight);
6218
+ const right = clampFraction((rect.x + rect.width) / frame.viewportWidth);
6219
+ const bottom = clampFraction((rect.y + rect.height) / frame.viewportHeight);
6220
+ if (right <= left || bottom <= top) return null;
6221
+ return {
6222
+ height: bottom - top,
6223
+ width: right - left,
6224
+ x: left,
6225
+ y: top
6226
+ };
6227
+ }
6228
+ function shownScreenshots(detail) {
6229
+ const shots = /* @__PURE__ */ new Map();
6230
+ for (const evidence of detail.evidence) {
6231
+ const url = evidence.status === "available" ? evidence.image?.url : null;
6232
+ if (evidence.image && url) shots.set(evidence.id, {
6233
+ evidence,
6234
+ image: {
6235
+ ...evidence.image,
6236
+ url
6237
+ }
6238
+ });
6239
+ }
6240
+ return shots;
6241
+ }
6242
+ const readingOrderY = (callout) => callout.outline?.y ?? 0;
6243
+ function calloutReport(detail) {
6244
+ const shots = shownScreenshots(detail);
6245
+ const screensById = /* @__PURE__ */ new Map();
6246
+ const unplaced = [];
6247
+ const reported = detail.proposals.filter((proposal) => proposal.mergedIntoId === null && proposal.attachment === null);
6248
+ for (const proposal of reported) {
6249
+ const label = proposal.callout ?? proposal.need;
6250
+ const target = proposal.calloutTarget;
6251
+ const shot = target ? shots.get(target.evidenceId) : void 0;
6252
+ if (!(target && shot)) {
6253
+ unplaced.push({
6254
+ label,
6255
+ outline: null,
6256
+ proposal
6257
+ });
6258
+ continue;
6259
+ }
6260
+ const { frame } = shot.evidence;
6261
+ const outline = target.element && frame ? visibleFraction(target.element.rect, frame) : null;
6262
+ const screen = screensById.get(shot.evidence.id) ?? {
6263
+ ...shot,
6264
+ callouts: []
6265
+ };
6266
+ screen.callouts.push({
6267
+ label,
6268
+ outline,
6269
+ proposal
6270
+ });
6271
+ screensById.set(shot.evidence.id, screen);
6272
+ }
6273
+ let number = 0;
6274
+ const numbered = (callout) => {
5918
6275
  number += 1;
5919
6276
  return {
5920
6277
  ...callout,
@@ -6036,6 +6393,150 @@ function layoutCallouts(options) {
6036
6393
  };
6037
6394
  }
6038
6395
  //#endregion
6396
+ //#region src/sessions/ui/review/optimistic-change.ts
6397
+ function useOptimisticChange(review) {
6398
+ const run = useSessionAction();
6399
+ const applyChange = async (change) => {
6400
+ review.patch(change.show);
6401
+ const saved = await run(change.failureLabel, change.request, () => {
6402
+ applyChange(change);
6403
+ });
6404
+ if (saved === null) {
6405
+ review.patch(change.rollback);
6406
+ return null;
6407
+ }
6408
+ review.patch((detail) => change.settle(detail, saved));
6409
+ return saved;
6410
+ };
6411
+ return applyChange;
6412
+ }
6413
+ //#endregion
6414
+ //#region src/sessions/ui/review/proposals/use-proposal-actions.ts
6415
+ const NOTICE_NEED_LENGTH = 60;
6416
+ const DECISION_NOTICE = {
6417
+ dismissed: "Dismissed",
6418
+ kept: "Confirmed",
6419
+ later: "Saved for later",
6420
+ pending: "Back to undecided"
6421
+ };
6422
+ function quotedNeed(need) {
6423
+ return `“${need.length > NOTICE_NEED_LENGTH ? `${need.slice(0, 59)}…` : need}”`;
6424
+ }
6425
+ function decisionToggle(actions, proposal, decision) {
6426
+ return (event) => {
6427
+ if (event.detail > 1) return;
6428
+ actions.decide([proposal], proposal.decision === decision ? "pending" : decision);
6429
+ };
6430
+ }
6431
+ function useProposalSave(review) {
6432
+ const { api } = useSessionWorkspace();
6433
+ const run = useSessionAction();
6434
+ return async (failureLabel, request) => {
6435
+ const proposal = await run(failureLabel, () => request(api));
6436
+ if (proposal) review.patch((detail) => withProposal(detail, proposal));
6437
+ return proposal;
6438
+ };
6439
+ }
6440
+ function withDecisions(decisions) {
6441
+ return (detail) => ({
6442
+ ...detail,
6443
+ proposals: detail.proposals.map((item) => {
6444
+ const decision = decisions.get(item.id);
6445
+ return decision ? {
6446
+ ...item,
6447
+ decision
6448
+ } : item;
6449
+ })
6450
+ });
6451
+ }
6452
+ function decisionNotice(proposals, decision) {
6453
+ const [only] = proposals;
6454
+ const subject = proposals.length === 1 && only ? quotedNeed(only.need) : countLabel(proposals.length, "intention");
6455
+ return `${DECISION_NOTICE[decision]} ${subject}`;
6456
+ }
6457
+ function useDecide(review) {
6458
+ const { api } = useSessionWorkspace();
6459
+ const applyChange = useOptimisticChange(review);
6460
+ const changeDecisions = async (failureLabel, from, to) => {
6461
+ const saved = await applyChange({
6462
+ failureLabel,
6463
+ request: () => Promise.all([...to].map(([proposalId, decision]) => api.decideProposal({
6464
+ decision,
6465
+ proposalId
6466
+ }))),
6467
+ rollback: withDecisions(from),
6468
+ settle: (detail, proposals) => proposals.reduce(withProposal, detail),
6469
+ show: withDecisions(to)
6470
+ });
6471
+ if (!saved && to.size > 1) review.reload();
6472
+ return saved;
6473
+ };
6474
+ return async (proposals, decision) => {
6475
+ const previous = new Map(proposals.map((proposal) => [proposal.id, proposal.decision]));
6476
+ const next = new Map(proposals.map((proposal) => [proposal.id, decision]));
6477
+ if (!await changeDecisions("The decision was not saved", previous, next)) return;
6478
+ announce(decisionNotice(proposals, decision), {
6479
+ label: "Undo",
6480
+ run: async () => {
6481
+ if (await changeDecisions("The decision was not undone", next, previous)) announce("Decision undone");
6482
+ }
6483
+ });
6484
+ };
6485
+ }
6486
+ function withFeedbackIds(feedbackIds) {
6487
+ return (detail) => ({
6488
+ ...detail,
6489
+ proposals: detail.proposals.map((item) => {
6490
+ const feedbackId = feedbackIds.get(item.id);
6491
+ return feedbackId ? {
6492
+ ...item,
6493
+ feedbackId
6494
+ } : item;
6495
+ })
6496
+ });
6497
+ }
6498
+ function useAddToBoard(review) {
6499
+ const { api } = useSessionWorkspace();
6500
+ const run = useSessionAction();
6501
+ return async (proposals) => {
6502
+ const created = await Promise.all(proposals.map(async (proposal) => {
6503
+ const result = await run(`${quotedNeed(proposal.need)} was not added to the board`, () => api.createFeedback(proposal.id));
6504
+ return result ? [proposal.id, result.feedbackId] : null;
6505
+ }));
6506
+ const added = new Map(created.flatMap((entry) => entry ? [entry] : []));
6507
+ if (added.size === 0) return;
6508
+ review.patch(withFeedbackIds(added));
6509
+ const onlyAdded = added.size === 1 ? proposals.find((proposal) => added.has(proposal.id)) : void 0;
6510
+ announce(onlyAdded ? `Added ${quotedNeed(onlyAdded.need)} to the board` : `Added ${countLabel(added.size, "intention")} to the board`);
6511
+ };
6512
+ }
6513
+ function useProposalActions(review) {
6514
+ const save = useProposalSave(review);
6515
+ const decide = useDecide(review);
6516
+ return {
6517
+ addToBoard: useAddToBoard(review),
6518
+ clarify: (proposal, text) => save("The precision was not sent", (client) => client.clarifyProposal({
6519
+ expectedRevision: proposal.revision,
6520
+ proposalId: proposal.id,
6521
+ text
6522
+ })),
6523
+ decide,
6524
+ merge: async (targetId, sourceIds) => {
6525
+ if (await save("The intentions were not merged", (client) => client.mergeProposals({
6526
+ sourceIds,
6527
+ targetId
6528
+ }))) review.reload();
6529
+ },
6530
+ save,
6531
+ separate: (proposalId) => save("The suggestion was not cleared", (client) => client.separateProposal(proposalId)),
6532
+ verify: (proposalId, { note, status }) => save("The check was not saved", (client) => client.verifyProposal({
6533
+ note: note || void 0,
6534
+ proposalId,
6535
+ status
6536
+ }))
6537
+ };
6538
+ }
6539
+ //#endregion
6039
6540
  //#region src/sessions/ui/review/screens/callout-pill.tsx
6040
6541
  const TOGGLES = [{
6041
6542
  decision: "kept",
@@ -6065,22 +6566,16 @@ function CalloutPill({ callout, context, pillRef, style }) {
6065
6566
  type: "button",
6066
6567
  children: label
6067
6568
  }),
6068
- context.canEdit && TOGGLES.map(({ decision, icon, verb }) => {
6069
- const isOn = proposal.decision === decision;
6070
- return /* @__PURE__ */ jsx("button", {
6071
- "aria-disabled": isOn,
6072
- "aria-label": `${verb} “${label}”`,
6073
- "aria-pressed": isOn,
6074
- className: "dt-ses-callout-btn",
6075
- "data-decision": decision,
6076
- onClick: () => {
6077
- if (!isOn) context.actions.decide(proposal, decision);
6078
- },
6079
- title: verb,
6080
- type: "button",
6081
- children: icon
6082
- }, decision);
6083
- })
6569
+ context.canEdit && TOGGLES.map(({ decision, icon, verb }) => /* @__PURE__ */ jsx("button", {
6570
+ "aria-label": `${verb} “${label}”`,
6571
+ "aria-pressed": proposal.decision === decision,
6572
+ className: "dt-ses-callout-btn",
6573
+ "data-decision": decision,
6574
+ onClick: decisionToggle(context.actions, proposal, decision),
6575
+ title: verb,
6576
+ type: "button",
6577
+ children: icon
6578
+ }, decision))
6084
6579
  ]
6085
6580
  });
6086
6581
  }
@@ -6121,12 +6616,6 @@ function useMeasuredPills(measureKey) {
6121
6616
  pillRef
6122
6617
  };
6123
6618
  }
6124
- function screenCaption(screen) {
6125
- const { evidence } = screen;
6126
- const time = formatClock(evidence.capturedAtMs ?? evidence.atMs);
6127
- const page = pagePath(evidence.frame?.url ?? null);
6128
- return page ? `${time} · ${page}` : time;
6129
- }
6130
6619
  function CalloutMark({ callout, imageSize, imageTop }) {
6131
6620
  const { outline, proposal } = callout;
6132
6621
  if (!outline) return null;
@@ -6142,7 +6631,8 @@ function CalloutMark({ callout, imageSize, imageTop }) {
6142
6631
  }
6143
6632
  });
6144
6633
  }
6145
- function ScreenFigure({ context, screen }) {
6634
+ function ScreenFigure({ context, newPage, screen }) {
6635
+ const atMs = screen.evidence.capturedAtMs ?? screen.evidence.atMs;
6146
6636
  const { callouts, image } = screen;
6147
6637
  const { canvasRef, measured, pillRef } = useMeasuredPills(callouts.map(({ label, proposal }) => `${proposal.id}:${label}`).join("|"));
6148
6638
  const width = measured?.width ?? 0;
@@ -6162,7 +6652,10 @@ function ScreenFigure({ context, screen }) {
6162
6652
  const belowImage = layout ? layout.height - imageTop - imageSize.height : 0;
6163
6653
  return /* @__PURE__ */ jsxs("figure", {
6164
6654
  className: "dt-ses-report",
6165
- children: [/* @__PURE__ */ jsxs("div", {
6655
+ children: [/* @__PURE__ */ jsx("figcaption", { children: /* @__PURE__ */ jsx(ShotCaption, {
6656
+ atMs,
6657
+ children: newPage
6658
+ }) }), /* @__PURE__ */ jsxs("div", {
6166
6659
  className: "dt-ses-report-canvas",
6167
6660
  "data-measured": layout !== null,
6168
6661
  ref: canvasRef,
@@ -6172,7 +6665,7 @@ function ScreenFigure({ context, screen }) {
6172
6665
  },
6173
6666
  children: [
6174
6667
  /* @__PURE__ */ jsx("img", {
6175
- alt: `Screenshot at ${screenCaption(screen)}`,
6668
+ alt: `Screenshot at ${formatClock(atMs)}`,
6176
6669
  height: image.height,
6177
6670
  src: image.url,
6178
6671
  width: image.width
@@ -6220,15 +6713,17 @@ function ScreenFigure({ context, screen }) {
6220
6713
  })
6221
6714
  })
6222
6715
  ]
6223
- }), /* @__PURE__ */ jsx("figcaption", {
6224
- className: "dt-meta",
6225
- children: screenCaption(screen)
6226
6716
  })]
6227
6717
  });
6228
6718
  }
6229
6719
  function ScreenReport({ context, report }) {
6230
- return /* @__PURE__ */ jsxs(Fragment, { children: [report.screens.map((screen) => /* @__PURE__ */ jsx(ScreenFigure, {
6720
+ const figures = report.screens.map((screen) => ({
6721
+ page: pagePath(screen.evidence.frame?.url ?? null),
6722
+ screen
6723
+ }));
6724
+ return /* @__PURE__ */ jsxs(Fragment, { children: [figures.map(({ page, screen }, index) => /* @__PURE__ */ jsx(ScreenFigure, {
6231
6725
  context,
6726
+ newPage: figures[index - 1]?.page === page ? null : page,
6232
6727
  screen
6233
6728
  }, screen.evidence.id)), report.unplaced.length > 0 && /* @__PURE__ */ jsxs("section", {
6234
6729
  className: "dt-ses-block",
@@ -6439,141 +6934,6 @@ function StartSession({ onStarted, onWriteNote }) {
6439
6934
  ] });
6440
6935
  }
6441
6936
  //#endregion
6442
- //#region src/sessions/ui/review/optimistic-change.ts
6443
- function useOptimisticChange(review) {
6444
- const run = useSessionAction();
6445
- const applyChange = async (change) => {
6446
- review.patch(change.show);
6447
- const saved = await run(change.failureLabel, change.request, () => {
6448
- applyChange(change);
6449
- });
6450
- if (saved === null) {
6451
- review.patch(change.rollback);
6452
- return null;
6453
- }
6454
- review.patch((detail) => change.settle(detail, saved));
6455
- return saved;
6456
- };
6457
- return applyChange;
6458
- }
6459
- //#endregion
6460
- //#region src/sessions/ui/review/proposals/use-proposal-actions.ts
6461
- const NOTICE_NEED_LENGTH = 60;
6462
- const DECISION_NOTICE = {
6463
- dismissed: "Dismissed",
6464
- kept: "Confirmed",
6465
- later: "Saved for later",
6466
- pending: "Back to undecided"
6467
- };
6468
- function quotedNeed(need) {
6469
- return `“${need.length > NOTICE_NEED_LENGTH ? `${need.slice(0, 59)}…` : need}”`;
6470
- }
6471
- function useProposalSave(review) {
6472
- const { api } = useSessionWorkspace();
6473
- const run = useSessionAction();
6474
- return async (failureLabel, request) => {
6475
- const proposal = await run(failureLabel, () => request(api));
6476
- if (proposal) review.patch((detail) => withProposal(detail, proposal));
6477
- return proposal;
6478
- };
6479
- }
6480
- function withDecision(proposalId, decision) {
6481
- return (detail) => ({
6482
- ...detail,
6483
- proposals: detail.proposals.map((item) => item.id === proposalId ? {
6484
- ...item,
6485
- decision
6486
- } : item)
6487
- });
6488
- }
6489
- function useDecide(review) {
6490
- const { api } = useSessionWorkspace();
6491
- const applyChange = useOptimisticChange(review);
6492
- const changeDecision = (options) => applyChange({
6493
- failureLabel: options.failureLabel,
6494
- request: () => api.decideProposal({
6495
- decision: options.to,
6496
- proposalId: options.proposalId
6497
- }),
6498
- rollback: withDecision(options.proposalId, options.from),
6499
- settle: withProposal,
6500
- show: withDecision(options.proposalId, options.to)
6501
- });
6502
- return async (proposal, decision) => {
6503
- const previous = proposal.decision;
6504
- if (!await changeDecision({
6505
- failureLabel: "The decision was not saved",
6506
- from: previous,
6507
- proposalId: proposal.id,
6508
- to: decision
6509
- })) return;
6510
- announce(`${DECISION_NOTICE[decision]} ${quotedNeed(proposal.need)}`, {
6511
- label: "Undo",
6512
- run: async () => {
6513
- if (await changeDecision({
6514
- failureLabel: "The decision was not undone",
6515
- from: decision,
6516
- proposalId: proposal.id,
6517
- to: previous
6518
- })) announce("Decision undone");
6519
- }
6520
- });
6521
- };
6522
- }
6523
- function withFeedbackIds(feedbackIds) {
6524
- return (detail) => ({
6525
- ...detail,
6526
- proposals: detail.proposals.map((item) => {
6527
- const feedbackId = feedbackIds.get(item.id);
6528
- return feedbackId ? {
6529
- ...item,
6530
- feedbackId
6531
- } : item;
6532
- })
6533
- });
6534
- }
6535
- function useAddToBoard(review) {
6536
- const { api } = useSessionWorkspace();
6537
- const run = useSessionAction();
6538
- return async (proposals) => {
6539
- const created = await Promise.all(proposals.map(async (proposal) => {
6540
- const result = await run(`${quotedNeed(proposal.need)} was not added to the board`, () => api.createFeedback(proposal.id));
6541
- return result ? [proposal.id, result.feedbackId] : null;
6542
- }));
6543
- const added = new Map(created.flatMap((entry) => entry ? [entry] : []));
6544
- if (added.size === 0) return;
6545
- review.patch(withFeedbackIds(added));
6546
- const onlyAdded = added.size === 1 ? proposals.find((proposal) => added.has(proposal.id)) : void 0;
6547
- announce(onlyAdded ? `Added ${quotedNeed(onlyAdded.need)} to the board` : `Added ${countLabel(added.size, "intention")} to the board`);
6548
- };
6549
- }
6550
- function useProposalActions(review) {
6551
- const save = useProposalSave(review);
6552
- const decide = useDecide(review);
6553
- return {
6554
- addToBoard: useAddToBoard(review),
6555
- clarify: (proposal, text) => save("The precision was not sent", (client) => client.clarifyProposal({
6556
- expectedRevision: proposal.revision,
6557
- proposalId: proposal.id,
6558
- text
6559
- })),
6560
- decide,
6561
- merge: async (targetId, sourceIds) => {
6562
- if (await save("The intentions were not merged", (client) => client.mergeProposals({
6563
- sourceIds,
6564
- targetId
6565
- }))) review.reload();
6566
- },
6567
- save,
6568
- separate: (proposalId) => save("The suggestion was not cleared", (client) => client.separateProposal(proposalId)),
6569
- verify: (proposalId, { note, status }) => save("The check was not saved", (client) => client.verifyProposal({
6570
- note: note || void 0,
6571
- proposalId,
6572
- status
6573
- }))
6574
- };
6575
- }
6576
- //#endregion
6577
6937
  //#region src/sessions/ui/review/proposals/delete-intention.tsx
6578
6938
  const DELETE_INTENTION_COPY = "Delete this intention? What later sessions added to it goes too, and so do the briefs Reflet saved that include it. Briefs you already copied or downloaded keep their text.";
6579
6939
  function DeleteConfirmation$1({ context, onCancel, proposal }) {
@@ -6884,32 +7244,26 @@ function IntentionSource({ context, proposal }) {
6884
7244
  if (!segment) return null;
6885
7245
  const [firstContext] = segment.context;
6886
7246
  const range = `${formatClock(segment.startMs)}–${formatClock(segment.endMs)}`;
6887
- return /* @__PURE__ */ jsxs("section", {
6888
- className: "dt-ses-block",
6889
- children: [
6890
- /* @__PURE__ */ jsxs("p", {
6891
- className: "dt-ses-source",
6892
- children: [
6893
- /* @__PURE__ */ jsx("span", {
6894
- className: "dt-ses-time",
6895
- children: range
6896
- }),
6897
- " “",
6898
- segment.text,
6899
- "”"
6900
- ]
6901
- }),
6902
- firstContext && /* @__PURE__ */ jsx("p", {
6903
- className: "dt-meta",
6904
- children: contextLine$1(firstContext)
6905
- }),
6906
- /* @__PURE__ */ jsx("button", {
6907
- className: "dt-ses-link",
6908
- onClick: (event) => context.navigation.onShowMoment(segment.id, event.currentTarget),
6909
- type: "button",
6910
- children: "Show context"
6911
- })
6912
- ]
7247
+ return /* @__PURE__ */ jsxs("button", {
7248
+ className: "dt-ses-source-btn",
7249
+ onClick: (event) => context.navigation.onShowMoment(segment.id, event.currentTarget),
7250
+ title: "Show the moment",
7251
+ type: "button",
7252
+ children: [/* @__PURE__ */ jsxs("span", {
7253
+ className: "dt-ses-source",
7254
+ children: [
7255
+ /* @__PURE__ */ jsx("span", {
7256
+ className: "dt-ses-time",
7257
+ children: range
7258
+ }),
7259
+ " “",
7260
+ segment.text,
7261
+ "”"
7262
+ ]
7263
+ }), firstContext && /* @__PURE__ */ jsx("span", {
7264
+ className: "dt-meta",
7265
+ children: contextLine$1(firstContext)
7266
+ })]
6913
7267
  });
6914
7268
  }
6915
7269
  //#endregion
@@ -7156,29 +7510,25 @@ function MergeSuggestion({ context, proposal }) {
7156
7510
  });
7157
7511
  }
7158
7512
  function DecisionButtons({ context, onEdit, proposal }) {
7159
- const decide = (decision) => () => {
7160
- if (proposal.decision !== decision) context.actions.decide(proposal, decision);
7161
- };
7513
+ const toggle = (decision) => decisionToggle(context.actions, proposal, decision);
7162
7514
  const isKept = proposal.decision === "kept";
7163
7515
  return /* @__PURE__ */ jsxs("div", {
7164
7516
  className: "dt-actions",
7165
7517
  children: [
7166
7518
  /* @__PURE__ */ jsxs("button", {
7167
- "aria-disabled": isKept,
7168
7519
  "aria-pressed": isKept,
7169
7520
  className: "dt-btn",
7170
7521
  "data-variant": "primary",
7171
- onClick: decide("kept"),
7522
+ onClick: toggle("kept"),
7172
7523
  type: "button",
7173
7524
  children: [/* @__PURE__ */ jsx(CheckIcon, { size: 14 }), "Confirm"]
7174
7525
  }),
7175
7526
  ICON_DECISIONS.map(({ decision, icon, label }) => /* @__PURE__ */ jsx("button", {
7176
- "aria-disabled": proposal.decision === decision,
7177
7527
  "aria-label": label,
7178
7528
  "aria-pressed": proposal.decision === decision,
7179
7529
  className: "dt-btn",
7180
7530
  "data-icon-only": true,
7181
- onClick: decide(decision),
7531
+ onClick: toggle(decision),
7182
7532
  title: label,
7183
7533
  type: "button",
7184
7534
  children: icon
@@ -7356,87 +7706,57 @@ function ProposalCard({ context, proposal }) {
7356
7706
  //#endregion
7357
7707
  //#region src/sessions/ui/review/proposals/proposals-tab.tsx
7358
7708
  function emptyCopy(detail) {
7359
- const { session } = detail;
7360
- if (session.collection !== "ended") return "Intentions appear here once the recording has uploaded and Reflet has read it.";
7709
+ if (detail.session.collection !== "ended") return "Intentions appear here once the recording has uploaded and Reflet has read it.";
7361
7710
  if (detail.segments.length === 0 && detail.marks.length === 0) return "Nothing was said or marked in this session.";
7362
- if (isAnalysisBusy(session)) return "Intentions appear here once Reflet has read the session.";
7363
- if (session.analysis.state === "failed") return "The transcript is kept. Read it, or write the intention yourself.";
7364
- if (session.analysis.state === "succeeded") return "No request stood out in this session. Read the transcript or write the intention yourself.";
7365
- return "Intentions appear here once the session is analyzed.";
7366
- }
7367
- function useAddProposal(context, onDone) {
7368
- const [need, setNeed] = useState("");
7369
- const [observed, setObserved] = useState("");
7370
- const [clientKey] = useState(() => crypto.randomUUID());
7371
- const [isSaving, setIsSaving] = useState(false);
7372
- const sessionId = context.detail.session.id;
7373
- const add = async () => {
7374
- setIsSaving(true);
7375
- try {
7376
- if (await context.actions.save("The intention was not added", (api) => api.createProposal({
7377
- clientKey,
7378
- need: need.trim(),
7379
- observed: observed.trim() || void 0,
7380
- sessionId
7381
- }))) onDone();
7382
- } finally {
7383
- setIsSaving(false);
7384
- }
7385
- };
7386
- return {
7387
- add,
7388
- isSaving,
7389
- need,
7390
- observed,
7391
- setNeed,
7392
- setObserved
7393
- };
7394
- }
7395
- function AddProposalForm({ context, onDone }) {
7396
- const { add, isSaving, need, observed, setNeed, setObserved } = useAddProposal(context, onDone);
7397
- return /* @__PURE__ */ jsxs("form", {
7398
- className: "dt-card dt-ses-form",
7399
- onSubmit: (event) => {
7400
- event.preventDefault();
7401
- add();
7402
- },
7403
- children: [
7404
- /* @__PURE__ */ jsx(TextField, {
7405
- label: "Need",
7406
- maxLength: 300,
7407
- onChange: setNeed,
7408
- rows: 2,
7409
- value: need
7410
- }),
7411
- /* @__PURE__ */ jsx(TextField, {
7412
- label: "What you observed (optional)",
7413
- maxLength: 2e3,
7414
- onChange: setObserved,
7415
- rows: 3,
7416
- value: observed
7417
- }),
7418
- /* @__PURE__ */ jsx(FormActions, {
7419
- isSubmitDisabled: isSaving || need.trim() === "",
7420
- onCancel: onDone,
7421
- submitLabel: "Add intention"
7422
- })
7423
- ]
7711
+ return "No request stood out in this session.";
7712
+ }
7713
+ function ConfirmAll({ context, undecided }) {
7714
+ return /* @__PURE__ */ jsx("div", {
7715
+ className: "dt-actions",
7716
+ children: /* @__PURE__ */ jsxs("button", {
7717
+ className: "dt-btn",
7718
+ onClick: () => context.actions.decide(undecided, "kept"),
7719
+ type: "button",
7720
+ children: [
7721
+ /* @__PURE__ */ jsx(CheckIcon, { size: 14 }),
7722
+ "Confirm all",
7723
+ /* @__PURE__ */ jsx("span", {
7724
+ className: "dt-btn-count",
7725
+ children: undecided.length
7726
+ })
7727
+ ]
7728
+ })
7424
7729
  });
7425
7730
  }
7426
7731
  function ProposalsTab({ context, view }) {
7427
- const [isAdding, setIsAdding] = useState(false);
7428
- const proposals = context.detail.proposals.filter((proposal) => proposal.mergedIntoId === null);
7429
- const { continuation } = context.detail;
7430
- const report = calloutReport(context.detail);
7732
+ const { detail, review } = context;
7733
+ const steps = useProcessingSteps(detail);
7734
+ const isProcessing = steps.some((step) => step.state !== "done");
7735
+ const proposals = detail.proposals.filter((proposal) => proposal.mergedIntoId === null);
7736
+ const undecided = proposals.filter((proposal) => proposal.decision === "pending" && proposal.attachment === null);
7737
+ const { continuation } = detail;
7738
+ const hasNothingToReview = proposals.length === 0 && !continuation;
7739
+ const report = calloutReport(detail);
7431
7740
  const showsScreens = report.screens.length > 0 && view === "screens";
7432
7741
  return /* @__PURE__ */ jsxs(Fragment, { children: [
7742
+ /* @__PURE__ */ jsx(ProcessingProgress, {
7743
+ detail,
7744
+ fillsPanel: hasNothingToReview,
7745
+ isProcessing,
7746
+ review,
7747
+ steps
7748
+ }),
7433
7749
  continuation && /* @__PURE__ */ jsx(UpdateCard, {
7434
7750
  context,
7435
7751
  continuation
7436
7752
  }),
7437
- proposals.length === 0 && !continuation && /* @__PURE__ */ jsx("p", {
7753
+ hasNothingToReview && !isProcessing && /* @__PURE__ */ jsx("p", {
7438
7754
  className: "dt-empty",
7439
- children: emptyCopy(context.detail)
7755
+ children: emptyCopy(detail)
7756
+ }),
7757
+ context.canEdit && undecided.length > 1 && /* @__PURE__ */ jsx(ConfirmAll, {
7758
+ context,
7759
+ undecided
7440
7760
  }),
7441
7761
  showsScreens && /* @__PURE__ */ jsx(ScreenReport, {
7442
7762
  context,
@@ -7445,57 +7765,41 @@ function ProposalsTab({ context, view }) {
7445
7765
  proposals.filter((proposal) => !showsScreens || proposal.attachment !== null).map((proposal) => /* @__PURE__ */ jsx(ProposalCard, {
7446
7766
  context,
7447
7767
  proposal
7448
- }, proposal.id)),
7449
- context.canEdit && (isAdding ? /* @__PURE__ */ jsx(AddProposalForm, {
7450
- context,
7451
- onDone: () => setIsAdding(false)
7452
- }) : /* @__PURE__ */ jsx("div", {
7453
- className: "dt-actions",
7454
- children: /* @__PURE__ */ jsx("button", {
7455
- className: "dt-btn",
7456
- onClick: () => setIsAdding(true),
7457
- type: "button",
7458
- children: "Add an intention"
7459
- })
7460
- }))
7768
+ }, proposal.id))
7461
7769
  ] });
7462
7770
  }
7463
7771
  //#endregion
7464
7772
  //#region src/sessions/ui/review/review-footer.tsx
7773
+ const NOTHING_CONFIRMED = "Confirm an intention first";
7465
7774
  function ReviewFooter({ context, onOpenBrief }) {
7466
7775
  const confirmed = keptProposals(context.detail);
7467
7776
  const offBoard = confirmed.filter((proposal) => proposal.feedbackId === null);
7777
+ const hasConfirmed = confirmed.length > 0;
7778
+ const boardBlocker = hasConfirmed ? "Every confirmed intention is on the board" : NOTHING_CONFIRMED;
7468
7779
  return /* @__PURE__ */ jsxs("div", {
7469
- className: "dt-sheet-foot",
7470
- children: [
7471
- /* @__PURE__ */ jsxs("button", {
7472
- "aria-describedby": confirmed.length === 0 ? "rf-brief-hint" : void 0,
7473
- className: "dt-btn",
7474
- "data-variant": "primary",
7475
- disabled: confirmed.length === 0,
7476
- onClick: onOpenBrief,
7477
- type: "button",
7478
- children: ["Prepare brief", confirmed.length > 0 && /* @__PURE__ */ jsx("span", {
7479
- className: "dt-btn-count",
7480
- children: confirmed.length
7481
- })]
7482
- }),
7483
- offBoard.length > 0 && /* @__PURE__ */ jsxs("button", {
7484
- className: "dt-btn",
7485
- onClick: () => context.actions.addToBoard(offBoard),
7486
- type: "button",
7487
- children: [
7488
- "Add ",
7489
- offBoard.length,
7490
- " to board"
7491
- ]
7492
- }),
7493
- confirmed.length === 0 && /* @__PURE__ */ jsx("span", {
7494
- className: "dt-status",
7495
- id: "rf-brief-hint",
7496
- children: "Confirm an intention to brief your agent or add it to the board"
7497
- })
7498
- ]
7780
+ className: "dt-sheet-foot dt-ses-foot",
7781
+ children: [/* @__PURE__ */ jsxs("button", {
7782
+ className: "dt-btn",
7783
+ "data-variant": "primary",
7784
+ disabled: !hasConfirmed,
7785
+ onClick: onOpenBrief,
7786
+ title: hasConfirmed ? void 0 : NOTHING_CONFIRMED,
7787
+ type: "button",
7788
+ children: ["Prepare brief", hasConfirmed && /* @__PURE__ */ jsx("span", {
7789
+ className: "dt-btn-count",
7790
+ children: confirmed.length
7791
+ })]
7792
+ }), /* @__PURE__ */ jsxs("button", {
7793
+ className: "dt-btn",
7794
+ disabled: offBoard.length === 0,
7795
+ onClick: () => context.actions.addToBoard(offBoard),
7796
+ title: offBoard.length === 0 ? boardBlocker : void 0,
7797
+ type: "button",
7798
+ children: ["Add to board", offBoard.length > 0 && /* @__PURE__ */ jsx("span", {
7799
+ className: "dt-btn-count",
7800
+ children: offBoard.length
7801
+ })]
7802
+ })]
7499
7803
  });
7500
7804
  }
7501
7805
  //#endregion
@@ -7750,6 +8054,24 @@ function DeleteConfirmation({ onCancel, onDeleted, sessionId }) {
7750
8054
  })]
7751
8055
  });
7752
8056
  }
8057
+ function SessionFacts({ session }) {
8058
+ const isShared = session.visibility === "team";
8059
+ const sharing = isShared ? "Shared with team" : "Private";
8060
+ return /* @__PURE__ */ jsxs("p", {
8061
+ className: "dt-ses-facts",
8062
+ children: [
8063
+ /* @__PURE__ */ jsx("span", { children: formatDate(session.startedAt) }),
8064
+ /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx(ClockIcon, {}), shortDuration(session.durationMs)] }),
8065
+ /* @__PURE__ */ jsx("span", {
8066
+ "aria-label": sharing,
8067
+ role: "img",
8068
+ title: sharing,
8069
+ children: isShared ? /* @__PURE__ */ jsx(TeamIcon, {}) : /* @__PURE__ */ jsx(LockIcon, {})
8070
+ }),
8071
+ !session.isMine && /* @__PURE__ */ jsx("span", { children: session.authorName ?? "A teammate" })
8072
+ ]
8073
+ });
8074
+ }
7753
8075
  function ReviewHeader({ onDeleted, review, session }) {
7754
8076
  const [isRenaming, setIsRenaming] = useState(false);
7755
8077
  const [isConfirmingDelete, setIsConfirmingDelete] = useState(false);
@@ -7772,44 +8094,34 @@ function ReviewHeader({ onDeleted, review, session }) {
7772
8094
  ];
7773
8095
  return /* @__PURE__ */ jsxs("header", {
7774
8096
  className: "dt-ses-head",
7775
- children: [
7776
- isRenaming ? /* @__PURE__ */ jsx(TitleEditor, {
7777
- onDone: () => setIsRenaming(false),
7778
- review,
7779
- session
7780
- }) : /* @__PURE__ */ jsxs("div", {
7781
- className: "dt-card-head",
7782
- children: [/* @__PURE__ */ jsx("h2", {
8097
+ children: [isRenaming ? /* @__PURE__ */ jsx(TitleEditor, {
8098
+ onDone: () => setIsRenaming(false),
8099
+ review,
8100
+ session
8101
+ }) : /* @__PURE__ */ jsxs("div", {
8102
+ className: "dt-card-head",
8103
+ children: [
8104
+ /* @__PURE__ */ jsx("h2", {
7783
8105
  className: "dt-ses-title",
7784
8106
  tabIndex: -1,
8107
+ title: session.title,
7785
8108
  children: session.title
7786
- }), session.canEdit && /* @__PURE__ */ jsx(Menu, {
8109
+ }),
8110
+ /* @__PURE__ */ jsx(SessionFacts, { session }),
8111
+ session.canEdit && /* @__PURE__ */ jsx(Menu, {
7787
8112
  items: menuItems,
7788
8113
  trigger: {
7789
8114
  className: "icon-btn",
7790
8115
  content: /* @__PURE__ */ jsx(MoreIcon, {}),
7791
8116
  label: "Session options"
7792
8117
  }
7793
- })]
7794
- }),
7795
- /* @__PURE__ */ jsxs("p", {
7796
- className: "dt-meta",
7797
- children: [
7798
- formatDate(session.startedAt),
7799
- " · ",
7800
- shortDuration(session.durationMs),
7801
- " ·",
7802
- " ",
7803
- isShared ? "Shared with team" : "Private",
7804
- session.isMine ? "" : ` · ${session.authorName ?? "A teammate"}`
7805
- ]
7806
- }),
7807
- isConfirmingDelete && session.canEdit && /* @__PURE__ */ jsx(DeleteConfirmation, {
7808
- onCancel: () => setIsConfirmingDelete(false),
7809
- onDeleted,
7810
- sessionId: session.id
7811
- })
7812
- ]
8118
+ })
8119
+ ]
8120
+ }), isConfirmingDelete && session.canEdit && /* @__PURE__ */ jsx(DeleteConfirmation, {
8121
+ onCancel: () => setIsConfirmingDelete(false),
8122
+ onDeleted,
8123
+ sessionId: session.id
8124
+ })]
7813
8125
  });
7814
8126
  }
7815
8127
  //#endregion
@@ -7835,210 +8147,51 @@ function IntentionViewMenu({ place }) {
7835
8147
  }
7836
8148
  });
7837
8149
  }
7838
- function tabOptions(detail) {
7839
- return [
7840
- {
7841
- count: detail.proposals.filter((proposal) => proposal.mergedIntoId === null && proposal.attachment === null).length,
7842
- icon: /* @__PURE__ */ jsx(IntentionIcon, {}),
7843
- label: "Intentions",
7844
- value: "proposals"
7845
- },
7846
- {
7847
- count: null,
7848
- icon: /* @__PURE__ */ jsx(TranscriptIcon, {}),
7849
- label: "Transcript",
7850
- value: "transcript"
7851
- },
7852
- {
7853
- count: detail.evidence.length,
7854
- icon: /* @__PURE__ */ jsx(ImageIcon, {}),
7855
- label: "Evidence",
7856
- value: "evidence"
7857
- }
7858
- ];
7859
- }
7860
- function ReviewTabs({ detail, place }) {
7861
- const canPickView = place.tab === "proposals" && calloutReport(detail).screens.length > 0;
7862
- return /* @__PURE__ */ jsxs("div", {
7863
- className: "dt-toolbar",
7864
- children: [/* @__PURE__ */ jsx("div", {
7865
- className: "dt-tabs",
7866
- role: "tablist",
7867
- children: tabOptions(detail).map((option) => /* @__PURE__ */ jsxs("button", {
7868
- "aria-selected": option.value === place.tab,
7869
- className: "dt-tab",
7870
- onClick: () => place.selectTab(option.value),
7871
- role: "tab",
7872
- type: "button",
7873
- children: [
7874
- option.icon,
7875
- option.label,
7876
- option.count !== null && /* @__PURE__ */ jsx("span", {
7877
- className: "dt-tab-count",
7878
- children: option.count
7879
- })
7880
- ]
7881
- }, option.value))
7882
- }), canPickView && /* @__PURE__ */ jsx(IntentionViewMenu, { place })]
7883
- });
7884
- }
7885
- //#endregion
7886
- //#region src/sessions/ui/review/processing-steps.ts
7887
- function uploadStep(pendingUploads) {
7888
- return pendingUploads > 0 ? {
7889
- analyzeLabel: null,
7890
- key: "upload",
7891
- label: `Uploading · ${pendingUploads} waiting`,
7892
- state: "active"
7893
- } : {
7894
- analyzeLabel: null,
7895
- key: "upload",
7896
- label: "Uploaded",
7897
- state: "done"
7898
- };
7899
- }
7900
- function transcriptionStep(detail, isAnalysisQueued) {
7901
- const voice = detail.segments.filter((segment) => segment.source === "voice");
7902
- const transcribing = voice.filter(({ transcription }) => transcription.state === "pending" || transcription.state === "running").length;
7903
- const step = {
7904
- analyzeLabel: null,
7905
- key: "transcription"
7906
- };
7907
- if (transcribing > 0) {
7908
- const transcribed = voice.length - transcribing;
7909
- return {
7910
- ...step,
7911
- label: `Transcribing your voice · ${transcribed} of ${voice.length}`,
7912
- state: "active"
7913
- };
7914
- }
7915
- if (voice.length > 0) return {
7916
- ...step,
7917
- label: "Voice transcribed",
7918
- state: "done"
7919
- };
7920
- return isAnalysisQueued ? {
7921
- ...step,
7922
- label: "Transcribe your voice",
7923
- state: "waiting"
7924
- } : null;
7925
- }
7926
- function comparedRevision(detail) {
7927
- const { continuation } = detail;
7928
- return continuation?.intention && continuation.contribution.detachedAt === null ? continuation.contribution.baseRevision : null;
7929
- }
7930
- function intentionStep(detail, isAnalysisQueued) {
7931
- const { session } = detail;
7932
- const { analysis } = session;
7933
- const revision = comparedRevision(detail);
7934
- const isComparing = revision !== null;
7935
- const step = {
7936
- analyzeLabel: null,
7937
- key: "intentions"
7938
- };
7939
- if (analysis.state === "running") {
7940
- const label = isComparing ? `Comparing with revision ${revision}…` : "Finding intentions with AI…";
7941
- return {
7942
- ...step,
7943
- label,
7944
- state: "active"
7945
- };
7946
- }
7947
- if (analysis.state === "waiting" || isAnalysisQueued) {
7948
- const label = isComparing ? `Compare with revision ${revision}` : "Find intentions with AI";
7949
- return {
7950
- ...step,
7951
- label,
7952
- state: "waiting"
7953
- };
7954
- }
7955
- if (analysis.state === "failed") {
7956
- const failure = isComparing ? "The comparison failed" : "Analysis failed";
7957
- const reason = analysis.error ?? "Unknown error.";
7958
- return {
7959
- ...step,
7960
- analyzeLabel: "Try again",
7961
- label: `${failure}: ${reason}`,
7962
- state: "failed"
7963
- };
7964
- }
7965
- if (isAnalysisOutdated(session)) return {
7966
- ...step,
7967
- analyzeLabel: "Analyze again",
7968
- label: "The transcript changed since the analysis.",
7969
- state: "waiting"
7970
- };
7971
- if (analysis.state === "idle") return detail.segments.length > 0 ? {
7972
- ...step,
7973
- analyzeLabel: "Analyze",
7974
- label: "Not analyzed yet.",
7975
- state: "waiting"
7976
- } : null;
7977
- const label = isComparing ? `Compared with revision ${revision}` : `Found ${countLabel(analysis.proposalCount ?? 0, "intention")}`;
7978
- return {
7979
- ...step,
7980
- label,
7981
- state: "done"
7982
- };
7983
- }
7984
- function processingSteps(detail, pendingUploads) {
7985
- if (detail.session.collection !== "ended") return [];
7986
- const isAnalysisQueued = pendingUploads > 0 && detail.session.analysis.state === "idle";
7987
- return [
7988
- detail.session.isMine ? uploadStep(pendingUploads) : null,
7989
- transcriptionStep(detail, isAnalysisQueued),
7990
- intentionStep(detail, isAnalysisQueued)
7991
- ].filter((step) => step !== null);
7992
- }
7993
- //#endregion
7994
- //#region src/sessions/ui/review/status-strip.tsx
7995
- function AnalyzeButton({ detail, label, review }) {
7996
- const { api } = useSessionWorkspace();
7997
- const run = useSessionAction();
7998
- const analyze = async () => {
7999
- const status = await run("The analysis did not start", () => api.analyze(detail.session.id));
8000
- if (status) review.patch((current) => ({
8001
- ...current,
8002
- session: {
8003
- ...current.session,
8004
- analysis: status
8005
- }
8006
- }));
8007
- };
8008
- return /* @__PURE__ */ jsx("button", {
8009
- className: "dt-btn",
8010
- onClick: analyze,
8011
- type: "button",
8012
- children: label
8013
- });
8014
- }
8015
- function StepRow({ detail, review, step }) {
8016
- return /* @__PURE__ */ jsxs("li", {
8017
- "data-state": step.state,
8018
- children: [/* @__PURE__ */ jsx("span", { children: step.label }), step.analyzeLabel && detail.session.canEdit && /* @__PURE__ */ jsx(AnalyzeButton, {
8019
- detail,
8020
- label: step.analyzeLabel,
8021
- review
8022
- })]
8023
- });
8150
+ function tabOptions(detail) {
8151
+ return [
8152
+ {
8153
+ count: detail.proposals.filter((proposal) => proposal.mergedIntoId === null && proposal.attachment === null).length,
8154
+ icon: /* @__PURE__ */ jsx(IntentionIcon, {}),
8155
+ label: "Intentions",
8156
+ value: "proposals"
8157
+ },
8158
+ {
8159
+ count: null,
8160
+ icon: /* @__PURE__ */ jsx(TranscriptIcon, {}),
8161
+ label: "Transcript",
8162
+ value: "transcript"
8163
+ },
8164
+ {
8165
+ count: detail.evidence.length,
8166
+ icon: /* @__PURE__ */ jsx(ImageIcon, {}),
8167
+ label: "Screenshots",
8168
+ value: "evidence"
8169
+ }
8170
+ ];
8024
8171
  }
8025
- function StatusStrip({ detail, review }) {
8026
- const { account } = useSessionWorkspace();
8027
- const sync = useSyncStatus(account.key);
8028
- const steps = processingSteps(detail, detail.session.isMine ? sync.pendingOperations : 0);
8029
- const isProcessed = steps.every((step) => step.state === "done");
8030
- return /* @__PURE__ */ jsx("div", {
8031
- "aria-live": "polite",
8032
- className: "dt-ses-strip",
8033
- children: !isProcessed && /* @__PURE__ */ jsx("ol", {
8034
- "aria-label": "Processing",
8035
- className: "dt-ses-steps",
8036
- children: steps.map((step) => /* @__PURE__ */ jsx(StepRow, {
8037
- detail,
8038
- review,
8039
- step
8040
- }, step.key))
8041
- })
8172
+ function ReviewTabs({ detail, place }) {
8173
+ const canPickView = place.tab === "proposals" && calloutReport(detail).screens.length > 0;
8174
+ return /* @__PURE__ */ jsxs("div", {
8175
+ className: "dt-toolbar",
8176
+ children: [/* @__PURE__ */ jsx("div", {
8177
+ className: "dt-tabs",
8178
+ role: "tablist",
8179
+ children: tabOptions(detail).map((option) => /* @__PURE__ */ jsxs("button", {
8180
+ "aria-selected": option.value === place.tab,
8181
+ className: "dt-tab",
8182
+ onClick: () => place.selectTab(option.value),
8183
+ role: "tab",
8184
+ type: "button",
8185
+ children: [
8186
+ option.icon,
8187
+ option.label,
8188
+ option.count !== null && /* @__PURE__ */ jsx("span", {
8189
+ className: "dt-tab-count",
8190
+ children: option.count
8191
+ })
8192
+ ]
8193
+ }, option.value))
8194
+ }), canPickView && /* @__PURE__ */ jsx(IntentionViewMenu, { place })]
8042
8195
  });
8043
8196
  }
8044
8197
  //#endregion
@@ -8190,17 +8343,16 @@ function LinkEvidenceItem({ link }) {
8190
8343
  ]
8191
8344
  });
8192
8345
  }
8193
- function MomentLink({ atMs, onShowMoment }) {
8194
- if (!onShowMoment) return null;
8195
- return /* @__PURE__ */ jsx("button", {
8196
- "aria-label": `Show the moment at ${formatClock(atMs)}`,
8197
- className: "dt-ses-link",
8198
- onClick: (event) => onShowMoment(event.currentTarget),
8199
- type: "button",
8200
- children: "Show moment"
8201
- });
8346
+ function EvidenceFace({ evidence }) {
8347
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ShotCaption, {
8348
+ atMs: evidence.atMs,
8349
+ children: evidenceCaption(evidence)
8350
+ }), /* @__PURE__ */ jsx(EvidenceThumb, {
8351
+ evidence,
8352
+ isLarge: true
8353
+ })] });
8202
8354
  }
8203
- function EvidenceItem({ evidence, openers, removal }) {
8355
+ function EvidenceItem({ evidence, onShowMoment, removal }) {
8204
8356
  const caption = `${formatClock(evidence.atMs)} · ${evidenceCaption(evidence)}`;
8205
8357
  if (evidence.link) return /* @__PURE__ */ jsx(LinkEvidenceItem, { link: evidence.link });
8206
8358
  if (!isViewable(evidence)) return /* @__PURE__ */ jsx(MissingEvidenceItem, {
@@ -8209,27 +8361,22 @@ function EvidenceItem({ evidence, openers, removal }) {
8209
8361
  isJustRemoved: evidence.id === removal.removedId
8210
8362
  });
8211
8363
  return /* @__PURE__ */ jsxs("li", {
8212
- className: "dt-card",
8364
+ className: "dt-card dt-openable",
8213
8365
  children: [
8214
- /* @__PURE__ */ jsx("button", {
8215
- "aria-label": `Enlarge: ${caption}`,
8216
- className: "dt-ses-shot-btn",
8217
- onClick: openers.onEnlarge,
8366
+ onShowMoment ? /* @__PURE__ */ jsx("button", {
8367
+ "aria-label": `Show the moment at ${caption}`,
8368
+ className: "dt-open dt-ses-shot-face",
8369
+ onClick: (event) => onShowMoment(event.currentTarget),
8218
8370
  type: "button",
8219
- children: /* @__PURE__ */ jsx(EvidenceThumb, { evidence })
8220
- }),
8221
- /* @__PURE__ */ jsx("p", {
8222
- className: "dt-meta",
8223
- children: caption
8371
+ children: /* @__PURE__ */ jsx(EvidenceFace, { evidence })
8372
+ }) : /* @__PURE__ */ jsx("div", {
8373
+ className: "dt-ses-shot-face",
8374
+ children: /* @__PURE__ */ jsx(EvidenceFace, { evidence })
8224
8375
  }),
8225
8376
  evidence.note && /* @__PURE__ */ jsx("p", {
8226
8377
  className: "dt-note-text",
8227
8378
  children: evidence.note
8228
8379
  }),
8229
- /* @__PURE__ */ jsx(MomentLink, {
8230
- atMs: evidence.atMs,
8231
- onShowMoment: openers.onShowMoment
8232
- }),
8233
8380
  removal.canRemove && /* @__PURE__ */ jsx(RemoveEvidence, {
8234
8381
  evidence,
8235
8382
  removal
@@ -8237,54 +8384,6 @@ function EvidenceItem({ evidence, openers, removal }) {
8237
8384
  ]
8238
8385
  });
8239
8386
  }
8240
- function EnlargedEvidence({ evidence, onBack, removal }) {
8241
- return /* @__PURE__ */ jsxs(Fragment, { children: [
8242
- /* @__PURE__ */ jsx("button", {
8243
- className: "dt-ses-link",
8244
- onClick: onBack,
8245
- type: "button",
8246
- children: "← All evidence"
8247
- }),
8248
- /* @__PURE__ */ jsx(EvidenceThumb, {
8249
- evidence,
8250
- isLarge: true
8251
- }),
8252
- /* @__PURE__ */ jsxs("p", {
8253
- className: "dt-meta",
8254
- children: [
8255
- formatClock(evidence.atMs),
8256
- " · ",
8257
- evidenceCaption(evidence)
8258
- ]
8259
- }),
8260
- removal.canRemove && /* @__PURE__ */ jsx(RemoveEvidence, {
8261
- evidence,
8262
- removal
8263
- })
8264
- ] });
8265
- }
8266
- function EvidenceList({ detail, openers, removal }) {
8267
- const { onEnlarge, onShowMoment } = openers;
8268
- const { evidence } = detail;
8269
- const momentOpener = (atMs) => {
8270
- const segmentId = nearestSegmentId(detail.segments, atMs);
8271
- return segmentId ? (opener) => onShowMoment(segmentId, opener) : null;
8272
- };
8273
- return /* @__PURE__ */ jsxs(Fragment, { children: [evidence.length === 0 && /* @__PURE__ */ jsx("p", {
8274
- className: "dt-empty",
8275
- children: "No screenshots or links in this session."
8276
- }), /* @__PURE__ */ jsx("ul", {
8277
- className: "dt-ses-rows",
8278
- children: evidence.map((item) => /* @__PURE__ */ jsx(EvidenceItem, {
8279
- evidence: item,
8280
- openers: {
8281
- onEnlarge: () => onEnlarge(item.id),
8282
- onShowMoment: momentOpener(item.atMs)
8283
- },
8284
- removal
8285
- }, item.id))
8286
- })] });
8287
- }
8288
8387
  function AttachLinkAction({ detail, review }) {
8289
8388
  const [isAttaching, setIsAttaching] = useState(false);
8290
8389
  if (isAttaching) return /* @__PURE__ */ jsx(AttachLink, {
@@ -8303,34 +8402,34 @@ function AttachLinkAction({ detail, review }) {
8303
8402
  });
8304
8403
  }
8305
8404
  function EvidenceTab({ detail, onShowMoment, review }) {
8306
- const [enlargedId, setEnlargedId] = useState(null);
8307
8405
  const [removedId, setRemovedId] = useState(null);
8308
- const enlarged = detail.evidence.find((item) => item.id === enlargedId && isViewable(item));
8406
+ const { canEdit } = detail.session;
8309
8407
  const removal = {
8310
- canRemove: detail.session.canEdit,
8311
- onRemoved: (evidenceId) => {
8312
- setEnlargedId(null);
8313
- setRemovedId(evidenceId);
8314
- },
8408
+ canRemove: canEdit,
8409
+ onRemoved: setRemovedId,
8315
8410
  removedId,
8316
8411
  review
8317
8412
  };
8318
- if (enlarged) return /* @__PURE__ */ jsx(EnlargedEvidence, {
8319
- evidence: enlarged,
8320
- onBack: () => setEnlargedId(null),
8321
- removal
8322
- });
8323
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(EvidenceList, {
8324
- detail,
8325
- openers: {
8326
- onEnlarge: setEnlargedId,
8327
- onShowMoment
8328
- },
8329
- removal
8330
- }), detail.session.canEdit && /* @__PURE__ */ jsx(AttachLinkAction, {
8413
+ const momentOpener = (atMs) => {
8414
+ const segmentId = nearestSegmentId(detail.segments, atMs);
8415
+ return segmentId ? (opener) => onShowMoment(segmentId, opener) : null;
8416
+ };
8417
+ const attachLink = canEdit && /* @__PURE__ */ jsx(AttachLinkAction, {
8331
8418
  detail,
8332
8419
  review
8333
- })] });
8420
+ });
8421
+ if (detail.evidence.length === 0) return /* @__PURE__ */ jsxs("div", {
8422
+ className: "dt-empty",
8423
+ children: [/* @__PURE__ */ jsx("p", { children: "No screenshots or links in this session." }), attachLink]
8424
+ });
8425
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("ul", {
8426
+ className: "dt-ses-rows",
8427
+ children: detail.evidence.map((item) => /* @__PURE__ */ jsx(EvidenceItem, {
8428
+ evidence: item,
8429
+ onShowMoment: momentOpener(item.atMs),
8430
+ removal
8431
+ }, item.id))
8432
+ }), attachLink] });
8334
8433
  }
8335
8434
  //#endregion
8336
8435
  //#region src/sessions/ui/review/transcript/segment-row.tsx
@@ -8444,38 +8543,57 @@ function ContextChips({ segment }) {
8444
8543
  }, `${item.kind}:${item.atMs}:${item.label}`))]
8445
8544
  });
8446
8545
  }
8447
- function SegmentLinks({ onEdit, onShowMoment, time }) {
8448
- return /* @__PURE__ */ jsxs("div", {
8449
- className: "dt-actions",
8450
- children: [/* @__PURE__ */ jsx("button", {
8451
- "aria-label": `Show context at ${time}`,
8452
- className: "dt-ses-link",
8453
- onClick: (event) => onShowMoment(event.currentTarget),
8454
- type: "button",
8455
- children: "Show context"
8456
- }), onEdit && /* @__PURE__ */ jsx("button", {
8457
- "aria-label": `Edit passage at ${time}`,
8458
- className: "dt-ses-link",
8459
- onClick: onEdit,
8460
- type: "button",
8461
- children: "Edit"
8462
- })]
8546
+ function MomentTime({ onShowMoment, time }) {
8547
+ if (!onShowMoment) return /* @__PURE__ */ jsx("span", {
8548
+ className: "dt-ses-time",
8549
+ children: time
8550
+ });
8551
+ return /* @__PURE__ */ jsx("button", {
8552
+ "aria-label": `Show the moment at ${time}`,
8553
+ className: "dt-ses-time dt-open",
8554
+ onClick: (event) => onShowMoment(event.currentTarget),
8555
+ type: "button",
8556
+ children: time
8463
8557
  });
8464
8558
  }
8559
+ const TEXT_DRAG_THRESHOLD_PX = 4;
8560
+ function useOpenMomentOnRowClick() {
8561
+ const pressedAt = useRef(null);
8562
+ return {
8563
+ onClick(event) {
8564
+ const start = pressedAt.current;
8565
+ const isTextDrag = start !== null && Math.hypot(event.clientX - start.x, event.clientY - start.y) > TEXT_DRAG_THRESHOLD_PX;
8566
+ const isOnControl = event.target instanceof Element && event.target.closest("a, button, input, select, textarea") !== null;
8567
+ if (isTextDrag || isOnControl) return;
8568
+ event.currentTarget.querySelector(":scope > .dt-open")?.click();
8569
+ },
8570
+ onMouseDown(event) {
8571
+ pressedAt.current = {
8572
+ x: event.clientX,
8573
+ y: event.clientY
8574
+ };
8575
+ }
8576
+ };
8577
+ }
8465
8578
  function SegmentRow({ editing, onShowMoment, segment }) {
8466
8579
  const { canEdit, review } = editing;
8467
8580
  const [isEditing, setIsEditing] = useState(false);
8468
8581
  const time = formatClock(segment.startMs);
8469
8582
  const isSettled = ["done", "empty"].includes(segment.transcription.state);
8583
+ const canStartEditing = canEdit && isSettled && !isEditing;
8584
+ const rowClick = useOpenMomentOnRowClick();
8470
8585
  return /* @__PURE__ */ jsxs("li", {
8471
- className: "dt-ses-seg",
8472
- children: [/* @__PURE__ */ jsx("span", {
8473
- className: "dt-ses-time",
8474
- children: time
8475
- }), /* @__PURE__ */ jsxs("div", {
8476
- className: "dt-ses-seg-body",
8477
- children: [
8478
- isEditing ? /* @__PURE__ */ jsx(SegmentEditor, {
8586
+ className: "dt-ses-seg dt-openable",
8587
+ onClick: rowClick.onClick,
8588
+ onMouseDown: rowClick.onMouseDown,
8589
+ children: [
8590
+ /* @__PURE__ */ jsx(MomentTime, {
8591
+ onShowMoment: isEditing ? null : onShowMoment,
8592
+ time
8593
+ }),
8594
+ /* @__PURE__ */ jsxs("div", {
8595
+ className: "dt-ses-seg-body",
8596
+ children: [isEditing ? /* @__PURE__ */ jsx(SegmentEditor, {
8479
8597
  onDone: () => setIsEditing(false),
8480
8598
  review,
8481
8599
  segment
@@ -8483,15 +8601,20 @@ function SegmentRow({ editing, onShowMoment, segment }) {
8483
8601
  onType: canEdit ? () => setIsEditing(true) : void 0,
8484
8602
  review,
8485
8603
  segment
8486
- }),
8487
- /* @__PURE__ */ jsx(ContextChips, { segment }),
8488
- /* @__PURE__ */ jsx(SegmentLinks, {
8489
- onEdit: canEdit && isSettled && !isEditing ? () => setIsEditing(true) : null,
8490
- onShowMoment,
8491
- time
8604
+ }), /* @__PURE__ */ jsx(ContextChips, { segment })]
8605
+ }),
8606
+ canStartEditing && /* @__PURE__ */ jsx("div", {
8607
+ className: "dt-hover-actions",
8608
+ children: /* @__PURE__ */ jsx("button", {
8609
+ "aria-label": `Edit passage at ${time}`,
8610
+ className: "icon-btn glass",
8611
+ onClick: () => setIsEditing(true),
8612
+ title: "Edit",
8613
+ type: "button",
8614
+ children: /* @__PURE__ */ jsx(PencilIcon, {})
8492
8615
  })
8493
- ]
8494
- })]
8616
+ })
8617
+ ]
8495
8618
  });
8496
8619
  }
8497
8620
  //#endregion
@@ -8573,25 +8696,21 @@ function transcriptForAgent(detail) {
8573
8696
  //#endregion
8574
8697
  //#region src/sessions/ui/review/transcript/transcript-tab.tsx
8575
8698
  function TimelineNote({ atMs, children, onShowMoment }) {
8576
- const time = formatClock(atMs);
8699
+ const rowClick = useOpenMomentOnRowClick();
8577
8700
  return /* @__PURE__ */ jsxs("li", {
8578
- className: "dt-ses-seg",
8701
+ className: "dt-ses-seg dt-openable",
8579
8702
  "data-kind": "note",
8580
- children: [/* @__PURE__ */ jsx("span", {
8581
- className: "dt-ses-time",
8582
- children: time
8583
- }), /* @__PURE__ */ jsxs("div", {
8703
+ onClick: rowClick.onClick,
8704
+ onMouseDown: rowClick.onMouseDown,
8705
+ children: [/* @__PURE__ */ jsx(MomentTime, {
8706
+ onShowMoment,
8707
+ time: formatClock(atMs)
8708
+ }), /* @__PURE__ */ jsx("div", {
8584
8709
  className: "dt-ses-seg-body",
8585
- children: [/* @__PURE__ */ jsx("p", {
8710
+ children: /* @__PURE__ */ jsx("p", {
8586
8711
  className: "dt-meta",
8587
8712
  children
8588
- }), onShowMoment && /* @__PURE__ */ jsx("button", {
8589
- "aria-label": `Show the moment at ${time}`,
8590
- className: "dt-ses-link",
8591
- onClick: (event) => onShowMoment(event.currentTarget),
8592
- type: "button",
8593
- children: "Show moment"
8594
- })]
8713
+ })
8595
8714
  })]
8596
8715
  });
8597
8716
  }
@@ -8786,21 +8905,14 @@ function ReviewPanels({ context, place }) {
8786
8905
  }
8787
8906
  function ReviewTop({ context, links }) {
8788
8907
  const { detail, review } = context;
8789
- return /* @__PURE__ */ jsxs(Fragment, { children: [
8790
- /* @__PURE__ */ jsx(ReviewHeader, {
8791
- onDeleted: links.onDeleted,
8792
- review,
8793
- session: detail.session
8794
- }),
8795
- /* @__PURE__ */ jsx(StatusStrip, {
8796
- detail,
8797
- review
8798
- }),
8799
- detail.continuation && /* @__PURE__ */ jsx(ContinuationHeader, {
8800
- continuation: detail.continuation,
8801
- onOpenSession: links.onOpenSession
8802
- })
8803
- ] });
8908
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ReviewHeader, {
8909
+ onDeleted: links.onDeleted,
8910
+ review,
8911
+ session: detail.session
8912
+ }), detail.continuation && /* @__PURE__ */ jsx(ContinuationHeader, {
8913
+ continuation: detail.continuation,
8914
+ onOpenSession: links.onOpenSession
8915
+ })] });
8804
8916
  }
8805
8917
  function ReviewScreen({ detail, isBriefOpen, links, review }) {
8806
8918
  const place = useReviewPlace();
@@ -9159,6 +9271,7 @@ const INTENTION_STYLES = `
9159
9271
  .dt-ses-attached { flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px; }
9160
9272
  .dt-ses-attached p { margin: 0; }
9161
9273
  .dt-ses-history[data-removed="true"] > .dt-ses-subhead { color: var(--rf-text-muted); text-decoration: line-through; }
9274
+ .dt-ses-history:has(.dt-open) { margin-inline: -8px; padding: 6px 8px; border-radius: var(--dt-radius-field); }
9162
9275
  .dt-ses-change { padding: 8px 10px; border-radius: var(--dt-radius-card); background: var(--rf-bg-subtle); list-style: none; }
9163
9276
  .dt-ses-change[data-decision="rejected"] .dt-ses-diff { color: var(--rf-text-muted); text-decoration: line-through; }
9164
9277
  .dt-ses-diff { display: grid; grid-template-columns: auto 1fr; gap: 2px 8px; margin: 0; overflow-wrap: anywhere; }
@@ -9169,7 +9282,8 @@ p.dt-ses-diff { display: block; }
9169
9282
  //#endregion
9170
9283
  //#region src/sessions/ui/review/screens/report-styles.ts
9171
9284
  const REPORT_STYLES = `
9172
- .dt-ses-report { display: flex; flex-direction: column; gap: 4px; width: 100%; min-width: 0; margin: 0; }
9285
+ .dt-ses-report { display: flex; flex-direction: column; gap: 6px; width: 100%; min-width: 0; margin: 0; }
9286
+ .dt-ses-report + .dt-ses-report { margin-top: 12px; }
9173
9287
  .dt-ses-report-canvas { position: relative; width: 100%; }
9174
9288
  .dt-ses-report-canvas img { display: block; width: 100%; max-width: 100%; height: auto; border-radius: 8px; box-shadow: var(--rf-shadow-inset); }
9175
9289
  .dt-ses-report-canvas[data-measured="false"] .dt-ses-callout { visibility: hidden; }
@@ -9183,21 +9297,25 @@ const REPORT_STYLES = `
9183
9297
  .dt-ses-callouts { margin: 0; padding: 0; list-style: none; }
9184
9298
  .dt-ses-callouts[data-flow="list"] { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
9185
9299
  .dt-ses-callout {
9186
- display: flex; align-items: center; gap: 4px; max-width: 100%; padding: 3px; border-radius: var(--rf-pill);
9187
- background: var(--rf-bg); box-shadow: var(--rf-shadow-inset), var(--rf-shadow-low); font-size: 12px; line-height: 1.3;
9300
+ --dt-callout-control: 22px; --dt-callout-inset: 3px;
9301
+ display: flex; align-items: flex-start; gap: 2px; max-width: 100%; padding: var(--dt-callout-inset);
9302
+ border-radius: calc(var(--dt-callout-control) / 2 + var(--dt-callout-inset));
9303
+ background: var(--rf-bg); box-shadow: var(--rf-shadow-inset), var(--rf-shadow-low); font-size: 12px; line-height: 16px;
9188
9304
  }
9189
9305
  .dt-ses-report-canvas .dt-ses-callout { position: absolute; top: 0; left: 0; width: max-content; max-width: min(280px, 100%); }
9190
9306
  .dt-ses-callout-number {
9191
- display: grid; place-items: center; flex: none; min-width: 20px; height: 20px; padding: 0 5px; border-radius: var(--rf-pill);
9192
- background: var(--rf-accent); color: var(--rf-accent-text, #fff); font-size: 11px; font-weight: 650; font-variant-numeric: tabular-nums;
9307
+ display: grid; place-items: center; flex: none; min-width: var(--dt-callout-control); height: var(--dt-callout-control); padding: 0 6px;
9308
+ border-radius: calc(var(--dt-callout-control) / 2); background: var(--rf-accent); color: var(--rf-accent-text, #fff);
9309
+ font-size: 11px; font-weight: 650; font-variant-numeric: tabular-nums;
9193
9310
  }
9194
9311
  .dt-ses-callout[data-decision="kept"] .dt-ses-callout-number { background: var(--rf-success); }
9195
9312
  .dt-ses-callout[data-decision="dismissed"] .dt-ses-callout-number { background: var(--rf-text-muted); }
9196
- .dt-ses-callout-label { min-width: 0; padding: 0 4px; font-weight: 550; text-align: left; overflow-wrap: anywhere; }
9313
+ .dt-ses-callout-label { flex: 1 1 auto; min-width: 0; padding: 3px 4px; font-weight: 550; text-align: left; overflow-wrap: anywhere; }
9197
9314
  .dt-ses-callout-label:hover { color: var(--rf-accent); }
9198
9315
  .dt-ses-callout[data-decision="dismissed"] .dt-ses-callout-label { color: var(--rf-text-muted); text-decoration: line-through; }
9199
9316
  .dt-ses-callout-btn {
9200
- display: grid; place-items: center; flex: none; width: 22px; height: 22px; border-radius: 50%; color: var(--rf-text-muted);
9317
+ display: grid; place-items: center; flex: none; width: var(--dt-callout-control); height: var(--dt-callout-control);
9318
+ border-radius: calc(var(--dt-callout-control) / 2); color: var(--rf-text-muted);
9201
9319
  transition: background-color 120ms var(--rf-ease), color 120ms var(--rf-ease);
9202
9320
  }
9203
9321
  .dt-ses-callout-btn svg { width: 13px; height: 13px; }
@@ -9239,21 +9357,33 @@ const SESSIONS_STYLES = `${`
9239
9357
  .dt-ses-list li + li { margin-top: 3px; }
9240
9358
  .dt-ses-link { align-self: flex-start; padding: 0; color: var(--rf-accent); font-size: 12px; text-align: left; }
9241
9359
  .dt-ses-link:hover { text-decoration: underline; }
9242
- .dt-ses-problem, .dt-ses-banner, .dt-ses-form, .dt-ses-head, .dt-ses-strip, .dt-ses-handoff { display: flex; flex-direction: column; gap: 6px; }
9360
+ .dt-ses-problem, .dt-ses-banner, .dt-ses-form, .dt-ses-head, .dt-ses-handoff { display: flex; flex-direction: column; gap: 6px; }
9243
9361
  .dt-ses-banner { padding: 8px 10px; border-radius: var(--dt-radius-card); background: color-mix(in srgb, var(--rf-accent) 10%, transparent); }
9244
9362
  .dt-ses-banner p { margin: 0; overflow-wrap: anywhere; }
9245
- .dt-ses-strip:empty { display: none; }
9246
- .dt-ses-steps { display: flex; flex-direction: column; gap: 6px; margin: 0; padding: 0; list-style: none; color: var(--rf-text-muted); font-size: 12px; }
9247
- .dt-ses-steps li { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; }
9248
- .dt-ses-steps li > span { flex: 1 1 0; min-width: 0; overflow-wrap: anywhere; }
9363
+ .dt-ses-progress:empty { display: none; }
9364
+ .dt-ses-progress[data-fills-panel="true"] { align-self: center; width: min(100%, 280px); margin: auto; padding: 24px 0; }
9365
+ .dt-sheet-body:has(.dt-ses-progress[data-fills-panel="true"]) > .dt-sheet-foot { margin-top: 0; }
9366
+ .dt-ses-steps { display: flex; flex-direction: column; gap: 8px; margin: 0; padding: 0; list-style: none; color: var(--rf-text-muted); font-size: 12.5px; }
9367
+ .dt-ses-steps li { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; }
9368
+ .dt-ses-steps li > span:first-child { flex: 1 1 0; min-width: 0; overflow-wrap: anywhere; }
9249
9369
  .dt-ses-steps li::before { content: ""; flex: none; box-sizing: border-box; width: 10px; height: 10px; border: 1.5px solid currentColor; border-radius: 50%; }
9250
9370
  .dt-ses-steps li[data-state="done"]::before { border-color: var(--rf-accent); background: var(--rf-accent); }
9251
9371
  .dt-ses-steps li[data-state="active"] { color: var(--rf-text); }
9252
9372
  .dt-ses-steps li[data-state="active"]::before { border-color: var(--rf-accent); border-right-color: transparent; animation: dt-step-spin 800ms linear infinite; }
9253
9373
  .dt-ses-steps li[data-state="failed"] { color: var(--rf-danger); }
9254
9374
  .dt-ses-steps li[data-state="failed"]::before { background: currentColor; }
9375
+ .dt-ses-bar { position: relative; flex: 1 1 100%; height: 4px; margin-left: 18px; overflow: hidden; border-radius: var(--rf-pill); background: var(--rf-bg-hover); }
9376
+ .dt-ses-bar > span { position: absolute; inset: 0 auto 0 0; border-radius: inherit; background: var(--rf-accent); transition: width 300ms var(--rf-ease); }
9377
+ .dt-ses-bar > span[data-indeterminate="true"] { width: 35%; animation: dt-bar-slide 1.3s ease-in-out infinite; }
9255
9378
  @keyframes dt-step-spin { to { rotate: 360deg; } }
9256
- @media (prefers-reduced-motion: reduce) { .dt-ses-steps li::before { animation: none; } }
9379
+ @keyframes dt-bar-slide { from { translate: -100% 0; } to { translate: 300% 0; } }
9380
+ @media (prefers-reduced-motion: reduce) {
9381
+ .dt-ses-steps li::before { animation: none; }
9382
+ .dt-ses-bar > span[data-indeterminate="true"] { width: 100%; opacity: .4; animation: none; }
9383
+ }
9384
+ .dt-ses-seg:has(> .dt-open) { cursor: pointer; }
9385
+ .dt-ses-seg:has(> .dt-open) > .dt-ses-seg-body { position: relative; z-index: 1; }
9386
+ @media (hover: none) { .dt-ses-seg:has(> .dt-hover-actions) { padding-right: 40px; } }
9257
9387
  .dt-ses-rows { display: flex; flex-direction: column; gap: 4px; margin: 0; padding: 0; list-style: none; }
9258
9388
  .dt-ses-row {
9259
9389
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px; width: 100%;
@@ -9263,8 +9393,17 @@ button.dt-ses-row:hover { background: var(--rf-bg-hover); }
9263
9393
  .dt-ses-row .dt-card-title { max-width: 100%; }
9264
9394
  ${`
9265
9395
  .dt-ses-title { flex: 1; min-width: 0; margin: 0; font-size: 15px; font-weight: 650; overflow-wrap: anywhere; }
9396
+ .dt-ses-head > .dt-card-head { gap: 10px; }
9397
+ .dt-ses-head .dt-ses-title { flex: 0 1 auto; overflow: hidden; font-size: 14px; text-overflow: ellipsis; white-space: nowrap; }
9266
9398
  .dt-ses-head .dt-menu > .icon-btn { width: 30px; height: 30px; }
9399
+ .dt-ses-facts { display: flex; flex: 1 0 auto; align-items: center; gap: 10px; margin: 0; color: var(--rf-text-muted); font-size: 12px; white-space: nowrap; }
9400
+ .dt-ses-facts > span { display: inline-flex; align-items: center; gap: 4px; }
9401
+ .dt-ses-facts svg { width: 13px; height: 13px; }
9402
+ .dt-ses-foot { flex-wrap: nowrap; }
9403
+ .dt-ses-foot > .dt-btn { flex: 1 1 0; justify-content: center; height: 32px; }
9267
9404
  .dt-btn[data-icon-only] { justify-content: center; width: 28px; padding: 0; }
9405
+ .dt-btn[aria-pressed="true"] { background: color-mix(in srgb, var(--rf-text) 16%, transparent); }
9406
+ .dt-btn[data-variant="primary"][aria-pressed="true"] { background: var(--rf-success); color: #fff; }
9268
9407
  .dt-ses-field { display: flex; flex-direction: column; gap: 3px; font-size: 11.5px; color: var(--rf-text-muted); }
9269
9408
  .dt-ses-need {
9270
9409
  margin: 0; font-weight: 600; overflow-wrap: anywhere;
@@ -9279,7 +9418,7 @@ ${`
9279
9418
  .dt-ses-details section { display: flex; flex-direction: column; gap: 2px; }
9280
9419
  .dt-ses-quote { padding: 2px 0; font-size: 12px; text-align: left; overflow-wrap: anywhere; }
9281
9420
  .dt-ses-quote:hover { color: var(--rf-accent); }
9282
- .dt-ses-time { flex: none; color: var(--rf-text-muted); font: 11.5px ${MONO_FONT$1}; font-variant-numeric: tabular-nums; }
9421
+ .dt-ses-time { flex: none; align-self: flex-start; color: var(--rf-text-muted); font: 11.5px ${MONO_FONT$1}; font-variant-numeric: tabular-nums; }
9283
9422
  .dt-ses-verify { display: flex; flex-direction: column; gap: 6px; margin: 0; padding: 8px 0 0; border: 0; border-top: 1px solid var(--rf-border); }
9284
9423
  .dt-ses-timeline { display: flex; flex-direction: column; gap: 2px; margin: 0; padding: 0; list-style: none; }
9285
9424
  .dt-ses-seg { display: flex; gap: 8px; padding: 6px 8px; border-radius: var(--dt-radius-field); }
@@ -9289,10 +9428,18 @@ ${`
9289
9428
  `}${`
9290
9429
  .dt-ses-thumbs { display: flex; flex-wrap: wrap; gap: 6px; }
9291
9430
  .dt-ses-shot { position: relative; display: inline-block; max-width: 100%; line-height: 0; }
9292
- .dt-ses-shot img { display: block; max-width: 100%; height: auto; max-height: 132px; border-radius: var(--dt-radius-field); box-shadow: var(--rf-shadow-inset); }
9431
+ .dt-ses-shot img { display: block; width: auto; max-width: 100%; height: auto; max-height: 132px; border-radius: var(--dt-radius-field); box-shadow: var(--rf-shadow-inset); }
9293
9432
  .dt-ses-shot[data-large="true"] img { max-height: none; }
9294
9433
  .dt-ses-shot-mark { position: absolute; border: 2px solid var(--rf-accent); border-radius: var(--dt-radius-tiny); pointer-events: none; }
9295
- .dt-ses-shot-btn { align-self: flex-start; padding: 0; border-radius: var(--dt-radius-field); }
9434
+ .dt-ses-shot-face { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; width: 100%; }
9435
+ .dt-ses-caption { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 8px; min-width: 0; color: var(--rf-text-muted); font-size: 11.5px; line-height: 1.4; overflow-wrap: anywhere; }
9436
+ .dt-ses-caption-time { display: inline-flex; align-items: center; gap: 4px; color: var(--rf-text); font-weight: 550; font-variant-numeric: tabular-nums; }
9437
+ .dt-ses-caption-time svg { width: 12px; height: 12px; color: var(--rf-text-muted); }
9438
+ .dt-ses-source-btn {
9439
+ display: flex; flex-direction: column; gap: 2px; margin: 0 -8px; padding: 6px 8px; border-radius: var(--dt-radius-field);
9440
+ text-align: left; transition: background-color 120ms var(--rf-ease);
9441
+ }
9442
+ .dt-ses-source-btn:hover { background: var(--rf-bg-hover); }
9296
9443
  .dt-ses-start { display: flex; flex-direction: column; gap: 8px; }
9297
9444
  .dt-ses-brief-entry > summary { display: flex; flex-direction: column; gap: 2px; padding: 8px 10px; border-radius: var(--dt-radius-card); background: var(--rf-bg-subtle); cursor: pointer; list-style: none; }
9298
9445
  .dt-ses-brief-entry > summary::-webkit-details-marker { display: none; }
@@ -9584,55 +9731,59 @@ function SelectionRow({ onOpenCode, onShowSelector, selection }) {
9584
9731
  request,
9585
9732
  title: selection.label
9586
9733
  }) : null;
9587
- return /* @__PURE__ */ jsxs(Fragment, { children: [
9588
- /* @__PURE__ */ jsxs("p", {
9589
- className: "dt-meta",
9590
- children: [/* @__PURE__ */ jsx("strong", { children: selection.label }), selection.region && ` · ${selection.region}`]
9591
- }),
9592
- selection.comment && /* @__PURE__ */ jsx("p", {
9593
- className: "dt-note-text",
9594
- children: selection.comment
9595
- }),
9596
- selection.sourceLocation && openSource && /* @__PURE__ */ jsx("button", {
9597
- className: "dt-source",
9598
- onClick: openSource,
9599
- type: "button",
9600
- children: selection.sourceLocation
9601
- }),
9602
- selection.sourceLocation && !onOpenCode && /* @__PURE__ */ jsx("p", {
9603
- className: "dt-meta",
9604
- children: selection.sourceLocation
9605
- }),
9606
- /* @__PURE__ */ jsxs("div", {
9607
- className: "dt-actions",
9608
- children: [/* @__PURE__ */ jsx("button", {
9609
- className: "dt-btn",
9610
- onClick: () => setIsOffPage(!onShowSelector(selection.selector)),
9611
- type: "button",
9612
- children: "Show on page"
9613
- }), onOpenCode && !(request || isSearching) && /* @__PURE__ */ jsx("button", {
9614
- className: "dt-btn",
9615
- onClick: () => setIsSearching(true),
9734
+ return /* @__PURE__ */ jsxs("div", {
9735
+ className: "dt-selection dt-openable",
9736
+ children: [
9737
+ /* @__PURE__ */ jsx("p", {
9738
+ className: "dt-meta",
9739
+ children: /* @__PURE__ */ jsxs("button", {
9740
+ className: "dt-open",
9741
+ onClick: () => setIsOffPage(!onShowSelector(selection.selector)),
9742
+ title: "Show on page",
9743
+ type: "button",
9744
+ children: [/* @__PURE__ */ jsx("strong", { children: selection.label }), selection.region && ` · ${selection.region}`]
9745
+ })
9746
+ }),
9747
+ selection.comment && /* @__PURE__ */ jsx("p", {
9748
+ className: "dt-note-text",
9749
+ children: selection.comment
9750
+ }),
9751
+ selection.sourceLocation && openSource && /* @__PURE__ */ jsx("button", {
9752
+ className: "dt-source",
9753
+ onClick: openSource,
9616
9754
  type: "button",
9617
- children: "Find in code"
9618
- })]
9619
- }),
9620
- isOffPage && /* @__PURE__ */ jsx("p", {
9621
- className: "dt-meta",
9622
- children: "This element is not on the page right now. Open the state the reporter was in, then try again."
9623
- }),
9624
- isSearching && onOpenCode && /* @__PURE__ */ jsx(CodeSearch, {
9625
- initialQuery: suggestedSearch(selection),
9626
- onOpenMatch: (match) => onOpenCode({
9627
- request: {
9628
- column: null,
9629
- fileName: match.absolutePath,
9630
- line: match.line
9631
- },
9632
- title: selection.label
9755
+ children: selection.sourceLocation
9756
+ }),
9757
+ selection.sourceLocation && !onOpenCode && /* @__PURE__ */ jsx("p", {
9758
+ className: "dt-meta",
9759
+ children: selection.sourceLocation
9760
+ }),
9761
+ onOpenCode && !(request || isSearching) && /* @__PURE__ */ jsx("div", {
9762
+ className: "dt-actions",
9763
+ children: /* @__PURE__ */ jsx("button", {
9764
+ className: "dt-btn",
9765
+ onClick: () => setIsSearching(true),
9766
+ type: "button",
9767
+ children: "Find in code"
9768
+ })
9769
+ }),
9770
+ isOffPage && /* @__PURE__ */ jsx("p", {
9771
+ className: "dt-meta",
9772
+ children: "This element is not on the page right now. Open the state the reporter was in, then try again."
9773
+ }),
9774
+ isSearching && onOpenCode && /* @__PURE__ */ jsx(CodeSearch, {
9775
+ initialQuery: suggestedSearch(selection),
9776
+ onOpenMatch: (match) => onOpenCode({
9777
+ request: {
9778
+ column: null,
9779
+ fileName: match.absolutePath,
9780
+ line: match.line
9781
+ },
9782
+ title: selection.label
9783
+ })
9633
9784
  })
9634
- })
9635
- ] });
9785
+ ]
9786
+ });
9636
9787
  }
9637
9788
  function FeedbackCard({ item, onOpenCode, onShowSelector }) {
9638
9789
  const selections = item.context?.selections ?? [];
@@ -12271,6 +12422,7 @@ const DEVTOOLS_STYLES = `
12271
12422
  position: sticky; bottom: -10px; display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
12272
12423
  margin: auto -10px -10px; padding: 10px; border-top: 1px solid var(--rf-border); background: var(--rf-bg);
12273
12424
  }
12425
+ .dt-sheet-body:has(.dt-empty) > .dt-sheet-foot { margin-top: 0; }
12274
12426
  .dt-archived { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 8px 10px; border: 1px dashed var(--rf-border); border-radius: var(--dt-radius-card); }
12275
12427
  .dt-archived .dt-meta { flex: 1 1 100%; }
12276
12428
  .dt-group { margin: 6px 2px 0; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; color: var(--rf-text-muted); }
@@ -12286,6 +12438,25 @@ const DEVTOOLS_STYLES = `
12286
12438
  .dt-source:hover { text-decoration: underline; }
12287
12439
  .dt-thumb { display: block; align-self: flex-start; max-width: 100%; height: auto; max-height: 132px; object-fit: contain; border-radius: var(--dt-radius-field); box-shadow: var(--rf-shadow-inset); }
12288
12440
  .dt-actions { display: flex; flex-wrap: wrap; gap: 4px; }
12441
+ .dt-openable { position: relative; transition: background-color 120ms var(--rf-ease); }
12442
+ .dt-openable:has(.dt-open):hover { background: var(--rf-bg-hover); }
12443
+ .dt-open { padding: 0; text-align: left; }
12444
+ a.dt-open { color: inherit; text-decoration: none; }
12445
+ .dt-open::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }
12446
+ .dt-open:focus-visible { outline: none; }
12447
+ .dt-open:focus-visible::after { outline: 2px solid var(--rf-accent); outline-offset: -2px; }
12448
+ .dt-card-title > .dt-open { display: block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
12449
+ .dt-selection { display: flex; flex-direction: column; gap: 4px; margin-inline: -6px; padding: 6px; border-radius: var(--dt-radius-field); }
12450
+ .dt-openable :is(a, button, input, select, textarea):not(.dt-open) { position: relative; z-index: 1; }
12451
+ .dt-hover-actions {
12452
+ position: absolute; top: 6px; right: 6px; z-index: 2; display: flex; gap: 4px; opacity: 0;
12453
+ transition: opacity 120ms var(--rf-ease);
12454
+ }
12455
+ .dt-hover-actions > .icon-btn { width: 28px; height: 28px; }
12456
+ .dt-hover-actions svg { width: 14px; height: 14px; }
12457
+ .dt-openable:hover > .dt-hover-actions, .dt-hover-actions:focus-within { opacity: 1; }
12458
+ .dt-card:has(> .dt-hover-actions):not(:has(> .dt-thumb)) > .dt-card-head { padding-right: 66px; }
12459
+ @media (hover: none) { .dt-hover-actions { opacity: 1; } }
12289
12460
  .dt-btn {
12290
12461
  display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 10px;
12291
12462
  border-radius: var(--rf-pill); background: var(--rf-bg-hover); color: var(--rf-text);
@@ -12843,9 +13014,26 @@ function NoteEditor({ initialText, noteId, onDone }) {
12843
13014
  })]
12844
13015
  })] });
12845
13016
  }
12846
- function NoteCardHead({ isSending, note, pinNumber }) {
13017
+ function NoteTitle({ note, onShowOnPage, pageLink }) {
12847
13018
  const { selection } = note;
12848
13019
  const owner = selection.componentStack[0];
13020
+ const title = owner ? `<${owner}> ${selection.label}` : selection.label;
13021
+ if (onShowOnPage) return /* @__PURE__ */ jsx("button", {
13022
+ className: "dt-open",
13023
+ onClick: onShowOnPage,
13024
+ title: "Show on page",
13025
+ type: "button",
13026
+ children: title
13027
+ });
13028
+ if (pageLink) return /* @__PURE__ */ jsx("a", {
13029
+ className: "dt-open",
13030
+ href: note.capturedContext.url,
13031
+ title: "Open page",
13032
+ children: title
13033
+ });
13034
+ return title;
13035
+ }
13036
+ function NoteCardHead({ isSending, note, onShowOnPage, pageLink, pinNumber }) {
12849
13037
  return /* @__PURE__ */ jsxs("div", {
12850
13038
  className: "dt-card-head",
12851
13039
  children: [
@@ -12856,7 +13044,11 @@ function NoteCardHead({ isSending, note, pinNumber }) {
12856
13044
  }),
12857
13045
  /* @__PURE__ */ jsx("h3", {
12858
13046
  className: "dt-card-title",
12859
- children: owner ? `<${owner}> ${selection.label}` : selection.label
13047
+ children: /* @__PURE__ */ jsx(NoteTitle, {
13048
+ note,
13049
+ onShowOnPage,
13050
+ pageLink
13051
+ })
12860
13052
  }),
12861
13053
  isSending && /* @__PURE__ */ jsx("span", {
12862
13054
  className: "dt-chip",
@@ -12893,36 +13085,25 @@ function NoteSourceLine({ isLocating, note, onOpenCode }) {
12893
13085
  children: selection.region ?? selection.selector
12894
13086
  });
12895
13087
  }
12896
- function NoteActions({ isSending, note, onEdit, onShowSelector, pageLink }) {
13088
+ function NoteHoverActions({ isSending, note, onEdit }) {
12897
13089
  return /* @__PURE__ */ jsxs("div", {
12898
- className: "dt-actions",
12899
- children: [
12900
- onShowSelector && /* @__PURE__ */ jsx("button", {
12901
- className: "dt-btn",
12902
- onClick: onShowSelector,
12903
- type: "button",
12904
- children: "Show on page"
12905
- }),
12906
- pageLink && /* @__PURE__ */ jsx("a", {
12907
- className: "dt-btn",
12908
- href: note.capturedContext.url,
12909
- children: "Open page"
12910
- }),
12911
- !(note.sent || isSending) && /* @__PURE__ */ jsx("button", {
12912
- className: "dt-btn",
12913
- onClick: onEdit,
12914
- type: "button",
12915
- children: "Edit"
12916
- }),
12917
- /* @__PURE__ */ jsx("button", {
12918
- className: "dt-btn",
12919
- "data-variant": "danger",
12920
- disabled: isSending,
12921
- onClick: () => deleteNote(note),
12922
- type: "button",
12923
- children: "Delete"
12924
- })
12925
- ]
13090
+ className: "dt-hover-actions",
13091
+ children: [!(note.sent || isSending) && /* @__PURE__ */ jsx("button", {
13092
+ "aria-label": "Edit note",
13093
+ className: "icon-btn glass",
13094
+ onClick: onEdit,
13095
+ title: "Edit note",
13096
+ type: "button",
13097
+ children: /* @__PURE__ */ jsx(PencilIcon, {})
13098
+ }), /* @__PURE__ */ jsx("button", {
13099
+ "aria-label": "Delete note",
13100
+ className: "icon-btn glass",
13101
+ disabled: isSending,
13102
+ onClick: () => deleteNote(note),
13103
+ title: "Delete note",
13104
+ type: "button",
13105
+ children: /* @__PURE__ */ jsx(TrashIcon, {})
13106
+ })]
12926
13107
  });
12927
13108
  }
12928
13109
  function NoteCard({ isLocating, isSending = false, note, onOpenCode, onShowSelector, pageLink, pinNumber }) {
@@ -12931,7 +13112,7 @@ function NoteCard({ isLocating, isSending = false, note, onOpenCode, onShowSelec
12931
13112
  const closeUp = useCloseUpPreview(note.id, isLocating);
12932
13113
  const showOnPage = onShowSelector ? () => setIsOffPage(!onShowSelector(note.selection.selector)) : void 0;
12933
13114
  return /* @__PURE__ */ jsxs("article", {
12934
- className: "dt-card",
13115
+ className: "dt-card dt-openable",
12935
13116
  "data-sent": Boolean(note.sent),
12936
13117
  children: [
12937
13118
  closeUp && /* @__PURE__ */ jsx("img", {
@@ -12944,6 +13125,8 @@ function NoteCard({ isLocating, isSending = false, note, onOpenCode, onShowSelec
12944
13125
  /* @__PURE__ */ jsx(NoteCardHead, {
12945
13126
  isSending,
12946
13127
  note,
13128
+ onShowOnPage: showOnPage,
13129
+ pageLink,
12947
13130
  pinNumber
12948
13131
  }),
12949
13132
  /* @__PURE__ */ jsx(NoteSourceLine, {
@@ -12963,12 +13146,10 @@ function NoteCard({ isLocating, isSending = false, note, onOpenCode, onShowSelec
12963
13146
  className: "dt-meta",
12964
13147
  children: "The element is not on the page right now."
12965
13148
  }),
12966
- !isEditing && /* @__PURE__ */ jsx(NoteActions, {
13149
+ !isEditing && /* @__PURE__ */ jsx(NoteHoverActions, {
12967
13150
  isSending,
12968
13151
  note,
12969
- onEdit: () => setIsEditing(true),
12970
- onShowSelector: showOnPage,
12971
- pageLink
13152
+ onEdit: () => setIsEditing(true)
12972
13153
  })
12973
13154
  ]
12974
13155
  });
@@ -13414,4 +13595,4 @@ function useSheetTab() {
13414
13595
  //#endregion
13415
13596
  export { sessionSheetPlace as A, teamTransport as B, AccountFooter as C, MissingRouteHint as D, MISSING_SECRET_KEY_HINT as E, useWorkspaceLoader as F, SessionApiError as G, disconnectBoard as H, purgeAccount as I, reportFailure as J, sessionApiErrorSchema as K, bridgeBoardCall as L, RecordingLayer as M, SessionWorkspaceProvider as N, SESSIONS_STYLES as O, failureMessage as P, bridgeTransport as R, InboxPanel as S, CONNECT_REQUIREMENT as T, probeDevRoute as U, callReflet as V, startBoardConnect as W, DevtoolsBarButtons as _, NotePicker as a, CodeTab as b, isActiveNote as c, useNotes as d, findOnPage as f, DevtoolsBar as g, DevtoolsSheet as h, NotesPanel as i, useSessionNavigation as j, SessionsPanel as k, isNoteOnPage as l, DEVTOOLS_STYLES as m, useSheetTab as n, enrichNote as o, usePageHighlight as p, Notices as q, NotePins as r, hasTriedToLocate as s, FIRST_SHEET_TAB as t, unsentNoteCount as u, SHEET_TABS as v, AccountNote as w, useCodeLookup as x, SessionsBarButton as y, devtoolsTransport as z };
13416
13597
 
13417
- //# sourceMappingURL=use-sheet-tab-CacYFVjI.js.map
13598
+ //# sourceMappingURL=use-sheet-tab-B-q4G0Hn.js.map