const assert = require('assert/strict'); const fs = require('fs'); const path = require('path'); const root = path.resolve(__dirname, '..'); const app = fs.readFileSync(path.join(root, 'mcp', 'src', 'dashboard', 'app.js'), 'utf8'); const styles = fs.readFileSync(path.join(root, 'mcp', 'src', 'dashboard', 'styles.css'), 'utf8'); const results = []; function check(name, fn) { fn(); results.push({ name, status: 'passed' }); } function functionSource(name, nextName) { const start = app.indexOf(`function ${name}`); const end = app.indexOf(`function ${nextName}`, start + 1); assert.ok(start >= 0 && end > start, `missing function source: ${name}`); return app.slice(start, end); } check('active Agent view uses a compact command bar and drawers', () => { const source = functionSource('renderAgentWorkspaceV2', 'responseMonitorRenderSignature'); assert.match(source, /agent-commandbar/); assert.match(source, /agent-drawer settings/); assert.match(source, /agent-drawer reminders/); assert.doesNotMatch(source, /agent-hero|agent-modebar/); }); check('skills workspace does not receive Agent-only drawers', () => { const source = functionSource('renderSkillsWorkspace', 'renderSkillsPage'); assert.doesNotMatch(source, /agent-drawer|renderAgentIntakeSettings|renderResponseMonitor/); }); check('workspace reserves viewport height and internal scrolling', () => { assert.match(styles, /\.agent-page\s*\{[\s\S]*?height:\s*calc\(100dvh\s*-\s*144px\)/); assert.match(styles, /\.agent-workspace\s*\{[\s\S]*?min-height:\s*0;[\s\S]*?overflow:\s*hidden;/); assert.match(styles, /\.agent-list-scroll\s*\{[^}]*overflow-y:\s*auto;/); assert.match(styles, /\.agent-message-stream\s*\{[\s\S]*?overflow-y:\s*auto;/); assert.match(styles, /\.agent-insight-panel\s*\{[\s\S]*?overflow-y:\s*auto;/); }); check('short desktop viewports keep the chat column bounded and usable', () => { assert.match(styles, /\.agent-chat-panel\s*>\s*\*\s*\{\s*min-width:\s*0;/); assert.match(styles, /@media\s*\(min-width:\s*761px\)\s*and\s*\(max-height:\s*760px\)/); assert.match(styles, /\.agent-composer-actions\s*\{\s*align-items:\s*stretch;\s*flex-direction:\s*column;/); assert.match(styles, /\.agent-review-actions\s*\{\s*flex-wrap:\s*nowrap;/); }); check('mobile layout is bounded and keeps controls reachable', () => { const mobile = styles.slice(styles.indexOf('@media (max-width: 760px)')); assert.match(mobile, /\.agent-workspace\s*\{[^}]*grid-template-columns:\s*1fr;/); assert.match(mobile, /\.agent-command-actions\s*\{[^}]*overflow-x:\s*auto;/); assert.match(mobile, /\.agent-drawer\s*\{[^}]*width:\s*100vw;/); assert.match(mobile, /\.agent-chat-panel\s*\{[^}]*min-height:\s*720px;[^}]*minmax\(160px,\s*1fr\)/); assert.match(styles, /\.page\.agent-page\s*\{\s*animation:\s*none;\s*transform:\s*none;/); }); check('conversation ordering has explicit invalid-time and stable-tie behavior', () => { assert.match(app, /Number\.NEGATIVE_INFINITY/); assert.match(app, /b\.timestamp\s*-\s*a\.timestamp\s*\|\|\s*a\.index\s*-\s*b\.index/); assert.match(app, /sortAgentItemsByLatest\(conversations\.filter/); }); check('activity timeline uses existing trace and audit without raw JSON rendering', () => { const source = functionSource('renderAgentActivityTimeline', 'scrollAgentMessagesToLatest'); assert.match(source, /Plan/); assert.match(source, /Act/); assert.match(source, /Observe/); assert.match(source, /Evaluate/); assert.doesNotMatch(source, /JSON\.stringify/); assert.doesNotMatch(app, /JSON\.stringify\(viewModel\.toolTrace\[0\]/); }); check('Agent theme is semantic and tenant-overridable', () => { for (const token of ['brand', 'page-bg', 'panel', 'ink', 'line', 'focus', 'success', 'warning', 'error']) { assert.match(styles, new RegExp(`--agent-${token}:`)); } assert.match(styles, /\.agent-page\[data-theme="xiaoniu"\]/); assert.match(app, /page\.dataset\.theme/); }); check('intake settings use the fixed API contract and explicit confirmation', () => { assert.match(app, /\/api\/agent\/intake-policy/); assert.match(app, /welcomeEnabled/); assert.match(app, /welcomeText/); assert.match(app, /welcomeSendMode/); assert.match(app, /ENABLE_AUTO_ENROLL_AUTOPILOT/); }); console.log(JSON.stringify({ status: 'ok', passed: results.length, results }, null, 2));