@hachej/boring-ui-kit 0.4.26 → 0.4.27

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 (79) hide show
  1. package/README.md +15 -2
  2. package/dist/chat-dictation.d.ts +27 -0
  3. package/dist/chat-dictation.js +71 -0
  4. package/dist/chat-react-session.d.ts +170 -0
  5. package/dist/chat-react-session.js +367 -0
  6. package/dist/chat-react.d.ts +2 -0
  7. package/dist/chat-react.js +1 -0
  8. package/dist/chat-text.d.ts +9 -0
  9. package/dist/chat-text.js +4 -0
  10. package/dist/chat-voice-message.d.ts +65 -0
  11. package/dist/chat-voice-message.js +235 -0
  12. package/dist/chat-voice.d.ts +7 -0
  13. package/dist/chat-voice.js +4 -0
  14. package/dist/experience-catalog.d.ts +2 -2
  15. package/dist/experience-region-tree.d.ts +1 -1
  16. package/dist/markdown-editor-labels.d.ts +83 -0
  17. package/dist/markdown-editor-labels.js +75 -0
  18. package/dist/markdown-editor.d.ts +7 -1
  19. package/dist/markdown-editor.js +28 -18
  20. package/dist/markdown-extensions.d.ts +4 -2
  21. package/dist/markdown-extensions.js +2 -2
  22. package/dist/markdown-preserved.d.ts +5 -1
  23. package/dist/markdown-preserved.js +15 -4
  24. package/dist/markdown-proposal-diff.d.ts +3 -1
  25. package/dist/markdown-proposal-diff.js +6 -5
  26. package/dist/text-draft-controls.d.ts +5 -1
  27. package/dist/text-draft-controls.js +6 -5
  28. package/dist/text-draft-labels.d.ts +26 -0
  29. package/dist/text-draft-labels.js +47 -0
  30. package/dist/voice-attempt.d.ts +33 -0
  31. package/dist/voice-attempt.js +122 -0
  32. package/package.json +6 -2
  33. package/r/agent-target.json +4 -4
  34. package/r/app-shell.json +14 -14
  35. package/r/board-viewer.json +7 -6
  36. package/r/button.json +1 -1
  37. package/r/dock.json +2 -2
  38. package/r/documents.json +12 -12
  39. package/r/feedback-preview.json +1 -1
  40. package/r/feedback.json +7 -7
  41. package/r/file-tree.json +4 -4
  42. package/r/highlights.json +3 -3
  43. package/r/html-viewer.json +3 -3
  44. package/r/looks-viewer.json +3 -2
  45. package/r/maquette-viewer.json +1 -1
  46. package/r/markdown-editor.json +3 -3
  47. package/r/mockup-viewer.json +1 -1
  48. package/r/pi-ambient.json +9 -9
  49. package/r/pi-app-kinds.json +10 -10
  50. package/r/pi-app.json +13 -13
  51. package/r/pi-artifacts.json +2 -2
  52. package/r/pi-chat-approval.json +2 -2
  53. package/r/pi-chat-attachments.json +1 -1
  54. package/r/pi-chat-markdown.json +2 -2
  55. package/r/pi-chat-mentions.json +1 -1
  56. package/r/pi-chat-pickers.json +1 -1
  57. package/r/pi-chat-plugins.json +11 -11
  58. package/r/pi-chat-question.json +1 -1
  59. package/r/pi-chat-slash.json +2 -2
  60. package/r/pi-chat-subagent.json +1 -1
  61. package/r/pi-chat-voice.json +5 -5
  62. package/r/pi-chat.json +19 -19
  63. package/r/pi-workspace.json +4 -4
  64. package/r/placed-agent.json +4 -4
  65. package/r/provider-setup.json +1 -1
  66. package/r/registry.json +180 -168
  67. package/r/requests-board.json +1 -1
  68. package/r/shared-chat.json +9 -9
  69. package/r/task-list-viewer.json +2 -2
  70. package/r/utils.json +14 -2
  71. package/r/version-history.json +2 -2
  72. package/r/viewer-code.json +2 -2
  73. package/r/viewer-frame.json +7 -7
  74. package/r/viewer-html.json +4 -4
  75. package/r/viewer-image.json +2 -2
  76. package/r/viewer-markdown.json +5 -5
  77. package/r/viewer-pdf.json +3 -3
  78. package/r/viewers.json +6 -6
  79. package/r/windows.json +3 -3
@@ -8,7 +8,7 @@
8
8
  "lucide-react@^1.51.0"
9
9
  ],
10
10
  "registryDependencies": [
11
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.26/r/pi-chat.json"
11
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.27/r/pi-chat.json"
12
12
  ],
13
13
  "files": [
14
14
  {
@@ -7,17 +7,17 @@
7
7
  "react@^19.0.0"
8
8
  ],
9
9
  "registryDependencies": [
10
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.26/r/pi-chat.json",
11
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.26/r/pi-chat-markdown.json",
12
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.26/r/pi-chat-slash.json",
13
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.26/r/pi-chat-mentions.json",
14
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.26/r/pi-chat-pickers.json",
15
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.26/r/pi-chat-voice.json",
16
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.26/r/pi-chat-attachments.json",
17
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.26/r/pi-chat-question.json",
18
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.26/r/pi-chat-approval.json",
19
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.26/r/pi-chat-subagent.json",
20
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.26/r/pi-artifacts.json"
10
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.27/r/pi-chat.json",
11
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.27/r/pi-chat-markdown.json",
12
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.27/r/pi-chat-slash.json",
13
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.27/r/pi-chat-mentions.json",
14
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.27/r/pi-chat-pickers.json",
15
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.27/r/pi-chat-voice.json",
16
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.27/r/pi-chat-attachments.json",
17
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.27/r/pi-chat-question.json",
18
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.27/r/pi-chat-approval.json",
19
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.27/r/pi-chat-subagent.json",
20
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.27/r/pi-artifacts.json"
21
21
  ],
22
22
  "files": [
23
23
  {
@@ -9,7 +9,7 @@
9
9
  "@earendil-works/pi-ai@~1.2.0"
10
10
  ],
11
11
  "registryDependencies": [
12
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.26/r/pi-chat.json"
12
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.27/r/pi-chat.json"
13
13
  ],
14
14
  "files": [
15
15
  {
@@ -4,12 +4,12 @@
4
4
  "title": "Pi chat slash menu",
5
5
  "description": "The composer's `/` menu of commands and skills (and the Commands entry of the + menu). Plug it in with `slash={{ ...config, Picker: SlashPicker }}`; without it pi-chat has no `/` menu.",
6
6
  "dependencies": [
7
- "@hachej/boring-ui-kit@^0.4.26",
7
+ "@hachej/boring-ui-kit@^0.4.27",
8
8
  "react@^19.0.0",
9
9
  "lucide-react@^1.51.0"
10
10
  ],
11
11
  "registryDependencies": [
12
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.26/r/pi-chat.json"
12
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.27/r/pi-chat.json"
13
13
  ],
14
14
  "files": [
15
15
  {
@@ -9,7 +9,7 @@
9
9
  "@earendil-works/pi-ai@~1.2.0"
10
10
  ],
11
11
  "registryDependencies": [
12
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.26/r/pi-chat.json"
12
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.27/r/pi-chat.json"
13
13
  ],
14
14
  "files": [
15
15
  {
@@ -2,15 +2,15 @@
2
2
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
3
  "name": "pi-chat-voice",
4
4
  "title": "Pi chat voice messages",
5
- "description": "Hold-to-record voice messages: the recorder hook over the kit's voice capture, the composer's record button and the pending voice bubble, plus useHoldToRecord. Plug it in with `voiceMessage={{ ...config, ...voiceMessagePlugin }}`; without it pi-chat has no voice button.",
5
+ "description": "Tap-to-record voice messages: a compatibility export of the kit's chat-react/voice recorder hook, the composer's record button and the pending voice bubble, plus useHoldToRecord. Plug it in with `voiceMessage={{ ...config, ...voiceMessagePlugin }}`; without it pi-chat has no voice button.",
6
6
  "dependencies": [
7
- "@hachej/boring-ui-kit@^0.4.26",
7
+ "@hachej/boring-ui-kit@^0.4.27",
8
8
  "react@^19.0.0",
9
9
  "lucide-react@^1.51.0"
10
10
  ],
11
11
  "registryDependencies": [
12
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.26/r/utils.json",
13
- "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.26/r/pi-chat.json"
12
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.27/r/utils.json",
13
+ "https://cdn.jsdelivr.net/npm/@hachej/boring-ui-kit@0.4.27/r/pi-chat.json"
14
14
  ],
15
15
  "files": [
16
16
  {
@@ -21,7 +21,7 @@
21
21
  },
22
22
  {
23
23
  "path": "registry/pi-chat-voice/use-voice-message.ts",
24
- "content": "'use client';\n\nimport { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react';\nimport { createVoiceCapture } from '@hachej/boring-ui-kit/voice-capture';\nimport type { VoiceCapture, VoiceRecording, VoiceRefusal } from '@hachej/boring-ui-kit/voice-capture';\nimport type { ChatController, ChatDraft } from '@hachej/boring-ui-kit/native-chat';\nimport { appendToDraft, isVoicePath, mentionedPaths, mentionToken, withoutMentions } from '@hachej/boring-ui-kit/chat-text';\nimport type { AttachmentsConfig, PendingVoice, VoiceMessageConfig, VoiceMessageHandle, VoicePhase, VoiceRecorderOptions } from '../pi-chat/config';\nimport { useRecordingCancellation } from './use-hold-to-record';\nimport type { ChatLabels } from '../pi-chat/labels';\n\ninterface Attempt {\n readonly id: number;\n readonly controller: ChatController;\n readonly target: string;\n readonly transcribe: VoiceMessageConfig['transcribe'];\n readonly upload: AttachmentsConfig['upload'] | undefined;\n readonly abort: AbortController;\n readonly capture: VoiceCapture;\n mentions: readonly string[];\n phase: VoicePhase;\n seconds: number;\n levels: number[];\n paused: boolean;\n canPause: boolean;\n file?: File;\n path?: string;\n transcript?: string;\n requestId?: string;\n}\nexport type { PendingVoice } from '../pi-chat/config';\nconst REFUSAL: Readonly<Record<VoiceRefusal, keyof Pick<ChatLabels, 'voiceDenied' | 'voiceNoMicrophone' | 'voiceBusy' | 'voiceInsecure' | 'voiceUnsupported' | 'voiceStartFailed'>>> = {\n denied: 'voiceDenied', 'no-microphone': 'voiceNoMicrophone', busy: 'voiceBusy', insecure: 'voiceInsecure', unsupported: 'voiceUnsupported', failed: 'voiceStartFailed',\n};\nconst targetOf = (controller: ChatController) => {\n const { identity, conversationId } = controller.getSnapshot();\n return JSON.stringify([identity.runtimeId, identity.scopeId, identity.principalId, conversationId]);\n};\nconst noSubscribe = () => () => {};\n\n/** Local admission preparation only. Native send/reconcile owns the submission; uploaded bytes stay in the host workspace. */\nexport function useVoiceMessage({ controller, config, attachments, labels, admitted, activeController }: VoiceRecorderOptions): VoiceMessageHandle {\n const selected = config ? controller : undefined;\n const readObservation = useCallback(() => {\n if (!selected) return '';\n const snapshot = selected.getSnapshot(), send = snapshot.send;\n return JSON.stringify([targetOf(selected), snapshot.disposed, send.kind, 'attempt' in send ? send.attempt.requestId : undefined]);\n }, [selected]);\n const observation = useSyncExternalStore(selected?.subscribe ?? noSubscribe, readObservation, readObservation);\n // eslint-disable-next-line react-hooks/refs -- async capture and send callbacks check the props of the latest render; assigned in render so they never see the previous controller\n const latest = useRef({ controller, config, admitted, activeController }); latest.current = { controller, config, admitted, activeController };\n const mounted = useRef(false), attempts = useRef<Attempt[]>([]), captureSequence = useRef(0);\n const [, changed] = useState(0), [problem, setProblem] = useState<string>();\n const update = () => changed(value => value + 1);\n const valid = (item: Attempt) => mounted.current && attempts.current.includes(item) && latest.current.controller === item.controller && (!latest.current.activeController || latest.current.activeController.current === item.controller) && Boolean(latest.current.config) && !item.abort.signal.aborted && !item.controller.getSnapshot().disposed && targetOf(item.controller) === item.target;\n const remove = (item: Attempt) => {\n attempts.current = attempts.current.filter(current => current !== item);\n item.abort.abort(); item.capture.cancel();\n if (mounted.current) update();\n };\n const active = () => attempts.current.find(item => item.phase === 'starting' || item.phase === 'recording') ?? attempts.current.find(item => item.phase === 'processing' || item.phase === 'sending' || item.phase === 'unconfirmed');\n const cancel = () => { const item = active(); if (item) remove(item); };\n useRecordingCancellation(() => { const item = active(); if (item?.phase === 'starting' || item?.phase === 'recording') cancel(); });\n const clear = () => { for (const item of [...attempts.current]) remove(item); };\n const target = selected ? targetOf(selected) : undefined;\n // eslint-disable-next-line react-hooks/exhaustive-deps -- mount/unmount only: unmounting drops whatever attempts exist then\n useEffect(() => { mounted.current = true; return () => { mounted.current = false; clear(); }; }, []);\n const configured = Boolean(config);\n // eslint-disable-next-line react-hooks/exhaustive-deps, react-hooks/set-state-in-effect -- another controller, conversation or config drops the attempts and the problem, once per change\n useEffect(() => { clear(); setProblem(undefined); }, [controller, target, configured]);\n useEffect(() => {\n for (const item of [...attempts.current]) {\n if (!valid(item)) { remove(item); continue; }\n if (item.phase !== 'unconfirmed') continue;\n const snapshot = selected?.getSnapshot();\n if (snapshot?.send.kind === 'admitted' && 'attempt' in snapshot.send && snapshot.send.attempt.requestId === item.requestId) {\n remove(item); latest.current.admitted?.();\n // eslint-disable-next-line react-hooks/set-state-in-effect -- the chat's send state settles an unconfirmed attempt; update() republishes the attempt ref\n } else if (snapshot?.send.kind === 'blocked') { item.phase = 'blocked'; update(); }\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- runs when the chat's observation changes; remove/update are per-render closures over refs\n }, [selected, observation]);\n // eslint-disable-next-line react-hooks/refs -- the attempts live in a ref that every change pairs with update(); render reads the list as of that update\n const recording = active(), phase = recording?.phase ?? 'idle';\n useEffect(() => {\n const item = recording;\n if (item?.phase !== 'recording') return;\n let elapsedMs = 0;\n const timer = setInterval(() => {\n if (!valid(item)) return;\n const progress = item.capture.progress();\n if (!progress) return;\n item.seconds = Math.floor(progress.elapsedMs / 1000);\n item.canPause = progress.canPause; item.paused = progress.paused;\n if (progress.level !== undefined && !progress.paused && progress.elapsedMs > elapsedMs) item.levels = [...item.levels.slice(-31), progress.level];\n elapsedMs = progress.elapsedMs;\n update();\n }, 100);\n return () => clearInterval(timer);\n }, [recording, phase]);\n\n const send = async (item: Attempt, audioOnly = false) => {\n if (!valid(item) || !item.path || item.phase === 'sending' || item.phase === 'unconfirmed') return;\n const transcript = audioOnly ? '' : item.transcript ?? '';\n const text = [...item.mentions.map(mentionToken), mentionToken(item.path), transcript].filter(Boolean).join(' ');\n const before = item.controller.getSnapshot();\n if (before.send.kind === 'unknown') {\n // This recording was never submitted. Do not bind it to another message's uncertain request.\n const draft = item.controller.getSnapshot().draft.text;\n if (transcript) item.controller.setText(appendToDraft(draft, transcript));\n item.phase = 'blocked'; update(); return;\n }\n item.phase = 'sending'; update();\n let submittedDraft: ChatDraft | undefined;\n try {\n const pending = item.controller.send('followUp', { text, restore: { text: transcript } });\n const after = item.controller.getSnapshot();\n // Native send synchronously publishes this immutable draft in validation or at its outbox position; equal text is not identity.\n submittedDraft = after.send.kind === 'validating' && (before.send.kind !== 'validating' || after.send.draft !== before.send.draft)\n ? after.send.draft : after.outbox[before.outbox.length];\n const result = await pending;\n if (!valid(item)) return;\n if (result) { remove(item); latest.current.admitted?.(); return; }\n } catch {\n if (!valid(item)) return;\n const draft = item.controller.getSnapshot().draft.text;\n if (transcript) item.controller.setText(appendToDraft(draft, transcript));\n }\n const native = item.controller.getSnapshot().send;\n if (native.kind === 'admitted' && 'attempt' in native && submittedDraft !== undefined && native.attempt.draft === submittedDraft) {\n remove(item); latest.current.admitted?.(); return;\n }\n const ownUnknown = native.kind === 'unknown' && 'attempt' in native && submittedDraft !== undefined && native.attempt.draft === submittedDraft;\n item.phase = native.kind === 'blocked' || (native.kind === 'unknown' && 'attempt' in native && !ownUnknown) ? 'blocked' : 'unconfirmed';\n if (ownUnknown) item.requestId = native.attempt.requestId;\n update();\n };\n const process = async (item: Attempt) => {\n if (!valid(item) || !item.file) return;\n item.phase = 'processing'; update();\n // Cache each successful half independently: retrying transcription must not upload the same recording again.\n await Promise.all([\n item.path ? undefined : (async () => {\n try {\n const results = await item.upload?.([item.file!], item.abort.signal);\n const path = results?.length === 1 ? results[0]?.path : undefined;\n if (valid(item) && path && isVoicePath(path)) item.path = path;\n } catch { /* Failed uploads remain pending for explicit retry. */ }\n })(),\n item.transcript !== undefined ? undefined : (async () => {\n try { const text = (await item.transcribe(item.file!, item.abort.signal)).trim(); if (valid(item) && text) item.transcript = text; }\n catch { /* Never send automatically when transcription fails. */ }\n })(),\n ]);\n if (!valid(item)) return;\n if (!item.path) { item.phase = 'upload-failed'; update(); }\n else if (!item.transcript) { item.phase = 'transcribe-failed'; update(); }\n else await send(item);\n };\n const finish = () => {\n const item = active();\n if (!item || !valid(item)) return;\n if (item.phase === 'starting') { cancel(); return; }\n if (item.phase !== 'recording') return;\n const draft = item.controller.getSnapshot().draft.text;\n item.mentions = mentionedPaths(draft);\n item.controller.setText(withoutMentions(draft));\n item.phase = 'processing'; update();\n void (async () => {\n const recording: VoiceRecording | null = await item.capture.stop().catch(() => null);\n if (!valid(item)) return;\n if (!recording) { remove(item); setProblem(labels.voiceNothingHeard); update(); return; }\n item.seconds = Math.max(0, Math.round((recording.endedAt - recording.startedAt) / 1000));\n const type = recording.mimeType.split(';')[0]!, ext = type === 'audio/wav' ? 'wav' : type === 'audio/mp4' ? 'mp4' : type === 'audio/ogg' ? 'ogg' : 'webm';\n const stamp = new Date().toISOString().replace(/[-:]/g, '').replace('T', '-').replace(/\\.(\\d+)Z$/, '-$1');\n item.file = new File([recording.audio], `voice-${stamp}.${ext}`, { type });\n await process(item);\n })();\n };\n const start = () => {\n if (!controller || !config || active() || controller.getSnapshot().disposed || controller.getSnapshot().send.kind === 'unknown') return;\n captureSequence.current += 1; setProblem(undefined);\n const item: Attempt = { id: captureSequence.current, controller, mentions: [], transcribe: config.transcribe.bind(config), upload: config.upload?.bind(config) ?? attachments?.upload.bind(attachments), target: targetOf(controller), abort: new AbortController(), capture: createVoiceCapture({ ...(config.maxMs !== undefined ? { maxMs: config.maxMs } : {}) }), phase: 'starting', seconds: 0, levels: [], paused: false, canPause: false };\n attempts.current = [...attempts.current, item]; update();\n void item.capture.start().then(result => {\n if (!valid(item)) { item.capture.cancel(); return; }\n if (result.kind === 'refused') { remove(item); setProblem(result.code ? labels[REFUSAL[result.code]] : result.reason); update(); return; }\n item.phase = 'recording'; update();\n });\n };\n // eslint-disable-next-line react-hooks/refs -- the attempts live in a ref that every change pairs with update(); render reads the list as of that update\n const item = active();\n // eslint-disable-next-line react-hooks/refs -- the attempts live in a ref that every change pairs with update(); render reads the list as of that update\n const pending: readonly PendingVoice[] = attempts.current.filter(item => item.phase !== 'starting' && item.phase !== 'recording').map(item => ({\n id: item.id, path: item.path, phase: item.phase, seconds: item.seconds,\n retry: () => { if (item.phase === 'upload-failed' || item.phase === 'transcribe-failed') void process(item); },\n audioOnly: item.phase === 'transcribe-failed' && item.path ? () => { if (item.phase === 'transcribe-failed') void send(item, true); } : undefined,\n discard: () => { if (valid(item)) remove(item); },\n }));\n // eslint-disable-next-line react-hooks/refs -- the capture sequence is a ref bumped with update(); render reads it as of that update\n return { canStart: Boolean(controller && config && !item && !controller.getSnapshot().disposed && controller.getSnapshot().send.kind !== 'unknown'), phase: item?.phase ?? 'idle', seconds: item?.seconds ?? 0, levels: item?.levels ?? [], paused: item?.paused ?? false, pause: item?.phase === 'recording' && item.canPause ? () => { if (item.capture.pause()) { item.paused = true; update(); } } : undefined, resume: item?.phase === 'recording' && item.canPause ? () => { if (item.capture.resume()) { item.paused = false; update(); } } : undefined, captureSequence: captureSequence.current, problem, pending, start, finish, cancel };\n}\n",
24
+ "content": "'use client';\n\nexport { useVoiceMessage } from '@hachej/boring-ui-kit/chat-react/voice';\nexport type { PendingVoice } from '@hachej/boring-ui-kit/chat-react/voice';\n",
25
25
  "type": "registry:hook",
26
26
  "target": "components/pi-chat-voice/use-voice-message.ts"
27
27
  },