fix(tui): avoided auto-linked token rate format

Rendered the status-line token rate as an explicit tok/s unit so Ghostty no longer auto-detects the numeric value as a URL.

Added a regression test for the token_rate segment rendering contract.

Fixes #4541
This commit is contained in:
roboomp
2026-07-04 17:42:56 +00:00
parent 38565c58cb
commit 55e2b473aa
3 changed files with 39 additions and 2 deletions
+2
View File
@@ -12,6 +12,8 @@
### Fixed
- Fixed the status-line token-rate segment rendering as `<number>/s`, which Ghostty auto-detected as a hyperlink on Ctrl+hover. ([#4541](https://github.com/can1357/oh-my-pi/issues/4541))
- Fixed raw `read` ranges not contributing to edit seen-line provenance, so re-reading an anchor range with `:raw` now unblocks hashline edits without adding non-raw line prefixes.
- Fixed replan-driven session title refresh updating the statusline but not the terminal window title: terminal-title updates now fire from the session-name-changed listener, so every `setSessionName` path (first-input titling, `/rename`, plan seeding, replan refresh) sets the OSC title consistently.
- Fixed user-interrupt aborts rendering the persisted `Interrupted by user` label in assistant transcripts; replay and live views now suppress that redundant line again while preserving generic/custom abort labels.
@@ -379,7 +379,7 @@ const tokenRateSegment: StatusLineSegment = {
const { tokensPerSecond } = ctx.usageStats;
if (!tokensPerSecond) return { content: "", visible: false };
const content = withIcon(theme.icon.throughput, `${tokensPerSecond.toFixed(1)}/s`);
const content = withIcon(theme.icon.throughput, `${tokensPerSecond.toFixed(1)} tok/s`);
return { content: theme.fg("statusLineOutput", content), visible: true };
},
};
@@ -1,6 +1,14 @@
import { describe, expect, it } from "bun:test";
import { beforeAll, describe, expect, it } from "bun:test";
import { stripVTControlCharacters } from "node:util";
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
import { renderSegment } from "@oh-my-pi/pi-coding-agent/modes/components/status-line/segments";
import { calculateTokensPerSecond } from "@oh-my-pi/pi-coding-agent/modes/components/status-line/token-rate";
import type { SegmentContext } from "@oh-my-pi/pi-coding-agent/modes/components/status-line/types";
import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
beforeAll(async () => {
await initTheme();
});
function assistantMessage(overrides?: Partial<AssistantMessage>): AssistantMessage {
return {
@@ -23,6 +31,33 @@ function assistantMessage(overrides?: Partial<AssistantMessage>): AssistantMessa
};
}
function ctxWithTokenRate(tokensPerSecond: number | null): SegmentContext {
return {
usageStats: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
premiumRequests: 0,
cost: 0,
tokensPerSecond,
},
} as unknown as SegmentContext;
}
describe("token_rate status-line segment", () => {
it("renders per-second throughput without a numeric slash path", () => {
const rendered = renderSegment("token_rate", ctxWithTokenRate(35.5));
const content = stripVTControlCharacters(rendered.content);
expect(rendered.visible).toBe(true);
expect(content).toContain("35.5");
expect(content).toMatch(/(?:\/s|\bs\b|\bsec(?:ond)?s?\b|\btps\b)/i);
expect(content).not.toContain("35.5/s");
expect(content).not.toMatch(/\b\d+(?:\.\d+)?\/s\b/);
});
});
describe("token rate calculation", () => {
it("computes from completed message duration metadata", () => {
const base = assistantMessage();