@drscrewdriver/dsh-better-display 0.3.3-fork.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/CHANGELOG.md +259 -0
- package/DESIGN.md +72 -0
- package/LICENSE +24 -0
- package/README.en.md +55 -0
- package/README.md +55 -0
- package/THIRD_PARTY_NOTICES.md +65 -0
- package/compat/harness-rc2.json +99 -0
- package/cordis.patch.yml +5 -0
- package/cordis.yml +4 -0
- package/docs/official-rendering-bridge.md +65 -0
- package/dshx.yml +5 -0
- package/lib/client.js +49292 -0
- package/lib/client.js.map +1 -0
- package/lib/dsh-better-display.js +101 -0
- package/lib/types/client/Blocks.d.ts +49 -0
- package/lib/types/client/Blocks.d.ts.map +1 -0
- package/lib/types/client/Blocks.js +143 -0
- package/lib/types/client/Blocks.js.map +1 -0
- package/lib/types/client/ChoreographedFlow.d.ts +22 -0
- package/lib/types/client/ChoreographedFlow.d.ts.map +1 -0
- package/lib/types/client/ChoreographedFlow.js +218 -0
- package/lib/types/client/ChoreographedFlow.js.map +1 -0
- package/lib/types/client/ClosedProcessSummary.d.ts +9 -0
- package/lib/types/client/ClosedProcessSummary.d.ts.map +1 -0
- package/lib/types/client/ClosedProcessSummary.js +16 -0
- package/lib/types/client/ClosedProcessSummary.js.map +1 -0
- package/lib/types/client/DiffPanel.d.ts +13 -0
- package/lib/types/client/DiffPanel.d.ts.map +1 -0
- package/lib/types/client/DiffPanel.js +115 -0
- package/lib/types/client/DiffPanel.js.map +1 -0
- package/lib/types/client/LiveFold.d.ts +10 -0
- package/lib/types/client/LiveFold.d.ts.map +1 -0
- package/lib/types/client/LiveFold.js +39 -0
- package/lib/types/client/LiveFold.js.map +1 -0
- package/lib/types/client/McpAppFrame.d.ts +32 -0
- package/lib/types/client/McpAppFrame.d.ts.map +1 -0
- package/lib/types/client/McpAppFrame.js +193 -0
- package/lib/types/client/McpAppFrame.js.map +1 -0
- package/lib/types/client/OfficialContent.d.ts +25 -0
- package/lib/types/client/OfficialContent.d.ts.map +1 -0
- package/lib/types/client/OfficialContent.js +53 -0
- package/lib/types/client/OfficialContent.js.map +1 -0
- package/lib/types/client/Reader.d.ts +3 -0
- package/lib/types/client/Reader.d.ts.map +1 -0
- package/lib/types/client/Reader.js +603 -0
- package/lib/types/client/Reader.js.map +1 -0
- package/lib/types/client/ReasoningCard.d.ts +11 -0
- package/lib/types/client/ReasoningCard.d.ts.map +1 -0
- package/lib/types/client/ReasoningCard.js +322 -0
- package/lib/types/client/ReasoningCard.js.map +1 -0
- package/lib/types/client/SettingsSection.d.ts +37 -0
- package/lib/types/client/SettingsSection.d.ts.map +1 -0
- package/lib/types/client/SettingsSection.js +63 -0
- package/lib/types/client/SettingsSection.js.map +1 -0
- package/lib/types/client/StickyLane.d.ts +8 -0
- package/lib/types/client/StickyLane.d.ts.map +1 -0
- package/lib/types/client/StickyLane.js +39 -0
- package/lib/types/client/StickyLane.js.map +1 -0
- package/lib/types/client/TimelineRail.d.ts +10 -0
- package/lib/types/client/TimelineRail.d.ts.map +1 -0
- package/lib/types/client/TimelineRail.js +168 -0
- package/lib/types/client/TimelineRail.js.map +1 -0
- package/lib/types/client/ToolActivity.d.ts +17 -0
- package/lib/types/client/ToolActivity.d.ts.map +1 -0
- package/lib/types/client/ToolActivity.js +244 -0
- package/lib/types/client/ToolActivity.js.map +1 -0
- package/lib/types/client/TurnMetrics.d.ts +11 -0
- package/lib/types/client/TurnMetrics.d.ts.map +1 -0
- package/lib/types/client/TurnMetrics.js +44 -0
- package/lib/types/client/TurnMetrics.js.map +1 -0
- package/lib/types/client/WaitClock.d.ts +16 -0
- package/lib/types/client/WaitClock.d.ts.map +1 -0
- package/lib/types/client/WaitClock.js +29 -0
- package/lib/types/client/WaitClock.js.map +1 -0
- package/lib/types/client/WaitingClock.d.ts +15 -0
- package/lib/types/client/WaitingClock.d.ts.map +1 -0
- package/lib/types/client/WaitingClock.js +30 -0
- package/lib/types/client/WaitingClock.js.map +1 -0
- package/lib/types/client/WaitingStatus.d.ts +6 -0
- package/lib/types/client/WaitingStatus.d.ts.map +1 -0
- package/lib/types/client/WaitingStatus.js +9 -0
- package/lib/types/client/WaitingStatus.js.map +1 -0
- package/lib/types/client/conversation-scroll.d.ts +19 -0
- package/lib/types/client/conversation-scroll.d.ts.map +1 -0
- package/lib/types/client/conversation-scroll.js +25 -0
- package/lib/types/client/conversation-scroll.js.map +1 -0
- package/lib/types/client/deliverables.d.ts +24 -0
- package/lib/types/client/deliverables.d.ts.map +1 -0
- package/lib/types/client/deliverables.js +104 -0
- package/lib/types/client/deliverables.js.map +1 -0
- package/lib/types/client/entry-policy.d.ts +10 -0
- package/lib/types/client/entry-policy.d.ts.map +1 -0
- package/lib/types/client/entry-policy.js +24 -0
- package/lib/types/client/entry-policy.js.map +1 -0
- package/lib/types/client/entry.d.ts +4 -0
- package/lib/types/client/entry.d.ts.map +1 -0
- package/lib/types/client/entry.js +34 -0
- package/lib/types/client/entry.js.map +1 -0
- package/lib/types/client/fold-choreography.d.ts +40 -0
- package/lib/types/client/fold-choreography.d.ts.map +1 -0
- package/lib/types/client/fold-choreography.js +48 -0
- package/lib/types/client/fold-choreography.js.map +1 -0
- package/lib/types/client/fold-intensity.d.ts +11 -0
- package/lib/types/client/fold-intensity.d.ts.map +1 -0
- package/lib/types/client/fold-intensity.js +32 -0
- package/lib/types/client/fold-intensity.js.map +1 -0
- package/lib/types/client/index.d.ts +7 -0
- package/lib/types/client/index.d.ts.map +1 -0
- package/lib/types/client/index.js +195 -0
- package/lib/types/client/index.js.map +1 -0
- package/lib/types/client/live-turn.d.ts +58 -0
- package/lib/types/client/live-turn.d.ts.map +1 -0
- package/lib/types/client/live-turn.js +190 -0
- package/lib/types/client/live-turn.js.map +1 -0
- package/lib/types/client/markdown/MarkdownText.d.ts +41 -0
- package/lib/types/client/markdown/MarkdownText.d.ts.map +1 -0
- package/lib/types/client/markdown/MarkdownText.js +178 -0
- package/lib/types/client/markdown/MarkdownText.js.map +1 -0
- package/lib/types/client/markdown/cjkFriendlyStrong.d.ts +9 -0
- package/lib/types/client/markdown/cjkFriendlyStrong.d.ts.map +1 -0
- package/lib/types/client/markdown/cjkFriendlyStrong.js +73 -0
- package/lib/types/client/markdown/cjkFriendlyStrong.js.map +1 -0
- package/lib/types/client/markdown/incremental.d.ts +69 -0
- package/lib/types/client/markdown/incremental.d.ts.map +1 -0
- package/lib/types/client/markdown/incremental.js +108 -0
- package/lib/types/client/markdown/incremental.js.map +1 -0
- package/lib/types/client/markdown/katex.d.ts +27 -0
- package/lib/types/client/markdown/katex.d.ts.map +1 -0
- package/lib/types/client/markdown/katex.js +88 -0
- package/lib/types/client/markdown/katex.js.map +1 -0
- package/lib/types/client/markdown/mathCompatibility.d.ts +11 -0
- package/lib/types/client/markdown/mathCompatibility.d.ts.map +1 -0
- package/lib/types/client/markdown/mathCompatibility.js +293 -0
- package/lib/types/client/markdown/mathCompatibility.js.map +1 -0
- package/lib/types/client/markdown/parse.d.ts +25 -0
- package/lib/types/client/markdown/parse.d.ts.map +1 -0
- package/lib/types/client/markdown/parse.js +42 -0
- package/lib/types/client/markdown/parse.js.map +1 -0
- package/lib/types/client/markdown/render.d.ts +121 -0
- package/lib/types/client/markdown/render.d.ts.map +1 -0
- package/lib/types/client/markdown/render.js +452 -0
- package/lib/types/client/markdown/render.js.map +1 -0
- package/lib/types/client/mcp-app.d.ts +50 -0
- package/lib/types/client/mcp-app.d.ts.map +1 -0
- package/lib/types/client/mcp-app.js +328 -0
- package/lib/types/client/mcp-app.js.map +1 -0
- package/lib/types/client/message-chrome.d.ts +19 -0
- package/lib/types/client/message-chrome.d.ts.map +1 -0
- package/lib/types/client/message-chrome.js +42 -0
- package/lib/types/client/message-chrome.js.map +1 -0
- package/lib/types/client/motion.d.ts +38 -0
- package/lib/types/client/motion.d.ts.map +1 -0
- package/lib/types/client/motion.js +453 -0
- package/lib/types/client/motion.js.map +1 -0
- package/lib/types/client/native/ContextBody.d.ts +130 -0
- package/lib/types/client/native/ContextBody.d.ts.map +1 -0
- package/lib/types/client/native/ContextBody.js +398 -0
- package/lib/types/client/native/ContextBody.js.map +1 -0
- package/lib/types/client/native/ContextInjectionRow.d.ts +35 -0
- package/lib/types/client/native/ContextInjectionRow.d.ts.map +1 -0
- package/lib/types/client/native/ContextInjectionRow.js +42 -0
- package/lib/types/client/native/ContextInjectionRow.js.map +1 -0
- package/lib/types/client/native/ReferenceIcon.d.ts +16 -0
- package/lib/types/client/native/ReferenceIcon.d.ts.map +1 -0
- package/lib/types/client/native/ReferenceIcon.js +16 -0
- package/lib/types/client/native/ReferenceIcon.js.map +1 -0
- package/lib/types/client/native/tool-call-model.d.ts +58 -0
- package/lib/types/client/native/tool-call-model.d.ts.map +1 -0
- package/lib/types/client/native/tool-call-model.js +219 -0
- package/lib/types/client/native/tool-call-model.js.map +1 -0
- package/lib/types/client/native/tool-update.d.ts +23 -0
- package/lib/types/client/native/tool-update.d.ts.map +1 -0
- package/lib/types/client/native/tool-update.js +22 -0
- package/lib/types/client/native/tool-update.js.map +1 -0
- package/lib/types/client/official-slots.d.ts +71 -0
- package/lib/types/client/official-slots.d.ts.map +1 -0
- package/lib/types/client/official-slots.js +183 -0
- package/lib/types/client/official-slots.js.map +1 -0
- package/lib/types/client/open-file.d.ts +29 -0
- package/lib/types/client/open-file.d.ts.map +1 -0
- package/lib/types/client/open-file.js +71 -0
- package/lib/types/client/open-file.js.map +1 -0
- package/lib/types/client/pending-submission.d.ts +28 -0
- package/lib/types/client/pending-submission.d.ts.map +1 -0
- package/lib/types/client/pending-submission.js +28 -0
- package/lib/types/client/pending-submission.js.map +1 -0
- package/lib/types/client/platform-media.d.ts +6 -0
- package/lib/types/client/platform-media.d.ts.map +1 -0
- package/lib/types/client/platform-media.js +18 -0
- package/lib/types/client/platform-media.js.map +1 -0
- package/lib/types/client/primitive-labels.d.ts +10 -0
- package/lib/types/client/primitive-labels.d.ts.map +1 -0
- package/lib/types/client/primitive-labels.js +38 -0
- package/lib/types/client/primitive-labels.js.map +1 -0
- package/lib/types/client/projection.d.ts +41 -0
- package/lib/types/client/projection.d.ts.map +1 -0
- package/lib/types/client/projection.js +112 -0
- package/lib/types/client/projection.js.map +1 -0
- package/lib/types/client/reasoning-follow.d.ts +10 -0
- package/lib/types/client/reasoning-follow.d.ts.map +1 -0
- package/lib/types/client/reasoning-follow.js +21 -0
- package/lib/types/client/reasoning-follow.js.map +1 -0
- package/lib/types/client/resource-address.d.ts +3 -0
- package/lib/types/client/resource-address.d.ts.map +1 -0
- package/lib/types/client/resource-address.js +33 -0
- package/lib/types/client/resource-address.js.map +1 -0
- package/lib/types/client/settings-copy.d.ts +7 -0
- package/lib/types/client/settings-copy.d.ts.map +1 -0
- package/lib/types/client/settings-copy.js +53 -0
- package/lib/types/client/settings-copy.js.map +1 -0
- package/lib/types/client/settings-slots.d.ts +23 -0
- package/lib/types/client/settings-slots.d.ts.map +1 -0
- package/lib/types/client/settings-slots.js +2 -0
- package/lib/types/client/settings-slots.js.map +1 -0
- package/lib/types/client/settings.d.ts +4 -0
- package/lib/types/client/settings.d.ts.map +1 -0
- package/lib/types/client/settings.js +60 -0
- package/lib/types/client/settings.js.map +1 -0
- package/lib/types/client/skill-status.d.ts +25 -0
- package/lib/types/client/skill-status.d.ts.map +1 -0
- package/lib/types/client/skill-status.js +44 -0
- package/lib/types/client/skill-status.js.map +1 -0
- package/lib/types/client/store.d.ts +35 -0
- package/lib/types/client/store.d.ts.map +1 -0
- package/lib/types/client/store.js +37 -0
- package/lib/types/client/store.js.map +1 -0
- package/lib/types/client/stream-buffer.d.ts +37 -0
- package/lib/types/client/stream-buffer.d.ts.map +1 -0
- package/lib/types/client/stream-buffer.js +150 -0
- package/lib/types/client/stream-buffer.js.map +1 -0
- package/lib/types/client/streaming.d.ts +18 -0
- package/lib/types/client/streaming.d.ts.map +1 -0
- package/lib/types/client/streaming.js +84 -0
- package/lib/types/client/streaming.js.map +1 -0
- package/lib/types/client/timeline.d.ts +26 -0
- package/lib/types/client/timeline.d.ts.map +1 -0
- package/lib/types/client/timeline.js +48 -0
- package/lib/types/client/timeline.js.map +1 -0
- package/lib/types/client/tool-activity.d.ts +98 -0
- package/lib/types/client/tool-activity.d.ts.map +1 -0
- package/lib/types/client/tool-activity.js +301 -0
- package/lib/types/client/tool-activity.js.map +1 -0
- package/lib/types/client/types.d.ts +91 -0
- package/lib/types/client/types.d.ts.map +1 -0
- package/lib/types/client/types.js +2 -0
- package/lib/types/client/types.js.map +1 -0
- package/lib/types/client/waiting-clock.d.ts +49 -0
- package/lib/types/client/waiting-clock.d.ts.map +1 -0
- package/lib/types/client/waiting-clock.js +80 -0
- package/lib/types/client/waiting-clock.js.map +1 -0
- package/lib/types/client/word-motion.d.ts +16 -0
- package/lib/types/client/word-motion.d.ts.map +1 -0
- package/lib/types/client/word-motion.js +90 -0
- package/lib/types/client/word-motion.js.map +1 -0
- package/lib/types/client/word-timeline.d.ts +32 -0
- package/lib/types/client/word-timeline.d.ts.map +1 -0
- package/lib/types/client/word-timeline.js +119 -0
- package/lib/types/client/word-timeline.js.map +1 -0
- package/lib/types/dsh-better-display.d.ts +17 -0
- package/lib/types/dsh-better-display.d.ts.map +1 -0
- package/lib/types/dsh-better-display.js +93 -0
- package/lib/types/dsh-better-display.js.map +1 -0
- package/lib/types/skill-roots.d.ts +22 -0
- package/lib/types/skill-roots.d.ts.map +1 -0
- package/lib/types/skill-roots.js +126 -0
- package/lib/types/skill-roots.js.map +1 -0
- package/lib/types/skill-status.d.ts +36 -0
- package/lib/types/skill-status.d.ts.map +1 -0
- package/lib/types/skill-status.js +51 -0
- package/lib/types/skill-status.js.map +1 -0
- package/package.json +133 -0
- package/scripts/build-motion-fixture.mjs +26 -0
- package/scripts/check-harness-compat.mjs +71 -0
- package/scripts/link-harness-dependencies.mjs +54 -0
- package/scripts/verify-auto-fold.mjs +114 -0
- package/scripts/verify-official-bridge.mjs +121 -0
- package/scripts/verify-tool-shimmer.mjs +62 -0
- package/skills/generative-mcpapps/SKILL.md +47 -0
- package/skills/generative-mcpapps/examples/interactive_quiz.html +153 -0
- package/skills/generative-mcpapps/references/html_boilerplate.md +122 -0
- package/skills/generative-mcpapps/references/sep1865_protocol.md +54 -0
- package/src/client/Blocks.tsx +213 -0
- package/src/client/ChoreographedFlow.tsx +232 -0
- package/src/client/ClosedProcessSummary.tsx +23 -0
- package/src/client/DiffPanel.tsx +124 -0
- package/src/client/LiveFold.tsx +43 -0
- package/src/client/McpAppFrame.module.css +210 -0
- package/src/client/McpAppFrame.tsx +317 -0
- package/src/client/OfficialContent.tsx +69 -0
- package/src/client/Reader.module.css +692 -0
- package/src/client/Reader.tsx +878 -0
- package/src/client/ReasoningCard.tsx +306 -0
- package/src/client/SettingsSection.module.css +184 -0
- package/src/client/SettingsSection.tsx +194 -0
- package/src/client/StickyLane.tsx +34 -0
- package/src/client/TimelineRail.module.css +170 -0
- package/src/client/TimelineRail.tsx +262 -0
- package/src/client/ToolActivity.tsx +274 -0
- package/src/client/TurnMetrics.module.css +139 -0
- package/src/client/TurnMetrics.tsx +160 -0
- package/src/client/WaitClock.tsx +33 -0
- package/src/client/WaitingStatus.tsx +13 -0
- package/src/client/conversation-scroll.ts +28 -0
- package/src/client/deliverables.ts +122 -0
- package/src/client/entry-policy.ts +20 -0
- package/src/client/entry.tsx +40 -0
- package/src/client/fold-choreography.ts +55 -0
- package/src/client/fold-intensity.ts +36 -0
- package/src/client/index.tsx +222 -0
- package/src/client/live-turn.ts +198 -0
- package/src/client/markdown/MarkdownText.module.css +304 -0
- package/src/client/markdown/MarkdownText.tsx +195 -0
- package/src/client/markdown/cjkFriendlyStrong.ts +84 -0
- package/src/client/markdown/incremental.ts +131 -0
- package/src/client/markdown/katex.tsx +91 -0
- package/src/client/markdown/mathCompatibility.ts +350 -0
- package/src/client/markdown/parse.ts +45 -0
- package/src/client/markdown/render.tsx +647 -0
- package/src/client/markdown/upstream.json +14 -0
- package/src/client/mcp-app.ts +323 -0
- package/src/client/message-chrome.ts +47 -0
- package/src/client/motion.tsx +410 -0
- package/src/client/native/ContextBody.module.css +161 -0
- package/src/client/native/ContextBody.tsx +591 -0
- package/src/client/native/ContextInjectionRow.module.css +66 -0
- package/src/client/native/ContextInjectionRow.tsx +106 -0
- package/src/client/native/ReferenceIcon.tsx +35 -0
- package/src/client/native/tool-call-model.ts +248 -0
- package/src/client/native/tool-update.ts +32 -0
- package/src/client/native/upstream.json +41 -0
- package/src/client/official-slots.tsx +211 -0
- package/src/client/open-file.ts +94 -0
- package/src/client/pending-submission.ts +49 -0
- package/src/client/platform-media.ts +17 -0
- package/src/client/primitive-labels.ts +54 -0
- package/src/client/projection.ts +118 -0
- package/src/client/reasoning-follow.ts +28 -0
- package/src/client/settings-copy.ts +80 -0
- package/src/client/settings-slots.ts +20 -0
- package/src/client/settings.tsx +73 -0
- package/src/client/skill-status.ts +78 -0
- package/src/client/store.ts +75 -0
- package/src/client/stream-buffer.ts +136 -0
- package/src/client/streaming.tsx +70 -0
- package/src/client/style.d.ts +4 -0
- package/src/client/timeline.ts +77 -0
- package/src/client/tool-activity.ts +298 -0
- package/src/client/types.ts +89 -0
- package/src/client/waiting-clock.ts +83 -0
- package/src/client/word-motion.tsx +92 -0
- package/src/client/word-timeline.ts +113 -0
- package/src/dsh-better-display.ts +108 -0
- package/src/skill-roots.ts +138 -0
- package/src/skill-status.ts +81 -0
- package/tests/browser-auto-fold.tsx +64 -0
- package/tests/browser-closed-gap.tsx +20 -0
- package/tests/browser-motion.tsx +65 -0
- package/tests/browser-official-bridge.tsx +152 -0
- package/tests/browser-sticky-lanes.tsx +22 -0
- package/tests/browser-waiting-clock.tsx +14 -0
- package/tests/chat-flow-hook.test.ts +66 -0
- package/tests/cloud-gate.test.ts +34 -0
- package/tests/context-tool-update.test.ts +33 -0
- package/tests/conversation-scroll.test.ts +47 -0
- package/tests/deliverables.test.ts +128 -0
- package/tests/entry.test.ts +42 -0
- package/tests/fixtures/maid-composer-hide.html +31 -0
- package/tests/fold-choreography.test.ts +54 -0
- package/tests/fold-intensity.test.ts +94 -0
- package/tests/footer-precedence.test.ts +129 -0
- package/tests/history-word-regression.test.ts +16 -0
- package/tests/live-turn.test.ts +218 -0
- package/tests/mcp-apps.test.ts +157 -0
- package/tests/message-chrome.test.ts +29 -0
- package/tests/official-slots.test.ts +112 -0
- package/tests/open-file.test.ts +114 -0
- package/tests/pending-submission.test.ts +45 -0
- package/tests/platform-media.test.ts +23 -0
- package/tests/projection.test.ts +184 -0
- package/tests/reasoning-follow.test.ts +34 -0
- package/tests/settings-copy.test.ts +63 -0
- package/tests/skill-status.test.ts +137 -0
- package/tests/stock-install.test.ts +58 -0
- package/tests/stream-buffer.test.ts +102 -0
- package/tests/timeline.test.ts +59 -0
- package/tests/tool-diff.test.ts +57 -0
- package/tests/waiting-clock.test.ts +83 -0
- package/tests/word-timeline.test.ts +146 -0
- package/tsconfig.json +11 -0
- package/tsdown.config.ts +51 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mergeTimelineItems } from '../src/client/timeline.ts';
|
|
4
|
+
|
|
5
|
+
test('mergeTimelineItems handles empty inputs gracefully', () => {
|
|
6
|
+
assert.deepEqual(mergeTimelineItems([], undefined), []);
|
|
7
|
+
assert.deepEqual(mergeTimelineItems([], []), []);
|
|
8
|
+
assert.deepEqual(mergeTimelineItems(undefined, null), []);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test('mergeTimelineItems parses and normalizes wire turn outline entries', () => {
|
|
12
|
+
const outline = [
|
|
13
|
+
{ turn: 1, seq: 10, prompt: 'Hello world', response: 'Hi there' },
|
|
14
|
+
{ turn: 2, seq: 25, prompt: 'Fix the bug', response: 'Fixed' },
|
|
15
|
+
{ turn: 'invalid', seq: 30 }, // should be ignored
|
|
16
|
+
null,
|
|
17
|
+
{ turn: 0, seq: 1, prompt: '', response: '' },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const items = mergeTimelineItems([], outline);
|
|
21
|
+
assert.equal(items.length, 3);
|
|
22
|
+
assert.equal(items[0].turn, 0);
|
|
23
|
+
assert.equal(items[1].turn, 1);
|
|
24
|
+
assert.equal(items[1].prompt, 'Hello world');
|
|
25
|
+
assert.equal(items[1].anchor.kind, 'unloaded');
|
|
26
|
+
if (items[1].anchor.kind === 'unloaded') {
|
|
27
|
+
assert.equal(items[1].anchor.seq, 10);
|
|
28
|
+
}
|
|
29
|
+
assert.equal(items[2].turn, 2);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('mergeTimelineItems prioritizes loaded anchors and preserves deliverable flags', () => {
|
|
33
|
+
const outline = [
|
|
34
|
+
{ turn: 1, seq: 10, prompt: 'Outline prompt', response: 'Outline reply' },
|
|
35
|
+
{ turn: 2, seq: 20, prompt: 'Turn 2 prompt', response: '' },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const loaded = [
|
|
39
|
+
{ turn: 1, anchorKey: 'node-turn-1', prompt: 'Loaded prompt 1', response: 'Loaded reply 1' },
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const deliverables = new Set([1]);
|
|
43
|
+
const items = mergeTimelineItems(loaded, outline, deliverables);
|
|
44
|
+
|
|
45
|
+
assert.equal(items.length, 2);
|
|
46
|
+
// Turn 1 should be loaded anchor with freshest prompt & hasDeliverables = true
|
|
47
|
+
assert.equal(items[0].turn, 1);
|
|
48
|
+
assert.equal(items[0].anchor.kind, 'loaded');
|
|
49
|
+
if (items[0].anchor.kind === 'loaded') {
|
|
50
|
+
assert.equal(items[0].anchor.key, 'node-turn-1');
|
|
51
|
+
}
|
|
52
|
+
assert.equal(items[0].prompt, 'Loaded prompt 1');
|
|
53
|
+
assert.equal(items[0].hasDeliverables, true);
|
|
54
|
+
|
|
55
|
+
// Turn 2 is still unloaded
|
|
56
|
+
assert.equal(items[1].turn, 2);
|
|
57
|
+
assert.equal(items[1].anchor.kind, 'unloaded');
|
|
58
|
+
assert.equal(items[1].hasDeliverables, false);
|
|
59
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { callDiffHunks, DIFF_FALLBACK_TOOLS, diffTotals } from '../src/client/tool-activity.ts';
|
|
4
|
+
import type { ToolCallBlock } from '@deepseek-ai/dsh-client-ui-conversation/client';
|
|
5
|
+
|
|
6
|
+
test('diff argument fallback is whitelisted to write/edit/str_replace_editor', () => {
|
|
7
|
+
assert.deepEqual([...DIFF_FALLBACK_TOOLS], ['write', 'edit', 'str_replace_editor']);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test('meta.diffs wins; memory-like content fields do not invent counts', () => {
|
|
11
|
+
const fromMeta = callDiffHunks({
|
|
12
|
+
kind: 'tool-result',
|
|
13
|
+
callId: 'w1',
|
|
14
|
+
time: 1,
|
|
15
|
+
callTime: 0,
|
|
16
|
+
content: [],
|
|
17
|
+
isError: false,
|
|
18
|
+
meta: { diffs: [{ path: 'a.ts', oldText: 'old\n', newText: 'new\nline\n' }] },
|
|
19
|
+
call: { name: 'write', argsRaw: '{"path":"a.ts","content":"ignored"}' },
|
|
20
|
+
subCalls: [],
|
|
21
|
+
} as unknown as ToolCallBlock, { path: 'a.ts', content: 'ignored' }, 'write');
|
|
22
|
+
assert.equal(fromMeta.length, 1);
|
|
23
|
+
assert.equal(fromMeta[0]?.path, 'a.ts');
|
|
24
|
+
|
|
25
|
+
const note = callDiffHunks(undefined, { content: 'line1\nline2\nline3\nline4\nline5' }, 'memory_note_pre');
|
|
26
|
+
assert.deepEqual(note, []);
|
|
27
|
+
assert.equal(diffTotals(undefined, { content: 'a\nb\nc' }, 'memory_note_pre'), null);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('write/edit/str_replace_editor fall back to arguments; parent rolls up subCalls', () => {
|
|
31
|
+
const write = callDiffHunks(undefined, { path: 'src/a.ts', content: 'one\ntwo\n' }, 'write');
|
|
32
|
+
assert.equal(write.length, 1);
|
|
33
|
+
assert.equal(write[0]?.path, 'src/a.ts');
|
|
34
|
+
|
|
35
|
+
const parent = callDiffHunks({
|
|
36
|
+
kind: 'tool-result',
|
|
37
|
+
callId: 'run',
|
|
38
|
+
time: 1,
|
|
39
|
+
callTime: 0,
|
|
40
|
+
content: [],
|
|
41
|
+
isError: false,
|
|
42
|
+
meta: {},
|
|
43
|
+
call: { name: 'run_code', argsRaw: '{}' },
|
|
44
|
+
subCalls: [{
|
|
45
|
+
kind: 'tool-result',
|
|
46
|
+
callId: 'child',
|
|
47
|
+
time: 1,
|
|
48
|
+
callTime: 0,
|
|
49
|
+
content: [],
|
|
50
|
+
isError: false,
|
|
51
|
+
meta: {},
|
|
52
|
+
call: { name: 'edit', argsRaw: '{"path":"nested.ts","old_string":"a\\n","new_string":"b\\nc\\n"}' },
|
|
53
|
+
subCalls: [],
|
|
54
|
+
}],
|
|
55
|
+
} as unknown as ToolCallBlock, {}, 'run_code');
|
|
56
|
+
assert.equal(parent.some(hunk => hunk.path === 'nested.ts'), true);
|
|
57
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { HANDOVER_KINDS, handsBackToModel, WAIT_AFTER, waitingAnchor } from '../src/client/waiting-clock.js';
|
|
4
|
+
|
|
5
|
+
const nodes = new Map<string, { kind: string; data: unknown }>([
|
|
6
|
+
['u1', { kind: 'user', data: { time: 1000 } }],
|
|
7
|
+
['s1', { kind: 'steering', data: { time: 301000 } }],
|
|
8
|
+
['s2', { kind: 'steering', data: { time: 305000 } }],
|
|
9
|
+
['a1', { kind: 'assistant-step', data: { time: 302000 } }],
|
|
10
|
+
['ctx', { kind: 'context', data: { time: 399000 } }],
|
|
11
|
+
// A settled tool: the root carries its result, so control returns to the model.
|
|
12
|
+
['tr', { kind: 'tool-call', data: { time: 61000, root: { kind: 'tool-result', callId: 'c1' } } }],
|
|
13
|
+
// A running tool: no result yet, so the tool — not the model — is working.
|
|
14
|
+
['tc', { kind: 'tool-call', data: { time: 62000, root: { callId: 'c2', name: 'read', argsRaw: '{}' } } }],
|
|
15
|
+
// A command that already reported an outcome.
|
|
16
|
+
['cmd', { kind: 'command', data: { time: 70000, outcome: { kind: 'success' } } }],
|
|
17
|
+
// A command that has not settled yet.
|
|
18
|
+
['cmd-open', { kind: 'command', data: { time: 71000, outcome: null } }],
|
|
19
|
+
]);
|
|
20
|
+
const get = (key: string) => nodes.get(key);
|
|
21
|
+
|
|
22
|
+
// The regression this guards: the handover set is matched against the CHAT layer's
|
|
23
|
+
// kind union, because that is what `snapshot.nodes` holds. A conversation-layer name
|
|
24
|
+
// ('tool-result') or an invented one ('tool-return') never occurs there, so the branch
|
|
25
|
+
// it guards is silently dead — which is how a returned tool stopped restarting the wait.
|
|
26
|
+
test('handover kinds are chat-layer kinds, verified against the host type', () => {
|
|
27
|
+
// The compile-time check lives in the `satisfies` in waiting-clock; this is the
|
|
28
|
+
// runtime echo so a cast cannot quietly defeat it.
|
|
29
|
+
for (const kind of HANDOVER_KINDS) {
|
|
30
|
+
assert.ok(WAIT_AFTER.has(kind) || kind === 'user' || kind === 'steering' || kind === 'tool-call' || kind === 'command', kind);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('the conversation-layer name for a returned tool is NOT a chat kind', () => {
|
|
35
|
+
// 'tool-result' is real in the conversation contract, but the reader never sees a
|
|
36
|
+
// node with that kind: it is nested inside tool-call data. Keeping it in the set
|
|
37
|
+
// was the bug, so pin its absence.
|
|
38
|
+
assert.ok(!WAIT_AFTER.has('tool-result' as never), 'tool-result is not a chat node kind');
|
|
39
|
+
assert.ok(!WAIT_AFTER.has('tool-return' as never), 'tool-return was never a kind at all');
|
|
40
|
+
assert.ok(!WAIT_AFTER.has('command-input' as never), 'command-input is not a chat node kind');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('a returned tool hands control back to the model', () => {
|
|
44
|
+
const node = get('tr')!;
|
|
45
|
+
assert.ok(handsBackToModel(node));
|
|
46
|
+
assert.deepEqual(waitingAnchor(['u1', 's1', 'tr'], get), { key: 'tr', time: 61000 });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('a running tool does NOT hand control back — the tool is the one working', () => {
|
|
50
|
+
assert.ok(!handsBackToModel(get('tc')!));
|
|
51
|
+
// Falling back to the user's message: the wait keeps its earlier clock.
|
|
52
|
+
assert.deepEqual(waitingAnchor(['u1', 'tc'], get), { key: 'u1', time: 1000 });
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('a settled command hands over; one still executing does not', () => {
|
|
56
|
+
assert.ok(handsBackToModel(get('cmd')!));
|
|
57
|
+
assert.ok(!handsBackToModel(get('cmd-open')!));
|
|
58
|
+
assert.deepEqual(waitingAnchor(['u1', 'cmd'], get), { key: 'cmd', time: 70000 });
|
|
59
|
+
assert.deepEqual(waitingAnchor(['u1', 'cmd-open'], get), { key: 'u1', time: 1000 });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('ordinary waiting starts at user input', () => assert.deepEqual(waitingAnchor(['u1'], get), { key: 'u1', time: 1000 }));
|
|
63
|
+
test('steering resets a five-minute-old turn to the new input', () => assert.deepEqual(waitingAnchor(['u1', 's1'], get), { key: 's1', time: 301000 }));
|
|
64
|
+
test('repeated steering resets even while the waiting indicator stays mounted', () => assert.deepEqual(waitingAnchor(['u1', 's1', 's2'], get), { key: 's2', time: 305000 }));
|
|
65
|
+
|
|
66
|
+
// An assistant-step mid-turn is not a handover, so the wait keeps its earlier clock.
|
|
67
|
+
test('an assistant step does not reset waiting', () => assert.deepEqual(waitingAnchor(['u1', 's1', 'a1'], get), { key: 's1', time: 301000 }));
|
|
68
|
+
|
|
69
|
+
// Context injection IS a handover: the model owes the next move from that moment,
|
|
70
|
+
// and the clock must not keep counting the minutes spent before it.
|
|
71
|
+
test('context injection restarts the wait', () => assert.deepEqual(waitingAnchor(['u1', 's1', 'a1', 'ctx'], get), { key: 'ctx', time: 399000 }));
|
|
72
|
+
|
|
73
|
+
test('local echo resets immediately before steering admission', () => assert.deepEqual(waitingAnchor(['u1'], get, [{ requestId: 'new', time: 301000, placement: 'next-step' }]), { key: 'pending:new', time: 301000 }));
|
|
74
|
+
|
|
75
|
+
test('queued future turns and stale echoes cannot reset current waiting', () => {
|
|
76
|
+
assert.deepEqual(waitingAnchor(['u1', 's1'], get, [{ requestId: 'q', time: 400000, placement: 'queued' }]), { key: 's1', time: 301000 });
|
|
77
|
+
assert.deepEqual(waitingAnchor(['u1', 's1'], get, [{ requestId: 'old', time: 1000 }]), { key: 's1', time: 301000 });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('missing latest timestamp falls back to a new-input clock, not old history', () => {
|
|
81
|
+
assert.deepEqual(waitingAnchor(['u1', 'bad'], key => key === 'bad' ? { kind: 'steering', data: {} } : get(key)), { key: 'bad', time: null });
|
|
82
|
+
assert.deepEqual(waitingAnchor([], get), { key: 'unresolved', time: null });
|
|
83
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { test } from 'node:test';
|
|
3
|
+
import type { Nodes } from 'mdast';
|
|
4
|
+
import { WORD_MOTION, WordTimeline } from '../src/client/word-timeline.js';
|
|
5
|
+
import { IncrementalMarkdownParser } from '../src/client/markdown/incremental.js';
|
|
6
|
+
import { parseGfm, parseGfmWithMath } from '../src/client/markdown/parse.js';
|
|
7
|
+
|
|
8
|
+
function textLeaves(node: Nodes): { value: string; offset: number }[] {
|
|
9
|
+
if (node.type === 'text') return [{ value: node.value, offset: node.position!.start.offset! }];
|
|
10
|
+
return 'children' in node ? node.children.flatMap(textLeaves) : [];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
test('CJK resegmentation and unfinished Markdown cannot reveal later text before queued earlier text', () => {
|
|
14
|
+
for (const full of [
|
|
15
|
+
'前面的字还在排队,后面 **加粗重点** 怎么抢先出现。',
|
|
16
|
+
'前面 words waiting... [后面的链接](https://example.com) 接着文字。',
|
|
17
|
+
]) {
|
|
18
|
+
const timeline = new WordTimeline();
|
|
19
|
+
timeline.begin('', true, 0, 0);
|
|
20
|
+
for (let end = 1; end <= full.length; end++) {
|
|
21
|
+
const now = 100 + end * 8;
|
|
22
|
+
const source = full.slice(0, end);
|
|
23
|
+
timeline.begin(source, true, 0, now);
|
|
24
|
+
const words = textLeaves(parseGfm(source)).flatMap(leaf => timeline.words(leaf.value, leaf.offset)).filter(word => word.text.trim());
|
|
25
|
+
for (let index = 1; index < words.length; index++) {
|
|
26
|
+
const previous = words[index - 1]!;
|
|
27
|
+
const current = words[index]!;
|
|
28
|
+
assert.ok((previous.born ?? -Infinity) <= (current.born ?? -Infinity), `${source}: ${JSON.stringify(previous)} overtaken by ${JSON.stringify(current)}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('leaf render order cannot determine the order of word arrival', () => {
|
|
35
|
+
const timeline = new WordTimeline();
|
|
36
|
+
timeline.begin('', true, 0, 0);
|
|
37
|
+
timeline.begin('alpha beta', true, 0, 100);
|
|
38
|
+
const later = timeline.words('beta', 6)[0]!;
|
|
39
|
+
const earlier = timeline.words('alpha', 0)[0]!;
|
|
40
|
+
assert.ok(earlier.born! <= later.born!, 'A later-rendered earlier source leaf was scheduled last');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('historical and already-received prefixes never replay', () => {
|
|
44
|
+
const history = new WordTimeline();
|
|
45
|
+
history.begin('已经完成的正文。', false, 0, 100);
|
|
46
|
+
assert.equal(history.hasLiveText, false);
|
|
47
|
+
assert.ok(history.words('已经完成的正文。', 0).every(word => word.born === null));
|
|
48
|
+
const running = new WordTimeline();
|
|
49
|
+
running.begin('已经收到的部分。', true, 0, 100);
|
|
50
|
+
assert.ok(running.words('已经收到的部分。', 0).every(word => word.born === null));
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('only new word identities receive the stagger, and the queue stays bounded', () => {
|
|
54
|
+
const timeline = new WordTimeline();
|
|
55
|
+
timeline.begin('', true, 0, 0);
|
|
56
|
+
timeline.begin('one two three four five six seven', true, 0, 100);
|
|
57
|
+
const words = timeline.words('one two three four five six seven', 0).filter(word => word.text.trim());
|
|
58
|
+
const born = words.map(word => word.born!);
|
|
59
|
+
// Every word of one batch is scheduled from the same clock, in order, and the
|
|
60
|
+
// whole batch lands inside the batch window — that is what lets a fast model's
|
|
61
|
+
// words arrive together instead of dripping out at a fixed per-word cadence.
|
|
62
|
+
assert.deepEqual(born, [...born].sort((a, b) => a - b));
|
|
63
|
+
assert.ok(born[0]! >= 100);
|
|
64
|
+
assert.ok(born.at(-1)! <= 100 + WORD_MOTION.batchMs, `batch ran past its window: ${born.at(-1)}`);
|
|
65
|
+
// A single later word keeps the original typing rhythm rather than being
|
|
66
|
+
// silently absorbed into the previous batch.
|
|
67
|
+
timeline.begin('one two three four five six seven eight', true, 0, 900);
|
|
68
|
+
const first = timeline.words('one two three', 0).filter(word => word.text.trim()).map(word => word.born);
|
|
69
|
+
assert.deepEqual(first, born.slice(0, 3));
|
|
70
|
+
const added = timeline.words('eight', 40).filter(word => word.text.trim());
|
|
71
|
+
assert.ok(added[0]!.born! >= 900, 'a lone new word must not be scheduled in the past');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('stop, motion-off and authoritative replacement cancel old births', () => {
|
|
75
|
+
const timeline = new WordTimeline();
|
|
76
|
+
timeline.begin('', true, 0, 0);
|
|
77
|
+
timeline.begin('原始输出', true, 0, 100);
|
|
78
|
+
assert.ok(timeline.words('原始输出', 0).some(word => word.born !== null));
|
|
79
|
+
const previous = timeline.generation;
|
|
80
|
+
timeline.begin('修正的输出', true, 1, 200);
|
|
81
|
+
assert.ok(timeline.generation > previous);
|
|
82
|
+
assert.ok(timeline.words('修正的输出', 0).every(word => word.born === null));
|
|
83
|
+
timeline.begin('修正的输出', false, 1, 300);
|
|
84
|
+
timeline.begin('修正的输出', true, 1, 300);
|
|
85
|
+
assert.ok(timeline.words('修正的输出', 0).every(word => word.born === null));
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('word segmentation retains all whitespace, CJK, and combined emoji', () => {
|
|
89
|
+
const text = '中文 English\n👩🏽💻 e\u0301 🇨🇳';
|
|
90
|
+
const timeline = new WordTimeline();
|
|
91
|
+
timeline.begin('', true, 0, 0);
|
|
92
|
+
timeline.begin(text, true, 0, 100);
|
|
93
|
+
const words = timeline.words(text, 0);
|
|
94
|
+
assert.equal(words.map(word => word.text).join(''), text);
|
|
95
|
+
assert.ok(words.some(word => word.text === '👩🏽💻'));
|
|
96
|
+
assert.ok(words.some(word => word.text === 'e\u0301'));
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('animated word boxes preserve Chinese punctuation wrapping and source offsets', () => {
|
|
100
|
+
const text = '慢慢走着,看看「夜晚」与(远方)。 English, too!';
|
|
101
|
+
const timeline = new WordTimeline();
|
|
102
|
+
timeline.begin('', true, 0, 0);
|
|
103
|
+
timeline.begin(' '.repeat(20) + text, true, 0, 100);
|
|
104
|
+
const words = timeline.words(text, 20);
|
|
105
|
+
assert.equal(words.map(word => word.text).join(''), text);
|
|
106
|
+
for (const word of words) assert.equal(text.slice(word.key - 20, word.key - 20 + word.text.length), word.text);
|
|
107
|
+
assert.ok(words.some(word => word.text === '着,'));
|
|
108
|
+
assert.ok(words.some(word => word.text === '「夜晚」'));
|
|
109
|
+
assert.ok(words.some(word => word.text === '(远方)。'));
|
|
110
|
+
assert.ok(words.some(word => word.text === 'English,'));
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('a closing mark arriving later does not restart the preceding word', () => {
|
|
114
|
+
const timeline = new WordTimeline();
|
|
115
|
+
timeline.begin('', true, 0, 0);
|
|
116
|
+
timeline.begin('夜晚', true, 0, 100);
|
|
117
|
+
const first = timeline.words('夜晚', 0)[0];
|
|
118
|
+
timeline.begin('夜晚。', true, 0, 500);
|
|
119
|
+
const next = timeline.words('夜晚。', 0)[0];
|
|
120
|
+
assert.equal(next.key, first.key);
|
|
121
|
+
assert.equal(next.born, first.born);
|
|
122
|
+
assert.equal(next.text, '夜晚。');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('incremental source offsets survive freezing and match the final parse', () => {
|
|
126
|
+
const text = '第一段。\n\n第二段 **加粗**。\n\n第三段。\n\n第四段。';
|
|
127
|
+
const parser = new IncrementalMarkdownParser(parseGfm);
|
|
128
|
+
for (let i = 1; i <= text.length; i++) parser.update(text.slice(0, i));
|
|
129
|
+
const parsed = parser.update(text);
|
|
130
|
+
const blocks = [...parsed.frozen, ...parsed.tail];
|
|
131
|
+
assert.deepEqual(blocks.map(block => block.key), parseGfm(text).children.map(node => node.position!.start.offset));
|
|
132
|
+
for (const block of blocks) {
|
|
133
|
+
const base = block.key - block.node.position!.start.offset!;
|
|
134
|
+
assert.equal(text.slice(base + block.node.position!.start.offset!, base + block.node.position!.end.offset!), text.slice(block.key, block.key + block.node.position!.end.offset! - block.node.position!.start.offset!));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('the pinned DSH grammar retains CJK emphasis, GFM and math compatibility', () => {
|
|
139
|
+
const parsed = parseGfm('**加粗。**中文\n\n| 项目 | 数量 |\n| --- | --- |\n| A | 2 |\n\n- [x] 完成');
|
|
140
|
+
assert.ok(parsed.children.some(node => node.type === 'table'));
|
|
141
|
+
assert.ok(parsed.children.some(node => node.type === 'list'));
|
|
142
|
+
const first = parsed.children[0];
|
|
143
|
+
assert.ok(first?.type === 'paragraph' && first.children.some(node => node.type === 'strong'));
|
|
144
|
+
const math = parseGfmWithMath('公式 \\(x^2\\)');
|
|
145
|
+
assert.ok(math.children.some(node => node.type === 'paragraph' && node.children.some(child => child.type === 'inlineMath')));
|
|
146
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2024", "module": "NodeNext", "moduleResolution": "NodeNext",
|
|
4
|
+
"strict": true, "skipLibCheck": true, "declaration": true,
|
|
5
|
+
"declarationMap": true, "sourceMap": true,
|
|
6
|
+
"rootDir": "src", "outDir": "lib/types", "jsx": "react-jsx",
|
|
7
|
+
"lib": ["ES2024", "DOM", "DOM.Iterable"],
|
|
8
|
+
"types": ["node", "react"]
|
|
9
|
+
},
|
|
10
|
+
"include": ["src"]
|
|
11
|
+
}
|
package/tsdown.config.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
5
|
+
import type { TsdownPlugin, UserConfig } from 'tsdown';
|
|
6
|
+
|
|
7
|
+
const vendored = fileURLToPath(new URL('./tools/client-build.js', import.meta.url));
|
|
8
|
+
|
|
9
|
+
function resolveHarnessAdapter(): string {
|
|
10
|
+
const configured = process.env.DSHX_HARNESS?.trim();
|
|
11
|
+
if (configured) return join(resolve(configured), 'tools/dshx/src/client-build.js');
|
|
12
|
+
const configPath = join(homedir(), '.config/dshx/harness');
|
|
13
|
+
const recorded = existsSync(configPath) ? readFileSync(configPath, 'utf8').trim() : undefined;
|
|
14
|
+
if (!recorded) {
|
|
15
|
+
throw new Error('client rebuild requires a Harness root from DSHX_HARNESS or ~/.config/dshx/harness');
|
|
16
|
+
}
|
|
17
|
+
return join(resolve(recorded), 'tools/dshx/src/client-build.js');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const adapter = existsSync(vendored) ? vendored : resolveHarnessAdapter();
|
|
21
|
+
if (!existsSync(adapter)) throw new Error('externalClientBundle adapter is missing.');
|
|
22
|
+
const { externalClientBundle } = await import(pathToFileURL(adapter).href);
|
|
23
|
+
|
|
24
|
+
const bundle = externalClientBundle('dsh-better-display', ['src/dsh-better-display.ts'], {
|
|
25
|
+
clientEntry: 'src/client/index.tsx',
|
|
26
|
+
}) as UserConfig[];
|
|
27
|
+
|
|
28
|
+
const portableOutput: TsdownPlugin = {
|
|
29
|
+
name: 'dsh-better-display-portable-output',
|
|
30
|
+
generateBundle(_options, output) {
|
|
31
|
+
const client = output['client.js'];
|
|
32
|
+
if (client?.type !== 'chunk') this.error('client.js was not emitted');
|
|
33
|
+
client.code = client.code.replace(
|
|
34
|
+
/^([ \t]*\/\/#region \\0dshx-css-module:).*[\\/]([^/\\\r\n]+\.module\.css\.mjs)(\r?)$/gmu,
|
|
35
|
+
'$1$2$3',
|
|
36
|
+
);
|
|
37
|
+
if (/^.*\/\/#region \\0dshx-css-module:.*[\\/].*$/mu.test(client.code)) {
|
|
38
|
+
this.error('client.js contains a non-portable CSS module path');
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default bundle.map((config) => {
|
|
44
|
+
if (config.name !== 'dsh-better-display/client') return config;
|
|
45
|
+
const plugins = Array.isArray(config.plugins)
|
|
46
|
+
? config.plugins
|
|
47
|
+
: config.plugins === undefined
|
|
48
|
+
? []
|
|
49
|
+
: [config.plugins];
|
|
50
|
+
return { ...config, plugins: [...plugins, portableOutput] };
|
|
51
|
+
});
|