We're going to move to an interface-like pattern for tabs to remove a
lot of shared state and UI logic from tui.zig to the individual tabs. It
is not a full interface like Allocator because we do not need dynamic
dispatch here...the tabs are still known by tui.zig. This gives us the
ability to perform comptime evaluation and adherence to the spec.
IO-as-an-interface refactor across the codebase. The big shifts:
- std.io → std.Io, std.fs → std.Io.Dir/File, std.process.Child → spawn/run.
- Juicy Main: pub fn main(init: std.process.Init) gives gpa, io, arena,
environ_map up front. main.zig + the build/ scripts use it directly.
- Threading io through everywhere that touches the outside world (HTTP,
files, stderr, sleep, terminal detection). Functions taking `io` now
announce side effects at the call site — the smell is the feature.
- date math takes `as_of: Date`, not `today: Date`. Caller resolves
`--as-of` flag vs wall-clock at the boundary; the function operates
on whatever date it's given. Every "today" parameter renamed and
the as_of: ?Date + today: Date pattern collapsed.
- now_s: i64 (or before_s/after_s pairs) for sub-second metadata
fields like snapshot captured_at, audit cadence, formatAge/fmtTimeAgo.
Also pure and testable.
- legitimate Timestamp.now callers (cache TTL math, FetchResult
timestamps, rate limiter, per-frame TUI "now" captures) gain
`// wall-clock required: ...` comments justifying the read.
Test discovery: replaced the local refAllDeclsRecursive with bare
std.testing.refAllDecls(@This()). Sema-pulling main.zig's top-level
decls reaches every test file transitively through the import graph;
no explicit _ = @import(...) lines needed.
Cleanup along the way:
- Dropped DataService.allocator()/io() accessor methods; renamed the
fields to drop the base_ prefix. Callers use self.allocator and
self.io directly.
- Dropped now-vestigial io parameters from buildSnapshot,
analyzePortfolio, compareSchwabSummary, compareAccounts,
buildPortfolioData, divs.display, quote.display, parsePortfolioOpts,
aggregateLiveStocks, renderEarningsLines, capitalGainsIndicator,
aggregateDripLots, printLotRow, portfolio.display, printSnapNote.
- Dropped the unused contributions.computeAttribution date-form
wrapper (only computeAttributionSpec is called).
- formatAge/fmtTimeAgo take (before_s, after_s) instead of io and
reading the clock internally.
- parseProjectionsConfig uses an internal stack-buffer
FixedBufferAllocator instead of an allocator parameter.
- ThreadSafeAllocator wrappers in cache concurrency tests dropped
(0.16's DebugAllocator is thread-safe by default).
- analyzePortfolio bug surfaced by the rename: snapshot.zig was
passing wall-clock today instead of as_of, mis-valuing cash/CDs
for historical backfills.
83 new unit tests added due to removal of IO, bringing coverage from 58%
-> 64%