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,311 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
// `theme.js` reads `process.env` at module-load to compute DEFAULT_THEME,
|
|
4
|
+
// and `fromSkin` closes over DEFAULT_THEME. A developer shell with
|
|
5
|
+
// NASTECH_TUI_THEME=light (or NASTECH_TUI_BACKGROUND set to something
|
|
6
|
+
// bright) would flip the base and turn these assertions into a local-
|
|
7
|
+
// only failure. We sterilize the relevant env vars + dynamically
|
|
8
|
+
// import the module fresh so EVERY symbol that closes over the env
|
|
9
|
+
// (DEFAULT_THEME, DARK_THEME, LIGHT_THEME, fromSkin) is loaded against
|
|
10
|
+
// a known-empty environment.
|
|
11
|
+
//
|
|
12
|
+
// `detectLightMode` takes env as an explicit arg, so it's safe to import
|
|
13
|
+
// statically — but we stay consistent and dynamic-import it too.
|
|
14
|
+
const RELEVANT_ENV = [
|
|
15
|
+
'NASTECH_TUI_LIGHT',
|
|
16
|
+
'NASTECH_TUI_THEME',
|
|
17
|
+
'NASTECH_TUI_BACKGROUND',
|
|
18
|
+
'COLORFGBG',
|
|
19
|
+
'COLORTERM',
|
|
20
|
+
'TERM_PROGRAM'
|
|
21
|
+
] as const
|
|
22
|
+
|
|
23
|
+
async function importThemeWithEnv(env: Partial<Record<(typeof RELEVANT_ENV)[number], string>> = {}) {
|
|
24
|
+
for (const key of RELEVANT_ENV) {
|
|
25
|
+
vi.stubEnv(key, env[key] ?? '')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
vi.resetModules()
|
|
29
|
+
|
|
30
|
+
return import('../theme.js')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function importThemeWithCleanEnv() {
|
|
34
|
+
return importThemeWithEnv()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
vi.unstubAllEnvs()
|
|
39
|
+
vi.resetModules()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
describe('DEFAULT_THEME', () => {
|
|
43
|
+
it('has brand defaults', async () => {
|
|
44
|
+
const { DEFAULT_THEME } = await importThemeWithCleanEnv()
|
|
45
|
+
|
|
46
|
+
expect(DEFAULT_THEME.brand.name).toBe('NasTech Agent')
|
|
47
|
+
expect(DEFAULT_THEME.brand.prompt).toBe('❯')
|
|
48
|
+
expect(DEFAULT_THEME.brand.tool).toBe('┊')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('has color palette', async () => {
|
|
52
|
+
const { DEFAULT_THEME } = await importThemeWithCleanEnv()
|
|
53
|
+
|
|
54
|
+
expect(DEFAULT_THEME.color.primary).toBe('#FFD700')
|
|
55
|
+
expect(DEFAULT_THEME.color.error).toBe('#ef5350')
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe('LIGHT_THEME', () => {
|
|
60
|
+
it('avoids bright-yellow accents unreadable on white backgrounds (#11300)', async () => {
|
|
61
|
+
const { LIGHT_THEME } = await importThemeWithCleanEnv()
|
|
62
|
+
|
|
63
|
+
expect(LIGHT_THEME.color.primary).not.toBe('#FFD700')
|
|
64
|
+
expect(LIGHT_THEME.color.accent).not.toBe('#FFBF00')
|
|
65
|
+
expect(LIGHT_THEME.color.muted).not.toBe('#B8860B')
|
|
66
|
+
expect(LIGHT_THEME.color.statusWarn).not.toBe('#FFD700')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('keeps the same shape as DARK_THEME', async () => {
|
|
70
|
+
const { DARK_THEME, LIGHT_THEME } = await importThemeWithCleanEnv()
|
|
71
|
+
|
|
72
|
+
expect(Object.keys(LIGHT_THEME.color).sort()).toEqual(Object.keys(DARK_THEME.color).sort())
|
|
73
|
+
expect(LIGHT_THEME.brand).toEqual(DARK_THEME.brand)
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
describe('DEFAULT_THEME aliasing', () => {
|
|
78
|
+
it('defaults to DARK_THEME when nothing signals light', async () => {
|
|
79
|
+
const { DEFAULT_THEME, DARK_THEME: DARK } = await importThemeWithCleanEnv()
|
|
80
|
+
|
|
81
|
+
expect(DEFAULT_THEME).toBe(DARK)
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
describe('detectLightMode', () => {
|
|
86
|
+
it('returns false on empty env', async () => {
|
|
87
|
+
const { detectLightMode } = await importThemeWithCleanEnv()
|
|
88
|
+
|
|
89
|
+
expect(detectLightMode({})).toBe(false)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('defaults Apple Terminal to light when no stronger signal is present', async () => {
|
|
93
|
+
const { detectLightMode } = await importThemeWithCleanEnv()
|
|
94
|
+
|
|
95
|
+
expect(detectLightMode({ TERM_PROGRAM: 'Apple_Terminal' })).toBe(true)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('honors NASTECH_TUI_LIGHT on/off', async () => {
|
|
99
|
+
const { detectLightMode } = await importThemeWithCleanEnv()
|
|
100
|
+
|
|
101
|
+
expect(detectLightMode({ NASTECH_TUI_LIGHT: '1' })).toBe(true)
|
|
102
|
+
expect(detectLightMode({ NASTECH_TUI_LIGHT: 'true' })).toBe(true)
|
|
103
|
+
expect(detectLightMode({ NASTECH_TUI_LIGHT: 'on' })).toBe(true)
|
|
104
|
+
expect(detectLightMode({ NASTECH_TUI_LIGHT: '0' })).toBe(false)
|
|
105
|
+
expect(detectLightMode({ NASTECH_TUI_LIGHT: 'off' })).toBe(false)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('sniffs COLORFGBG bg slots 7 and 15 as light (#11300)', async () => {
|
|
109
|
+
const { detectLightMode } = await importThemeWithCleanEnv()
|
|
110
|
+
|
|
111
|
+
expect(detectLightMode({ COLORFGBG: '0;15' })).toBe(true)
|
|
112
|
+
expect(detectLightMode({ COLORFGBG: '0;default;15' })).toBe(true)
|
|
113
|
+
expect(detectLightMode({ COLORFGBG: '0;7' })).toBe(true)
|
|
114
|
+
expect(detectLightMode({ COLORFGBG: '15;0' })).toBe(false)
|
|
115
|
+
expect(detectLightMode({ COLORFGBG: '7;default;0' })).toBe(false)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('falls through on malformed COLORFGBG with empty/non-numeric trailing field', async () => {
|
|
119
|
+
const { detectLightMode } = await importThemeWithCleanEnv()
|
|
120
|
+
// `Number('')` is 0, so `'15;'` would have been read as bg=0
|
|
121
|
+
// (authoritative dark) and incorrectly blocked TERM_PROGRAM.
|
|
122
|
+
// The strict /^\d+$/ guard makes these fall through instead.
|
|
123
|
+
const allowList = new Set(['Apple_Terminal'])
|
|
124
|
+
|
|
125
|
+
expect(detectLightMode({ COLORFGBG: '15;', TERM_PROGRAM: 'Apple_Terminal' }, allowList)).toBe(true)
|
|
126
|
+
expect(detectLightMode({ COLORFGBG: 'default;default', TERM_PROGRAM: 'Apple_Terminal' }, allowList)).toBe(true)
|
|
127
|
+
// Without an allow-list match, fall-through still defaults to dark.
|
|
128
|
+
expect(detectLightMode({ COLORFGBG: '15;' })).toBe(false)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('lets NASTECH_TUI_LIGHT=0 override a light COLORFGBG', async () => {
|
|
132
|
+
const { detectLightMode } = await importThemeWithCleanEnv()
|
|
133
|
+
|
|
134
|
+
expect(detectLightMode({ COLORFGBG: '0;15', NASTECH_TUI_LIGHT: '0' })).toBe(false)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('honors NASTECH_TUI_THEME=light/dark as a symmetric explicit override', async () => {
|
|
138
|
+
const { detectLightMode } = await importThemeWithCleanEnv()
|
|
139
|
+
|
|
140
|
+
expect(detectLightMode({ NASTECH_TUI_THEME: 'light' })).toBe(true)
|
|
141
|
+
expect(detectLightMode({ NASTECH_TUI_THEME: 'dark' })).toBe(false)
|
|
142
|
+
expect(detectLightMode({ COLORFGBG: '0;15', NASTECH_TUI_THEME: 'dark' })).toBe(false)
|
|
143
|
+
expect(detectLightMode({ COLORFGBG: '15;0', NASTECH_TUI_THEME: 'light' })).toBe(true)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('uses NASTECH_TUI_BACKGROUND luminance when COLORFGBG is missing', async () => {
|
|
147
|
+
const { detectLightMode } = await importThemeWithCleanEnv()
|
|
148
|
+
|
|
149
|
+
expect(detectLightMode({ NASTECH_TUI_BACKGROUND: '#ffffff' })).toBe(true)
|
|
150
|
+
expect(detectLightMode({ NASTECH_TUI_BACKGROUND: '#000000' })).toBe(false)
|
|
151
|
+
expect(detectLightMode({ NASTECH_TUI_BACKGROUND: '#1e1e1e' })).toBe(false)
|
|
152
|
+
// Three-char hex normalises like CSS.
|
|
153
|
+
expect(detectLightMode({ NASTECH_TUI_BACKGROUND: '#fff' })).toBe(true)
|
|
154
|
+
// Garbage falls through to the default-dark path.
|
|
155
|
+
expect(detectLightMode({ NASTECH_TUI_BACKGROUND: 'not-a-colour' })).toBe(false)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('rejects partially-invalid hex instead of silently truncating', async () => {
|
|
159
|
+
const { detectLightMode } = await importThemeWithCleanEnv()
|
|
160
|
+
// `parseInt('fffgff'.slice(2,4), 16)` would return 15 — the strict
|
|
161
|
+
// regex must reject these inputs so they fall through to default-
|
|
162
|
+
// dark instead of producing a false-positive light reading.
|
|
163
|
+
expect(detectLightMode({ NASTECH_TUI_BACKGROUND: '#fffgff' })).toBe(false)
|
|
164
|
+
expect(detectLightMode({ NASTECH_TUI_BACKGROUND: 'ffggff' })).toBe(false)
|
|
165
|
+
expect(detectLightMode({ NASTECH_TUI_BACKGROUND: '#xyz' })).toBe(false)
|
|
166
|
+
// Wrong length also rejected (no implicit padding/truncation).
|
|
167
|
+
expect(detectLightMode({ NASTECH_TUI_BACKGROUND: '#fffff' })).toBe(false)
|
|
168
|
+
expect(detectLightMode({ NASTECH_TUI_BACKGROUND: '#fffffff' })).toBe(false)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('treats COLORFGBG as authoritative when present so it dominates the TERM_PROGRAM allow-list', async () => {
|
|
172
|
+
const { detectLightMode } = await importThemeWithCleanEnv()
|
|
173
|
+
// Injecting the allow-list keeps this precedence rule explicit even if
|
|
174
|
+
// production defaults change.
|
|
175
|
+
const allowList = new Set(['Apple_Terminal'])
|
|
176
|
+
|
|
177
|
+
// Sanity: the allow-list alone WOULD turn this terminal light.
|
|
178
|
+
expect(detectLightMode({ TERM_PROGRAM: 'Apple_Terminal' }, allowList)).toBe(true)
|
|
179
|
+
|
|
180
|
+
// Dark COLORFGBG must beat the allow-list.
|
|
181
|
+
expect(detectLightMode({ COLORFGBG: '15;0', TERM_PROGRAM: 'Apple_Terminal' }, allowList)).toBe(false)
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
describe('fromSkin', () => {
|
|
186
|
+
// `fromSkin` closes over DEFAULT_THEME (which is env-derived), so we
|
|
187
|
+
// must dynamic-import it after sterilizing env — otherwise an ambient
|
|
188
|
+
// NASTECH_TUI_THEME=light would flip the base palette and make these
|
|
189
|
+
// assertions order-dependent on the developer's shell.
|
|
190
|
+
|
|
191
|
+
it('overrides banner colors', async () => {
|
|
192
|
+
const { fromSkin } = await importThemeWithCleanEnv()
|
|
193
|
+
|
|
194
|
+
expect(fromSkin({ banner_title: '#FF0000' }, {}).color.primary).toBe('#FF0000')
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('preserves unset colors', async () => {
|
|
198
|
+
const { DEFAULT_THEME, fromSkin } = await importThemeWithCleanEnv()
|
|
199
|
+
|
|
200
|
+
expect(fromSkin({ banner_title: '#FF0000' }, {}).color.accent).toBe(DEFAULT_THEME.color.accent)
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
it('derives completion current background from resolved completion background', async () => {
|
|
204
|
+
const { fromSkin } = await importThemeWithCleanEnv()
|
|
205
|
+
|
|
206
|
+
const theme = fromSkin({ banner_accent: '#000000', completion_menu_bg: '#ffffff' }, {})
|
|
207
|
+
|
|
208
|
+
expect(theme.color.completionBg).toBe('#ffffff')
|
|
209
|
+
expect(theme.color.completionCurrentBg).toBe('#bfbfbf')
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
it('uses active completion color as the selection highlight fallback', async () => {
|
|
213
|
+
const { fromSkin } = await importThemeWithCleanEnv()
|
|
214
|
+
|
|
215
|
+
const theme = fromSkin({ completion_menu_current_bg: '#123456' }, {})
|
|
216
|
+
|
|
217
|
+
expect(theme.color.selectionBg).toBe('#123456')
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
it('maps completion meta background colors from skins', async () => {
|
|
221
|
+
const { fromSkin } = await importThemeWithCleanEnv()
|
|
222
|
+
|
|
223
|
+
const theme = fromSkin({
|
|
224
|
+
completion_menu_meta_bg: '#111111',
|
|
225
|
+
completion_menu_meta_current_bg: '#222222'
|
|
226
|
+
}, {})
|
|
227
|
+
|
|
228
|
+
expect(theme.color.completionMetaBg).toBe('#111111')
|
|
229
|
+
expect(theme.color.completionMetaCurrentBg).toBe('#222222')
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
it('lets selection_bg override completion highlight colors', async () => {
|
|
233
|
+
const { fromSkin } = await importThemeWithCleanEnv()
|
|
234
|
+
|
|
235
|
+
const theme = fromSkin({ completion_menu_current_bg: '#123456', selection_bg: '#654321' }, {})
|
|
236
|
+
|
|
237
|
+
expect(theme.color.selectionBg).toBe('#654321')
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it('overrides branding', async () => {
|
|
241
|
+
const { fromSkin } = await importThemeWithCleanEnv()
|
|
242
|
+
const { brand } = fromSkin({}, { agent_name: 'TestBot', prompt_symbol: '$' })
|
|
243
|
+
|
|
244
|
+
expect(brand.name).toBe('TestBot')
|
|
245
|
+
expect(brand.prompt).toBe('$')
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
it('normalizes skin prompt symbols to trimmed single-line text', async () => {
|
|
249
|
+
const { DEFAULT_THEME, fromSkin } = await importThemeWithCleanEnv()
|
|
250
|
+
|
|
251
|
+
expect(fromSkin({}, { prompt_symbol: ' ⚔ ❯ \n' }).brand.prompt).toBe('⚔ ❯')
|
|
252
|
+
expect(fromSkin({}, { prompt_symbol: ' Ψ > \n' }).brand.prompt).toBe('Ψ >')
|
|
253
|
+
expect(fromSkin({}, { prompt_symbol: '\n\t' }).brand.prompt).toBe(DEFAULT_THEME.brand.prompt)
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
it('defaults for empty skin', async () => {
|
|
257
|
+
const { DEFAULT_THEME, fromSkin } = await importThemeWithCleanEnv()
|
|
258
|
+
|
|
259
|
+
expect(fromSkin({}, {}).color).toEqual(DEFAULT_THEME.color)
|
|
260
|
+
expect(fromSkin({}, {}).brand.icon).toBe(DEFAULT_THEME.brand.icon)
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
it('normalizes non-banner foregrounds on light Apple Terminal', async () => {
|
|
264
|
+
const { fromSkin } = await importThemeWithEnv({ TERM_PROGRAM: 'Apple_Terminal' })
|
|
265
|
+
|
|
266
|
+
const theme = fromSkin({
|
|
267
|
+
banner_accent: '#FFBF00',
|
|
268
|
+
banner_border: '#CD7F32',
|
|
269
|
+
banner_dim: '#B8860B',
|
|
270
|
+
banner_text: '#FFF8DC',
|
|
271
|
+
banner_title: '#FFD700',
|
|
272
|
+
prompt: '#FFF8DC'
|
|
273
|
+
}, {})
|
|
274
|
+
|
|
275
|
+
expect(theme.color.primary).toBe('#FFD700')
|
|
276
|
+
expect(theme.color.accent).toBe('#FFBF00')
|
|
277
|
+
expect(theme.color.border).toBe('#CD7F32')
|
|
278
|
+
expect(theme.color.muted).toBe('ansi256(245)')
|
|
279
|
+
expect(theme.color.text).toBe('ansi256(136)')
|
|
280
|
+
expect(theme.color.prompt).toBe('ansi256(136)')
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
it('does not normalize light Apple Terminal when truecolor is advertised', async () => {
|
|
284
|
+
const { fromSkin } = await importThemeWithEnv({ COLORTERM: 'truecolor', TERM_PROGRAM: 'Apple_Terminal' })
|
|
285
|
+
const theme = fromSkin({ banner_text: '#FFF8DC' }, {})
|
|
286
|
+
|
|
287
|
+
expect(theme.color.text).toBe('#FFF8DC')
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
it('normalizes Apple Terminal names before matching', async () => {
|
|
291
|
+
const { fromSkin } = await importThemeWithEnv({ TERM_PROGRAM: ' Apple_Terminal ' })
|
|
292
|
+
const theme = fromSkin({ banner_text: '#FFF8DC' }, {})
|
|
293
|
+
|
|
294
|
+
expect(theme.color.text).toBe('ansi256(136)')
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
it('passes banner logo/hero', async () => {
|
|
298
|
+
const { fromSkin } = await importThemeWithCleanEnv()
|
|
299
|
+
|
|
300
|
+
expect(fromSkin({}, {}, 'LOGO', 'HERO').bannerLogo).toBe('LOGO')
|
|
301
|
+
expect(fromSkin({}, {}, 'LOGO', 'HERO').bannerHero).toBe('HERO')
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
it('maps ui_ color keys + cascades to status', async () => {
|
|
305
|
+
const { fromSkin } = await importThemeWithCleanEnv()
|
|
306
|
+
const { color } = fromSkin({ ui_ok: '#008000' }, {})
|
|
307
|
+
|
|
308
|
+
expect(color.ok).toBe('#008000')
|
|
309
|
+
expect(color.statusGood).toBe('#008000')
|
|
310
|
+
})
|
|
311
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { turnController } from '../app/turnController.js'
|
|
4
|
+
import { resetTurnState } from '../app/turnStore.js'
|
|
5
|
+
import { getUiState, patchUiState, resetUiState } from '../app/uiStore.js'
|
|
6
|
+
|
|
7
|
+
// turnController.startMessage() treats "flash and yield" notices (the usage-band
|
|
8
|
+
// credits.usage and the one-time credits.grant_spent transition) as "show until next
|
|
9
|
+
// prompt": they flash once, then yield when the next turn starts. Depletion (and
|
|
10
|
+
// other notices) are sticky until the policy clears them.
|
|
11
|
+
describe('turnController.startMessage — flash-and-yield notices clear on next prompt', () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
resetUiState()
|
|
14
|
+
resetTurnState()
|
|
15
|
+
turnController.fullReset()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('clears a standing credits.usage notice when a new turn starts', () => {
|
|
19
|
+
patchUiState({
|
|
20
|
+
notice: { key: 'credits.usage', kind: 'sticky', level: 'warn', text: '⚠ Credits 90% used · $20.00 cap' }
|
|
21
|
+
})
|
|
22
|
+
turnController.startMessage()
|
|
23
|
+
expect(getUiState().notice).toBeNull()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('clears a standing credits.grant_spent notice when a new turn starts', () => {
|
|
27
|
+
// One-time "you've crossed onto top-up" heads-up — shouldn't camp the bar
|
|
28
|
+
// (e.g. "Grant spent · $990 top-up left" with plenty of top-up remaining).
|
|
29
|
+
patchUiState({
|
|
30
|
+
notice: { key: 'credits.grant_spent', kind: 'sticky', level: 'info', text: '• Grant spent · $990.00 top-up left' }
|
|
31
|
+
})
|
|
32
|
+
turnController.startMessage()
|
|
33
|
+
expect(getUiState().notice).toBeNull()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('leaves a sticky credits.depleted notice across a new turn', () => {
|
|
37
|
+
patchUiState({
|
|
38
|
+
notice: { key: 'credits.depleted', kind: 'sticky', level: 'error', text: '✕ Credit access paused · run /usage for balance' }
|
|
39
|
+
})
|
|
40
|
+
turnController.startMessage()
|
|
41
|
+
expect(getUiState().notice?.key).toBe('credits.depleted')
|
|
42
|
+
})
|
|
43
|
+
})
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
archiveDoneTodos,
|
|
5
|
+
archiveTodosAtTurnEnd,
|
|
6
|
+
getTurnState,
|
|
7
|
+
patchTurnState,
|
|
8
|
+
resetTurnState,
|
|
9
|
+
toggleTodoCollapsed
|
|
10
|
+
} from '../app/turnStore.js'
|
|
11
|
+
|
|
12
|
+
describe('turnStore live progress helpers', () => {
|
|
13
|
+
beforeEach(() => resetTurnState())
|
|
14
|
+
|
|
15
|
+
it('archives completed todos into a transcript trail and clears the live anchor', () => {
|
|
16
|
+
patchTurnState({
|
|
17
|
+
todos: [
|
|
18
|
+
{ content: 'prep', id: 'prep', status: 'completed' },
|
|
19
|
+
{ content: 'serve', id: 'serve', status: 'completed' }
|
|
20
|
+
]
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
expect(archiveTodosAtTurnEnd()).toEqual([
|
|
24
|
+
{
|
|
25
|
+
kind: 'trail',
|
|
26
|
+
role: 'system',
|
|
27
|
+
text: '',
|
|
28
|
+
todoCollapsedByDefault: true,
|
|
29
|
+
todos: [
|
|
30
|
+
{ content: 'prep', id: 'prep', status: 'completed' },
|
|
31
|
+
{ content: 'serve', id: 'serve', status: 'completed' }
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
])
|
|
35
|
+
expect(getTurnState().todos).toEqual([])
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('archives incomplete todos with an incomplete flag so the hint renders', () => {
|
|
39
|
+
patchTurnState({
|
|
40
|
+
todos: [
|
|
41
|
+
{ content: 'cook', id: 'cook', status: 'completed' },
|
|
42
|
+
{ content: 'serve', id: 'serve', status: 'in_progress' },
|
|
43
|
+
{ content: 'eat', id: 'eat', status: 'pending' }
|
|
44
|
+
]
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const archived = archiveTodosAtTurnEnd()
|
|
48
|
+
expect(archived).toHaveLength(1)
|
|
49
|
+
expect(archived[0]!.todoIncomplete).toBe(true)
|
|
50
|
+
expect(archived[0]!.todos?.map(t => t.id)).toEqual(['cook', 'serve', 'eat'])
|
|
51
|
+
expect(getTurnState().todos).toEqual([])
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('returns nothing when there are no todos at turn end', () => {
|
|
55
|
+
expect(archiveTodosAtTurnEnd()).toEqual([])
|
|
56
|
+
expect(archiveDoneTodos()).toEqual([])
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('tracks collapsed state independently of todo content', () => {
|
|
60
|
+
toggleTodoCollapsed()
|
|
61
|
+
expect(getTurnState().todoCollapsed).toBe(true)
|
|
62
|
+
|
|
63
|
+
toggleTodoCollapsed()
|
|
64
|
+
expect(getTurnState().todoCollapsed).toBe(false)
|
|
65
|
+
})
|
|
66
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { completionRequestForInput } from '../hooks/useCompletion.js'
|
|
4
|
+
|
|
5
|
+
describe('completionRequestForInput', () => {
|
|
6
|
+
it('routes real slash commands to slash completion', () => {
|
|
7
|
+
expect(completionRequestForInput('/help')).toMatchObject({
|
|
8
|
+
method: 'complete.slash',
|
|
9
|
+
params: { text: '/help' },
|
|
10
|
+
replaceFrom: 1
|
|
11
|
+
})
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('does not route absolute paths through slash completion', () => {
|
|
15
|
+
expect(
|
|
16
|
+
completionRequestForInput('/home/d/Desktop/agenda/CrimsonRed/.nastech/plans/2026-05-04-HANDOFF-NEXT.md')
|
|
17
|
+
).toMatchObject({
|
|
18
|
+
method: 'complete.path',
|
|
19
|
+
params: { word: '/home/d/Desktop/agenda/CrimsonRed/.nastech/plans/2026-05-04-HANDOFF-NEXT.md' },
|
|
20
|
+
replaceFrom: 0
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('keeps path completion for trailing absolute path tokens', () => {
|
|
25
|
+
expect(completionRequestForInput('read /home/d/Desktop/file.md')).toMatchObject({
|
|
26
|
+
method: 'complete.path',
|
|
27
|
+
params: { word: '/home/d/Desktop/file.md' },
|
|
28
|
+
replaceFrom: 5
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('leaves plain text alone', () => {
|
|
33
|
+
expect(completionRequestForInput('hello there')).toBeNull()
|
|
34
|
+
})
|
|
35
|
+
})
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { looksLikeDroppedPath } from '../app/useComposerState.js'
|
|
4
|
+
|
|
5
|
+
describe('looksLikeDroppedPath', () => {
|
|
6
|
+
it('recognizes macOS screenshot temp paths and file URIs', () => {
|
|
7
|
+
expect(looksLikeDroppedPath('/var/folders/x/T/TemporaryItems/Screenshot\\ 2026-04-21\\ at\\ 1.04.43 PM.png')).toBe(
|
|
8
|
+
true
|
|
9
|
+
)
|
|
10
|
+
expect(
|
|
11
|
+
looksLikeDroppedPath('file:///var/folders/x/T/TemporaryItems/Screenshot%202026-04-21%20at%201.04.43%20PM.png')
|
|
12
|
+
).toBe(true)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('rejects normal multiline or plain text paste', () => {
|
|
16
|
+
expect(looksLikeDroppedPath('hello world')).toBe(false)
|
|
17
|
+
expect(looksLikeDroppedPath('line one\nline two')).toBe(false)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('recognizes common image file extensions', () => {
|
|
21
|
+
expect(looksLikeDroppedPath('/Users/me/Desktop/photo.jpg')).toBe(true)
|
|
22
|
+
expect(looksLikeDroppedPath('/Users/me/Desktop/diagram.png')).toBe(true)
|
|
23
|
+
expect(looksLikeDroppedPath('/tmp/capture.webp')).toBe(true)
|
|
24
|
+
expect(looksLikeDroppedPath('/tmp/image.gif')).toBe(true)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('recognizes file:// URIs with various extensions', () => {
|
|
28
|
+
expect(looksLikeDroppedPath('file:///home/user/doc.pdf')).toBe(true)
|
|
29
|
+
expect(looksLikeDroppedPath('file:///tmp/screenshot.png')).toBe(true)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('recognizes paths with spaces (not backslash-escaped)', () => {
|
|
33
|
+
expect(looksLikeDroppedPath('/var/folders/x/T/TemporaryItems/Screenshot 2026-04-21 at 1.04.43 PM.png')).toBe(true)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('rejects empty/whitespace-only input', () => {
|
|
37
|
+
expect(looksLikeDroppedPath('')).toBe(false)
|
|
38
|
+
expect(looksLikeDroppedPath(' ')).toBe(false)
|
|
39
|
+
expect(looksLikeDroppedPath('\n')).toBe(false)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('rejects URLs that are not file:// URIs', () => {
|
|
43
|
+
expect(looksLikeDroppedPath('https://example.com/image.png')).toBe(false)
|
|
44
|
+
expect(looksLikeDroppedPath('http://localhost/file.pdf')).toBe(false)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('rejects short slash-like strings without path structure', () => {
|
|
48
|
+
// No second '/' or '.' → not a plausible file path
|
|
49
|
+
expect(looksLikeDroppedPath('/help')).toBe(false)
|
|
50
|
+
expect(looksLikeDroppedPath('/model sonnet')).toBe(false)
|
|
51
|
+
expect(looksLikeDroppedPath('/api')).toBe(false)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('accepts absolute paths with directory separators or extensions', () => {
|
|
55
|
+
expect(looksLikeDroppedPath('/usr/bin/test')).toBe(true)
|
|
56
|
+
expect(looksLikeDroppedPath('/tmp/file.txt')).toBe(true)
|
|
57
|
+
expect(looksLikeDroppedPath('/etc/hosts')).toBe(true) // has second /
|
|
58
|
+
})
|
|
59
|
+
})
|