fix(ui): make chapter bridge manual-only and add release gate

Remove auto chapter-bridge prompt on book/chapter open so users trigger it from the cockpit. Fix AiPanel duplicate import that broke dev builds, and add release smoke tests plus test:release/test:ci scripts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-09 19:09:18 +08:00
parent 8e5045b882
commit 958bb18539
9 changed files with 146 additions and 14 deletions
+18
View File
@@ -0,0 +1,18 @@
import { existsSync } from 'fs'
import { join } from 'path'
import { describe, it, expect } from 'vitest'
const ROOT = join(import.meta.dirname, '../..')
const REQUIRED_ARTIFACTS = [
'out/main/index.js',
'out/preload/index.js',
'out/renderer/index.html'
]
describe('release gate: build artifacts', () => {
it('UT-RELEASE-02: production build outputs exist', () => {
const missing = REQUIRED_ARTIFACTS.filter((rel) => !existsSync(join(ROOT, rel)))
expect(missing, `Run npm run build first. Missing: ${missing.join(', ')}`).toEqual([])
})
})
+58
View File
@@ -0,0 +1,58 @@
import { readFileSync, readdirSync, statSync } from 'fs'
import { join } from 'path'
import { describe, it, expect } from 'vitest'
const ROOT = join(import.meta.dirname, '../..')
function collectSourceFiles(dir: string, acc: string[] = []): string[] {
for (const name of readdirSync(dir)) {
const full = join(dir, name)
const st = statSync(full)
if (st.isDirectory()) {
if (name === 'node_modules' || name === 'out') continue
collectSourceFiles(full, acc)
} else if (/\.(ts|tsx)$/.test(name)) {
acc.push(full)
}
}
return acc
}
function extractNamedImports(content: string): { from: string; names: string[] }[] {
const results: { from: string; names: string[] }[] = []
const re = /import\s+(?:type\s+)?\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/g
let m: RegExpExecArray | null
while ((m = re.exec(content)) !== null) {
const names = m[1]
.split(',')
.map((part) => part.trim())
.filter(Boolean)
.map((part) => part.split(/\s+as\s+/)[0]!.trim())
results.push({ from: m[2]!, names })
}
return results
}
describe('release gate: import hygiene', () => {
it('UT-RELEASE-01: no duplicate named imports from the same module', () => {
const files = collectSourceFiles(join(ROOT, 'src'))
const offenders: string[] = []
for (const file of files) {
const blocks = extractNamedImports(readFileSync(file, 'utf8'))
const seen = new Map<string, Set<string>>()
for (const { from, names } of blocks) {
if (!seen.has(from)) seen.set(from, new Set())
const bucket = seen.get(from)!
for (const name of names) {
if (bucket.has(name)) {
offenders.push(`${file}: duplicate import { ${name} } from '${from}'`)
}
bucket.add(name)
}
}
}
expect(offenders, offenders.join('\n')).toEqual([])
})
})