style: clarify lifecycle diagrams at presentation distance

This commit is contained in:
lda
2026-07-14 01:57:01 +07:00 Verified
parent c659d3af9b
commit 90b446aea8
9 changed files with 511 additions and 144 deletions
@@ -121,8 +121,9 @@ describe("PresentationRoute", () => {
expect(within(result).getByText("missing_outcome_edge")).toBeInTheDocument();
expect(within(result).getByText("nodes[analyze]")).toBeInTheDocument();
expect(within(result).getByText(/missing edges for outcomes.*ok/i)).toBeInTheDocument();
expect(within(result).getByRole("note", { name: /prepared fault injection/i })).toHaveTextContent(
"wf draft remove-route lda_report_workflow --revision 2 --step analyze --outcome ok",
expect(within(result).queryByRole("note", { name: /prepared fault injection/i })).not.toBeInTheDocument();
expect(screen.getByRole("note", { name: /prepared fault injection/i })).toHaveTextContent(
/valid revision 2.*remove analyze\.ok.*invalid revision 3/i,
);
expect(screen.queryByText(/missing output projection/i)).not.toBeInTheDocument();
}, 15000);
@@ -76,9 +76,7 @@ describe("AuthoringPhaseVisual", () => {
expect(within(result).getByText(/missing edges for outcomes.*ok/i)).toBeInTheDocument();
expect(within(result).getByText(/cannot prove where execution goes next/i)).toBeInTheDocument();
expect(within(result).getByText("Revision 3")).toBeInTheDocument();
expect(within(result).getByRole("note", { name: /prepared fault injection/i })).toHaveTextContent(
"wf draft remove-route lda_report_workflow --revision 2 --step analyze --outcome ok",
);
expect(within(result).queryByRole("note", { name: /prepared fault injection/i })).not.toBeInTheDocument();
});
it("renders route repair as a valid revision with compact prior context", () => {
@@ -173,11 +173,6 @@ const DiagnosticResult = ({ evidence }: { readonly evidence: EvidenceOf<"diagnos
]}
/>
<p className="authoring-result__explanation">{evidence.diagnostic.explanation}</p>
<section className="authoring-result__fault-injection" role="note" aria-label={evidence.faultInjection.label}>
<span>{evidence.faultInjection.label}</span>
<code>{evidence.faultInjection.command}</code>
<p>Valid revision {evidence.faultInjection.fromRevision} became invalid revision {evidence.faultInjection.toRevision}.</p>
</section>
</>
)}
/>
@@ -36,9 +36,7 @@ describe("AuthoringWorkflowDiagram", () => {
it("shows an absent analyze.ok route as the diagnostic headline", () => {
renderDiagram("diagnose");
expect(screen.getByText("Missing route")).toBeInTheDocument();
expect(screen.getByRole("img", { name: /analyze ok route is missing/i })).toBeInTheDocument();
expect(document.querySelector('[data-authoring-edge-id="analyze.ok"]')).not.toBeInTheDocument();
});
it("restores analyze.ok without retaining the missing-route marker", () => {
@@ -46,6 +44,15 @@ describe("AuthoringWorkflowDiagram", () => {
expect(screen.getByRole("img", { name: /analyze ok route restored/i })).toBeInTheDocument();
expect(screen.queryByText("Missing route")).not.toBeInTheDocument();
expect(document.querySelector('[data-authoring-edge-id="analyze.ok"]')).toBeInTheDocument();
});
it("reserves a wider rank for the route-state edge label", () => {
renderDiagram("repair");
const analyze = document.querySelector('[data-id="analyze"]');
const end = document.querySelector('[data-id="__end__"]');
expect(analyze).toHaveStyle({ width: "224px" });
expect(end).toHaveStyle({ width: "224px" });
expect(analyze?.getAttribute("style")).not.toEqual(end?.getAttribute("style"));
});
});
@@ -7,9 +7,12 @@ import {
type Node,
type NodeProps,
type NodeTypes,
type ReactFlowInstance,
} from "@xyflow/react";
import "@xyflow/react/dist/style.css";
import { CircleStop, Database, SquareFunction, TriangleAlert } from "lucide-react";
import Dagre from "@dagrejs/dagre";
import { CircleStop, Database, SquareFunction } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import type { ReviewedAuthoringEvidence } from "./reviewed-authoring-evidence.js";
type WorkflowEvidence = Extract<
@@ -49,28 +52,56 @@ const AuthoringFlowNode = ({ id, data }: NodeProps<Node<AuthoringNodeData>>) =>
const nodeTypes: NodeTypes = { authoring: AuthoringFlowNode };
// Stable positions make Draft -> Diagnose -> Repair read as one changing object.
const workflowNodes: Node<AuthoringNodeData>[] = [
const NODE_WIDTH = 224;
const NODE_HEIGHT = 76;
const FIT_VIEW_OPTIONS = { padding: 0.16, minZoom: 0.55, maxZoom: 1.25, duration: 0 } as const;
const workflowNodeDefinitions: Omit<Node<AuthoringNodeData>, "position">[] = [
{
id: "read_documents",
type: "authoring",
position: { x: 0, y: 60 },
style: { width: NODE_WIDTH },
data: { label: "read_documents", role: "source" },
},
{
id: "analyze",
type: "authoring",
position: { x: 310, y: 60 },
style: { width: NODE_WIDTH },
data: { label: "analyze", role: "action" },
},
{
id: "__end__",
type: "authoring",
position: { x: 620, y: 60 },
style: { width: NODE_WIDTH },
data: { label: "END", role: "outcome" },
},
];
const layoutWorkflowNodes = (): Node<AuthoringNodeData>[] => {
const graph = new Dagre.graphlib.Graph();
graph.setGraph({ rankdir: "LR", ranksep: 72, nodesep: 48, marginx: 0, marginy: 0 });
graph.setDefaultEdgeLabel(() => ({}));
for (const node of workflowNodeDefinitions) {
graph.setNode(node.id, { width: NODE_WIDTH, height: NODE_HEIGHT });
}
graph.setEdge("read_documents", "analyze", { width: 28, height: 22 });
// Reserve the widest label once so all three beats retain identical node positions.
graph.setEdge("analyze", "__end__", { width: 154, height: 24 });
Dagre.layout(graph);
return workflowNodeDefinitions.map((node) => {
const position = graph.node(node.id);
return {
...node,
position: {
x: position.x - NODE_WIDTH / 2,
y: position.y - NODE_HEIGHT / 2,
},
};
});
};
const workflowNodes = layoutWorkflowNodes();
const edgesForMode = (mode: AuthoringWorkflowMode): Edge[] => [
{
id: "read_documents.ok",
@@ -79,18 +110,13 @@ const edgesForMode = (mode: AuthoringWorkflowMode): Edge[] => [
label: "ok",
className: "authoring-workflow-diagram__edge",
},
...(mode === "diagnostic"
? []
: [
{
id: "analyze.ok",
source: "analyze",
target: "__end__",
label: mode === "repair" ? "restored · ok" : "ok",
animated: mode === "repair",
className: `authoring-workflow-diagram__edge${mode === "repair" ? " authoring-workflow-diagram__edge--restored" : ""}`,
label: mode === "diagnostic" ? "Missing route · ok" : mode === "repair" ? "Route restored · ok" : "ok",
className: `authoring-workflow-diagram__edge${mode === "diagnostic" ? " authoring-workflow-diagram__edge--missing" : ""}${mode === "repair" ? " authoring-workflow-diagram__edge--restored" : ""}`,
},
]),
];
const accessibleLabelForMode = (mode: AuthoringWorkflowMode): string => {
@@ -99,8 +125,30 @@ const accessibleLabelForMode = (mode: AuthoringWorkflowMode): string => {
return "Authoring workflow diagram: valid draft routes";
};
const AuthoringWorkflowDiagramInner = ({ mode }: AuthoringWorkflowDiagramProps) => (
const AuthoringWorkflowDiagramInner = ({ mode }: AuthoringWorkflowDiagramProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const [flow, setFlow] = useState<ReactFlowInstance<Node<AuthoringNodeData>, Edge>>();
useEffect(() => {
if (!flow) return undefined;
let frame = window.requestAnimationFrame(() => void flow.fitView(FIT_VIEW_OPTIONS));
// Scene columns resize without remounting React Flow, so fit the settled box again.
const observer = typeof ResizeObserver === "undefined" || !containerRef.current
? undefined
: new ResizeObserver(() => {
window.cancelAnimationFrame(frame);
frame = window.requestAnimationFrame(() => void flow.fitView(FIT_VIEW_OPTIONS));
});
if (containerRef.current) observer?.observe(containerRef.current);
return () => {
window.cancelAnimationFrame(frame);
observer?.disconnect();
};
}, [flow, mode]);
return (
<div
ref={containerRef}
className="authoring-workflow-diagram"
role="img"
aria-label={accessibleLabelForMode(mode)}
@@ -110,8 +158,9 @@ const AuthoringWorkflowDiagramInner = ({ mode }: AuthoringWorkflowDiagramProps)
nodes={workflowNodes}
edges={edgesForMode(mode)}
nodeTypes={nodeTypes}
onInit={setFlow}
fitView
fitViewOptions={{ padding: 0.16, minZoom: 0.7, maxZoom: 1.25 }}
fitViewOptions={FIT_VIEW_OPTIONS}
nodesDraggable={false}
nodesConnectable={false}
elementsSelectable={false}
@@ -121,24 +170,9 @@ const AuthoringWorkflowDiagramInner = ({ mode }: AuthoringWorkflowDiagramProps)
preventScrolling={false}
proOptions={{ hideAttribution: true }}
/>
<div className="authoring-workflow-diagram__route-state" aria-hidden="true">
{mode === "diagnostic" ? (
<span className="authoring-workflow-diagram__missing-route">
<TriangleAlert />
<strong>Missing route</strong>
<small>analyze · ok</small>
</span>
) : (
<span
data-authoring-edge-id="analyze.ok"
data-route-state={mode === "repair" ? "restored" : "present"}
>
{mode === "repair" ? "Route restored" : "Complete route"}
</span>
)}
</div>
</div>
);
};
/** Renders the same authored workflow while its route state changes between beats. */
export const AuthoringWorkflowDiagram = (props: AuthoringWorkflowDiagramProps) => (
@@ -52,6 +52,12 @@ describe("PreparedAuthoringLifecycleScene", () => {
expect(frame).toHaveTextContent("missing_outcome_edge");
expect(frame).toHaveTextContent("nodes[analyze]");
expect(frame).toHaveTextContent(/missing edges for outcomes.*ok/i);
const setup = within(frame).getByRole("note", { name: /prepared fault injection/i });
expect(setup).toHaveTextContent(/valid revision 2/i);
expect(setup).toHaveTextContent(/remove analyze\.ok/i);
expect(setup).toHaveTextContent(/invalid revision 3/i);
expect(within(screen.getByRole("region", { name: "draft validation diagnostic" }))
.queryByRole("note", { name: /prepared fault injection/i })).not.toBeInTheDocument();
expect(frame).toHaveAttribute("data-authoring-step", "diagnose");
expect(frame).toHaveAttribute("data-recording-phase", "validate");
expect(screen.getByRole("region", { name: "draft validation diagnostic" })).toHaveAttribute(
@@ -127,6 +127,24 @@ export const PreparedAuthoringLifecycleScene = ({ scene, beat, onAdvance, discus
</div>
</dl>
</header>
{projection.evidence.kind === "diagnostic" && (
<aside
className="prepared-lifecycle-scene__setup-strip"
role="note"
aria-label={projection.evidence.faultInjection.label}
>
<span>Prepared setup</span>
<strong>
Valid revision {projection.evidence.faultInjection.fromRevision}
{" → remove analyze.ok → "}
invalid revision {projection.evidence.faultInjection.toRevision}
</strong>
<details>
<summary>Exact command</summary>
<code>{projection.evidence.faultInjection.command}</code>
</details>
</aside>
)}
<AuthoringPhaseVisual projection={projection} />
</article>
</div>
@@ -138,33 +138,29 @@ describe("presentation.css", () => {
expect(editorialScene).not.toContain("0.76fr");
});
it("gives factual result roots a scrollable editorial hierarchy", () => {
it("gives lifecycle diagrams the primary editorial hierarchy", () => {
const result = cssBlocks(css, ".authoring-result")
.find((body) => body.includes("grid-template-rows: auto minmax(0, 1fr);"));
const diagnostic = cssBlocks(css, ".authoring-result--diagnostic .authoring-result__body")
.find((body) => body.includes("grid-template-columns: minmax(0, 1fr);"));
const primaryDiagnostic = cssBlocks(
css,
'.authoring-result--diagnostic [data-result-primary="true"]',
).find((body) => body.includes("min-width: 0;"));
const repair = cssBlocks(css, ".authoring-result--repair .authoring-result__body")
.find((body) => body.includes("gap: 1rem;"));
const repairCommand = cssBlocks(
css,
'.authoring-result--repair [data-result-primary="true"] .authoring-result__rows > div:first-child',
).find((body) => body.includes("border-block"));
const composition = cssBlock(css, ".authoring-result__composition");
const diagram = cssBlock(css, ".authoring-result__diagram");
const receipt = cssBlocks(css, ".authoring-result__receipt")
.find((body) => body.includes("display: flex;"));
const status = cssBlocks(css, ".authoring-result__header > strong")
.find((body) => body.includes("border: 1px solid"));
const diagnoseFrame = cssBlocks(css, '.prepared-lifecycle-scene__frame[data-authoring-step="diagnose"]')
.find((body) => body.includes("grid-template-rows: auto auto minmax(0, 1fr);"));
expect(result).toContain("min-height: 0;");
expect(result).toContain("overflow: auto;");
expect(diagnostic).toContain("grid-template-columns: minmax(0, 1fr);");
expect(primaryDiagnostic).toContain("min-width: 0;");
expect(repair).toContain("gap: 1rem;");
expect(repairCommand).toContain("border-block");
expect(composition).toContain("grid-template-rows: minmax(12rem, 1fr) auto;");
expect(composition).toContain("min-height: min-content;");
expect(diagram).toContain("min-height: 0;");
expect(diagram).toContain("overflow: hidden;");
expect(receipt).toContain("flex: 0 0 auto;");
expect(receipt).toMatch(/font-size:\s*clamp/);
expect(status).toContain("border: 1px solid");
expect(css).not.toContain(".authoring-repair__");
expect(css).not.toContain(".authoring-visual--repair");
expect(diagnoseFrame).toBeDefined();
expect(css).toContain(".authoring-workflow-diagram .react-flow__node");
});
it("keeps result evidence internally scrollable and stacks metadata on narrow canvases", () => {
@@ -172,14 +168,18 @@ describe("presentation.css", () => {
const narrowContainer = cssBlock(css, "@container presentation-canvas (max-width: 600px)") ?? "";
const compactResult = cssBlocks(compactContainer, ".authoring-result")
.find((body) => body.includes("overflow: auto;"));
const narrowBody = cssBlocks(narrowContainer, ".authoring-result__body")
.find((body) => body.includes("grid-template-columns: minmax(0, 1fr);"));
const compactComposition = cssBlocks(compactContainer, ".authoring-result__composition")
.find((body) => body.includes("grid-template-rows: minmax(12rem, 1fr) auto;"));
const narrowComposition = cssBlocks(narrowContainer, ".authoring-result__composition")
.find((body) => body.includes("grid-template-rows: minmax(16rem, 1fr) auto;"));
const narrowRows = cssBlocks(narrowContainer, ".authoring-result__rows")
.find((body) => body.includes("grid-template-columns: minmax(0, 1fr);"));
expect(compactResult).toContain("min-height: 0;");
expect(compactResult).toContain("overflow: auto;");
expect(narrowBody).toContain("grid-template-columns: minmax(0, 1fr);");
expect(compactComposition).toContain("min-height: min-content;");
expect(compactComposition).toContain("overflow: visible;");
expect(narrowComposition).toContain("min-width: 42rem;");
expect(narrowRows).toContain("grid-template-columns: minmax(0, 1fr);");
});
@@ -197,12 +197,11 @@ describe("presentation.css", () => {
it("gives the prepared lifecycle rail and operation frame the primary hierarchy", () => {
const frameRules = cssBlocks(css, '.prepared-lifecycle-scene[data-presentation-surface="editorial"] .prepared-lifecycle-scene__frame');
const editorialFrame = frameRules.find((body) => body.includes("background: var(--authoring-paper);"));
const frameContent = cssBlock(css, '.prepared-lifecycle-scene[data-presentation-surface="editorial"] .prepared-lifecycle-scene__frame > *');
expect(cssBlocks(css, '.prepared-lifecycle-scene[data-presentation-surface="editorial"] .prepared-lifecycle-scene__rail')
.some((body) => body.includes("grid-template-columns: repeat(6, minmax(0, 1fr));"))).toBe(true);
expect(cssBlocks(css, '.prepared-lifecycle-scene[data-presentation-surface="editorial"] .prepared-lifecycle-scene__rail')
.some((body) => body.includes("min-height: 5.4rem;"))).toBe(true);
.some((body) => body.includes("min-height: 4.4rem;"))).toBe(true);
expect(cssBlocks(css, '.prepared-lifecycle-scene[data-presentation-surface="editorial"] .prepared-lifecycle-scene__rail li')
.some((body) => body.includes("opacity: 0.78;"))).toBe(true);
expect(cssBlocks(css, ".prepared-lifecycle-scene__rail li")
@@ -211,7 +210,7 @@ describe("presentation.css", () => {
expect(frameRules.some((body) => body.includes("grid-template-rows: auto minmax(0, 1fr);"))).toBe(true);
expect(editorialFrame).toContain("border: 0;");
expect(editorialFrame).not.toContain("border-bottom:");
expect(frameContent).toContain("animation: authoring-frame-content-enter 220ms");
expect(css).not.toContain("authoring-frame-content-enter");
});
it("keeps the lifecycle rail scrollable at narrow presentation widths", () => {
@@ -3577,6 +3577,10 @@
overflow: auto;
}
.prepared-lifecycle-scene__frame[data-authoring-step="diagnose"] {
grid-template-rows: auto auto minmax(0, 1fr);
}
.prepared-lifecycle-scene__frame > .authoring-visual {
min-width: 0;
}
@@ -3796,6 +3800,10 @@
overflow: auto;
}
.prepared-lifecycle-scene[data-presentation-surface="editorial"] .prepared-lifecycle-scene__frame[data-authoring-step="diagnose"] {
grid-template-rows: auto auto minmax(0, 1fr);
}
.prepared-lifecycle-scene[data-presentation-surface="editorial"] .prepared-lifecycle-scene__frame-header {
border-color: var(--authoring-rule);
}
@@ -3929,6 +3937,10 @@
overflow: visible;
}
.prepared-lifecycle-scene[data-presentation-surface="editorial"] .prepared-lifecycle-scene__frame[data-authoring-step="diagnose"] {
grid-template-rows: auto auto auto;
}
.prepared-lifecycle-scene[data-presentation-surface="editorial"] .prepared-lifecycle-scene__presentation {
min-height: max-content;
overflow: visible;
@@ -3945,6 +3957,13 @@
overflow: auto;
}
/* Preserve the graph's authored height; the result root owns any vertical overflow. */
.prepared-lifecycle-scene[data-presentation-surface="editorial"] .authoring-result__composition {
grid-template-rows: minmax(12rem, 1fr) auto;
min-height: min-content;
overflow: visible;
}
.prepared-lifecycle-scene[data-presentation-surface="editorial"] .authoring-result__rows {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
@@ -3996,6 +4015,10 @@
overflow: visible;
}
.prepared-lifecycle-scene[data-presentation-surface="editorial"] .prepared-lifecycle-scene__frame[data-authoring-step="diagnose"] {
grid-template-rows: auto auto max-content;
}
.prepared-lifecycle-scene[data-presentation-surface="editorial"] .authoring-result {
height: min(34rem, 56vh);
max-height: min(34rem, 56vh);
@@ -4017,6 +4040,11 @@
grid-template-columns: minmax(0, 1fr);
}
.prepared-lifecycle-scene[data-presentation-surface="editorial"] .authoring-result__composition {
grid-template-rows: minmax(16rem, 1fr) auto;
min-width: 42rem;
}
.prepared-lifecycle-scene[data-presentation-surface="editorial"] > .presentation-assistant-pane {
grid-area: assistant;
align-self: start;
@@ -4052,10 +4080,96 @@
min-height: 0;
height: 100%;
overflow: auto;
scrollbar-width: none;
padding: 0.2rem 0.1rem 0.4rem;
color: var(--authoring-ink, var(--text-primary));
}
.authoring-result::-webkit-scrollbar {
display: none;
}
.authoring-result__composition {
display: grid;
grid-template-rows: minmax(12rem, 1fr) auto;
gap: 0.65rem;
min-width: 0;
min-height: min-content;
overflow: visible;
}
.authoring-result__diagram {
min-width: 0;
min-height: 0;
overflow: hidden;
}
.prepared-lifecycle-scene__setup-strip {
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
align-items: center;
gap: 0.65rem;
padding: 0.42rem 0.6rem;
border-bottom: 1px solid var(--authoring-rule, var(--stage-line));
color: var(--authoring-muted, var(--text-secondary));
font: 650 0.72rem/1.25 var(--font-evidence);
}
.prepared-lifecycle-scene__setup-strip > span {
color: var(--accent-amber);
font-weight: 750;
text-transform: uppercase;
}
.prepared-lifecycle-scene__setup-strip > strong {
color: var(--authoring-ink, var(--text-primary));
}
.prepared-lifecycle-scene__setup-strip details {
position: relative;
}
.prepared-lifecycle-scene__setup-strip summary {
cursor: pointer;
white-space: nowrap;
}
.prepared-lifecycle-scene__setup-strip details[open] code {
position: absolute;
z-index: 8;
inset-inline-end: 0;
top: calc(100% + 0.4rem);
width: min(34rem, 70vw);
padding: 0.65rem;
border: 1px solid var(--authoring-rule, var(--stage-line));
background: var(--authoring-paper, white);
box-shadow: 0 0.7rem 1.4rem rgb(0 0 0 / 12%);
white-space: normal;
}
.authoring-result__receipt {
display: flex;
flex: 0 0 auto;
gap: 0.7rem;
min-width: 0;
padding-top: 0.55rem;
border-top: 1px solid var(--authoring-rule, var(--stage-line));
color: var(--authoring-muted, var(--text-secondary));
font-size: clamp(0.68rem, 0.8vw, 0.82rem);
}
.authoring-result__receipt > * {
flex: 1 1 auto;
min-width: 0;
}
.authoring-result__receipt > h3 {
flex: 0 0 auto;
margin: 0;
color: var(--authoring-ink, var(--text-primary));
font: 700 0.7rem/1.2 var(--font-evidence);
}
.authoring-result__header {
display: flex;
align-items: flex-start;
@@ -4244,73 +4358,268 @@
border-bottom: 1px solid var(--authoring-rule, var(--stage-line));
}
.authoring-result--inventory .authoring-result__body,
.authoring-result--artifact .authoring-result__body,
.authoring-result--deployment .authoring-result__body {
grid-template-columns: minmax(0, 1fr) minmax(13rem, 0.9fr);
}
.authoring-result--inventory .authoring-result__rows,
.authoring-result--artifact .authoring-result__rows,
.authoring-result--deployment .authoring-result__rows {
grid-column: 1 / -1;
}
.authoring-result--inventory .authoring-result__list-section,
.authoring-result--artifact .authoring-result__list-section,
.authoring-result--deployment .authoring-result__list-section {
grid-column: 1;
}
.authoring-result--inventory .authoring-result__contract {
grid-column: 2;
}
.authoring-result--draft .authoring-result__body {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.authoring-result--draft .authoring-result__rows {
grid-column: 1 / -1;
}
.authoring-result--diagnostic .authoring-result__body {
grid-template-columns: minmax(0, 1fr);
max-width: 54rem;
}
.authoring-result--diagnostic {
grid-template-rows: auto auto auto;
align-content: start;
}
.authoring-result--diagnostic [data-result-primary="true"] {
min-width: 0;
padding-inline-start: 0.9rem;
border-inline-start: 3px solid var(--accent-amber);
}
.authoring-result--repair .authoring-result__body {
grid-template-columns: minmax(0, 1fr);
gap: 1rem;
max-width: 58rem;
}
.authoring-result--repair [data-result-primary="true"] {
.authoring-result--repair .authoring-result__receipt {
display: grid;
gap: 1rem;
min-width: 0;
padding-inline-start: 0.9rem;
border-inline-start: 3px solid var(--success);
grid-template-columns: minmax(10rem, 0.48fr) minmax(0, 1.52fr);
}
.authoring-result--repair .authoring-result__prior {
display: grid;
align-content: start;
gap: 0.2rem;
padding: 0.35rem 0.75rem 0.35rem 0;
border-right: 1px solid var(--authoring-rule, var(--stage-line));
border-bottom: 0;
overflow-wrap: anywhere;
}
.authoring-result--repair .authoring-result__prior code {
white-space: normal;
}
.authoring-result--repair [data-result-primary="true"] .authoring-result__rows {
grid-template-columns: repeat(2, minmax(0, 1fr));
grid-template-columns: minmax(12rem, 1.4fr) repeat(2, minmax(6rem, 0.6fr));
}
.authoring-result--repair [data-result-primary="true"] .authoring-result__rows > div:first-child {
grid-column: 1 / -1;
padding: 0.8rem 0.9rem;
border-block: 1px solid var(--authoring-rule, var(--stage-line));
background: color-mix(in oklch, var(--authoring-paper, white) 92%, var(--success));
.authoring-workflow-diagram {
position: relative;
min-width: 42rem;
min-height: 12rem;
height: 100%;
overflow: hidden;
}
.authoring-workflow-diagram .react-flow__pane {
cursor: default;
}
.authoring-workflow-diagram .react-flow__node {
width: 14rem;
}
.authoring-workflow-diagram__node {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
align-items: center;
gap: 0.65rem;
width: 100%;
min-height: 4.8rem;
padding: 0.8rem 0.9rem;
border: 2px solid var(--authoring-ink, var(--text-primary));
border-radius: 0.55rem;
background: var(--authoring-paper, white);
color: var(--authoring-ink, var(--text-primary));
}
.authoring-workflow-diagram__node[data-node-role="outcome"] {
grid-template-columns: minmax(0, 1fr);
justify-items: center;
border-radius: 999px;
}
.authoring-workflow-diagram__node > svg {
width: 1.35rem;
height: 1.35rem;
}
.authoring-workflow-diagram__node strong {
overflow-wrap: anywhere;
font: 700 clamp(0.95rem, 1.5vw, 1.3rem)/1.1 var(--font-evidence);
}
.authoring-workflow-diagram__node[data-node-role="source"] strong {
font-size: clamp(0.82rem, 1.15vw, 1rem);
white-space: nowrap;
}
.authoring-workflow-diagram .react-flow__handle {
width: 0.62rem;
height: 0.62rem;
border: 2px solid var(--authoring-paper, white);
background: var(--authoring-ink, var(--text-primary));
}
.authoring-workflow-diagram .react-flow__edge-path {
stroke: var(--authoring-ink, var(--text-primary));
stroke-width: 2.4;
}
.authoring-workflow-diagram .react-flow__edge-text {
fill: var(--authoring-ink, var(--text-primary));
font: 700 0.78rem var(--font-evidence);
}
.authoring-workflow-diagram .react-flow__edge-textbg {
fill: var(--authoring-paper, white);
fill-opacity: 1;
}
.authoring-workflow-diagram__edge--restored .react-flow__edge-path {
stroke: var(--success);
stroke-width: 3;
}
.authoring-workflow-diagram__edge--missing .react-flow__edge-path {
stroke: var(--accent-amber);
stroke-width: 2.8;
stroke-dasharray: 7 6;
}
.authoring-workflow-diagram__edge--missing .react-flow__edge-text,
.authoring-workflow-diagram__edge--restored .react-flow__edge-text {
font-size: 0.88rem;
}
.authoring-lifecycle-diagram {
display: grid;
grid-template-columns: minmax(13rem, 0.9fr) minmax(5rem, auto) minmax(16rem, 1.1fr);
align-items: center;
gap: clamp(1rem, 2vw, 2rem);
min-width: 42rem;
min-height: 12rem;
height: 100%;
color: var(--authoring-ink, var(--text-primary));
}
.authoring-lifecycle-diagram__flow {
display: grid;
justify-items: center;
gap: 0.4rem;
color: var(--authoring-muted, var(--text-secondary));
font: 700 0.72rem/1 var(--font-evidence);
}
.authoring-lifecycle-diagram__flow > svg {
width: 2rem;
height: 2rem;
}
.authoring-lifecycle-diagram__source-stack,
.authoring-lifecycle-diagram__bindings {
display: grid;
gap: 0.55rem;
}
.authoring-lifecycle-diagram__source,
.authoring-lifecycle-diagram__binding {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
align-items: center;
gap: 0.55rem;
padding: 0.7rem 0.8rem;
border-bottom: 1px solid var(--authoring-rule, var(--stage-line));
}
.authoring-lifecycle-diagram__binding {
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
}
.authoring-lifecycle-diagram__source code,
.authoring-lifecycle-diagram__binding code {
overflow-wrap: anywhere;
}
.authoring-lifecycle-diagram__capability,
.authoring-lifecycle-diagram__draft-shape,
.authoring-lifecycle-diagram__artifact-object,
.authoring-lifecycle-diagram__deployment-object {
display: grid;
align-content: center;
gap: 0.55rem;
min-width: 0;
min-height: 9rem;
padding: 1rem 1.1rem;
border: 2px solid var(--authoring-ink, var(--text-primary));
border-radius: 0.65rem;
}
.authoring-lifecycle-diagram__capability > svg,
.authoring-lifecycle-diagram__draft-shape > svg,
.authoring-lifecycle-diagram__artifact-object > svg,
.authoring-lifecycle-diagram__deployment-object > svg {
width: 1.6rem;
height: 1.6rem;
}
.authoring-lifecycle-diagram__capability > span,
.authoring-lifecycle-diagram__draft-shape > span,
.authoring-lifecycle-diagram__artifact-object > span,
.authoring-lifecycle-diagram__deployment-object > span {
color: var(--authoring-muted, var(--text-secondary));
font: 700 0.72rem/1.1 var(--font-evidence);
text-transform: uppercase;
}
.authoring-lifecycle-diagram__capability > strong,
.authoring-lifecycle-diagram__artifact-object > strong,
.authoring-lifecycle-diagram__deployment-object > strong {
overflow-wrap: anywhere;
font: 700 clamp(1rem, 1.7vw, 1.45rem)/1.1 var(--font-display);
}
.authoring-lifecycle-diagram__capability dl {
display: flex;
flex-wrap: wrap;
gap: 0.45rem 1rem;
margin: 0;
}
.authoring-lifecycle-diagram__capability dl > div {
display: flex;
gap: 0.35rem;
}
.authoring-lifecycle-diagram__capability dt {
color: var(--authoring-muted, var(--text-secondary));
}
.authoring-lifecycle-diagram__capability dd {
margin: 0;
font-weight: 700;
}
.authoring-lifecycle-diagram__draft-shape > div {
display: flex;
align-items: center;
gap: 0.35rem;
}
.authoring-lifecycle-diagram__draft-shape code:not(:last-child)::after {
content: " →";
color: var(--authoring-muted, var(--text-secondary));
}
.authoring-lifecycle-diagram__artifact-object {
border-color: var(--success);
}
.authoring-lifecycle-diagram__artifact-object > b,
.authoring-lifecycle-diagram__deployment-object > b {
color: var(--success);
font: 700 0.82rem/1.1 var(--font-evidence);
text-transform: uppercase;
}
.authoring-lifecycle-diagram__requirements {
display: flex;
flex-wrap: wrap;
gap: 0.35rem;
}
.authoring-lifecycle-diagram__requirements code {
padding: 0.22rem 0.38rem;
border: 1px solid var(--authoring-rule, var(--stage-line));
}
.authoring-lifecycle-diagram--deployment {
grid-template-columns: minmax(21rem, 1.1fr) minmax(4rem, auto) minmax(18rem, 0.9fr);
}
.authoring-lifecycle-diagram__binding-headings {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
gap: 2.5rem;
color: var(--authoring-muted, var(--text-secondary));
font: 700 0.68rem/1.1 var(--font-evidence);
text-transform: uppercase;
}