nastech-tui 0.0.1
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.
- package/.prettierrc +11 -0
- package/README.md +346 -0
- package/eslint.config.mjs +111 -0
- package/package.json +51 -0
- package/packages/nastech-ink/ambient.d.ts +83 -0
- package/packages/nastech-ink/index.d.ts +40 -0
- package/packages/nastech-ink/index.js +1 -0
- package/packages/nastech-ink/nastech-ink/ambient.d.ts +83 -0
- package/packages/nastech-ink/nastech-ink/index.d.ts +40 -0
- package/packages/nastech-ink/nastech-ink/index.js +1 -0
- package/packages/nastech-ink/nastech-ink/package.json +54 -0
- package/packages/nastech-ink/nastech-ink/src/bootstrap/state.ts +9 -0
- package/packages/nastech-ink/nastech-ink/src/entry-exports.ts +32 -0
- package/packages/nastech-ink/nastech-ink/src/hooks/use-stderr.ts +15 -0
- package/packages/nastech-ink/nastech-ink/src/hooks/use-stdout.ts +15 -0
- package/packages/nastech-ink/nastech-ink/src/ink/Ansi.tsx +435 -0
- package/packages/nastech-ink/nastech-ink/src/ink/app-mouse.test.ts +123 -0
- package/packages/nastech-ink/nastech-ink/src/ink/bidi.ts +145 -0
- package/packages/nastech-ink/nastech-ink/src/ink/cache-eviction.ts +45 -0
- package/packages/nastech-ink/nastech-ink/src/ink/clearTerminal.ts +68 -0
- package/packages/nastech-ink/nastech-ink/src/ink/colorize.test.ts +60 -0
- package/packages/nastech-ink/nastech-ink/src/ink/colorize.ts +277 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/AlternateScreen.tsx +133 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/App.tsx +830 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/AppContext.ts +20 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/Box.tsx +294 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/Button.tsx +236 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/ClockContext.tsx +133 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/CursorAdvanceContext.ts +35 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/CursorDeclarationContext.ts +28 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/ErrorOverview.tsx +130 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/Link.tsx +38 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/Newline.tsx +43 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/NoSelect.tsx +73 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/RawAnsi.tsx +61 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/ScrollBox.tsx +290 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/Spacer.tsx +23 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/StdinContext.ts +25 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/TerminalFocusContext.tsx +63 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/TerminalSizeContext.tsx +7 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/Text.test.ts +38 -0
- package/packages/nastech-ink/nastech-ink/src/ink/components/Text.tsx +336 -0
- package/packages/nastech-ink/nastech-ink/src/ink/constants.ts +6 -0
- package/packages/nastech-ink/nastech-ink/src/ink/cursor.ts +5 -0
- package/packages/nastech-ink/nastech-ink/src/ink/devtools.ts +2 -0
- package/packages/nastech-ink/nastech-ink/src/ink/dom.ts +495 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/click-event.ts +38 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/cmd-shortcuts.test.ts +65 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/dispatcher.ts +242 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/emitter.ts +40 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/event-handlers.ts +84 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/event.ts +11 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/focus-event.ts +18 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/input-event.ts +176 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/keyboard-event.ts +57 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/mouse-event.ts +18 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/paste-event.ts +10 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/resize-event.ts +12 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/terminal-event.ts +107 -0
- package/packages/nastech-ink/nastech-ink/src/ink/events/terminal-focus-event.ts +19 -0
- package/packages/nastech-ink/nastech-ink/src/ink/focus.ts +219 -0
- package/packages/nastech-ink/nastech-ink/src/ink/frame.ts +124 -0
- package/packages/nastech-ink/nastech-ink/src/ink/get-max-width.ts +27 -0
- package/packages/nastech-ink/nastech-ink/src/ink/global.d.ts +1 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hit-test.test.ts +38 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hit-test.ts +224 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-animation-frame.ts +62 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-app.ts +9 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-cursor-advance.ts +33 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-declared-cursor.ts +75 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-external-process.ts +27 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-input.ts +95 -0
- package/packages/nastech-ink/nastech-ink/src/ink/hooks/use-interval.ts +71 -0
- package/packages/nastech-ink/package.json +57 -0
- package/packages/nastech-ink/src/bootstrap/state.ts +9 -0
- package/packages/nastech-ink/src/entry-exports.ts +32 -0
- package/packages/nastech-ink/src/hooks/use-stderr.ts +15 -0
- package/packages/nastech-ink/src/hooks/use-stdout.ts +15 -0
- package/packages/nastech-ink/src/ink/Ansi.tsx +435 -0
- package/packages/nastech-ink/src/ink/app-mouse.test.ts +123 -0
- package/packages/nastech-ink/src/ink/app-rawmode-mouse.test.ts +91 -0
- package/packages/nastech-ink/src/ink/bidi.ts +145 -0
- package/packages/nastech-ink/src/ink/cache-eviction.ts +45 -0
- package/packages/nastech-ink/src/ink/clearTerminal.ts +68 -0
- package/packages/nastech-ink/src/ink/colorize.test.ts +60 -0
- package/packages/nastech-ink/src/ink/colorize.ts +277 -0
- package/packages/nastech-ink/src/ink/components/AlternateScreen.tsx +133 -0
- package/packages/nastech-ink/src/ink/components/App.tsx +855 -0
- package/packages/nastech-ink/src/ink/components/AppContext.ts +20 -0
- package/packages/nastech-ink/src/ink/components/Box.tsx +294 -0
- package/packages/nastech-ink/src/ink/components/Button.tsx +236 -0
- package/packages/nastech-ink/src/ink/components/ClockContext.tsx +133 -0
- package/packages/nastech-ink/src/ink/components/CursorAdvanceContext.ts +35 -0
- package/packages/nastech-ink/src/ink/components/CursorDeclarationContext.ts +28 -0
- package/packages/nastech-ink/src/ink/components/ErrorOverview.tsx +130 -0
- package/packages/nastech-ink/src/ink/components/Link.tsx +38 -0
- package/packages/nastech-ink/src/ink/components/Newline.tsx +43 -0
- package/packages/nastech-ink/src/ink/components/NoSelect.tsx +73 -0
- package/packages/nastech-ink/src/ink/components/RawAnsi.tsx +61 -0
- package/packages/nastech-ink/src/ink/components/ScrollBox.tsx +290 -0
- package/packages/nastech-ink/src/ink/components/Spacer.tsx +23 -0
- package/packages/nastech-ink/src/ink/components/StdinContext.ts +25 -0
- package/packages/nastech-ink/src/ink/components/TerminalFocusContext.tsx +63 -0
- package/packages/nastech-ink/src/ink/components/TerminalSizeContext.tsx +7 -0
- package/packages/nastech-ink/src/ink/components/Text.test.ts +38 -0
- package/packages/nastech-ink/src/ink/components/Text.tsx +336 -0
- package/packages/nastech-ink/src/ink/constants.ts +6 -0
- package/packages/nastech-ink/src/ink/cursor.ts +5 -0
- package/packages/nastech-ink/src/ink/devtools.ts +2 -0
- package/packages/nastech-ink/src/ink/dom.ts +495 -0
- package/packages/nastech-ink/src/ink/events/click-event.ts +38 -0
- package/packages/nastech-ink/src/ink/events/cmd-shortcuts.test.ts +65 -0
- package/packages/nastech-ink/src/ink/events/dispatcher.ts +242 -0
- package/packages/nastech-ink/src/ink/events/emitter.ts +40 -0
- package/packages/nastech-ink/src/ink/events/event-handlers.ts +84 -0
- package/packages/nastech-ink/src/ink/events/event.ts +11 -0
- package/packages/nastech-ink/src/ink/events/focus-event.ts +18 -0
- package/packages/nastech-ink/src/ink/events/input-event.ts +176 -0
- package/packages/nastech-ink/src/ink/events/keyboard-event.ts +57 -0
- package/packages/nastech-ink/src/ink/events/mouse-event.ts +18 -0
- package/packages/nastech-ink/src/ink/events/paste-event.ts +10 -0
- package/packages/nastech-ink/src/ink/events/resize-event.ts +12 -0
- package/packages/nastech-ink/src/ink/events/terminal-event.ts +107 -0
- package/packages/nastech-ink/src/ink/events/terminal-focus-event.ts +19 -0
- package/packages/nastech-ink/src/ink/focus.ts +219 -0
- package/packages/nastech-ink/src/ink/frame.ts +124 -0
- package/packages/nastech-ink/src/ink/get-max-width.ts +27 -0
- package/packages/nastech-ink/src/ink/global.d.ts +1 -0
- package/packages/nastech-ink/src/ink/hit-test.test.ts +38 -0
- package/packages/nastech-ink/src/ink/hit-test.ts +224 -0
- package/packages/nastech-ink/src/ink/hooks/use-animation-frame.ts +62 -0
- package/packages/nastech-ink/src/ink/hooks/use-app.ts +9 -0
- package/packages/nastech-ink/src/ink/hooks/use-cursor-advance.ts +33 -0
- package/packages/nastech-ink/src/ink/hooks/use-declared-cursor.ts +75 -0
- package/packages/nastech-ink/src/ink/hooks/use-external-process.ts +27 -0
- package/packages/nastech-ink/src/ink/hooks/use-input.ts +95 -0
- package/packages/nastech-ink/src/ink/hooks/use-interval.ts +71 -0
- package/packages/nastech-ink/src/ink/hooks/use-search-highlight.ts +56 -0
- package/packages/nastech-ink/src/ink/hooks/use-selection.ts +101 -0
- package/packages/nastech-ink/src/ink/hooks/use-stdin.ts +9 -0
- package/packages/nastech-ink/src/ink/hooks/use-tab-status.ts +71 -0
- package/packages/nastech-ink/src/ink/hooks/use-terminal-focus.ts +18 -0
- package/packages/nastech-ink/src/ink/hooks/use-terminal-title.ts +34 -0
- package/packages/nastech-ink/src/ink/hooks/use-terminal-viewport.ts +100 -0
- package/packages/nastech-ink/src/ink/hyperlinkHover.ts +52 -0
- package/packages/nastech-ink/src/ink/ink-cursor-advance.test.ts +234 -0
- package/packages/nastech-ink/src/ink/ink-resize.test.ts +50 -0
- package/packages/nastech-ink/src/ink/ink.tsx +2705 -0
- package/packages/nastech-ink/src/ink/instances.ts +10 -0
- package/packages/nastech-ink/src/ink/layout/engine.ts +6 -0
- package/packages/nastech-ink/src/ink/layout/geometry.ts +98 -0
- package/packages/nastech-ink/src/ink/layout/node.ts +145 -0
- package/packages/nastech-ink/src/ink/layout/yoga.ts +313 -0
- package/packages/nastech-ink/src/ink/line-width-cache.ts +38 -0
- package/packages/nastech-ink/src/ink/log-update.test.ts +223 -0
- package/packages/nastech-ink/src/ink/log-update.ts +752 -0
- package/packages/nastech-ink/src/ink/lru.ts +14 -0
- package/packages/nastech-ink/src/ink/measure-element.ts +23 -0
- package/packages/nastech-ink/src/ink/measure-text.ts +50 -0
- package/packages/nastech-ink/src/ink/node-cache.ts +53 -0
- package/packages/nastech-ink/src/ink/optimizer.ts +99 -0
- package/packages/nastech-ink/src/ink/output.ts +845 -0
- package/packages/nastech-ink/src/ink/parse-keypress.test.ts +133 -0
- package/packages/nastech-ink/src/ink/parse-keypress.ts +848 -0
- package/packages/nastech-ink/src/ink/reconciler.ts +382 -0
- package/packages/nastech-ink/src/ink/render-border.ts +206 -0
- package/packages/nastech-ink/src/ink/render-node-to-output.ts +1582 -0
- package/packages/nastech-ink/src/ink/render-to-screen.ts +236 -0
- package/packages/nastech-ink/src/ink/renderer.ts +169 -0
- package/packages/nastech-ink/src/ink/root.ts +204 -0
- package/packages/nastech-ink/src/ink/screen.ts +1590 -0
- package/packages/nastech-ink/src/ink/searchHighlight.ts +91 -0
- package/packages/nastech-ink/src/ink/selection.test.ts +82 -0
- package/packages/nastech-ink/src/ink/selection.ts +1143 -0
- package/packages/nastech-ink/src/ink/squash-text-nodes.ts +74 -0
- package/packages/nastech-ink/src/ink/stringWidth.ts +341 -0
- package/packages/nastech-ink/src/ink/styles.ts +750 -0
- package/packages/nastech-ink/src/ink/supports-hyperlinks.ts +51 -0
- package/packages/nastech-ink/src/ink/tabstops.ts +44 -0
- package/packages/nastech-ink/src/ink/terminal-focus-state.ts +52 -0
- package/packages/nastech-ink/src/ink/terminal-querier.ts +222 -0
- package/packages/nastech-ink/src/ink/terminal.test.ts +15 -0
- package/packages/nastech-ink/src/ink/terminal.ts +299 -0
- package/packages/nastech-ink/src/ink/termio/ansi.ts +75 -0
- package/packages/nastech-ink/src/ink/termio/csi.ts +334 -0
- package/packages/nastech-ink/src/ink/termio/dec.ts +99 -0
- package/packages/nastech-ink/src/ink/termio/esc.ts +69 -0
- package/packages/nastech-ink/src/ink/termio/osc.test.ts +191 -0
- package/packages/nastech-ink/src/ink/termio/osc.ts +724 -0
- package/packages/nastech-ink/src/ink/termio/parser.ts +467 -0
- package/packages/nastech-ink/src/ink/termio/sgr.ts +362 -0
- package/packages/nastech-ink/src/ink/termio/tokenize.test.ts +185 -0
- package/packages/nastech-ink/src/ink/termio/tokenize.ts +350 -0
- package/packages/nastech-ink/src/ink/termio/types.ts +230 -0
- package/packages/nastech-ink/src/ink/termio.ts +42 -0
- package/packages/nastech-ink/src/ink/useTerminalNotification.ts +110 -0
- package/packages/nastech-ink/src/ink/warn.ts +15 -0
- package/packages/nastech-ink/src/ink/widest-line.ts +22 -0
- package/packages/nastech-ink/src/ink/wrap-text.test.ts +17 -0
- package/packages/nastech-ink/src/ink/wrap-text.ts +144 -0
- package/packages/nastech-ink/src/ink/wrapAnsi.ts +13 -0
- package/packages/nastech-ink/src/native-ts/yoga-layout/enums.ts +112 -0
- package/packages/nastech-ink/src/native-ts/yoga-layout/index.ts +2326 -0
- package/packages/nastech-ink/src/utils/debug.ts +6 -0
- package/packages/nastech-ink/src/utils/earlyInput.ts +131 -0
- package/packages/nastech-ink/src/utils/env.ts +66 -0
- package/packages/nastech-ink/src/utils/envUtils.ts +13 -0
- package/packages/nastech-ink/src/utils/execFileNoThrow.test.ts +146 -0
- package/packages/nastech-ink/src/utils/execFileNoThrow.ts +115 -0
- package/packages/nastech-ink/src/utils/fullscreen.ts +3 -0
- package/packages/nastech-ink/src/utils/intl.ts +87 -0
- package/packages/nastech-ink/src/utils/log.ts +7 -0
- package/packages/nastech-ink/src/utils/semver.ts +57 -0
- package/packages/nastech-ink/src/utils/sliceAnsi.ts +106 -0
- package/packages/nastech-ink/text-input.d.ts +2 -0
- package/packages/nastech-ink/text-input.js +1 -0
- package/scripts/build.mjs +61 -0
- package/scripts/profile-tui.mjs +121 -0
- package/src/__tests__/activeSessionSwitcher.test.ts +157 -0
- package/src/__tests__/appChromeStatusRule.test.tsx +84 -0
- package/src/__tests__/appChromeStatusRuleDevCredits.test.tsx +73 -0
- package/src/__tests__/approvalAction.test.ts +50 -0
- package/src/__tests__/asCommandDispatch.test.ts +27 -0
- package/src/__tests__/blockLayout.test.ts +122 -0
- package/src/__tests__/clipboard.test.ts +369 -0
- package/src/__tests__/constants.test.ts +53 -0
- package/src/__tests__/createGatewayEventHandler.test.ts +1091 -0
- package/src/__tests__/createSlashHandler.test.ts +822 -0
- package/src/__tests__/creditsCommand.test.ts +144 -0
- package/src/__tests__/cursorDriftRegression.test.ts +114 -0
- package/src/__tests__/details.test.ts +115 -0
- package/src/__tests__/emoji.test.ts +64 -0
- package/src/__tests__/externalLink.test.ts +144 -0
- package/src/__tests__/forceTruecolor.test.ts +191 -0
- package/src/__tests__/gatewayClient.test.ts +394 -0
- package/src/__tests__/gatewayRecovery.test.ts +47 -0
- package/src/__tests__/markdown.test.ts +331 -0
- package/src/__tests__/mathUnicode.test.ts +293 -0
- package/src/__tests__/memoryMonitor.test.ts +102 -0
- package/src/__tests__/messageLine.test.ts +19 -0
- package/src/__tests__/messages.test.ts +92 -0
- package/src/__tests__/orchestratorPromptSession.test.ts +64 -0
- package/src/__tests__/osc52.test.ts +67 -0
- package/src/__tests__/parentLog.test.ts +75 -0
- package/src/__tests__/paths.test.ts +70 -0
- package/src/__tests__/platform.test.ts +556 -0
- package/src/__tests__/precisionWheel.test.ts +44 -0
- package/src/__tests__/prompt.test.ts +31 -0
- package/src/__tests__/providers.test.ts +65 -0
- package/src/__tests__/reasoning.test.ts +76 -0
- package/src/__tests__/rpc.test.ts +27 -0
- package/src/__tests__/scroll.test.ts +99 -0
- package/src/__tests__/slashParity.test.ts +123 -0
- package/src/__tests__/spawnHistoryStore.test.ts +46 -0
- package/src/__tests__/stateIsolation.test.ts +46 -0
- package/src/__tests__/statusBarTicker.test.ts +18 -0
- package/src/__tests__/statusRule.test.ts +32 -0
- package/src/__tests__/streamingMarkdown.test.ts +121 -0
- package/src/__tests__/subagentTree.test.ts +407 -0
- package/src/__tests__/syntax.test.ts +45 -0
- package/src/__tests__/terminalModes.test.ts +39 -0
- package/src/__tests__/terminalParity.test.ts +77 -0
- package/src/__tests__/terminalSetup.test.ts +386 -0
- package/src/__tests__/termux.test.ts +35 -0
- package/src/__tests__/termuxComposerLayout.test.ts +40 -0
- package/src/__tests__/text.test.ts +233 -0
- package/src/__tests__/textInputBurstInput.test.ts +40 -0
- package/src/__tests__/textInputCursorSourceOfTruth.test.ts +50 -0
- package/src/__tests__/textInputFastEcho.test.ts +200 -0
- package/src/__tests__/textInputLineNav.test.ts +55 -0
- package/src/__tests__/textInputPassThrough.test.ts +59 -0
- package/src/__tests__/textInputRightClick.test.ts +48 -0
- package/src/__tests__/textInputWrap.test.ts +151 -0
- package/src/__tests__/theme.test.ts +311 -0
- package/src/__tests__/turnControllerNotice.test.ts +43 -0
- package/src/__tests__/turnStore.test.ts +66 -0
- package/src/__tests__/useCompletion.test.ts +35 -0
- package/src/__tests__/useComposerState.test.ts +59 -0
- package/src/__tests__/useConfigSync.test.ts +460 -0
- package/src/__tests__/useInputHandlers.test.ts +77 -0
- package/src/__tests__/useQueue.test.ts +28 -0
- package/src/__tests__/useSessionLifecycle.test.ts +60 -0
- package/src/__tests__/useVirtualHistoryHeights.test.ts +39 -0
- package/src/__tests__/viewport.test.ts +58 -0
- package/src/__tests__/viewportStore.test.ts +85 -0
- package/src/__tests__/virtualHeights.test.ts +96 -0
- package/src/__tests__/virtualHistoryClamp.test.ts +19 -0
- package/src/__tests__/virtualHistoryOffsetCache.test.ts +282 -0
- package/src/__tests__/wheelAccel.test.ts +138 -0
- package/src/app/createGatewayEventHandler.ts +833 -0
- package/src/app/createSlashHandler.ts +130 -0
- package/src/app/delegationStore.ts +77 -0
- package/src/app/gatewayContext.tsx +19 -0
- package/src/app/gatewayRecovery.ts +35 -0
- package/src/app/inputSelectionStore.ts +15 -0
- package/src/app/interfaces.ts +394 -0
- package/src/app/overlayStore.ts +53 -0
- package/src/app/scroll.ts +71 -0
- package/src/app/setupHandoff.ts +54 -0
- package/src/app/slash/commands/core.ts +648 -0
- package/src/app/slash/commands/credits.ts +57 -0
- package/src/app/slash/commands/debug.ts +48 -0
- package/src/app/slash/commands/ops.ts +717 -0
- package/src/app/slash/commands/session.ts +554 -0
- package/src/app/slash/commands/setup.ts +20 -0
- package/src/app/slash/registry.ts +20 -0
- package/src/app/slash/types.ts +21 -0
- package/src/app/spawnHistoryStore.ts +159 -0
- package/src/app/turnController.ts +866 -0
- package/src/app/turnStore.ts +85 -0
- package/src/app/uiStore.ts +44 -0
- package/src/app/useComposerState.ts +367 -0
- package/src/app/useConfigSync.ts +288 -0
- package/src/app/useInputHandlers.ts +576 -0
- package/src/app/useLongRunToolCharms.ts +69 -0
- package/src/app/useMainApp.ts +1039 -0
- package/src/app/useSessionLifecycle.ts +366 -0
- package/src/app/useSubmission.ts +429 -0
- package/src/app.tsx +25 -0
- package/src/banner.ts +93 -0
- package/src/components/activeSessionSwitcher.tsx +635 -0
- package/src/components/agentsOverlay.tsx +1073 -0
- package/src/components/appChrome.tsx +554 -0
- package/src/components/appLayout.tsx +444 -0
- package/src/components/appOverlays.tsx +254 -0
- package/src/components/branding.tsx +466 -0
- package/src/components/fpsOverlay.tsx +30 -0
- package/src/components/helpHint.tsx +73 -0
- package/src/components/markdown.tsx +1119 -0
- package/src/components/maskedPrompt.tsx +34 -0
- package/src/components/messageLine.tsx +237 -0
- package/src/components/modelPicker.tsx +527 -0
- package/src/components/overlayControls.tsx +50 -0
- package/src/components/pluginsHub.tsx +238 -0
- package/src/components/prompts.tsx +276 -0
- package/src/components/queuedMessages.tsx +64 -0
- package/src/components/sessionPicker.tsx +227 -0
- package/src/components/skillsHub.tsx +308 -0
- package/src/components/streamingAssistant.tsx +110 -0
- package/src/components/streamingMarkdown.tsx +174 -0
- package/src/components/textInput.tsx +1340 -0
- package/src/components/themed.tsx +30 -0
- package/src/components/thinking.tsx +1224 -0
- package/src/components/todoPanel.tsx +93 -0
- package/src/config/env.ts +64 -0
- package/src/config/limits.ts +13 -0
- package/src/config/timing.ts +6 -0
- package/src/content/charms.ts +1 -0
- package/src/content/faces.ts +17 -0
- package/src/content/fortunes.ts +30 -0
- package/src/content/hotkeys.ts +37 -0
- package/src/content/placeholders.ts +13 -0
- package/src/content/setup.ts +17 -0
- package/src/content/verbs.ts +38 -0
- package/src/domain/blockLayout.ts +146 -0
- package/src/domain/details.ts +76 -0
- package/src/domain/messages.ts +91 -0
- package/src/domain/paths.ts +16 -0
- package/src/domain/providers.ts +11 -0
- package/src/domain/roles.ts +9 -0
- package/src/domain/slash.ts +10 -0
- package/src/domain/usage.ts +3 -0
- package/src/domain/viewport.ts +51 -0
- package/src/entry.tsx +104 -0
- package/src/gatewayClient.ts +730 -0
- package/src/gatewayTypes.ts +568 -0
- package/src/hooks/useCompletion.ts +112 -0
- package/src/hooks/useGitBranch.ts +72 -0
- package/src/hooks/useInputHistory.ts +11 -0
- package/src/hooks/useQueue.ts +76 -0
- package/src/hooks/useVirtualHistory.ts +554 -0
- package/src/lib/circularBuffer.ts +48 -0
- package/src/lib/clipboard.ts +182 -0
- package/src/lib/editor.test.ts +74 -0
- package/src/lib/editor.ts +47 -0
- package/src/lib/emoji.ts +55 -0
- package/src/lib/externalCli.ts +16 -0
- package/src/lib/externalLink.ts +435 -0
- package/src/lib/forceTruecolor.ts +60 -0
- package/src/lib/fpsStore.ts +51 -0
- package/src/lib/fuzzy.test.ts +109 -0
- package/src/lib/fuzzy.ts +177 -0
- package/src/lib/gracefulExit.ts +47 -0
- package/src/lib/history.ts +82 -0
- package/src/lib/inputMetrics.ts +203 -0
- package/src/lib/liveProgress.test.ts +116 -0
- package/src/lib/liveProgress.ts +79 -0
- package/src/lib/mathUnicode.ts +770 -0
- package/src/lib/memory.test.ts +155 -0
- package/src/lib/memory.ts +188 -0
- package/src/lib/memoryMonitor.ts +109 -0
- package/src/lib/messages.test.ts +29 -0
- package/src/lib/messages.ts +8 -0
- package/src/lib/openExternalUrl.test.ts +217 -0
- package/src/lib/openExternalUrl.ts +158 -0
- package/src/lib/osc52.ts +73 -0
- package/src/lib/parentLog.ts +57 -0
- package/src/lib/perfPane.tsx +107 -0
- package/src/lib/platform.ts +409 -0
- package/src/lib/precisionWheel.ts +48 -0
- package/src/lib/prompt.ts +35 -0
- package/src/lib/reasoning.ts +55 -0
- package/src/lib/rpc.ts +41 -0
- package/src/lib/subagentTree.ts +355 -0
- package/src/lib/syntax.ts +117 -0
- package/src/lib/terminalModes.ts +51 -0
- package/src/lib/terminalParity.ts +78 -0
- package/src/lib/terminalSetup.ts +444 -0
- package/src/lib/termux.ts +29 -0
- package/src/lib/text.test.ts +18 -0
- package/src/lib/text.ts +339 -0
- package/src/lib/todo.test.ts +21 -0
- package/src/lib/todo.ts +9 -0
- package/src/lib/viewportStore.ts +124 -0
- package/src/lib/virtualHeights.ts +145 -0
- package/src/lib/wheelAccel.ts +190 -0
- package/src/protocol/interpolation.ts +3 -0
- package/src/protocol/paste.ts +1 -0
- package/src/theme.ts +589 -0
- package/src/types/nastech-ink.d.ts +176 -0
- package/src/types.ts +212 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.json +19 -0
- package/vitest.config.ts +7 -0
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { $uiState, resetUiState } from '../app/uiStore.js'
|
|
4
|
+
import {
|
|
5
|
+
applyDisplay,
|
|
6
|
+
hydrateFullConfig,
|
|
7
|
+
normalizeBusyInputMode,
|
|
8
|
+
normalizeIndicatorStyle,
|
|
9
|
+
normalizeMouseTracking,
|
|
10
|
+
normalizeStatusBar
|
|
11
|
+
} from '../app/useConfigSync.js'
|
|
12
|
+
import type { ParsedVoiceRecordKey } from '../lib/platform.js'
|
|
13
|
+
|
|
14
|
+
describe('applyDisplay', () => {
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
resetUiState()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('fans every display flag out to $uiState and the bell callback', () => {
|
|
20
|
+
const setBell = vi.fn()
|
|
21
|
+
|
|
22
|
+
applyDisplay(
|
|
23
|
+
{
|
|
24
|
+
config: {
|
|
25
|
+
display: {
|
|
26
|
+
bell_on_complete: true,
|
|
27
|
+
details_mode: 'expanded',
|
|
28
|
+
inline_diffs: false,
|
|
29
|
+
show_cost: true,
|
|
30
|
+
show_reasoning: true,
|
|
31
|
+
streaming: false,
|
|
32
|
+
tui_compact: true,
|
|
33
|
+
tui_statusbar: false
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
setBell
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
const s = $uiState.get()
|
|
41
|
+
expect(setBell).toHaveBeenCalledWith(true)
|
|
42
|
+
expect(s.compact).toBe(true)
|
|
43
|
+
expect(s.detailsMode).toBe('expanded')
|
|
44
|
+
expect(s.inlineDiffs).toBe(false)
|
|
45
|
+
expect(s.showCost).toBe(true)
|
|
46
|
+
expect(s.showReasoning).toBe(true)
|
|
47
|
+
expect(s.statusBar).toBe('off')
|
|
48
|
+
expect(s.streaming).toBe(false)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('coerces legacy true + "on" alias to top', () => {
|
|
52
|
+
const setBell = vi.fn()
|
|
53
|
+
|
|
54
|
+
applyDisplay({ config: { display: { tui_statusbar: true as unknown as 'on' } } }, setBell)
|
|
55
|
+
expect($uiState.get().statusBar).toBe('top')
|
|
56
|
+
|
|
57
|
+
applyDisplay({ config: { display: { tui_statusbar: 'on' } } }, setBell)
|
|
58
|
+
expect($uiState.get().statusBar).toBe('top')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('applies v1 parity defaults when display fields are missing', () => {
|
|
62
|
+
const setBell = vi.fn()
|
|
63
|
+
|
|
64
|
+
applyDisplay({ config: { display: {} } }, setBell)
|
|
65
|
+
|
|
66
|
+
const s = $uiState.get()
|
|
67
|
+
expect(setBell).toHaveBeenCalledWith(false)
|
|
68
|
+
expect(s.inlineDiffs).toBe(true)
|
|
69
|
+
expect(s.showCost).toBe(false)
|
|
70
|
+
expect(s.showReasoning).toBe(false)
|
|
71
|
+
expect(s.statusBar).toBe('top')
|
|
72
|
+
expect(s.streaming).toBe(true)
|
|
73
|
+
expect(s.sections).toEqual({})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('uses documented mouse_tracking with legacy tui_mouse fallback', () => {
|
|
77
|
+
const setBell = vi.fn()
|
|
78
|
+
|
|
79
|
+
applyDisplay({ config: { display: { mouse_tracking: false } } }, setBell)
|
|
80
|
+
expect($uiState.get().mouseTracking).toBe('off')
|
|
81
|
+
|
|
82
|
+
applyDisplay({ config: { display: { mouse_tracking: true, tui_mouse: false } } }, setBell)
|
|
83
|
+
expect($uiState.get().mouseTracking).toBe('all')
|
|
84
|
+
|
|
85
|
+
applyDisplay({ config: { display: { tui_mouse: false } } }, setBell)
|
|
86
|
+
expect($uiState.get().mouseTracking).toBe('off')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('threads mouse_tracking presets through to $uiState', () => {
|
|
90
|
+
const setBell = vi.fn()
|
|
91
|
+
|
|
92
|
+
applyDisplay({ config: { display: { mouse_tracking: 'wheel' } } }, setBell)
|
|
93
|
+
expect($uiState.get().mouseTracking).toBe('wheel')
|
|
94
|
+
|
|
95
|
+
applyDisplay({ config: { display: { mouse_tracking: 'buttons' } } }, setBell)
|
|
96
|
+
expect($uiState.get().mouseTracking).toBe('buttons')
|
|
97
|
+
|
|
98
|
+
applyDisplay({ config: { display: { mouse_tracking: 'all' } } }, setBell)
|
|
99
|
+
expect($uiState.get().mouseTracking).toBe('all')
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('parses display.sections into per-section overrides', () => {
|
|
103
|
+
const setBell = vi.fn()
|
|
104
|
+
|
|
105
|
+
applyDisplay(
|
|
106
|
+
{
|
|
107
|
+
config: {
|
|
108
|
+
display: {
|
|
109
|
+
details_mode: 'collapsed',
|
|
110
|
+
sections: {
|
|
111
|
+
activity: 'hidden',
|
|
112
|
+
tools: 'expanded',
|
|
113
|
+
thinking: 'expanded',
|
|
114
|
+
bogus: 'expanded'
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
setBell
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
const s = $uiState.get()
|
|
123
|
+
expect(s.detailsMode).toBe('collapsed')
|
|
124
|
+
expect(s.sections).toEqual({
|
|
125
|
+
activity: 'hidden',
|
|
126
|
+
tools: 'expanded',
|
|
127
|
+
thinking: 'expanded'
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('drops invalid section modes', () => {
|
|
132
|
+
const setBell = vi.fn()
|
|
133
|
+
|
|
134
|
+
applyDisplay(
|
|
135
|
+
{
|
|
136
|
+
config: {
|
|
137
|
+
display: {
|
|
138
|
+
sections: { tools: 'maximised' as unknown as string, activity: 'hidden' }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
setBell
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
expect($uiState.get().sections).toEqual({ activity: 'hidden' })
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('treats a null config like an empty display block', () => {
|
|
149
|
+
const setBell = vi.fn()
|
|
150
|
+
|
|
151
|
+
applyDisplay(null, setBell)
|
|
152
|
+
|
|
153
|
+
const s = $uiState.get()
|
|
154
|
+
expect(setBell).toHaveBeenCalledWith(false)
|
|
155
|
+
expect(s.inlineDiffs).toBe(true)
|
|
156
|
+
expect(s.streaming).toBe(true)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('accepts the new string statusBar modes', () => {
|
|
160
|
+
const setBell = vi.fn()
|
|
161
|
+
|
|
162
|
+
applyDisplay({ config: { display: { tui_statusbar: 'bottom' } } }, setBell)
|
|
163
|
+
expect($uiState.get().statusBar).toBe('bottom')
|
|
164
|
+
|
|
165
|
+
applyDisplay({ config: { display: { tui_statusbar: 'top' } } }, setBell)
|
|
166
|
+
expect($uiState.get().statusBar).toBe('top')
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
describe('normalizeStatusBar', () => {
|
|
171
|
+
it('maps legacy bool + on alias to top/off', () => {
|
|
172
|
+
expect(normalizeStatusBar(true)).toBe('top')
|
|
173
|
+
expect(normalizeStatusBar(false)).toBe('off')
|
|
174
|
+
expect(normalizeStatusBar('on')).toBe('top')
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('passes through the canonical enum', () => {
|
|
178
|
+
expect(normalizeStatusBar('off')).toBe('off')
|
|
179
|
+
expect(normalizeStatusBar('top')).toBe('top')
|
|
180
|
+
expect(normalizeStatusBar('bottom')).toBe('bottom')
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('defaults missing/unknown values to top', () => {
|
|
184
|
+
expect(normalizeStatusBar(undefined)).toBe('top')
|
|
185
|
+
expect(normalizeStatusBar(null)).toBe('top')
|
|
186
|
+
expect(normalizeStatusBar('sideways')).toBe('top')
|
|
187
|
+
expect(normalizeStatusBar(42)).toBe('top')
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
it('trims whitespace and folds case', () => {
|
|
191
|
+
expect(normalizeStatusBar(' Bottom ')).toBe('bottom')
|
|
192
|
+
expect(normalizeStatusBar('TOP')).toBe('top')
|
|
193
|
+
expect(normalizeStatusBar(' on ')).toBe('top')
|
|
194
|
+
expect(normalizeStatusBar('OFF')).toBe('off')
|
|
195
|
+
})
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
describe('normalizeMouseTracking', () => {
|
|
199
|
+
it('defaults to all and prefers canonical mouse_tracking over legacy tui_mouse', () => {
|
|
200
|
+
expect(normalizeMouseTracking({})).toBe('all')
|
|
201
|
+
expect(normalizeMouseTracking({ mouse_tracking: false })).toBe('off')
|
|
202
|
+
expect(normalizeMouseTracking({ mouse_tracking: 0 })).toBe('off')
|
|
203
|
+
expect(normalizeMouseTracking({ mouse_tracking: 'off' })).toBe('off')
|
|
204
|
+
expect(normalizeMouseTracking({ mouse_tracking: 'false' })).toBe('off')
|
|
205
|
+
expect(normalizeMouseTracking({ mouse_tracking: null, tui_mouse: false })).toBe('all')
|
|
206
|
+
expect(normalizeMouseTracking({ mouse_tracking: true, tui_mouse: false })).toBe('all')
|
|
207
|
+
expect(normalizeMouseTracking({ tui_mouse: false })).toBe('off')
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it('accepts preset strings (wheel/buttons/all) and their aliases', () => {
|
|
211
|
+
expect(normalizeMouseTracking({ mouse_tracking: 'wheel' })).toBe('wheel')
|
|
212
|
+
expect(normalizeMouseTracking({ mouse_tracking: 'scroll' })).toBe('wheel')
|
|
213
|
+
expect(normalizeMouseTracking({ mouse_tracking: 'buttons' })).toBe('buttons')
|
|
214
|
+
expect(normalizeMouseTracking({ mouse_tracking: 'click' })).toBe('buttons')
|
|
215
|
+
expect(normalizeMouseTracking({ mouse_tracking: 'all' })).toBe('all')
|
|
216
|
+
expect(normalizeMouseTracking({ mouse_tracking: 'full' })).toBe('all')
|
|
217
|
+
expect(normalizeMouseTracking({ mouse_tracking: 'on' })).toBe('all')
|
|
218
|
+
expect(normalizeMouseTracking({ mouse_tracking: ' WHEEL ' })).toBe('wheel')
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('falls back to all for unknown strings', () => {
|
|
222
|
+
expect(normalizeMouseTracking({ mouse_tracking: 'rainbows' })).toBe('all')
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
describe('normalizeBusyInputMode', () => {
|
|
227
|
+
it('passes through the canonical CLI parity values', () => {
|
|
228
|
+
expect(normalizeBusyInputMode('queue')).toBe('queue')
|
|
229
|
+
expect(normalizeBusyInputMode('steer')).toBe('steer')
|
|
230
|
+
expect(normalizeBusyInputMode('interrupt')).toBe('interrupt')
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
it('trims and lowercases input', () => {
|
|
234
|
+
expect(normalizeBusyInputMode(' Queue ')).toBe('queue')
|
|
235
|
+
expect(normalizeBusyInputMode('STEER')).toBe('steer')
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('defaults to queue for missing/unknown values (TUI-only override)', () => {
|
|
239
|
+
// CLI / messaging adapters keep `interrupt` as the framework default
|
|
240
|
+
// (see nastech_cli/config.py + tui_gateway/server.py::_load_busy_input_mode);
|
|
241
|
+
// the TUI ships `queue` because typing a follow-up while the agent
|
|
242
|
+
// streams is the common authoring pattern and an unintended interrupt
|
|
243
|
+
// loses work.
|
|
244
|
+
expect(normalizeBusyInputMode(undefined)).toBe('queue')
|
|
245
|
+
expect(normalizeBusyInputMode(null)).toBe('queue')
|
|
246
|
+
expect(normalizeBusyInputMode('')).toBe('queue')
|
|
247
|
+
expect(normalizeBusyInputMode('drop')).toBe('queue')
|
|
248
|
+
expect(normalizeBusyInputMode(42)).toBe('queue')
|
|
249
|
+
})
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
describe('normalizeIndicatorStyle', () => {
|
|
253
|
+
it('passes through the canonical enum', () => {
|
|
254
|
+
expect(normalizeIndicatorStyle('kaomoji')).toBe('kaomoji')
|
|
255
|
+
expect(normalizeIndicatorStyle('emoji')).toBe('emoji')
|
|
256
|
+
expect(normalizeIndicatorStyle('unicode')).toBe('unicode')
|
|
257
|
+
expect(normalizeIndicatorStyle('ascii')).toBe('ascii')
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
it('trims and lowercases input', () => {
|
|
261
|
+
expect(normalizeIndicatorStyle(' Emoji ')).toBe('emoji')
|
|
262
|
+
expect(normalizeIndicatorStyle('UNICODE')).toBe('unicode')
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
it('defaults to kaomoji for missing/unknown values', () => {
|
|
266
|
+
expect(normalizeIndicatorStyle(undefined)).toBe('kaomoji')
|
|
267
|
+
expect(normalizeIndicatorStyle(null)).toBe('kaomoji')
|
|
268
|
+
expect(normalizeIndicatorStyle('')).toBe('kaomoji')
|
|
269
|
+
expect(normalizeIndicatorStyle('sparkle')).toBe('kaomoji')
|
|
270
|
+
expect(normalizeIndicatorStyle(42)).toBe('kaomoji')
|
|
271
|
+
})
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
describe('applyDisplay → busy_input_mode', () => {
|
|
275
|
+
beforeEach(() => {
|
|
276
|
+
resetUiState()
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
it('threads display.busy_input_mode into $uiState', () => {
|
|
280
|
+
const setBell = vi.fn()
|
|
281
|
+
|
|
282
|
+
applyDisplay({ config: { display: { busy_input_mode: 'queue' } } }, setBell)
|
|
283
|
+
expect($uiState.get().busyInputMode).toBe('queue')
|
|
284
|
+
|
|
285
|
+
applyDisplay({ config: { display: { busy_input_mode: 'steer' } } }, setBell)
|
|
286
|
+
expect($uiState.get().busyInputMode).toBe('steer')
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
it('falls back to queue when value is missing or invalid (TUI-only default)', () => {
|
|
290
|
+
const setBell = vi.fn()
|
|
291
|
+
|
|
292
|
+
applyDisplay({ config: { display: {} } }, setBell)
|
|
293
|
+
expect($uiState.get().busyInputMode).toBe('queue')
|
|
294
|
+
|
|
295
|
+
applyDisplay({ config: { display: { busy_input_mode: 'drop' } } }, setBell)
|
|
296
|
+
expect($uiState.get().busyInputMode).toBe('queue')
|
|
297
|
+
})
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
describe('applyDisplay → tui_status_indicator', () => {
|
|
301
|
+
beforeEach(() => {
|
|
302
|
+
resetUiState()
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
it('threads display.tui_status_indicator into $uiState', () => {
|
|
306
|
+
const setBell = vi.fn()
|
|
307
|
+
|
|
308
|
+
applyDisplay({ config: { display: { tui_status_indicator: 'emoji' } } }, setBell)
|
|
309
|
+
expect($uiState.get().indicatorStyle).toBe('emoji')
|
|
310
|
+
|
|
311
|
+
applyDisplay({ config: { display: { tui_status_indicator: 'unicode' } } }, setBell)
|
|
312
|
+
expect($uiState.get().indicatorStyle).toBe('unicode')
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
it('falls back to kaomoji default when missing or invalid', () => {
|
|
316
|
+
const setBell = vi.fn()
|
|
317
|
+
|
|
318
|
+
applyDisplay({ config: { display: {} } }, setBell)
|
|
319
|
+
expect($uiState.get().indicatorStyle).toBe('kaomoji')
|
|
320
|
+
|
|
321
|
+
applyDisplay({ config: { display: { tui_status_indicator: 'rainbow' } } }, setBell)
|
|
322
|
+
expect($uiState.get().indicatorStyle).toBe('kaomoji')
|
|
323
|
+
})
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
// Regressions from Copilot review on #19835: the config-hydration path
|
|
327
|
+
// for voice.record_key was untested, so a future regression in the
|
|
328
|
+
// hydration or mtime-reapply wiring would slip past the suite.
|
|
329
|
+
describe('applyDisplay → voice.record_key (#18994)', () => {
|
|
330
|
+
beforeEach(() => {
|
|
331
|
+
resetUiState()
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
it('parses voice.record_key and pushes it through the setter', () => {
|
|
335
|
+
const setBell = vi.fn()
|
|
336
|
+
const setVoiceRecordKey = vi.fn()
|
|
337
|
+
|
|
338
|
+
applyDisplay(
|
|
339
|
+
{ config: { display: {}, voice: { record_key: 'ctrl+space' } } },
|
|
340
|
+
setBell,
|
|
341
|
+
setVoiceRecordKey
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
expect(setVoiceRecordKey).toHaveBeenCalledWith(
|
|
345
|
+
expect.objectContaining({ ch: 'space', mod: 'ctrl', named: 'space', raw: 'ctrl+space' })
|
|
346
|
+
)
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
it('falls back to the documented default when voice.record_key is missing', () => {
|
|
350
|
+
const setBell = vi.fn()
|
|
351
|
+
const setVoiceRecordKey = vi.fn()
|
|
352
|
+
|
|
353
|
+
applyDisplay({ config: { display: {} } }, setBell, setVoiceRecordKey)
|
|
354
|
+
|
|
355
|
+
expect(setVoiceRecordKey).toHaveBeenCalledWith(
|
|
356
|
+
expect.objectContaining({ ch: 'b', mod: 'ctrl', raw: 'ctrl+b' })
|
|
357
|
+
)
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
it('is a no-op when the voice setter is not passed (back-compat)', () => {
|
|
361
|
+
const setBell = vi.fn()
|
|
362
|
+
|
|
363
|
+
// applyDisplay is used in the setVoiceEnabled-less init path too;
|
|
364
|
+
// omitting the third arg must not throw.
|
|
365
|
+
expect(() =>
|
|
366
|
+
applyDisplay({ config: { display: {}, voice: { record_key: 'alt+r' } } }, setBell)
|
|
367
|
+
).not.toThrow()
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
it('does not reset voiceRecordKey when cfg is null (transient RPC failure)', () => {
|
|
371
|
+
const setBell = vi.fn()
|
|
372
|
+
const setVoiceRecordKey = vi.fn()
|
|
373
|
+
|
|
374
|
+
// quietRpc() collapses request failures to null. Resetting the
|
|
375
|
+
// cached shortcut on every null would clobber a custom binding
|
|
376
|
+
// after one transient error until the next successful poll
|
|
377
|
+
// (Copilot round-8 review on #19835).
|
|
378
|
+
applyDisplay(null, setBell, setVoiceRecordKey)
|
|
379
|
+
|
|
380
|
+
expect(setVoiceRecordKey).not.toHaveBeenCalled()
|
|
381
|
+
// bell is still applied (defaults to false on null), so the setter
|
|
382
|
+
// runs — we specifically only skip voiceRecordKey.
|
|
383
|
+
expect(setBell).toHaveBeenCalledWith(false)
|
|
384
|
+
})
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
// Round-12 Copilot review regression on #19835: the live mtime-reload
|
|
388
|
+
// path was previously untested, so a regression in the polling/RPC
|
|
389
|
+
// wiring to applyDisplay would only be visible at runtime. The fetch
|
|
390
|
+
// + apply body is now shared as ``hydrateFullConfig()``, exercised
|
|
391
|
+
// directly from both the initial hydration and the poll-tick body.
|
|
392
|
+
describe('hydrateFullConfig', () => {
|
|
393
|
+
beforeEach(() => {
|
|
394
|
+
resetUiState()
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
const makeFakeGw = (payload: unknown) =>
|
|
398
|
+
({
|
|
399
|
+
request: vi.fn(() => Promise.resolve(payload)),
|
|
400
|
+
on: vi.fn(),
|
|
401
|
+
off: vi.fn()
|
|
402
|
+
}) as any
|
|
403
|
+
|
|
404
|
+
it('re-applies voice.record_key from a fresh config.get full response', async () => {
|
|
405
|
+
const gw = makeFakeGw({ config: { display: {}, voice: { record_key: 'ctrl+o' } } })
|
|
406
|
+
const setBell = vi.fn()
|
|
407
|
+
const setVoiceRecordKey = vi.fn()
|
|
408
|
+
|
|
409
|
+
await hydrateFullConfig(gw, setBell, setVoiceRecordKey)
|
|
410
|
+
|
|
411
|
+
expect(gw.request).toHaveBeenCalledWith('config.get', { key: 'full' })
|
|
412
|
+
expect(setVoiceRecordKey).toHaveBeenCalledWith(
|
|
413
|
+
expect.objectContaining({ ch: 'o', mod: 'ctrl', raw: 'ctrl+o' })
|
|
414
|
+
)
|
|
415
|
+
expect(setBell).toHaveBeenCalledWith(false)
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
it('reapplies the latest value on each invocation (mtime-reload semantics)', async () => {
|
|
419
|
+
const gw = makeFakeGw({ config: { display: {}, voice: { record_key: 'ctrl+b' } } })
|
|
420
|
+
const setBell = vi.fn()
|
|
421
|
+
const setVoiceRecordKey = vi.fn()
|
|
422
|
+
|
|
423
|
+
await hydrateFullConfig(gw, setBell, setVoiceRecordKey)
|
|
424
|
+
expect(setVoiceRecordKey).toHaveBeenLastCalledWith(expect.objectContaining({ ch: 'b' }))
|
|
425
|
+
|
|
426
|
+
// Simulate a config edit: gw now returns a new shortcut.
|
|
427
|
+
gw.request = vi.fn(() => Promise.resolve({ config: { display: {}, voice: { record_key: 'alt+space' } } }))
|
|
428
|
+
|
|
429
|
+
await hydrateFullConfig(gw, setBell, setVoiceRecordKey)
|
|
430
|
+
expect(setVoiceRecordKey).toHaveBeenLastCalledWith(
|
|
431
|
+
expect.objectContaining({ ch: 'space', mod: 'alt', named: 'space' })
|
|
432
|
+
)
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
it('leaves cached voiceRecordKey untouched when the RPC fails', async () => {
|
|
436
|
+
const gw = { request: vi.fn(() => Promise.reject(new Error('boom'))), on: vi.fn(), off: vi.fn() } as any
|
|
437
|
+
const setBell = vi.fn()
|
|
438
|
+
const setVoiceRecordKey = vi.fn()
|
|
439
|
+
|
|
440
|
+
const result = await hydrateFullConfig(gw, setBell, setVoiceRecordKey)
|
|
441
|
+
|
|
442
|
+
// quietRpc() swallows the error and returns null; applyDisplay
|
|
443
|
+
// sees cfg=null and skips the voice setter (Copilot round-8).
|
|
444
|
+
expect(result).toBeNull()
|
|
445
|
+
expect(setVoiceRecordKey).not.toHaveBeenCalled()
|
|
446
|
+
// bell setter still fires — applyDisplay's null-cfg path applies
|
|
447
|
+
// the documented bell default (false).
|
|
448
|
+
expect(setBell).toHaveBeenCalledWith(false)
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
it('threads through without a voice setter (back-compat call sites)', async () => {
|
|
452
|
+
const gw = makeFakeGw({ config: { display: { bell_on_complete: true } } })
|
|
453
|
+
const setBell = vi.fn()
|
|
454
|
+
|
|
455
|
+
// No third arg — applyDisplay must not throw and must still apply
|
|
456
|
+
// display flags (round-2 / round-8 invariant).
|
|
457
|
+
await expect(hydrateFullConfig(gw, setBell)).resolves.toBeTruthy()
|
|
458
|
+
expect(setBell).toHaveBeenCalledWith(true)
|
|
459
|
+
})
|
|
460
|
+
})
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { applyVoiceRecordResponse, shouldFallThroughForScroll } from '../app/useInputHandlers.js'
|
|
4
|
+
|
|
5
|
+
const baseKey = {
|
|
6
|
+
downArrow: false,
|
|
7
|
+
pageDown: false,
|
|
8
|
+
pageUp: false,
|
|
9
|
+
shift: false,
|
|
10
|
+
upArrow: false,
|
|
11
|
+
wheelDown: false,
|
|
12
|
+
wheelUp: false
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('shouldFallThroughForScroll — keep transcript scrolling alive during prompt overlays', () => {
|
|
16
|
+
it('falls through for wheel scrolls', () => {
|
|
17
|
+
expect(shouldFallThroughForScroll({ ...baseKey, wheelUp: true })).toBe(true)
|
|
18
|
+
expect(shouldFallThroughForScroll({ ...baseKey, wheelDown: true })).toBe(true)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('falls through for PageUp / PageDown', () => {
|
|
22
|
+
expect(shouldFallThroughForScroll({ ...baseKey, pageUp: true })).toBe(true)
|
|
23
|
+
expect(shouldFallThroughForScroll({ ...baseKey, pageDown: true })).toBe(true)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('falls through for Shift+ArrowUp / Shift+ArrowDown', () => {
|
|
27
|
+
expect(shouldFallThroughForScroll({ ...baseKey, shift: true, upArrow: true })).toBe(true)
|
|
28
|
+
expect(shouldFallThroughForScroll({ ...baseKey, shift: true, downArrow: true })).toBe(true)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('does NOT fall through for plain arrows — those drive in-prompt selection', () => {
|
|
32
|
+
expect(shouldFallThroughForScroll({ ...baseKey, upArrow: true })).toBe(false)
|
|
33
|
+
expect(shouldFallThroughForScroll({ ...baseKey, downArrow: true })).toBe(false)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('does NOT fall through for plain Shift — without an arrow it is a no-op', () => {
|
|
37
|
+
expect(shouldFallThroughForScroll({ ...baseKey, shift: true })).toBe(false)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('does NOT fall through for unrelated state (no scroll keys held)', () => {
|
|
41
|
+
expect(shouldFallThroughForScroll(baseKey)).toBe(false)
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe('applyVoiceRecordResponse', () => {
|
|
46
|
+
it('reverts optimistic REC state when the gateway reports voice busy', () => {
|
|
47
|
+
const setProcessing = vi.fn()
|
|
48
|
+
const setRecording = vi.fn()
|
|
49
|
+
const sys = vi.fn()
|
|
50
|
+
|
|
51
|
+
applyVoiceRecordResponse({ status: 'busy' }, true, { setProcessing, setRecording }, sys)
|
|
52
|
+
|
|
53
|
+
expect(setRecording).toHaveBeenCalledWith(false)
|
|
54
|
+
expect(setProcessing).toHaveBeenCalledWith(true)
|
|
55
|
+
expect(sys).toHaveBeenCalledWith('voice: still transcribing; try again shortly')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('keeps optimistic REC state for successful recording starts', () => {
|
|
59
|
+
const setProcessing = vi.fn()
|
|
60
|
+
const setRecording = vi.fn()
|
|
61
|
+
|
|
62
|
+
applyVoiceRecordResponse({ status: 'recording' }, true, { setProcessing, setRecording }, vi.fn())
|
|
63
|
+
|
|
64
|
+
expect(setRecording).not.toHaveBeenCalled()
|
|
65
|
+
expect(setProcessing).not.toHaveBeenCalled()
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('reverts optimistic REC state when the gateway returns null', () => {
|
|
69
|
+
const setProcessing = vi.fn()
|
|
70
|
+
const setRecording = vi.fn()
|
|
71
|
+
|
|
72
|
+
applyVoiceRecordResponse(null, true, { setProcessing, setRecording }, vi.fn())
|
|
73
|
+
|
|
74
|
+
expect(setRecording).toHaveBeenCalledWith(false)
|
|
75
|
+
expect(setProcessing).toHaveBeenCalledWith(false)
|
|
76
|
+
})
|
|
77
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { removeAtInPlace } from '../hooks/useQueue.js'
|
|
4
|
+
|
|
5
|
+
describe('removeAtInPlace', () => {
|
|
6
|
+
it('removes the item at the given index in place', () => {
|
|
7
|
+
const arr = ['a', 'b', 'c']
|
|
8
|
+
|
|
9
|
+
removeAtInPlace(arr, 1)
|
|
10
|
+
expect(arr).toEqual(['a', 'c'])
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('is a no-op when the index is out of bounds', () => {
|
|
14
|
+
const arr = ['a', 'b']
|
|
15
|
+
|
|
16
|
+
removeAtInPlace(arr, -1)
|
|
17
|
+
removeAtInPlace(arr, 5)
|
|
18
|
+
expect(arr).toEqual(['a', 'b'])
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('returns the same reference (mutates in place)', () => {
|
|
22
|
+
const arr = ['x']
|
|
23
|
+
const same = removeAtInPlace(arr, 0)
|
|
24
|
+
|
|
25
|
+
expect(same).toBe(arr)
|
|
26
|
+
expect(arr).toEqual([])
|
|
27
|
+
})
|
|
28
|
+
})
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { mkdtempSync, readFileSync, rmSync } from 'node:fs'
|
|
2
|
+
import { tmpdir } from 'node:os'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
6
|
+
|
|
7
|
+
import { turnController } from '../app/turnController.js'
|
|
8
|
+
import { getTurnState, resetTurnState } from '../app/turnStore.js'
|
|
9
|
+
import { patchUiState, resetUiState } from '../app/uiStore.js'
|
|
10
|
+
import { hydrateLiveSessionInflight, liveSessionInflightMessages, writeActiveSessionFile } from '../app/useSessionLifecycle.js'
|
|
11
|
+
|
|
12
|
+
describe('writeActiveSessionFile', () => {
|
|
13
|
+
let dir = ''
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
if (dir) {
|
|
17
|
+
rmSync(dir, { force: true, recursive: true })
|
|
18
|
+
dir = ''
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('writes the actual resumed session id for the shell exit summary', () => {
|
|
23
|
+
dir = mkdtempSync(join(tmpdir(), 'nastech-tui-active-'))
|
|
24
|
+
const path = join(dir, 'active.json')
|
|
25
|
+
|
|
26
|
+
writeActiveSessionFile('actual_session', path)
|
|
27
|
+
|
|
28
|
+
expect(JSON.parse(readFileSync(path, 'utf8'))).toEqual({ session_id: 'actual_session' })
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
describe('live session activation in-flight state', () => {
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
resetUiState()
|
|
36
|
+
resetTurnState()
|
|
37
|
+
turnController.fullReset()
|
|
38
|
+
patchUiState({ streaming: true })
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('keeps the in-flight user prompt in history and hydrates partial assistant text', () => {
|
|
42
|
+
const inflight = { assistant: 'partial answer', streaming: true, user: 'write a long answer' }
|
|
43
|
+
|
|
44
|
+
expect(liveSessionInflightMessages(inflight)).toEqual([{ role: 'user', text: 'write a long answer' }])
|
|
45
|
+
|
|
46
|
+
hydrateLiveSessionInflight(inflight)
|
|
47
|
+
|
|
48
|
+
expect(turnController.bufRef).toBe('partial answer')
|
|
49
|
+
expect(getTurnState().streaming).toBe('partial answer')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('ignores empty in-flight payloads', () => {
|
|
53
|
+
expect(liveSessionInflightMessages({ assistant: '', streaming: false, user: ' ' })).toEqual([])
|
|
54
|
+
|
|
55
|
+
hydrateLiveSessionInflight({ assistant: '', streaming: false, user: '' })
|
|
56
|
+
|
|
57
|
+
expect(turnController.bufRef).toBe('')
|
|
58
|
+
expect(getTurnState().streaming).toBe('')
|
|
59
|
+
})
|
|
60
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { ensureVirtualItemHeight } from '../hooks/useVirtualHistory.js'
|
|
4
|
+
|
|
5
|
+
describe('ensureVirtualItemHeight', () => {
|
|
6
|
+
it('reuses cached heights without invoking the estimator', () => {
|
|
7
|
+
const heights = new Map([['a', 7]])
|
|
8
|
+
const estimateHeight = vi.fn(() => 99)
|
|
9
|
+
|
|
10
|
+
expect(ensureVirtualItemHeight(heights, 'a', 0, 4, estimateHeight)).toBe(7)
|
|
11
|
+
expect(estimateHeight).not.toHaveBeenCalled()
|
|
12
|
+
expect(heights.get('a')).toBe(7)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('lazily seeds missing heights from the estimator', () => {
|
|
16
|
+
const heights = new Map<string, number>()
|
|
17
|
+
const estimateHeight = vi.fn((index: number) => 10 + index)
|
|
18
|
+
|
|
19
|
+
expect(ensureVirtualItemHeight(heights, 'b', 2, 4, estimateHeight)).toBe(12)
|
|
20
|
+
expect(estimateHeight).toHaveBeenCalledTimes(1)
|
|
21
|
+
expect(estimateHeight).toHaveBeenCalledWith(2, 'b')
|
|
22
|
+
expect(heights.get('b')).toBe(12)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('falls back to the default estimate when no estimator is provided', () => {
|
|
26
|
+
const heights = new Map<string, number>()
|
|
27
|
+
|
|
28
|
+
expect(ensureVirtualItemHeight(heights, 'c', 0, 4)).toBe(4)
|
|
29
|
+
expect(heights.get('c')).toBe(4)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('normalizes non-positive estimates to a minimum of one row', () => {
|
|
33
|
+
const heights = new Map<string, number>()
|
|
34
|
+
const estimateHeight = vi.fn(() => 0)
|
|
35
|
+
|
|
36
|
+
expect(ensureVirtualItemHeight(heights, 'd', 0, 0, estimateHeight)).toBe(1)
|
|
37
|
+
expect(heights.get('d')).toBe(1)
|
|
38
|
+
})
|
|
39
|
+
})
|