fix: keep presenter pairing visible in Q&A
This commit is contained in:
@@ -47,3 +47,26 @@ Passed with no whitespace errors.
|
|||||||
## Self-Review
|
## Self-Review
|
||||||
|
|
||||||
No critical or important findings. The change is limited to the presenter synchronization seam and tests. The untracked active LAN synchronization plan was not staged. The pre-existing intentional `presenter.css` mobile sticky-navigation edit was retained and verified as part of this task.
|
No critical or important findings. The change is limited to the presenter synchronization seam and tests. The untracked active LAN synchronization plan was not staged. The pre-existing intentional `presenter.css` mobile sticky-navigation edit was retained and verified as part of this task.
|
||||||
|
|
||||||
|
## Important Finding Fix: Pairing Persistence On Q&A
|
||||||
|
|
||||||
|
The original Task 7 render condition mounted `PresenterNavigationBar` only when `navigation.note` existed. Valid discussion hashes resolve with `note: null`, so Q&A content remained visible but the pairing controls, session-end action, and ended state disappeared.
|
||||||
|
|
||||||
|
The stable navigation surface now renders for either a valid presenter note or a resolved discussion branch. Discussion routes pass a nullable progress index and display `Q&A`; Previous and Next remain disabled because no note or destination is fabricated. The existing Q&A content, hash parsing, local note navigation, and presenter mobile CSS are unchanged.
|
||||||
|
|
||||||
|
TDD evidence:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RED: pnpm --dir web --filter @lda/console test -- src/presentation/presenter/PresenterRoute.test.tsx
|
||||||
|
1 test file failed; 1 failed and 9 passed (10 total).
|
||||||
|
The regression test could not find the Pair presentation button on #discuss/where-is-ai-agent.
|
||||||
|
|
||||||
|
GREEN: pnpm --dir web --filter @lda/console test -- src/presentation/presenter/PresenterRoute.test.tsx
|
||||||
|
1 test file passed; 10 tests passed.
|
||||||
|
|
||||||
|
FINAL: pnpm --dir web --filter @lda/console test -- src/presentation/presenter/PresenterRoute.test.tsx src/presentation/presenter/PresenterShell.test.tsx
|
||||||
|
2 test files passed; 16 tests passed.
|
||||||
|
|
||||||
|
pnpm --dir web --filter @lda/console typecheck
|
||||||
|
@lda/presentation-sync build and console TypeScript project build passed.
|
||||||
|
```
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { PresentationPairingPanel } from "../sync/PresentationPairingPanel.js";
|
|||||||
import type { PresentationSyncController } from "../sync/presentation-sync-state.js";
|
import type { PresentationSyncController } from "../sync/presentation-sync-state.js";
|
||||||
|
|
||||||
type PresenterNavigationBarProps = {
|
type PresenterNavigationBarProps = {
|
||||||
readonly currentIndex: number;
|
readonly currentIndex: number | null;
|
||||||
readonly total: number;
|
readonly total: number;
|
||||||
readonly previous: PresenterBeatNote | null;
|
readonly previous: PresenterBeatNote | null;
|
||||||
readonly next: PresenterBeatNote | null;
|
readonly next: PresenterBeatNote | null;
|
||||||
@@ -19,7 +19,7 @@ const DirectionLink = ({ note, children }: { readonly note: PresenterBeatNote |
|
|||||||
export const PresenterNavigationBar = ({ currentIndex, total, previous, next, syncController }: PresenterNavigationBarProps) => (
|
export const PresenterNavigationBar = ({ currentIndex, total, previous, next, syncController }: PresenterNavigationBarProps) => (
|
||||||
<nav className="presenter-navigation" aria-label="Presenter note navigation">
|
<nav className="presenter-navigation" aria-label="Presenter note navigation">
|
||||||
<DirectionLink note={previous}>← Previous</DirectionLink>
|
<DirectionLink note={previous}>← Previous</DirectionLink>
|
||||||
<span>{currentIndex + 1} / {total}</span>
|
<span>{currentIndex === null ? "Q&A" : `${currentIndex + 1} / ${total}`}</span>
|
||||||
<DirectionLink note={next}>Next →</DirectionLink>
|
<DirectionLink note={next}>Next →</DirectionLink>
|
||||||
<PresentationPairingPanel role="presenter" controller={syncController} />
|
<PresentationPairingPanel role="presenter" controller={syncController} />
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -178,4 +178,21 @@ describe("PresenterRoute", () => {
|
|||||||
expect(screen.getByText(/Defense Q&A/i).closest("details")).toHaveAttribute("open");
|
expect(screen.getByText(/Defense Q&A/i).closest("details")).toHaveAttribute("open");
|
||||||
expect(screen.getByRole("link", { name: /Where is the AI agent in this thesis/i })).toHaveAttribute("aria-current", "page");
|
expect(screen.getByRole("link", { name: /Where is the AI agent in this thesis/i })).toHaveAttribute("aria-current", "page");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps pairing and ended-session state available on Q&A routes", () => {
|
||||||
|
mockedUsePresentationSync.mockReturnValue({
|
||||||
|
...idleController(),
|
||||||
|
state: { kind: "ended", reason: "presenter_ended" },
|
||||||
|
});
|
||||||
|
window.location.hash = "#discuss/where-is-ai-agent";
|
||||||
|
|
||||||
|
render(<PresenterRoute />);
|
||||||
|
|
||||||
|
expect(screen.getByRole("heading", { name: /Where is the AI agent/i })).toBeInTheDocument();
|
||||||
|
const navigation = screen.getByRole("navigation", { name: /presenter note navigation/i });
|
||||||
|
expect(navigation).toHaveTextContent("Q&A");
|
||||||
|
expect(navigation.querySelectorAll('[aria-disabled="true"]')).toHaveLength(2);
|
||||||
|
expect(screen.getByRole("button", { name: /pair presentation/i })).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("The presenter ended this session.")).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -65,9 +65,9 @@ export const PresenterRoute = () => {
|
|||||||
? () => { moveFromCurrentHash("previous"); }
|
? () => { moveFromCurrentHash("previous"); }
|
||||||
: undefined}
|
: undefined}
|
||||||
>
|
>
|
||||||
{navigation.note && (
|
{(navigation.note || discussion) && (
|
||||||
<PresenterNavigationBar
|
<PresenterNavigationBar
|
||||||
currentIndex={navigation.index}
|
currentIndex={navigation.note ? navigation.index : null}
|
||||||
total={presenterNotes.length}
|
total={presenterNotes.length}
|
||||||
previous={navigation.previous}
|
previous={navigation.previous}
|
||||||
next={navigation.next}
|
next={navigation.next}
|
||||||
|
|||||||
Reference in New Issue
Block a user