feat: strengthen evaluation and conclusion visuals

This commit is contained in:
lda
2026-07-13 03:29:37 +07:00 Verified
parent f6a8bb1b36
commit 8679d96123
5 changed files with 145 additions and 39 deletions
@@ -76,9 +76,18 @@ describe("ConclusionScene", () => {
it("states the closing boundary and recedes future branches", () => { it("states the closing boundary and recedes future branches", () => {
render(<ConclusionScene scene={scene} beat={beat("conclusion")} />); render(<ConclusionScene scene={scene} beat={beat("conclusion")} />);
const map = screen.getByRole("region", { name: "thesis contribution boundary" });
const substrate = map.querySelector('[data-node-id="substrate"]');
const nonClaims = screen.getByRole("list", { name: "explicit non-claims" });
const future = screen.getByRole("list", { name: "future work layers" });
expect(map).toHaveAttribute("data-conclusion-focus", "conclusion");
expect(substrate).toHaveAttribute("data-contribution-focus", "primary");
expect(screen.getByText("Planner proposes; runtime executes.")).toBeInTheDocument(); expect(screen.getByText("Planner proposes; runtime executes.")).toBeInTheDocument();
expect(screen.getByRole("list", { name: "future work layers" })).toHaveAttribute("data-state", "receded"); expect(screen.getByText("Planner proposes; runtime executes.")).toHaveAttribute("data-contribution-focus", "primary");
expect(screen.getByText("Planner proposes; runtime executes.")).toHaveAttribute("data-conclusion-support", "primary"); expect(nonClaims).toHaveAttribute("data-conclusion-support", "receded");
expect(future).toHaveAttribute("data-state", "receded");
expect(future).toHaveAttribute("data-conclusion-support", "receded");
}); });
it("attaches evidence vertically beneath the typed substrate rather than extending the contribution line", () => { it("attaches evidence vertically beneath the typed substrate rather than extending the contribution line", () => {
@@ -37,6 +37,7 @@ export const ConclusionScene: FC<ConclusionSceneProps> = ({ scene, beat }) => {
className="conclusion-map" className="conclusion-map"
aria-label="thesis contribution boundary" aria-label="thesis contribution boundary"
data-conclusion-beat={beatId} data-conclusion-beat={beatId}
data-conclusion-focus={beatId}
data-visual-role="contribution-boundary" data-visual-role="contribution-boundary"
data-presentation-surface="editorial" data-presentation-surface="editorial"
> >
@@ -56,6 +57,7 @@ export const ConclusionScene: FC<ConclusionSceneProps> = ({ scene, beat }) => {
className="conclusion-map__node conclusion-map__node--substrate" className="conclusion-map__node conclusion-map__node--substrate"
data-node-id={contributionNodes[1].id} data-node-id={contributionNodes[1].id}
data-emphasis="substrate" data-emphasis="substrate"
data-contribution-focus={beatId === "conclusion" ? "primary" : "support"}
> >
<span className="conclusion-map__node-index">02</span> <span className="conclusion-map__node-index">02</span>
<strong>{contributionNodes[1].label}</strong> <strong>{contributionNodes[1].label}</strong>
@@ -87,6 +89,7 @@ export const ConclusionScene: FC<ConclusionSceneProps> = ({ scene, beat }) => {
aria-label="explicit non-claims" aria-label="explicit non-claims"
data-emphasis={beatId === "limits" ? "limits" : "neutral"} data-emphasis={beatId === "limits" ? "limits" : "neutral"}
data-conclusion-support={beatId === "limits" ? "primary" : "receded"} data-conclusion-support={beatId === "limits" ? "primary" : "receded"}
data-state={beatId === "limits" ? "primary" : "receded"}
> >
{nonClaims.map((claim) => <li key={claim}>{claim}</li>)} {nonClaims.map((claim) => <li key={claim}>{claim}</li>)}
</ul> </ul>
@@ -111,7 +114,13 @@ export const ConclusionScene: FC<ConclusionSceneProps> = ({ scene, beat }) => {
})} })}
</ul> </ul>
<p className="conclusion-map__statement" data-conclusion-support={beatId === "conclusion" ? "primary" : "receded"}>Planner proposes; runtime executes.</p> <p
className="conclusion-map__statement"
data-conclusion-support={beatId === "conclusion" ? "primary" : "receded"}
data-contribution-focus={beatId === "conclusion" ? "primary" : "support"}
>
Planner proposes; runtime executes.
</p>
</section> </section>
<p className="scene-body__evidence">{scene.evidencePointer}</p> <p className="scene-body__evidence">{scene.evidencePointer}</p>
</> </>
@@ -1,4 +1,4 @@
import { cleanup, render, screen } from "@testing-library/react"; import { cleanup, render, screen, within } from "@testing-library/react";
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { EvaluationEvidenceScene } from "./EvaluationEvidenceScene.js"; import { EvaluationEvidenceScene } from "./EvaluationEvidenceScene.js";
@@ -58,6 +58,19 @@ describe("EvaluationEvidenceScene", () => {
it("renders six labelled finding icons without ranking language", () => { it("renders six labelled finding icons without ranking language", () => {
render(<EvaluationEvidenceScene scene={scene} beat={beat("findings")} />); render(<EvaluationEvidenceScene scene={scene} beat={beat("findings")} />);
const findings = screen.getByRole("list", { name: /ux gaps exposed by trials/i }); const findings = screen.getByRole("list", { name: /ux gaps exposed by trials/i });
const board = screen.getByRole("group", { name: /evaluation evidence board/i });
const campaignStrip = screen.getByRole("group", { name: /campaign and outcome evidence strip/i });
expect(board).toHaveAttribute("data-evaluation-layout", "3x2-with-evidence-strip");
expect(findings).toHaveAttribute("data-grid-layout", "3x2");
expect(within(findings).getAllByRole("listitem")).toHaveLength(6);
expect(campaignStrip).toHaveAttribute("data-visual-role", "campaign-evidence");
expect(within(campaignStrip).getByText("36")).toBeInTheDocument();
expect(campaignStrip).toHaveTextContent(/3\s+waves/);
expect(
screen.getByText("Bounded longitudinal engineering evidence, not a controlled model comparison."),
).toBeInTheDocument();
expect(findings.querySelectorAll("svg")).toHaveLength(6); expect(findings.querySelectorAll("svg")).toHaveLength(6);
for (const label of [ for (const label of [
"Schema discovery", "Schema discovery",
@@ -33,30 +33,33 @@ export const EvaluationEvidenceScene: FC<EvaluationEvidenceSceneProps> = ({ scen
aria-label="evaluation evidence board" aria-label="evaluation evidence board"
data-evaluation-beat={beatId} data-evaluation-beat={beatId}
data-evaluation-focus={beatId} data-evaluation-focus={beatId}
data-evaluation-layout={beatId === "findings" ? "3x2-with-evidence-strip" : "campaign-audit-board"}
data-visual-role="evaluation-summary" data-visual-role="evaluation-summary"
data-presentation-surface="editorial" data-presentation-surface="editorial"
> >
<div className="evaluation-board__cohort" aria-label="campaign cohort equation"> <div className="evaluation-board__campaign-strip" role="group" aria-label="campaign and outcome evidence strip" data-visual-role="campaign-evidence">
<span className="evaluation-board__cohort-total">{evaluationEvidence.totalTrials}</span> <div className="evaluation-board__cohort" aria-label="campaign cohort equation">
<span className="evaluation-board__cohort-label">audited trials</span> <span className="evaluation-board__cohort-total">{evaluationEvidence.totalTrials}</span>
<span className="evaluation-board__cohort-equation" aria-label="cohort factors"> <span className="evaluation-board__cohort-label">audited trials</span>
{evaluationEvidence.cohortFactors.map((factor, index) => ( <span className="evaluation-board__cohort-equation" aria-label="cohort factors">
<span className="evaluation-board__factor" key={factor.label}> {evaluationEvidence.cohortFactors.map((factor, index) => (
{index > 0 ? " × " : ""} <span className="evaluation-board__factor" key={factor.label}>
<strong>{factor.value}</strong> {factor.label} {index > 0 ? " × " : ""}
</span> <strong>{factor.value}</strong> {factor.label}
))} </span>
</span> ))}
</div> </span>
</div>
<ol className="evaluation-board__outcomes" aria-label="audited outcomes"> <ol className="evaluation-board__outcomes" aria-label="audited outcomes">
{evaluationEvidence.outcomes.map((outcome) => ( {evaluationEvidence.outcomes.map((outcome) => (
<li className={`evaluation-board__outcome evaluation-board__outcome--${outcome.kind}`} key={outcome.label}> <li className={`evaluation-board__outcome evaluation-board__outcome--${outcome.kind}`} key={outcome.label}>
<strong>{outcome.value}</strong> <strong>{outcome.value}</strong>
<span>{outcome.label}</span> <span>{outcome.label}</span>
</li> </li>
))} ))}
</ol> </ol>
</div>
<div className="evaluation-board__audit" aria-label="automatic and manual audit reconciliation"> <div className="evaluation-board__audit" aria-label="automatic and manual audit reconciliation">
<p className="evaluation-board__section-label">Automatic grading manual audit</p> <p className="evaluation-board__section-label">Automatic grading manual audit</p>
@@ -69,7 +72,7 @@ export const EvaluationEvidenceScene: FC<EvaluationEvidenceSceneProps> = ({ scen
))} ))}
</div> </div>
<ol className="evaluation-board__findings" aria-label="UX gaps exposed by trials"> <ol className="evaluation-board__findings" aria-label="UX gaps exposed by trials" data-grid-layout="3x2">
{evaluationEvidence.findings.map((finding) => { {evaluationEvidence.findings.map((finding) => {
const Icon = findingIcons[finding.icon]; const Icon = findingIcons[finding.icon];
return ( return (
@@ -152,20 +152,27 @@
.evaluation-board[data-evaluation-focus="cohort"] { .evaluation-board[data-evaluation-focus="cohort"] {
grid-template-columns: 1fr; grid-template-columns: 1fr;
grid-template-areas: "cohort" "outcomes" "boundary"; grid-template-areas: "campaign" "boundary";
} }
.evaluation-board[data-evaluation-focus="validity"] { .evaluation-board[data-evaluation-focus="validity"] {
grid-template-areas: "cohort outcomes" "audit audit" "boundary boundary"; grid-template-areas: "campaign campaign" "audit audit" "boundary boundary";
} }
.evaluation-board[data-evaluation-focus="findings"] { .evaluation-board[data-evaluation-focus="findings"] {
grid-template-columns: 1fr; grid-template-columns: 1fr;
grid-template-areas: "findings" "boundary"; grid-template-areas: "campaign" "findings" "boundary";
min-height: 0;
align-content: start;
}
.evaluation-board__campaign-strip {
display: grid;
grid-area: campaign;
grid-template-columns: minmax(14rem, 0.8fr) minmax(20rem, 1.2fr);
min-width: 0;
} }
.evaluation-board__cohort { grid-area: cohort; }
.evaluation-board__outcomes { grid-area: outcomes; }
.evaluation-board__audit { grid-area: audit; } .evaluation-board__audit { grid-area: audit; }
.evaluation-board__findings { grid-area: findings; } .evaluation-board__findings { grid-area: findings; }
.evaluation-board__boundary { grid-area: boundary; } .evaluation-board__boundary { grid-area: boundary; }
@@ -173,12 +180,19 @@
.evaluation-board[data-evaluation-focus="cohort"] .evaluation-board__audit, .evaluation-board[data-evaluation-focus="cohort"] .evaluation-board__audit,
.evaluation-board[data-evaluation-focus="cohort"] .evaluation-board__findings, .evaluation-board[data-evaluation-focus="cohort"] .evaluation-board__findings,
.evaluation-board[data-evaluation-focus="validity"] .evaluation-board__findings, .evaluation-board[data-evaluation-focus="validity"] .evaluation-board__findings,
.evaluation-board[data-evaluation-focus="findings"] .evaluation-board__cohort,
.evaluation-board[data-evaluation-focus="findings"] .evaluation-board__outcomes,
.evaluation-board[data-evaluation-focus="findings"] .evaluation-board__audit { .evaluation-board[data-evaluation-focus="findings"] .evaluation-board__audit {
display: none; display: none;
} }
.evaluation-board[data-evaluation-focus="cohort"] .evaluation-board__campaign-strip {
grid-template-columns: 1fr;
}
.evaluation-board[data-evaluation-focus="findings"] .evaluation-board__campaign-strip {
/* grid-template-columns: minmax(12rem, 0.7fr) minmax(20rem, 1.3fr); */
/* difference between two beats -> shifting */
}
.evaluation-board[data-evaluation-focus="cohort"] .evaluation-board__cohort { .evaluation-board[data-evaluation-focus="cohort"] .evaluation-board__cohort {
border-right: 0; border-right: 0;
padding-block: 1.35rem 0.8rem; padding-block: 1.35rem 0.8rem;
@@ -186,8 +200,9 @@
.evaluation-board[data-evaluation-focus="findings"] .evaluation-board__findings { .evaluation-board[data-evaluation-focus="findings"] .evaluation-board__findings {
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));
grid-template-rows: repeat(2, minmax(2.4rem, auto));
border-top: 0; border-top: 0;
padding-block: 1.35rem; padding-block: 0.95rem;
} }
.evaluation-board[data-evaluation-beat="cohort"] .evaluation-board__cohort, .evaluation-board[data-evaluation-beat="cohort"] .evaluation-board__cohort,
@@ -279,6 +294,7 @@
.evaluation-board__findings { .evaluation-board__findings {
display: grid; display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));
grid-template-rows: repeat(2, minmax(2.4rem, auto));
gap: 0.45rem 0.8rem; gap: 0.45rem 0.8rem;
} }
@@ -302,12 +318,21 @@
@media (max-width: 1080px) { @media (max-width: 1080px) {
.evaluation-board { grid-template-columns: minmax(12rem, 0.7fr) minmax(20rem, 1.3fr); } .evaluation-board { grid-template-columns: minmax(12rem, 0.7fr) minmax(20rem, 1.3fr); }
.evaluation-board__campaign-strip { grid-template-columns: minmax(12rem, 0.7fr) minmax(20rem, 1.3fr); }
.evaluation-board__cohort { border-right: 1px solid var(--evaluation-rule); } .evaluation-board__cohort { border-right: 1px solid var(--evaluation-rule); }
.evaluation-board__audit-row { grid-template-columns: 1fr; } .evaluation-board__audit-row { grid-template-columns: 1fr; }
.evaluation-board[data-evaluation-focus="cohort"], .evaluation-board[data-evaluation-focus="cohort"],
.evaluation-board[data-evaluation-focus="findings"] { .evaluation-board[data-evaluation-focus="findings"] {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.evaluation-board[data-evaluation-focus="cohort"] .evaluation-board__campaign-strip,
.evaluation-board[data-evaluation-focus="findings"] .evaluation-board__campaign-strip {
grid-template-columns: 1fr;
}
.evaluation-board[data-evaluation-focus="cohort"] .evaluation-board__cohort,
.evaluation-board[data-evaluation-focus="findings"] .evaluation-board__cohort {
border-right: 0;
}
} }
.stage-caption__eyebrow { .stage-caption__eyebrow {
@@ -520,9 +545,7 @@
} }
.conclusion-map[data-conclusion-beat="limits"] .conclusion-map__future, .conclusion-map[data-conclusion-beat="limits"] .conclusion-map__future,
.conclusion-map[data-conclusion-beat="conclusion"] .conclusion-map__future, .conclusion-map[data-conclusion-beat="future"] .conclusion-map__non-claims {
.conclusion-map[data-conclusion-beat="future"] .conclusion-map__non-claims,
.conclusion-map[data-conclusion-beat="conclusion"] .conclusion-map__non-claims {
display: none; display: none;
} }
@@ -535,11 +558,59 @@
margin-bottom: 0.2rem; margin-bottom: 0.2rem;
} }
.conclusion-map[data-conclusion-beat="conclusion"] .conclusion-map__node--substrate {
min-height: 7.5rem;
padding: 1rem 1.25rem;
border-width: 2px;
box-shadow: 0 0 0 0.25rem color-mix(in oklch, var(--conclusion-ink) 10%, transparent);
}
.conclusion-map[data-conclusion-beat="conclusion"] {
min-height: min(26rem, 62vh);
}
.conclusion-map[data-conclusion-beat="conclusion"] .conclusion-map__flow-unit--substrate-stack {
align-content: start;
}
.conclusion-map[data-conclusion-beat="conclusion"] .conclusion-map__node--evidence {
min-height: 3.6rem;
padding: 0.55rem 0.75rem;
}
.conclusion-map[data-conclusion-beat="conclusion"] .conclusion-map__node--evidence small {
font-size: 0.76rem;
}
.conclusion-map[data-conclusion-beat="conclusion"] .conclusion-map__non-claims li {
padding: 0.35rem 0.5rem;
font-size: 0.8rem;
}
.conclusion-map[data-conclusion-beat="conclusion"] .conclusion-map__future li {
gap: 0.35rem;
padding: 0.3rem 0.45rem;
}
.conclusion-map[data-conclusion-beat="conclusion"] .conclusion-map__future small {
display: none;
}
.conclusion-map[data-conclusion-beat="conclusion"] .conclusion-map__node--substrate strong {
font-size: clamp(1.35rem, 2.3vw, 1.75rem);
}
.conclusion-map[data-conclusion-beat="conclusion"] .conclusion-map__non-claims,
.conclusion-map[data-conclusion-beat="conclusion"] .conclusion-map__future {
opacity: 0.38;
transform: translateY(0.15rem);
}
.conclusion-map[data-conclusion-beat="conclusion"] .conclusion-map__statement { .conclusion-map[data-conclusion-beat="conclusion"] .conclusion-map__statement {
padding-block: 1.1rem 0.45rem; padding-block: 0.45rem 0.2rem;
border-top-color: var(--conclusion-ink); border-top-color: var(--conclusion-ink);
color: var(--conclusion-ink); color: var(--conclusion-ink);
font-size: 1.25rem; font-size: clamp(1.45rem, 2.7vw, 2rem);
font-style: normal; font-style: normal;
font-weight: 650; font-weight: 650;
text-align: center; text-align: center;
@@ -2169,7 +2240,7 @@
display: grid; display: grid;
flex: 1 1 auto; flex: 1 1 auto;
min-height: 0; min-height: 0;
grid-template-rows: 8rem minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr);
gap: 0.9rem; gap: 0.9rem;
} }
@@ -2215,6 +2286,7 @@
.opening-thesis__statement h2 { .opening-thesis__statement h2 {
margin: 0; margin: 0;
border-bottom: 0;
color: var(--opening-ink); color: var(--opening-ink);
font: 800 clamp(2.85rem, 5.5vw, 4.2rem) / 0.94 var(--font-editorial); font: 800 clamp(2.85rem, 5.5vw, 4.2rem) / 0.94 var(--font-editorial);
letter-spacing: -0.03em; letter-spacing: -0.03em;