diff --git a/.changeset/fn-8430-tui-log-timestamps.md b/.changeset/fn-8430-tui-log-timestamps.md
new file mode 100644
index 0000000000..c3fdba9eb9
--- /dev/null
+++ b/.changeset/fn-8430-tui-log-timestamps.md
@@ -0,0 +1,7 @@
+---
+"@runfusion/fusion": patch
+---
+
+summary: Stop the dashboard TUI Logs tab from showing detailed timestamps on each log line.
+category: fix
+dev: Compact LogsPanel time to HH:MM:SS; strip leading YYYY-MM-DD HH:MM:SS.mmm TZ prefixes from displayed messages (e.g. embedded Postgres logs).
diff --git a/packages/cli/src/commands/dashboard-tui/__tests__/app.test.tsx b/packages/cli/src/commands/dashboard-tui/__tests__/app.test.tsx
index 6627450af1..7ef6e080d0 100644
--- a/packages/cli/src/commands/dashboard-tui/__tests__/app.test.tsx
+++ b/packages/cli/src/commands/dashboard-tui/__tests__/app.test.tsx
@@ -2,8 +2,14 @@ import React from "react";
import { describe, it, expect, vi, afterEach } from "vitest";
import { render } from "ink-testing-library";
import { I18nextProvider } from "react-i18next";
-import { DashboardApp } from "../app.js";
+import { DashboardApp, stripLeadingDetailedTimestamp } from "../app.js";
import { initCliI18n } from "../../../i18n/index.js";
+
+const copyToClipboardMock = vi.hoisted(() => vi.fn());
+
+vi.mock("../utils.js", () => ({
+ copyToClipboard: copyToClipboardMock,
+}));
import { DashboardTUI } from "../controller.js";
import { createInitialState } from "../state.js";
import type { ProjectItem, TaskItem, AgentItem, AgentDetailItem, ModelItem, SettingsValues, TaskDetailData } from "../state.js";
@@ -168,6 +174,7 @@ function setTerminalSize(instance: { stdout: unknown }, columns: number, rows: n
afterEach(() => {
vi.useRealTimers();
+ copyToClipboardMock.mockReset();
});
// 10s bound: ink schedules frames on timer ticks and has flaked past 3s under
@@ -1189,7 +1196,7 @@ describe("LogsPanel narrow formatting", () => {
wideRender.unmount();
});
- it("preserves full timestamp formatting in wide terminals", async () => {
+ it("uses compact timestamp formatting in wide terminals", async () => {
const controller = newController();
controller.setSystemInfo(makeSystemInfo());
controller.setActiveSection("logs");
@@ -1203,7 +1210,87 @@ describe("LogsPanel narrow formatting", () => {
const frame = rendered.lastFrame() ?? "";
expect(frame).toContain("wide timestamp");
- expect(frame).toMatch(/\d{2}:\d{2}:\d{2}\.\d{3}/);
+ expect(frame).toMatch(/\d{2}:\d{2}:\d{2}/);
+ expect(frame).not.toMatch(/\d{2}:\d{2}:\d{2}\.\d{3}/);
+ rendered.unmount();
+ });
+
+ it("strips embedded detailed timestamps from wide and narrow log rows", async () => {
+ for (const columns of [60, 120]) {
+ const controller = newController();
+ controller.setSystemInfo(makeSystemInfo());
+ controller.setActiveSection("logs");
+ controller.log("2026-07-20 09:20:38.221 PDT checkpoint starting", "startup-factory");
+ controller.setSelectedLogIndex(0);
+
+ const rendered = render(renderDashboardAppNode(controller));
+ setTerminalSize(rendered, columns, 24);
+ rendered.rerender(renderDashboardAppNode(controller));
+ await flushFrames();
+ const frame = rendered.lastFrame() ?? "";
+
+ expect(frame).toContain("checkpoint starting");
+ expect(frame).toContain(columns < 80 ? "[start…]" : "[startup-facto");
+ expect(frame).toMatch(/[✓⚠✗]/);
+ expect(frame).not.toContain("2026-07-20");
+ expect(frame).not.toContain("PDT");
+ expect(frame).not.toMatch(/\d{2}:\d{2}:\d{2}\.\d{3}/);
+ rendered.unmount();
+ }
+ });
+
+ it("keeps raw embedded timestamp text in expanded logs with compact capture time", async () => {
+ const controller = newController();
+ controller.setSystemInfo(makeSystemInfo());
+ controller.setActiveSection("logs");
+ controller.log("2026-07-20 09:20:38.221 PDT checkpoint starting", "startup-factory");
+ controller.setSelectedLogIndex(0);
+
+ const rendered = render(renderDashboardAppNode(controller));
+ setTerminalSize(rendered, 120, 24);
+ rendered.rerender(renderDashboardAppNode(controller));
+ rendered.stdin.write("\r");
+ await waitForFrameContains(rendered.lastFrame, "2026-07-20 09:20:38.221 PDT checkpoint starting");
+ const frame = rendered.lastFrame() ?? "";
+ const timeLine = frame.split("\n").find((line) => line.includes("Time:")) ?? "";
+
+ expect(timeLine).toMatch(/Time:\s+\d{2}:\d{2}:\d{2}/);
+ expect(timeLine).not.toMatch(/\d{2}:\d{2}:\d{2}\.\d{3}/);
+ expect(frame).toContain("2026-07-20 09:20:38.221 PDT checkpoint starting");
+ rendered.unmount();
+ });
+
+ it("copies compact time and stripped list message for timestamped entries", async () => {
+ copyToClipboardMock.mockResolvedValue(true);
+ const controller = newController();
+ controller.setSystemInfo(makeSystemInfo());
+ controller.setActiveSection("logs");
+ controller.log("2026-07-20 09:20:38.221 PDT checkpoint starting", "startup-factory");
+ controller.setSelectedLogIndex(0);
+
+ const rendered = render(renderDashboardAppNode(controller));
+ rendered.stdin.write("c");
+ await vi.waitFor(() => {
+ expect(copyToClipboardMock).toHaveBeenCalledTimes(1);
+ });
+
+ const copied = copyToClipboardMock.mock.calls[0][0] as string;
+ expect(copied).toMatch(/^\d{2}:\d{2}:\d{2} INFO \[startup-factory\] checkpoint starting$/);
+ expect(copied).not.toContain("2026-07-20");
+ expect(copied).not.toContain("PDT");
+ expect(copied).not.toMatch(/\d{2}:\d{2}:\d{2}\.\d{3}/);
rendered.unmount();
});
});
+
+describe("stripLeadingDetailedTimestamp", () => {
+ it.each([
+ ["clean message", "clean message"],
+ ["2026-07-20 09:20:38.221 PDT checkpoint starting", "checkpoint starting"],
+ ["2026-07-20 09:20:38 UTC checkpoint starting", "checkpoint starting"],
+ ["checkpoint starting", "checkpoint starting"],
+ ["", ""],
+ ])("returns %j for %j", (message, expected) => {
+ expect(stripLeadingDetailedTimestamp(message)).toBe(expected);
+ });
+});
diff --git a/packages/cli/src/commands/dashboard-tui/app.tsx b/packages/cli/src/commands/dashboard-tui/app.tsx
index 795a499da2..11e647b89c 100644
--- a/packages/cli/src/commands/dashboard-tui/app.tsx
+++ b/packages/cli/src/commands/dashboard-tui/app.tsx
@@ -89,12 +89,32 @@ import {
// ── Format helpers ────────────────────────────────────────────────────────────
+/*
+FNXC:TuiLogs 2026-07-20-09:50:
+Dense TUI log rows must reserve horizontal space for the level, prefix, and
+message instead of detailed capture timestamps. A compact clock is sufficient
+for list rows, clipboard text, and expanded-entry capture time; the Date stays
+intact for ordering and raw-message debugging.
+*/
function formatTimestamp(date: Date): string {
const h = date.getHours().toString().padStart(2, "0");
const m = date.getMinutes().toString().padStart(2, "0");
const s = date.getSeconds().toString().padStart(2, "0");
- const ms = date.getMilliseconds().toString().padStart(3, "0");
- return `${h}:${m}:${s}.${ms}`;
+ return `${h}:${m}:${s}`;
+}
+
+/*
+FNXC:TuiLogs 2026-07-20-09:55:
+Captured upstream logs can repeat a date, fractional seconds, and timezone in
+message text after the TUI already records capture time. Strip that leading
+wall-clock block only at list/copy display time so narrow panes show the useful
+content while ExpandedLog keeps the raw message for diagnosis.
+*/
+export function stripLeadingDetailedTimestamp(message: string): string {
+ return message.replace(
+ /^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}(?:\.\d+)?\s+(?:[A-Za-z]{2,5}|[+-]\d{2}:?\d{2})\s*/,
+ "",
+ );
}
function formatUptime(ms: number): string {
@@ -665,6 +685,8 @@ function LogsPanel({
// the outer header off the top of the alt-screen.
const entryHeight = logsWrapEnabled ? undefined : 1;
+ const displayMessage = stripLeadingDetailedTimestamp(entry.message);
+
if (isNarrow) {
const idx = narrowTimestamp(absoluteIndex);
const pfx = narrowPrefix(entry.prefix, NARROW_PREFIX_WIDTH);
@@ -683,7 +705,7 @@ function LogsPanel({
{idx}
{lvl}
{` ${pfx} `}
- {entry.message}
+ {displayMessage}
);
@@ -708,7 +730,7 @@ function LogsPanel({
{ts}
{lvl}
{` ${prefixSlot} `}
- {entry.message}
+ {displayMessage}
);
@@ -4574,7 +4596,8 @@ export function DashboardApp({ controller }: DashboardAppProps) {
if (target) {
const ts = formatTimestamp(target.timestamp);
const prefix = target.prefix ? `[${target.prefix}] ` : "";
- const text = `${ts} ${target.level.toUpperCase()} ${prefix}${target.message}`;
+ const displayMessage = stripLeadingDetailedTimestamp(target.message);
+ const text = `${ts} ${target.level.toUpperCase()} ${prefix}${displayMessage}`;
void copyToClipboard(text).then((ok) => {
controller.flashClipboard(ok);
if (ok) {