feat: strengthen authoring and lifecycle visual hierarchy
This commit is contained in:
@@ -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", () => {
|
it("shows immutable artifact identity and version", () => {
|
||||||
render(<AuthoringPhaseVisual projection={projectPreparedAuthoringPhase("artifact")} />);
|
render(<AuthoringPhaseVisual projection={projectPreparedAuthoringPhase("artifact")} />);
|
||||||
expect(screen.getByText("lda_report_case_study")).toBeInTheDocument();
|
expect(screen.getByText("lda_report_case_study")).toBeInTheDocument();
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ type AuthoringPhaseVisualProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const InventoryVisual = ({ visual }: { visual: Extract<AuthoringPhaseProjection["visual"], { kind: "inventory" }> }) => (
|
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">
|
<div className="authoring-inventory__sources">
|
||||||
{visual.sources.map((source) => (
|
{visual.sources.map((source) => (
|
||||||
<div key={source}><Database aria-hidden="true" /><code>{source}</code></div>
|
<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" }> }) => (
|
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__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__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>
|
<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"
|
className="authoring-visual authoring-visual--repair"
|
||||||
aria-label="validation repair evidence"
|
aria-label="validation repair evidence"
|
||||||
data-presentation-surface="editorial"
|
data-presentation-surface="editorial"
|
||||||
data-visual-role="authoring-phase"
|
data-visual-role="primary"
|
||||||
data-authoring-focus={focus}
|
data-authoring-focus={focus}
|
||||||
>
|
>
|
||||||
<div className="authoring-repair__diagnostic"><AlertTriangle aria-hidden="true" /><span>Diagnostic</span><strong>{visual.diagnostic}</strong></div>
|
<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" }> }) => (
|
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" />
|
<LockKeyhole aria-hidden="true" />
|
||||||
<div><span>Immutable workflow artifact</span><strong>{visual.artifactId}</strong></div>
|
<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>
|
<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" }> }) => (
|
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>
|
<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">
|
<div className="authoring-bindings__rows">
|
||||||
{visual.bindings.map((binding) => (
|
{visual.bindings.map((binding) => (
|
||||||
|
|||||||
+20
-1
@@ -73,14 +73,33 @@ describe("PreparedAuthoringLifecycleScene", () => {
|
|||||||
expect(workspace).toContainElement(visual);
|
expect(workspace).toContainElement(visual);
|
||||||
expect(assistant).toHaveAttribute("data-phase", "draft");
|
expect(assistant).toHaveAttribute("data-phase", "draft");
|
||||||
expect(assistant).toHaveAttribute("data-surface", "prepared-replay");
|
expect(assistant).toHaveAttribute("data-surface", "prepared-replay");
|
||||||
|
expect(assistant).toHaveAttribute("data-visual-role", "support");
|
||||||
expect(assistant.querySelector('[data-surface="stage"]')).toBeInTheDocument();
|
expect(assistant.querySelector('[data-surface="stage"]')).toBeInTheDocument();
|
||||||
expect(workspace.querySelector(".prepared-lifecycle-scene__dock")).not.toBeInTheDocument();
|
expect(workspace.querySelector(".prepared-lifecycle-scene__dock")).not.toBeInTheDocument();
|
||||||
expect(workspace.querySelector("[role='dialog']")).not.toBeInTheDocument();
|
expect(workspace.querySelector("[role='dialog']")).not.toBeInTheDocument();
|
||||||
expect(workspace.querySelector("[aria-label='authoring phase rail']"))
|
expect(workspace.querySelector("[aria-label='authoring phase rail']"))
|
||||||
.toBeInTheDocument();
|
.toBeInTheDocument();
|
||||||
expect(visual).toHaveAttribute("data-presentation-surface", "editorial");
|
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.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", () => {
|
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 { projectPreparedAuthoringPhase } from "./authoring-projection.js";
|
||||||
import { AuthoringPhaseVisual } from "./AuthoringPhaseVisual.js";
|
import { AuthoringPhaseVisual } from "./AuthoringPhaseVisual.js";
|
||||||
import { PresentationAssistantPane } from "./PresentationAssistantPane.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.
|
// 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 beatId = phases.find((phase) => phase.id === beat.id)?.id ?? "discover";
|
||||||
const projection = projectPreparedAuthoringPhase(beatId);
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -49,6 +58,7 @@ export const PreparedAuthoringLifecycleScene = ({ scene, beat, onAdvance }: Prep
|
|||||||
</StageCaption>
|
</StageCaption>
|
||||||
<section
|
<section
|
||||||
className="prepared-lifecycle-scene"
|
className="prepared-lifecycle-scene"
|
||||||
|
ref={lifecycleRef}
|
||||||
aria-label="prepared workflow authoring lifecycle"
|
aria-label="prepared workflow authoring lifecycle"
|
||||||
data-active-phase={beatId}
|
data-active-phase={beatId}
|
||||||
data-primary-surface="authoring-phase"
|
data-primary-surface="authoring-phase"
|
||||||
@@ -85,6 +95,8 @@ export const PreparedAuthoringLifecycleScene = ({ scene, beat, onAdvance }: Prep
|
|||||||
|
|
||||||
<article
|
<article
|
||||||
className="prepared-lifecycle-scene__projection"
|
className="prepared-lifecycle-scene__projection"
|
||||||
|
role="region"
|
||||||
|
aria-label="active lifecycle evidence"
|
||||||
data-visual-role="lifecycle-primary"
|
data-visual-role="lifecycle-primary"
|
||||||
key={beatId}
|
key={beatId}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1711,6 +1711,7 @@
|
|||||||
.scene-body__authoring-evidence {
|
.scene-body__authoring-evidence {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto minmax(0, 1fr);
|
grid-template-rows: auto minmax(0, 1fr);
|
||||||
|
flex: 1 1 auto;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid var(--authoring-rule, color-mix(in oklch, var(--authoring-muted) 32%, transparent));
|
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 {
|
.scene-body__authoring-evidence .authoring-visual {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
height: auto;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1807,6 +1809,7 @@
|
|||||||
|
|
||||||
.scene-body__authoring-composition .scene-body__authoring-loop {
|
.scene-body__authoring-composition .scene-body__authoring-loop {
|
||||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||||
|
flex: 0 0 auto;
|
||||||
min-height: 6.4rem;
|
min-height: 6.4rem;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
padding-top: 0.55rem;
|
padding-top: 0.55rem;
|
||||||
@@ -3530,10 +3533,11 @@
|
|||||||
/* Scene 9 — persistent assistant beside the dominant phase projection. */
|
/* Scene 9 — persistent assistant beside the dominant phase projection. */
|
||||||
.prepared-lifecycle-scene {
|
.prepared-lifecycle-scene {
|
||||||
display: grid;
|
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;
|
gap: 0.55rem;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
height: 100%;
|
flex: 1 1 auto;
|
||||||
|
height: auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3697,7 +3701,7 @@
|
|||||||
--authoring-ink: var(--color-editorial-ink, oklch(0.19 0.015 65));
|
--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-muted: var(--color-editorial-muted, oklch(0.48 0.025 65));
|
||||||
--authoring-rule: color-mix(in oklch, var(--authoring-muted) 32%, transparent);
|
--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;
|
gap: 0.8rem;
|
||||||
background: var(--authoring-paper);
|
background: var(--authoring-paper);
|
||||||
color: var(--authoring-ink);
|
color: var(--authoring-ink);
|
||||||
@@ -3723,6 +3727,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.prepared-lifecycle-scene[data-presentation-surface="editorial"] .prepared-lifecycle-scene__projection {
|
.prepared-lifecycle-scene[data-presentation-surface="editorial"] .prepared-lifecycle-scene__projection {
|
||||||
|
display: flex;
|
||||||
|
min-height: 0;
|
||||||
|
flex-direction: column;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 1.35rem 1.4rem;
|
padding: 1.35rem 1.4rem;
|
||||||
border: 0;
|
border: 0;
|
||||||
@@ -3817,6 +3824,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@container presentation-canvas (max-width: 1050px) {
|
@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"] {
|
.problem-loop-scene[data-presentation-surface="editorial"] {
|
||||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
Reference in New Issue
Block a user