fix: move audience pairing above deck
This commit is contained in:
@@ -2,20 +2,10 @@ import { cleanup, render, screen, within } from "@testing-library/react";
|
|||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
import type { EvidenceRecord } from "../app/state.js";
|
import type { EvidenceRecord } from "../app/state.js";
|
||||||
import type { DemoChromePresentation } from "./presentation-demo-chrome.js";
|
import type { DemoChromePresentation } from "./presentation-demo-chrome.js";
|
||||||
import type { PresentationSyncController } from "./sync/presentation-sync-state.js";
|
|
||||||
import { PresentationFooter } from "./PresentationFooter.js";
|
import { PresentationFooter } from "./PresentationFooter.js";
|
||||||
|
|
||||||
afterEach(() => cleanup());
|
afterEach(() => cleanup());
|
||||||
|
|
||||||
const standaloneSyncController = (): PresentationSyncController => ({
|
|
||||||
state: { kind: "standalone" },
|
|
||||||
startSession: vi.fn(async () => {}),
|
|
||||||
joinSession: vi.fn(async () => {}),
|
|
||||||
retry: vi.fn(),
|
|
||||||
leaveSession: vi.fn(),
|
|
||||||
endSession: vi.fn(),
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("PresentationFooter", () => {
|
describe("PresentationFooter", () => {
|
||||||
it("combines scene progress and evidence provenance", () => {
|
it("combines scene progress and evidence provenance", () => {
|
||||||
const evidence: EvidenceRecord = {
|
const evidence: EvidenceRecord = {
|
||||||
@@ -40,7 +30,6 @@ describe("PresentationFooter", () => {
|
|||||||
}}
|
}}
|
||||||
evidence={[evidence]}
|
evidence={[evidence]}
|
||||||
demoRail={hiddenRail}
|
demoRail={hiddenRail}
|
||||||
syncController={standaloneSyncController()}
|
|
||||||
retryHealth={vi.fn()}
|
retryHealth={vi.fn()}
|
||||||
showEvidenceReceipt
|
showEvidenceReceipt
|
||||||
inspectEvidence={vi.fn()}
|
inspectEvidence={vi.fn()}
|
||||||
@@ -63,7 +52,6 @@ describe("PresentationFooter", () => {
|
|||||||
}}
|
}}
|
||||||
evidence={[]}
|
evidence={[]}
|
||||||
demoRail={{ kind: "hidden" }}
|
demoRail={{ kind: "hidden" }}
|
||||||
syncController={standaloneSyncController()}
|
|
||||||
retryHealth={vi.fn()}
|
retryHealth={vi.fn()}
|
||||||
showEvidenceReceipt={false}
|
showEvidenceReceipt={false}
|
||||||
inspectEvidence={vi.fn()}
|
inspectEvidence={vi.fn()}
|
||||||
@@ -92,7 +80,6 @@ describe("PresentationFooter", () => {
|
|||||||
canRun: true,
|
canRun: true,
|
||||||
canRetry: true,
|
canRetry: true,
|
||||||
}}
|
}}
|
||||||
syncController={standaloneSyncController()}
|
|
||||||
retryHealth={vi.fn()}
|
retryHealth={vi.fn()}
|
||||||
showEvidenceReceipt={false}
|
showEvidenceReceipt={false}
|
||||||
inspectEvidence={vi.fn()}
|
inspectEvidence={vi.fn()}
|
||||||
@@ -103,7 +90,7 @@ describe("PresentationFooter", () => {
|
|||||||
expect(screen.getByText("Live target ready")).toBeInTheDocument();
|
expect(screen.getByText("Live target ready")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps audience pairing in the non-demo utility area beside one demo rail", () => {
|
it("keeps the footer focused on one demo rail without the pairing surface", () => {
|
||||||
render(
|
render(
|
||||||
<PresentationFooter
|
<PresentationFooter
|
||||||
location={{ kind: "main", sceneId: "agent-handoff", beatId: "request", focusPath: [] }}
|
location={{ kind: "main", sceneId: "agent-handoff", beatId: "request", focusPath: [] }}
|
||||||
@@ -121,7 +108,6 @@ describe("PresentationFooter", () => {
|
|||||||
canRun: true,
|
canRun: true,
|
||||||
canRetry: true,
|
canRetry: true,
|
||||||
}}
|
}}
|
||||||
syncController={standaloneSyncController()}
|
|
||||||
retryHealth={vi.fn()}
|
retryHealth={vi.fn()}
|
||||||
showEvidenceReceipt={false}
|
showEvidenceReceipt={false}
|
||||||
inspectEvidence={vi.fn()}
|
inspectEvidence={vi.fn()}
|
||||||
@@ -132,8 +118,8 @@ describe("PresentationFooter", () => {
|
|||||||
const utility = footer.querySelector<HTMLElement>(".presentation-footer__utility");
|
const utility = footer.querySelector<HTMLElement>(".presentation-footer__utility");
|
||||||
expect(utility).not.toBeNull();
|
expect(utility).not.toBeNull();
|
||||||
if (utility === null) throw new Error("presentation utility area is missing");
|
if (utility === null) throw new Error("presentation utility area is missing");
|
||||||
expect(within(utility).getByRole("complementary", { name: "Presentation pairing" }))
|
expect(within(utility).queryByRole("complementary", { name: "Presentation pairing" }))
|
||||||
.toHaveAttribute("data-role", "audience");
|
.not.toBeInTheDocument();
|
||||||
expect(screen.getAllByTestId("presentation-demo-rail")).toHaveLength(1);
|
expect(screen.getAllByTestId("presentation-demo-rail")).toHaveLength(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import { EvidenceReceipt } from "./evidence/EvidenceReceipt.js";
|
|||||||
import type { DemoChromePresentation } from "./presentation-demo-chrome.js";
|
import type { DemoChromePresentation } from "./presentation-demo-chrome.js";
|
||||||
import { PresentationDemoRail } from "./PresentationDemoRail.js";
|
import { PresentationDemoRail } from "./PresentationDemoRail.js";
|
||||||
import type { MainLocation } from "./storyboard.js";
|
import type { MainLocation } from "./storyboard.js";
|
||||||
import { PresentationPairingPanel } from "./sync/PresentationPairingPanel.js";
|
|
||||||
import type { PresentationSyncController } from "./sync/presentation-sync-state.js";
|
|
||||||
|
|
||||||
type PresentationFooterProps = {
|
type PresentationFooterProps = {
|
||||||
readonly location: MainLocation;
|
readonly location: MainLocation;
|
||||||
@@ -16,7 +14,6 @@ type PresentationFooterProps = {
|
|||||||
readonly retryHealth: () => void;
|
readonly retryHealth: () => void;
|
||||||
readonly showEvidenceReceipt: boolean;
|
readonly showEvidenceReceipt: boolean;
|
||||||
readonly inspectEvidence: () => void;
|
readonly inspectEvidence: () => void;
|
||||||
readonly syncController: PresentationSyncController;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PresentationFooter = ({
|
export const PresentationFooter = ({
|
||||||
@@ -27,7 +24,6 @@ export const PresentationFooter = ({
|
|||||||
retryHealth,
|
retryHealth,
|
||||||
showEvidenceReceipt,
|
showEvidenceReceipt,
|
||||||
inspectEvidence,
|
inspectEvidence,
|
||||||
syncController,
|
|
||||||
}: PresentationFooterProps) => (
|
}: PresentationFooterProps) => (
|
||||||
<footer className="presentation-footer" aria-label="presentation footer">
|
<footer className="presentation-footer" aria-label="presentation footer">
|
||||||
<SceneProgress location={location} />
|
<SceneProgress location={location} />
|
||||||
@@ -37,7 +33,6 @@ export const PresentationFooter = ({
|
|||||||
retryHealth={retryHealth}
|
retryHealth={retryHealth}
|
||||||
/>
|
/>
|
||||||
<div className="presentation-footer__utility">
|
<div className="presentation-footer__utility">
|
||||||
<PresentationPairingPanel role="audience" controller={syncController} />
|
|
||||||
<EvidenceReceipt
|
<EvidenceReceipt
|
||||||
records={evidence}
|
records={evidence}
|
||||||
visible={showEvidenceReceipt}
|
visible={showEvidenceReceipt}
|
||||||
|
|||||||
@@ -178,6 +178,8 @@ describe("PresentationRoute", () => {
|
|||||||
|
|
||||||
const panel = screen.getByRole("complementary", { name: "Presentation pairing" });
|
const panel = screen.getByRole("complementary", { name: "Presentation pairing" });
|
||||||
expect(panel).toHaveAttribute("data-role", "audience");
|
expect(panel).toHaveAttribute("data-role", "audience");
|
||||||
|
expect(panel.closest(".presentation-stage__sync")).not.toBeNull();
|
||||||
|
expect(panel.closest(".presentation-footer")).toBeNull();
|
||||||
expect(within(panel).getByRole("button", { name: "Pair presentation" })).toBeInTheDocument();
|
expect(within(panel).getByRole("button", { name: "Pair presentation" })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import type { PresentationState } from "./presentation-state.js";
|
|||||||
import { compositionForState } from "./presentation-state.js";
|
import { compositionForState } from "./presentation-state.js";
|
||||||
import type { PresentationTargetHealth } from "./presentation-target-status.js";
|
import type { PresentationTargetHealth } from "./presentation-target-status.js";
|
||||||
import type { PresentationSyncController } from "./sync/presentation-sync-state.js";
|
import type { PresentationSyncController } from "./sync/presentation-sync-state.js";
|
||||||
|
import { PresentationPairingPanel } from "./sync/PresentationPairingPanel.js";
|
||||||
import { demoChromeFor } from "./presentation-demo-chrome.js";
|
import { demoChromeFor } from "./presentation-demo-chrome.js";
|
||||||
import type { DemoTimelineController } from "../demo/useDemoTimeline.js";
|
import type { DemoTimelineController } from "../demo/useDemoTimeline.js";
|
||||||
import { findScene, type MainLocation } from "./storyboard.js";
|
import { findScene, type MainLocation } from "./storyboard.js";
|
||||||
@@ -89,6 +90,9 @@ export const PresentationStage = ({
|
|||||||
data-evidence-presentation={composition.evidencePresentation}
|
data-evidence-presentation={composition.evidencePresentation}
|
||||||
data-scene-view={activeSceneView}
|
data-scene-view={activeSceneView}
|
||||||
>
|
>
|
||||||
|
<div className="presentation-stage__sync">
|
||||||
|
<PresentationPairingPanel role="audience" controller={syncController} />
|
||||||
|
</div>
|
||||||
<aside className="presentation-stage__chat" aria-label="agent chat region">
|
<aside className="presentation-stage__chat" aria-label="agent chat region">
|
||||||
<OperatorChat state={state} messages={messages} timelineAgent={timelineAgent} onApprove={onApprove} onRequestRevision={onRequestRevision} />
|
<OperatorChat state={state} messages={messages} timelineAgent={timelineAgent} onApprove={onApprove} onRequestRevision={onRequestRevision} />
|
||||||
</aside>
|
</aside>
|
||||||
@@ -129,7 +133,6 @@ export const PresentationStage = ({
|
|||||||
retryHealth={retryHealth}
|
retryHealth={retryHealth}
|
||||||
showEvidenceReceipt={composition.evidencePresentation !== "hidden"}
|
showEvidenceReceipt={composition.evidencePresentation !== "hidden"}
|
||||||
inspectEvidence={openEvidence}
|
inspectEvidence={openEvidence}
|
||||||
syncController={syncController}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<EvidenceInspector
|
<EvidenceInspector
|
||||||
|
|||||||
@@ -1077,6 +1077,12 @@
|
|||||||
.presentation-stage__chat { grid-area: chat; }
|
.presentation-stage__chat { grid-area: chat; }
|
||||||
.presentation-stage__primary { grid-area: primary; }
|
.presentation-stage__primary { grid-area: primary; }
|
||||||
|
|
||||||
|
/* The audience pairing control floats above the deck instead of competing with demo footer controls. */
|
||||||
|
.presentation-stage__sync .presentation-pairing {
|
||||||
|
top: 1rem;
|
||||||
|
bottom: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.presentation-footer {
|
.presentation-footer {
|
||||||
grid-area: footer;
|
grid-area: footer;
|
||||||
min-height: var(--presentation-footer-height);
|
min-height: var(--presentation-footer-height);
|
||||||
@@ -1225,6 +1231,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
|
.presentation-stage__sync .presentation-pairing {
|
||||||
|
top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.presentation-footer__utility {
|
.presentation-footer__utility {
|
||||||
flex: 0 1 auto;
|
flex: 0 1 auto;
|
||||||
max-width: 52%;
|
max-width: 52%;
|
||||||
|
|||||||
Reference in New Issue
Block a user