fix(replay-ui): scope the screenshot property to the named state panel

This commit is contained in:
pj committed 2026-08-13 01:09:26 +05:30
1 parent 4fce5f7c82
commit d945c8bb13
2 files changed
+33 -15

No files matched your search

+31 -13
View File
@@ -43,12 +43,15 @@ const stepRows = extract("stepRows", (s) =>
})), })),
); );
// The screenshot panels' claim, read off the image URL each one is rendering. // What the "state before" panel is showing, read off the image URL it renders.
// The "state before" panel comes first in the DOM and shows the selected step; // Scoped to that panel by name rather than by DOM position: an earlier draft
// "state after" shows the next one. // took the first screenshot on the page, and the fuzzer put the before panel on
const screenshotSteps = extract("screenshotSteps", (s) => // another tab, which left the after panel's image first and fired the property
s.ax.findAll({ "data-testid": "screenshot" }).map((image) => numberOf(dataOf(image, "step"))), // against a UI that was behaving correctly.
); const beforeScreenshotStep = extract("beforeScreenshotStep", (s) => {
const image = s.ax.find([{ "data-testid": "state-before" }, { "data-testid": "screenshot" }]);
return image ? numberOf(dataOf(image, "step")) : null;
});
// Which tab is selected in each tab strip, as one comparable string. // Which tab is selected in each tab strip, as one comparable string.
const activeTabs = extract("activeTabs", (s) => const activeTabs = extract("activeTabs", (s) =>
@@ -100,10 +103,9 @@ const stepCountMatchesTheList = always(() => {
// prints the step from the URL. They must name the same step. // prints the step from the URL. They must name the same step.
const screenshotShowsTheSelectedStep = always(() => { const screenshotShowsTheSelectedStep = always(() => {
const current = toolbar.current; const current = toolbar.current;
const shown = screenshotSteps.current; const shown = beforeScreenshotStep.current;
if (!current || current.step === null || shown.length === 0) return true; if (!current || current.step === null || shown === null) return true;
const before = shown[0]; return shown === current.step;
return before === null || before === current.step;
}); });
// Switching a tab is a view change, not a navigation: it must never move the // Switching a tab is a view change, not a navigation: it must never move the
@@ -154,6 +156,22 @@ const selectAStep = actions(() => {
const arrowKeys = from<Key>(["left", "right"]); const arrowKeys = from<Key>(["left", "right"]);
const navigateByKeyboard = actions(() => [PressKey({ key: arrowKeys.generate() })]); const navigateByKeyboard = actions(() => [PressKey({ key: arrowKeys.generate() })]);
// defaultActions carries the rest: the tab strips, the jump-to-violation // Tabs get their own weight rather than being left to the undirected tap mix:
// button, the theme toggle, the link back to the run list, and the scrolling. // with ~15 clickable elements on the page, an undirected run went 40 steps
export const actionsRoot = weighted([35, selectAStep], [25, navigateByKeyboard], [40, defaultActions]); // without switching a single tab, which left both tab-facing properties
// vacuously true. Weighting them is what makes those properties mean something.
const tabElements = extract("tabElements", (s) => s.ax.findAll({ "data-testid": "tab" }));
const switchATab = actions(() => {
const tabs = tabElements.current;
return tabs.length === 0 ? [] : [Tap({ on: from(tabs).generate() })];
});
// defaultActions carries the rest: the jump-to-violation button, the theme
// toggle, the link back to the run list, and the scrolling.
export const actionsRoot = weighted(
[30, selectAStep],
[20, navigateByKeyboard],
[25, switchATab],
[25, defaultActions],
);
+2 -2
View File
@@ -323,14 +323,14 @@ export default function RunDetail() {
</div> </div>
</aside> </aside>
<section className="detail-state-before detail-panel"> <section className="detail-state-before detail-panel" data-testid="state-before">
<h2>state before</h2> <h2>state before</h2>
<div className="detail-panel-body"> <div className="detail-panel-body">
<Tabs tabs={beforeTabs} defaultTabId="screenshot" ariaLabel="state before" /> <Tabs tabs={beforeTabs} defaultTabId="screenshot" ariaLabel="state before" />
</div> </div>
</section> </section>
<section className="detail-state-after detail-panel"> <section className="detail-state-after detail-panel" data-testid="state-after">
<h2>state after</h2> <h2>state after</h2>
<div className="detail-panel-body"> <div className="detail-panel-body">
<Tabs tabs={afterTabs} defaultTabId="screenshot" ariaLabel="state after" /> <Tabs tabs={afterTabs} defaultTabId="screenshot" ariaLabel="state after" />