diff --git a/.changeset/taskcard-time-indicator-renamed-lanes.md b/.changeset/taskcard-time-indicator-renamed-lanes.md
new file mode 100644
index 0000000000..858d8a728c
--- /dev/null
+++ b/.changeset/taskcard-time-indicator-renamed-lanes.md
@@ -0,0 +1,7 @@
+---
+"@runfusion/fusion": patch
+---
+
+summary: Cards in renamed in-progress, review or completion lanes now show their elapsed-time indicator.
+category: fix
+dev: The `TIME_INDICATOR_COLUMNS` legacy id set gated both the indicator memo and the chip layout; it is now a role question with that set kept as the no-flags fallback.
diff --git a/packages/dashboard/app/components/TaskCard.tsx b/packages/dashboard/app/components/TaskCard.tsx
index e96da7faf8..9e1c638023 100644
--- a/packages/dashboard/app/components/TaskCard.tsx
+++ b/packages/dashboard/app/components/TaskCard.tsx
@@ -1063,6 +1063,26 @@ function TaskCardComponent({
const isCompleteColumn = isCompleteColumnRole(taskColumnFlags, task.column);
const isArchivedColumn = isArchivedColumnRole(taskColumnFlags, task.column);
+ /*
+ FNXC:WorkflowResolvedColumns 2026-07-31-03:15:
+ THE TIME-INDICATOR GATE IS A ROLE QUESTION, not membership of a hardcoded id set.
+
+ `TIME_INDICATOR_COLUMNS` is `{in-progress, in-review, done}`, so a card in a renamed WIP, review or
+ completion lane was rejected before its traits were consulted and rendered no elapsed time at all.
+ #2996 fixed the SUBSCRIPTION for these cards — the memo that decides whether to join the shared
+ ticker kept a pre-load answer — which made them eligible and still not visible, because this gate
+ rejects them first. Both halves are needed; that PR's claim covered only one.
+
+ The census could not point here: it counts COMPARISONS against legacy ids, and this is a Set
+ literal, which is a DEFINITION. Same blind spot that hid `BLOCKER_ESCALATION_COLUMNS`.
+
+ DELIBERATE-LITERAL — the legacy set stays as the no-flags fallback, so a card whose traits have not
+ resolved (first paint, or a lane its workflow no longer declares) behaves exactly as before.
+ */
+ const showsTimeIndicator = taskColumnFlags
+ ? isWipColumn || isReviewColumn || isCompleteColumn
+ : TIME_INDICATOR_COLUMNS.has(task.column);
+
const [isSaving, setIsSaving] = useState(false);
const [showSteps, setShowSteps] = useState(
isWipColumn ||
@@ -1754,7 +1774,7 @@ function TaskCardComponent({
const timeIndicatorNowMs = useLiveTimeTicker(wantsLiveTimeIndicator);
const timeIndicator = useMemo(() => {
- if (!TIME_INDICATOR_COLUMNS.has(task.column)) {
+ if (!showsTimeIndicator) {
return null;
}
@@ -3129,7 +3149,7 @@ function TaskCardComponent({
return null;
})();
- const chipFarRight = TIME_INDICATOR_COLUMNS.has(task.column)
+ const chipFarRight = showsTimeIndicator
&& filesChangedButton == null
&& showTrackingIndicator
&& Boolean(githubTrackedIssue);
diff --git a/packages/dashboard/app/components/__tests__/TaskCard.time-indicator-renamed-lanes.test.tsx b/packages/dashboard/app/components/__tests__/TaskCard.time-indicator-renamed-lanes.test.tsx
new file mode 100644
index 0000000000..0a80d47432
--- /dev/null
+++ b/packages/dashboard/app/components/__tests__/TaskCard.time-indicator-renamed-lanes.test.tsx
@@ -0,0 +1,90 @@
+/*
+FNXC:WorkflowResolvedColumns 2026-07-31-03:10:
+THE TIME INDICATOR IS GATED ON A HARDCODED LEGACY LANE SET, so it never renders on a renamed board.
+
+`TIME_INDICATOR_COLUMNS` is `{in-progress, in-review, done}`. Both the `timeIndicator` memo and the
+`chipFarRight` layout test `task.column` against it directly, so a card in a renamed WIP, review or
+completion lane returns `null` no matter what its resolved traits say.
+
+WHY THIS IS A SEPARATE DEFECT FROM #2996, and a correction to that PR's claim: #2996 fixed the
+SUBSCRIPTION — `wantsLiveTimeIndicator` kept a pre-load answer and the card never joined the shared
+ticker. That was real, and it was not sufficient. The card now subscribes and still renders nothing,
+because this gate rejects it first. I described that PR as making renamed-lane cards "show their live
+elapsed-time indicator"; it made them eligible to.
+
+WHY NO CHECK SAW IT. The census counts COMPARISONS against legacy ids. This is a Set literal — a
+DEFINITION — so nothing in the backlog ever pointed here, the same blind spot that hid
+`BLOCKER_ESCALATION_COLUMNS` until it was found by hand.
+*/
+
+import { describe, it, expect, vi } from "vitest";
+import { render } from "@testing-library/react";
+import type { Task } from "@fusion/core";
+import { TaskCard } from "../TaskCard";
+
+vi.mock("../../hooks/useLiveTimeTicker", () => ({
+ useLiveTimeTicker: () => Date.parse("2026-06-01T01:00:00.000Z"),
+}));
+
+const noop = () => {};
+
+function runningTaskIn(column: string): Task {
+ return {
+ id: "KB-1",
+ title: "a running card",
+ description: "t",
+ column,
+ createdAt: "2026-06-01T00:00:00.000Z",
+ updatedAt: "2026-06-01T00:00:00.000Z",
+ executionStartedAt: "2026-06-01T00:00:00.000Z",
+ cumulativeActiveMs: 120_000,
+ timedExecutionMs: 120_000,
+ steps: [],
+ } as unknown as Task;
+}
+
+/** The indicator renders an elapsed duration; any of these means it is present. */
+const hasDuration = (root: HTMLElement) => /\d+\s*(s|m|h)\b|\d+:\d\d/.test(root.textContent ?? "");
+
+describe("the card time indicator under a renamed board vocabulary", () => {
+ /* Control: the legacy vocabulary renders it, with no flags at all. */
+ it("default vocabulary: a card in `in-progress` shows an elapsed time", () => {
+ const { container } = render(
+ ,
+ );
+
+ expect(hasDuration(container as unknown as HTMLElement)).toBe(true);
+ });
+
+ /* The defect: the renamed WIP lane is not in the hardcoded set, so nothing renders. */
+ it("renamed vocabulary: a card whose traits say WIP shows an elapsed time", () => {
+ const { container } = render(
+ ,
+ );
+
+ expect(hasDuration(container as unknown as HTMLElement)).toBe(true);
+ });
+
+ /*
+ The paired negative: resolving traits must not put a live timer on every lane. A card in the
+ renamed INTAKE lane has not started, so it must stay out — otherwise the fix trades a missing
+ indicator for a running clock on work that has not begun.
+ */
+ it("renamed vocabulary: a card in the intake lane shows no elapsed time", () => {
+ const { container } = render(
+ ,
+ );
+
+ expect(hasDuration(container as unknown as HTMLElement)).toBe(false);
+ });
+});