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,142 @@
// Lightweight benchmark for updateCode throttling and batching logic.
// This script mocks a minimal subset of Monaco editor API used by useMonaco
// and exercises updateCode at high frequency to compare CPU/time behavior.
import { performance } from 'node:perf_hooks'
// Load the compiled source if available, otherwise load TS source via ts-node is out of scope.
// We'll directly import the source file (ESM) assuming Node can load it; however the module
// depends on 'monaco-shim' and browser APIs. Instead, we'll directly benchmark the
// core flush/update logic by copying minimal parts into this script as a function.
// For simplicity, we simulate a minimal "editor wrapper" with similar logic to useMonaco
// focusing on updateCode/flushPendingUpdate/append buffering behavior.
function createMockEditor() {
let value = ''
return {
getModel() {
return {
getValue() { return value },
setValue(v) { value = v },
getLineCount() { return value.split('\n').length },
getLanguageId() { return 'javascript' },
getLineMaxColumn(line) { return (value.split('\n')[line - 1] || '').length + 1 },
}
},
getValue() { return value },
executeEdits(_, edits) {
for (const e of edits) {
// append-only benchmark: assume appends only
value += e.text
}
},
getOption() { return false },
}
}
// Minimal wrapper implementing only the paths used by the benchmark
function createWrapper(opts = {}) {
const appendBuffer = []
const appendBufferScheduled = false
let pendingUpdate = null
let lastKnownCode = ''
const throttleMs = opts.updateThrottleMs ?? 50
let lastFlushTime = 0
let throttleTimer = null
const editorView = createMockEditor()
function flushPendingUpdate() {
lastFlushTime = Date.now()
if (!pendingUpdate)
return
const model = editorView.getModel()
const { code } = pendingUpdate
pendingUpdate = null
const prev = lastKnownCode || model.getValue()
if (prev === code)
return
if (code.startsWith(prev)) {
const suffix = code.slice(prev.length)
if (suffix) {
appendBuffer.push(suffix)
}
lastKnownCode = code
return
}
// fallback full replace
model.setValue(code)
lastKnownCode = code
}
function flushAppendBuffer() {
if (appendBuffer.length === 0)
return
const text = appendBuffer.join('')
appendBuffer.length = 0
editorView.executeEdits('append', [{ range: null, text }])
}
function updateCode(code) {
pendingUpdate = { code }
// schedule via RAF-like (here immediate) then apply throttle
// emulate raf by setImmediate
setImmediate(() => {
if (!throttleMs) {
flushPendingUpdate(); return
}
const now = Date.now()
const since = now - lastFlushTime
if (since >= throttleMs) { flushPendingUpdate(); return }
if (throttleTimer != null)
return
const wait = throttleMs - since
throttleTimer = setTimeout(() => { throttleTimer = null; setImmediate(flushPendingUpdate) }, wait)
})
}
return { updateCode, flushAppendBuffer, editorView }
}
// Run benchmark: send N updates at freq Hz
async function run({ updates = 2000, freqHz = 200, throttleMs = 50 }) {
const wrapper = createWrapper({ updateThrottleMs: throttleMs })
const intervalMs = 1000 / freqHz
const sent = 0
const start = performance.now()
for (let i = 0; i < updates; i++) {
wrapper.updateCode(''.padEnd(i + 1, 'a'))
// busy wait to respect frequency
const t0 = performance.now()
while (performance.now() - t0 < intervalMs) { /* noop */ }
}
// allow pending timers to flush
await new Promise(r => setTimeout(r, throttleMs + 100))
const end = performance.now()
const finalValue = wrapper.editorView.getModel().getValue()
const elapsed = end - start
const avgMsPerUpdate = elapsed / updates
const out = {
updates,
freqHz,
throttleMs,
elapsedMs: Math.round(elapsed),
avgMsPerUpdate: avgMsPerUpdate.toFixed(3),
finalLength: finalValue.length,
}
console.log(JSON.stringify(out, null, 2))
}
if (process.argv.includes('--help')) {
console.log('Usage: node scripts/stream-benchmark.mjs [updates] [freqHz] [throttleMs]')
process.exit(0)
}
const updates = Number(process.argv[2] || 2000)
const freqHz = Number(process.argv[3] || 200)
const throttleMs = Number(process.argv[4] || 50)
run({ updates, freqHz, throttleMs }).catch((err) => { console.error(err); process.exit(1) })