Skip to content

Feat/web dashboard - #5

Merged
7vignesh merged 5 commits into
mainfrom
feat/web-dashboard
Apr 1, 2026
Merged

Feat/web dashboard#5
7vignesh merged 5 commits into
mainfrom
feat/web-dashboard

Conversation

@7vignesh

@7vignesh 7vignesh commented Apr 1, 2026

Copy link
Copy Markdown
Owner

Summary

This PR delivers the StackForge web dashboard end-to-end and makes agent execution production-realistic with provider readiness checks.


Problem

The dashboard experience existed conceptually, but running in true real-agent mode could fail late and feel ambiguous to users. There was no explicit runtime readiness signal before triggering generation.


Solution

This PR separates concerns across UI, web app, API runtime, tests, and docs.


Changes

🧩 UI Foundation

  • Added a reusable UI component library (packages/ui) to standardize dashboard look and behavior
  • Included core primitives: Button, Card, Badge, Spinner, and Toast

🖥️ Web Dashboard

  • Scaffolded the web app with Vite + React + TypeScript
  • Added routes for home and job views
  • Implemented prompt submission flow and stack selection controls
  • Implemented SSE-driven live timeline updates for agent progress
  • Added blueprint rendering experience for completed jobs
  • Preserved demo mode simulation flow for offline/product-demo use

⚙️ API Runtime Readiness & Provider Behavior

  • Added runtime provider resolution supporting openrouter, mock, and auto modes
  • Added GET /runtime/status endpoint to expose provider and readiness state
  • Added fail-fast check on generation requests — users get immediate feedback when runtime is not ready
  • Updated .env.example to include provider mode configuration

🧪 Quality & Docs

  • Added API integration coverage for runtime endpoint behavior
  • Updated docs with provider mode usage and runtime endpoint details

Commits

Commit Description
feat(ui) Add reusable dashboard component library
feat(web) Scaffold dashboard app with realtime job UI
feat(api) Add provider runtime status and readiness gating
test(api) Cover runtime status endpoint
docs Document provider modes and runtime endpoint

Testing

  • Ran monorepo typecheck successfully
  • Ran monorepo tests successfully
  • Verified API integration tests pass, including runtime status coverage
  • Verified branch push completed successfully

Notes

Demo mode remains available for showcase and visual QA.
Real generation mode is now explicit and safer through runtime readiness gating.


Checklist

  • Feature implementation
  • Runtime/API safeguards
  • Test updates
  • Documentation updates
  • Changes split into focused commits

Copilot AI review requested due to automatic review settings April 1, 2026 13:29
@7vignesh
7vignesh merged commit 1533aeb into main Apr 1, 2026
1 check passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements the StackForge web dashboard (Vite/React) backed by a new reusable UI package, and adds API runtime/provider status + readiness gating so generation fails fast when an LLM provider isn’t configured.

Changes:

  • Added @stackforge/ui component primitives (Button/Card/Badge/Spinner/Toast) for consistent dashboard UI.
  • Added @stackforge/web app with Home + Job pages, demo-mode simulation, and SSE-driven live agent timeline + blueprint rendering.
  • Added API runtime provider resolution (openrouter/mock/auto), /api/runtime status endpoint, and generation readiness gating + integration test coverage.

Reviewed changes

Copilot reviewed 34 out of 36 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
README.md Documents provider modes and runtime endpoint.
packages/ui/tsconfig.json TypeScript config for new UI workspace package.
packages/ui/package.json Defines UI package entrypoints, scripts, and React peer deps.
packages/ui/src/index.ts Barrel exports for UI primitives.
packages/ui/src/Button.tsx Adds reusable Button component (variants/sizes/loading).
packages/ui/src/Card.tsx Adds reusable Card component with hover/glow styling.
packages/ui/src/Badge.tsx Adds status Badge component for agent/job states.
packages/ui/src/Spinner.tsx Adds Spinner component for loading indicators.
packages/ui/src/Toast.tsx Adds ToastProvider + useToast hook for notifications.
apps/web/package.json Scaffolds web app dependencies and Vite/TS build scripts.
apps/web/tsconfig.json TS config for the web app + reference to packages/ui.
apps/web/vite.config.ts Adds Vite config + API proxy for local dev.
apps/web/vite-env.d.ts Adds Vite client type reference.
apps/web/index.html Adds web app HTML entry.
apps/web/src/main.tsx Bootstraps React root rendering.
apps/web/src/App.tsx Adds router + global ToastProvider + layout shell.
apps/web/src/index.css Adds Tailwind import + global tokens/animations/utilities.
apps/web/src/lib/api.ts Adds web-facing API client for generate/job/runtime endpoints.
apps/web/src/lib/mock-data.ts Adds demo blueprint + demo SSE simulation utilities.
apps/web/src/hooks/useJobStream.ts Adds SSE hook for live job/agent updates (demo + real).
apps/web/src/pages/Home.tsx Implements prompt + stack selection + runtime readiness UI + demo entry.
apps/web/src/pages/JobPage.tsx Implements job view: live timeline + blueprint rendering + toasts.
apps/web/src/components/Layout.tsx Adds dashboard layout with nav/footer.
apps/web/src/components/AgentTimeline.tsx Renders vertical agent timeline from stream state.
apps/web/src/components/AgentCard.tsx Renders individual agent status cards.
apps/web/src/components/BlueprintView.tsx Renders generated blueprint sections + JSON download.
apps/web/src/components/CollapsibleSection.tsx Adds reusable collapsible section UI for blueprint panels.
apps/web/src/components/FileTree.tsx Adds file tree renderer for blueprint folder structure.
apps/api/src/services/generate.service.ts Adds provider mode resolution + runtime status + mock provider support.
apps/api/src/controllers/generate.controller.ts Adds fail-fast readiness check before job creation.
apps/api/src/controllers/jobs.controller.ts Adds runtime status controller + wires into routing.
apps/api/src/routes/index.ts Adds /runtime route under /api.
apps/api/src/index.ts Adds CORS headers middleware (for web dev/prod).
apps/api/test/integration.test.ts Adds integration test coverage for runtime endpoint.
apps/api/.env.example Documents STACKFORGE_PROVIDER config with default auto.
bun.lock Captures new dependencies for web/ui (React, router, Vite, Tailwind, etc.).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread README.md
Comment on lines +110 to +120
STACKFORGE_PROVIDER=openrouter
OPENROUTER_API_KEY=your_key_here
OPENROUTER_ENDPOINT=https://openrouter.ai/api/v1/chat/completions
OPENROUTER_APP_NAME=stackforge-api
OPENROUTER_APP_URL=http://localhost:3001
```

`STACKFORGE_PROVIDER` supports:
- `openrouter`: forces real LLM execution (requires `OPENROUTER_API_KEY`)
- `mock`: forces deterministic offline agent responses
- omitted/`auto`: uses `openrouter` when API key is present, otherwise `mock`

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

STACKFORGE_PROVIDER is set to openrouter in the README example, but the text below says the default/auto mode will fall back to mock when no API key is present. Setting openrouter here will force 503 errors for anyone who hasn’t set OPENROUTER_API_KEY. Consider changing the example to STACKFORGE_PROVIDER=auto (or omitting it) and mentioning openrouter as an optional override.

Copilot uses AI. Check for mistakes.
Comment thread README.md
Comment on lines +122 to +124
Runtime/provider health is exposed at:
- `GET /api/runtime`

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description mentions a GET /runtime/status endpoint, but the docs/implementation/tests here use GET /api/runtime. Please align the PR description and docs so there’s a single canonical runtime status endpoint name/path.

Copilot uses AI. Check for mistakes.
Comment thread packages/ui/package.json
Comment on lines +6 to +11
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": {
"import": "./src/index.ts",
"types": "./src/index.ts"

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

main/types/exports point at ./src/index.ts, but this package’s build emits to dist/ (and other workspace packages export dist artifacts). Pointing exports at source TS can break non-bundler consumers and makes the tsc build output unused. Consider switching to ./dist/index.js + ./dist/index.d.ts (and keeping src for dev via TS project references if needed).

Suggested change
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": {
"import": "./src/index.ts",
"types": "./src/index.ts"
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"

Copilot uses AI. Check for mistakes.
Comment thread packages/ui/src/Toast.tsx
Comment on lines +37 to +46
export function ToastProvider({ children }: { children: React.ReactNode }) {
const [toasts, setToasts] = useState<ToastItem[]>([]);

const addToast = useCallback((type: ToastType, message: string) => {
const id = ++nextId;
setToasts((prev) => [...prev, { id, type, message }]);
setTimeout(() => {
setToasts((prev) => prev.filter((t) => t.id !== id));
}, 4000);
}, []);

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ToastProvider schedules setTimeout calls but never clears them on unmount. If the provider unmounts (HMR, tests, embedding), pending timers can call setToasts after unmount and trigger warnings/leaks. Track timeout IDs in a ref and clear them in a useEffect cleanup.

Copilot uses AI. Check for mistakes.
Comment thread apps/api/src/index.ts
Comment on lines 8 to +19

app.use(express.json());

// CORS for Vite dev server
app.use((_req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
if (_req.method === "OPTIONS") {
res.sendStatus(204);
return;
}

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wildcard CORS (Access-Control-Allow-Origin: *) is applied unconditionally to the entire API. If this service is deployed with real LLM credentials, any website can call the generation endpoints from a browser and read responses, increasing abuse risk. Consider enabling CORS only in development, or restricting origins via an allowlist/env var (or using the cors middleware).

Suggested change
app.use(express.json());
// CORS for Vite dev server
app.use((_req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
if (_req.method === "OPTIONS") {
res.sendStatus(204);
return;
}
const allowedOrigins =
(process.env["CORS_ORIGINS"] ?? "")
.split(",")
.map((origin) => origin.trim())
.filter((origin) => origin.length > 0);
app.use(express.json());
// CORS for Vite dev server (permissive in development, restricted by allowlist otherwise)
app.use((_req, res, next) => {
const origin = _req.headers.origin as string | undefined;
if (process.env["NODE_ENV"] === "development") {
res.setHeader("Access-Control-Allow-Origin", "*");
} else if (origin && allowedOrigins.includes(origin)) {
res.setHeader("Access-Control-Allow-Origin", origin);
}
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
if (_req.method === "OPTIONS") {
res.sendStatus(204);
return;
}

Copilot uses AI. Check for mistakes.
Comment on lines +4 to +7
runtimeController,
listJobsController,
getJobController,
streamController,

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This import block uses tab indentation, while the surrounding codebase uses spaces. This can cause noisy diffs and potentially fail formatting checks. Please reformat this block to match the existing spacing style.

Suggested change
runtimeController,
listJobsController,
getJobController,
streamController,
runtimeController,
listJobsController,
getJobController,
streamController,

Copilot uses AI. Check for mistakes.
const [runtimeError, setRuntimeError] = useState<string | null>(null);

const isValid = prompt.trim().length >= 10;
const canGenerate = isValid && runtime?.ready !== false;

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

canGenerate is computed as runtime?.ready !== false, which evaluates to true when runtime status hasn’t loaded yet (runtime === null) and also doesn’t account for runtimeError. This allows users to submit generation requests even when runtime status fetch failed or is still pending (undermining the “readiness gating” UX). Consider requiring runtime?.ready === true (or disabling until status is known) and incorporating runtimeError into the disabled state/message.

Suggested change
const canGenerate = isValid && runtime?.ready !== false;
const canGenerate = isValid && runtime?.ready === true && !runtimeError;

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +15
a.click();
URL.revokeObjectURL(url);

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleDownload revokes the object URL immediately after triggering a.click(). In some browsers this can cancel the download or produce intermittent failures. Consider revoking in a setTimeout (or after the click via requestAnimationFrame) and optionally appending/removing the anchor element for better cross-browser behavior.

Suggested change
a.click();
URL.revokeObjectURL(url);
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 0);

Copilot uses AI. Check for mistakes.
Comment on lines +30 to +47
<button
onClick={() => setOpen(!open)}
style={{
width: "100%",
display: "flex",
alignItems: "center",
gap: "10px",
padding: "16px 20px",
background: "transparent",
border: "none",
color: "#f0f0f5",
fontSize: "15px",
fontWeight: 600,
fontFamily: "'Inter', system-ui, sans-serif",
cursor: "pointer",
textAlign: "left",
}}
>

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The collapsible toggle button doesn’t expose its expanded/collapsed state to assistive tech. Consider adding aria-expanded={open}, an aria-controls pointing at the panel region, and ensuring the disclosure icon is marked decorative so screen readers announce a meaningful label.

Copilot uses AI. Check for mistakes.
Comment thread apps/web/src/lib/api.ts
Comment on lines +102 to +110
export async function getRuntime(): Promise<RuntimeResponse> {
const res = await fetch(`${API_BASE}/runtime`);

if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new Error((body as { error?: string }).error ?? `Request failed (${res.status})`);
}

return res.json() as Promise<RuntimeResponse>;

Copilot AI Apr 1, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getRuntime() currently throws for any non-2xx response. The runtime endpoint intentionally uses 503 to signal “not ready” while still returning a structured { provider, ready: false, reason } payload. Treating 503 as an exception here loses the reason and makes the UI show a generic “Request failed (503)” instead of the real readiness status. Consider allowing 200 and 503 to both parse into RuntimeResponse (only throw for other status codes / invalid JSON).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants