Add syntax-highlighted DOT graph display below TOML config

Add a custom Shiki TextMate grammar for GraphViz DOT files (converted
from vscode-graphviz) and display each workflow's DOT graph source
below the TOML configuration on the definition tab.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-02-28 15:07:26 -05:00
parent bc1b5d54ab
commit b218c6b280
3 changed files with 415 additions and 3 deletions

View file

@ -0,0 +1,106 @@
import type { LanguageRegistration } from "shiki";
export const dotLanguage: LanguageRegistration = {
name: "dot",
scopeName: "source.dot",
fileTypes: ["dot", "DOT", "gv"],
firstLineMatch: "digraph.*",
patterns: [
{
match: " ?(digraph)[ \\t]+([A-Za-z0-9]+) ?(\\{)",
captures: {
"1": { name: "storage.type.dot" },
"2": { name: "variable.other.dot" },
"3": { name: "punctuation.section.dot" },
},
},
{
match: "(<|-)(>|-)",
name: "keyword.operator.dot",
},
{
match: "\\b(node|edge|graph|digraph|subgraph|strict)\\b",
name: "storage.type.dot",
},
{
match:
"\\b(bottomlabel|color|comment|distortion|fillcolor|fixedsize|fontcolor|fontname|fontsize|group|height|label|layer|orientation|peripheries|regular|shape|shapefile|sides|skew|style|toplabel|URL|width|z)\\b",
name: "support.constant.attribute.node.dot",
},
{
match:
"\\b(arrowhead|arrowsize|arrowtail|color|comment|constraint|decorate|dir|fontcolor|fontname|fontsize|headlabel|headport|headURL|label|labelangle|labeldistance|labelfloat|labelcolor|labelfontname|labelfontsize|layer|lhead|ltail|minlen|samehead|sametail|splines|style|taillabel|tailport|tailURL|weight)\\b",
name: "support.constant.attribute.edge.dot",
},
{
match:
"\\b(bgcolor|center|clusterrank|color|comment|compound|concentrate|fillcolor|fontname|fontpath|fontsize|label|labeljust|labelloc|layers|margin|mclimit|nodesep|nslimit|nslimit1|ordering|orientation|page|pagedir|quantum|rank|rankdir|ranksep|ratio|remincross|rotate|samplepoints|searchsize|size|style|URL)\\b",
name: "support.constant.attribute.graph.dot",
},
{
match:
"\\b(box|polygon|ellipse|circle|point|egg|triangle|plaintext|diamond|trapezium|parallelogram|house|pentagon|hexagon|septagon|octagon|doublecircle|doubleoctagon|tripleoctagon|invtriangle|invtrapezium|invhouse|Mdiamond|Msquare|Mcircle|rect|rectangle|none|note|tab|folder|box3d|component|max|min|same)\\b",
name: "variable.other.dot",
},
{
begin: '"',
beginCaptures: {
"0": { name: "punctuation.definition.string.begin.dot" },
},
end: '"',
endCaptures: {
"0": { name: "punctuation.definition.string.end.dot" },
},
name: "string.quoted.double.dot",
patterns: [
{
match: "\\\\.",
name: "constant.character.escape.dot",
},
],
},
{
begin: "(^[ \\t]+)?(?=//)",
beginCaptures: {
"1": { name: "punctuation.whitespace.comment.leading.dot" },
},
end: "(?!\\G)",
patterns: [
{
begin: "//",
beginCaptures: {
"0": { name: "punctuation.definition.comment.dot" },
},
end: "\\n",
name: "comment.line.double-slash.dot",
},
],
},
{
begin: "(^[ \\t]+)?(?=#)",
beginCaptures: {
"1": { name: "punctuation.whitespace.comment.leading.dot" },
},
end: "(?!\\G)",
patterns: [
{
begin: "#",
beginCaptures: {
"0": { name: "punctuation.definition.comment.dot" },
},
end: "\\n",
name: "comment.line.number-sign.dot",
},
],
},
{
begin: "/\\*",
captures: {
"0": { name: "punctuation.definition.comment.dot" },
},
end: "\\*/",
name: "comment.block.dot",
},
],
repository: {},
};

View file

@ -1,3 +1,82 @@
export default function WorkflowDefinition() {
return <p className="text-sm text-navy-600">Workflow definition will appear here.</p>;
import { useEffect, useRef, useState } from "react";
import { useParams } from "react-router";
import { workflowData } from "./workflow-detail";
import { dotLanguage } from "../data/dot-grammar";
function CodeBlock({
code,
lang,
filename,
}: {
code: string;
lang: string;
filename: string;
}) {
const containerRef = useRef<HTMLDivElement>(null);
const [ready, setReady] = useState(false);
useEffect(() => {
let cancelled = false;
async function highlight() {
const { createHighlighter } = await import("shiki");
if (cancelled) return;
const highlighter = await createHighlighter({
themes: ["nord"],
langs: lang === "dot" ? [dotLanguage] : [lang],
});
if (cancelled) return;
const html = highlighter.codeToHtml(code, {
lang,
theme: "nord",
});
if (cancelled || containerRef.current == null) return;
containerRef.current.innerHTML = html;
setReady(true);
}
highlight();
return () => {
cancelled = true;
};
}, [code, lang]);
return (
<div className="rounded-lg border border-white/[0.06] bg-navy-800/50 overflow-hidden">
<div className="flex items-center gap-2 border-b border-white/[0.06] px-4 py-2.5">
<span className="font-mono text-xs text-navy-600">{filename}</span>
</div>
<div
ref={containerRef}
className={`shiki-container overflow-x-auto transition-opacity duration-200 ${ready ? "opacity-100" : "opacity-0"}`}
/>
{!ready && (
<pre className="px-4 py-4 font-mono text-sm leading-relaxed text-navy-600">
{code}
</pre>
)}
</div>
);
}
export default function WorkflowDefinition() {
const { name } = useParams();
const workflow = workflowData[name ?? ""];
if (workflow == null) {
return <p className="text-sm text-navy-600">No configuration found.</p>;
}
return (
<div className="flex flex-col gap-6">
<CodeBlock code={workflow.config} lang="toml" filename="task.toml" />
<CodeBlock code={workflow.graph} lang="dot" filename={workflow.filename} />
</div>
);
}

View file

@ -1,26 +1,253 @@
import { Link, Outlet, useLocation, useParams } from "react-router";
import type { Route } from "./+types/workflow-detail";
export const workflowData: Record<string, { title: string; description: string; filename: string }> = {
interface WorkflowEntry {
title: string;
description: string;
filename: string;
config: string;
graph: string;
}
export const workflowData: Record<string, WorkflowEntry> = {
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.",
config: `version = 1
task = "Diagnose and fix CI build failures"
graph = "fix_build.dot"
[llm]
model = "claude-sonnet"
[vars]
repo_url = "https://github.com/org/service"
branch = "main"
[execution]
environment = "daytona"
[execution.daytona.sandbox]
auto_stop_interval = 60
[execution.daytona.sandbox.labels]
project = "fix-build"
[execution.daytona.snapshot]
name = "fix-build-dev"
cpu = 4
memory = 8
disk = 10
`,
graph: `digraph fix_build {
graph [
goal="Diagnose and fix CI build failures",
label="Fix Build"
]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
diagnose [label="Diagnose Failure", prompt="@prompts/fix_build/diagnose.md", reasoning_effort="high"]
fix [label="Apply Fix", prompt="@prompts/fix_build/fix.md"]
validate [label="Run Build", prompt="@prompts/fix_build/validate.md", goal_gate=true]
gate [shape=diamond, label="Build passing?"]
start -> diagnose -> fix -> validate -> gate
gate -> exit [label="Yes", condition="outcome=success"]
gate -> diagnose [label="No", condition="outcome!=success", max_visits=3]
}
`,
},
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.",
config: `version = 1
task = "Implement feature from technical blueprint"
graph = "implement.dot"
[llm]
model = "claude-sonnet"
[vars]
spec_path = "specs/feature.md"
test_framework = "vitest"
[setup]
commands = ["bun install", "bun run typecheck"]
timeout_ms = 120000
[execution]
environment = "daytona"
[execution.daytona.sandbox]
auto_stop_interval = 120
[execution.daytona.sandbox.labels]
project = "implement"
team = "engineering"
[execution.daytona.snapshot]
name = "implement-dev"
cpu = 4
memory = 8
disk = 20
`,
graph: `digraph implement {
graph [
goal="",
label="Implement"
]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
strategy [shape=hexagon, label="Choose decomposition strategy:"]
subgraph cluster_impl {
label="Implementation Loop"
node [fidelity="full", thread_id="impl"]
plan [label="Plan Implementation", prompt="@prompts/implement/plan.md", reasoning_effort="high"]
implement [label="Implement", prompt="@prompts/implement/implement.md"]
review [label="Review", prompt="@prompts/implement/review.md"]
validate [label="Validate", prompt="@prompts/implement/validate.md", goal_gate=true]
fix [label="Fix Failures", prompt="@prompts/implement/fix.md", max_visits=3]
}
start -> strategy
strategy -> plan [label="[L] Layer-by-layer"]
strategy -> plan [label="[F] Feature slice"]
strategy -> plan [label="[P] Embarrassingly parallel"]
strategy -> plan [label="[S] Sequential / linear"]
plan -> implement -> review -> validate
validate -> exit [condition="outcome=success"]
validate -> fix [condition="outcome!=success", label="Fix"]
fix -> validate
}
`,
},
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.",
config: `version = 1
task = "Detect and reconcile configuration drift across environments"
graph = "sync_drift.dot"
[llm]
model = "claude-sonnet"
[vars]
source_env = "production"
target_env = "staging"
drift_threshold = "warn"
[execution]
environment = "daytona"
[execution.daytona.sandbox]
auto_stop_interval = 120
[execution.daytona.sandbox.labels]
project = "sync-drift"
team = "platform"
[execution.daytona.snapshot]
name = "sync-drift-dev"
cpu = 2
memory = 4
disk = 10
dockerfile = """
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y --no-install-recommends \\
git curl ca-certificates jq diffutils \\
&& rm -rf /var/lib/apt/lists/*
RUN useradd -m -s /bin/bash daytona
USER daytona
WORKDIR /home/daytona
"""
`,
graph: `digraph sync {
graph [
goal="Detect and resolve drift between product docs, architecture docs, and code",
label="Sync"
]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
detect [label="Detect Drift", prompt="@prompts/sync/detect.md", reasoning_effort="high"]
propose [label="Propose Changes", prompt="@prompts/sync/propose.md"]
review [shape=hexagon, label="Review Changes"]
apply [label="Apply Changes", prompt="@prompts/sync/apply.md"]
start -> detect
detect -> exit [condition="context.drift_found=false", label="No drift"]
detect -> propose [condition="context.drift_found=true", label="Drift found"]
propose -> review
review -> apply [label="[A] Accept"]
review -> propose [label="[R] Revise"]
apply -> exit
}
`,
},
expand: {
title: "Expand Product",
filename: "expand.dot",
description: "Evolves the product by analyzing usage patterns and specifications to propose and implement incremental improvements.",
config: `version = 1
task = "Propose and implement incremental product improvements"
graph = "expand.dot"
[llm]
model = "claude-sonnet"
[vars]
analytics_window = "30d"
min_confidence = "0.8"
[execution]
environment = "daytona"
[execution.daytona.sandbox]
auto_stop_interval = 180
[execution.daytona.sandbox.labels]
project = "expand"
team = "product"
[execution.daytona.snapshot]
name = "expand-dev"
cpu = 2
memory = 4
disk = 10
`,
graph: `digraph expand {
graph [
goal="",
label="Expand"
]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
propose [label="Propose Changes", prompt="@prompts/expand/propose.md", reasoning_effort="high"]
approve [shape=hexagon, label="Approve Changes"]
execute [label="Execute Changes", prompt="@prompts/expand/execute.md"]
start -> propose -> approve
approve -> execute [label="[A] Accept"]
approve -> propose [label="[R] Revise"]
execute -> exit
}
`,
},
};