update docs
This commit is contained in:
parent
3c0e8a3f4d
commit
63adf6c517
2 changed files with 98 additions and 32 deletions
71
AGENTS.md
71
AGENTS.md
|
|
@ -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
|
||||
|
||||
|
|
|
|||
59
README.md
59
README.md
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue