AI web starter: bands, review split pane, approve gate, server stub (checkpoint)

React + Vite + Tailwind starter with the no-AI shell, band legend, review
split pane with drafts and verify, and a server stub that refuses approval
while flagged fields are unverified. Band self-check and AI page check.
Both page checks share one Chromium via tests/conftest.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
VasanthandClaude Opus 5.5 committed 2026-10-16 15:06:44 +05:30
1 parent 3eb3fdf00e
commit d4d3f83fb1
24 files changed
+3429 -73

No files matched your search

+105
View File
@@ -0,0 +1,105 @@
import { useState } from 'react'
import { act, clearRun, toggleDark, useStore } from './store'
import { Overview } from './components/Overview'
import { RunPanel } from './components/RunPanel'
import { Report } from './components/Report'
import { Review } from './components/Review'
const TABS = {
dashboard: ['Dashboard', 'Overview of every batch', <><rect x="3" y="3" width="7" height="7" /><rect x="14" y="3" width="7" height="7" /><rect x="3" y="14" width="7" height="7" /><rect x="14" y="14" width="7" height="7" /></>],
run: ['Run', 'Live progress and log', <polygon points="6 4 20 12 6 20 6 4" />],
review: ['Review', 'Check extracted fields against the document', <><path d="M4 4h10l6 6v10H4z" /><path d="m8 14 3 3 5-6" /></>],
report: ['Report', 'Result of every processed item', <><path d="M4 4h16v16H4z" /><path d="M8 9h8M8 13h8M8 17h5" /></>],
// OPTIONAL: admin/roles tab goes here
} as const
type Tab = keyof typeof TABS
const DOT = { ready: 'bg-ok', done: 'bg-ok', running: 'bg-warn animate-pulse', error: 'bg-bad' }
export function App() {
const { status, user, dark } = useStore()
const [tab, setTab] = useState<Tab>('dashboard')
const [unsaved, setUnsaved] = useState(0) // review edits not saved yet
const show = (t: Tab) => {
if (t === tab) return
if (unsaved && !window.confirm(`${unsaved} unsaved change(s) on this document. Discard them and leave?`)) return
setUnsaved(0)
setTab(t)
if (t === 'dashboard') act('load_dashboard')
if (t === 'report') act('load_latest_report')
}
const run = () => {
show('run')
clearRun()
act('start_automation')
}
return (
<div className="flex h-screen">
<aside className="flex w-[230px] min-w-[230px] flex-col border-r border-line bg-glass max-[820px]:w-[66px] max-[820px]:min-w-[66px]" style={{ backdropFilter: 'var(--blur)' }}>
<div className="flex items-center gap-3 border-b border-line px-5 py-5">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl border border-line bg-glass-strong">
{LOGO_SRC && <img src={LOGO_SRC} alt="" className="h-7 w-7 object-contain" onError={(e) => e.currentTarget.remove()} />}
</div>
<div className="max-[820px]:hidden">
<div className="font-head text-[15px] font-bold">{APP_NAME}</div>
<div className="mt-0.5 text-[10px] font-semibold uppercase tracking-wide text-muted">{APP_SUBTITLE}</div>
</div>
</div>
<nav className="flex-1 px-3 py-4">
{(Object.keys(TABS) as Tab[]).map((t) => (
<button
key={t}
title={TABS[t][0]}
onClick={() => show(t)}
aria-current={tab === t ? 'page' : undefined}
className={`mb-1 flex w-full cursor-pointer items-center gap-3 rounded-[10px] px-3 py-2.5 text-left text-[13.5px] font-semibold ${tab === t ? 'text-white shadow-md' : 'text-muted hover:bg-glass-strong hover:text-ink'}`}
style={tab === t ? { background: 'var(--nav-active-bg)' } : undefined}
>
<svg viewBox="0 0 24 24" className="h-[18px] w-[18px] shrink-0 fill-none stroke-current stroke-2">{TABS[t][2]}</svg>
<span className="max-[820px]:hidden">{TABS[t][0]}</span>
</button>
))}
</nav>
<div className="flex items-center gap-2.5 border-t border-line p-3.5">
<div className="flex h-[34px] w-[34px] shrink-0 items-center justify-center rounded-full text-xs font-bold text-white" style={{ background: 'var(--nav-active-bg)' }}>
{(user.username || 'U')[0].toUpperCase()}
</div>
<div className="max-[820px]:hidden">
<div className="text-[13px] font-bold">{user.username}</div>
<div className="text-[10.5px] text-muted">{user.role}</div>
</div>
</div>
</aside>
<main className="flex flex-1 flex-col overflow-hidden">
<header className="flex items-center gap-3.5 border-b border-line px-7 py-5 shadow-[0_6px_18px_rgba(20,50,70,.10)]"
style={{ background: 'var(--header-bg)', color: 'var(--header-ink)', backdropFilter: 'var(--blur)' }}>
<div>
<div className="font-head text-[21px] font-extrabold">{TABS[tab][0]}</div>
<div className="mt-0.5 text-[12.5px]" style={{ color: 'var(--header-muted)' }}>{TABS[tab][1]}</div>
</div>
<div className="ml-auto flex items-center gap-3">
<button className="btn btn-primary" onClick={run} disabled={status.state === 'running'}>▶ Run</button>
<button className="h-[38px] w-[38px] cursor-pointer rounded-[11px] border border-line bg-glass-strong text-base text-ink"
title="Dark mode" aria-label="Dark mode" aria-pressed={dark} onClick={toggleDark}>
{dark ? '☀' : '🌙'}
</button>
<div className="flex items-center gap-2 rounded-full border border-line bg-glass-strong px-3 py-1.5 text-xs font-bold text-ink">
<span className={`h-2 w-2 rounded-full ${DOT[status.state] ?? 'bg-muted'}`} />
<span>{status.msg || status.state}</span>
</div>
</div>
</header>
<section className="flex min-h-0 flex-1 flex-col overflow-y-auto px-7 py-6 max-[820px]:px-4">
{tab === 'dashboard' && <Overview />}
{tab === 'run' && <RunPanel />}
{tab === 'review' && <Review onUnsaved={setUnsaved} />}
{tab === 'report' && <Report />}
</section>
</main>
</div>
)
}