fix(web): close final workbench interaction gaps
This commit is contained in:
@@ -18,6 +18,7 @@ const requiredFiles = [
|
||||
'src/pages/projects/project-sidebar.tsx',
|
||||
'src/pages/projects/project-topbar.tsx',
|
||||
'src/pages/projects/project-statusbar.tsx',
|
||||
'src/pages/projects/workbench-item-preview.tsx',
|
||||
'src/pages/projects/project-types.ts',
|
||||
'src/api/client.ts',
|
||||
'src/api/projects.ts',
|
||||
@@ -26,6 +27,7 @@ const requiredFiles = [
|
||||
'src/api/inbox.ts',
|
||||
'src/api/ai.ts',
|
||||
'scripts/api-client.test.mjs',
|
||||
'scripts/workspace-refresh-gate.test.mjs',
|
||||
]
|
||||
|
||||
const failures = requiredFiles.filter((file) => !existsSync(file)).map((file) => `missing ${file}`)
|
||||
@@ -57,6 +59,9 @@ if (existsSync('src/app/App.tsx')) {
|
||||
failures.push(`src/app/App.tsx still contains ${forbidden}`)
|
||||
}
|
||||
}
|
||||
for (const required of ['createWorkspaceRefreshGate', 'captureProjectInbox', 'refreshProjectWorkspace']) {
|
||||
if (!appSource.includes(required)) failures.push(`src/app/App.tsx must include ${required}`)
|
||||
}
|
||||
}
|
||||
|
||||
const apiFiles = existsSync('src/api')
|
||||
@@ -92,6 +97,8 @@ for (const match of apiSource.matchAll(/export type (\w+DTO)\s*=\s*\{([\s\S]*?)\
|
||||
if (/^\s*(?:id|\w+Id)\s*:\s*number\b/im.test(body)) failures.push(`${name} identities must be strings`)
|
||||
}
|
||||
if (/\|\s*undefined/.test(projectsSource)) failures.push('project DTOs must not use undefined fields')
|
||||
if (/\bfilePath\s*:/.test(projectsSource)) failures.push('public source DTO must not expose filePath')
|
||||
if (!/\bstorageKey\s*:\s*string/.test(projectsSource)) failures.push('public source DTO must expose an opaque storageKey')
|
||||
|
||||
const searchSource = existsSync('src/api/search.ts') ? readFileSync('src/api/search.ts', 'utf8') : ''
|
||||
if (searchSource && !searchSource.includes("'/api/v1/search'")) failures.push('search API must use /api/v1/search')
|
||||
@@ -112,6 +119,12 @@ if (!inboxSource.includes('body: { suggestionIds }')) failures.push('inbox confi
|
||||
|
||||
const inboxPageSource = existsSync('src/pages/projects/project-inbox.tsx') ? readFileSync('src/pages/projects/project-inbox.tsx', 'utf8') : ''
|
||||
if (inboxPageSource && !inboxPageSource.includes('确认创建')) failures.push('project inbox must expose the single confirmation action')
|
||||
for (const required of ['收集到 Inbox', 'captureDraft', 'onCapture']) {
|
||||
if (!inboxPageSource.includes(required)) failures.push(`project inbox capture flow must include ${required}`)
|
||||
}
|
||||
|
||||
const channelPageSource = existsSync('src/pages/projects/project-channel-page.tsx') ? readFileSync('src/pages/projects/project-channel-page.tsx', 'utf8') : ''
|
||||
if (!channelPageSource.includes("activeChannel.startsWith('custom:')")) failures.push('custom_link channels must render an explicit unavailable state')
|
||||
|
||||
const aiApiSource = existsSync('src/api/ai.ts') ? readFileSync('src/api/ai.ts', 'utf8') : ''
|
||||
for (const required of ['/api/v1/projects/', '/ai-sessions', 'listAISessions', 'createAISession']) {
|
||||
@@ -151,6 +164,23 @@ const unsupportedControls = [
|
||||
file: 'src/pages/projects/project-topbar.tsx',
|
||||
forbidden: ['停靠左边', '停靠右边', 'DockIcon', 'isDesktopRuntime'],
|
||||
},
|
||||
{
|
||||
file: 'src/pages/projects/project-cron.tsx',
|
||||
forbidden: ['<Switch', '最近一次自动任务', '资料索引刷新成功'],
|
||||
},
|
||||
{
|
||||
file: 'src/pages/projects/project-overview.tsx',
|
||||
forbidden: ['/share/projects/', 'projectShareURL', '查看全部'],
|
||||
},
|
||||
{
|
||||
file: 'src/pages/projects/project-notes.tsx',
|
||||
forbidden: ['搜索资料', '按更新时间排序'],
|
||||
},
|
||||
{
|
||||
file: 'src/pages/login.tsx',
|
||||
required: 'localStorage',
|
||||
forbidden: ['无法连接?', '隐私政策', '服务协议', 'check-dot'],
|
||||
},
|
||||
]
|
||||
for (const check of unsupportedControls) {
|
||||
const source = existsSync(check.file) ? readFileSync(check.file, 'utf8') : ''
|
||||
@@ -179,6 +209,9 @@ if (!appStyles.includes('.brand-icon {')) failures.push('brand-icon must have an
|
||||
if (!appStyles.includes('.brand-lockup.large .brand-icon')) failures.push('login brand icon must have an explicit size')
|
||||
if (!appStyles.includes('.rail-brand {')) failures.push('rail brand button must have explicit layout')
|
||||
if (appStyles.includes('.brand-symbol')) failures.push('legacy brand-symbol styles must be removed')
|
||||
if (!appStyles.includes('@media (max-width: 1179px)')) failures.push('channel sidebar must collapse below 1180px')
|
||||
if (!appStyles.includes('.project-stack-scroll')) failures.push('long project rails need a dedicated scroll region')
|
||||
if (!appStyles.includes('.mobile-search-button')) failures.push('mobile workbench must retain a global search entry')
|
||||
for (const file of ['src/pages/login.tsx', 'src/pages/projects/project-topbar.tsx', 'src/pages/projects/project-rail.tsx']) {
|
||||
const source = readFileSync(file, 'utf8')
|
||||
if (!source.includes('/senlinai-icon.svg')) failures.push(`${file} must use the brand icon`)
|
||||
|
||||
Reference in New Issue
Block a user