feat: strengthen authoring and lifecycle visual hierarchy

This commit is contained in:
lda
2026-07-13 02:37:49 +07:00 Verified
parent 0027d496fb
commit 70f1d1e9cf
5 changed files with 69 additions and 10 deletions
@@ -51,6 +51,14 @@ describe("AuthoringPhaseVisual", () => {
);
});
it.each(["diagnose", "repair"] as const)("marks %s evidence as the primary visual", (focus) => {
render(<AuthoringPhaseVisual projection={projectPreparedAuthoringPhase("validate")} focus={focus} />);
expect(screen.getByRole("region", { name: /validation repair evidence/i })).toHaveAttribute(
"data-visual-role",
"primary",
);
});
it("shows immutable artifact identity and version", () => {
render(<AuthoringPhaseVisual projection={projectPreparedAuthoringPhase("artifact")} />);
expect(screen.getByText("lda_report_case_study")).toBeInTheDocument();
@@ -16,7 +16,7 @@ type AuthoringPhaseVisualProps = {
};
const InventoryVisual = ({ visual }: { visual: Extract<AuthoringPhaseProjection["visual"], { kind: "inventory" }> }) => (
<section className="authoring-visual authoring-visual--inventory" aria-label="discovery evidence" data-presentation-surface="editorial" data-visual-role="authoring-phase">
<section className="authoring-visual authoring-visual--inventory" aria-label="discovery evidence" data-presentation-surface="editorial" data-visual-role="primary">
<div className="authoring-inventory__sources">
{visual.sources.map((source) => (
<div key={source}><Database aria-hidden="true" /><code>{source}</code></div>
@@ -32,7 +32,7 @@ const InventoryVisual = ({ visual }: { visual: Extract<AuthoringPhaseProjection[
);
const GraphVisual = ({ visual }: { visual: Extract<AuthoringPhaseProjection["visual"], { kind: "graph" }> }) => (
<section className="authoring-visual authoring-visual--graph" aria-label="draft graph evidence" data-presentation-surface="editorial" data-visual-role="authoring-phase">
<section className="authoring-visual authoring-visual--graph" aria-label="draft graph evidence" data-presentation-surface="editorial" data-visual-role="primary">
<div className="authoring-graph__node"><Database aria-hidden="true" /><strong>{visual.nodes[0]}</strong></div>
<div className="authoring-graph__edge"><span>{visual.inputBinding}</span><ArrowRight aria-hidden="true" /></div>
<div className="authoring-graph__node"><Workflow aria-hidden="true" /><strong>{visual.nodes[1]}</strong></div>
@@ -52,7 +52,7 @@ const RepairVisual = ({
className="authoring-visual authoring-visual--repair"
aria-label="validation repair evidence"
data-presentation-surface="editorial"
data-visual-role="authoring-phase"
data-visual-role="primary"
data-authoring-focus={focus}
>
<div className="authoring-repair__diagnostic"><AlertTriangle aria-hidden="true" /><span>Diagnostic</span><strong>{visual.diagnostic}</strong></div>
@@ -63,7 +63,7 @@ const RepairVisual = ({
);
const ArtifactVisual = ({ visual }: { visual: Extract<AuthoringPhaseProjection["visual"], { kind: "artifact" }> }) => (
<section className="authoring-visual authoring-visual--artifact" aria-label="artifact evidence" data-presentation-surface="editorial" data-visual-role="authoring-phase">
<section className="authoring-visual authoring-visual--artifact" aria-label="artifact evidence" data-presentation-surface="editorial" data-visual-role="primary">
<LockKeyhole aria-hidden="true" />
<div><span>Immutable workflow artifact</span><strong>{visual.artifactId}</strong></div>
<dl><div><dt>Version</dt><dd>Version {visual.version}</dd></div><div><dt>Requirements</dt><dd>{visual.requiredSources} local sources</dd></div></dl>
@@ -71,7 +71,7 @@ const ArtifactVisual = ({ visual }: { visual: Extract<AuthoringPhaseProjection["
);
const BindingsVisual = ({ visual }: { visual: Extract<AuthoringPhaseProjection["visual"], { kind: "bindings" }> }) => (
<section className="authoring-visual authoring-visual--bindings" aria-label="deployment binding evidence" data-presentation-surface="editorial" data-visual-role="authoring-phase">
<section className="authoring-visual authoring-visual--bindings" aria-label="deployment binding evidence" data-presentation-surface="editorial" data-visual-role="primary">
<header><Link2 aria-hidden="true" /><div><span>Deployment</span><strong>{visual.deploymentId}</strong></div><b><CheckCircle2 aria-hidden="true" />{visual.status}</b></header>
<div className="authoring-bindings__rows">
{visual.bindings.map((binding) => (
@@ -73,14 +73,33 @@ describe("PreparedAuthoringLifecycleScene", () => {
expect(workspace).toContainElement(visual);
expect(assistant).toHaveAttribute("data-phase", "draft");
expect(assistant).toHaveAttribute("data-surface", "prepared-replay");
expect(assistant).toHaveAttribute("data-visual-role", "support");
expect(assistant.querySelector('[data-surface="stage"]')).toBeInTheDocument();
expect(workspace.querySelector(".prepared-lifecycle-scene__dock")).not.toBeInTheDocument();
expect(workspace.querySelector("[role='dialog']")).not.toBeInTheDocument();
expect(workspace.querySelector("[aria-label='authoring phase rail']"))
.toBeInTheDocument();
expect(visual).toHaveAttribute("data-presentation-surface", "editorial");
expect(visual).toHaveAttribute("data-visual-role", "authoring-phase");
expect(visual).toHaveAttribute("data-visual-role", "primary");
expect(workspace.querySelector('[data-visual-role="lifecycle-primary"]')).toBe(visual.parentElement);
expect(workspace.querySelectorAll('[data-visual-role="lifecycle-primary"]')).toHaveLength(1);
});
it.each([
["discover", "Discover"],
["draft", "Draft"],
["validate", "Validate"],
["artifact", "Artifact"],
["deployment", "Deployment"],
] as const)("marks %s as the active lifecycle evidence beat", (beatId, label) => {
renderBeat(beatId);
const evidence = screen.getByRole("region", { name: /active lifecycle evidence/i });
const workspace = screen.getByRole("region", { name: "prepared workflow authoring lifecycle" });
expect(evidence).toHaveAttribute("data-visual-role", "lifecycle-primary");
expect(workspace.querySelectorAll('[data-visual-role="lifecycle-primary"]')).toHaveLength(1);
expect(screen.getByLabelText("authoring phase rail").querySelector('[data-active="true"]'))
.toHaveTextContent(label);
});
it("synchronizes the assistant phase, active group, rail, and visual", () => {
@@ -1,4 +1,4 @@
import { useReducer } from "react";
import { useLayoutEffect, useReducer, useRef } from "react";
import { projectPreparedAuthoringPhase } from "./authoring-projection.js";
import { AuthoringPhaseVisual } from "./AuthoringPhaseVisual.js";
import { PresentationAssistantPane } from "./PresentationAssistantPane.js";
@@ -41,6 +41,15 @@ export const PreparedAuthoringLifecycleScene = ({ scene, beat, onAdvance }: Prep
// if a future beat reaches this scene before its authoring mapping is added.
const beatId = phases.find((phase) => phase.id === beat.id)?.id ?? "discover";
const projection = projectPreparedAuthoringPhase(beatId);
const lifecycleRef = useRef<HTMLElement>(null);
useLayoutEffect(() => {
// The assistant owns its aside markup; decorate that existing boundary here
// so the scene can expose hierarchy metadata without widening its API.
lifecycleRef.current
?.querySelector<HTMLElement>('[aria-label="prepared authoring assistant"]')
?.setAttribute("data-visual-role", "support");
}, [beatId]);
return (
<>
@@ -49,6 +58,7 @@ export const PreparedAuthoringLifecycleScene = ({ scene, beat, onAdvance }: Prep
</StageCaption>
<section
className="prepared-lifecycle-scene"
ref={lifecycleRef}
aria-label="prepared workflow authoring lifecycle"
data-active-phase={beatId}
data-primary-surface="authoring-phase"
@@ -85,6 +95,8 @@ export const PreparedAuthoringLifecycleScene = ({ scene, beat, onAdvance }: Prep
<article
className="prepared-lifecycle-scene__projection"
role="region"
aria-label="active lifecycle evidence"
data-visual-role="lifecycle-primary"
key={beatId}
>