@phenx-inc/ctlsurf 0.8.0 → 0.9.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.
Files changed (37) hide show
  1. package/out/headless/index.mjs +144 -1
  2. package/out/headless/index.mjs.map +2 -2
  3. package/out/main/index.js +144 -0
  4. package/out/preload/index.js +2 -0
  5. package/out/renderer/assets/{cssMode-BQN8v2ok.js → cssMode-BdYp0fp9.js} +3 -3
  6. package/out/renderer/assets/{freemarker2-DbxGYYVp.js → freemarker2-Cya-9zr-.js} +1 -1
  7. package/out/renderer/assets/{handlebars-3auU1CAd.js → handlebars-EJs9W5Ub.js} +1 -1
  8. package/out/renderer/assets/{html-D8xFiRmI.js → html-Ex4qy2lh.js} +1 -1
  9. package/out/renderer/assets/{htmlMode-M3MApZ4n.js → htmlMode-CCm0aJ6C.js} +3 -3
  10. package/out/renderer/assets/{index---H6cxNl.js → index-Cj7K9-Ba.js} +265 -242
  11. package/out/renderer/assets/{index-B-iM7dFC.css → index-nvsNYP5c.css} +34 -0
  12. package/out/renderer/assets/{javascript-BO_ViZM5.js → javascript-B2niCouI.js} +2 -2
  13. package/out/renderer/assets/{jsonMode-CKp2zvZu.js → jsonMode-BgCUHaFo.js} +3 -3
  14. package/out/renderer/assets/{liquid-C1eHcrht.js → liquid-DOQmSo4G.js} +1 -1
  15. package/out/renderer/assets/{lspLanguageFeatures-CHWJx_Tl.js → lspLanguageFeatures-8SIcRAMh.js} +1 -1
  16. package/out/renderer/assets/{mdx-Qqdtk7fL.js → mdx-DuKdGNKE.js} +1 -1
  17. package/out/renderer/assets/{python-DKu7rNbs.js → python-BUa4XFG-.js} +1 -1
  18. package/out/renderer/assets/{razor-BOMpCo6z.js → razor-BUw8jho0.js} +1 -1
  19. package/out/renderer/assets/{transformers.web-DtSCnG36.js → transformers.web-_PuZoIho.js} +824 -19
  20. package/out/renderer/assets/{tsMode-yAjlPR-D.js → tsMode-8FLRnobw.js} +1 -1
  21. package/out/renderer/assets/{typescript-BiJRCUcL.js → typescript-rdwAjCi4.js} +1 -1
  22. package/out/renderer/assets/{xml-D4PvYeQq.js → xml-HOzT59Qg.js} +1 -1
  23. package/out/renderer/assets/{yaml-BeHVkmnS.js → yaml-B81JfLsW.js} +1 -1
  24. package/out/renderer/index.html +64 -2
  25. package/out/repro-toggle.mjs +305 -0
  26. package/package.json +1 -1
  27. package/src/main/index.ts +2 -0
  28. package/src/main/orchestrator.ts +13 -1
  29. package/src/main/transcripts.ts +134 -0
  30. package/src/preload/index.ts +3 -0
  31. package/src/renderer/App.tsx +25 -11
  32. package/src/renderer/components/FloatingMic.tsx +48 -0
  33. package/src/renderer/components/VoiceInput.tsx +36 -149
  34. package/src/renderer/index.html +62 -0
  35. package/src/renderer/lib/localWhisper.ts +46 -2
  36. package/src/renderer/lib/speech.ts +18 -0
  37. package/src/renderer/styles.css +34 -0
@@ -1,4 +1,4 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./cssMode-BQN8v2ok.js","./lspLanguageFeatures-CHWJx_Tl.js","./htmlMode-M3MApZ4n.js","./jsonMode-CKp2zvZu.js","./javascript-BO_ViZM5.js","./typescript-BiJRCUcL.js"])))=>i.map(i=>d[i]);
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./cssMode-BdYp0fp9.js","./lspLanguageFeatures-8SIcRAMh.js","./htmlMode-CCm0aJ6C.js","./jsonMode-BgCUHaFo.js","./javascript-B2niCouI.js","./typescript-rdwAjCi4.js"])))=>i.map(i=>d[i]);
2
2
  function getDefaultExportFromCjs(x) {
3
3
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
4
4
  }
@@ -19188,7 +19188,7 @@ async function loadTranscriber(onProgress) {
19188
19188
  if (!transcriberPromise) {
19189
19189
  transcriberPromise = (async () => {
19190
19190
  const { pipeline, env: env2 } = await __vitePreload(async () => {
19191
- const { pipeline: pipeline2, env: env3 } = await import("./transformers.web-DtSCnG36.js");
19191
+ const { pipeline: pipeline2, env: env3 } = await import("./transformers.web-_PuZoIho.js");
19192
19192
  return { pipeline: pipeline2, env: env3 };
19193
19193
  }, true ? [] : void 0, import.meta.url);
19194
19194
  env2.allowLocalModels = false;
@@ -19232,7 +19232,7 @@ async function blobToPcm16k(blob) {
19232
19232
  const rendered = await offline.startRendering();
19233
19233
  return rendered.getChannelData(0);
19234
19234
  }
19235
- async function transcribeBlob(blob, onProgress) {
19235
+ async function transcribeBlob(blob, onProgress, onPartial) {
19236
19236
  const pcm = await blobToPcm16k(blob);
19237
19237
  if (!pcm) return "";
19238
19238
  const rms = computeRms(pcm);
@@ -19241,7 +19241,38 @@ async function transcribeBlob(blob, onProgress) {
19241
19241
  return "";
19242
19242
  }
19243
19243
  const transcriber = await loadTranscriber(onProgress);
19244
- const result = await transcriber(pcm);
19244
+ let streamer;
19245
+ if (onPartial) {
19246
+ try {
19247
+ const { WhisperTextStreamer } = await __vitePreload(async () => {
19248
+ const { WhisperTextStreamer: WhisperTextStreamer2 } = await import("./transformers.web-_PuZoIho.js");
19249
+ return { WhisperTextStreamer: WhisperTextStreamer2 };
19250
+ }, true ? [] : void 0, import.meta.url);
19251
+ const tokenizer = transcriber.tokenizer;
19252
+ let committed = "";
19253
+ let current = "";
19254
+ streamer = new WhisperTextStreamer(tokenizer, {
19255
+ on_chunk_start: () => {
19256
+ current = "";
19257
+ },
19258
+ callback_function: (delta) => {
19259
+ current += delta;
19260
+ onPartial((committed + current).replace(/\s+/g, " ").trim());
19261
+ },
19262
+ on_chunk_end: () => {
19263
+ committed = (committed + current).trim() + " ";
19264
+ current = "";
19265
+ }
19266
+ });
19267
+ } catch (err) {
19268
+ console.warn("[voice] streaming preview unavailable", err);
19269
+ }
19270
+ }
19271
+ const result = await transcriber(pcm, {
19272
+ chunk_length_s: 30,
19273
+ stride_length_s: 5,
19274
+ ...streamer ? { streamer } : {}
19275
+ });
19245
19276
  const text2 = (Array.isArray(result) ? result.map((r) => r.text).join(" ") : result?.text || "").trim();
19246
19277
  if (text2 && rms < LOW_CONFIDENCE_RMS && isFillerOnly(text2)) {
19247
19278
  console.info(`[voice] dropping filler-only output "${text2}" from quiet clip (rms=${rms.toFixed(4)})`);
@@ -19249,11 +19280,6 @@ async function transcribeBlob(blob, onProgress) {
19249
19280
  }
19250
19281
  return text2;
19251
19282
  }
19252
- function getRecognitionCtor() {
19253
- const w = window;
19254
- return w.SpeechRecognition || w.webkitSpeechRecognition || null;
19255
- }
19256
- const ENGINE_KEY$1 = "ctlsurf.voiceEngine";
19257
19283
  const DEVICE_KEY = "ctlsurf.voiceDeviceId";
19258
19284
  function loadDeviceId() {
19259
19285
  try {
@@ -19262,20 +19288,7 @@ function loadDeviceId() {
19262
19288
  return null;
19263
19289
  }
19264
19290
  }
19265
- const WEB_SPEECH_SUPPORTED = getRecognitionCtor() !== null;
19266
19291
  const LOCAL_SUPPORTED = typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia && typeof MediaRecorder !== "undefined" && typeof OfflineAudioContext !== "undefined";
19267
- const ANY_SUPPORTED = WEB_SPEECH_SUPPORTED || LOCAL_SUPPORTED;
19268
- function loadInitialEngine() {
19269
- if (!WEB_SPEECH_SUPPORTED && LOCAL_SUPPORTED) return "local";
19270
- try {
19271
- if (localStorage.getItem(ENGINE_KEY$1) === "local" && LOCAL_SUPPORTED) return "local";
19272
- } catch {
19273
- }
19274
- return WEB_SPEECH_SUPPORTED ? "web-speech" : "local";
19275
- }
19276
- function isEngineUnavailable(code) {
19277
- return code === "network" || code === "service-not-allowed";
19278
- }
19279
19292
  function describeMicError(err) {
19280
19293
  const name = err?.name;
19281
19294
  if (name === "NotAllowedError" || name === "SecurityError") return "Microphone access denied";
@@ -19283,10 +19296,9 @@ function describeMicError(err) {
19283
19296
  return "Could not start microphone";
19284
19297
  }
19285
19298
  function VoiceInput({ onTranscript, variant = "titlebar" }) {
19286
- const [engine, setEngine2] = reactExports.useState(loadInitialEngine);
19287
19299
  const [phase, setPhase] = reactExports.useState("idle");
19288
- const [interim, setInterim] = reactExports.useState("");
19289
19300
  const [modelPct, setModelPct] = reactExports.useState(null);
19301
+ const [partial, setPartial] = reactExports.useState("");
19290
19302
  const [error, setError] = reactExports.useState(null);
19291
19303
  const [notice, setNotice] = reactExports.useState(null);
19292
19304
  const [devices, setDevices] = reactExports.useState([]);
@@ -19297,16 +19309,10 @@ function VoiceInput({ onTranscript, variant = "titlebar" }) {
19297
19309
  selectedDeviceIdRef.current = selectedDeviceId;
19298
19310
  }, [selectedDeviceId]);
19299
19311
  const wrapRef = reactExports.useRef(null);
19300
- const recognitionRef = reactExports.useRef(null);
19301
- const finalRef = reactExports.useRef("");
19302
19312
  const streamRef = reactExports.useRef(null);
19303
19313
  const recorderRef = reactExports.useRef(null);
19304
19314
  const chunksRef = reactExports.useRef([]);
19305
19315
  const cancelGestureRef = reactExports.useRef(false);
19306
- const engineRef = reactExports.useRef(engine);
19307
- reactExports.useEffect(() => {
19308
- engineRef.current = engine;
19309
- }, [engine]);
19310
19316
  const onTranscriptRef = reactExports.useRef(onTranscript);
19311
19317
  reactExports.useEffect(() => {
19312
19318
  onTranscriptRef.current = onTranscript;
@@ -19321,14 +19327,6 @@ function VoiceInput({ onTranscript, variant = "titlebar" }) {
19321
19327
  const t = setTimeout(() => setNotice(null), 5e3);
19322
19328
  return () => clearTimeout(t);
19323
19329
  }, [notice]);
19324
- const switchToLocal = reactExports.useCallback((reason) => {
19325
- try {
19326
- localStorage.setItem(ENGINE_KEY$1, "local");
19327
- } catch {
19328
- }
19329
- setEngine2("local");
19330
- setNotice(reason);
19331
- }, []);
19332
19330
  const stopStream = reactExports.useCallback(() => {
19333
19331
  streamRef.current?.getTracks().forEach((t) => t.stop());
19334
19332
  streamRef.current = null;
@@ -19406,60 +19404,6 @@ function VoiceInput({ onTranscript, variant = "titlebar" }) {
19406
19404
  document.addEventListener("mousedown", onDocDown);
19407
19405
  return () => document.removeEventListener("mousedown", onDocDown);
19408
19406
  }, [showDevicePicker]);
19409
- const startWebSpeech = reactExports.useCallback(() => {
19410
- const Ctor = getRecognitionCtor();
19411
- if (!Ctor || recognitionRef.current) return;
19412
- setError(null);
19413
- setNotice(null);
19414
- setInterim("");
19415
- finalRef.current = "";
19416
- const rec = new Ctor();
19417
- rec.lang = navigator.language || "en-US";
19418
- rec.continuous = true;
19419
- rec.interimResults = true;
19420
- rec.onresult = (event) => {
19421
- let finalText = "";
19422
- let interimText = "";
19423
- for (let i2 = 0; i2 < event.results.length; i2++) {
19424
- const res = event.results[i2];
19425
- if (res.isFinal) finalText += res[0].transcript;
19426
- else interimText += res[0].transcript;
19427
- }
19428
- finalRef.current = finalText;
19429
- setInterim(interimText);
19430
- };
19431
- rec.onerror = (event) => {
19432
- if (isEngineUnavailable(event.error) && LOCAL_SUPPORTED) {
19433
- finalRef.current = "";
19434
- switchToLocal("Voice service unavailable — switched to on-device. Press again.");
19435
- } else if (event.error !== "no-speech" && event.error !== "aborted") {
19436
- setError(event.error === "not-allowed" ? "Microphone access denied" : `Voice error: ${event.error}`);
19437
- }
19438
- };
19439
- rec.onend = () => {
19440
- const text2 = finalRef.current.trim();
19441
- recognitionRef.current = null;
19442
- setPhase("idle");
19443
- setInterim("");
19444
- if (text2) onTranscriptRef.current(text2);
19445
- };
19446
- recognitionRef.current = rec;
19447
- try {
19448
- rec.start();
19449
- setPhase("listening");
19450
- } catch (err) {
19451
- recognitionRef.current = null;
19452
- setPhase("idle");
19453
- setError("Could not start microphone");
19454
- console.error("[voice] web speech start failed", err);
19455
- }
19456
- }, [switchToLocal]);
19457
- const stopWebSpeech = reactExports.useCallback(() => {
19458
- try {
19459
- recognitionRef.current?.stop();
19460
- } catch {
19461
- }
19462
- }, []);
19463
19407
  const handleModelProgress = reactExports.useCallback((p) => {
19464
19408
  if (p.status === "progress" && typeof p.progress === "number") {
19465
19409
  setModelPct(Math.min(100, Math.round(p.progress)));
@@ -19475,9 +19419,12 @@ function VoiceInput({ onTranscript, variant = "titlebar" }) {
19475
19419
  return;
19476
19420
  }
19477
19421
  setPhase("transcribing");
19478
- setInterim("");
19422
+ setPartial("");
19479
19423
  try {
19480
- const text2 = await transcribeBlob(blob, handleModelProgress);
19424
+ const text2 = await transcribeBlob(blob, handleModelProgress, (t) => {
19425
+ setModelPct(null);
19426
+ setPartial(t);
19427
+ });
19481
19428
  if (text2) onTranscriptRef.current(text2);
19482
19429
  else setNotice("No speech detected — check the mic source (▾).");
19483
19430
  } catch (err) {
@@ -19486,12 +19433,12 @@ function VoiceInput({ onTranscript, variant = "titlebar" }) {
19486
19433
  } finally {
19487
19434
  setPhase("idle");
19488
19435
  setModelPct(null);
19436
+ setPartial("");
19489
19437
  }
19490
19438
  }, [stopStream, handleModelProgress]);
19491
19439
  const startLocal = reactExports.useCallback(async () => {
19492
19440
  setError(null);
19493
19441
  setNotice(null);
19494
- setInterim("");
19495
19442
  cancelGestureRef.current = false;
19496
19443
  try {
19497
19444
  const stream = await getStream();
@@ -19530,22 +19477,16 @@ function VoiceInput({ onTranscript, variant = "titlebar" }) {
19530
19477
  }
19531
19478
  }, []);
19532
19479
  const handlePointerDown = (e) => {
19533
- if (!ANY_SUPPORTED || phase !== "idle") return;
19480
+ if (!LOCAL_SUPPORTED || phase !== "idle") return;
19534
19481
  e.preventDefault();
19535
19482
  e.currentTarget.setPointerCapture?.(e.pointerId);
19536
- if (engineRef.current === "web-speech" && WEB_SPEECH_SUPPORTED) startWebSpeech();
19537
- else if (LOCAL_SUPPORTED) void startLocal();
19483
+ void startLocal();
19538
19484
  };
19539
19485
  const handlePointerUp = (e) => {
19540
19486
  e.currentTarget.releasePointerCapture?.(e.pointerId);
19541
- if (engineRef.current === "web-speech") stopWebSpeech();
19542
- else stopLocal();
19487
+ stopLocal();
19543
19488
  };
19544
19489
  reactExports.useEffect(() => () => {
19545
- try {
19546
- recognitionRef.current?.abort();
19547
- } catch {
19548
- }
19549
19490
  try {
19550
19491
  recorderRef.current?.stop();
19551
19492
  } catch {
@@ -19554,12 +19495,18 @@ function VoiceInput({ onTranscript, variant = "titlebar" }) {
19554
19495
  }, []);
19555
19496
  const listening = phase === "listening";
19556
19497
  const busy = phase === "transcribing";
19557
- const title = !ANY_SUPPORTED ? "Voice typing not supported in this build" : listening ? "Listening… release to insert" : busy ? "Transcribing…" : engine === "local" ? "Hold to talk (on-device) — speech is typed into the terminal" : "Hold to talk — speech is typed into the terminal";
19498
+ const title = !LOCAL_SUPPORTED ? "Voice typing not supported in this build" : listening ? "Listening… release to insert" : busy ? "Transcribing…" : "Hold to talk (on-device) — speech is typed into the terminal";
19558
19499
  let chip = null;
19559
19500
  if (error && phase === "idle") chip = { kind: "error", text: error };
19560
19501
  else if (notice && phase === "idle") chip = { kind: "notice", text: notice };
19561
- else if (listening) chip = { kind: "listening", text: interim || (engine === "local" ? "Recording…" : "Listening…") };
19562
- else if (busy) chip = { kind: "busy", text: modelPct !== null ? `Downloading voice model… ${modelPct}%` : "Transcribing…" };
19502
+ else if (listening) chip = { kind: "listening", text: "Recording…" };
19503
+ else if (busy) {
19504
+ const preview = partial.length > 90 ? "…" + partial.slice(-90) : partial;
19505
+ chip = {
19506
+ kind: "busy",
19507
+ text: modelPct !== null ? `Downloading voice model… ${modelPct}%` : preview || "Transcribing…"
19508
+ };
19509
+ }
19563
19510
  const floating = variant === "floating";
19564
19511
  const btnClass = floating ? `voice-btn voice-btn-floating ${listening ? "listening" : ""} ${busy ? "busy" : ""}` : `titlebar-btn titlebar-icon-btn voice-btn ${listening ? "listening" : ""} ${busy ? "busy" : ""}`;
19565
19512
  const realDevices = devices.filter(
@@ -19575,7 +19522,7 @@ function VoiceInput({ onTranscript, variant = "titlebar" }) {
19575
19522
  {
19576
19523
  type: "button",
19577
19524
  className: btnClass,
19578
- disabled: !ANY_SUPPORTED,
19525
+ disabled: !LOCAL_SUPPORTED,
19579
19526
  onPointerDown: handlePointerDown,
19580
19527
  onPointerUp: handlePointerUp,
19581
19528
  onPointerCancel: handlePointerUp,
@@ -19637,108 +19584,6 @@ function VoiceInput({ onTranscript, variant = "titlebar" }) {
19637
19584
  chip && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `voice-chip ${chip.kind} ${floating ? "voice-chip-floating" : ""}`, children: chip.text })
19638
19585
  ] });
19639
19586
  }
19640
- const POS_KEY = "ctlsurf.floatingMicPos";
19641
- const EDGE = 20;
19642
- const TOP_MIN = 46;
19643
- const BOTTOM_GAP = 36;
19644
- function loadPos() {
19645
- try {
19646
- const raw = localStorage.getItem(POS_KEY);
19647
- if (raw) {
19648
- const p = JSON.parse(raw);
19649
- if (typeof p.x === "number" && typeof p.y === "number") return { x: p.x, y: p.y };
19650
- }
19651
- } catch {
19652
- }
19653
- return null;
19654
- }
19655
- function FloatingMic({ onTranscript, onHide }) {
19656
- const [pos, setPos] = reactExports.useState(loadPos);
19657
- const elRef = reactExports.useRef(null);
19658
- const dragRef = reactExports.useRef(null);
19659
- const clamp2 = reactExports.useCallback((x, y) => {
19660
- const el = elRef.current;
19661
- const w = el?.offsetWidth ?? 64;
19662
- const h2 = el?.offsetHeight ?? 90;
19663
- return {
19664
- x: Math.max(EDGE, Math.min(x, window.innerWidth - w - EDGE)),
19665
- y: Math.max(TOP_MIN, Math.min(y, window.innerHeight - h2 - BOTTOM_GAP))
19666
- };
19667
- }, []);
19668
- reactExports.useEffect(() => {
19669
- if (pos) return;
19670
- const el = elRef.current;
19671
- const w = el?.offsetWidth ?? 64;
19672
- const h2 = el?.offsetHeight ?? 90;
19673
- setPos({
19674
- x: window.innerWidth - w - EDGE,
19675
- y: window.innerHeight - h2 - BOTTOM_GAP
19676
- });
19677
- }, [pos]);
19678
- reactExports.useEffect(() => {
19679
- const onResize = () => setPos((p) => p ? clamp2(p.x, p.y) : p);
19680
- window.addEventListener("resize", onResize);
19681
- return () => window.removeEventListener("resize", onResize);
19682
- }, [clamp2]);
19683
- const onHandleDown = reactExports.useCallback((e) => {
19684
- const el = elRef.current;
19685
- if (!el) return;
19686
- e.preventDefault();
19687
- const rect = el.getBoundingClientRect();
19688
- dragRef.current = { dx: e.clientX - rect.left, dy: e.clientY - rect.top };
19689
- e.currentTarget.setPointerCapture?.(e.pointerId);
19690
- }, []);
19691
- const onHandleMove = reactExports.useCallback((e) => {
19692
- const d = dragRef.current;
19693
- if (!d) return;
19694
- setPos(clamp2(e.clientX - d.dx, e.clientY - d.dy));
19695
- }, [clamp2]);
19696
- const onHandleUp = reactExports.useCallback((e) => {
19697
- if (!dragRef.current) return;
19698
- dragRef.current = null;
19699
- e.currentTarget.releasePointerCapture?.(e.pointerId);
19700
- setPos((p) => {
19701
- if (p) {
19702
- try {
19703
- localStorage.setItem(POS_KEY, JSON.stringify(p));
19704
- } catch {
19705
- }
19706
- }
19707
- return p;
19708
- });
19709
- }, []);
19710
- const style = pos ? { left: pos.x, top: pos.y } : { left: -9999, top: -9999, visibility: "hidden" };
19711
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref: elRef, className: "floating-mic", style, children: [
19712
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
19713
- "div",
19714
- {
19715
- className: "floating-mic-handle",
19716
- onPointerDown: onHandleDown,
19717
- onPointerMove: onHandleMove,
19718
- onPointerUp: onHandleUp,
19719
- onPointerCancel: onHandleUp,
19720
- title: "Drag to move",
19721
- "aria-label": "Drag floating mic",
19722
- children: [
19723
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "floating-mic-grip", "aria-hidden": "true", children: "⠿" }),
19724
- /* @__PURE__ */ jsxRuntimeExports.jsx(
19725
- "button",
19726
- {
19727
- type: "button",
19728
- className: "floating-mic-hide",
19729
- onPointerDown: (e) => e.stopPropagation(),
19730
- onClick: onHide,
19731
- title: "Hide floating mic",
19732
- "aria-label": "Hide floating mic",
19733
- children: "×"
19734
- }
19735
- )
19736
- ]
19737
- }
19738
- ),
19739
- /* @__PURE__ */ jsxRuntimeExports.jsx(VoiceInput, { variant: "floating", onTranscript })
19740
- ] });
19741
- }
19742
19587
  const ENGINE_KEY = "ctlsurf.tts.engine";
19743
19588
  const VOICE_KEY = "ctlsurf.tts.voiceURI";
19744
19589
  const RATE_KEY = "ctlsurf.tts.rate";
@@ -19833,7 +19678,7 @@ function loadSynthesizer(onProgress) {
19833
19678
  if (!synthPromise) {
19834
19679
  synthPromise = (async () => {
19835
19680
  const { pipeline, env: env2 } = await __vitePreload(async () => {
19836
- const { pipeline: pipeline2, env: env3 } = await import("./transformers.web-DtSCnG36.js");
19681
+ const { pipeline: pipeline2, env: env3 } = await import("./transformers.web-_PuZoIho.js");
19837
19682
  return { pipeline: pipeline2, env: env3 };
19838
19683
  }, true ? [] : void 0, import.meta.url);
19839
19684
  env2.allowLocalModels = false;
@@ -19871,6 +19716,22 @@ class SpeechController {
19871
19716
  // the visible Stop button.
19872
19717
  onActivityChange = null;
19873
19718
  onError = null;
19719
+ // Multiple UI surfaces (titlebar SpeakControls + floating read button) need
19720
+ // the speaking state at once, so beyond the single onActivityChange slot we
19721
+ // also keep a listener set.
19722
+ activityListeners = /* @__PURE__ */ new Set();
19723
+ get isActive() {
19724
+ return this.active;
19725
+ }
19726
+ // Subscribe to speaking-state changes. Fires immediately with the current
19727
+ // state so a late subscriber syncs. Returns an unsubscribe function.
19728
+ onActivity(fn) {
19729
+ this.activityListeners.add(fn);
19730
+ fn(this.active);
19731
+ return () => {
19732
+ this.activityListeners.delete(fn);
19733
+ };
19734
+ }
19874
19735
  // Prime/resume the AudioContext from a user gesture so neural playback isn't
19875
19736
  // blocked by the browser autoplay policy (the System engine is unaffected).
19876
19737
  unlock() {
@@ -19925,6 +19786,7 @@ class SpeechController {
19925
19786
  if (this.active === a) return;
19926
19787
  this.active = a;
19927
19788
  this.onActivityChange?.(a);
19789
+ this.activityListeners.forEach((fn) => fn(a));
19928
19790
  }
19929
19791
  async drain() {
19930
19792
  if (this.draining) return;
@@ -20003,6 +19865,162 @@ class SpeechController {
20003
19865
  }
20004
19866
  }
20005
19867
  const speech = new SpeechController();
19868
+ const POS_KEY = "ctlsurf.floatingMicPos";
19869
+ const EDGE = 20;
19870
+ const TOP_MIN = 46;
19871
+ const BOTTOM_GAP = 36;
19872
+ function loadPos() {
19873
+ try {
19874
+ const raw = localStorage.getItem(POS_KEY);
19875
+ if (raw) {
19876
+ const p = JSON.parse(raw);
19877
+ if (typeof p.x === "number" && typeof p.y === "number") return { x: p.x, y: p.y };
19878
+ }
19879
+ } catch {
19880
+ }
19881
+ return null;
19882
+ }
19883
+ function FloatingMic({ onTranscript, onHide }) {
19884
+ const [pos, setPos] = reactExports.useState(loadPos);
19885
+ const elRef = reactExports.useRef(null);
19886
+ const dragRef = reactExports.useRef(null);
19887
+ const [speaking, setSpeaking] = reactExports.useState(false);
19888
+ const [note, setNote] = reactExports.useState(null);
19889
+ reactExports.useEffect(() => speech.onActivity(setSpeaking), []);
19890
+ reactExports.useEffect(() => {
19891
+ if (!note) return;
19892
+ const t = setTimeout(() => setNote(null), 3e3);
19893
+ return () => clearTimeout(t);
19894
+ }, [note]);
19895
+ const onReadAloud = reactExports.useCallback(async () => {
19896
+ if (speaking) {
19897
+ speech.stop();
19898
+ return;
19899
+ }
19900
+ speech.unlock();
19901
+ let text2 = null;
19902
+ try {
19903
+ text2 = await window.worker.getLastReply();
19904
+ } catch {
19905
+ }
19906
+ if (text2) speech.enqueue(text2);
19907
+ else setNote("No reply to read yet");
19908
+ }, [speaking]);
19909
+ const clamp2 = reactExports.useCallback((x, y) => {
19910
+ const el = elRef.current;
19911
+ const w = el?.offsetWidth ?? 64;
19912
+ const h2 = el?.offsetHeight ?? 90;
19913
+ return {
19914
+ x: Math.max(EDGE, Math.min(x, window.innerWidth - w - EDGE)),
19915
+ y: Math.max(TOP_MIN, Math.min(y, window.innerHeight - h2 - BOTTOM_GAP))
19916
+ };
19917
+ }, []);
19918
+ reactExports.useEffect(() => {
19919
+ if (pos) return;
19920
+ const el = elRef.current;
19921
+ const w = el?.offsetWidth ?? 64;
19922
+ const h2 = el?.offsetHeight ?? 90;
19923
+ setPos({
19924
+ x: window.innerWidth - w - EDGE,
19925
+ y: window.innerHeight - h2 - BOTTOM_GAP
19926
+ });
19927
+ }, [pos]);
19928
+ reactExports.useEffect(() => {
19929
+ const onResize = () => setPos((p) => p ? clamp2(p.x, p.y) : p);
19930
+ window.addEventListener("resize", onResize);
19931
+ return () => window.removeEventListener("resize", onResize);
19932
+ }, [clamp2]);
19933
+ const onHandleDown = reactExports.useCallback((e) => {
19934
+ const el = elRef.current;
19935
+ if (!el) return;
19936
+ e.preventDefault();
19937
+ const rect = el.getBoundingClientRect();
19938
+ dragRef.current = { dx: e.clientX - rect.left, dy: e.clientY - rect.top };
19939
+ e.currentTarget.setPointerCapture?.(e.pointerId);
19940
+ }, []);
19941
+ const onHandleMove = reactExports.useCallback((e) => {
19942
+ const d = dragRef.current;
19943
+ if (!d) return;
19944
+ setPos(clamp2(e.clientX - d.dx, e.clientY - d.dy));
19945
+ }, [clamp2]);
19946
+ const onHandleUp = reactExports.useCallback((e) => {
19947
+ if (!dragRef.current) return;
19948
+ dragRef.current = null;
19949
+ e.currentTarget.releasePointerCapture?.(e.pointerId);
19950
+ setPos((p) => {
19951
+ if (p) {
19952
+ try {
19953
+ localStorage.setItem(POS_KEY, JSON.stringify(p));
19954
+ } catch {
19955
+ }
19956
+ }
19957
+ return p;
19958
+ });
19959
+ }, []);
19960
+ const style = pos ? { left: pos.x, top: pos.y } : { left: -9999, top: -9999, visibility: "hidden" };
19961
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref: elRef, className: "floating-mic", style, children: [
19962
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
19963
+ "div",
19964
+ {
19965
+ className: "floating-mic-handle",
19966
+ onPointerDown: onHandleDown,
19967
+ onPointerMove: onHandleMove,
19968
+ onPointerUp: onHandleUp,
19969
+ onPointerCancel: onHandleUp,
19970
+ title: "Drag to move",
19971
+ "aria-label": "Drag floating mic",
19972
+ children: [
19973
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "floating-mic-grip", "aria-hidden": "true", children: "⠿" }),
19974
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
19975
+ "button",
19976
+ {
19977
+ type: "button",
19978
+ className: "floating-mic-hide",
19979
+ onPointerDown: (e) => e.stopPropagation(),
19980
+ onClick: onHide,
19981
+ title: "Hide floating mic",
19982
+ "aria-label": "Hide floating mic",
19983
+ children: "×"
19984
+ }
19985
+ )
19986
+ ]
19987
+ }
19988
+ ),
19989
+ /* @__PURE__ */ jsxRuntimeExports.jsx(VoiceInput, { variant: "floating", onTranscript }),
19990
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
19991
+ "button",
19992
+ {
19993
+ type: "button",
19994
+ className: `floating-mic-read ${speaking ? "speaking" : ""}`,
19995
+ onPointerDown: (e) => e.stopPropagation(),
19996
+ onClick: onReadAloud,
19997
+ title: speaking ? "Stop reading" : "Read last agent reply aloud",
19998
+ "aria-label": speaking ? "Stop reading" : "Read last agent reply aloud",
19999
+ "aria-pressed": speaking,
20000
+ children: speaking ? /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { viewBox: "0 0 24 24", width: "14", height: "14", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: "6", y: "6", width: "12", height: "12", rx: "2" }) }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(
20001
+ "svg",
20002
+ {
20003
+ viewBox: "0 0 24 24",
20004
+ width: "15",
20005
+ height: "15",
20006
+ fill: "none",
20007
+ stroke: "currentColor",
20008
+ strokeWidth: "2",
20009
+ strokeLinecap: "round",
20010
+ strokeLinejoin: "round",
20011
+ "aria-hidden": "true",
20012
+ children: [
20013
+ /* @__PURE__ */ jsxRuntimeExports.jsx("polygon", { points: "11 5 6 9 2 9 2 15 6 15 11 19 11 5" }),
20014
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M15.54 8.46a5 5 0 0 1 0 7.07" }),
20015
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M19.07 4.93a10 10 0 0 1 0 14.14" })
20016
+ ]
20017
+ }
20018
+ )
20019
+ }
20020
+ ),
20021
+ note && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "floating-mic-note", children: note })
20022
+ ] });
20023
+ }
20006
20024
  const SAMPLE = "This is how spoken agent replies will sound.";
20007
20025
  function SpeakControls() {
20008
20026
  const [enabled, setEnabled] = reactExports.useState(false);
@@ -207823,7 +207841,7 @@ const lessDefaults = new LanguageServiceDefaultsImpl$3(
207823
207841
  modeConfigurationDefault$2
207824
207842
  );
207825
207843
  function getMode$3() {
207826
- return __vitePreload(() => import("./cssMode-BQN8v2ok.js"), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
207844
+ return __vitePreload(() => import("./cssMode-BdYp0fp9.js"), true ? __vite__mapDeps([0,1]) : void 0, import.meta.url);
207827
207845
  }
207828
207846
  languages.onLanguage("less", () => {
207829
207847
  getMode$3().then((mode2) => mode2.setupMode(lessDefaults));
@@ -207928,7 +207946,7 @@ const razorLanguageService = registerHTMLLanguageService(
207928
207946
  );
207929
207947
  const razorDefaults = razorLanguageService.defaults;
207930
207948
  function getMode$2() {
207931
- return __vitePreload(() => import("./htmlMode-M3MApZ4n.js"), true ? __vite__mapDeps([2,1]) : void 0, import.meta.url);
207949
+ return __vitePreload(() => import("./htmlMode-CCm0aJ6C.js"), true ? __vite__mapDeps([2,1]) : void 0, import.meta.url);
207932
207950
  }
207933
207951
  function registerHTMLLanguageService(languageId, options = optionsDefault, modeConfiguration = getConfigurationDefault(languageId)) {
207934
207952
  const defaults = new LanguageServiceDefaultsImpl$2(languageId, options, modeConfiguration);
@@ -208012,7 +208030,7 @@ const jsonDefaults = new LanguageServiceDefaultsImpl$1(
208012
208030
  );
208013
208031
  const getWorker$1 = () => getMode$1().then((mode2) => mode2.getWorker());
208014
208032
  function getMode$1() {
208015
- return __vitePreload(() => import("./jsonMode-CKp2zvZu.js"), true ? __vite__mapDeps([3,1]) : void 0, import.meta.url);
208033
+ return __vitePreload(() => import("./jsonMode-BgCUHaFo.js"), true ? __vite__mapDeps([3,1]) : void 0, import.meta.url);
208016
208034
  }
208017
208035
  languages.register({
208018
208036
  id: "json",
@@ -208258,7 +208276,7 @@ const getJavaScriptWorker = () => {
208258
208276
  return getMode().then((mode) => mode.getJavaScriptWorker());
208259
208277
  };
208260
208278
  function getMode() {
208261
- return __vitePreload(() => import("./tsMode-yAjlPR-D.js"), true ? [] : void 0, import.meta.url);
208279
+ return __vitePreload(() => import("./tsMode-8FLRnobw.js"), true ? [] : void 0, import.meta.url);
208262
208280
  }
208263
208281
  languages.onLanguage("typescript", () => {
208264
208282
  return getMode().then((mode) => mode.setupTypeScript(typescriptDefaults));
@@ -208453,49 +208471,49 @@ registerLanguage({
208453
208471
  extensions: [".ftl", ".ftlh", ".ftlx"],
208454
208472
  aliases: ["FreeMarker2", "Apache FreeMarker2"],
208455
208473
  loader: () => {
208456
- return __vitePreload(() => import("./freemarker2-DbxGYYVp.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
208474
+ return __vitePreload(() => import("./freemarker2-Cya-9zr-.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
208457
208475
  }
208458
208476
  });
208459
208477
  registerLanguage({
208460
208478
  id: "freemarker2.tag-angle.interpolation-dollar",
208461
208479
  aliases: ["FreeMarker2 (Angle/Dollar)", "Apache FreeMarker2 (Angle/Dollar)"],
208462
208480
  loader: () => {
208463
- return __vitePreload(() => import("./freemarker2-DbxGYYVp.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationDollar);
208481
+ return __vitePreload(() => import("./freemarker2-Cya-9zr-.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationDollar);
208464
208482
  }
208465
208483
  });
208466
208484
  registerLanguage({
208467
208485
  id: "freemarker2.tag-bracket.interpolation-dollar",
208468
208486
  aliases: ["FreeMarker2 (Bracket/Dollar)", "Apache FreeMarker2 (Bracket/Dollar)"],
208469
208487
  loader: () => {
208470
- return __vitePreload(() => import("./freemarker2-DbxGYYVp.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationDollar);
208488
+ return __vitePreload(() => import("./freemarker2-Cya-9zr-.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationDollar);
208471
208489
  }
208472
208490
  });
208473
208491
  registerLanguage({
208474
208492
  id: "freemarker2.tag-angle.interpolation-bracket",
208475
208493
  aliases: ["FreeMarker2 (Angle/Bracket)", "Apache FreeMarker2 (Angle/Bracket)"],
208476
208494
  loader: () => {
208477
- return __vitePreload(() => import("./freemarker2-DbxGYYVp.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationBracket);
208495
+ return __vitePreload(() => import("./freemarker2-Cya-9zr-.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAngleInterpolationBracket);
208478
208496
  }
208479
208497
  });
208480
208498
  registerLanguage({
208481
208499
  id: "freemarker2.tag-bracket.interpolation-bracket",
208482
208500
  aliases: ["FreeMarker2 (Bracket/Bracket)", "Apache FreeMarker2 (Bracket/Bracket)"],
208483
208501
  loader: () => {
208484
- return __vitePreload(() => import("./freemarker2-DbxGYYVp.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationBracket);
208502
+ return __vitePreload(() => import("./freemarker2-Cya-9zr-.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagBracketInterpolationBracket);
208485
208503
  }
208486
208504
  });
208487
208505
  registerLanguage({
208488
208506
  id: "freemarker2.tag-auto.interpolation-dollar",
208489
208507
  aliases: ["FreeMarker2 (Auto/Dollar)", "Apache FreeMarker2 (Auto/Dollar)"],
208490
208508
  loader: () => {
208491
- return __vitePreload(() => import("./freemarker2-DbxGYYVp.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
208509
+ return __vitePreload(() => import("./freemarker2-Cya-9zr-.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationDollar);
208492
208510
  }
208493
208511
  });
208494
208512
  registerLanguage({
208495
208513
  id: "freemarker2.tag-auto.interpolation-bracket",
208496
208514
  aliases: ["FreeMarker2 (Auto/Bracket)", "Apache FreeMarker2 (Auto/Bracket)"],
208497
208515
  loader: () => {
208498
- return __vitePreload(() => import("./freemarker2-DbxGYYVp.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationBracket);
208516
+ return __vitePreload(() => import("./freemarker2-Cya-9zr-.js"), true ? [] : void 0, import.meta.url).then((m) => m.TagAutoInterpolationBracket);
208499
208517
  }
208500
208518
  });
208501
208519
  registerLanguage({
@@ -208516,7 +208534,7 @@ registerLanguage({
208516
208534
  extensions: [".handlebars", ".hbs"],
208517
208535
  aliases: ["Handlebars", "handlebars", "hbs"],
208518
208536
  mimetypes: ["text/x-handlebars-template"],
208519
- loader: () => __vitePreload(() => import("./handlebars-3auU1CAd.js"), true ? [] : void 0, import.meta.url)
208537
+ loader: () => __vitePreload(() => import("./handlebars-EJs9W5Ub.js"), true ? [] : void 0, import.meta.url)
208520
208538
  });
208521
208539
  registerLanguage({
208522
208540
  id: "hcl",
@@ -208529,7 +208547,7 @@ registerLanguage({
208529
208547
  extensions: [".html", ".htm", ".shtml", ".xhtml", ".mdoc", ".jsp", ".asp", ".aspx", ".jshtm"],
208530
208548
  aliases: ["HTML", "htm", "html", "xhtml"],
208531
208549
  mimetypes: ["text/html", "text/x-jshtm", "text/template", "text/ng-template"],
208532
- loader: () => __vitePreload(() => import("./html-D8xFiRmI.js"), true ? [] : void 0, import.meta.url)
208550
+ loader: () => __vitePreload(() => import("./html-Ex4qy2lh.js"), true ? [] : void 0, import.meta.url)
208533
208551
  });
208534
208552
  registerLanguage({
208535
208553
  id: "ini",
@@ -208552,7 +208570,7 @@ registerLanguage({
208552
208570
  filenames: ["jakefile"],
208553
208571
  aliases: ["JavaScript", "javascript", "js"],
208554
208572
  mimetypes: ["text/javascript"],
208555
- loader: () => __vitePreload(() => import("./javascript-BO_ViZM5.js"), true ? __vite__mapDeps([4,5]) : void 0, import.meta.url)
208573
+ loader: () => __vitePreload(() => import("./javascript-B2niCouI.js"), true ? __vite__mapDeps([4,5]) : void 0, import.meta.url)
208556
208574
  });
208557
208575
  registerLanguage({
208558
208576
  id: "julia",
@@ -208591,7 +208609,7 @@ registerLanguage({
208591
208609
  extensions: [".liquid", ".html.liquid"],
208592
208610
  aliases: ["Liquid", "liquid"],
208593
208611
  mimetypes: ["application/liquid"],
208594
- loader: () => __vitePreload(() => import("./liquid-C1eHcrht.js"), true ? [] : void 0, import.meta.url)
208612
+ loader: () => __vitePreload(() => import("./liquid-DOQmSo4G.js"), true ? [] : void 0, import.meta.url)
208595
208613
  });
208596
208614
  registerLanguage({
208597
208615
  id: "m3",
@@ -208609,7 +208627,7 @@ registerLanguage({
208609
208627
  id: "mdx",
208610
208628
  extensions: [".mdx"],
208611
208629
  aliases: ["MDX", "mdx"],
208612
- loader: () => __vitePreload(() => import("./mdx-Qqdtk7fL.js"), true ? [] : void 0, import.meta.url)
208630
+ loader: () => __vitePreload(() => import("./mdx-DuKdGNKE.js"), true ? [] : void 0, import.meta.url)
208613
208631
  });
208614
208632
  registerLanguage({
208615
208633
  id: "mips",
@@ -208708,7 +208726,7 @@ registerLanguage({
208708
208726
  extensions: [".py", ".rpy", ".pyw", ".cpy", ".gyp", ".gypi"],
208709
208727
  aliases: ["Python", "py"],
208710
208728
  firstLine: "^#!/.*\\bpython[0-9.-]*\\b",
208711
- loader: () => __vitePreload(() => import("./python-DKu7rNbs.js"), true ? [] : void 0, import.meta.url)
208729
+ loader: () => __vitePreload(() => import("./python-BUa4XFG-.js"), true ? [] : void 0, import.meta.url)
208712
208730
  });
208713
208731
  registerLanguage({
208714
208732
  id: "qsharp",
@@ -208727,7 +208745,7 @@ registerLanguage({
208727
208745
  extensions: [".cshtml"],
208728
208746
  aliases: ["Razor", "razor"],
208729
208747
  mimetypes: ["text/x-cshtml"],
208730
- loader: () => __vitePreload(() => import("./razor-BOMpCo6z.js"), true ? [] : void 0, import.meta.url)
208748
+ loader: () => __vitePreload(() => import("./razor-BUw8jho0.js"), true ? [] : void 0, import.meta.url)
208731
208749
  });
208732
208750
  registerLanguage({
208733
208751
  id: "redis",
@@ -208860,7 +208878,7 @@ registerLanguage({
208860
208878
  aliases: ["TypeScript", "ts", "typescript"],
208861
208879
  mimetypes: ["text/typescript"],
208862
208880
  loader: () => {
208863
- return __vitePreload(() => import("./typescript-BiJRCUcL.js"), true ? [] : void 0, import.meta.url);
208881
+ return __vitePreload(() => import("./typescript-rdwAjCi4.js"), true ? [] : void 0, import.meta.url);
208864
208882
  }
208865
208883
  });
208866
208884
  registerLanguage({
@@ -208905,14 +208923,14 @@ registerLanguage({
208905
208923
  firstLine: "(\\<\\?xml.*)|(\\<svg)|(\\<\\!doctype\\s+svg)",
208906
208924
  aliases: ["XML", "xml"],
208907
208925
  mimetypes: ["text/xml", "application/xml", "application/xaml+xml", "application/xml-dtd"],
208908
- loader: () => __vitePreload(() => import("./xml-D4PvYeQq.js"), true ? [] : void 0, import.meta.url)
208926
+ loader: () => __vitePreload(() => import("./xml-HOzT59Qg.js"), true ? [] : void 0, import.meta.url)
208909
208927
  });
208910
208928
  registerLanguage({
208911
208929
  id: "yaml",
208912
208930
  extensions: [".yaml", ".yml"],
208913
208931
  aliases: ["YAML", "yaml", "YML", "yml"],
208914
208932
  mimetypes: ["application/x-yaml", "text/x-yaml"],
208915
- loader: () => __vitePreload(() => import("./yaml-BeHVkmnS.js"), true ? [] : void 0, import.meta.url)
208933
+ loader: () => __vitePreload(() => import("./yaml-B81JfLsW.js"), true ? [] : void 0, import.meta.url)
208916
208934
  });
208917
208935
  var __defProp = Object.defineProperty;
208918
208936
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -213334,15 +213352,19 @@ function App() {
213334
213352
  const visiblePaneIds = findPaneIds(layout2);
213335
213353
  reactExports.useEffect(() => {
213336
213354
  async function init2() {
213337
- const list2 = await window.worker.listAgents();
213338
- setAgents(list2);
213339
- const status2 = await window.worker.getWorkerStatus();
213340
- setWsStatus(status2);
213341
- const initialCwd = await window.worker.getCwd().catch(() => window.worker.getHomePath());
213342
- setCwd(initialCwd);
213343
- cwdRef.current = initialCwd;
213344
- window.worker.getProjectName().then(setProjectName).catch(() => {
213345
- });
213355
+ try {
213356
+ const list2 = await window.worker.listAgents();
213357
+ setAgents(list2);
213358
+ const status2 = await window.worker.getWorkerStatus();
213359
+ setWsStatus(status2);
213360
+ const initialCwd = await window.worker.getCwd().catch(() => window.worker.getHomePath());
213361
+ setCwd(initialCwd);
213362
+ cwdRef.current = initialCwd;
213363
+ window.worker.getProjectName().then(setProjectName).catch(() => {
213364
+ });
213365
+ } finally {
213366
+ window.__dismissSplash?.();
213367
+ }
213346
213368
  }
213347
213369
  init2();
213348
213370
  }, []);
@@ -213399,8 +213421,9 @@ function App() {
213399
213421
  const handleVoiceTranscript = reactExports.useCallback((text2) => {
213400
213422
  const trimmed = text2.trim();
213401
213423
  if (!trimmed) return;
213402
- window.worker.writePty(activeTabId, trimmed + "\r");
213424
+ window.worker.writePty(activeTabId, trimmed);
213403
213425
  focusTerminal(activeTabId);
213426
+ setTimeout(() => window.worker.writePty(activeTabId, "\r"), 80);
213404
213427
  }, [activeTabId]);
213405
213428
  const cwdRef = reactExports.useRef(null);
213406
213429
  const handleSpawn = reactExports.useCallback(async (tabId, agent) => {