fix: hide unknown activity state in inspector

This commit is contained in:
whoisasx 2026-07-03 18:28:44 +05:30
parent 8d8fb42a94
commit 60bb0ef064
2 changed files with 53 additions and 10 deletions

View File

@ -173,7 +173,7 @@ describe("SessionInspector Activity section", () => {
it.each([ it.each([
["idle", "Idle"], ["idle", "Idle"],
["active", "Working"], ["active", "Working"],
["waiting_input", "Input needed"], ["waiting_input", "Input Needed"],
["exited", "Exited"], ["exited", "Exited"],
] as const)("renders %s from raw session activity", (state, label) => { ] as const)("renders %s from raw session activity", (state, label) => {
renderWithQuery( renderWithQuery(
@ -188,6 +188,40 @@ describe("SessionInspector Activity section", () => {
expect(activitySection().getByText(label)).toBeInTheDocument(); expect(activitySection().getByText(label)).toBeInTheDocument();
}); });
it("renders unknown activity as unavailable instead of leaking the internal enum", () => {
renderWithQuery(
<SessionInspector
session={session([], {
status: "working",
activity: { state: "unknown", lastActivityAt: "2026-06-15T10:00:00Z" },
})}
/>,
);
expect(activitySection().getByText("Activity Unavailable")).toBeInTheDocument();
expect(activitySection().queryByText("Unknown")).not.toBeInTheDocument();
});
it("falls back to unavailable when no activity has been reported", () => {
renderWithQuery(<SessionInspector session={session([], { status: "working" })} />);
expect(activitySection().getByText("Activity Unavailable")).toBeInTheDocument();
});
it("keeps the last known activity visible when the daemon reports no signal", () => {
renderWithQuery(
<SessionInspector
session={session([], {
status: "no_signal",
activity: { state: "idle", lastActivityAt: "2026-06-15T10:00:00Z" },
})}
/>,
);
const activityRow = activitySection().getByText("Idle").closest(".inspector-timeline__ev") as HTMLElement;
expect(within(activityRow).getByText("No Signal")).toBeInTheDocument();
});
it("does not derive the Activity label from PR-oriented session status", () => { it("does not derive the Activity label from PR-oriented session status", () => {
renderWithQuery( renderWithQuery(
<SessionInspector <SessionInspector
@ -199,12 +233,12 @@ describe("SessionInspector Activity section", () => {
); );
expect(activitySection().getByText("Idle")).toBeInTheDocument(); expect(activitySection().getByText("Idle")).toBeInTheDocument();
expect(activitySection().queryByText("Input needed")).not.toBeInTheDocument(); expect(activitySection().queryByText("Input Needed")).not.toBeInTheDocument();
}); });
it.each([ it.each([
["ci_failed", "CI failed"], ["ci_failed", "CI Failed"],
["changes_requested", "Changes requested"], ["changes_requested", "Changes Requested"],
] as const)("renders %s as an SCM state in the current Activity row", (status, label) => { ] as const)("renders %s as an SCM state in the current Activity row", (status, label) => {
renderWithQuery( renderWithQuery(
<SessionInspector <SessionInspector
@ -265,8 +299,8 @@ describe("SessionInspector Activity section", () => {
expect(activitySection().getByText("Opened")).toBeInTheDocument(); expect(activitySection().getByText("Opened")).toBeInTheDocument();
expect(activitySection().getByText("PR #7")).toBeInTheDocument(); expect(activitySection().getByText("PR #7")).toBeInTheDocument();
const activityRow = activitySection().getByText("Idle").closest(".inspector-timeline__ev") as HTMLElement; const activityRow = activitySection().getByText("Idle").closest(".inspector-timeline__ev") as HTMLElement;
expect(within(activityRow).getByText("CI failed")).toBeInTheDocument(); expect(within(activityRow).getByText("CI Failed")).toBeInTheDocument();
expect(within(activityRow).getByText("Changes requested")).toBeInTheDocument(); expect(within(activityRow).getByText("Changes Requested")).toBeInTheDocument();
}); });
it("orders timeline milestones around the combined current state row", () => { it("orders timeline milestones around the combined current state row", () => {

View File

@ -298,6 +298,11 @@ function ActivityTimeline({ session }: { session: WorkspaceSession }) {
<span className="inspector-timeline__badge"> <span className="inspector-timeline__badge">
<InspectorActivityPill state={session.activity?.state ?? "unknown"} /> <InspectorActivityPill state={session.activity?.state ?? "unknown"} />
</span> </span>
{session.status === "no_signal" ? (
<span className="inspector-timeline__badge">
<TimelinePill {...ACTIVITY_WARNING_PILL.no_signal} />
</span>
) : null}
{scmTimelineStates(session).map((state) => ( {scmTimelineStates(session).map((state) => (
<span key={state} className="inspector-timeline__badge"> <span key={state} className="inspector-timeline__badge">
<InspectorScmPill state={state} /> <InspectorScmPill state={state} />
@ -352,16 +357,20 @@ function ActivityTimeline({ session }: { session: WorkspaceSession }) {
const ACTIVITY_PILL: Record<SessionActivityState, { label: string; tone: string; breathe: boolean }> = { const ACTIVITY_PILL: Record<SessionActivityState, { label: string; tone: string; breathe: boolean }> = {
active: { label: "Working", tone: "var(--orange)", breathe: true }, active: { label: "Working", tone: "var(--orange)", breathe: true },
idle: { label: "Idle", tone: "var(--fg-muted)", breathe: false }, idle: { label: "Idle", tone: "var(--fg-muted)", breathe: false },
waiting_input: { label: "Input needed", tone: "var(--amber)", breathe: false }, waiting_input: { label: "Input Needed", tone: "var(--amber)", breathe: false },
exited: { label: "Exited", tone: "var(--fg-muted)", breathe: false }, exited: { label: "Exited", tone: "var(--fg-muted)", breathe: false },
unknown: { label: "Unknown", tone: "var(--fg-muted)", breathe: false }, unknown: { label: "Activity Unavailable", tone: "var(--fg-muted)", breathe: false },
};
const ACTIVITY_WARNING_PILL: Record<"no_signal", { label: string; tone: string; breathe: boolean }> = {
no_signal: { label: "No Signal", tone: "var(--fg-muted)", breathe: false },
}; };
type ScmTimelineState = "ci_failed" | "changes_requested" | "conflict"; type ScmTimelineState = "ci_failed" | "changes_requested" | "conflict";
const SCM_PILL: Record<ScmTimelineState, { label: string; tone: string; breathe: boolean }> = { const SCM_PILL: Record<ScmTimelineState, { label: string; tone: string; breathe: boolean }> = {
ci_failed: { label: "CI failed", tone: "var(--red)", breathe: false }, ci_failed: { label: "CI Failed", tone: "var(--red)", breathe: false },
changes_requested: { label: "Changes requested", tone: "var(--amber)", breathe: false }, changes_requested: { label: "Changes Requested", tone: "var(--amber)", breathe: false },
conflict: { label: "Conflict", tone: "var(--red)", breathe: false }, conflict: { label: "Conflict", tone: "var(--red)", breathe: false },
}; };