H4 + H2 formatter half: carry raw numbers across the progress boundary

Closes H4 and the remaining formatter half of H2.

- New @shared/format.ts is the single home for fmtBytes/fmtSpeed/fmtEta, imported
  by both main and renderer. main/lib/formatters.ts re-exports them.
- DownloadProgress now carries raw speedBytesPerSec/etaSeconds instead of
  pre-formatted speed/eta strings. main's parseProgress emits the raw numbers.
- The renderer stores the raw numbers on DownloadItem; QueueItem formats them for
  per-item display, and summarizeQueue sums/maxes them directly. The lossy
  string->number->string round-trip in queueStats (parseSpeed / parseEtaSeconds /
  a local formatSpeed / formatEta) is deleted, along with a duplicate fmtEta in
  store/downloads.ts.
- Per-item and aggregate speed now use the same 1024-based scale; the aggregate
  previously used a 1000-based formatter (a latent inconsistency).

Tests updated for the raw-number contract (parseProgress, summarizeQueue).
typecheck + 248 tests + eslint + prettier green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 21:03:14 -04:00
parent d050e48af6
commit 3d09174c16
9 changed files with 122 additions and 135 deletions
+7 -5
View File
@@ -66,7 +66,8 @@ describe('parseProgress', () => {
const p = parseProgress(raw)
expect(p).not.toBeNull()
expect(p!.progress).toBeCloseTo(0.5)
expect(p!.eta).toBe('0:02')
expect(p!.speedBytesPerSec).toBe(262144)
expect(p!.etaSeconds).toBe(2)
expect(p!.sizeLabel).toBe('1.0 MB')
expect(p!.sizeUnknown).toBe(false)
})
@@ -84,8 +85,8 @@ describe('parseProgress', () => {
const p = parseProgress(raw)
expect(p).not.toBeNull()
expect(p!.sizeUnknown).toBe(true)
expect(p!.eta).toBeUndefined()
expect(p!.speed).toBeUndefined()
expect(p!.etaSeconds).toBeUndefined()
expect(p!.speedBytesPerSec).toBeUndefined()
})
it('clamps progress to 1.0', () => {
@@ -94,9 +95,10 @@ describe('parseProgress', () => {
expect(p!.progress).toBe(1)
})
it('formats an ETA over 1 hour correctly (L166)', () => {
it('carries a raw ETA in seconds; the renderer formats it (L166)', () => {
const raw = makeRaw(['downloading', '100', '1000', '1000', '10', '3661'])
const p = parseProgress(raw)
expect(p!.eta).toBe('1:01:01')
expect(p!.etaSeconds).toBe(3661)
expect(fmtEta(p!.etaSeconds)).toBe('1:01:01')
})
})