From 4dac57685c62b35df47e63e3565b2b81633dc63d Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Sat, 28 Feb 2026 13:50:06 -0500 Subject: [PATCH] Add workflow detail page with tabs at /workflows/:name Detail page shows title, filename, description, and horizontal tabs for Definition, Diagram, and Runs. Workflow names in the list are now links to their detail pages. Co-Authored-By: Claude Opus 4.6 --- apps/arc-web/app/routes.ts | 1 + apps/arc-web/app/routes/workflow-detail.tsx | 88 +++++++++++++++++++++ apps/arc-web/app/routes/workflows.tsx | 86 +++++++++++++++++--- 3 files changed, 166 insertions(+), 9 deletions(-) create mode 100644 apps/arc-web/app/routes/workflow-detail.tsx diff --git a/apps/arc-web/app/routes.ts b/apps/arc-web/app/routes.ts index 44fd548b1..ad5dd550b 100644 --- a/apps/arc-web/app/routes.ts +++ b/apps/arc-web/app/routes.ts @@ -10,6 +10,7 @@ export default [ layout("layouts/app-shell.tsx", [ route("start", "routes/start.tsx"), route("workflows", "routes/workflows.tsx"), + route("workflows/:name", "routes/workflow-detail.tsx"), route("runs", "routes/pipelines.tsx"), route("insights", "routes/insights.tsx"), route("settings", "routes/settings.tsx"), diff --git a/apps/arc-web/app/routes/workflow-detail.tsx b/apps/arc-web/app/routes/workflow-detail.tsx new file mode 100644 index 000000000..1dfa9466b --- /dev/null +++ b/apps/arc-web/app/routes/workflow-detail.tsx @@ -0,0 +1,88 @@ +import { useState } from "react"; +import { useParams } from "react-router"; +import type { Route } from "./+types/workflow-detail"; + +const workflowData: Record = { + fix_build: { + title: "Fix Build", + filename: "fix_build.dot", + description: "Automatically diagnoses and fixes CI build failures by analyzing error logs, identifying root causes, and applying targeted code changes.", + }, + implement: { + title: "Implement Feature", + filename: "implement.dot", + description: "Generates production-ready code from a technical blueprint, including tests, documentation, and a pull request ready for review.", + }, + sync_drift: { + title: "Sync Drift", + filename: "sync_drift.dot", + description: "Detects configuration and code drift between environments, then generates reconciliation patches to bring everything back in sync.", + }, + expand: { + title: "Expand Product", + filename: "expand.dot", + description: "Evolves the product by analyzing usage patterns and specifications to propose and implement incremental improvements.", + }, +}; + +const tabs = ["Definition", "Diagram", "Runs"] as const; +type Tab = (typeof tabs)[number]; + +export function meta({ params }: Route.MetaArgs) { + const workflow = workflowData[params.name ?? ""]; + const title = workflow?.title ?? params.name; + return [{ title: `${title} — Arc` }]; +} + +export default function WorkflowDetail() { + const { name } = useParams(); + const workflow = workflowData[name ?? ""]; + const [activeTab, setActiveTab] = useState("Definition"); + + if (workflow == null) { + return

Workflow not found.

; + } + + return ( +
+
+
+

{workflow.title}

+ {workflow.filename} +
+

{workflow.description}

+
+ +
+ +
+ +
+ {activeTab === "Definition" && ( +

Workflow definition will appear here.

+ )} + {activeTab === "Diagram" && ( +

Workflow diagram will appear here.

+ )} + {activeTab === "Runs" && ( +

Recent runs will appear here.

+ )} +
+
+ ); +} diff --git a/apps/arc-web/app/routes/workflows.tsx b/apps/arc-web/app/routes/workflows.tsx index 9de72a081..754843434 100644 --- a/apps/arc-web/app/routes/workflows.tsx +++ b/apps/arc-web/app/routes/workflows.tsx @@ -1,20 +1,66 @@ +import { useState } from "react"; +import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react"; +import { ChevronDownIcon, PlusIcon } from "@heroicons/react/20/solid"; +import { MagnifyingGlassIcon } from "@heroicons/react/24/outline"; +import { Link } from "react-router"; import type { Route } from "./+types/workflows"; export function meta({}: Route.MetaArgs) { return [{ title: "Workflows — Arc" }]; } +export const handle = { + headerExtra: ( +
+ + + + + + + + + + + + + +
+ ), +}; + interface Workflow { name: string; + slug: string; filename: string; lastRun: string; } const workflows: Workflow[] = [ - { name: "Fix Build", filename: "fix_build.dot", lastRun: "2 hours ago" }, - { name: "Implement Feature", filename: "implement.dot", lastRun: "4 days ago" }, - { name: "Sync Drift", filename: "sync_drift.dot", lastRun: "1 day ago" }, - { name: "Expand Product", filename: "expand.dot", lastRun: "2 weeks ago" }, + { name: "Fix Build", slug: "fix_build", filename: "fix_build.dot", lastRun: "2 hours ago" }, + { name: "Implement Feature", slug: "implement", filename: "implement.dot", lastRun: "4 days ago" }, + { name: "Sync Drift", slug: "sync_drift", filename: "sync_drift.dot", lastRun: "1 day ago" }, + { name: "Expand Product", slug: "expand", filename: "expand.dot", lastRun: "2 weeks ago" }, ]; function PlayIcon({ className }: { className?: string }) { @@ -46,7 +92,7 @@ function WorkflowCard({ workflow }: { workflow: Workflow }) {
- {workflow.name} + {workflow.name} {workflow.filename}

Last run {workflow.lastRun}

@@ -64,11 +110,33 @@ function WorkflowCard({ workflow }: { workflow: Workflow }) { } export default function Workflows() { + const [query, setQuery] = useState(""); + const filtered = workflows.filter( + (w) => + w.name.toLowerCase().includes(query.toLowerCase()) || + w.filename.toLowerCase().includes(query.toLowerCase()), + ); + return ( -
- {workflows.map((workflow) => ( - - ))} +
+
+ + setQuery(e.target.value)} + className="w-full rounded-lg border border-white/[0.06] bg-navy-800/80 py-2 pl-9 pr-3 text-sm text-ice-100 placeholder-navy-600 outline-none transition-colors focus:border-teal-500/40 focus:ring-0" + /> +
+
+ {filtered.map((workflow) => ( + + ))} + {filtered.length === 0 && ( +

No workflows match "{query}"

+ )} +
); }