diff --git a/frontend/src/renderer/components/SessionInspector.test.tsx b/frontend/src/renderer/components/SessionInspector.test.tsx
index a5d654777..77c0bd67a 100644
--- a/frontend/src/renderer/components/SessionInspector.test.tsx
+++ b/frontend/src/renderer/components/SessionInspector.test.tsx
@@ -173,7 +173,7 @@ describe("SessionInspector Activity section", () => {
it.each([
["idle", "Idle"],
["active", "Working"],
- ["waiting_input", "Input needed"],
+ ["waiting_input", "Input Needed"],
["exited", "Exited"],
] as const)("renders %s from raw session activity", (state, label) => {
renderWithQuery(
@@ -188,6 +188,40 @@ describe("SessionInspector Activity section", () => {
expect(activitySection().getByText(label)).toBeInTheDocument();
});
+ it("renders unknown activity as unavailable instead of leaking the internal enum", () => {
+ renderWithQuery(
+ ,
+ );
+
+ expect(activitySection().getByText("Activity Unavailable")).toBeInTheDocument();
+ expect(activitySection().queryByText("Unknown")).not.toBeInTheDocument();
+ });
+
+ it("falls back to unavailable when no activity has been reported", () => {
+ renderWithQuery();
+
+ expect(activitySection().getByText("Activity Unavailable")).toBeInTheDocument();
+ });
+
+ it("keeps the last known activity visible when the daemon reports no signal", () => {
+ renderWithQuery(
+ ,
+ );
+
+ 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", () => {
renderWithQuery(
{
);
expect(activitySection().getByText("Idle")).toBeInTheDocument();
- expect(activitySection().queryByText("Input needed")).not.toBeInTheDocument();
+ expect(activitySection().queryByText("Input Needed")).not.toBeInTheDocument();
});
it.each([
- ["ci_failed", "CI failed"],
- ["changes_requested", "Changes requested"],
+ ["ci_failed", "CI Failed"],
+ ["changes_requested", "Changes Requested"],
] as const)("renders %s as an SCM state in the current Activity row", (status, label) => {
renderWithQuery(
{
expect(activitySection().getByText("Opened")).toBeInTheDocument();
expect(activitySection().getByText("PR #7")).toBeInTheDocument();
const activityRow = activitySection().getByText("Idle").closest(".inspector-timeline__ev") as HTMLElement;
- expect(within(activityRow).getByText("CI failed")).toBeInTheDocument();
- expect(within(activityRow).getByText("Changes requested")).toBeInTheDocument();
+ expect(within(activityRow).getByText("CI Failed")).toBeInTheDocument();
+ expect(within(activityRow).getByText("Changes Requested")).toBeInTheDocument();
});
it("orders timeline milestones around the combined current state row", () => {
diff --git a/frontend/src/renderer/components/SessionInspector.tsx b/frontend/src/renderer/components/SessionInspector.tsx
index 485ea448d..192c2677c 100644
--- a/frontend/src/renderer/components/SessionInspector.tsx
+++ b/frontend/src/renderer/components/SessionInspector.tsx
@@ -298,6 +298,11 @@ function ActivityTimeline({ session }: { session: WorkspaceSession }) {
+ {session.status === "no_signal" ? (
+
+
+
+ ) : null}
{scmTimelineStates(session).map((state) => (
@@ -352,16 +357,20 @@ function ActivityTimeline({ session }: { session: WorkspaceSession }) {
const ACTIVITY_PILL: Record = {
active: { label: "Working", tone: "var(--orange)", breathe: true },
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 },
- 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";
const SCM_PILL: Record = {
- ci_failed: { label: "CI failed", tone: "var(--red)", breathe: false },
- changes_requested: { label: "Changes requested", tone: "var(--amber)", breathe: false },
+ ci_failed: { label: "CI Failed", tone: "var(--red)", breathe: false },
+ changes_requested: { label: "Changes Requested", tone: "var(--amber)", breathe: false },
conflict: { label: "Conflict", tone: "var(--red)", breathe: false },
};