Problem
Four sibling formatters live in three different files, all with overlapping "compact unit-bucket" logic:
| File |
Function |
Output |
| `src/tab-state.ts` |
`formatElapsed(startMs)` |
`0:45`, `1:15:30`, `7d 14h` |
| `src/tab-state.ts` |
`formatResidentSize(bytes)` |
`512K`, `156M`, `1.2G` |
| `src/notifications.ts` |
`formatDuration(ms)` |
`5s`, `2m 13s`, `1h 30m` |
| `src/notification-tray.ts` |
`formatAge(ms)` |
`5s ago`, `2m ago`, `3h ago` |
`formatDuration` and `formatAge` are ~80% identical (same s/m/h/d bucketing, different suffix). `formatElapsed` is a third compact-time variant. The placement is locality-correct (each lives next to its caller), but the duplication will rot — a future tweak to the bucket boundaries needs to land in 2-3 places to stay consistent.
Fix
Consolidate into a single `src/format.ts` (or extend `tab-state.ts`) with:
```ts
export function formatDurationCompact(ms: number, opts?: { suffix?: string; precision?: "short" | "long" }): string;
export function formatElapsed(startMs: number): string; // keep (different shape — colons + days)
export function formatBytes(bytes: number): string; // rename formatResidentSize
```
Then `formatAge(ms)` becomes `formatDurationCompact(ms, { suffix: " ago" })` and `formatDuration(ms)` becomes `formatDurationCompact(ms)` with the existing call sites updated.
Out of scope
- Localization ("5s" → "5 seconds"). Current consumers all use the short form intentionally for footer / tray density.
- Pluralization ("1m" vs "2m" → no plural "s"). Already by design — these are compact, not English.
Files
- `src/tab-state.ts` (formatElapsed:344, formatResidentSize)
- `src/notifications.ts` (formatDuration)
- `src/notification-tray.ts` (formatAge)
Problem
Four sibling formatters live in three different files, all with overlapping "compact unit-bucket" logic:
`formatDuration` and `formatAge` are ~80% identical (same s/m/h/d bucketing, different suffix). `formatElapsed` is a third compact-time variant. The placement is locality-correct (each lives next to its caller), but the duplication will rot — a future tweak to the bucket boundaries needs to land in 2-3 places to stay consistent.
Fix
Consolidate into a single `src/format.ts` (or extend `tab-state.ts`) with:
```ts
export function formatDurationCompact(ms: number, opts?: { suffix?: string; precision?: "short" | "long" }): string;
export function formatElapsed(startMs: number): string; // keep (different shape — colons + days)
export function formatBytes(bytes: number): string; // rename formatResidentSize
```
Then `formatAge(ms)` becomes `formatDurationCompact(ms, { suffix: " ago" })` and `formatDuration(ms)` becomes `formatDurationCompact(ms)` with the existing call sites updated.
Out of scope
Files