diff --git a/frontend/src/landing/components/LandingHero.tsx b/frontend/src/landing/components/LandingHero.tsx index 29b761fba..87c3b5d19 100644 --- a/frontend/src/landing/components/LandingHero.tsx +++ b/frontend/src/landing/components/LandingHero.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState, type ReactNode } from "react"; import gsap from "gsap"; import { useGSAP } from "@gsap/react"; import { ScaledMockup } from "./ScaledMockup"; @@ -56,53 +56,58 @@ function formatCompactNumber(value: number): string { return String(value); } -const appProjects = [ - { - name: "api-gateway", - id: "api-gateway", - count: 4, - sessions: [ - { title: "Split terminal mux responsibilities", zone: "working" }, - { title: "fix auth timeout retry loop", zone: "error" }, - { title: "add rate limit headers", zone: "pending" }, - ], - }, - { - name: "webgl-preview", - id: "webgl-preview", - count: 8, - sessions: [ - { title: "Restore fallback renderer affordance", zone: "warning" }, - { title: "cache compiled shader programs", zone: "error" }, - { title: "ship frame statistics overlay", zone: "pending" }, - ], - }, - { - name: "mobile-shell", - id: "mobile-shell", - count: 2, - sessions: [ - { title: "repair back swipe gesture", zone: "working" }, - { title: "profile sheet accessibility pass", zone: "success" }, - ], - }, - { - name: "billing-portal", - id: "billing-portal", - count: 2, - sessions: [ - { title: "invoice CSV export", zone: "pending" }, - { title: "tax id validation errors", zone: "error" }, - ], - }, -]; +type BoardColumnId = "working" | "action" | "pending" | "merge"; +type SessionZone = "working" | "warning" | "error" | "success" | "pending"; -const appColumns = [ - { +type BoardCard = { + agent: string; + branch: string; + column: BoardColumnId; + meta: string; + status: string; + title: string; + zone: SessionZone; +}; + +type AppProject = { + description: string; + id: string; + name: string; + shortName: string; + cards: BoardCard[]; +}; + +const columnDefinitions: Record = { + working: { title: "Working", - level: "working", color: "#f59f4c", glow: "rgba(245,159,76,0.07)", + }, + action: { + title: "Needs you", + color: "#e8c14a", + glow: "rgba(232,193,74,0.06)", + }, + pending: { + title: "In review", + color: "#7f8794", + glow: "rgba(255,255,255,0.02)", + }, + merge: { + title: "Ready to merge", + color: "#74b98a", + glow: "rgba(116,185,138,0.07)", + }, +}; + +const columnOrder: BoardColumnId[] = ["working", "action", "pending", "merge"]; + +const appProjects: AppProject[] = [ + { + name: "atlas-api", + id: "atlas-api", + shortName: "API", + description: "Edge API, auth, rate limits", cards: [ { status: "Working", @@ -110,58 +115,140 @@ const appColumns = [ title: "Split terminal mux responsibilities", branch: "session/ao-204", meta: "no PR yet", + column: "working", + zone: "working", }, - ], - }, - { - title: "Needs you", - level: "action", - color: "#e8c14a", - glow: "rgba(232,193,74,0.06)", - cards: [ { status: "CI failed", agent: "codex", title: "fix auth timeout retry loop", branch: "fix/auth-timeouts", meta: "PR #184 · open", + column: "action", + zone: "error", }, - ], - }, - { - title: "In review", - level: "pending", - color: "#646a73", - glow: "rgba(255,255,255,0.02)", - cards: [ { status: "Review pending", agent: "opencode", title: "add rate limit headers", branch: "feat/rate-limit-headers", meta: "PR #185 · open", + column: "pending", + zone: "pending", }, - ], - }, - { - title: "Ready to merge", - level: "merge", - color: "#74b98a", - glow: "rgba(116,185,138,0.07)", - cards: [ { status: "Ready", agent: "cursor", title: "Ship onboarding smoke test", branch: "test/onboarding-harness", meta: "PR #204 · approved", + column: "merge", + zone: "success", + }, + ], + }, + { + name: "canvas-preview", + id: "canvas-preview", + shortName: "GL", + description: "In-app browser and preview runtime", + cards: [ + { + status: "Working", + agent: "goose", + title: "Restore fallback renderer affordance", + branch: "fix/webgl-fallback", + meta: "no PR yet", + column: "working", + zone: "working", + }, + { + status: "Blocked", + agent: "codex", + title: "cache compiled shader programs", + branch: "perf/shader-cache", + meta: "needs repro trace", + column: "action", + zone: "error", + }, + { + status: "Review pending", + agent: "aider", + title: "ship frame statistics overlay", + branch: "feat/frame-stats", + meta: "PR #219 · open", + column: "pending", + zone: "pending", }, { status: "Approved", + agent: "cursor", + title: "stabilize browser preview sizing", + branch: "fix/browser-bounds", + meta: "PR #221 · approved", + column: "merge", + zone: "success", + }, + ], + }, + { + name: "mobile-client", + id: "mobile-client", + shortName: "IOS", + description: "Mobile shell and handoff flows", + cards: [ + { + status: "Working", + agent: "claude", + title: "repair back swipe gesture", + branch: "fix/back-swipe", + meta: "no PR yet", + column: "working", + zone: "working", + }, + { + status: "Ready", + agent: "opencode", + title: "profile sheet accessibility pass", + branch: "a11y/profile-sheet", + meta: "PR #232 · approved", + column: "merge", + zone: "success", + }, + ], + }, + { + name: "revenue-portal", + id: "revenue-portal", + shortName: "REV", + description: "Billing, invoices, tax flows", + cards: [ + { + status: "Review pending", agent: "aider", - title: "publish linux desktop install path", - branch: "docs/linux-install", - meta: "PR #211 · 2 approvals", + title: "invoice CSV export", + branch: "feat/invoice-csv", + meta: "PR #240 · open", + column: "pending", + zone: "pending", + }, + { + status: "Needs input", + agent: "codex", + title: "tax id validation errors", + branch: "fix/tax-id-errors", + meta: "review comment waiting", + column: "action", + zone: "error", + }, + { + status: "Approved", + agent: "cursor", + title: "receipt email copy refresh", + branch: "copy/receipt-email", + meta: "PR #244 · approved", + column: "merge", + zone: "success", }, ], }, @@ -257,18 +344,59 @@ function SidebarIcon({ className = "" }: { className?: string }) { } function HeroDashboardMockup() { - const [activeProject, setActiveProject] = useState("api-gateway"); + const [projectsState, setProjectsState] = useState(appProjects); + const [activeProject, setActiveProject] = useState("all"); const [activeCard, setActiveCard] = useState("fix auth timeout retry loop"); + const [activePanel, setActivePanel] = useState<"board" | "settings" | "terminal" | "newTask">("board"); + const [terminalTitle, setTerminalTitle] = useState("Session terminal"); const [sidebarOpen, setSidebarOpen] = useState(true); const [openProjects, setOpenProjects] = useState>({ - "api-gateway": true, - "webgl-preview": true, - "mobile-shell": true, - "billing-portal": true, + "atlas-api": true, + "canvas-preview": true, + "mobile-client": true, + "revenue-portal": true, }); + const selectedProject = projectsState.find((project) => project.id === activeProject); + const visibleCards = selectedProject ? selectedProject.cards : projectsState.flatMap((project) => project.cards); + const activeProjectLabel = selectedProject ? selectedProject.name : "All projects"; + const doneCount = selectedProject ? Math.max(1, Math.floor(selectedProject.cards.length / 2)) : 7; + const boardColumns = columnOrder.map((columnId) => ({ + ...columnDefinitions[columnId], + id: columnId, + cards: visibleCards.filter((card) => card.column === columnId), + })); + + function showBoard(projectId: string) { + setActiveProject(projectId); + setActivePanel("board"); + const project = projectsState.find((entry) => entry.id === projectId); + const firstCard = project?.cards[0]; + if (firstCard) setActiveCard(firstCard.title); + } + + function showAllProjects() { + setActiveProject("all"); + setActivePanel("board"); + setActiveCard("All project sessions"); + } + + function showTerminal(title: string) { + setActivePanel("terminal"); + setTerminalTitle(title); + setActiveCard(title); + } + + function showNewTask() { + if (!selectedProject) { + showBoard(projectsState[0].id); + } + setActivePanel("newTask"); + setActiveCard("New task"); + } + function toggleProject(projectName: string) { - setActiveProject(projectName); + showBoard(projectName); setOpenProjects((current) => ({ ...current, [projectName]: !current[projectName], @@ -297,7 +425,7 @@ function HeroDashboardMockup() { type="button" onClick={() => { if (sidebarOpen) { - setActiveProject("api-gateway"); + showAllProjects(); } else { setSidebarOpen(true); } @@ -336,12 +464,17 @@ function HeroDashboardMockup() {
Projects
-
- {appProjects.map((project) => ( + {projectsState.map((project) => (
@@ -370,8 +503,13 @@ function HeroDashboardMockup() { type="button" onClick={(event) => { event.stopPropagation(); - setActiveProject(project.id); - if (index === 1) setActiveCard(`Spawn ${project.name} orchestrator`); + if (index === 0) showBoard(project.id); + if (index === 1) showTerminal(`Spawn ${project.name} orchestrator`); + if (index === 2) { + setActiveProject(project.id); + setActivePanel("settings"); + setActiveCard(`${project.name} settings`); + } }} className="grid size-5 place-items-center rounded-md text-[#646a73] transition-colors hover:bg-white/[0.04] hover:text-[#f4f5f7] [&_svg]:size-[15px]" aria-label={`${project.name} action ${index + 1}`} @@ -382,12 +520,12 @@ function HeroDashboardMockup() {
{openProjects[project.id] ? (
- {project.sessions.map((session) => ( + {project.cards.slice(0, 3).map((session) => (
) : (
- {appProjects.map((project) => ( + {projectsState.map((project) => (
-
+

Board

- Live agent sessions flowing from work → review → merge. + {activeProjectLabel} · sessions flowing from work → review → merge.
-
- {appColumns.map((column) => ( -
-
- -
- {column.title} + {activePanel === "settings" ? ( + + ) : activePanel === "terminal" ? ( + + ) : ( +
+ {boardColumns.map((column) => ( +
+
+ +
+ {column.title} +
+ + {column.cards.length} +
- - {column.cards.length} - -
-
-
- {column.cards.map((card) => ( - - ))} +
+
+ {column.cards.map((card) => ( + + ))} +
-
-
- ))} -
+ + ))} +
+ )}
+ {activePanel === "newTask" ? ( + setActivePanel("board")} + onStart={(newTask) => { + setProjectsState((current) => + current.map((p) => { + if (p.id === (selectedProject?.id ?? current[0].id)) { + return { ...p, cards: [newTask, ...p.cards] }; + } + return p; + }), + ); + showTerminal(newTask.title); + }} + /> + ) : null}
Done / Terminated - 3 + {doneCount}
@@ -552,6 +725,332 @@ function HeroDashboardMockup() { ); } +function MockTerminalPanel({ activeProjectLabel, title }: { activeProjectLabel: string; title: string }) { + return ( +
+
+
+
+ + Terminal + + {activeProjectLabel} +
+
+ + 13px + +
+
+
+
⚠ `--dangerously-bypass-hook-trust` is enabled.
+
+
OpenAI Codex
+
+ model: + + gpt-5.5 /model + + directory: + ~/.ao/data/worktrees/{activeProjectLabel.toLowerCase().replaceAll(" ", "-")} + permissions: + YOLO mode +
+
+
› ao session open "{title}"
+
• What would you like me to do?
+
+ + + Use /skills to list available skills +
+
+ gpt-5.5 default ·{" "} + + ~/.ao/data/worktrees/{activeProjectLabel.toLowerCase().replaceAll(" ", "-")} + +
+
+
+ +
+ ); +} + +function MockSettingsPanel({ activeProjectLabel }: { activeProjectLabel: string }) { + return ( +
+
+

Settings

+
/repos/{activeProjectLabel}
+
+
+ + + + + + + + + + + + + + + +
+ + Saved. +
+
+
+ ); +} + +function MockNewTaskDialog({ + activeProjectLabel, + onClose, + onStart, +}: { + activeProjectLabel: string; + onClose: () => void; + onStart: (task: BoardCard) => void; +}) { + const [title, setTitle] = useState(""); + const [brief, setBrief] = useState(""); + const [agent, setAgent] = useState("claude-code"); + const [branch, setBranch] = useState(""); + + const handleStart = () => { + if (!title.trim()) return; + onStart({ + agent, + branch: branch || "feat/new-task", + column: "working", + meta: "Started just now", + status: "Working", + title, + zone: "working", + }); + }; + + return ( +
+
+
+
+

New task

+

Start a worker directly from {activeProjectLabel}.

+
+ +
+
+ +
+ +