reflet-sdk 0.6.8 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,10 @@
1
- import { a as EDITORS, c as handoffFileSchema, d as sessionEvidenceSchema, f as sessionHandoffSchema, g as sessionSummarySchema, h as sessionSegmentSchema, l as sessionApiErrorSchema, m as sessionProposalSchema, n as DEVTOOLS_REQUEST_HEADER, p as sessionIdentitySchema, r as DEVTOOLS_ROUTE_BASE, s as analysisStatusSchema, t as DEVTOOLS_ENDPOINTS, u as sessionDetailSchema } from "./protocol-Cg_cI4xS.js";
2
- import { B as clip, D as MoreIcon, G as describeElement, K as getElementRect, M as TargetIcon, R as DEFAULT_WIDGET_LABELS, 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, tt as captureViewport, u as visibleViewport, w as CloseIcon } from "./submit-CafYVnCb.js";
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, R as DEFAULT_WIDGET_LABELS, 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, tt as captureViewport, u as visibleViewport, w as CloseIcon } from "./submit-CahCBj1P.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";
6
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ import { flushSync } from "react-dom";
7
8
  //#region src/devtools/client/notices.tsx
8
9
  const STATUS_VISIBLE_MS = 2400;
9
10
  const ACTION_VISIBLE_MS = 6e3;
@@ -82,10 +83,531 @@ function useRecorderSnapshot(recorder) {
82
83
  return useSyncExternalStore(recorder.subscribe, recorder.getSnapshot, recorder.getSnapshot);
83
84
  }
84
85
  //#endregion
86
+ //#region src/sessions/intention-protocol.ts
87
+ const INTENTION_FIELDS = [
88
+ "need",
89
+ "observed",
90
+ "response",
91
+ "unknowns",
92
+ "setAside"
93
+ ];
94
+ const INTENTION_CHANGE_KINDS = [
95
+ "precision",
96
+ "contradiction",
97
+ "constraint",
98
+ "solution",
99
+ "outcome"
100
+ ];
101
+ const fieldValueSchema = z.union([z.string(), z.array(z.string())]);
102
+ const intentionChangeSchema = z.object({
103
+ decision: z.enum([
104
+ "pending",
105
+ "accepted",
106
+ "rejected"
107
+ ]),
108
+ evidenceIds: z.array(z.string()),
109
+ field: z.enum(INTENTION_FIELDS),
110
+ hypothesis: z.boolean(),
111
+ id: z.string(),
112
+ kind: z.enum(INTENTION_CHANGE_KINDS),
113
+ operation: z.enum([
114
+ "replace",
115
+ "add",
116
+ "remove"
117
+ ]),
118
+ reason: z.string(),
119
+ segmentIds: z.array(z.string()),
120
+ value: z.string()
121
+ });
122
+ const appliedChangeSchema = z.object({
123
+ at: z.number(),
124
+ byName: z.string().nullable(),
125
+ fields: z.array(z.object({
126
+ after: fieldValueSchema,
127
+ before: fieldValueSchema,
128
+ field: z.enum(INTENTION_FIELDS)
129
+ })),
130
+ revision: z.number()
131
+ });
132
+ const sessionContributionSchema = z.object({
133
+ answers: z.array(z.object({
134
+ at: z.number(),
135
+ text: z.string()
136
+ })),
137
+ applied: z.array(appliedChangeSchema),
138
+ baseRevision: z.number(),
139
+ changes: z.array(intentionChangeSchema),
140
+ detachedAt: z.number().nullable(),
141
+ id: z.string(),
142
+ intentionId: z.string(),
143
+ needsShareConsent: z.boolean(),
144
+ origin: z.enum(["continuation", "attach"]),
145
+ question: z.string().nullable(),
146
+ sharedAt: z.number().nullable(),
147
+ stale: z.boolean(),
148
+ state: z.enum([
149
+ "recording",
150
+ "analyzing",
151
+ "ready",
152
+ "no_change",
153
+ "failed"
154
+ ]),
155
+ verdict: z.object({
156
+ reason: z.string(),
157
+ segmentIds: z.array(z.string()),
158
+ status: z.enum([
159
+ "resolved",
160
+ "needs_work",
161
+ "insufficient"
162
+ ])
163
+ }).nullable()
164
+ });
165
+ const continuedIntentionSchema = z.object({
166
+ id: z.string(),
167
+ need: z.string(),
168
+ observed: z.string(),
169
+ question: z.string().nullable(),
170
+ response: z.string().nullable(),
171
+ revision: z.number(),
172
+ sessionId: z.string(),
173
+ sessionTitle: z.string(),
174
+ setAside: z.array(z.string()),
175
+ unknowns: z.array(z.string()),
176
+ verification: z.object({
177
+ at: z.number(),
178
+ sessionId: z.string().nullable(),
179
+ status: z.enum(["works", "needs_work"])
180
+ }).nullable()
181
+ });
182
+ const sessionContinuationSchema = z.object({
183
+ contribution: sessionContributionSchema,
184
+ intention: continuedIntentionSchema.nullable()
185
+ });
186
+ const contributionQuoteSchema = z.object({
187
+ context: z.array(z.string()),
188
+ segmentId: z.string(),
189
+ startMs: z.number(),
190
+ text: z.string()
191
+ });
192
+ const intentionHistoryEntrySchema = z.object({
193
+ applied: z.array(appliedChangeSchema),
194
+ detachedAt: z.number().nullable(),
195
+ evidenceIds: z.array(z.string()),
196
+ extractsHidden: z.boolean(),
197
+ id: z.string(),
198
+ intentionId: z.string(),
199
+ origin: z.enum(["continuation", "attach"]),
200
+ quotes: z.array(contributionQuoteSchema),
201
+ session: z.object({
202
+ canOpen: z.boolean(),
203
+ id: z.string(),
204
+ startedAt: z.number(),
205
+ title: z.string()
206
+ })
207
+ });
208
+ const intentionSuggestionSchema = z.object({
209
+ intentionId: z.string(),
210
+ need: z.string(),
211
+ needsShareConsent: z.boolean(),
212
+ reason: z.string(),
213
+ sessionId: z.string(),
214
+ sessionTitle: z.string()
215
+ });
216
+ const lastHandoffSchema = z.object({
217
+ at: z.number(),
218
+ need: z.string(),
219
+ response: z.string().nullable(),
220
+ revision: z.number(),
221
+ unknowns: z.array(z.string()).nullable()
222
+ });
223
+ //#endregion
224
+ //#region src/sessions/protocol.ts
225
+ const SESSION_SURFACES = ["devtools", "app"];
226
+ const SESSION_GAP_REASONS = [
227
+ "paused",
228
+ "hidden",
229
+ "interrupted",
230
+ "limit"
231
+ ];
232
+ const SESSION_LIMIT_KINDS = [
233
+ "duration",
234
+ "segments",
235
+ "events",
236
+ "evidence",
237
+ "bytes"
238
+ ];
239
+ const PROPOSAL_DECISIONS = [
240
+ "pending",
241
+ "kept",
242
+ "later",
243
+ "dismissed"
244
+ ];
245
+ const EVIDENCE_TRIGGERS = [
246
+ "start",
247
+ "mark",
248
+ "navigation",
249
+ "select",
250
+ "manual"
251
+ ];
252
+ const sessionLimitsSchema = z.object({
253
+ audioRetentionMs: z.number(),
254
+ maxDurationMs: z.number(),
255
+ maxEvents: z.number(),
256
+ maxEventsPerBatch: z.number(),
257
+ maxEvidence: z.number(),
258
+ maxImageBytes: z.number(),
259
+ maxLocalBufferBytes: z.number(),
260
+ maxSegmentBytes: z.number(),
261
+ maxSegmentMs: z.number(),
262
+ maxSegments: z.number(),
263
+ minAutoCaptureIntervalMs: z.number(),
264
+ warnBeforeLimitMs: z.number()
265
+ });
266
+ const sessionIdentitySchema = z.object({
267
+ analysisConfigured: z.boolean(),
268
+ limits: sessionLimitsSchema,
269
+ organizationId: z.string(),
270
+ organizationName: z.string(),
271
+ transcriptionConfigured: z.boolean(),
272
+ user: z.object({
273
+ email: z.string().nullable(),
274
+ id: z.string(),
275
+ name: z.string().nullable()
276
+ })
277
+ });
278
+ const analysisStatusSchema = z.object({
279
+ analyzedRevision: z.number().nullable(),
280
+ error: z.string().nullable(),
281
+ proposalCount: z.number().nullable(),
282
+ state: z.enum([
283
+ "idle",
284
+ "waiting",
285
+ "running",
286
+ "succeeded",
287
+ "failed"
288
+ ]),
289
+ transcriptRevision: z.number(),
290
+ updatedAt: z.number().nullable()
291
+ });
292
+ const sessionSummarySchema = z.object({
293
+ analysis: analysisStatusSchema,
294
+ authorName: z.string().nullable(),
295
+ collection: z.enum([
296
+ "recording",
297
+ "paused",
298
+ "ended"
299
+ ]),
300
+ counts: z.object({
301
+ evidence: z.number(),
302
+ failedSegments: z.number(),
303
+ handoffs: z.number(),
304
+ kept: z.number(),
305
+ pendingSegments: z.number(),
306
+ proposals: z.number(),
307
+ segments: z.number()
308
+ }),
309
+ durationMs: z.number(),
310
+ endedAt: z.number().nullable(),
311
+ id: z.string(),
312
+ isMine: z.boolean(),
313
+ startedAt: z.number(),
314
+ surface: z.enum(SESSION_SURFACES),
315
+ title: z.string(),
316
+ visibility: z.enum(["private", "team"])
317
+ });
318
+ const rectSchema = z.object({
319
+ height: z.number(),
320
+ width: z.number(),
321
+ x: z.number(),
322
+ y: z.number()
323
+ });
324
+ const elementRefSchema = z.object({
325
+ component: z.string().nullable(),
326
+ label: z.string(),
327
+ rect: rectSchema,
328
+ role: z.string().nullable(),
329
+ selector: z.string(),
330
+ source: z.string().nullable()
331
+ });
332
+ const frameSchema = z.object({
333
+ devicePixelRatio: z.number(),
334
+ scrollX: z.number(),
335
+ scrollY: z.number(),
336
+ title: z.string().nullable(),
337
+ url: z.string(),
338
+ viewportHeight: z.number(),
339
+ viewportWidth: z.number()
340
+ });
341
+ const pointerSchema = z.object({
342
+ x: z.number(),
343
+ y: z.number()
344
+ });
345
+ z.discriminatedUnion("kind", [
346
+ z.object({
347
+ atMs: z.number(),
348
+ frame: frameSchema,
349
+ kind: z.literal("navigation")
350
+ }),
351
+ z.object({
352
+ atMs: z.number(),
353
+ element: elementRefSchema,
354
+ frame: frameSchema,
355
+ kind: z.literal("click"),
356
+ pointer: pointerSchema.nullable()
357
+ }),
358
+ z.object({
359
+ atMs: z.number(),
360
+ durationMs: z.number(),
361
+ element: elementRefSchema,
362
+ frame: frameSchema,
363
+ kind: z.literal("dwell"),
364
+ pointer: pointerSchema
365
+ }),
366
+ z.object({
367
+ atMs: z.number(),
368
+ kind: z.literal("scroll"),
369
+ scrollX: z.number(),
370
+ scrollY: z.number()
371
+ }),
372
+ z.object({
373
+ atMs: z.number(),
374
+ devicePixelRatio: z.number(),
375
+ kind: z.literal("viewport"),
376
+ viewportHeight: z.number(),
377
+ viewportWidth: z.number()
378
+ }),
379
+ z.object({
380
+ atMs: z.number(),
381
+ element: elementRefSchema.nullable(),
382
+ evidenceClientKey: z.string().nullable(),
383
+ frame: frameSchema,
384
+ kind: z.literal("mark"),
385
+ note: z.string().nullable()
386
+ }),
387
+ z.object({
388
+ atMs: z.number(),
389
+ element: elementRefSchema,
390
+ evidenceClientKey: z.string().nullable(),
391
+ frame: frameSchema,
392
+ kind: z.literal("select"),
393
+ note: z.string().nullable()
394
+ })
395
+ ]);
396
+ const sessionGapSchema = z.object({
397
+ endMs: z.number().nullable(),
398
+ reason: z.enum(SESSION_GAP_REASONS),
399
+ startMs: z.number()
400
+ });
401
+ const segmentContextSchema = z.object({
402
+ atMs: z.number(),
403
+ durationMs: z.number().nullable(),
404
+ element: elementRefSchema.nullable(),
405
+ evidenceId: z.string().nullable(),
406
+ evidenceMatch: z.enum(["exact", "approximate"]).nullable(),
407
+ evidenceOffsetMs: z.number().nullable(),
408
+ frame: frameSchema.nullable(),
409
+ kind: z.enum([
410
+ "mark",
411
+ "select",
412
+ "click",
413
+ "dwell",
414
+ "navigation",
415
+ "screenshot"
416
+ ]),
417
+ label: z.string(),
418
+ pointer: pointerSchema.nullable(),
419
+ url: z.string().nullable()
420
+ });
421
+ const contextChoiceSchema = z.discriminatedUnion("kind", [
422
+ z.object({ kind: z.literal("auto") }),
423
+ z.object({ kind: z.literal("unknown") }),
424
+ z.object({
425
+ atMs: z.number(),
426
+ eventKind: z.enum([
427
+ "mark",
428
+ "select",
429
+ "click",
430
+ "dwell",
431
+ "navigation"
432
+ ]),
433
+ kind: z.literal("event")
434
+ }),
435
+ z.object({
436
+ evidenceId: z.string(),
437
+ kind: z.literal("evidence")
438
+ })
439
+ ]);
440
+ const contextOptionSchema = z.object({
441
+ choice: contextChoiceSchema,
442
+ item: segmentContextSchema,
443
+ selected: z.boolean()
444
+ });
445
+ const sessionSegmentSchema = z.object({
446
+ audioExpiresAt: z.number().nullable(),
447
+ context: z.array(segmentContextSchema),
448
+ contextAmbiguous: z.boolean(),
449
+ contextCorrectedAt: z.number().nullable(),
450
+ contextOrigin: z.enum([
451
+ "auto",
452
+ "human",
453
+ "unknown"
454
+ ]),
455
+ endMs: z.number(),
456
+ hasAudio: z.boolean(),
457
+ id: z.string(),
458
+ language: z.string().nullable(),
459
+ revision: z.number(),
460
+ source: z.enum(["voice", "typed"]),
461
+ startMs: z.number(),
462
+ text: z.string(),
463
+ textOrigin: z.enum(["transcription", "human"]),
464
+ transcription: z.object({
465
+ attempts: z.number(),
466
+ error: z.string().nullable(),
467
+ state: z.enum([
468
+ "pending",
469
+ "running",
470
+ "done",
471
+ "empty",
472
+ "failed",
473
+ "expired"
474
+ ])
475
+ })
476
+ });
477
+ const evidenceLinkSchema = z.object({
478
+ access: z.string(),
479
+ label: z.string(),
480
+ url: z.string()
481
+ });
482
+ const sessionEvidenceSchema = z.object({
483
+ atMs: z.number(),
484
+ capturedAtMs: z.number().nullable(),
485
+ element: elementRefSchema.nullable(),
486
+ frame: frameSchema.nullable(),
487
+ id: z.string(),
488
+ image: z.object({
489
+ height: z.number(),
490
+ url: z.string().nullable(),
491
+ width: z.number()
492
+ }).nullable(),
493
+ kind: z.enum(["screenshot", "link"]),
494
+ link: evidenceLinkSchema.nullable(),
495
+ missingReason: z.string().nullable(),
496
+ note: z.string().nullable(),
497
+ status: z.enum(["available", "missing"]),
498
+ trigger: z.enum(EVIDENCE_TRIGGERS)
499
+ });
500
+ const sessionProposalSchema = z.object({
501
+ attachment: z.object({
502
+ contributionId: z.string(),
503
+ intentionId: z.string(),
504
+ need: z.string()
505
+ }).nullable(),
506
+ certainty: z.enum(["clear", "uncertain"]),
507
+ clarifications: z.array(z.object({
508
+ at: z.number(),
509
+ text: z.string()
510
+ })),
511
+ decision: z.enum(PROPOSAL_DECISIONS),
512
+ evidenceIds: z.array(z.string()),
513
+ feedbackId: z.string().nullable(),
514
+ formulation: z.enum(["ai", "human"]),
515
+ handedOffRevision: z.number().nullable(),
516
+ id: z.string(),
517
+ intentionSuggestion: intentionSuggestionSchema.nullable(),
518
+ lastHandoff: lastHandoffSchema.nullable(),
519
+ mergedIntoId: z.string().nullable(),
520
+ mergeSuggestion: z.object({
521
+ reason: z.string(),
522
+ withIds: z.array(z.string())
523
+ }).nullable(),
524
+ need: z.string(),
525
+ needsReview: z.boolean(),
526
+ observed: z.string(),
527
+ origin: z.enum(["ai", "human"]),
528
+ question: z.string().nullable(),
529
+ refinement: z.object({
530
+ error: z.string().nullable(),
531
+ state: z.enum([
532
+ "running",
533
+ "failed",
534
+ "outdated"
535
+ ])
536
+ }).nullable(),
537
+ response: z.string().nullable(),
538
+ revision: z.number(),
539
+ segmentIds: z.array(z.string()),
540
+ setAside: z.array(z.string()),
541
+ unknowns: z.array(z.string()),
542
+ updatedAt: z.number(),
543
+ verification: z.object({
544
+ at: z.number(),
545
+ note: z.string().nullable(),
546
+ sessionId: z.string().nullable(),
547
+ status: z.enum(["works", "needs_work"])
548
+ }).nullable()
549
+ });
550
+ const sessionHandoffSchema = z.object({
551
+ brief: z.string(),
552
+ createdAt: z.number(),
553
+ deliveries: z.array(z.object({
554
+ at: z.number(),
555
+ kind: z.enum(["copied", "exported"])
556
+ })),
557
+ evidenceIds: z.array(z.string()),
558
+ id: z.string(),
559
+ proposals: z.array(z.object({
560
+ id: z.string(),
561
+ need: z.string(),
562
+ revision: z.number()
563
+ }))
564
+ });
565
+ const sessionMarkSchema = z.object({
566
+ atMs: z.number(),
567
+ kind: z.enum([
568
+ "mark",
569
+ "select",
570
+ "navigation"
571
+ ]),
572
+ label: z.string(),
573
+ note: z.string().nullable(),
574
+ url: z.string().nullable()
575
+ });
576
+ const sessionDetailSchema = z.object({
577
+ continuation: sessionContinuationSchema.nullable(),
578
+ contributedEvidence: z.array(sessionEvidenceSchema),
579
+ evidence: z.array(sessionEvidenceSchema),
580
+ handoffs: z.array(sessionHandoffSchema),
581
+ history: z.array(intentionHistoryEntrySchema),
582
+ marks: z.array(sessionMarkSchema),
583
+ proposals: z.array(sessionProposalSchema),
584
+ segments: z.array(sessionSegmentSchema),
585
+ session: sessionSummarySchema.extend({
586
+ canEdit: z.boolean(),
587
+ gaps: z.array(sessionGapSchema),
588
+ startUrl: z.string().nullable()
589
+ })
590
+ });
591
+ const handoffFileSchema = z.object({
592
+ evidenceId: z.string(),
593
+ name: z.string(),
594
+ url: z.string()
595
+ });
596
+ const sessionApiErrorSchema = z.object({
597
+ error: z.string(),
598
+ limit: z.enum(SESSION_LIMIT_KINDS).optional()
599
+ });
600
+ //#endregion
85
601
  //#region src/sessions/api/proposal-endpoints.ts
86
602
  const proposalResponse = z.object({ proposal: sessionProposalSchema });
87
- function createProposalEndpoints({ post }) {
88
- const postProposal = async (path, body) => (await post(path, body, proposalResponse)).proposal;
603
+ const intentionUpdateResponse = z.object({
604
+ contribution: sessionContributionSchema,
605
+ intention: continuedIntentionSchema.nullable()
606
+ });
607
+ const proposalPoster = ({ post }) => async (path, body) => (await post(path, body, proposalResponse)).proposal;
608
+ function createProposalEndpoints(requests) {
609
+ const { post } = requests;
610
+ const postProposal = proposalPoster(requests);
89
611
  return {
90
612
  clarifyProposal: (body) => postProposal("/api/v1/sessions/proposals/clarify", body),
91
613
  createFeedback: (proposalId) => post("/api/v1/sessions/proposals/feedback", { proposalId }, z.object({
@@ -105,6 +627,22 @@ function createProposalEndpoints({ post }) {
105
627
  verifyProposal: (body) => postProposal("/api/v1/sessions/proposals/verify", body)
106
628
  };
107
629
  }
630
+ function createIntentionEndpoints(requests) {
631
+ const { post } = requests;
632
+ const postProposal = proposalPoster(requests);
633
+ const postIntentionUpdate = (path, body) => post(path, body, intentionUpdateResponse);
634
+ return {
635
+ acceptIntentionChanges: (body) => postIntentionUpdate("/api/v1/sessions/intentions/accept", body),
636
+ answerIntentionQuestion: (body) => postIntentionUpdate("/api/v1/sessions/intentions/answer", body),
637
+ attachToIntention: (body) => postProposal("/api/v1/sessions/intentions/attach", body),
638
+ deleteProposal: async (proposalId) => {
639
+ await post("/api/v1/sessions/proposals/delete", { proposalId }, z.object({ deleted: z.literal(true) }));
640
+ },
641
+ detachContribution: (contributionId) => postIntentionUpdate("/api/v1/sessions/intentions/detach", { contributionId }),
642
+ keepSeparate: (proposalId) => postProposal("/api/v1/sessions/intentions/keep-separate", { proposalId }),
643
+ reopenOrRejectChange: (body) => postIntentionUpdate("/api/v1/sessions/intentions/change", body)
644
+ };
645
+ }
108
646
  //#endregion
109
647
  //#region src/sessions/api/recording-endpoints.ts
110
648
  const summaryResponse = z.object({ session: sessionSummarySchema });
@@ -127,9 +665,7 @@ function createRecordingEndpoints({ post }) {
127
665
  //#region src/sessions/api/review-endpoints.ts
128
666
  function createReviewEndpoints({ get, post }) {
129
667
  return {
130
- addTypedSegment: async (body) => (await post("/api/v1/sessions/segments", body, segmentResponse)).segment,
131
668
  analyze: async (sessionId) => (await post("/api/v1/sessions/analyze", { sessionId }, z.object({ analysis: analysisStatusSchema }))).analysis,
132
- correctSegment: async (body) => (await post("/api/v1/sessions/segments/correct", body, segmentResponse)).segment,
133
669
  deleteSession: async (sessionId) => {
134
670
  await post("/api/v1/sessions/delete", { sessionId }, z.object({ deleted: z.literal(true) }));
135
671
  },
@@ -137,10 +673,19 @@ function createReviewEndpoints({ get, post }) {
137
673
  identity: () => get("/api/v1/sessions/identity", sessionIdentitySchema),
138
674
  list: async () => (await get("/api/v1/sessions/list", z.object({ sessions: z.array(sessionSummarySchema) }))).sessions,
139
675
  removeEvidence: async (evidenceId) => (await post("/api/v1/sessions/evidence/remove", { evidenceId }, z.object({ evidence: sessionEvidenceSchema }))).evidence,
140
- retrySegment: async (segmentId) => (await post("/api/v1/sessions/segments/retry", { segmentId }, segmentResponse)).segment,
141
676
  update: async (body) => (await post("/api/v1/sessions/update", body, summaryResponse)).session
142
677
  };
143
678
  }
679
+ function createSegmentEndpoints({ get, post }) {
680
+ const postSegment = async (path, body) => (await post(path, body, segmentResponse)).segment;
681
+ return {
682
+ addTypedSegment: (body) => postSegment("/api/v1/sessions/segments", body),
683
+ contextChoices: async (segmentId) => (await get("/api/v1/sessions/segments/context-choices", z.object({ options: z.array(contextOptionSchema) }), { segmentId })).options,
684
+ correctSegment: (body) => postSegment("/api/v1/sessions/segments/correct", body),
685
+ retrySegment: (segmentId) => postSegment("/api/v1/sessions/segments/retry", { segmentId }),
686
+ setSegmentContext: (body) => postSegment("/api/v1/sessions/segments/context", body)
687
+ };
688
+ }
144
689
  //#endregion
145
690
  //#region src/sessions/api/session-requests.ts
146
691
  function createSessionRequests(transport) {
@@ -176,7 +721,9 @@ function createSessionApi(transport) {
176
721
  return {
177
722
  ...createRecordingEndpoints(requests),
178
723
  ...createReviewEndpoints(requests),
179
- ...createProposalEndpoints(requests)
724
+ ...createSegmentEndpoints(requests),
725
+ ...createProposalEndpoints(requests),
726
+ ...createIntentionEndpoints(requests)
180
727
  };
181
728
  }
182
729
  //#endregion
@@ -1493,7 +2040,7 @@ function captureFrame() {
1493
2040
  }
1494
2041
  //#endregion
1495
2042
  //#region src/sessions/capture/page/interaction-recorder.ts
1496
- const DWELL_MS = 700;
2043
+ const MIN_DWELL_MS = 700;
1497
2044
  const POINTER_SAMPLE_INTERVAL_MS = 100;
1498
2045
  const SETTLE_DEBOUNCE_MS = 400;
1499
2046
  function eventElement(event) {
@@ -1504,14 +2051,14 @@ function eventElement(event) {
1504
2051
  return element === document.body || element === document.documentElement ? null : element;
1505
2052
  }
1506
2053
  function pointerOf(event) {
1507
- return event instanceof MouseEvent ? {
2054
+ return {
1508
2055
  x: Math.round(event.clientX),
1509
2056
  y: Math.round(event.clientY)
1510
- } : {
1511
- x: 0,
1512
- y: 0
1513
2057
  };
1514
2058
  }
2059
+ function isPhysicalPointerClick(event) {
2060
+ return event instanceof MouseEvent && event.detail > 0;
2061
+ }
1515
2062
  function debounced(signal, run) {
1516
2063
  let settleTimer;
1517
2064
  signal.addEventListener("abort", () => window.clearTimeout(settleTimer));
@@ -1528,34 +2075,50 @@ function recordClick(options) {
1528
2075
  element: describeElementRef(element, options),
1529
2076
  frame: captureFrame(),
1530
2077
  kind: "click",
1531
- pointer: pointerOf(event)
2078
+ pointer: isPhysicalPointerClick(event) ? pointerOf(event) : null
1532
2079
  });
1533
2080
  };
1534
2081
  }
1535
2082
  function watchDwell(options, signal) {
1536
2083
  let hovered = null;
1537
2084
  let lastSampleAt = Number.NEGATIVE_INFINITY;
1538
- let dwellTimer;
1539
- signal.addEventListener("abort", () => window.clearTimeout(dwellTimer));
1540
- return (event) => {
2085
+ let entered = null;
2086
+ const settle = () => {
2087
+ if (!entered) return;
2088
+ const durationMs = options.atMs() - entered.atMs;
2089
+ if (durationMs >= MIN_DWELL_MS) options.onEvent({
2090
+ ...entered,
2091
+ durationMs
2092
+ });
2093
+ entered = null;
2094
+ };
2095
+ signal.addEventListener("abort", settle);
2096
+ const onPointerMove = (event) => {
2097
+ if (!(event instanceof MouseEvent)) return;
1541
2098
  const now = Date.now();
1542
2099
  if (now - lastSampleAt < POINTER_SAMPLE_INTERVAL_MS) return;
1543
2100
  lastSampleAt = now;
1544
2101
  const element = eventElement(event);
1545
2102
  if (element === hovered) return;
2103
+ settle();
1546
2104
  hovered = element;
1547
- window.clearTimeout(dwellTimer);
1548
- if (!element) return;
1549
- const atMs = options.atMs();
1550
- const pointer = pointerOf(event);
1551
- dwellTimer = window.setTimeout(() => options.onEvent({
1552
- atMs,
1553
- durationMs: DWELL_MS,
2105
+ if (element) entered = {
2106
+ atMs: options.atMs(),
1554
2107
  element: describeElementRef(element, options),
1555
2108
  frame: captureFrame(),
1556
2109
  kind: "dwell",
1557
- pointer
1558
- }), DWELL_MS);
2110
+ pointer: pointerOf(event)
2111
+ };
2112
+ };
2113
+ const onPointerLeaveWindow = (event) => {
2114
+ if (event instanceof MouseEvent && event.relatedTarget === null) {
2115
+ settle();
2116
+ hovered = null;
2117
+ }
2118
+ };
2119
+ return {
2120
+ onPointerLeaveWindow,
2121
+ onPointerMove
1559
2122
  };
1560
2123
  }
1561
2124
  function startInteractionRecorder(options) {
@@ -1579,8 +2142,10 @@ function startInteractionRecorder(options) {
1579
2142
  viewportHeight: window.innerHeight,
1580
2143
  viewportWidth: window.innerWidth
1581
2144
  }));
2145
+ const dwell = watchDwell(options, signal);
1582
2146
  document.addEventListener("click", recordClick(options), listen);
1583
- document.addEventListener("pointermove", watchDwell(options, signal), listen);
2147
+ document.addEventListener("pointermove", dwell.onPointerMove, listen);
2148
+ document.addEventListener("pointerout", dwell.onPointerLeaveWindow, listen);
1584
2149
  window.addEventListener("scroll", recordScroll, listen);
1585
2150
  window.addEventListener("resize", recordViewport, listen);
1586
2151
  return { stop: () => controller.abort() };
@@ -1713,25 +2278,26 @@ function blankFieldValues(root) {
1713
2278
  const NAVIGATION_SETTLE_MS = 1500;
1714
2279
  const CAPTURE_FAILED = "The page could not be captured.";
1715
2280
  const CAPTURE_TOO_LARGE = "The screenshot was larger than the upload limit.";
1716
- async function shoot(base, maxImageBytes) {
2281
+ const STOPPED_BEFORE_CAPTURE = "Recording stopped before the screenshot was taken.";
2282
+ const withoutImage = (base, missingReason) => ({
2283
+ ...base,
2284
+ capturedAtMs: null,
2285
+ image: null,
2286
+ missingReason
2287
+ });
2288
+ async function shoot(base, options) {
1717
2289
  const image = await captureViewport({
1718
2290
  excludeSelectors: SESSION_MASK_SELECTORS,
1719
2291
  prepareClone: blankFieldValues
1720
2292
  });
1721
2293
  releaseCapture(image);
1722
- if (!image) return {
1723
- ...base,
1724
- image: null,
1725
- missingReason: CAPTURE_FAILED
1726
- };
1727
- if (image.blob.size > maxImageBytes) return {
1728
- ...base,
1729
- image: null,
1730
- missingReason: CAPTURE_TOO_LARGE
1731
- };
2294
+ if (!image) return withoutImage(base, CAPTURE_FAILED);
2295
+ if (image.blob.size > options.limits.maxImageBytes) return withoutImage(base, CAPTURE_TOO_LARGE);
1732
2296
  const { blob, height, width } = image;
1733
2297
  return {
1734
2298
  ...base,
2299
+ capturedAtMs: options.atMs(),
2300
+ frame: captureFrame(),
1735
2301
  image: {
1736
2302
  blob,
1737
2303
  height,
@@ -1740,31 +2306,43 @@ async function shoot(base, maxImageBytes) {
1740
2306
  missingReason: null
1741
2307
  };
1742
2308
  }
2309
+ function evidenceBase(trigger, request, options) {
2310
+ return {
2311
+ atMs: options.atMs(),
2312
+ clientKey: crypto.randomUUID(),
2313
+ element: request.element ? describeElementRef(request.element, options) : null,
2314
+ frame: captureFrame(),
2315
+ note: request.note ?? null,
2316
+ trigger
2317
+ };
2318
+ }
1743
2319
  function createEvidenceScheduler(options) {
1744
2320
  const { limits } = options;
1745
2321
  let captured = 0;
1746
2322
  let lastCaptureAtMs = Number.NEGATIVE_INFINITY;
1747
2323
  let navigationTimer;
1748
2324
  let previousCapture = Promise.resolve();
2325
+ let collectionRun = 0;
1749
2326
  const take = (trigger, request) => {
1750
2327
  if (captured >= limits.maxEvidence) return null;
1751
2328
  captured += 1;
1752
- const atMs = options.atMs();
1753
- lastCaptureAtMs = atMs;
1754
- const clientKey = crypto.randomUUID();
1755
- const base = {
1756
- atMs,
1757
- clientKey,
1758
- element: request.element ? describeElementRef(request.element, options) : null,
1759
- frame: captureFrame(),
1760
- note: request.note ?? null,
1761
- trigger
2329
+ const base = evidenceBase(trigger, request, options);
2330
+ lastCaptureAtMs = base.atMs;
2331
+ const takenInRun = collectionRun;
2332
+ const isCurrent = () => takenInRun === collectionRun;
2333
+ const stopped = withoutImage(base, STOPPED_BEFORE_CAPTURE);
2334
+ const captureUnlessStopped = async () => {
2335
+ const evidence = isCurrent() ? await shoot(base, options) : stopped;
2336
+ return isCurrent() ? evidence : stopped;
1762
2337
  };
1763
- previousCapture = previousCapture.then(() => shoot(base, limits.maxImageBytes)).then(options.onEvidence);
1764
- return clientKey;
2338
+ previousCapture = previousCapture.then(captureUnlessStopped).then(options.onEvidence);
2339
+ return base.clientKey;
1765
2340
  };
1766
2341
  return {
1767
- cancelPending: () => window.clearTimeout(navigationTimer),
2342
+ cancelPending: () => {
2343
+ window.clearTimeout(navigationTimer);
2344
+ collectionRun += 1;
2345
+ },
1768
2346
  capture: (request) => take(request.trigger, request),
1769
2347
  navigated: () => {
1770
2348
  window.clearTimeout(navigationTimer);
@@ -1866,7 +2444,8 @@ function startPayload(draft) {
1866
2444
  },
1867
2445
  startedAt: draft.startedAt,
1868
2446
  surface: draft.surface,
1869
- ...draft.title ? { title: draft.title } : {}
2447
+ ...draft.title ? { title: draft.title } : {},
2448
+ ...draft.continues ? { continues: draft.continues } : {}
1870
2449
  },
1871
2450
  kind: "start"
1872
2451
  };
@@ -1876,6 +2455,7 @@ function evidencePayload(captured) {
1876
2455
  return {
1877
2456
  evidence: {
1878
2457
  atMs: captured.atMs,
2458
+ capturedAtMs: captured.capturedAtMs ?? void 0,
1879
2459
  clientKey: captured.clientKey,
1880
2460
  element: element ?? void 0,
1881
2461
  frame: captured.frame,
@@ -2252,6 +2832,7 @@ var RecordingController = class {
2252
2832
  if (!voice) return;
2253
2833
  const draft = newSessionDraft({
2254
2834
  accountKey: this.options.account.key,
2835
+ continues: options.continues,
2255
2836
  surface: this.options.surface,
2256
2837
  title: cleanText(options.title, MAX_TITLE_CHARS) ?? ""
2257
2838
  });
@@ -2603,6 +3184,9 @@ function shortDuration(ms) {
2603
3184
  if (minutes === 0) return `${seconds} s`;
2604
3185
  return seconds === 0 ? `${minutes} min` : `${minutes} min ${seconds} s`;
2605
3186
  }
3187
+ function preciseSeconds(ms) {
3188
+ return `${(Math.abs(ms) / SECOND_MS).toFixed(1)} s`;
3189
+ }
2606
3190
  function minutesLeft(remainingMs) {
2607
3191
  return Math.max(1, Math.ceil(remainingMs / MINUTE_MS));
2608
3192
  }
@@ -2626,12 +3210,13 @@ function sessionStatusLine(summary) {
2626
3210
  if (isAnalysisBusy(summary)) return "Reading";
2627
3211
  if (analysis.state === "failed") return "Analysis failed";
2628
3212
  if (analysis.state === "idle") return "Not analyzed yet";
2629
- return `${countLabel(counts.proposals, "proposal")} · ${counts.kept} kept`;
3213
+ return `${countLabel(counts.proposals, "intention")} · ${counts.kept} confirmed`;
2630
3214
  }
2631
3215
  function isDetailProcessing(detail) {
2632
3216
  const transcribing = detail.segments.some((segment) => segment.transcription.state === "pending" || segment.transcription.state === "running");
2633
3217
  const refining = detail.proposals.some((proposal) => proposal.refinement?.state === "running");
2634
- return transcribing || refining || isAnalysisBusy(detail.session) || detail.session.collection !== "ended";
3218
+ const comparing = detail.continuation?.contribution.state === "analyzing";
3219
+ return transcribing || refining || comparing || isAnalysisBusy(detail.session) || detail.session.collection !== "ended";
2635
3220
  }
2636
3221
  //#endregion
2637
3222
  //#region src/sessions/ui/session-icons.tsx
@@ -2917,6 +3502,34 @@ function RecordingLayer({ onFinished }) {
2917
3502
  })] });
2918
3503
  }
2919
3504
  //#endregion
3505
+ //#region src/sessions/ui/intention-styles.ts
3506
+ const INTENTION_STYLES = `
3507
+ .dt-ses-panel { display: contents; }
3508
+ .dt-ses-panel[hidden] { display: none; }
3509
+ .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; }
3510
+ .dt-ses-moment-item { display: flex; flex-direction: column; gap: 6px; padding: 8px 10px; border-radius: 10px; background: var(--rf-bg-subtle); }
3511
+ .dt-ses-moment-line { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 0; overflow-wrap: anywhere; }
3512
+ .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; }
3513
+ .dt-ses-moment-notices { color: var(--rf-text-muted); font-size: 12px; }
3514
+ .dt-ses-moment-shot { display: flex; flex-direction: column; gap: 4px; margin: 0; max-width: 100%; }
3515
+ .dt-ses-shot-mark[data-match="approximate"] { border-style: dashed; }
3516
+ .dt-ses-shot-pointer {
3517
+ position: absolute; width: 10px; height: 10px; margin: -5px 0 0 -5px; border-radius: 50%;
3518
+ background: var(--rf-accent); box-shadow: 0 0 0 2px var(--rf-bg); pointer-events: none;
3519
+ }
3520
+ .dt-ses-source { margin: 0; font-size: 12px; overflow-wrap: anywhere; }
3521
+ .dt-ses-context-line { display: block; }
3522
+ .dt-ses-attached { flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px; }
3523
+ .dt-ses-attached p { margin: 0; }
3524
+ .dt-ses-history[data-removed="true"] > .dt-ses-subhead { color: var(--rf-text-muted); text-decoration: line-through; }
3525
+ .dt-ses-change { padding: 8px 10px; border-radius: 10px; background: var(--rf-bg-subtle); list-style: none; }
3526
+ .dt-ses-change[data-decision="rejected"] .dt-ses-diff { color: var(--rf-text-muted); text-decoration: line-through; }
3527
+ .dt-ses-diff { display: grid; grid-template-columns: auto 1fr; gap: 2px 8px; margin: 0; overflow-wrap: anywhere; }
3528
+ p.dt-ses-diff { display: block; }
3529
+ .dt-ses-diff dd { margin: 0; }
3530
+ .dt-card:focus-visible { outline: 2px solid var(--rf-accent); outline-offset: 2px; }
3531
+ `;
3532
+ //#endregion
2920
3533
  //#region src/sessions/ui/sessions-styles.ts
2921
3534
  const MONO_FONT$1 = "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace";
2922
3535
  const SESSIONS_STYLES = `${`
@@ -3011,7 +3624,7 @@ ${`
3011
3624
  .dt-team-anchor[data-edge="top"] { top: calc(var(--rf-viewport-top, 0px) + env(safe-area-inset-top) + var(--dt-edge-gap)); }
3012
3625
  .dt-team-card { display: flex; flex-direction: column; gap: 6px; padding: 12px 14px; border-radius: 14px; animation: dt-popover-in 200ms var(--rf-ease); }
3013
3626
  .dt-bar > .dt-btn { margin-inline: 2px 4px; }
3014
- `}`;
3627
+ `}${INTENTION_STYLES}`;
3015
3628
  //#endregion
3016
3629
  //#region src/devtools/client/clipboard.ts
3017
3630
  /**
@@ -3661,21 +4274,28 @@ function evidenceCaption(evidence) {
3661
4274
  const element = evidence.element ? ` · ${evidence.element.label}` : "";
3662
4275
  return `${TRIGGER_LABELS[evidence.trigger]}${element}`;
3663
4276
  }
3664
- function highlightStyle(evidence) {
3665
- const { element, frame } = evidence;
3666
- if (!(element && frame) || frame.viewportWidth === 0 || frame.viewportHeight === 0) return null;
3667
- const percent = (value, total) => `${value / total * 100}%`;
4277
+ const hasViewport = (frame) => frame.viewportWidth > 0 && frame.viewportHeight > 0;
4278
+ const percent = (value, total) => `${value / total * 100}%`;
4279
+ function percentBox(rect, frame) {
4280
+ if (!hasViewport(frame)) return null;
4281
+ return {
4282
+ height: percent(rect.height, frame.viewportHeight),
4283
+ left: percent(rect.x, frame.viewportWidth),
4284
+ top: percent(rect.y, frame.viewportHeight),
4285
+ width: percent(rect.width, frame.viewportWidth)
4286
+ };
4287
+ }
4288
+ function percentPoint(point, frame) {
4289
+ if (!hasViewport(frame)) return null;
3668
4290
  return {
3669
- height: percent(element.rect.height, frame.viewportHeight),
3670
- left: percent(element.rect.x, frame.viewportWidth),
3671
- top: percent(element.rect.y, frame.viewportHeight),
3672
- width: percent(element.rect.width, frame.viewportWidth)
4291
+ left: percent(point.x, frame.viewportWidth),
4292
+ top: percent(point.y, frame.viewportHeight)
3673
4293
  };
3674
4294
  }
3675
4295
  function EvidenceThumb({ evidence, isLarge = false }) {
3676
- const { image } = evidence;
4296
+ const { element, frame, image } = evidence;
3677
4297
  if (!image?.url) return null;
3678
- const highlight = highlightStyle(evidence);
4298
+ const highlight = element && frame ? percentBox(element.rect, frame) : null;
3679
4299
  return /* @__PURE__ */ jsxs("span", {
3680
4300
  className: "dt-ses-shot",
3681
4301
  "data-large": isLarge,
@@ -3692,13 +4312,246 @@ function EvidenceThumb({ evidence, isLarge = false }) {
3692
4312
  });
3693
4313
  }
3694
4314
  //#endregion
4315
+ //#region src/sessions/ui/review/continuation/intention-history.tsx
4316
+ const FIELD_LABELS = {
4317
+ need: "Need",
4318
+ observed: "Observed",
4319
+ response: "Considered response",
4320
+ setAside: "Set aside",
4321
+ unknowns: "To check"
4322
+ };
4323
+ function fieldValueText(value) {
4324
+ return (Array.isArray(value) ? value.join("; ") : value) || "—";
4325
+ }
4326
+ function AppliedChanges({ applied }) {
4327
+ if (applied.length === 0) return null;
4328
+ return /* @__PURE__ */ jsx("ul", {
4329
+ className: "dt-ses-rows",
4330
+ children: applied.map((change) => /* @__PURE__ */ jsxs("li", {
4331
+ className: "dt-ses-row",
4332
+ children: [/* @__PURE__ */ jsxs("p", {
4333
+ className: "dt-meta",
4334
+ children: [
4335
+ "Accepted into revision ",
4336
+ change.revision,
4337
+ " by",
4338
+ " ",
4339
+ change.byName ?? "a teammate",
4340
+ " · ",
4341
+ formatDate(change.at)
4342
+ ]
4343
+ }), /* @__PURE__ */ jsx("ul", {
4344
+ className: "dt-ses-list",
4345
+ children: change.fields.map((field) => /* @__PURE__ */ jsxs("li", { children: [
4346
+ FIELD_LABELS[field.field],
4347
+ ": ",
4348
+ fieldValueText(field.before),
4349
+ " →",
4350
+ " ",
4351
+ fieldValueText(field.after)
4352
+ ] }, field.field))
4353
+ })]
4354
+ }, change.revision))
4355
+ });
4356
+ }
4357
+ function HistoryExtracts({ evidence, quotes }) {
4358
+ return /* @__PURE__ */ jsxs(Fragment, { children: [quotes.map((quote) => /* @__PURE__ */ jsxs("blockquote", {
4359
+ className: "dt-ses-moment-quote",
4360
+ children: [
4361
+ /* @__PURE__ */ jsx("span", {
4362
+ className: "dt-ses-time",
4363
+ children: formatClock(quote.startMs)
4364
+ }),
4365
+ ` “${quote.text}”`,
4366
+ quote.context.map((line) => /* @__PURE__ */ jsx("span", {
4367
+ className: "dt-meta dt-ses-context-line",
4368
+ children: line
4369
+ }, line))
4370
+ ]
4371
+ }, quote.segmentId)), evidence.length > 0 && /* @__PURE__ */ jsx("div", {
4372
+ className: "dt-ses-thumbs",
4373
+ children: evidence.map((item) => /* @__PURE__ */ jsx(EvidenceThumb, { evidence: item }, item.id))
4374
+ })] });
4375
+ }
4376
+ function entryTitle(entry) {
4377
+ const { session } = entry;
4378
+ return entry.origin === "continuation" ? `Continued in “${session.title}” · ${formatDate(session.startedAt)}` : `Added from “${session.title}” · ${formatDate(session.startedAt)}`;
4379
+ }
4380
+ function HistoryEntry({ context, entry }) {
4381
+ const evidenceIds = new Set(entry.evidenceIds);
4382
+ const evidence = context.detail.contributedEvidence.filter((item) => evidenceIds.has(item.id));
4383
+ return /* @__PURE__ */ jsxs("li", {
4384
+ className: "dt-ses-history",
4385
+ "data-removed": entry.detachedAt !== null,
4386
+ children: [
4387
+ /* @__PURE__ */ jsx("p", {
4388
+ className: "dt-ses-subhead",
4389
+ children: entryTitle(entry)
4390
+ }),
4391
+ entry.detachedAt !== null && /* @__PURE__ */ jsxs("p", {
4392
+ className: "dt-meta",
4393
+ children: [
4394
+ "Removed on ",
4395
+ formatDate(entry.detachedAt),
4396
+ ": what it added was taken back."
4397
+ ]
4398
+ }),
4399
+ /* @__PURE__ */ jsx(AppliedChanges, { applied: entry.applied }),
4400
+ entry.extractsHidden ? /* @__PURE__ */ jsx("p", {
4401
+ className: "dt-meta",
4402
+ children: "Extracts are private to the author"
4403
+ }) : /* @__PURE__ */ jsx(HistoryExtracts, {
4404
+ evidence,
4405
+ quotes: entry.quotes
4406
+ }),
4407
+ entry.session.canOpen && /* @__PURE__ */ jsx("button", {
4408
+ className: "dt-ses-link",
4409
+ onClick: () => context.navigation.onOpenSession(entry.session.id),
4410
+ type: "button",
4411
+ children: "Open session"
4412
+ })
4413
+ ]
4414
+ });
4415
+ }
4416
+ function IntentionHistory({ context, intentionId }) {
4417
+ const entries = context.detail.history.filter((entry) => entry.intentionId === intentionId);
4418
+ if (entries.length === 0) return null;
4419
+ return /* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("h4", {
4420
+ className: "dt-ses-label",
4421
+ children: "History"
4422
+ }), /* @__PURE__ */ jsx("ul", {
4423
+ className: "dt-ses-rows",
4424
+ children: entries.map((entry) => /* @__PURE__ */ jsx(HistoryEntry, {
4425
+ context,
4426
+ entry
4427
+ }, entry.id))
4428
+ })] });
4429
+ }
4430
+ //#endregion
4431
+ //#region src/sessions/ui/handoff/handoff-changes.tsx
4432
+ function differences(proposal, last) {
4433
+ return [
4434
+ {
4435
+ after: proposal.need,
4436
+ before: last.need,
4437
+ label: "Need"
4438
+ },
4439
+ {
4440
+ after: fieldValueText(proposal.response),
4441
+ before: fieldValueText(last.response),
4442
+ label: "Considered response"
4443
+ },
4444
+ {
4445
+ after: fieldValueText(proposal.unknowns),
4446
+ before: last.unknowns === null ? null : fieldValueText(last.unknowns),
4447
+ label: "To check"
4448
+ }
4449
+ ].flatMap(({ after, before, label }) => before === null || before === after ? [] : [{
4450
+ after,
4451
+ before,
4452
+ label
4453
+ }]);
4454
+ }
4455
+ function ChangeSummary({ last, proposal }) {
4456
+ return /* @__PURE__ */ jsxs("li", {
4457
+ className: "dt-ses-row",
4458
+ children: [
4459
+ /* @__PURE__ */ jsx("p", {
4460
+ className: "dt-ses-subhead",
4461
+ children: proposal.need
4462
+ }),
4463
+ /* @__PURE__ */ jsxs("p", {
4464
+ className: "dt-meta",
4465
+ children: [
4466
+ "Changed since the brief of ",
4467
+ formatDate(last.at),
4468
+ " (revision",
4469
+ " ",
4470
+ last.revision,
4471
+ " → ",
4472
+ proposal.revision,
4473
+ ")"
4474
+ ]
4475
+ }),
4476
+ /* @__PURE__ */ jsx("ul", {
4477
+ className: "dt-ses-list",
4478
+ children: differences(proposal, last).map((difference) => /* @__PURE__ */ jsxs("li", { children: [
4479
+ difference.label,
4480
+ ": ",
4481
+ difference.before,
4482
+ " → ",
4483
+ difference.after
4484
+ ] }, difference.label))
4485
+ })
4486
+ ]
4487
+ });
4488
+ }
4489
+ function ChangedSinceBriefs({ detail, proposalIds }) {
4490
+ const selected = new Set(proposalIds);
4491
+ const changed = detail.proposals.flatMap((proposal) => {
4492
+ const last = proposal.lastHandoff;
4493
+ return selected.has(proposal.id) && last && last.revision < proposal.revision ? [{
4494
+ last,
4495
+ proposal
4496
+ }] : [];
4497
+ });
4498
+ if (changed.length === 0) return null;
4499
+ return /* @__PURE__ */ jsx("ul", {
4500
+ className: "dt-ses-rows",
4501
+ children: changed.map(({ last, proposal }) => /* @__PURE__ */ jsx(ChangeSummary, {
4502
+ last,
4503
+ proposal
4504
+ }, proposal.id))
4505
+ });
4506
+ }
4507
+ function deliveryLine(handoff) {
4508
+ const kinds = new Set(handoff.deliveries.map((delivery) => delivery.kind));
4509
+ if (kinds.size === 0) return "not copied or exported";
4510
+ return [...kinds].join(" and ");
4511
+ }
4512
+ function intentionsLine(handoff) {
4513
+ const [only, ...others] = handoff.proposals;
4514
+ if (only && others.length === 0) return `“${only.need}” revision ${only.revision}`;
4515
+ return countLabel(handoff.proposals.length, "intention");
4516
+ }
4517
+ function PreviousBriefs({ handoffs }) {
4518
+ if (handoffs.length === 0) return null;
4519
+ return /* @__PURE__ */ jsxs("section", {
4520
+ className: "dt-ses-form",
4521
+ children: [/* @__PURE__ */ jsx("h3", {
4522
+ className: "dt-ses-label",
4523
+ children: "Previous briefs"
4524
+ }), /* @__PURE__ */ jsx("ul", {
4525
+ className: "dt-ses-rows",
4526
+ children: handoffs.map((handoff) => {
4527
+ const date = formatDate(handoff.createdAt);
4528
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("details", { children: [/* @__PURE__ */ jsxs("summary", { children: [
4529
+ date,
4530
+ " · ",
4531
+ intentionsLine(handoff),
4532
+ " · ",
4533
+ deliveryLine(handoff)
4534
+ ] }), /* @__PURE__ */ jsx("textarea", {
4535
+ "aria-label": `Brief of ${date}`,
4536
+ className: "dt-field dt-ses-brief",
4537
+ readOnly: true,
4538
+ rows: 8,
4539
+ value: handoff.brief
4540
+ })] }) }, handoff.id);
4541
+ })
4542
+ })]
4543
+ });
4544
+ }
4545
+ //#endregion
3695
4546
  //#region src/sessions/ui/handoff/handoff-selection.tsx
3696
4547
  function keptProposals(detail) {
3697
4548
  return detail.proposals.filter((proposal) => proposal.decision === "kept" && proposal.mergedIntoId === null);
3698
4549
  }
3699
4550
  function evidenceOf(detail, proposals) {
3700
- const cited = new Set(proposals.flatMap((proposal) => proposal.evidenceIds));
3701
- return detail.evidence.filter((item) => cited.has(item.id) && item.status === "available");
4551
+ const intentionIds = new Set(proposals.map((proposal) => proposal.id));
4552
+ const contributed = detail.history.filter((entry) => intentionIds.has(entry.intentionId) && entry.detachedAt === null);
4553
+ const cited = /* @__PURE__ */ new Set([...proposals.flatMap((proposal) => proposal.evidenceIds), ...contributed.flatMap((entry) => entry.evidenceIds)]);
4554
+ return [...detail.evidence, ...detail.contributedEvidence].filter((item) => cited.has(item.id) && item.status === "available");
3702
4555
  }
3703
4556
  function initialSelection(detail) {
3704
4557
  const proposals = keptProposals(detail);
@@ -3746,7 +4599,7 @@ function HandoffChoices({ detail, onChange, selection }) {
3746
4599
  const chosenProposalIds = new Set(selection.proposalIds);
3747
4600
  const chosenEvidenceIds = new Set(selection.evidenceIds);
3748
4601
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ChoiceGroup, {
3749
- legend: "Proposals",
4602
+ legend: "Intentions",
3750
4603
  children: proposals.map((proposal) => /* @__PURE__ */ jsx(CheckRow, {
3751
4604
  isChecked: chosenProposalIds.has(proposal.id),
3752
4605
  label: proposal.need,
@@ -3883,6 +4736,10 @@ function SelectionStep({ detail, handoff }) {
3883
4736
  onChange: handoff.select,
3884
4737
  selection: handoff.selection
3885
4738
  }),
4739
+ /* @__PURE__ */ jsx(ChangedSinceBriefs, {
4740
+ detail,
4741
+ proposalIds: handoff.selection.proposalIds
4742
+ }),
3886
4743
  /* @__PURE__ */ jsxs("div", {
3887
4744
  className: "dt-actions",
3888
4745
  children: [/* @__PURE__ */ jsx("button", {
@@ -3933,196 +4790,60 @@ function HandoffView({ detail, onClose, review }) {
3933
4790
  /* @__PURE__ */ jsx("p", {
3934
4791
  className: "dt-meta",
3935
4792
  children: "Copied or exported briefs can't be recalled."
3936
- })
4793
+ }),
4794
+ /* @__PURE__ */ jsx(PreviousBriefs, { handoffs: detail.handoffs })
3937
4795
  ]
3938
4796
  });
3939
4797
  }
3940
4798
  //#endregion
3941
- //#region src/sessions/ui/review/proposals/proposal-details.tsx
3942
- function ListSection({ items, title }) {
3943
- if (items.length === 0) return null;
3944
- return /* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("h4", {
3945
- className: "dt-ses-label",
3946
- children: title
3947
- }), /* @__PURE__ */ jsx("ul", {
3948
- className: "dt-ses-list",
3949
- children: items.map((item) => /* @__PURE__ */ jsx("li", { children: item }, item))
3950
- })] });
4799
+ //#region src/sessions/ui/review/guarded-save.tsx
4800
+ function useGuardedSave() {
4801
+ const { onUnauthorized } = useSessionWorkspace();
4802
+ const [problem, setProblem] = useState(null);
4803
+ const [isSaving, setIsSaving] = useState(false);
4804
+ const save = async (write) => {
4805
+ setIsSaving(true);
4806
+ setProblem(null);
4807
+ try {
4808
+ await write();
4809
+ } catch (error) {
4810
+ if (isUnauthorized(error)) onUnauthorized();
4811
+ else if (error instanceof SessionApiError && error.status === 409) setProblem({ kind: "stale" });
4812
+ else setProblem({
4813
+ kind: "failed",
4814
+ message: failureMessage(error)
4815
+ });
4816
+ }
4817
+ setIsSaving(false);
4818
+ };
4819
+ return {
4820
+ isSaving,
4821
+ problem,
4822
+ save
4823
+ };
3951
4824
  }
3952
- function RefinementState({ onRetry, proposal }) {
3953
- const { refinement } = proposal;
3954
- if (refinement?.state === "running") return /* @__PURE__ */ jsx("p", {
3955
- className: "dt-status",
3956
- children: "Refining…"
3957
- });
3958
- if (refinement?.state === "outdated") return /* @__PURE__ */ jsx("p", {
3959
- className: "dt-meta",
3960
- children: "The proposal changed while it was being refined. Add your precision again."
3961
- });
3962
- if (refinement?.state !== "failed") return null;
4825
+ function SaveProblemMessage({ onReload, problem }) {
3963
4826
  return /* @__PURE__ */ jsxs("div", {
3964
4827
  className: "dt-actions",
3965
- children: [/* @__PURE__ */ jsxs("p", {
4828
+ role: "alert",
4829
+ children: [/* @__PURE__ */ jsx("p", {
3966
4830
  className: "dt-error",
3967
- children: ["Refining failed: ", refinement.error ?? "Unknown error."]
3968
- }), /* @__PURE__ */ jsx("button", {
4831
+ children: problem.kind === "stale" ? "Someone changed this since you opened it. Reload it, then edit again." : `Not saved: ${problem.message}`
4832
+ }), problem.kind === "stale" && /* @__PURE__ */ jsx("button", {
3969
4833
  className: "dt-btn",
3970
- onClick: onRetry,
4834
+ onClick: onReload,
3971
4835
  type: "button",
3972
- children: "Retry"
4836
+ children: "Reload"
3973
4837
  })]
3974
4838
  });
3975
4839
  }
3976
- function ClarifyBox({ context, proposal }) {
3977
- const [text, setText] = useState("");
3978
- const isRefining = proposal.refinement?.state === "running";
3979
- const lastClarification = proposal.clarifications.at(-1)?.text;
3980
- const send = async (value) => {
3981
- if (await context.actions.clarify(proposal, value)) setText("");
3982
- };
3983
- return /* @__PURE__ */ jsxs("div", {
3984
- className: "dt-ses-form",
3985
- children: [
3986
- proposal.question && /* @__PURE__ */ jsx("p", {
3987
- className: "dt-note-text",
3988
- children: proposal.question
3989
- }),
3990
- /* @__PURE__ */ jsx("textarea", {
3991
- "aria-label": proposal.question ? "Your answer" : "Add a precision…",
3992
- className: "dt-field",
3993
- disabled: isRefining,
3994
- maxLength: 1e3,
3995
- onChange: (event) => setText(event.target.value),
3996
- placeholder: "Add a precision…",
3997
- rows: 2,
3998
- value: text
3999
- }),
4000
- /* @__PURE__ */ jsx("div", {
4001
- className: "dt-actions",
4002
- children: /* @__PURE__ */ jsx("button", {
4003
- className: "dt-btn",
4004
- disabled: isRefining || text.trim() === "",
4005
- onClick: () => send(text.trim()),
4006
- type: "button",
4007
- children: "Update proposal"
4008
- })
4009
- }),
4010
- /* @__PURE__ */ jsx(RefinementState, {
4011
- onRetry: () => lastClarification && send(lastClarification),
4012
- proposal
4013
- })
4014
- ]
4015
- });
4016
- }
4017
- function Sources({ context, proposal }) {
4018
- const segmentIds = new Set(proposal.segmentIds);
4019
- const evidenceIds = new Set(proposal.evidenceIds);
4020
- const segments = context.detail.segments.filter((segment) => segmentIds.has(segment.id));
4021
- const evidence = context.detail.evidence.filter((item) => evidenceIds.has(item.id));
4022
- if (segments.length + evidence.length === 0) return null;
4023
- return /* @__PURE__ */ jsxs("section", { children: [
4024
- /* @__PURE__ */ jsx("h4", {
4025
- className: "dt-ses-label",
4026
- children: "Sources"
4027
- }),
4028
- segments.map((segment) => /* @__PURE__ */ jsxs("button", {
4029
- className: "dt-ses-quote",
4030
- onClick: () => context.onShowSegment(segment.id),
4031
- type: "button",
4032
- children: [/* @__PURE__ */ jsx("span", {
4033
- className: "dt-ses-time",
4034
- children: formatClock(segment.startMs)
4035
- }), ` “${segment.text}”`]
4036
- }, segment.id)),
4037
- /* @__PURE__ */ jsx("div", {
4038
- className: "dt-ses-thumbs",
4039
- children: evidence.map((item) => /* @__PURE__ */ jsx(EvidenceThumb, { evidence: item }, item.id))
4040
- })
4041
- ] });
4042
- }
4043
- function ProposalDetails({ context, proposal }) {
4044
- return /* @__PURE__ */ jsxs(Fragment, { children: [
4045
- /* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("h4", {
4046
- className: "dt-ses-label",
4047
- children: "What you said or showed — not verified"
4048
- }), /* @__PURE__ */ jsx("p", {
4049
- className: "dt-note-text",
4050
- children: proposal.observed
4051
- })] }),
4052
- proposal.response && /* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("h4", {
4053
- className: "dt-ses-label",
4054
- children: "Considered response"
4055
- }), /* @__PURE__ */ jsx("p", {
4056
- className: "dt-note-text",
4057
- children: proposal.response
4058
- })] }),
4059
- /* @__PURE__ */ jsx(ListSection, {
4060
- items: proposal.setAside,
4061
- title: "Set aside"
4062
- }),
4063
- /* @__PURE__ */ jsx(ListSection, {
4064
- items: proposal.unknowns,
4065
- title: "Unknowns"
4066
- }),
4067
- context.canEdit && /* @__PURE__ */ jsx(ClarifyBox, {
4068
- context,
4069
- proposal
4070
- }),
4071
- /* @__PURE__ */ jsx(Sources, {
4072
- context,
4073
- proposal
4074
- })
4075
- ] });
4076
- }
4077
- //#endregion
4078
- //#region src/sessions/ui/review/guarded-save.tsx
4079
- function useGuardedSave() {
4080
- const { onUnauthorized } = useSessionWorkspace();
4081
- const [problem, setProblem] = useState(null);
4082
- const [isSaving, setIsSaving] = useState(false);
4083
- const save = async (write) => {
4084
- setIsSaving(true);
4085
- setProblem(null);
4086
- try {
4087
- await write();
4088
- } catch (error) {
4089
- if (isUnauthorized(error)) onUnauthorized();
4090
- else if (error instanceof SessionApiError && error.status === 409) setProblem({ kind: "stale" });
4091
- else setProblem({
4092
- kind: "failed",
4093
- message: failureMessage(error)
4094
- });
4095
- }
4096
- setIsSaving(false);
4097
- };
4098
- return {
4099
- isSaving,
4100
- problem,
4101
- save
4102
- };
4103
- }
4104
- function SaveProblemMessage({ onReload, problem }) {
4105
- return /* @__PURE__ */ jsxs("div", {
4106
- className: "dt-actions",
4107
- role: "alert",
4108
- children: [/* @__PURE__ */ jsx("p", {
4109
- className: "dt-error",
4110
- children: problem.kind === "stale" ? "Someone changed this since you opened it. Reload it, then edit again." : `Not saved: ${problem.message}`
4111
- }), problem.kind === "stale" && /* @__PURE__ */ jsx("button", {
4112
- className: "dt-btn",
4113
- onClick: onReload,
4114
- type: "button",
4115
- children: "Reload"
4116
- })]
4117
- });
4118
- }
4119
- //#endregion
4120
- //#region src/sessions/ui/review/use-session-detail.ts
4121
- const POLL_INTERVAL_MS = 2e3;
4122
- function nextRequest(current) {
4123
- return {
4124
- ...current,
4125
- count: current.count + 1
4840
+ //#endregion
4841
+ //#region src/sessions/ui/review/use-session-detail.ts
4842
+ const POLL_INTERVAL_MS = 2e3;
4843
+ function nextRequest(current) {
4844
+ return {
4845
+ ...current,
4846
+ count: current.count + 1
4126
4847
  };
4127
4848
  }
4128
4849
  function withProposal(detail, proposal) {
@@ -4342,147 +5063,2295 @@ function ProposalEditor({ onDone, proposal, review }) {
4342
5063
  ]
4343
5064
  });
4344
5065
  }
4345
- //#endregion
4346
- //#region src/sessions/ui/review/proposals/use-proposal-actions.ts
4347
- const NOTICE_NEED_LENGTH = 60;
4348
- const DECISION_NOTICE = {
4349
- dismissed: "Dismissed",
4350
- kept: "Kept",
4351
- later: "Saved for later",
4352
- pending: "Back to undecided"
4353
- };
4354
- function quotedNeed(need) {
4355
- return `“${need.length > NOTICE_NEED_LENGTH ? `${need.slice(0, 59)}…` : need}”`;
4356
- }
4357
- function useProposalSave(review) {
5066
+ function ResponseEditor({ onDone, proposal, review }) {
4358
5067
  const { api } = useSessionWorkspace();
4359
- const run = useSessionAction();
4360
- return async (failureLabel, request) => {
4361
- const proposal = await run(failureLabel, () => request(api));
4362
- if (proposal) review.patch((detail) => withProposal(detail, proposal));
4363
- return proposal;
4364
- };
4365
- }
4366
- function decideWithUndo(save) {
4367
- const restore = async (proposalId, decision) => {
4368
- if (await save("The decision was not undone", (client) => client.decideProposal({
4369
- decision,
4370
- proposalId
4371
- }))) announce("Decision undone");
4372
- };
4373
- return async (proposal, decision) => {
4374
- const previous = proposal.decision;
4375
- if (await save("The decision was not saved", (client) => client.decideProposal({
4376
- decision,
4377
- proposalId: proposal.id
4378
- }))) announce(`${DECISION_NOTICE[decision]} ${quotedNeed(proposal.need)}`, {
4379
- label: "Undo",
4380
- run: () => restore(proposal.id, previous)
4381
- });
4382
- };
4383
- }
4384
- function useProposalActions(review) {
4385
- const save = useProposalSave(review);
4386
- return {
4387
- clarify: (proposal, text) => save("The precision was not sent", (client) => client.clarifyProposal({
5068
+ const { isSaving, problem, save } = useGuardedSave();
5069
+ const [response, setResponse] = useState(proposal.response ?? "");
5070
+ const submit = () => save(async () => {
5071
+ const saved = await api.editProposal({
4388
5072
  expectedRevision: proposal.revision,
4389
5073
  proposalId: proposal.id,
4390
- text
4391
- })),
4392
- decide: decideWithUndo(save),
4393
- merge: async (targetId, sourceIds) => {
4394
- if (await save("The proposals were not merged", (client) => client.mergeProposals({
4395
- sourceIds,
4396
- targetId
4397
- }))) review.reload();
5074
+ response: response.trim() || null
5075
+ });
5076
+ review.patch((detail) => withProposal(detail, saved));
5077
+ onDone();
5078
+ });
5079
+ return /* @__PURE__ */ jsxs("form", {
5080
+ className: "dt-ses-form",
5081
+ onSubmit: (event) => {
5082
+ event.preventDefault();
5083
+ submit();
4398
5084
  },
4399
- save,
4400
- separate: (proposalId) => save("The suggestion was not cleared", (client) => client.separateProposal(proposalId)),
4401
- verify: (proposalId, { note, status }) => save("The check was not saved", (client) => client.verifyProposal({
4402
- note: note || void 0,
4403
- proposalId,
4404
- status
4405
- }))
4406
- };
4407
- }
4408
- //#endregion
4409
- //#region src/sessions/ui/review/proposals/verification.tsx
4410
- const OUTCOME_LABELS = {
4411
- needs_work: "Needs more work",
4412
- works: "Works"
4413
- };
4414
- function Verification({ context, proposal }) {
4415
- const [note, setNote] = useState(proposal.verification?.note ?? "");
4416
- if (proposal.handedOffRevision === null) return null;
4417
- const { verification } = proposal;
4418
- return /* @__PURE__ */ jsxs("fieldset", {
4419
- className: "dt-ses-verify",
4420
5085
  children: [
4421
- /* @__PURE__ */ jsx("legend", {
4422
- className: "dt-ses-label",
4423
- children: "Did the change work?"
4424
- }),
4425
- verification && /* @__PURE__ */ jsxs("p", {
4426
- className: "dt-meta",
4427
- children: [
4428
- "Checked: ",
4429
- OUTCOME_LABELS[verification.status],
4430
- verification.note ? ` · ${verification.note}` : ""
4431
- ]
4432
- }),
4433
- /* @__PURE__ */ jsx("input", {
4434
- "aria-label": "Note about the change (optional)",
4435
- className: "dt-field",
5086
+ /* @__PURE__ */ jsx(TextField, {
5087
+ label: "Considered response",
4436
5088
  maxLength: 1e3,
4437
- onChange: (event) => setNote(event.target.value),
4438
- placeholder: "Note (optional)",
4439
- value: note
5089
+ onChange: setResponse,
5090
+ rows: 3,
5091
+ value: response
4440
5092
  }),
4441
- /* @__PURE__ */ jsx("div", {
4442
- className: "dt-actions",
4443
- children: ["works", "needs_work"].map((status) => /* @__PURE__ */ jsx("button", {
4444
- "aria-pressed": verification?.status === status,
4445
- className: "dt-btn",
4446
- onClick: () => context.actions.verify(proposal.id, {
4447
- note: note.trim(),
4448
- status
4449
- }),
4450
- type: "button",
4451
- children: OUTCOME_LABELS[status]
4452
- }, status))
5093
+ problem && /* @__PURE__ */ jsx(SaveProblemMessage, {
5094
+ onReload: review.reload,
5095
+ problem
5096
+ }),
5097
+ /* @__PURE__ */ jsx(FormActions, {
5098
+ isSubmitDisabled: isSaving,
5099
+ onCancel: onDone,
5100
+ submitLabel: "Save response"
4453
5101
  })
4454
5102
  ]
4455
5103
  });
4456
5104
  }
4457
5105
  //#endregion
4458
- //#region src/sessions/ui/review/proposals/proposal-card.tsx
4459
- const DECISION_CHIPS = {
4460
- dismissed: "Dismissed",
4461
- kept: "Kept",
4462
- later: "Later"
4463
- };
4464
- const DECISION_BUTTONS = [
4465
- {
4466
- decision: "kept",
4467
- label: "Keep"
4468
- },
4469
- {
4470
- decision: "later",
4471
- label: "Later"
4472
- },
4473
- {
4474
- decision: "dismissed",
4475
- label: "Dismiss"
4476
- }
4477
- ];
4478
- function ProposalChips({ proposal }) {
4479
- const decisionChip = DECISION_CHIPS[proposal.decision];
4480
- const { handedOffRevision } = proposal;
4481
- let handoffChip = null;
4482
- if (handedOffRevision !== null) handoffChip = proposal.revision > handedOffRevision ? "Changed since handoff" : `Handed off rev ${handedOffRevision}`;
5106
+ //#region src/sessions/ui/review/continuation/share-preview.tsx
5107
+ function ExtractList({ detail, extracts }) {
5108
+ const segmentIds = new Set(extracts.segmentIds);
5109
+ const evidenceIds = new Set(extracts.evidenceIds);
5110
+ const quotes = detail.segments.filter((segment) => segmentIds.has(segment.id));
5111
+ const screenshots = detail.evidence.filter((item) => evidenceIds.has(item.id) && item.status === "available");
5112
+ if (quotes.length + screenshots.length === 0) return /* @__PURE__ */ jsx("p", { children: "No quote or screenshot from this private session becomes visible to your team." });
5113
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
5114
+ /* @__PURE__ */ jsx("p", { children: "These extracts from this private session become visible to your team:" }),
5115
+ /* @__PURE__ */ jsx("ul", {
5116
+ className: "dt-ses-list",
5117
+ children: quotes.map((segment) => /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("span", {
5118
+ className: "dt-ses-time",
5119
+ children: formatClock(segment.startMs)
5120
+ }), ` “${segment.text}”`] }, segment.id))
5121
+ }),
5122
+ /* @__PURE__ */ jsx("div", {
5123
+ className: "dt-ses-thumbs",
5124
+ children: screenshots.map((item) => /* @__PURE__ */ jsx(EvidenceThumb, { evidence: item }, item.id))
5125
+ })
5126
+ ] });
5127
+ }
5128
+ function SharePreview({ confirmation, detail, extracts }) {
5129
+ return /* @__PURE__ */ jsxs("div", {
5130
+ className: "dt-ses-banner",
5131
+ children: [/* @__PURE__ */ jsx(ExtractList, {
5132
+ detail,
5133
+ extracts
5134
+ }), /* @__PURE__ */ jsxs("div", {
5135
+ className: "dt-actions",
5136
+ children: [/* @__PURE__ */ jsx("button", {
5137
+ className: "dt-btn",
5138
+ "data-variant": "primary",
5139
+ disabled: confirmation.isBusy,
5140
+ onClick: confirmation.onConfirm,
5141
+ type: "button",
5142
+ children: confirmation.label
5143
+ }), /* @__PURE__ */ jsx("button", {
5144
+ className: "dt-btn",
5145
+ onClick: confirmation.onCancel,
5146
+ type: "button",
5147
+ children: "Cancel"
5148
+ })]
5149
+ })]
5150
+ });
5151
+ }
5152
+ //#endregion
5153
+ //#region src/sessions/ui/review/continuation/intention-change.tsx
5154
+ const KIND_LABELS$1 = {
5155
+ constraint: "New constraint",
5156
+ contradiction: "Contradiction",
5157
+ outcome: "Observed result",
5158
+ precision: "Precision",
5159
+ solution: "Different solution"
5160
+ };
5161
+ function ChangeValue({ change, intention }) {
5162
+ if (change.operation === "add") return /* @__PURE__ */ jsxs("p", {
5163
+ className: "dt-ses-diff",
5164
+ children: ["+ ", change.value]
5165
+ });
5166
+ if (change.operation === "remove") return /* @__PURE__ */ jsxs("p", {
5167
+ className: "dt-ses-diff",
5168
+ children: ["− ", change.value]
5169
+ });
5170
+ return /* @__PURE__ */ jsxs("dl", {
5171
+ className: "dt-ses-diff",
5172
+ children: [
5173
+ /* @__PURE__ */ jsx("dt", {
5174
+ className: "dt-ses-label",
5175
+ children: "Current"
5176
+ }),
5177
+ /* @__PURE__ */ jsx("dd", { children: fieldValueText(intention[change.field]) }),
5178
+ /* @__PURE__ */ jsx("dt", {
5179
+ className: "dt-ses-label",
5180
+ children: "Proposed"
5181
+ }),
5182
+ /* @__PURE__ */ jsx("dd", { children: change.value })
5183
+ ]
5184
+ });
5185
+ }
5186
+ function ChangeSources({ change, context }) {
5187
+ const segmentIds = new Set(change.segmentIds);
5188
+ const evidenceIds = new Set(change.evidenceIds);
5189
+ const segments = context.detail.segments.filter((segment) => segmentIds.has(segment.id));
5190
+ const evidence = context.detail.evidence.filter((item) => evidenceIds.has(item.id));
5191
+ return /* @__PURE__ */ jsxs(Fragment, { children: [segments.map((segment) => /* @__PURE__ */ jsxs("button", {
5192
+ className: "dt-ses-quote",
5193
+ onClick: (event) => context.navigation.onShowMoment(segment.id, event.currentTarget),
5194
+ type: "button",
5195
+ children: [/* @__PURE__ */ jsx("span", {
5196
+ className: "dt-ses-time",
5197
+ children: formatClock(segment.startMs)
5198
+ }), ` “${segment.text}”`]
5199
+ }, segment.id)), evidence.length > 0 && /* @__PURE__ */ jsx("div", {
5200
+ className: "dt-ses-thumbs",
5201
+ children: evidence.map((item) => /* @__PURE__ */ jsx(EvidenceThumb, { evidence: item }, item.id))
5202
+ })] });
5203
+ }
5204
+ function EditedValueForm({ handlers, initialValue, isSaving, label }) {
5205
+ const [value, setValue] = useState(initialValue);
5206
+ return /* @__PURE__ */ jsxs("form", {
5207
+ className: "dt-ses-form",
5208
+ onSubmit: (event) => {
5209
+ event.preventDefault();
5210
+ handlers.onAccept(value.trim());
5211
+ },
5212
+ children: [/* @__PURE__ */ jsx(TextField, {
5213
+ label,
5214
+ maxLength: 2e3,
5215
+ onChange: setValue,
5216
+ rows: 3,
5217
+ value
5218
+ }), /* @__PURE__ */ jsx(FormActions, {
5219
+ isSubmitDisabled: isSaving || value.trim() === "",
5220
+ onCancel: handlers.onCancel,
5221
+ submitLabel: "Accept edited"
5222
+ })]
5223
+ });
5224
+ }
5225
+ function PendingChoices({ handlers, isSaving }) {
5226
+ return /* @__PURE__ */ jsxs("div", {
5227
+ className: "dt-actions",
5228
+ children: [
5229
+ /* @__PURE__ */ jsx("button", {
5230
+ className: "dt-btn",
5231
+ "data-variant": "primary",
5232
+ disabled: isSaving,
5233
+ onClick: handlers.onAccept,
5234
+ type: "button",
5235
+ children: "Accept"
5236
+ }),
5237
+ /* @__PURE__ */ jsx("button", {
5238
+ className: "dt-btn",
5239
+ onClick: handlers.onEdit,
5240
+ type: "button",
5241
+ children: "Edit then accept"
5242
+ }),
5243
+ /* @__PURE__ */ jsx("button", {
5244
+ className: "dt-btn",
5245
+ onClick: handlers.onReject,
5246
+ type: "button",
5247
+ children: "Reject"
5248
+ })
5249
+ ]
5250
+ });
5251
+ }
5252
+ function useChangeAcceptance(change, scope) {
5253
+ const [mode, setMode] = useState({ kind: "idle" });
5254
+ const { isSaving, problem, save } = useGuardedSave();
5255
+ const accept = (value, shareWithTeam) => {
5256
+ if (scope.continuation.contribution.needsShareConsent && !shareWithTeam) {
5257
+ setMode({
5258
+ kind: "sharing",
5259
+ value
5260
+ });
5261
+ return;
5262
+ }
5263
+ save(() => scope.actions.accept({
5264
+ changeId: change.id,
5265
+ shareWithTeam,
5266
+ value
5267
+ }));
5268
+ };
5269
+ return {
5270
+ accept,
5271
+ isSaving,
5272
+ mode,
5273
+ problem,
5274
+ setMode
5275
+ };
5276
+ }
5277
+ function PendingForm({ acceptance, change, scope }) {
5278
+ const { accept, isSaving, mode, setMode } = acceptance;
5279
+ const idle = () => setMode({ kind: "idle" });
5280
+ if (mode.kind === "sharing") return /* @__PURE__ */ jsx(SharePreview, {
5281
+ confirmation: {
5282
+ isBusy: isSaving,
5283
+ label: "Share and accept",
5284
+ onCancel: idle,
5285
+ onConfirm: () => accept(mode.value, true)
5286
+ },
5287
+ detail: scope.context.detail,
5288
+ extracts: change
5289
+ });
5290
+ if (mode.kind === "editing") return /* @__PURE__ */ jsx(EditedValueForm, {
5291
+ handlers: {
5292
+ onAccept: (value) => accept(value, false),
5293
+ onCancel: idle
5294
+ },
5295
+ initialValue: change.value,
5296
+ isSaving,
5297
+ label: `${FIELD_LABELS[change.field]} (your version)`
5298
+ });
5299
+ return /* @__PURE__ */ jsx(PendingChoices, {
5300
+ handlers: {
5301
+ onAccept: () => accept(null, false),
5302
+ onEdit: () => setMode({ kind: "editing" }),
5303
+ onReject: () => scope.actions.decide(change.id, "rejected")
5304
+ },
5305
+ isSaving
5306
+ });
5307
+ }
5308
+ function PendingActions({ change, scope }) {
5309
+ const acceptance = useChangeAcceptance(change, scope);
5310
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(PendingForm, {
5311
+ acceptance,
5312
+ change,
5313
+ scope
5314
+ }), acceptance.problem && /* @__PURE__ */ jsx(SaveProblemMessage, {
5315
+ onReload: scope.context.review.reload,
5316
+ problem: acceptance.problem
5317
+ })] });
5318
+ }
5319
+ function ChangeDecision({ change, scope }) {
5320
+ if (change.decision === "accepted") return /* @__PURE__ */ jsx("p", {
5321
+ className: "dt-meta",
5322
+ children: "Accepted"
5323
+ });
5324
+ if (change.decision === "rejected") return /* @__PURE__ */ jsxs("div", {
5325
+ className: "dt-actions",
5326
+ children: [/* @__PURE__ */ jsx("p", {
5327
+ className: "dt-meta",
5328
+ children: "Rejected"
5329
+ }), scope.context.canEdit && /* @__PURE__ */ jsx("button", {
5330
+ className: "dt-btn",
5331
+ onClick: () => scope.actions.decide(change.id, "pending"),
5332
+ type: "button",
5333
+ children: "Undo"
5334
+ })]
5335
+ });
5336
+ return scope.context.canEdit ? /* @__PURE__ */ jsx(PendingActions, {
5337
+ change,
5338
+ scope
5339
+ }) : null;
5340
+ }
5341
+ function IntentionChangeRow({ change, intention, scope }) {
5342
+ return /* @__PURE__ */ jsxs("li", {
5343
+ className: "dt-ses-change",
5344
+ "data-decision": change.decision,
5345
+ children: [
5346
+ /* @__PURE__ */ jsxs("div", {
5347
+ className: "dt-ses-chips",
5348
+ children: [
5349
+ /* @__PURE__ */ jsx("span", {
5350
+ className: "dt-chip",
5351
+ children: KIND_LABELS$1[change.kind]
5352
+ }),
5353
+ /* @__PURE__ */ jsx("span", {
5354
+ className: "dt-chip",
5355
+ children: FIELD_LABELS[change.field]
5356
+ }),
5357
+ change.hypothesis && /* @__PURE__ */ jsx("span", {
5358
+ className: "dt-chip",
5359
+ "data-tone": "accent",
5360
+ children: "Hypothesis"
5361
+ })
5362
+ ]
5363
+ }),
5364
+ /* @__PURE__ */ jsx(ChangeValue, {
5365
+ change,
5366
+ intention
5367
+ }),
5368
+ /* @__PURE__ */ jsx("p", {
5369
+ className: "dt-meta",
5370
+ children: change.reason
5371
+ }),
5372
+ /* @__PURE__ */ jsx(ChangeSources, {
5373
+ change,
5374
+ context: scope.context
5375
+ }),
5376
+ /* @__PURE__ */ jsx(ChangeDecision, {
5377
+ change,
5378
+ scope
5379
+ })
5380
+ ]
5381
+ });
5382
+ }
5383
+ //#endregion
5384
+ //#region src/sessions/ui/review/continuation/use-update-actions.ts
5385
+ function withUpdate(detail, update) {
5386
+ return {
5387
+ ...detail,
5388
+ continuation: {
5389
+ contribution: update.contribution,
5390
+ intention: update.intention
5391
+ }
5392
+ };
5393
+ }
5394
+ function useChangeDecisions({ context, continuation }) {
5395
+ const { api } = useSessionWorkspace();
5396
+ const run = useSessionAction();
5397
+ const { contribution, intention } = continuation;
5398
+ const apply = (update) => context.review.patch((detail) => withUpdate(detail, update));
5399
+ return {
5400
+ accept: async ({ changeId, shareWithTeam, value }) => {
5401
+ const update = await api.acceptIntentionChanges({
5402
+ changes: [value === null ? { changeId } : {
5403
+ changeId,
5404
+ value
5405
+ }],
5406
+ contributionId: contribution.id,
5407
+ expectedRevision: intention?.revision ?? contribution.baseRevision,
5408
+ ...shareWithTeam ? { shareWithTeam } : {}
5409
+ });
5410
+ apply(update);
5411
+ announce("Change accepted into the intention");
5412
+ },
5413
+ decide: async (changeId, decision) => {
5414
+ const update = await run("The change was not updated", () => api.reopenOrRejectChange({
5415
+ changeId,
5416
+ contributionId: contribution.id,
5417
+ decision
5418
+ }));
5419
+ if (update) apply(update);
5420
+ }
5421
+ };
5422
+ }
5423
+ function useReloadingAction(context) {
5424
+ const run = useSessionAction();
5425
+ return async (failureLabel, action) => {
5426
+ if (await run(failureLabel, action) === null) return false;
5427
+ context.review.reload();
5428
+ return true;
5429
+ };
5430
+ }
5431
+ function useContributionActions({ context, continuation }) {
5432
+ const { api } = useSessionWorkspace();
5433
+ const runThenReload = useReloadingAction(context);
5434
+ const contributionId = continuation.contribution.id;
5435
+ const intentionId = continuation.intention?.id;
5436
+ const sessionId = context.detail.session.id;
5437
+ return {
5438
+ answer: (text) => runThenReload("The answer was not sent", () => api.answerIntentionQuestion({
5439
+ contributionId,
5440
+ text
5441
+ })),
5442
+ detach: async () => {
5443
+ if (await runThenReload("The contribution was not removed", () => api.detachContribution(contributionId))) announce("This session's contribution was removed");
5444
+ },
5445
+ retry: async () => {
5446
+ await runThenReload("The comparison did not restart", () => api.analyze(sessionId));
5447
+ },
5448
+ verify: async (status, note) => {
5449
+ if (intentionId && await runThenReload("The check was not saved", () => api.verifyProposal({
5450
+ note,
5451
+ proposalId: intentionId,
5452
+ sessionId,
5453
+ status
5454
+ }))) announce("Check saved on the intention");
5455
+ }
5456
+ };
5457
+ }
5458
+ function useUpdateActions(scope) {
5459
+ return {
5460
+ ...useChangeDecisions(scope),
5461
+ ...useContributionActions(scope)
5462
+ };
5463
+ }
5464
+ //#endregion
5465
+ //#region src/sessions/ui/review/continuation/verdict.tsx
5466
+ const VERDICT_LABELS = {
5467
+ insufficient: "Not enough evidence",
5468
+ needs_work: "Still to improve",
5469
+ resolved: "Looks resolved"
5470
+ };
5471
+ function QuestionBox({ scope }) {
5472
+ const [text, setText] = useState("");
5473
+ const [isSending, setIsSending] = useState(false);
5474
+ const { contribution } = scope.continuation;
5475
+ if (!contribution.question) return null;
5476
+ const send = async () => {
5477
+ setIsSending(true);
5478
+ try {
5479
+ if (await scope.actions.answer(text.trim())) setText("");
5480
+ } finally {
5481
+ setIsSending(false);
5482
+ }
5483
+ };
5484
+ return /* @__PURE__ */ jsxs("section", {
5485
+ className: "dt-ses-form",
5486
+ children: [
5487
+ /* @__PURE__ */ jsx("h4", {
5488
+ className: "dt-ses-label",
5489
+ children: "Question"
5490
+ }),
5491
+ /* @__PURE__ */ jsx("p", {
5492
+ className: "dt-note-text",
5493
+ children: contribution.question
5494
+ }),
5495
+ /* @__PURE__ */ jsx("ul", {
5496
+ className: "dt-ses-list",
5497
+ children: contribution.answers.map((answer) => /* @__PURE__ */ jsx("li", { children: answer.text }, answer.at))
5498
+ }),
5499
+ scope.context.canEdit && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("textarea", {
5500
+ "aria-label": "Your answer",
5501
+ className: "dt-field",
5502
+ maxLength: 1e3,
5503
+ onChange: (event) => setText(event.target.value),
5504
+ rows: 2,
5505
+ value: text
5506
+ }), /* @__PURE__ */ jsx("div", {
5507
+ className: "dt-actions",
5508
+ children: /* @__PURE__ */ jsx("button", {
5509
+ className: "dt-btn",
5510
+ disabled: isSending || text.trim() === "",
5511
+ onClick: send,
5512
+ type: "button",
5513
+ children: "Send answer"
5514
+ })
5515
+ })] })
5516
+ ]
5517
+ });
5518
+ }
5519
+ function VerdictQuotes({ context, segmentIds }) {
5520
+ const cited = new Set(segmentIds);
5521
+ return context.detail.segments.filter((segment) => cited.has(segment.id)).map((segment) => /* @__PURE__ */ jsxs("button", {
5522
+ className: "dt-ses-quote",
5523
+ onClick: (event) => context.navigation.onShowMoment(segment.id, event.currentTarget),
5524
+ type: "button",
5525
+ children: [/* @__PURE__ */ jsx("span", {
5526
+ className: "dt-ses-time",
5527
+ children: formatClock(segment.startMs)
5528
+ }), ` “${segment.text}”`]
5529
+ }, segment.id));
5530
+ }
5531
+ const CHECK_LABELS = {
5532
+ needs_work: "needs more work",
5533
+ works: "works"
5534
+ };
5535
+ function VerdictBox({ scope }) {
5536
+ const { actions, context, continuation } = scope;
5537
+ const { verdict } = continuation.contribution;
5538
+ if (!verdict) return null;
5539
+ const verification = continuation.intention?.verification;
5540
+ const checkedHere = verification?.sessionId === context.detail.session.id ? verification : null;
5541
+ return /* @__PURE__ */ jsxs("section", {
5542
+ className: "dt-ses-form",
5543
+ children: [
5544
+ /* @__PURE__ */ jsx("h4", {
5545
+ className: "dt-ses-label",
5546
+ children: "AI reading"
5547
+ }),
5548
+ /* @__PURE__ */ jsxs("p", { children: [
5549
+ /* @__PURE__ */ jsx("span", {
5550
+ className: "dt-chip",
5551
+ children: VERDICT_LABELS[verdict.status]
5552
+ }),
5553
+ " ",
5554
+ verdict.reason
5555
+ ] }),
5556
+ /* @__PURE__ */ jsx(VerdictQuotes, {
5557
+ context,
5558
+ segmentIds: verdict.segmentIds
5559
+ }),
5560
+ checkedHere && /* @__PURE__ */ jsxs("p", {
5561
+ className: "dt-meta",
5562
+ role: "status",
5563
+ children: [
5564
+ "You confirmed it ",
5565
+ CHECK_LABELS[checkedHere.status],
5566
+ " on",
5567
+ " ",
5568
+ formatDate(checkedHere.at),
5569
+ "."
5570
+ ]
5571
+ }),
5572
+ context.canEdit && /* @__PURE__ */ jsxs("div", {
5573
+ className: "dt-actions",
5574
+ children: [/* @__PURE__ */ jsx("button", {
5575
+ className: "dt-btn",
5576
+ onClick: () => actions.verify("works", verdict.reason),
5577
+ type: "button",
5578
+ children: "Confirm: works"
5579
+ }), /* @__PURE__ */ jsx("button", {
5580
+ className: "dt-btn",
5581
+ onClick: () => actions.verify("needs_work", verdict.reason),
5582
+ type: "button",
5583
+ children: "Confirm: needs more work"
5584
+ })]
5585
+ })
5586
+ ]
5587
+ });
5588
+ }
5589
+ //#endregion
5590
+ //#region src/sessions/ui/review/continuation/update-card.tsx
5591
+ const DETACH_COPY = "Remove what this session added to the intention? Each field that still holds the value this session set goes back to its previous value. Fields changed since then are kept, and the intention is marked to re-examine.";
5592
+ const INTENTION_DELETED = "The intention this session continued was deleted. This session stays as it was recorded.";
5593
+ const INTENTION_HIDDEN = "The intention this session continued is no longer visible to you.";
5594
+ function ContinuationHeader({ continuation, onOpenSession }) {
5595
+ const { contribution, intention } = continuation;
5596
+ if (!intention) return /* @__PURE__ */ jsx("p", {
5597
+ className: "dt-meta",
5598
+ children: contribution.detachedAt === null ? INTENTION_HIDDEN : INTENTION_DELETED
5599
+ });
5600
+ return /* @__PURE__ */ jsxs("div", {
5601
+ className: "dt-ses-banner",
5602
+ children: [/* @__PURE__ */ jsxs("p", { children: [
5603
+ "Continues “",
5604
+ intention.need,
5605
+ "”"
5606
+ ] }), /* @__PURE__ */ jsx("div", {
5607
+ className: "dt-actions",
5608
+ children: /* @__PURE__ */ jsx("button", {
5609
+ className: "dt-btn",
5610
+ onClick: () => onOpenSession(intention.sessionId),
5611
+ type: "button",
5612
+ children: "Open the intention"
5613
+ })
5614
+ })]
5615
+ });
5616
+ }
5617
+ function ContributionState({ scope }) {
5618
+ const { actions, context, continuation } = scope;
5619
+ const { contribution } = continuation;
5620
+ if (contribution.state === "recording") return /* @__PURE__ */ jsx("p", {
5621
+ className: "dt-status",
5622
+ children: "This session is still recording. What it changes appears once it ends."
5623
+ });
5624
+ if (contribution.state === "analyzing") return /* @__PURE__ */ jsxs("p", {
5625
+ className: "dt-status",
5626
+ children: [
5627
+ "Comparing with revision ",
5628
+ contribution.baseRevision,
5629
+ "…"
5630
+ ]
5631
+ });
5632
+ if (contribution.state === "no_change") return /* @__PURE__ */ jsx("p", {
5633
+ className: "dt-meta",
5634
+ children: "No change to the intention"
5635
+ });
5636
+ if (contribution.state !== "failed") return null;
5637
+ const reason = context.detail.session.analysis.error ?? "Unknown error.";
5638
+ return /* @__PURE__ */ jsxs("div", {
5639
+ className: "dt-actions",
5640
+ role: "alert",
5641
+ children: [/* @__PURE__ */ jsxs("p", {
5642
+ className: "dt-error",
5643
+ children: ["The comparison failed: ", reason]
5644
+ }), context.canEdit && /* @__PURE__ */ jsx("button", {
5645
+ className: "dt-btn",
5646
+ onClick: actions.retry,
5647
+ type: "button",
5648
+ children: "Try again"
5649
+ })]
5650
+ });
5651
+ }
5652
+ function DetachContribution({ scope }) {
5653
+ const [isConfirming, setIsConfirming] = useState(false);
5654
+ if (!isConfirming) return /* @__PURE__ */ jsx("div", {
5655
+ className: "dt-actions",
5656
+ children: /* @__PURE__ */ jsx("button", {
5657
+ className: "dt-btn",
5658
+ "data-variant": "danger",
5659
+ onClick: () => setIsConfirming(true),
5660
+ type: "button",
5661
+ children: "Remove this session's contribution"
5662
+ })
5663
+ });
5664
+ return /* @__PURE__ */ jsxs("div", {
5665
+ className: "dt-ses-banner",
5666
+ children: [/* @__PURE__ */ jsx("p", { children: DETACH_COPY }), /* @__PURE__ */ jsxs("div", {
5667
+ className: "dt-actions",
5668
+ children: [/* @__PURE__ */ jsx("button", {
5669
+ className: "dt-btn",
5670
+ "data-variant": "danger",
5671
+ onClick: scope.actions.detach,
5672
+ type: "button",
5673
+ children: "Remove contribution"
5674
+ }), /* @__PURE__ */ jsx("button", {
5675
+ className: "dt-btn",
5676
+ onClick: () => setIsConfirming(false),
5677
+ type: "button",
5678
+ children: "Cancel"
5679
+ })]
5680
+ })]
5681
+ });
5682
+ }
5683
+ function ContributionBody({ intention, scope }) {
5684
+ const { contribution } = scope.continuation;
5685
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
5686
+ /* @__PURE__ */ jsx(ContributionState, { scope }),
5687
+ contribution.stale && /* @__PURE__ */ jsxs("p", {
5688
+ className: "dt-ses-banner",
5689
+ role: "status",
5690
+ children: [
5691
+ "The intention changed since this comparison: it is now at revision",
5692
+ " ",
5693
+ intention.revision,
5694
+ ". Check each change against it."
5695
+ ]
5696
+ }),
5697
+ contribution.state === "ready" && /* @__PURE__ */ jsx("ul", {
5698
+ className: "dt-ses-rows",
5699
+ children: contribution.changes.map((change) => /* @__PURE__ */ jsx(IntentionChangeRow, {
5700
+ change,
5701
+ intention,
5702
+ scope
5703
+ }, change.id))
5704
+ }),
5705
+ /* @__PURE__ */ jsx(QuestionBox, { scope }),
5706
+ /* @__PURE__ */ jsx(VerdictBox, { scope }),
5707
+ /* @__PURE__ */ jsx(AppliedChanges, { applied: contribution.applied }),
5708
+ scope.context.canEdit && /* @__PURE__ */ jsx(DetachContribution, { scope })
5709
+ ] });
5710
+ }
5711
+ function UpdateCard({ context, continuation }) {
5712
+ const headingId = useId();
5713
+ const actions = useUpdateActions({
5714
+ context,
5715
+ continuation
5716
+ });
5717
+ const { contribution, intention } = continuation;
5718
+ if (!intention) return null;
5719
+ const scope = {
5720
+ actions,
5721
+ context,
5722
+ continuation
5723
+ };
5724
+ return /* @__PURE__ */ jsxs("article", {
5725
+ "aria-labelledby": headingId,
5726
+ className: "dt-card",
5727
+ children: [/* @__PURE__ */ jsx("h3", {
5728
+ className: "dt-ses-subhead",
5729
+ id: headingId,
5730
+ children: "What changes"
5731
+ }), contribution.detachedAt === null ? /* @__PURE__ */ jsx(ContributionBody, {
5732
+ intention,
5733
+ scope
5734
+ }) : /* @__PURE__ */ jsx("p", {
5735
+ className: "dt-meta",
5736
+ children: "This session's contribution was removed from the intention."
5737
+ })]
5738
+ });
5739
+ }
5740
+ //#endregion
5741
+ //#region src/sessions/ui/review/transcript/remove-evidence.tsx
5742
+ const REMOVE_COPY = "Remove this screenshot? The image is deleted for good. Briefs you already copied or exported can't be recalled.";
5743
+ function useFocusWhen(shouldFocus) {
5744
+ const ref = useRef(null);
5745
+ useEffect(() => {
5746
+ if (shouldFocus) ref.current?.focus();
5747
+ }, [shouldFocus]);
5748
+ return ref;
5749
+ }
5750
+ function RemoveConfirmation({ evidence, onCancel, removal }) {
5751
+ const { api } = useSessionWorkspace();
5752
+ const run = useSessionAction();
5753
+ const cancelRef = useFocusWhen(true);
5754
+ const remove = async () => {
5755
+ const removed = await run("The screenshot was not removed", () => api.removeEvidence(evidence.id));
5756
+ if (removed !== null) {
5757
+ removal.review.patch((detail) => withEvidence(detail, removed));
5758
+ removal.onRemoved(removed.id);
5759
+ announce("Screenshot removed");
5760
+ }
5761
+ };
5762
+ return /* @__PURE__ */ jsxs("div", {
5763
+ className: "dt-ses-banner",
5764
+ children: [/* @__PURE__ */ jsx("p", { children: REMOVE_COPY }), /* @__PURE__ */ jsxs("div", {
5765
+ className: "dt-actions",
5766
+ children: [/* @__PURE__ */ jsx("button", {
5767
+ className: "dt-btn",
5768
+ "data-variant": "danger",
5769
+ onClick: remove,
5770
+ type: "button",
5771
+ children: "Remove"
5772
+ }), /* @__PURE__ */ jsx("button", {
5773
+ className: "dt-btn",
5774
+ onClick: onCancel,
5775
+ ref: cancelRef,
5776
+ type: "button",
5777
+ children: "Cancel"
5778
+ })]
5779
+ })]
5780
+ });
5781
+ }
5782
+ function RemoveEvidence({ evidence, removal }) {
5783
+ const [isConfirming, setIsConfirming] = useState(false);
5784
+ const [hasCancelled, setHasCancelled] = useState(false);
5785
+ const triggerRef = useFocusWhen(hasCancelled);
5786
+ if (isConfirming) return /* @__PURE__ */ jsx(RemoveConfirmation, {
5787
+ evidence,
5788
+ onCancel: () => {
5789
+ setIsConfirming(false);
5790
+ setHasCancelled(true);
5791
+ },
5792
+ removal
5793
+ });
5794
+ return /* @__PURE__ */ jsx("button", {
5795
+ "aria-label": `Remove screenshot at ${formatClock(evidence.atMs)}`,
5796
+ className: "dt-ses-link",
5797
+ onClick: () => {
5798
+ setIsConfirming(true);
5799
+ setHasCancelled(false);
5800
+ },
5801
+ ref: triggerRef,
5802
+ type: "button",
5803
+ children: "Remove"
5804
+ });
5805
+ }
5806
+ //#endregion
5807
+ //#region src/sessions/ui/review/moment/moment-labels.ts
5808
+ const URL_PATH = /^[a-z][a-z\d+.-]*:\/\/[^/?#]*([^?#]*)/i;
5809
+ const KIND_LABELS = {
5810
+ click: "Clicked",
5811
+ mark: "Marked",
5812
+ navigation: "Opened page",
5813
+ screenshot: "Screenshot",
5814
+ select: "Pointed at explicitly"
5815
+ };
5816
+ const ORIGIN_LABELS = {
5817
+ auto: "Linked automatically",
5818
+ human: "Corrected by you",
5819
+ unknown: "Context unknown"
5820
+ };
5821
+ function contextKindLabel(item) {
5822
+ if (item.kind === "dwell") return `Hovered ${preciseSeconds(item.durationMs ?? 0)}`;
5823
+ return KIND_LABELS[item.kind];
5824
+ }
5825
+ function pagePath(url) {
5826
+ if (!url) return null;
5827
+ return URL_PATH.exec(url)?.[1] || "/";
5828
+ }
5829
+ function contextLine(item) {
5830
+ const page = pagePath(item.url ?? item.frame?.url ?? null);
5831
+ const element = item.element?.label ?? item.label;
5832
+ return page ? `${element} · ${page}` : element;
5833
+ }
5834
+ function offsetFromPassage(atMs, segment) {
5835
+ if (atMs < segment.startMs) return `${preciseSeconds(segment.startMs - atMs)} before`;
5836
+ if (atMs > segment.endMs) return `${preciseSeconds(atMs - segment.endMs)} after`;
5837
+ return "during the passage";
5838
+ }
5839
+ const distanceFrom = (atMs, segment) => Math.max(segment.startMs - atMs, 0, atMs - segment.endMs);
5840
+ function nearestSegmentId(segments, atMs) {
5841
+ let nearest = null;
5842
+ for (const segment of segments) if (!nearest || distanceFrom(atMs, segment) < distanceFrom(atMs, nearest)) nearest = segment;
5843
+ return nearest?.id ?? null;
5844
+ }
5845
+ function gapsDuring(gaps, segment) {
5846
+ return gaps.filter((gap) => gap.startMs <= segment.endMs && (gap.endMs ?? Number.POSITIVE_INFINITY) >= segment.startMs);
5847
+ }
5848
+ //#endregion
5849
+ //#region src/sessions/ui/review/transcript/segment-row.tsx
5850
+ function SaveCancelActions({ isSaveDisabled, onCancel, onSave }) {
5851
+ return /* @__PURE__ */ jsxs("div", {
5852
+ className: "dt-actions",
5853
+ children: [/* @__PURE__ */ jsx("button", {
5854
+ className: "dt-btn",
5855
+ "data-variant": "primary",
5856
+ disabled: isSaveDisabled,
5857
+ onClick: onSave,
5858
+ type: "button",
5859
+ children: "Save"
5860
+ }), /* @__PURE__ */ jsx("button", {
5861
+ className: "dt-btn",
5862
+ onClick: onCancel,
5863
+ type: "button",
5864
+ children: "Cancel"
5865
+ })]
5866
+ });
5867
+ }
5868
+ function SegmentEditor({ onDone, review, segment }) {
5869
+ const { api } = useSessionWorkspace();
5870
+ const { isSaving, problem, save } = useGuardedSave();
5871
+ const [text, setText] = useState(segment.text);
5872
+ const submit = () => save(async () => {
5873
+ const saved = await api.correctSegment({
5874
+ expectedRevision: segment.revision,
5875
+ segmentId: segment.id,
5876
+ text: text.trim()
5877
+ });
5878
+ review.patch((detail) => withSegment(detail, saved));
5879
+ review.reload();
5880
+ onDone();
5881
+ });
5882
+ return /* @__PURE__ */ jsxs("div", {
5883
+ className: "dt-ses-form",
5884
+ children: [
5885
+ /* @__PURE__ */ jsx("textarea", {
5886
+ "aria-label": `Passage at ${formatClock(segment.startMs)}`,
5887
+ className: "dt-field",
5888
+ maxLength: 5e3,
5889
+ onChange: (event) => setText(event.target.value),
5890
+ rows: 3,
5891
+ value: text
5892
+ }),
5893
+ problem && /* @__PURE__ */ jsx(SaveProblemMessage, {
5894
+ onReload: review.reload,
5895
+ problem
5896
+ }),
5897
+ /* @__PURE__ */ jsx(SaveCancelActions, {
5898
+ isSaveDisabled: isSaving || text.trim() === "",
5899
+ onCancel: onDone,
5900
+ onSave: submit
5901
+ })
5902
+ ]
5903
+ });
5904
+ }
5905
+ function SegmentBody({ onType, review, segment }) {
5906
+ const { api } = useSessionWorkspace();
5907
+ const run = useSessionAction();
5908
+ const { state } = segment.transcription;
5909
+ const retry = async () => {
5910
+ const pending = await run("The transcription did not restart", () => api.retrySegment(segment.id));
5911
+ if (pending) review.patch((detail) => withSegment(detail, pending));
5912
+ };
5913
+ if (state === "pending" || state === "running") return /* @__PURE__ */ jsx("p", {
5914
+ className: "dt-status",
5915
+ children: "Transcribing…"
5916
+ });
5917
+ if (state === "failed" || state === "expired") return /* @__PURE__ */ jsxs("div", {
5918
+ className: "dt-actions",
5919
+ children: [
5920
+ /* @__PURE__ */ jsx("p", {
5921
+ className: "dt-error",
5922
+ children: state === "failed" ? "Couldn't transcribe" : "Audio expired — type what you said"
5923
+ }),
5924
+ onType && state === "failed" && segment.hasAudio && /* @__PURE__ */ jsx("button", {
5925
+ className: "dt-btn",
5926
+ onClick: retry,
5927
+ type: "button",
5928
+ children: "Retry"
5929
+ }),
5930
+ onType && /* @__PURE__ */ jsx("button", {
5931
+ className: "dt-btn",
5932
+ onClick: onType,
5933
+ type: "button",
5934
+ children: "Type it"
5935
+ })
5936
+ ]
5937
+ });
5938
+ if (state === "empty" && segment.text === "") return /* @__PURE__ */ jsx("p", {
5939
+ className: "dt-meta",
5940
+ children: "No speech was heard here."
5941
+ });
5942
+ return /* @__PURE__ */ jsx("p", {
5943
+ className: "dt-note-text",
5944
+ children: segment.text
5945
+ });
5946
+ }
5947
+ function ContextChips({ segment }) {
5948
+ if (segment.context.length === 0) return null;
5949
+ return /* @__PURE__ */ jsxs("div", {
5950
+ className: "dt-ses-chips",
5951
+ children: [segment.contextAmbiguous && /* @__PURE__ */ jsx("span", {
5952
+ className: "dt-chip",
5953
+ children: "Unclear which element"
5954
+ }), segment.context.map((item) => /* @__PURE__ */ jsx("span", {
5955
+ className: "dt-chip",
5956
+ children: item.label
5957
+ }, `${item.kind}:${item.atMs}:${item.label}`))]
5958
+ });
5959
+ }
5960
+ function SegmentLinks({ onEdit, onShowMoment, time }) {
5961
+ return /* @__PURE__ */ jsxs("div", {
5962
+ className: "dt-actions",
5963
+ children: [/* @__PURE__ */ jsx("button", {
5964
+ "aria-label": `Show context at ${time}`,
5965
+ className: "dt-ses-link",
5966
+ onClick: (event) => onShowMoment(event.currentTarget),
5967
+ type: "button",
5968
+ children: "Show context"
5969
+ }), onEdit && /* @__PURE__ */ jsx("button", {
5970
+ "aria-label": `Edit passage at ${time}`,
5971
+ className: "dt-ses-link",
5972
+ onClick: onEdit,
5973
+ type: "button",
5974
+ children: "Edit"
5975
+ })]
5976
+ });
5977
+ }
5978
+ function SegmentRow({ editing, onShowMoment, segment }) {
5979
+ const { canEdit, review } = editing;
5980
+ const [isEditing, setIsEditing] = useState(false);
5981
+ const time = formatClock(segment.startMs);
5982
+ const isSettled = ["done", "empty"].includes(segment.transcription.state);
5983
+ return /* @__PURE__ */ jsxs("li", {
5984
+ className: "dt-ses-seg",
5985
+ children: [/* @__PURE__ */ jsx("span", {
5986
+ className: "dt-ses-time",
5987
+ children: time
5988
+ }), /* @__PURE__ */ jsxs("div", {
5989
+ className: "dt-ses-seg-body",
5990
+ children: [
5991
+ isEditing ? /* @__PURE__ */ jsx(SegmentEditor, {
5992
+ onDone: () => setIsEditing(false),
5993
+ review,
5994
+ segment
5995
+ }) : /* @__PURE__ */ jsx(SegmentBody, {
5996
+ onType: canEdit ? () => setIsEditing(true) : void 0,
5997
+ review,
5998
+ segment
5999
+ }),
6000
+ /* @__PURE__ */ jsx(ContextChips, { segment }),
6001
+ /* @__PURE__ */ jsx(SegmentLinks, {
6002
+ onEdit: canEdit && isSettled && !isEditing ? () => setIsEditing(true) : null,
6003
+ onShowMoment,
6004
+ time
6005
+ })
6006
+ ]
6007
+ })]
6008
+ });
6009
+ }
6010
+ //#endregion
6011
+ //#region src/sessions/ui/review/transcript/transcript-tab.tsx
6012
+ const GAP_LABELS = {
6013
+ hidden: "Tab hidden",
6014
+ interrupted: "Interrupted",
6015
+ limit: "Time limit reached",
6016
+ paused: "Paused"
6017
+ };
6018
+ function gapLabel(gap) {
6019
+ const label = GAP_LABELS[gap.reason];
6020
+ return gap.endMs === null ? label : `${label} · ${shortDuration(gap.endMs - gap.startMs)}`;
6021
+ }
6022
+ function markLabel(mark) {
6023
+ const note = mark.note ? ` — ${mark.note}` : "";
6024
+ if (mark.kind === "navigation") return `Went to ${mark.url ?? mark.label}`;
6025
+ return `${mark.kind === "select" ? "Pointed at" : "Marked a moment on"} ${mark.label}${note}`;
6026
+ }
6027
+ function TimelineNote({ atMs, children, onShowMoment }) {
6028
+ const time = formatClock(atMs);
6029
+ return /* @__PURE__ */ jsxs("li", {
6030
+ className: "dt-ses-seg",
6031
+ "data-kind": "note",
6032
+ children: [/* @__PURE__ */ jsx("span", {
6033
+ className: "dt-ses-time",
6034
+ children: time
6035
+ }), /* @__PURE__ */ jsxs("div", {
6036
+ className: "dt-ses-seg-body",
6037
+ children: [/* @__PURE__ */ jsx("p", {
6038
+ className: "dt-meta",
6039
+ children
6040
+ }), onShowMoment && /* @__PURE__ */ jsx("button", {
6041
+ "aria-label": `Show the moment at ${time}`,
6042
+ className: "dt-ses-link",
6043
+ onClick: (event) => onShowMoment(event.currentTarget),
6044
+ type: "button",
6045
+ children: "Show moment"
6046
+ })]
6047
+ })]
6048
+ });
6049
+ }
6050
+ function buildTimeline(detail) {
6051
+ return [
6052
+ ...detail.segments.map((segment) => ({
6053
+ atMs: segment.startMs,
6054
+ key: `s:${segment.id}`,
6055
+ segment
6056
+ })),
6057
+ ...detail.session.gaps.map((gap) => ({
6058
+ atMs: gap.startMs,
6059
+ key: `g:${gap.startMs}`,
6060
+ label: gapLabel(gap),
6061
+ segmentId: null
6062
+ })),
6063
+ ...detail.marks.map((mark) => ({
6064
+ atMs: mark.atMs,
6065
+ key: `m:${mark.kind}:${mark.atMs}`,
6066
+ label: markLabel(mark),
6067
+ segmentId: nearestSegmentId(detail.segments, mark.atMs)
6068
+ }))
6069
+ ].sort((earlier, later) => earlier.atMs - later.atMs);
6070
+ }
6071
+ function noteMomentOpener(segmentId, onShowMoment) {
6072
+ if (!segmentId) return null;
6073
+ return (opener) => onShowMoment(segmentId, opener);
6074
+ }
6075
+ function useTypedPassage({ detail, review }) {
6076
+ const { api } = useSessionWorkspace();
6077
+ const run = useSessionAction();
6078
+ const [text, setText] = useState("");
6079
+ const [clientKey, setClientKey] = useState(() => crypto.randomUUID());
6080
+ const atMs = detail.segments.at(-1)?.endMs ?? 0;
6081
+ const add = async () => {
6082
+ const segment = await run("The passage was not added", () => api.addTypedSegment({
6083
+ clientKey,
6084
+ endMs: atMs,
6085
+ sessionId: detail.session.id,
6086
+ startMs: atMs,
6087
+ text: text.trim()
6088
+ }));
6089
+ if (segment) {
6090
+ review.patch((current) => withSegment(current, segment));
6091
+ setText("");
6092
+ setClientKey(crypto.randomUUID());
6093
+ }
6094
+ };
6095
+ return {
6096
+ add,
6097
+ setText,
6098
+ text
6099
+ };
6100
+ }
6101
+ function AddTypedPassage({ detail, review }) {
6102
+ const { add, setText, text } = useTypedPassage({
6103
+ detail,
6104
+ review
6105
+ });
6106
+ return /* @__PURE__ */ jsxs("div", {
6107
+ className: "dt-ses-form",
6108
+ children: [/* @__PURE__ */ jsx("textarea", {
6109
+ "aria-label": "Add a typed passage",
6110
+ className: "dt-field",
6111
+ maxLength: 5e3,
6112
+ onChange: (event) => setText(event.target.value),
6113
+ placeholder: "Add a typed passage",
6114
+ rows: 2,
6115
+ value: text
6116
+ }), /* @__PURE__ */ jsx("div", {
6117
+ className: "dt-actions",
6118
+ children: /* @__PURE__ */ jsx("button", {
6119
+ className: "dt-btn",
6120
+ disabled: text.trim() === "",
6121
+ onClick: add,
6122
+ type: "button",
6123
+ children: "Add passage"
6124
+ })
6125
+ })]
6126
+ });
6127
+ }
6128
+ function TranscriptTab({ detail, onShowMoment, review }) {
6129
+ const { canEdit } = detail.session;
6130
+ const timeline = buildTimeline(detail);
6131
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
6132
+ /* @__PURE__ */ jsx("p", {
6133
+ className: "dt-hint",
6134
+ children: "Audio is deleted once transcribed. You can still correct the text."
6135
+ }),
6136
+ timeline.length === 0 && /* @__PURE__ */ jsx("p", {
6137
+ className: "dt-empty",
6138
+ children: "Nothing was said in this session."
6139
+ }),
6140
+ /* @__PURE__ */ jsx("ol", {
6141
+ className: "dt-ses-timeline",
6142
+ children: timeline.map((item) => "segment" in item ? /* @__PURE__ */ jsx(SegmentRow, {
6143
+ editing: {
6144
+ canEdit,
6145
+ review
6146
+ },
6147
+ onShowMoment: (opener) => onShowMoment(item.segment.id, opener),
6148
+ segment: item.segment
6149
+ }, item.key) : /* @__PURE__ */ jsx(TimelineNote, {
6150
+ atMs: item.atMs,
6151
+ onShowMoment: noteMomentOpener(item.segmentId, onShowMoment),
6152
+ children: item.label
6153
+ }, item.key))
6154
+ }),
6155
+ canEdit && /* @__PURE__ */ jsx(AddTypedPassage, {
6156
+ detail,
6157
+ review
6158
+ })
6159
+ ] });
6160
+ }
6161
+ //#endregion
6162
+ //#region src/sessions/ui/review/moment/context-correction.tsx
6163
+ const AUTO_CHOICE = { kind: "auto" };
6164
+ const UNKNOWN_CHOICE = { kind: "unknown" };
6165
+ function useContextChoices(segmentId) {
6166
+ const { api, onUnauthorized } = useSessionWorkspace();
6167
+ const [state, setState] = useState({ kind: "loading" });
6168
+ useEffect(() => {
6169
+ let isCurrent = true;
6170
+ api.contextChoices(segmentId).then((options) => isCurrent && setState({
6171
+ kind: "ready",
6172
+ options
6173
+ }), (error) => {
6174
+ if (isUnauthorized(error)) onUnauthorized();
6175
+ else if (isCurrent) setState({
6176
+ kind: "failed",
6177
+ message: failureMessage(error)
6178
+ });
6179
+ });
6180
+ return () => {
6181
+ isCurrent = false;
6182
+ };
6183
+ }, [
6184
+ api,
6185
+ onUnauthorized,
6186
+ segmentId
6187
+ ]);
6188
+ return state;
6189
+ }
6190
+ function ChoiceRadio({ choice, group, label }) {
6191
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("label", {
6192
+ className: "dt-ses-check",
6193
+ children: [/* @__PURE__ */ jsx("input", {
6194
+ checked: group.chosen === choice,
6195
+ name: group.name,
6196
+ onChange: () => group.onPick(choice),
6197
+ type: "radio"
6198
+ }), /* @__PURE__ */ jsx("span", { children: label })]
6199
+ }) });
6200
+ }
6201
+ function optionLabel(option, segment) {
6202
+ const { item } = option;
6203
+ const element = item.element?.label ?? item.label;
6204
+ return `${contextKindLabel(item)} · ${element} · ${offsetFromPassage(item.atMs, segment)}`;
6205
+ }
6206
+ function ChoicesFieldset({ group, options, segment }) {
6207
+ return /* @__PURE__ */ jsxs("fieldset", {
6208
+ className: "dt-ses-choices",
6209
+ children: [/* @__PURE__ */ jsx("legend", {
6210
+ className: "dt-ses-label",
6211
+ children: "What this passage is about"
6212
+ }), /* @__PURE__ */ jsxs("ul", {
6213
+ className: "dt-ses-list",
6214
+ children: [
6215
+ options.map((option) => /* @__PURE__ */ jsx(ChoiceRadio, {
6216
+ choice: option.choice,
6217
+ group,
6218
+ label: optionLabel(option, segment)
6219
+ }, JSON.stringify(option.choice))),
6220
+ /* @__PURE__ */ jsx(ChoiceRadio, {
6221
+ choice: UNKNOWN_CHOICE,
6222
+ group,
6223
+ label: "Context unknown"
6224
+ }),
6225
+ segment.contextOrigin !== "auto" && /* @__PURE__ */ jsx(ChoiceRadio, {
6226
+ choice: AUTO_CHOICE,
6227
+ group,
6228
+ label: "Back to automatic"
6229
+ })
6230
+ ]
6231
+ })]
6232
+ });
6233
+ }
6234
+ function useSaveContext(correction, chosen) {
6235
+ const { onDone, review, segment } = correction;
6236
+ const { api } = useSessionWorkspace();
6237
+ const { isSaving, problem, save } = useGuardedSave();
6238
+ const submit = () => save(async () => {
6239
+ if (!chosen) return;
6240
+ const saved = await api.setSegmentContext({
6241
+ choice: chosen,
6242
+ expectedRevision: segment.revision,
6243
+ segmentId: segment.id
6244
+ });
6245
+ review.patch((detail) => withSegment(detail, saved));
6246
+ announce("Context corrected");
6247
+ onDone();
6248
+ });
6249
+ return {
6250
+ isSaving,
6251
+ problem,
6252
+ submit
6253
+ };
6254
+ }
6255
+ function ChoicesForm({ correction, options }) {
6256
+ const { onDone, review, segment } = correction;
6257
+ const [picked, setPicked] = useState(null);
6258
+ const current = options.find((option) => option.selected)?.choice ?? (segment.contextOrigin === "unknown" ? UNKNOWN_CHOICE : null);
6259
+ const chosen = picked ?? current;
6260
+ const { isSaving, problem, submit } = useSaveContext(correction, chosen);
6261
+ return /* @__PURE__ */ jsxs("form", {
6262
+ className: "dt-ses-form",
6263
+ onSubmit: (event) => {
6264
+ event.preventDefault();
6265
+ submit();
6266
+ },
6267
+ children: [
6268
+ /* @__PURE__ */ jsx(ChoicesFieldset, {
6269
+ group: {
6270
+ chosen,
6271
+ name: `rf-context-${segment.id}`,
6272
+ onPick: setPicked
6273
+ },
6274
+ options,
6275
+ segment
6276
+ }),
6277
+ problem && /* @__PURE__ */ jsx(SaveProblemMessage, {
6278
+ onReload: review.reload,
6279
+ problem
6280
+ }),
6281
+ /* @__PURE__ */ jsx(FormActions, {
6282
+ isSubmitDisabled: isSaving || picked === null,
6283
+ onCancel: onDone,
6284
+ submitLabel: "Save context"
6285
+ })
6286
+ ]
6287
+ });
6288
+ }
6289
+ function ContextChoices({ correction, onRetry }) {
6290
+ const state = useContextChoices(correction.segment.id);
6291
+ if (state.kind === "loading") return /* @__PURE__ */ jsx("p", {
6292
+ className: "dt-status",
6293
+ children: "Loading what was happening…"
6294
+ });
6295
+ if (state.kind === "failed") return /* @__PURE__ */ jsxs("div", {
6296
+ className: "dt-actions",
6297
+ role: "alert",
6298
+ children: [
6299
+ /* @__PURE__ */ jsxs("p", {
6300
+ className: "dt-error",
6301
+ children: ["The choices did not load: ", state.message]
6302
+ }),
6303
+ /* @__PURE__ */ jsx("button", {
6304
+ className: "dt-btn",
6305
+ onClick: onRetry,
6306
+ type: "button",
6307
+ children: "Try again"
6308
+ }),
6309
+ /* @__PURE__ */ jsx("button", {
6310
+ className: "dt-btn",
6311
+ onClick: correction.onDone,
6312
+ type: "button",
6313
+ children: "Cancel"
6314
+ })
6315
+ ]
6316
+ });
6317
+ return /* @__PURE__ */ jsx(ChoicesForm, {
6318
+ correction,
6319
+ options: state.options
6320
+ });
6321
+ }
6322
+ function ContextCorrection({ review, segment }) {
6323
+ const [isOpen, setIsOpen] = useState(false);
6324
+ const [hasClosed, setHasClosed] = useState(false);
6325
+ const [attempt, setAttempt] = useState(0);
6326
+ const buttonRef = useFocusWhen(hasClosed && !isOpen);
6327
+ if (isOpen) return /* @__PURE__ */ jsx(ContextChoices, {
6328
+ correction: {
6329
+ onDone: () => {
6330
+ setIsOpen(false);
6331
+ setHasClosed(true);
6332
+ },
6333
+ review,
6334
+ segment
6335
+ },
6336
+ onRetry: () => setAttempt(attempt + 1)
6337
+ }, attempt);
6338
+ return /* @__PURE__ */ jsx("button", {
6339
+ className: "dt-btn",
6340
+ onClick: () => {
6341
+ setIsOpen(true);
6342
+ setHasClosed(false);
6343
+ },
6344
+ ref: buttonRef,
6345
+ type: "button",
6346
+ children: "Change context"
6347
+ });
6348
+ }
6349
+ //#endregion
6350
+ //#region src/sessions/ui/review/moment/moment-screenshot.tsx
6351
+ const OTHER_STATE_CAPTION = "This screenshot was not taken at this moment: it may show another state of the page.";
6352
+ function approximateCaption(offsetMs) {
6353
+ if (offsetMs === null) return "Approximate — screenshot taken at another moment";
6354
+ const direction = offsetMs >= 0 ? "after" : "before";
6355
+ return `Approximate — screenshot taken ${preciseSeconds(offsetMs)} ${direction}`;
6356
+ }
6357
+ function overlayFor(item) {
6358
+ const { element, evidenceMatch, frame, pointer } = item;
6359
+ if (!(evidenceMatch && frame)) return null;
6360
+ const outline = element ? percentBox(element.rect, frame) : null;
6361
+ const dot = evidenceMatch === "exact" && pointer ? percentPoint(pointer, frame) : null;
6362
+ return outline ? {
6363
+ dot,
6364
+ match: evidenceMatch,
6365
+ outline
6366
+ } : null;
6367
+ }
6368
+ function screenshotAlt(item, evidence) {
6369
+ const time = formatClock(evidence.capturedAtMs ?? evidence.atMs);
6370
+ const element = item.element?.label;
6371
+ if (!(element && overlayFor(item))) return `Screenshot at ${time}`;
6372
+ return `Screenshot at ${time}, “${element}” outlined${item.evidenceMatch === "approximate" ? " approximately" : ""}`;
6373
+ }
6374
+ function screenshotCaption(item) {
6375
+ if (item.evidenceMatch === "approximate") return approximateCaption(item.evidenceOffsetMs);
6376
+ return item.evidenceMatch === "exact" ? null : OTHER_STATE_CAPTION;
6377
+ }
6378
+ function MissingScreenshot({ evidence }) {
6379
+ const reason = evidence?.missingReason;
6380
+ return /* @__PURE__ */ jsx("p", {
6381
+ className: "dt-meta",
6382
+ children: reason ? `Screenshot removed: ${reason}` : "Screenshot removed"
6383
+ });
6384
+ }
6385
+ function MomentScreenshot({ evidence, item }) {
6386
+ const image = evidence?.status === "available" ? evidence.image : null;
6387
+ if (!(evidence && image?.url)) return /* @__PURE__ */ jsx(MissingScreenshot, { evidence });
6388
+ const overlay = overlayFor(item);
6389
+ const caption = screenshotCaption(item);
6390
+ return /* @__PURE__ */ jsxs("figure", {
6391
+ className: "dt-ses-moment-shot",
6392
+ children: [/* @__PURE__ */ jsxs("span", {
6393
+ className: "dt-ses-shot",
6394
+ "data-large": "true",
6395
+ children: [
6396
+ /* @__PURE__ */ jsx("img", {
6397
+ alt: screenshotAlt(item, evidence),
6398
+ height: image.height,
6399
+ src: image.url,
6400
+ width: image.width
6401
+ }),
6402
+ overlay && /* @__PURE__ */ jsx("span", {
6403
+ "aria-hidden": "true",
6404
+ className: "dt-ses-shot-mark",
6405
+ "data-match": overlay.match,
6406
+ style: overlay.outline
6407
+ }),
6408
+ overlay?.dot && /* @__PURE__ */ jsx("span", {
6409
+ "aria-hidden": "true",
6410
+ className: "dt-ses-shot-pointer",
6411
+ style: overlay.dot
6412
+ })
6413
+ ]
6414
+ }), caption && /* @__PURE__ */ jsx("figcaption", {
6415
+ className: "dt-meta",
6416
+ children: caption
6417
+ })]
6418
+ });
6419
+ }
6420
+ //#endregion
6421
+ //#region src/sessions/ui/review/moment/moment-view.tsx
6422
+ const AMBIGUOUS_COPY = "Several things happened around this passage. Reflet linked the closest one: check it, or change it.";
6423
+ function ContextItem({ detail, item }) {
6424
+ const evidence = detail.evidence.find((candidate) => candidate.id === item.evidenceId) ?? null;
6425
+ return /* @__PURE__ */ jsxs("li", {
6426
+ className: "dt-ses-moment-item",
6427
+ children: [/* @__PURE__ */ jsxs("p", {
6428
+ className: "dt-ses-moment-line",
6429
+ children: [/* @__PURE__ */ jsx("span", {
6430
+ className: "dt-chip",
6431
+ children: contextKindLabel(item)
6432
+ }), /* @__PURE__ */ jsx("span", { children: contextLine(item) })]
6433
+ }), item.evidenceId && /* @__PURE__ */ jsx(MomentScreenshot, {
6434
+ evidence,
6435
+ item
6436
+ })]
6437
+ });
6438
+ }
6439
+ function MomentContext({ detail, segment }) {
6440
+ const hasScreenshot = segment.context.some((item) => item.evidenceId);
6441
+ const isAmbiguous = segment.contextAmbiguous && segment.contextOrigin === "auto";
6442
+ return /* @__PURE__ */ jsxs("section", {
6443
+ className: "dt-ses-moment-section",
6444
+ children: [
6445
+ /* @__PURE__ */ jsx("h4", {
6446
+ className: "dt-ses-label",
6447
+ children: "On the page"
6448
+ }),
6449
+ /* @__PURE__ */ jsx("div", {
6450
+ className: "dt-ses-chips",
6451
+ children: /* @__PURE__ */ jsx("span", {
6452
+ className: "dt-chip",
6453
+ children: ORIGIN_LABELS[segment.contextOrigin]
6454
+ })
6455
+ }),
6456
+ isAmbiguous && /* @__PURE__ */ jsx("p", {
6457
+ className: "dt-meta",
6458
+ children: AMBIGUOUS_COPY
6459
+ }),
6460
+ segment.context.length === 0 && /* @__PURE__ */ jsx("p", {
6461
+ className: "dt-meta",
6462
+ children: "Nothing on the page is linked to this passage."
6463
+ }),
6464
+ /* @__PURE__ */ jsx("ul", {
6465
+ className: "dt-ses-rows",
6466
+ children: segment.context.map((item) => /* @__PURE__ */ jsx(ContextItem, {
6467
+ detail,
6468
+ item
6469
+ }, `${item.kind}:${item.atMs}:${item.label}`))
6470
+ }),
6471
+ !hasScreenshot && /* @__PURE__ */ jsx("p", {
6472
+ className: "dt-meta",
6473
+ children: "No screenshot at this moment"
6474
+ })
6475
+ ]
6476
+ });
6477
+ }
6478
+ function MomentNotices({ detail, segment }) {
6479
+ const gaps = gapsDuring(detail.session.gaps, segment);
6480
+ const hasPageChange = segment.context.some((item) => item.kind === "navigation");
6481
+ if (gaps.length === 0 && !hasPageChange) return null;
6482
+ return /* @__PURE__ */ jsxs("ul", {
6483
+ className: "dt-ses-list dt-ses-moment-notices",
6484
+ children: [gaps.map((gap) => /* @__PURE__ */ jsxs("li", { children: [gapLabel(gap), " during this passage: part of what happened was not recorded."] }, gap.startMs)), hasPageChange && /* @__PURE__ */ jsx("li", { children: "The page changed during this passage." })]
6485
+ });
6486
+ }
6487
+ function CitingIntentions({ detail, onOpenIntention, segmentId }) {
6488
+ const citing = detail.proposals.filter((proposal) => proposal.mergedIntoId === null && proposal.segmentIds.includes(segmentId));
6489
+ if (citing.length === 0) return null;
6490
+ return /* @__PURE__ */ jsxs("section", {
6491
+ className: "dt-ses-moment-section",
6492
+ children: [/* @__PURE__ */ jsx("h4", {
6493
+ className: "dt-ses-label",
6494
+ children: "Intentions citing this passage"
6495
+ }), /* @__PURE__ */ jsx("ul", {
6496
+ className: "dt-ses-rows",
6497
+ children: citing.map((proposal) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
6498
+ className: "dt-ses-row",
6499
+ onClick: () => onOpenIntention(proposal.id),
6500
+ type: "button",
6501
+ children: proposal.need
6502
+ }) }, proposal.id))
6503
+ })]
6504
+ });
6505
+ }
6506
+ function MomentSteps({ detail, onShowMoment, segmentId }) {
6507
+ const ordered = [...detail.segments].sort((a, b) => a.startMs - b.startMs);
6508
+ const index = ordered.findIndex((segment) => segment.id === segmentId);
6509
+ const previous = ordered[index - 1];
6510
+ const next = ordered[index + 1];
6511
+ return /* @__PURE__ */ jsxs("nav", {
6512
+ "aria-label": "Moments",
6513
+ className: "dt-actions",
6514
+ children: [/* @__PURE__ */ jsx("button", {
6515
+ className: "dt-btn",
6516
+ disabled: !previous,
6517
+ onClick: () => previous && onShowMoment(previous.id),
6518
+ type: "button",
6519
+ children: "Previous moment"
6520
+ }), /* @__PURE__ */ jsx("button", {
6521
+ className: "dt-btn",
6522
+ disabled: !next,
6523
+ onClick: () => next && onShowMoment(next.id),
6524
+ type: "button",
6525
+ children: "Next moment"
6526
+ })]
6527
+ });
6528
+ }
6529
+ function MomentHeading({ onClose, segment }) {
6530
+ const headingRef = useFocusWhen(true);
6531
+ const range = segment ? `${formatClock(segment.startMs)}–${formatClock(segment.endMs)}` : "";
6532
+ return /* @__PURE__ */ jsxs("div", {
6533
+ className: "dt-card-head",
6534
+ children: [/* @__PURE__ */ jsx("h3", {
6535
+ className: "dt-ses-title",
6536
+ ref: headingRef,
6537
+ tabIndex: -1,
6538
+ children: segment ? `Moment ${range}` : "Moment"
6539
+ }), /* @__PURE__ */ jsx("button", {
6540
+ className: "dt-btn",
6541
+ onClick: onClose,
6542
+ type: "button",
6543
+ children: "Close"
6544
+ })]
6545
+ });
6546
+ }
6547
+ function useEscapeToClose(onClose) {
6548
+ const sectionRef = useRef(null);
6549
+ const onCloseRef = useRef(onClose);
6550
+ useLayoutEffect(() => {
6551
+ onCloseRef.current = onClose;
6552
+ });
6553
+ useEffect(() => {
6554
+ const section = sectionRef.current;
6555
+ const closeOnEscape = (event) => {
6556
+ if (event.key === "Escape") {
6557
+ event.stopPropagation();
6558
+ onCloseRef.current();
6559
+ }
6560
+ };
6561
+ section?.addEventListener("keydown", closeOnEscape);
6562
+ return () => section?.removeEventListener("keydown", closeOnEscape);
6563
+ }, []);
6564
+ return sectionRef;
6565
+ }
6566
+ function MomentView({ handlers, segmentId, session }) {
6567
+ const { detail, review } = session;
6568
+ const segment = detail.segments.find((item) => item.id === segmentId);
6569
+ const sectionRef = useEscapeToClose(handlers.onClose);
6570
+ return /* @__PURE__ */ jsxs("section", {
6571
+ "aria-label": "Moment",
6572
+ className: "dt-ses-moment",
6573
+ ref: sectionRef,
6574
+ children: [
6575
+ /* @__PURE__ */ jsx(MomentHeading, {
6576
+ onClose: handlers.onClose,
6577
+ segment: segment ?? null
6578
+ }, segmentId),
6579
+ segment ? /* @__PURE__ */ jsxs(Fragment, { children: [
6580
+ /* @__PURE__ */ jsx("blockquote", {
6581
+ className: "dt-ses-moment-quote",
6582
+ children: segment.text || "No speech was heard here."
6583
+ }),
6584
+ /* @__PURE__ */ jsx(MomentContext, {
6585
+ detail,
6586
+ segment
6587
+ }),
6588
+ /* @__PURE__ */ jsx(MomentNotices, {
6589
+ detail,
6590
+ segment
6591
+ }),
6592
+ /* @__PURE__ */ jsx(CitingIntentions, {
6593
+ detail,
6594
+ onOpenIntention: handlers.onOpenIntention,
6595
+ segmentId: segment.id
6596
+ }),
6597
+ detail.session.canEdit && /* @__PURE__ */ jsx(ContextCorrection, {
6598
+ review,
6599
+ segment
6600
+ })
6601
+ ] }) : /* @__PURE__ */ jsx("p", {
6602
+ className: "dt-meta",
6603
+ children: "This passage is no longer in the session."
6604
+ }),
6605
+ /* @__PURE__ */ jsx(MomentSteps, {
6606
+ detail,
6607
+ onShowMoment: handlers.onShowMoment,
6608
+ segmentId
6609
+ })
6610
+ ]
6611
+ });
6612
+ }
6613
+ //#endregion
6614
+ //#region src/sessions/ui/start/session-disclosure.tsx
6615
+ const SEEN_KEY_PREFIX = "reflet-sessions-disclosure:";
6616
+ function hasSeenDisclosure(accountKey) {
6617
+ try {
6618
+ return localStorage.getItem(`${SEEN_KEY_PREFIX}${accountKey}`) !== null;
6619
+ } catch {
6620
+ return false;
6621
+ }
6622
+ }
6623
+ function markDisclosureSeen(accountKey) {
6624
+ try {
6625
+ localStorage.setItem(`${SEEN_KEY_PREFIX}${accountKey}`, "1");
6626
+ } catch {}
6627
+ }
6628
+ function DisclosureList() {
6629
+ return /* @__PURE__ */ jsxs("section", {
6630
+ "aria-labelledby": "rf-session-disclosure",
6631
+ className: "dt-hint",
6632
+ children: [/* @__PURE__ */ jsx("h3", {
6633
+ className: "dt-ses-subhead",
6634
+ id: "rf-session-disclosure",
6635
+ children: "What a session records"
6636
+ }), /* @__PURE__ */ jsxs("ul", {
6637
+ className: "dt-ses-list",
6638
+ children: [
6639
+ /* @__PURE__ */ jsx("li", { children: "Your voice while recording. It is sent to Reflet and transcribed by Whisper through OpenRouter with zero data retention. The audio is deleted as soon as its transcript is saved, within 24 hours at most." }),
6640
+ /* @__PURE__ */ jsx("li", { children: "The page: its address without sensitive parameters, its title, the scroll position and the window size." }),
6641
+ /* @__PURE__ */ jsx("li", { children: "What you click, point at and mark: element names and positions, never your keystrokes." }),
6642
+ /* @__PURE__ */ jsxs("li", { children: [
6643
+ "Screenshots of this tab at the start, when you mark a moment and when the page changes. Form fields appear empty; password fields and elements marked ",
6644
+ /* @__PURE__ */ jsx("code", { children: "data-reflet-redact" }),
6645
+ " are hidden. Other text on screen, rich-text editors included, stays visible."
6646
+ ] }),
6647
+ /* @__PURE__ */ jsx("li", { children: "Not recorded: keystrokes, other tabs or apps, network calls." }),
6648
+ /* @__PURE__ */ jsx("li", { children: "Pausing stops everything." }),
6649
+ /* @__PURE__ */ jsx("li", { children: "A session stays private to you until you share it." })
6650
+ ]
6651
+ })]
6652
+ });
6653
+ }
6654
+ function SessionDisclosure({ accountKey }) {
6655
+ const [isOpen, setIsOpen] = useState(() => !hasSeenDisclosure(accountKey));
6656
+ if (isOpen) return /* @__PURE__ */ jsx(DisclosureList, {});
6657
+ return /* @__PURE__ */ jsx("button", {
6658
+ "aria-expanded": false,
6659
+ className: "dt-ses-link",
6660
+ onClick: () => setIsOpen(true),
6661
+ type: "button",
6662
+ children: "What a session records"
6663
+ });
6664
+ }
6665
+ //#endregion
6666
+ //#region src/sessions/ui/start/start-session.tsx
6667
+ const IS_MICROPHONE_FAILURE = {
6668
+ auth: false,
6669
+ insecure_context: true,
6670
+ limit: false,
6671
+ mic_blocked_by_site: true,
6672
+ mic_denied: true,
6673
+ mic_unavailable: true,
6674
+ network: false,
6675
+ storage: false
6676
+ };
6677
+ function StartProblem({ failure, onRetry, onWriteNote }) {
6678
+ return /* @__PURE__ */ jsxs("div", {
6679
+ className: "dt-ses-problem",
6680
+ role: "alert",
6681
+ children: [/* @__PURE__ */ jsx("p", {
6682
+ className: "dt-error",
6683
+ children: failure.message
6684
+ }), /* @__PURE__ */ jsxs("div", {
6685
+ className: "dt-actions",
6686
+ children: [/* @__PURE__ */ jsx("button", {
6687
+ className: "dt-btn",
6688
+ onClick: onRetry,
6689
+ type: "button",
6690
+ children: "Try again"
6691
+ }), IS_MICROPHONE_FAILURE[failure.kind] && onWriteNote && /* @__PURE__ */ jsx("button", {
6692
+ className: "dt-btn",
6693
+ onClick: onWriteNote,
6694
+ type: "button",
6695
+ children: "Write a note instead"
6696
+ })]
6697
+ })]
6698
+ });
6699
+ }
6700
+ function StartActions({ isBusy, onStart, onWriteNote }) {
6701
+ return /* @__PURE__ */ jsxs("div", {
6702
+ className: "dt-actions",
6703
+ children: [/* @__PURE__ */ jsx("button", {
6704
+ className: "dt-btn",
6705
+ "data-variant": "primary",
6706
+ disabled: isBusy,
6707
+ onClick: onStart,
6708
+ type: "button",
6709
+ children: "Start session"
6710
+ }), onWriteNote && /* @__PURE__ */ jsx("button", {
6711
+ className: "dt-btn",
6712
+ onClick: onWriteNote,
6713
+ type: "button",
6714
+ children: "Write a note instead"
6715
+ })]
6716
+ });
6717
+ }
6718
+ function useRecorderStart(onStarted) {
6719
+ const { account, recorder } = useSessionWorkspace();
6720
+ const snapshot = useRecorderSnapshot(recorder);
6721
+ const [hasTried, setHasTried] = useState(false);
6722
+ const start = async (options) => {
6723
+ markDisclosureSeen(account.key);
6724
+ setHasTried(true);
6725
+ await recorder.start(options);
6726
+ if (recorder.getSnapshot().phase === "recording") onStarted();
6727
+ };
6728
+ const isRequesting = snapshot.phase === "requesting";
6729
+ return {
6730
+ failure: hasTried && snapshot.phase === "failed" && snapshot.sessionKey === null ? snapshot.failure : null,
6731
+ isBusy: isRequesting || snapshot.phase === "finishing",
6732
+ isRequesting,
6733
+ start
6734
+ };
6735
+ }
6736
+ function MicrophonePrompt({ isRequesting }) {
6737
+ return /* @__PURE__ */ jsx("p", {
6738
+ "aria-live": "polite",
6739
+ className: "dt-meta",
6740
+ children: isRequesting ? "Allow the microphone in your browser to start." : ""
6741
+ });
6742
+ }
6743
+ function StartSession({ onStarted, onWriteNote }) {
6744
+ const { account } = useSessionWorkspace();
6745
+ const { failure, isBusy, isRequesting, start } = useRecorderStart(onStarted);
6746
+ const startBlank = () => start({});
6747
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
6748
+ /* @__PURE__ */ jsx(SessionDisclosure, { accountKey: account.key }),
6749
+ /* @__PURE__ */ jsx(MicrophonePrompt, { isRequesting }),
6750
+ failure ? /* @__PURE__ */ jsx(StartProblem, {
6751
+ failure,
6752
+ onRetry: startBlank,
6753
+ onWriteNote
6754
+ }) : /* @__PURE__ */ jsx(StartActions, {
6755
+ isBusy,
6756
+ onStart: startBlank,
6757
+ onWriteNote
6758
+ })
6759
+ ] });
6760
+ }
6761
+ //#endregion
6762
+ //#region src/sessions/ui/review/proposals/use-proposal-actions.ts
6763
+ const NOTICE_NEED_LENGTH = 60;
6764
+ const DECISION_NOTICE = {
6765
+ dismissed: "Dismissed",
6766
+ kept: "Confirmed",
6767
+ later: "Saved for later",
6768
+ pending: "Back to undecided"
6769
+ };
6770
+ function quotedNeed(need) {
6771
+ return `“${need.length > NOTICE_NEED_LENGTH ? `${need.slice(0, 59)}…` : need}”`;
6772
+ }
6773
+ function useProposalSave(review) {
6774
+ const { api } = useSessionWorkspace();
6775
+ const run = useSessionAction();
6776
+ return async (failureLabel, request) => {
6777
+ const proposal = await run(failureLabel, () => request(api));
6778
+ if (proposal) review.patch((detail) => withProposal(detail, proposal));
6779
+ return proposal;
6780
+ };
6781
+ }
6782
+ function decideWithUndo(save) {
6783
+ const restore = async (proposalId, decision) => {
6784
+ if (await save("The decision was not undone", (client) => client.decideProposal({
6785
+ decision,
6786
+ proposalId
6787
+ }))) announce("Decision undone");
6788
+ };
6789
+ return async (proposal, decision) => {
6790
+ const previous = proposal.decision;
6791
+ if (await save("The decision was not saved", (client) => client.decideProposal({
6792
+ decision,
6793
+ proposalId: proposal.id
6794
+ }))) announce(`${DECISION_NOTICE[decision]} ${quotedNeed(proposal.need)}`, {
6795
+ label: "Undo",
6796
+ run: () => restore(proposal.id, previous)
6797
+ });
6798
+ };
6799
+ }
6800
+ function useProposalActions(review) {
6801
+ const save = useProposalSave(review);
6802
+ return {
6803
+ clarify: (proposal, text) => save("The precision was not sent", (client) => client.clarifyProposal({
6804
+ expectedRevision: proposal.revision,
6805
+ proposalId: proposal.id,
6806
+ text
6807
+ })),
6808
+ decide: decideWithUndo(save),
6809
+ merge: async (targetId, sourceIds) => {
6810
+ if (await save("The intentions were not merged", (client) => client.mergeProposals({
6811
+ sourceIds,
6812
+ targetId
6813
+ }))) review.reload();
6814
+ },
6815
+ save,
6816
+ separate: (proposalId) => save("The suggestion was not cleared", (client) => client.separateProposal(proposalId)),
6817
+ verify: (proposalId, { note, status }) => save("The check was not saved", (client) => client.verifyProposal({
6818
+ note: note || void 0,
6819
+ proposalId,
6820
+ status
6821
+ }))
6822
+ };
6823
+ }
6824
+ //#endregion
6825
+ //#region src/sessions/ui/review/proposals/delete-intention.tsx
6826
+ 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.";
6827
+ function DeleteConfirmation$1({ context, onCancel, proposal }) {
6828
+ const { api } = useSessionWorkspace();
6829
+ const run = useSessionAction();
6830
+ const remove = async () => {
6831
+ if (await run("The intention was not deleted", () => api.deleteProposal(proposal.id)) !== null) {
6832
+ context.review.patch((detail) => ({
6833
+ ...detail,
6834
+ proposals: detail.proposals.filter((item) => item.id !== proposal.id)
6835
+ }));
6836
+ announce(`Deleted ${quotedNeed(proposal.need)}`);
6837
+ }
6838
+ };
6839
+ return /* @__PURE__ */ jsxs("div", {
6840
+ className: "dt-ses-banner",
6841
+ children: [/* @__PURE__ */ jsx("p", { children: DELETE_INTENTION_COPY }), /* @__PURE__ */ jsxs("div", {
6842
+ className: "dt-actions",
6843
+ children: [/* @__PURE__ */ jsx("button", {
6844
+ className: "dt-btn",
6845
+ "data-variant": "danger",
6846
+ onClick: remove,
6847
+ type: "button",
6848
+ children: "Delete"
6849
+ }), /* @__PURE__ */ jsx("button", {
6850
+ className: "dt-btn",
6851
+ onClick: onCancel,
6852
+ type: "button",
6853
+ children: "Cancel"
6854
+ })]
6855
+ })]
6856
+ });
6857
+ }
6858
+ function DeleteIntention({ context, proposal }) {
6859
+ const [isConfirming, setIsConfirming] = useState(false);
6860
+ const [hasCancelled, setHasCancelled] = useState(false);
6861
+ const buttonRef = useFocusWhen(hasCancelled && !isConfirming);
6862
+ if (isConfirming) return /* @__PURE__ */ jsx(DeleteConfirmation$1, {
6863
+ context,
6864
+ onCancel: () => {
6865
+ setIsConfirming(false);
6866
+ setHasCancelled(true);
6867
+ },
6868
+ proposal
6869
+ });
6870
+ return /* @__PURE__ */ jsx("div", {
6871
+ className: "dt-actions",
6872
+ children: /* @__PURE__ */ jsx("button", {
6873
+ className: "dt-btn",
6874
+ "data-variant": "danger",
6875
+ onClick: () => {
6876
+ setIsConfirming(true);
6877
+ setHasCancelled(false);
6878
+ },
6879
+ ref: buttonRef,
6880
+ type: "button",
6881
+ children: "Delete intention"
6882
+ })
6883
+ });
6884
+ }
6885
+ //#endregion
6886
+ //#region src/sessions/ui/review/proposals/proposal-details.tsx
6887
+ function ListSection({ items, title }) {
6888
+ if (items.length === 0) return null;
6889
+ return /* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("h4", {
6890
+ className: "dt-ses-label",
6891
+ children: title
6892
+ }), /* @__PURE__ */ jsx("ul", {
6893
+ className: "dt-ses-list",
6894
+ children: items.map((item) => /* @__PURE__ */ jsx("li", { children: item }, item))
6895
+ })] });
6896
+ }
6897
+ function RefinementState({ onRetry, proposal }) {
6898
+ const { refinement } = proposal;
6899
+ if (refinement?.state === "running") return /* @__PURE__ */ jsx("p", {
6900
+ className: "dt-status",
6901
+ children: "Refining…"
6902
+ });
6903
+ if (refinement?.state === "outdated") return /* @__PURE__ */ jsx("p", {
6904
+ className: "dt-meta",
6905
+ children: "The intention changed while it was being refined. Add your precision again."
6906
+ });
6907
+ if (refinement?.state !== "failed") return null;
6908
+ return /* @__PURE__ */ jsxs("div", {
6909
+ className: "dt-actions",
6910
+ children: [/* @__PURE__ */ jsxs("p", {
6911
+ className: "dt-error",
6912
+ children: ["Refining failed: ", refinement.error ?? "Unknown error."]
6913
+ }), /* @__PURE__ */ jsx("button", {
6914
+ className: "dt-btn",
6915
+ onClick: onRetry,
6916
+ type: "button",
6917
+ children: "Retry"
6918
+ })]
6919
+ });
6920
+ }
6921
+ function ClarifyBox({ context, proposal }) {
6922
+ const [text, setText] = useState("");
6923
+ const isRefining = proposal.refinement?.state === "running";
6924
+ const lastClarification = proposal.clarifications.at(-1)?.text;
6925
+ const send = async (value) => {
6926
+ if (await context.actions.clarify(proposal, value)) setText("");
6927
+ };
6928
+ return /* @__PURE__ */ jsxs("div", {
6929
+ className: "dt-ses-form",
6930
+ children: [
6931
+ proposal.question && /* @__PURE__ */ jsx("p", {
6932
+ className: "dt-note-text",
6933
+ children: proposal.question
6934
+ }),
6935
+ /* @__PURE__ */ jsx("textarea", {
6936
+ "aria-label": proposal.question ? "Your answer" : "Add a precision…",
6937
+ className: "dt-field",
6938
+ disabled: isRefining,
6939
+ maxLength: 1e3,
6940
+ onChange: (event) => setText(event.target.value),
6941
+ placeholder: "Add a precision…",
6942
+ rows: 2,
6943
+ value: text
6944
+ }),
6945
+ /* @__PURE__ */ jsx("div", {
6946
+ className: "dt-actions",
6947
+ children: /* @__PURE__ */ jsx("button", {
6948
+ className: "dt-btn",
6949
+ disabled: isRefining || text.trim() === "",
6950
+ onClick: () => send(text.trim()),
6951
+ type: "button",
6952
+ children: "Update intention"
6953
+ })
6954
+ }),
6955
+ /* @__PURE__ */ jsx(RefinementState, {
6956
+ onRetry: () => lastClarification && send(lastClarification),
6957
+ proposal
6958
+ })
6959
+ ]
6960
+ });
6961
+ }
6962
+ function Sources({ context, proposal }) {
6963
+ const segmentIds = new Set(proposal.segmentIds);
6964
+ const evidenceIds = new Set(proposal.evidenceIds);
6965
+ const segments = context.detail.segments.filter((segment) => segmentIds.has(segment.id));
6966
+ const evidence = context.detail.evidence.filter((item) => evidenceIds.has(item.id));
6967
+ if (segments.length + evidence.length === 0) return null;
6968
+ return /* @__PURE__ */ jsxs("section", { children: [
6969
+ /* @__PURE__ */ jsx("h4", {
6970
+ className: "dt-ses-label",
6971
+ children: "Sources"
6972
+ }),
6973
+ segments.map((segment) => /* @__PURE__ */ jsxs("button", {
6974
+ className: "dt-ses-quote",
6975
+ onClick: (event) => context.navigation.onShowMoment(segment.id, event.currentTarget),
6976
+ type: "button",
6977
+ children: [/* @__PURE__ */ jsx("span", {
6978
+ className: "dt-ses-time",
6979
+ children: formatClock(segment.startMs)
6980
+ }), ` “${segment.text}”`]
6981
+ }, segment.id)),
6982
+ /* @__PURE__ */ jsx("div", {
6983
+ className: "dt-ses-thumbs",
6984
+ children: evidence.map((item) => /* @__PURE__ */ jsx(EvidenceThumb, { evidence: item }, item.id))
6985
+ })
6986
+ ] });
6987
+ }
6988
+ function ProposalDetails({ context, proposal }) {
6989
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
6990
+ /* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("h4", {
6991
+ className: "dt-ses-label",
6992
+ children: "What you said or showed — not verified"
6993
+ }), /* @__PURE__ */ jsx("p", {
6994
+ className: "dt-note-text",
6995
+ children: proposal.observed
6996
+ })] }),
6997
+ /* @__PURE__ */ jsx(ListSection, {
6998
+ items: proposal.setAside,
6999
+ title: "Set aside"
7000
+ }),
7001
+ context.canEdit && /* @__PURE__ */ jsx(ClarifyBox, {
7002
+ context,
7003
+ proposal
7004
+ }),
7005
+ /* @__PURE__ */ jsx(Sources, {
7006
+ context,
7007
+ proposal
7008
+ }),
7009
+ /* @__PURE__ */ jsx(IntentionHistory, {
7010
+ context,
7011
+ intentionId: proposal.id
7012
+ }),
7013
+ context.canEdit && /* @__PURE__ */ jsx(DeleteIntention, {
7014
+ context,
7015
+ proposal
7016
+ })
7017
+ ] });
7018
+ }
7019
+ //#endregion
7020
+ //#region src/sessions/ui/review/continuation/continue-panel.tsx
7021
+ const MAX_TITLE_NEED_CHARS = 80;
7022
+ function continuationOf(proposal) {
7023
+ return {
7024
+ continues: {
7025
+ intentionId: proposal.id,
7026
+ revision: proposal.revision
7027
+ },
7028
+ title: continueTitle(proposal.need)
7029
+ };
7030
+ }
7031
+ function continueTitle(need) {
7032
+ return `Continue: ${need.length > MAX_TITLE_NEED_CHARS ? `${need.slice(0, 79)}…` : need}`;
7033
+ }
7034
+ function IntentionRecap({ proposal }) {
7035
+ const stillOpen = proposal.question ? [...proposal.unknowns, proposal.question] : proposal.unknowns;
7036
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
7037
+ /* @__PURE__ */ jsx("p", {
7038
+ className: "dt-note-text",
7039
+ children: proposal.need
7040
+ }),
7041
+ proposal.response && /* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("h5", {
7042
+ className: "dt-ses-label",
7043
+ children: "Considered response"
7044
+ }), /* @__PURE__ */ jsx("p", {
7045
+ className: "dt-note-text",
7046
+ children: proposal.response
7047
+ })] }),
7048
+ /* @__PURE__ */ jsx(ListSection, {
7049
+ items: stillOpen,
7050
+ title: "Still open"
7051
+ }),
7052
+ /* @__PURE__ */ jsx("p", {
7053
+ className: "dt-meta",
7054
+ children: "Speak to refine the need or check the result. Nothing is sent to an agent."
7055
+ })
7056
+ ] });
7057
+ }
7058
+ function ContinuePanel({ onCancel, onStarted, proposal }) {
7059
+ const headingId = useId();
7060
+ const headingRef = useFocusWhen(true);
7061
+ const { failure, isBusy, isRequesting, start } = useRecorderStart(onStarted);
7062
+ const startContinuation = () => start(continuationOf(proposal));
7063
+ return /* @__PURE__ */ jsxs("section", {
7064
+ "aria-labelledby": headingId,
7065
+ className: "dt-ses-banner",
7066
+ children: [
7067
+ /* @__PURE__ */ jsx("h4", {
7068
+ className: "dt-ses-subhead",
7069
+ id: headingId,
7070
+ ref: headingRef,
7071
+ tabIndex: -1,
7072
+ children: "Continue this intention"
7073
+ }),
7074
+ /* @__PURE__ */ jsx(IntentionRecap, { proposal }),
7075
+ /* @__PURE__ */ jsx(MicrophonePrompt, { isRequesting }),
7076
+ failure && /* @__PURE__ */ jsx(StartProblem, {
7077
+ failure,
7078
+ onRetry: startContinuation
7079
+ }),
7080
+ /* @__PURE__ */ jsxs("div", {
7081
+ className: "dt-actions",
7082
+ children: [!failure && /* @__PURE__ */ jsx("button", {
7083
+ className: "dt-btn",
7084
+ "data-variant": "primary",
7085
+ disabled: isBusy,
7086
+ onClick: startContinuation,
7087
+ type: "button",
7088
+ children: "Start session"
7089
+ }), /* @__PURE__ */ jsx("button", {
7090
+ className: "dt-btn",
7091
+ onClick: onCancel,
7092
+ type: "button",
7093
+ children: "Cancel"
7094
+ })]
7095
+ })
7096
+ ]
7097
+ });
7098
+ }
7099
+ function ContinueIntention({ context, proposal }) {
7100
+ const [isOpen, setIsOpen] = useState(false);
7101
+ const [hasClosed, setHasClosed] = useState(false);
7102
+ const buttonRef = useFocusWhen(hasClosed && !isOpen);
7103
+ if (!(context.canEdit && proposal.decision !== "dismissed" && proposal.mergedIntoId === null && proposal.attachment === null)) return null;
7104
+ if (isOpen) return /* @__PURE__ */ jsx(ContinuePanel, {
7105
+ onCancel: () => {
7106
+ setIsOpen(false);
7107
+ setHasClosed(true);
7108
+ },
7109
+ onStarted: context.navigation.onStarted,
7110
+ proposal
7111
+ });
7112
+ return /* @__PURE__ */ jsx("div", {
7113
+ className: "dt-actions",
7114
+ children: /* @__PURE__ */ jsx("button", {
7115
+ className: "dt-btn",
7116
+ onClick: () => {
7117
+ setIsOpen(true);
7118
+ setHasClosed(false);
7119
+ },
7120
+ ref: buttonRef,
7121
+ type: "button",
7122
+ children: "Continue this intention"
7123
+ })
7124
+ });
7125
+ }
7126
+ //#endregion
7127
+ //#region src/sessions/ui/review/moment/intention-source.tsx
7128
+ function IntentionSource({ context, proposal }) {
7129
+ const { segments } = context.detail;
7130
+ const firstSegmentId = proposal.segmentIds.find((segmentId) => segments.some((segment) => segment.id === segmentId));
7131
+ const segment = segments.find((item) => item.id === firstSegmentId);
7132
+ if (!segment) return null;
7133
+ const [firstContext] = segment.context;
7134
+ const range = `${formatClock(segment.startMs)}–${formatClock(segment.endMs)}`;
7135
+ return /* @__PURE__ */ jsxs("section", {
7136
+ className: "dt-ses-block",
7137
+ children: [
7138
+ /* @__PURE__ */ jsxs("p", {
7139
+ className: "dt-ses-source",
7140
+ children: [
7141
+ /* @__PURE__ */ jsx("span", {
7142
+ className: "dt-ses-time",
7143
+ children: range
7144
+ }),
7145
+ " “",
7146
+ segment.text,
7147
+ "”"
7148
+ ]
7149
+ }),
7150
+ firstContext && /* @__PURE__ */ jsx("p", {
7151
+ className: "dt-meta",
7152
+ children: contextLine(firstContext)
7153
+ }),
7154
+ /* @__PURE__ */ jsx("button", {
7155
+ className: "dt-ses-link",
7156
+ onClick: (event) => context.navigation.onShowMoment(segment.id, event.currentTarget),
7157
+ type: "button",
7158
+ children: "Show context"
7159
+ })
7160
+ ]
7161
+ });
7162
+ }
7163
+ //#endregion
7164
+ //#region src/sessions/ui/review/proposals/intention-suggestion.tsx
7165
+ function useAttach(context, proposal) {
7166
+ const [isBusy, setIsBusy] = useState(false);
7167
+ const attach = async (suggestion, shareWithTeam) => {
7168
+ setIsBusy(true);
7169
+ try {
7170
+ if (await context.actions.save("The session was not added to the intention", (api) => api.attachToIntention({
7171
+ intentionId: suggestion.intentionId,
7172
+ proposalId: proposal.id,
7173
+ ...shareWithTeam ? { shareWithTeam } : {}
7174
+ }))) announce(`Added to ${quotedNeed(suggestion.need)}`);
7175
+ } finally {
7176
+ setIsBusy(false);
7177
+ }
7178
+ };
7179
+ const keepSeparate = () => context.actions.save("The suggestion was not cleared", (api) => api.keepSeparate(proposal.id));
7180
+ return {
7181
+ attach,
7182
+ isBusy,
7183
+ keepSeparate
7184
+ };
7185
+ }
7186
+ function SuggestionChoices({ canEdit, handlers, suggestion }) {
7187
+ return /* @__PURE__ */ jsxs("div", {
7188
+ className: "dt-ses-banner",
7189
+ children: [/* @__PURE__ */ jsxs("p", { children: [
7190
+ "Might belong to “",
7191
+ suggestion.need,
7192
+ "” (from “",
7193
+ suggestion.sessionTitle,
7194
+ "”):",
7195
+ " ",
7196
+ suggestion.reason
7197
+ ] }), canEdit && /* @__PURE__ */ jsxs("div", {
7198
+ className: "dt-actions",
7199
+ children: [/* @__PURE__ */ jsx("button", {
7200
+ className: "dt-btn",
7201
+ disabled: handlers.isBusy,
7202
+ onClick: handlers.onAdd,
7203
+ type: "button",
7204
+ children: "Add to this intention"
7205
+ }), /* @__PURE__ */ jsx("button", {
7206
+ className: "dt-btn",
7207
+ onClick: handlers.onKeepSeparate,
7208
+ type: "button",
7209
+ children: "Keep separate"
7210
+ })]
7211
+ })]
7212
+ });
7213
+ }
7214
+ function IntentionSuggestionBanner({ context, proposal }) {
7215
+ const [isPreviewing, setIsPreviewing] = useState(false);
7216
+ const { attach, isBusy, keepSeparate } = useAttach(context, proposal);
7217
+ const suggestion = proposal.intentionSuggestion;
7218
+ if (!suggestion) return null;
7219
+ if (isPreviewing) return /* @__PURE__ */ jsx(SharePreview, {
7220
+ confirmation: {
7221
+ isBusy,
7222
+ label: "Share and add",
7223
+ onCancel: () => setIsPreviewing(false),
7224
+ onConfirm: () => attach(suggestion, true)
7225
+ },
7226
+ detail: context.detail,
7227
+ extracts: proposal
7228
+ });
7229
+ return /* @__PURE__ */ jsx(SuggestionChoices, {
7230
+ canEdit: context.canEdit,
7231
+ handlers: {
7232
+ isBusy,
7233
+ onAdd: () => suggestion.needsShareConsent ? setIsPreviewing(true) : attach(suggestion, false),
7234
+ onKeepSeparate: keepSeparate
7235
+ },
7236
+ suggestion
7237
+ });
7238
+ }
7239
+ function AttachedIntentionRow({ attachment, context }) {
7240
+ const { api } = useSessionWorkspace();
7241
+ const run = useSessionAction();
7242
+ const undo = async () => {
7243
+ if (await run("The session was not separated", () => api.detachContribution(attachment.contributionId))) {
7244
+ announce(`Separated from ${quotedNeed(attachment.need)}`);
7245
+ context.review.reload();
7246
+ }
7247
+ };
7248
+ return /* @__PURE__ */ jsxs("article", {
7249
+ className: "dt-card dt-ses-attached",
7250
+ children: [/* @__PURE__ */ jsxs("p", {
7251
+ className: "dt-meta",
7252
+ children: [
7253
+ "Added to intention “",
7254
+ attachment.need,
7255
+ "”"
7256
+ ]
7257
+ }), context.canEdit && /* @__PURE__ */ jsx("button", {
7258
+ className: "dt-btn",
7259
+ onClick: undo,
7260
+ type: "button",
7261
+ children: "Undo"
7262
+ })]
7263
+ });
7264
+ }
7265
+ //#endregion
7266
+ //#region src/sessions/ui/review/proposals/verification.tsx
7267
+ const OUTCOME_LABELS = {
7268
+ needs_work: "Needs more work",
7269
+ works: "Works"
7270
+ };
7271
+ function Verification({ context, proposal }) {
7272
+ const [note, setNote] = useState(proposal.verification?.note ?? "");
7273
+ if (proposal.handedOffRevision === null) return null;
7274
+ const { verification } = proposal;
7275
+ return /* @__PURE__ */ jsxs("fieldset", {
7276
+ className: "dt-ses-verify",
7277
+ children: [
7278
+ /* @__PURE__ */ jsx("legend", {
7279
+ className: "dt-ses-label",
7280
+ children: "Did the change work?"
7281
+ }),
7282
+ verification && /* @__PURE__ */ jsxs("p", {
7283
+ className: "dt-meta",
7284
+ children: [
7285
+ "Checked: ",
7286
+ OUTCOME_LABELS[verification.status],
7287
+ verification.note ? ` · ${verification.note}` : ""
7288
+ ]
7289
+ }),
7290
+ /* @__PURE__ */ jsx("input", {
7291
+ "aria-label": "Note about the change (optional)",
7292
+ className: "dt-field",
7293
+ maxLength: 1e3,
7294
+ onChange: (event) => setNote(event.target.value),
7295
+ placeholder: "Note (optional)",
7296
+ value: note
7297
+ }),
7298
+ /* @__PURE__ */ jsx("div", {
7299
+ className: "dt-actions",
7300
+ children: ["works", "needs_work"].map((status) => /* @__PURE__ */ jsx("button", {
7301
+ "aria-pressed": verification?.status === status,
7302
+ className: "dt-btn",
7303
+ onClick: () => context.actions.verify(proposal.id, {
7304
+ note: note.trim(),
7305
+ status
7306
+ }),
7307
+ type: "button",
7308
+ children: OUTCOME_LABELS[status]
7309
+ }, status))
7310
+ })
7311
+ ]
7312
+ });
7313
+ }
7314
+ //#endregion
7315
+ //#region src/sessions/ui/review/proposals/proposal-card.tsx
7316
+ const DECISION_CHIPS = {
7317
+ dismissed: "Dismissed",
7318
+ kept: "Confirmed",
7319
+ later: "Later"
7320
+ };
7321
+ const FORMULATION_CHIPS = {
7322
+ ai: "AI wording",
7323
+ human: "Your wording"
7324
+ };
7325
+ const DECISION_BUTTONS = [
7326
+ {
7327
+ decision: "kept",
7328
+ label: "Confirm"
7329
+ },
7330
+ {
7331
+ decision: "later",
7332
+ label: "Later"
7333
+ },
7334
+ {
7335
+ decision: "dismissed",
7336
+ label: "Dismiss"
7337
+ }
7338
+ ];
7339
+ function ProposalChips({ proposal }) {
7340
+ const decisionChip = DECISION_CHIPS[proposal.decision];
7341
+ const { handedOffRevision } = proposal;
7342
+ let handoffChip = null;
7343
+ if (handedOffRevision !== null) handoffChip = proposal.revision > handedOffRevision ? "Changed since handoff" : `Handed off rev ${handedOffRevision}`;
4483
7344
  return /* @__PURE__ */ jsxs("div", {
4484
7345
  className: "dt-ses-chips",
4485
7346
  children: [
7347
+ decisionChip && /* @__PURE__ */ jsx("span", {
7348
+ className: "dt-chip",
7349
+ children: decisionChip
7350
+ }),
7351
+ /* @__PURE__ */ jsx("span", {
7352
+ className: "dt-chip",
7353
+ children: FORMULATION_CHIPS[proposal.formulation]
7354
+ }),
4486
7355
  proposal.certainty === "uncertain" && /* @__PURE__ */ jsx("span", {
4487
7356
  className: "dt-chip",
4488
7357
  children: "Unclear"
@@ -4490,11 +7359,7 @@ function ProposalChips({ proposal }) {
4490
7359
  proposal.needsReview && /* @__PURE__ */ jsx("span", {
4491
7360
  className: "dt-chip",
4492
7361
  "data-tone": "accent",
4493
- children: "Needs review"
4494
- }),
4495
- decisionChip && /* @__PURE__ */ jsx("span", {
4496
- className: "dt-chip",
4497
- children: decisionChip
7362
+ children: "To re-examine"
4498
7363
  }),
4499
7364
  handoffChip && /* @__PURE__ */ jsx("span", {
4500
7365
  className: "dt-chip",
@@ -4585,34 +7450,94 @@ function DetailsDisclosure({ context, disclosure, proposal }) {
4585
7450
  })
4586
7451
  })] });
4587
7452
  }
4588
- function ProposalCard({ context, proposal }) {
4589
- const disclosure = useDisclosure();
7453
+ function ToCheck({ proposal }) {
7454
+ const items = proposal.question ? [...proposal.unknowns, proposal.question] : proposal.unknowns;
7455
+ return /* @__PURE__ */ jsx(ListSection, {
7456
+ items,
7457
+ title: "To check"
7458
+ });
7459
+ }
7460
+ function ResponseBlock({ context, proposal }) {
4590
7461
  const [isEditing, setIsEditing] = useState(false);
4591
- if (isEditing) return /* @__PURE__ */ jsx("article", {
4592
- className: "dt-card",
4593
- children: /* @__PURE__ */ jsx(ProposalEditor, {
4594
- onDone: () => setIsEditing(false),
4595
- proposal,
4596
- review: context.review
4597
- }, proposal.revision)
7462
+ if (isEditing) return /* @__PURE__ */ jsx(ResponseEditor, {
7463
+ onDone: () => setIsEditing(false),
7464
+ proposal,
7465
+ review: context.review
7466
+ });
7467
+ if (!(proposal.response || context.canEdit)) return null;
7468
+ return /* @__PURE__ */ jsxs("section", {
7469
+ className: "dt-ses-block",
7470
+ children: [
7471
+ /* @__PURE__ */ jsx("h4", {
7472
+ className: "dt-ses-label",
7473
+ children: "Considered response"
7474
+ }),
7475
+ /* @__PURE__ */ jsx("p", {
7476
+ className: proposal.response ? "dt-note-text" : "dt-meta",
7477
+ children: proposal.response ?? "No considered response yet."
7478
+ }),
7479
+ context.canEdit && /* @__PURE__ */ jsx("button", {
7480
+ className: "dt-ses-link",
7481
+ onClick: () => setIsEditing(true),
7482
+ type: "button",
7483
+ children: "Edit response"
7484
+ })
7485
+ ]
4598
7486
  });
7487
+ }
7488
+ function IntentionBody({ context, disclosure, proposal }) {
7489
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
7490
+ /* @__PURE__ */ jsx("p", {
7491
+ className: "dt-ses-label",
7492
+ children: "Intention"
7493
+ }),
7494
+ /* @__PURE__ */ jsx("p", {
7495
+ className: "dt-ses-need",
7496
+ "data-expanded": disclosure.isExpanded,
7497
+ children: proposal.need
7498
+ }),
7499
+ /* @__PURE__ */ jsx(ProposalChips, { proposal }),
7500
+ /* @__PURE__ */ jsx(IntentionSource, {
7501
+ context,
7502
+ proposal
7503
+ }),
7504
+ /* @__PURE__ */ jsx(ResponseBlock, {
7505
+ context,
7506
+ proposal
7507
+ }),
7508
+ /* @__PURE__ */ jsx(ToCheck, { proposal })
7509
+ ] });
7510
+ }
7511
+ function IntentionCard({ context, onEdit, proposal }) {
7512
+ const disclosure = useDisclosure();
7513
+ const cardRef = useFocusWhen(context.focusIntentionId === proposal.id);
4599
7514
  return /* @__PURE__ */ jsxs("article", {
7515
+ "aria-label": `Intention: ${proposal.need}`,
4600
7516
  className: "dt-card",
4601
7517
  "data-decision": proposal.decision,
7518
+ ref: cardRef,
7519
+ tabIndex: -1,
4602
7520
  children: [
4603
- /* @__PURE__ */ jsx("p", {
4604
- className: "dt-ses-need",
4605
- "data-expanded": disclosure.isExpanded,
4606
- children: proposal.need
7521
+ /* @__PURE__ */ jsx(IntentionBody, {
7522
+ context,
7523
+ disclosure,
7524
+ proposal
4607
7525
  }),
4608
- /* @__PURE__ */ jsx(ProposalChips, { proposal }),
4609
7526
  /* @__PURE__ */ jsx(MergeSuggestion, {
4610
7527
  context,
4611
7528
  proposal
4612
7529
  }),
7530
+ /* @__PURE__ */ jsx(IntentionSuggestionBanner, {
7531
+ context,
7532
+ proposal
7533
+ }),
4613
7534
  context.canEdit && /* @__PURE__ */ jsx(DecisionButtons, {
4614
7535
  context,
4615
- onEdit: () => setIsEditing(true),
7536
+ onEdit,
7537
+ proposal
7538
+ }),
7539
+ /* @__PURE__ */ jsx(ContinueIntention, {
7540
+ context,
4616
7541
  proposal
4617
7542
  }),
4618
7543
  /* @__PURE__ */ jsx(DetailsDisclosure, {
@@ -4627,16 +7552,36 @@ function ProposalCard({ context, proposal }) {
4627
7552
  ]
4628
7553
  });
4629
7554
  }
7555
+ function ProposalCard({ context, proposal }) {
7556
+ const [isEditing, setIsEditing] = useState(false);
7557
+ if (proposal.attachment) return /* @__PURE__ */ jsx(AttachedIntentionRow, {
7558
+ attachment: proposal.attachment,
7559
+ context
7560
+ });
7561
+ if (isEditing) return /* @__PURE__ */ jsx("article", {
7562
+ className: "dt-card",
7563
+ children: /* @__PURE__ */ jsx(ProposalEditor, {
7564
+ onDone: () => setIsEditing(false),
7565
+ proposal,
7566
+ review: context.review
7567
+ }, proposal.revision)
7568
+ });
7569
+ return /* @__PURE__ */ jsx(IntentionCard, {
7570
+ context,
7571
+ onEdit: () => setIsEditing(true),
7572
+ proposal
7573
+ });
7574
+ }
4630
7575
  //#endregion
4631
7576
  //#region src/sessions/ui/review/proposals/proposals-tab.tsx
4632
7577
  function emptyCopy(detail) {
4633
7578
  const { session } = detail;
4634
- if (session.collection !== "ended") return "Proposals appear here once the recording has uploaded and Reflet has read it.";
7579
+ if (session.collection !== "ended") return "Intentions appear here once the recording has uploaded and Reflet has read it.";
4635
7580
  if (detail.segments.length === 0 && detail.marks.length === 0) return "Nothing was said or marked in this session.";
4636
- if (isAnalysisBusy(session)) return "Proposals appear here once Reflet has read the session.";
4637
- if (session.analysis.state === "failed") return "The transcript is kept. Read it, or write the proposal yourself.";
4638
- if (session.analysis.state === "succeeded") return "No request stood out in this session. Read the transcript or write the proposal yourself.";
4639
- return "Proposals appear here once the session is analyzed.";
7581
+ if (isAnalysisBusy(session)) return "Intentions appear here once Reflet has read the session.";
7582
+ if (session.analysis.state === "failed") return "The transcript is kept. Read it, or write the intention yourself.";
7583
+ if (session.analysis.state === "succeeded") return "No request stood out in this session. Read the transcript or write the intention yourself.";
7584
+ return "Intentions appear here once the session is analyzed.";
4640
7585
  }
4641
7586
  function useAddProposal(context, onDone) {
4642
7587
  const [need, setNeed] = useState("");
@@ -4647,7 +7592,7 @@ function useAddProposal(context, onDone) {
4647
7592
  const add = async () => {
4648
7593
  setIsSaving(true);
4649
7594
  try {
4650
- if (await context.actions.save("The proposal was not added", (api) => api.createProposal({
7595
+ if (await context.actions.save("The intention was not added", (api) => api.createProposal({
4651
7596
  clientKey,
4652
7597
  need: need.trim(),
4653
7598
  observed: observed.trim() || void 0,
@@ -4692,7 +7637,7 @@ function AddProposalForm({ context, onDone }) {
4692
7637
  /* @__PURE__ */ jsx(FormActions, {
4693
7638
  isSubmitDisabled: isSaving || need.trim() === "",
4694
7639
  onCancel: onDone,
4695
- submitLabel: "Add proposal"
7640
+ submitLabel: "Add intention"
4696
7641
  })
4697
7642
  ]
4698
7643
  });
@@ -4700,8 +7645,13 @@ function AddProposalForm({ context, onDone }) {
4700
7645
  function ProposalsTab({ context }) {
4701
7646
  const [isAdding, setIsAdding] = useState(false);
4702
7647
  const proposals = context.detail.proposals.filter((proposal) => proposal.mergedIntoId === null);
7648
+ const { continuation } = context.detail;
4703
7649
  return /* @__PURE__ */ jsxs(Fragment, { children: [
4704
- proposals.length === 0 && /* @__PURE__ */ jsx("p", {
7650
+ continuation && /* @__PURE__ */ jsx(UpdateCard, {
7651
+ context,
7652
+ continuation
7653
+ }),
7654
+ proposals.length === 0 && !continuation && /* @__PURE__ */ jsx("p", {
4705
7655
  className: "dt-empty",
4706
7656
  children: emptyCopy(context.detail)
4707
7657
  }),
@@ -4718,14 +7668,14 @@ function ProposalsTab({ context }) {
4718
7668
  className: "dt-btn",
4719
7669
  onClick: () => setIsAdding(true),
4720
7670
  type: "button",
4721
- children: "Add a proposal"
7671
+ children: "Add an intention"
4722
7672
  })
4723
7673
  }))
4724
7674
  ] });
4725
7675
  }
4726
7676
  //#endregion
4727
7677
  //#region src/sessions/ui/review/review-header.tsx
4728
- const DELETE_COPY = "Delete this session? Its recordings, transcript and proposals are removed. Briefs you already copied or exported can't be recalled.";
7678
+ 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.";
4729
7679
  function useSessionUpdate(review, sessionId) {
4730
7680
  const { api } = useSessionWorkspace();
4731
7681
  const run = useSessionAction();
@@ -5084,60 +8034,23 @@ function AttachLink({ detail, onDone, review }) {
5084
8034
  });
5085
8035
  }
5086
8036
  //#endregion
5087
- //#region src/sessions/ui/review/transcript/remove-evidence.tsx
5088
- const REMOVE_COPY = "Remove this screenshot? The image is deleted for good. Briefs you already copied or exported can't be recalled.";
5089
- function RemoveConfirmation({ evidence, onCancel, review }) {
5090
- const { api } = useSessionWorkspace();
5091
- const run = useSessionAction();
5092
- const cancelRef = useRef(null);
5093
- useEffect(() => {
5094
- cancelRef.current?.focus();
5095
- }, []);
5096
- const remove = async () => {
5097
- const removed = await run("The screenshot was not removed", () => api.removeEvidence(evidence.id));
5098
- if (removed !== null) {
5099
- review.patch((detail) => withEvidence(detail, removed));
5100
- announce("Screenshot removed");
5101
- }
5102
- };
5103
- return /* @__PURE__ */ jsxs("div", {
5104
- className: "dt-ses-banner",
5105
- children: [/* @__PURE__ */ jsx("p", { children: REMOVE_COPY }), /* @__PURE__ */ jsxs("div", {
5106
- className: "dt-actions",
5107
- children: [/* @__PURE__ */ jsx("button", {
5108
- className: "dt-btn",
5109
- "data-variant": "danger",
5110
- onClick: remove,
5111
- type: "button",
5112
- children: "Remove"
5113
- }), /* @__PURE__ */ jsx("button", {
5114
- className: "dt-btn",
5115
- onClick: onCancel,
5116
- ref: cancelRef,
5117
- type: "button",
5118
- children: "Cancel"
5119
- })]
8037
+ //#region src/sessions/ui/review/transcript/evidence-tab.tsx
8038
+ const isViewable = (evidence) => evidence.status === "available" && Boolean(evidence.image?.url);
8039
+ function MissingEvidenceItem({ caption, evidence, isJustRemoved }) {
8040
+ const cardRef = useFocusWhen(isJustRemoved);
8041
+ return /* @__PURE__ */ jsxs("li", {
8042
+ className: "dt-card",
8043
+ ref: cardRef,
8044
+ tabIndex: isJustRemoved ? -1 : void 0,
8045
+ children: [/* @__PURE__ */ jsx("p", {
8046
+ className: "dt-meta",
8047
+ children: caption
8048
+ }), /* @__PURE__ */ jsxs("p", {
8049
+ className: "dt-meta",
8050
+ children: ["Screenshot missing: ", evidence.missingReason ?? "not captured."]
5120
8051
  })]
5121
8052
  });
5122
8053
  }
5123
- function RemoveEvidence({ evidence, review }) {
5124
- const [isConfirming, setIsConfirming] = useState(false);
5125
- if (isConfirming) return /* @__PURE__ */ jsx(RemoveConfirmation, {
5126
- evidence,
5127
- onCancel: () => setIsConfirming(false),
5128
- review
5129
- });
5130
- return /* @__PURE__ */ jsx("button", {
5131
- "aria-label": `Remove screenshot at ${formatClock(evidence.atMs)}`,
5132
- className: "dt-ses-link",
5133
- onClick: () => setIsConfirming(true),
5134
- type: "button",
5135
- children: "Remove"
5136
- });
5137
- }
5138
- //#endregion
5139
- //#region src/sessions/ui/review/transcript/evidence-tab.tsx
5140
- const isViewable = (evidence) => evidence.status === "available" && Boolean(evidence.image?.url);
5141
8054
  function LinkEvidenceItem({ link }) {
5142
8055
  return /* @__PURE__ */ jsxs("li", {
5143
8056
  className: "dt-card",
@@ -5160,18 +8073,23 @@ function LinkEvidenceItem({ link }) {
5160
8073
  ]
5161
8074
  });
5162
8075
  }
5163
- function EvidenceItem({ evidence, onEnlarge, removal }) {
8076
+ function MomentLink({ atMs, onShowMoment }) {
8077
+ if (!onShowMoment) return null;
8078
+ return /* @__PURE__ */ jsx("button", {
8079
+ "aria-label": `Show the moment at ${formatClock(atMs)}`,
8080
+ className: "dt-ses-link",
8081
+ onClick: (event) => onShowMoment(event.currentTarget),
8082
+ type: "button",
8083
+ children: "Show moment"
8084
+ });
8085
+ }
8086
+ function EvidenceItem({ evidence, openers, removal }) {
5164
8087
  const caption = `${formatClock(evidence.atMs)} · ${evidenceCaption(evidence)}`;
5165
8088
  if (evidence.link) return /* @__PURE__ */ jsx(LinkEvidenceItem, { link: evidence.link });
5166
- if (!isViewable(evidence)) return /* @__PURE__ */ jsxs("li", {
5167
- className: "dt-card",
5168
- children: [/* @__PURE__ */ jsx("p", {
5169
- className: "dt-meta",
5170
- children: caption
5171
- }), /* @__PURE__ */ jsxs("p", {
5172
- className: "dt-meta",
5173
- children: ["Screenshot missing: ", evidence.missingReason ?? "not captured."]
5174
- })]
8089
+ if (!isViewable(evidence)) return /* @__PURE__ */ jsx(MissingEvidenceItem, {
8090
+ caption,
8091
+ evidence,
8092
+ isJustRemoved: evidence.id === removal.removedId
5175
8093
  });
5176
8094
  return /* @__PURE__ */ jsxs("li", {
5177
8095
  className: "dt-card",
@@ -5179,7 +8097,7 @@ function EvidenceItem({ evidence, onEnlarge, removal }) {
5179
8097
  /* @__PURE__ */ jsx("button", {
5180
8098
  "aria-label": `Enlarge: ${caption}`,
5181
8099
  className: "dt-ses-shot-btn",
5182
- onClick: onEnlarge,
8100
+ onClick: openers.onEnlarge,
5183
8101
  type: "button",
5184
8102
  children: /* @__PURE__ */ jsx(EvidenceThumb, { evidence })
5185
8103
  }),
@@ -5191,9 +8109,13 @@ function EvidenceItem({ evidence, onEnlarge, removal }) {
5191
8109
  className: "dt-note-text",
5192
8110
  children: evidence.note
5193
8111
  }),
8112
+ /* @__PURE__ */ jsx(MomentLink, {
8113
+ atMs: evidence.atMs,
8114
+ onShowMoment: openers.onShowMoment
8115
+ }),
5194
8116
  removal.canRemove && /* @__PURE__ */ jsx(RemoveEvidence, {
5195
8117
  evidence,
5196
- review: removal.review
8118
+ removal
5197
8119
  })
5198
8120
  ]
5199
8121
  });
@@ -5220,11 +8142,17 @@ function EnlargedEvidence({ evidence, onBack, removal }) {
5220
8142
  }),
5221
8143
  removal.canRemove && /* @__PURE__ */ jsx(RemoveEvidence, {
5222
8144
  evidence,
5223
- review: removal.review
8145
+ removal
5224
8146
  })
5225
8147
  ] });
5226
8148
  }
5227
- function EvidenceList({ evidence, onEnlarge, removal }) {
8149
+ function EvidenceList({ detail, openers, removal }) {
8150
+ const { onEnlarge, onShowMoment } = openers;
8151
+ const { evidence } = detail;
8152
+ const momentOpener = (atMs) => {
8153
+ const segmentId = nearestSegmentId(detail.segments, atMs);
8154
+ return segmentId ? (opener) => onShowMoment(segmentId, opener) : null;
8155
+ };
5228
8156
  return /* @__PURE__ */ jsxs(Fragment, { children: [evidence.length === 0 && /* @__PURE__ */ jsx("p", {
5229
8157
  className: "dt-empty",
5230
8158
  children: "No screenshots or links in this session."
@@ -5232,352 +8160,86 @@ function EvidenceList({ evidence, onEnlarge, removal }) {
5232
8160
  className: "dt-ses-rows",
5233
8161
  children: evidence.map((item) => /* @__PURE__ */ jsx(EvidenceItem, {
5234
8162
  evidence: item,
5235
- onEnlarge: () => onEnlarge(item.id),
8163
+ openers: {
8164
+ onEnlarge: () => onEnlarge(item.id),
8165
+ onShowMoment: momentOpener(item.atMs)
8166
+ },
5236
8167
  removal
5237
8168
  }, item.id))
5238
- })] });
5239
- }
5240
- function EvidenceTab({ detail, review }) {
5241
- const [enlargedId, setEnlargedId] = useState(null);
5242
- const [isAttaching, setIsAttaching] = useState(false);
5243
- const enlarged = detail.evidence.find((item) => item.id === enlargedId && isViewable(item));
5244
- const removal = {
5245
- canRemove: detail.session.canEdit,
5246
- review
5247
- };
5248
- if (enlarged) return /* @__PURE__ */ jsx(EnlargedEvidence, {
5249
- evidence: enlarged,
5250
- onBack: () => setEnlargedId(null),
5251
- removal
5252
- });
5253
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(EvidenceList, {
5254
- evidence: detail.evidence,
5255
- onEnlarge: setEnlargedId,
5256
- removal
5257
- }), detail.session.canEdit && (isAttaching ? /* @__PURE__ */ jsx(AttachLink, {
5258
- detail,
5259
- onDone: () => setIsAttaching(false),
5260
- review
5261
- }) : /* @__PURE__ */ jsx("div", {
5262
- className: "dt-actions",
5263
- children: /* @__PURE__ */ jsx("button", {
5264
- className: "dt-btn",
5265
- onClick: () => setIsAttaching(true),
5266
- type: "button",
5267
- children: "Attach a link"
5268
- })
5269
- }))] });
5270
- }
5271
- //#endregion
5272
- //#region src/sessions/ui/review/transcript/segment-row.tsx
5273
- function SaveCancelActions({ isSaveDisabled, onCancel, onSave }) {
5274
- return /* @__PURE__ */ jsxs("div", {
5275
- className: "dt-actions",
5276
- children: [/* @__PURE__ */ jsx("button", {
5277
- className: "dt-btn",
5278
- "data-variant": "primary",
5279
- disabled: isSaveDisabled,
5280
- onClick: onSave,
5281
- type: "button",
5282
- children: "Save"
5283
- }), /* @__PURE__ */ jsx("button", {
5284
- className: "dt-btn",
5285
- onClick: onCancel,
5286
- type: "button",
5287
- children: "Cancel"
5288
- })]
5289
- });
5290
- }
5291
- function SegmentEditor({ onDone, review, segment }) {
5292
- const { api } = useSessionWorkspace();
5293
- const { isSaving, problem, save } = useGuardedSave();
5294
- const [text, setText] = useState(segment.text);
5295
- const submit = () => save(async () => {
5296
- const saved = await api.correctSegment({
5297
- expectedRevision: segment.revision,
5298
- segmentId: segment.id,
5299
- text: text.trim()
5300
- });
5301
- review.patch((detail) => withSegment(detail, saved));
5302
- review.reload();
5303
- onDone();
5304
- });
5305
- return /* @__PURE__ */ jsxs("div", {
5306
- className: "dt-ses-form",
5307
- children: [
5308
- /* @__PURE__ */ jsx("textarea", {
5309
- "aria-label": `Passage at ${formatClock(segment.startMs)}`,
5310
- className: "dt-field",
5311
- maxLength: 5e3,
5312
- onChange: (event) => setText(event.target.value),
5313
- rows: 3,
5314
- value: text
5315
- }),
5316
- problem && /* @__PURE__ */ jsx(SaveProblemMessage, {
5317
- onReload: review.reload,
5318
- problem
5319
- }),
5320
- /* @__PURE__ */ jsx(SaveCancelActions, {
5321
- isSaveDisabled: isSaving || text.trim() === "",
5322
- onCancel: onDone,
5323
- onSave: submit
5324
- })
5325
- ]
5326
- });
5327
- }
5328
- function SegmentBody({ onType, review, segment }) {
5329
- const { api } = useSessionWorkspace();
5330
- const run = useSessionAction();
5331
- const { state } = segment.transcription;
5332
- const retry = async () => {
5333
- const pending = await run("The transcription did not restart", () => api.retrySegment(segment.id));
5334
- if (pending) review.patch((detail) => withSegment(detail, pending));
5335
- };
5336
- if (state === "pending" || state === "running") return /* @__PURE__ */ jsx("p", {
5337
- className: "dt-status",
5338
- children: "Transcribing…"
5339
- });
5340
- if (state === "failed" || state === "expired") return /* @__PURE__ */ jsxs("div", {
5341
- className: "dt-actions",
5342
- children: [
5343
- /* @__PURE__ */ jsx("p", {
5344
- className: "dt-error",
5345
- children: state === "failed" ? "Couldn't transcribe" : "Audio expired — type what you said"
5346
- }),
5347
- onType && state === "failed" && segment.hasAudio && /* @__PURE__ */ jsx("button", {
5348
- className: "dt-btn",
5349
- onClick: retry,
5350
- type: "button",
5351
- children: "Retry"
5352
- }),
5353
- onType && /* @__PURE__ */ jsx("button", {
5354
- className: "dt-btn",
5355
- onClick: onType,
5356
- type: "button",
5357
- children: "Type it"
5358
- })
5359
- ]
5360
- });
5361
- if (state === "empty" && segment.text === "") return /* @__PURE__ */ jsx("p", {
5362
- className: "dt-meta",
5363
- children: "No speech was heard here."
5364
- });
5365
- return /* @__PURE__ */ jsx("p", {
5366
- className: "dt-note-text",
5367
- children: segment.text
5368
- });
5369
- }
5370
- function ContextChips({ segment }) {
5371
- if (segment.context.length === 0) return null;
5372
- return /* @__PURE__ */ jsxs("div", {
5373
- className: "dt-ses-chips",
5374
- children: [segment.contextAmbiguous && /* @__PURE__ */ jsx("span", {
5375
- className: "dt-chip",
5376
- children: "Unclear which element"
5377
- }), segment.context.map((item) => /* @__PURE__ */ jsx("span", {
5378
- className: "dt-chip",
5379
- children: item.label
5380
- }, `${item.kind}:${item.atMs}:${item.label}`))]
5381
- });
5382
- }
5383
- function EditPassageLink({ onEdit, time }) {
5384
- return /* @__PURE__ */ jsx("button", {
5385
- "aria-label": `Edit passage at ${time}`,
5386
- className: "dt-ses-link",
5387
- onClick: onEdit,
5388
- type: "button",
5389
- children: "Edit"
5390
- });
5391
- }
5392
- function SegmentRow({ canEdit, isFocused, review, segment }) {
5393
- const [isEditing, setIsEditing] = useState(false);
5394
- const time = formatClock(segment.startMs);
5395
- const isSettled = ["done", "empty"].includes(segment.transcription.state);
5396
- return /* @__PURE__ */ jsxs("li", {
5397
- className: "dt-ses-seg",
5398
- ref: isFocused ? (node) => node?.focus({ preventScroll: false }) : void 0,
5399
- tabIndex: -1,
5400
- children: [/* @__PURE__ */ jsx("span", {
5401
- className: "dt-ses-time",
5402
- children: time
5403
- }), /* @__PURE__ */ jsxs("div", {
5404
- className: "dt-ses-seg-body",
5405
- children: [
5406
- isEditing ? /* @__PURE__ */ jsx(SegmentEditor, {
5407
- onDone: () => setIsEditing(false),
5408
- review,
5409
- segment
5410
- }) : /* @__PURE__ */ jsx(SegmentBody, {
5411
- onType: canEdit ? () => setIsEditing(true) : void 0,
5412
- review,
5413
- segment
5414
- }),
5415
- /* @__PURE__ */ jsx(ContextChips, { segment }),
5416
- canEdit && isSettled && !isEditing && /* @__PURE__ */ jsx(EditPassageLink, {
5417
- onEdit: () => setIsEditing(true),
5418
- time
5419
- })
5420
- ]
5421
- })]
5422
- });
5423
- }
5424
- //#endregion
5425
- //#region src/sessions/ui/review/transcript/transcript-tab.tsx
5426
- const GAP_LABELS = {
5427
- hidden: "Tab hidden",
5428
- interrupted: "Interrupted",
5429
- limit: "Time limit reached",
5430
- paused: "Paused"
5431
- };
5432
- function gapLabel(gap) {
5433
- const label = GAP_LABELS[gap.reason];
5434
- return gap.endMs === null ? label : `${label} · ${shortDuration(gap.endMs - gap.startMs)}`;
5435
- }
5436
- function markLabel(mark) {
5437
- const note = mark.note ? ` — ${mark.note}` : "";
5438
- if (mark.kind === "navigation") return `Went to ${mark.url ?? mark.label}`;
5439
- return `${mark.kind === "select" ? "Pointed at" : "Marked a moment on"} ${mark.label}${note}`;
5440
- }
5441
- function TimelineNote({ atMs, children }) {
5442
- return /* @__PURE__ */ jsxs("li", {
5443
- className: "dt-ses-seg",
5444
- "data-kind": "note",
5445
- children: [/* @__PURE__ */ jsx("span", {
5446
- className: "dt-ses-time",
5447
- children: formatClock(atMs)
5448
- }), /* @__PURE__ */ jsx("p", {
5449
- className: "dt-meta",
5450
- children
5451
- })]
5452
- });
5453
- }
5454
- function buildTimeline(detail) {
5455
- return [
5456
- ...detail.segments.map((segment) => ({
5457
- atMs: segment.startMs,
5458
- key: `s:${segment.id}`,
5459
- segment
5460
- })),
5461
- ...detail.session.gaps.map((gap) => ({
5462
- atMs: gap.startMs,
5463
- key: `g:${gap.startMs}`,
5464
- label: gapLabel(gap)
5465
- })),
5466
- ...detail.marks.map((mark) => ({
5467
- atMs: mark.atMs,
5468
- key: `m:${mark.kind}:${mark.atMs}`,
5469
- label: markLabel(mark)
5470
- }))
5471
- ].sort((earlier, later) => earlier.atMs - later.atMs);
5472
- }
5473
- function useTypedPassage({ detail, review }) {
5474
- const { api } = useSessionWorkspace();
5475
- const run = useSessionAction();
5476
- const [text, setText] = useState("");
5477
- const [clientKey, setClientKey] = useState(() => crypto.randomUUID());
5478
- const atMs = detail.segments.at(-1)?.endMs ?? 0;
5479
- const add = async () => {
5480
- const segment = await run("The passage was not added", () => api.addTypedSegment({
5481
- clientKey,
5482
- endMs: atMs,
5483
- sessionId: detail.session.id,
5484
- startMs: atMs,
5485
- text: text.trim()
5486
- }));
5487
- if (segment) {
5488
- review.patch((current) => withSegment(current, segment));
5489
- setText("");
5490
- setClientKey(crypto.randomUUID());
5491
- }
5492
- };
5493
- return {
5494
- add,
5495
- setText,
5496
- text
5497
- };
8169
+ })] });
5498
8170
  }
5499
- function AddTypedPassage({ detail, review }) {
5500
- const { add, setText, text } = useTypedPassage({
8171
+ function AttachLinkAction({ detail, review }) {
8172
+ const [isAttaching, setIsAttaching] = useState(false);
8173
+ if (isAttaching) return /* @__PURE__ */ jsx(AttachLink, {
5501
8174
  detail,
8175
+ onDone: () => setIsAttaching(false),
5502
8176
  review
5503
8177
  });
5504
- return /* @__PURE__ */ jsxs("div", {
5505
- className: "dt-ses-form",
5506
- children: [/* @__PURE__ */ jsx("textarea", {
5507
- "aria-label": "Add a typed passage",
5508
- className: "dt-field",
5509
- maxLength: 5e3,
5510
- onChange: (event) => setText(event.target.value),
5511
- placeholder: "Add a typed passage",
5512
- rows: 2,
5513
- value: text
5514
- }), /* @__PURE__ */ jsx("div", {
5515
- className: "dt-actions",
5516
- children: /* @__PURE__ */ jsx("button", {
5517
- className: "dt-btn",
5518
- disabled: text.trim() === "",
5519
- onClick: add,
5520
- type: "button",
5521
- children: "Add passage"
5522
- })
5523
- })]
8178
+ return /* @__PURE__ */ jsx("div", {
8179
+ className: "dt-actions",
8180
+ children: /* @__PURE__ */ jsx("button", {
8181
+ className: "dt-btn",
8182
+ onClick: () => setIsAttaching(true),
8183
+ type: "button",
8184
+ children: "Attach a link"
8185
+ })
5524
8186
  });
5525
8187
  }
5526
- function TranscriptTab({ detail, focusSegmentId, review }) {
5527
- const { canEdit } = detail.session;
5528
- const timeline = buildTimeline(detail);
5529
- return /* @__PURE__ */ jsxs(Fragment, { children: [
5530
- /* @__PURE__ */ jsx("p", {
5531
- className: "dt-hint",
5532
- children: "Audio is deleted once transcribed. You can still correct the text."
5533
- }),
5534
- timeline.length === 0 && /* @__PURE__ */ jsx("p", {
5535
- className: "dt-empty",
5536
- children: "Nothing was said in this session."
5537
- }),
5538
- /* @__PURE__ */ jsx("ol", {
5539
- className: "dt-ses-timeline",
5540
- children: timeline.map((item) => "segment" in item ? /* @__PURE__ */ jsx(SegmentRow, {
5541
- canEdit,
5542
- isFocused: item.segment.id === focusSegmentId,
5543
- review,
5544
- segment: item.segment
5545
- }, item.key) : /* @__PURE__ */ jsx(TimelineNote, {
5546
- atMs: item.atMs,
5547
- children: item.label
5548
- }, item.key))
5549
- }),
5550
- canEdit && /* @__PURE__ */ jsx(AddTypedPassage, {
5551
- detail,
5552
- review
5553
- })
5554
- ] });
8188
+ function EvidenceTab({ detail, onShowMoment, review }) {
8189
+ const [enlargedId, setEnlargedId] = useState(null);
8190
+ const [removedId, setRemovedId] = useState(null);
8191
+ const enlarged = detail.evidence.find((item) => item.id === enlargedId && isViewable(item));
8192
+ const removal = {
8193
+ canRemove: detail.session.canEdit,
8194
+ onRemoved: (evidenceId) => {
8195
+ setEnlargedId(null);
8196
+ setRemovedId(evidenceId);
8197
+ },
8198
+ removedId,
8199
+ review
8200
+ };
8201
+ if (enlarged) return /* @__PURE__ */ jsx(EnlargedEvidence, {
8202
+ evidence: enlarged,
8203
+ onBack: () => setEnlargedId(null),
8204
+ removal
8205
+ });
8206
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(EvidenceList, {
8207
+ detail,
8208
+ openers: {
8209
+ onEnlarge: setEnlargedId,
8210
+ onShowMoment
8211
+ },
8212
+ removal
8213
+ }), detail.session.canEdit && /* @__PURE__ */ jsx(AttachLinkAction, {
8214
+ detail,
8215
+ review
8216
+ })] });
5555
8217
  }
5556
8218
  //#endregion
5557
8219
  //#region src/sessions/ui/review/session-review.tsx
5558
8220
  function HandoffFooter({ detail, onHandOff }) {
5559
- const kept = detail.proposals.filter((proposal) => proposal.decision === "kept" && proposal.mergedIntoId === null).length;
8221
+ const confirmed = detail.proposals.filter((proposal) => proposal.decision === "kept" && proposal.mergedIntoId === null).length;
5560
8222
  return /* @__PURE__ */ jsxs("div", {
5561
8223
  className: "dt-sheet-foot",
5562
8224
  children: [/* @__PURE__ */ jsx("button", {
5563
- "aria-describedby": kept === 0 ? "rf-handoff-hint" : void 0,
8225
+ "aria-describedby": confirmed === 0 ? "rf-handoff-hint" : void 0,
5564
8226
  className: "dt-btn",
5565
8227
  "data-variant": "primary",
5566
- disabled: kept === 0,
8228
+ disabled: confirmed === 0,
5567
8229
  onClick: onHandOff,
5568
8230
  type: "button",
5569
- children: kept === 0 ? "Hand off" : `Hand off ${kept} kept`
5570
- }), kept === 0 && /* @__PURE__ */ jsx("span", {
8231
+ children: confirmed === 0 ? "Hand off" : `Hand off ${confirmed} confirmed`
8232
+ }), confirmed === 0 && /* @__PURE__ */ jsx("span", {
5571
8233
  className: "dt-status",
5572
8234
  id: "rf-handoff-hint",
5573
- children: "Keep a proposal to hand it off"
8235
+ children: "Confirm an intention to hand it off"
5574
8236
  })]
5575
8237
  });
5576
8238
  }
5577
8239
  function ReviewTabs({ detail, onSelect, tab }) {
5578
8240
  const options = [
5579
8241
  {
5580
- label: `Proposals (${detail.proposals.filter((proposal) => proposal.mergedIntoId === null).length})`,
8242
+ label: `Intentions (${detail.proposals.filter((proposal) => proposal.mergedIntoId === null && proposal.attachment === null).length})`,
5581
8243
  value: "proposals"
5582
8244
  },
5583
8245
  {
@@ -5600,43 +8262,114 @@ function ReviewTabs({ detail, onSelect, tab }) {
5600
8262
  }, option.value))
5601
8263
  });
5602
8264
  }
5603
- function ReviewTabPanel({ context, focusSegmentId, tab }) {
5604
- const { detail, review } = context;
8265
+ function ReviewTabPanel({ context, tab }) {
8266
+ const { detail, navigation, review } = context;
5605
8267
  if (tab === "proposals") return /* @__PURE__ */ jsx(ProposalsTab, { context });
5606
8268
  if (tab === "transcript") return /* @__PURE__ */ jsx(TranscriptTab, {
5607
8269
  detail,
5608
- focusSegmentId,
8270
+ onShowMoment: navigation.onShowMoment,
5609
8271
  review
5610
8272
  });
5611
8273
  return /* @__PURE__ */ jsx(EvidenceTab, {
5612
8274
  detail,
8275
+ onShowMoment: navigation.onShowMoment,
5613
8276
  review
5614
8277
  });
5615
8278
  }
5616
- function useReviewTab() {
8279
+ function useReviewPlace() {
5617
8280
  const [tab, setTab] = useState("proposals");
5618
- const [focusSegmentId, setFocusSegmentId] = useState(null);
5619
- const showSegment = (segmentId) => {
5620
- setFocusSegmentId(segmentId);
5621
- setTab("transcript");
8281
+ const [focusIntentionId, setFocusIntentionId] = useState(null);
8282
+ const [moment, setMoment] = useState(null);
8283
+ const closeMoment = (shouldReturnFocus) => {
8284
+ const opener = moment?.opener;
8285
+ flushSync(() => setMoment(null));
8286
+ if (shouldReturnFocus && opener?.isConnected) opener.focus();
8287
+ };
8288
+ const showMoment = (segmentId, opener) => {
8289
+ setFocusIntentionId(null);
8290
+ setMoment((current) => ({
8291
+ opener: opener ?? current?.opener ?? null,
8292
+ segmentId
8293
+ }));
5622
8294
  };
5623
8295
  return {
5624
- focusSegmentId,
5625
- setTab,
5626
- showSegment,
8296
+ closeMoment,
8297
+ focusIntentionId,
8298
+ moment,
8299
+ openIntention: (proposalId) => {
8300
+ closeMoment(false);
8301
+ setTab("proposals");
8302
+ setFocusIntentionId(proposalId);
8303
+ },
8304
+ selectTab: (next) => {
8305
+ closeMoment(false);
8306
+ setTab(next);
8307
+ },
8308
+ showMoment,
5627
8309
  tab
5628
8310
  };
5629
8311
  }
5630
- function ReviewScreen({ detail, onBack, review }) {
5631
- const { focusSegmentId, setTab, showSegment, tab } = useReviewTab();
8312
+ function ReviewPanels({ context, place }) {
8313
+ const { detail, review } = context;
8314
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
8315
+ /* @__PURE__ */ jsx(ReviewTabs, {
8316
+ detail,
8317
+ onSelect: place.selectTab,
8318
+ tab: place.tab
8319
+ }),
8320
+ /* @__PURE__ */ jsx("div", {
8321
+ className: "dt-ses-panel",
8322
+ hidden: place.moment !== null,
8323
+ children: /* @__PURE__ */ jsx(ReviewTabPanel, {
8324
+ context,
8325
+ tab: place.tab
8326
+ })
8327
+ }),
8328
+ place.moment && /* @__PURE__ */ jsx(MomentView, {
8329
+ handlers: {
8330
+ onClose: () => place.closeMoment(true),
8331
+ onOpenIntention: place.openIntention,
8332
+ onShowMoment: (segmentId) => place.showMoment(segmentId)
8333
+ },
8334
+ segmentId: place.moment.segmentId,
8335
+ session: {
8336
+ detail,
8337
+ review
8338
+ }
8339
+ })
8340
+ ] });
8341
+ }
8342
+ function ReviewTop({ context, links }) {
8343
+ const { detail, review } = context;
8344
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
8345
+ /* @__PURE__ */ jsx(ReviewHeader, {
8346
+ onBack: links.onBack,
8347
+ review,
8348
+ session: detail.session
8349
+ }),
8350
+ /* @__PURE__ */ jsx(StatusStrip, {
8351
+ detail,
8352
+ review
8353
+ }),
8354
+ detail.continuation && /* @__PURE__ */ jsx(ContinuationHeader, {
8355
+ continuation: detail.continuation,
8356
+ onOpenSession: links.onOpenSession
8357
+ })
8358
+ ] });
8359
+ }
8360
+ function ReviewScreen({ detail, links, review }) {
8361
+ const place = useReviewPlace();
5632
8362
  const [isHandingOff, setIsHandingOff] = useState(false);
5633
- const actions = useProposalActions(review);
5634
- const { canEdit } = detail.session;
5635
8363
  const context = {
5636
- actions,
5637
- canEdit,
8364
+ actions: useProposalActions(review),
8365
+ canEdit: detail.session.canEdit,
5638
8366
  detail,
5639
- onShowSegment: showSegment,
8367
+ focusIntentionId: place.focusIntentionId,
8368
+ navigation: {
8369
+ onOpenSession: links.onOpenSession,
8370
+ onShowMoment: place.showMoment,
8371
+ onStarted: links.onStarted
8372
+ },
5640
8373
  review
5641
8374
  };
5642
8375
  if (isHandingOff) return /* @__PURE__ */ jsx(HandoffView, {
@@ -5645,32 +8378,21 @@ function ReviewScreen({ detail, onBack, review }) {
5645
8378
  review
5646
8379
  });
5647
8380
  return /* @__PURE__ */ jsxs(Fragment, { children: [
5648
- /* @__PURE__ */ jsx(ReviewHeader, {
5649
- onBack,
5650
- review,
5651
- session: detail.session
5652
- }),
5653
- /* @__PURE__ */ jsx(StatusStrip, {
5654
- detail,
5655
- review
5656
- }),
5657
- /* @__PURE__ */ jsx(ReviewTabs, {
5658
- detail,
5659
- onSelect: setTab,
5660
- tab
8381
+ /* @__PURE__ */ jsx(ReviewTop, {
8382
+ context,
8383
+ links
5661
8384
  }),
5662
- /* @__PURE__ */ jsx(ReviewTabPanel, {
8385
+ /* @__PURE__ */ jsx(ReviewPanels, {
5663
8386
  context,
5664
- focusSegmentId,
5665
- tab
8387
+ place
5666
8388
  }),
5667
- canEdit && /* @__PURE__ */ jsx(HandoffFooter, {
8389
+ context.canEdit && /* @__PURE__ */ jsx(HandoffFooter, {
5668
8390
  detail,
5669
8391
  onHandOff: () => setIsHandingOff(true)
5670
8392
  })
5671
8393
  ] });
5672
8394
  }
5673
- function SessionReview({ onBack, sessionId }) {
8395
+ function SessionReview({ links, sessionId }) {
5674
8396
  const handle = useSessionDetail(sessionId);
5675
8397
  const { state } = handle;
5676
8398
  if (state.kind === "loading") return /* @__PURE__ */ jsx("p", {
@@ -5679,7 +8401,7 @@ function SessionReview({ onBack, sessionId }) {
5679
8401
  });
5680
8402
  if (state.kind === "ready") return /* @__PURE__ */ jsx(ReviewScreen, {
5681
8403
  detail: state.detail,
5682
- onBack,
8404
+ links,
5683
8405
  review: handle
5684
8406
  });
5685
8407
  return /* @__PURE__ */ jsxs("div", {
@@ -5693,7 +8415,7 @@ function SessionReview({ onBack, sessionId }) {
5693
8415
  children: "Try again"
5694
8416
  }), /* @__PURE__ */ jsx("button", {
5695
8417
  className: "dt-btn",
5696
- onClick: onBack,
8418
+ onClick: links.onBack,
5697
8419
  type: "button",
5698
8420
  children: "All sessions"
5699
8421
  })]
@@ -5834,141 +8556,6 @@ function SessionList({ drafts, onOpen }) {
5834
8556
  ] });
5835
8557
  }
5836
8558
  //#endregion
5837
- //#region src/sessions/ui/start/session-disclosure.tsx
5838
- const SEEN_KEY_PREFIX = "reflet-sessions-disclosure:";
5839
- function hasSeenDisclosure(accountKey) {
5840
- try {
5841
- return localStorage.getItem(`${SEEN_KEY_PREFIX}${accountKey}`) !== null;
5842
- } catch {
5843
- return false;
5844
- }
5845
- }
5846
- function markDisclosureSeen(accountKey) {
5847
- try {
5848
- localStorage.setItem(`${SEEN_KEY_PREFIX}${accountKey}`, "1");
5849
- } catch {}
5850
- }
5851
- function DisclosureList() {
5852
- return /* @__PURE__ */ jsxs("section", {
5853
- "aria-labelledby": "rf-session-disclosure",
5854
- className: "dt-hint",
5855
- children: [/* @__PURE__ */ jsx("h3", {
5856
- className: "dt-ses-subhead",
5857
- id: "rf-session-disclosure",
5858
- children: "What a session records"
5859
- }), /* @__PURE__ */ jsxs("ul", {
5860
- className: "dt-ses-list",
5861
- children: [
5862
- /* @__PURE__ */ jsx("li", { children: "Your voice while recording. It is sent to Reflet and transcribed by Whisper through OpenRouter with zero data retention. The audio is deleted as soon as its transcript is saved, within 24 hours at most." }),
5863
- /* @__PURE__ */ jsx("li", { children: "The page: its address without sensitive parameters, its title, the scroll position and the window size." }),
5864
- /* @__PURE__ */ jsx("li", { children: "What you click, point at and mark: element names and positions, never your keystrokes." }),
5865
- /* @__PURE__ */ jsxs("li", { children: [
5866
- "Screenshots of this tab at the start, when you mark a moment and when the page changes. Form fields appear empty; password fields and elements marked ",
5867
- /* @__PURE__ */ jsx("code", { children: "data-reflet-redact" }),
5868
- " are hidden. Other text on screen, rich-text editors included, stays visible."
5869
- ] }),
5870
- /* @__PURE__ */ jsx("li", { children: "Not recorded: keystrokes, other tabs or apps, network calls." }),
5871
- /* @__PURE__ */ jsx("li", { children: "Pausing stops everything." }),
5872
- /* @__PURE__ */ jsx("li", { children: "A session stays private to you until you share it." })
5873
- ]
5874
- })]
5875
- });
5876
- }
5877
- function SessionDisclosure({ accountKey }) {
5878
- const [isOpen, setIsOpen] = useState(() => !hasSeenDisclosure(accountKey));
5879
- if (isOpen) return /* @__PURE__ */ jsx(DisclosureList, {});
5880
- return /* @__PURE__ */ jsx("button", {
5881
- "aria-expanded": false,
5882
- className: "dt-ses-link",
5883
- onClick: () => setIsOpen(true),
5884
- type: "button",
5885
- children: "What a session records"
5886
- });
5887
- }
5888
- //#endregion
5889
- //#region src/sessions/ui/start/start-session.tsx
5890
- const IS_MICROPHONE_FAILURE = {
5891
- auth: false,
5892
- insecure_context: true,
5893
- limit: false,
5894
- mic_blocked_by_site: true,
5895
- mic_denied: true,
5896
- mic_unavailable: true,
5897
- network: false,
5898
- storage: false
5899
- };
5900
- function StartProblem({ failure, onRetry, onWriteNote }) {
5901
- return /* @__PURE__ */ jsxs("div", {
5902
- className: "dt-ses-problem",
5903
- role: "alert",
5904
- children: [/* @__PURE__ */ jsx("p", {
5905
- className: "dt-error",
5906
- children: failure.message
5907
- }), /* @__PURE__ */ jsxs("div", {
5908
- className: "dt-actions",
5909
- children: [/* @__PURE__ */ jsx("button", {
5910
- className: "dt-btn",
5911
- onClick: onRetry,
5912
- type: "button",
5913
- children: "Try again"
5914
- }), IS_MICROPHONE_FAILURE[failure.kind] && onWriteNote && /* @__PURE__ */ jsx("button", {
5915
- className: "dt-btn",
5916
- onClick: onWriteNote,
5917
- type: "button",
5918
- children: "Write a note instead"
5919
- })]
5920
- })]
5921
- });
5922
- }
5923
- function StartActions({ isBusy, onStart, onWriteNote }) {
5924
- return /* @__PURE__ */ jsxs("div", {
5925
- className: "dt-actions",
5926
- children: [/* @__PURE__ */ jsx("button", {
5927
- className: "dt-btn",
5928
- "data-variant": "primary",
5929
- disabled: isBusy,
5930
- onClick: onStart,
5931
- type: "button",
5932
- children: "Start session"
5933
- }), onWriteNote && /* @__PURE__ */ jsx("button", {
5934
- className: "dt-btn",
5935
- onClick: onWriteNote,
5936
- type: "button",
5937
- children: "Write a note instead"
5938
- })]
5939
- });
5940
- }
5941
- function StartSession({ onStarted, onWriteNote }) {
5942
- const { account, recorder } = useSessionWorkspace();
5943
- const snapshot = useRecorderSnapshot(recorder);
5944
- const [hasTried, setHasTried] = useState(false);
5945
- const start = async () => {
5946
- markDisclosureSeen(account.key);
5947
- setHasTried(true);
5948
- await recorder.start({});
5949
- if (recorder.getSnapshot().phase === "recording") onStarted();
5950
- };
5951
- const isRequesting = snapshot.phase === "requesting";
5952
- const startFailure = hasTried && snapshot.phase === "failed" && snapshot.sessionKey === null ? snapshot.failure : null;
5953
- return /* @__PURE__ */ jsxs(Fragment, { children: [
5954
- /* @__PURE__ */ jsx(SessionDisclosure, { accountKey: account.key }),
5955
- /* @__PURE__ */ jsx("p", {
5956
- "aria-live": "polite",
5957
- className: "dt-meta",
5958
- children: isRequesting ? "Allow the microphone in your browser to start." : ""
5959
- }),
5960
- startFailure ? /* @__PURE__ */ jsx(StartProblem, {
5961
- failure: startFailure,
5962
- onRetry: start,
5963
- onWriteNote
5964
- }) : /* @__PURE__ */ jsx(StartActions, {
5965
- isBusy: isRequesting || snapshot.phase === "finishing",
5966
- onStart: start,
5967
- onWriteNote
5968
- })
5969
- ] });
5970
- }
5971
- //#endregion
5972
8559
  //#region src/sessions/ui/start/sessions-home.tsx
5973
8560
  function useDraftRecovery({ draft, onOpenSession, onStarted }) {
5974
8561
  const { account, api, recorder } = useSessionWorkspace();
@@ -6060,7 +8647,14 @@ function SessionsPanel({ navigation, onStarted, onWriteNote }) {
6060
8647
  const sessionId = useResolvedSessionId(navigation.open);
6061
8648
  const back = () => navigation.onOpen(null);
6062
8649
  if (sessionId) return /* @__PURE__ */ jsx(SessionReview, {
6063
- onBack: back,
8650
+ links: {
8651
+ onBack: back,
8652
+ onOpenSession: (openedId) => navigation.onOpen({
8653
+ sessionId: openedId,
8654
+ sessionKey: null
8655
+ }),
8656
+ onStarted
8657
+ },
6064
8658
  sessionId
6065
8659
  }, sessionId);
6066
8660
  if (navigation.open) return /* @__PURE__ */ jsxs("div", {
@@ -6080,6 +8674,6 @@ function SessionsPanel({ navigation, onStarted, onWriteNote }) {
6080
8674
  });
6081
8675
  }
6082
8676
  //#endregion
6083
- export { announce as A, searchCode as C, openDatabase as D, describeStorageFailure as E, 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, reportFailure as j, Notices 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 };
8677
+ 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 };
6084
8678
 
6085
- //# sourceMappingURL=sessions-panel-Dq5j1qcu.js.map
8679
+ //# sourceMappingURL=sessions-panel-DPhFYhbk.js.map