fix: turn discussion chips into presenter rail

This commit is contained in:
lda
2026-07-09 04:49:16 +07:00 Verified
parent 345d0f0670
commit fe1f3f90e1
3 changed files with 93 additions and 23 deletions
@@ -291,6 +291,50 @@ describe("SceneBody", () => {
expect(screen.getByText("Compile or save")).toBeInTheDocument(); expect(screen.getByText("Compile or save")).toBeInTheDocument();
}); });
it("renders discussion branches as a labelled presenter rail", () => {
const location: PresentationLocation = { kind: "main", sceneId: "positioning", beatId: "landscape", focusPath: [] };
render(
<SceneBody
location={location}
demo={demo}
selectedNodeId={null}
selectNode={noop}
openEvidence={noop}
openDiscussion={noop}
onFocusPathChange={noop}
motionDisabled={false}
/>,
);
const rail = screen.getByLabelText("defense discussion topics");
expect(rail).toHaveAttribute("data-discussion-rail", "true");
expect(within(rail).getByText("Defense questions")).toBeInTheDocument();
expect(within(rail).getByRole("list")).toBeInTheDocument();
expect(within(rail).getByRole("button", { name: /Hosted automation future-work/i })).toBeInTheDocument();
});
it("keeps discussion rail actions wired to branch ids", async () => {
const user = userEvent.setup();
const location: PresentationLocation = { kind: "main", sceneId: "positioning", beatId: "landscape", focusPath: [] };
const openDiscussion = vi.fn();
render(
<SceneBody
location={location}
demo={demo}
selectedNodeId={null}
selectNode={noop}
openEvidence={noop}
openDiscussion={openDiscussion}
onFocusPathChange={noop}
motionDisabled={false}
/>,
);
await user.click(screen.getByRole("button", { name: /Hosted automation future-work/i }));
expect(openDiscussion).toHaveBeenCalledWith("hosted-automation");
});
it("renders evidence before discussion links so the chip lane cannot cover evidence text", () => { it("renders evidence before discussion links so the chip lane cannot cover evidence text", () => {
const location: PresentationLocation = { kind: "main", sceneId: "positioning", beatId: "landscape", focusPath: [] }; const location: PresentationLocation = { kind: "main", sceneId: "positioning", beatId: "landscape", focusPath: [] };
const { container } = render( const { container } = render(
+15 -11
View File
@@ -32,17 +32,21 @@ const DiscussionLinks = ({
const branches = discussionBranches.filter((branch) => branch.parentSceneId === sceneId); const branches = discussionBranches.filter((branch) => branch.parentSceneId === sceneId);
if (branches.length === 0) return null; if (branches.length === 0) return null;
return ( return (
<div className="scene-body__discussion-links" aria-label="discussion topics"> <aside className="scene-body__discussion-links" aria-label="defense discussion topics" data-discussion-rail="true">
{branches.map((branch) => ( <span className="scene-body__discussion-label">Defense questions</span>
<button <ul className="scene-body__discussion-list">
key={branch.id} {branches.map((branch) => (
type="button" <li key={branch.id}>
onClick={() => openDiscussion(branch.id)} <button
> type="button"
{branch.title} onClick={() => openDiscussion(branch.id)}
</button> >
))} <span>{branch.title}</span> <small>{branch.claimClass}</small>
</div> </button>
</li>
))}
</ul>
</aside>
); );
}; };
@@ -118,28 +118,50 @@
.scene-body__discussion-links { .scene-body__discussion-links {
flex-shrink: 0; flex-shrink: 0;
display: grid;
grid-template-columns: auto minmax(0, 1fr);
align-items: center;
gap: 0.7rem;
margin-top: 0.55rem;
border-top: 1px solid color-mix(in oklch, var(--color-editorial-muted, oklch(0.48 0.025 65)) 30%, transparent);
padding-top: 0.45rem;
}
.scene-body__discussion-label {
color: color-mix(in oklch, var(--color-editorial-ink, oklch(0.19 0.015 65)) 72%, var(--color-editorial-muted, oklch(0.48 0.025 65)));
font: 650 0.72rem/1 var(--font-interface);
}
.scene-body__discussion-list {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.35rem; gap: 0.35rem;
margin-top: 0.45rem; margin: 0;
padding-bottom: 0.2rem; padding: 0;
list-style: none;
} }
.scene-body__discussion-links button { .scene-body__discussion-links button {
border: 1px solid oklch(0.82 0.04 82 / 0.22); display: inline-flex;
border-radius: 999px; align-items: center;
background: oklch(0.16 0.018 65 / 0.74); gap: 0.45rem;
color: oklch(0.9 0.025 82); border: 1px solid color-mix(in oklch, var(--color-editorial-muted, oklch(0.48 0.025 65)) 36%, transparent);
padding: 0.28rem 0.7rem; border-radius: 0.55rem;
font: 600 0.72rem/1 var(--font-interface); background: color-mix(in oklch, var(--color-editorial-paper, oklch(0.975 0.012 82)) 72%, transparent);
letter-spacing: 0.04em; color: var(--color-editorial-ink, oklch(0.19 0.015 65));
text-transform: uppercase; padding: 0.34rem 0.55rem;
font: 600 0.74rem/1 var(--font-interface);
}
.scene-body__discussion-links button small {
color: color-mix(in oklch, var(--color-editorial-ink, oklch(0.19 0.015 65)) 62%, var(--color-editorial-muted, oklch(0.48 0.025 65)));
font: 0.68rem/1 var(--font-interface);
} }
.scene-body__discussion-links button:hover, .scene-body__discussion-links button:hover,
.scene-body__discussion-links button:focus-visible { .scene-body__discussion-links button:focus-visible {
border-color: var(--accent-cyan); border-color: var(--color-intent, oklch(0.53 0.17 250));
color: white; background: color-mix(in oklch, var(--color-intent, oklch(0.53 0.17 250)) 8%, var(--color-editorial-paper, oklch(0.975 0.012 82)));
} }
.operator-chat { .operator-chat {