@burdenoff/fe-libs 2026.1028.3 → 2026.1028.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/shared/assistant/appControls/grammar.d.ts +2 -0
  2. package/dist/shared/assistant/appControls/grammar.js +101 -93
  3. package/dist/shared/assistant/appControls/registry.d.ts +2 -0
  4. package/dist/shared/assistant/appControls/registry.js +20 -12
  5. package/dist/shared/assistant/commandCompletions.d.ts +9 -0
  6. package/dist/shared/assistant/commandCompletions.js +161 -0
  7. package/dist/shared/assistant/index.d.ts +3 -0
  8. package/dist/shared/assistant/localUiCommands.d.ts +16 -0
  9. package/dist/shared/assistant/localUiCommands.js +192 -105
  10. package/dist/shared/assistant/localeCommands/engine.d.ts +6 -0
  11. package/dist/shared/assistant/localeCommands/engine.js +212 -116
  12. package/dist/shared/assistant/turnTimeline.d.ts +18 -6
  13. package/dist/shared/assistant/turnTimeline.js +57 -39
  14. package/dist/shared/assistant/turnTimingReport.d.ts +10 -1
  15. package/dist/shared/assistant/turnTimingReport.js +19 -6
  16. package/dist/shared/assistant/ui/AssistantChatArea.js +1491 -1406
  17. package/dist/shared/assistant/ui/AssistantSpeechProvider.js +239 -237
  18. package/dist/shared/assistant/ui/composer/AssistantComposer.d.ts +8 -0
  19. package/dist/shared/assistant/ui/composer/AssistantComposer.js +142 -131
  20. package/dist/shared/assistant/ui/draftCommandCheck.d.ts +7 -0
  21. package/dist/shared/assistant/ui/draftCommandCheck.js +17 -0
  22. package/dist/shared/assistant/ui/useVoiceSession.js +218 -202
  23. package/dist/shared/assistant/uiActions/backStack.d.ts +1 -0
  24. package/dist/shared/assistant/uiActions/backStack.js +13 -5
  25. package/dist/shared/assistant/uiActions/deixis.d.ts +2 -0
  26. package/dist/shared/assistant/uiActions/deixis.js +12 -1
  27. package/dist/shared/assistant/uiActions/draftPathname.d.ts +13 -0
  28. package/dist/shared/assistant/uiActions/draftPathname.js +31 -0
  29. package/dist/shared/assistant/uiActions/recordNames.js +33 -30
  30. package/dist/shared/assistant/voice/handsfree/handsFreeController.d.ts +3 -0
  31. package/dist/shared/assistant/voice/handsfree/handsFreeController.js +5 -1
  32. package/dist/shared/assistant/voice/handsfree/useHandsFreeInterrupt.d.ts +1 -0
  33. package/dist/shared/assistant/voice/handsfree/useHandsFreeInterrupt.js +1 -0
  34. package/dist/shared/assistant/voice/useVoiceInput.d.ts +3 -1
  35. package/dist/shared/assistant/voice/useVoiceInput.js +49 -43
  36. package/dist/shared-assistant.js +329 -326
  37. package/package.json +1 -1
@@ -55,6 +55,8 @@ interface Props {
55
55
  * sandbox while the person is still typing (BOFF-7801).
56
56
  */
57
57
  onDraftStart?: () => void;
58
+ /** Additive observer: every text/attachment change, including an empty draft. */
59
+ onDraftChange?: (draft: AssistantComposerDraft) => void;
58
60
  /**
59
61
  * ★ BOFF-9341: something covers the composer (the Avatar tab). Dictation
60
62
  * stops, keeping every word it heard in the draft, and stays stopped: a
@@ -69,6 +71,12 @@ interface Props {
69
71
  */
70
72
  onVoiceStateChange?: (state: AssistantComposerVoiceState) => void;
71
73
  }
74
+ /** A local draft snapshot; no files are uploaded by observing it. */
75
+ export interface AssistantComposerDraft {
76
+ readonly text: string;
77
+ readonly hasAttachments: boolean;
78
+ readonly dictated: boolean;
79
+ }
72
80
  /** What in the composer holds a voice conversation back (`onVoiceStateChange`). */
73
81
  export interface AssistantComposerVoiceState {
74
82
  readonly committing: boolean;
@@ -17,69 +17,80 @@ import { useCallback as s, useEffect as c, useLayoutEffect as le, useMemo as ue,
17
17
  import { ArrowUp as de, Loader2 as fe } from "lucide-react";
18
18
  import { jsx as d, jsxs as f } from "react/jsx-runtime";
19
19
  //#region src/shared/assistant/ui/composer/AssistantComposer.tsx
20
- var pe = 200, p = ({ writeAccessDenied: p = !1, onSend: m, disabled: h = !1, readOnly: me = !1, placeholder: he, mode: ge, availableModes: g, attachmentCharsPerFile: _e, onModeChange: ve, auto: ye, onStartVoiceSession: be, onDraftStart: _, suspendDictation: v = !1, onVoiceStateChange: y }) => {
21
- let b = r(), { locale: x } = e(), S = h || me, C = l(null), [w, T] = u(""), [E, D] = u(!1), [O, xe] = u(!1), k = l(!1), [A, j] = u(null), M = l(0), { attachments: N, isBusy: Se, preparingCount: Ce, sendBlocker: P, rejection: F, addFiles: I, remove: we, retry: Te, clear: L, clearRejection: Ee, canAddMore: De, commit: R, cancelCommit: z } = ce({ perFileChars: _e }), B = w.trim() !== "" || N.length > 0, V = l(!1);
20
+ var pe = 200, p = ({ writeAccessDenied: p = !1, onSend: m, disabled: h = !1, readOnly: me = !1, placeholder: he, mode: ge, availableModes: g, attachmentCharsPerFile: _e, onModeChange: ve, auto: ye, onStartVoiceSession: be, onDraftStart: _, onDraftChange: v, suspendDictation: y = !1, onVoiceStateChange: b }) => {
21
+ let x = r(), { locale: S } = e(), C = h || me, w = l(null), [T, E] = u(""), [xe, D] = u(!1), [O, k] = u(!1), A = l(!1), [j, M] = u(null), N = l(0), { attachments: P, isBusy: Se, preparingCount: Ce, sendBlocker: F, rejection: I, addFiles: L, remove: we, retry: Te, clear: R, clearRejection: Ee, canAddMore: De, commit: z, cancelCommit: B } = ce({ perFileChars: _e }), V = T.trim() !== "" || P.length > 0, H = l(!1), U = l(!1);
22
22
  c(() => {
23
- B && !V.current ? (V.current = !0, _?.()) : B || (V.current = !1);
24
- }, [B, _]);
25
- let H = s((e) => {
26
- T((t) => t ? `${t.replace(/\s+$/, "")} ${e}` : e);
27
- }, []), U = i((e) => e.assistantLanguage), W = n({
28
- onFinalize: H,
29
- language: ue(() => te(U, x), [U, x]).tag
30
- }), G = W.isActive, K = W.interim, q = W.stop;
23
+ V && !H.current ? (H.current = !0, _?.()) : V || (H.current = !1);
24
+ }, [V, _]), c(() => {
25
+ V || (U.current = !1), v?.({
26
+ text: T,
27
+ hasAttachments: P.length > 0,
28
+ dictated: U.current
29
+ });
30
+ }, [
31
+ T,
32
+ P.length,
33
+ V,
34
+ v
35
+ ]);
36
+ let W = s((e) => {
37
+ U.current = !0, E((t) => t ? `${t.replace(/\s+$/, "")} ${e}` : e);
38
+ }, []), G = i((e) => e.assistantLanguage), K = n({
39
+ onFinalize: W,
40
+ language: ue(() => te(G, S), [G, S]).tag
41
+ }), q = K.isActive, J = K.interim, Oe = K.stop;
31
42
  c(() => {
32
- !v || !G || (K.trim() !== "" && H(K.trim()), q());
43
+ !y || !q || (J.trim() !== "" && W(J.trim()), Oe());
33
44
  }, [
34
- v,
35
- G,
36
- K,
45
+ y,
37
46
  q,
38
- H
47
+ J,
48
+ Oe,
49
+ W
39
50
  ]), c(() => {
40
- y?.({
51
+ b?.({
41
52
  committing: O,
42
- dictating: G
53
+ dictating: q
43
54
  });
44
55
  }, [
45
- y,
56
+ b,
46
57
  O,
47
- G
58
+ q
48
59
  ]);
49
- let Oe = he ?? b("composer.placeholder", "Ask me anything…"), ke = w.trim(), J = a(N), Y = N.filter((e) => e.status === "error"), X = (ke.length > 0 || J.length > 0) && !S && !O && P === null, Ae = J.some((e) => !e.fileId && !e.inlineOnly), je = l(S);
50
- je.current = S;
51
- let Z = l(!0);
52
- c(() => (Z.current = !0, () => {
53
- Z.current = !1;
60
+ let ke = he ?? x("composer.placeholder", "Ask me anything…"), Ae = T.trim(), Y = a(P), X = P.filter((e) => e.status === "error"), Z = (Ae.length > 0 || Y.length > 0) && !C && !O && F === null, je = Y.some((e) => !e.fileId && !e.inlineOnly), Me = l(C);
61
+ Me.current = C;
62
+ let Q = l(!0);
63
+ c(() => (Q.current = !0, () => {
64
+ Q.current = !1;
54
65
  }), []), le(() => {
55
- let e = C.current;
66
+ let e = w.current;
56
67
  e && (e.style.height = "auto", e.style.height = `${Math.min(e.scrollHeight, pe)}px`);
57
- }, [w]);
58
- let Q = s(async () => {
59
- if (!X || k.current) return;
60
- W.isActive && W.stop();
61
- let e = w.trim();
62
- if (j(null), !Ae) {
63
- m(e, J), T(""), L();
68
+ }, [T]);
69
+ let $ = s(async () => {
70
+ if (!Z || A.current) return;
71
+ K.isActive && K.stop();
72
+ let e = T.trim();
73
+ if (M(null), !je) {
74
+ m(e, Y), E(""), R();
64
75
  return;
65
76
  }
66
77
  let t = i.getState().activeConversationId;
67
- k.current = !0, xe(!0);
78
+ A.current = !0, k(!0);
68
79
  try {
69
- let n = await R();
70
- if (!Z.current) {
80
+ let n = await z();
81
+ if (!Q.current) {
71
82
  n.ok && await n.rollback();
72
83
  return;
73
84
  }
74
85
  if (!n.ok) {
75
- n.cancelled || j({
86
+ n.cancelled || M({
76
87
  kind: "failed",
77
88
  failures: n.failures
78
89
  });
79
90
  return;
80
91
  }
81
- if (je.current) {
82
- await n.rollback(), Z.current && j({
92
+ if (Me.current) {
93
+ await n.rollback(), Q.current && M({
83
94
  kind: "busy",
84
95
  failures: []
85
96
  });
@@ -87,67 +98,67 @@ var pe = 200, p = ({ writeAccessDenied: p = !1, onSend: m, disabled: h = !1, rea
87
98
  }
88
99
  let r = i.getState();
89
100
  if (!(r.activeConversationId === t && (t === null || r.conversations.some((e) => e.id === t)))) {
90
- await n.rollback(), Z.current && j({
101
+ await n.rollback(), Q.current && M({
91
102
  kind: "moved",
92
103
  failures: []
93
104
  });
94
105
  return;
95
106
  }
96
- m(e, a(n.attachments)), n.handedOver(), T(""), L();
107
+ m(e, a(n.attachments)), n.handedOver(), E(""), R();
97
108
  } finally {
98
- k.current = !1, Z.current && xe(!1);
109
+ A.current = !1, Q.current && k(!1);
99
110
  }
100
111
  }, [
101
- X,
102
- Ae,
103
- R,
112
+ Z,
113
+ je,
114
+ z,
104
115
  m,
105
- w,
106
- J,
107
- L,
108
- W
109
- ]), Me = s(() => {
110
- z(), C.current?.focus();
111
- }, [z]);
116
+ T,
117
+ Y,
118
+ R,
119
+ K
120
+ ]), Ne = s(() => {
121
+ B(), w.current?.focus();
122
+ }, [B]);
112
123
  c(() => {
113
- j(null);
114
- }, [N.length]);
115
- let Ne = (e) => {
116
- if (e.kind === "busy") return b("attachments.saveBusy", "The assistant started another reply, so nothing was sent. Your message and files are still here.");
117
- if (e.kind === "moved") return b("attachments.saveMoved", "You switched conversations while the files were being saved, so nothing was sent. Your message and files are still here.");
124
+ M(null);
125
+ }, [P.length]);
126
+ let Pe = (e) => {
127
+ if (e.kind === "busy") return x("attachments.saveBusy", "The assistant started another reply, so nothing was sent. Your message and files are still here.");
128
+ if (e.kind === "moved") return x("attachments.saveMoved", "You switched conversations while the files were being saved, so nothing was sent. Your message and files are still here.");
118
129
  let { failures: t } = e;
119
- if (t.some((e) => e.code === o.quotaExceeded || e.code === o.quotaNotProvisioned)) return `${b("attachments.quotaFull", "File storage is full or not set up for this workspace, so the attachments couldn't be saved. Nothing was sent.")} ${b("attachments.retrySend", "Press Send to try again, or remove the file.")}`;
120
- let n = t[0], r = N.find((e) => e.id === n?.id)?.filename ?? "";
121
- return `${t.length <= 1 ? b("attachments.saveFailedOne", "{{name}} couldn't be saved, so nothing was sent.", { name: r }) : b("attachments.saveFailedMany", "{{count}} attachments couldn't be saved, so nothing was sent.", { count: t.length })} ${b("attachments.retrySend", "Press Send to try again, or remove the file.")}`;
122
- }, Pe = s((e) => {
123
- e.key === "Enter" && !e.shiftKey && (e.preventDefault(), Q());
124
- }, [Q]), Fe = s((e) => {
130
+ if (t.some((e) => e.code === o.quotaExceeded || e.code === o.quotaNotProvisioned)) return `${x("attachments.quotaFull", "File storage is full or not set up for this workspace, so the attachments couldn't be saved. Nothing was sent.")} ${x("attachments.retrySend", "Press Send to try again, or remove the file.")}`;
131
+ let n = t[0], r = P.find((e) => e.id === n?.id)?.filename ?? "";
132
+ return `${t.length <= 1 ? x("attachments.saveFailedOne", "{{name}} couldn't be saved, so nothing was sent.", { name: r }) : x("attachments.saveFailedMany", "{{count}} attachments couldn't be saved, so nothing was sent.", { count: t.length })} ${x("attachments.retrySend", "Press Send to try again, or remove the file.")}`;
133
+ }, Fe = s((e) => {
134
+ e.key === "Enter" && !e.shiftKey && (e.preventDefault(), $());
135
+ }, [$]), Ie = s((e) => {
125
136
  let t = Array.from(e.clipboardData?.files ?? []);
126
- t.length !== 0 && (e.preventDefault(), I(t));
127
- }, [I]), Ie = s((e) => {
128
- Array.from(e.dataTransfer?.types ?? []).includes("Files") && (M.current += 1, D(!0));
129
- }, []), Le = s(() => {
130
- M.current = Math.max(0, M.current - 1), M.current === 0 && D(!1);
131
- }, []), Re = s((e) => {
132
- Array.from(e.dataTransfer?.types ?? []).includes("Files") && e.preventDefault();
137
+ t.length !== 0 && (e.preventDefault(), L(t));
138
+ }, [L]), Le = s((e) => {
139
+ Array.from(e.dataTransfer?.types ?? []).includes("Files") && (N.current += 1, D(!0));
140
+ }, []), Re = s(() => {
141
+ N.current = Math.max(0, N.current - 1), N.current === 0 && D(!1);
133
142
  }, []), ze = s((e) => {
134
- e.preventDefault(), M.current = 0, D(!1), I(e.dataTransfer?.files ?? null);
135
- }, [I]), Be = (e) => {
143
+ Array.from(e.dataTransfer?.types ?? []).includes("Files") && e.preventDefault();
144
+ }, []), Be = s((e) => {
145
+ e.preventDefault(), N.current = 0, D(!1), L(e.dataTransfer?.files ?? null);
146
+ }, [L]), Ve = (e) => {
136
147
  switch (e) {
137
- case "too-many": return b("attachments.tooMany", "You can attach up to {{max}} files per message.", { max: 5 });
138
- case "too-large": return b("attachments.tooLarge", "That file is larger than 10 MB.");
139
- case "empty": return b("attachments.empty", "That file is empty.");
140
- case "unreadable": return b("attachments.unreadable", "That file could not be read.");
141
- case "unsupported-image": return b("attachments.unsupportedImage", "This device can't read that image format, so it can't be resized to send. Save it as JPEG and try again.");
142
- case "too-large-to-save": return b("attachments.tooLargeToSave", "That file is over 5 MB, so it can't be saved to Files, and no text could be read from it.");
148
+ case "too-many": return x("attachments.tooMany", "You can attach up to {{max}} files per message.", { max: 5 });
149
+ case "too-large": return x("attachments.tooLarge", "That file is larger than 10 MB.");
150
+ case "empty": return x("attachments.empty", "That file is empty.");
151
+ case "unreadable": return x("attachments.unreadable", "That file could not be read.");
152
+ case "unsupported-image": return x("attachments.unsupportedImage", "This device can't read that image format, so it can't be resized to send. Save it as JPEG and try again.");
153
+ case "too-large-to-save": return x("attachments.tooLargeToSave", "That file is over 5 MB, so it can't be saved to Files, and no text could be read from it.");
143
154
  }
144
- }, $ = (() => {
145
- switch (W.error) {
146
- case "not-allowed": return b("voice.denied", "Microphone access is blocked. Allow it in your browser settings to dictate.");
147
- case "audio-capture": return b("voice.noMic", "No microphone was found.");
148
- case "network": return b("voice.network", "Speech recognition is offline right now.");
149
- case "language-not-supported": return b("voice.languageNotSupported", "This browser cannot recognise spoken {{language}}. Try a different browser, or type instead.", { language: ee(W.language, x) });
150
- case "unknown": return b("voice.failed", "Dictation stopped unexpectedly.");
155
+ }, He = (() => {
156
+ switch (K.error) {
157
+ case "not-allowed": return x("voice.denied", "Microphone access is blocked. Allow it in your browser settings to dictate.");
158
+ case "audio-capture": return x("voice.noMic", "No microphone was found.");
159
+ case "network": return x("voice.network", "Speech recognition is offline right now.");
160
+ case "language-not-supported": return x("voice.languageNotSupported", "This browser cannot recognise spoken {{language}}. Try a different browser, or type instead.", { language: ee(K.language, S) });
161
+ case "unknown": return x("voice.failed", "Dictation stopped unexpectedly.");
151
162
  default: return null;
152
163
  }
153
164
  })();
@@ -155,59 +166,59 @@ var pe = 200, p = ({ writeAccessDenied: p = !1, onSend: m, disabled: h = !1, rea
155
166
  className: "flex-shrink-0 px-3 pb-3 pt-1.5",
156
167
  children: [
157
168
  /* @__PURE__ */ f("div", {
158
- onDragEnter: Ie,
159
- onDragLeave: Le,
160
- onDragOver: Re,
161
- onDrop: ze,
162
- className: `rounded-2xl border p-2 shadow-sm transition-colors focus-within:border-action-primary-bg focus-within:ring-2 focus-within:ring-action-primary-bg/15 ${E ? "border-action-primary-bg border-dashed bg-action-primary-bg/5" : "border-border-default bg-bg-sunken"}`,
169
+ onDragEnter: Le,
170
+ onDragLeave: Re,
171
+ onDragOver: ze,
172
+ onDrop: Be,
173
+ className: `rounded-2xl border p-2 shadow-sm transition-colors focus-within:border-action-primary-bg focus-within:ring-2 focus-within:ring-action-primary-bg/15 ${xe ? "border-action-primary-bg border-dashed bg-action-primary-bg/5" : "border-border-default bg-bg-sunken"}`,
163
174
  "data-testid": "assistant-composer",
164
175
  children: [
165
176
  /* @__PURE__ */ d(re, {
166
- attachments: N,
177
+ attachments: P,
167
178
  onRemove: we,
168
179
  onRetry: Te,
169
- disabled: S || O,
180
+ disabled: C || O,
170
181
  preparingCount: Ce
171
182
  }),
172
183
  /* @__PURE__ */ f("div", {
173
184
  className: "flex items-end gap-2",
174
185
  children: [
175
186
  /* @__PURE__ */ d(ne, {
176
- onFiles: I,
187
+ onFiles: L,
177
188
  canAddMore: De,
178
- disabled: S || O,
189
+ disabled: C || O,
179
190
  showLabel: !0
180
191
  }),
181
192
  /* @__PURE__ */ d("textarea", {
182
- ref: C,
183
- value: w,
184
- onChange: (e) => T(e.target.value),
185
- onKeyDown: Pe,
186
- onPaste: Fe,
187
- disabled: S,
193
+ ref: w,
194
+ value: T,
195
+ onChange: (e) => E(e.target.value),
196
+ onKeyDown: Fe,
197
+ onPaste: Ie,
198
+ disabled: C,
188
199
  readOnly: O,
189
200
  "aria-busy": O || void 0,
190
- placeholder: E ? b("composer.dropHere", "Drop files to attach") : Oe,
201
+ placeholder: xe ? x("composer.dropHere", "Drop files to attach") : ke,
191
202
  rows: 1,
192
203
  className: "min-h-[36px] flex-1 resize-none self-center bg-transparent px-1 py-2 text-[15px] leading-relaxed text-text-primary placeholder:text-text-muted focus:outline-none disabled:opacity-50",
193
- "aria-label": b("chatInput.ariaLabel", "Message input"),
204
+ "aria-label": x("chatInput.ariaLabel", "Message input"),
194
205
  "data-testid": "assistant-input"
195
206
  }),
196
- W.isSupported && /* @__PURE__ */ d(ae, {
197
- isListening: W.isListening,
198
- isStarting: W.isStarting,
199
- onToggle: W.toggle,
200
- disabled: S || O
207
+ K.isSupported && /* @__PURE__ */ d(ae, {
208
+ isListening: K.isListening,
209
+ isStarting: K.isStarting,
210
+ onToggle: K.toggle,
211
+ disabled: C || O
201
212
  }),
202
- be && ke.length === 0 && N.length === 0 && !W.isActive ? /* @__PURE__ */ d(se, {
213
+ be && Ae.length === 0 && P.length === 0 && !K.isActive ? /* @__PURE__ */ d(se, {
203
214
  onStart: be,
204
- disabled: S || O
215
+ disabled: C || O
205
216
  }) : /* @__PURE__ */ d("button", {
206
217
  type: "button",
207
- onClick: () => void Q(),
208
- disabled: !X,
209
- className: `flex size-9 flex-shrink-0 items-center justify-center rounded-full shadow-sm transition-all focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong ${X ? "bg-action-primary-bg text-action-primary-text hover:scale-105 hover:bg-action-primary-bg-hover" : "bg-bg-elevated text-text-muted"}`,
210
- "aria-label": b("chatInput.send", "Send message"),
218
+ onClick: () => void $(),
219
+ disabled: !Z,
220
+ className: `flex size-9 flex-shrink-0 items-center justify-center rounded-full shadow-sm transition-all focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong ${Z ? "bg-action-primary-bg text-action-primary-text hover:scale-105 hover:bg-action-primary-bg-hover" : "bg-bg-elevated text-text-muted"}`,
221
+ "aria-label": x("chatInput.send", "Send message"),
211
222
  "data-testid": "assistant-send",
212
223
  children: h || Se || O ? /* @__PURE__ */ d(fe, { className: "size-4 motion-safe:animate-spin" }) : /* @__PURE__ */ d(de, { className: "size-4" })
213
224
  })
@@ -219,7 +230,7 @@ var pe = 200, p = ({ writeAccessDenied: p = !1, onSend: m, disabled: h = !1, rea
219
230
  mode: ge,
220
231
  availableModes: g,
221
232
  onModeChange: ve,
222
- disabled: S || O,
233
+ disabled: C || O,
223
234
  auto: ye
224
235
  })
225
236
  })
@@ -228,13 +239,13 @@ var pe = 200, p = ({ writeAccessDenied: p = !1, onSend: m, disabled: h = !1, rea
228
239
  p ? /* @__PURE__ */ d("p", {
229
240
  className: "mt-1.5 px-1 text-center text-[11px] text-status-warning",
230
241
  "data-testid": "assistant-write-access-warning",
231
- children: b("composer.writeAccessDenied", "Changes may not save — your role in this workspace does not currently allow edits. You can still plan and review an action.")
242
+ children: x("composer.writeAccessDenied", "Changes may not save — your role in this workspace does not currently allow edits. You can still plan and review an action.")
232
243
  }) : null,
233
244
  /* @__PURE__ */ d("div", {
234
245
  className: "mt-1.5 px-1 text-center text-[11px]",
235
246
  "aria-live": "polite",
236
247
  children: O ? /* @__PURE__ */ d(t, {
237
- onDismiss: Me,
248
+ onDismiss: Ne,
238
249
  children: /* @__PURE__ */ f("p", {
239
250
  className: "flex items-center justify-center gap-2 text-text-muted",
240
251
  "data-testid": "assistant-commit-status",
@@ -243,46 +254,46 @@ var pe = 200, p = ({ writeAccessDenied: p = !1, onSend: m, disabled: h = !1, rea
243
254
  className: "size-3 motion-safe:animate-spin",
244
255
  "aria-hidden": "true"
245
256
  }),
246
- /* @__PURE__ */ d("span", { children: b("attachments.saving", "Saving your attachments…") }),
257
+ /* @__PURE__ */ d("span", { children: x("attachments.saving", "Saving your attachments…") }),
247
258
  /* @__PURE__ */ d("button", {
248
259
  type: "button",
249
- onClick: Me,
260
+ onClick: Ne,
250
261
  className: "rounded text-text-primary underline underline-offset-2 hover:text-action-primary-bg focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong",
251
262
  "data-testid": "assistant-commit-cancel",
252
- children: b("attachments.cancelSaving", "Cancel")
263
+ children: x("attachments.cancelSaving", "Cancel")
253
264
  })
254
265
  ]
255
266
  })
256
- }) : A ? /* @__PURE__ */ d("p", {
267
+ }) : j ? /* @__PURE__ */ d("p", {
257
268
  className: "text-status-error-text",
258
269
  role: "alert",
259
270
  "data-testid": "assistant-commit-failed",
260
- children: Ne(A)
261
- }) : W.isStarting && !W.isListening ? /* @__PURE__ */ d("p", {
271
+ children: Pe(j)
272
+ }) : K.isStarting && !K.isListening ? /* @__PURE__ */ d("p", {
262
273
  className: "text-text-muted",
263
274
  "data-testid": "assistant-voice-starting",
264
- children: b("voice.starting", "Starting the microphone…")
265
- }) : W.isListening ? /* @__PURE__ */ f("p", {
275
+ children: x("voice.starting", "Starting the microphone…")
276
+ }) : K.isListening ? /* @__PURE__ */ f("p", {
266
277
  className: "text-status-error-text",
267
278
  "data-testid": "assistant-voice-status",
268
- children: [/* @__PURE__ */ d(oe, {}), W.interim || b("voice.listening", "Listening… speak now, tap the square to stop")]
269
- }) : $ ? /* @__PURE__ */ d("p", {
279
+ children: [/* @__PURE__ */ d(oe, {}), K.interim || x("voice.listening", "Listening… speak now, tap the square to stop")]
280
+ }) : He ? /* @__PURE__ */ d("p", {
270
281
  className: "text-status-error-text",
271
282
  "data-testid": "assistant-voice-error",
272
- children: $
273
- }) : F ? /* @__PURE__ */ d("button", {
283
+ children: He
284
+ }) : I ? /* @__PURE__ */ d("button", {
274
285
  type: "button",
275
286
  onClick: Ee,
276
287
  className: "text-status-error-text underline-offset-2 hover:underline",
277
288
  "data-testid": "assistant-attachment-rejection",
278
- children: Be(F)
279
- }) : P === "failed" ? /* @__PURE__ */ d("p", {
289
+ children: Ve(I)
290
+ }) : F === "failed" ? /* @__PURE__ */ d("p", {
280
291
  className: "text-status-error-text",
281
292
  "data-testid": "assistant-attachment-blocked",
282
- children: Y.length === 1 ? b("attachments.blockedOne", "{{name}} didn't upload. Retry or remove it before sending.", { name: Y[0].filename }) : b("attachments.blockedMany", "{{count}} attachments didn't upload. Retry or remove them before sending.", { count: Y.length })
293
+ children: X.length === 1 ? x("attachments.blockedOne", "{{name}} didn't upload. Retry or remove it before sending.", { name: X[0].filename }) : x("attachments.blockedMany", "{{count}} attachments didn't upload. Retry or remove them before sending.", { count: X.length })
283
294
  }) : /* @__PURE__ */ d("p", {
284
295
  className: "text-text-muted",
285
- children: b("composer.hint", "Enter to send · Shift+Enter for newline · drag or paste files to attach")
296
+ children: x("composer.hint", "Enter to send · Shift+Enter for newline · drag or paste files to attach")
286
297
  })
287
298
  })
288
299
  ]
@@ -0,0 +1,7 @@
1
+ /** Keep speculative grammar work out of the keystroke's React effect. */
2
+ export declare const DRAFT_COMMAND_CHECK_DELAY_MS = 120;
3
+ /** A trailing check; new edits, cleared drafts and unmounts cancel stale work. */
4
+ export declare function createDraftCommandCheck(): {
5
+ cancel: () => void;
6
+ schedule(check: () => void): void;
7
+ };
@@ -0,0 +1,17 @@
1
+ //#region src/shared/assistant/ui/draftCommandCheck.ts
2
+ var e = 120;
3
+ function t() {
4
+ let e, t = () => {
5
+ e !== void 0 && clearTimeout(e), e = void 0;
6
+ };
7
+ return {
8
+ cancel: t,
9
+ schedule(n) {
10
+ t(), e = setTimeout(() => {
11
+ e = void 0, n();
12
+ }, 120);
13
+ }
14
+ };
15
+ }
16
+ //#endregion
17
+ export { e as DRAFT_COMMAND_CHECK_DELAY_MS, t as createDraftCommandCheck };