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}
>
@@ -1711,6 +1711,7 @@
.scene-body__authoring-evidence {
display: grid;
grid-template-rows: auto minmax(0, 1fr);
flex: 1 1 auto;
min-height: 0;
overflow: hidden;
border: 1px solid var(--authoring-rule, color-mix(in oklch, var(--authoring-muted) 32%, transparent));
@@ -1751,6 +1752,7 @@
.scene-body__authoring-evidence .authoring-visual {
min-height: 0;
height: auto;
overflow: auto;
}
@@ -1807,6 +1809,7 @@
.scene-body__authoring-composition .scene-body__authoring-loop {
grid-template-columns: repeat(5, minmax(0, 1fr));
flex: 0 0 auto;
min-height: 6.4rem;
margin-top: 0;
padding-top: 0.55rem;
@@ -3530,10 +3533,11 @@
/* Scene 9 — persistent assistant beside the dominant phase projection. */
.prepared-lifecycle-scene {
display: grid;
grid-template-columns: minmax(15rem, 0.35fr) minmax(0, 0.65fr);
grid-template-columns: minmax(12rem, 0.26fr) minmax(0, 0.74fr);
gap: 0.55rem;
min-height: 0;
height: 100%;
flex: 1 1 auto;
height: auto;
overflow: hidden;
}
@@ -3697,7 +3701,7 @@
--authoring-ink: var(--color-editorial-ink, oklch(0.19 0.015 65));
--authoring-muted: var(--color-editorial-muted, oklch(0.48 0.025 65));
--authoring-rule: color-mix(in oklch, var(--authoring-muted) 32%, transparent);
grid-template-columns: minmax(13rem, 0.3fr) minmax(0, 0.7fr);
grid-template-columns: minmax(12rem, 0.24fr) minmax(0, 0.76fr);
gap: 0.8rem;
background: var(--authoring-paper);
color: var(--authoring-ink);
@@ -3723,6 +3727,9 @@
}
.prepared-lifecycle-scene[data-presentation-surface="editorial"] .prepared-lifecycle-scene__projection {
display: flex;
min-height: 0;
flex-direction: column;
margin: 0;
padding: 1.35rem 1.4rem;
border: 0;
@@ -3817,6 +3824,19 @@
}
@container presentation-canvas (max-width: 1050px) {
.scene-body__authoring-composition .scene-body__authoring-evidence {
padding: 0.75rem 0.85rem;
}
.prepared-lifecycle-scene[data-presentation-surface="editorial"] {
grid-template-columns: minmax(11rem, 0.22fr) minmax(0, 0.78fr);
gap: 0.6rem;
}
.prepared-lifecycle-scene[data-presentation-surface="editorial"] .prepared-lifecycle-scene__projection {
padding: 0.9rem;
}
.problem-loop-scene[data-presentation-surface="editorial"] {
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
align-items: center;