add todo items after review of full application surface

This commit is contained in:
Emil Lerch 2026-05-16 13:07:29 -07:00
parent ddb50923ca
commit adf33b9e21
Signed by: lobo
GPG key ID: A7B62D657EF764F8

248
TODO.md
View file

@ -160,6 +160,254 @@ Out of scope for V1: file-format alternatives (SVG, PDF), themed
color overrides for export (always uses the active terminal theme),
non-chart command output as PNG.
## CLI architecture overhaul — priority MEDIUM
The CLI surface has grown to 21 top-level commands and the dispatch
machinery in `src/main.zig` is starting to bloat. Three related gaps,
all best addressed together because they share the same refactor
vehicle:
1. **No per-command `--help`.** `zfin help` shows a wall of every
command and every flag. There's no way to ask "what does
`projections` accept?" without grepping the top-level usage.
Every command should support `zfin <command> --help` printing
just that command's synopsis, flags, and a few examples.
2. **`usage` text is roughly chronological-by-feature.** Re-group it
into workflow sections so users can scan for what they need:
- **Per-symbol lookups:** `perf`, `quote`, `history` (symbol
mode), `divs`, `splits`, `options`, `earnings`, `etf`
- **Portfolio analysis:** `portfolio`, `analysis`, `history`
(portfolio mode), `projections`, `milestones`
- **Time-series & journaling:** `snapshot`, `compare`,
`contributions`
- **Data hygiene:** `audit`, `enrich`, `lookup`
- **Infra:** `cache`, `version`, `interactive`
3. **Per-command flag parsing lives inline in `runCli`.** It's
~500 lines now. The `projections`, `contributions`, and `compare`
branches each carry ~50 lines of inline flag parsing plus their
own conflict-detection. Each command module should own its own
`parseArgs()`; `runCli` should be pure dispatch.
The natural mechanism for all three is a **command registry table**
mirroring the `tab_modules` registry in `src/tui.zig`:
```zig
// src/main.zig
const command_registry = .{
.perf = @import("commands/perf.zig"),
.quote = @import("commands/quote.zig"),
// ...
};
// each command module exposes:
// pub const meta = .{
// .help = "perf <SYMBOL> Show trailing returns ...",
// .group = .symbol_lookup,
// .takes_symbol = true,
// .needs_portfolio_path = false,
// };
// pub fn parseArgs(allocator, args, today) !ParsedArgs
// pub fn run(io, allocator, *DataService, parsed, today, color, *Writer) !void
```
A comptime walk over the registry produces:
- The grouped `usage` text (sort by `meta.group`, render section
headers).
- Per-command help dispatch (`zfin <cmd> --help` prints
`meta.help` plus the parsed flag descriptors).
- The dispatch chain itself (replace the giant `if-else if`).
- The "does this command take a symbol?" / "does it need a
portfolio path?" decisions currently scattered across `runCli`.
**Driver:** the per-command `--help` is the loudest user-facing gap;
the registry refactor is the cleanest way to land it without
scattering help text across more files. Also unblocks `zfin doctor`
(below) and the unified time-range parser (below) by giving them
a clean module shape to plug into.
**Open question for when this is picked up:** how to handle the
`history` command's two modes (symbol vs portfolio) under a single
registry entry. Probably one entry with a parser that branches on
`args[0]`, same as today — but the registry shape should accommodate
that without ceremony.
## `zfin doctor` health-check command — priority LOW
Front-door command for the file constellation and environment.
Answers "is my zfin setup sane?" without making any changes.
The configuration surface has grown organically and only the README
explains the file layout:
- `portfolio.srf` — lots
- `metadata.srf` — classifications
- `accounts.srf` — tax types
- `projections.srf` — retirement config
- `transaction_log.srf` — internal transfers
- `imported_values.srf` — back-history
- `history/*-portfolio.srf` — snapshots
- `~/.config/zfin/keys.srf` — keybinds
- `~/.config/zfin/theme.srf` — colors
Plus 5 API keys, a cache directory, and an optional `ZFIN_SERVER`.
### v1 scope (full health check)
- **File inventory + parse check.** For each of the files above:
does it exist, where was it resolved from (cwd vs `ZFIN_HOME` vs
`~/.config/zfin`), and does it parse cleanly. No fixes.
- **Cross-reference checks.** Every account in `portfolio.srf`
has an `accounts.srf` entry. Every held symbol has a
`metadata.srf` entry (or is opted out). Every snapshot file
parses as a portfolio. `transaction_log.srf` records reference
real account names.
- **Environment audit.** Which API keys are set (presence only,
never the value). Cache size + symbol count. `ZFIN_SERVER`
reachability if set (HEAD/OPTIONS, low timeout). Staleness of
hand-maintained data files (T-bill rates, Shiller `ie_data.csv`)
— same registry as `data/staleness.zig`.
- **Output shape.** Sectioned, color-coded. Every check is
`OK` / `WARN` / `FAIL` with a one-line context. Exit code 0 on
all-OK or warnings only; non-zero only on FAIL. Suitable for
CI / cron / pre-commit.
### Driver
The file constellation has grown to nine files in three locations,
plus five API keys, plus the cache, plus the optional server.
Today only the README explains the structure. A `doctor` command
surfaces it, catches regressions, and is the obvious place to point
new users (or to point future-self after a long break).
### Open question for when this is picked up
Does this *replace* the portfolio-hygiene portion of `audit`, or
live alongside it? Probably alongside — `audit` is reconciliation
against external broker exports, `doctor` is internal-state
validation. But worth confirming the boundary before implementing
to avoid duplicated checks.
## TUI tab re-order — priority LOW
Current tab order interleaves the two natural categories:
```
Portfolio Quote Performance Options Earnings Analysis History Projections
P S S S S P P P
```
That's `P S S S S P P P` — the per-portfolio tabs (P) are split
across the bar (slot 1, then slots 6-8). Reflects history of feature
additions rather than current shape.
### Suggested re-order to consider
```
Portfolio Analysis History Projections | Quote Performance Options Earnings
```
"Your money" tabs first (1-4), "research a symbol" tabs second
(5-8). Categorical split is clean; the 4↔5 boundary is a natural
divider in the tab bar.
### Alternative orderings worth weighing
- `Portfolio Analysis Projections History | Quote Performance Earnings Options`
— within each category, ordered by frequency of use rather than
by category cohesion.
- Status quo with just Analysis moved up next to Portfolio (minimum
churn).
### Mechanics
The `tab_modules` registry in `src/tui.zig` makes the actual reorder
a one-edit change (reorder fields in the registry literal). Cost is
in retraining muscle memory and updating the README's keybinding
table + screenshots.
### Doc drift to fix while we're there
README still says "six tabs," actual count is eight (Portfolio,
Quote, Performance, Options, Earnings, Analysis, History,
Projections).
## Unified time-range flag parser — priority LOW
`compare`, `contributions`, `projections --vs`, and `projections
--as-of` all accept overlapping but slightly different time-range
inputs. Today there are two helpers in `src/commands/common.zig`
(`parseAsOfDate`, `parseCommitSpec`) and the conflict-detection
logic (`since != null and before_spec != null`, etc.) is duplicated
across each command's flag-parsing block in `main.zig`.
### Sketch
```zig
// src/commands/common.zig
pub const TimeRange = struct {
before: Endpoint,
after: Endpoint,
pub const Endpoint = union(enum) {
date: Date,
commit_spec: CommitSpec,
live,
head,
};
};
pub fn parseTimeRange(args: ...) !TimeRange { ... }
```
Each command consumes a `TimeRange` and decides which endpoint
combinations make sense:
- `compare` rejects `live`-on-both-sides.
- `contributions` rejects `live` entirely (no meaningful "live
contributions diff").
- `projections --vs` accepts `live` on either side.
### Naturally folds into the CLI architecture overhaul
Once each command has its own `parseArgs()` (per the CLI overhaul
entry above), the time-range parser becomes a shared utility those
parsers call. Could either land standalone or be pulled in as part
of the overhaul. If the overhaul lands first, this is one of its
follow-ups; if this lands first, the overhaul inherits it for free.
## Split `audit.zig` into per-broker reconcilers — priority LOW
`src/commands/audit.zig` is 3438 lines — the largest command file
by ~2x. It bundles four logically distinct responsibilities:
- Portfolio hygiene check (no-flag mode)
- Fidelity positions CSV reconciler (`--fidelity`)
- Schwab per-account positions CSV reconciler (`--schwab`)
- Schwab account-summary stdin parser (`--schwab-summary`)
### Sketch
```
src/commands/audit/
mod.zig ← thin dispatcher; current public `run()` lives here
hygiene.zig ← portfolio hygiene check (no-flag mode)
fidelity.zig ← --fidelity CSV reconciler
schwab.zig ← --schwab CSV + --schwab-summary stdin reconciler
common.zig ← shared types (Discrepancy, ReconcileResult), formatters
```
Adding a new broker (Vanguard, Robinhood, etc.) becomes a one-file
add against a documented contract. The hygiene check can be
referenced from `zfin doctor` (above) without pulling in CSV-parser
baggage.
### Driver
Maintenance friction. The next person adding a broker reconciler
— likely future-you — has to navigate 3438 lines to find the
pattern. The split also makes the audit-bug investigations already
in this TODO file (phantom discrepancy on freshly-added lots) easier
to localize.
Pure internal refactor; no user-visible change.
## Refactor: trim `src/format.zig` once Money / Date have absorbed their helpers — priority LOW
`src/format.zig` is still a ~1700-line grab-bag, but the money- and