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,477 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
function installRafMocks() {
vi.stubGlobal('requestAnimationFrame', (cb: any) => {
return setTimeout(() => cb(Date.now()), 0) as unknown as number
})
vi.stubGlobal('cancelAnimationFrame', (id: number) => {
clearTimeout(id as unknown as ReturnType<typeof setTimeout>)
})
}
async function loadUseMonaco() {
vi.resetModules()
vi.doMock('../src/utils/registerMonacoThemes', () => {
return {
clearHighlighterCache: () => {},
getOrCreateHighlighter: async () => null,
registerMonacoThemes: async () => null,
}
})
vi.doMock('../src/monaco-shim', () => {
let lastCreatedModel: any = null
let lastCreatedEditor: any = null
class Range {
constructor(
public startLineNumber: number,
public startColumn: number,
public endLineNumber: number,
public endColumn: number,
) {}
}
function createModel(initialValue: string, initialLanguage: string) {
let value = initialValue
let languageId = initialLanguage
let getValueCallCount = 0
const contentSizeListeners = new Set<() => void>()
const contentChangeListeners = new Set<() => void>()
function lines() {
return value.split('\n')
}
function getOffsetAt(lineNumber: number, column: number) {
const parts = lines()
let offset = 0
for (let i = 0; i < lineNumber - 1; i++)
offset += (parts[i] ?? '').length + 1
return offset + column - 1
}
function emitChange() {
contentChangeListeners.forEach(listener => listener())
contentSizeListeners.forEach(listener => listener())
}
return {
getValue() {
getValueCallCount += 1
return value
},
setValue(next: string) {
value = next
emitChange()
},
getLineCount() {
return lines().length
},
getLineMaxColumn(lineNumber: number) {
return (lines()[lineNumber - 1] ?? '').length + 1
},
getPositionAt(offset: number) {
const consumed = value.slice(0, offset).split('\n')
return {
lineNumber: consumed.length,
column: consumed[consumed.length - 1].length + 1,
}
},
getLanguageId() {
return languageId
},
setLanguageId(next: string) {
languageId = next
},
applyEdits(edits: Array<{ range: Range, text: string }>) {
for (const edit of edits) {
const start = getOffsetAt(
edit.range.startLineNumber,
edit.range.startColumn,
)
const end = getOffsetAt(
edit.range.endLineNumber,
edit.range.endColumn,
)
value = value.slice(0, start) + edit.text + value.slice(end)
}
emitChange()
},
onDidContentSizeChange(listener: () => void) {
contentSizeListeners.add(listener)
return {
dispose() {
contentSizeListeners.delete(listener)
},
}
},
onDidChangeContent(listener: () => void) {
contentChangeListeners.add(listener)
return {
dispose() {
contentChangeListeners.delete(listener)
},
}
},
__getGetValueCallCount() {
return getValueCallCount
},
}
}
const editor = {
EditorOption: {
lineHeight: 'lineHeight',
readOnly: 'readOnly',
},
ScrollType: {
Smooth: 'smooth',
},
create: vi.fn((_: any, options: any) => {
const model = createModel(options.value ?? '', options.language ?? 'plaintext')
lastCreatedModel = model
const scrollListeners = new Set<(e: any) => void>()
let scrollTop = 0
const domNode = {
addEventListener() {},
removeEventListener() {},
}
const editorApi = {
getModel() {
return model
},
getValue() {
return model.getValue()
},
getOption(option: string) {
if (option === editor.EditorOption.lineHeight)
return 20
if (option === editor.EditorOption.readOnly)
return options.readOnly ?? true
return undefined
},
getLayoutInfo() {
return { height: 240 }
},
getScrollTop() {
return scrollTop
},
setScrollTop: vi.fn((next: number) => {
scrollTop = next
}),
getScrollHeight() {
return model.getLineCount() * 20
},
getContentHeight() {
return model.getLineCount() * 20
},
onDidContentSizeChange(listener: () => void) {
return model.onDidContentSizeChange(listener)
},
onDidChangeModelContent(listener: () => void) {
return model.onDidChangeContent(listener)
},
onDidScrollChange(listener: (e: any) => void) {
scrollListeners.add(listener)
return {
dispose() {
scrollListeners.delete(listener)
},
}
},
executeEdits(_: string, edits: Array<{ range: Range, text: string }>) {
model.applyEdits(edits)
},
revealLine: vi.fn(),
revealLineInCenter: vi.fn(),
revealLineInCenterIfOutsideViewport: vi.fn(),
dispose() {},
getDomNode() {
return domNode as any
},
__setScrollTop(next: number) {
scrollTop = next
},
}
lastCreatedEditor = editorApi
return editorApi
}),
setTheme: vi.fn(),
setModelLanguage: vi.fn((model: any, language: string) => {
model.setLanguageId(language)
}),
}
const languages = {
getLanguages: () => [],
register: vi.fn(),
}
return {
default: { editor, languages, Range },
editor,
languages,
Range,
ScrollType: editor.ScrollType,
__getLastModel() {
return lastCreatedModel
},
__getLastEditor() {
return lastCreatedEditor
},
}
})
const base = await import('../src/index.base')
const monacoModule: any = await import('../src/monaco-shim')
return {
...base,
__getLastModel: monacoModule.__getLastModel,
__getLastEditor: monacoModule.__getLastEditor,
}
}
describe('EditorManager update throttling', () => {
beforeEach(() => {
vi.useFakeTimers()
vi.setSystemTime(new Date('2026-04-01T10:00:00.000Z'))
installRafMocks()
})
afterEach(() => {
vi.unstubAllGlobals()
vi.useRealTimers()
vi.restoreAllMocks()
})
it('respects updateThrottleMs after createEditor uses EditorManager', async () => {
const { useMonaco } = await loadUseMonaco()
const monaco = useMonaco({
themes: ['vitesse-dark', 'vitesse-light'],
languages: ['javascript'],
readOnly: true,
updateThrottleMs: 50,
})
const container = { style: {}, innerHTML: '' } as any
await monaco.createEditor(container, '', 'javascript')
await vi.runAllTimersAsync()
monaco.updateCode('a', 'javascript')
await vi.runAllTimersAsync()
expect(monaco.getCode()).toBe('a')
monaco.updateCode('ab', 'javascript')
await vi.advanceTimersByTimeAsync(20)
expect(monaco.getCode()).toBe('a')
await vi.advanceTimersByTimeAsync(40)
expect(monaco.getCode()).toBe('ab')
})
it('propagates runtime throttle changes into the active EditorManager', async () => {
const { useMonaco } = await loadUseMonaco()
const monaco = useMonaco({
themes: ['vitesse-dark', 'vitesse-light'],
languages: ['javascript'],
readOnly: true,
updateThrottleMs: 100,
})
const container = { style: {}, innerHTML: '' } as any
await monaco.createEditor(container, '', 'javascript')
await vi.runAllTimersAsync()
monaco.updateCode('a', 'javascript')
await vi.runAllTimersAsync()
expect(monaco.getCode()).toBe('a')
monaco.updateCode('ab', 'javascript')
await vi.advanceTimersByTimeAsync(20)
expect(monaco.getCode()).toBe('a')
monaco.setUpdateThrottleMs(0)
expect(monaco.getUpdateThrottleMs()).toBe(0)
await vi.runAllTimersAsync()
expect(monaco.getCode()).toBe('ab')
})
it('avoids rereading the whole model during programmatic streaming appends', async () => {
const { useMonaco, __getLastModel } = await loadUseMonaco()
const monaco = useMonaco({
themes: ['vitesse-dark', 'vitesse-light'],
languages: ['javascript'],
readOnly: true,
updateThrottleMs: 0,
})
const container = { style: {}, innerHTML: '' } as any
await monaco.createEditor(container, '', 'javascript')
await vi.runAllTimersAsync()
const model = __getLastModel()
const beforeFirstUpdate = model.__getGetValueCallCount()
monaco.updateCode('a', 'javascript')
await vi.runAllTimersAsync()
expect(model.__getGetValueCallCount() - beforeFirstUpdate).toBe(0)
const beforeSecondUpdate = model.__getGetValueCallCount()
monaco.updateCode('ab', 'javascript')
await vi.runAllTimersAsync()
expect(model.__getGetValueCallCount() - beforeSecondUpdate).toBe(0)
expect(monaco.getCode()).toBe('ab')
})
it('keeps frame-by-frame streaming appends byte-for-byte aligned', async () => {
const { useMonaco } = await loadUseMonaco()
const monaco = useMonaco({
themes: ['vitesse-dark', 'vitesse-light'],
languages: ['json'],
readOnly: true,
updateThrottleMs: 0,
})
const code = `{
".c": "C",
".cpp": "C++",
".cc": "C++"
}`
const container = { style: {}, innerHTML: '' } as any
await monaco.createEditor(container, '', 'json')
await vi.runAllTimersAsync()
for (let i = 1; i <= code.length; i++) {
monaco.updateCode(code.slice(0, i), 'json')
await vi.runAllTimersAsync()
}
expect(monaco.getCode()).toBe(code)
})
it('does not reveal while streamed content is still shorter than the editor height', async () => {
const { useMonaco, __getLastEditor } = await loadUseMonaco()
const monaco = useMonaco({
themes: ['vitesse-dark', 'vitesse-light'],
languages: ['json'],
readOnly: true,
updateThrottleMs: 0,
})
const code = `{
".c": "C",
".cpp": "C++",
".cc": "C++"
}`
const container = { style: {}, innerHTML: '' } as any
await monaco.createEditor(container, '', 'json')
await vi.runAllTimersAsync()
const editor = __getLastEditor()
for (const line of code.split('\n')) {
const next = monaco.getCode()
? `${monaco.getCode()}\n${line}`
: line
monaco.updateCode(next, 'json')
await vi.runAllTimersAsync()
}
expect(editor.revealLine).not.toHaveBeenCalled()
expect(editor.revealLineInCenter).not.toHaveBeenCalled()
expect(editor.revealLineInCenterIfOutsideViewport).not.toHaveBeenCalled()
})
it('uses Monaco content height without forcing a minimum host height', async () => {
const { useMonaco } = await loadUseMonaco()
const monaco = useMonaco({
themes: ['vitesse-dark', 'vitesse-light'],
languages: ['json'],
readOnly: true,
updateThrottleMs: 0,
})
const container = { style: {}, innerHTML: '' } as any
await monaco.createEditor(container, '', 'json')
await vi.runAllTimersAsync()
expect(container.style.minHeight).toBeUndefined()
expect(container.style.height).toBe('20px')
monaco.updateCode('{\n "a": 1\n}', 'json')
await vi.runAllTimersAsync()
expect(container.style.height).toBe('60px')
})
it('keeps scroll pinned to top before streamed content reaches max height', async () => {
const { useMonaco, __getLastEditor } = await loadUseMonaco()
const monaco = useMonaco({
themes: ['vitesse-dark', 'vitesse-light'],
languages: ['json'],
readOnly: true,
updateThrottleMs: 0,
})
const container = { style: {}, innerHTML: '' } as any
await monaco.createEditor(container, '', 'json')
await vi.runAllTimersAsync()
const editor = __getLastEditor()
editor.__setScrollTop(8)
monaco.updateCode('{\n "a": 1\n}', 'json')
await vi.runAllTimersAsync()
expect(container.style.height).toBe('60px')
expect(container.style.overflow).toBe('hidden')
expect(editor.setScrollTop).toHaveBeenCalledWith(0)
})
it('does not fight an external host height within the height hysteresis', async () => {
const { useMonaco } = await loadUseMonaco()
const monaco = useMonaco({
themes: ['vitesse-dark', 'vitesse-light'],
languages: ['json'],
readOnly: true,
updateThrottleMs: 0,
})
const container = {
style: {},
innerHTML: '',
getBoundingClientRect: () => ({ height: 41 }),
} as any
await monaco.createEditor(container, '', 'json')
await vi.runAllTimersAsync()
monaco.updateCode('{\n}', 'json')
container.style.height = '41px'
await vi.runAllTimersAsync()
expect(container.style.height).toBe('41px')
})
it('still syncs externally edited content before the next streamed update', async () => {
const { useMonaco, __getLastModel } = await loadUseMonaco()
const monaco = useMonaco({
themes: ['vitesse-dark', 'vitesse-light'],
languages: ['javascript'],
readOnly: false,
updateThrottleMs: 0,
})
const container = { style: {}, innerHTML: '' } as any
await monaco.createEditor(container, '', 'javascript')
await vi.runAllTimersAsync()
const model = __getLastModel()
model.setValue('hello')
await vi.runAllTimersAsync()
monaco.updateCode('hello world', 'javascript')
await vi.runAllTimersAsync()
expect(monaco.getCode()).toBe('hello world')
})
})