reflet-sdk 0.7.3 → 0.7.5

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.
Files changed (34) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +54 -17
  3. package/dist/desktop.d.ts +24 -0
  4. package/dist/desktop.d.ts.map +1 -0
  5. package/dist/desktop.js +118 -0
  6. package/dist/desktop.js.map +1 -0
  7. package/dist/{devtools-layer-CsIcpepI.js → devtools-layer-o1O55wu1.js} +396 -277
  8. package/dist/devtools-layer-o1O55wu1.js.map +1 -0
  9. package/dist/devtools-next.d.ts.map +1 -1
  10. package/dist/devtools-next.js +1 -1
  11. package/dist/{sessions-panel-BYM0UUgT.js → devtools-styles-TWR5Ps34.js} +2078 -1686
  12. package/dist/devtools-styles-TWR5Ps34.js.map +1 -0
  13. package/dist/devtools-vite.js +2 -1
  14. package/dist/devtools-vite.js.map +1 -1
  15. package/dist/feedback.d.ts +7 -3
  16. package/dist/feedback.d.ts.map +1 -1
  17. package/dist/feedback.js +21 -12
  18. package/dist/feedback.js.map +1 -1
  19. package/dist/{handler-17FmwoSX.js → handler-ndF3gZBx.js} +11 -416
  20. package/dist/handler-ndF3gZBx.js.map +1 -0
  21. package/dist/reflet-proxy-BW6q9xds.js +467 -0
  22. package/dist/reflet-proxy-BW6q9xds.js.map +1 -0
  23. package/dist/{submit-Dl0rm6TV.js → submit-DJIKxRUp.js} +2 -2
  24. package/dist/submit-DJIKxRUp.js.map +1 -0
  25. package/dist/{team-layer-Dvjkh-_y.js → team-layer-_It1_oSo.js} +159 -95
  26. package/dist/team-layer-_It1_oSo.js.map +1 -0
  27. package/dist/team-tools-bridge-C51PpkUI.d.ts +31 -0
  28. package/dist/team-tools-bridge-C51PpkUI.d.ts.map +1 -0
  29. package/package.json +5 -1
  30. package/dist/devtools-layer-CsIcpepI.js.map +0 -1
  31. package/dist/handler-17FmwoSX.js.map +0 -1
  32. package/dist/sessions-panel-BYM0UUgT.js.map +0 -1
  33. package/dist/submit-Dl0rm6TV.js.map +0 -1
  34. package/dist/team-layer-Dvjkh-_y.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { a as EDITORS, n as DEVTOOLS_REQUEST_HEADER, r as DEVTOOLS_ROUTE_BASE, t as DEVTOOLS_ENDPOINTS } from "./protocol-cJVVwnPe.js";
2
- import { B as clip, D as MoreIcon, G as describeElement, K as getElementRect, M as TargetIcon, O as PencilIcon, R as DEFAULT_WIDGET_LABELS, S as CheckIcon, T as GripIcon, W as buildSelector, Y as redactUrl, Z as inspectReactElement, _ as matchesHotkey, a as uploadBlob, c as useFloatingPosition, g as isSubmitEnter, it as releaseCapture, j as Svg, l as onViewportChange, m as startsInWidget, o as ElementPicker, p as listenToKeydown, q as REDACT_ATTRIBUTE, s as isWidgetOwned, tt as captureViewport, u as visibleViewport, w as CloseIcon } from "./submit-Dl0rm6TV.js";
2
+ import { B as clip, D as MoreIcon, G as describeElement, K as getElementRect, M as TargetIcon, O as PencilIcon, R as DEFAULT_WIDGET_LABELS, S as CheckIcon, T as GripIcon, W as buildSelector, Y as redactUrl, Z as inspectReactElement, _ as matchesHotkey, a as uploadBlob, c as useFloatingPosition, g as isSubmitEnter, it as releaseCapture, j as Svg, l as onViewportChange, m as startsInWidget, o as ElementPicker, p as listenToKeydown, q as REDACT_ATTRIBUTE, s as isWidgetOwned, tt as captureViewport, u as visibleViewport, w as CloseIcon } from "./submit-DJIKxRUp.js";
3
3
  import { t as REFLET_Z_INDEX } from "./z-index-CwokqVq_.js";
4
4
  import { z } from "zod";
5
5
  import { createContext, useContext, useEffect, useId, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
@@ -274,6 +274,8 @@ const sessionIdentitySchema = z.object({
274
274
  user: z.object({
275
275
  email: z.string().nullable(),
276
276
  id: z.string(),
277
+ /** Absent from older Reflet backends. */
278
+ image: z.string().nullable().optional(),
277
279
  name: z.string().nullable()
278
280
  })
279
281
  });
@@ -1068,6 +1070,27 @@ function teamTransport(options) {
1068
1070
  return schema.parse(await response.json().catch(() => null));
1069
1071
  } };
1070
1072
  }
1073
+ function parseReplyBody(body) {
1074
+ try {
1075
+ return JSON.parse(body);
1076
+ } catch {
1077
+ return null;
1078
+ }
1079
+ }
1080
+ /** Calls go through the desktop app's privileged process, which holds the credential. Absent keys stay absent: IPC keeps an `undefined` value. */
1081
+ function bridgeTransport(bridge) {
1082
+ return { async request({ body, method, path, query, schema }) {
1083
+ const reply = await bridge.request({
1084
+ method,
1085
+ path,
1086
+ ...method === "POST" && { body: JSON.stringify(body ?? {}) },
1087
+ ...query && { query }
1088
+ });
1089
+ const payload = parseReplyBody(reply.body);
1090
+ if (!(reply.status >= 200 && reply.status < 300)) throw toSessionApiError(payload, reply.status);
1091
+ return schema.parse(payload);
1092
+ } };
1093
+ }
1071
1094
  function uploadSessionBlob(uploadUrl, blob) {
1072
1095
  return uploadBlob(uploadUrl, {
1073
1096
  blob,
@@ -3285,11 +3308,25 @@ function preciseSeconds(ms) {
3285
3308
  function minutesLeft(remainingMs) {
3286
3309
  return Math.max(1, Math.ceil(remainingMs / MINUTE_MS));
3287
3310
  }
3288
- function formatDate(epochMs) {
3289
- return new Date(epochMs).toLocaleString(void 0, {
3290
- dateStyle: "medium",
3291
- timeStyle: "short"
3292
- });
3311
+ function startOfDay(epochMs, daysBack) {
3312
+ const day = new Date(epochMs);
3313
+ day.setHours(0, 0, 0, 0);
3314
+ day.setDate(day.getDate() - daysBack);
3315
+ return day.getTime();
3316
+ }
3317
+ function dayLabel(epochMs, now) {
3318
+ if (epochMs >= startOfDay(now, 0)) return "Today";
3319
+ if (epochMs >= startOfDay(now, 1)) return "Yesterday";
3320
+ const date = new Date(epochMs);
3321
+ const isThisYear = date.getFullYear() === new Date(now).getFullYear();
3322
+ return date.toLocaleDateString(void 0, isThisYear ? {
3323
+ day: "numeric",
3324
+ month: "short"
3325
+ } : { dateStyle: "medium" });
3326
+ }
3327
+ function formatDate(epochMs, now = Date.now()) {
3328
+ const time = new Date(epochMs).toLocaleTimeString(void 0, { timeStyle: "short" });
3329
+ return `${dayLabel(epochMs, now)}, ${time}`;
3293
3330
  }
3294
3331
  function isAnalysisBusy(summary) {
3295
3332
  return summary.analysis.state === "waiting" || summary.analysis.state === "running";
@@ -3333,13 +3370,6 @@ function PauseIcon() {
3333
3370
  function PlayIcon() {
3334
3371
  return /* @__PURE__ */ jsx(Svg, { children: /* @__PURE__ */ jsx("path", { d: "m8 5 11 7-11 7z" }) });
3335
3372
  }
3336
- function PersonIcon() {
3337
- return /* @__PURE__ */ jsxs(Svg, { children: [/* @__PURE__ */ jsx("circle", {
3338
- cx: "12",
3339
- cy: "8",
3340
- r: "4"
3341
- }), /* @__PURE__ */ jsx("path", { d: "M4.5 20a7.5 7.5 0 0 1 15 0" })] });
3342
- }
3343
3373
  function ClockIcon() {
3344
3374
  return /* @__PURE__ */ jsxs(Svg, { children: [/* @__PURE__ */ jsx("circle", {
3345
3375
  cx: "12",
@@ -3347,6 +3377,29 @@ function ClockIcon() {
3347
3377
  r: "8.5"
3348
3378
  }), /* @__PURE__ */ jsx("path", { d: "M12 7.5V12l3 2" })] });
3349
3379
  }
3380
+ function IntentionIcon() {
3381
+ 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" }) });
3382
+ }
3383
+ function TranscriptIcon() {
3384
+ return /* @__PURE__ */ jsx(Svg, { children: /* @__PURE__ */ jsx("path", { d: "M4 6h16M4 11h16M4 16h10" }) });
3385
+ }
3386
+ function ImageIcon() {
3387
+ return /* @__PURE__ */ jsxs(Svg, { children: [
3388
+ /* @__PURE__ */ jsx("rect", {
3389
+ height: "16",
3390
+ rx: "2.5",
3391
+ width: "18",
3392
+ x: "3",
3393
+ y: "4"
3394
+ }),
3395
+ /* @__PURE__ */ jsx("circle", {
3396
+ cx: "9",
3397
+ cy: "10",
3398
+ r: "1.6"
3399
+ }),
3400
+ /* @__PURE__ */ jsx("path", { d: "m21 16-5-5-9 9" })
3401
+ ] });
3402
+ }
3350
3403
  //#endregion
3351
3404
  //#region src/sessions/ui/recording/level-meter.tsx
3352
3405
  const REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
@@ -3604,179 +3657,6 @@ function RecordingLayer({ onFinished }) {
3604
3657
  })] });
3605
3658
  }
3606
3659
  //#endregion
3607
- //#region src/sessions/ui/intention-styles.ts
3608
- const INTENTION_STYLES = `
3609
- .dt-ses-panel { display: contents; }
3610
- .dt-ses-panel[hidden] { display: none; }
3611
- .dt-ses-moment, .dt-ses-moment-section, .dt-ses-block, .dt-ses-change, .dt-ses-history { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
3612
- .dt-ses-moment-item { display: flex; flex-direction: column; gap: 6px; padding: 8px 10px; border-radius: 10px; background: var(--rf-bg-subtle); }
3613
- .dt-ses-moment-line { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 0; overflow-wrap: anywhere; }
3614
- .dt-ses-moment-quote { display: flex; flex-direction: column; gap: 2px; margin: 0; padding-left: 10px; border-left: 2px solid var(--rf-border); overflow-wrap: anywhere; }
3615
- .dt-ses-moment-notices { color: var(--rf-text-muted); font-size: 12px; }
3616
- .dt-ses-moment-shot { display: flex; flex-direction: column; gap: 4px; margin: 0; max-width: 100%; }
3617
- .dt-ses-shot-mark[data-match="approximate"] { border-style: dashed; }
3618
- .dt-ses-shot-pointer {
3619
- position: absolute; width: 10px; height: 10px; margin: -5px 0 0 -5px; border-radius: 50%;
3620
- background: var(--rf-accent); box-shadow: 0 0 0 2px var(--rf-bg); pointer-events: none;
3621
- }
3622
- .dt-ses-source { margin: 0; font-size: 12px; overflow-wrap: anywhere; }
3623
- .dt-ses-context-line { display: block; }
3624
- .dt-ses-attached { flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px; }
3625
- .dt-ses-attached p { margin: 0; }
3626
- .dt-ses-history[data-removed="true"] > .dt-ses-subhead { color: var(--rf-text-muted); text-decoration: line-through; }
3627
- .dt-ses-change { padding: 8px 10px; border-radius: 10px; background: var(--rf-bg-subtle); list-style: none; }
3628
- .dt-ses-change[data-decision="rejected"] .dt-ses-diff { color: var(--rf-text-muted); text-decoration: line-through; }
3629
- .dt-ses-diff { display: grid; grid-template-columns: auto 1fr; gap: 2px 8px; margin: 0; overflow-wrap: anywhere; }
3630
- p.dt-ses-diff { display: block; }
3631
- .dt-ses-diff dd { margin: 0; }
3632
- .dt-card:focus-visible { outline: 2px solid var(--rf-accent); outline-offset: 2px; }
3633
- `;
3634
- //#endregion
3635
- //#region src/sessions/ui/review/screens/report-styles.ts
3636
- const REPORT_STYLES = `
3637
- .dt-ses-report { display: flex; flex-direction: column; gap: 4px; width: 100%; min-width: 0; margin: 0; }
3638
- .dt-ses-report-canvas { position: relative; width: 100%; }
3639
- .dt-ses-report-canvas img { display: block; width: 100%; max-width: 100%; height: auto; border-radius: 8px; box-shadow: var(--rf-shadow-inset); }
3640
- .dt-ses-report-canvas[data-measured="false"] .dt-ses-callout { visibility: hidden; }
3641
- .dt-ses-report-mark { --dt-mark: var(--rf-accent); position: absolute; border: 2px solid var(--dt-mark); border-radius: 4px; pointer-events: none; }
3642
- .dt-ses-report-lines { position: absolute; inset: 0; overflow: visible; pointer-events: none; }
3643
- .dt-ses-report-lines g { color: var(--rf-accent); }
3644
- .dt-ses-report-lines line { stroke: currentColor; stroke-width: 1.5; }
3645
- .dt-ses-report-lines circle { fill: currentColor; }
3646
- .dt-ses-report-mark[data-decision="kept"], .dt-ses-report-lines g[data-decision="kept"] { --dt-mark: var(--rf-success); color: var(--rf-success); }
3647
- .dt-ses-report-mark[data-decision="dismissed"], .dt-ses-report-lines g[data-decision="dismissed"] { opacity: .35; }
3648
- .dt-ses-callouts { margin: 0; padding: 0; list-style: none; }
3649
- .dt-ses-callouts[data-flow="list"] { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
3650
- .dt-ses-callout {
3651
- display: flex; align-items: center; gap: 4px; max-width: 100%; padding: 3px; border-radius: var(--rf-pill);
3652
- background: var(--rf-bg); box-shadow: var(--rf-shadow-inset), var(--rf-shadow-low); font-size: 12px; line-height: 1.3;
3653
- }
3654
- .dt-ses-report-canvas .dt-ses-callout { position: absolute; top: 0; left: 0; width: max-content; max-width: min(280px, 100%); }
3655
- .dt-ses-callout-number {
3656
- display: grid; place-items: center; flex: none; min-width: 20px; height: 20px; padding: 0 5px; border-radius: var(--rf-pill);
3657
- background: var(--rf-accent); color: var(--rf-accent-text, #fff); font-size: 11px; font-weight: 650; font-variant-numeric: tabular-nums;
3658
- }
3659
- .dt-ses-callout[data-decision="kept"] .dt-ses-callout-number { background: var(--rf-success); }
3660
- .dt-ses-callout[data-decision="dismissed"] .dt-ses-callout-number { background: var(--rf-text-muted); }
3661
- .dt-ses-callout-label { min-width: 0; padding: 0 4px; font-weight: 550; text-align: left; overflow-wrap: anywhere; }
3662
- .dt-ses-callout-label:hover { color: var(--rf-accent); }
3663
- .dt-ses-callout[data-decision="dismissed"] .dt-ses-callout-label { color: var(--rf-text-muted); text-decoration: line-through; }
3664
- .dt-ses-callout-btn {
3665
- display: grid; place-items: center; flex: none; width: 22px; height: 22px; border-radius: 50%; color: var(--rf-text-muted);
3666
- transition: background-color 120ms var(--rf-ease), color 120ms var(--rf-ease);
3667
- }
3668
- .dt-ses-callout-btn svg { width: 13px; height: 13px; }
3669
- .dt-ses-callout-btn:hover { background: var(--rf-bg-hover); color: var(--rf-text); }
3670
- .dt-ses-callout-btn[data-decision="kept"][aria-pressed="true"] { background: var(--rf-success); color: #fff; }
3671
- .dt-ses-callout-btn[data-decision="dismissed"][aria-pressed="true"] { background: var(--rf-bg-hover); color: var(--rf-text); }
3672
- `;
3673
- //#endregion
3674
- //#region src/sessions/ui/sessions-styles.ts
3675
- const MONO_FONT$1 = "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace";
3676
- const SESSIONS_STYLES = `${`
3677
- .dt-rec {
3678
- position: fixed; z-index: ${REFLET_Z_INDEX.devtoolsBar}; left: 50%; translate: -50% 0;
3679
- bottom: calc(var(--rf-viewport-bottom, 0px) + env(safe-area-inset-bottom) + 16px);
3680
- display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 6px;
3681
- max-width: calc(100vw - 24px); padding: 6px 6px 6px 12px; border-radius: 18px;
3682
- animation: dt-rec-in 240ms var(--rf-ease);
3683
- }
3684
- @keyframes dt-rec-in { from { transform: translateY(8px); opacity: 0; } }
3685
- .dt-rec-dot { flex: none; width: 9px; height: 9px; border-radius: 50%; background: var(--rf-danger); }
3686
- .dt-rec[data-phase="recording"] .dt-rec-dot { animation: dt-rec-pulse 1.6s ease-in-out infinite; }
3687
- .dt-rec[data-phase="paused"] .dt-rec-dot, .dt-rec[data-phase="finishing"] .dt-rec-dot { background: var(--rf-text-muted); }
3688
- @keyframes dt-rec-pulse { 50% { opacity: .35; } }
3689
- .dt-rec-meter { display: inline-flex; align-items: center; gap: 2px; height: 16px; }
3690
- .dt-rec-meter span {
3691
- width: 3px; height: 100%; border-radius: 2px; background: var(--rf-text-muted);
3692
- scale: 1 calc(.2 + var(--dt-level, 0) * .8); transition: scale 90ms linear;
3693
- }
3694
- .dt-rec-meter span:nth-child(2) { scale: 1 calc(.2 + var(--dt-level, 0)); }
3695
- .dt-rec-time { font-variant-numeric: tabular-nums; font-weight: 600; min-width: 5ch; }
3696
- .dt-rec-status { color: var(--rf-text-muted); font-size: 12px; overflow-wrap: anywhere; }
3697
- .dt-rec-status:empty { display: none; }
3698
- .dt-rec-note { display: flex; align-items: center; gap: 6px; flex: 1 1 100%; font-size: 12px; color: var(--rf-text-muted); }
3699
- .dt-rec-note .dt-field { flex: 1; min-width: 0; padding: 4px 8px; }
3700
- `}
3701
- .dt-ses-subhead, .dt-ses-label { margin: 0 0 4px; font-size: 12px; font-weight: 600; color: var(--rf-text); }
3702
- .dt-ses-label { font-size: 11px; color: var(--rf-text-muted); }
3703
- .dt-ses-list { margin: 0; padding-left: 18px; overflow-wrap: anywhere; }
3704
- .dt-ses-list li + li { margin-top: 3px; }
3705
- .dt-ses-link { align-self: flex-start; padding: 0; color: var(--rf-accent); font-size: 12px; text-align: left; }
3706
- .dt-ses-link:hover { text-decoration: underline; }
3707
- .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; }
3708
- .dt-ses-banner { padding: 8px 10px; border-radius: 10px; background: color-mix(in srgb, var(--rf-accent) 10%, transparent); }
3709
- .dt-ses-banner p { margin: 0; overflow-wrap: anywhere; }
3710
- .dt-ses-strip:empty { display: none; }
3711
- .dt-ses-rows { display: flex; flex-direction: column; gap: 4px; margin: 0; padding: 0; list-style: none; }
3712
- .dt-ses-row {
3713
- display: flex; flex-direction: column; align-items: flex-start; gap: 2px; width: 100%;
3714
- padding: 8px 10px; border-radius: 10px; background: var(--rf-bg-subtle); text-align: left;
3715
- }
3716
- button.dt-ses-row:hover { background: var(--rf-bg-hover); }
3717
- .dt-ses-row .dt-card-title { max-width: 100%; }
3718
- ${`
3719
- .dt-ses-title { flex: 1; min-width: 0; margin: 0; font-size: 15px; font-weight: 650; overflow-wrap: anywhere; }
3720
- .dt-ses-menu { position: relative; flex: none; }
3721
- .dt-ses-menu > .icon-btn { width: 30px; height: 30px; }
3722
- .dt-ses-menu-list {
3723
- position: absolute; top: calc(100% + 4px); right: 0; z-index: 2; display: flex; flex-direction: column; min-width: 168px; padding: 4px;
3724
- border: 1px solid var(--rf-border); border-radius: var(--rf-radius-sm); background: var(--rf-bg); box-shadow: var(--rf-shadow);
3725
- }
3726
- .dt-ses-menu-item { display: flex; align-items: center; height: 30px; padding: 0 10px; border-radius: 6px; color: var(--rf-text); font-size: 12.5px; text-align: left; }
3727
- .dt-ses-menu-item:hover, .dt-ses-menu-item:focus-visible { background: var(--rf-bg-hover); }
3728
- .dt-ses-menu-item[data-variant="danger"] { color: var(--rf-danger); }
3729
- .dt-btn[data-icon-only] { justify-content: center; width: 28px; padding: 0; }
3730
- .dt-ses-tabs { flex-wrap: wrap; margin: 0; }
3731
- .dt-ses-field { display: flex; flex-direction: column; gap: 3px; font-size: 11.5px; color: var(--rf-text-muted); }
3732
- .dt-ses-need {
3733
- margin: 0; font-weight: 600; overflow-wrap: anywhere;
3734
- display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
3735
- }
3736
- .dt-ses-need[data-expanded="true"] { display: block; }
3737
- .dt-card[data-decision="dismissed"] .dt-ses-need { color: var(--rf-text-muted); text-decoration: line-through; }
3738
- .dt-ses-chips { display: flex; flex-wrap: wrap; gap: 4px; }
3739
- .dt-ses-chips:empty { display: none; }
3740
- .dt-ses-details { display: flex; flex-direction: column; gap: 8px; }
3741
- .dt-ses-details[hidden] { display: none; }
3742
- .dt-ses-details section { display: flex; flex-direction: column; gap: 2px; }
3743
- .dt-ses-quote { padding: 2px 0; font-size: 12px; text-align: left; overflow-wrap: anywhere; }
3744
- .dt-ses-quote:hover { color: var(--rf-accent); }
3745
- .dt-ses-time { flex: none; color: var(--rf-text-muted); font: 11.5px ${MONO_FONT$1}; font-variant-numeric: tabular-nums; }
3746
- .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); }
3747
- .dt-ses-timeline { display: flex; flex-direction: column; gap: 2px; margin: 0; padding: 0; list-style: none; }
3748
- .dt-ses-seg { display: flex; gap: 8px; padding: 6px 8px; border-radius: 8px; }
3749
- .dt-ses-seg:focus { outline: 2px solid var(--rf-accent); outline-offset: -2px; }
3750
- .dt-ses-seg[data-kind="note"] { padding-block: 2px; }
3751
- .dt-ses-seg-body { display: flex; flex: 1; flex-direction: column; gap: 4px; min-width: 0; }
3752
- `}${`
3753
- .dt-ses-thumbs { display: flex; flex-wrap: wrap; gap: 6px; }
3754
- .dt-ses-shot { position: relative; display: inline-block; max-width: 100%; line-height: 0; }
3755
- .dt-ses-shot img { display: block; max-width: 100%; height: auto; max-height: 132px; border-radius: 8px; box-shadow: var(--rf-shadow-inset); }
3756
- .dt-ses-shot[data-large="true"] img { max-height: none; }
3757
- .dt-ses-shot-mark { position: absolute; border: 2px solid var(--rf-accent); border-radius: 4px; pointer-events: none; }
3758
- .dt-ses-shot-btn { align-self: flex-start; padding: 0; border-radius: 8px; }
3759
- .dt-ses-check { display: flex; align-items: flex-start; gap: 6px; overflow-wrap: anywhere; }
3760
- .dt-ses-check input { flex: none; margin-top: 3px; accent-color: var(--rf-accent); }
3761
- .dt-ses-choices { margin: 0; padding: 0; border: 0; }
3762
- .dt-ses-choices .dt-ses-list { padding-left: 0; list-style: none; }
3763
- .dt-ses-brief {
3764
- max-height: 50vh; overflow: auto; margin: 0; padding: 10px; border-radius: 10px;
3765
- background: var(--rf-bg-subtle); font: 11.5px/1.55 ${MONO_FONT$1}; white-space: pre-wrap; overflow-wrap: anywhere;
3766
- }
3767
- `}${`
3768
- .dt-team-anchor {
3769
- --dt-edge-gap: calc(max(var(--rf-offset), 12px) + 48px);
3770
- position: fixed; z-index: ${REFLET_Z_INDEX.devtoolsBar}; width: min(300px, calc(100vw - 24px));
3771
- }
3772
- .dt-team-anchor[data-side="left"] { left: calc(var(--rf-viewport-left, 0px) + max(12px, var(--rf-offset))); }
3773
- .dt-team-anchor[data-side="right"] { right: calc(var(--rf-viewport-right, 0px) + max(12px, var(--rf-offset))); }
3774
- .dt-team-anchor[data-edge="bottom"] { bottom: calc(var(--rf-viewport-bottom, 0px) + env(safe-area-inset-bottom) + var(--dt-edge-gap)); }
3775
- .dt-team-anchor[data-edge="top"] { top: calc(var(--rf-viewport-top, 0px) + env(safe-area-inset-top) + var(--dt-edge-gap)); }
3776
- .dt-team-card { display: flex; flex-direction: column; gap: 6px; padding: 12px 14px; border-radius: 14px; animation: dt-popover-in 200ms var(--rf-ease); }
3777
- .dt-bar > .dt-btn { margin-inline: 2px 4px; }
3778
- `}${INTENTION_STYLES}${REPORT_STYLES}`;
3779
- //#endregion
3780
3660
  //#region src/devtools/client/clipboard.ts
3781
3661
  /**
3782
3662
  * Safari only lets a page write the clipboard inside the key press or click
@@ -3789,1334 +3669,833 @@ async function copyToClipboard(text) {
3789
3669
  await navigator.clipboard.write([new ClipboardItem({ "text/plain": blob })]);
3790
3670
  }
3791
3671
  //#endregion
3792
- //#region src/devtools/client/devtools-icons.tsx
3793
- function NotesIcon() {
3794
- return /* @__PURE__ */ jsx(Svg, { children: /* @__PURE__ */ jsx("path", { d: "M8 6h13M8 12h13M8 18h13M3.5 6h.01M3.5 12h.01M3.5 18h.01" }) });
3672
+ //#region src/sessions/ui/handoff/zip-archive.ts
3673
+ const LOCAL_HEADER_SIGNATURE = 67324752;
3674
+ const CENTRAL_HEADER_SIGNATURE = 33639248;
3675
+ const END_SIGNATURE = 101010256;
3676
+ const LOCAL_HEADER_SIZE = 30;
3677
+ const CENTRAL_HEADER_SIZE = 46;
3678
+ const END_RECORD_SIZE = 22;
3679
+ const ZIP_VERSION = 20;
3680
+ const UTF8_NAMES_FLAG = 2048;
3681
+ const DEFLATE_METHOD = 8;
3682
+ const GZIP_HEADER_SIZE = 10;
3683
+ const GZIP_FLAGS_OFFSET = 3;
3684
+ const GZIP_TRAILER_SIZE = 8;
3685
+ const DOS_EPOCH_YEAR = 1980;
3686
+ function dosStamp(date) {
3687
+ return {
3688
+ date: (date.getFullYear() - DOS_EPOCH_YEAR) * 512 + (date.getMonth() + 1) * 32 + date.getDate(),
3689
+ time: date.getHours() * 2048 + date.getMinutes() * 32 + Math.floor(date.getSeconds() / 2)
3690
+ };
3795
3691
  }
3796
- function InboxIcon() {
3797
- return /* @__PURE__ */ jsxs(Svg, { children: [/* @__PURE__ */ jsx("path", { d: "M22 12h-6l-2 3h-4l-2-3H2" }), /* @__PURE__ */ jsx("path", { d: "M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" })] });
3692
+ /** gzip wraps raw deflate data and ends with the CRC-32 zip needs, so the browser computes both natively. */
3693
+ async function deflateEntry(data) {
3694
+ const stream = new Response(data).body?.pipeThrough(new CompressionStream("gzip"));
3695
+ const gzip = new Uint8Array(await new Response(stream).arrayBuffer());
3696
+ if (gzip[GZIP_FLAGS_OFFSET] !== 0) throw new Error("The browser wrote an unexpected gzip header.");
3697
+ return {
3698
+ checksum: new DataView(gzip.buffer, gzip.length - GZIP_TRAILER_SIZE).getUint32(0, true),
3699
+ compressed: gzip.slice(GZIP_HEADER_SIZE, gzip.length - GZIP_TRAILER_SIZE)
3700
+ };
3798
3701
  }
3799
- //#endregion
3800
- //#region src/devtools/client/devtools-bar.tsx
3801
- const BAR_POSITION_KEY = "reflet-devtools-bar-position";
3802
- const PICK_BUTTON_LABEL = "Pick an element: note it, copy it, or Shift+click to open its code";
3803
- function BarButton({ children, label, onClick, pressed }) {
3804
- return /* @__PURE__ */ jsx("button", {
3805
- "aria-label": label,
3806
- "aria-pressed": pressed,
3807
- className: "icon-btn",
3808
- onClick,
3809
- title: label,
3810
- type: "button",
3811
- children
3702
+ function writeCommonFields(view, start, options) {
3703
+ const { record, stamp } = options;
3704
+ view.setUint16(start, ZIP_VERSION, true);
3705
+ view.setUint16(start + 2, UTF8_NAMES_FLAG, true);
3706
+ view.setUint16(start + 4, DEFLATE_METHOD, true);
3707
+ view.setUint16(start + 6, stamp.time, true);
3708
+ view.setUint16(start + 8, stamp.date, true);
3709
+ view.setUint32(start + 10, record.checksum, true);
3710
+ view.setUint32(start + 14, record.compressed.length, true);
3711
+ view.setUint32(start + 18, record.size, true);
3712
+ view.setUint16(start + 22, record.name.length, true);
3713
+ view.setUint16(start + 24, 0, true);
3714
+ }
3715
+ function localHeader(record, stamp) {
3716
+ const bytes = new Uint8Array(LOCAL_HEADER_SIZE + record.name.length);
3717
+ const view = new DataView(bytes.buffer);
3718
+ view.setUint32(0, LOCAL_HEADER_SIGNATURE, true);
3719
+ writeCommonFields(view, 4, {
3720
+ record,
3721
+ stamp
3812
3722
  });
3723
+ bytes.set(record.name, LOCAL_HEADER_SIZE);
3724
+ return bytes;
3813
3725
  }
3814
- function DevtoolsBar({ children, edge, label = "Reflet devtools", side }) {
3815
- const floating = useFloatingPosition(`${edge}-${side}`, false, {
3816
- anchorSelector: ".dt-bar",
3817
- persistKey: BAR_POSITION_KEY
3818
- });
3819
- return /* @__PURE__ */ jsxs("div", {
3820
- "aria-label": label,
3821
- className: "dt-bar glass",
3822
- "data-edge": edge,
3823
- "data-moved": floating.position !== null,
3824
- "data-side": side,
3825
- ref: floating.rootRef,
3826
- role: "toolbar",
3827
- children: [
3828
- /* @__PURE__ */ jsx("style", { children: floating.styles }),
3829
- /* @__PURE__ */ jsx("button", {
3830
- "aria-label": `Move ${label} (double-click to reset)`,
3831
- className: "icon-btn drag-handle dt-grip",
3832
- onDoubleClick: floating.resetPosition,
3833
- title: "Drag to move · double-click to reset",
3834
- type: "button",
3835
- ...floating.handleProps,
3836
- children: /* @__PURE__ */ jsx(GripIcon, {})
3837
- }),
3838
- children
3839
- ]
3726
+ function centralHeader(record, stamp) {
3727
+ const bytes = new Uint8Array(CENTRAL_HEADER_SIZE + record.name.length);
3728
+ const view = new DataView(bytes.buffer);
3729
+ view.setUint32(0, CENTRAL_HEADER_SIGNATURE, true);
3730
+ view.setUint16(4, ZIP_VERSION, true);
3731
+ writeCommonFields(view, 6, {
3732
+ record,
3733
+ stamp
3840
3734
  });
3735
+ view.setUint32(42, record.offset, true);
3736
+ bytes.set(record.name, CENTRAL_HEADER_SIZE);
3737
+ return bytes;
3841
3738
  }
3842
- function SessionsBarButton({ isOpen, onToggle }) {
3843
- return /* @__PURE__ */ jsx(BarButton, {
3844
- label: "Sessions",
3845
- onClick: onToggle,
3846
- pressed: isOpen,
3847
- children: /* @__PURE__ */ jsx(MicIcon, {})
3739
+ function endRecord(options) {
3740
+ const bytes = new Uint8Array(END_RECORD_SIZE);
3741
+ const view = new DataView(bytes.buffer);
3742
+ view.setUint32(0, END_SIGNATURE, true);
3743
+ view.setUint16(8, options.count, true);
3744
+ view.setUint16(10, options.count, true);
3745
+ view.setUint32(12, options.directorySize, true);
3746
+ view.setUint32(16, options.directoryOffset, true);
3747
+ return bytes;
3748
+ }
3749
+ async function createZipArchive(entries, modifiedAt = /* @__PURE__ */ new Date()) {
3750
+ const stamp = dosStamp(modifiedAt);
3751
+ const encoder = new TextEncoder();
3752
+ const deflated = await Promise.all(entries.map(async (entry) => ({
3753
+ ...await deflateEntry(entry.data),
3754
+ name: encoder.encode(entry.name),
3755
+ size: entry.data.length
3756
+ })));
3757
+ const records = [];
3758
+ const parts = [];
3759
+ let offset = 0;
3760
+ for (const entry of deflated) {
3761
+ const record = {
3762
+ ...entry,
3763
+ offset
3764
+ };
3765
+ const header = localHeader(record, stamp);
3766
+ parts.push(header, record.compressed);
3767
+ records.push(record);
3768
+ offset += header.length + record.compressed.length;
3769
+ }
3770
+ const directory = records.map((record) => centralHeader(record, stamp));
3771
+ const directorySize = directory.reduce((size, part) => size + part.length, 0);
3772
+ const end = endRecord({
3773
+ count: records.length,
3774
+ directoryOffset: offset,
3775
+ directorySize
3848
3776
  });
3777
+ return new Blob([
3778
+ ...parts,
3779
+ ...directory,
3780
+ end
3781
+ ], { type: "application/zip" });
3849
3782
  }
3850
- function DevtoolsBarButtons({ noteCount, onPick, onToggleSheet, openTab }) {
3851
- return /* @__PURE__ */ jsxs(Fragment, { children: [
3852
- /* @__PURE__ */ jsx(BarButton, {
3853
- label: PICK_BUTTON_LABEL,
3854
- onClick: onPick,
3855
- pressed: false,
3856
- children: /* @__PURE__ */ jsx(TargetIcon, {})
3857
- }),
3858
- /* @__PURE__ */ jsxs(BarButton, {
3859
- label: "Dev notes",
3860
- onClick: () => onToggleSheet("notes"),
3861
- pressed: openTab === "notes",
3862
- children: [/* @__PURE__ */ jsx(NotesIcon, {}), noteCount > 0 && /* @__PURE__ */ jsx("span", {
3863
- className: "dt-count",
3864
- children: noteCount
3865
- })]
3866
- }),
3867
- /* @__PURE__ */ jsx(SessionsBarButton, {
3868
- isOpen: openTab === "sessions",
3869
- onToggle: () => onToggleSheet("sessions")
3870
- }),
3871
- /* @__PURE__ */ jsx(BarButton, {
3872
- label: "Board feedback",
3873
- onClick: () => onToggleSheet("inbox"),
3874
- pressed: openTab === "inbox",
3875
- children: /* @__PURE__ */ jsx(InboxIcon, {})
3876
- })
3877
- ] });
3783
+ //#endregion
3784
+ //#region src/sessions/ui/handoff/brief-export.ts
3785
+ const BRIEF_FILE_NAME = "brief.md";
3786
+ const UNSAFE_FILE_CHARACTERS = /[^a-z0-9]+/g;
3787
+ const EDGE_DASHES = /^-|-$/g;
3788
+ async function downloadFile(file) {
3789
+ const response = await fetch(file.url);
3790
+ if (!response.ok) throw new Error(`${file.name} could not be downloaded (${response.status}).`);
3791
+ return {
3792
+ data: new Uint8Array(await response.arrayBuffer()),
3793
+ name: file.name
3794
+ };
3795
+ }
3796
+ function zipFileName(sessionTitle) {
3797
+ return `reflet-session-${sessionTitle.toLowerCase().replace(UNSAFE_FILE_CHARACTERS, "-").replace(EDGE_DASHES, "") || "brief"}.zip`;
3798
+ }
3799
+ /** File URLs are short-lived, so they are fetched right when the export is asked for. */
3800
+ async function buildBriefZip(options) {
3801
+ const evidence = await Promise.all(options.files.map(downloadFile));
3802
+ return await createZipArchive([{
3803
+ data: new TextEncoder().encode(options.brief),
3804
+ name: BRIEF_FILE_NAME
3805
+ }, ...evidence]);
3806
+ }
3807
+ function saveBlob(options) {
3808
+ const url = URL.createObjectURL(options.blob);
3809
+ const link = document.createElement("a");
3810
+ link.download = options.fileName;
3811
+ link.href = url;
3812
+ link.click();
3813
+ setTimeout(() => URL.revokeObjectURL(url), 0);
3878
3814
  }
3879
3815
  //#endregion
3880
- //#region src/devtools/client/use-sheet-size.ts
3881
- const SIZE_KEY = "reflet-devtools-sheet-size";
3882
- const DEFAULT_WIDTH = 440;
3883
- const MIN_WIDTH = 320;
3884
- const MIN_HEIGHT = 240;
3885
- const KEYBOARD_STEP = 24;
3886
- const ARROW_DELTAS = {
3887
- ArrowDown: {
3888
- dx: 0,
3889
- dy: KEYBOARD_STEP
3890
- },
3891
- ArrowLeft: {
3892
- dx: -24,
3893
- dy: 0
3894
- },
3895
- ArrowRight: {
3896
- dx: KEYBOARD_STEP,
3897
- dy: 0
3898
- },
3899
- ArrowUp: {
3900
- dx: 0,
3901
- dy: -24
3902
- }
3816
+ //#region src/sessions/ui/review/transcript/evidence-thumb.tsx
3817
+ const TRIGGER_LABELS = {
3818
+ hover: "Long hover",
3819
+ manual: "Added by hand",
3820
+ mark: "Marked moment",
3821
+ navigation: "Page change",
3822
+ select: "Pointed element",
3823
+ start: "Session start"
3903
3824
  };
3904
- function readStoredSize() {
3905
- const fallback = {
3906
- height: null,
3907
- width: DEFAULT_WIDTH
3908
- };
3909
- try {
3910
- const stored = JSON.parse(localStorage.getItem(SIZE_KEY) ?? "null");
3911
- if (typeof stored === "object" && stored !== null && "width" in stored && typeof stored.width === "number" && "height" in stored && (stored.height === null || typeof stored.height === "number")) return {
3912
- height: stored.height,
3913
- width: stored.width
3914
- };
3915
- } catch {
3916
- return fallback;
3917
- }
3918
- return fallback;
3825
+ function evidenceCaption(evidence) {
3826
+ const element = evidence.element ? ` · ${evidence.element.label}` : "";
3827
+ return `${TRIGGER_LABELS[evidence.trigger]}${element}`;
3919
3828
  }
3920
- /**
3921
- * The stored size is the preference; what renders is that preference fitted
3922
- * to today's window, so shrinking the window never loses the chosen size.
3923
- */
3924
- function fitted(size, viewport) {
3925
- const maxWidth = viewport.width - 24;
3926
- const maxHeight = viewport.height - 24;
3829
+ const hasViewport = (frame) => frame.viewportWidth > 0 && frame.viewportHeight > 0;
3830
+ const percent = (value, total) => `${value / total * 100}%`;
3831
+ function percentBox(rect, frame) {
3832
+ if (!hasViewport(frame)) return null;
3927
3833
  return {
3928
- height: size.height === null || size.height >= maxHeight ? null : Math.max(Math.min(MIN_HEIGHT, maxHeight), size.height),
3929
- width: Math.min(maxWidth, Math.max(MIN_WIDTH, size.width))
3834
+ height: percent(rect.height, frame.viewportHeight),
3835
+ left: percent(rect.x, frame.viewportWidth),
3836
+ top: percent(rect.y, frame.viewportHeight),
3837
+ width: percent(rect.width, frame.viewportWidth)
3930
3838
  };
3931
3839
  }
3932
- /**
3933
- * Width grows away from the docked side, height grows downward from the top;
3934
- * a height reaching the bottom of the window snaps back to full height.
3935
- */
3936
- function useSheetSize(side) {
3937
- const [preference, setPreference] = useState(readStoredSize);
3938
- const [viewport, setViewport] = useState(visibleViewport);
3939
- const drag = useRef(null);
3940
- const widthDirection = side === "right" ? -1 : 1;
3941
- const size = fitted(preference, viewport);
3942
- useEffect(() => onViewportChange(() => setViewport(visibleViewport())), []);
3943
- useEffect(() => {
3944
- try {
3945
- localStorage.setItem(SIZE_KEY, JSON.stringify(preference));
3946
- } catch {}
3947
- }, [preference]);
3948
- const resize = (axis, from, dx, dy) => {
3949
- const fullHeight = viewport.height - 24;
3950
- setPreference(fitted({
3951
- height: axis === "width" ? from.height : (from.height ?? fullHeight) + dy,
3952
- width: axis === "height" ? from.width : from.width + dx * widthDirection
3953
- }, viewport));
3954
- };
3955
- const handleProps = (axis) => ({
3956
- onDoubleClick: () => setPreference((current) => ({
3957
- height: axis === "width" ? current.height : null,
3958
- width: axis === "height" ? current.width : DEFAULT_WIDTH
3959
- })),
3960
- onKeyDown: (event) => {
3961
- const delta = ARROW_DELTAS[event.key];
3962
- if (!delta) return;
3963
- event.preventDefault();
3964
- const sheetHeight = event.currentTarget.parentElement?.getBoundingClientRect().height ?? null;
3965
- resize(axis, {
3966
- height: size.height ?? sheetHeight,
3967
- width: size.width
3968
- }, delta.dx, delta.dy);
3969
- },
3970
- onLostPointerCapture: () => {
3971
- drag.current = null;
3972
- },
3973
- onPointerDown: (event) => {
3974
- if (event.button !== 0) return;
3975
- const sheet = event.currentTarget.parentElement?.getBoundingClientRect();
3976
- event.currentTarget.setPointerCapture(event.pointerId);
3977
- drag.current = {
3978
- axis,
3979
- size: {
3980
- height: sheet?.height ?? size.height,
3981
- width: size.width
3982
- },
3983
- x: event.clientX,
3984
- y: event.clientY
3985
- };
3986
- },
3987
- onPointerMove: (event) => {
3988
- const start = drag.current;
3989
- if (start && event.currentTarget.hasPointerCapture(event.pointerId)) resize(start.axis, start.size, event.clientX - start.x, event.clientY - start.y);
3990
- }
3991
- });
3840
+ function percentPoint(point, frame) {
3841
+ if (!hasViewport(frame)) return null;
3992
3842
  return {
3993
- handleProps,
3994
- size
3843
+ left: percent(point.x, frame.viewportWidth),
3844
+ top: percent(point.y, frame.viewportHeight)
3995
3845
  };
3996
3846
  }
3997
- //#endregion
3998
- //#region src/devtools/client/devtools-sheet.tsx
3999
- const FULL_HEIGHT = "calc(100dvh - 24px)";
4000
- const TAB_LABELS = {
4001
- code: "Code",
4002
- inbox: "Board",
4003
- notes: "Notes",
4004
- sessions: "Sessions"
4005
- };
4006
- const SHEET_POSITION_KEY = "reflet-devtools-sheet-position";
4007
- function useSheetFocus(rootRef) {
4008
- useLayoutEffect(() => {
4009
- const root = rootRef.current;
4010
- const scope = root?.getRootNode();
4011
- const opener = scope instanceof ShadowRoot || scope instanceof Document ? scope.activeElement : null;
4012
- if (opener instanceof HTMLElement && typeof opener.checkVisibility === "function" && !opener.checkVisibility({ visibilityProperty: true })) root?.querySelector("[role=\"tab\"][aria-selected=\"true\"]")?.focus({ preventScroll: true });
4013
- return () => {
4014
- if (opener instanceof HTMLElement && opener.isConnected) opener.focus({ preventScroll: true });
4015
- };
4016
- }, [rootRef]);
4017
- }
4018
- function DevtoolsSheet({ children, label = "Reflet devtools", onClose, onPick, onSelectTab, side, tab, tabs }) {
4019
- const { handleProps, size } = useSheetSize(side);
4020
- const floating = useFloatingPosition(`top-${side}`, false, {
4021
- anchorSelector: ".dt-sheet",
4022
- persistKey: SHEET_POSITION_KEY
3847
+ function EvidenceThumb({ evidence, isLarge = false }) {
3848
+ const { element, frame, image } = evidence;
3849
+ if (!image?.url) return null;
3850
+ const highlight = element && frame ? percentBox(element.rect, frame) : null;
3851
+ return /* @__PURE__ */ jsxs("span", {
3852
+ className: "dt-ses-shot",
3853
+ "data-large": isLarge,
3854
+ children: [/* @__PURE__ */ jsx("img", {
3855
+ alt: evidenceCaption(evidence),
3856
+ height: image.height,
3857
+ src: image.url,
3858
+ width: image.width
3859
+ }), highlight && /* @__PURE__ */ jsx("span", {
3860
+ "aria-hidden": "true",
3861
+ className: "dt-ses-shot-mark",
3862
+ style: highlight
3863
+ })]
4023
3864
  });
4024
- const isMoved = floating.position !== null;
4025
- const { rootRef } = floating;
4026
- useSheetFocus(rootRef);
4027
- useEffect(() => listenToKeydown(rootRef.current, (event) => {
4028
- if (event.key === "Escape" && !event.isComposing) onClose();
4029
- }), [onClose, rootRef]);
4030
- return /* @__PURE__ */ jsxs("aside", {
4031
- "aria-label": label,
4032
- className: "dt-sheet glass",
4033
- "data-moved": isMoved,
4034
- "data-side": side,
4035
- ref: rootRef,
4036
- style: {
4037
- height: size.height ?? (isMoved ? FULL_HEIGHT : void 0),
4038
- width: size.width
4039
- },
3865
+ }
3866
+ //#endregion
3867
+ //#region src/sessions/ui/review/continuation/intention-history.tsx
3868
+ const FIELD_LABELS = {
3869
+ need: "Need",
3870
+ observed: "Observed",
3871
+ response: "Considered response",
3872
+ setAside: "Set aside",
3873
+ unknowns: "To check"
3874
+ };
3875
+ function fieldValueText(value) {
3876
+ return (Array.isArray(value) ? value.join("; ") : value) || "—";
3877
+ }
3878
+ function AppliedChanges({ applied }) {
3879
+ if (applied.length === 0) return null;
3880
+ return /* @__PURE__ */ jsx("ul", {
3881
+ className: "dt-ses-rows",
3882
+ children: applied.map((change) => /* @__PURE__ */ jsxs("li", {
3883
+ className: "dt-ses-row",
3884
+ children: [/* @__PURE__ */ jsxs("p", {
3885
+ className: "dt-meta",
3886
+ children: [
3887
+ "Accepted into revision ",
3888
+ change.revision,
3889
+ " by",
3890
+ " ",
3891
+ change.byName ?? "a teammate",
3892
+ " · ",
3893
+ formatDate(change.at)
3894
+ ]
3895
+ }), /* @__PURE__ */ jsx("ul", {
3896
+ className: "dt-ses-list",
3897
+ children: change.fields.map((field) => /* @__PURE__ */ jsxs("li", { children: [
3898
+ FIELD_LABELS[field.field],
3899
+ ": ",
3900
+ fieldValueText(field.before),
3901
+ " →",
3902
+ " ",
3903
+ fieldValueText(field.after)
3904
+ ] }, field.field))
3905
+ })]
3906
+ }, change.revision))
3907
+ });
3908
+ }
3909
+ function HistoryExtracts({ evidence, quotes }) {
3910
+ return /* @__PURE__ */ jsxs(Fragment, { children: [quotes.map((quote) => /* @__PURE__ */ jsxs("blockquote", {
3911
+ className: "dt-ses-moment-quote",
4040
3912
  children: [
4041
- /* @__PURE__ */ jsxs("style", { children: [floating.styles, isMoved ? "" : `.dt-bar[data-moved="false"][data-side="${side}"] { ${side}: ${size.width + 24}px; }`] }),
4042
- /* @__PURE__ */ jsx("button", {
4043
- "aria-label": "Resize panel width (double-click to reset)",
4044
- className: "dt-resize",
4045
- "data-axis": "width",
4046
- type: "button",
4047
- ...handleProps("width")
4048
- }),
4049
- /* @__PURE__ */ jsx("button", {
4050
- "aria-label": "Resize panel height (double-click for full height)",
4051
- className: "dt-resize",
4052
- "data-axis": "height",
4053
- type: "button",
4054
- ...handleProps("height")
3913
+ /* @__PURE__ */ jsx("span", {
3914
+ className: "dt-ses-time",
3915
+ children: formatClock(quote.startMs)
4055
3916
  }),
4056
- /* @__PURE__ */ jsx("button", {
4057
- "aria-label": "Resize panel (double-click to reset)",
4058
- className: "dt-resize",
4059
- "data-axis": "both",
4060
- type: "button",
4061
- ...handleProps("both")
3917
+ ` “${quote.text}”`,
3918
+ quote.context.map((line) => /* @__PURE__ */ jsx("span", {
3919
+ className: "dt-meta dt-ses-context-line",
3920
+ children: line
3921
+ }, line))
3922
+ ]
3923
+ }, quote.segmentId)), evidence.length > 0 && /* @__PURE__ */ jsx("div", {
3924
+ className: "dt-ses-thumbs",
3925
+ children: evidence.map((item) => /* @__PURE__ */ jsx(EvidenceThumb, { evidence: item }, item.id))
3926
+ })] });
3927
+ }
3928
+ function entryTitle(entry) {
3929
+ const { session } = entry;
3930
+ return entry.origin === "continuation" ? `Continued in “${session.title}” · ${formatDate(session.startedAt)}` : `Added from “${session.title}” · ${formatDate(session.startedAt)}`;
3931
+ }
3932
+ function HistoryEntry({ context, entry }) {
3933
+ const evidenceIds = new Set(entry.evidenceIds);
3934
+ const evidence = context.detail.contributedEvidence.filter((item) => evidenceIds.has(item.id));
3935
+ return /* @__PURE__ */ jsxs("li", {
3936
+ className: "dt-ses-history",
3937
+ "data-removed": entry.detachedAt !== null,
3938
+ children: [
3939
+ /* @__PURE__ */ jsx("p", {
3940
+ className: "dt-ses-subhead",
3941
+ children: entryTitle(entry)
4062
3942
  }),
4063
- /* @__PURE__ */ jsxs("div", {
4064
- className: "dt-sheet-head",
3943
+ entry.detachedAt !== null && /* @__PURE__ */ jsxs("p", {
3944
+ className: "dt-meta",
4065
3945
  children: [
4066
- /* @__PURE__ */ jsx("button", {
4067
- "aria-label": "Move panel (double-click to dock it back)",
4068
- className: "dt-sheet-drag",
4069
- onDoubleClick: floating.resetPosition,
4070
- title: "Drag to move · double-click to dock",
4071
- type: "button",
4072
- ...floating.handleProps
4073
- }),
4074
- onPick && /* @__PURE__ */ jsx("button", {
4075
- "aria-label": "Pick an element: note it, copy it, or Shift+click to open its code",
4076
- className: "icon-btn",
4077
- onClick: onPick,
4078
- title: "Pick an element",
4079
- type: "button",
4080
- children: /* @__PURE__ */ jsx(TargetIcon, {})
4081
- }),
4082
- /* @__PURE__ */ jsx("div", {
4083
- className: "dt-tabs",
4084
- role: "tablist",
4085
- children: tabs.map((option) => /* @__PURE__ */ jsx("button", {
4086
- "aria-selected": option === tab,
4087
- className: "dt-tab",
4088
- onClick: () => onSelectTab(option),
4089
- role: "tab",
4090
- type: "button",
4091
- children: TAB_LABELS[option]
4092
- }, option))
4093
- }),
4094
- /* @__PURE__ */ jsx("button", {
4095
- "aria-label": `Close ${label} panel`,
4096
- className: "icon-btn",
4097
- onClick: onClose,
4098
- title: "Close",
4099
- type: "button",
4100
- children: /* @__PURE__ */ jsx(CloseIcon, {})
4101
- })
3946
+ "Removed on ",
3947
+ formatDate(entry.detachedAt),
3948
+ ": what it added was taken back."
4102
3949
  ]
4103
3950
  }),
4104
- /* @__PURE__ */ jsx("div", {
4105
- className: "dt-sheet-body",
4106
- role: "tabpanel",
4107
- children
4108
- }, tab)
3951
+ /* @__PURE__ */ jsx(AppliedChanges, { applied: entry.applied }),
3952
+ entry.extractsHidden ? /* @__PURE__ */ jsx("p", {
3953
+ className: "dt-meta",
3954
+ children: "Extracts are private to the author"
3955
+ }) : /* @__PURE__ */ jsx(HistoryExtracts, {
3956
+ evidence,
3957
+ quotes: entry.quotes
3958
+ }),
3959
+ entry.session.canOpen && /* @__PURE__ */ jsx("button", {
3960
+ className: "dt-ses-link",
3961
+ onClick: () => context.navigation.onOpenSession(entry.session.id),
3962
+ type: "button",
3963
+ children: "Open session"
3964
+ })
4109
3965
  ]
4110
3966
  });
4111
3967
  }
3968
+ function IntentionHistory({ context, intentionId }) {
3969
+ const entries = context.detail.history.filter((entry) => entry.intentionId === intentionId);
3970
+ if (entries.length === 0) return null;
3971
+ return /* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("h4", {
3972
+ className: "dt-ses-label",
3973
+ children: "History"
3974
+ }), /* @__PURE__ */ jsx("ul", {
3975
+ className: "dt-ses-rows",
3976
+ children: entries.map((entry) => /* @__PURE__ */ jsx(HistoryEntry, {
3977
+ context,
3978
+ entry
3979
+ }, entry.id))
3980
+ })] });
3981
+ }
4112
3982
  //#endregion
4113
- //#region src/devtools/client/devtools-styles.ts
4114
- const MONO_FONT = "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace";
4115
- const DEVTOOLS_STYLES = `
4116
- .dt { font: 13px/1.45 var(--rf-font, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif); color: var(--rf-text); }
4117
- .dt[data-recessed="true"] .dt-bar, .dt[data-recessed="true"] .dt-sheet { visibility: hidden; pointer-events: none; }
4118
- @media (max-width: 600px) { .dt[data-sheet-open="true"] .dt-bar { visibility: hidden; pointer-events: none; } }
4119
- .dt-pin {
4120
- position: fixed; z-index: ${REFLET_Z_INDEX.widgetRoot}; top: 0; left: 0;
4121
- display: flex; align-items: center; justify-content: center;
4122
- min-width: 24px; height: 24px; padding: 0 7px;
4123
- border-radius: 12px 12px 12px 3px; background: var(--rf-accent); color: var(--rf-accent-text);
4124
- font-size: 11.5px; font-weight: 650; box-shadow: 0 0 0 2px var(--rf-bg), var(--rf-shadow-low);
4125
- animation: dt-pin-in 260ms var(--rf-ease);
4126
- transition: scale 140ms var(--rf-ease), opacity 160ms var(--rf-ease);
3983
+ //#region src/sessions/ui/handoff/handoff-changes.tsx
3984
+ function differences(proposal, last) {
3985
+ return [
3986
+ {
3987
+ after: proposal.need,
3988
+ before: last.need,
3989
+ label: "Need"
3990
+ },
3991
+ {
3992
+ after: fieldValueText(proposal.response),
3993
+ before: fieldValueText(last.response),
3994
+ label: "Considered response"
3995
+ },
3996
+ {
3997
+ after: fieldValueText(proposal.unknowns),
3998
+ before: last.unknowns === null ? null : fieldValueText(last.unknowns),
3999
+ label: "To check"
4000
+ }
4001
+ ].flatMap(({ after, before, label }) => before === null || before === after ? [] : [{
4002
+ after,
4003
+ before,
4004
+ label
4005
+ }]);
4127
4006
  }
4128
- .dt-pin:hover, .dt-pin[aria-expanded="true"] { scale: 1.12; }
4129
- .dt-pin[data-sent="true"] { background: var(--rf-text-muted); }
4130
- .dt-pin-badge { display: inline-flex; flex: none; align-items: center; justify-content: center; min-width: 20px; height: 20px; padding: 0 6px; border-radius: 10px 10px 10px 3px; background: var(--rf-accent); color: var(--rf-accent-text); font-size: 10.5px; font-weight: 650; }
4131
- .dt-card[data-sent="true"] .dt-pin-badge { background: var(--rf-text-muted); }
4132
- .dt-pin[data-locating="true"] { animation: dt-pin-in 260ms var(--rf-ease), dt-pin-pulse 1.2s ease-in-out infinite; }
4133
- @keyframes dt-pin-in { from { scale: .4; opacity: 0; } }
4134
- @keyframes dt-pin-pulse { 50% { box-shadow: 0 0 0 2px var(--rf-bg), 0 0 0 6px color-mix(in srgb, var(--rf-accent) 30%, transparent); } }
4135
- .dt-popover {
4136
- position: fixed; z-index: ${REFLET_Z_INDEX.widgetRoot}; width: 320px; max-height: calc(100dvh - 24px); overflow: auto;
4137
- inset-inline-end: auto; margin: 0; padding: 6px; border-radius: 14px; background: transparent; color: inherit;
4138
- animation: dt-popover-in 200ms var(--rf-ease);
4007
+ function ChangeSummary({ last, proposal }) {
4008
+ return /* @__PURE__ */ jsxs("li", {
4009
+ className: "dt-ses-row",
4010
+ children: [
4011
+ /* @__PURE__ */ jsx("p", {
4012
+ className: "dt-ses-subhead",
4013
+ children: proposal.need
4014
+ }),
4015
+ /* @__PURE__ */ jsxs("p", {
4016
+ className: "dt-meta",
4017
+ children: ["Changed since your brief of ", formatDate(last.at)]
4018
+ }),
4019
+ /* @__PURE__ */ jsx("ul", {
4020
+ className: "dt-ses-list",
4021
+ children: differences(proposal, last).map((difference) => /* @__PURE__ */ jsxs("li", { children: [
4022
+ difference.label,
4023
+ ": ",
4024
+ difference.before,
4025
+ " → ",
4026
+ difference.after
4027
+ ] }, difference.label))
4028
+ })
4029
+ ]
4030
+ });
4139
4031
  }
4140
- .dt-popover.glass::before { animation: rf-fade 180ms var(--rf-ease); }
4141
- .dt-popover .dt-card { background: transparent; }
4142
- @keyframes dt-popover-in { from { transform: translateY(4px); } }
4143
- @keyframes dt-notice-in { from { transform: translateY(-6px); } }
4144
- .dt-notices {
4145
- position: fixed; z-index: ${REFLET_Z_INDEX.annotatorOverlay}; left: 50%; top: 16px; translate: -50% 0;
4146
- display: flex; flex-direction: column; gap: 6px; width: min(460px, calc(100vw - 24px));
4147
- }
4148
- .dt-notice {
4149
- display: flex; align-items: center; gap: 6px; padding: 6px 6px 6px 14px; border-radius: 12px;
4150
- color: var(--rf-danger); font-size: 12.5px; animation: dt-notice-in 220ms var(--rf-ease);
4151
- }
4152
- .dt-notice[data-kind="status"] { color: var(--rf-text); }
4153
- .dt-notice p { flex: 1; margin: 0; overflow-wrap: anywhere; }
4154
- .dt-notice .icon-btn { width: 30px; height: 30px; }
4155
- .dt-bar {
4156
- --dt-edge-gap: calc(max(var(--rf-offset), 12px) + 48px);
4157
- position: fixed; z-index: ${REFLET_Z_INDEX.devtoolsBar};
4158
- display: flex; align-items: center; gap: 2px; padding: 3px;
4159
- border-radius: var(--rf-pill);
4160
- }
4161
- .dt-bar[data-moved="false"] { transition: left 260ms var(--rf-ease), right 260ms var(--rf-ease); }
4162
- .dt-bar[data-side="left"] { left: calc(var(--rf-viewport-left, 0px) + max(12px, var(--rf-offset))); }
4163
- .dt-bar[data-side="right"] { right: calc(var(--rf-viewport-right, 0px) + max(12px, var(--rf-offset))); }
4164
- .dt-bar[data-edge="bottom"] { bottom: calc(var(--rf-viewport-bottom, 0px) + env(safe-area-inset-bottom) + var(--dt-edge-gap)); }
4165
- .dt-bar[data-edge="top"] { top: calc(var(--rf-viewport-top, 0px) + env(safe-area-inset-top) + var(--dt-edge-gap)); }
4166
- .dt-bar .icon-btn { position: relative; width: 34px; height: 34px; }
4167
- .dt-bar .icon-btn.dt-grip { width: 22px; color: var(--rf-text-muted); opacity: .6; }
4168
- .dt-bar .icon-btn.dt-grip:hover { opacity: 1; }
4169
- .dt-bar .icon-btn[aria-pressed="true"] { background: var(--rf-bg-hover); color: var(--rf-text); }
4170
- .dt-count {
4171
- position: absolute; top: 1px; right: 1px; display: flex; align-items: center; justify-content: center;
4172
- min-width: 15px; height: 15px; padding: 0 4px; border-radius: var(--rf-pill);
4173
- background: var(--rf-accent); color: var(--rf-accent-text); font-size: 9.5px; font-weight: 600;
4032
+ function ChangedSinceBriefs({ detail, proposalIds }) {
4033
+ const selected = new Set(proposalIds);
4034
+ const changed = detail.proposals.flatMap((proposal) => {
4035
+ const last = proposal.lastHandoff;
4036
+ return selected.has(proposal.id) && last && last.revision < proposal.revision ? [{
4037
+ last,
4038
+ proposal
4039
+ }] : [];
4040
+ });
4041
+ if (changed.length === 0) return null;
4042
+ return /* @__PURE__ */ jsx("ul", {
4043
+ className: "dt-ses-rows",
4044
+ children: changed.map(({ last, proposal }) => /* @__PURE__ */ jsx(ChangeSummary, {
4045
+ last,
4046
+ proposal
4047
+ }, proposal.id))
4048
+ });
4174
4049
  }
4175
- .dt-sheet {
4176
- --dt-enter-x: 20px;
4177
- position: fixed; z-index: ${REFLET_Z_INDEX.widgetRoot}; top: 12px; bottom: 12px;
4178
- display: flex; flex-direction: column; max-width: calc(100vw - 24px); max-height: calc(100dvh - 24px);
4179
- border-radius: 16px; overflow: hidden;
4180
- animation: dt-sheet-slide 320ms var(--rf-ease);
4050
+ const DELIVERY_LABELS = {
4051
+ copied: "Copied",
4052
+ exported: "Downloaded"
4053
+ };
4054
+ function deliveryLine(handoff) {
4055
+ const kinds = new Set(handoff.deliveries.map((delivery) => delivery.kind));
4056
+ if (kinds.size === 0) return "Not copied yet";
4057
+ return [...kinds].map((kind) => DELIVERY_LABELS[kind]).join(", ");
4181
4058
  }
4182
- .dt-sheet[data-side="left"] { --dt-enter-x: -20px; left: 12px; }
4183
- .dt-sheet[data-side="right"] { right: 12px; }
4184
- .dt-sheet.glass::before { animation: rf-fade 220ms var(--rf-ease); }
4185
- .dt-sheet-head, .dt-sheet-body { animation: rf-fade 260ms 60ms var(--rf-ease) both; }
4186
- @keyframes dt-sheet-slide { from { transform: translateX(var(--dt-enter-x)); box-shadow: none; } }
4187
- .dt-resize { position: absolute; z-index: 2; padding: 0; touch-action: none; }
4188
- .dt-resize:active:not(:disabled) { scale: 1; }
4189
- .dt-resize:focus-visible { outline: none; }
4190
- .dt-resize[data-axis="width"] { top: 16px; bottom: 16px; width: 10px; cursor: ew-resize; }
4191
- .dt-sheet[data-side="right"] .dt-resize[data-axis="width"] { left: 0; }
4192
- .dt-sheet[data-side="left"] .dt-resize[data-axis="width"] { right: 0; }
4193
- .dt-resize[data-axis="height"] { left: 16px; right: 16px; bottom: 0; height: 10px; cursor: ns-resize; }
4194
- .dt-resize[data-axis="both"] { bottom: 0; width: 16px; height: 16px; }
4195
- .dt-sheet[data-side="right"] .dt-resize[data-axis="both"] { left: 0; cursor: nesw-resize; }
4196
- .dt-sheet[data-side="left"] .dt-resize[data-axis="both"] { right: 0; cursor: nwse-resize; }
4197
- .dt-resize[data-axis="width"]::after, .dt-resize[data-axis="height"]::after {
4198
- content: ""; position: absolute; border-radius: var(--rf-pill); background: var(--rf-text-muted);
4199
- opacity: 0; transition: opacity 160ms var(--rf-ease);
4059
+ function briefTitle(handoff) {
4060
+ const [only, ...others] = handoff.proposals;
4061
+ if (only && others.length === 0) return only.need;
4062
+ return countLabel(handoff.proposals.length, "intention");
4200
4063
  }
4201
- .dt-resize[data-axis="width"]::after { top: 50%; left: 3px; width: 4px; height: 36px; translate: 0 -50%; }
4202
- .dt-resize[data-axis="height"]::after { left: 50%; top: 3px; width: 36px; height: 4px; translate: -50% 0; }
4203
- .dt-resize:hover::after, .dt-resize:focus-visible::after, .dt-resize:active::after { opacity: .5; }
4204
- .dt-sheet-head { position: relative; display: flex; align-items: center; gap: 4px; padding: 8px 8px 8px 6px; border-bottom: 1px solid var(--rf-border); }
4205
- .dt-sheet-head > :not(.dt-sheet-drag) { position: relative; z-index: 1; }
4206
- .dt-sheet-head .icon-btn { width: 32px; height: 32px; }
4207
- .dt-sheet .dt-sheet-drag { position: absolute; inset: 0; z-index: 0; padding: 0; border-radius: 0; cursor: grab; touch-action: none; }
4208
- .dt-sheet .dt-sheet-drag::before { content: ""; position: absolute; top: 4px; left: 50%; width: 36px; height: 4px; translate: -50% 0; border-radius: var(--rf-pill); background: var(--rf-text-muted); opacity: .3; transition: opacity 160ms var(--rf-ease); }
4209
- .dt-sheet .dt-sheet-drag:hover::before, .dt-sheet .dt-sheet-drag:active::before { opacity: .7; }
4210
- .dt-sheet .dt-sheet-drag:active:not(:disabled) { cursor: grabbing; scale: 1; }
4211
- .dt-sheet .dt-sheet-drag:focus-visible { outline-offset: -2px; }
4212
- .dt-tabs { display: flex; gap: 2px; margin-right: auto; }
4213
- .dt-tab { height: 30px; padding: 0 11px; border-radius: var(--rf-pill); color: var(--rf-text-muted); font-weight: 500; }
4214
- .dt-tab:hover { color: var(--rf-text); }
4215
- .dt-tab[aria-selected="true"], .dt-tab[aria-pressed="true"] { background: var(--rf-bg-hover); color: var(--rf-text); }
4216
- .dt-sheet-body { flex: 1; min-height: 0; overflow: auto; display: flex; flex-direction: column; gap: 8px; padding: 10px; }
4217
- .dt-sheet-foot {
4218
- position: sticky; bottom: -10px; display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
4219
- margin: auto -10px -10px; padding: 10px; border-top: 1px solid var(--rf-border); background: var(--rf-bg);
4064
+ function EarlierBriefs({ handoffs }) {
4065
+ if (handoffs.length === 0) return null;
4066
+ return /* @__PURE__ */ jsxs("section", {
4067
+ className: "dt-ses-form",
4068
+ children: [/* @__PURE__ */ jsx("h3", {
4069
+ className: "dt-group",
4070
+ children: "Earlier briefs"
4071
+ }), /* @__PURE__ */ jsx("ul", {
4072
+ className: "dt-ses-rows",
4073
+ children: handoffs.map((handoff) => {
4074
+ const date = formatDate(handoff.createdAt);
4075
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("details", {
4076
+ className: "dt-ses-brief-entry",
4077
+ children: [/* @__PURE__ */ jsxs("summary", { children: [/* @__PURE__ */ jsx("span", {
4078
+ className: "dt-card-title",
4079
+ children: briefTitle(handoff)
4080
+ }), /* @__PURE__ */ jsxs("span", {
4081
+ className: "dt-meta",
4082
+ children: [
4083
+ date,
4084
+ " · ",
4085
+ deliveryLine(handoff)
4086
+ ]
4087
+ })] }), /* @__PURE__ */ jsx("textarea", {
4088
+ "aria-label": `Brief of ${date}`,
4089
+ className: "dt-field dt-ses-brief",
4090
+ readOnly: true,
4091
+ rows: 8,
4092
+ value: handoff.brief
4093
+ })]
4094
+ }) }, handoff.id);
4095
+ })
4096
+ })]
4097
+ });
4220
4098
  }
4221
- .dt-archived { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 8px 10px; border: 1px dashed var(--rf-border); border-radius: 12px; }
4222
- .dt-archived .dt-meta { flex: 1 1 100%; }
4223
- .dt-group { margin: 6px 2px 0; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; color: var(--rf-text-muted); }
4224
- .dt-card { display: flex; flex-direction: column; gap: 6px; padding: 10px 12px; border-radius: 12px; background: var(--rf-bg-subtle); }
4225
- .dt-card[data-sent="true"] { opacity: .7; }
4226
- .dt-card-head { display: flex; align-items: center; gap: 6px; min-width: 0; }
4227
- .dt-card-title { min-width: 0; margin: 0; overflow: hidden; font-size: 13px; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }
4228
- .dt-meta { margin: 0; font-size: 11.5px; color: var(--rf-text-muted); overflow-wrap: anywhere; }
4229
- .dt-note-text { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
4230
- .dt-chip { flex: none; padding: 1px 7px; border-radius: var(--rf-pill); background: var(--rf-bg-hover); color: var(--rf-text-muted); font-size: 10.5px; font-weight: 600; }
4231
- .dt-chip[data-tone="accent"] { background: color-mix(in srgb, var(--rf-accent) 16%, transparent); color: var(--rf-accent); }
4232
- .dt-source { padding: 0; font: 11.5px/1.4 ${MONO_FONT}; color: var(--rf-accent); text-align: left; overflow-wrap: anywhere; }
4233
- .dt-source:hover { text-decoration: underline; }
4234
- .dt-thumb { display: block; align-self: flex-start; max-width: 100%; height: auto; max-height: 132px; object-fit: contain; border-radius: 8px; box-shadow: var(--rf-shadow-inset); }
4235
- .dt-actions { display: flex; flex-wrap: wrap; gap: 4px; }
4236
- .dt-btn {
4237
- display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 10px;
4238
- border-radius: var(--rf-pill); background: var(--rf-bg-hover); color: var(--rf-text);
4239
- font-size: 12px; font-weight: 500; text-decoration: none;
4099
+ //#endregion
4100
+ //#region src/sessions/ui/handoff/handoff-selection.tsx
4101
+ function keptProposals(detail) {
4102
+ return detail.proposals.filter((proposal) => proposal.decision === "kept" && proposal.mergedIntoId === null);
4240
4103
  }
4241
- .dt-btn:hover { background: color-mix(in srgb, var(--rf-text) 12%, transparent); }
4242
- .dt-btn[data-variant="primary"] { background: var(--rf-primary); color: var(--rf-primary-text); }
4243
- .dt-btn[data-variant="danger"] { color: var(--rf-danger); }
4244
- .dt-empty { margin: auto; padding: 24px 16px; color: var(--rf-text-muted); font-size: 12.5px; text-align: center; }
4245
- .dt-empty p { margin: 0 0 8px; }
4246
- .dt-empty code, .dt-hint code { padding: 1px 4px; border-radius: 4px; background: var(--rf-bg-hover); font: 11.5px ${MONO_FONT}; overflow-wrap: anywhere; }
4247
- .dt-hint { margin: 0; padding: 8px 10px; border-radius: 10px; background: var(--rf-bg-subtle); color: var(--rf-text-muted); font-size: 12px; }
4248
- .dt-error { margin: 0; color: var(--rf-danger); font-size: 12px; }
4249
- .dt-status { margin: 0 auto 0 0; color: var(--rf-text-muted); font-size: 12px; }
4250
- .dt-field {
4251
- width: 100%; padding: 7px 9px; border: 1px solid var(--rf-border); border-radius: 8px;
4252
- background: var(--rf-bg); color: var(--rf-text); font: inherit; resize: vertical;
4104
+ function evidenceOf(detail, proposals) {
4105
+ const intentionIds = new Set(proposals.map((proposal) => proposal.id));
4106
+ const contributed = detail.history.filter((entry) => intentionIds.has(entry.intentionId) && entry.detachedAt === null);
4107
+ const cited = /* @__PURE__ */ new Set([...proposals.flatMap((proposal) => proposal.evidenceIds), ...contributed.flatMap((entry) => entry.evidenceIds)]);
4108
+ return [...detail.evidence, ...detail.contributedEvidence].filter((item) => cited.has(item.id) && item.status === "available");
4253
4109
  }
4254
- .dt-field:focus { outline: 2px solid var(--rf-accent); outline-offset: 0; }
4255
- .dt-search { display: flex; gap: 6px; }
4256
- .dt-matches { display: flex; flex-direction: column; gap: 2px; margin: 0; padding: 0; list-style: none; }
4257
- .dt-match { display: flex; flex-direction: column; gap: 1px; width: 100%; padding: 6px 8px; border-radius: 8px; text-align: left; }
4258
- .dt-match:hover { background: var(--rf-bg-hover); }
4259
- .dt-match code { font: 11.5px ${MONO_FONT}; color: var(--rf-accent); }
4260
- .dt-match span { font: 11.5px ${MONO_FONT}; color: var(--rf-text-muted); white-space: pre; overflow: hidden; text-overflow: ellipsis; }
4261
- .dt-code-head { display: flex; flex-direction: column; gap: 6px; }
4262
- .dt-code-path { margin: 0; font: 12px/1.4 ${MONO_FONT}; overflow-wrap: anywhere; }
4263
- .dt-code { flex: 1; min-height: 0; overflow: auto; margin: 0; padding: 8px 0; border-radius: 10px; background: var(--rf-bg-subtle); font: 12px/1.6 ${MONO_FONT}; }
4264
- .dt-code-lines { display: table; min-width: 100%; }
4265
- .dt-line { display: table-row; white-space: pre; }
4266
- .dt-line > span { display: table-cell; padding-right: 14px; }
4267
- .dt-line > .dt-line-number { width: 1%; padding: 0 12px 0 10px; color: var(--rf-text-muted); text-align: right; user-select: none; }
4268
- .dt-line[data-marked="true"] { background: color-mix(in srgb, var(--rf-accent) 16%, transparent); }
4269
- .dt-line[data-marked="true"] > .dt-line-number { color: var(--rf-accent); }
4270
- `;
4271
- //#endregion
4272
- //#region src/sessions/ui/handoff/zip-archive.ts
4273
- const LOCAL_HEADER_SIGNATURE = 67324752;
4274
- const CENTRAL_HEADER_SIGNATURE = 33639248;
4275
- const END_SIGNATURE = 101010256;
4276
- const LOCAL_HEADER_SIZE = 30;
4277
- const CENTRAL_HEADER_SIZE = 46;
4278
- const END_RECORD_SIZE = 22;
4279
- const ZIP_VERSION = 20;
4280
- const UTF8_NAMES_FLAG = 2048;
4281
- const DEFLATE_METHOD = 8;
4282
- const GZIP_HEADER_SIZE = 10;
4283
- const GZIP_FLAGS_OFFSET = 3;
4284
- const GZIP_TRAILER_SIZE = 8;
4285
- const DOS_EPOCH_YEAR = 1980;
4286
- function dosStamp(date) {
4110
+ function initialSelection(detail) {
4111
+ const proposals = keptProposals(detail);
4287
4112
  return {
4288
- date: (date.getFullYear() - DOS_EPOCH_YEAR) * 512 + (date.getMonth() + 1) * 32 + date.getDate(),
4289
- time: date.getHours() * 2048 + date.getMinutes() * 32 + Math.floor(date.getSeconds() / 2)
4113
+ evidenceIds: evidenceOf(detail, proposals).map((item) => item.id),
4114
+ proposalIds: proposals.map((proposal) => proposal.id)
4290
4115
  };
4291
4116
  }
4292
- /** gzip wraps raw deflate data and ends with the CRC-32 zip needs, so the browser computes both natively. */
4293
- async function deflateEntry(data) {
4294
- const stream = new Response(data).body?.pipeThrough(new CompressionStream("gzip"));
4295
- const gzip = new Uint8Array(await new Response(stream).arrayBuffer());
4296
- if (gzip[GZIP_FLAGS_OFFSET] !== 0) throw new Error("The browser wrote an unexpected gzip header.");
4297
- return {
4298
- checksum: new DataView(gzip.buffer, gzip.length - GZIP_TRAILER_SIZE).getUint32(0, true),
4299
- compressed: gzip.slice(GZIP_HEADER_SIZE, gzip.length - GZIP_TRAILER_SIZE)
4300
- };
4117
+ function toggled(ids, id) {
4118
+ return ids.includes(id) ? ids.filter((other) => other !== id) : [...ids, id];
4301
4119
  }
4302
- function writeCommonFields(view, start, options) {
4303
- const { record, stamp } = options;
4304
- view.setUint16(start, ZIP_VERSION, true);
4305
- view.setUint16(start + 2, UTF8_NAMES_FLAG, true);
4306
- view.setUint16(start + 4, DEFLATE_METHOD, true);
4307
- view.setUint16(start + 6, stamp.time, true);
4308
- view.setUint16(start + 8, stamp.date, true);
4309
- view.setUint32(start + 10, record.checksum, true);
4310
- view.setUint32(start + 14, record.compressed.length, true);
4311
- view.setUint32(start + 18, record.size, true);
4312
- view.setUint16(start + 22, record.name.length, true);
4313
- view.setUint16(start + 24, 0, true);
4120
+ function CheckRow({ isChecked, label, onToggle }) {
4121
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("label", {
4122
+ className: "dt-ses-check",
4123
+ children: [/* @__PURE__ */ jsx("input", {
4124
+ checked: isChecked,
4125
+ onChange: onToggle,
4126
+ type: "checkbox"
4127
+ }), /* @__PURE__ */ jsx("span", { children: label })]
4128
+ }) });
4314
4129
  }
4315
- function localHeader(record, stamp) {
4316
- const bytes = new Uint8Array(LOCAL_HEADER_SIZE + record.name.length);
4317
- const view = new DataView(bytes.buffer);
4318
- view.setUint32(0, LOCAL_HEADER_SIGNATURE, true);
4319
- writeCommonFields(view, 4, {
4320
- record,
4321
- stamp
4130
+ function ChoiceGroup({ children, legend }) {
4131
+ return /* @__PURE__ */ jsxs("fieldset", {
4132
+ className: "dt-ses-choices",
4133
+ children: [/* @__PURE__ */ jsx("legend", {
4134
+ className: "dt-ses-label",
4135
+ children: legend
4136
+ }), /* @__PURE__ */ jsx("ul", {
4137
+ className: "dt-ses-list",
4138
+ children
4139
+ })]
4322
4140
  });
4323
- bytes.set(record.name, LOCAL_HEADER_SIZE);
4324
- return bytes;
4325
4141
  }
4326
- function centralHeader(record, stamp) {
4327
- const bytes = new Uint8Array(CENTRAL_HEADER_SIZE + record.name.length);
4328
- const view = new DataView(bytes.buffer);
4329
- view.setUint32(0, CENTRAL_HEADER_SIGNATURE, true);
4330
- view.setUint16(4, ZIP_VERSION, true);
4331
- writeCommonFields(view, 6, {
4332
- record,
4333
- stamp
4142
+ function HandoffChoices({ detail, onChange, selection }) {
4143
+ const proposals = keptProposals(detail);
4144
+ const evidence = evidenceOf(detail, proposals);
4145
+ const toggleProposal = (id) => onChange({
4146
+ ...selection,
4147
+ proposalIds: toggled(selection.proposalIds, id)
4334
4148
  });
4335
- view.setUint32(42, record.offset, true);
4336
- bytes.set(record.name, CENTRAL_HEADER_SIZE);
4337
- return bytes;
4338
- }
4339
- function endRecord(options) {
4340
- const bytes = new Uint8Array(END_RECORD_SIZE);
4341
- const view = new DataView(bytes.buffer);
4342
- view.setUint32(0, END_SIGNATURE, true);
4343
- view.setUint16(8, options.count, true);
4344
- view.setUint16(10, options.count, true);
4345
- view.setUint32(12, options.directorySize, true);
4346
- view.setUint32(16, options.directoryOffset, true);
4347
- return bytes;
4348
- }
4349
- async function createZipArchive(entries, modifiedAt = /* @__PURE__ */ new Date()) {
4350
- const stamp = dosStamp(modifiedAt);
4351
- const encoder = new TextEncoder();
4352
- const deflated = await Promise.all(entries.map(async (entry) => ({
4353
- ...await deflateEntry(entry.data),
4354
- name: encoder.encode(entry.name),
4355
- size: entry.data.length
4356
- })));
4357
- const records = [];
4358
- const parts = [];
4359
- let offset = 0;
4360
- for (const entry of deflated) {
4361
- const record = {
4362
- ...entry,
4363
- offset
4364
- };
4365
- const header = localHeader(record, stamp);
4366
- parts.push(header, record.compressed);
4367
- records.push(record);
4368
- offset += header.length + record.compressed.length;
4369
- }
4370
- const directory = records.map((record) => centralHeader(record, stamp));
4371
- const directorySize = directory.reduce((size, part) => size + part.length, 0);
4372
- const end = endRecord({
4373
- count: records.length,
4374
- directoryOffset: offset,
4375
- directorySize
4149
+ const toggleEvidence = (id) => onChange({
4150
+ ...selection,
4151
+ evidenceIds: toggled(selection.evidenceIds, id)
4376
4152
  });
4377
- return new Blob([
4378
- ...parts,
4379
- ...directory,
4380
- end
4381
- ], { type: "application/zip" });
4153
+ const chosenProposalIds = new Set(selection.proposalIds);
4154
+ const chosenEvidenceIds = new Set(selection.evidenceIds);
4155
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ChoiceGroup, {
4156
+ legend: "Intentions",
4157
+ children: proposals.map((proposal) => /* @__PURE__ */ jsx(CheckRow, {
4158
+ isChecked: chosenProposalIds.has(proposal.id),
4159
+ label: proposal.need,
4160
+ onToggle: () => toggleProposal(proposal.id)
4161
+ }, proposal.id))
4162
+ }), evidence.length > 0 && /* @__PURE__ */ jsx(ChoiceGroup, {
4163
+ legend: "Evidence",
4164
+ children: evidence.map((item) => /* @__PURE__ */ jsx(CheckRow, {
4165
+ isChecked: chosenEvidenceIds.has(item.id),
4166
+ label: `${formatClock(item.atMs)} · ${evidenceCaption(item)}`,
4167
+ onToggle: () => toggleEvidence(item.id)
4168
+ }, item.id))
4169
+ })] });
4382
4170
  }
4383
4171
  //#endregion
4384
- //#region src/sessions/ui/handoff/brief-export.ts
4385
- const BRIEF_FILE_NAME = "brief.md";
4386
- const UNSAFE_FILE_CHARACTERS = /[^a-z0-9]+/g;
4387
- const EDGE_DASHES = /^-|-$/g;
4388
- async function downloadFile(file) {
4389
- const response = await fetch(file.url);
4390
- if (!response.ok) throw new Error(`${file.name} could not be downloaded (${response.status}).`);
4172
+ //#region src/sessions/ui/handoff/handoff-view.tsx
4173
+ function useDelivery(options) {
4174
+ const { api } = useSessionWorkspace();
4175
+ const run = useSessionAction();
4176
+ const { files, handoff } = options.prepared;
4177
+ const deliver = async (kind, send) => {
4178
+ try {
4179
+ await send();
4180
+ } catch (error) {
4181
+ reportFailure(`The brief was not ${kind}`, error);
4182
+ return;
4183
+ }
4184
+ announce(kind === "copied" ? "Brief copied" : "Brief downloaded");
4185
+ await run("The delivery was not recorded", () => api.markHandoffDelivered({
4186
+ handoffId: handoff.id,
4187
+ kind
4188
+ }));
4189
+ };
4391
4190
  return {
4392
- data: new Uint8Array(await response.arrayBuffer()),
4393
- name: file.name
4191
+ copy: () => deliver("copied", () => copyToClipboard(Promise.resolve(handoff.brief))),
4192
+ exportZip: () => deliver("exported", async () => {
4193
+ saveBlob({
4194
+ blob: await buildBriefZip({
4195
+ brief: handoff.brief,
4196
+ files
4197
+ }),
4198
+ fileName: zipFileName(options.title)
4199
+ });
4200
+ })
4394
4201
  };
4395
4202
  }
4396
- function zipFileName(sessionTitle) {
4397
- return `reflet-session-${sessionTitle.toLowerCase().replace(UNSAFE_FILE_CHARACTERS, "-").replace(EDGE_DASHES, "") || "brief"}.zip`;
4398
- }
4399
- /** File URLs are short-lived, so they are fetched right when the export is asked for. */
4400
- async function buildBriefZip(options) {
4401
- const evidence = await Promise.all(options.files.map(downloadFile));
4402
- return await createZipArchive([{
4403
- data: new TextEncoder().encode(options.brief),
4404
- name: BRIEF_FILE_NAME
4405
- }, ...evidence]);
4406
- }
4407
- function saveBlob(options) {
4408
- const url = URL.createObjectURL(options.blob);
4409
- const link = document.createElement("a");
4410
- link.download = options.fileName;
4411
- link.href = url;
4412
- link.click();
4413
- setTimeout(() => URL.revokeObjectURL(url), 0);
4414
- }
4415
- //#endregion
4416
- //#region src/sessions/ui/review/transcript/evidence-thumb.tsx
4417
- const TRIGGER_LABELS = {
4418
- hover: "Long hover",
4419
- manual: "Added by hand",
4420
- mark: "Marked moment",
4421
- navigation: "Page change",
4422
- select: "Pointed element",
4423
- start: "Session start"
4424
- };
4425
- function evidenceCaption(evidence) {
4426
- const element = evidence.element ? ` · ${evidence.element.label}` : "";
4427
- return `${TRIGGER_LABELS[evidence.trigger]}${element}`;
4203
+ function PreparedBrief({ onChangeSelection, prepared, title }) {
4204
+ const { copy, exportZip } = useDelivery({
4205
+ prepared,
4206
+ title
4207
+ });
4208
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("textarea", {
4209
+ "aria-label": "Brief",
4210
+ className: "dt-field dt-ses-brief",
4211
+ readOnly: true,
4212
+ rows: 12,
4213
+ value: prepared.handoff.brief
4214
+ }), /* @__PURE__ */ jsxs("div", {
4215
+ className: "dt-actions",
4216
+ children: [
4217
+ /* @__PURE__ */ jsx("button", {
4218
+ className: "dt-btn",
4219
+ "data-variant": "primary",
4220
+ onClick: copy,
4221
+ type: "button",
4222
+ children: "Copy brief"
4223
+ }),
4224
+ /* @__PURE__ */ jsx("button", {
4225
+ className: "dt-btn",
4226
+ onClick: exportZip,
4227
+ type: "button",
4228
+ children: "Download .zip"
4229
+ }),
4230
+ /* @__PURE__ */ jsx("button", {
4231
+ className: "dt-btn",
4232
+ onClick: onChangeSelection,
4233
+ type: "button",
4234
+ children: "Edit selection"
4235
+ })
4236
+ ]
4237
+ })] });
4428
4238
  }
4429
- const hasViewport = (frame) => frame.viewportWidth > 0 && frame.viewportHeight > 0;
4430
- const percent = (value, total) => `${value / total * 100}%`;
4431
- function percentBox(rect, frame) {
4432
- if (!hasViewport(frame)) return null;
4433
- return {
4434
- height: percent(rect.height, frame.viewportHeight),
4435
- left: percent(rect.x, frame.viewportWidth),
4436
- top: percent(rect.y, frame.viewportHeight),
4437
- width: percent(rect.width, frame.viewportWidth)
4239
+ function usePrepareHandoff(options) {
4240
+ const { api } = useSessionWorkspace();
4241
+ const run = useSessionAction();
4242
+ const [request, setRequest] = useState(() => ({
4243
+ clientKey: crypto.randomUUID(),
4244
+ selection: initialSelection(options.detail)
4245
+ }));
4246
+ const [prepared, setPrepared] = useState(null);
4247
+ const [isPreparing, setIsPreparing] = useState(false);
4248
+ const prepare = async () => {
4249
+ setIsPreparing(true);
4250
+ const result = await run("The brief was not prepared", () => api.createHandoff({
4251
+ clientKey: request.clientKey,
4252
+ sessionId: options.detail.session.id,
4253
+ ...request.selection
4254
+ }));
4255
+ setIsPreparing(false);
4256
+ if (result) {
4257
+ setPrepared(result);
4258
+ options.review.reload();
4259
+ }
4438
4260
  };
4439
- }
4440
- function percentPoint(point, frame) {
4441
- if (!hasViewport(frame)) return null;
4442
4261
  return {
4443
- left: percent(point.x, frame.viewportWidth),
4444
- top: percent(point.y, frame.viewportHeight)
4262
+ clear: () => setPrepared(null),
4263
+ isPreparing,
4264
+ prepare,
4265
+ prepared,
4266
+ select: (selection) => setRequest({
4267
+ clientKey: crypto.randomUUID(),
4268
+ selection
4269
+ }),
4270
+ selection: request.selection
4445
4271
  };
4446
4272
  }
4447
- function EvidenceThumb({ evidence, isLarge = false }) {
4448
- const { element, frame, image } = evidence;
4449
- if (!image?.url) return null;
4450
- const highlight = element && frame ? percentBox(element.rect, frame) : null;
4451
- return /* @__PURE__ */ jsxs("span", {
4452
- className: "dt-ses-shot",
4453
- "data-large": isLarge,
4454
- children: [/* @__PURE__ */ jsx("img", {
4455
- alt: evidenceCaption(evidence),
4456
- height: image.height,
4457
- src: image.url,
4458
- width: image.width
4459
- }), highlight && /* @__PURE__ */ jsx("span", {
4460
- "aria-hidden": "true",
4461
- className: "dt-ses-shot-mark",
4462
- style: highlight
4463
- })]
4273
+ function SelectionStep({ detail, handoff }) {
4274
+ const count = handoff.selection.proposalIds.length;
4275
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
4276
+ /* @__PURE__ */ jsx("p", {
4277
+ className: "dt-hint",
4278
+ children: "Tick what your agent should get. Nothing leaves Reflet until you copy or download the brief."
4279
+ }),
4280
+ /* @__PURE__ */ jsx(HandoffChoices, {
4281
+ detail,
4282
+ onChange: handoff.select,
4283
+ selection: handoff.selection
4284
+ }),
4285
+ /* @__PURE__ */ jsx(ChangedSinceBriefs, {
4286
+ detail,
4287
+ proposalIds: handoff.selection.proposalIds
4288
+ }),
4289
+ /* @__PURE__ */ jsx("div", {
4290
+ className: "dt-actions",
4291
+ children: /* @__PURE__ */ jsx("button", {
4292
+ className: "dt-btn",
4293
+ "data-variant": "primary",
4294
+ disabled: count === 0 || handoff.isPreparing,
4295
+ onClick: handoff.prepare,
4296
+ type: "button",
4297
+ children: "Build brief"
4298
+ })
4299
+ })
4300
+ ] });
4301
+ }
4302
+ function HandoffView({ detail, review }) {
4303
+ const handoff = usePrepareHandoff({
4304
+ detail,
4305
+ review
4306
+ });
4307
+ return /* @__PURE__ */ jsxs("section", {
4308
+ "aria-labelledby": "rf-handoff-title",
4309
+ className: "dt-ses-handoff",
4310
+ children: [
4311
+ /* @__PURE__ */ jsx("h2", {
4312
+ className: "dt-ses-title",
4313
+ id: "rf-handoff-title",
4314
+ tabIndex: -1,
4315
+ children: "Brief for your agent"
4316
+ }),
4317
+ handoff.prepared ? /* @__PURE__ */ jsx(PreparedBrief, {
4318
+ onChangeSelection: handoff.clear,
4319
+ prepared: handoff.prepared,
4320
+ title: detail.session.title
4321
+ }) : /* @__PURE__ */ jsx(SelectionStep, {
4322
+ detail,
4323
+ handoff
4324
+ }),
4325
+ /* @__PURE__ */ jsx(EarlierBriefs, { handoffs: detail.handoffs })
4326
+ ]
4464
4327
  });
4465
4328
  }
4466
4329
  //#endregion
4467
- //#region src/sessions/ui/review/continuation/intention-history.tsx
4468
- const FIELD_LABELS = {
4469
- need: "Need",
4470
- observed: "Observed",
4471
- response: "Considered response",
4472
- setAside: "Set aside",
4473
- unknowns: "To check"
4474
- };
4475
- function fieldValueText(value) {
4476
- return (Array.isArray(value) ? value.join("; ") : value) || "—";
4477
- }
4478
- function AppliedChanges({ applied }) {
4479
- if (applied.length === 0) return null;
4480
- return /* @__PURE__ */ jsx("ul", {
4481
- className: "dt-ses-rows",
4482
- children: applied.map((change) => /* @__PURE__ */ jsxs("li", {
4483
- className: "dt-ses-row",
4484
- children: [/* @__PURE__ */ jsxs("p", {
4485
- className: "dt-meta",
4486
- children: [
4487
- "Accepted into revision ",
4488
- change.revision,
4489
- " by",
4490
- " ",
4491
- change.byName ?? "a teammate",
4492
- " · ",
4493
- formatDate(change.at)
4494
- ]
4495
- }), /* @__PURE__ */ jsx("ul", {
4496
- className: "dt-ses-list",
4497
- children: change.fields.map((field) => /* @__PURE__ */ jsxs("li", { children: [
4498
- FIELD_LABELS[field.field],
4499
- ": ",
4500
- fieldValueText(field.before),
4501
- " →",
4502
- " ",
4503
- fieldValueText(field.after)
4504
- ] }, field.field))
4505
- })]
4506
- }, change.revision))
4507
- });
4508
- }
4509
- function HistoryExtracts({ evidence, quotes }) {
4510
- return /* @__PURE__ */ jsxs(Fragment, { children: [quotes.map((quote) => /* @__PURE__ */ jsxs("blockquote", {
4511
- className: "dt-ses-moment-quote",
4512
- children: [
4513
- /* @__PURE__ */ jsx("span", {
4514
- className: "dt-ses-time",
4515
- children: formatClock(quote.startMs)
4516
- }),
4517
- ` “${quote.text}”`,
4518
- quote.context.map((line) => /* @__PURE__ */ jsx("span", {
4519
- className: "dt-meta dt-ses-context-line",
4520
- children: line
4521
- }, line))
4522
- ]
4523
- }, quote.segmentId)), evidence.length > 0 && /* @__PURE__ */ jsx("div", {
4524
- className: "dt-ses-thumbs",
4525
- children: evidence.map((item) => /* @__PURE__ */ jsx(EvidenceThumb, { evidence: item }, item.id))
4526
- })] });
4527
- }
4528
- function entryTitle(entry) {
4529
- const { session } = entry;
4530
- return entry.origin === "continuation" ? `Continued in “${session.title}” · ${formatDate(session.startedAt)}` : `Added from “${session.title}” · ${formatDate(session.startedAt)}`;
4531
- }
4532
- function HistoryEntry({ context, entry }) {
4533
- const evidenceIds = new Set(entry.evidenceIds);
4534
- const evidence = context.detail.contributedEvidence.filter((item) => evidenceIds.has(item.id));
4535
- return /* @__PURE__ */ jsxs("li", {
4536
- className: "dt-ses-history",
4537
- "data-removed": entry.detachedAt !== null,
4538
- children: [
4539
- /* @__PURE__ */ jsx("p", {
4540
- className: "dt-ses-subhead",
4541
- children: entryTitle(entry)
4542
- }),
4543
- entry.detachedAt !== null && /* @__PURE__ */ jsxs("p", {
4544
- className: "dt-meta",
4545
- children: [
4546
- "Removed on ",
4547
- formatDate(entry.detachedAt),
4548
- ": what it added was taken back."
4549
- ]
4550
- }),
4551
- /* @__PURE__ */ jsx(AppliedChanges, { applied: entry.applied }),
4552
- entry.extractsHidden ? /* @__PURE__ */ jsx("p", {
4553
- className: "dt-meta",
4554
- children: "Extracts are private to the author"
4555
- }) : /* @__PURE__ */ jsx(HistoryExtracts, {
4556
- evidence,
4557
- quotes: entry.quotes
4558
- }),
4559
- entry.session.canOpen && /* @__PURE__ */ jsx("button", {
4560
- className: "dt-ses-link",
4561
- onClick: () => context.navigation.onOpenSession(entry.session.id),
4562
- type: "button",
4563
- children: "Open session"
4564
- })
4565
- ]
4566
- });
4567
- }
4568
- function IntentionHistory({ context, intentionId }) {
4569
- const entries = context.detail.history.filter((entry) => entry.intentionId === intentionId);
4570
- if (entries.length === 0) return null;
4571
- return /* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("h4", {
4572
- className: "dt-ses-label",
4573
- children: "History"
4574
- }), /* @__PURE__ */ jsx("ul", {
4575
- className: "dt-ses-rows",
4576
- children: entries.map((entry) => /* @__PURE__ */ jsx(HistoryEntry, {
4577
- context,
4578
- entry
4579
- }, entry.id))
4580
- })] });
4581
- }
4582
- //#endregion
4583
- //#region src/sessions/ui/handoff/handoff-changes.tsx
4584
- function differences(proposal, last) {
4585
- return [
4586
- {
4587
- after: proposal.need,
4588
- before: last.need,
4589
- label: "Need"
4590
- },
4591
- {
4592
- after: fieldValueText(proposal.response),
4593
- before: fieldValueText(last.response),
4594
- label: "Considered response"
4595
- },
4596
- {
4597
- after: fieldValueText(proposal.unknowns),
4598
- before: last.unknowns === null ? null : fieldValueText(last.unknowns),
4599
- label: "To check"
4330
+ //#region src/sessions/ui/review/guarded-save.tsx
4331
+ function useGuardedSave() {
4332
+ const { onUnauthorized } = useSessionWorkspace();
4333
+ const [problem, setProblem] = useState(null);
4334
+ const [isSaving, setIsSaving] = useState(false);
4335
+ const save = async (write) => {
4336
+ setIsSaving(true);
4337
+ setProblem(null);
4338
+ try {
4339
+ await write();
4340
+ } catch (error) {
4341
+ if (isUnauthorized(error)) onUnauthorized();
4342
+ else if (error instanceof SessionApiError && error.status === 409) setProblem({ kind: "stale" });
4343
+ else setProblem({
4344
+ kind: "failed",
4345
+ message: failureMessage(error)
4346
+ });
4600
4347
  }
4601
- ].flatMap(({ after, before, label }) => before === null || before === after ? [] : [{
4602
- after,
4603
- before,
4604
- label
4605
- }]);
4606
- }
4607
- function ChangeSummary({ last, proposal }) {
4608
- return /* @__PURE__ */ jsxs("li", {
4609
- className: "dt-ses-row",
4610
- children: [
4611
- /* @__PURE__ */ jsx("p", {
4612
- className: "dt-ses-subhead",
4613
- children: proposal.need
4614
- }),
4615
- /* @__PURE__ */ jsxs("p", {
4616
- className: "dt-meta",
4617
- children: [
4618
- "Changed since the brief of ",
4619
- formatDate(last.at),
4620
- " (revision",
4621
- " ",
4622
- last.revision,
4623
- " → ",
4624
- proposal.revision,
4625
- ")"
4626
- ]
4627
- }),
4628
- /* @__PURE__ */ jsx("ul", {
4629
- className: "dt-ses-list",
4630
- children: differences(proposal, last).map((difference) => /* @__PURE__ */ jsxs("li", { children: [
4631
- difference.label,
4632
- ": ",
4633
- difference.before,
4634
- " → ",
4635
- difference.after
4636
- ] }, difference.label))
4637
- })
4638
- ]
4639
- });
4640
- }
4641
- function ChangedSinceBriefs({ detail, proposalIds }) {
4642
- const selected = new Set(proposalIds);
4643
- const changed = detail.proposals.flatMap((proposal) => {
4644
- const last = proposal.lastHandoff;
4645
- return selected.has(proposal.id) && last && last.revision < proposal.revision ? [{
4646
- last,
4647
- proposal
4648
- }] : [];
4649
- });
4650
- if (changed.length === 0) return null;
4651
- return /* @__PURE__ */ jsx("ul", {
4652
- className: "dt-ses-rows",
4653
- children: changed.map(({ last, proposal }) => /* @__PURE__ */ jsx(ChangeSummary, {
4654
- last,
4655
- proposal
4656
- }, proposal.id))
4657
- });
4658
- }
4659
- function deliveryLine(handoff) {
4660
- const kinds = new Set(handoff.deliveries.map((delivery) => delivery.kind));
4661
- if (kinds.size === 0) return "not copied or exported";
4662
- return [...kinds].join(" and ");
4663
- }
4664
- function intentionsLine(handoff) {
4665
- const [only, ...others] = handoff.proposals;
4666
- if (only && others.length === 0) return `“${only.need}” revision ${only.revision}`;
4667
- return countLabel(handoff.proposals.length, "intention");
4348
+ setIsSaving(false);
4349
+ };
4350
+ return {
4351
+ isSaving,
4352
+ problem,
4353
+ save
4354
+ };
4668
4355
  }
4669
- function PreviousBriefs({ handoffs }) {
4670
- if (handoffs.length === 0) return null;
4671
- return /* @__PURE__ */ jsxs("section", {
4672
- className: "dt-ses-form",
4673
- children: [/* @__PURE__ */ jsx("h3", {
4674
- className: "dt-ses-label",
4675
- children: "Previous briefs"
4676
- }), /* @__PURE__ */ jsx("ul", {
4677
- className: "dt-ses-rows",
4678
- children: handoffs.map((handoff) => {
4679
- const date = formatDate(handoff.createdAt);
4680
- return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("details", { children: [/* @__PURE__ */ jsxs("summary", { children: [
4681
- date,
4682
- " · ",
4683
- intentionsLine(handoff),
4684
- " · ",
4685
- deliveryLine(handoff)
4686
- ] }), /* @__PURE__ */ jsx("textarea", {
4687
- "aria-label": `Brief of ${date}`,
4688
- className: "dt-field dt-ses-brief",
4689
- readOnly: true,
4690
- rows: 8,
4691
- value: handoff.brief
4692
- })] }) }, handoff.id);
4693
- })
4356
+ function SaveProblemMessage({ onReload, problem }) {
4357
+ return /* @__PURE__ */ jsxs("div", {
4358
+ className: "dt-actions",
4359
+ role: "alert",
4360
+ children: [/* @__PURE__ */ jsx("p", {
4361
+ className: "dt-error",
4362
+ children: problem.kind === "stale" ? "Someone changed this since you opened it. Reload it, then edit again." : `Not saved: ${problem.message}`
4363
+ }), problem.kind === "stale" && /* @__PURE__ */ jsx("button", {
4364
+ className: "dt-btn",
4365
+ onClick: onReload,
4366
+ type: "button",
4367
+ children: "Reload"
4694
4368
  })]
4695
4369
  });
4696
4370
  }
4697
4371
  //#endregion
4698
- //#region src/sessions/ui/handoff/handoff-selection.tsx
4699
- function keptProposals(detail) {
4700
- return detail.proposals.filter((proposal) => proposal.decision === "kept" && proposal.mergedIntoId === null);
4701
- }
4702
- function evidenceOf(detail, proposals) {
4703
- const intentionIds = new Set(proposals.map((proposal) => proposal.id));
4704
- const contributed = detail.history.filter((entry) => intentionIds.has(entry.intentionId) && entry.detachedAt === null);
4705
- const cited = /* @__PURE__ */ new Set([...proposals.flatMap((proposal) => proposal.evidenceIds), ...contributed.flatMap((entry) => entry.evidenceIds)]);
4706
- return [...detail.evidence, ...detail.contributedEvidence].filter((item) => cited.has(item.id) && item.status === "available");
4707
- }
4708
- function initialSelection(detail) {
4709
- const proposals = keptProposals(detail);
4372
+ //#region src/sessions/ui/review/use-session-detail.ts
4373
+ const POLL_INTERVAL_MS = 2e3;
4374
+ function nextRequest(current) {
4710
4375
  return {
4711
- evidenceIds: evidenceOf(detail, proposals).map((item) => item.id),
4712
- proposalIds: proposals.map((proposal) => proposal.id)
4376
+ ...current,
4377
+ count: current.count + 1
4713
4378
  };
4714
4379
  }
4715
- function toggled(ids, id) {
4716
- return ids.includes(id) ? ids.filter((other) => other !== id) : [...ids, id];
4380
+ function withProposal(detail, proposal) {
4381
+ const isKnown = detail.proposals.some((item) => item.id === proposal.id);
4382
+ return {
4383
+ ...detail,
4384
+ proposals: isKnown ? detail.proposals.map((item) => item.id === proposal.id ? proposal : item) : [...detail.proposals, proposal]
4385
+ };
4717
4386
  }
4718
- function CheckRow({ isChecked, label, onToggle }) {
4719
- return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("label", {
4720
- className: "dt-ses-check",
4721
- children: [/* @__PURE__ */ jsx("input", {
4722
- checked: isChecked,
4723
- onChange: onToggle,
4724
- type: "checkbox"
4725
- }), /* @__PURE__ */ jsx("span", { children: label })]
4726
- }) });
4387
+ function withSegment(detail, segment) {
4388
+ const others = detail.segments.filter((item) => item.id !== segment.id);
4389
+ return {
4390
+ ...detail,
4391
+ segments: [...others, segment].sort((a, b) => a.startMs - b.startMs)
4392
+ };
4727
4393
  }
4728
- function ChoiceGroup({ children, legend }) {
4729
- return /* @__PURE__ */ jsxs("fieldset", {
4730
- className: "dt-ses-choices",
4731
- children: [/* @__PURE__ */ jsx("legend", {
4732
- className: "dt-ses-label",
4733
- children: legend
4734
- }), /* @__PURE__ */ jsx("ul", {
4735
- className: "dt-ses-list",
4736
- children
4737
- })]
4738
- });
4394
+ function withEvidence(detail, evidence) {
4395
+ return {
4396
+ ...detail,
4397
+ evidence: detail.evidence.map((item) => item.id === evidence.id ? evidence : item)
4398
+ };
4739
4399
  }
4740
- function HandoffChoices({ detail, onChange, selection }) {
4741
- const proposals = keptProposals(detail);
4742
- const evidence = evidenceOf(detail, proposals);
4743
- const toggleProposal = (id) => onChange({
4744
- ...selection,
4745
- proposalIds: toggled(selection.proposalIds, id)
4746
- });
4747
- const toggleEvidence = (id) => onChange({
4748
- ...selection,
4749
- evidenceIds: toggled(selection.evidenceIds, id)
4750
- });
4751
- const chosenProposalIds = new Set(selection.proposalIds);
4752
- const chosenEvidenceIds = new Set(selection.evidenceIds);
4753
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ChoiceGroup, {
4754
- legend: "Intentions",
4755
- children: proposals.map((proposal) => /* @__PURE__ */ jsx(CheckRow, {
4756
- isChecked: chosenProposalIds.has(proposal.id),
4757
- label: proposal.need,
4758
- onToggle: () => toggleProposal(proposal.id)
4759
- }, proposal.id))
4760
- }), evidence.length > 0 && /* @__PURE__ */ jsx(ChoiceGroup, {
4761
- legend: "Evidence",
4762
- children: evidence.map((item) => /* @__PURE__ */ jsx(CheckRow, {
4763
- isChecked: chosenEvidenceIds.has(item.id),
4764
- label: `${formatClock(item.atMs)} · ${evidenceCaption(item)}`,
4765
- onToggle: () => toggleEvidence(item.id)
4766
- }, item.id))
4767
- })] });
4400
+ function isHidden() {
4401
+ return typeof document !== "undefined" && document.visibilityState === "hidden";
4768
4402
  }
4769
- //#endregion
4770
- //#region src/sessions/ui/handoff/handoff-view.tsx
4771
- function useDelivery(options) {
4772
- const { api } = useSessionWorkspace();
4773
- const run = useSessionAction();
4774
- const { files, handoff } = options.prepared;
4775
- const deliver = async (kind, send) => {
4776
- try {
4777
- await send();
4778
- } catch (error) {
4779
- reportFailure(`The brief was not ${kind}`, error);
4780
- return;
4781
- }
4782
- announce(kind === "copied" ? "Brief copied" : "Brief exported");
4783
- await run("The delivery was not recorded", () => api.markHandoffDelivered({
4784
- handoffId: handoff.id,
4785
- kind
4786
- }));
4787
- };
4403
+ function usePollTick(lastAnswer) {
4404
+ const [tick, setTick] = useState(0);
4405
+ useEffect(() => {
4406
+ if (lastAnswer.kind !== "ready" || !isDetailProcessing(lastAnswer.detail)) return;
4407
+ let timer = 0;
4408
+ const schedule = () => {
4409
+ window.clearTimeout(timer);
4410
+ if (!isHidden()) timer = window.setTimeout(() => setTick((current) => current + 1), POLL_INTERVAL_MS);
4411
+ };
4412
+ schedule();
4413
+ document.addEventListener("visibilitychange", schedule);
4414
+ return () => {
4415
+ window.clearTimeout(timer);
4416
+ document.removeEventListener("visibilitychange", schedule);
4417
+ };
4418
+ }, [lastAnswer]);
4419
+ return tick;
4420
+ }
4421
+ function failedState(error, current) {
4422
+ if (error instanceof SessionApiError && error.status === 404) return { kind: "missing" };
4423
+ if (current.kind === "ready") return current;
4788
4424
  return {
4789
- copy: () => deliver("copied", () => copyToClipboard(Promise.resolve(handoff.brief))),
4790
- exportZip: () => deliver("exported", async () => {
4791
- saveBlob({
4792
- blob: await buildBriefZip({
4793
- brief: handoff.brief,
4794
- files
4795
- }),
4796
- fileName: zipFileName(options.title)
4797
- });
4798
- })
4425
+ kind: "failed",
4426
+ message: failureMessage(error)
4799
4427
  };
4800
4428
  }
4801
- function PreparedBrief({ onChangeSelection, prepared, title }) {
4802
- const { copy, exportZip } = useDelivery({
4803
- prepared,
4804
- title
4429
+ function useLandedUploadTick() {
4430
+ const { account } = useSessionWorkspace();
4431
+ const { pendingOperations } = useSyncStatus(account.key);
4432
+ const [seen, setSeen] = useState({
4433
+ pending: pendingOperations,
4434
+ tick: 0
4805
4435
  });
4806
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("textarea", {
4807
- "aria-label": "Brief",
4808
- className: "dt-field dt-ses-brief",
4809
- readOnly: true,
4810
- rows: 12,
4811
- value: prepared.handoff.brief
4812
- }), /* @__PURE__ */ jsxs("div", {
4813
- className: "dt-actions",
4814
- children: [
4815
- /* @__PURE__ */ jsx("button", {
4816
- className: "dt-btn",
4817
- "data-variant": "primary",
4818
- onClick: copy,
4819
- type: "button",
4820
- children: "Copy brief"
4821
- }),
4822
- /* @__PURE__ */ jsx("button", {
4823
- className: "dt-btn",
4824
- onClick: exportZip,
4825
- type: "button",
4826
- children: "Export .zip"
4827
- }),
4828
- /* @__PURE__ */ jsx("button", {
4829
- className: "dt-btn",
4830
- onClick: onChangeSelection,
4831
- type: "button",
4832
- children: "Change selection"
4833
- })
4834
- ]
4835
- })] });
4436
+ if (seen.pending !== pendingOperations) setSeen({
4437
+ pending: pendingOperations,
4438
+ tick: pendingOperations < seen.pending ? seen.tick + 1 : seen.tick
4439
+ });
4440
+ return seen.tick;
4836
4441
  }
4837
- function useInternalFeedback(proposalIds) {
4838
- const { api } = useSessionWorkspace();
4839
- const run = useSessionAction();
4840
- return async () => {
4841
- if ((await Promise.all(proposalIds.map((proposalId) => run("The feedback was not created", () => api.createFeedback(proposalId))))).every((result) => result !== null)) announce("Internal feedback created — agents don't pick it up from the queue; hand off the brief instead.");
4442
+ function useSessionDetail(sessionId) {
4443
+ const { api, onUnauthorized } = useSessionWorkspace();
4444
+ const [state, setState] = useState({ kind: "loading" });
4445
+ const [request, setRequest] = useState({
4446
+ count: 0,
4447
+ sessionId
4448
+ });
4449
+ const pollTick = usePollTick(state);
4450
+ const uploadTick = useLandedUploadTick();
4451
+ useEffect(() => {
4452
+ let isCurrent = true;
4453
+ api.detail(request.sessionId).then((detail) => isCurrent && setState({
4454
+ detail,
4455
+ kind: "ready"
4456
+ }), (error) => {
4457
+ if (isUnauthorized(error)) {
4458
+ onUnauthorized();
4459
+ return;
4460
+ }
4461
+ if (isCurrent) setState((current) => failedState(error, current));
4462
+ const isRefresh = request.count + pollTick + uploadTick > 0;
4463
+ if (isCurrent && isRefresh) reportFailure("The session did not refresh", error);
4464
+ });
4465
+ return () => {
4466
+ isCurrent = false;
4467
+ };
4468
+ }, [
4469
+ api,
4470
+ onUnauthorized,
4471
+ request,
4472
+ pollTick,
4473
+ uploadTick
4474
+ ]);
4475
+ return {
4476
+ patch: (update) => setState((current) => current.kind === "ready" ? {
4477
+ detail: update(current.detail),
4478
+ kind: "ready"
4479
+ } : current),
4480
+ reload: () => setRequest(nextRequest),
4481
+ state
4842
4482
  };
4843
4483
  }
4844
- function usePrepareHandoff(options) {
4845
- const { api } = useSessionWorkspace();
4846
- const run = useSessionAction();
4847
- const [request, setRequest] = useState(() => ({
4848
- clientKey: crypto.randomUUID(),
4849
- selection: initialSelection(options.detail)
4850
- }));
4851
- const [prepared, setPrepared] = useState(null);
4852
- const [isPreparing, setIsPreparing] = useState(false);
4853
- const prepare = async () => {
4854
- setIsPreparing(true);
4855
- const result = await run("The brief was not prepared", () => api.createHandoff({
4856
- clientKey: request.clientKey,
4857
- sessionId: options.detail.session.id,
4858
- ...request.selection
4859
- }));
4860
- setIsPreparing(false);
4861
- if (result) {
4862
- setPrepared(result);
4863
- options.review.reload();
4864
- }
4865
- };
4484
+ //#endregion
4485
+ //#region src/sessions/ui/review/proposals/proposal-editor.tsx
4486
+ const MAX_LIST_ITEMS = 8;
4487
+ function lines(text) {
4488
+ return text.split("\n").map((line) => line.trim()).filter((line) => line !== "").slice(0, MAX_LIST_ITEMS);
4489
+ }
4490
+ function editBody(proposal, draft) {
4866
4491
  return {
4867
- clear: () => setPrepared(null),
4868
- isPreparing,
4869
- prepare,
4870
- prepared,
4871
- select: (selection) => setRequest({
4872
- clientKey: crypto.randomUUID(),
4873
- selection
4874
- }),
4875
- selection: request.selection
4876
- };
4877
- }
4878
- function SelectionStep({ detail, handoff }) {
4879
- const createFeedback = useInternalFeedback(handoff.selection.proposalIds);
4880
- const hasProposals = handoff.selection.proposalIds.length > 0;
4881
- return /* @__PURE__ */ jsxs(Fragment, { children: [
4882
- /* @__PURE__ */ jsx("p", {
4883
- className: "dt-hint",
4884
- children: "Coding agents get exactly the brief and files you pick here."
4885
- }),
4886
- /* @__PURE__ */ jsx(HandoffChoices, {
4887
- detail,
4888
- onChange: handoff.select,
4889
- selection: handoff.selection
4890
- }),
4891
- /* @__PURE__ */ jsx(ChangedSinceBriefs, {
4892
- detail,
4893
- proposalIds: handoff.selection.proposalIds
4894
- }),
4895
- /* @__PURE__ */ jsxs("div", {
4896
- className: "dt-actions",
4897
- children: [/* @__PURE__ */ jsx("button", {
4898
- className: "dt-btn",
4899
- "data-variant": "primary",
4900
- disabled: !hasProposals || handoff.isPreparing,
4901
- onClick: handoff.prepare,
4902
- type: "button",
4903
- children: "Prepare brief"
4904
- }), /* @__PURE__ */ jsx("button", {
4905
- className: "dt-btn",
4906
- disabled: !hasProposals,
4907
- onClick: createFeedback,
4908
- type: "button",
4909
- children: "Create internal feedback"
4910
- })]
4911
- })
4912
- ] });
4913
- }
4914
- function HandoffView({ detail, onClose, review }) {
4915
- const handoff = usePrepareHandoff({
4916
- detail,
4917
- review
4918
- });
4919
- return /* @__PURE__ */ jsxs("section", {
4920
- "aria-labelledby": "rf-handoff-title",
4921
- className: "dt-ses-handoff",
4922
- children: [
4923
- /* @__PURE__ */ jsx("button", {
4924
- className: "dt-ses-link",
4925
- onClick: onClose,
4926
- type: "button",
4927
- children: "← Back to review"
4928
- }),
4929
- /* @__PURE__ */ jsx("h2", {
4930
- className: "dt-ses-title",
4931
- id: "rf-handoff-title",
4932
- children: "Hand off"
4933
- }),
4934
- handoff.prepared ? /* @__PURE__ */ jsx(PreparedBrief, {
4935
- onChangeSelection: handoff.clear,
4936
- prepared: handoff.prepared,
4937
- title: detail.session.title
4938
- }) : /* @__PURE__ */ jsx(SelectionStep, {
4939
- detail,
4940
- handoff
4941
- }),
4942
- /* @__PURE__ */ jsx("p", {
4943
- className: "dt-meta",
4944
- children: "Copied or exported briefs can't be recalled."
4945
- }),
4946
- /* @__PURE__ */ jsx(PreviousBriefs, { handoffs: detail.handoffs })
4947
- ]
4948
- });
4949
- }
4950
- //#endregion
4951
- //#region src/sessions/ui/review/guarded-save.tsx
4952
- function useGuardedSave() {
4953
- const { onUnauthorized } = useSessionWorkspace();
4954
- const [problem, setProblem] = useState(null);
4955
- const [isSaving, setIsSaving] = useState(false);
4956
- const save = async (write) => {
4957
- setIsSaving(true);
4958
- setProblem(null);
4959
- try {
4960
- await write();
4961
- } catch (error) {
4962
- if (isUnauthorized(error)) onUnauthorized();
4963
- else if (error instanceof SessionApiError && error.status === 409) setProblem({ kind: "stale" });
4964
- else setProblem({
4965
- kind: "failed",
4966
- message: failureMessage(error)
4967
- });
4968
- }
4969
- setIsSaving(false);
4970
- };
4971
- return {
4972
- isSaving,
4973
- problem,
4974
- save
4975
- };
4976
- }
4977
- function SaveProblemMessage({ onReload, problem }) {
4978
- return /* @__PURE__ */ jsxs("div", {
4979
- className: "dt-actions",
4980
- role: "alert",
4981
- children: [/* @__PURE__ */ jsx("p", {
4982
- className: "dt-error",
4983
- children: problem.kind === "stale" ? "Someone changed this since you opened it. Reload it, then edit again." : `Not saved: ${problem.message}`
4984
- }), problem.kind === "stale" && /* @__PURE__ */ jsx("button", {
4985
- className: "dt-btn",
4986
- onClick: onReload,
4987
- type: "button",
4988
- children: "Reload"
4989
- })]
4990
- });
4991
- }
4992
- //#endregion
4993
- //#region src/sessions/ui/review/use-session-detail.ts
4994
- const POLL_INTERVAL_MS = 2e3;
4995
- function nextRequest(current) {
4996
- return {
4997
- ...current,
4998
- count: current.count + 1
4999
- };
5000
- }
5001
- function withProposal(detail, proposal) {
5002
- const isKnown = detail.proposals.some((item) => item.id === proposal.id);
5003
- return {
5004
- ...detail,
5005
- proposals: isKnown ? detail.proposals.map((item) => item.id === proposal.id ? proposal : item) : [...detail.proposals, proposal]
5006
- };
5007
- }
5008
- function withSegment(detail, segment) {
5009
- const others = detail.segments.filter((item) => item.id !== segment.id);
5010
- return {
5011
- ...detail,
5012
- segments: [...others, segment].sort((a, b) => a.startMs - b.startMs)
5013
- };
5014
- }
5015
- function withEvidence(detail, evidence) {
5016
- return {
5017
- ...detail,
5018
- evidence: detail.evidence.map((item) => item.id === evidence.id ? evidence : item)
5019
- };
5020
- }
5021
- function isHidden() {
5022
- return typeof document !== "undefined" && document.visibilityState === "hidden";
5023
- }
5024
- function usePollTick(lastAnswer) {
5025
- const [tick, setTick] = useState(0);
5026
- useEffect(() => {
5027
- if (lastAnswer.kind !== "ready" || !isDetailProcessing(lastAnswer.detail)) return;
5028
- let timer = 0;
5029
- const schedule = () => {
5030
- window.clearTimeout(timer);
5031
- if (!isHidden()) timer = window.setTimeout(() => setTick((current) => current + 1), POLL_INTERVAL_MS);
5032
- };
5033
- schedule();
5034
- document.addEventListener("visibilitychange", schedule);
5035
- return () => {
5036
- window.clearTimeout(timer);
5037
- document.removeEventListener("visibilitychange", schedule);
5038
- };
5039
- }, [lastAnswer]);
5040
- return tick;
5041
- }
5042
- function failedState(error, current) {
5043
- if (error instanceof SessionApiError && error.status === 404) return { kind: "missing" };
5044
- if (current.kind === "ready") return current;
5045
- return {
5046
- kind: "failed",
5047
- message: failureMessage(error)
5048
- };
5049
- }
5050
- function useLandedUploadTick() {
5051
- const { account } = useSessionWorkspace();
5052
- const { pendingOperations } = useSyncStatus(account.key);
5053
- const [seen, setSeen] = useState({
5054
- pending: pendingOperations,
5055
- tick: 0
5056
- });
5057
- if (seen.pending !== pendingOperations) setSeen({
5058
- pending: pendingOperations,
5059
- tick: pendingOperations < seen.pending ? seen.tick + 1 : seen.tick
5060
- });
5061
- return seen.tick;
5062
- }
5063
- function useSessionDetail(sessionId) {
5064
- const { api, onUnauthorized } = useSessionWorkspace();
5065
- const [state, setState] = useState({ kind: "loading" });
5066
- const [request, setRequest] = useState({
5067
- count: 0,
5068
- sessionId
5069
- });
5070
- const pollTick = usePollTick(state);
5071
- const uploadTick = useLandedUploadTick();
5072
- useEffect(() => {
5073
- let isCurrent = true;
5074
- api.detail(request.sessionId).then((detail) => isCurrent && setState({
5075
- detail,
5076
- kind: "ready"
5077
- }), (error) => {
5078
- if (isUnauthorized(error)) {
5079
- onUnauthorized();
5080
- return;
5081
- }
5082
- if (isCurrent) setState((current) => failedState(error, current));
5083
- const isRefresh = request.count + pollTick + uploadTick > 0;
5084
- if (isCurrent && isRefresh) reportFailure("The session did not refresh", error);
5085
- });
5086
- return () => {
5087
- isCurrent = false;
5088
- };
5089
- }, [
5090
- api,
5091
- onUnauthorized,
5092
- request,
5093
- pollTick,
5094
- uploadTick
5095
- ]);
5096
- return {
5097
- patch: (update) => setState((current) => current.kind === "ready" ? {
5098
- detail: update(current.detail),
5099
- kind: "ready"
5100
- } : current),
5101
- reload: () => setRequest(nextRequest),
5102
- state
5103
- };
5104
- }
5105
- //#endregion
5106
- //#region src/sessions/ui/review/proposals/proposal-editor.tsx
5107
- const MAX_LIST_ITEMS = 8;
5108
- function lines(text) {
5109
- return text.split("\n").map((line) => line.trim()).filter((line) => line !== "").slice(0, MAX_LIST_ITEMS);
5110
- }
5111
- function editBody(proposal, draft) {
5112
- return {
5113
- expectedRevision: proposal.revision,
5114
- need: draft.need.trim(),
5115
- observed: draft.observed.trim(),
5116
- proposalId: proposal.id,
5117
- response: draft.response.trim() || null,
5118
- setAside: lines(draft.setAside),
5119
- unknowns: lines(draft.unknowns)
4492
+ expectedRevision: proposal.revision,
4493
+ need: draft.need.trim(),
4494
+ observed: draft.observed.trim(),
4495
+ proposalId: proposal.id,
4496
+ response: draft.response.trim() || null,
4497
+ setAside: lines(draft.setAside),
4498
+ unknowns: lines(draft.unknowns)
5120
4499
  };
5121
4500
  }
5122
4501
  function TextField({ label, maxLength, onChange, rows, value }) {
@@ -5880,7 +5259,7 @@ function UpdateCard({ context, continuation }) {
5880
5259
  }
5881
5260
  //#endregion
5882
5261
  //#region src/sessions/ui/review/transcript/remove-evidence.tsx
5883
- const REMOVE_COPY = "Remove this screenshot? The image is deleted for good. Briefs you already copied or exported can't be recalled.";
5262
+ const REMOVE_COPY = "Remove this screenshot? The image is deleted for good. Briefs you already downloaded keep their copy.";
5884
5263
  function useFocusWhen(shouldFocus) {
5885
5264
  const ref = useRef(null);
5886
5265
  useEffect(() => {
@@ -6412,7 +5791,7 @@ function MomentHeading({ onClose, segment }) {
6412
5791
  })]
6413
5792
  });
6414
5793
  }
6415
- function useEscapeToClose(onClose) {
5794
+ function useEscapeToClose$1(onClose) {
6416
5795
  const sectionRef = useRef(null);
6417
5796
  const onCloseRef = useRef(onClose);
6418
5797
  useLayoutEffect(() => {
@@ -6434,7 +5813,7 @@ function useEscapeToClose(onClose) {
6434
5813
  function MomentView({ handlers, segmentId, session }) {
6435
5814
  const { detail, review } = session;
6436
5815
  const segment = detail.segments.find((item) => item.id === segmentId);
6437
- const sectionRef = useEscapeToClose(handlers.onClose);
5816
+ const sectionRef = useEscapeToClose$1(handlers.onClose);
6438
5817
  return /* @__PURE__ */ jsxs("section", {
6439
5818
  "aria-label": "Moment",
6440
5819
  className: "dt-ses-moment",
@@ -6873,6 +6252,50 @@ function ScreenReport({ context, report }) {
6873
6252
  })] });
6874
6253
  }
6875
6254
  //#endregion
6255
+ //#region src/devtools/client/devtools-icons.tsx
6256
+ function NotesIcon() {
6257
+ return /* @__PURE__ */ jsx(Svg, { children: /* @__PURE__ */ jsx("path", { d: "M8 6h13M8 12h13M8 18h13M3.5 6h.01M3.5 12h.01M3.5 18h.01" }) });
6258
+ }
6259
+ function InboxIcon() {
6260
+ return /* @__PURE__ */ jsxs(Svg, { children: [/* @__PURE__ */ jsx("path", { d: "M22 12h-6l-2 3h-4l-2-3H2" }), /* @__PURE__ */ jsx("path", { d: "M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" })] });
6261
+ }
6262
+ function PanelIcon() {
6263
+ return /* @__PURE__ */ jsxs(Svg, { children: [/* @__PURE__ */ jsx("rect", {
6264
+ height: "16",
6265
+ rx: "2.5",
6266
+ width: "18",
6267
+ x: "3",
6268
+ y: "4"
6269
+ }), /* @__PURE__ */ jsx("path", { d: "M14 4v16" })] });
6270
+ }
6271
+ function CodeIcon() {
6272
+ return /* @__PURE__ */ jsx(Svg, { children: /* @__PURE__ */ jsx("path", { d: "m8 7-5 5 5 5M16 7l5 5-5 5" }) });
6273
+ }
6274
+ function RefreshIcon() {
6275
+ return /* @__PURE__ */ jsx(Svg, { children: /* @__PURE__ */ jsx("path", { d: "M20 11a8 8 0 0 0-14.5-4.5L4 8M4 4v4h4M4 13a8 8 0 0 0 14.5 4.5L20 16M20 20v-4h-4" }) });
6276
+ }
6277
+ function ChevronLeftIcon() {
6278
+ return /* @__PURE__ */ jsx(Svg, { children: /* @__PURE__ */ jsx("path", { d: "m15 6-6 6 6 6" }) });
6279
+ }
6280
+ function ChevronUpDownIcon() {
6281
+ return /* @__PURE__ */ jsx(Svg, { children: /* @__PURE__ */ jsx("path", { d: "m8 10 4-4 4 4M8 14l4 4 4-4" }) });
6282
+ }
6283
+ function ViewOptionsIcon() {
6284
+ return /* @__PURE__ */ jsxs(Svg, { children: [
6285
+ /* @__PURE__ */ jsx("path", { d: "M4 7h10M18 7h2M4 17h2M10 17h10" }),
6286
+ /* @__PURE__ */ jsx("circle", {
6287
+ cx: "16",
6288
+ cy: "7",
6289
+ r: "2"
6290
+ }),
6291
+ /* @__PURE__ */ jsx("circle", {
6292
+ cx: "8",
6293
+ cy: "17",
6294
+ r: "2"
6295
+ })
6296
+ ] });
6297
+ }
6298
+ //#endregion
6876
6299
  //#region src/sessions/ui/start/session-disclosure.tsx
6877
6300
  const SEEN_KEY_PREFIX = "reflet-sessions-disclosure:";
6878
6301
  function hasSeenDisclosure(accountKey) {
@@ -6960,21 +6383,22 @@ function StartProblem({ failure, onRetry, onWriteNote }) {
6960
6383
  })]
6961
6384
  });
6962
6385
  }
6963
- function StartActions({ isBusy, onStart, onWriteNote }) {
6386
+ function StartActions({ isBusy, onStart }) {
6964
6387
  return /* @__PURE__ */ jsxs("div", {
6965
- className: "dt-actions",
6966
- children: [/* @__PURE__ */ jsx("button", {
6967
- className: "dt-btn",
6968
- "data-variant": "primary",
6969
- disabled: isBusy,
6970
- onClick: onStart,
6971
- type: "button",
6972
- children: "Start session"
6973
- }), onWriteNote && /* @__PURE__ */ jsx("button", {
6974
- className: "dt-btn",
6975
- onClick: onWriteNote,
6976
- type: "button",
6977
- children: "Write a note instead"
6388
+ className: "dt-ses-start",
6389
+ children: [/* @__PURE__ */ jsx("p", {
6390
+ className: "dt-meta",
6391
+ children: "Talk while you use the app. Reflet turns what you say and show into intentions you can brief an agent with."
6392
+ }), /* @__PURE__ */ jsx("div", {
6393
+ className: "dt-actions",
6394
+ children: /* @__PURE__ */ jsxs("button", {
6395
+ className: "dt-btn",
6396
+ "data-variant": "primary",
6397
+ disabled: isBusy,
6398
+ onClick: onStart,
6399
+ type: "button",
6400
+ children: [/* @__PURE__ */ jsx(MicIcon, {}), "Start session"]
6401
+ })
6978
6402
  })]
6979
6403
  });
6980
6404
  }
@@ -7006,19 +6430,21 @@ function MicrophonePrompt({ isRequesting }) {
7006
6430
  function StartSession({ onStarted, onWriteNote }) {
7007
6431
  const { account } = useSessionWorkspace();
7008
6432
  const { failure, isBusy, isRequesting, start } = useRecorderStart(onStarted);
6433
+ const [isFirstVisit] = useState(() => !hasSeenDisclosure(account.key));
7009
6434
  const startBlank = () => start({});
6435
+ const disclosure = /* @__PURE__ */ jsx(SessionDisclosure, { accountKey: account.key });
7010
6436
  return /* @__PURE__ */ jsxs(Fragment, { children: [
7011
- /* @__PURE__ */ jsx(SessionDisclosure, { accountKey: account.key }),
7012
- /* @__PURE__ */ jsx(MicrophonePrompt, { isRequesting }),
6437
+ isFirstVisit && disclosure,
7013
6438
  failure ? /* @__PURE__ */ jsx(StartProblem, {
7014
6439
  failure,
7015
6440
  onRetry: startBlank,
7016
6441
  onWriteNote
7017
6442
  }) : /* @__PURE__ */ jsx(StartActions, {
7018
6443
  isBusy,
7019
- onStart: startBlank,
7020
- onWriteNote
7021
- })
6444
+ onStart: startBlank
6445
+ }),
6446
+ /* @__PURE__ */ jsx(MicrophonePrompt, { isRequesting }),
6447
+ !isFirstVisit && disclosure
7022
6448
  ] });
7023
6449
  }
7024
6450
  //#endregion
@@ -7103,15 +6529,44 @@ function useDecide(review) {
7103
6529
  });
7104
6530
  };
7105
6531
  }
7106
- function useProposalActions(review) {
7107
- const save = useProposalSave(review);
6532
+ function withFeedbackIds(feedbackIds) {
6533
+ return (detail) => ({
6534
+ ...detail,
6535
+ proposals: detail.proposals.map((item) => {
6536
+ const feedbackId = feedbackIds.get(item.id);
6537
+ return feedbackId ? {
6538
+ ...item,
6539
+ feedbackId
6540
+ } : item;
6541
+ })
6542
+ });
6543
+ }
6544
+ function useAddToBoard(review) {
6545
+ const { api } = useSessionWorkspace();
6546
+ const run = useSessionAction();
6547
+ return async (proposals) => {
6548
+ const created = await Promise.all(proposals.map(async (proposal) => {
6549
+ const result = await run(`${quotedNeed(proposal.need)} was not added to the board`, () => api.createFeedback(proposal.id));
6550
+ return result ? [proposal.id, result.feedbackId] : null;
6551
+ }));
6552
+ const added = new Map(created.flatMap((entry) => entry ? [entry] : []));
6553
+ if (added.size === 0) return;
6554
+ review.patch(withFeedbackIds(added));
6555
+ const onlyAdded = added.size === 1 ? proposals.find((proposal) => added.has(proposal.id)) : void 0;
6556
+ announce(onlyAdded ? `Added ${quotedNeed(onlyAdded.need)} to the board` : `Added ${countLabel(added.size, "intention")} to the board`);
6557
+ };
6558
+ }
6559
+ function useProposalActions(review) {
6560
+ const save = useProposalSave(review);
6561
+ const decide = useDecide(review);
7108
6562
  return {
6563
+ addToBoard: useAddToBoard(review),
7109
6564
  clarify: (proposal, text) => save("The precision was not sent", (client) => client.clarifyProposal({
7110
6565
  expectedRevision: proposal.revision,
7111
6566
  proposalId: proposal.id,
7112
6567
  text
7113
6568
  })),
7114
- decide: useDecide(review),
6569
+ decide,
7115
6570
  merge: async (targetId, sourceIds) => {
7116
6571
  if (await save("The intentions were not merged", (client) => client.mergeProposals({
7117
6572
  sourceIds,
@@ -7129,7 +6584,7 @@ function useProposalActions(review) {
7129
6584
  }
7130
6585
  //#endregion
7131
6586
  //#region src/sessions/ui/review/proposals/delete-intention.tsx
7132
- const DELETE_INTENTION_COPY = "Delete this intention? What later sessions added to it is deleted too, along with Reflet's copies of the briefs that included it. Briefs already copied or exported can't be recalled.";
6587
+ 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.";
7133
6588
  function DeleteConfirmation$1({ context, onCancel, proposal }) {
7134
6589
  const { api } = useSessionWorkspace();
7135
6590
  const run = useSessionAction();
@@ -7640,8 +7095,8 @@ const ICON_DECISIONS = [{
7640
7095
  function ProposalChips({ proposal }) {
7641
7096
  const decisionChip = DECISION_CHIPS[proposal.decision];
7642
7097
  const { handedOffRevision } = proposal;
7643
- let handoffChip = null;
7644
- if (handedOffRevision !== null) handoffChip = proposal.revision > handedOffRevision ? "Changed since handoff" : `Handed off rev ${handedOffRevision}`;
7098
+ let briefChip = null;
7099
+ if (handedOffRevision !== null) briefChip = proposal.revision > handedOffRevision ? "Changed since brief" : "In a brief";
7645
7100
  return /* @__PURE__ */ jsxs("div", {
7646
7101
  className: "dt-ses-chips",
7647
7102
  children: [
@@ -7662,9 +7117,13 @@ function ProposalChips({ proposal }) {
7662
7117
  "data-tone": "accent",
7663
7118
  children: "To re-examine"
7664
7119
  }),
7665
- handoffChip && /* @__PURE__ */ jsx("span", {
7120
+ briefChip && /* @__PURE__ */ jsx("span", {
7666
7121
  className: "dt-chip",
7667
- children: handoffChip
7122
+ children: briefChip
7123
+ }),
7124
+ proposal.feedbackId !== null && /* @__PURE__ */ jsx("span", {
7125
+ className: "dt-chip",
7126
+ children: "On the board"
7668
7127
  })
7669
7128
  ]
7670
7129
  });
@@ -7741,6 +7200,15 @@ function DecisionButtons({ context, onEdit, proposal }) {
7741
7200
  title: "Edit",
7742
7201
  type: "button",
7743
7202
  children: /* @__PURE__ */ jsx(PencilIcon, {})
7203
+ }),
7204
+ proposal.feedbackId === null && proposal.decision !== "dismissed" && /* @__PURE__ */ jsx("button", {
7205
+ "aria-label": "Add to board",
7206
+ className: "dt-btn",
7207
+ "data-icon-only": true,
7208
+ onClick: () => context.actions.addToBoard([proposal]),
7209
+ title: "Add to board",
7210
+ type: "button",
7211
+ children: /* @__PURE__ */ jsx(InboxIcon, {})
7744
7212
  })
7745
7213
  ]
7746
7214
  });
@@ -7896,13 +7364,6 @@ function ProposalCard({ context, proposal }) {
7896
7364
  }
7897
7365
  //#endregion
7898
7366
  //#region src/sessions/ui/review/proposals/proposals-tab.tsx
7899
- const VIEW_OPTIONS = [{
7900
- label: "On screen",
7901
- value: "screens"
7902
- }, {
7903
- label: "Details",
7904
- value: "cards"
7905
- }];
7906
7367
  function emptyCopy(detail) {
7907
7368
  const { session } = detail;
7908
7369
  if (session.collection !== "ended") return "Intentions appear here once the recording has uploaded and Reflet has read it.";
@@ -7971,25 +7432,12 @@ function AddProposalForm({ context, onDone }) {
7971
7432
  ]
7972
7433
  });
7973
7434
  }
7974
- function ViewSwitch({ onSelect, view }) {
7975
- return /* @__PURE__ */ jsx("div", {
7976
- className: "dt-tabs",
7977
- children: VIEW_OPTIONS.map((option) => /* @__PURE__ */ jsx("button", {
7978
- "aria-pressed": option.value === view,
7979
- className: "dt-tab",
7980
- onClick: () => onSelect(option.value),
7981
- type: "button",
7982
- children: option.label
7983
- }, option.value))
7984
- });
7985
- }
7986
- function ProposalsTab({ context, onSelectView, view }) {
7435
+ function ProposalsTab({ context, view }) {
7987
7436
  const [isAdding, setIsAdding] = useState(false);
7988
7437
  const proposals = context.detail.proposals.filter((proposal) => proposal.mergedIntoId === null);
7989
7438
  const { continuation } = context.detail;
7990
7439
  const report = calloutReport(context.detail);
7991
- const hasScreens = report.screens.length > 0;
7992
- const showsScreens = hasScreens && view === "screens";
7440
+ const showsScreens = report.screens.length > 0 && view === "screens";
7993
7441
  return /* @__PURE__ */ jsxs(Fragment, { children: [
7994
7442
  continuation && /* @__PURE__ */ jsx(UpdateCard, {
7995
7443
  context,
@@ -7999,10 +7447,6 @@ function ProposalsTab({ context, onSelectView, view }) {
7999
7447
  className: "dt-empty",
8000
7448
  children: emptyCopy(context.detail)
8001
7449
  }),
8002
- hasScreens && /* @__PURE__ */ jsx(ViewSwitch, {
8003
- onSelect: onSelectView,
8004
- view
8005
- }),
8006
7450
  showsScreens && /* @__PURE__ */ jsx(ScreenReport, {
8007
7451
  context,
8008
7452
  report
@@ -8026,8 +7470,199 @@ function ProposalsTab({ context, onSelectView, view }) {
8026
7470
  ] });
8027
7471
  }
8028
7472
  //#endregion
7473
+ //#region src/sessions/ui/review/review-footer.tsx
7474
+ function ReviewFooter({ context, onOpenBrief }) {
7475
+ const confirmed = keptProposals(context.detail);
7476
+ const offBoard = confirmed.filter((proposal) => proposal.feedbackId === null);
7477
+ return /* @__PURE__ */ jsxs("div", {
7478
+ className: "dt-sheet-foot",
7479
+ children: [
7480
+ /* @__PURE__ */ jsxs("button", {
7481
+ "aria-describedby": confirmed.length === 0 ? "rf-brief-hint" : void 0,
7482
+ className: "dt-btn",
7483
+ "data-variant": "primary",
7484
+ disabled: confirmed.length === 0,
7485
+ onClick: onOpenBrief,
7486
+ type: "button",
7487
+ children: ["Prepare brief", confirmed.length > 0 && /* @__PURE__ */ jsx("span", {
7488
+ className: "dt-btn-count",
7489
+ children: confirmed.length
7490
+ })]
7491
+ }),
7492
+ offBoard.length > 0 && /* @__PURE__ */ jsxs("button", {
7493
+ className: "dt-btn",
7494
+ onClick: () => context.actions.addToBoard(offBoard),
7495
+ type: "button",
7496
+ children: [
7497
+ "Add ",
7498
+ offBoard.length,
7499
+ " to board"
7500
+ ]
7501
+ }),
7502
+ confirmed.length === 0 && /* @__PURE__ */ jsx("span", {
7503
+ className: "dt-status",
7504
+ id: "rf-brief-hint",
7505
+ children: "Confirm an intention to brief your agent or add it to the board"
7506
+ })
7507
+ ]
7508
+ });
7509
+ }
7510
+ //#endregion
7511
+ //#region src/devtools/client/focused-element.ts
7512
+ function focusedElementAround(node) {
7513
+ const scope = node.getRootNode();
7514
+ return scope instanceof ShadowRoot || scope instanceof Document ? scope.activeElement : null;
7515
+ }
7516
+ //#endregion
7517
+ //#region src/devtools/client/menu.tsx
7518
+ const MENU_ITEM_SELECTOR = "[role^=\"menuitem\"]";
7519
+ function nextIndex(key, current, count) {
7520
+ if (key === "ArrowDown") return (current + 1) % count;
7521
+ if (key === "ArrowUp") return (current - 1 + count) % count;
7522
+ if (key === "Home") return 0;
7523
+ if (key === "End") return count - 1;
7524
+ return null;
7525
+ }
7526
+ function useCloseOnOutsidePointer(containerRef, options) {
7527
+ const closeRef = useRef(options.close);
7528
+ useLayoutEffect(() => {
7529
+ closeRef.current = options.close;
7530
+ });
7531
+ const { isOpen } = options;
7532
+ useEffect(() => {
7533
+ if (!isOpen) return;
7534
+ const closeOnOutsidePointer = (event) => {
7535
+ const container = containerRef.current;
7536
+ if (container && !event.composedPath().includes(container)) closeRef.current();
7537
+ };
7538
+ document.addEventListener("pointerdown", closeOnOutsidePointer, true);
7539
+ return () => document.removeEventListener("pointerdown", closeOnOutsidePointer, true);
7540
+ }, [containerRef, isOpen]);
7541
+ }
7542
+ function useFocusOnOpen(listRef, isOpen) {
7543
+ useLayoutEffect(() => {
7544
+ const list = listRef.current;
7545
+ if (!(isOpen && list)) return;
7546
+ const items = [...list.querySelectorAll(MENU_ITEM_SELECTOR)];
7547
+ (items.find((item) => item.getAttribute("aria-checked") === "true") ?? items[0])?.focus({ preventScroll: true });
7548
+ }, [isOpen, listRef]);
7549
+ }
7550
+ /** Native: the panel's Escape listener shares the shadow root with React's, and runs before React's handler. */
7551
+ function useEscapeToClose(listRef, options) {
7552
+ const { isOpen, onEscape } = options;
7553
+ useEffect(() => {
7554
+ const list = listRef.current;
7555
+ if (!(isOpen && list)) return;
7556
+ const closeOnEscape = (event) => {
7557
+ if (event.key === "Escape" && !event.isComposing) {
7558
+ event.stopPropagation();
7559
+ onEscape();
7560
+ }
7561
+ };
7562
+ list.addEventListener("keydown", closeOnEscape);
7563
+ return () => list.removeEventListener("keydown", closeOnEscape);
7564
+ }, [
7565
+ isOpen,
7566
+ listRef,
7567
+ onEscape
7568
+ ]);
7569
+ }
7570
+ function MenuItemButton({ hasChoices, item, onChosen }) {
7571
+ const select = () => {
7572
+ onChosen();
7573
+ item.onSelect();
7574
+ };
7575
+ if (!hasChoices) return /* @__PURE__ */ jsx("button", {
7576
+ className: "dt-menu-item",
7577
+ "data-variant": item.variant,
7578
+ onClick: select,
7579
+ role: "menuitem",
7580
+ tabIndex: -1,
7581
+ type: "button",
7582
+ children: item.label
7583
+ });
7584
+ return /* @__PURE__ */ jsxs("button", {
7585
+ "aria-checked": item.checked === true,
7586
+ className: "dt-menu-item",
7587
+ onClick: select,
7588
+ role: "menuitemradio",
7589
+ tabIndex: -1,
7590
+ type: "button",
7591
+ children: [/* @__PURE__ */ jsx("span", {
7592
+ className: "dt-menu-check",
7593
+ children: item.checked && /* @__PURE__ */ jsx(CheckIcon, { size: 14 })
7594
+ }), item.label]
7595
+ });
7596
+ }
7597
+ function Menu({ items, placement = "below", trigger }) {
7598
+ const [isOpen, setIsOpen] = useState(false);
7599
+ const menuId = useId();
7600
+ const containerRef = useRef(null);
7601
+ const triggerRef = useRef(null);
7602
+ const listRef = useRef(null);
7603
+ const close = () => setIsOpen(false);
7604
+ const closeAndRefocus = () => {
7605
+ close();
7606
+ triggerRef.current?.focus({ preventScroll: true });
7607
+ };
7608
+ useCloseOnOutsidePointer(containerRef, {
7609
+ close,
7610
+ isOpen
7611
+ });
7612
+ useFocusOnOpen(listRef, isOpen);
7613
+ useEscapeToClose(listRef, {
7614
+ isOpen,
7615
+ onEscape: closeAndRefocus
7616
+ });
7617
+ const hasChoices = items.some((item) => item.checked !== void 0);
7618
+ const moveWithKeys = (event) => {
7619
+ if (event.key === "Tab") {
7620
+ close();
7621
+ return;
7622
+ }
7623
+ const list = event.currentTarget;
7624
+ const options = [...list.querySelectorAll(MENU_ITEM_SELECTOR)];
7625
+ const focused = focusedElementAround(list);
7626
+ const current = focused instanceof HTMLElement ? options.indexOf(focused) : -1;
7627
+ const index = nextIndex(event.key, current, options.length);
7628
+ if (index !== null) {
7629
+ event.preventDefault();
7630
+ options[index]?.focus();
7631
+ }
7632
+ };
7633
+ return /* @__PURE__ */ jsxs("div", {
7634
+ className: "dt-menu",
7635
+ ref: containerRef,
7636
+ children: [/* @__PURE__ */ jsx("button", {
7637
+ "aria-controls": isOpen ? menuId : void 0,
7638
+ "aria-expanded": isOpen,
7639
+ "aria-haspopup": "menu",
7640
+ "aria-label": trigger.label,
7641
+ className: trigger.className,
7642
+ onClick: () => setIsOpen(!isOpen),
7643
+ ref: triggerRef,
7644
+ title: trigger.label,
7645
+ type: "button",
7646
+ children: trigger.content
7647
+ }), isOpen && /* @__PURE__ */ jsx("div", {
7648
+ "aria-label": trigger.label,
7649
+ className: "dt-menu-list",
7650
+ "data-placement": placement,
7651
+ id: menuId,
7652
+ onKeyDown: moveWithKeys,
7653
+ ref: listRef,
7654
+ role: "menu",
7655
+ children: items.map((item) => /* @__PURE__ */ jsx(MenuItemButton, {
7656
+ hasChoices,
7657
+ item,
7658
+ onChosen: closeAndRefocus
7659
+ }, item.label))
7660
+ })]
7661
+ });
7662
+ }
7663
+ //#endregion
8029
7664
  //#region src/sessions/ui/review/review-header.tsx
8030
- const DELETE_COPY = "Delete this session? Its recordings, transcript and intentions are removed, including what later sessions added to those intentions. Briefs you already copied or exported can't be recalled.";
7665
+ const DELETE_COPY = "Delete this session? Its recording, transcript, screenshots and intentions go, along with what later sessions added to those intentions.";
8031
7666
  function withSessionFields(change) {
8032
7667
  return (detail) => ({
8033
7668
  ...detail,
@@ -8124,94 +7759,29 @@ function DeleteConfirmation({ onCancel, onDeleted, sessionId }) {
8124
7759
  })]
8125
7760
  });
8126
7761
  }
8127
- function useDismissOnOutside(isOpen, close) {
8128
- const containerRef = useRef(null);
8129
- const closeRef = useRef(close);
8130
- useLayoutEffect(() => {
8131
- closeRef.current = close;
8132
- });
8133
- useEffect(() => {
8134
- if (!isOpen) return;
8135
- const container = containerRef.current;
8136
- const closeOnOutsidePointer = (event) => {
8137
- if (container && !event.composedPath().includes(container)) closeRef.current();
8138
- };
8139
- const closeOnEscape = (event) => {
8140
- if (event instanceof KeyboardEvent && event.key === "Escape") {
8141
- event.stopPropagation();
8142
- closeRef.current();
8143
- }
8144
- };
8145
- document.addEventListener("pointerdown", closeOnOutsidePointer, true);
8146
- container?.addEventListener("keydown", closeOnEscape);
8147
- return () => {
8148
- document.removeEventListener("pointerdown", closeOnOutsidePointer, true);
8149
- container?.removeEventListener("keydown", closeOnEscape);
8150
- };
8151
- }, [isOpen]);
8152
- return containerRef;
8153
- }
8154
- function SessionMenu({ actions, isShared }) {
8155
- const [isOpen, setIsOpen] = useState(false);
8156
- const menuId = useId();
8157
- const close = () => setIsOpen(false);
8158
- const containerRef = useDismissOnOutside(isOpen, close);
8159
- const choose = (action) => () => {
8160
- close();
8161
- action();
8162
- };
8163
- return /* @__PURE__ */ jsxs("div", {
8164
- className: "dt-ses-menu",
8165
- ref: containerRef,
8166
- children: [/* @__PURE__ */ jsx("button", {
8167
- "aria-controls": menuId,
8168
- "aria-expanded": isOpen,
8169
- "aria-label": "Session options",
8170
- className: "icon-btn",
8171
- onClick: () => setIsOpen(!isOpen),
8172
- type: "button",
8173
- children: /* @__PURE__ */ jsx(MoreIcon, {})
8174
- }), isOpen && /* @__PURE__ */ jsxs("div", {
8175
- className: "dt-ses-menu-list",
8176
- id: menuId,
8177
- children: [
8178
- /* @__PURE__ */ jsx("button", {
8179
- className: "dt-ses-menu-item",
8180
- onClick: choose(actions.onRename),
8181
- type: "button",
8182
- children: "Rename"
8183
- }),
8184
- /* @__PURE__ */ jsx("button", {
8185
- className: "dt-ses-menu-item",
8186
- onClick: choose(actions.onToggleSharing),
8187
- type: "button",
8188
- children: isShared ? "Make private" : "Share with team"
8189
- }),
8190
- /* @__PURE__ */ jsx("button", {
8191
- className: "dt-ses-menu-item",
8192
- "data-variant": "danger",
8193
- onClick: choose(actions.onDelete),
8194
- type: "button",
8195
- children: "Delete session"
8196
- })
8197
- ]
8198
- })]
8199
- });
8200
- }
8201
- function ReviewHeader({ onBack, review, session }) {
7762
+ function ReviewHeader({ onDeleted, review, session }) {
8202
7763
  const [isRenaming, setIsRenaming] = useState(false);
8203
7764
  const [isConfirmingDelete, setIsConfirmingDelete] = useState(false);
8204
7765
  const update = useSessionUpdate(review, session);
8205
7766
  const isShared = session.visibility === "team";
7767
+ const menuItems = [
7768
+ {
7769
+ label: "Rename",
7770
+ onSelect: () => setIsRenaming(true)
7771
+ },
7772
+ {
7773
+ label: isShared ? "Make private" : "Share with team",
7774
+ onSelect: () => update("The sharing was not changed", { visibility: isShared ? "private" : "team" })
7775
+ },
7776
+ {
7777
+ label: "Delete session",
7778
+ onSelect: () => setIsConfirmingDelete(true),
7779
+ variant: "danger"
7780
+ }
7781
+ ];
8206
7782
  return /* @__PURE__ */ jsxs("header", {
8207
7783
  className: "dt-ses-head",
8208
7784
  children: [
8209
- /* @__PURE__ */ jsx("button", {
8210
- className: "dt-ses-link",
8211
- onClick: onBack,
8212
- type: "button",
8213
- children: "← All sessions"
8214
- }),
8215
7785
  isRenaming ? /* @__PURE__ */ jsx(TitleEditor, {
8216
7786
  onDone: () => setIsRenaming(false),
8217
7787
  review,
@@ -8220,14 +7790,15 @@ function ReviewHeader({ onBack, review, session }) {
8220
7790
  className: "dt-card-head",
8221
7791
  children: [/* @__PURE__ */ jsx("h2", {
8222
7792
  className: "dt-ses-title",
7793
+ tabIndex: -1,
8223
7794
  children: session.title
8224
- }), session.canEdit && /* @__PURE__ */ jsx(SessionMenu, {
8225
- actions: {
8226
- onDelete: () => setIsConfirmingDelete(true),
8227
- onRename: () => setIsRenaming(true),
8228
- onToggleSharing: () => update("The sharing was not changed", { visibility: isShared ? "private" : "team" })
8229
- },
8230
- isShared
7795
+ }), session.canEdit && /* @__PURE__ */ jsx(Menu, {
7796
+ items: menuItems,
7797
+ trigger: {
7798
+ className: "icon-btn",
7799
+ content: /* @__PURE__ */ jsx(MoreIcon, {}),
7800
+ label: "Session options"
7801
+ }
8231
7802
  })]
8232
7803
  }),
8233
7804
  /* @__PURE__ */ jsxs("p", {
@@ -8244,13 +7815,83 @@ function ReviewHeader({ onBack, review, session }) {
8244
7815
  }),
8245
7816
  isConfirmingDelete && session.canEdit && /* @__PURE__ */ jsx(DeleteConfirmation, {
8246
7817
  onCancel: () => setIsConfirmingDelete(false),
8247
- onDeleted: onBack,
7818
+ onDeleted,
8248
7819
  sessionId: session.id
8249
7820
  })
8250
7821
  ]
8251
7822
  });
8252
7823
  }
8253
7824
  //#endregion
7825
+ //#region src/sessions/ui/review/review-tabs.tsx
7826
+ const INTENTION_VIEWS = [{
7827
+ label: "On screen",
7828
+ value: "screens"
7829
+ }, {
7830
+ label: "Details",
7831
+ value: "cards"
7832
+ }];
7833
+ function IntentionViewMenu({ place }) {
7834
+ return /* @__PURE__ */ jsx(Menu, {
7835
+ items: INTENTION_VIEWS.map((option) => ({
7836
+ checked: option.value === place.intentionView,
7837
+ label: option.label,
7838
+ onSelect: () => place.selectIntentionView(option.value)
7839
+ })),
7840
+ trigger: {
7841
+ className: "icon-btn",
7842
+ content: /* @__PURE__ */ jsx(ViewOptionsIcon, {}),
7843
+ label: "Show intentions"
7844
+ }
7845
+ });
7846
+ }
7847
+ function tabOptions(detail) {
7848
+ return [
7849
+ {
7850
+ count: detail.proposals.filter((proposal) => proposal.mergedIntoId === null && proposal.attachment === null).length,
7851
+ icon: /* @__PURE__ */ jsx(IntentionIcon, {}),
7852
+ label: "Intentions",
7853
+ value: "proposals"
7854
+ },
7855
+ {
7856
+ count: null,
7857
+ icon: /* @__PURE__ */ jsx(TranscriptIcon, {}),
7858
+ label: "Transcript",
7859
+ value: "transcript"
7860
+ },
7861
+ {
7862
+ count: detail.evidence.length,
7863
+ icon: /* @__PURE__ */ jsx(ImageIcon, {}),
7864
+ label: "Evidence",
7865
+ value: "evidence"
7866
+ }
7867
+ ];
7868
+ }
7869
+ function ReviewTabs({ detail, place }) {
7870
+ const canPickView = place.tab === "proposals" && calloutReport(detail).screens.length > 0;
7871
+ return /* @__PURE__ */ jsxs("div", {
7872
+ className: "dt-toolbar",
7873
+ children: [/* @__PURE__ */ jsx("div", {
7874
+ className: "dt-tabs",
7875
+ role: "tablist",
7876
+ children: tabOptions(detail).map((option) => /* @__PURE__ */ jsxs("button", {
7877
+ "aria-selected": option.value === place.tab,
7878
+ className: "dt-tab",
7879
+ onClick: () => place.selectTab(option.value),
7880
+ role: "tab",
7881
+ type: "button",
7882
+ children: [
7883
+ option.icon,
7884
+ option.label,
7885
+ option.count !== null && /* @__PURE__ */ jsx("span", {
7886
+ className: "dt-tab-count",
7887
+ children: option.count
7888
+ })
7889
+ ]
7890
+ }, option.value))
7891
+ }), canPickView && /* @__PURE__ */ jsx(IntentionViewMenu, { place })]
7892
+ });
7893
+ }
7894
+ //#endregion
8254
7895
  //#region src/sessions/ui/review/status-strip.tsx
8255
7896
  function transcribingLine(detail) {
8256
7897
  const voice = detail.segments.filter((segment) => segment.source === "voice");
@@ -8995,56 +8636,10 @@ function TranscriptTab({ detail, onShowMoment, review }) {
8995
8636
  }
8996
8637
  //#endregion
8997
8638
  //#region src/sessions/ui/review/session-review.tsx
8998
- function HandoffFooter({ detail, onHandOff }) {
8999
- const confirmed = detail.proposals.filter((proposal) => proposal.decision === "kept" && proposal.mergedIntoId === null).length;
9000
- return /* @__PURE__ */ jsxs("div", {
9001
- className: "dt-sheet-foot",
9002
- children: [/* @__PURE__ */ jsx("button", {
9003
- "aria-describedby": confirmed === 0 ? "rf-handoff-hint" : void 0,
9004
- className: "dt-btn",
9005
- "data-variant": "primary",
9006
- disabled: confirmed === 0,
9007
- onClick: onHandOff,
9008
- type: "button",
9009
- children: confirmed === 0 ? "Hand off" : `Hand off ${confirmed} confirmed`
9010
- }), confirmed === 0 && /* @__PURE__ */ jsx("span", {
9011
- className: "dt-status",
9012
- id: "rf-handoff-hint",
9013
- children: "Confirm an intention to hand it off"
9014
- })]
9015
- });
9016
- }
9017
- function ReviewTabs({ detail, onSelect, tab }) {
9018
- const options = [
9019
- {
9020
- label: `Intentions (${detail.proposals.filter((proposal) => proposal.mergedIntoId === null && proposal.attachment === null).length})`,
9021
- value: "proposals"
9022
- },
9023
- {
9024
- label: "Transcript",
9025
- value: "transcript"
9026
- },
9027
- {
9028
- label: `Evidence (${detail.evidence.length})`,
9029
- value: "evidence"
9030
- }
9031
- ];
9032
- return /* @__PURE__ */ jsx("div", {
9033
- className: "dt-tabs dt-ses-tabs",
9034
- children: options.map((option) => /* @__PURE__ */ jsx("button", {
9035
- "aria-pressed": option.value === tab,
9036
- className: "dt-tab",
9037
- onClick: () => onSelect(option.value),
9038
- type: "button",
9039
- children: option.label
9040
- }, option.value))
9041
- });
9042
- }
9043
8639
  function ReviewTabPanel({ context, place }) {
9044
8640
  const { detail, navigation, review } = context;
9045
8641
  if (place.tab === "proposals") return /* @__PURE__ */ jsx(ProposalsTab, {
9046
8642
  context,
9047
- onSelectView: place.selectIntentionView,
9048
8643
  view: place.intentionView
9049
8644
  });
9050
8645
  if (place.tab === "transcript") return /* @__PURE__ */ jsx(TranscriptTab, {
@@ -9103,8 +8698,7 @@ function ReviewPanels({ context, place }) {
9103
8698
  return /* @__PURE__ */ jsxs(Fragment, { children: [
9104
8699
  /* @__PURE__ */ jsx(ReviewTabs, {
9105
8700
  detail,
9106
- onSelect: place.selectTab,
9107
- tab: place.tab
8701
+ place
9108
8702
  }),
9109
8703
  /* @__PURE__ */ jsx("div", {
9110
8704
  className: "dt-ses-panel",
@@ -9132,7 +8726,7 @@ function ReviewTop({ context, links }) {
9132
8726
  const { detail, review } = context;
9133
8727
  return /* @__PURE__ */ jsxs(Fragment, { children: [
9134
8728
  /* @__PURE__ */ jsx(ReviewHeader, {
9135
- onBack: links.onBack,
8729
+ onDeleted: links.onDeleted,
9136
8730
  review,
9137
8731
  session: detail.session
9138
8732
  }),
@@ -9146,9 +8740,8 @@ function ReviewTop({ context, links }) {
9146
8740
  })
9147
8741
  ] });
9148
8742
  }
9149
- function ReviewScreen({ detail, links, review }) {
8743
+ function ReviewScreen({ detail, isBriefOpen, links, review }) {
9150
8744
  const place = useReviewPlace();
9151
- const [isHandingOff, setIsHandingOff] = useState(false);
9152
8745
  const context = {
9153
8746
  actions: useProposalActions(review),
9154
8747
  canEdit: detail.session.canEdit,
@@ -9162,9 +8755,8 @@ function ReviewScreen({ detail, links, review }) {
9162
8755
  },
9163
8756
  review
9164
8757
  };
9165
- if (isHandingOff) return /* @__PURE__ */ jsx(HandoffView, {
8758
+ if (isBriefOpen) return /* @__PURE__ */ jsx(HandoffView, {
9166
8759
  detail,
9167
- onClose: () => setIsHandingOff(false),
9168
8760
  review
9169
8761
  });
9170
8762
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -9176,13 +8768,13 @@ function ReviewScreen({ detail, links, review }) {
9176
8768
  context,
9177
8769
  place
9178
8770
  }),
9179
- context.canEdit && /* @__PURE__ */ jsx(HandoffFooter, {
9180
- detail,
9181
- onHandOff: () => setIsHandingOff(true)
8771
+ context.canEdit && /* @__PURE__ */ jsx(ReviewFooter, {
8772
+ context,
8773
+ onOpenBrief: links.onOpenBrief
9182
8774
  })
9183
8775
  ] });
9184
8776
  }
9185
- function SessionReview({ links, sessionId }) {
8777
+ function SessionReview({ isBriefOpen, links, sessionId }) {
9186
8778
  const handle = useSessionDetail(sessionId);
9187
8779
  const { state } = handle;
9188
8780
  if (state.kind === "loading") return /* @__PURE__ */ jsx("p", {
@@ -9191,24 +8783,22 @@ function SessionReview({ links, sessionId }) {
9191
8783
  });
9192
8784
  if (state.kind === "ready") return /* @__PURE__ */ jsx(ReviewScreen, {
9193
8785
  detail: state.detail,
8786
+ isBriefOpen,
9194
8787
  links,
9195
8788
  review: handle
9196
8789
  });
8790
+ if (state.kind === "missing") return /* @__PURE__ */ jsx("p", {
8791
+ className: "dt-empty",
8792
+ children: "This session is gone: it was deleted or is no longer shared with you."
8793
+ });
9197
8794
  return /* @__PURE__ */ jsxs("div", {
9198
8795
  className: "dt-empty",
9199
- children: [/* @__PURE__ */ jsx("p", { children: state.kind === "missing" ? "This session is gone: it was deleted or is no longer shared with you." : `The session did not load: ${state.message}` }), /* @__PURE__ */ jsxs("div", {
9200
- className: "dt-actions",
9201
- children: [state.kind === "failed" && /* @__PURE__ */ jsx("button", {
9202
- className: "dt-btn",
9203
- onClick: handle.reload,
9204
- type: "button",
9205
- children: "Try again"
9206
- }), /* @__PURE__ */ jsx("button", {
9207
- className: "dt-btn",
9208
- onClick: links.onBack,
9209
- type: "button",
9210
- children: "All sessions"
9211
- })]
8796
+ role: "alert",
8797
+ children: [/* @__PURE__ */ jsxs("p", { children: ["The session did not load: ", state.message] }), /* @__PURE__ */ jsx("button", {
8798
+ className: "dt-btn",
8799
+ onClick: handle.reload,
8800
+ type: "button",
8801
+ children: "Try again"
9212
8802
  })]
9213
8803
  });
9214
8804
  }
@@ -9335,7 +8925,7 @@ function SessionList({ drafts, onOpen }) {
9335
8925
  interruptedIds,
9336
8926
  onOpen,
9337
8927
  sessions: mine,
9338
- title: "Mine"
8928
+ title: "Your sessions"
9339
8929
  }),
9340
8930
  /* @__PURE__ */ jsx(SessionGroup, {
9341
8931
  interruptedIds,
@@ -9398,17 +8988,7 @@ function SessionsHome({ onOpenSession, onStarted, onWriteNote }) {
9398
8988
  const { account } = useSessionWorkspace();
9399
8989
  const drafts = useLocalDrafts(account.key);
9400
8990
  const interrupted = drafts.filter((draft) => draft.state === "interrupted");
9401
- const name = account.userName ?? account.userEmail ?? "you";
9402
8991
  return /* @__PURE__ */ jsxs(Fragment, { children: [
9403
- /* @__PURE__ */ jsxs("p", {
9404
- className: "dt-meta",
9405
- children: [
9406
- "Recording as ",
9407
- name,
9408
- " · ",
9409
- account.organizationName
9410
- ]
9411
- }),
9412
8992
  interrupted.map((draft) => /* @__PURE__ */ jsx(InterruptedDraft, {
9413
8993
  draft,
9414
8994
  onOpenSession,
@@ -9426,6 +9006,32 @@ function SessionsHome({ onOpenSession, onStarted, onWriteNote }) {
9426
9006
  }
9427
9007
  //#endregion
9428
9008
  //#region src/sessions/ui/sessions-panel.tsx
9009
+ const SESSIONS_HOME = { view: "home" };
9010
+ function useSessionNavigation() {
9011
+ const [place, setPlace] = useState(SESSIONS_HOME);
9012
+ return {
9013
+ go: setPlace,
9014
+ place
9015
+ };
9016
+ }
9017
+ /** Where the panel header's back button leads; null on the sessions home. */
9018
+ function sessionSheetPlace({ go, place }) {
9019
+ if (place.view === "home") return null;
9020
+ const sessionRef = place.target.sessionId ?? place.target.sessionKey ?? "";
9021
+ if (place.view === "review") return {
9022
+ backLabel: "Sessions",
9023
+ id: `review:${sessionRef}`,
9024
+ onBack: () => go(SESSIONS_HOME)
9025
+ };
9026
+ return {
9027
+ backLabel: "Session",
9028
+ id: `brief:${sessionRef}`,
9029
+ onBack: () => go({
9030
+ target: place.target,
9031
+ view: "review"
9032
+ })
9033
+ };
9034
+ }
9429
9035
  function useResolvedSessionId(target) {
9430
9036
  const { account } = useSessionWorkspace();
9431
9037
  const drafts = useLocalDrafts(account.key);
@@ -9434,36 +9040,822 @@ function useResolvedSessionId(target) {
9434
9040
  }
9435
9041
  /** A just-finished session may not have reached Reflet yet: it opens once its id is known. */
9436
9042
  function SessionsPanel({ navigation, onStarted, onWriteNote }) {
9437
- const sessionId = useResolvedSessionId(navigation.open);
9438
- const back = () => navigation.onOpen(null);
9439
- if (sessionId) return /* @__PURE__ */ jsx(SessionReview, {
9440
- links: {
9441
- onBack: back,
9442
- onOpenSession: (openedId) => navigation.onOpen({
9443
- sessionId: openedId,
9444
- sessionKey: null
9445
- }),
9446
- onStarted
9447
- },
9448
- sessionId
9449
- }, sessionId);
9450
- if (navigation.open) return /* @__PURE__ */ jsxs("div", {
9043
+ const { go, place } = navigation;
9044
+ const target = place.view === "home" ? null : place.target;
9045
+ const sessionId = useResolvedSessionId(target);
9046
+ if (!target) return /* @__PURE__ */ jsx(SessionsHome, {
9047
+ onOpenSession: (opened) => go({
9048
+ target: opened,
9049
+ view: "review"
9050
+ }),
9051
+ onStarted,
9052
+ onWriteNote
9053
+ });
9054
+ if (!sessionId) return /* @__PURE__ */ jsx("p", {
9451
9055
  className: "dt-empty",
9452
9056
  role: "status",
9453
- children: [/* @__PURE__ */ jsx("p", { children: "This session is still uploading. It opens here once it reaches Reflet." }), /* @__PURE__ */ jsx("button", {
9454
- className: "dt-btn",
9455
- onClick: back,
9456
- type: "button",
9457
- children: "All sessions"
9057
+ children: "This session is still uploading. It opens here once it reaches Reflet."
9058
+ });
9059
+ return /* @__PURE__ */ jsx(SessionReview, {
9060
+ isBriefOpen: place.view === "brief",
9061
+ links: {
9062
+ onDeleted: () => go(SESSIONS_HOME),
9063
+ onOpenBrief: () => go({
9064
+ target,
9065
+ view: "brief"
9066
+ }),
9067
+ onOpenSession: (openedId) => go({
9068
+ target: {
9069
+ sessionId: openedId,
9070
+ sessionKey: null
9071
+ },
9072
+ view: "review"
9073
+ }),
9074
+ onStarted
9075
+ },
9076
+ sessionId
9077
+ }, sessionId);
9078
+ }
9079
+ //#endregion
9080
+ //#region src/sessions/ui/intention-styles.ts
9081
+ const INTENTION_STYLES = `
9082
+ .dt-ses-panel { display: contents; }
9083
+ .dt-ses-panel[hidden] { display: none; }
9084
+ .dt-ses-moment, .dt-ses-moment-section, .dt-ses-block, .dt-ses-change, .dt-ses-history { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
9085
+ .dt-ses-moment-item { display: flex; flex-direction: column; gap: 6px; padding: 8px 10px; border-radius: var(--dt-radius-card); background: var(--rf-bg-subtle); }
9086
+ .dt-ses-moment-line { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 0; overflow-wrap: anywhere; }
9087
+ .dt-ses-moment-quote { display: flex; flex-direction: column; gap: 2px; margin: 0; padding-left: 10px; border-left: 2px solid var(--rf-border); overflow-wrap: anywhere; }
9088
+ .dt-ses-moment-notices { color: var(--rf-text-muted); font-size: 12px; }
9089
+ .dt-ses-moment-shot { display: flex; flex-direction: column; gap: 4px; margin: 0; max-width: 100%; }
9090
+ .dt-ses-shot-mark[data-match="approximate"] { border-style: dashed; }
9091
+ .dt-ses-shot-pointer {
9092
+ position: absolute; width: 10px; height: 10px; margin: -5px 0 0 -5px; border-radius: 50%;
9093
+ background: var(--rf-accent); box-shadow: 0 0 0 2px var(--rf-bg); pointer-events: none;
9094
+ }
9095
+ .dt-ses-source { margin: 0; font-size: 12px; overflow-wrap: anywhere; }
9096
+ .dt-ses-context-line { display: block; }
9097
+ .dt-ses-attached { flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px; }
9098
+ .dt-ses-attached p { margin: 0; }
9099
+ .dt-ses-history[data-removed="true"] > .dt-ses-subhead { color: var(--rf-text-muted); text-decoration: line-through; }
9100
+ .dt-ses-change { padding: 8px 10px; border-radius: var(--dt-radius-card); background: var(--rf-bg-subtle); list-style: none; }
9101
+ .dt-ses-change[data-decision="rejected"] .dt-ses-diff { color: var(--rf-text-muted); text-decoration: line-through; }
9102
+ .dt-ses-diff { display: grid; grid-template-columns: auto 1fr; gap: 2px 8px; margin: 0; overflow-wrap: anywhere; }
9103
+ p.dt-ses-diff { display: block; }
9104
+ .dt-ses-diff dd { margin: 0; }
9105
+ .dt-card:focus-visible { outline: 2px solid var(--rf-accent); outline-offset: 2px; }
9106
+ `;
9107
+ //#endregion
9108
+ //#region src/sessions/ui/review/screens/report-styles.ts
9109
+ const REPORT_STYLES = `
9110
+ .dt-ses-report { display: flex; flex-direction: column; gap: 4px; width: 100%; min-width: 0; margin: 0; }
9111
+ .dt-ses-report-canvas { position: relative; width: 100%; }
9112
+ .dt-ses-report-canvas img { display: block; width: 100%; max-width: 100%; height: auto; border-radius: 8px; box-shadow: var(--rf-shadow-inset); }
9113
+ .dt-ses-report-canvas[data-measured="false"] .dt-ses-callout { visibility: hidden; }
9114
+ .dt-ses-report-mark { --dt-mark: var(--rf-accent); position: absolute; border: 2px solid var(--dt-mark); border-radius: 4px; pointer-events: none; }
9115
+ .dt-ses-report-lines { position: absolute; inset: 0; overflow: visible; pointer-events: none; }
9116
+ .dt-ses-report-lines g { color: var(--rf-accent); }
9117
+ .dt-ses-report-lines line { stroke: currentColor; stroke-width: 1.5; }
9118
+ .dt-ses-report-lines circle { fill: currentColor; }
9119
+ .dt-ses-report-mark[data-decision="kept"], .dt-ses-report-lines g[data-decision="kept"] { --dt-mark: var(--rf-success); color: var(--rf-success); }
9120
+ .dt-ses-report-mark[data-decision="dismissed"], .dt-ses-report-lines g[data-decision="dismissed"] { opacity: .35; }
9121
+ .dt-ses-callouts { margin: 0; padding: 0; list-style: none; }
9122
+ .dt-ses-callouts[data-flow="list"] { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; }
9123
+ .dt-ses-callout {
9124
+ display: flex; align-items: center; gap: 4px; max-width: 100%; padding: 3px; border-radius: var(--rf-pill);
9125
+ background: var(--rf-bg); box-shadow: var(--rf-shadow-inset), var(--rf-shadow-low); font-size: 12px; line-height: 1.3;
9126
+ }
9127
+ .dt-ses-report-canvas .dt-ses-callout { position: absolute; top: 0; left: 0; width: max-content; max-width: min(280px, 100%); }
9128
+ .dt-ses-callout-number {
9129
+ display: grid; place-items: center; flex: none; min-width: 20px; height: 20px; padding: 0 5px; border-radius: var(--rf-pill);
9130
+ background: var(--rf-accent); color: var(--rf-accent-text, #fff); font-size: 11px; font-weight: 650; font-variant-numeric: tabular-nums;
9131
+ }
9132
+ .dt-ses-callout[data-decision="kept"] .dt-ses-callout-number { background: var(--rf-success); }
9133
+ .dt-ses-callout[data-decision="dismissed"] .dt-ses-callout-number { background: var(--rf-text-muted); }
9134
+ .dt-ses-callout-label { min-width: 0; padding: 0 4px; font-weight: 550; text-align: left; overflow-wrap: anywhere; }
9135
+ .dt-ses-callout-label:hover { color: var(--rf-accent); }
9136
+ .dt-ses-callout[data-decision="dismissed"] .dt-ses-callout-label { color: var(--rf-text-muted); text-decoration: line-through; }
9137
+ .dt-ses-callout-btn {
9138
+ display: grid; place-items: center; flex: none; width: 22px; height: 22px; border-radius: 50%; color: var(--rf-text-muted);
9139
+ transition: background-color 120ms var(--rf-ease), color 120ms var(--rf-ease);
9140
+ }
9141
+ .dt-ses-callout-btn svg { width: 13px; height: 13px; }
9142
+ .dt-ses-callout-btn:hover { background: var(--rf-bg-hover); color: var(--rf-text); }
9143
+ .dt-ses-callout-btn[data-decision="kept"][aria-pressed="true"] { background: var(--rf-success); color: #fff; }
9144
+ .dt-ses-callout-btn[data-decision="dismissed"][aria-pressed="true"] { background: var(--rf-bg-hover); color: var(--rf-text); }
9145
+ `;
9146
+ //#endregion
9147
+ //#region src/sessions/ui/sessions-styles.ts
9148
+ const MONO_FONT$1 = "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace";
9149
+ const SESSIONS_STYLES = `${`
9150
+ .dt-rec {
9151
+ position: fixed; z-index: ${REFLET_Z_INDEX.devtoolsBar}; left: 50%; translate: -50% 0;
9152
+ bottom: calc(var(--rf-viewport-bottom, 0px) + env(safe-area-inset-bottom) + 16px);
9153
+ display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 6px;
9154
+ max-width: calc(100vw - 24px); padding: 6px 6px 6px 12px; border-radius: var(--dt-radius-surface);
9155
+ animation: dt-rec-in 240ms var(--rf-ease);
9156
+ }
9157
+ @keyframes dt-rec-in { from { transform: translateY(8px); opacity: 0; } }
9158
+ .dt-rec-dot { flex: none; width: 9px; height: 9px; border-radius: 50%; background: var(--rf-danger); }
9159
+ .dt-rec[data-phase="recording"] .dt-rec-dot { animation: dt-rec-pulse 1.6s ease-in-out infinite; }
9160
+ .dt-rec[data-phase="paused"] .dt-rec-dot, .dt-rec[data-phase="finishing"] .dt-rec-dot { background: var(--rf-text-muted); }
9161
+ @keyframes dt-rec-pulse { 50% { opacity: .35; } }
9162
+ .dt-rec-meter { display: inline-flex; align-items: center; gap: 2px; height: 16px; }
9163
+ .dt-rec-meter span {
9164
+ width: 3px; height: 100%; border-radius: 2px; background: var(--rf-text-muted);
9165
+ scale: 1 calc(.2 + var(--dt-level, 0) * .8); transition: scale 90ms linear;
9166
+ }
9167
+ .dt-rec-meter span:nth-child(2) { scale: 1 calc(.2 + var(--dt-level, 0)); }
9168
+ .dt-rec-time { font-variant-numeric: tabular-nums; font-weight: 600; min-width: 5ch; }
9169
+ .dt-rec-status { color: var(--rf-text-muted); font-size: 12px; overflow-wrap: anywhere; }
9170
+ .dt-rec-status:empty { display: none; }
9171
+ .dt-rec-note { display: flex; align-items: center; gap: 6px; flex: 1 1 100%; font-size: 12px; color: var(--rf-text-muted); }
9172
+ .dt-rec-note .dt-field { flex: 1; min-width: 0; padding: 4px 8px; }
9173
+ `}
9174
+ .dt-ses-subhead, .dt-ses-label { margin: 0 0 4px; font-size: 12px; font-weight: 600; color: var(--rf-text); }
9175
+ .dt-ses-label { font-size: 11px; color: var(--rf-text-muted); }
9176
+ .dt-ses-list { margin: 0; padding-left: 18px; overflow-wrap: anywhere; }
9177
+ .dt-ses-list li + li { margin-top: 3px; }
9178
+ .dt-ses-link { align-self: flex-start; padding: 0; color: var(--rf-accent); font-size: 12px; text-align: left; }
9179
+ .dt-ses-link:hover { text-decoration: underline; }
9180
+ .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; }
9181
+ .dt-ses-banner { padding: 8px 10px; border-radius: var(--dt-radius-card); background: color-mix(in srgb, var(--rf-accent) 10%, transparent); }
9182
+ .dt-ses-banner p { margin: 0; overflow-wrap: anywhere; }
9183
+ .dt-ses-strip:empty { display: none; }
9184
+ .dt-ses-rows { display: flex; flex-direction: column; gap: 4px; margin: 0; padding: 0; list-style: none; }
9185
+ .dt-ses-row {
9186
+ display: flex; flex-direction: column; align-items: flex-start; gap: 2px; width: 100%;
9187
+ padding: 8px 10px; border-radius: var(--dt-radius-card); background: var(--rf-bg-subtle); text-align: left;
9188
+ }
9189
+ button.dt-ses-row:hover { background: var(--rf-bg-hover); }
9190
+ .dt-ses-row .dt-card-title { max-width: 100%; }
9191
+ ${`
9192
+ .dt-ses-title { flex: 1; min-width: 0; margin: 0; font-size: 15px; font-weight: 650; overflow-wrap: anywhere; }
9193
+ .dt-ses-head .dt-menu > .icon-btn { width: 30px; height: 30px; }
9194
+ .dt-btn[data-icon-only] { justify-content: center; width: 28px; padding: 0; }
9195
+ .dt-ses-field { display: flex; flex-direction: column; gap: 3px; font-size: 11.5px; color: var(--rf-text-muted); }
9196
+ .dt-ses-need {
9197
+ margin: 0; font-weight: 600; overflow-wrap: anywhere;
9198
+ display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
9199
+ }
9200
+ .dt-ses-need[data-expanded="true"] { display: block; }
9201
+ .dt-card[data-decision="dismissed"] .dt-ses-need { color: var(--rf-text-muted); text-decoration: line-through; }
9202
+ .dt-ses-chips { display: flex; flex-wrap: wrap; gap: 4px; }
9203
+ .dt-ses-chips:empty { display: none; }
9204
+ .dt-ses-details { display: flex; flex-direction: column; gap: 8px; }
9205
+ .dt-ses-details[hidden] { display: none; }
9206
+ .dt-ses-details section { display: flex; flex-direction: column; gap: 2px; }
9207
+ .dt-ses-quote { padding: 2px 0; font-size: 12px; text-align: left; overflow-wrap: anywhere; }
9208
+ .dt-ses-quote:hover { color: var(--rf-accent); }
9209
+ .dt-ses-time { flex: none; color: var(--rf-text-muted); font: 11.5px ${MONO_FONT$1}; font-variant-numeric: tabular-nums; }
9210
+ .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); }
9211
+ .dt-ses-timeline { display: flex; flex-direction: column; gap: 2px; margin: 0; padding: 0; list-style: none; }
9212
+ .dt-ses-seg { display: flex; gap: 8px; padding: 6px 8px; border-radius: var(--dt-radius-field); }
9213
+ .dt-ses-seg:focus { outline: 2px solid var(--rf-accent); outline-offset: -2px; }
9214
+ .dt-ses-seg[data-kind="note"] { padding-block: 2px; }
9215
+ .dt-ses-seg-body { display: flex; flex: 1; flex-direction: column; gap: 4px; min-width: 0; }
9216
+ `}${`
9217
+ .dt-ses-thumbs { display: flex; flex-wrap: wrap; gap: 6px; }
9218
+ .dt-ses-shot { position: relative; display: inline-block; max-width: 100%; line-height: 0; }
9219
+ .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); }
9220
+ .dt-ses-shot[data-large="true"] img { max-height: none; }
9221
+ .dt-ses-shot-mark { position: absolute; border: 2px solid var(--rf-accent); border-radius: var(--dt-radius-tiny); pointer-events: none; }
9222
+ .dt-ses-shot-btn { align-self: flex-start; padding: 0; border-radius: var(--dt-radius-field); }
9223
+ .dt-ses-start { display: flex; flex-direction: column; gap: 8px; }
9224
+ .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; }
9225
+ .dt-ses-brief-entry > summary::-webkit-details-marker { display: none; }
9226
+ .dt-ses-brief-entry > summary:hover { background: var(--rf-bg-hover); }
9227
+ .dt-ses-brief-entry[open] > summary { margin-bottom: 6px; }
9228
+ .dt-ses-check { display: flex; align-items: flex-start; gap: 6px; overflow-wrap: anywhere; }
9229
+ .dt-ses-check input { flex: none; margin-top: 3px; accent-color: var(--rf-accent); }
9230
+ .dt-ses-choices { margin: 0; padding: 0; border: 0; }
9231
+ .dt-ses-choices .dt-ses-list { padding-left: 0; list-style: none; }
9232
+ .dt-ses-brief {
9233
+ max-height: 50vh; overflow: auto; margin: 0; padding: 10px; border-radius: var(--dt-radius-card);
9234
+ background: var(--rf-bg-subtle); font: 11.5px/1.55 ${MONO_FONT$1}; white-space: pre-wrap; overflow-wrap: anywhere;
9235
+ }
9236
+ `}${`
9237
+ .dt-team-anchor {
9238
+ --dt-edge-gap: calc(max(var(--rf-offset), 12px) + 48px);
9239
+ position: fixed; z-index: ${REFLET_Z_INDEX.devtoolsBar}; width: min(300px, calc(100vw - 24px));
9240
+ }
9241
+ .dt-team-anchor[data-side="left"] { left: calc(var(--rf-viewport-left, 0px) + max(12px, var(--rf-offset))); }
9242
+ .dt-team-anchor[data-side="right"] { right: calc(var(--rf-viewport-right, 0px) + max(12px, var(--rf-offset))); }
9243
+ .dt-team-anchor[data-edge="bottom"] { bottom: calc(var(--rf-viewport-bottom, 0px) + env(safe-area-inset-bottom) + var(--dt-edge-gap)); }
9244
+ .dt-team-anchor[data-edge="top"] { top: calc(var(--rf-viewport-top, 0px) + env(safe-area-inset-top) + var(--dt-edge-gap)); }
9245
+ .dt-team-card { display: flex; flex-direction: column; gap: 6px; padding: 12px 14px; border-radius: var(--dt-radius-surface); animation: dt-popover-in 200ms var(--rf-ease); }
9246
+ .dt-bar > .dt-btn { margin-inline: 2px 4px; }
9247
+ `}${INTENTION_STYLES}${REPORT_STYLES}`;
9248
+ //#endregion
9249
+ //#region src/devtools/client/account/account-footer.tsx
9250
+ const WHITESPACE = /\s+/;
9251
+ function initials(text) {
9252
+ return text.split(WHITESPACE).filter(Boolean).slice(0, 2).map((word) => word.charAt(0).toUpperCase()).join("");
9253
+ }
9254
+ function Avatar({ image, name }) {
9255
+ return /* @__PURE__ */ jsxs("span", {
9256
+ "aria-hidden": "true",
9257
+ className: "dt-avatar",
9258
+ children: [initials(name), image && /* @__PURE__ */ jsx("img", {
9259
+ alt: "",
9260
+ height: 28,
9261
+ referrerPolicy: "no-referrer",
9262
+ src: image,
9263
+ width: 28
9458
9264
  })]
9459
9265
  });
9460
- return /* @__PURE__ */ jsx(SessionsHome, {
9461
- onOpenSession: navigation.onOpen,
9462
- onStarted,
9463
- onWriteNote
9266
+ }
9267
+ function AccountFooter({ actions, organizationName, person }) {
9268
+ const name = person?.name ?? person?.email ?? null;
9269
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
9270
+ /* @__PURE__ */ jsx(Avatar, {
9271
+ image: person?.image ?? null,
9272
+ name: name ?? organizationName
9273
+ }),
9274
+ /* @__PURE__ */ jsxs("span", {
9275
+ className: "dt-account-text",
9276
+ children: [/* @__PURE__ */ jsx("span", {
9277
+ className: "dt-account-name",
9278
+ children: name ?? organizationName
9279
+ }), name && /* @__PURE__ */ jsx("span", {
9280
+ className: "dt-meta",
9281
+ children: organizationName
9282
+ })]
9283
+ }),
9284
+ /* @__PURE__ */ jsx(ChevronUpDownIcon, {})
9285
+ ] });
9286
+ return /* @__PURE__ */ jsx("div", {
9287
+ className: "dt-sheet-account",
9288
+ children: /* @__PURE__ */ jsx(Menu, {
9289
+ items: actions,
9290
+ placement: "above",
9291
+ trigger: {
9292
+ className: "dt-account",
9293
+ content,
9294
+ label: name ? `${name}, ${organizationName}` : organizationName
9295
+ }
9296
+ })
9297
+ });
9298
+ }
9299
+ function AccountNote({ children }) {
9300
+ return /* @__PURE__ */ jsx("div", {
9301
+ className: "dt-sheet-account",
9302
+ children: /* @__PURE__ */ jsx("p", {
9303
+ className: "dt-meta",
9304
+ children
9305
+ })
9306
+ });
9307
+ }
9308
+ //#endregion
9309
+ //#region src/devtools/client/devtools-bar.tsx
9310
+ const BAR_POSITION_KEY = "reflet-devtools-bar-position";
9311
+ const PICK_BUTTON_LABEL = "Pick an element: note it, copy it, or Shift+click to open its code";
9312
+ const SHEET_TABS = [
9313
+ "inbox",
9314
+ "notes",
9315
+ "sessions",
9316
+ "code"
9317
+ ];
9318
+ function BarButton({ children, label, onClick }) {
9319
+ return /* @__PURE__ */ jsx("button", {
9320
+ "aria-label": label,
9321
+ className: "icon-btn",
9322
+ onClick,
9323
+ title: label,
9324
+ type: "button",
9325
+ children
9326
+ });
9327
+ }
9328
+ function DevtoolsBar({ children, edge, label = "Reflet devtools", side }) {
9329
+ const floating = useFloatingPosition(`${edge}-${side}`, false, {
9330
+ anchorSelector: ".dt-bar",
9331
+ persistKey: BAR_POSITION_KEY
9332
+ });
9333
+ return /* @__PURE__ */ jsxs("div", {
9334
+ "aria-label": label,
9335
+ className: "dt-bar glass",
9336
+ "data-edge": edge,
9337
+ "data-moved": floating.position !== null,
9338
+ "data-side": side,
9339
+ ref: floating.rootRef,
9340
+ role: "toolbar",
9341
+ children: [
9342
+ /* @__PURE__ */ jsx("style", { children: floating.styles }),
9343
+ /* @__PURE__ */ jsx("button", {
9344
+ "aria-label": `Move ${label} (double-click to reset)`,
9345
+ className: "icon-btn drag-handle dt-grip",
9346
+ onDoubleClick: floating.resetPosition,
9347
+ title: "Drag to move · double-click to reset",
9348
+ type: "button",
9349
+ ...floating.handleProps,
9350
+ children: /* @__PURE__ */ jsx(GripIcon, {})
9351
+ }),
9352
+ children
9353
+ ]
9354
+ });
9355
+ }
9356
+ function SessionsBarButton({ onOpen }) {
9357
+ return /* @__PURE__ */ jsx(BarButton, {
9358
+ label: "Sessions",
9359
+ onClick: onOpen,
9360
+ children: /* @__PURE__ */ jsx(MicIcon, {})
9464
9361
  });
9465
9362
  }
9363
+ function panelLabel(unsentNoteCount) {
9364
+ if (unsentNoteCount === 0) return "Open the Reflet panel";
9365
+ return `Open the Reflet panel (${unsentNoteCount} ${unsentNoteCount === 1 ? "note" : "notes"} not on the board)`;
9366
+ }
9367
+ function DevtoolsBarButtons({ onOpen, onPick, unsentNoteCount }) {
9368
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
9369
+ /* @__PURE__ */ jsx(BarButton, {
9370
+ label: PICK_BUTTON_LABEL,
9371
+ onClick: onPick,
9372
+ children: /* @__PURE__ */ jsx(TargetIcon, {})
9373
+ }),
9374
+ /* @__PURE__ */ jsx(SessionsBarButton, { onOpen: () => onOpen("sessions") }),
9375
+ /* @__PURE__ */ jsxs(BarButton, {
9376
+ label: panelLabel(unsentNoteCount),
9377
+ onClick: () => onOpen(null),
9378
+ children: [/* @__PURE__ */ jsx(PanelIcon, {}), unsentNoteCount > 0 && /* @__PURE__ */ jsx("span", {
9379
+ className: "dt-count",
9380
+ children: unsentNoteCount
9381
+ })]
9382
+ })
9383
+ ] });
9384
+ }
9385
+ //#endregion
9386
+ //#region src/devtools/client/use-sheet-size.ts
9387
+ const SIZE_KEY = "reflet-devtools-sheet-size";
9388
+ const DEFAULT_WIDTH = 480;
9389
+ const MIN_WIDTH = 320;
9390
+ const MIN_HEIGHT = 240;
9391
+ const KEYBOARD_STEP = 24;
9392
+ const ARROW_DELTAS = {
9393
+ ArrowDown: {
9394
+ dx: 0,
9395
+ dy: KEYBOARD_STEP
9396
+ },
9397
+ ArrowLeft: {
9398
+ dx: -24,
9399
+ dy: 0
9400
+ },
9401
+ ArrowRight: {
9402
+ dx: KEYBOARD_STEP,
9403
+ dy: 0
9404
+ },
9405
+ ArrowUp: {
9406
+ dx: 0,
9407
+ dy: -24
9408
+ }
9409
+ };
9410
+ function readStoredSize() {
9411
+ const fallback = {
9412
+ height: null,
9413
+ width: DEFAULT_WIDTH
9414
+ };
9415
+ try {
9416
+ const stored = JSON.parse(localStorage.getItem(SIZE_KEY) ?? "null");
9417
+ if (typeof stored === "object" && stored !== null && "width" in stored && typeof stored.width === "number" && "height" in stored && (stored.height === null || typeof stored.height === "number")) return {
9418
+ height: stored.height,
9419
+ width: stored.width
9420
+ };
9421
+ } catch {
9422
+ return fallback;
9423
+ }
9424
+ return fallback;
9425
+ }
9426
+ /**
9427
+ * The stored size is the preference; what renders is that preference fitted
9428
+ * to today's window, so shrinking the window never loses the chosen size.
9429
+ */
9430
+ function fitted(size, viewport) {
9431
+ const maxWidth = viewport.width - 24;
9432
+ const maxHeight = viewport.height - 24;
9433
+ return {
9434
+ height: size.height === null || size.height >= maxHeight ? null : Math.max(Math.min(MIN_HEIGHT, maxHeight), size.height),
9435
+ width: Math.min(maxWidth, Math.max(MIN_WIDTH, size.width))
9436
+ };
9437
+ }
9438
+ /**
9439
+ * Width grows away from the docked side, height grows downward from the top;
9440
+ * a height reaching the bottom of the window snaps back to full height.
9441
+ */
9442
+ function useSheetSize(side) {
9443
+ const [preference, setPreference] = useState(readStoredSize);
9444
+ const [viewport, setViewport] = useState(visibleViewport);
9445
+ const drag = useRef(null);
9446
+ const widthDirection = side === "right" ? -1 : 1;
9447
+ const size = fitted(preference, viewport);
9448
+ useEffect(() => onViewportChange(() => setViewport(visibleViewport())), []);
9449
+ useEffect(() => {
9450
+ try {
9451
+ localStorage.setItem(SIZE_KEY, JSON.stringify(preference));
9452
+ } catch {}
9453
+ }, [preference]);
9454
+ const resize = (axis, from, dx, dy) => {
9455
+ const fullHeight = viewport.height - 24;
9456
+ setPreference(fitted({
9457
+ height: axis === "width" ? from.height : (from.height ?? fullHeight) + dy,
9458
+ width: axis === "height" ? from.width : from.width + dx * widthDirection
9459
+ }, viewport));
9460
+ };
9461
+ const handleProps = (axis) => ({
9462
+ onDoubleClick: () => setPreference((current) => ({
9463
+ height: axis === "width" ? current.height : null,
9464
+ width: axis === "height" ? current.width : DEFAULT_WIDTH
9465
+ })),
9466
+ onKeyDown: (event) => {
9467
+ const delta = ARROW_DELTAS[event.key];
9468
+ if (!delta) return;
9469
+ event.preventDefault();
9470
+ const sheetHeight = event.currentTarget.parentElement?.getBoundingClientRect().height ?? null;
9471
+ resize(axis, {
9472
+ height: size.height ?? sheetHeight,
9473
+ width: size.width
9474
+ }, delta.dx, delta.dy);
9475
+ },
9476
+ onLostPointerCapture: () => {
9477
+ drag.current = null;
9478
+ },
9479
+ onPointerDown: (event) => {
9480
+ if (event.button !== 0) return;
9481
+ const sheet = event.currentTarget.parentElement?.getBoundingClientRect();
9482
+ event.currentTarget.setPointerCapture(event.pointerId);
9483
+ drag.current = {
9484
+ axis,
9485
+ size: {
9486
+ height: sheet?.height ?? size.height,
9487
+ width: size.width
9488
+ },
9489
+ x: event.clientX,
9490
+ y: event.clientY
9491
+ };
9492
+ },
9493
+ onPointerMove: (event) => {
9494
+ const start = drag.current;
9495
+ if (start && event.currentTarget.hasPointerCapture(event.pointerId)) resize(start.axis, start.size, event.clientX - start.x, event.clientY - start.y);
9496
+ }
9497
+ });
9498
+ return {
9499
+ handleProps,
9500
+ size
9501
+ };
9502
+ }
9503
+ //#endregion
9504
+ //#region src/devtools/client/devtools-sheet.tsx
9505
+ const FULL_HEIGHT = "calc(100dvh - 24px)";
9506
+ const TABS = {
9507
+ code: {
9508
+ icon: /* @__PURE__ */ jsx(CodeIcon, {}),
9509
+ label: "Code"
9510
+ },
9511
+ inbox: {
9512
+ icon: /* @__PURE__ */ jsx(InboxIcon, {}),
9513
+ label: "Board"
9514
+ },
9515
+ notes: {
9516
+ icon: /* @__PURE__ */ jsx(NotesIcon, {}),
9517
+ label: "Notes"
9518
+ },
9519
+ sessions: {
9520
+ icon: /* @__PURE__ */ jsx(MicIcon, {}),
9521
+ label: "Sessions"
9522
+ }
9523
+ };
9524
+ const SHEET_POSITION_KEY = "reflet-devtools-sheet-position";
9525
+ function useSheetFocus(rootRef) {
9526
+ useLayoutEffect(() => {
9527
+ const root = rootRef.current;
9528
+ const opener = root ? focusedElementAround(root) : null;
9529
+ if (opener instanceof HTMLElement && typeof opener.checkVisibility === "function" && !opener.checkVisibility({ visibilityProperty: true })) root?.querySelector("[role=\"tab\"][aria-selected=\"true\"], .dt-sheet-back, .dt-sheet-title")?.focus({ preventScroll: true });
9530
+ return () => {
9531
+ queueMicrotask(() => {
9532
+ if (opener instanceof HTMLElement && opener.isConnected) opener.focus({ preventScroll: true });
9533
+ });
9534
+ };
9535
+ }, [rootRef]);
9536
+ }
9537
+ const ROOT_FOCUS_SELECTOR = "[role=\"tab\"][aria-selected=\"true\"], .dt-sheet-title";
9538
+ function nestedViewStart(root) {
9539
+ return root.querySelector(".dt-sheet-body h2") ?? root.querySelector(".dt-sheet-body");
9540
+ }
9541
+ /** Lands keyboard users at the top of the view they moved to, never on a control that changed meaning. */
9542
+ function useFocusOnNavigation(rootRef, placeId) {
9543
+ const shownPlaceId = useRef(placeId);
9544
+ useLayoutEffect(() => {
9545
+ const root = rootRef.current;
9546
+ if (shownPlaceId.current === placeId || !root) return;
9547
+ shownPlaceId.current = placeId;
9548
+ const body = root.querySelector(".dt-sheet-body");
9549
+ if (body) body.scrollTop = 0;
9550
+ (placeId === null ? root.querySelector(ROOT_FOCUS_SELECTOR) : nestedViewStart(root))?.focus({ preventScroll: true });
9551
+ }, [placeId, rootRef]);
9552
+ }
9553
+ function SheetNavigation({ onSelectTab, place, tab, tabs }) {
9554
+ if (place) return /* @__PURE__ */ jsxs("button", {
9555
+ "aria-label": `Back to ${place.backLabel}`,
9556
+ className: "dt-sheet-back",
9557
+ onClick: place.onBack,
9558
+ type: "button",
9559
+ children: [/* @__PURE__ */ jsx(ChevronLeftIcon, {}), place.backLabel]
9560
+ });
9561
+ if (tabs.length === 1) return /* @__PURE__ */ jsxs("h2", {
9562
+ className: "dt-sheet-title",
9563
+ tabIndex: -1,
9564
+ children: [TABS[tab].icon, TABS[tab].label]
9565
+ });
9566
+ return /* @__PURE__ */ jsx("div", {
9567
+ className: "dt-tabs",
9568
+ role: "tablist",
9569
+ children: tabs.map((option) => /* @__PURE__ */ jsxs("button", {
9570
+ "aria-selected": option === tab,
9571
+ className: "dt-tab",
9572
+ onClick: () => onSelectTab(option),
9573
+ role: "tab",
9574
+ type: "button",
9575
+ children: [TABS[option].icon, TABS[option].label]
9576
+ }, option))
9577
+ });
9578
+ }
9579
+ function DevtoolsSheet({ children, footer, label = "Reflet devtools", navigation, onClose, side }) {
9580
+ const { handleProps, size } = useSheetSize(side);
9581
+ const floating = useFloatingPosition(`top-${side}`, false, {
9582
+ anchorSelector: ".dt-sheet",
9583
+ persistKey: SHEET_POSITION_KEY
9584
+ });
9585
+ const isMoved = floating.position !== null;
9586
+ const { rootRef } = floating;
9587
+ useSheetFocus(rootRef);
9588
+ useFocusOnNavigation(rootRef, navigation.place?.id ?? null);
9589
+ useEffect(() => listenToKeydown(rootRef.current, (event) => {
9590
+ if (event.key === "Escape" && !event.isComposing) onClose();
9591
+ }), [onClose, rootRef]);
9592
+ return /* @__PURE__ */ jsxs("aside", {
9593
+ "aria-label": label,
9594
+ className: "dt-sheet glass",
9595
+ "data-moved": isMoved,
9596
+ "data-side": side,
9597
+ ref: rootRef,
9598
+ style: {
9599
+ height: size.height ?? (isMoved ? FULL_HEIGHT : void 0),
9600
+ width: size.width
9601
+ },
9602
+ children: [
9603
+ /* @__PURE__ */ jsx("style", { children: floating.styles }),
9604
+ /* @__PURE__ */ jsx("button", {
9605
+ "aria-label": "Resize panel width (double-click to reset)",
9606
+ className: "dt-resize",
9607
+ "data-axis": "width",
9608
+ type: "button",
9609
+ ...handleProps("width")
9610
+ }),
9611
+ /* @__PURE__ */ jsx("button", {
9612
+ "aria-label": "Resize panel height (double-click for full height)",
9613
+ className: "dt-resize",
9614
+ "data-axis": "height",
9615
+ type: "button",
9616
+ ...handleProps("height")
9617
+ }),
9618
+ /* @__PURE__ */ jsx("button", {
9619
+ "aria-label": "Resize panel (double-click to reset)",
9620
+ className: "dt-resize",
9621
+ "data-axis": "both",
9622
+ type: "button",
9623
+ ...handleProps("both")
9624
+ }),
9625
+ /* @__PURE__ */ jsxs("div", {
9626
+ className: "dt-sheet-head",
9627
+ children: [
9628
+ /* @__PURE__ */ jsx("button", {
9629
+ "aria-label": "Move panel (double-click to dock it back)",
9630
+ className: "dt-sheet-drag",
9631
+ onDoubleClick: floating.resetPosition,
9632
+ title: "Drag to move · double-click to dock",
9633
+ type: "button",
9634
+ ...floating.handleProps
9635
+ }),
9636
+ /* @__PURE__ */ jsx(SheetNavigation, { ...navigation }),
9637
+ /* @__PURE__ */ jsx("button", {
9638
+ "aria-label": `Close ${label} panel`,
9639
+ className: "icon-btn dt-sheet-close",
9640
+ onClick: onClose,
9641
+ title: "Close",
9642
+ type: "button",
9643
+ children: /* @__PURE__ */ jsx(CloseIcon, {})
9644
+ })
9645
+ ]
9646
+ }),
9647
+ /* @__PURE__ */ jsx("div", {
9648
+ className: "dt-sheet-body",
9649
+ role: navigation.place === null && navigation.tabs.length > 1 ? "tabpanel" : void 0,
9650
+ tabIndex: navigation.place === null ? void 0 : -1,
9651
+ children
9652
+ }, `${navigation.tab}:${navigation.place === null ? "root" : "nested"}`),
9653
+ footer
9654
+ ]
9655
+ });
9656
+ }
9657
+ //#endregion
9658
+ //#region src/devtools/client/devtools-styles.ts
9659
+ const MONO_FONT = "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace";
9660
+ const DEVTOOLS_STYLES = `
9661
+ .dt {
9662
+ --dt-radius-surface: 16px; --dt-radius-card: 12px; --dt-radius-field: 8px; --dt-radius-tiny: 4px;
9663
+ font: 13px/1.45 var(--rf-font, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif); color: var(--rf-text);
9664
+ }
9665
+ .dt button:active:not(:disabled, .dt-btn, .icon-btn) { scale: 1; }
9666
+ .dt[data-recessed="true"] .dt-bar, .dt[data-recessed="true"] .dt-sheet, .dt-sheet ~ .dt-bar { visibility: hidden; pointer-events: none; }
9667
+ .dt-pin {
9668
+ position: fixed; z-index: ${REFLET_Z_INDEX.widgetRoot}; top: 0; left: 0;
9669
+ display: flex; align-items: center; justify-content: center;
9670
+ min-width: 24px; height: 24px; padding: 0 7px;
9671
+ border-radius: 12px 12px 12px 3px; background: var(--rf-accent); color: var(--rf-accent-text);
9672
+ font-size: 11.5px; font-weight: 650; box-shadow: 0 0 0 2px var(--rf-bg), var(--rf-shadow-low);
9673
+ animation: dt-pin-in 260ms var(--rf-ease);
9674
+ transition: scale 140ms var(--rf-ease), opacity 160ms var(--rf-ease);
9675
+ }
9676
+ .dt-pin:hover, .dt-pin[aria-expanded="true"] { scale: 1.12; }
9677
+ .dt-pin[data-sent="true"] { background: var(--rf-text-muted); }
9678
+ .dt-pin-badge { display: inline-flex; flex: none; align-items: center; justify-content: center; min-width: 20px; height: 20px; padding: 0 6px; border-radius: 10px 10px 10px 3px; background: var(--rf-accent); color: var(--rf-accent-text); font-size: 10.5px; font-weight: 650; }
9679
+ .dt-card[data-sent="true"] .dt-pin-badge { background: var(--rf-text-muted); }
9680
+ .dt-pin[data-locating="true"] { animation: dt-pin-in 260ms var(--rf-ease), dt-pin-pulse 1.2s ease-in-out infinite; }
9681
+ @keyframes dt-pin-in { from { scale: .4; opacity: 0; } }
9682
+ @keyframes dt-pin-pulse { 50% { box-shadow: 0 0 0 2px var(--rf-bg), 0 0 0 6px color-mix(in srgb, var(--rf-accent) 30%, transparent); } }
9683
+ .dt-popover {
9684
+ position: fixed; z-index: ${REFLET_Z_INDEX.widgetRoot}; width: 320px; max-height: calc(100dvh - 24px); overflow: auto;
9685
+ inset-inline-end: auto; margin: 0; padding: 6px; border-radius: var(--dt-radius-surface); background: transparent; color: inherit;
9686
+ animation: dt-popover-in 200ms var(--rf-ease);
9687
+ }
9688
+ .dt-popover.glass::before { animation: rf-fade 180ms var(--rf-ease); }
9689
+ .dt-popover .dt-card { background: transparent; }
9690
+ @keyframes dt-popover-in { from { transform: translateY(4px); } }
9691
+ @keyframes dt-notice-in { from { transform: translateY(-6px); } }
9692
+ .dt-notices {
9693
+ position: fixed; z-index: ${REFLET_Z_INDEX.annotatorOverlay}; left: 50%; top: 16px; translate: -50% 0;
9694
+ display: flex; flex-direction: column; gap: 6px; width: min(460px, calc(100vw - 24px));
9695
+ }
9696
+ .dt-notice {
9697
+ display: flex; align-items: center; gap: 6px; padding: 6px 6px 6px 14px; border-radius: var(--dt-radius-card);
9698
+ color: var(--rf-danger); font-size: 12.5px; animation: dt-notice-in 220ms var(--rf-ease);
9699
+ }
9700
+ .dt-notice[data-kind="status"] { color: var(--rf-text); }
9701
+ .dt-notice p { flex: 1; margin: 0; overflow-wrap: anywhere; }
9702
+ .dt-notice .icon-btn { width: 30px; height: 30px; }
9703
+ .dt-bar {
9704
+ --dt-edge-gap: calc(max(var(--rf-offset), 12px) + 48px);
9705
+ position: fixed; z-index: ${REFLET_Z_INDEX.devtoolsBar};
9706
+ display: flex; align-items: center; gap: 2px; padding: 3px;
9707
+ border-radius: var(--rf-pill);
9708
+ }
9709
+ .dt-bar[data-side="left"] { left: calc(var(--rf-viewport-left, 0px) + max(12px, var(--rf-offset))); }
9710
+ .dt-bar[data-side="right"] { right: calc(var(--rf-viewport-right, 0px) + max(12px, var(--rf-offset))); }
9711
+ .dt-bar[data-edge="bottom"] { bottom: calc(var(--rf-viewport-bottom, 0px) + env(safe-area-inset-bottom) + var(--dt-edge-gap)); }
9712
+ .dt-bar[data-edge="top"] { top: calc(var(--rf-viewport-top, 0px) + env(safe-area-inset-top) + var(--dt-edge-gap)); }
9713
+ .dt-bar .icon-btn { position: relative; width: 34px; height: 34px; }
9714
+ .dt-bar .icon-btn.dt-grip { width: 22px; color: var(--rf-text-muted); opacity: .6; }
9715
+ .dt-bar .icon-btn.dt-grip:hover { opacity: 1; }
9716
+ .dt-count {
9717
+ position: absolute; top: 1px; right: 1px; display: flex; align-items: center; justify-content: center;
9718
+ min-width: 15px; height: 15px; padding: 0 4px; border-radius: var(--rf-pill);
9719
+ background: var(--rf-accent); color: var(--rf-accent-text); font-size: 9.5px; font-weight: 600;
9720
+ }
9721
+ .dt-sheet {
9722
+ --dt-enter-x: 20px;
9723
+ position: fixed; z-index: ${REFLET_Z_INDEX.widgetRoot}; top: 12px; bottom: 12px;
9724
+ display: flex; flex-direction: column; max-width: calc(100vw - 24px); max-height: calc(100dvh - 24px);
9725
+ border-radius: var(--dt-radius-surface); overflow: hidden;
9726
+ animation: dt-sheet-slide 320ms var(--rf-ease);
9727
+ }
9728
+ .dt-sheet[data-side="left"] { --dt-enter-x: -20px; left: 12px; }
9729
+ .dt-sheet[data-side="right"] { right: 12px; }
9730
+ .dt-sheet.glass::before { animation: rf-fade 220ms var(--rf-ease); }
9731
+ .dt-sheet-head, .dt-sheet-body { animation: rf-fade 260ms 60ms var(--rf-ease) both; }
9732
+ @keyframes dt-sheet-slide { from { transform: translateX(var(--dt-enter-x)); box-shadow: none; } }
9733
+ .dt-resize { position: absolute; z-index: 2; padding: 0; touch-action: none; }
9734
+ .dt-resize:active:not(:disabled) { scale: 1; }
9735
+ .dt-resize:focus-visible { outline: none; }
9736
+ .dt-resize[data-axis="width"] { top: 16px; bottom: 16px; width: 10px; cursor: ew-resize; }
9737
+ .dt-sheet[data-side="right"] .dt-resize[data-axis="width"] { left: 0; }
9738
+ .dt-sheet[data-side="left"] .dt-resize[data-axis="width"] { right: 0; }
9739
+ .dt-resize[data-axis="height"] { left: 16px; right: 16px; bottom: 0; height: 10px; cursor: ns-resize; }
9740
+ .dt-resize[data-axis="both"] { bottom: 0; width: 16px; height: 16px; }
9741
+ .dt-sheet[data-side="right"] .dt-resize[data-axis="both"] { left: 0; cursor: nesw-resize; }
9742
+ .dt-sheet[data-side="left"] .dt-resize[data-axis="both"] { right: 0; cursor: nwse-resize; }
9743
+ .dt-resize[data-axis="width"]::after, .dt-resize[data-axis="height"]::after {
9744
+ content: ""; position: absolute; border-radius: var(--rf-pill); background: var(--rf-text-muted);
9745
+ opacity: 0; transition: opacity 160ms var(--rf-ease);
9746
+ }
9747
+ .dt-resize[data-axis="width"]::after { top: 50%; left: 3px; width: 4px; height: 36px; translate: 0 -50%; }
9748
+ .dt-resize[data-axis="height"]::after { left: 50%; top: 3px; width: 36px; height: 4px; translate: -50% 0; }
9749
+ .dt-resize:hover::after, .dt-resize:focus-visible::after, .dt-resize:active::after { opacity: .5; }
9750
+ .dt-sheet-head { position: relative; display: flex; align-items: center; gap: 4px; padding: 8px 8px 8px 6px; border-bottom: 1px solid var(--rf-border); }
9751
+ .dt-sheet-head > :not(.dt-sheet-drag) { position: relative; z-index: 1; }
9752
+ .dt-sheet-head .icon-btn { width: 32px; height: 32px; }
9753
+ .dt-sheet .dt-sheet-drag { position: absolute; inset: 0; z-index: 0; padding: 0; border-radius: 0; cursor: grab; touch-action: none; }
9754
+ .dt-sheet .dt-sheet-drag::before { content: ""; position: absolute; top: 4px; left: 50%; width: 36px; height: 4px; translate: -50% 0; border-radius: var(--rf-pill); background: var(--rf-text-muted); opacity: .3; transition: opacity 160ms var(--rf-ease); }
9755
+ .dt-sheet .dt-sheet-drag:hover::before, .dt-sheet .dt-sheet-drag:active::before { opacity: .7; }
9756
+ .dt-sheet .dt-sheet-drag:active:not(:disabled) { cursor: grabbing; scale: 1; }
9757
+ .dt-sheet .dt-sheet-drag:focus-visible { outline-offset: -2px; }
9758
+ .dt-tabs { display: flex; gap: 2px; min-width: 0; margin-right: auto; overflow-x: auto; scrollbar-width: none; }
9759
+ .dt-tab { display: inline-flex; flex: none; align-items: center; gap: 6px; height: 30px; padding: 0 11px 0 9px; border-radius: var(--rf-pill); color: var(--rf-text-muted); font-weight: 500; }
9760
+ .dt-tab svg { flex: none; width: 15px; height: 15px; }
9761
+ .dt-tab:hover { color: var(--rf-text); }
9762
+ .dt-tab[aria-selected="true"] { background: var(--rf-bg-hover); color: var(--rf-text); }
9763
+ .dt-tab-count { min-width: 18px; padding: 0 5px; border-radius: var(--rf-pill); background: var(--rf-bg-hover); font-size: 10.5px; font-weight: 600; font-variant-numeric: tabular-nums; }
9764
+ .dt-tab[aria-selected="true"] .dt-tab-count { background: var(--rf-bg); }
9765
+ .dt-sheet-back { display: inline-flex; align-items: center; gap: 4px; height: 32px; margin-right: auto; padding: 0 12px 0 6px; border-radius: var(--rf-pill); font-weight: 500; }
9766
+ .dt-sheet-back:hover { background: var(--rf-bg-hover); }
9767
+ .dt-sheet-back svg, .dt-sheet-title svg { flex: none; width: 16px; height: 16px; }
9768
+ .dt-sheet-title { display: inline-flex; align-items: center; gap: 6px; margin: 0 auto 0 8px; font-size: 13px; font-weight: 600; }
9769
+ .dt-sheet-title:focus { outline: none; }
9770
+ .dt-toolbar { display: flex; align-items: center; gap: 2px; min-height: 30px; }
9771
+ .dt-toolbar .icon-btn, .dt-toolbar .dt-menu > .icon-btn { width: 30px; height: 30px; }
9772
+ .dt-sheet-body { flex: 1; min-height: 0; overflow: auto; display: flex; flex-direction: column; gap: 8px; padding: 10px; }
9773
+ .dt-sheet-foot {
9774
+ position: sticky; bottom: -10px; display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
9775
+ margin: auto -10px -10px; padding: 10px; border-top: 1px solid var(--rf-border); background: var(--rf-bg);
9776
+ }
9777
+ .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); }
9778
+ .dt-archived .dt-meta { flex: 1 1 100%; }
9779
+ .dt-group { margin: 6px 2px 0; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; color: var(--rf-text-muted); }
9780
+ .dt-card { display: flex; flex-direction: column; gap: 6px; padding: 10px 12px; border-radius: var(--dt-radius-card); background: var(--rf-bg-subtle); }
9781
+ .dt-card[data-sent="true"] { opacity: .7; }
9782
+ .dt-card-head { display: flex; align-items: center; gap: 6px; min-width: 0; }
9783
+ .dt-card-title { min-width: 0; margin: 0; overflow: hidden; font-size: 13px; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }
9784
+ .dt-meta { margin: 0; font-size: 11.5px; color: var(--rf-text-muted); overflow-wrap: anywhere; }
9785
+ .dt-note-text { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
9786
+ .dt-chip { flex: none; padding: 1px 7px; border-radius: var(--rf-pill); background: var(--rf-bg-hover); color: var(--rf-text-muted); font-size: 10.5px; font-weight: 600; }
9787
+ .dt-chip[data-tone="accent"] { background: color-mix(in srgb, var(--rf-accent) 16%, transparent); color: var(--rf-accent); }
9788
+ .dt-source { padding: 0; font: 11.5px/1.4 ${MONO_FONT}; color: var(--rf-accent); text-align: left; overflow-wrap: anywhere; }
9789
+ .dt-source:hover { text-decoration: underline; }
9790
+ .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); }
9791
+ .dt-actions { display: flex; flex-wrap: wrap; gap: 4px; }
9792
+ .dt-btn {
9793
+ display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 10px;
9794
+ border-radius: var(--rf-pill); background: var(--rf-bg-hover); color: var(--rf-text);
9795
+ font-size: 12px; font-weight: 500; text-decoration: none;
9796
+ }
9797
+ .dt-btn:hover { background: color-mix(in srgb, var(--rf-text) 12%, transparent); }
9798
+ .dt-btn[data-variant="primary"] { background: var(--rf-primary); color: var(--rf-primary-text); }
9799
+ .dt-btn[data-variant="danger"] { color: var(--rf-danger); }
9800
+ .dt-btn svg { flex: none; width: 14px; height: 14px; }
9801
+ .dt-btn-count { padding: 0 6px; border-radius: var(--rf-pill); background: color-mix(in srgb, currentColor 18%, transparent); font-size: 10.5px; font-weight: 600; font-variant-numeric: tabular-nums; }
9802
+ .dt-empty { margin: auto; padding: 24px 16px; color: var(--rf-text-muted); font-size: 12.5px; text-align: center; text-wrap: pretty; }
9803
+ .dt-empty p { margin: 0 0 8px; }
9804
+ .dt-empty-title { margin: 0 0 4px; color: var(--rf-text); font-size: 13.5px; font-weight: 600; text-wrap: balance; }
9805
+ .dt-empty .dt-btn { margin-top: 4px; }
9806
+ .dt-empty code, .dt-hint code { padding: 1px 4px; border-radius: var(--dt-radius-tiny); background: var(--rf-bg-hover); font: 11.5px ${MONO_FONT}; overflow-wrap: anywhere; }
9807
+ .dt-hint { margin: 0; padding: 8px 10px; border-radius: var(--dt-radius-card); background: var(--rf-bg-subtle); color: var(--rf-text-muted); font-size: 12px; }
9808
+ .dt-error { margin: 0; color: var(--rf-danger); font-size: 12px; }
9809
+ .dt-status { margin: 0 auto 0 0; color: var(--rf-text-muted); font-size: 12px; }
9810
+ .dt-field {
9811
+ width: 100%; padding: 7px 9px; border: 1px solid var(--rf-border); border-radius: var(--dt-radius-field);
9812
+ background: var(--rf-bg); color: var(--rf-text); font: inherit; resize: vertical;
9813
+ }
9814
+ .dt-field:focus { outline: 2px solid var(--rf-accent); outline-offset: 0; }
9815
+ .dt-search { display: flex; gap: 6px; }
9816
+ .dt-matches { display: flex; flex-direction: column; gap: 2px; margin: 0; padding: 0; list-style: none; }
9817
+ .dt-match { display: flex; flex-direction: column; gap: 1px; width: 100%; padding: 6px 8px; border-radius: var(--dt-radius-field); text-align: left; }
9818
+ .dt-match:hover { background: var(--rf-bg-hover); }
9819
+ .dt-match code { font: 11.5px ${MONO_FONT}; color: var(--rf-accent); }
9820
+ .dt-match span { font: 11.5px ${MONO_FONT}; color: var(--rf-text-muted); white-space: pre; overflow: hidden; text-overflow: ellipsis; }
9821
+ .dt-code-head { display: flex; flex-direction: column; gap: 6px; }
9822
+ .dt-code-path { margin: 0; font: 12px/1.4 ${MONO_FONT}; overflow-wrap: anywhere; }
9823
+ .dt-code { flex: 1; min-height: 0; overflow: auto; margin: 0; padding: 8px 0; border-radius: var(--dt-radius-card); background: var(--rf-bg-subtle); font: 12px/1.6 ${MONO_FONT}; }
9824
+ .dt-code-lines { display: table; min-width: 100%; }
9825
+ .dt-line { display: table-row; white-space: pre; }
9826
+ .dt-line > span { display: table-cell; padding-right: 14px; }
9827
+ .dt-line > .dt-line-number { width: 1%; padding: 0 12px 0 10px; color: var(--rf-text-muted); text-align: right; user-select: none; }
9828
+ .dt-line[data-marked="true"] { background: color-mix(in srgb, var(--rf-accent) 16%, transparent); }
9829
+ .dt-line[data-marked="true"] > .dt-line-number { color: var(--rf-accent); }
9830
+ .dt-menu { position: relative; flex: none; }
9831
+ .dt-menu-list {
9832
+ position: absolute; right: 0; z-index: 3; display: flex; flex-direction: column; min-width: 176px; padding: 4px;
9833
+ border: 1px solid var(--rf-border); border-radius: var(--dt-radius-card); background: var(--rf-bg); box-shadow: var(--rf-shadow);
9834
+ animation: rf-fade 120ms var(--rf-ease);
9835
+ }
9836
+ .dt-menu-list[data-placement="below"] { top: calc(100% + 4px); }
9837
+ .dt-menu-list[data-placement="above"] { bottom: calc(100% + 4px); }
9838
+ .dt-menu-item { display: flex; align-items: center; gap: 6px; height: 30px; padding: 0 10px; border-radius: var(--dt-radius-field); color: var(--rf-text); font-size: 12.5px; text-align: left; white-space: nowrap; }
9839
+ .dt-menu-item:hover, .dt-menu-item:focus-visible { background: var(--rf-bg-hover); outline: none; }
9840
+ .dt-menu-item[data-variant="danger"] { color: var(--rf-danger); }
9841
+ .dt-menu-check { display: inline-flex; width: 14px; color: var(--rf-accent); }
9842
+ .dt-sheet-account { display: flex; flex: none; flex-wrap: wrap; align-items: center; gap: 8px; padding: 8px; border-top: 1px solid var(--rf-border); }
9843
+ .dt-sheet-account > .dt-meta { flex: 1 1 160px; padding-inline: 4px; }
9844
+ .dt-sheet-account > .dt-menu { flex: 1; min-width: 0; }
9845
+ .dt-sheet-account .dt-menu-list { left: 0; }
9846
+ .dt-account { display: flex; align-items: center; gap: 8px; width: 100%; padding: 4px 8px 4px 4px; border-radius: var(--dt-radius-card); text-align: left; }
9847
+ .dt-account:hover, .dt-account[aria-expanded="true"] { background: var(--rf-bg-hover); }
9848
+ .dt-account > svg { flex: none; width: 14px; height: 14px; color: var(--rf-text-muted); }
9849
+ .dt-account-text { display: flex; flex: 1; flex-direction: column; min-width: 0; line-height: 1.3; }
9850
+ .dt-account-name, .dt-account-text .dt-meta { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
9851
+ .dt-account-name { font-size: 12.5px; font-weight: 600; }
9852
+ .dt-avatar {
9853
+ position: relative; display: flex; flex: none; align-items: center; justify-content: center; width: 28px; height: 28px; overflow: hidden;
9854
+ border-radius: 50%; background: color-mix(in srgb, var(--rf-accent) 18%, transparent); color: var(--rf-accent); font-size: 11px; font-weight: 650;
9855
+ }
9856
+ .dt-avatar img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
9857
+ `;
9466
9858
  //#endregion
9467
- export { Notices as A, searchCode as C, openDatabase as D, describeStorageFailure as E, reportFailure as M, settle as O, probeDevRoute as S, committed as T, teamTransport as _, DevtoolsBar as a, fetchBoardFeedback as b, copyToClipboard as c, PersonIcon as d, SessionWorkspaceProvider as f, devtoolsTransport as g, purgeAccount as h, BarButton as i, announce as j, sessionApiErrorSchema as k, SESSIONS_STYLES as l, useWorkspaceLoader as m, DEVTOOLS_STYLES as n, DevtoolsBarButtons as o, failureMessage as p, DevtoolsSheet as r, SessionsBarButton as s, SessionsPanel as t, RecordingLayer as u, callReflet as v, startBoardConnect as w, fetchSourceFile as x, disconnectBoard as y };
9859
+ export { probeDevRoute as A, reportFailure as B, bridgeTransport as C, disconnectBoard as D, callReflet as E, openDatabase as F, settle as I, sessionApiErrorSchema as L, startBoardConnect as M, committed as N, fetchBoardFeedback as O, describeStorageFailure as P, Notices as R, purgeAccount as S, teamTransport as T, RecordingLayer as _, SHEET_TABS as a, failureMessage as b, AccountNote as c, sessionSheetPlace as d, useSessionNavigation as f, copyToClipboard as g, ViewOptionsIcon as h, DevtoolsBarButtons as i, searchCode as j, fetchSourceFile as k, SESSIONS_STYLES as l, RefreshIcon as m, DevtoolsSheet as n, SessionsBarButton as o, Menu as p, DevtoolsBar as r, AccountFooter as s, DEVTOOLS_STYLES as t, SessionsPanel as u, countLabel as v, devtoolsTransport as w, useWorkspaceLoader as x, SessionWorkspaceProvider as y, announce as z };
9468
9860
 
9469
- //# sourceMappingURL=sessions-panel-BYM0UUgT.js.map
9861
+ //# sourceMappingURL=devtools-styles-TWR5Ps34.js.map