update docs

This commit is contained in:
Emil Lerch 2026-05-16 12:44:12 -07:00
parent 3c0e8a3f4d
commit 63adf6c517
Signed by: lobo
GPG key ID: A7B62D657EF764F8
2 changed files with 98 additions and 32 deletions

View file

@ -94,7 +94,15 @@ function genuinely needs to do I/O for other reasons.
- `cache/store.zig` — cache entry timestamps and TTL math
- `service.zig` — per-fetch `FetchResult.timestamp`
- `net/RateLimiter.zig` — token-bucket refill
- `commands/audit.zig`, `commands/cache.zig`, `commands/history.zig`
— per-invocation `now_s` captures for staleness math, "X ago"
age displays, and rollup `#!created=` directives. Each call
site has a justifying comment.
- TUI per-frame "now" captures for relative-time display
(e.g. earnings, options, quote tabs)
- `tui.zig` `shouldDebounceWheel` — uses `.awake` (monotonic
clock) for sub-millisecond input-event debounce; resists
system clock jumps that `.real` would expose
- The single `Timestamp.now` capture in `main.zig`'s dispatch
entry that produces `today` and `now_s` for the rest of the
invocation
@ -147,8 +155,10 @@ already exist and have caught me out:
inner type via `Padded(T)` so the same wrapper works for any
`format`-bearing type.
- `format.fmtIntCommas` — "1,234,567" without `$`.
- `format.formatReturn` — signed percent for trailing-returns
and gain/loss displays.
- `analytics/performance.formatReturn` — signed percent for
trailing-returns and gain/loss displays. (Lives in
`analytics/performance.zig` because returns are a
performance-domain concept; reusing it from elsewhere is fine.)
**Search recipes that catch the most cases:**
@ -371,19 +381,22 @@ User input → main.zig (CLI dispatch) or tui.zig (TUI event loop)
- **Negative cache entries.** When a provider fetch fails permanently (not rate-limited), a negative cache entry is written to prevent repeated retries for nonexistent symbols.
- **TUI tab framework.** The TUI is a registry-driven framework with eight tabs sharing infrastructure. The single source of truth is `tab_modules` in `src/tui.zig` (an anonymous struct literal mapping tag → module). Everything else — the `Tab` enum, tab-bar labels, the `TabStates` aggregator, key/mouse dispatch, help overlay rows, status-line hints, draw routing — is derived from `tab_modules` at comptime. Each tab module conforms to a contract documented in `src/tui/tab_framework.zig`: declare an `Action` enum, a `State` struct, a `tab` namespace with required hooks, and exactly one of `buildStyledLines` or `drawContent`. A comptime validator (`tab_framework.validateTabModule`) checks every registered module at build time, including hook signatures and a "tabs cannot bind globally-bound keys" rule. See "Adding a new TUI tab" below for the workflow.
### Module map
| Directory | Purpose |
|-----------|---------|
| `src/models/` | Data types: `Candle`, `Dividend`, `Split`, `Lot`, `Portfolio`, `OptionContract`, `EarningsEvent`, `EtfProfile`, `Quote`. (`Date` and `Money` are top-level types in `src/Date.zig` and `src/Money.zig`.) |
| `src/providers/` | API clients: each provider has its own struct with `init(allocator, api_key)` + fetch methods. `json_utils.zig` has shared JSON parsing helpers. |
| `src/analytics/` | Pure computation: `performance.zig` (Morningstar-style trailing returns), `risk.zig` (Sharpe, drawdown), `valuation.zig` (portfolio summary), `analysis.zig` (breakdowns by class/sector/geo) |
| `src/commands/` | CLI command handlers: each has a `run()` function taking `(allocator, *DataService, symbol, color, *Writer)`. `common.zig` has shared CLI helpers and color constants. |
| `src/tui/` | TUI tab renderers: each tab (portfolio, quote, perf, options, earnings, analysis) is a separate file. `keybinds.zig` and `theme.zig` handle configurable input/colors. `chart.zig` renders pixel charts via Kitty graphics protocol. |
| `src/views/` | View models producing renderer-agnostic display data with `StyleIntent` |
| `src/cache/` | `store.zig`: SRF cache read/write with TTL freshness checks |
| `src/analytics/` | Pure computation: `performance.zig` (Morningstar-style trailing returns), `risk.zig` (Sharpe, drawdown), `valuation.zig` (portfolio summary), `analysis.zig` (breakdowns by class/sector/geo), `benchmark.zig` (per-position benchmark returns), `indicators.zig` (SMA/Bollinger/RSI), `milestones.zig` (retirement-attainment grid), `projections.zig` (Monte Carlo + percentile bands), `timeline.zig` (history-tab tier rollup). |
| `src/data/` | Static / semi-static datasets: `imported_values.zig` (back-history values), `shiller.zig` (S&P + CPI series, Shiller's data set), `staleness.zig` (account-cadence checks). `ie_data.csv` is the raw Shiller dataset. |
| `src/views/` | View models producing renderer-agnostic display data with `StyleIntent`: `compare.zig`, `history.zig`, `portfolio_sections.zig`, `projections.zig`. |
| `src/commands/` | CLI command handlers: each has a `pub fn run(...)` entry point. Signatures vary by command's needs (some take `as_of`, `now_s`, `args`, etc.); `common.zig` has shared CLI helpers and color constants. See "Adding a new CLI command" below. |
| `src/tui/` | Eight-tab interactive TUI. Each tab is a separate file conforming to the framework contract documented in `tab_framework.zig`: `portfolio_tab.zig`, `quote_tab.zig`, `performance_tab.zig`, `options_tab.zig`, `earnings_tab.zig`, `analysis_tab.zig`, `history_tab.zig`, `projections_tab.zig`. Plus `keybinds.zig` (configurable input + scoped bindings), `theme.zig` (configurable colors), `chart.zig` (Kitty graphics chart renderer), `projection_chart.zig` (percentile-band overlay), `input_buffer.zig` (modal text-input state machine). The `App` orchestrator lives in the parent `src/tui.zig`. |
| `src/cache/` | `store.zig`: SRF cache read/write with TTL freshness checks. |
| `src/net/` | `http.zig`: HTTP client with retry and error classification. `RateLimiter.zig`: token-bucket rate limiter. |
| `build/` | Build-time support: `Coverage.zig` (kcov integration) |
| `build/` | Build-time support: `Coverage.zig` (kcov integration), `download_kcov.zig` (kcov binary fetcher), `gen_shiller.zig` (CSV → comptime data converter), `bcov.css` (kcov report styling). |
## Code patterns and conventions
@ -543,9 +556,41 @@ will fail to catch real bugs later.
### Adding a new TUI tab
1. Create `src/tui/newtab_tab.zig`
2. Add the tab variant to `tui.Tab` enum
3. Wire rendering in `tui.zig`'s draw and event handling
The TUI uses a comptime-derived tab registry (`tab_modules` in
`src/tui.zig`). Adding a new tab is one append to the registry
plus a tab module that conforms to the framework contract. The
`Tab` enum, tab-bar label, `TabStates` aggregator, key/mouse
dispatch, help overlay, status hint, and draw routing all flow
from the registry at comptime — App needs no hand-edits.
1. **Create `src/tui/newtab_tab.zig`** with:
- `pub const Action = enum { ... };` — tab-local keybind
actions (or empty).
- `pub const State = struct { ... };` — tab-private state
(cursor, expansion flags, cached load state, etc).
- `pub const tab = struct { ... };` — the framework
contract: `label`, `default_bindings`, `action_labels`,
`status_hints`, lifecycle hooks (`init`, `deinit`,
`activate`, `deactivate`, `reload`, `tick`),
`handleAction`, optional event hooks (`handleKey`,
`handleMouse`, `handlePaste`, `statusOverride`,
`onSymbolChange`, `onScroll`, `onCursorMove`,
`isDisabled`).
- **Exactly one** of `pub fn buildStyledLines(state, app, arena)`
(line-list rendering) or `pub fn drawContent(state, app,
arena, buf, width, height)` (direct cell-buffer rendering,
for charts). The framework validator enforces this.
2. **Append to `tab_modules`** at the top of `src/tui.zig`:
`.newtab = @import("tui/newtab_tab.zig"),`
3. Done. The compiler's framework validator (in
`tab_framework.validateTabModule`) will reject the build
with a precise message if any required hook is missing or
has the wrong signature. There's also a comptime check that
the tab's keybindings don't conflict with the global keymap.
For the contract details, read the doc-block at the top of
`src/tui/tab_framework.zig` — it shows the full required
shape with example signatures.
### `anytype` is almost never the right answer — pause and ask first
@ -645,8 +690,8 @@ command.
| Dependency | Purpose |
|------------|---------|
| [SRF](https://git.lerch.org/lobo/srf) | Cache file format, portfolio/watchlist parsing, serialization |
| [libvaxis](https://github.com/rockorager/libvaxis) (v0.5.1) | Terminal UI rendering |
| [z2d](https://github.com/vancluever/z2d) (v0.10.0) | Pixel chart rendering (Kitty graphics protocol) |
| [libvaxis](https://github.com/rockorager/libvaxis) (v0.6.0) | Terminal UI rendering |
| [z2d](https://github.com/vancluever/z2d) (v0.11.0) | Pixel chart rendering (Kitty graphics protocol) |
## Build system rules

View file

@ -251,11 +251,11 @@ If no portfolio or symbol is specified and `portfolio.srf` exists in the current
## Interactive TUI
The TUI has six tabs: Portfolio, Quote, Performance, Options, Earnings, and Analysis.
The TUI has eight tabs: Portfolio, Quote, Performance, Options, Earnings, Analysis, History, and Projections.
### Tabs
**Portfolio** -- navigable list of positions with market value, gain/loss, weight, and purchase date. Multi-lot positions can be expanded to show individual lots with per-lot gain/loss, capital gains indicator (ST/LT), and account name.
**Portfolio** -- navigable list of positions with market value, gain/loss, weight, and purchase date. Multi-lot positions can be expanded to show individual lots with per-lot gain/loss, capital gains indicator (ST/LT), and account name. Press `a` to open an account-filter picker (with `/` search).
**Quote** -- current price, OHLCV, daily change, and a 60-day ASCII chart with recent history table.
@ -267,6 +267,10 @@ The TUI has six tabs: Portfolio, Quote, Performance, Options, Earnings, and Anal
**Analysis** -- portfolio breakdown by asset class, sector, geographic region, account, and tax type. Uses classification data from `metadata.srf` and account tax types from `accounts.srf`. Displays horizontal bar charts with sub-character precision using Unicode block elements.
**History** -- portfolio value over time, sourced from snapshot files in `<portfolio-dir>/history/` plus optional `imported_values.srf`. Cycle the metric column with `m` (liquid / total / contributions / etc.) and the time-bucket resolution with `t` (week / month / quarter / year). Press `s` (or space) to mark a row for compare; mark a second row, then `c` to commit a side-by-side compare against the live portfolio. Esc cancels an in-flight compare.
**Projections** -- Monte Carlo retirement projection with percentile bands. Press `d` to set an as-of date (back-date the projection to a historical snapshot), `o` to overlay realized actuals from snapshots / `imported_values.srf` on top of the bands, `v` to toggle the chart vs the text-only report, and `e` to toggle simulated lifecycle events (RMDs, lump-sum withdrawals). Esc clears an active as-of override.
### Keybindings
All keybindings are configurable via `~/.config/zfin/keys.srf`. Generate the default config:
@ -275,7 +279,9 @@ All keybindings are configurable via `~/.config/zfin/keys.srf`. Generate the def
zfin i --default-keys > ~/.config/zfin/keys.srf
```
Default keybindings:
The generated file has two parts: a global section (keys that work in every tab) and a per-tab section (keys that only fire when that tab is active). Tab-local bindings cannot override globally-bound keys — zfin refuses to start if your config creates that conflict.
Default global keybindings:
| Key | Action |
|------------------------|------------------------------------------------------|
@ -284,30 +290,45 @@ Default keybindings:
| `R` | Reload portfolio from disk (no network) |
| `h`, Left, Shift+Tab | Previous tab |
| `l`, Right, Tab | Next tab |
| `1`-`6` | Jump to tab |
| `1`-`8` | Jump to tab N |
| `j`, Down | Select next row |
| `k`, Up | Select previous row |
| `Enter` | Expand/collapse (positions, expirations, calls/puts) |
| `s` | Select symbol from portfolio for other tabs |
| `/` | Enter symbol search |
| `e` | Edit portfolio/watchlist in `$EDITOR` |
| `<` | Sort by previous column (portfolio tab) |
| `>` | Sort by next column (portfolio tab) |
| `o` | Reverse sort direction (portfolio tab) |
| `[` | Previous chart timeframe (quote tab) |
| `]` | Next chart timeframe (quote tab) |
| `c` | Toggle all calls collapsed/expanded (options tab) |
| `p` | Toggle all puts collapsed/expanded (options tab) |
| `Ctrl+1`-`Ctrl+9` | Set options near-the-money filter to +/- N strikes |
| `g` | Scroll to top |
| `G` | Scroll to bottom |
| `Ctrl+d` | Half-page down |
| `Ctrl+u` | Half-page up |
| `PageDown` | Page down |
| `PageUp` | Page up |
| `PageDown`, `Ctrl+f` | Page down |
| `PageUp`, `Ctrl+b` | Page up |
| `/` | Symbol input prompt |
| `?` | Help screen |
Mouse: scroll wheel navigates, left-click selects rows and switches tabs, double-click expands/collapses.
Default tab-local keybindings (only active on the matching tab):
| Tab | Key | Action |
|-------------|--------------------|----------------------------------------------------------|
| Portfolio | `Enter` | Expand/collapse position |
| Portfolio | `>` / `<` | Sort by next / previous column |
| Portfolio | `o` | Reverse sort direction |
| Portfolio | `a` | Open account-filter picker (`/` to search inside picker) |
| Portfolio | `Esc` | Clear active account filter |
| Portfolio | `s`, `Space` | Select symbol (sets active symbol for other tabs) |
| Quote | `[` / `]` | Previous / next chart timeframe |
| Options | `Enter` | Expand/collapse expiration or section |
| Options | `c` / `p` | Toggle all calls / puts collapsed |
| Options | `Ctrl+1`-`Ctrl+9` | Set near-the-money filter to +/- N strikes |
| History | `Enter` | Expand/collapse tier |
| History | `m` | Cycle metric column |
| History | `t` | Cycle time-bucket resolution |
| History | `s`, `Space` | Mark / unmark row for compare |
| History | `c` | Commit compare (after two rows marked) |
| History | `Esc` | Cancel in-flight compare selection |
| Projections | `d` | Set as-of date prompt |
| Projections | `Esc` | Clear as-of date |
| Projections | `o` | Toggle realized-actuals overlay |
| Projections | `v` | Toggle chart vs text-only report |
| Projections | `e` | Toggle simulated lifecycle events |
Mouse: scroll wheel navigates, left-click selects rows and switches tabs.
### Theme