Artifact chip opens the viewer first-click even when the rail is hidden

The select-listener was gated on rail visibility, so the chip's one event only
unhid an empty rail; now it listens whenever a session is active.
This commit is contained in:
Rohit C Prasad
2026-08-15 14:11:16 -07:00
committed by Rohit P
parent 44e0e8566f
commit 8e2058d2a9
3 changed files with 28 additions and 2 deletions
+15
View File
@@ -36,3 +36,18 @@ test("HTML artifact offers Open in browser as the unsandboxed escape hatch", asy
await openReport(page);
await expect(page.getByTestId("artifact-open-browser")).toBeVisible();
});
test("a transcript chip opens the viewer on the FIRST click even with the rail hidden", async ({
page,
}) => {
// Owner-hit 2026-08-15: the chip fires one event; the rail's select-listener was only
// registered while the rail was visible, so click #1 unhid an empty rail and the
// selection was lost — the viewer appeared only on a later click.
await page.goto("/");
await page.getByPlaceholder(/Ask the coworker/).fill("show the report");
await page.getByRole("button", { name: "Send" }).click();
await page.getByRole("button", { name: "Hide side panel" }).click();
await page.getByTestId("artifact-chip").click();
await expect(page.getByTestId("artifact-frame")).toBeVisible();
});
+8
View File
@@ -610,6 +610,14 @@ export async function mockApi(page: import("@playwright/test").Page) {
});
return; // suspended on the approval
}
// A deliverable turn ending in an artifact chip (§34) — for the chip-open flow.
if (/show the report/i.test(msg.text)) {
send("assistant_message", {
text: "Done — [Security review](artifact:reports/security-review.html)",
});
send("turn_done");
return;
}
// The pre-fix payload shape (owner-hit 2026-08-14): no installable/version/summary
// — an older sidecar, or any surface that forgets the field. Must render NOT
// installable, never a guessed Install offer.
+5 -2
View File
@@ -118,8 +118,11 @@ export function RightRail({
// §34 (UX-016): [Title](artifact:path) chips in the transcript open the viewer directly.
// Resolve against the loaded list first; on a miss, refresh once (the file may be
// seconds old), then fall back to a minimal record — readArtifact validates the path.
// Registered even while the rail is HIDDEN (owner-hit 2026-08-15): the chip fires ONE
// event, and App's unhide listener and this one race it — gating this on `active`
// dropped the selection, so the first click only opened an empty rail.
useEffect(() => {
if (!active) return;
if (!sessionId) return;
const minimal = (path: string): ArtifactInfo => ({
path,
name: path.split("/").pop() || path,
@@ -146,7 +149,7 @@ export function RightRail({
};
window.addEventListener(OPEN_ARTIFACT_EVENT, onOpen);
return () => window.removeEventListener(OPEN_ARTIFACT_EVENT, onOpen);
}, [active, sessionId, artifacts]);
}, [sessionId, artifacts]);
if (!active) return null;