feat: show presenter goals and anchor terms
This commit is contained in:
@@ -0,0 +1,48 @@
|
|||||||
|
import { cleanup, render, screen, within } from "@testing-library/react";
|
||||||
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
|
import { PresenterNote } from "./PresenterNote.js";
|
||||||
|
import { presenterNotes } from "./presenter-notes.js";
|
||||||
|
|
||||||
|
afterEach(cleanup);
|
||||||
|
|
||||||
|
describe("PresenterNote", () => {
|
||||||
|
it("renders the rehearsal hierarchy and preserves note metadata", () => {
|
||||||
|
const note = presenterNotes.find(
|
||||||
|
(candidate) => candidate.sceneId === "run-from-deployment" && candidate.beatId === "operation",
|
||||||
|
);
|
||||||
|
const next = presenterNotes.find(
|
||||||
|
(candidate) => candidate.sceneId === "run-from-deployment" && candidate.beatId === "graph",
|
||||||
|
);
|
||||||
|
if (!note || !next) throw new Error("missing run-from-deployment presenter notes");
|
||||||
|
|
||||||
|
render(
|
||||||
|
<PresenterNote
|
||||||
|
note={note}
|
||||||
|
cumulativeSeconds={23}
|
||||||
|
next={next}
|
||||||
|
covered={false}
|
||||||
|
onCoveredChange={() => {}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByRole("region", { name: "Beat goal" })).toHaveTextContent(note.goal);
|
||||||
|
|
||||||
|
const anchors = screen.getByRole("region", { name: "Anchor terms" });
|
||||||
|
const anchorList = within(anchors).getByRole("list");
|
||||||
|
for (const keyword of note.keywords) {
|
||||||
|
expect(anchorList).toHaveTextContent(keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
const wording = screen.getByRole("region", { name: "Suggested wording" });
|
||||||
|
expect(wording).toHaveTextContent(note.mustSay.replaceAll("**", ""));
|
||||||
|
expect(within(wording).getByText("workflow.runs.start").tagName).toBe("STRONG");
|
||||||
|
|
||||||
|
expect(screen.queryByText("Must say")).not.toBeInTheDocument();
|
||||||
|
expect(screen.getByText(`Evidence (${note.evidencePointers.length})`)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(`Linked Q&A (${note.qnaBranchIds.length})`)).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole("link", { name: "Go to next beat" })).toHaveAttribute(
|
||||||
|
"href",
|
||||||
|
"#scene/run-from-deployment/graph",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -26,8 +26,20 @@ export const PresenterNote = ({ note, cumulativeSeconds, next, covered, onCovere
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<section className="presenter-note__goal" aria-labelledby="presenter-goal">
|
||||||
|
<span id="presenter-goal">Beat goal</span>
|
||||||
|
<p>{note.goal}</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="presenter-note__anchors" aria-labelledby="presenter-anchors">
|
||||||
|
<span id="presenter-anchors">Anchor terms</span>
|
||||||
|
<ul>
|
||||||
|
{note.keywords.map((keyword) => <li key={keyword}>{keyword}</li>)}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section className="presenter-note__say" aria-labelledby="presenter-say">
|
<section className="presenter-note__say" aria-labelledby="presenter-say">
|
||||||
<span id="presenter-say">Say</span>
|
<span id="presenter-say">Suggested wording</span>
|
||||||
<div className="presenter-note__markdown"><ReactMarkdown>{note.mustSay}</ReactMarkdown></div>
|
<div className="presenter-note__markdown"><ReactMarkdown>{note.mustSay}</ReactMarkdown></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -46,9 +46,15 @@
|
|||||||
.presenter-note__header span, .presenter-note__timing { color: #67655f; font: 0.78rem "IBM Plex Mono", monospace; }
|
.presenter-note__header span, .presenter-note__timing { color: #67655f; font: 0.78rem "IBM Plex Mono", monospace; }
|
||||||
.presenter-note h1, .presenter-qna h1 { margin: 0.25rem 0 0; font: 700 2.5rem/1 "Barlow Condensed", sans-serif; text-transform: uppercase; }
|
.presenter-note h1, .presenter-qna h1 { margin: 0.25rem 0 0; font: 700 2.5rem/1 "Barlow Condensed", sans-serif; text-transform: uppercase; }
|
||||||
.presenter-note__timing { display: grid; align-content: start; gap: 0.25rem; text-align: right; }
|
.presenter-note__timing { display: grid; align-content: start; gap: 0.25rem; text-align: right; }
|
||||||
|
.presenter-note__goal { margin: 2rem 0 1.35rem; }
|
||||||
|
.presenter-note__goal > span, .presenter-note__anchors > span, .presenter-note__say > span { display: block; color: #67655f; font: 700 0.8rem "IBM Plex Mono", monospace; text-transform: uppercase; }
|
||||||
|
.presenter-note__goal p { max-width: 58ch; margin: 0.45rem 0 0; color: #20201e; font-size: 1.62rem; font-weight: 650; line-height: 1.25; text-wrap: balance; }
|
||||||
|
.presenter-note__anchors { margin-bottom: 2rem; padding: 0.8rem 0; border-block: 1px solid #d7d5d0; }
|
||||||
|
.presenter-note__anchors ul { display: flex; flex-wrap: wrap; gap: 0.4rem 1.3rem; margin: 0.55rem 0 0; padding: 0; list-style: none; }
|
||||||
|
.presenter-note__anchors li { color: #20201e; font: 0.82rem/1.4 "IBM Plex Mono", monospace; }
|
||||||
|
.presenter-note__anchors li::before { margin-right: 0.45rem; color: #1e6b55; content: "•"; }
|
||||||
.presenter-note__say { margin: 2rem 0; }
|
.presenter-note__say { margin: 2rem 0; }
|
||||||
.presenter-note__say > span { color: #1e6b55; font: 700 0.8rem "IBM Plex Mono", monospace; text-transform: uppercase; }
|
.presenter-note__markdown p { margin: 0.55rem 0 0; font-size: 1.28rem; line-height: 1.58; text-wrap: pretty; }
|
||||||
.presenter-note__markdown p { margin: 0.55rem 0 0; font-size: 1.42rem; line-height: 1.58; text-wrap: pretty; }
|
|
||||||
.presenter-note__markdown strong, .presenter-note__next-copy strong { color: #155b49; font-weight: 750; }
|
.presenter-note__markdown strong, .presenter-note__next-copy strong { color: #155b49; font-weight: 750; }
|
||||||
.presenter-note__warning, .presenter-note__fallback, .presenter-qna aside { margin: 1.2rem 0; padding: 0.8rem 0; border-block: 1px solid #a36919; }
|
.presenter-note__warning, .presenter-note__fallback, .presenter-qna aside { margin: 1.2rem 0; padding: 0.8rem 0; border-block: 1px solid #a36919; }
|
||||||
.presenter-note__fallback { border-color: #3c6380; }
|
.presenter-note__fallback { border-color: #3c6380; }
|
||||||
@@ -72,6 +78,18 @@
|
|||||||
.presenter-sidebar { position: static; height: auto; max-height: 15rem; border-right: 0; border-bottom: 1px solid #d7d5d0; }
|
.presenter-sidebar { position: static; height: auto; max-height: 15rem; border-right: 0; border-bottom: 1px solid #d7d5d0; }
|
||||||
.presenter-sidebar ol { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
.presenter-sidebar ol { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||||
.presenter-navigation { position: sticky; top: 0; z-index: 3; margin-inline: -0.5rem; padding-inline: 0.5rem; background: #f7f7f5; }
|
.presenter-navigation { position: sticky; top: 0; z-index: 3; margin-inline: -0.5rem; padding-inline: 0.5rem; background: #f7f7f5; }
|
||||||
|
.presenter-note__goal p { font-size: 1.42rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 560px) {
|
||||||
|
.presenter-route__reader { padding: 1rem 1rem 3.5rem; }
|
||||||
|
.presenter-note__header { flex-direction: column; gap: 0.8rem; }
|
||||||
|
.presenter-note h1 { font-size: 2.15rem; }
|
||||||
|
.presenter-note__timing { grid-auto-flow: column; justify-content: start; gap: 1rem; text-align: left; }
|
||||||
|
.presenter-note__goal { margin-top: 1.5rem; }
|
||||||
|
.presenter-note__goal p { font-size: 1.28rem; }
|
||||||
|
.presenter-note__markdown p { font-size: 1.12rem; }
|
||||||
|
.presenter-note__actions { align-items: flex-start; flex-direction: column; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
|
|||||||
Reference in New Issue
Block a user