feat(desktop): embed BoosAPI Desktop client as a subproject

Vendor the rebranded BoosAPI Desktop client (Tauri + Vue) into metapi as
a monorepo subdirectory under desktop/. Source only — node_modules,
dist, Cargo target and the upstream .git history are excluded.

desktop/ keeps its own independent Cargo workspace (desktop/src-tauri
with crates/*); it is not part of the backend boosapi crate, so a
`cargo check` at the metapi root still compiles only the backend. Build
the desktop app from desktop/src-tauri per desktop/README.md.

Note: app icons are still placeholder assets pending the final BoosAPI
logo. The updater signing keys live outside the repo and are not
included.
This commit is contained in:
boos
2026-06-22 11:32:57 +08:00
parent d9c78ecc73
commit e0a5f57793
2305 changed files with 449355 additions and 0 deletions
@@ -0,0 +1,99 @@
/**
* @vitest-environment jsdom
*/
import { mount } from '@vue/test-utils'
import { describe, expect, it } from 'vitest'
import NodeRenderer from '../src/components/NodeRenderer'
import { flushAll } from './setup/flush-all'
describe('text node streaming consistency', () => {
it('settles a finished strong-node delta when following sibling text keeps streaming', async () => {
const wrapper = mount(NodeRenderer, {
props: {
content: '1. **记忆化递归(动态规划',
batchRendering: false,
viewportPriority: false,
deferNodesUntilVisible: false,
maxLiveNodes: 0,
},
})
try {
await wrapper.setProps({
content: '1. **记忆化递归(动态规划)*',
})
await flushAll()
let strongDelta = wrapper.find('.strong-node .text-node-stream-delta')
expect(strongDelta.exists()).toBe(true)
expect(strongDelta.text()).toBe(')')
await wrapper.setProps({
content: '1. **记忆化递归(动态规划)**:',
})
await flushAll()
strongDelta = wrapper.find('.strong-node .text-node-stream-delta')
expect(strongDelta.exists()).toBe(false)
expect(wrapper.get('.strong-node').text()).toBe('记忆化递归(动态规划)')
await wrapper.setProps({
content: '1. **记忆化递归(动态规划)**:使',
})
await flushAll()
expect(wrapper.find('.strong-node .text-node-stream-delta').exists()).toBe(false)
expect(wrapper.get('.list-item').text()).toContain('记忆化递归(动态规划):使')
}
finally {
wrapper.unmount()
}
})
it('keeps explanatory list text visible while an inline code span is still streaming', async () => {
const initial = `- **计算工具验证**
通过数学计算工具确认结果:`
const mid = `${initial}
\`363 ÷ 15,135 × 100 = 2.39841427...`
const final = `${mid}\``
const wrapper = mount(NodeRenderer, {
props: {
content: initial,
batchRendering: false,
deferNodesUntilVisible: false,
viewportPriority: false,
maxLiveNodes: 0,
},
})
try {
await flushAll()
await wrapper.setProps({
content: mid,
})
await flushAll()
const listItem = wrapper.get('.list-item')
const midText = listItem.text().replace(/\s*\n\s*/g, '')
expect(midText).toContain('计算工具验证通过数学计算工具确认结果:363 ÷ 15,135 × 100 = 2.39841427')
expect(wrapper.get('.strong-node').text()).toBe('计算工具验证')
expect(wrapper.get('code').text()).toContain('363 ÷ 15,135 × 100 = 2.39841427')
await wrapper.setProps({
content: final,
})
await flushAll()
const finalText = wrapper.get('.list-item').text().replace(/\s*\n\s*/g, '')
expect(finalText).toContain('计算工具验证通过数学计算工具确认结果:363 ÷ 15,135 × 100 = 2.39841427...')
expect(wrapper.get('.strong-node').text()).toBe('计算工具验证')
expect(wrapper.get('code').text()).toBe('363 ÷ 15,135 × 100 = 2.39841427...')
}
finally {
wrapper.unmount()
}
})
})