import fs from 'node:fs'; import path from 'node:path'; const root = process.cwd(); const read = (file) => fs.readFileSync(path.join(root, file), 'utf8'); const exists = (file) => fs.existsSync(path.join(root, file)); const checks = []; const pass = (name, ok, detail = '') => checks.push({ name, ok, detail }); const blueprint = read('src/features/blueprint/pages/BlueprintEngine.tsx'); pass('Blueprint ListChecks import is present', blueprint.includes('ListChecks')); pass('Blueprint route should not trigger missing ListChecks ReferenceError', /import\s*\{[^}]*ListChecks[^}]*\}\s*from\s*["']lucide-react["']/.test(blueprint)); const appLayout = read('src/components/layout/AppLayout.tsx'); pass('Main shell has compact responsive header offset', appLayout.includes('pt-6') && appLayout.includes('lg:pt-10')); pass('Main shell prevents page-level horizontal overflow', appLayout.includes('overflow-x-hidden') && appLayout.includes('min-w-0')); const pageHeader = read('src/components/ui/PageHeader.tsx'); pass('PageHeader wraps actions on mobile', pageHeader.includes('flex-wrap') && pageHeader.includes('md:w-auto') && pageHeader.includes('break-words')); const routes = read('src/app/routes.tsx'); pass('Route scroll reset is enabled', routes.includes('ScrollToTop') && routes.includes('data-projectbrain-main')); pass('Route loading fallback explains project loading', routes.includes('Project laden…') && routes.includes('aria-label="ProjectBrain-pagina laden"')); const sidebar = read('src/components/layout/Sidebar.tsx'); pass('Sidebar descriptions are hidden on smaller desktop widths', sidebar.includes('xl:block')); const prompt = read('src/features/prompts/pages/PromptGenerator.tsx'); pass('Prompt Builder starts with task input', prompt.includes('What should AI/Codex do?')); pass('Prompt Builder uses multiline output area', prompt.includes('min-h-40 font-mono text-xs')); const knowledge = read('src/features/knowledge/pages/KnowledgeVault.tsx'); pass('Knowledge Vault avoids preview wording in primary actions', !knowledge.includes('preview')); const audit = read('src/features/audit/pages/ProjectAuditCenter.tsx'); pass('Audit Center treats old priority labels as historical', audit.includes('was {finding.severity}')); const header = read('src/components/layout/Header.tsx'); pass('Header New Project menu navigates to unified Start', header.includes('navigate("/start")')); pass('Header exposes runtime status popover', header.includes('Runtimestatus') && header.includes('useBackendSystemReadinessData')); pass('Header runtime status links to Runtime page', header.includes('Runtimepagina') && header.includes('/runtime')); pass('Header labels unavailable backend as local mode', header.includes('Lokale modus') && !header.includes('degraded: "Degraded"')); const commandMenu = read('src/components/layout/CommandMenu.tsx'); const commandMenuSearch = read('src/components/layout/commandMenuSearch.ts'); pass('Command palette shortcut listens in capture phase', commandMenu.includes('window.addEventListener("keydown", down, { capture: true })')); pass( 'Command palette prioritizes best-matching navigation while searching', commandMenu.includes('rankNavigationItems') && commandMenu.includes('') && commandMenu.includes('{navigationSearchGroup}') && commandMenuSearch.includes('getNavigationScore') && commandMenuSearch.includes('label.startsWith(normalizedQuery)'), ); pass('Command palette teaches route-oriented examples', commandMenu.includes('Zoek op repo, review, contextpack, historiek, bron of skill')); const commandUi = read('src/components/ui/command.tsx'); pass('Command dialog has screenreader title and description', commandUi.includes('DialogTitle className="sr-only"') && commandUi.includes('DialogDescription className="sr-only"')); const projectsPage = read('src/features/projects/pages/Projects.tsx'); pass( 'Projects mobile hero actions are full-width and stacked', projectsPage.includes('flex w-full flex-col gap-2 sm:w-auto sm:flex-row') && projectsPage.includes('className="w-full sm:w-auto"') && projectsPage.includes('{refreshing ? "Bijwerken…" : "Bijwerken"}') && projectsPage.includes('disabled={refreshing}'), ); pass('Projects progressively reveal large portfolios', projectsPage.includes('PROJECT_PAGE_SIZE') && projectsPage.includes('remainingProjectCount')); const runtimeReadiness = read('src/features/projects/pages/RuntimeReadiness.tsx'); pass( 'System status prevents page-level mobile overflow', runtimeReadiness.includes('min-w-0 space-y-6 overflow-x-hidden') && runtimeReadiness.includes('grid min-w-0 gap-3 sm:grid-cols-2 xl:grid-cols-3'), ); pass( 'System status capability cards wrap long runtime evidence safely', runtimeReadiness.includes('className="min-w-0 overflow-hidden"') && runtimeReadiness.includes('[overflow-wrap:anywhere]') && runtimeReadiness.includes('max-w-[45%] shrink-0 whitespace-normal text-center'), ); pass( 'System status release/build proof avoids mobile overflow', runtimeReadiness.includes('min-w-0 overflow-hidden scroll-mt-24 border-primary/25') && runtimeReadiness.includes('break-all font-semibold') && runtimeReadiness.includes('break-all font-mono text-xs'), ); const review = read('src/features/operations/pages/ReviewQueue.tsx'); pass('Review Queue bulk accept requires confirmation', review.includes('Confirm bulk accept')); pass('V84 documentation exists', exists('docs/STABILIZATION_PASS_V84_VISUAL_RC_POLISH_2.md')); pass('V85 documentation exists', exists('docs/STABILIZATION_PASS_V85_RUNTIME_STATUS_POLISH.md')); pass('V87 correction documentation exists', exists('docs/STABILIZATION_PASS_V87_REAL_PRODUCT_IMPROVEMENT_CORRECTION.md')); pass('Audit Center no longer exposes V86 catalog tab', !audit.includes('V86 1000') && !audit.includes('megaImprovementItems')); const relationshipGraph = read('src/features/relationships/pages/RelationshipGraph.tsx'); pass('Relationship Graph shows real suggestions instead of only empty state', relationshipGraph.includes('buildRelationshipSuggestions') && relationshipGraph.includes('suggestions.map')); const repoBuilder = read('src/features/repo-builder/pages/RepoBuilder.tsx'); pass('Repo Builder mobile lifecycle has swipe affordance', repoBuilder.includes('Veeg zijwaarts om de latere fasen te bekijken.') && repoBuilder.includes('overflow-x-auto')); pass( 'Repo Builder archive actions are confirmation-gated', repoBuilder.includes('ConfirmActionButton') && repoBuilder.includes('title="Geselecteerde repository-intake archiveren?"') && repoBuilder.includes('onConfirm={archiveSelectedRepositoryIntake}') && repoBuilder.includes('title="Deze repositorybasis archiveren?"') && repoBuilder.includes('onConfirm={archiveBlueprint}') && repoBuilder.includes('title="Deze uitbreidingsbrief archiveren?"') && repoBuilder.includes('onConfirm={archiveExpansion}') && repoBuilder.includes('title="Dit deliverypakket archiveren?"') && repoBuilder.includes('onConfirm={archiveDeliveryPackage}') && repoBuilder.includes('title="Dit repositorywerkplan archiveren?"') && repoBuilder.includes('onConfirm={archiveWorkPlan}') && repoBuilder.includes('title="Deze repositorystatus archiveren?"') && repoBuilder.includes('onConfirm={archiveStateSnapshot}'), ); const sessionPacks = read('src/features/session-packs/pages/SessionPacks.tsx'); pass('Session Packs exposes top next action for empty state', sessionPacks.includes('Create a Codex handoff before packaging a session.')); pass('Session Packs archive action is confirmation-gated', sessionPacks.includes('Archive this session pack?') && sessionPacks.includes('AlertDialogAction onClick={archiveSelected}')); const browserBridge = read('src/features/browser-bridge/pages/BrowserBridge.tsx'); pass('Browser Bridge exposes top session-pack-required action', browserBridge.includes('Session pack required') && browserBridge.includes('Open Session Packs')); pass('Browser Bridge archive action is confirmation-gated', browserBridge.includes('Archive this browser bridge session?') && browserBridge.includes('AlertDialogAction onClick={archiveSession}')); const codexHandoffs = read('src/features/handoffs/pages/CodexHandoffs.tsx'); pass('Codex handoff archive actions are confirmation-gated', codexHandoffs.includes('Archive this Codex handoff?') && codexHandoffs.includes('Archive this impact review?')); const runtimeStateManager = read('src/features/operations/pages/RuntimeStateManager.tsx'); pass('Runtime reset uses accessible AlertDialog instead of native confirm', runtimeStateManager.includes('AlertDialogTitle') && !runtimeStateManager.includes('window.confirm')); pass('Compact runtime state manager hides import-only control', runtimeStateManager.includes('!compact ? (') && runtimeStateManager.includes('Import JSON')); const confirmActionButton = read('src/shared/components/ConfirmActionButton.tsx'); pass('Shared confirm action button uses accessible AlertDialog', confirmActionButton.includes('AlertDialogTitle') && confirmActionButton.includes('confirmLabel')); const executionFeedback = read('src/features/execution-feedback/pages/ExecutionFeedback.tsx'); pass('Execution Feedback archive action is confirmation-gated', executionFeedback.includes('Archive this execution feedback?')); const sourceLibrary = read('src/features/source-library/pages/SourceLibrary.tsx'); pass( 'Source Library archive action is confirmation-gated', sourceLibrary.includes('ConfirmActionButton') && sourceLibrary.includes('title="Deze bron archiveren?"') && sourceLibrary.includes('confirmLabel="Bron archiveren"') && sourceLibrary.includes('onConfirm={() => archive(source.id)}'), ); const projectHistory = read('src/features/history/pages/ProjectHistory.tsx'); pass('Project History remove action is confirmation-gated', projectHistory.includes('Dit geschiedenisitem verbergen?')); const runtimeGateEvidence = read('src/features/operations/pages/RuntimeGateEvidence.tsx'); pass('Runtime evidence reset is confirmation-gated', runtimeGateEvidence.includes('Reset P2 evidence history?')); const runtimeGateChecklist = read('src/features/operations/pages/RuntimeGateChecklist.tsx'); pass('Runtime checklist reset is confirmation-gated', runtimeGateChecklist.includes('Reset P2 checklist?')); const runtimeTodoBoard = read('src/features/operations/pages/RuntimeTodoBoard.tsx'); pass('Runtime todo removal is confirmation-gated', runtimeTodoBoard.includes('Remove this runtime todo?')); const packageJson = read('package.json'); pass('Mega audit command is removed from package scripts', !packageJson.includes('audit:mega')); const failed = checks.filter((check) => !check.ok); if (failed.length > 0) { console.error('ProjectBrain visual polish audit: FAIL'); for (const check of failed) console.error(`- ${check.name}${check.detail ? `: ${check.detail}` : ''}`); process.exit(1); } console.log('ProjectBrain visual polish audit: PASS'); for (const check of checks) console.log(`- ${check.name}`);