feat(main): CC8 — file-backed leveled logger + renderer log sink

Diagnostics were invisible in a packaged build: main catches did a bare
console.error (DevTools suppressed in prod, M31) and the renderer's logError
(M29) wrote to an unreachable console. Add a small leveled logger
(src/main/logger.ts) appending to <userData>/logs/aerofetch.log with size-capped
rotation; all app/fs access is lazy+guarded so it's a safe no-op under tests.

- Route the 5 main-process console.* catch sites (jsonStore write-fail, settings
  write-fail + plaintext-secret warn, cookie loadURL, yt-dlp auto-update)
  through the logger.
- Add a fire-and-forget log:write IPC channel + preload logError() so the
  renderer's logError forwards failures to the main file sink (closes the M29
  "sink" half the code comments deferred to CC8). mockApi + Api type updated.
- Unit-test pure formatLine/composeMessage + no-op safety; update the R6
  jsonStore test to assert against the logger.

The user-facing toast half of CC8 ties to the global status surface (UI25/UX9).
typecheck + 258 tests + lint + prettier green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-01 08:53:06 -04:00
parent 6bd29efd23
commit 519e522917
13 changed files with 185 additions and 15 deletions
+7
View File
@@ -50,6 +50,7 @@ import { syncWatchedSources } from './sync'
import { getScheduledSync, setScheduledSync } from './schedule'
import { getActiveBadge, getErrorBadge } from './badge'
import { openPoTokenWindow } from './poToken'
import { logger } from './logger'
// --- Theme helpers (shared with index's system-theme bridge) ----------------
@@ -87,6 +88,12 @@ export function registerIpcHandlers(getMainWindow: () => BrowserWindow | null):
app.quit()
})
// CC8: persist renderer-side failures (the M29 `logError` sites) to the main log
// file — the renderer console is unreachable in a packaged build. Fire-and-forget.
ipcMain.on(IpcChannels.logWrite, (_e, op: string, detail: string) =>
logger.error(`[renderer] ${op}`, detail)
)
ipcMain.handle(IpcChannels.appVersion, () => app.getVersion())
ipcMain.handle(IpcChannels.appUpdateCheck, () => checkForAppUpdate())