@miphamai/cli 0.32.4 → 0.32.6

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/ui/input.tsx +35 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miphamai/cli",
3
- "version": "0.32.4",
3
+ "version": "0.32.6",
4
4
  "description": "Mipham Code — Multi-model open-core intelligent coding terminal by MiphamAI",
5
5
  "keywords": [
6
6
  "ai",
package/src/ui/input.tsx CHANGED
@@ -79,6 +79,11 @@ export function InputBar({
79
79
  }: InputBarProps) {
80
80
  const { t } = useI18n()
81
81
  const [value, setValue] = useState('')
82
+ // Ref mirror of value — used by useInput to read latest without stale closure.
83
+ // Also used by throttled onChange to hold the latest pending value.
84
+ const valueRef = useRef(value)
85
+ // Throttle timer for onChange — prevents React render floods during paste.
86
+ const onChangeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
82
87
  const [verb, setVerb] = useState(() => t(pick(LOADING_KEYS)))
83
88
  const [completionVerb, setCompletionVerb] = useState<string | null>(null)
84
89
  const prevLoading = useRef(isLoading)
@@ -330,14 +335,27 @@ export function InputBar({
330
335
  prevValueRef.current = value
331
336
  }, [value, vimMode])
332
337
 
338
+ // Keep valueRef in sync with state (so useInput handlers read latest value)
339
+ useEffect(() => {
340
+ valueRef.current = value
341
+ }, [value])
342
+
333
343
  const handleSubmit = (val: string) => {
334
- if (!val.trim() || isLoading) return
344
+ // Flush any pending throttled onChange before submitting
345
+ if (onChangeTimerRef.current) {
346
+ clearTimeout(onChangeTimerRef.current)
347
+ onChangeTimerRef.current = null
348
+ }
349
+ // Use latest value from ref (may be ahead of state during throttle)
350
+ const finalValue = val || valueRef.current
351
+ if (!finalValue.trim() || isLoading) return
335
352
  // Save to message history for arrow-key navigation
336
- setSubmittedHistory((prev) => [...prev, val])
353
+ setSubmittedHistory((prev) => [...prev, finalValue])
337
354
  historyIndexRef.current = -1
338
355
  savedDraftRef.current = ''
339
- onSubmit(val)
356
+ onSubmit(finalValue)
340
357
  setValue('')
358
+ valueRef.current = ''
341
359
  setPickerActive(false)
342
360
  }
343
361
 
@@ -380,7 +398,20 @@ export function InputBar({
380
398
  historyIndexRef.current = -1
381
399
  savedDraftRef.current = ''
382
400
  }
383
- setValue(val)
401
+ // Normalize newlines → spaces. ink-text-input is single-line; multi-line
402
+ // paste would trap arrow-key navigation on the first line.
403
+ const normalized = val.replace(/\n/g, ' ')
404
+ // Throttle: first keystroke renders immediately, then batch at ~30fps.
405
+ // During paste, the terminal may send hundreds of characters in rapid
406
+ // succession — without throttling, each triggers a React render+Ink write
407
+ // cycle that starves the event loop and freezes the UI.
408
+ valueRef.current = normalized
409
+ if (onChangeTimerRef.current) return // timer pending, latest value in ref
410
+ setValue(normalized)
411
+ onChangeTimerRef.current = setTimeout(() => {
412
+ onChangeTimerRef.current = null
413
+ setValue(valueRef.current)
414
+ }, 33) // ~30fps
384
415
  }}
385
416
  onSubmit={handleSubmit}
386
417
  placeholder={