@hachej/boring-feedback 0.2.4 → 0.3.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.
@@ -23,7 +23,7 @@ const FIELDS = {
23
23
  resolve: { required: ['id', 'expectedRevision', 'note'], optional: [] },
24
24
  };
25
25
  const ALL_FIELDS = ['status', 'subject', 'cursor', 'id', 'anchor', 'note', 'expectedRevision'];
26
- const reply = (value) => ({ content: [{ type: 'text', text: JSON.stringify(value) }] });
26
+ const reply = (value) => ({ output: [{ type: 'text', text: JSON.stringify(value) }] });
27
27
  const isObject = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
28
28
  function age(created, now) {
29
29
  const minutes = Math.max(0, Math.floor((now - Date.parse(created)) / 60000));
@@ -135,6 +135,8 @@ export type FeedbackAttachment = {
135
135
  export interface FeedbackSession extends ExternalState<FeedbackSessionSnapshot> {
136
136
  /** Enters feedback mode; `{ mode: 'point' }` for point mode on phones (a tap picks at once). */
137
137
  readonly start: (options?: SessionStartOptions) => void;
138
+ /** From the review, back to picking with the notes kept (more notes before Send); otherwise like `start`. */
139
+ readonly resume: (options?: SessionStartOptions) => void;
138
140
  /** Done: closes feedback mode, transcribes and aligns voice, and leaves the draft for review. */
139
141
  readonly done: () => Promise<void>;
140
142
  /** ✕: discards everything (feedback mode or the attached draft). */
@@ -393,15 +393,8 @@ export function createFeedbackSession(options) {
393
393
  operation = operationId();
394
394
  mayHaveSent = false;
395
395
  };
396
- const start = (startOptions = {}) => {
397
- const current = state.getSnapshot();
398
- const mode = startOptions.mode === 'point' ? 'point' : 'feedback';
399
- if (current.phase === 'active' || current.phase === 'processing')
400
- return;
401
- stopPicking();
402
- reset();
403
- lastRoute = routeNow();
404
- record({ kind: 'route', route: lastRoute });
396
+ /** Starts listening and picking for an active session (shared by start and resume). */
397
+ const arm = (mode) => {
405
398
  // Keys belong to feedback mode from now on: focus left on the Feedback button (an ignored subtree) would keep them from it.
406
399
  const focused = document.activeElement;
407
400
  if (focused && focused !== document.body && isExcluded(focused) && typeof focused.blur === 'function')
@@ -422,9 +415,40 @@ export function createFeedbackSession(options) {
422
415
  onHover(next.current); },
423
416
  });
424
417
  void picker.start();
418
+ };
419
+ const start = (startOptions = {}) => {
420
+ const current = state.getSnapshot();
421
+ const mode = startOptions.mode === 'point' ? 'point' : 'feedback';
422
+ if (current.phase === 'active' || current.phase === 'processing')
423
+ return;
424
+ stopPicking();
425
+ reset();
426
+ lastRoute = routeNow();
427
+ record({ kind: 'route', route: lastRoute });
428
+ arm(mode);
425
429
  state.set({ phase: 'active', mode, using: false, useToggle: false, general: '', generalOpen: false, voice: { kind: 'off' }, transcribing: false, send: { kind: 'idle' }, copy: { kind: 'idle' }, route: lastRoute });
426
430
  publish();
427
431
  };
432
+ /**
433
+ * Back to picking from the review, keeping the notes, anchors and pins: the unsent draft continues. A draft already saved gets a fresh
434
+ * operation id (it is a different report now); one that may have reached the store keeps its id until the draft changes (`edited`).
435
+ * Anything but a review starts a new session.
436
+ */
437
+ const resume = (resumeOptions = {}) => {
438
+ if (state.getSnapshot().phase !== 'review')
439
+ return start(resumeOptions);
440
+ const mode = resumeOptions.mode === 'point' ? 'point' : 'feedback';
441
+ stopPicking();
442
+ if (saved) {
443
+ saved = undefined;
444
+ operation = operationId();
445
+ mayHaveSent = false;
446
+ }
447
+ observed = undefined;
448
+ arm(mode);
449
+ state.set({ phase: 'active', mode, using: false, useToggle: false, generalOpen: false, voice: { kind: 'off' }, transcribing: false, send: state.getSnapshot().send.kind === 'saved' ? { kind: 'idle' } : state.getSnapshot().send, copy: { kind: 'idle' } });
450
+ publish();
451
+ };
428
452
  // --- voice --------------------------------------------------------------------------------------------------------------------
429
453
  // Stop shows "stopping" at once, but the capture hands the audio over later (the recorder's last chunk, decoding its duration): Done
430
454
  // pressed meanwhile must wait for it, or the recording would be silently lost. One stop at a time; a discard in between drops it.
@@ -704,6 +728,7 @@ export function createFeedbackSession(options) {
704
728
  getSnapshot: state.getSnapshot,
705
729
  subscribe: state.subscribe,
706
730
  start,
731
+ resume,
707
732
  done,
708
733
  discard,
709
734
  setBubbleText: text => { if (bubble) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hachej/boring-feedback",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
4
4
  "license": "MIT",
5
5
  "private": false,
6
6
  "type": "module",
@@ -52,12 +52,12 @@
52
52
  }
53
53
  },
54
54
  "peerDependencies": {
55
- "@hachej/boring-ui-kit": "^0.2.4",
56
- "@hachej/boring-files": "^0.2.4",
57
- "@hachej/boring-agent": "^0.2.4",
58
- "@earendil-works/pi-durable": "~1.1.0",
59
- "@earendil-works/pi-ai": "~1.1.0",
60
- "@earendil-works/chord": "~1.1.0",
55
+ "@hachej/boring-ui-kit": "^0.3.0",
56
+ "@hachej/boring-files": "^0.3.0",
57
+ "@hachej/boring-agent": "^0.3.0",
58
+ "@earendil-works/pi-durable": "~1.2.0",
59
+ "@earendil-works/pi-ai": "~1.2.0",
60
+ "@earendil-works/chord": "~1.2.0",
61
61
  "react": "^19.0.0",
62
62
  "esbuild": "^0.28.2"
63
63
  },