Files
nexu-io__open-design/apps/web/tests/runtime/slide-nav.test.ts
lefarcen e19c057d8d fix(web): flip deck preview to a queued send's marked slide (#3668)
* fix(web): flip deck preview to a queued send's marked slide

When several edits are queued against marked elements on different deck
slides, the preview stayed parked on slide 1 while the agent drained the
queue — the user couldn't see each edit land in context.

The mark's slide index was already captured into the comment snapshot and
target, but it was dropped when the snapshot became a ChatCommentAttachment,
so nothing downstream knew which slide a queued send touched. Carry
`slideIndex` through the contract and both attachment builders, then have
ProjectView arm a `slideNavRequest` (mirroring `shareRequest`) the moment a
queued send starts processing. FileWorkspace activates the deck tab if it is
open and FileViewer consumes the nonce once to flip the preview via the
existing `od:slide` go bridge, writing the cached slide state first so a
freshly-mounted iframe restores to the same slide on load.

No-op for plain prompts, free pins, or marks without a slide index.

* fix(web): keep deck slide-nav consume-once across viewer remounts

The consumed-nonce guard lived in a per-mount ref, so leaving the deck tab
and returning remounted HtmlViewer, reset the ref, and replayed the still-live
slideNavRequest — yanking the preview back to the queued slide and clobbering
manual navigation. Move the guard into shouldConsumeSlideNav, keyed by preview
state key outside the component, so "consume once" survives remounts. Add a
unit spec covering first-handling, fresh-nonce, and per-file isolation.

* fix(web): only flip a deck whose tab was open when the queued send started

slideNavRequest stayed live in parent state, so a request for a deck that was
closed at processing-start would resurface and jump the preview when the user
opened that file by hand much later — not the follow-along behavior the feature
promises. Decide deliverability once, at fire time: FileWorkspace marks the
nonce deliverable only when the target deck is already an open tab, and the
viewer is fed the request only for that deliverable nonce on the active file.
A closed deck never becomes deliverable, so opening it later cannot jump.

Pure helpers isSlideNavDeliverableNow / deliverableSlideNavForActiveFile carry
the decision, with specs for the closed-deck-opened-later case.
2026-06-04 13:48:03 +00:00

63 lines
2.9 KiB
TypeScript

import { beforeEach, describe, expect, it } from 'vitest';
import {
deliverableSlideNavForActiveFile,
isSlideNavDeliverableNow,
resetConsumedSlideNavForTests,
shouldConsumeSlideNav,
} from '../../src/runtime/slide-nav';
describe('shouldConsumeSlideNav', () => {
beforeEach(() => resetConsumedSlideNavForTests());
it('navigates only on the first handling of a request, even across remounts', () => {
const key = 'proj:deck.html';
// First mount handles the queued send's request → flip the preview.
expect(shouldConsumeSlideNav(key, 5)).toBe(true);
// The request stays live in parent state; leaving the deck tab and coming
// back remounts the viewer with the same nonce. It must NOT flip again —
// otherwise manual navigation after the queued send gets yanked back.
expect(shouldConsumeSlideNav(key, 5)).toBe(false);
expect(shouldConsumeSlideNav(key, 5)).toBe(false);
});
it('navigates again when a new queued send arms a fresh nonce', () => {
const key = 'proj:deck.html';
expect(shouldConsumeSlideNav(key, 5)).toBe(true);
expect(shouldConsumeSlideNav(key, 9)).toBe(true);
expect(shouldConsumeSlideNav(key, 9)).toBe(false);
});
it('tracks consumed nonces per file independently', () => {
expect(shouldConsumeSlideNav('proj:a.html', 5)).toBe(true);
expect(shouldConsumeSlideNav('proj:b.html', 5)).toBe(true);
expect(shouldConsumeSlideNav('proj:a.html', 5)).toBe(false);
expect(shouldConsumeSlideNav('proj:b.html', 5)).toBe(false);
});
});
describe('slide-nav deliverability (follow-along only)', () => {
it('is deliverable only when the target deck is already an open tab', () => {
expect(isSlideNavDeliverableNow({ name: 'deck.html' }, ['deck.html', 'index.html'])).toBe(true);
expect(isSlideNavDeliverableNow({ name: 'deck.html' }, ['index.html'])).toBe(false);
expect(isSlideNavDeliverableNow({ name: '' }, ['index.html'])).toBe(false);
expect(isSlideNavDeliverableNow(null, ['deck.html'])).toBe(false);
});
it('forwards a request to the active file once it is marked deliverable', () => {
const req = { name: 'deck.html', slideIndex: 3, nonce: 7 };
// Target was open at fire time → its nonce became deliverable.
expect(deliverableSlideNavForActiveFile(req, 'deck.html', 7)).toEqual({ slideIndex: 3, nonce: 7 });
});
it('does NOT jump a deck the user opens after a queued send started while it was closed', () => {
const req = { name: 'deck.html', slideIndex: 3, nonce: 7 };
// Deck was closed when the send started → nonce never became deliverable
// (deliverableNonce stays null). Later opening the deck makes it the active
// file, but the stale request must not resurface and flip the preview.
expect(deliverableSlideNavForActiveFile(req, 'deck.html', null)).toBeNull();
// A different deck being active never matches either.
expect(deliverableSlideNavForActiveFile(req, 'other.html', 7)).toBeNull();
});
});