@alumbwe/anvil 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -0
- package/bin/anvil +2 -0
- package/bin/tree-sitter.wasm +0 -0
- package/package.json +67 -0
- package/src/__tests__/README.md +256 -0
- package/src/__tests__/bash-mode.test.ts +442 -0
- package/src/__tests__/cli-args.test.ts +181 -0
- package/src/__tests__/cli-harness.test.ts +52 -0
- package/src/__tests__/e2e/first-time-login.test.ts +173 -0
- package/src/__tests__/e2e/logout-relogin-flow.test.ts +110 -0
- package/src/__tests__/e2e/returning-user-auth.test.ts +113 -0
- package/src/__tests__/e2e-cli.test.ts +183 -0
- package/src/__tests__/helpers/launcher-disconnect-fixture.cjs +176 -0
- package/src/__tests__/helpers/mock-api-client.ts +64 -0
- package/src/__tests__/helpers/renderer-cleanup-fixture.tsx +89 -0
- package/src/__tests__/helpers/terminal-watchdog-fixture.ts +75 -0
- package/src/__tests__/home-directory-detection.test.ts +213 -0
- package/src/__tests__/integration/api-integration.test.ts +288 -0
- package/src/__tests__/integration/credentials-storage.test.ts +486 -0
- package/src/__tests__/integration/local-agents.test.ts +1187 -0
- package/src/__tests__/integration/login-flow-instrumentation.test.ts +134 -0
- package/src/__tests__/integration/login-polling-working.test.ts +285 -0
- package/src/__tests__/integration/queue-editing.test.tsx +261 -0
- package/src/__tests__/integration/usage-refresh-on-completion.test.ts +168 -0
- package/src/__tests__/integration-tmux.test.ts +188 -0
- package/src/__tests__/launcher-avx2-fallback.test.ts +630 -0
- package/src/__tests__/launcher-disconnect.test.ts +104 -0
- package/src/__tests__/mocks/hover-toggle-controller.ts +71 -0
- package/src/__tests__/open-tui-native-bundle.test.ts +378 -0
- package/src/__tests__/path-completion.test.ts +243 -0
- package/src/__tests__/project-files-chat-id.test.ts +34 -0
- package/src/__tests__/release/proxy-http-get.test.ts +667 -0
- package/src/__tests__/release/wrapper-safety.test.ts +412 -0
- package/src/__tests__/renderer-cleanup.test.ts +174 -0
- package/src/__tests__/rerender-perf.integration.test.ts +283 -0
- package/src/__tests__/terminal-reset-sequences.test.ts +36 -0
- package/src/__tests__/terminal-watchdog.test.ts +388 -0
- package/src/__tests__/test-utils.ts +271 -0
- package/src/__tests__/tmux-poc.ts +150 -0
- package/src/__tests__/unit/agent-mode-toggle.test.ts +137 -0
- package/src/__tests__/unit/copy-button.test.ts +157 -0
- package/src/__tests__/unit/create-run-config.test.ts +66 -0
- package/src/__tests__/unit/publish-confirmation.test.ts +70 -0
- package/src/__tests__/unit/segmented-control.test.ts +152 -0
- package/src/__tests__/utils/env.test.ts +169 -0
- package/src/__tests__/utils/project-picker.test.ts +39 -0
- package/src/agents/bundled-agents.generated.d.ts +14 -0
- package/src/agents/bundled-agents.generated.ts +3692 -0
- package/src/app.tsx +399 -0
- package/src/chat.tsx +1698 -0
- package/src/cli-args.ts +134 -0
- package/src/commands/__tests__/bash-command.test.ts +445 -0
- package/src/commands/__tests__/command-args.test.ts +349 -0
- package/src/commands/__tests__/copy-conversation-clipboard.test.ts +108 -0
- package/src/commands/__tests__/copy-conversation.test.ts +226 -0
- package/src/commands/__tests__/diagnostics-command.test.ts +35 -0
- package/src/commands/__tests__/freebuff-command-aliases.test.ts +56 -0
- package/src/commands/__tests__/image.test.ts +99 -0
- package/src/commands/__tests__/init.test.ts +456 -0
- package/src/commands/__tests__/new-command-rotates-chat.test.ts +91 -0
- package/src/commands/__tests__/process-diagnostics.test.ts +54 -0
- package/src/commands/__tests__/prompt-builders.test.ts +28 -0
- package/src/commands/__tests__/router-input.test.ts +298 -0
- package/src/commands/ads.ts +44 -0
- package/src/commands/command-registry.ts +726 -0
- package/src/commands/copy-conversation.ts +392 -0
- package/src/commands/help.ts +13 -0
- package/src/commands/image.ts +20 -0
- package/src/commands/init.ts +121 -0
- package/src/commands/process-diagnostics.ts +120 -0
- package/src/commands/prompt-builders.ts +100 -0
- package/src/commands/publish.ts +203 -0
- package/src/commands/router-utils.ts +80 -0
- package/src/commands/router.ts +487 -0
- package/src/commands/usage.ts +26 -0
- package/src/components/__tests__/ad-banner.test.tsx +83 -0
- package/src/components/__tests__/chat-input-bar.test.tsx +130 -0
- package/src/components/__tests__/freebuff-landing-heading-row.test.tsx +91 -0
- package/src/components/__tests__/freebuff-model-selector.test.tsx +519 -0
- package/src/components/__tests__/freebuff-offer-invariants.test.ts +54 -0
- package/src/components/__tests__/freebuff-takeover-prompt.test.tsx +212 -0
- package/src/components/__tests__/grid-layout.integration.test.tsx +304 -0
- package/src/components/__tests__/grid-layout.test.tsx +1051 -0
- package/src/components/__tests__/message-block.completion.test.tsx +104 -0
- package/src/components/__tests__/message-block.streaming.test.tsx +78 -0
- package/src/components/__tests__/message-with-agents.test.tsx +579 -0
- package/src/components/__tests__/multiline-input.test.tsx +1132 -0
- package/src/components/__tests__/queue-panel.test.tsx +398 -0
- package/src/components/__tests__/selectable-list.test.ts +232 -0
- package/src/components/__tests__/status-bar.test.tsx +44 -0
- package/src/components/__tests__/status-indicator.test.tsx +204 -0
- package/src/components/__tests__/terminal-thinking-orb.test.tsx +111 -0
- package/src/components/__tests__/user-error-banner.test.tsx +102 -0
- package/src/components/ad-banner.tsx +323 -0
- package/src/components/agent-checklist.tsx +350 -0
- package/src/components/agent-mode-toggle.tsx +241 -0
- package/src/components/anvil-mark.tsx +52 -0
- package/src/components/ask-user/__tests__/multiple-choice-form.test.ts +432 -0
- package/src/components/ask-user/__tests__/validation.test.ts +213 -0
- package/src/components/ask-user/components/accordion-question.tsx +153 -0
- package/src/components/ask-user/components/custom-answer-input.tsx +66 -0
- package/src/components/ask-user/components/options-list.tsx +133 -0
- package/src/components/ask-user/components/question-header.tsx +80 -0
- package/src/components/ask-user/components/question-option.tsx +89 -0
- package/src/components/ask-user/constants.ts +38 -0
- package/src/components/ask-user/index.tsx +631 -0
- package/src/components/ask-user/utils/validation.ts +85 -0
- package/src/components/attachment-card.tsx +65 -0
- package/src/components/blocks/agent-block-grid.tsx +58 -0
- package/src/components/blocks/agent-branch-item.tsx +311 -0
- package/src/components/blocks/agent-branch-wrapper.tsx +493 -0
- package/src/components/blocks/agent-list-branch.tsx +77 -0
- package/src/components/blocks/ask-user-branch.tsx +81 -0
- package/src/components/blocks/block-helpers.ts +11 -0
- package/src/components/blocks/blocks-renderer.tsx +289 -0
- package/src/components/blocks/content-with-markdown.tsx +33 -0
- package/src/components/blocks/image-block.tsx +128 -0
- package/src/components/blocks/implementor-row.tsx +429 -0
- package/src/components/blocks/single-block.tsx +201 -0
- package/src/components/blocks/thinking-block.tsx +67 -0
- package/src/components/blocks/tool-block-group.tsx +57 -0
- package/src/components/blocks/tool-branch.tsx +206 -0
- package/src/components/blocks/user-content-copy.tsx +157 -0
- package/src/components/bottom-banner.tsx +136 -0
- package/src/components/build-mode-buttons.tsx +110 -0
- package/src/components/button.tsx +66 -0
- package/src/components/chat-header.tsx +77 -0
- package/src/components/chat-history-screen.tsx +413 -0
- package/src/components/chat-input-bar.tsx +523 -0
- package/src/components/clickable-title-box.tsx +66 -0
- package/src/components/clickable.tsx +119 -0
- package/src/components/collapse-button.tsx +34 -0
- package/src/components/copy-button.tsx +203 -0
- package/src/components/elapsed-timer.tsx +55 -0
- package/src/components/error-boundary.tsx +55 -0
- package/src/components/feedback-container.tsx +181 -0
- package/src/components/feedback-icon-button.tsx +89 -0
- package/src/components/feedback-input-mode.tsx +344 -0
- package/src/components/file-attachment-card.tsx +98 -0
- package/src/components/freebuff-active-session-summary.tsx +63 -0
- package/src/components/freebuff-landing-screen.tsx +865 -0
- package/src/components/freebuff-model-selector.tsx +1087 -0
- package/src/components/freebuff-referral-banner.tsx +604 -0
- package/src/components/freebuff-superseded-screen.tsx +62 -0
- package/src/components/grid-layout.tsx +80 -0
- package/src/components/help-banner.tsx +127 -0
- package/src/components/highlighted-text.tsx +52 -0
- package/src/components/image-card.tsx +167 -0
- package/src/components/image-thumbnail.tsx +114 -0
- package/src/components/input-cursor.tsx +77 -0
- package/src/components/input-mode-banner.tsx +57 -0
- package/src/components/load-previous-button.tsx +48 -0
- package/src/components/login-modal.tsx +475 -0
- package/src/components/message-block.tsx +346 -0
- package/src/components/message-footer.tsx +258 -0
- package/src/components/message-with-agents.tsx +506 -0
- package/src/components/mode-divider.tsx +43 -0
- package/src/components/multiline-input.tsx +1241 -0
- package/src/components/out-of-credits-banner.tsx +160 -0
- package/src/components/pending-attachments-banner.tsx +116 -0
- package/src/components/pending-bash-message.tsx +59 -0
- package/src/components/progress-bar.tsx +81 -0
- package/src/components/project-picker-screen.tsx +485 -0
- package/src/components/publish-confirmation.tsx +493 -0
- package/src/components/publish-container.tsx +687 -0
- package/src/components/queue-panel.tsx +310 -0
- package/src/components/raised-pill.tsx +95 -0
- package/src/components/renderers/plan-box.tsx +59 -0
- package/src/components/review-screen.tsx +112 -0
- package/src/components/scroll-to-bottom-button.tsx +34 -0
- package/src/components/segmented-control.tsx +187 -0
- package/src/components/selectable-list.tsx +248 -0
- package/src/components/selected-chips.tsx +80 -0
- package/src/components/separator.tsx +40 -0
- package/src/components/session-ended-banner.tsx +226 -0
- package/src/components/shimmer-text.tsx +248 -0
- package/src/components/status-bar.tsx +313 -0
- package/src/components/subscription-limit-banner.tsx +206 -0
- package/src/components/suggested-prompts.tsx +184 -0
- package/src/components/suggestion-menu.tsx +215 -0
- package/src/components/terminal-command-display.tsx +151 -0
- package/src/components/terminal-link.tsx +97 -0
- package/src/components/terminal-thinking-orb.tsx +95 -0
- package/src/components/text-attachment-card.tsx +77 -0
- package/src/components/thinking.tsx +109 -0
- package/src/components/tools/__tests__/apply-patch.test.tsx +84 -0
- package/src/components/tools/__tests__/code-search.test.tsx +45 -0
- package/src/components/tools/__tests__/gravity-index.test.ts +76 -0
- package/src/components/tools/__tests__/render-ui.test.tsx +92 -0
- package/src/components/tools/__tests__/run-terminal-command.test.ts +233 -0
- package/src/components/tools/apply-patch.tsx +95 -0
- package/src/components/tools/code-search.tsx +45 -0
- package/src/components/tools/composio.tsx +152 -0
- package/src/components/tools/diff-viewer.tsx +76 -0
- package/src/components/tools/glob.tsx +61 -0
- package/src/components/tools/gravity-index.tsx +77 -0
- package/src/components/tools/list-directory.tsx +61 -0
- package/src/components/tools/read-docs.tsx +37 -0
- package/src/components/tools/read-files.tsx +91 -0
- package/src/components/tools/read-subtree.tsx +44 -0
- package/src/components/tools/read-url.tsx +33 -0
- package/src/components/tools/registry.ts +126 -0
- package/src/components/tools/render-ui.tsx +122 -0
- package/src/components/tools/run-terminal-command.tsx +79 -0
- package/src/components/tools/skill.tsx +29 -0
- package/src/components/tools/str-replace.tsx +77 -0
- package/src/components/tools/suggest-followups.tsx +369 -0
- package/src/components/tools/task-completed.tsx +16 -0
- package/src/components/tools/tool-call-item.tsx +273 -0
- package/src/components/tools/types.ts +55 -0
- package/src/components/tools/web-search.tsx +33 -0
- package/src/components/tools/write-file.tsx +16 -0
- package/src/components/tools/write-todos.tsx +95 -0
- package/src/components/top-banner.tsx +178 -0
- package/src/components/usage-banner.tsx +244 -0
- package/src/components/user-error-banner.tsx +56 -0
- package/src/components/validation-error-popover.tsx +192 -0
- package/src/contexts/__tests__/chat-runtime-context.test.tsx +149 -0
- package/src/contexts/chat-runtime-context.tsx +240 -0
- package/src/data/slash-commands.test.ts +24 -0
- package/src/data/slash-commands.ts +249 -0
- package/src/entry.ts +12 -0
- package/src/hooks/__tests__/holds-live-freebuff-slot.test.ts +49 -0
- package/src/hooks/__tests__/session-fetch-signal.test.ts +63 -0
- package/src/hooks/__tests__/use-activity-query.test.ts +1000 -0
- package/src/hooks/__tests__/use-ask-user-bridge.test.ts +176 -0
- package/src/hooks/__tests__/use-auth-query.test.ts +21 -0
- package/src/hooks/__tests__/use-chat-input-remount.test.tsx +66 -0
- package/src/hooks/__tests__/use-clipboard.test.tsx +73 -0
- package/src/hooks/__tests__/use-connection-status.test.ts +112 -0
- package/src/hooks/__tests__/use-directory-browser.test.ts +203 -0
- package/src/hooks/__tests__/use-gravity-ad.test.ts +73 -0
- package/src/hooks/__tests__/use-grid-layout.test.ts +349 -0
- package/src/hooks/__tests__/use-input-history.test.ts +699 -0
- package/src/hooks/__tests__/use-message-queue-editing.test.tsx +209 -0
- package/src/hooks/__tests__/use-message-queue.test.tsx +71 -0
- package/src/hooks/__tests__/use-path-tab-completion.test.ts +315 -0
- package/src/hooks/__tests__/use-queue-controls.test.ts +68 -0
- package/src/hooks/__tests__/use-queue-ui.test.tsx +83 -0
- package/src/hooks/__tests__/use-searchable-list.test.ts +359 -0
- package/src/hooks/__tests__/use-send-message-timer.test.ts +146 -0
- package/src/hooks/__tests__/use-suggestion-engine-mention.test.ts +361 -0
- package/src/hooks/__tests__/use-suggestion-engine.test.ts +485 -0
- package/src/hooks/__tests__/use-terminal-focus.test.ts +66 -0
- package/src/hooks/__tests__/use-terminal-layout.test.ts +675 -0
- package/src/hooks/__tests__/use-timeout.test.ts +330 -0
- package/src/hooks/__tests__/use-usage-query.test.ts +502 -0
- package/src/hooks/__tests__/use-user-details-query.test.ts +195 -0
- package/src/hooks/helpers/__tests__/send-message.test.ts +1918 -0
- package/src/hooks/helpers/send-message.ts +654 -0
- package/src/hooks/stream-state.ts +83 -0
- package/src/hooks/use-activity-query.ts +668 -0
- package/src/hooks/use-agent-validation.ts +74 -0
- package/src/hooks/use-ask-user-bridge.ts +92 -0
- package/src/hooks/use-auth-query.ts +253 -0
- package/src/hooks/use-auth-state.ts +152 -0
- package/src/hooks/use-chat-input.ts +105 -0
- package/src/hooks/use-chat-keyboard.ts +321 -0
- package/src/hooks/use-chat-messages.ts +254 -0
- package/src/hooks/use-chat-state.ts +165 -0
- package/src/hooks/use-chat-streaming.ts +180 -0
- package/src/hooks/use-chat-ui.ts +131 -0
- package/src/hooks/use-clipboard.ts +151 -0
- package/src/hooks/use-connection-status.ts +142 -0
- package/src/hooks/use-directory-browser.ts +88 -0
- package/src/hooks/use-elapsed-time.ts +116 -0
- package/src/hooks/use-event.ts +34 -0
- package/src/hooks/use-exit-handler.ts +66 -0
- package/src/hooks/use-fetch-login-url.ts +65 -0
- package/src/hooks/use-fingerprint.ts +61 -0
- package/src/hooks/use-freebuff-ctrl-c-exit.ts +23 -0
- package/src/hooks/use-freebuff-session-progress.ts +34 -0
- package/src/hooks/use-freebuff-session.ts +806 -0
- package/src/hooks/use-freebuff-streak-query.ts +61 -0
- package/src/hooks/use-gravity-ad.ts +656 -0
- package/src/hooks/use-grid-layout.ts +73 -0
- package/src/hooks/use-input-history.ts +173 -0
- package/src/hooks/use-login-keyboard-handlers.ts +75 -0
- package/src/hooks/use-login-polling.ts +114 -0
- package/src/hooks/use-logo.tsx +161 -0
- package/src/hooks/use-message-queue.ts +387 -0
- package/src/hooks/use-now.ts +20 -0
- package/src/hooks/use-path-tab-completion.ts +88 -0
- package/src/hooks/use-publish-mutation.ts +60 -0
- package/src/hooks/use-queue-controls.ts +68 -0
- package/src/hooks/use-queue-ui.ts +74 -0
- package/src/hooks/use-scroll-management.ts +184 -0
- package/src/hooks/use-searchable-list.ts +98 -0
- package/src/hooks/use-send-message.ts +773 -0
- package/src/hooks/use-sheen-animation.tsx +88 -0
- package/src/hooks/use-subscription-query.ts +71 -0
- package/src/hooks/use-suggestion-engine.ts +783 -0
- package/src/hooks/use-terminal-breakpoints.ts +77 -0
- package/src/hooks/use-terminal-dimensions.ts +46 -0
- package/src/hooks/use-terminal-focus.ts +29 -0
- package/src/hooks/use-terminal-layout.ts +189 -0
- package/src/hooks/use-theme.tsx +140 -0
- package/src/hooks/use-timeout.ts +85 -0
- package/src/hooks/use-update-preference.ts +66 -0
- package/src/hooks/use-usage-monitor.ts +55 -0
- package/src/hooks/use-usage-query.ts +124 -0
- package/src/hooks/use-user-details-query.ts +93 -0
- package/src/hooks/use-why-did-you-update.ts +177 -0
- package/src/index.tsx +441 -0
- package/src/init/__tests__/init-direnv.test.ts +526 -0
- package/src/init/init-app.ts +34 -0
- package/src/init/init-direnv.ts +133 -0
- package/src/login/constants.ts +60 -0
- package/src/login/login-flow.ts +204 -0
- package/src/login/plain-login.ts +108 -0
- package/src/login/utils.ts +183 -0
- package/src/native/ripgrep.ts +80 -0
- package/src/polyfills/bun-strip-ansi.ts +12 -0
- package/src/pre-init/tree-sitter-wasm.ts +96 -0
- package/src/project-files.ts +102 -0
- package/src/smoke/terminal-command-broker.ts +439 -0
- package/src/state/__tests__/chat-store-focus.test.ts +22 -0
- package/src/state/__tests__/feedback-store.test.ts +257 -0
- package/src/state/chat-history-store.ts +30 -0
- package/src/state/chat-store.ts +544 -0
- package/src/state/feedback-store.ts +160 -0
- package/src/state/freebuff-model-store.ts +42 -0
- package/src/state/freebuff-session-store.ts +53 -0
- package/src/state/login-store.ts +150 -0
- package/src/state/message-block-store.ts +149 -0
- package/src/state/publish-store.ts +146 -0
- package/src/state/queue-panel-store.ts +26 -0
- package/src/state/review-store.ts +24 -0
- package/src/testing/env.ts +46 -0
- package/src/types/chat-state.ts +15 -0
- package/src/types/chat.ts +241 -0
- package/src/types/contracts/send-message.ts +12 -0
- package/src/types/env.ts +98 -0
- package/src/types/freebuff-session.ts +17 -0
- package/src/types/function-params.ts +28 -0
- package/src/types/react19-compat.d.ts +19 -0
- package/src/types/store.ts +111 -0
- package/src/types/theme-system.ts +144 -0
- package/src/types/utils.ts +3 -0
- package/src/utils/__tests__/active-run.test.ts +140 -0
- package/src/utils/__tests__/activity-tracker.test.ts +440 -0
- package/src/utils/__tests__/agent-display.test.ts +139 -0
- package/src/utils/__tests__/analytics-client.test.ts +262 -0
- package/src/utils/__tests__/anonymous-id.test.ts +74 -0
- package/src/utils/__tests__/anvil-api.test.ts +578 -0
- package/src/utils/__tests__/arrays.test.ts +264 -0
- package/src/utils/__tests__/bash-context-processor.test.ts +38 -0
- package/src/utils/__tests__/block-processor.test.ts +810 -0
- package/src/utils/__tests__/chat-history.test.ts +203 -0
- package/src/utils/__tests__/chat-input-key-intercept.test.ts +40 -0
- package/src/utils/__tests__/chat-meta.test.ts +130 -0
- package/src/utils/__tests__/clipboard-linux.test.ts +359 -0
- package/src/utils/__tests__/clipboard.test.ts +910 -0
- package/src/utils/__tests__/code-search-summary.test.ts +84 -0
- package/src/utils/__tests__/collapse-helpers.test.ts +1109 -0
- package/src/utils/__tests__/error-handling.test.ts +658 -0
- package/src/utils/__tests__/exit-cleanly.test.ts +110 -0
- package/src/utils/__tests__/feedback-helpers.test.ts +444 -0
- package/src/utils/__tests__/feedback-submission.test.ts +26 -0
- package/src/utils/__tests__/fetch-usage.test.ts +355 -0
- package/src/utils/__tests__/fingerprint.test.ts +144 -0
- package/src/utils/__tests__/fixtures/terminal-command-broker-child.ts +7 -0
- package/src/utils/__tests__/fixtures/terminal-command-broker-entry.ts +3 -0
- package/src/utils/__tests__/fixtures/terminal-command-broker-owner.ts +33 -0
- package/src/utils/__tests__/format-elapsed-time.test.ts +103 -0
- package/src/utils/__tests__/format-timeout.test.ts +87 -0
- package/src/utils/__tests__/freebuff-instance-owner.test.ts +69 -0
- package/src/utils/__tests__/freebuff-model-navigation.test.ts +102 -0
- package/src/utils/__tests__/freebuff-premium-reset.test.ts +79 -0
- package/src/utils/__tests__/freebuff-referral-cache.test.ts +116 -0
- package/src/utils/__tests__/freebuff-session-api.test.ts +215 -0
- package/src/utils/__tests__/freebuff-session-display.test.ts +23 -0
- package/src/utils/__tests__/freebuff-streak-line.test.ts +217 -0
- package/src/utils/__tests__/image-dimensions.test.ts +249 -0
- package/src/utils/__tests__/image-processor.test.ts +209 -0
- package/src/utils/__tests__/implementor-helpers.test.ts +1458 -0
- package/src/utils/__tests__/keyboard-actions.test.ts +660 -0
- package/src/utils/__tests__/layout-helpers.test.ts +36 -0
- package/src/utils/__tests__/lazy-response-ads.test.ts +167 -0
- package/src/utils/__tests__/log-shipper.test.ts +53 -0
- package/src/utils/__tests__/markdown-renderer.test.tsx +421 -0
- package/src/utils/__tests__/message-block-helpers.test.ts +1263 -0
- package/src/utils/__tests__/message-updater.test.ts +645 -0
- package/src/utils/__tests__/osc-timeout-scenarios.test.ts +172 -0
- package/src/utils/__tests__/pending-attachments.test.ts +277 -0
- package/src/utils/__tests__/polling-backoff.test.ts +51 -0
- package/src/utils/__tests__/queue-panel-actions.test.ts +148 -0
- package/src/utils/__tests__/run-state-storage.test.ts +919 -0
- package/src/utils/__tests__/safe-json.test.ts +77 -0
- package/src/utils/__tests__/sdk-event-handlers.test.ts +685 -0
- package/src/utils/__tests__/send-message-helpers.test.ts +1898 -0
- package/src/utils/__tests__/send-message-timer.test.ts +64 -0
- package/src/utils/__tests__/settings.test.ts +99 -0
- package/src/utils/__tests__/strings.test.ts +192 -0
- package/src/utils/__tests__/terminal-color-detection.test.ts +425 -0
- package/src/utils/__tests__/terminal-command-broker.test.ts +528 -0
- package/src/utils/__tests__/terminal-enter-detection.test.ts +77 -0
- package/src/utils/__tests__/terminal-io.test.ts +137 -0
- package/src/utils/__tests__/terminal-protocol-controller.test.ts +119 -0
- package/src/utils/__tests__/text-layout.test.ts +82 -0
- package/src/utils/__tests__/theme-platform-detection.test.ts +125 -0
- package/src/utils/__tests__/think-tag-parser.test.ts +159 -0
- package/src/utils/__tests__/trace-writer.test.ts +223 -0
- package/src/utils/__tests__/trim-chat-logs.test.ts +78 -0
- package/src/utils/__tests__/usage-banner-state.test.ts +298 -0
- package/src/utils/__tests__/validation-error-formatting.test.ts +126 -0
- package/src/utils/__tests__/windows-terminal-health.test.ts +101 -0
- package/src/utils/__tests__/write-file-atomic.test.ts +127 -0
- package/src/utils/active-run.ts +131 -0
- package/src/utils/activity-tracker.ts +66 -0
- package/src/utils/agent-display.ts +87 -0
- package/src/utils/agent-helpers.ts +27 -0
- package/src/utils/agent-id-utils.ts +14 -0
- package/src/utils/ai-message-id.ts +12 -0
- package/src/utils/analytics.ts +361 -0
- package/src/utils/anonymous-id.ts +87 -0
- package/src/utils/anvil-api.ts +612 -0
- package/src/utils/anvil-client.ts +217 -0
- package/src/utils/arrays.ts +117 -0
- package/src/utils/auth.ts +258 -0
- package/src/utils/bash-context-processor.ts +47 -0
- package/src/utils/bash-messages.ts +108 -0
- package/src/utils/block-margins.ts +35 -0
- package/src/utils/block-operations.ts +523 -0
- package/src/utils/block-processor.ts +213 -0
- package/src/utils/chat-history.ts +253 -0
- package/src/utils/chat-input-key-intercept.ts +52 -0
- package/src/utils/chat-meta.ts +90 -0
- package/src/utils/chat-scroll-accel.ts +151 -0
- package/src/utils/clipboard-image.ts +588 -0
- package/src/utils/clipboard.ts +397 -0
- package/src/utils/code-search-summary.ts +70 -0
- package/src/utils/collapse-helpers.ts +256 -0
- package/src/utils/config-dir.ts +24 -0
- package/src/utils/constants.ts +190 -0
- package/src/utils/create-event-handler-state.ts +73 -0
- package/src/utils/create-run-config.ts +121 -0
- package/src/utils/detect-shell.ts +116 -0
- package/src/utils/directory-browser.ts +72 -0
- package/src/utils/engagement.ts +55 -0
- package/src/utils/env.ts +92 -0
- package/src/utils/error-handling.ts +255 -0
- package/src/utils/error-messages.ts +62 -0
- package/src/utils/exit-cleanly.ts +86 -0
- package/src/utils/feedback-helpers.ts +103 -0
- package/src/utils/feedback-submission.ts +22 -0
- package/src/utils/fetch-usage.ts +76 -0
- package/src/utils/fingerprint.ts +245 -0
- package/src/utils/format-elapsed-time.ts +33 -0
- package/src/utils/format-session-units.ts +6 -0
- package/src/utils/format-timeout.ts +28 -0
- package/src/utils/format-validation-errors-for-message.ts +84 -0
- package/src/utils/freebuff-agent-selection.ts +33 -0
- package/src/utils/freebuff-instance-owner.ts +66 -0
- package/src/utils/freebuff-model-navigation.ts +50 -0
- package/src/utils/freebuff-premium-reset.ts +55 -0
- package/src/utils/freebuff-referral-cache.ts +51 -0
- package/src/utils/freebuff-session-api.ts +243 -0
- package/src/utils/freebuff-session-display.ts +21 -0
- package/src/utils/freebuff-streak-line.ts +93 -0
- package/src/utils/git.ts +17 -0
- package/src/utils/helpers.ts +47 -0
- package/src/utils/image-display.ts +67 -0
- package/src/utils/image-handler.ts +325 -0
- package/src/utils/image-processor.ts +133 -0
- package/src/utils/image-thumbnail.ts +78 -0
- package/src/utils/implementor-helpers.ts +787 -0
- package/src/utils/input-modes.ts +174 -0
- package/src/utils/keyboard-actions.ts +392 -0
- package/src/utils/keypad-keys.ts +47 -0
- package/src/utils/layout-helpers.ts +33 -0
- package/src/utils/lazy-response-ads.ts +1 -0
- package/src/utils/local-agent-registry.ts +474 -0
- package/src/utils/log-shipper.ts +126 -0
- package/src/utils/logger.ts +327 -0
- package/src/utils/markdown-renderer.tsx +1090 -0
- package/src/utils/math.ts +3 -0
- package/src/utils/message-block-helpers.ts +684 -0
- package/src/utils/message-history.ts +143 -0
- package/src/utils/message-tree-utils.ts +67 -0
- package/src/utils/message-updater.ts +279 -0
- package/src/utils/open-file.ts +146 -0
- package/src/utils/open-url.ts +63 -0
- package/src/utils/path-completion.ts +111 -0
- package/src/utils/path-helpers.ts +38 -0
- package/src/utils/pending-attachments.ts +360 -0
- package/src/utils/polling-backoff.ts +59 -0
- package/src/utils/project-picker.ts +12 -0
- package/src/utils/queue-panel-actions.ts +70 -0
- package/src/utils/recent-projects.ts +156 -0
- package/src/utils/renderer-cleanup.ts +233 -0
- package/src/utils/response-ad-positions.ts +1 -0
- package/src/utils/run-state-storage.ts +603 -0
- package/src/utils/safe-json.ts +153 -0
- package/src/utils/sdk-event-handlers.ts +571 -0
- package/src/utils/send-message-helpers.ts +210 -0
- package/src/utils/send-message-timer.ts +110 -0
- package/src/utils/settings.ts +226 -0
- package/src/utils/skill-registry.ts +94 -0
- package/src/utils/spawn-agent-matcher.ts +55 -0
- package/src/utils/status-indicator-state.ts +107 -0
- package/src/utils/stream-chunk-processor.ts +57 -0
- package/src/utils/strings.ts +271 -0
- package/src/utils/subscription.ts +31 -0
- package/src/utils/syntax-highlighter.tsx +19 -0
- package/src/utils/terminal-color-detection.ts +474 -0
- package/src/utils/terminal-command-broker.ts +467 -0
- package/src/utils/terminal-enter-detection.ts +63 -0
- package/src/utils/terminal-images.ts +229 -0
- package/src/utils/terminal-io.ts +85 -0
- package/src/utils/terminal-protocol-controller.ts +173 -0
- package/src/utils/terminal-reset-sequences.ts +33 -0
- package/src/utils/terminal-title.ts +84 -0
- package/src/utils/terminal-watchdog.ts +366 -0
- package/src/utils/text-layout.ts +149 -0
- package/src/utils/theme-config.ts +141 -0
- package/src/utils/theme-system.ts +1206 -0
- package/src/utils/think-tag-parser.ts +95 -0
- package/src/utils/time-format.ts +21 -0
- package/src/utils/trace-writer.ts +162 -0
- package/src/utils/ui-constants.ts +69 -0
- package/src/utils/update-checker.ts +110 -0
- package/src/utils/usage-banner-state.ts +146 -0
- package/src/utils/validation-error-formatting.ts +86 -0
- package/src/utils/validation-error-helpers.ts +19 -0
- package/src/utils/windows-terminal-health.ts +124 -0
- package/src/utils/word-wrap-utils.ts +53 -0
- package/src/utils/write-file-atomic.ts +54 -0
- package/src/utils/yield-to-event-loop.ts +9 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createLazyResponseAdQueue,
|
|
5
|
+
getResponseAdForSlot,
|
|
6
|
+
MAX_RESPONSE_AD_POOL_SIZE,
|
|
7
|
+
requestLazyResponseAds,
|
|
8
|
+
responseAdDisplayCount,
|
|
9
|
+
} from '../lazy-response-ads'
|
|
10
|
+
|
|
11
|
+
type TestAd = { impUrl: string; clickUrl?: string }
|
|
12
|
+
|
|
13
|
+
describe('requestLazyResponseAds', () => {
|
|
14
|
+
test('fetches only when slots become eligible and caps the pool at four', async () => {
|
|
15
|
+
const queue = createLazyResponseAdQueue<TestAd>()
|
|
16
|
+
const shown: TestAd[] = []
|
|
17
|
+
let fetchCount = 0
|
|
18
|
+
const fetchOne = async () => ({ impUrl: `imp-${++fetchCount}` })
|
|
19
|
+
|
|
20
|
+
expect(fetchCount).toBe(0)
|
|
21
|
+
expect(
|
|
22
|
+
requestLazyResponseAds({
|
|
23
|
+
queue,
|
|
24
|
+
messageId: 'ai-1',
|
|
25
|
+
count: 0,
|
|
26
|
+
fetchOne,
|
|
27
|
+
onAd: (ad) => shown.push(ad),
|
|
28
|
+
}),
|
|
29
|
+
).toBeNull()
|
|
30
|
+
expect(fetchCount).toBe(0)
|
|
31
|
+
|
|
32
|
+
await requestLazyResponseAds({
|
|
33
|
+
queue,
|
|
34
|
+
messageId: 'ai-1',
|
|
35
|
+
count: 1,
|
|
36
|
+
fetchOne,
|
|
37
|
+
onAd: (ad) => shown.push(ad),
|
|
38
|
+
})
|
|
39
|
+
expect(fetchCount).toBe(1)
|
|
40
|
+
|
|
41
|
+
await requestLazyResponseAds({
|
|
42
|
+
queue,
|
|
43
|
+
messageId: 'ai-1',
|
|
44
|
+
count: 10,
|
|
45
|
+
fetchOne,
|
|
46
|
+
onAd: (ad) => shown.push(ad),
|
|
47
|
+
})
|
|
48
|
+
expect(fetchCount).toBe(MAX_RESPONSE_AD_POOL_SIZE)
|
|
49
|
+
expect(shown).toHaveLength(MAX_RESPONSE_AD_POOL_SIZE)
|
|
50
|
+
|
|
51
|
+
await requestLazyResponseAds({
|
|
52
|
+
queue,
|
|
53
|
+
messageId: 'ai-1',
|
|
54
|
+
count: 100,
|
|
55
|
+
fetchOne,
|
|
56
|
+
onAd: (ad) => shown.push(ad),
|
|
57
|
+
})
|
|
58
|
+
expect(fetchCount).toBe(MAX_RESPONSE_AD_POOL_SIZE)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('does not retry no-fill or duplicate results for the same slots', async () => {
|
|
62
|
+
const queue = createLazyResponseAdQueue<TestAd>()
|
|
63
|
+
const shown: TestAd[] = []
|
|
64
|
+
const results: Array<TestAd | null> = [
|
|
65
|
+
null,
|
|
66
|
+
{ impUrl: 'same' },
|
|
67
|
+
{ impUrl: 'same' },
|
|
68
|
+
]
|
|
69
|
+
let fetchCount = 0
|
|
70
|
+
|
|
71
|
+
await requestLazyResponseAds({
|
|
72
|
+
queue,
|
|
73
|
+
messageId: 'ai-1',
|
|
74
|
+
count: 3,
|
|
75
|
+
fetchOne: async () => results[fetchCount++] ?? null,
|
|
76
|
+
onAd: (ad) => shown.push(ad),
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
expect(fetchCount).toBe(3)
|
|
80
|
+
expect(shown).toEqual([{ impUrl: 'same' }])
|
|
81
|
+
|
|
82
|
+
await requestLazyResponseAds({
|
|
83
|
+
queue,
|
|
84
|
+
messageId: 'ai-1',
|
|
85
|
+
count: 3,
|
|
86
|
+
fetchOne: async () => {
|
|
87
|
+
fetchCount++
|
|
88
|
+
return { impUrl: 'new' }
|
|
89
|
+
},
|
|
90
|
+
onAd: (ad) => shown.push(ad),
|
|
91
|
+
})
|
|
92
|
+
expect(fetchCount).toBe(3)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test('extends an in-flight queue when more slots become eligible', async () => {
|
|
96
|
+
const queue = createLazyResponseAdQueue<TestAd>()
|
|
97
|
+
const shown: TestAd[] = []
|
|
98
|
+
let fetchCount = 0
|
|
99
|
+
let releaseFirst!: () => void
|
|
100
|
+
const firstFetchBlocked = new Promise<void>((resolve) => {
|
|
101
|
+
releaseFirst = resolve
|
|
102
|
+
})
|
|
103
|
+
const fetchOne = async () => {
|
|
104
|
+
const current = ++fetchCount
|
|
105
|
+
if (current === 1) await firstFetchBlocked
|
|
106
|
+
return { impUrl: `imp-${current}` }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const firstTask = requestLazyResponseAds({
|
|
110
|
+
queue,
|
|
111
|
+
messageId: 'ai-1',
|
|
112
|
+
count: 1,
|
|
113
|
+
fetchOne,
|
|
114
|
+
onAd: (ad) => shown.push(ad),
|
|
115
|
+
})
|
|
116
|
+
const extendedTask = requestLazyResponseAds({
|
|
117
|
+
queue,
|
|
118
|
+
messageId: 'ai-1',
|
|
119
|
+
count: 3,
|
|
120
|
+
fetchOne,
|
|
121
|
+
onAd: (ad) => shown.push(ad),
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
expect(extendedTask).toBe(firstTask)
|
|
125
|
+
releaseFirst()
|
|
126
|
+
await extendedTask
|
|
127
|
+
expect(fetchCount).toBe(3)
|
|
128
|
+
expect(shown.map((ad) => ad.impUrl)).toEqual(['imp-1', 'imp-2', 'imp-3'])
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
describe('repeated response ad pool', () => {
|
|
133
|
+
const ads: TestAd[] = Array.from(
|
|
134
|
+
{ length: MAX_RESPONSE_AD_POOL_SIZE },
|
|
135
|
+
(_, i) => ({
|
|
136
|
+
impUrl: `imp-${i + 1}`,
|
|
137
|
+
clickUrl: `https://click.example/${i + 1}`,
|
|
138
|
+
}),
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
test('waits for the distinct pool, then fills every eligible slot', () => {
|
|
142
|
+
expect(responseAdDisplayCount({ eligibleCount: 10, poolSize: 0 })).toBe(0)
|
|
143
|
+
expect(responseAdDisplayCount({ eligibleCount: 10, poolSize: 3 })).toBe(3)
|
|
144
|
+
expect(responseAdDisplayCount({ eligibleCount: 10, poolSize: 4 })).toBe(10)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
test('repeats the same four ad objects and click URLs in order', () => {
|
|
148
|
+
const sequence = Array.from({ length: 10 }, (_, slot) =>
|
|
149
|
+
getResponseAdForSlot(ads, slot),
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
expect(sequence.map((ad) => ad?.impUrl)).toEqual([
|
|
153
|
+
'imp-1',
|
|
154
|
+
'imp-2',
|
|
155
|
+
'imp-3',
|
|
156
|
+
'imp-4',
|
|
157
|
+
'imp-1',
|
|
158
|
+
'imp-2',
|
|
159
|
+
'imp-3',
|
|
160
|
+
'imp-4',
|
|
161
|
+
'imp-1',
|
|
162
|
+
'imp-2',
|
|
163
|
+
])
|
|
164
|
+
expect(sequence[0]).toBe(sequence[4])
|
|
165
|
+
expect(sequence[0]?.clickUrl).toBe(sequence[4]?.clickUrl)
|
|
166
|
+
})
|
|
167
|
+
})
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import { ensureCliTestEnv } from '../../__tests__/test-utils'
|
|
4
|
+
|
|
5
|
+
import type { LogRecordInput } from '@anvil/common/schemas/logs'
|
|
6
|
+
|
|
7
|
+
ensureCliTestEnv()
|
|
8
|
+
|
|
9
|
+
const { createClientLogFlusher } = await import('../log-shipper')
|
|
10
|
+
|
|
11
|
+
describe('client log draining', () => {
|
|
12
|
+
test('waits for an active request and drains every buffered batch', async () => {
|
|
13
|
+
let releaseFirstRequest: (() => void) | undefined
|
|
14
|
+
let markFirstRequestStarted: (() => void) | undefined
|
|
15
|
+
const firstRequestStarted = new Promise<void>((resolve) => {
|
|
16
|
+
markFirstRequestStarted = resolve
|
|
17
|
+
})
|
|
18
|
+
const firstRequestGate = new Promise<void>((resolve) => {
|
|
19
|
+
releaseFirstRequest = resolve
|
|
20
|
+
})
|
|
21
|
+
const pending: LogRecordInput[] = Array.from(
|
|
22
|
+
{ length: 101 },
|
|
23
|
+
(_, index) => ({ level: 'info', message: `record-${index}` }),
|
|
24
|
+
)
|
|
25
|
+
const batchSizes: number[] = []
|
|
26
|
+
const flusher = createClientLogFlusher({
|
|
27
|
+
takeBatch: () => pending.splice(0, 50),
|
|
28
|
+
hasPending: () => pending.length > 0,
|
|
29
|
+
sendBatch: async (batch) => {
|
|
30
|
+
batchSizes.push(batch.length)
|
|
31
|
+
if (batchSizes.length === 1) {
|
|
32
|
+
markFirstRequestStarted?.()
|
|
33
|
+
await firstRequestGate
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
void flusher.flush()
|
|
39
|
+
await firstRequestStarted
|
|
40
|
+
|
|
41
|
+
let drainFinished = false
|
|
42
|
+
const drainPromise = flusher.drain().then(() => {
|
|
43
|
+
drainFinished = true
|
|
44
|
+
})
|
|
45
|
+
await Promise.resolve()
|
|
46
|
+
expect(drainFinished).toBe(false)
|
|
47
|
+
|
|
48
|
+
releaseFirstRequest?.()
|
|
49
|
+
await drainPromise
|
|
50
|
+
|
|
51
|
+
expect(batchSizes).toEqual([50, 50, 1])
|
|
52
|
+
})
|
|
53
|
+
})
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import { TextAttributes } from '@opentui/core'
|
|
2
|
+
import { describe, expect, test } from 'bun:test'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
|
|
5
|
+
import { renderMarkdown, renderStreamingMarkdown } from '../markdown-renderer'
|
|
6
|
+
|
|
7
|
+
type El = React.ReactElement<Record<string, unknown>>
|
|
8
|
+
|
|
9
|
+
const flattenNodes = (input: unknown): React.ReactNode[] => {
|
|
10
|
+
const result: React.ReactNode[] = []
|
|
11
|
+
|
|
12
|
+
const visit = (value: unknown): void => {
|
|
13
|
+
if (value === null || value === undefined || typeof value === 'boolean') {
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (Array.isArray(value)) {
|
|
18
|
+
value.forEach(visit)
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (React.isValidElement(value) && value.type === React.Fragment) {
|
|
23
|
+
visit((value as El).props.children)
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
result.push(value as React.ReactNode)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
visit(input)
|
|
31
|
+
return result
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const flattenChildren = (value: unknown): React.ReactNode[] =>
|
|
35
|
+
flattenNodes(value)
|
|
36
|
+
|
|
37
|
+
describe('markdown renderer', () => {
|
|
38
|
+
test('renders bold and italic emphasis', () => {
|
|
39
|
+
const output = renderMarkdown('Hello **bold** and *italic*!')
|
|
40
|
+
const nodes = flattenNodes(output)
|
|
41
|
+
|
|
42
|
+
expect(nodes[0]).toBe('Hello ')
|
|
43
|
+
|
|
44
|
+
const bold = nodes[1] as El
|
|
45
|
+
expect(bold.props.attributes).toBe(TextAttributes.BOLD)
|
|
46
|
+
expect(flattenChildren(bold.props.children)).toEqual(['bold'])
|
|
47
|
+
|
|
48
|
+
expect(nodes[2]).toBe(' and ')
|
|
49
|
+
|
|
50
|
+
const italic = nodes[3] as El
|
|
51
|
+
expect(italic.props.attributes).toBe(TextAttributes.ITALIC)
|
|
52
|
+
expect(flattenChildren(italic.props.children)).toEqual(['italic'])
|
|
53
|
+
|
|
54
|
+
expect(nodes[4]).toBe('!')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('renders inline code with palette colors', () => {
|
|
58
|
+
const output = renderMarkdown('Use `ls` to list files.')
|
|
59
|
+
const nodes = flattenNodes(output)
|
|
60
|
+
|
|
61
|
+
expect(nodes[0]).toBe('Use ')
|
|
62
|
+
|
|
63
|
+
const inlineCode = nodes[1] as El
|
|
64
|
+
expect(inlineCode.props.fg).toBe('#86efac')
|
|
65
|
+
expect(inlineCode.props.bg).toBe('#0d1117')
|
|
66
|
+
expect(flattenChildren(inlineCode.props.children)).toEqual([' ls '])
|
|
67
|
+
|
|
68
|
+
expect(nodes[2]).toBe(' to list files.')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('renders headings with color and bold attribute', () => {
|
|
72
|
+
const output = renderMarkdown('# Heading One')
|
|
73
|
+
const nodes = flattenNodes(output)
|
|
74
|
+
|
|
75
|
+
const heading = nodes[0] as El
|
|
76
|
+
expect(heading.props.attributes).toBe(TextAttributes.BOLD)
|
|
77
|
+
expect(heading.props.fg).toBe('magenta')
|
|
78
|
+
expect(flattenChildren(heading.props.children)).toEqual(['Heading One'])
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('renders inline emphasis inside headings without extra spacing', () => {
|
|
82
|
+
const output = renderMarkdown(
|
|
83
|
+
'# Other**.github/** - GitHub workflows and config',
|
|
84
|
+
)
|
|
85
|
+
const nodes = flattenNodes(output)
|
|
86
|
+
|
|
87
|
+
const heading = nodes[0] as El
|
|
88
|
+
const contents = flattenChildren(heading.props.children)
|
|
89
|
+
|
|
90
|
+
expect(contents[0]).toBe('Other')
|
|
91
|
+
|
|
92
|
+
const strong = contents[1] as El
|
|
93
|
+
expect(strong.props.attributes).toBe(TextAttributes.BOLD)
|
|
94
|
+
expect(flattenChildren(strong.props.children)).toEqual(['.github/'])
|
|
95
|
+
|
|
96
|
+
expect(contents[2]).toBe(' - GitHub workflows and config')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('renders blockquotes with prefix', () => {
|
|
100
|
+
const output = renderMarkdown('> note')
|
|
101
|
+
const nodes = flattenNodes(output)
|
|
102
|
+
|
|
103
|
+
const prefixSpan = nodes[0] as El
|
|
104
|
+
expect(prefixSpan.props.fg).toBe('gray')
|
|
105
|
+
expect(flattenChildren(prefixSpan.props.children)).toEqual(['> '])
|
|
106
|
+
|
|
107
|
+
const textSpan = nodes[1] as El
|
|
108
|
+
expect(textSpan.props.fg).toBe('gray')
|
|
109
|
+
expect(flattenChildren(textSpan.props.children)).toEqual(['note'])
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('renders lists with bullet markers', () => {
|
|
113
|
+
const output = renderMarkdown('- first\n- second')
|
|
114
|
+
const nodes = flattenNodes(output)
|
|
115
|
+
|
|
116
|
+
const bulletSpans = nodes.filter(
|
|
117
|
+
(node): node is El =>
|
|
118
|
+
React.isValidElement(node) &&
|
|
119
|
+
node.type === 'span' &&
|
|
120
|
+
flattenChildren((node as El).props.children).join('') === '- ',
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
expect(bulletSpans).toHaveLength(2)
|
|
124
|
+
bulletSpans.forEach((span) => expect(span.props.fg).toBe('white'))
|
|
125
|
+
|
|
126
|
+
const textNodes = nodes
|
|
127
|
+
.filter((node): node is string => typeof node === 'string')
|
|
128
|
+
.map((value) => value.trim())
|
|
129
|
+
.filter(Boolean)
|
|
130
|
+
expect(textNodes).toContain('first')
|
|
131
|
+
expect(textNodes).toContain('second')
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
test('renders markdown without closing code fence while streaming', () => {
|
|
135
|
+
const content = '**done**\n```js\nconsole.log('
|
|
136
|
+
const output = renderStreamingMarkdown(content)
|
|
137
|
+
const nodes = flattenNodes(output)
|
|
138
|
+
|
|
139
|
+
const boldNode = nodes.find(
|
|
140
|
+
(node): node is El =>
|
|
141
|
+
React.isValidElement(node) &&
|
|
142
|
+
(node as El).props !== undefined &&
|
|
143
|
+
(node as El).props.attributes === TextAttributes.BOLD,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
expect(boldNode).toBeDefined()
|
|
147
|
+
expect(flattenChildren(boldNode!.props.children)).toEqual(['done'])
|
|
148
|
+
expect(nodes[nodes.length - 1]).toBe('```js\nconsole.log(')
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
test('renders strikethrough text with GFM', () => {
|
|
152
|
+
const output = renderMarkdown('This is ~~deleted~~ text')
|
|
153
|
+
const nodes = flattenNodes(output)
|
|
154
|
+
|
|
155
|
+
expect(nodes[0]).toBe('This is ')
|
|
156
|
+
|
|
157
|
+
const strikethrough = nodes[1] as El
|
|
158
|
+
expect(strikethrough.props.attributes).toBe(TextAttributes.DIM)
|
|
159
|
+
expect(flattenChildren(strikethrough.props.children)).toEqual(['deleted'])
|
|
160
|
+
|
|
161
|
+
expect(nodes[2]).toBe(' text')
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
test('renders task lists with GFM', () => {
|
|
165
|
+
const output = renderMarkdown('- [ ] Todo\n- [x] Done')
|
|
166
|
+
const nodes = flattenNodes(output)
|
|
167
|
+
|
|
168
|
+
const checkboxSpans = nodes.filter(
|
|
169
|
+
(node): node is El =>
|
|
170
|
+
React.isValidElement(node) &&
|
|
171
|
+
node.type === 'span' &&
|
|
172
|
+
(flattenChildren((node as El).props.children).join('') === '[ ] ' ||
|
|
173
|
+
flattenChildren((node as El).props.children).join('') === '[x] '),
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
expect(checkboxSpans).toHaveLength(2)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
test('renders tables with GFM', () => {
|
|
180
|
+
const markdown = `| Name | Age |
|
|
181
|
+
| ---- | --- |
|
|
182
|
+
| John | 30 |
|
|
183
|
+
| Jane | 25 |`
|
|
184
|
+
const output = renderMarkdown(markdown)
|
|
185
|
+
const nodes = flattenNodes(output)
|
|
186
|
+
|
|
187
|
+
// Check that table structure is rendered with box-drawing characters
|
|
188
|
+
const textContent = nodes
|
|
189
|
+
.map((node) => {
|
|
190
|
+
if (typeof node === 'string') return node
|
|
191
|
+
if (React.isValidElement(node)) {
|
|
192
|
+
return flattenChildren((node as El).props.children).join('')
|
|
193
|
+
}
|
|
194
|
+
return ''
|
|
195
|
+
})
|
|
196
|
+
.join('')
|
|
197
|
+
|
|
198
|
+
expect(textContent).toContain('Name')
|
|
199
|
+
expect(textContent).toContain('Age')
|
|
200
|
+
expect(textContent).toContain('John')
|
|
201
|
+
expect(textContent).toContain('Jane')
|
|
202
|
+
expect(textContent).toContain('30')
|
|
203
|
+
expect(textContent).toContain('25')
|
|
204
|
+
// Table uses box-drawing characters for borders
|
|
205
|
+
expect(textContent).toContain('│')
|
|
206
|
+
expect(textContent).toContain('─')
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
test('renders code fence followed by text with quotes correctly', () => {
|
|
210
|
+
const markdown = `\`\`\`bash
|
|
211
|
+
# Start using it
|
|
212
|
+
anvil "add a new feature to handle user authentication"
|
|
213
|
+
\`\`\``
|
|
214
|
+
const output = renderMarkdown(markdown)
|
|
215
|
+
const nodes = flattenNodes(output)
|
|
216
|
+
|
|
217
|
+
// Get the text content from all nodes
|
|
218
|
+
const textContent = nodes
|
|
219
|
+
.map((node) => {
|
|
220
|
+
if (typeof node === 'string') return node
|
|
221
|
+
if (React.isValidElement(node)) {
|
|
222
|
+
return flattenChildren((node as El).props.children).join('')
|
|
223
|
+
}
|
|
224
|
+
return ''
|
|
225
|
+
})
|
|
226
|
+
.join('')
|
|
227
|
+
|
|
228
|
+
// Should contain the complete command text
|
|
229
|
+
expect(textContent).toContain('# Start using it')
|
|
230
|
+
expect(textContent).toContain(
|
|
231
|
+
'anvil "add a new feature to handle user authentication"',
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
// Should NOT have quotes concatenated with backticks
|
|
235
|
+
expect(textContent).not.toContain('it"')
|
|
236
|
+
expect(textContent).not.toContain('```"')
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
test('renders inline code followed by quotes correctly', () => {
|
|
240
|
+
const markdown = 'Use `anvil "fix bug"` to fix bugs.'
|
|
241
|
+
const output = renderMarkdown(markdown)
|
|
242
|
+
const nodes = flattenNodes(output)
|
|
243
|
+
|
|
244
|
+
expect(nodes[0]).toBe('Use ')
|
|
245
|
+
|
|
246
|
+
const inlineCode = nodes[1] as El
|
|
247
|
+
expect(inlineCode.props.fg).toBe('#86efac')
|
|
248
|
+
const inlineContent = flattenChildren(inlineCode.props.children).join('')
|
|
249
|
+
expect(inlineContent).toContain('anvil "fix bug"')
|
|
250
|
+
|
|
251
|
+
expect(nodes[2]).toBe(' to fix bugs.')
|
|
252
|
+
|
|
253
|
+
// Verify quotes are inside the inline code, not concatenated after
|
|
254
|
+
expect(inlineContent).toMatch(/anvil\s+"fix bug"/)
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
test('renders multiple code blocks with text between them', () => {
|
|
258
|
+
const markdown = `First block:
|
|
259
|
+
|
|
260
|
+
\`\`\`js
|
|
261
|
+
console.log("hello")
|
|
262
|
+
\`\`\`
|
|
263
|
+
|
|
264
|
+
Middle text with "quotes"
|
|
265
|
+
|
|
266
|
+
\`\`\`js
|
|
267
|
+
console.log("world")
|
|
268
|
+
\`\`\``
|
|
269
|
+
const output = renderMarkdown(markdown)
|
|
270
|
+
const nodes = flattenNodes(output)
|
|
271
|
+
|
|
272
|
+
const textContent = nodes
|
|
273
|
+
.map((node) => {
|
|
274
|
+
if (typeof node === 'string') return node
|
|
275
|
+
if (React.isValidElement(node)) {
|
|
276
|
+
return flattenChildren((node as El).props.children).join('')
|
|
277
|
+
}
|
|
278
|
+
return ''
|
|
279
|
+
})
|
|
280
|
+
.join('')
|
|
281
|
+
|
|
282
|
+
// All content should be present
|
|
283
|
+
expect(textContent).toContain('First block:')
|
|
284
|
+
expect(textContent).toContain('console.log("hello")')
|
|
285
|
+
expect(textContent).toContain('Middle text with "quotes"')
|
|
286
|
+
expect(textContent).toContain('console.log("world")')
|
|
287
|
+
|
|
288
|
+
// Verify no quote concatenation issues
|
|
289
|
+
expect(textContent).not.toContain('```"')
|
|
290
|
+
expect(textContent).not.toContain('"```')
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
test('renders code fence with command and quotes on same line', () => {
|
|
294
|
+
const markdown = `\`\`\`bash
|
|
295
|
+
anvil "implement feature" --verbose
|
|
296
|
+
\`\`\``
|
|
297
|
+
const output = renderMarkdown(markdown)
|
|
298
|
+
const nodes = flattenNodes(output)
|
|
299
|
+
|
|
300
|
+
const textContent = nodes
|
|
301
|
+
.map((node) => {
|
|
302
|
+
if (typeof node === 'string') return node
|
|
303
|
+
if (React.isValidElement(node)) {
|
|
304
|
+
return flattenChildren((node as El).props.children).join('')
|
|
305
|
+
}
|
|
306
|
+
return ''
|
|
307
|
+
})
|
|
308
|
+
.join('')
|
|
309
|
+
|
|
310
|
+
// Should preserve the complete command with quotes
|
|
311
|
+
expect(textContent).toContain('anvil "implement feature" --verbose')
|
|
312
|
+
expect(textContent).not.toContain('```"')
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
test('renders inline code with special characters correctly', () => {
|
|
316
|
+
const markdown = 'Run `git commit -m "fix: bug"` to commit.'
|
|
317
|
+
const output = renderMarkdown(markdown)
|
|
318
|
+
const nodes = flattenNodes(output)
|
|
319
|
+
|
|
320
|
+
const inlineCode = nodes[1] as El
|
|
321
|
+
const inlineContent = flattenChildren(inlineCode.props.children).join('')
|
|
322
|
+
|
|
323
|
+
// Should preserve quotes and special characters within inline code
|
|
324
|
+
expect(inlineContent).toContain('git commit -m "fix: bug"')
|
|
325
|
+
expect(nodes[2]).toBe(' to commit.')
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
test('wraps table columns when content exceeds available width', () => {
|
|
329
|
+
// Table with very long content that should be wrapped
|
|
330
|
+
const markdown = `| ID | This is a very long column header that should wrap |
|
|
331
|
+
| -- | -------------------------------------------------- |
|
|
332
|
+
| 1 | This cell has extremely long content that definitely exceeds the width |`
|
|
333
|
+
|
|
334
|
+
// Use a narrow codeBlockWidth to force wrapping
|
|
335
|
+
const output = renderMarkdown(markdown, { codeBlockWidth: 50 })
|
|
336
|
+
const nodes = flattenNodes(output)
|
|
337
|
+
|
|
338
|
+
const textContent = nodes
|
|
339
|
+
.map((node) => {
|
|
340
|
+
if (typeof node === 'string') return node
|
|
341
|
+
if (React.isValidElement(node)) {
|
|
342
|
+
return flattenChildren((node as El).props.children).join('')
|
|
343
|
+
}
|
|
344
|
+
return ''
|
|
345
|
+
})
|
|
346
|
+
.join('')
|
|
347
|
+
|
|
348
|
+
// Should NOT contain ellipsis - content wraps instead of truncating
|
|
349
|
+
expect(textContent).not.toContain('…')
|
|
350
|
+
// The short column content should be present
|
|
351
|
+
expect(textContent).toContain('ID')
|
|
352
|
+
expect(textContent).toContain('1')
|
|
353
|
+
// Box-drawing characters should still be present
|
|
354
|
+
expect(textContent).toContain('│')
|
|
355
|
+
expect(textContent).toContain('─')
|
|
356
|
+
// The full content should be present across wrapped lines
|
|
357
|
+
expect(textContent).toContain('long')
|
|
358
|
+
expect(textContent).toContain('header')
|
|
359
|
+
expect(textContent).toContain('wrap')
|
|
360
|
+
expect(textContent).toContain('extremely')
|
|
361
|
+
expect(textContent).toContain('exceeds')
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
test('does not wrap table columns when content fits available width', () => {
|
|
365
|
+
const markdown = `| Name | Age |
|
|
366
|
+
| ---- | --- |
|
|
367
|
+
| John | 30 |`
|
|
368
|
+
|
|
369
|
+
// Use a wide codeBlockWidth so no wrapping is needed
|
|
370
|
+
const output = renderMarkdown(markdown, { codeBlockWidth: 80 })
|
|
371
|
+
const nodes = flattenNodes(output)
|
|
372
|
+
|
|
373
|
+
const textContent = nodes
|
|
374
|
+
.map((node) => {
|
|
375
|
+
if (typeof node === 'string') return node
|
|
376
|
+
if (React.isValidElement(node)) {
|
|
377
|
+
return flattenChildren((node as El).props.children).join('')
|
|
378
|
+
}
|
|
379
|
+
return ''
|
|
380
|
+
})
|
|
381
|
+
.join('')
|
|
382
|
+
|
|
383
|
+
// All content should be present in full
|
|
384
|
+
expect(textContent).toContain('Name')
|
|
385
|
+
expect(textContent).toContain('Age')
|
|
386
|
+
expect(textContent).toContain('John')
|
|
387
|
+
expect(textContent).toContain('30')
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
test('wraps and shows full content when table is too wide', () => {
|
|
391
|
+
// Three columns of roughly equal width
|
|
392
|
+
const markdown = `| Column One | Column Two | Column Three |
|
|
393
|
+
| ---------- | ---------- | ------------ |
|
|
394
|
+
| Value1 | Value2 | Value3 |`
|
|
395
|
+
|
|
396
|
+
// Very narrow width to force significant wrapping
|
|
397
|
+
const output = renderMarkdown(markdown, { codeBlockWidth: 30 })
|
|
398
|
+
const nodes = flattenNodes(output)
|
|
399
|
+
|
|
400
|
+
const textContent = nodes
|
|
401
|
+
.map((node) => {
|
|
402
|
+
if (typeof node === 'string') return node
|
|
403
|
+
if (React.isValidElement(node)) {
|
|
404
|
+
return flattenChildren((node as El).props.children).join('')
|
|
405
|
+
}
|
|
406
|
+
return ''
|
|
407
|
+
})
|
|
408
|
+
.join('')
|
|
409
|
+
|
|
410
|
+
// Table structure should still be present
|
|
411
|
+
expect(textContent).toContain('│')
|
|
412
|
+
expect(textContent).toContain('┌')
|
|
413
|
+
expect(textContent).toContain('└')
|
|
414
|
+
// Full content should still be visible (wrapped, not truncated)
|
|
415
|
+
expect(textContent).not.toContain('…')
|
|
416
|
+
// All values should be present
|
|
417
|
+
expect(textContent).toContain('Value1')
|
|
418
|
+
expect(textContent).toContain('Value2')
|
|
419
|
+
expect(textContent).toContain('Value3')
|
|
420
|
+
})
|
|
421
|
+
})
|