Add web product Phase 1 skeleton

This commit is contained in:
2026-03-20 00:20:38 +08:00
parent 0355d7a847
commit a7ef1e0154
24 changed files with 2287 additions and 6 deletions
+104
View File
@@ -0,0 +1,104 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import {
Link,
Outlet,
RouterProvider,
createRootRoute,
createRoute,
createRouter,
} from '@tanstack/react-router';
const queryClient = new QueryClient();
const rootRoute = createRootRoute({
component: RootLayout,
});
const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: HomePage,
});
const routeTree = rootRoute.addChildren([indexRoute]);
const router = createRouter({
routeTree,
});
declare module '@tanstack/react-router' {
interface Register {
router: typeof router;
}
}
export function App() {
return (
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
);
}
function RootLayout() {
return (
<div className="shell">
<header className="masthead">
<div>
<p className="eyebrow">AI Workflow Skill</p>
<Link className="brand" to="/">
Orch Control Plane
</Link>
</div>
<p className="masthead-copy">
Phase 1 keeps the UI thin while the backend contract settles around
`orchd`.
</p>
</header>
<main className="content">
<Outlet />
</main>
</div>
);
}
function HomePage() {
return (
<section className="hero-grid">
<article className="hero-card hero-card-primary">
<p className="eyebrow">Current slice</p>
<h1>Read-only operator shell for a future multi-user web product.</h1>
<p className="lede">
The monorepo now has a dedicated React app, a Go HTTP service, and a
first API contract for runs, blocked work, and thread history.
</p>
</article>
<article className="hero-card hero-card-secondary">
<h2>Backend spine</h2>
<ul className="detail-list">
<li>`cmd/orchd` serves `chi` routes against the existing SQLite state.</li>
<li>`internal/query` shapes run, blocked-task, and thread reads.</li>
<li>`api/openapi.yaml` is the contract anchor for future typed clients.</li>
</ul>
</article>
<article className="hero-card">
<h2>Frontend posture</h2>
<p>
React, Vite, TanStack Router, and TanStack Query are present now so
Phase 2 can focus on actual operator views instead of build plumbing.
</p>
</article>
<article className="hero-card">
<h2>Next UI targets</h2>
<ul className="detail-list">
<li>Runs dashboard with task-count health signals.</li>
<li>Run detail board grouped by orchestration state.</li>
<li>Blocked queue and thread timeline wired to live refresh.</li>
</ul>
</article>
</section>
);
}
+17
View File
@@ -0,0 +1,17 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { App } from './app';
import './styles.css';
const rootElement = document.getElementById('root');
if (!rootElement) {
throw new Error('Missing root element');
}
createRoot(rootElement).render(
<StrictMode>
<App />
</StrictMode>,
);
+154
View File
@@ -0,0 +1,154 @@
:root {
color-scheme: light;
font-family: "IBM Plex Sans", "Avenir Next", "Segoe UI", sans-serif;
line-height: 1.5;
font-weight: 400;
background:
radial-gradient(circle at top left, rgba(255, 196, 98, 0.32), transparent 28%),
radial-gradient(circle at bottom right, rgba(8, 145, 178, 0.16), transparent 26%),
linear-gradient(135deg, #f4efe3 0%, #f9f7f1 55%, #eef3f4 100%);
color: #172026;
--border: rgba(23, 32, 38, 0.12);
--panel: rgba(255, 255, 255, 0.82);
--panel-strong: rgba(255, 255, 255, 0.92);
--ink-soft: rgba(23, 32, 38, 0.7);
--accent: #c96f20;
--accent-deep: #0f766e;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
min-width: 320px;
min-height: 100vh;
}
a {
color: inherit;
text-decoration: none;
}
#root {
min-height: 100vh;
}
.shell {
min-height: 100vh;
padding: 32px 20px 48px;
}
.masthead {
display: grid;
gap: 18px;
align-items: end;
max-width: 1200px;
margin: 0 auto 28px;
}
.brand {
display: inline-block;
font-family: "Iowan Old Style", "Palatino Linotype", serif;
font-size: clamp(2rem, 4vw, 3.2rem);
letter-spacing: -0.04em;
}
.eyebrow {
margin: 0 0 10px;
text-transform: uppercase;
letter-spacing: 0.16em;
font-size: 0.78rem;
color: var(--accent-deep);
}
.masthead-copy {
max-width: 34rem;
margin: 0;
color: var(--ink-soft);
}
.content {
max-width: 1200px;
margin: 0 auto;
}
.hero-grid {
display: grid;
gap: 18px;
grid-template-columns: repeat(12, minmax(0, 1fr));
}
.hero-card {
grid-column: span 12;
padding: 24px;
border: 1px solid var(--border);
border-radius: 24px;
background: var(--panel);
backdrop-filter: blur(12px);
box-shadow: 0 20px 50px rgba(23, 32, 38, 0.08);
}
.hero-card-primary {
background:
linear-gradient(145deg, rgba(255, 255, 255, 0.92), rgba(255, 248, 237, 0.94));
}
.hero-card-secondary {
background:
linear-gradient(145deg, rgba(240, 249, 255, 0.95), rgba(240, 253, 250, 0.9));
}
.hero-card h1,
.hero-card h2 {
margin: 0 0 12px;
font-family: "Iowan Old Style", "Palatino Linotype", serif;
letter-spacing: -0.03em;
}
.hero-card h1 {
font-size: clamp(2rem, 4.4vw, 4.4rem);
line-height: 0.94;
max-width: 12ch;
}
.hero-card h2 {
font-size: 1.5rem;
}
.lede,
.hero-card p {
margin: 0;
max-width: 48rem;
color: var(--ink-soft);
}
.detail-list {
margin: 0;
padding-left: 18px;
color: var(--ink-soft);
}
.detail-list li + li {
margin-top: 10px;
}
@media (min-width: 840px) {
.masthead {
grid-template-columns: 1.2fr 0.8fr;
}
.hero-card-primary {
grid-column: span 7;
min-height: 420px;
}
.hero-card-secondary {
grid-column: span 5;
}
.hero-card:not(.hero-card-primary):not(.hero-card-secondary) {
grid-column: span 6;
}
}
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />