Add web product Phase 1 skeleton
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Orch Control Plane</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "@ai-workflow-skill/web",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc --noEmit && vite build",
|
||||
"preview": "vite preview",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.91.2",
|
||||
"@tanstack/react-router": "^1.167.5",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^8.0.1"
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>,
|
||||
);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src", "vite.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 5173,
|
||||
proxy: {
|
||||
'/api': 'http://localhost:8080',
|
||||
'/health': 'http://localhost:8080',
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user