mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-10-02 07:34:35 +08:00
fix(dsh-plugin): show observation outages on the collapsed capsule
The expanded header already replaced a stale "s1 · clicking" line with "reconnecting…", but the collapsed capsule and both status dots still read the last session as live. Collapse is the form users leave on the page, so an outage there looked like an in-progress action. - treat reconnecting as its own chrome state so the header and capsule dots leave the active color - keep the capsule's session count and swap the action timer for "reconnecting…" until the feed delivers a frame again
This commit is contained in:
@@ -239,14 +239,16 @@ function StopSessionAction(props: {
|
||||
);
|
||||
}
|
||||
|
||||
type ChromeState = "active" | "idle" | "error" | "reconnecting";
|
||||
|
||||
/** Flat status dot, specced after the BSK popup's ConnectionStatusIndicator. */
|
||||
function StatusDot({ state }: { state: "active" | "idle" | "error" | "dead" }) {
|
||||
function StatusDot({ state }: { state: ChromeState | "dead" }) {
|
||||
const color =
|
||||
state === "active"
|
||||
? "bg-emerald-500"
|
||||
: state === "error"
|
||||
? "bg-red-500"
|
||||
: state === "dead"
|
||||
: state === "dead" || state === "reconnecting"
|
||||
? "bg-amber-500"
|
||||
: "bg-muted-foreground/40";
|
||||
return (
|
||||
@@ -390,7 +392,13 @@ export function OverlayBody(props: {
|
||||
: focus === undefined
|
||||
? "no session"
|
||||
: `${focus.sessionId} · ${focus.action === "idle" ? "idle" : focus.action} · ${formatElapsed(focus.since, now)}`;
|
||||
const state = !available ? "error" : focus !== undefined ? statusOf(focus) : "idle";
|
||||
const state: ChromeState = reconnecting
|
||||
? "reconnecting"
|
||||
: !available
|
||||
? "error"
|
||||
: focus !== undefined
|
||||
? statusOf(focus)
|
||||
: "idle";
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -406,7 +414,7 @@ export function OverlayBody(props: {
|
||||
onPointerDown={onHeaderPointerDown}
|
||||
role="presentation"
|
||||
>
|
||||
<StatusDot state={state === "error" ? "error" : state === "active" ? "active" : "idle"} />
|
||||
<StatusDot state={state} />
|
||||
<span className={css["status-text"]}>{statusText}</span>
|
||||
{onUseFloating !== undefined ? (
|
||||
<IconAction
|
||||
@@ -660,21 +668,28 @@ export function ObservationOverlay({ store }: { store: ObservationClientStore })
|
||||
if (snapshot.sessions.length === 0) return null;
|
||||
|
||||
if (collapsed) {
|
||||
const state = focus !== undefined ? statusOf(focus) : "idle";
|
||||
const state: ChromeState = snapshot.reconnecting
|
||||
? "reconnecting"
|
||||
: focus !== undefined
|
||||
? statusOf(focus)
|
||||
: "idle";
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={cn(css.capsule, "bsk-obs")}
|
||||
data-state={state}
|
||||
data-testid="obs-capsule"
|
||||
aria-label="Expand browser observation overlay"
|
||||
onClick={() => setCollapsed(false)}
|
||||
>
|
||||
<StatusDot state={state} />
|
||||
<span className={css["capsule-text"]}>
|
||||
{snapshot.sessions.length} session{snapshot.sessions.length === 1 ? "" : "s"}
|
||||
{focus !== undefined && focus.action !== "idle"
|
||||
? ` · ${focus.action} · ${formatElapsed(focus.since, now)}`
|
||||
: ""}
|
||||
{snapshot.reconnecting
|
||||
? " · reconnecting…"
|
||||
: focus !== undefined && focus.action !== "idle"
|
||||
? ` · ${focus.action} · ${formatElapsed(focus.since, now)}`
|
||||
: ""}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -228,13 +228,37 @@ describe("ObservationOverlay", () => {
|
||||
h.es().readyState = 0;
|
||||
h.es().onerror?.({});
|
||||
});
|
||||
expect(screen.getByTestId("obs-header").textContent).toContain("reconnecting…");
|
||||
const header = screen.getByTestId("obs-header");
|
||||
expect(header.textContent).toContain("reconnecting…");
|
||||
expect(header.querySelector("[data-state]")?.getAttribute("data-state")).toBe("reconnecting");
|
||||
expect(screen.queryByText(/s1 · clicking/)).toBeNull();
|
||||
act(() => h.emitRaw({ type: "snapshot", sessions: [BUSY], available: true }));
|
||||
expect(screen.getByTestId("obs-header").textContent).not.toContain("reconnecting");
|
||||
expect(header.textContent).not.toContain("reconnecting");
|
||||
expect(header.querySelector("[data-state]")?.getAttribute("data-state")).toBe("active");
|
||||
expect(screen.getByText(/s1 · clicking/)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("collapses to a capsule that drops stale action timing while reconnecting", async () => {
|
||||
const h = makeHarness([BUSY]);
|
||||
render(<ObservationOverlay store={h.store} />);
|
||||
await screen.findByText(/s1 · clicking/);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Collapse" }));
|
||||
const capsule = await screen.findByTestId("obs-capsule");
|
||||
expect(capsule.textContent).toContain("clicking");
|
||||
expect(capsule.getAttribute("data-state")).toBe("active");
|
||||
act(() => {
|
||||
h.es().readyState = 0;
|
||||
h.es().onerror?.({});
|
||||
});
|
||||
expect(capsule.textContent).toContain("reconnecting…");
|
||||
expect(capsule.textContent).not.toContain("clicking");
|
||||
expect(capsule.getAttribute("data-state")).toBe("reconnecting");
|
||||
expect(capsule.querySelector("[data-state]")?.getAttribute("data-state")).toBe("reconnecting");
|
||||
act(() => h.emitRaw({ type: "snapshot", sessions: [BUSY], available: true }));
|
||||
expect(capsule.textContent).toContain("clicking");
|
||||
expect(capsule.getAttribute("data-state")).toBe("active");
|
||||
});
|
||||
|
||||
it("shows the status row and a placeholder without a thumbnail", async () => {
|
||||
const h = makeHarness([BUSY]);
|
||||
render(<ObservationOverlay store={h.store} />);
|
||||
|
||||
Reference in New Issue
Block a user