fix: restore readable presentation graph language
This commit is contained in:
@@ -13,6 +13,7 @@ export type WorkflowGraphNodeData = {
|
||||
readonly nodeId: string;
|
||||
readonly kind: WorkflowGraphNodeKind;
|
||||
readonly label: string;
|
||||
readonly detail?: string | null;
|
||||
readonly nodeRef: string | null;
|
||||
readonly raw: Readonly<Record<string, unknown>>;
|
||||
readonly onSelect?: (nodeId: string) => void;
|
||||
@@ -144,6 +145,7 @@ export const buildWorkflowGraph = (
|
||||
nodeId: id,
|
||||
kind: mapNodeKind(node.type as string),
|
||||
label: buildLabel(node, layout.label),
|
||||
detail: typeof node.detail === "string" ? node.detail : null,
|
||||
nodeRef: (node.node as string | null) ?? null,
|
||||
raw: node as Record<string, unknown>,
|
||||
},
|
||||
|
||||
@@ -22,9 +22,9 @@ export const NodeSpotlight = ({ nodeId, close }: NodeSpotlightProps) => {
|
||||
const node = presentationNodes.find((candidate) => candidate.id === nodeId);
|
||||
const label = node && "label" in node
|
||||
? node.label
|
||||
: node?.id === "review_issues"
|
||||
: nodeId === "review_issues"
|
||||
? "Review issues"
|
||||
: node?.id ?? nodeId;
|
||||
: nodeId;
|
||||
|
||||
useEffect(() => {
|
||||
const previouslyFocused = document.activeElement instanceof HTMLElement
|
||||
|
||||
@@ -164,8 +164,8 @@ describe("PresentationRoute", () => {
|
||||
window.location.hash = "#scene/run-from-deployment/graph";
|
||||
const { PresentationRoute } = await import("./PresentationRoute.js");
|
||||
render(<PresentationRoute />);
|
||||
fireEvent.click(graphNodeByLabel(/issue review/i));
|
||||
expect(screen.getByRole("dialog", { name: /issue review/i })).toBeInTheDocument();
|
||||
fireEvent.click(graphNodeByLabel(/review issues/i));
|
||||
expect(screen.getByRole("dialog", { name: /review issues/i })).toBeInTheDocument();
|
||||
expect(screen.getByText("Workflow node")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
||||
@@ -380,6 +380,24 @@ describe("SceneBody", () => {
|
||||
expect(within(rail).getByRole("button", { name: /Hosted automation future-work/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps discussion rails out of workflow proof scenes", () => {
|
||||
const location: PresentationLocation = { kind: "main", sceneId: "run-from-deployment", beatId: "graph", focusPath: [] };
|
||||
render(
|
||||
<SceneBody
|
||||
location={location}
|
||||
demo={demo}
|
||||
selectedNodeId={null}
|
||||
selectNode={noop}
|
||||
openEvidence={noop}
|
||||
openDiscussion={noop}
|
||||
onFocusPathChange={noop}
|
||||
motionDisabled={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByLabelText("defense discussion topics")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps discussion rail actions wired to branch ids", async () => {
|
||||
const user = userEvent.setup();
|
||||
const location: PresentationLocation = { kind: "main", sceneId: "positioning", beatId: "landscape", focusPath: [] };
|
||||
|
||||
@@ -41,6 +41,13 @@ type SceneBodyProps = {
|
||||
readonly onScene9Advance?: (() => void) | undefined;
|
||||
};
|
||||
|
||||
const workflowDemoSceneIds = new Set([
|
||||
"prepared-lifecycle",
|
||||
"run-from-deployment",
|
||||
"typed-human-boundary",
|
||||
"resume-output-evidence",
|
||||
]);
|
||||
|
||||
const DiscussionLinks = ({
|
||||
sceneId,
|
||||
openDiscussion,
|
||||
@@ -315,9 +322,12 @@ export const SceneBody = ({ location, demo, timelineAgent, selectedNodeId, selec
|
||||
const scene = findScene(sceneId) ?? findScene("thesis")!;
|
||||
const beat = findBeat(sceneId, beatId) ?? scene.beats[0]!;
|
||||
|
||||
// The questions beat renders its own grouped discussion index. Suppress the
|
||||
// generic per-scene rail there so the same actions do not appear twice.
|
||||
const showDiscussionRail = !(scene.id === "conclusion" && beat.id === "questions");
|
||||
// The questions beat owns the full discussion index. Workflow demo scenes
|
||||
// keep the product proof uncluttered; their discussion topics remain
|
||||
// available from the global presenter controls.
|
||||
const isWorkflowDemoScene = workflowDemoSceneIds.has(scene.id);
|
||||
const showDiscussionRail = !(scene.id === "conclusion" && beat.id === "questions")
|
||||
&& !isWorkflowDemoScene;
|
||||
const discussionLinks = showDiscussionRail ? <DiscussionLinks sceneId={scene.id} openDiscussion={openDiscussion} /> : null;
|
||||
const content = (() => {
|
||||
switch (scene.view) {
|
||||
|
||||
@@ -49,7 +49,7 @@ describe("WorkflowGraphStage", () => {
|
||||
expect(graph).toHaveTextContent("Create issues");
|
||||
expect(graph).toHaveTextContent("Finalise");
|
||||
expect(graph).toHaveTextContent("Revision requested");
|
||||
expect(graph).toHaveTextContent("completed");
|
||||
expect(graph).toHaveTextContent("persisted run");
|
||||
expect(graph).not.toHaveTextContent("end_cancelled");
|
||||
expect(document.querySelectorAll(".workflow-graph-stage__node")).toHaveLength(10);
|
||||
expect(presentationWorkflowNodeIds).toEqual([
|
||||
@@ -98,8 +98,9 @@ describe("WorkflowGraphStage", () => {
|
||||
expect(screen.queryByText("Current")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Current interrupt")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Queued")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Completed")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Human boundary")).not.toBeInTheDocument();
|
||||
expect(screen.getByLabelText("workflow graph node types")).toHaveTextContent("Action");
|
||||
expect(screen.getByLabelText("workflow graph node types")).toHaveTextContent("Human boundary");
|
||||
expect(screen.getByLabelText("workflow graph node types")).toHaveTextContent("Outcome");
|
||||
});
|
||||
|
||||
it("keeps raw plan facts and canonical labeled edge order", () => {
|
||||
|
||||
@@ -47,14 +47,15 @@ const PresentationFlowNode = ({ data }: NodeProps<Node<PresentationNodeData>>) =
|
||||
data-kind={data.kind}
|
||||
data-selected={data.selected}
|
||||
aria-pressed={data.selected}
|
||||
aria-label={data.label}
|
||||
aria-label={`${data.label}${data.detail ? `, ${data.detail}` : ""}`}
|
||||
title={data.nodeRef ?? undefined}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
data.selectNode(data.nodeId);
|
||||
}}
|
||||
>
|
||||
<strong>{data.label}</strong>
|
||||
{data.nodeRef && <small>{data.nodeRef}</small>}
|
||||
<small>{data.detail ?? data.nodeRef ?? "workflow step"}</small>
|
||||
</button>
|
||||
<Handle type="source" position={Position.Right} />
|
||||
</>
|
||||
@@ -72,10 +73,10 @@ const WorkflowGraphStageInner = ({
|
||||
const model = useMemo(
|
||||
() => buildWorkflowGraph(presentationWorkflowPlan, {
|
||||
direction: "LR",
|
||||
nodeWidth: 190,
|
||||
nodeHeight: 72,
|
||||
nodesep: 55,
|
||||
ranksep: 100,
|
||||
nodeWidth: 150,
|
||||
nodeHeight: 64,
|
||||
nodesep: 35,
|
||||
ranksep: 80,
|
||||
label: (node) => {
|
||||
if (typeof node.label === "string") return node.label;
|
||||
if (node.id === "review_issues") return "Review issues";
|
||||
@@ -131,6 +132,11 @@ const WorkflowGraphStageInner = ({
|
||||
data-graph-variant={variant}
|
||||
data-graph-direction="horizontal"
|
||||
>
|
||||
<div className="workflow-graph-stage__legend" aria-label="workflow graph node types">
|
||||
<span data-kind="use"><i aria-hidden="true" />Action</span>
|
||||
<span data-kind="interrupt"><i aria-hidden="true" />Human boundary</span>
|
||||
<span data-kind="end"><i aria-hidden="true" />Outcome</span>
|
||||
</div>
|
||||
{variant === "full" && proof && (
|
||||
<div className="workflow-graph-stage__proof" aria-label="workflow graph proof">
|
||||
<span><b>Run</b><code>{proof.runId ?? "run unavailable"}</code></span>
|
||||
@@ -143,7 +149,7 @@ const WorkflowGraphStageInner = ({
|
||||
nodeTypes={nodeTypes}
|
||||
onNodeClick={handleNodeClick}
|
||||
fitView
|
||||
fitViewOptions={{ padding: 0.12, minZoom: 0.45, maxZoom: 1 }}
|
||||
fitViewOptions={{ padding: 0.02, minZoom: 0.5, maxZoom: 1 }}
|
||||
minZoom={0.25}
|
||||
maxZoom={1.5}
|
||||
nodesDraggable={false}
|
||||
|
||||
@@ -791,7 +791,7 @@
|
||||
|
||||
.workflow-graph-stage {
|
||||
position: relative;
|
||||
min-height: 24rem;
|
||||
min-height: 25rem;
|
||||
flex: 1;
|
||||
border: 1px solid oklch(0.3 0.035 250);
|
||||
border-radius: 0.7rem;
|
||||
@@ -802,46 +802,108 @@
|
||||
.workflow-graph-stage__node {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
min-height: 4.5rem;
|
||||
min-height: 4.2rem;
|
||||
gap: 0.35rem;
|
||||
padding: 0.7rem 0.8rem;
|
||||
border: 1px solid oklch(0.45 0.04 250);
|
||||
border-radius: 0.45rem;
|
||||
background: oklch(0.19 0.03 250);
|
||||
align-content: center;
|
||||
padding: 0.62rem 0.72rem;
|
||||
border: 1px solid oklch(0.48 0.04 250);
|
||||
border-radius: 0.55rem;
|
||||
background: oklch(0.17 0.03 250);
|
||||
color: var(--text-primary);
|
||||
text-align: left;
|
||||
transition: border-color 160ms ease, background 160ms ease, box-shadow 160ms ease;
|
||||
}
|
||||
|
||||
.workflow-graph-stage[data-graph-direction="horizontal"] .react-flow__node {
|
||||
width: 11.875rem;
|
||||
min-height: 4.5rem;
|
||||
width: 9.375rem;
|
||||
min-height: 4rem;
|
||||
}
|
||||
|
||||
.workflow-graph-stage__node[data-kind="use"] {
|
||||
border-color: oklch(0.62 0.12 195 / 0.78);
|
||||
background: oklch(0.16 0.035 220);
|
||||
}
|
||||
|
||||
.workflow-graph-stage__node[data-kind="interrupt"] {
|
||||
border-color: var(--accent-amber);
|
||||
border: 2px solid var(--accent-amber);
|
||||
border-radius: 1rem;
|
||||
background: oklch(0.19 0.04 70 / 0.22);
|
||||
}
|
||||
|
||||
.workflow-graph-stage__node[data-kind="end"] {
|
||||
border-color: oklch(0.5 0.025 250);
|
||||
min-width: 9.5rem;
|
||||
border-color: oklch(0.64 0.025 250);
|
||||
border-radius: 999px;
|
||||
background: oklch(0.16 0.02 250);
|
||||
}
|
||||
|
||||
.workflow-graph-stage__node[data-selected="true"] {
|
||||
outline: 2px solid var(--accent-cyan);
|
||||
outline-offset: 2px;
|
||||
box-shadow: 0 0 0 4px oklch(0.72 0.17 195 / 0.18);
|
||||
}
|
||||
|
||||
.workflow-graph-stage__node strong {
|
||||
color: var(--text-primary);
|
||||
font: 700 0.9rem/1.05 var(--font-display);
|
||||
}
|
||||
|
||||
.workflow-graph-stage__node[data-kind="interrupt"] strong {
|
||||
color: var(--accent-amber);
|
||||
}
|
||||
|
||||
.workflow-graph-stage__node small {
|
||||
color: var(--text-secondary);
|
||||
font: 0.62rem/1.25 var(--font-evidence);
|
||||
font: 600 0.64rem/1.25 var(--font-evidence);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.workflow-graph-stage__legend {
|
||||
position: absolute;
|
||||
top: 0.7rem;
|
||||
right: 0.8rem;
|
||||
z-index: 5;
|
||||
display: flex;
|
||||
gap: 0.85rem;
|
||||
color: var(--text-muted);
|
||||
font: 700 0.62rem/1 var(--font-evidence);
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.workflow-graph-stage__legend span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
.workflow-graph-stage__legend i {
|
||||
width: 0.42rem;
|
||||
height: 0.42rem;
|
||||
border-radius: 50%;
|
||||
background: oklch(0.62 0.12 195);
|
||||
}
|
||||
|
||||
.workflow-graph-stage__legend [data-kind="interrupt"] i {
|
||||
background: var(--accent-amber);
|
||||
}
|
||||
|
||||
.workflow-graph-stage__legend [data-kind="end"] i {
|
||||
background: oklch(0.68 0.025 250);
|
||||
}
|
||||
|
||||
.workflow-graph-stage .react-flow__edge-text {
|
||||
paint-order: stroke;
|
||||
stroke: oklch(0.13 0.025 250);
|
||||
stroke-width: 0.4rem;
|
||||
stroke-linejoin: round;
|
||||
fill: var(--text-primary);
|
||||
font: 700 0.68rem/1 var(--font-evidence);
|
||||
font: 700 0.78rem/1 var(--font-evidence);
|
||||
}
|
||||
|
||||
.workflow-graph-stage .react-flow__edge-textbg {
|
||||
fill: oklch(0.13 0.025 250);
|
||||
stroke: oklch(0.13 0.025 250);
|
||||
}
|
||||
|
||||
.workflow-graph-stage .react-flow__edge-path {
|
||||
@@ -1847,7 +1909,7 @@
|
||||
}
|
||||
|
||||
.workflow-graph-stage {
|
||||
min-height: 10rem;
|
||||
min-height: 22rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
*/
|
||||
export const presentationWorkflowPlan = {
|
||||
nodes: [
|
||||
{ id: "reset_board", type: "node", node: "local.issue_board.reset_issue_board", label: "Reset issue board" },
|
||||
{ id: "read_docs", type: "node", node: "local.lda_docs.read_documents", label: "Read documents" },
|
||||
{ id: "analyze", type: "node", node: "local.lda_report.analyze_documents", label: "Analyze documents" },
|
||||
{ id: "build_report", type: "node", node: "local.lda_report.build_report", label: "Build report" },
|
||||
{ id: "draft_issues", type: "node", node: "local.lda_report.create_issue_drafts", label: "Draft issues" },
|
||||
{ id: "review_issues", type: "interrupt", kind: "issue_review" },
|
||||
{ id: "create_issues", type: "node", node: "local.issue_board.create_issues", label: "Create issues" },
|
||||
{ id: "finalise", type: "node", node: "local.lda_report.finalise_report", label: "Finalise report" },
|
||||
{ id: "revision_requested", type: "node", node: "local.lda_report.record_revision_request", label: "Revision requested" },
|
||||
{ id: "end_completed", type: "end", outcome: "completed" },
|
||||
{ id: "reset_board", type: "node", node: "local.issue_board.reset_issue_board", label: "Reset issue board", detail: "issue board source" },
|
||||
{ id: "read_docs", type: "node", node: "local.lda_docs.read_documents", label: "Read documents", detail: "document source" },
|
||||
{ id: "analyze", type: "node", node: "local.lda_report.analyze_documents", label: "Analyze documents", detail: "report source" },
|
||||
{ id: "build_report", type: "node", node: "local.lda_report.build_report", label: "Build report", detail: "Markdown output" },
|
||||
{ id: "draft_issues", type: "node", node: "local.lda_report.create_issue_drafts", label: "Draft issues", detail: "issue proposals" },
|
||||
{ id: "review_issues", type: "interrupt", kind: "issue_review", label: "Review issues", detail: "typed human boundary" },
|
||||
{ id: "create_issues", type: "node", node: "local.issue_board.create_issues", label: "Create issues", detail: "selected issues only" },
|
||||
{ id: "finalise", type: "node", node: "local.lda_report.finalise_report", label: "Finalise report", detail: "state output" },
|
||||
{ id: "revision_requested", type: "node", node: "local.lda_report.record_revision_request", label: "Revision requested", detail: "operator branch" },
|
||||
{ id: "end_completed", type: "end", outcome: "completed", label: "Completed", detail: "persisted run" },
|
||||
],
|
||||
edges: [
|
||||
{ from: "reset_board", to: "read_docs", outcome: "ok" },
|
||||
|
||||
Reference in New Issue
Block a user