# CHANGELOG



## v0.67.1 (2026-05-14)

### Fix

* fix(web): overview slow load, edit-layout chrome, verify-pill copy, lifetime-overlay formatting

- Defer the lifetime account-value brush (~1.8s Yahoo fetches) to a new
  /portfolio/equity-curve/brush endpoint that hx-loads after first paint;
  cold YTD body drops from ~3.1s to ~1.2s.
- Edit layout: shrink the toolbar button to match Period/Account selects,
  ship hidden rows as compact placeholders so Edit layout can surface them,
  and make the grip/eye affordances visible (subtle backdrop + border).
- Verify pill: relabel each state as &#34;Data check: …&#34; with an explanatory
  tooltip so the header reads as a self-audit, not an action prompt.
- Cash curve: fix trailing-dot header (use period_label, not undefined year),
  drop the &#34;+&#34; prefix on positive Y-axis values, and move the
  Lifetime min/max annotation labels off mid-chart (offsetX:80) to the
  right edge with no border.
- Monthly P&amp;L: same right-edge fix for the Lifetime avg annotation.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`14593df`](https://github.com/chen-star/net_alpha/commit/14593dfdcb590fd2f002df4b127660befa00c963))


## v0.67.0 (2026-05-14)

### Feature

* feat(web): equity-curve brush strip with drag-to-reperiod

Adds a paired ApexCharts brush strip below the main equity curve when
lifetime_account_points is present (period != lifetime). The strip shows
the lifetime account-value series, highlights the current period&#39;s range,
and on pointer release snaps to YTD or a calendar year and dispatches a
change event on the period &lt;select&gt; to trigger the existing HTMX flow.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`d483c6a`](https://github.com/chen-star/net_alpha/commit/d483c6a7db6140710286de55e56985da53e160a1))

* feat(web): lifetime subtitle on Total Return + Realized P/L KPI tiles ([`cc00b6a`](https://github.com/chen-star/net_alpha/commit/cc00b6a822181f43c92ac7670f6fddbee3a73c4d))

* feat(web): monthly P/L lifetime-avg reference line + best/worst footnote ([`5ef98c6`](https://github.com/chen-star/net_alpha/commit/5ef98c6b73ca45a0ede2049c2dfd3773efc139fd))

* feat(web): cash curve lifetime min/max reference lines + footnote ([`82ff2e9`](https://github.com/chen-star/net_alpha/commit/82ff2e9065108550eeca080885c3e44289c079b5))

* feat(web): cache lifetime equity curve + lifetime stats in portfolio body ctx

Adds `_build_lifetime_account_points` + `_get_or_compute_lifetime_curve`
(module-level, fragment_cache-backed) so period switches never recompute the
expensive lifetime equity-curve fan-out. Extends `_compute_portfolio_body_context`
with six new ctx keys (lifetime_account_points, cash_lifetime_extremes,
monthly_pl_lifetime, lifetime_kpis, current_year, available_years) and a
cache regression test that asserts ≤1 builder call across three period requests.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`522a811`](https://github.com/chen-star/net_alpha/commit/522a811828322dc0a28be5fd6b9991c6414747bc))

* feat(portfolio): monthly_pl_lifetime_stats helper for lifetime reference line ([`ab936e7`](https://github.com/chen-star/net_alpha/commit/ab936e7e0947195752828bcbbd1ac9c7742b7150))

* feat(portfolio): cash_balance_extremes helper for lifetime reference markers ([`3805de4`](https://github.com/chen-star/net_alpha/commit/3805de48199af97f082c89d3b9cf789a7fe9a4b1))

### Fix

* fix(web): sign-correct monthly P/L lifetime best/worst footnote

If every month was profitable the &#34;worst&#34; month is still a gain — but
the prior template hardcoded `text-neg` + `−$` prefix on `worst`, and
symmetrically green `+$` on `best`. Branch on sign for both. Drop the
unnecessary `raise_server_exceptions=False` in the cache test so route
failures surface real tracebacks.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`0b9832e`](https://github.com/chen-star/net_alpha/commit/0b9832eb2504c3f1094d9dc1e645d6ced46a1060))


## v0.66.0 (2026-05-14)

### Documentation

* docs(readme): refresh to reflect current project status

- Default UI port 8765 → 18765 (matches 904830b)
- §1256: open positions are now marked-to-market per §1256(a)(1) (schema v21)
- Add Rev. Rul. 2008-5 IRA-trap detection (permanent_ira kind)
- Add Verify badge and weekly verify service job
- Add account-typed multi-select (taxable / IRA / Roth / 401(k) / HSA)
- Add local backup / restore subsystem and daily 03:30 UTC backup job
- Architecture diagram and service job list updated to four jobs

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`295744c`](https://github.com/chen-star/net_alpha/commit/295744c1b714c15105cbd37bc9d390ca18257430))

### Feature

* feat(web): edit-mode CSS for overview draggable layout

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`f08199b`](https://github.com/chen-star/net_alpha/commit/f08199b59a4ae5effb5b7c7e3e43b7a10717fae1))

* feat(web): SortableJS glue for overview drag-to-reorder

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`440bfaa`](https://github.com/chen-star/net_alpha/commit/440bfaa63d4ac0d4787d8cef7d196da785e813d3))

* feat(web): Edit layout / Done / Reset toolbar controls

Adds `x-data=&#34;{ editing: false }&#34;` to the toolbar form and three
client-side-only buttons: Edit layout (btn-ghost, visible when not
editing), Done (btn, visible when editing), and Reset to default
(btn-ghost muted, visible when editing). Toggling editing flips
`#overview-rows[data-edit-mode]` and dispatches
`overview:edit-mode-change` for future Sortable wiring.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`33c065b`](https://github.com/chen-star/net_alpha/commit/33c065b664ec730cb00b62c6c2ec586da7f7d61a))

* feat(web): iterate overview body over layout.rows

Rewrites _portfolio_body.html to loop over `layout.rows`, rendering
each row via its _portfolio_row_*.html partial. Hidden rows are
suppressed in normal mode; `data-row-key` + Alpine `hidden` state
power the edit-mode eye-toggle. Adds three DOM-assertion tests that
verify default order, saved order, and hidden-row suppression.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`a41ba6e`](https://github.com/chen-star/net_alpha/commit/a41ba6e243970f1af57b3700cab3f440dc7c48d7))

* feat(web): POST /portfolio/layout/reset

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`121a744`](https://github.com/chen-star/net_alpha/commit/121a7441f9f55960542dea5037aaf26ea043b893))

* feat(web): POST /portfolio/layout/visibility

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`36c6ddc`](https://github.com/chen-star/net_alpha/commit/36c6ddc2cf0702cb41382ee33e04c7086b8203d3))

* feat(web): POST /portfolio/layout/reorder

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`1a4c3aa`](https://github.com/chen-star/net_alpha/commit/1a4c3aa173a6d741be8296ff7e6ea6d5ee6a77b9))

* feat(web): pass overview_layout into /portfolio/body context

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`0ebd770`](https://github.com/chen-star/net_alpha/commit/0ebd770a7b2a91956af103b37766e92ff923b098))

* feat(repo): read/write overview_layout per profile

Also updates five stale CURRENT_SCHEMA_VERSION==24 sentinels in prior
migration test files to reflect the new v25 baseline.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`b198106`](https://github.com/chen-star/net_alpha/commit/b198106f64676f84fc795b55579be8b751afdf9a))

* feat(prefs): add overview_layout pure module

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`16da684`](https://github.com/chen-star/net_alpha/commit/16da684d05795c04118e66842fd76784fc2bbee7))

* feat(db): add overview_layout table (schema v25)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`449fc4f`](https://github.com/chen-star/net_alpha/commit/449fc4fc7a447f87847e2968b6126d91258a4a1f))

### Fix

* fix(web): render grip + eye icons via vendored Lucide SVGs

The initial edit-mode template used &lt;i data-lucide=&#34;...&#34;&gt; tags, but this
project ships pre-rendered SVGs (lucide-static) rather than the Lucide JS
runtime. The unreplaced &lt;i&gt; tags collapsed to 0x0, making the grip + eye
buttons invisible (8x8 with no content). Vendor grip-vertical, eye, and
eye-off SVGs and swap to the project&#39;s &lt;img src=&#34;...&#34;&gt; + icon-mono pattern.
Also raise button z-index and add hover affordance so the affordances
read clearly over panel content.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`bc31e9d`](https://github.com/chen-star/net_alpha/commit/bc31e9d4cad833a8a321d4afdfd7e2dd17f97317))

### Refactor

* refactor(web): extract per-row partials for overview body

Six new _portfolio_row_*.html partials wrap the existing panel
includes — inbox, kpis, curves, monthly_pl, allocation, top_movers.
Not yet referenced; app behaviour is unchanged.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`d0c3e0e`](https://github.com/chen-star/net_alpha/commit/d0c3e0e5e49bb70eb4208416477a3601ee285949))

### Style

* style: ruff lint + format fixups for overview_layout

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`9b0bd8a`](https://github.com/chen-star/net_alpha/commit/9b0bd8a8984c4ad588fa057dac4958927030fc27))


## v0.65.2 (2026-05-12)

### Fix

* fix(web): keep Account label inline with dropdown on toolbar

Make the multi-select root inline-block so the Account label and trigger
button sit on one row, matching the Period control&#39;s layout.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`e9f51c1`](https://github.com/chen-star/net_alpha/commit/e9f51c1672172133b5562d0b17e7c20519100611))


## v0.65.1 (2026-05-12)

### Chore

* chore: refresh gitnexus stats and lockfile version

Catches up AGENTS.md / CLAUDE.md gitnexus symbol counts to the current
index and bumps wash-alpha in uv.lock to the v0.65.0 release.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`90799c0`](https://github.com/chen-star/net_alpha/commit/90799c0428b3729bc2b150d5d84a20e176e39db3))

* chore(ui): move default port from 8765 to 18765

Shifts the free-port scan range, all CLI/service defaults, sandbox
profile pin, and status pill display to 18765–18775. Existing installs
need `net-alpha service uninstall &amp;&amp; install` to pick up the new port.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`904830b`](https://github.com/chen-star/net_alpha/commit/904830b29c342a18752e3ae5af2a2ccd33dd42e0))

* chore(pytest): register e2e marker

Silences PytestUnknownMarkWarning emitted when running the Playwright
golden flows added in the flow-and-clarity-pass. Also enables marker-
based filtering (e.g. `pytest -m &#39;not e2e&#39;` to skip the slow
browser-driven tests). ([`e8b96d4`](https://github.com/chen-star/net_alpha/commit/e8b96d44878b7257e91f91c1f2be2d77c1f8e38b))

### Fix

* fix(web): size Verify badge as compact pill on Overview toolbar

The `verify-badge` class had no CSS, so the badge inherited the
default font-size and rendered with no padding/border — towering
over the adjacent Period/Account/Prices toolbar. Apply the same
Tailwind pill utilities used by the header pill so the badge
matches the toolbar&#39;s visual scale.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`be87edc`](https://github.com/chen-star/net_alpha/commit/be87edccffee67324149ec0219681bcea61ba97f))

### Refactor

* refactor(account): drop redundant spinner from multi-select trigger

The multi-select trigger button sits inside the Portfolio toolbar form
and lit up via the descendant CSS selector .htmx-request .spinner-inline
whenever the form fired — duplicating with #portfolio-toolbar-spinner.
On non-HTMX pages it was dead markup that never animated.

Removed the include from the trigger; the toolbar-wide spinner still
covers the HTMX path. Updated test_account_multi_select_has_spinner to
test_portfolio_toolbar_has_spinner so the assertion lands on the page
where the spinner actually has work to do. ([`dd253c5`](https://github.com/chen-star/net_alpha/commit/dd253c5982703a5d5d3fe614ca904c42b050c0a9))

* refactor(web): consolidate DOM-id slug into dom_id_slug helper

The harvest tax-saved cell IDs ran the account label through a hand-rolled
`/`→`__`/` `→`_` replace chain in two places (Jinja template + Python
route OOB builder). Expanded coverage with a regex-based `dom_id_slug`
helper in `web/format.py`, registered as a Jinja filter, used by both
sites. Anything outside `[A-Za-z0-9_-]` collapses to `_`.

Why: account labels are user-set via Settings — characters like `:`,
`#`, or `+` are unlikely in practice but would silently break OOB
`getElementById` lookup. One source of truth + a permissive filter
removes the brittleness without changing observable behavior on existing
labels. ([`ae5a66d`](https://github.com/chen-star/net_alpha/commit/ae5a66dd3b32eeb52fa723464b0b3ea075bb667e))

### Test

* test(harvest): seed real harvest row for stable-cell-ID assertion

test_tax_saved_cells_have_stable_ids previously seeded only an open buy
and no price quote, so compute_harvest_queue returned empty and both
nested &#39;if … in body&#39; guards short-circuited to a no-op pass — the test
asserted nothing meaningful.

Added a _seed_harvest_row helper that materializes lots (stitch +
recompute) and writes a price_cache quote below basis, mirroring the
recipe proven by the e2e fixture. The test now positively asserts the
stable cell id renders against a populated row. ([`6b5e25a`](https://github.com/chen-star/net_alpha/commit/6b5e25a5eb7e85b678a88b49bd801557e3750134))

### Unknown

* Merge pull request #10 from chen-star/worktree-flow-and-clarity-followups

chore: flow-and-clarity follow-ups ([`a3368fa`](https://github.com/chen-star/net_alpha/commit/a3368fa40a52eb5bfd1afbfe294a0efc68be5dc9))


## v0.65.0 (2026-05-12)

### Ci

* ci: install Playwright Chromium for E2E tests

PR #8 introduced tests/web/e2e/ Playwright tests but did not update the CI
workflow to install the browser binary, so every run after that PR fails
with &#34;Executable doesn&#39;t exist at .../chrome-headless-shell&#34;. Adds an
explicit `playwright install --with-deps chromium` step after dependency
sync.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`295b306`](https://github.com/chen-star/net_alpha/commit/295b306d4b94c91a73eee63a6891546be0f095f5))

### Feature

* feat(ticker): responsive KPI grid with collapsible secondaries (B3) ([`8eb1d18`](https://github.com/chen-star/net_alpha/commit/8eb1d18519ec92b7f1533194ad9c7c01303b5786))

* feat(portfolio): declutter Total Account Value hero tile (B1) ([`31fce24`](https://github.com/chen-star/net_alpha/commit/31fce241f97006c130e163f51a15ce2370a0331d))

* feat(web): in-flight spinners for HTMX-driven forms (A5) ([`346779b`](https://github.com/chen-star/net_alpha/commit/346779bc24421c7d161140451cd23902de2c1a6e))

* feat(web): arrow-key row nav + visible search focus ring (A4) ([`b665efb`](https://github.com/chen-star/net_alpha/commit/b665efb131f9125bf9d7a194873a315db1d4119a))

* feat(web): skeleton loaders for lazy-loaded panels (A3) ([`80b074c`](https://github.com/chen-star/net_alpha/commit/80b074c594cf4680d666e30762420d5d8077ce54))

* feat(harvest): split summary + table regions for OOB swap (A2.3) ([`40e51fe`](https://github.com/chen-star/net_alpha/commit/40e51fe88a8d6d406cacdc56bd346c290760c6bb))

* feat(portfolio): HTMX toolbar swap with OOB subline (A2.2)

Replace the full-page reload on Portfolio period/account toolbar changes
with an HTMX swap targeting #portfolio-body. The page H1 subline (which
sits outside #portfolio-body) is updated via an OOB fragment emitted
from /portfolio/body when called with HX-Request.

Also fix the HTMX-bypass bug in the account multi-select Alpine helper
and the profile-switcher selects: form.submit() does not fire the
&#39;submit&#39; event so HTMX never intercepted; requestSubmit() does.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`acf2bea`](https://github.com/chen-star/net_alpha/commit/acf2beafb0f10e6c39fa2ffae2aabb3ee0f88800))

* feat(positions): per-tab page memory via localStorage (A2.1) ([`5e06253`](https://github.com/chen-star/net_alpha/commit/5e062538d876b5bca74a8cf30dd14ac07be14b5a))

* feat(sim): add next-step CTAs to buy/sell result panels (A1)

After running a sim trade the result panel was a dead end. Append a &#34;Next
steps&#34; CTA strip with deep links to the position view and ticker timeline
(both buy + sell), plus an &#34;Add to harvest plan&#34; link on sell-at-loss that
piggybacks on the harvest planner&#39;s existing `pick={sym}::{acct}` query
contract.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`be3d396`](https://github.com/chen-star/net_alpha/commit/be3d3965dc9dfae78fc8fc6bef2c1396de70d223))

### Fix

* fix(web): table-nav HTMX re-init + row focus ring (A4) ([`45bcfb3`](https://github.com/chen-star/net_alpha/commit/45bcfb3202d65c78b4560445800b00167390456b))

* fix(portfolio): scope toolbar HTMX to Portfolio page only (A2.2) ([`35768d4`](https://github.com/chen-star/net_alpha/commit/35768d4cbf6c2c7cc8500d22bafd6dfdab0c1ce6))

* fix(portfolio): dispatch change event instead of requestSubmit() in account multi-select (A2.2) ([`b7fa303`](https://github.com/chen-star/net_alpha/commit/b7fa303d5be2cbd924a102458abb209af9274932))

### Refactor

* refactor(tax): single-source taxpayer-level offset caveat (B2)

Move the taxpayer-level offset caveat into a shared partial,
_taxpayer_caveat.html, included from both tax.html (page-level for
/tax routes) and _harvest_plan.html (HTMX fragment loaded into
/positions?view=at-loss). The two contexts never co-render, so the
partial is the single source of truth for the caveat copy.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`be262bf`](https://github.com/chen-star/net_alpha/commit/be262bf3d8c0e24be3a264ab61860089268212ee))

### Style

* style: apply ruff format to tax route harvest OOB block ([`dbfa18e`](https://github.com/chen-star/net_alpha/commit/dbfa18ea24555aeb2b9f8b9bf5287f2e0927e648))

* style: apply ruff format to flow-and-clarity-pass test files ([`8de9920`](https://github.com/chen-star/net_alpha/commit/8de99201b8c5b84b2e831d7345dac7edbc69d2dc))

* style: apply ruff format to test_palette_in_pages.py

Fixes CI lint failure on master where ruff format --check rejected
nested-quote and line-wrap style in two assertion messages.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`0990c2b`](https://github.com/chen-star/net_alpha/commit/0990c2bbacb854197e409809f7738ab9e330dd36))

### Test

* test(e2e): scroll preservation + arrow-key nav golden flows

Two Playwright tests cover behaviors that TestClient HTML snapshots can&#39;t:
scroll Y is preserved across the harvest checkbox HTMX summary swap (A2.3),
and ArrowDown traverses rows under tbody[data-table-nav] (A4). Builds a
self-contained live_server_harvest fixture that seeds buys + price_cache
quotes (well below basis) so compute_harvest_queue produces 8 loss rows.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`946fa70`](https://github.com/chen-star/net_alpha/commit/946fa70a9d38d9d35fe32779ec1b86304ca05d22))

### Unknown

* Merge pull request #9 from chen-star/worktree-flow-and-clarity-pass

feat(web): flow &amp; clarity UX pass ([`e7e95fd`](https://github.com/chen-star/net_alpha/commit/e7e95fd69430de02769bd0c986110d799df7513f))


## v0.64.0 (2026-05-12)

### Chore

* chore(docs): refresh GitNexus stats in AGENTS.md and CLAUDE.md

Auto-generated metadata refresh from the most recent npx gitnexus
analyze run. No functional changes. ([`9aea02d`](https://github.com/chen-star/net_alpha/commit/9aea02d8d77a889bf503a1ab7a15b61f3e4f653f))

### Feature

* feat(palette): jump to verify findings, Sim sell/buy actions, expanded pages

PAGES grows from 8 → 14 (adds /verify, /wash-sales, /tax/harvest/plan,
/tax/projection-config, /settings/accounts, /backup). The index now
emits two new kinds:

  * Action — &#34;Sim sell SYM&#34; / &#34;Sim buy SYM&#34; per held symbol, routing
    to /sim?ticker=SYM&amp;action=… so the user can pre-fill the simulator
    from the palette
  * Finding — up to MAX_FINDINGS=20 rows from the latest verify run
    (capped so a noisy week doesn&#39;t bloat every page render); all link
    to /verify and are searchable by rule_id, scope, or severity

JS rank() / recentItems() / commit() learn the new kinds; Findings are
deliberately not persisted to recents (they&#39;re ephemeral per run).
loadIndex() back-fills the new keys for older bootstrap blobs.

6 new tests cover page coverage, action-emission-only-for-held,
finding pull from latest run, MAX_FINDINGS cap. ([`4064e9d`](https://github.com/chen-star/net_alpha/commit/4064e9d0e53efccd4a19e1e30c7a849db1ef9472))

* feat(verify): inline &#34;Why?&#34; explainer + one-click suppress + freshness banner

Polish on top of the verify engine merge. Each verify_finding row now
expands to a rule-specific explanation (with a /reconciliation deep
link for BasisRecon/RealizedRecon and an /imports link for
StaleReference). The same panel hosts a &#34;Suppress for 1 year&#34; form that
appends to ~/.net_alpha/config.yaml (idempotent on rule_id+scope,
preserving other top-level keys) and re-runs verify so the finding
disappears immediately. The /verify dashboard gets a freshness banner
for the broker All-Positions CSV (never-uploaded vs &gt;30 days vs OK),
and the global header pill tooltip now reports positions CSV age.

10 new tests cover explainer copy, suppress YAML idempotency, and
banner states. ([`3b16c9a`](https://github.com/chen-star/net_alpha/commit/3b16c9a99af31c35fcded561b07c0c0a2ffabe04))

### Fix

* fix(palette): resolve &#34;paletteOverlay is not defined&#34; at Alpine init

Three things had to align before the ⌘K overlay could mount cleanly:

1. _palette.html: x-data=&#34;paletteOverlay()&#34; → x-data=&#34;window.paletteOverlay()&#34;
   Alpine 3&#39;s expression evaluator does not resolve bare global
   identifiers — it does follow window.*.
2. palette.js: replace the brittle alpine:init listener + fallback
   dance (Alpine.data registration races the init dispatch under
   defer loading) with a one-line window.paletteOverlay = paletteOverlay
   export. Timing-independent.
3. base.html: move palette.js (and its palette-index JSON) to load
   BEFORE alpine.min.js. Defer scripts run in document order; Alpine&#39;s
   first init pass evaluates x-data before subsequent defer scripts
   execute, so the factory has to be on window already by then.

Two regression tests assert (a) the template uses the window-prefixed
form and (b) palette.js exposes the factory on window. ([`46d25df`](https://github.com/chen-star/net_alpha/commit/46d25dff06836181ba68047f1ef9c165e3aeb7e2))

### Unknown

* Merge pull request #8 from chen-star/feat/verify-engine-polish

Feat/verify engine polish ([`96a0fe9`](https://github.com/chen-star/net_alpha/commit/96a0fe984e43b16daafb8964ee7406ce3ab40062))

* Merge origin/master: integrate multi-account filter + v0.63.0

Reconciles two parallel feature lines:
  * origin/master — multi-account filter across Portfolio/Positions/Plan/
    Tax/Wash Sales/Imports, schema v23 baseline, v0.63.0 release tag.
  * feat/verify-engine-polish — the 3-layer verification engine plus this
    session&#39;s polish (inline explainer + suppress, palette upgrades,
    Alpine init fix), schema bumped to v24 for verify_result /
    verify_finding / broker_position tables.

Conflict resolution
-------------------
src/net_alpha/web/templates/positions.html
  Both sides edited the toolbar block. Combined: keep this branch&#39;s
  verify-badge-slot wrapper AND adopt origin&#39;s relaxed gating
  ({% if imports %} without the selected_view restriction — origin
  intentionally exposed the toolbar on every view in 3599afe).

Adaptations
-----------
src/net_alpha/web/routes/verify.py
  compute_open_positions(account=None) → accounts=None to match the
  widened signature from origin&#39;s multi-account work.

tests/verify/golden/test_golden.py
  Same param rename for compute_kpis(account=None) → accounts=None.

Schema migration ladder auto-merged cleanly: CURRENT_SCHEMA_VERSION=24,
_migrate_v23_to_v24 is the verify-tables migration, freeze tests assert
24. Origin&#39;s earlier confusion around v23/v24 was already reverted
upstream in 037214d, so the merge picks up the corrected v23 baseline
and our v24 bump sits on top.

All 2164 tests pass on the merged tree. ([`4db1625`](https://github.com/chen-star/net_alpha/commit/4db1625f1f9a793bf702d9156cea86db0e58ef0a))


## v0.63.0 (2026-05-12)

### Feature

* feat(web): add multi-account filter to Imports page

Adds the account multi-select toolbar to /imports/_legacy_page (Task 12).
The route now accepts `account: list[str]` via parse_accounts(), filters
ImportRecord rows by selected accounts (OR logic), and passes
selected_accounts / accounts_available / account_filter_active to the
template. Data-hygiene rollups are intentionally left unfiltered (HygieneIssue
carries no per-account field) with a TODO comment. Template gains the
_account_multi_select macro and data-testid=&#34;imports-table&#34; on the table div.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`f6c2781`](https://github.com/chen-star/net_alpha/commit/f6c27813bd7b9835b14f6096c9aa2b9e40133851))

* feat(web): multi-account OR semantic on Wash Sales tab + remove single-account bridge

_wash_sales_context now accepts accounts: list[str] (replaces account: str | None).
Filter uses set-membership OR: a cross-account violation (loss=A, buy=B) appears
under filter {A}, filter {B}, and filter {A,B} — never double-counted.
recent_loss_closes and list_exempt_matches updated to use accounts= keyword.
Context dict gains selected_accounts / accounts_available / account_filter_active;
filter_account removed. tax.py call site passes accounts= directly, removing the
single_account bridge added in Task 10. wash_sales.html updated to use the new
context keys (template is dead/redirected, updated for consistency).

11 new tests in test_wash_sales_multi_account.py covering OR semantics end-to-end.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`9b799e0`](https://github.com/chen-star/net_alpha/commit/9b799e0a4e345049eda9f15fd5297c404d180a78))

* feat(web): multi-account filter on Tax page + taxpayer-level caveats

- get_tax: account param widened to list[str] via parse_accounts; derives
  accounts, account_filter_active, single_account (bridge for Task 11).
- harvest_plan: account param added; compute_harvest_queue called with
  account_id resolved from single-account selection; multi-select degrades
  to all-accounts (compute_harvest_queue / build_plan are not widened — Task 11).
- _build_performance_ctx: account: str | None → accounts: list[str];
  passes accounts= to compute_after_tax and section_1256_mtm_rows.
- tax.html: always-visible account label (All accounts / selected); tab
  hrefs updated to multi-param loop; performance caveat injected outside
  has_any_tax_data guard so it renders on empty DB too.
- _tax_wash_sales_tab.html: account text input + datalist replaced with
  account_multi_select macro; qs_no_view loop updated; chip display updated.
- _tax_performance_panel.html: &#34;Carryforward applied is taxpayer-level&#34;
  caveat when account_filter_active.
- _harvest_plan.html: &#34;Offset budget is taxpayer-level&#34; caveat when
  account_filter_active.
- Tests: test_tax_multi_account.py (6 HTTP-level), test_tax_caveats.py (4).
  All 596 web tests pass.

Note: _wash_sales_context still takes account: str | None (single-account
bridge via single_account). Task 11 will widen it to accounts: list[str].

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`a7ab700`](https://github.com/chen-star/net_alpha/commit/a7ab700591efd42f52f9a406cc7124c6ce47d845))

* feat(web): multi-account filter on Positions + Plan pages

Migrates all positions_page, _build_plan_view_for_request, and
_render_plan_body handlers from single-string account param to
list[str] + parse_accounts(), matching the Portfolio pattern from
Task 8. Updates _positions_plan_toolbar, positions.html, _positions_tabs,
_positions_view_closed, and _positions_view_plan to carry multi-account
state forward via repeated hidden inputs and Jinja loops.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`17c8dc1`](https://github.com/chen-star/net_alpha/commit/17c8dc14d7b2d1753df70006146aa016b583582d))

* feat(web): multi-account filter on Portfolio page

Migrate all 13 route handlers in portfolio.py from single-string
`account: str | None` to `account: list[str] = Query(default_factory=list)`
with `parse_accounts()` normalization. Swap the toolbar&#39;s single &lt;select&gt;
for the shared `_account_multi_select.html` macro. Update portfolio.html
to use `selected_accounts` / `accounts_available` context names and build
the HTMX fragment URL with multi-account params.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`09182a2`](https://github.com/chen-star/net_alpha/commit/09182a2b7e2c9fd63992f20db7503c81d0b04be1))

* feat(portfolio): widen calendar/cash_flow/wash_watch aggregations to accept accounts list

Add `accounts: Sequence[str] | None = None` to monthly_realized_pl,
monthly_realized_pl_series, build_cash_balance_series, compute_cash_kpis,
cash_allocation_slice, and recent_loss_closes. Derives `_filter` set so
existing single-account callers are additive-only — no breaking changes.
13 new tests covering single-account equivalence, multi-account union, and
empty-list passthrough.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`0dd05c4`](https://github.com/chen-star/net_alpha/commit/0dd05c424b42485b4e870bcf2900fed91dfc5adf))

* feat(portfolio): widen compute_after_tax to accept accounts list

Add `accounts: Sequence[str] | None = None` keyword-only param; pass it
(alongside `account=`) to every repo call so multi-account filter works
end-to-end. Empty list is normalised to None (no filter). account_filter
label is comma-joined when multi-select is active. Updated _StubRepo fakes
to accept the new kwargs.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`9fb1825`](https://github.com/chen-star/net_alpha/commit/9fb1825ab7f5a4f5fad1d5c7267293ad01ec193c))

* feat(db): widen Repository SQL methods to accept accounts list

Add `accounts: Sequence[str] | None = None` parameter to 8 SQL-touching
Repository methods, alongside the existing `account: str | None` parameter.
Empty list and None both mean &#34;no filter&#34; (backward-compatible). The new
parameter supports OR semantics on cross-account violations/exempt-matches
via .in_() on TEXT columns and a new `_account_ids_for_displays` helper for
FK-int columns. All 8 methods keep their existing single-account paths
working unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`df4b248`](https://github.com/chen-star/net_alpha/commit/df4b2488a3cae68e9b16232c80b9ead237bd12a8))

* feat(portfolio): widen position aggregations to accept accounts list

Add `accounts: Sequence[str] | None` param to compute_open_positions,
compute_open_option_positions, and compute_closed_lots. The new param
accepts a set-based multi-account filter; the existing `account` /
`account_display` singular params continue to work unchanged. Four new
tests cover single-account parity with legacy and two-account union.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`523e244`](https://github.com/chen-star/net_alpha/commit/523e244c22e1ed2f47f8a2d7aa8ecacddaee63d4))

* feat(portfolio): widen compute_kpis/compute_wash_impact to accept accounts list

Adds an `accounts: Sequence[str] | None` keyword parameter to both
`compute_kpis` and `compute_wash_impact`. An empty list or None falls back
to the legacy `account=` single-string filter; a non-empty list builds a
set filter (OR semantics for wash-impact, union for KPIs). Existing callers
are unaffected — `account=` retains its existing behaviour and now defaults
to None, keeping keyword-only call sites compatible.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`f010fb9`](https://github.com/chen-star/net_alpha/commit/f010fb903f011c7257f8f81d61a0c6be500e9edd))

* feat(web): add _account_multi_select.html Jinja macro ([`bb2cf4c`](https://github.com/chen-star/net_alpha/commit/bb2cf4c1a9da33cde0c592e8f03890e556dae001))

* feat(web): add parse_accounts() helper for multi-account URL filter ([`d870707`](https://github.com/chen-star/net_alpha/commit/d870707c84830b0b09b660ed65c5b09d9ae11f33))

* feat(verify): suppression list (user-acknowledged known issues)

Adds verify/suppress.py with SuppressionRule + load_suppressions() +
is_suppressed(). Source: &#39;verify.suppress&#39; section in
~/.net_alpha/config.yaml. Matching is rule_id-exact, scope-prefix
(&#39;BRK.B&#39; suppresses both &#39;BRK.B/IRA&#39; and &#39;BRK.B/Taxable&#39;), and
auto-expiring (today &gt; until disqualifies). Malformed entries are
skipped, never fatal.

Drops the Phase 5 try/except import stub in service/jobs/verify.py
and switches it to the real is_suppressed call.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`92939a7`](https://github.com/chen-star/net_alpha/commit/92939a77ab16153386e3d044d4cfe4bef54a8d93))

* feat(verify): global header status pill summarizing latest verify run

Anchored on every page via base.html&#39;s topbar, linking to /verify. Status
mirrors the latest verify_result row: ok/warn/fail/stale, or &#39;grey&#39; when
no run has been recorded yet. Payload comes from a Jinja global function
(verify_pill_data) following the same single-LIMIT-1 cheap-read pattern
used by imports_badge_count and profile_switcher_data.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`295250a`](https://github.com/chen-star/net_alpha/commit/295250a4b62a8d3cee2c54fbb443436b179a1264))

* feat(verify): /verify page, /verify/run, /verify/findings/{id}

Extends the existing verify router (Task 10 shipped only the inline
/verify/badge fragment). The page lists the latest run summary, the
embedded findings table, and a 30-row run history; POST /verify/run
triggers a synchronous run with trigger=&#34;manual&#34; then redirects;
GET /verify/findings/{id} returns the same findings-table fragment for
a historical run id (404 if unknown).

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`a35f557`](https://github.com/chen-star/net_alpha/commit/a35f557c5c17e0c19bd5e58093396d1c0ba79035))

* feat(verify): register weekly Sunday 04:30 verify job in APScheduler

Sunday 04:30 UTC sits 30 min after the daily 04:00 washsale-watch and an
hour after the 03:30 backup, so the three nightly jobs never overlap.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`307d63c`](https://github.com/chen-star/net_alpha/commit/307d63c074eec18a39f3c0e1fada3972c66c110e))

* feat(verify): job orchestrator + repository persistence (verify_result + findings) ([`bd7fce2`](https://github.com/chen-star/net_alpha/commit/bd7fce248fe5c370129ead20f4ada9848fca758c))

* feat(verify): open-positions reconciler with 30-day staleness gate ([`f40c6c2`](https://github.com/chen-star/net_alpha/commit/f40c6c24e9d6c67bd60704fad0b874039736211d))

* feat(verify): realized-G/L reconciler emits verify_finding rows ([`949c2e7`](https://github.com/chen-star/net_alpha/commit/949c2e7f6db9e76817f27f365970870127cf8e14))

* feat(verify): /imports/positions upload endpoint + broker_position persistence ([`20ea286`](https://github.com/chen-star/net_alpha/commit/20ea286bb4df3505c3662730fe1a9665f7003f44))

* feat(verify): Schwab All-Positions CSV parser with header-date extraction ([`2abe024`](https://github.com/chen-star/net_alpha/commit/2abe024ba5f33e22f8734715b4e743e83cb9b2c5))

* feat(verify): wire badge fragment into /positions shell ([`7eabc8e`](https://github.com/chen-star/net_alpha/commit/7eabc8efec13dfcb52c20f5ca2da7dd6ebba6496))

* feat(verify): /verify/badge HTMX fragment for Overview page ([`20c4101`](https://github.com/chen-star/net_alpha/commit/20c4101f7987d3caf0089d3e80d00a52f2b2d7e8))

* feat(verify): BadgeStatus + run_inline orchestrator (L1 entrypoint) ([`1316519`](https://github.com/chen-star/net_alpha/commit/13165194cfcf29a6a27b6c6145577f4c4637404d))

* feat(verify): XP-1 cross-page invariant + module-level OverviewTotalCache ([`ec2e819`](https://github.com/chen-star/net_alpha/commit/ec2e819ce252f901e34bbb6911611acbe8c6ad7e))

* feat(verify): PL-1, PL-2, PL-3 per-lot invariants (incl. §1223(4) holding period) ([`4b873f8`](https://github.com/chen-star/net_alpha/commit/4b873f8b53b954a39a8ac8d6f98e10ed312d3af0))

* feat(verify): AL-1, AL-2 allocation invariants ([`1528b51`](https://github.com/chen-star/net_alpha/commit/1528b519f7a1c0cda29bfe26d02058fcec8568ec))

* feat(verify): OV-1, OV-2, OV-3 invariant check functions ([`94fbaa3`](https://github.com/chen-star/net_alpha/commit/94fbaa344ffe990b223fef68367483dc6854b668))

* feat(verify): tolerance config (defaults + ~/.net_alpha/config.yaml overrides) ([`bc8ab1f`](https://github.com/chen-star/net_alpha/commit/bc8ab1f69d061b3047637fa9ddb2802b1b1337c6))

* feat(verify): BrokerPosition SQLModel for Schwab All-Positions CSV rows ([`4564add`](https://github.com/chen-star/net_alpha/commit/4564addf958ccf7a33712ff5cc3791c8dbfe0c8c))

* feat(verify): VerifyResult and VerifyFinding SQLModel rows ([`2ad8f1c`](https://github.com/chen-star/net_alpha/commit/2ad8f1c4f21230ed78817909aeb7d9ed4540a987))

* feat(verify): schema v24 adds verify_result, verify_finding, broker_position tables ([`9405165`](https://github.com/chen-star/net_alpha/commit/940516573692bbb2928ab49cb04db8cb2d221eda))

### Fix

* fix(web): preserve multi-account filter on HTMX URLs + UX gaps from review

Final code review caught three Critical issues silently breaking the
multi-account feature on user interaction, plus a few UX gaps:

1. HTMX fragment URLs lost the filter on sort/paginate/explain clicks —
   templates emitted `account={selected_account}` (singular), which is
   empty when 2+ accounts are selected. Switched to Jinja loops over
   `selected_accounts`, fixing portfolio_table, positions_view_*,
   portfolio_kpis, portfolio_open_options, positions_plan_tag_strip,
   and the body HTMX URL in portfolio.html.

   Also fixed a Jinja loop-scope bug where the `{% set x = x + ... %}`
   pattern inside `{% for %}` doesn&#39;t mutate the outer scope. Switched
   to block-set `{% set x %}...{% endset %}` syntax (the correct way
   to accumulate inside a loop in Jinja).

2. `_build_chips_clear_urls` collapsed repeated `?account=` params via
   `dict(request.query_params)` — switched to `multi_items()` so
   clicking the multi-account chip clears all selected accounts.

3. Harvest plan offset-budget caveat lied in multi-account mode (text
   said &#34;Candidates are filtered to your selected accounts&#34; but the
   queue was actually all-accounts). Made the text conditional on
   single vs multi selection.

Important UX gaps fixed:
- Positions toolbar now visible on every view (was only `all`/`stocks`)
- Tax page-level toolbar added so Performance/Projection tabs can
  set/change the filter
- Strengthened `test_plan_toolbar_renders_hidden_account_inputs_for_multi`
  with real assertions (was only checking status code)
- Added `test_portfolio_table_htmx_urls_preserve_multi_account`
  regression test for issue #1

Imports form-action issue was deemed by design (full-page GET is
consistent with every other filter page).

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`3599afe`](https://github.com/chen-star/net_alpha/commit/3599afe2198635c8a0a4c1f582f5867b3e63bbd6))

### Refactor

* refactor: drop legacy &#39;account: str | None&#39; param from widened aggregations

Remove the Phase-2 transitional shim `account: str | None = None` from all
16 aggregation functions and repository SQL methods. The sole filter parameter
is now `accounts: Sequence[str] | None`. Simplify filter derivation from
`set(accounts) if accounts else ({account} if account else None)` to
`set(accounts) if accounts else None` throughout. Update all call sites in
production routes (portfolio.py, positions.py, sim.py) and all test files;
rewrite transition &#34;account= ≡ accounts=&#34; comparison tests to test filtering
behavior directly.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`180392e`](https://github.com/chen-star/net_alpha/commit/180392e8103c5f998321a1e79cee821bb5d7b451))

### Style

* style(verify): ruff format golden test runner

Collapse short multi-line f-string assertion messages onto one line so
ruff format is clean.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`9f5e492`](https://github.com/chen-star/net_alpha/commit/9f5e492e9fd85a38522224b13f28dcb9784c60f4))

* style(1091-ira): ruff format migrations.py

Collapse the v22→v23 ALTER TABLE call to a single line so `ruff format
--check` (CI gate) passes. Functionally identical.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`220094d`](https://github.com/chen-star/net_alpha/commit/220094d88e3d0d801ed77e085b9779b17b60fb41))

### Test

* test(e2e): Playwright golden flow for multi-account dropdown

Boots the FastAPI app against an isolated tmp_path data dir seeded by
build_demo_db (two accounts: schwab/taxable + schwab/ira). Seeds one
AccountPreference row to suppress the first-visit profile-picker modal.

Five tests exercising the account_multi_select macro:
  1. Dropdown renders exactly once with &#39;All accounts&#39; label
  2. Trigger opens/closes the listbox popover
  3. Both demo accounts appear as individual checkboxes
  4. Toggling one account off updates URL with account= param
  5. Partial filter changes the trigger label away from &#39;All accounts&#39;

_ready() helper force-hides the palette overlay backdrop before every
click: the paletteOverlay Alpine component fails to initialise in headless
Chromium (Alpine&#39;s DOMContentLoaded init races the defer-loaded palette.js),
leaving the fixed inset-0 backdrop rendered and intercepting pointer events.
This is a known headless Chromium/Alpine rendering discrepancy; the master
toggle test was adjusted to avoid depending on the broken Alpine component.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`5621925`](https://github.com/chen-star/net_alpha/commit/5621925ab9bdc1cbab1a3727d1af0e5aac959365))

* test(engine): guard against UI &#39;accounts&#39; filter leaking into engine

A multi-account UI filter on the read side must never reach detect_wash_sales
or washsale_watch — those would miss cross-account violations (Rev. Rul. 2008-5
IRA traps especially). This test introspects engine module signatures and
fails CI if anyone adds an &#39;accounts&#39; kwarg.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`98eb658`](https://github.com/chen-star/net_alpha/commit/98eb65825cdf02b062a307c7233468687dcb3dfb))

* test(db): bump stale schema-version freeze tests from 23 to 24

Pre-existing baseline failure unrelated to multi-account filter work.
CURRENT_SCHEMA_VERSION moved to 24 but the three name-based freeze tests
weren&#39;t updated alongside the bump.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`0d15f6a`](https://github.com/chen-star/net_alpha/commit/0d15f6a6f85277b08aa7256033f1f9e584048148))

* test(verify): e2e — trade import → positions import → verify run → page render

Exercises the full Phase 1-6 pipeline through the FastAPI TestClient:

  1. POST /imports (Schwab trade CSV) → trades + lots + wash recompute
  2. POST /imports/positions → broker_position rows + implicit verify run
  3. POST /verify/run → manual verify run
  4. GET /verify renders the latest-run summary
  5. GET / and /positions ship the verify-badge slot
  6. GET /verify/badge?page=overview returns a real data-verify-badge fragment

Reuses tests/fixtures/schwab_sample.csv as the trade fixture (the plan&#39;s
referenced schwab_sample_trades.csv doesn&#39;t exist).

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`a7c0518`](https://github.com/chen-star/net_alpha/commit/a7c0518c75cae501fe42aee9d94b215f1da62a55))

* test(verify): 4 hand-authored golden KPI snapshots

Adds end-to-end snapshot tests that drive trades through the real
create_manual_trade → recompute_all_violations → compute_kpis pipeline
and lock in the resulting KPIs and lot state.

Cases:
  01_simple_buy_hold  — clean unrealized gain on a single open lot
  02_partial_sale     — FIFO-consumed partial sale; period realized
  03_wash_sale_tacking — wash sale: locks in actual engine behavior
                         (see YAML for two observed quirks worth
                         chasing later — earliest-candidate wash-sale
                         match and the GL-only disallowed-loss add-back
                         path in compute_kpis)
  04_section_1256     — closed SPX option; gain shows as period_realized
                        (60/40 split lives in section_1256/classifier.py,
                         not in compute_kpis)

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`f58ad0a`](https://github.com/chen-star/net_alpha/commit/f58ad0a83421ae8bbf024fe2ba1f720393678a1e))

* test(verify): Hypothesis property tests for OV-*, AL-1, PL-1, PL-2

Adds 5 property tests that exercise the same invariant functions used at
L1 render-time against generated portfolio shapes. The same functions
verify the badge and these tests, guaranteeing the two agree on what
&#34;correct&#34; means.

- max_examples=50 per test (~0.8s total)
- Tolerance is widened to (0.10 abs, 0.001 rel) to absorb float rounding
  across multi-lot sums

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`a95fb33`](https://github.com/chen-star/net_alpha/commit/a95fb33ebe5fae8dee13912214c0a5916bfecb8c))

* test(verify): guard service uninstall leaves no verify residue

Two guards: (1) outside scheduler.py and control.py no service-layer module
may mention &#39;verify&#39;, so a future launchd-plist drift is loud; (2) a smoke
test that calls control.uninstall() inside a temp HOME to ensure no
verify-specific cleanup path raises.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`ec1b11f`](https://github.com/chen-star/net_alpha/commit/ec1b11f7ab8a0abee4b28c2784c0bab136e469f6))

### Unknown

* Merge pull request #7 from chen-star/worktree-feat+multi-account-filter

feat(web): multi-account filter across filter pages ([`be3424f`](https://github.com/chen-star/net_alpha/commit/be3424f3d94634aa2f5083d768040b8d3feb4fc8))

* revert: restore schema-version freeze tests to v23 (worktree base is on v23)

I bumped these to v24 at session start thinking the source was v24, but
that bump only exists in the original repo&#39;s uncommitted working tree —
the worktree branched from origin/master where the source is still v23.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`037214d`](https://github.com/chen-star/net_alpha/commit/037214d6b47449a70363c8a256890bc10eb07444))

* Merge feat/verify-engine: Overview/Positions verification engine (3-layer: invariants + broker recon + Hypothesis) ([`48f5f56`](https://github.com/chen-star/net_alpha/commit/48f5f566eceafd074a019a6b79d3ac56e5988b0a))


## v0.62.0 (2026-05-11)

### Feature

* feat(1091-ira): Rev. Rul. 2008-5 permanent disallowance for IRA/Roth replacement legs

Detector now branches on account type via a new
`Repository.account_types_by_display()` lookup threaded through
`detect_in_window` and `recompute_all_violations`. When a §1091 loss in a
taxable account is replaced by a buy in a tax-advantaged account (IRA / Roth /
401(k) / HSA) within ±30 days, the violation is classified
`WashSaleViolation.kind=&#34;permanent_ira&#34;` and we skip the §1091(d) basis
rollover plus §1223(4) holding-period tacking — the loss is permanently lost
because IRAs have no basis ledger to receive it. Losses sold INSIDE a
tax-advantaged account bypass detection entirely (no taxable event → no §1091).

Schema v23 adds `wash_sale_violations.kind TEXT NOT NULL DEFAULT &#39;deferred&#39;`;
pre-existing rows reinterpret as deferred (existing behavior).

`compute_after_tax` exposes `wash_sale_deferred_total` and
`wash_sale_permanent_total` separately; the `/tax` performance panel renders
both lines when permanent disallowances exist. Violations list shows a
&#34;Permanent (IRA trap)&#34; pill and the explain fragment / CLI `--detail` cite
Rev. Rul. 2008-5.

26 files changed, ~480 net lines. 1957 tests pass (+4 IRA-trap suites:
detector, recompute, after_tax, explain). Version bumped to 0.61.0.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`c068444`](https://github.com/chen-star/net_alpha/commit/c068444bac60477aa19a0d5d6a44d130b90a8b37))


## v0.61.0 (2026-05-11)

### Feature

* feat(1091-tacking): IRC §1223(4) holding-period tacking on wash-sale replacement lots

When a wash sale rolls disallowed loss into a replacement lot, the replacement&#39;s
holding-period clock now tacks back to the acquisition date of the lot whose
sale triggered the wash sale (IRC §1223(4)). Previously the engine adjusted
basis but classified the replacement&#39;s later sale as ST/LT using only the raw
buy date — distorting both the after-tax P&amp;L bucket and the §1256 mix bar.

Engine: detector walks all sells in date order, FIFO-consuming lots to find
the loss-side donor&#39;s effective acquired date, then sets
``Lot.tacked_acquired_date`` on the replacement. Chains transitively across
multi-step wash sales.

Persistence: schema v22 adds ``lots.tacked_acquired_date``; round-trips through
``_lot_to_row`` / ``_row_to_lot``.

Classifiers consume the effective date everywhere:
  - Repository.realized_pnl_split_by_year (per-lot FIFO; falls back to trade
    FIFO when no lot rows exist)
  - tax_planner._classify_st_lt_gains (now delegates to the repo method)
  - engine/lot_selector ST/LT chip in the Sim comparison table
  - portfolio/lot_aging &#34;crossing LTCG&#34; view
  - portfolio/positions per-symbol LT/ST quantity split
  - tax_planner harvest-opportunity LT/ST + tax-light is_st heuristic

The repository classifier rewrite also fixes a pre-existing FIFO bug: it had
been using ``chain[0].date`` (earliest buy of the ticker) regardless of which
lot the sell actually consumed.

17 new tests across engine/db/portfolio. 1938 total passing, zero regressions.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`ffa2c0b`](https://github.com/chen-star/net_alpha/commit/ffa2c0b2a4f7159c061186f07b0fe574c2c6850e))


## v0.60.0 (2026-05-11)

### Chore

* chore: docs + version bump to 0.60.0 for §1256 MTM

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`c4d0206`](https://github.com/chen-star/net_alpha/commit/c4d02063d11f4889ed4faf19343297eb7ec8e53e))

### Feature

* feat(1256-mtm): /tax performance view renders year-end MTM table

Adds a §1256 year-end mark-to-market section to the /tax?view=performance
page: new _section_1256_mtm.html fragment, route wiring in
_build_performance_ctx, and 2 passing web tests.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ff508c1`](https://github.com/chen-star/net_alpha/commit/ff508c1902c2090f62b56f7b6d6c8c822c25a8ae))

* feat(1256-mtm): fold MTM into AfterTaxBreakdown; replace stale caveat

Adds section_1256_mtm_pnl / _lt_portion / _st_portion fields to
AfterTaxBreakdown (default Decimal(&#34;0&#34;) for backward compat). compute_after_tax
now calls repo.section_1256_mtm_pnl and combines it with realized §1256 P&amp;L
before the 60/40 split, so year-end MTM gains/losses appear in the tax bill on
/tax?view=performance. Replaces the stale &#34;not marked-to-market&#34; caveat with the
accurate §1256(a) MTM source description.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ab646fb`](https://github.com/chen-star/net_alpha/commit/ab646fbff666a0952c11be66448174763cf28e0f))

* feat(1256-mtm): wire MTM into recompute; classifier re-runs with prior-year basis

Adds a new _run_section_1256_pass shared helper invoked from both
recompute_all_violations and migrate_existing_violations. The helper:

1. Runs classify_closed_trades (cost-basis pass).
2. Walks back from current year to discover the earliest year any open
   §1256 position existed at year-end, then writes per-(position, year)
   Section1256MTMRow rows from there through current year.
3. Re-runs classify_closed_trades with prior_year_mtm_basis_fn so
   realized P&amp;L on a sell in year N uses the year N-1 MTM FMV as basis
   per IRC §1256(a)(2).

The public signatures are unchanged (recompute_all_violations and
migrate_existing_violations). A new module-level _fmv_fn_for_recompute
factory wraps PricingService + year_end_fmv and is monkeypatchable for
tests. `date` is imported at module level so tests can pin &#34;today&#34;.

Pricing seam: Repository does not expose a price provider, so the
factory mirrors cli/refresh_cache.py — PriceCache(repo.engine) +
YahooPriceProvider() wrapped in PricingService with config-driven
enabled flag. When enable_remote is false the provider returns None
and year_end_fmv falls back to (None, &#34;missing&#34;), producing MTM rows
with fmv=0 and source=&#34;missing&#34;.

Idempotency: clear_section_1256_mtm + clear_section_1256_classifications
are called inside the pass so running recompute twice yields the same
DB state. ([`550a3f2`](https://github.com/chen-star/net_alpha/commit/550a3f28ca0c5252d0d9273d3fe77a31c77e25fd))

* feat(1256-mtm): classifier uses prior-year MTM as basis per §1256(a)(2)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`9e62703`](https://github.com/chen-star/net_alpha/commit/9e62703ce29c5a27ced5a91b0f3ac9c8b651fa9f))

* feat(1256-mtm): repository plumbing (save/clear/read/pnl/prior-basis)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`dfb8377`](https://github.com/chen-star/net_alpha/commit/dfb8377687269936628252a1cdde50ad0b43a1fc))

* feat(1256-mtm): pure mark_to_market module + open-position discovery

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`841bf9d`](https://github.com/chen-star/net_alpha/commit/841bf9da0325439434fb5cd242a198825d8bdd8f))

* feat(1256-mtm): year-end FMV cascade (Yahoo close → Black-Scholes → intrinsic)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`a5546c7`](https://github.com/chen-star/net_alpha/commit/a5546c7978408c91d60cf01d5ebaf0a1419dd6ea))

* feat(1256-mtm): add Section1256MTM model, table, and schema v21 migration

Introduces the domain model, SQLModel table, and idempotent CREATE TABLE
migration for IRC §1256(a)(1) year-end mark-to-market rows. Schema version
bumped from 20 to 21; older version-pinned tests updated to stay green.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`cc28ff3`](https://github.com/chen-star/net_alpha/commit/cc28ff3662b9d3526db1f866b4391ac3254b7ca3))

### Fix

* fix(1256-mtm): scan all years from earliest §1256 trade — gap-stop probe missed multi-position chains

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`14b5cb3`](https://github.com/chen-star/net_alpha/commit/14b5cb352f7721057976f392b5a7d31c1a6e4786))

### Unknown

* Merge pull request #6 from chen-star/feat/section-1256-mtm

feat: IRC §1256(a) year-end mark-to-market for open contracts ([`b217fa4`](https://github.com/chen-star/net_alpha/commit/b217fa450b53d5587b2ff6710490c650aa264e1b))


## v0.59.0 (2026-05-11)

### Build

* build(deps): add cryptography for backup encryption ([`1368007`](https://github.com/chen-star/net_alpha/commit/13680071d88a7d4bd67b4c5af39c41dc34fb3192))

### Documentation

* docs: document the backup subsystem

Add Backup subsystem section to CLAUDE.md and AGENTS.md immediately after
the v2 Always-on service section. Also fix ruff lint/format issues across
backup source files and tests (UP017 datetime.UTC alias, I001 import order,
F401 unused imports, E501 line length).

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`2945562`](https://github.com/chen-star/net_alpha/commit/29455625ddc98547b86bf41cda31b874bd446fab))

### Feature

* feat(web): /settings/backup page with list + create-now button

Adds a backup management page at /settings/backup showing all bundles
with their timestamp, reason, size, and filename. A &#34;Create backup now&#34;
button triggers an HTMX POST to /settings/backup/create which returns
the refreshed bundle list fragment. Links to the page from the Settings
drawer About tab under the Data location section.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`f9c4bef`](https://github.com/chen-star/net_alpha/commit/f9c4bef6524f237022c8df82f371c793a9c1e11b))

* feat(service): register daily backup job at 03:30 UTC ([`e3aff14`](https://github.com/chen-star/net_alpha/commit/e3aff14cf25f3747d60a2baabd16852d729d352b))

* feat(service): daily backup job (snapshot + retention prune) ([`96d83f2`](https://github.com/chen-star/net_alpha/commit/96d83f2219f558a04503767f5ce9110b91738241))

* feat(cli): pre-migrate snapshot before v1→v2 migration ([`9e92f6b`](https://github.com/chen-star/net_alpha/commit/9e92f6b81e0f21127b6c0760e4c21aac035c1972))

* feat(cli): pre-imports-rm snapshot before deletion ([`aea0895`](https://github.com/chen-star/net_alpha/commit/aea08953011e8fcd748711c4dd7697ef3c3d027f))

* feat(cli): pre-import snapshot before CSV import commits ([`9cfc133`](https://github.com/chen-star/net_alpha/commit/9cfc133f9a593d1442cb0552b07585fe2abd83bc))

* feat(cli): register backup, restore, backups in the root Typer app ([`308d5de`](https://github.com/chen-star/net_alpha/commit/308d5ded6b87b795bf9c774f0e4c9586dc39173d))

* feat(cli): backup, restore, backups ls/rm/prune subcommands

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`9f6c929`](https://github.com/chen-star/net_alpha/commit/9f6c92939e81e2ad2e60b772855514597728c1bb))

* feat(backup): restore with schema-version + sha256 safety checks

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`e6d4e6f`](https://github.com/chen-star/net_alpha/commit/e6d4e6fa005f54cfc062cc4b7fb655f34b052baa))

* feat(backup): public seams (create_bundle, list_bundles, prune, snapshot_pre) ([`75c2b3e`](https://github.com/chen-star/net_alpha/commit/75c2b3edcd7397ce3fc1799a1fd7acf0c3c20279))

* feat(backup): create/extract tarball + SQLite-safe DB capture

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`1dd18d8`](https://github.com/chen-star/net_alpha/commit/1dd18d85b43269cf1f1e6ee7a18c2c5df148f3c8))

* feat(backup): pure-function retention with manual-is-sacred rule ([`7f7a441`](https://github.com/chen-star/net_alpha/commit/7f7a441f30c1e379ec5d6065a783b46506171cdd))

* feat(backup): AES-256-GCM envelope with scrypt KDF ([`2d60877`](https://github.com/chen-star/net_alpha/commit/2d60877cfd583cb2f50878e2756435482b1d3ff7))

* feat(backup): Manifest pydantic model with json round-trip ([`4c61993`](https://github.com/chen-star/net_alpha/commit/4c6199321170e7f11688bfe73ab6a2bd3afa948e))

* feat(backup): paths module + filename convention ([`3c09080`](https://github.com/chen-star/net_alpha/commit/3c09080b9627e22aa264a7aa1fd0c48815fdbf3a))

### Fix

* fix(backup): use SQLite online backup API for WAL safety

Replace checkpoint(PASSIVE)+shutil.copy2 with sqlite3.Connection.backup(),
which is WAL-safe and lock-friendly even under concurrent writers. Relax
the byte-identity test to a semantic-equivalence check (same rows) since
SQLite page reuse and free-page lists mean byte identity is not a reliable
invariant for logically-equivalent copies.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`5df26ca`](https://github.com/chen-star/net_alpha/commit/5df26ca898d67637f02b9ecf4504a40ff734439e))

### Refactor

* refactor(backup): code-review fixes — tar filter, scratch cleanup, restore crash-safety, web error handling, dead code

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`0143a1b`](https://github.com/chen-star/net_alpha/commit/0143a1bdf142f93e8bb476dd2a20ac3a78c4f74d))

### Unknown

* Merge pull request #5 from chen-star/feat/backup-restore

feat(backup): cross-machine backup + restore subsystem ([`ddee9ce`](https://github.com/chen-star/net_alpha/commit/ddee9ce778e41fae8aa505d1804dc01b494c69ed))


## v0.58.0 (2026-05-10)

### Feature

* feat(palette): trap Tab key + role=&#39;listbox&#39; on results

Two ARIA fixes for the ⌘K palette modal:
- The dialog declares role=&#39;dialog&#39; aria-modal=&#39;true&#39;, but Tab from the
  search input previously escaped to the underlying page. Adds
  @keydown.tab.prevent so focus stays trapped in the dialog (matching
  the keyboard semantics screen readers expect).
- &lt;ul#palette-results&gt; now carries role=&#39;listbox&#39;, completing the ARIA
  ownership chain: input.aria-controls -&gt; ul.role=&#39;listbox&#39; -&gt; li.role=&#39;option&#39;.
  Without this, the per-&lt;li&gt; role=&#39;option&#39; attributes were orphaned and
  some screen readers would not announce option navigation correctly.

Adds two render-time smoke tests so future template churn can&#39;t quietly
drop either attribute. ([`41b7a1a`](https://github.com/chen-star/net_alpha/commit/41b7a1a0edb2fbf1510d8ab4ec22fd0c939be8d4))

* feat(ui): sticky headers + sticky-left Symbol on harvest queue table

Closes a B1/B2 spec gap from the palette/table-ergonomics work — the
harvest-queue table on /tax is long-scrolling and wide-enough to scroll
horizontally, but was missed by the original sticky pass because it
wasn&#39;t in the plan&#39;s template list. Adds the standard sticky-top thead
+ sticky-left Symbol column treatment, plus an overflow-x-auto wrapper
so position: sticky has a scrolling ancestor for the horizontal axis.

Extends test_sticky_table_classes.py to require these classes on
_harvest_queue.html, matching the other six sticky-bearing tables. ([`cf829ab`](https://github.com/chen-star/net_alpha/commit/cf829ab71e9cea79a41e8d39d270ee029bdb3d69))

### Refactor

* refactor(db): atomic Repository.mark_plan_seen helper

Collapses write_plan_snapshot + set_plan_last_seen_at — previously two
separate Session/commits — into one transaction so a crash between the
snapshot write and the timestamp update cannot leave the snapshot
ahead of (or behind) plan_last_seen_at. Local single-user app makes
the crash edge case inconsequential in practice, but the atomic API
removes the conceptual landmine for future maintainers.

plan_mark_seen route now calls the new helper; old two-step methods
are kept on Repository for callers that only need one side. ([`6e8f24a`](https://github.com/chen-star/net_alpha/commit/6e8f24afc4fbff74bbce5304bee56a153c2576ea))


## v0.57.0 (2026-05-10)

### Chore

* chore(palette): tighten TypedDict shapes for palette index ([`5d51856`](https://github.com/chen-star/net_alpha/commit/5d51856f8e12b51656824f298b848a3f8732bcc7))

* chore(ui): cross-reference rail-col width; parenthesize sticky-left test

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`df273d7`](https://github.com/chen-star/net_alpha/commit/df273d70317be53f56edd23197ef53cb05535851))

### Documentation

* docs(readme): add demo-data screenshots showing the cross-account story

Captured from the bundled demo dataset (TSLA, NVDA cross-account, AAPL,
SPY puts, plus open MSFT/AMZN/GOOGL/META/AMD) so the headline pitch —
&#34;the wash sale your single broker can&#39;t see&#34; — is visible without the
reader installing anything. Layout uses a vertical stack with
`&lt;img width=&#34;900&#34;&gt;` so it renders consistently on both GitHub and PyPI
(table-cell `width%` attributes get stripped by readme_renderer&#39;s
bleach allowlist).

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`60de621`](https://github.com/chen-star/net_alpha/commit/60de6212c915f14b9d5eb1d1b1cd6a2413c742fc))

### Feature

* feat(positions): POST /positions/plan/mark-seen handler

Extract _build_plan_diff_rows helper (shared by _compute_change_states
and the new route) and add plan_mark_seen route that persists the current
Plan view snapshot and updates plan_last_seen_at, returning 204.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ef31ab7`](https://github.com/chen-star/net_alpha/commit/ef31ab70e2eae8e2b0fb816f9d989ecf3c19200e))

* feat(positions): change-state badge + &#39;Mark as seen&#39; button on Plan view

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`c20056e`](https://github.com/chen-star/net_alpha/commit/c20056e6391234ddc57348288db0abc49b8bb1c7))

* feat(positions): compute change_states for Plan view via diff_plan

Refactor _build_plan_view_for_request to return (plan_view, pos_by_sym),
update both callers, and compute change_states in _render_plan_body using
diff_plan so the template context carries per-symbol new/changed/None badges.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`698fa88`](https://github.com/chen-star/net_alpha/commit/698fa88a6340cfca32eb4ea31c537b49166c2231))

* feat(db): plan_view_snapshot read/write + plan_last_seen_at helpers

Add four Repository methods for the Plan-view &#39;what changed&#39; feature:
read_plan_snapshot, write_plan_snapshot, get_plan_last_seen_at, and
set_plan_last_seen_at, with 4 new tests covering round-trip and replace. ([`62c8877`](https://github.com/chen-star/net_alpha/commit/62c8877e68acde3f7c0914c076ad7ee265bdfa1c))

* feat(portfolio): pure diff_plan() for Plan-view &#39;what changed&#39; badges

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`34158b7`](https://github.com/chen-star/net_alpha/commit/34158b7137156fb18b35ef2ac899a8b4379ba98d))

* feat(db): add v20 migration for plan_view_snapshot table

Adds the plan_view_snapshot table (ticker, target_kind, target_value,
risk_pill, pl_bucket, snapshot_taken_at) used by the Plan-view diff
badge logic. Bumps CURRENT_SCHEMA_VERSION to 20 and updates all pinned
schema-version assertions in the test suite.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`63a2299`](https://github.com/chen-star/net_alpha/commit/63a22990846d4851de82e73fc402586cd4580b51))

* feat(palette): ⌘K overlay with client-side fuzzy match

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ea70419`](https://github.com/chen-star/net_alpha/commit/ea70419520f9d8d4fef11652e72abd9dd84bd29c))

* feat(palette): bootstrap palette-index JSON into every page

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`fa2fae1`](https://github.com/chen-star/net_alpha/commit/fa2fae1fabbfa7087d27332e471981411e43880e))

* feat(db): tickers_with_open_lots helper for palette tier

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`273cdc8`](https://github.com/chen-star/net_alpha/commit/273cdc8e75bcf27ed5a2b0b5aa6126a5cec6abaf))

* feat(palette): pure index builder (pages + tier-ranked tickers)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`6c2e651`](https://github.com/chen-star/net_alpha/commit/6c2e651454b6ea0927df0f57b264129a463ab9c9))

* feat(ui): layered sticky-left on timeline (rail + Date column)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`bbb8887`](https://github.com/chen-star/net_alpha/commit/bbb88877457cf5be3976465c21439123c21cfc2b))

* feat(ui): sticky-left first column on wide tables

Add sticky left-0 classes to the first &lt;th&gt; (corner, z-20) and first
&lt;td&gt; (bg-inherit, z-[1]) of each wide table so the anchoring column
stays visible during horizontal scroll, with a hairline shadow seam.
Extend test_sticky_table_classes.py with 4 parametrized smoke tests.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`c454cda`](https://github.com/chen-star/net_alpha/commit/c454cda534a0c3bfc9a1e96eca4f1e5f06ec8621))

* feat(ui): sticky headers on detail-table and reconciliation-diff

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`58d4c8c`](https://github.com/chen-star/net_alpha/commit/58d4c8ce7871b15320e200e21bf3db9467e2c1db))

* feat(ui): sticky table headers across long-scrolling tables

Add `sticky top-0 z-10 bg-bg` to &lt;thead&gt; in _lots_table, _ticker_view_timeline,
_ticker_view_lots (open-shorts table), and _imports_table so headers stay visible
during vertical scroll. Add regression test that asserts the sticky utility is
present on every covered template&#39;s &lt;thead&gt;.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`84dc883`](https://github.com/chen-star/net_alpha/commit/84dc883a10c2a1e77df94d254168982fc178d079))

### Fix

* fix(positions): mark-seen snapshot is always global, not account-filtered ([`8a0444b`](https://github.com/chen-star/net_alpha/commit/8a0444bf3905d1ac21316cb3257aa79b6f753325))

* fix(palette): escape &lt;script&gt; via Jinja tojson to prevent CSV-borne XSS

Replace json.dumps() + | safe with Jinja&#39;s tojson filter, which
unicode-escapes &lt;, &gt;, and &amp; — preventing a malicious ticker string
(e.g. &lt;/script&gt;&lt;script&gt;alert(1)&lt;/script&gt;) from breaking out of the
palette bootstrap &lt;script&gt; tag on any page render.

Adds an XSS regression test asserting no literal &#39;&lt;/&#39; appears in the
rendered JSON blob. ([`a5440dc`](https://github.com/chen-star/net_alpha/commit/a5440dc640fc7ec517a7d675dfa6046e3f4e5613))

### Refactor

* refactor(positions): extract _compute_change_states helper

Eliminates the 19-line change-states computation block that was
duplicated verbatim between positions_page and _render_plan_body.
The new helper returns (change_states, watch_results) as a tuple
(Option A) so callers avoid a second watch_results_by_target() query.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`7bfdad3`](https://github.com/chen-star/net_alpha/commit/7bfdad3b57b6acb2e944935b1a2a927792bfecb5))

* refactor(ticker): move pairing import to module level; tighten jump test assertions

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`23003f3`](https://github.com/chen-star/net_alpha/commit/23003f373b3aaf7d99990cd37475f05ba3f2b179))

### Test

* test(ui): tokenize &lt;thead&gt; class attribute for order-independent matching

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`9786f64`](https://github.com/chen-star/net_alpha/commit/9786f6458914f79b4385d89befe0ba3c5f81f1d5))

### Unknown

* Merge feat/palette-table-ergonomics: ⌘K palette + table ergonomics

Two paired UX improvements for the local web UI:

- ⌘K command palette: top-aligned modal, pages + tickers, client-side fuzzy
  match with held &gt; targeted &gt; traded tier ranking. Bootstrapped via inline
  JSON; no per-keystroke server roundtrip; tojson-escaped to prevent
  CSV-borne XSS.

- Table ergonomics: sticky &lt;thead&gt; across long-scrolling tables, sticky-left
  first column on wide tables (with right-edge shadow + layered offset for
  the timeline rail-col), and Plan-view &#39;what changed since last open&#39;
  badges (● new / ▲ changed) with a &#39;Mark as seen&#39; toolbar button.

Schema bumped to v20 with a plan_view_snapshot table; plan_last_seen_at
lives in the existing meta key/value table. The diff is a pure function
in portfolio/plan_diff.py with -or-5%-of-basis bucketing to suppress
P&amp;L jitter.

20 commits, all tests green (1812 passed, 1 documented flake skipped). ([`e0dc37c`](https://github.com/chen-star/net_alpha/commit/e0dc37cb1e707f7a868440cc50b156a61eceac4f))

* Merge pull request #4 from chen-star/refactor/pairing-cleanup

refactor(ticker): module-level pairing import + tighter jump test assertions ([`fcc796b`](https://github.com/chen-star/net_alpha/commit/fcc796bc775bc6a48fe30e38816c9e6770c9a6c8))


## v0.56.1 (2026-05-11)

### Chore

* chore: bump to 0.56.1 — fix README logo URL on PyPI

Relative img src `assets/logo.svg` doesn&#39;t resolve on PyPI; switch
to absolute raw-GitHub URL pinned at the release tag.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`4417d44`](https://github.com/chen-star/net_alpha/commit/4417d44e7247e801067c6304fbb1ec5054adedfc))

### Ci

* ci: gate releases on master to prevent stray-tag publishes

release.yml now refuses to publish if the tag&#39;s commit isn&#39;t an
ancestor of origin/master. version.yml&#39;s workflow_dispatch is
restricted to master so manual bumps from side branches can&#39;t
mint version tags. v2.0.0 was published from a parallel
always-on-service branch and ended up as PyPI&#39;s &#34;latest&#34; because
semver puts 2.0.0 above 0.56.0 — these guards close that path.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`f2c9540`](https://github.com/chen-star/net_alpha/commit/f2c95409178ff19f242f6c5c0a158545817a9e45))

### Documentation

* docs(readme): restructure for clarity and add current features

Reorganize the README around user intent — Why → Quickstart → Features
→ Usage → Rules → Service → Architecture — instead of leading with
service management. Bucket Features into Detection / Planning /
Reporting / Local &amp; private so readers can scan to the capability
they care about. Replace the freeform Web UI prose with a per-route
table.

Surfaces features that had landed but were missing from the README:
§1092 straddle detection (`net-alpha straddles`, QCC test, holding-
period suspension warnings), Sim-page lot-strategy comparison
(FIFO/LIFO/HIFO/MIN_TAX/MAX_LOSS), capital-loss carryforward + override
page, Action Inbox, demo-mode tour, and drag-to-reorder Plan view.
Architecture diagram extended to show the §1092 detector and the
launchd service tier.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`da52eec`](https://github.com/chen-star/net_alpha/commit/da52eecb7f779d3de156a83c11694300a74ac7f1))

### Feature

* feat(ticker): CSS for rail, pair chips, and annotation pills

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`f834b85`](https://github.com/chen-star/net_alpha/commit/f834b85807b9c0b560e973546873932b406b991b))

* feat(ticker): rail column + Pairing chips in Timeline template

Adds leftmost rail-col &lt;th&gt;/&lt;td&gt; for pair-role visual indicator, row
id=&#34;trade-{id}&#34; anchor for jump-scroll, Pairing column with pair-chip
links and pair-annot annotations, and updates empty-state colspan from 9
to 11.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`249178b`](https://github.com/chen-star/net_alpha/commit/249178b863dba339c452f58e82dfa7c9d7a262c3))

* feat(ticker): resolve ?jump=trade-{id} to the page containing the row

Adds a `jump` query parameter to the ticker drilldown route that computes
which page a target trade lives on and overrides `page` before pagination
slicing, enabling server-side click-to-partner navigation.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ca8e4b9`](https://github.com/chen-star/net_alpha/commit/ca8e4b99a1a39bbc45d8c4d3d2f9fc4c36304b64))

* feat(ticker): wire pairing into TimelineRow

Add `pair: object | None` field to TimelineRow, update _build_timeline_rows
to accept lots/open_lot_ids kwargs and run a pairing pass after sorting, and
update the ticker_drilldown call site to supply raw_lots and open_lot_ids.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`b64ad13`](https://github.com/chen-star/net_alpha/commit/b64ad13340f90e83f088044aa2a984b630be955b))

* feat(pairing): stable color index per pair_key

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`2f733ed`](https://github.com/chen-star/net_alpha/commit/2f733edaf5458092ed07c1d3143465b320670f41))

* feat(pairing): bidirectional assignment cross-reference

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`afae330`](https://github.com/chen-star/net_alpha/commit/afae330014f3d875dba8086a803ef7f31cc57ffb))

* feat(pairing): roll detection annotates new opener

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`545acbb`](https://github.com/chen-star/net_alpha/commit/545acbb122402ed2847393c4768b23903a65207d))

* feat(pairing): stock lot pairing with FIFO attribution

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`9e4239b`](https://github.com/chen-star/net_alpha/commit/9e4239b43f12947df333c619aaf35aff394587c9))

* feat(pairing): option pair-keying with open/close roles and caps

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`63905d6`](https://github.com/chen-star/net_alpha/commit/63905d6474cad123093a9ae443f28b8ed49a54ae))

* feat(pairing): scaffold pairing module with PairFields dataclass

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`7b0a97f`](https://github.com/chen-star/net_alpha/commit/7b0a97f2ba97b94ed46a5b0ea83f14f212e97745))

### Refactor

* refactor(pairing): decompose, fix lint, deterministic roll selection

- Fix 1: Auto-fixed UP035 (Iterable from collections.abc), F401 (unused
  pytest/PairFields imports); manually wrapped E501 long lines in test file.
- Fix 2: Removed dead TimelineRow alias; replaced with duck-typing comment.
- Fix 3: Added _ALL_OPT_SOURCES module constant to avoid recomputing set
  union on every _option_pair_key call.
- Fix 4: Sort roll candidates before picking candidates[0] for deterministic
  output regardless of row arrival order.
- Fix 5: Extracted compute_pair_fields passes into five private helpers
  (_apply_option_pairs, _apply_stock_lot_pairs, _apply_roll_annotations,
  _apply_assignment_links, _with_colors); main function is now ~15 lines.
- Fix 6: Added explicit basis_source=&#39;long_option_open&#39;/&#39;long_option_close&#39;
  to make_bto/make_stc builders in tests/web/conftest.py with docstring note.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`a6ee533`](https://github.com/chen-star/net_alpha/commit/a6ee533d54f5a142567ddf402605bceb78f23def))

### Style

* style(ticker): annotate lots: list[Lot]; ruff format ([`7a53128`](https://github.com/chen-star/net_alpha/commit/7a531289d4e499ea697e8c9c0c55169c04cfa9d8))

### Test

* test(ticker): smoke-render rail + pairing chips for STO/BTC and still-open lot

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`9554a64`](https://github.com/chen-star/net_alpha/commit/9554a645171e9808b1ff366553ed5e610abf6070))

* test(pairing): synthetic expiry row joins parent pair

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`a073da4`](https://github.com/chen-star/net_alpha/commit/a073da4582e16d315d94473b357a953063c82f80))

* test(pairing): cover scale-in close with three-row pair

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`bb8712f`](https://github.com/chen-star/net_alpha/commit/bb8712f43636c35e317bff5b6b37abfdd393a58f))

* test(ticker): add option trade builders for pairing tests

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`1e0d766`](https://github.com/chen-star/net_alpha/commit/1e0d766cb0388428f8c2f8a890ff1c0dc23ef0b6))

### Unknown

* Merge ci/release-master-guard: pairing feature + 0.56.1 release ([`cc3d5c4`](https://github.com/chen-star/net_alpha/commit/cc3d5c4456d676583aa51efde1cff350def5952f))

* Merge pull request #3 from chen-star/ci/release-master-guard

ci: gate releases on master to prevent stray-tag publishes ([`65c9d5b`](https://github.com/chen-star/net_alpha/commit/65c9d5b89d748dd1c8d88c65b6a76fc971e9e9d4))


## v0.56.0 (2026-05-10)

### Documentation

* docs(1092): document straddle detection in README and CHANGELOG ([`a54c7cb`](https://github.com/chen-star/net_alpha/commit/a54c7cb41cee0e922b4dbf68a4b18ec50d4c41dd))

### Feature

* feat(1092): surface §1092 straddle warning in sim output ([`d503473`](https://github.com/chen-star/net_alpha/commit/d5034736b8fdaba666db582cb27f1d87df7b0883))

* feat(1092): add &#39;net-alpha straddles&#39; CLI command ([`0aee643`](https://github.com/chen-star/net_alpha/commit/0aee643e05fd1097a1a2ff90b9cf47c811a50ed0))

* feat(1092): plain-text renderer for straddles command ([`639dbc4`](https://github.com/chen-star/net_alpha/commit/639dbc42fb7259ccbd6366bbea9668a57651cedb))

* feat(1092): compute holding-period suspension warnings (§1092(f)) ([`4742c93`](https://github.com/chen-star/net_alpha/commit/4742c932fb028ee3b887e6e857ab97a3b7fbf3ee))

* feat(1092): detect vertical spreads (same expiry, opposite legs) ([`b1ff3b5`](https://github.com/chen-star/net_alpha/commit/b1ff3b58e9544bb732b025fdf6ebf9772f0ab10b))

* feat(1092): detect non-qualified covered calls (QCC-gated) ([`39f5472`](https://github.com/chen-star/net_alpha/commit/39f5472d02492ba1cd2e4ac36cd75f4a9282bd28))

* feat(1092): detect married put (long stock + long put) ([`24789c2`](https://github.com/chen-star/net_alpha/commit/24789c27beff03322b380562e73946775022f12f))

* feat(1092): detect literal straddle (long call + long put) ([`ad57e40`](https://github.com/chen-star/net_alpha/commit/ad57e40338cb65542e27f256b855673d207b1977))

* feat(1092): add qualified covered call test ([`99722c8`](https://github.com/chen-star/net_alpha/commit/99722c886615e05b3c536b62202062fbd044a4ee))

* feat(1092): add OffsettingPosition, OffsettingGroup, HoldingPeriodSuspension domain models ([`74d227c`](https://github.com/chen-star/net_alpha/commit/74d227c721409326872af4c90e83019924174fbf))

### Fix

* fix(1092): wire QCC test to live PriceCache via shared helper

The CLI surfaces (`straddles`, `sim`) called a non-existent `PriceCache.snapshot()`
method, which was silently swallowed by a bare `except Exception` and caused every
covered-call group to fall through to the &#34;QCC test skipped&#34; branch. Replaced with
a shared `cached_underlying_prices(repo, tickers)` helper that uses the actual
`PriceCache.get_many()` API. Manual smoke confirms HOOD&#39;s covered call (which passes
QCC) is now correctly suppressed instead of emitting a false-positive warning.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`baadb4d`](https://github.com/chen-star/net_alpha/commit/baadb4dbfd2eb29e250be4e0addb56b6c3ce4e6e))

### Style

* style(1092): ruff format detector.py for CI

Two helper signatures collapsed to single-line by ruff format under the
project&#39;s 120-char line-length. CI&#39;s `ruff format --check .` flagged them.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`9f4572a`](https://github.com/chen-star/net_alpha/commit/9f4572a22be6c84e5bbc5b9866b30ef2acdae3f9))

### Unknown

* Merge pull request #2 from chen-star/fix/ui-three-issues

feat: add §1092 straddle detection v1 ([`dff0503`](https://github.com/chen-star/net_alpha/commit/dff0503eadba6cb0a232bcec88e5a011247fdf5c))

* Merge remote-tracking branch &#39;origin/master&#39; into fix/ui-three-issues

# Conflicts:
#	CHANGELOG.md ([`94e867f`](https://github.com/chen-star/net_alpha/commit/94e867f1c632a924c2d3973e8cbb9bc185f0c8b7))

* ui(web): style topbar service status pill

The .status-pill / .pill--* classes never had CSS rules, so the
topbar fell through to default link styling and rendered &#34;Running&#34;
as oversized white text next to the profile chip. Add a proper
compact pill: surface chrome, hairline border, 7px state dot with
a colored glow per state (running=green, paused/stale=warn,
stopped=red, unknown=label-3).

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`d1a6cee`](https://github.com/chen-star/net_alpha/commit/d1a6ceeb37588f4d4f98de0b8db98e3f48fd659a))


## v0.55.5 (2026-05-10)

### Fix

* fix(web): rebuild demo.db on tour replay to avoid 500

Replay flipped demo_mode=True without ensuring the demo SQLite
existed; an earlier 0-byte demo.db left behind by an aborted open
then tripped profile_switcher_data() with `no such table: accounts`.
Add ensure_demo_db() to self-heal missing/empty/schema-incomplete
files and call it from both /tour/replay and /welcome/start-tour.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`2f98ff8`](https://github.com/chen-star/net_alpha/commit/2f98ff85caadbcc041e30f9f522e37211a1225d0))

### Style

* style: ruff format ensure_demo_db

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`b365730`](https://github.com/chen-star/net_alpha/commit/b36573083e25d4458f4a085d5162fb211c8036d1))

* style: ruff check + format fixes for CI

- fragment_cache.py: import Hashable from collections.abc (UP035)
- app.py: reorder import block (I001)
- pricing/service.py, routes/portfolio.py: collapse multi-line calls

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`2f26bf0`](https://github.com/chen-star/net_alpha/commit/2f26bf0d344352061378a71f387c4da2a4c10060))

### Unknown

* Merge pull request #1 from chen-star/fix/ui-three-issues

Fix/UI three issues ([`0207b1b`](https://github.com/chen-star/net_alpha/commit/0207b1b1e97241a2644f6d568eb034b0371cc18a))

* ui(web): redesign /settings/service with lifecycle controls

Replaced the unstyled &lt;dl&gt;/&lt;table&gt; markup with a proper panel
layout using the project&#39;s design tokens (.panel, .btn, .btn-ghost,
status pill). Added state-aware lifecycle buttons — Install when
absent; Start/Stop/Restart and Pause/Resume jobs once installed;
Uninstall with a confirmation prompt. Errors from control verbs
surface inline via ?err=… so failed installs (missing uv) and
restart-while-stopped no longer return raw 4xx pages. The runs
table now uses semantic status badges instead of bare CSS classes
that had no styles defined.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`42048cc`](https://github.com/chen-star/net_alpha/commit/42048cc9a58effb2804d9fef474546901dd201fb))

* ui(web): collapse Action Inbox by default

Auto-expanding when urgent items existed surprised users on every
page load. Header counts (urgent/watch chips) still surface what&#39;s
inside, so collapsed-by-default keeps the affordance without the
visual weight.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`ff95122`](https://github.com/chen-star/net_alpha/commit/ff951225aec7ac6326f0d5d60e4591f46ad74e22))

* ui(positions): clarify harvest auto-budget label as &#34;gross gains&#34;

The at-loss tab loads the harvest plan, which labeled the auto-budget
pool as &#34;$X realized gains + $3,000 ordinary&#34;. That read like net
realized P/L and conflicted with the overview KPI (which is net,
GL-paired, wash-adjusted). Relabel as &#34;YTD gross gains&#34; with a tooltip.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`8b480fa`](https://github.com/chen-star/net_alpha/commit/8b480fa44d9635be9cf7faf419e55c1170703673))


## v0.55.4 (2026-05-10)

### Performance

* perf(web): cache portfolio_body compute + pin today&#39;s close

Every reload of /portfolio/body re-ran the full compute fan-out (KPIs,
equity-curve, allocation, top movers, projections, inbox) and re-fetched
today&#39;s price from Yahoo for every ticker. Today&#39;s close was never
cached because the warm path only negative-caches *bracketed* gaps and
today is always trailing.

- pricing: pin today&#39;s live quote into historical_price_cache so the
  equity-curve warm path stops re-fetching the trailing edge each reload.
  End-of-day price refreshes overwrite with the authoritative close.
- web: add FragmentCache (60s TTL) keyed on (path, params, revision).
  portfolio_body memoizes its compute context; the template still renders
  per-request so request-scoped Jinja globals work.
- web: bump fragment_revision on every write path — trades CRUD, imports
  upload/delete/bulk-delete, accounts, preferences, splits/sync,
  prices/refresh — so user-visible changes appear on the next reload.
- web: DEBUG-level timing log in portfolio_body to make the cache_hit
  state observable.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`2b21c07`](https://github.com/chen-star/net_alpha/commit/2b21c07748507869fcf011ac1105d2111babf711))

### Style

* style: ruff format pnl.py

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`fe0e5e8`](https://github.com/chen-star/net_alpha/commit/fe0e5e8d3ce113335314ef2d4f5173f9da7bf1d3))


## v0.55.3 (2026-05-09)

### Fix

* fix: realized P/L provenance ties out + Linux CI launchctl patch

The provenance modal&#39;s headline total was computed via
realized_pl_from_trades (which sums Sells, BTC option closes, and broker
GL lots), but the contributing-trades list only iterated raw Sell rows
with gross proceeds in `amount`. With GL lots or BTC closures in scope,
rows didn&#39;t sum to the headline. Add realized_pl_contributions() so the
total and breakdown share one decomposition; per-row amount is now signed
realized P/L.

Also patch test_install_port_embedded_in_artifacts to mock
_launchctl_bootout (called via _launchctl_reload), which was failing on
Linux CI with FileNotFoundError: &#39;launchctl&#39;.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`c187f49`](https://github.com/chen-star/net_alpha/commit/c187f493b0d514da6145cc8c0c30dd5fd8b8c9c6))


## v0.55.2 (2026-05-09)

### Chore

* chore: sync GitNexus index counts in AGENTS.md/CLAUDE.md

Auto-managed gitnexus blocks were out of date with the current index
(symbols/relationships/flows numbers re-counted, plus debugging and
refactoring playbook sections that the latest gitnexus template emits).

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`c4c6d21`](https://github.com/chen-star/net_alpha/commit/c4c6d21b18b6fbcc1ca63e0a7b388662a46b696d))

### Fix

* fix(service): unblock listen() + multiprocessing under launchd sandbox

Three more bugs surfaced after the TCC-clear venv landed; the service
process started but never accepted connections. Root causes:

1. macOS 26 (Darwin 25) gates TCP `listen()` behind `network-inbound`,
   separately from `network-bind`. With only the bind allow, uvicorn
   would log &#34;Uvicorn running on http://127.0.0.1:8765&#34; but the listen
   syscall was silently denied — no listener, no connection. Added
   `(allow network-inbound (local ip &#34;localhost:PORT&#34;))` alongside the
   existing bind rule.

2. loguru&#39;s enqueue=True path uses `multiprocessing.SimpleQueue`, which
   creates a POSIX named semaphore via `sem_open()`. Sandbox-exec gates
   that under `ipc-posix-sem` — not covered by the existing
   `ipc-posix-shm` rule. Added `(allow ipc-posix-sem)`.

3. `_provision_service_venv` installed wash-alpha without extras, so
   `yfinance`, `fastapi`, `uvicorn`, `apscheduler`, `jinja2`, and
   `python-multipart` (all in the [ui] optional-deps group) were
   missing — the service crashed during import. Switched the install
   target to `&lt;project&gt;[ui]`.

Plus two install-flow gaps: `uv venv` rejects an existing target so
re-running install failed with `A virtual environment already exists`
— added `--clear`. Dropped `capture_output=True` on the uv subprocess
calls so the actual stderr is visible to the user instead of being
buried in CalledProcessError.

Tests cover all of: posix-sem rule, network-inbound rule with the
pinned port, [ui] extras in the install command, and `--clear` on the
venv command.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`a519b24`](https://github.com/chen-star/net_alpha/commit/a519b249431ebafd6f318b7759c279d3c30b8e9f))


## v0.55.1 (2026-05-09)

### Fix

* fix(service): unblock launchd install — TCC-clear venv + idempotent reload

Three bugs prevented `net-alpha service install` / `start` from
producing a running service:

1. sandbox-exec rejected `(local ip &#34;127.0.0.1:PORT&#34;)` — the host must
   be `*` or `localhost`. The wrapped Python crashed with EX_DATAERR on
   every launchd respawn. Switched the rule to `localhost:PORT`.

2. `install`/`start` called `launchctl bootstrap` directly, which exits
   5 if the agent is already loaded. Added `_launchctl_reload()` (bootout
   + bootstrap) and routed `install`, `start`, and `restart` through it,
   so re-running any of them is safe.

3. The wrapper pointed at `~/Documents/project/net_alpha/.venv/bin/net-alpha`,
   but launchd-spawned processes have a TCC identity that cannot read
   `~/Documents/`. Added `_provision_service_venv()` which uses `uv` to
   create a dedicated runtime venv at `~/.net_alpha/venv/` and installs
   wash-alpha from the local project source. The runtime now lives
   entirely inside the sandbox profile&#39;s writable subpath.

`uninstall` now also removes the runtime venv. Project data at
`~/.net_alpha/net_alpha.db` is still preserved.

README service-management section updated to document the new
`~/.net_alpha/` layout, idempotency, and the non-editable-snapshot
re-install requirement after code changes.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`6a6646b`](https://github.com/chen-star/net_alpha/commit/6a6646b244d205b56ba45622f61b39683f98924d))


## v0.55.0 (2026-05-09)

### Feature

* feat(web): import preview modal renders robinhood branch

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`16edd88`](https://github.com/chen-star/net_alpha/commit/16edd88340c01bc7930bffcc19c68ebd880f557f))

* feat(cli): default-run route routes per-broker via parser.name

- supported-parsers error message includes robinhood
- per-(broker, label) cache pattern mirrors web upload route
- duck-typed dispatch on hasattr(parser, &#39;parse_full&#39;)
- post-loop stitch iterates touched accounts
- SchwabRealizedGLParser maps to &#39;schwab&#39; broker key (shares the Schwab account)
- removed now-unused SchwabParser import

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`1e43599`](https://github.com/chen-star/net_alpha/commit/1e435992fccd89557cd9c3ff1b4387f6420a77bf))

* feat(web): upload route routes per-broker via parser.name

- preview_upload + upload now duck-type on hasattr(parser, &#39;parse_full&#39;)
- Account creation moves inside the per-file loop with a per-(broker, label) cache
- Mixed-broker batches resolve to distinct schwab/&lt;label&gt; + robinhood/&lt;label&gt; accounts
- SchwabRealizedGLParser maps to the &#39;schwab&#39; broker key (GL lots share the same account)
- Post-loop stitch iterates over all touched accounts
- Remove now-unused SchwabParser import

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`86ecf70`](https://github.com/chen-star/net_alpha/commit/86ecf70361a306628434f7ee794242dde3778f23))

* feat(robinhood): skip crypto + warn on unknown trans codes

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`689f05d`](https://github.com/chen-star/net_alpha/commit/689f05de1bceec26d29a396bc7e5e9f19424fe69))

* feat(robinhood): REC share-transfer rows with basis_unknown

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`f50a640`](https://github.com/chen-star/net_alpha/commit/f50a640aca0ca42e216b36a1a3e03f03898a02e4))

* feat(robinhood): cash events (DIV/INT/fees/transfers)

Mirrors SchwabParser cash-event mapping. Sign-source semantics:
- always_positive: dividends, interest
- always_negative: GOLD/MFEE/MINT/DTAX/DFEE → fee
- amount-signed: ACH/ACATS → transfer_in/transfer_out

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`7b04b53`](https://github.com/chen-star/net_alpha/commit/7b04b533fdedf9e7bda884cd7ccaaba1718c6442))

* feat(robinhood): SPL split rows emit warning, no trade

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`84d834f`](https://github.com/chen-star/net_alpha/commit/84d834f707b2aa7a559bf8035bc5e82ff48d593e))

* feat(robinhood): short-put assignment folds premium into underlying basis

Mirrors SchwabParser put-assignment basis-offset logic per IRS Pub 550.
Path A: Robinhood emits both an OASGN option row and a separate underlying
Buy row on assignment date — the Buy&#39;s cost basis is reduced by the prior
STO premium.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`494cdbd`](https://github.com/chen-star/net_alpha/commit/494cdbd08d87cc1df0a0d42ce380df2dc6203dae))

* feat(robinhood): same-day duplicate fills get distinct occurrence_index

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`f75312d`](https://github.com/chen-star/net_alpha/commit/f75312d793a84a540a2164bb0738aa631bae003b))

* feat(robinhood): OEXP option-expiration silent skip

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`7db2f50`](https://github.com/chen-star/net_alpha/commit/7db2f505b74ff5699f159037f33539dbfe0cf2b5))

* feat(robinhood): short-option open/close (STO/BTC)

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`ec6d2db`](https://github.com/chen-star/net_alpha/commit/ec6d2db8e5f36d99c1a1c39ba93f860966bd5f77))

* feat(robinhood): long-option open/close (BTO/STC)

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`cdbc39e`](https://github.com/chen-star/net_alpha/commit/cdbc39ee5becefe403e0f1041fefcc4f1c56431f))

* feat(robinhood): equity buy/sell parsing

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`5d9a210`](https://github.com/chen-star/net_alpha/commit/5d9a210669a6f9aea93854e4e8ed69525de4871a))

* feat(robinhood): add parser scaffold + register in detection

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`dc692f2`](https://github.com/chen-star/net_alpha/commit/dc692f24ddf16fa5c7d7efaf8095a70da048c70c))

### Style

* style: ruff format Robinhood-related sources and tests

Cosmetic-only — dict/kwargs alignment per ruff format. No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`76fd349`](https://github.com/chen-star/net_alpha/commit/76fd349f6346f6a8c679c80804ab51df63de009b))

### Test

* test(integration): cross-broker wash sale (Schwab loss → Robinhood rebuy)

Confirms the ingest plumbing handles cross-broker scenarios end-to-end.
The wash-sale engine has been cross-broker since v1; this test guards the
ingest seam (parse → add_import → recompute) on a realistic scenario.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`996a81d`](https://github.com/chen-star/net_alpha/commit/996a81d1820db8327172dacfc4acca43b2f3438e))

* test(robinhood): close Phase B coverage gaps

- Parametrize over all dividend aliases (DIV/DIVNRA/CDIV)
- Parametrize over all fee aliases (GOLD/MFEE/MINT/DTAX/DFEE)
- Cover invalid-date cash event warning path
- Cover multi-contract OASGN partial-close (net premium calculation)

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`a170a16`](https://github.com/chen-star/net_alpha/commit/a170a16db4233f4288fe2afe236971804b5e6246))

* test(robinhood): assert STC leaves basis_source unchanged

Mirrors the BTO test at line 77 — both long-option codes should leave
basis_source at the model default of &#34;unknown&#34;.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`a18ef00`](https://github.com/chen-star/net_alpha/commit/a18ef002db472354589117ffc33c7f4f3a476acc))

### Unknown

* Merge branch &#39;worktree-feat-robinhood-broker&#39; — Robinhood broker support

Adds Robinhood as the second supported broker. Parity with Schwab on trades
+ cash events. Reconciliation is out of scope (Robinhood does not export a
Realized G/L CSV).

Highlights
- New parser: src/net_alpha/brokers/robinhood.py (~330 LOC)
- Trans-code coverage: Buy / Sell / BTO / STC / STO / BTC / OEXP / OASGN /
  REC / SPL / DIV / DIVNRA / CDIV / INT / GOLD / MFEE / MINT / DTAX / DFEE /
  ACH / ACATS, plus crypto skip and unknown-trans-code warnings.
- §5.2 OASGN short-put assignment folds STO premium into the underlying-stock
  Buy basis (IRS Pub 550). Path A: Robinhood emits a separate underlying
  Buy on the assignment date.
- Web upload route + CLI default route switch from hard-coded &#34;schwab&#34;
  broker key to per-(broker, label) account cache. Mixed-broker batches
  now resolve to distinct schwab/&lt;label&gt; + robinhood/&lt;label&gt; accounts.
- Dispatch sites use hasattr(parser, &#34;parse_full&#34;) instead of
  isinstance(parser, SchwabParser); SchwabRealizedGLParser keeps its
  existing branch and is mapped to the &#34;schwab&#34; broker key (the GL CSV
  shares the Schwab account).
- Import preview modal renders a Robinhood branch.
- Cross-broker integration smoke: Schwab loss + Robinhood rebuy within
  30 days triggers a wash-sale violation end-to-end.

Hard constraint preserved: zero edits to existing Schwab files
(brokers/schwab.py, brokers/schwab_realized_gl.py,
audit/brokers/schwab.py, and all Schwab test files).

Deferred to a follow-up PR (awaiting real Robinhood CSV from the user):
Task 1 (discovery + lock canonical Trans Code list), Task 13 (real-fixture
golden test), Task 18 (placeholder fixture cleanup).

Branch: 18 commits.
Tests: 1696 passed, 1 skipped (baseline + 49 new).

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`505558c`](https://github.com/chen-star/net_alpha/commit/505558cd4a1f4aafb0627fbeb06a2deb59f2cdeb))


## v0.54.1 (2026-05-09)

### Fix

* fix(lint): drop unused WatchResult import + ruff format pass ([`b266360`](https://github.com/chen-star/net_alpha/commit/b266360b5b55c65f5003b19000336981b32056ea))

### Unknown

* Merge remote-tracking branch &#39;origin/master&#39;

# Conflicts:
#	pyproject.toml ([`acf50f3`](https://github.com/chen-star/net_alpha/commit/acf50f3790549508a943248911635b6c8165a049))

* Merge branch &#39;worktree-feat-v2-always-on-service&#39; — v2.0.0 always-on service ([`41c5fa2`](https://github.com/chen-star/net_alpha/commit/41c5fa216334a776164773c530c40ba7cbc29594))


## v2.0.0 (2026-05-09)

### Chore

* chore(deps): add apscheduler to [ui] extra ([`030ac4c`](https://github.com/chen-star/net_alpha/commit/030ac4cb09520dc2605f19ff47b821114b8500d2))

### Documentation

* docs(v2): document always-on service install + management

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`0f7e48a`](https://github.com/chen-star/net_alpha/commit/0f7e48a23c77f40a0321c600932429be09777a00))

### Feature

* feat(service): rotate log at 10 MB / 7-day retention

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`11cabbb`](https://github.com/chen-star/net_alpha/commit/11cabbb3c2899f4fe668e97115c553102d062587))

* feat(service): control.logs (tail + follow)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`2217cad`](https://github.com/chen-star/net_alpha/commit/2217cad06fa017bbd04b482013c8171855becca1))

* feat(service): install requires uv on PATH; clear error if missing

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`a02f80b`](https://github.com/chen-star/net_alpha/commit/a02f80b6ca8986ca60667b49597fb45cf32634e6))

* feat(onboarding): install-offer screen when no service is detected

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`8f1b29e`](https://github.com/chen-star/net_alpha/commit/8f1b29e6258e7c63b6ceadbd702886e6cc853c04))

* feat(web): plan-row watch pill (clean / wash-sale / IRA trap with severity)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`083ccd6`](https://github.com/chen-star/net_alpha/commit/083ccd6227cc0c44b9c1b3f56e9a65c3525a9de5))

* feat(imports): enqueue one-shot washsale_watch after each commit

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`23b5ccc`](https://github.com/chen-star/net_alpha/commit/23b5ccc290eb87293cb346a12c370256b494b50e))

* feat(scheduler): register washsale_watch daily at 04:00 ([`ea3718e`](https://github.com/chen-star/net_alpha/commit/ea3718e70448d48d7634c2ba60a193f001a05761))

* feat(jobs): washsale_watch iterates targets and upserts watch_result ([`8f97bfb`](https://github.com/chen-star/net_alpha/commit/8f97bfb2ed681221c4fa1a72d68c034a81e73bf7))

* feat(engine): forward-looking washsale_watch with §1091 IRA-trap detection

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ae3d3e8`](https://github.com/chen-star/net_alpha/commit/ae3d3e845546fae0bbbff2d68e36cf09931d4e96))

* feat(web): /settings/accounts editor (taxable / IRA / Roth / 401k / HSA / other)

Add /settings/accounts GET+POST route backed by repo.set_account_type;
renders a fragment with per-account type dropdowns that submit via HTMX.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`7d13ae0`](https://github.com/chen-star/net_alpha/commit/7d13ae01d32257e3cea2d90654e5ff9a631ab83c))

* feat(repo): list/get/set account types

Add get_account_type/set_account_type methods to Repository; update
list_accounts() to include the type field from AccountRow.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`9a6a4d8`](https://github.com/chen-star/net_alpha/commit/9a6a4d8a93de2eab479872720ba65439fd121bdc))

* feat(models): AccountType enum (with is_tax_advantaged) + Account ([`cce6bf6`](https://github.com/chen-star/net_alpha/commit/cce6bf6ea8d7836673c5b0439f83848c1e1bea00))

* feat(service): CLI pause/resume reach running process via HTTP

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`dc4dbb9`](https://github.com/chen-star/net_alpha/commit/dc4dbb9f561ade8fab92f5650713502ab05a255e))

* feat(web): pause/resume/restart/stop control POST endpoint

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`36cd36c`](https://github.com/chen-star/net_alpha/commit/36cd36c7884153da84a7f503160cadd5e275b969))

* feat(web): site-header status pill (HTMX fragment, polls every 30s)

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`096b0af`](https://github.com/chen-star/net_alpha/commit/096b0af6c6c183d898d4d3e43bc2ac33f6884141))

* feat(web): /settings/service health page

Add /settings/service route with status panel (state, PID, schedules)
and /settings/service/runs HTMX fragment backed by repo.list_service_runs.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`68a4841`](https://github.com/chen-star/net_alpha/commit/68a484113cc2761dc235ea02675393fb4e22ebb2))

* feat(service): service run starts FastAPI + scheduler under uvicorn

Wire AsyncIOScheduler into the create_app() factory lifespan; add
`net-alpha service run` hidden subcommand that acquires the pid lock
and boots uvicorn via the factory entry-point.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`488bc15`](https://github.com/chen-star/net_alpha/commit/488bc15b8e988d972ce6aeaf37a76721f657fbb7))

* feat(service): APScheduler bootstrap with price_refresh every 4h ([`5bc103c`](https://github.com/chen-star/net_alpha/commit/5bc103c5d28aa24b2a4a1162be9e49aa3393346d))

* feat(jobs): price_refresh collects ticker universe and calls PricingService

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`9afc607`](https://github.com/chen-star/net_alpha/commit/9afc60778370244bb142ed8696f50f42870c5da2))

* feat(jobs): runner wraps job calls in audit + uniform error handling ([`8d3b1a9`](https://github.com/chen-star/net_alpha/commit/8d3b1a908e70088a17fabbbda712363fef43b2a3))

* feat(service): in-memory ServiceState ([`0bdb7a2`](https://github.com/chen-star/net_alpha/commit/0bdb7a298987c539519d972d4ed58f8045d62e40))

* feat(repo): record_service_run + list_service_runs

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`c6493e9`](https://github.com/chen-star/net_alpha/commit/c6493e9c3a456035e58b2343294abe073913859a))

* feat(db): SQLModel rows for service_run, washsale_watch_result ([`624f610`](https://github.com/chen-star/net_alpha/commit/624f610d01c5864b899a0767e79d3458585e3977))

* feat(db): migration v19 adds accounts, service_run, washsale_watch_result tables

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`524fec8`](https://github.com/chen-star/net_alpha/commit/524fec8a8c2380ca8c52445cf788b96df6e0c3b6))

* feat(cli): net-alpha service subcommand group wired to control module

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`193908e`](https://github.com/chen-star/net_alpha/commit/193908e69f64b1d8c3a5fb296afcc767434f0f15))

* feat(service): control.status combines plist + launchctl + disabled flag ([`8370442`](https://github.com/chen-star/net_alpha/commit/837044283d137b67dcfdad9e1e472b99f6599c56))

* feat(service): control.restart guards against stopped state ([`f560f5e`](https://github.com/chen-star/net_alpha/commit/f560f5eed36c0f003a0ff7cf5bd256a47a7af9f7))

* feat(service): control.stop sets disabled flag + bootouts launchd ([`f88e456`](https://github.com/chen-star/net_alpha/commit/f88e456bf5ff3d0a8a1f2ca106b0a035bd7ace44))

* feat(service): control.start clears disabled flag and bootstraps ([`f9e8d39`](https://github.com/chen-star/net_alpha/commit/f9e8d397c020389c07769a40ef312cb0661386f6))

* feat(service): control.uninstall removes plist+wrapper+sandbox, leaves DB ([`ae14563`](https://github.com/chen-star/net_alpha/commit/ae14563bc5ce25d084f5d8b31f78be981953687e))

* feat(service): control.install renders plist+wrapper+sandbox and bootstraps launchd

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`96eb7d7`](https://github.com/chen-star/net_alpha/commit/96eb7d7822a78247e05d1cf1cc4389efdafa47b1))

* feat(service): sandbox-exec profile renderer ([`3b26775`](https://github.com/chen-star/net_alpha/commit/3b26775309b91cee6583a8fb7d148144d124995f))

* feat(service): launchd wrapper script renderer ([`a546399`](https://github.com/chen-star/net_alpha/commit/a546399121a9e1fb5b622ff7aa4448e779003728))

* feat(service): disabled-flag latch (the kill switch) ([`de18df5`](https://github.com/chen-star/net_alpha/commit/de18df55a81a56b5f725481c2ab76848da4d9a98))

* feat(service): launchd plist renderer ([`16b8c4e`](https://github.com/chen-star/net_alpha/commit/16b8c4e63281d4105702842c15d3b88fd8ad7a73))

* feat(service): single-instance pid lock ([`4bd32f6`](https://github.com/chen-star/net_alpha/commit/4bd32f609bd53816cc80b58785440af6ddce6a57))

* feat(service): canonical filesystem paths module ([`f865fb8`](https://github.com/chen-star/net_alpha/commit/f865fb801386e13bce54b15c7d326453437bef02))

### Fix

* fix(db): v19 ALTERs existing accounts table instead of CREATEing a new one

The first v19 attempt (524fec8) tried to CREATE TABLE accounts with a
fresh (label PK, type, created_at) schema, but the codebase already has
an AccountRow SQLModel with (id PK, broker, label, UNIQUE(broker,label))
that ~15 call sites depend on. This rev adds the v2 type/created_at
columns to the existing table via ALTER, and extends AccountRow to
expose them. ([`2ecd1a9`](https://github.com/chen-star/net_alpha/commit/2ecd1a975a24a104c47a17a82a4a6d4f2d9e2938))

* fix(deps): actually add apscheduler line to pyproject.toml [ui] extras ([`9f7784d`](https://github.com/chen-star/net_alpha/commit/9f7784d061d0e4c7694586194e911ca5685e1d80))

### Style

* style(service): add render() docstring per code review ([`4327eb6`](https://github.com/chen-star/net_alpha/commit/4327eb6a7fcedcc0026c9f2605028d7e0a219df1))

### Test

* test(integration): full v2 service smoke (both jobs, both record) ([`dc109c0`](https://github.com/chen-star/net_alpha/commit/dc109c06f1fff3fa0b74c1321c22c4ad17d336d7))

* test(integration): v19 migration preserves accounts with taxable default

Phase 3 integration test verifying that v19 migration adds type and created_at
columns to existing accounts table. Test simulates upgrading from v18 to v19:
- Creates a v18-schema DB with legacy account rows
- Runs migrate() to apply v19 changes
- Verifies type=&#39;taxable&#39; is set and created_at is backfilled
- Verifies migration is idempotent

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`be668b0`](https://github.com/chen-star/net_alpha/commit/be668b0c198af152131fa2de6e9709068ef04aae))

* test(integration): phase-2 scheduler+price-refresh smoke ([`82cde59`](https://github.com/chen-star/net_alpha/commit/82cde59360e1170237dd989443a1dc217ee5bed9))

* test(integration): phase-1 service lifecycle artifacts ([`74cba82`](https://github.com/chen-star/net_alpha/commit/74cba8280d9da808aaaa8297942a0c3da70071fb))


## v0.54.0 (2026-05-08)

### Documentation

* docs(readme): drop portfolio screenshots

Removes assets/screenshot-portfolio.png and assets/screenshot-tax-wash.png
along with their README references — they showed real account values.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`4479c58`](https://github.com/chen-star/net_alpha/commit/4479c588730afcf00d0dec877515833d31e586f5))

### Feature

* feat(cli): --demo flag on net-alpha ui builds demo.db and opens tour ([`550d585`](https://github.com/chen-star/net_alpha/commit/550d585b5480e297202d66804e36dfa4242e680b))

* feat(imports): preview parsed-trade sample + post-commit success page

Task 11: /imports/preview now parses the uploaded Schwab transaction file
and surfaces the first 5 trades in a small table inside the import modal,
so users see exactly what they&#39;re about to commit (date, action, ticker,
qty, amount) along with the total parsed count.

Task 12: POST /imports redirects to a new /imports/success?id=N page
that shows the trade count, wash-sale count + disallowed-loss total,
and clear CTAs to /tax and /. The previous post-commit destination
(/settings/imports) is kept as a fallback when no import_id is recorded.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`b5d4954`](https://github.com/chen-star/net_alpha/commit/b5d4954d389cf1e00bf9558371da034605cc1825))

* feat(ui): empty-state hero on positions, tax, imports pages

Replaces the legacy empty-state copy on /positions tabs, /tax, and the
imports table fragment with calls to the shared `_empty_state_hero.html`
partial (Tasks 8/9). /imports keeps no CTAs because the drop zone above
is the action; /positions and /tax both surface &#34;Take the tour&#34; + &#34;Import
CSV&#34;. Stale tab-content tests that asserted on rendered tax content with
an empty DB now seed a no-violation import so the page-level hero does
not suppress the body they assert on.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`77ddc1e`](https://github.com/chen-star/net_alpha/commit/77ddc1ee077936a575d999f34a630cbcc4e02583))

* feat(ui): shared empty-state hero partial; apply to portfolio

Adds `_empty_state_hero.html` — a parameterized partial that takes
title, body, icon, and up to two CTAs (each suppressed if its label
is empty). Matches the project&#39;s existing idiom: `.panel`, `.btn` /
`.btn-ghost`, vendored SVG icons under `/static/icons/` with the
`.icon-mono` filter (no Lucide JS dependency).

Replaces `_portfolio_empty_state.html` with a `{% with %}` block that
includes the new partial. Copy: &#34;No portfolio yet&#34; / &#34;Import a Schwab
CSV or take a 30-second tour with sample data.&#34; Primary CTA → /welcome
(&#34;Take a 30s tour&#34;), secondary → /imports (&#34;Import CSV&#34;).

Tasks 10+ will propagate the partial to /positions, /tax, /imports.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`e6a1e43`](https://github.com/chen-star/net_alpha/commit/e6a1e4323c469d26606d44960b198392c76a7f90))

* feat(tour): dismiss, replay, exit-to-real state mutations

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`0762ff4`](https://github.com/chen-star/net_alpha/commit/0762ff41124bbc51af90e62ba0e05f77466a8869))

* feat(tour): sticky tour banner rendered when ?tour=N in URL ([`bb29d27`](https://github.com/chen-star/net_alpha/commit/bb29d27e5e4a3aac1e8054929e2f45a45c9e2cd2))

* feat(welcome): splash route with start-tour and start-import handlers

Adds GET /welcome (renders splash; 303 -&gt; / when real imports already
exist; ?replay=1 always renders) and POST /welcome/start-tour (builds
demo.db on first call, flips app.state.demo_mode, 303 -&gt; /?tour=1) and
POST /welcome/start-import (303 -&gt; /imports?wizard=1).

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`af25325`](https://github.com/chen-star/net_alpha/commit/af25325fcaee26dfaeb93f945c2aeca24c4898c4))

* feat(demo): build_demo_db ingests fixture rows via existing import seams

Renders DEMO_TAXABLE/DEMO_IRA to Schwab-format CSV in memory, runs them
through SchwabParser + Repository.add_import, then stitch_account +
recompute_all_violations — the same path /imports and the CLI use.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`0c0f3dc`](https://github.com/chen-star/net_alpha/commit/0c0f3dc8b20c0e670ebff67fa19b62a7f52414f5))

* feat(demo): add hand-curated demo trade rows for taxable + IRA ([`481d5ee`](https://github.com/chen-star/net_alpha/commit/481d5eedb854ebfaaae48dda3a46436685a9a539))

* feat(db): add get/set tour_completed meta helpers ([`6135946`](https://github.com/chen-star/net_alpha/commit/6135946904b421eed0582ac882b89ea37ddfaaae))

* feat(web): add effective_db_path helper and app.state.demo_mode flag

Foundation for the onboarding tour: when demo_mode is True, requests route
to &lt;data_dir&gt;/demo.db so the tour can operate on isolated fixture data
without touching the user&#39;s real DB. Default behavior (demo_mode=False)
is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`8bbe747`](https://github.com/chen-star/net_alpha/commit/8bbe747adc125d53a4dd645b4f86192e872fa636))

### Fix

* fix(onboarding): block imports in demo mode; add replay button; hide banner on welcome; drop dead wizard param

- /welcome/start-import now redirects to /imports (the ?wizard=1 flag was
  never consumed). Test renamed accordingly.
- POST /imports now early-returns to /welcome when app.state.demo_mode is
  set, preventing accidental writes to the real DB while in tour mode.
- Settings &gt; About now exposes a &#34;Replay onboarding tour&#34; form posting to
  /tour/replay so the tour is reachable from inside the app.
- _tour_banner.html no longer renders on /welcome even with ?tour=N — the
  splash already owns the start-tour CTA, and the banner duplicates it.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`9c894ce`](https://github.com/chen-star/net_alpha/commit/9c894ce0cc74fc04e189b4a96d76b7eeae7512ce))

* fix(web): preserve query string on /imports → /settings/imports redirect ([`35ab078`](https://github.com/chen-star/net_alpha/commit/35ab0789844d9d3e0c07f3234b35e83e3ffd9233))

### Refactor

* refactor(web): use direct app.state access; tighten docstring ([`d5eac5d`](https://github.com/chen-star/net_alpha/commit/d5eac5d22bd419c650d0c97ad99372db8a5dba1a))

### Style

* style: ruff format pass after onboarding feature work ([`15dc8f2`](https://github.com/chen-star/net_alpha/commit/15dc8f2acd8625e10a178271173cb5f5931eab45))

### Unknown

* Merge branch &#39;worktree-onboarding-first-60s&#39;

First-60-seconds onboarding overhaul:

- Welcome splash at /welcome with two doors (tour | import)
- Staged 3-step tour banner over isolated demo.db (~/.net_alpha/demo.db),
  built deterministically from in-code Schwab-format trade rows
- effective_db_path helper + app.state.demo_mode flag for mode switching
- Tour state mutations: dismiss/replay/exit-to-real (real-DB pinned)
- Shared empty-state hero partial applied to /, /positions, /tax, /imports
- Imports preview: sample of 5 parsed trades before commit
- Imports success page summarizing trade + wash-sale counts
- net-alpha ui --demo flag for landing-page entry
- /imports POST guarded against demo-mode pollution
- &#34;Replay onboarding tour&#34; entry in Settings → About

Tests: 1526 passed (+ 41 new across 11 test files), 1 pre-existing skip.
Lint: clean. Format: clean. ([`76db5b6`](https://github.com/chen-star/net_alpha/commit/76db5b651e2938b553b57bb473fe0dc9f6cd3dbe))


## v0.53.0 (2026-05-06)

### Chore

* chore: refresh GitNexus index stats and uv.lock to v0.52.2

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`5f46245`](https://github.com/chen-star/net_alpha/commit/5f462454f4bb4ab04aee162ef88b041ce5e48fb4))

### Documentation

* docs(readme): rewrite with logo, screenshots, and tighter structure

Replaces the ASCII-art header with a gradient α logo, promotes two UI
screenshots from the local audit set, and reorganizes the body around
Overview / Quickstart / Features / Usage / Rules / Architecture so a
first-time visitor can size up the project in a single scroll.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`5e03b10`](https://github.com/chen-star/net_alpha/commit/5e03b10e18e159b229faafc27c9f30c4a3b786a4))

* docs(claude): document carryforward + lot_selector subsystems

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`8ccd72a`](https://github.com/chen-star/net_alpha/commit/8ccd72a3e0be2d62529f4aebaf522d0c8d6df500))

### Feature

* feat(sim): 5-strategy lot comparison with recommendation chip

Wires engine.lot_selector into the Sim page. POST /sim with action=Sell
now runs FIFO, LIFO, HIFO, Min Tax, and Max Loss in parallel and renders
a comparison table under the per-account result cards. The recommendation
chip prefers no-wash-sale combos first, then highest after-tax P&amp;L.

Insufficient-lots requests render a graceful &#34;comparison unavailable&#34;
panel instead of crashing. The Buy path is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`7f47b05`](https://github.com/chen-star/net_alpha/commit/7f47b0509a66c44b35e2e4c9cc1737c8317e8139))

* feat(lot_selector): MIN_TAX brute-force + greedy fallback

Restructure select_lots so MIN_TAX/MAX_LOSS go through a combo
enumerator: brute-force every subset that covers the requested qty for
≤12 lots, fall back to a HIFO-ordered greedy with an &#34;approximate&#34; note
beyond that. FIFO/LIFO/HIFO continue through the order-then-consume
path, now sharing a single `_evaluate` composer with the combo branch.

MIN_TAX scores combos by the largest tax saving on the transaction
(`after_tax_pnl - pre_tax_pnl`), so a tax-aware planner picks the loss
lot that banks a deduction over a gain lot that nets a higher absolute
after-tax dollar amount but generates tax owed.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`39d3d40`](https://github.com/chen-star/net_alpha/commit/39d3d40481188e0888e8d21a111bcd629188e857))

* feat(lot_selector): after-tax math with carryforward + brackets ([`7ca607b`](https://github.com/chen-star/net_alpha/commit/7ca607b7664b9aad8bc27309ffa6b6c8f3efd87e))

* feat(lot_selector): wash-sale awareness via trades_for_ticker_in_window

LotPick.adjusted_basis is now per-share (was a copy of the lot&#39;s total
basis), so partial fills compute correct P&amp;L. Wired _check_wash_sale into
select_lots: each loss-pick is checked against the trade history for a
substantially-identical buy in ±30 days, including ETF-pair siblings.

Repository gains a ticker-scoped trades_for_ticker_in_window helper —
named distinctly from the existing date-only trades_in_window so its 18
upstream callers stay untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`e48e38e`](https://github.com/chen-star/net_alpha/commit/e48e38ef0f6e282a8b319efd0ada01f381616c16))

* feat(engine): lot_selector skeleton with FIFO/LIFO/HIFO ordering

Adds the LotPick / LotPickResult / InsufficientLotsError contract plus the
select_lots entry point with FIFO/LIFO/HIFO consumption orderings. Wash-sale
checks, after-tax math, and the MIN_TAX/MAX_LOSS combo strategies land in
Tasks 12-16. The repo / etf_pairs / brackets / carryforward parameters are
plumbed through but unused at this stage. ([`c27d3c1`](https://github.com/chen-star/net_alpha/commit/c27d3c1dd33ce47474d40f62e2314edb9356963a))

* feat(tax_planner): offset budget includes incoming carryforward

Thread the prior-year ST/LT capital-loss carryforward through
``compute_offset_budget`` so the YTD-vs-$3K projection accounts for losses
rolling in from prior years. Production callers (portfolio, positions,
wash-sales) now resolve the effective carryforward and pass it through.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`5bdb67f`](https://github.com/chen-star/net_alpha/commit/5bdb67fa7194ccb0b2bddde45362fd946dd56a62))

* feat(after_tax): consume carryforward, drop unmodeled caveat

Wire the new Carryforward value through compute_after_tax so the projected
tax bill correctly accounts for prior-year ST/LT capital-loss carryforwards.
Carryforward magnitudes absorb same-bucket P&amp;L first; surplus crosses
categories per §1212(b)(1)(B). Remove the legacy &#34;Capital-loss limitation
... not modeled&#34; caveat (which existed precisely because we hadn&#39;t modeled
this) and replace it with a &#34;Carryforward applied&#34; caveat surfaced only
when a non-zero carryforward is consumed.

Production caller (_build_performance_ctx in /tax?view=performance) now
resolves the effective carryforward (override-wins) for year-scoped
periods; Lifetime period passes None. Test-only callers can omit the
kwarg — default None preserves legacy zero-carryforward behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`e3217a2`](https://github.com/chen-star/net_alpha/commit/e3217a262c5cd8ad48a256e89c8eeaf9545ca45d))

* feat(web): settings carryforward upsert + reset endpoints

Adds GET /settings/carryforward/edit (inline edit-row form),
POST /settings/carryforward/save (upsert with 422 on negative/non-numeric),
and POST /settings/carryforward/reset (delete override). Save/Reset
re-render the whole carryforward section by calling the existing GET
handler. The Edit/Reset buttons wired in Task 7&#39;s _settings_carryforward
fragment now resolve instead of 404.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`d5ceb6a`](https://github.com/chen-star/net_alpha/commit/d5ceb6a7fb1aa658717c1d933a9bd350edaffff9))

* feat(web): settings carryforward GET + template

Adds /settings/carryforward HTMX fragment that lists per-year ST/LT
carryforward state — derived for years with prior-trade history,
override-sourced for years the user has set explicitly. Read-only in
this task; POST endpoints (edit / reset) follow in Task 8.

Adapted from the original plan:
- Project has no settings.html — mounted the lazy fragment in
  settings_entry.html (the page body shown beneath the drawer).
- Substituted plan&#39;s badge/btn-link CSS tokens for the project&#39;s
  inline-style + bg-surface/hairline conventions used by other
  drawer tabs.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`8a24b19`](https://github.com/chen-star/net_alpha/commit/8a24b190335948fa8b1422e926a9920c6840bda4))

* feat(carryforward): override-wins effective resolver + repo adapters

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`49555e8`](https://github.com/chen-star/net_alpha/commit/49555e82eff88b0709cf7b740f6552bfb5fdf3b1))

* feat(carryforward): §1212(b) cross-category + §1211 cap

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`c2fcc83`](https://github.com/chen-star/net_alpha/commit/c2fcc830c0d804176de06a308d93e455c0749915))

* feat(carryforward): single-year derive baseline ([`d69dd15`](https://github.com/chen-star/net_alpha/commit/d69dd15a1282a17ef517cd9dc6ca07299b05f9eb))

* feat(db): repository CRUD for loss carryforward overrides

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`4c8fb20`](https://github.com/chen-star/net_alpha/commit/4c8fb20ba8eba82f946e3d1dde692b41219e61a6))

* feat(db): migrate v17-&gt;v18 with loss_carryforward table ([`a8747a1`](https://github.com/chen-star/net_alpha/commit/a8747a12b73d2aab1f072444e9af69448a7d223e))

* feat(db): add LossCarryforwardRow SQLModel ([`37caec8`](https://github.com/chen-star/net_alpha/commit/37caec8039f717e90d3bc2c5a6413acffb17057f))

### Fix

* fix(lot_selector): HIFO sorts on basis-per-share + deterministic tiebreaks ([`b780fe2`](https://github.com/chen-star/net_alpha/commit/b780fe2c8b85f48c544a6aafa4095ee0cf1ae65c))

* fix(db): use UTC for carryforward updated_at timestamp ([`8ced102`](https://github.com/chen-star/net_alpha/commit/8ced1023d146c452325a29c843f513bff7f06efb))

### Test

* test(integration): carryforward + best-lot end-to-end

Capstone smoke test verifying multi-year history rolls into a derived ST
carryforward at /settings/carryforward, sim Sell renders the 5-strategy
lot comparison with a recommendation, and a user override at $0/$0 wins
over the derived value (override-wins semantics).

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`ce0377e`](https://github.com/chen-star/net_alpha/commit/ce0377eef1216a7ebdb78bbca47d7b943d375ef6))

* test(lot_selector): MAX_LOSS strategy coverage

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`3d04306`](https://github.com/chen-star/net_alpha/commit/3d043069e57fac67cb4a850376a2a2ec12293345))

### Unknown

* Merge branch &#39;feat/carryforward-and-best-lot&#39;

Net-loss carryforward tracking + best-lot picker on Sim.

Carryforward subsystem (portfolio/carryforward.py): pure-function ST/LT
capital-loss carryforward derive with §1212(b) cross-category netting and
§1211 $3K cap, hybrid auto-derive + user-override resolution. Stored via
new LossCarryforwardRow / loss_carryforward table (schema v18). Surfaced
at /settings/carryforward, consumed by compute_after_tax and
compute_offset_budget.

Lot selector (engine/lot_selector.py): pure-function select_lots() with
five strategies (FIFO/LIFO/HIFO/MIN_TAX/MAX_LOSS), wash-sale-aware via
new Repository.trades_for_ticker_in_window. MIN_TAX brute-forces ≤12
lots, greedy beyond. Surfaced as a comparison table on the Sim page
Sell action.

Follow-ups: project_year_end_tax not yet wired to carryforward;
realized_pnl_split (broker G/L) and realized_pnl_split_by_year
(transactions FIFO) data-source asymmetry; _classify_st_lt_gains
duplication can be consolidated. ([`87f1eff`](https://github.com/chen-star/net_alpha/commit/87f1eff40c3af910b987dfce80a50cefc8b6c887))


## v0.52.2 (2026-05-06)

### Chore

* chore: refresh GitNexus index stats and uv.lock to v0.52.1

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`3947c37`](https://github.com/chen-star/net_alpha/commit/3947c37804a7407b0cf19f5d17401650e4329523))

### Documentation

* docs(readme): switch pepy badge to lifetime downloads

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`5caf6b7`](https://github.com/chen-star/net_alpha/commit/5caf6b7a6dd47723016fe0a4ca532bc6f583ce20))

### Fix

* fix(portfolio): reconcile account-value explainer against external flows

The Account Value explainer 500&#39;d whenever cash held dividends, interest,
fees, or in-kind share transfers — those move the cash balance and lot
basis without going through `net_contributed` or `lifetime_realized`,
breaking the composition vs. source invariant. Add explicit source-side
terms for non-contribution cash flows and share-transfer basis, and fold
any remaining gap into a labelled &#34;Other / unattributed&#34; residual row
with an amber caveat banner so the panel always renders.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`fc0f2aa`](https://github.com/chen-star/net_alpha/commit/fc0f2aa0ee346b8c6b7da1cc7af2985ebed5ea46))

### Style

* style: ruff format pass on test_portfolio_explain

Pre-existing formatting drift surfaced by ruff format --check in CI. ([`ee56b93`](https://github.com/chen-star/net_alpha/commit/ee56b934a6158a710df3b0309b574431f5e6a1ab))


## v0.52.1 (2026-05-05)

### Performance

* perf(pricing): negative-cache bracketed weekday holidays in warm path

Every Portfolio page load re-fetched all ~93 symbols from Yahoo (~12s)
because each symbol had several uncached weekday market holidays in the
warm range, and the previous policy negative-cached only weekends —
leaving holidays as _MISS forever, so warm never reached steady state.

Now we negative-cache a missing weekday when it&#39;s bracketed by present
trading days in the same bulk response (a real holiday signal vs. a
partial Yahoo response). The fetch range is also padded by 7 days on
each side, clamped to the requested range, so leading/trailing holidays
like Jan 1 New Year&#39;s get a present trading day before them. Trailing/
leading weekday gaps with no surrounding present day still stay _MISS,
preserving self-healing on Yahoo glitches and avoiding poisoning today&#39;s
not-yet-published close.

Measured against the real local DB (92 equity tickers + SPY benchmark):
  YTD:      13.05s -&gt; 0.97s steady-state (~13x)
  Lifetime: 17.67s -&gt; 1.98s steady-state (~9x)

The first page load after this change still takes the original time once
to populate the holiday negative-cache; every subsequent load is fast. ([`c08572b`](https://github.com/chen-star/net_alpha/commit/c08572b6a5cb466279b97a7ec07a1012223082d7))


## v0.52.0 (2026-05-05)

### Feature

* feat(web): wire Account Value explain panel trigger

Info-circle button next to TOTAL ACCOUNT VALUE label opens the new
explainer fragment via HTMX.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`7c3590f`](https://github.com/chen-star/net_alpha/commit/7c3590f7253f4ec63705a4699d585cada8556a57))

* feat(web): _explain_account_value.html template

Two equation grids (Composition + Source), short-option estimate caveat,
missing-quotes caveat, fetched_at footer.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`c3b0843`](https://github.com/chen-star/net_alpha/commit/c3b08435866c8238d3611fe795d73375296ed84d))

* feat(web): /portfolio/explain/account-value route

Period-agnostic explainer endpoint for the TOTAL ACCOUNT VALUE hero KPI.
Placeholder template will be replaced in the next commit.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`0cde7a5`](https://github.com/chen-star/net_alpha/commit/0cde7a5beebb8d38e2d3bd6c4569860efdf96a8d))

* feat(explain): add build_account_value_breakdown

Pure-function builder for the Total Account Value explainer panel.
Two equations (Composition + Source) both reconcile to the same total.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`d13c92b`](https://github.com/chen-star/net_alpha/commit/d13c92b84400cfd3371e09c7afd5affa6d52ba83))

### Fix

* fix(web): apply sign-aware coloring on Account Value total rows

Matches the pattern used by _explain_total_return.html. A negative
total (theoretical: short-option liability exceeds all other components)
now renders in red instead of default text color.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`f7d9e93`](https://github.com/chen-star/net_alpha/commit/f7d9e93073d886ab4dc5755c7c10c10897f40f53))

* fix(web): drop misleading None-guard around last_snapshot()

PricingService.last_snapshot() is typed -&gt; PricingSnapshot (never None);
only the snapshot&#39;s fetched_at field is Optional. Aligns with the rest
of portfolio.py.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`197b75d`](https://github.com/chen-star/net_alpha/commit/197b75dcc4e9d28cc85e9aeddccd236464ad210b))

* fix(explain): use ValueError for reconciliation guard; drop per-lot quantize

AssertionError is silenced by python -O. The reconciliation check is a
real consistency guard against bad input data, not a debug assertion.

Per-lot quantize on long stock MV could drift up to ~$0.005/lot before
summing; quantize once at the end matches what short_liability_total
already does.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`af7a219`](https://github.com/chen-star/net_alpha/commit/af7a21918640b689c238de5e89bf94597edfafe0))

### Refactor

* refactor(explain): extract _estimate_short_option_liability helper

Pure refactor. Will be reused by upcoming build_account_value_breakdown.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`94d9956`](https://github.com/chen-star/net_alpha/commit/94d99562406429366f3013227d8daa955249a01c))

### Style

* style: ruff format pass on explain.py + portfolio.py

Pure formatting — no behavior change. Brings the new code added in
the account-value explainer series up to the project&#39;s ruff format style.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`5c55ed4`](https://github.com/chen-star/net_alpha/commit/5c55ed4d7c4b5cafc0a3c1f9487238e1d6c83e4b))

### Test

* test(web): cover account-value missing-quotes caveat banner

Seed an unpriced lot and assert the data-explain=&#34;missing-quotes-caveat&#34;
div renders in the explainer fragment. Closes the test-pyramid gap
flagged by the final review.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`a20abfe`](https://github.com/chen-star/net_alpha/commit/a20abfeef1ef402b3ca764a2ac97678e6d9164ff))

### Unknown

* Merge branch &#39;feat/account-value-explain&#39;

Account Value explainer panel: HTMX info-circle on the TOTAL ACCOUNT
VALUE hero KPI tile opens a fragment showing two equation grids
(Composition + Source) that reconcile to the hero number. ([`088e3ab`](https://github.com/chen-star/net_alpha/commit/088e3abe28f13abfec16d096af2736da9eee58cb))


## v0.51.0 (2026-05-05)

### Chore

* chore: sync uv.lock to wash-alpha 0.50.0

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`4ee71e6`](https://github.com/chen-star/net_alpha/commit/4ee71e666c4128d92625173f179a82ab5ffd5753))

* chore(docs): refresh GitNexus index stats

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`1f32b58`](https://github.com/chen-star/net_alpha/commit/1f32b583f5883ca3cfda9f0a49705dd9e1642fce))

* chore(web): vendor SortableJS 1.15.2 for Plan drag-to-reorder ([`543db9f`](https://github.com/chen-star/net_alpha/commit/543db9f69e833bdbe3f109d965d9949aa38a87a6))

### Documentation

* docs(claude): note Plan Manual mode + SortableJS vendor target ([`5acd490`](https://github.com/chen-star/net_alpha/commit/5acd4908029f45c777acf622a0351a75969e5ca6))

### Feature

* feat(web): halve monthly P&amp;L chart height to 90px

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`2859a17`](https://github.com/chen-star/net_alpha/commit/2859a17b66293768f8b384bbad545a1adc30d0bf))

* feat(web): move Cash KPI ahead of Realized/Unrealized in portfolio header

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`dd5b94d`](https://github.com/chen-star/net_alpha/commit/dd5b94d2af4d3dbb2dbed842819aee5644bc3eeb))

* feat(web): add drag-handle + ghost styles for Plan reorder ([`5caf535`](https://github.com/chen-star/net_alpha/commit/5caf535f58efe5fb5c1b3a14d01c49d7c0dc09b9))

* feat(web): wire SortableJS + plan_reorder.js into base layout ([`e1f046f`](https://github.com/chen-star/net_alpha/commit/e1f046fa47130f8d1e62718cebe64c5891c5ccc2))

* feat(web): add POST /positions/plan/reorder for Manual mode

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`5312aed`](https://github.com/chen-star/net_alpha/commit/5312aed31ac5b88e6a41cfc088379b299bdd7636))

* feat(web): render drag-handle column in Plan Manual mode

Add id/data-sort-mode to plan tbody and a conditional drag-handle &lt;td&gt;
(inline SVG grip) that appears only when sort_key == &#39;manual&#39;.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`d76d010`](https://github.com/chen-star/net_alpha/commit/d76d01055d7738ee395d72f0c144459c7621526a))

* feat(web): add Manual option to Plan sort dropdown ([`b808003`](https://github.com/chen-star/net_alpha/commit/b808003a9cbd166e17ee581c0d96860692ddb231))

* feat(web): use manual-order target loader when sort=manual

Switches _build_plan_view_for_request to call list_targets_by_manual_order()
when sort=manual, so drag-reordered rows are reflected in the Plan tab view.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`9f383e3`](https://github.com/chen-star/net_alpha/commit/9f383e33dbc43eb01a07128caf3e45dc6081ca29))

* feat(targets): accept sort_key=&#39;manual&#39; (no-op sort, caller ordered) ([`ad19aef`](https://github.com/chen-star/net_alpha/commit/ad19aef74ae13f0e5cc634fe446bc2d52a24d697))

* feat(repo): add set_target_order for Plan manual reorder

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`211ac06`](https://github.com/chen-star/net_alpha/commit/211ac069e8021d7b88063c1308b8f80eea5e0829))

* feat(repo): add list_targets_by_manual_order for Plan Manual sort

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`9fae732`](https://github.com/chen-star/net_alpha/commit/9fae7320d3684957d5469ebe3b021a81dc0086f2))

* feat(repo): assign next sort_order on target insert; preserve on update ([`3af42e4`](https://github.com/chen-star/net_alpha/commit/3af42e43f3e7b5443b2f89d2c1c3552c688df4f6))

* feat(repo): expose sort_order on PositionTarget reads ([`c465c57`](https://github.com/chen-star/net_alpha/commit/c465c574a30058dacbd69ed20233f7a3b5281a28))

* feat(targets): add sort_order field to PositionTarget ([`4d33c84`](https://github.com/chen-star/net_alpha/commit/4d33c844bb575af044f3adbc5add1edee5ae5e5f))

* feat(db): add sort_order to PositionTargetRow ([`eb3c8f5`](https://github.com/chen-star/net_alpha/commit/eb3c8f58176c7519ac289e61113db594ec6f617d))

* feat(db): add schema v17 — position_targets.sort_order for Plan manual reorder

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`98489ea`](https://github.com/chen-star/net_alpha/commit/98489eaa2850f39e6902813d89f568c437e479a4))

### Fix

* fix(tests): repair lint + KPI-order assertions to unblock CI

Three classes of failure:
- tests/web/test_plan_modal.py had redundant in-function imports
  (Decimal, TargetUnit) that ruff flagged as F401/I001. The names are
  already imported at module top, so dropping the inline shadow imports
  is the simplest fix.
- tests/db/test_migration_v16.py, tests/db/test_repository_target_tags.py,
  and tests/targets/test_tags.py needed `ruff format` (text() call
  reflows from a recent ruff version).
- tests/web/test_kpi_hero_order.py and tests/web/test_phase3_smoke.py
  still asserted the pre-f8e6e07 KPI order. Update to the new
  hero / total_return / cash / realized / unrealized order.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`b7aa6ed`](https://github.com/chen-star/net_alpha/commit/b7aa6ed5df11e149c496128d6a012b1e932e843e))

* fix(inbox): surface near-expiring open short option positions

The detector creates Lots only for Buy actions, so STO trades never
produced a Lot row. The inbox option-expiry signal walked
repo.all_lots() exclusively, leaving CSPs and covered calls invisible
to the inbox.

Reuse compute_open_short_option_positions — the same aggregator the
portfolio&#39;s open-shorts panel uses — to reconstruct open short chains
from the trade table, plumbing get_option_gl_closures so broker-closed
chains don&#39;t resurface as phantom shorts. Synthetic trade_id encodes
the chain key, keeping dismiss keys stable across re-imports.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`ef89173`](https://github.com/chen-star/net_alpha/commit/ef89173cb36fe938058dffbe31a57a7e523bd35b))

* fix(db): chain v17 migration with current = 17 (was return) ([`fafbfd8`](https://github.com/chen-star/net_alpha/commit/fafbfd889a6531aed9ba459d26ee36b03829c766))

### Refactor

* refactor(db): clarify v17 docstring and split idempotency test

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`69a459a`](https://github.com/chen-star/net_alpha/commit/69a459a2d669c86d37b1f4a5e959a1a6379f8c67))

### Style

* style: apply ruff format to files touched in this branch ([`8815b4b`](https://github.com/chen-star/net_alpha/commit/8815b4bfa2d1295da10c8858b72741c797ea9ba5))

### Test

* test(repo): restore target_amount assertion in upsert update test ([`cff79ab`](https://github.com/chen-star/net_alpha/commit/cff79abc8b4bbc109c14f72990e0a194351cd36c))


## v0.50.1 (2026-05-04)

### Fix

* fix(portfolio): correct Total Return decomposition for per-account scope and unpriced lots

Two related bugs made the YTD Total Return decomposition disagree with the
standalone Realized P/L + Unrealized P/L tiles:

1. Per-account GL leak in the explainer route. /portfolio/explain/total-return
   pre-filtered trades and lots by account but passed account=None into
   compute_kpis along with all-account gl_lots. compute_kpis only filters
   gl_lots when its account arg is truthy, so other accounts&#39; GL realizations
   silently leaked into the decomposition&#39;s realized leg.

2. Negative-cached historical closes silently treated as $0 in the period
   anchor. account_value_at drops lots whose forward-fill close lookup
   returns None, so missing 12/31 closes for held lots undercounted
   starting_value and inflated Total Return. The bulk warmer was the source:
   it stored None for every calendar day in a yfinance bulk response,
   including real trading days the partial response missed.

Fixes:
- explain_total_return now passes account through to compute_kpis.
- warm_historical_range only negative-caches Sat/Sun. Weekday misses stay
  _MISS so the single-fetch fallback can recover them — self-healing.
- New account_value_at_with_warnings surfaces lot drops; the Total Return
  panel renders a caveat banner with the affected tickers and basis sum.
- New net-alpha refresh-historical-cache [--since DATE] CLI command purges
  legacy NULL rows and re-warms from yfinance for users with already-poisoned
  caches.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`a9cd627`](https://github.com/chen-star/net_alpha/commit/a9cd627464f0aaf05015b944531daf0554049859))


## v0.50.0 (2026-05-04)

### Chore

* chore: ruff format on tests/portfolio/test_calendar_pnl.py

Single blank line between section comment and first new test function.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`2e62cb8`](https://github.com/chen-star/net_alpha/commit/2e62cb8f6bb758081566c5cee0e88c944d22247b))

* chore(tests): hoist monthly_realized_pl_series imports to top of file

Removes the # noqa: E402 suppressions on mid-file imports flagged in Phase A
review. Pure cleanup, no behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`8a37e23`](https://github.com/chen-star/net_alpha/commit/8a37e23919bf1f1967971549778011c7899f4eee))

### Feature

* feat(web): insert monthly realized P&amp;L row into overview body ([`02f9c9f`](https://github.com/chen-star/net_alpha/commit/02f9c9f51551e49159d3835f385c80c0f8ea09ac))

* feat(web): compute monthly realized P&amp;L series in /portfolio/body handler ([`4f40139`](https://github.com/chen-star/net_alpha/commit/4f40139ac1665b9f777240c0a9b28d1c44a78886))

* feat(web): _portfolio_monthly_pl partial — wide ApexCharts bar chart for monthly realized P&amp;L ([`dd0c77c`](https://github.com/chen-star/net_alpha/commit/dd0c77c157e88a9c5537886b2725b26ce8ea397f))

* feat(portfolio): monthly_realized_pl_series for chronological multi-year P&amp;L bars ([`276946d`](https://github.com/chen-star/net_alpha/commit/276946d4873f080b0defcf5f5a65498b8a93e302))

* feat(portfolio): add MonthlyPnlPoint view model for chronological monthly P&amp;L series ([`8cb4579`](https://github.com/chen-star/net_alpha/commit/8cb45790d691ba7b897a8bba8098e1091d45aed5))

### Fix

* fix(web): label decomposition total row in Total Return explainer

The Decomposition section&#39;s total row had an empty label cell with a
hanging hairline rule — the value floated alone with no &#39;=&#39; label,
breaking the equation&#39;s visual symmetry against the upper grid.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`4fd6ac0`](https://github.com/chen-star/net_alpha/commit/4fd6ac07b3bca30805e1955b18007f29f7097f55))

### Test

* test(web): tighten specific-year monthly P&amp;L smoke test contract

The old &#39;assert &#34;2025&#34; in html&#39; was satisfied by the period meta label
regardless of which template branch fired, so the test couldn&#39;t catch a
regression that swapped the empty-state for zero bars (or vice versa).
Pin both halves of the contract: panel chrome present AND empty-state
copy is what fills the chart area when no closes exist in the period.

Per final whole-branch review (M-1).

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`78efabd`](https://github.com/chen-star/net_alpha/commit/78efabd7a55c80a4460d67598f4083b3fbe6a634))

* test(web): smoke tests for monthly realized P&amp;L panel across YTD/year/Lifetime

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`fa3c934`](https://github.com/chen-star/net_alpha/commit/fa3c9348c5393560f3e7d22a3c282769113249dd))


## v0.49.1 (2026-05-04)

### Fix

* fix(ui): Total Return tile must include long-option basis in account value

The Portfolio body route was feeding `compute_cash_kpis` an equity-only
holdings total (sum of `compute_open_positions` market values, which
skips long option lots), while the Hero tile, the period-start anchor,
and the explain panel all used `kpis.open_position_value` which carries
open long options at basis. Result: the Total Return KPI read low by the
long-option-basis amount and disagreed with both the Hero tile and the
explain panel on the same page.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`18ee01d`](https://github.com/chen-star/net_alpha/commit/18ee01d0f7404ef4493a3df9ebf5d73a97a208e0))


## v0.49.0 (2026-05-04)

### Chore

* chore: sync uv.lock to v0.48.0

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`918d2b4`](https://github.com/chen-star/net_alpha/commit/918d2b4ccdac0748aad605cf1a52a4ae98748c47))

* chore: ruff import-order autofixes on plan-ux modules

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`f31e6cc`](https://github.com/chen-star/net_alpha/commit/f31e6cc4f6f1abb8e933c5f0d7be587e509a3e76))

* chore: update uv.lock ([`636f0b1`](https://github.com/chen-star/net_alpha/commit/636f0b18bebdacf5b514565bab5c4b15f09a92af))

### Feature

* feat(ui): recompose Plan tab with KPI/tag/toolbar strips and bullet rows

Extracts the plan table row into _positions_plan_row.html (includes the
bullet chart and inline tag chip editor) and rewires _positions_view_plan.html
to compose all four chrome partials (KPI strip, tag strip, toolbar, row)
around the table.

Also registers the `ord` built-in as a Jinja2 filter in app.py so the
|ord usage in _positions_plan_tag_strip.html and
_positions_plan_tag_chip_editor.html (added in earlier tasks but never
wired) renders without a 500 error.

Adjusted test_plan_tab_renders_target_rows: &#34;Total to fill&#34; → &#34;To Fill&#34;
and &#34;Free cash&#34; → &#34;Free Cash&#34; to match the new KPI strip wording.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`21ceba9`](https://github.com/chen-star/net_alpha/commit/21ceba9acdffe62597d35adde4df26a7f86a2f46))

* feat(ui): Plan tab toolbar (sort dropdown + Add target) ([`1af089a`](https://github.com/chen-star/net_alpha/commit/1af089ae27a6391dc55f7423355b8bc59f44a40d))

* feat(ui): Plan tab tag summary strip (clickable filter tiles) ([`7be0da7`](https://github.com/chen-star/net_alpha/commit/7be0da7a32f95db9d36babb7fc4d7b77807e85e0))

* feat(ui): Plan tab KPI strip (planned · to-fill · cash · coverage) ([`239f749`](https://github.com/chen-star/net_alpha/commit/239f749e4318262331f96086977feeb353fea1d0))

* feat(ui): inline tag chip editor (pills + add popover) ([`3f9f574`](https://github.com/chen-star/net_alpha/commit/3f9f574b90b45936d5cc1f36da050f76ff28cb15))

* feat(ui): add bullet-chart partial for Plan rows ([`19aad2e`](https://github.com/chen-star/net_alpha/commit/19aad2e6635bb3a7b2b73b93dea91448de21a501))

* feat(plan-modal): add tags chips field with datalist autocomplete

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`b0bbe5b`](https://github.com/chen-star/net_alpha/commit/b0bbe5beb21819fcb75f2a94ba1d6a2bb6fb6817))

* feat(web): thread ?tag= and ?sort= into Plan view

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`19c4a92`](https://github.com/chen-star/net_alpha/commit/19c4a92ce99b50faa2e8d8f6f4586c7910f25ca6))

* feat(web): POST/DELETE tag endpoints with normalization + 404/422 ([`9b23733`](https://github.com/chen-star/net_alpha/commit/9b2373392b9d94360455d6c1cbe2ed27c2384bc6))

* feat(web): GET /positions/plan/tags autocomplete endpoint ([`a53529b`](https://github.com/chen-star/net_alpha/commit/a53529b309a922d317cbfe2cae1df91612e22a58))

* feat(view): add tag filter + 4-way sort to build_plan_view

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`32ca7b4`](https://github.com/chen-star/net_alpha/commit/32ca7b481c035ab3d4d4fc0dd723ccaeae4748bf))

* feat(view): add PlanTagSummary and tag-aware PlanView fields

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`5a78176`](https://github.com/chen-star/net_alpha/commit/5a78176eb2cc9bc5d6aec9364bb9f1fbb7ed364a))

* feat(repo): populate PositionTarget.tags in list_targets/get_target

Extend list_targets() with a single bulk tag query (no N+1) and
get_target() with a per-symbol tag query; _row_to_target gains an
optional tags parameter (default empty tuple) so upsert_target is
unaffected.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`607517e`](https://github.com/chen-star/net_alpha/commit/607517e97cafc34b6f1656cf593148576f274bef))

* feat(repo): tag CRUD on position targets (set/add/remove/list)

Adds set_target_tags, add_target_tag, remove_target_tag, list_target_tags,
and list_all_tags methods to Repository; imports normalize_tag/normalize_tags
from targets.tags. All 11 cascade + normalization tests pass.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`1c0f223`](https://github.com/chen-star/net_alpha/commit/1c0f223685ef97f07842b99754e21d790763744c))

* feat(targets): add PositionTarget.tags (default empty tuple) ([`b8914ec`](https://github.com/chen-star/net_alpha/commit/b8914ece51dc935a25f9345018599b3e24c22ef4))

* feat(db): schema v16 — add position_target_tag with FK cascade ([`bc172c7`](https://github.com/chen-star/net_alpha/commit/bc172c7afb8a6f9149ac83d63e9a7ed670617046))

* feat(targets): add tag normalization (lowercase, hyphenize, validate, dedupe) ([`e7084ed`](https://github.com/chen-star/net_alpha/commit/e7084ed2ec2f382ecbf034315d53430c62df2792))

### Fix

* fix(repo): explicit cascade in delete_target so fresh-install DBs don&#39;t orphan tags

SQLite ON DELETE CASCADE only fires when foreign_keys=ON AND the table was
created via the v16 migration. SQLModel.metadata.create_all (used on fresh
installs) produces position_target_tag without the FK, so target deletion
would leak orphan tag rows into list_all_tags. Application-level cascade is
unconditional and DB-origin-independent.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`a7f5673`](https://github.com/chen-star/net_alpha/commit/a7f5673f2b2fe3631d1980b76bd5eba53d88ea73))


## v0.48.0 (2026-05-04)

### Chore

* chore: refresh GitNexus index counts in AGENTS.md / CLAUDE.md

Auto-bumped by the GitNexus PostToolUse hook: 4382→4384 symbols,
15207→15285 relationships. Documentation only, no code change. ([`576b735`](https://github.com/chen-star/net_alpha/commit/576b735c8cf092d4476299827d79ea4453db74af))

### Feature

* feat(ui): polish overview command-center and positions table

Eight UI improvements identified during a fresh design review of the
Portfolio, Positions, and chart surfaces:

- Apply tabular numerals to KPI values and table money columns so
  digit columns line up across rows without forcing every template
  to wrap content in `.num`.
- Flatten the Total Account Value hero (drop the violet aurora
  gradient) so it sits as a peer to the four small KPIs.
- Replace literal `?` and `ⓘ` glyph help icons with an inline Lucide
  `info` SVG everywhere a metric explains itself.
- Fix duplicate-month axis ticks on the equity &amp; cash curves by
  switching to `datetimeFormatter` with a single `MMM d` shape across
  every Apex granularity bucket — yields unique labels without
  closure-state hacks that fail across Apex&#39;s measurement passes.
- Light-mode chart text contrast: bump axis labels from label3 to
  label2 and legend from label2 to label1 so YTD-scale chart labels
  meet WCAG on white.
- Hide the floating ApexCharts toolbar (+/−/pan/home) on equity and
  cash charts; drag-to-zoom + double-click-reset still work.
- Positions table declutter: hide per-row `↗ Sim` link until row
  hover/focus via new `.row-sim-link`, and reduce `— no target` to a
  faint em-dash so rows with real progress bars pop.
- Bump section-header hierarchy: `.panel-head h4` lifted from 14px to
  15px / 600 weight with tighter tracking so panel titles register as
  a clear tier between H1 and body.

433/433 web tests still pass. ([`09a679f`](https://github.com/chen-star/net_alpha/commit/09a679fbe31d496a8f4f7d36cadecab0712f52af))

* feat(ui): collapse KPI block into single command-center grid

Hero (TOTAL ACCOUNT VALUE) anchors the left as a 4-col × 2-row tile.
The four small KPIs auto-flow into the 2×2 area on the right at
lg:col-span-4 each. Hero uses flex+justify-center so the big number
sits at the visual midpoint of the stretched card (same height as
two stacked small KPIs + 10px gap).

DOM order preserved (hero, total_return, realized, unrealized, cash);
slot-order regression tests stay green untouched.

CSS rebuild required: lg:row-span-2 is the first row-span-* utility
used in the project, so Tailwind&#39;s JIT pass had to compile a new rule.
Regenerated app.css ships in this commit. ([`529b003`](https://github.com/chen-star/net_alpha/commit/529b003423c04d5acc16f2cdc6053b8f4293307b))

* feat(ui): collapse Total Return into the small-KPI row

Hero promotes to col-span-12 so the headline number owns row 1 alone.
Total Return drops kpi-promoted (28px → 22px) and joins Realized,
Unrealized, and Cash as the four equal-weight tiles in row 2 — each at
lg:col-span-3. The data-kpi-slot order (hero, total_return, realized,
unrealized, cash) is preserved so the existing slot-order regression
tests stay green without edits. ([`1bf49e2`](https://github.com/chen-star/net_alpha/commit/1bf49e2fc7ff41fbbdd7b826f67bb7ed7ccea9a7))

* feat(ui): widen cash curve — equity:cash row goes 2:1 → 3:2

The cash + contributions chart was getting cramped at 1/3 width — its
second series and dollar labels had nowhere to breathe. 3:2 (60/40) keeps
equity dominant as the primary &#34;am I winning?&#34; chart but gives cash room
to read. The allocation row below stays at 2:1. ([`b751cdd`](https://github.com/chen-star/net_alpha/commit/b751cdd033a0edb1d50420f1d3bb82ba69916149))

### Test

* test(ui): lock KPI command-center layout (4-col hero + 2x2 small KPIs)

Updates the layout regression tests for the upcoming command-center
header. Hero is now lg:col-span-4 lg:row-span-2 with flex+justify-center
so the big number sits at the visual midpoint of the stretched card.
The four small KPIs each move from lg:col-span-3 to lg:col-span-4 and
auto-flow into the 2x2 area to the right of the hero.

These will fail against the current template; the next commit makes them
pass. ([`eb540d8`](https://github.com/chen-star/net_alpha/commit/eb540d881a66322b89aab662790b0dca7eaa2ee7))


## v0.47.0 (2026-05-04)

### Chore

* chore: sync uv.lock to v0.46.2 ([`76cac0e`](https://github.com/chen-star/net_alpha/commit/76cac0ed288105ea499b381785105bb2ab4855fd))

### Feature

* feat(ui): introduce real KPI hierarchy on Portfolio overview

Promote Total Account Value to a 38px hero spanning two-thirds of the top
row, add a 28px Total Return tile beside it, and reduce the bottom row to
three tiles (Realized / Unrealized / Cash). Net Contributed folds into the
Cash tile subtitle — it&#39;s operational metadata that was diluting the
performance row at the same visual weight as Realized/Unrealized. ([`f41fc3f`](https://github.com/chen-star/net_alpha/commit/f41fc3f6297a4bcb1e7fdb17c2b2e3c14c84cdd3))

### Fix

* fix(ui): preserve base.html footer disclosure; scope toolbar test

The Task 4 toolbar declutter removed the global &#39;Prices via Yahoo Finance&#39;
footer block, which was the only price-source disclosure on Sim, Tax,
Imports, Ticker, Wash Sales, Settings, and Error pages. The freshness chip
that absorbs it lives in the toolbar — present on only 2 of 9 pages — so
the original change broke disclosure coverage.

Restore the footer block and tighten the toolbar regression test to scope
its assertion to the toolbar form only, matching the plan&#39;s actual intent
(declutter the toolbar, not kill the global disclosure).

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`251f985`](https://github.com/chen-star/net_alpha/commit/251f985d8da2c3af88ad371d47484d2e11445dcc))

* fix(ui): collapse toolbar overflow into kebab; chip absorbs disclaimer

Sync splits was a primary-position button most users never touch — move
it behind an ⋯ overflow menu using the same Alpine pattern as the row
action menus. The &#39;Prices via Yahoo (~15 min delay)&#39; inline disclaimer
moves into the freshness chip tooltip so users still discover the data
source without burning toolbar real estate on it.

Also drops the duplicate footer &#34;Prices via Yahoo Finance, ~15 min
delayed.&#34; line in base.html — the per-page freshness chip is now the
single source of truth for that disclosure. ([`0ae6ad2`](https://github.com/chen-star/net_alpha/commit/0ae6ad2b7e5029dc23da5e47faf10a770ccec3a3))

* fix(ui): widen equity curve to 2/3 and cash curve to 1/3

Equity is the page&#39;s primary &#39;am I winning?&#39; chart; the cash curve is
operational context. Equal weighting undersold equity — the new split
mirrors the Allocation + Deployment row directly below. ([`61ce904`](https://github.com/chen-star/net_alpha/commit/61ce9046d26e75898b1d8cc47e44e6a5448743fd))

### Refactor

* refactor: remove orphaned Today tile and dead compute path

The _portfolio_today_tile.html template was never wired into any body or
fragment, so its supporting fields (today_change, today_pct on KpiSet) and
the compute_today_change function in positions.py were producing values that
nothing consumed. Monthly CSV-import workflows have no use for an intraday
delta — drop the dead code rather than wire up a tile users don&#39;t want. ([`e8a77e8`](https://github.com/chen-star/net_alpha/commit/e8a77e86cb9a0081c2f7e3b74ae662f4aa280762))

### Style

* style: ruff-format test_overview_layout.py ([`435073f`](https://github.com/chen-star/net_alpha/commit/435073fb219d2cdec1c8f9d60c6456b9f7b84f5d))


## v0.46.2 (2026-05-03)

### Fix

* fix(ui): rebuild CSS, drop closed lots from inbox, refresh inbox styling

Three issues from the user, each with its own root cause:

1. Explainer × buttons unclickable. Last edit added `top-2 right-3
   text-[18px] grid-cols-[1fr_auto] gap-x-4 gap-y-1 items-baseline pr-6
   mb-3 hover:text-text` to the templates without rebuilding app.css.
   Without those positioning utilities the absolutely-positioned ×
   collapsed under the panel title — invisible and unreachable. The same
   missing utilities also broke the equation grid layout, which is why
   the numbers and labels weren&#39;t aligned. Re-ran `make build-css` so
   Tailwind picks up the new arbitrary-value classes.

2. Action Inbox kept showing positions the user had closed (e.g. WULF
   23C). The detector only ever creates Lot rows from buy trades, so the
   original BTO lot stays in the DB at its original quantity even after
   STC. `option_expiry` and `lt_eligibility` were filtering on raw
   `lot.quantity`, so they never saw the close. Now both signals run
   `open_lots_view` (FIFO consumption) before filtering. Negative-qty
   short option lots are merged back in since FIFO drops them and
   shorts aren&#39;t netted in this codebase.

3. Inbox panel itself was visually inconsistent with the rest of the
   dashboard — raw `bg-zinc-700`, `bg-red-600/20`, `divide-zinc-800`
   instead of the design tokens used everywhere else. Rewrote the
   template using the same idiom as `_portfolio_open_options.html`:
   `panel`, `text-label-*`, `--color-pos/neg/warn/info(+tint)`,
   hairline borders, glow dots scaled to severity, dollar impact
   right-aligned in severity color, urgent/watch counts as pill chips
   in the header.

Tests: 1281 passed (+ 3 new failing-then-passing for the inbox
filter); ruff format/check clean; smoke-tested against live DB —
inbox went from 15 stale items to 5 actually-open items, WULF/PL/MU
ghosts gone; explainer × button now lives at top-right and dismisses
the panel via `/portfolio/explain/dismiss`.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`7f23a6e`](https://github.com/chen-star/net_alpha/commit/7f23a6e58258b5a45faea6b2368fb68df66f36ea))


## v0.46.1 (2026-05-03)

### Chore

* chore: sync uv.lock to v0.46.0

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`7f97e2c`](https://github.com/chen-star/net_alpha/commit/7f97e2c3bb6b86b2d9682c694e36d7d8b1f47227))

### Fix

* fix(ui): explainer readability + pagination shared partial

Four user-reported issues:

1. The × close button on the explainer panels was using text-label-3,
   which resolves to ~35% alpha white on dark theme — barely visible
   against the panel surface. Switched to text-label-1 + bold + larger
   font so it actually renders.

2. The Total Return explainer used a 2-column table where the right
   cell crammed both the number and a parenthetical description like
   &#34;(account value at period start)&#34;. Long descriptions expanded the
   column and pushed labels to wrap mid-word. Restructured to a
   1fr/auto grid: labels left, right-aligned numbers. Footnotes for
   variable definitions live in their own block below.

3. The Unrealized P/L explainer listed every position with full math.
   User wants aggregate formulas only. Replaced per-line breakdown
   with three sums: long stock &amp; options (Σ market − Σ basis),
   short options (Σ premium − Σ liability), and a final total. Added
   long_market_total / long_cost_total / short_premium_total /
   short_liability_total fields to UnrealizedBreakdown to support this.

4. Pagination footer markup was duplicated four times across closed,
   options, harvest, and plan templates. Extracted into a shared
   _pagination_footer.html partial parameterized by base URL, extra
   query string, target id, and swap mode. All four views now render
   identical UI by construction.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`1655162`](https://github.com/chen-star/net_alpha/commit/16551620f3297e063a518fa2df1505fb918b6307))


## v0.46.0 (2026-05-03)

### Chore

* chore: sync uv.lock to v0.45.0

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`661c4d8`](https://github.com/chen-star/net_alpha/commit/661c4d80b0dcfd7f6cadee735fa021588c56dc63))

### Feature

* feat(ui): paginate plan view

Add server-side pagination to /positions?view=plan following the same
pattern as the closed and options views; _render_plan_body also computes
pagination so POST/DELETE re-renders are consistent.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`982ca3b`](https://github.com/chen-star/net_alpha/commit/982ca3bf4cbddb8b4a63b3f4571620ce103faed8))

### Fix

* fix(ui): explainer panel close button, account filter, always-show pagination

Three user-reported issues:

1. Explainer panels had no close button — added × button that swaps in
   an empty fragment via a new GET /portfolio/explain/dismiss route.

2. Total Return / Unrealized P/L explainer showed all-account numbers
   even when a specific account was selected. portfolio_kpis and
   portfolio_body routes were not passing selected_period /
   selected_account to the KPI template, so the ? button URL fell
   through to the empty default. Now passing both.

3. Pagination footer was hidden when total_pages == 1, so users with
   ≤25 rows couldn&#39;t see the per-page selector. Switched the visibility
   gate from total_pages &gt; 1 to total_rows &gt; 0 across closed positions,
   open options, and harvest plan templates.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`7cacab3`](https://github.com/chen-star/net_alpha/commit/7cacab3e84ccf4d03e30fc05623d045eb7a6dcb5))


## v0.45.0 (2026-05-03)

### Chore

* chore(sim): lint cleanup + docstring note on per-symbol aggregation

Drops 3 ruff errors introduced in the prior commit and notes the
per-symbol granularity for the chip&#39;s account pre-fill in the helper
docstring.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`53e4c97`](https://github.com/chen-star/net_alpha/commit/53e4c972328d6910a08c3a266f538b7c32d1219f))

### Documentation

* docs(ui): update Unrealized P/L tooltip for short-option intrinsic floor

Calls out that short options (sold puts and covered calls) are
estimated rather than marked-to-market, so the user knows the number
isn&#39;t broker-verified.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`60cc9ba`](https://github.com/chen-star/net_alpha/commit/60cc9bae1dbba05d602c8d8241612c680a928ac0))

* docs(ui): update Total Return tooltip + suffix for Period P/L semantics

Tooltip now describes ending − starting − contributions; the small text
under the number reads &#34;% vs starting + contributed&#34; to match the new
denominator.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`8d2a3b0`](https://github.com/chen-star/net_alpha/commit/8d2a3b081e7f7ae699b74e790ef1c27d46faa5d8))

### Feature

* feat(ui): paginate harvest plan rows

Add page/page_size params to /tax/harvest/plan; keep full rows list
for summary metrics and pass rows_page slice + picks to template so
manual pick checkboxes survive page transitions.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`c8ade85`](https://github.com/chen-star/net_alpha/commit/c8ade858c88920a4a5ad2b97af8d425bd8c5fd98))

* feat(ui): paginate open-options table

Add page/page_size params to /holdings/options; pass selected_account
to template so pagination links preserve account filter context.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`4c836b2`](https://github.com/chen-star/net_alpha/commit/4c836b2c0eee4088295696f78c00f0a0120f20b1))

* feat(ui): paginate closed-positions view

Add page/page_size query params (default 25/page) to the closed tab;
sort by closed_date desc so most recent lots appear on page 1.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`df6b38d`](https://github.com/chen-star/net_alpha/commit/df6b38d4e98487ebefdf937455f50a354c1129f9))

* feat(ui): add ? icons to Total Return and Unrealized P/L KPI tiles

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`e2d7289`](https://github.com/chen-star/net_alpha/commit/e2d728985f76a87a8d0854f9d782decc4939a6b5))

* feat(ui): explain Jinja templates for Total Return and Unrealized P/L

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`40e269b`](https://github.com/chen-star/net_alpha/commit/40e269b9d13daf01dfd06f20f3de558d12d51d5c))

* feat(web): explain endpoints for Total Return and Unrealized P/L

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`06609bb`](https://github.com/chen-star/net_alpha/commit/06609bb337d5b2cb4eb3a5e8b037eb3da3e647a3))

* feat(portfolio): explain.py — pure-function payloads for KPI explainer panels

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`4e35bca`](https://github.com/chen-star/net_alpha/commit/4e35bca0a92dc5404e5ee5d4f570693b6c000e10))

* feat(portfolio): intrinsic-value floor for short option unrealized P/L

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`a2b2a5c`](https://github.com/chen-star/net_alpha/commit/a2b2a5cd507c63b1bb1c25dc14bb937d2308f0af))

* feat(ui): stop computing SPY benchmark series for equity curve

Drops the build_benchmark_series import and replaces both call sites
(equity-curve fragment route + portfolio_body) with an empty list.
Template still receives benchmark_points/benchmark_symbol context vars
(no usage now); benchmark.py module is kept in case a future feature
wants it.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`4e3feee`](https://github.com/chen-star/net_alpha/commit/4e3feeed05f4b0bbaa89698eb32bcc3a31ce6319))

* feat(ui): remove SPY benchmark line from equity curve

User feedback: the SPY (buy &amp; hold) overlay distracts from the user&#39;s
own equity curve. Drop the series push and the matching color/width
extension; benchmark_points and benchmark_symbol context vars are still
passed but unused, so the route can stop computing the series in a
follow-up commit.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`0af70d3`](https://github.com/chen-star/net_alpha/commit/0af70d39606c7f9e8b017eb11d3d14037c31b76c))

* feat(portfolio): wire period-start value into Total Return

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`a9fe50a`](https://github.com/chen-star/net_alpha/commit/a9fe50aebe24e75058c5ba9a5443ba9e76b3198b))

* feat(portfolio): account_value_at — pinned period-start lookup

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`6ebf9f1`](https://github.com/chen-star/net_alpha/commit/6ebf9f1acd64343b8fa63ce37c145d5079718a1f))

* feat(portfolio): switch Total Return to Period P/L formula

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`d0f7067`](https://github.com/chen-star/net_alpha/commit/d0f7067bc883e0e20a8b051fd4cac4d37b5411fc))

* feat(portfolio): add period_starting_value field to CashFlowKPIs

Defaults to 0 so existing call sites are unaffected. Will carry the
account value at the period boundary for the upcoming Period P/L
formula change to Total Return.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`39c7f35`](https://github.com/chen-star/net_alpha/commit/39c7f35f440ef26ef5745c920487aa555ce1abed))

### Fix

* fix(ui): GL-closure parity in explainer + total counts in pagination headers

- explain_unrealized now passes gl_closures + gl_option_closures (account-
  scoped) to consume_lots_fifo, matching the compute_kpis path so the
  explainer panel total agrees with the KPI tile when Realized G/L CSVs
  are imported
- positions closed-view header now reads closed_rows_total (pre-slice) so
  the &#34;N closed lots&#34; count reflects all pages, not just the current page
- holdings/options summary header now reads option_counts dict (pre-slice)
  so long/short totals are correct across pages
- Added tests: test_portfolio_explain.py (GL-closure wiring smoke) and a
  pagination-header assertion in test_positions_view_closed.py

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`22f9065`](https://github.com/chen-star/net_alpha/commit/22f9065094996cbf35b60a53117a441d659e2ebc))

* fix(sim): consume FIFO so closed positions drop from suggestion chips

Replaces the raw lot-quantity sum in the /sim/suggestions handler with
_build_sim_positions(), which delegates to compute_open_positions() for
proper FIFO consumption. Fully-sold positions like WOLF now have qty=0
after FIFO and are excluded; the loss-closes block retains its own quote
lookup for recently-closed tickers.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`c2f3691`](https://github.com/chen-star/net_alpha/commit/c2f3691c6e939a8172d07f71225d62354db131f6))

* fix(portfolio): thread GL closures into short-option unrealized adjustment

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`52fbe5b`](https://github.com/chen-star/net_alpha/commit/52fbe5bbeefdd12407e014203bb0d4b5a083727e))

* fix(portfolio): forward-fill equity curve through today when quotes lag

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`d71d616`](https://github.com/chen-star/net_alpha/commit/d71d616addca2905b5b4fd112c7b59abbf3c9b03))

### Style

* style: ruff format + ruff lint cleanup across new code

Auto-fix from ruff format + ruff check --fix on the files modified by
the portfolio KPI clarity work. uv.lock sync to match committed
version 0.44.0.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`89c00d5`](https://github.com/chen-star/net_alpha/commit/89c00d5d27ce591b63d035b7a191db7901f9995f))

* style: ruff format tests/portfolio/test_pnl.py

Restores CI lint step (failed since 225ac25) by reflowing long lot/trade
constructor calls onto one argument per line.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`ee0e6f8`](https://github.com/chen-star/net_alpha/commit/ee0e6f89c74138a09546f652c791c1c94eed55cb))

### Test

* test(web): failing test for closed-position filter in sim chips

Adds test_fully_closed_position_does_not_appear_in_chips: seeds a WOLF
buy+full-sell plus an open SQQQ position, then asserts _build_sim_positions
(via stub pricing) excludes WOLF and includes SQQQ.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`b6122c1`](https://github.com/chen-star/net_alpha/commit/b6122c13f208a361c0a2eea57422442ea9a8efb0))

* test(portfolio): cover explain payload builders for long, short, and excluded cases

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`31ba2ec`](https://github.com/chen-star/net_alpha/commit/31ba2ec75b84138fef10a0a33f06d8fccf563ee2))

* test(portfolio): failing tests for short option intrinsic-value floor

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`2ec14bf`](https://github.com/chen-star/net_alpha/commit/2ec14bfe76ee698a3571954e34fa86b76054ed57))

* test(portfolio): cover account_value_at silent-skip + expired-option behavior

Add three missing tests for account_value_at: equity lot pricing via
get_close, silent-skip of unpriced lots (vs holdings_value_at returning
None), and expired-option contribution of zero. Also fix stale docstring
on CashFlowKPIs.growth_pct to reflect the actual denominator guard.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`6ac630c`](https://github.com/chen-star/net_alpha/commit/6ac630cbef0adb61ab5d8e26df350ed2c638edb4))

* test(portfolio): add failing tests for period-aware Total Return

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ef4a7f7`](https://github.com/chen-star/net_alpha/commit/ef4a7f7aed7a2122a6cba31114a97a762945a05d))


## v0.44.0 (2026-05-03)

### Chore

* chore: sync uv.lock to v0.43.0

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`b56dc4b`](https://github.com/chen-star/net_alpha/commit/b56dc4b0932c00de57c84f2fff41c747205058ec))

### Feature

* feat(portfolio): include option basis &amp; treat sweeps as cash-neutral

Two reconciliation gaps closed so the headline Total Account Value
tracks Schwab&#39;s combined account total:

1. compute_kpis now includes unexpired option lots in
   open_position_value, carried at basis (no live option-quote provider
   exists — basis-carry contributes equally to market AND basis so
   unrealized stays $0, the only honest answer without true marks).
   Expired option lots still contribute nothing.

2. sweep_in / sweep_out events move money between Schwab1 brokerage
   cash and the Schwab Futures sub-account; both are part of the same
   account total. Treating them as real cash flows made our combined
   cash balance drift from Schwab as cash sloshed between sub-accounts.
   They&#39;re now cash-neutral in cash_flow.py and excluded from the cash
   provenance trace.

On a real account with 7 open option lots and ~$4.6k parked in
futures, this brings net-alpha&#39;s reported total from $33,611 to
$43,798 versus Schwab&#39;s reported ~$43,000. ([`225ac25`](https://github.com/chen-star/net_alpha/commit/225ac255649de2239631e2abcba596ed7e266ee1))

### Fix

* fix(portfolio): correct account-value reporting on KPI strip &amp; equity curve

Three independent bugs were inflating the headline Total Account Value
and the equity curve series for users with active trade history:

1. compute_kpis (pnl._open_market_and_basis) iterated raw Lot rows,
   but lot.quantity is the original buy size and is never decremented
   when the user sells — only consume_lots_fifo applies that in memory.
   Wire compute_kpis through consume_lots_fifo (with imported Realized
   G/L closures) so open_position_value and today_change reflect only
   the still-open portion.

2. portfolio_kpis (web/routes/portfolio.py) pre-scoped cash_events to
   the selected account but passed unscoped trades to compute_cash_kpis
   — leaking every other account&#39;s trade cash impact into the displayed
   cash balance. Pre-scope trades the same way the body &amp; equity-curve
   handlers already do.

3. holdings_value_at carried option lots at basis indefinitely, even
   after expiry. Schwab logs option expirations only in Realized G/L,
   so users who didn&#39;t import the GL CSV saw the original premium
   accumulate forever in the equity curve. Treat option lots whose
   expiry &lt; as_of date as worth $0.

Tests added for each: Buy-then-Sell FIFO, GL-only closure, and
post-expiry option carry. ([`ae2b683`](https://github.com/chen-star/net_alpha/commit/ae2b683d2276f670ef05c01a89b8c43b262c7d58))

### Refactor

* refactor(web): use cached tax config for inbox; clarify body comment

- _resolve_inbox_rates now takes the TaxConfig from
  request.app.state.tax_brackets_cfg (loaded once at startup, refreshed
  by /settings POST) instead of re-reading config.yaml per request.
  Aligns the inbox with the rest of the dashboard&#39;s tax projections so
  rate updates surface uniformly.
- Update _portfolio_body.html comment to acknowledge the Action Inbox
  alongside the historical wash-watch-moved note.
- Sync uv.lock with the 0.42.1 version bump that landed upstream.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`e86d9e8`](https://github.com/chen-star/net_alpha/commit/e86d9e8cfda0a9e00ac7e8d6a7d5832b32ead498))


## v0.43.0 (2026-05-03)

### Feature

* feat(web): add Action Inbox panel to portfolio dashboard

Replace the stub _portfolio_inbox.html with the full production template
(severity sections, Alpine toggle, dismiss buttons, deep links, dollar
impact). Wire up the lazy-load wrapper in _portfolio_body.html above the
KPIs row, and pass `account` into the portfolio_body route context so
the inbox fragment honours the active account filter.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`6cf7a62`](https://github.com/chen-star/net_alpha/commit/6cf7a62f58784bab41630d4a0d2d0ceaaff5b26e))

* feat(web): add /portfolio/inbox GET and dismiss endpoints

Registers two new routes on the portfolio router: GET /portfolio/inbox
renders an HTMX fragment via a minimal stub template (_portfolio_inbox.html),
and POST /portfolio/inbox/dismiss/{dismiss_key:path} toggles dismissal state
then re-renders the panel. Adds load_tax_config integration to derive ST/LT
rates for gather_inbox, and three passing route tests.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`9e57253`](https://github.com/chen-star/net_alpha/commit/9e572534009e62ae46953f49c33f1b6907b3b57f))

* feat(inbox): add aggregator with severity-then-distance-then-dollars sort

Implements gather_inbox() — the single entry point the web layer calls to
collect all inbox signals, sweep orphaned dismissals, filter dismissed
items, and return a sorted list (URGENT → WATCH → INFO, then |days_until|
ascending, then |dollar_impact| descending). Five tests green.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`015e81b`](https://github.com/chen-star/net_alpha/commit/015e81bc0d085387ee6798b90ae5ae1348d2f74c))

* feat(inbox): add InboxConfig loader with safe defaults

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`5813ae8`](https://github.com/chen-star/net_alpha/commit/5813ae8275ee3073586d3ece76d38fc010755786))

* feat(inbox): add dismissals module with toggle and orphan sweep

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`0aee7cf`](https://github.com/chen-star/net_alpha/commit/0aee7cf6f8c758a70e6f0fac801cca3706d9806c))

* feat(inbox): add option expiry + assignment risk signals

Implements O2 (OPTION_EXPIRY) and O4 (ASSIGNMENT_RISK) signal computers
in a single module. Short ITM lots within the 7-day assignment window
emit both items with independent dismiss keys; long lots never produce
assignment-risk items. Also adds a default for make_lot&#39;s `acquired`
parameter in the shared test conftest.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`afe3ac5`](https://github.com/chen-star/net_alpha/commit/afe3ac59c91171743b98f934ef973d397cf30480))

* feat(inbox): add LT eligibility countdown signal

Implements the H3 signal: walks open equity lots, emits an InboxItem for
any lot whose 1-year LT threshold falls within the lookahead window and
has positive unrealized gain, with dollar_impact = gain × (ST − LT rate).

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`afaaf57`](https://github.com/chen-star/net_alpha/commit/afaaf576481475a52fecaac17f2c32c23fdefcff))

* feat(inbox): add wash safe-to-rebuy signal

Implement compute_wash_rebuy (H2 signal) as a pure function over
repo.all_violations(). Emits an InboxItem on day 31 after the loss sale
date and keeps it visible for DEFAULT_VISIBLE_DAYS (14) afterward.
Also adds the shared tests/inbox/conftest.py fixture helpers for Tasks 4–5.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`8322fcd`](https://github.com/chen-star/net_alpha/commit/8322fcdaf70c257cfcdc08eb0a5eda14f7ba851f))

* feat(inbox): add InboxItem, SignalType, Severity view models

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`7699f69`](https://github.com/chen-star/net_alpha/commit/7699f69b10cd79200e3ab6858685bf1294a983be))

* feat(db): add DismissedInboxItemRow SQLModel for fresh-DB support

Adds DismissedInboxItemRow to tables.py so SQLModel.metadata.create_all()
creates dismissed_inbox_items on fresh installs (the current==0 path in
migrate()). Adds test_v15_table_exists_on_fresh_db to verify this path.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`1f3c1e3`](https://github.com/chen-star/net_alpha/commit/1f3c1e30060d54467d2a04ca6a9729d060ab4ed3))

* feat(db): add v15 migration for dismissed_inbox_items

Adds the dismissed_inbox_items table (dismiss_key PK + dismissed_at
ISO timestamp) needed by the Action Inbox panel to persist per-item
dismissals across requests. Bumps CURRENT_SCHEMA_VERSION to 15 and
updates the two version-literal assertions in the existing test suite.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`77275a9`](https://github.com/chen-star/net_alpha/commit/77275a946a25fda82292a1b66e819d442324a046))

### Fix

* fix(web): drop unused repo arg; add account-filter route test

Remove the unused `repo` parameter from `_resolve_inbox_rates` (it reads
from disk only) and update the single call site in `portfolio_inbox`.
Add `test_account_filter_query_param_scopes_signals` to verify that
`?account=X` correctly narrows inbox signals to the requested account.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`aca525a`](https://github.com/chen-star/net_alpha/commit/aca525afd06f350811dd9fd24022a1a7ee51875b))

* fix(inbox): fall back to defaults when config values violate constraints

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`5ee8860`](https://github.com/chen-star/net_alpha/commit/5ee8860f2d1d27f7fe6c7ae63b77ef92459343c1))

### Test

* test(inbox): cover boundary + account-filter cases; tighten quote check

- Drop getattr guard on quote.price in lt_eligibility and option_expiry;
  Quote always has .price — match the pattern used elsewhere in the codebase.
- Tighten wash_rebuy account filter to `account is not None` to match the
  `account is None or ...` pattern used in the other two signal modules.
- Add comment on OPTION_EXPIRY extras payload noting template dependency.
- Add 7 new tests: 366-day boundary and account-filter for lt_eligibility;
  14d/1d severity boundaries and account-filter for option_expiry;
  account-filter (exclude + cross-account buy) for wash_rebuy.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`3ea5bcf`](https://github.com/chen-star/net_alpha/commit/3ea5bcf0cf270e886b441f2e5d4468b165db4f20))

* test(db): rename misleading v14 test; comment UTC on dismissed_at

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`aa6172f`](https://github.com/chen-star/net_alpha/commit/aa6172fe35b0a0c30dde891daf96fdc0ac515a6e))

### Unknown

* Merge branch &#39;feat/action-inbox&#39;: Action Inbox panel (4 time-aware signals) ([`5ced4af`](https://github.com/chen-star/net_alpha/commit/5ced4af0f9a95e602474fdbae8019a9c8b54ab83))


## v0.42.1 (2026-05-03)

### Fix

* fix(portfolio): bulk-prefetch historical closes so Lifetime view loads

The Lifetime equity-curve / benchmark builders fanned out to
O(eval_dates × tickers × 7) sequential yfinance calls — ~12K rate-limited
requests for a multi-year, ~30-ticker portfolio. The HTMX /portfolio/body
fragment never returned, leaving the Overview page body empty.

Add a bulk get_historical_closes(symbol, start, end) provider API plus
PricingService.warm_historical_range, called from portfolio_body and
portfolio_equity_curve before the curve build. Collapses ~12K calls into
~30 (one per symbol). Provider failure returns None so we don&#39;t poison
the cache with negatives during a rate-limit episode.

Also: register HistoricalPriceCacheRow as a SQLModel so fresh DBs (test
fixtures + new prod installs) actually create the table — migrate()
short-circuits at schema_version=0 instead of running v11→v12. Switch
test fixtures to init_db() to mirror production schema.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`b8abdf1`](https://github.com/chen-star/net_alpha/commit/b8abdf1df54d866f1acffbf963c98fc2f8edf3ba))

### Refactor

* refactor(portfolio): drop legacy equity_curve module

Removes the realized-only build_equity_curve helper, its EquityPoint view
model (and orphan total_pl field), the dead points/year wiring in
portfolio_body, and the test file. The new build_account_value_series
fully replaced these in the equity-curve redesign.

Also resyncs uv.lock to v0.41.2 and rewords two doc strings that
referenced the removed types.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`cba2df0`](https://github.com/chen-star/net_alpha/commit/cba2df0b018b093c8db69abdad4c2434d9015ca3))

### Style

* style: apply ruff format

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`4d2edf0`](https://github.com/chen-star/net_alpha/commit/4d2edf0798bb44b1832bcbe57d942f7f81d0a607))

### Unknown

* Merge remote-tracking branch &#39;origin/master&#39; ([`7e9f310`](https://github.com/chen-star/net_alpha/commit/7e9f31030e419dd3bbd62d226fcde4da8163b9ab))


## v0.42.0 (2026-05-02)

### Chore

* chore: consolidate test imports + ruff format

Imports were scattered through the file as each task added them at the
bottom of its own block. Ruff flagged E402 (mid-file imports), I001
(unsorted), and F811 (duplicate AccountValuePoint). Consolidates all
imports at the top and reformats per ruff defaults.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`bced4bb`](https://github.com/chen-star/net_alpha/commit/bced4bbfceec36bcec4e9349021a8d5d467c4466))

### Feature

* feat(web): wire account-value equity curve into /portfolio/equity-curve

Replaces the realized-P&amp;L-anchored equity curve with the redesigned
account-value series (cumulative contributions baseline, account_value
area, optional benchmark overlay). Both the dedicated /portfolio/equity-curve
fragment route and the bundled /portfolio/body fragment now feed
account_points + period_label into the rewritten template.

Updates the test_phase3_charts fixture to seed a deposit cash event so
the chart actually renders (the new curve anchors on contributions, not
trades alone).

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`59f7e37`](https://github.com/chen-star/net_alpha/commit/59f7e378d44bc8941ccfb7287631df8e2d32cb5b))

* feat(portfolio): orchestrate build_account_value_series

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`f169ab7`](https://github.com/chen-star/net_alpha/commit/f169ab776ce1924e94adcf1e96e11f5a8f854a60))

* feat(portfolio): add holdings_value_at helper with forward-fill

Implements marked-to-market valuation of open lots as of an arbitrary
historical date D: equities priced at rem_qty × close with 7-day back-fill,
option lots carried at adjusted_basis, returns (None, missing_tickers) when
any equity close is unresolvable.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`08f2d6c`](https://github.com/chen-star/net_alpha/commit/08f2d6c9ce9a6476f2965b4d6c7f871b457adc3f))

* feat(portfolio): add eval_dates sampler for equity curve

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`404398a`](https://github.com/chen-star/net_alpha/commit/404398ad3bfd01900b30e324a940f21e7f0391c5))

* feat(portfolio): add AccountValuePoint view model

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`f5e1bfd`](https://github.com/chen-star/net_alpha/commit/f5e1bfd77379fa5b928f4f838004e6d0596aa218))

### Unknown

* Merge remote-tracking branch &#39;origin/master&#39; ([`7f578ae`](https://github.com/chen-star/net_alpha/commit/7f578ae74ff7e602d9f0e93bf29fcb0e681a0aae))

* Merge feat/equity-curve-redesign: account-value chart

# Conflicts:
#	src/net_alpha/web/routes/portfolio.py ([`9838109`](https://github.com/chen-star/net_alpha/commit/98381097febc835366512fc11a76c25888e86477))

* wip: positions search pagination, ticker pagination, P&amp;L economic split

Bundles in-progress work across three themes that had been accumulating
on master:

* Positions: free-form search now hits the server (`q=` param on
  /portfolio/positions) so matches on later pages surface, not just
  client-side DOM rows. Static JS debounces a server refresh in addition
  to the existing client filter.
* Ticker drilldown: paginated lots + timeline views (PAGE_SIZE=25 default)
  with a new `_ticker_pagination.html` partial.
* Realized P&amp;L: separate &#34;economic&#34; (cash-netted) vs &#34;recognized&#34;
  (Schwab/Form-8949) numbers on KpiSet, surfaced on the Realized tile.
  Pairing logic learns Schwab&#39;s T+1/T+4 settlement offsets via a new
  `_GL_PAIR_DAY_TOLERANCE = 4` constant.
* Misc: gl_lots threading through compute_kpis/positions for a more
  faithful KPI baseline, AGENTS.md and CLAUDE.md tweaks.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`9dcff8e`](https://github.com/chen-star/net_alpha/commit/9dcff8ee509f6a53f05bcfe56bc5b9f2c893c093))


## v0.41.2 (2026-05-02)

### Chore

* chore: regen lock for 0.41.1 ([`1946708`](https://github.com/chen-star/net_alpha/commit/19467089ec156e0372fa5d7ce1fde0e5e8c6cdde))

### Fix

* fix(portfolio): correct realized P&amp;L pairing, transfer basis, and account chips

Three independent fixes plus pre-existing transfer-basis editor work:

1. realized_pl_from_trades now distributes STO premium across multiple
   BTCs by contract quantity. Previously each BTC subtracted its cost
   from the *full* STO premium for the (account, ticker, strike, expiry,
   cp) key, so a 2-contract STO closed by two 1-contract BTCs credited
   the entire premium twice — inflating lifetime realized P&amp;L. Verified
   against Schwab&#39;s Realized G/L: TSLA lifetime drops from $3,841.11 to
   $2,142.44 to match.

2. compute_open_positions splits accounts into open vs all-trade maps so
   the position-row chip on the Open tab reflects only currently-held
   accounts, not historical churn. NVDA YTD only in `lt` no longer
   surfaces `lt+st`.

3. update_trade_basis preserves basis_source for transfer rows
   (transfer_in / transfer_out) and flips transfer_basis_user_set=True
   instead of clobbering basis_source to &#34;user_set&#34;. The clobber was
   making compute_open_positions count user-set transfer basis as cash
   buys, inflating Cash invested / sh.

4. Schema migration v14 retroactively restores basis_source on existing
   transfer rows where transfer_date is set but basis_source had been
   clobbered to user_set by the old single-lot save path.

5. Imports page and positions-pane set-basis multi-lot editor refinements
   that were already in flight, included for cohesion since they share
   the transfer-basis editing surface.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`9001a08`](https://github.com/chen-star/net_alpha/commit/9001a08d372f6ba151be7dee10cc2ac79753dced))


## v0.41.1 (2026-05-02)

### Fix

* fix(web): unbreak imports drop-zone and delete; add bulk delete

The settings drawer&#39;s Imports tab loader uses hx-select=&#34;main &gt; *&#34;
to strip page chrome from the lazy-loaded /imports/_legacy_page.
HTMX inherits hx-* attributes to descendants, so the drop-zone&#39;s
preview POST and the per-row delete DELETE inherited that selector
too. Their responses are bare HTML fragments without a &lt;main&gt;
wrapper, so the inherited selector matched nothing and HTMX
silently swapped empty content into the target — blanking the
import modal (broken file import) and the imports table (delete
appeared to wipe the page). Adding hx-disinherit=&#34;hx-select&#34; on
the loader keeps children using their own targets verbatim.

While in there, added a POST /imports/bulk-delete endpoint plus
per-row checkboxes and a &#34;Delete N selected&#34; submit button so
users can clear multiple imports without N confirms. The bulk
endpoint batches the wash-sale recompute (one re-stitch per
affected account, one global recompute) instead of doing it per
removed import.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`104d70b`](https://github.com/chen-star/net_alpha/commit/104d70b9dd5809270cfcb40797376e52296db3ce))


## v0.41.0 (2026-05-02)

### Feature

* feat(web): add light mode with Light/Dark/System toggle

Adds a full light theme alongside the existing Apple HIG dark theme.
Users pick Light, Dark, or System (follow OS preference) from the new
Appearance section in the settings drawer&#39;s Density tab.

Theme tokens are CSS custom properties under [data-theme=&#34;light&#34;] /
[data-theme=&#34;dark&#34;] selectors; Tailwind utilities resolve via var() so
the entire UI flips at runtime. Light palette uses Apple&#39;s documented
HIG light-mode equivalents (systemRed.light, systemGreen.light, etc.)
that meet WCAG AA on a white surface.

The preference persists per-account via the existing user_preferences
pipeline (new theme column, v12 → v13 migration). A localStorage shadow
plus an inline FOUC-prevention script in &lt;head&gt; resolves and applies
data-theme before stylesheets load, so first paint is always correct.
A matchMedia listener re-applies on OS-pref flips when System is
selected.

ApexCharts re-renders on theme change: charts.js now reads colors live
from CSS vars via getComputedStyle and exposes a render-function
registry that refresh()es on the theme:change window event the toggle
dispatches.

Sweep replaces ~150 hardcoded literal colors across 30+ templates with
the matching tokens (rgba hairlines → --color-hairline; bg-black/X
backdrops → --color-overlay-*; inline semantic hex → var(--color-pos)
etc.; 6 .invert opacity-XX icons → new .icon-mono utility).

11 new tests cover: v13 migration (column add, default, idempotent,
schema bump) and the /preferences route (theme write, default, invalid
rejection). Existing schema-version assertions bumped from 12 to 13.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`894be54`](https://github.com/chen-star/net_alpha/commit/894be542083205e75e243c2675e8e23b6591472b))


## v0.40.1 (2026-05-01)

### Chore

* chore: scrub references to internal docs/ folder

Removes dangling pointers to docs/superpowers/specs/* now that the docs/
folder is local-only and excluded from the repo.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`16be1ab`](https://github.com/chen-star/net_alpha/commit/16be1ab8871174c447e0171ca5dcaa6f83a67ee9))

* chore: scrub AI scaffolding, generated artifacts, and internal docs from repo

Untracks third-party AI skill plugin data (.agent/, .agents/, .junie/,
.claude/skills/), generated coverage report, skills-lock.json, the empty
&#39;nano&#39; file, and the docs/ folder. Adds matching .gitignore entries plus
.DS_Store / editor swap files. Files remain on local disk; only their
tracked status is removed.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`3048af6`](https://github.com/chen-star/net_alpha/commit/3048af6f66bc4f4200359790e0089268c5fb6626))

### Documentation

* docs: add monthly PyPI downloads badge to README

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`fe82b14`](https://github.com/chen-star/net_alpha/commit/fe82b14c4819c938cb342f338a315d574e43e3fd))

* docs: refresh AGENTS.md, CLAUDE.md, README.md for v0.40.0

Bring the top-level docs in line with the matured codebase: web UI is now
the primary interactive surface (Portfolio, Positions, Tax, Sim, Imports,
Ticker, Settings), with new subsystems (audit/reconciliation, splits,
position targets, harvest planner, sim suggestions, manual trade CRUD).

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`e736073`](https://github.com/chen-star/net_alpha/commit/e736073709a655428e246f100c70ec646682a39c))

### Fix

* fix(packaging): add PyPI metadata, project URLs, and LICENSE

The PyPI page rendered with no README, no GitHub link, and no classifiers,
giving installers no path back to the repo. Adds:

- readme = &#34;README.md&#34; so the full README renders on PyPI
- [project.urls] with Homepage / Repository / Issues / Changelog so the
  PyPI sidebar links to GitHub
- license, classifiers, and keywords for category browsing and search
- LICENSE file to back the MIT claim in the README
- Star call-to-action near the top of the README to convert PyPI readers

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`cfdc046`](https://github.com/chen-star/net_alpha/commit/cfdc046b47ae395f1e3886663fa61bf6beddbfaa))


## v0.40.0 (2026-04-30)

### Feature

* feat(web): resizable settings drawer, chart zoom, positions search, sim form alignment

- Settings drawer: drag the left edge to expand/shrink. Persists to
  localStorage; clamped to [360px, viewport-60px]. Handle uses inline
  styles so it doesn&#39;t depend on Tailwind utility compilation.
- Equity / Cash &amp; Contributions charts: enabled ApexCharts&#39; zoom toolbar
  (drag-select range, zoom in/out, pan, reset) with autoScaleYaxis.
- Positions page: new symbol-search box above the tabs filters every row
  (stocks, options, at-loss, closed, plan) by underlying ticker. Options
  match on the underlying so typing &#39;SPY&#39; shows every contract on it.
  Filter survives HTMX swaps via htmx:afterSwap.
- Sim form: switched to items-start with fixed label/input heights so all
  six controls share the same horizontal baseline regardless of helper-text
  presence.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`ee2006c`](https://github.com/chen-star/net_alpha/commit/ee2006cc111eaf3a060dd0646cff14af8e7a4d04))

* feat(web): wire real content into Profile / ETF pairs / About drawer tabs

Replaces the Phase 1 &#39;Coming soon&#39; placeholders with the per-tab templates,
exposes app_version / data_dir_path / pricing_remote_enabled / etf_pairs_data
to Jinja so the new tabs can render, and updates the regression test to assert
real content rather than placeholder copy. Also bumps GitNexus index counts
in AGENTS.md / CLAUDE.md.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`9227416`](https://github.com/chen-star/net_alpha/commit/92274160b1b5a169192dd826da635509f871b84f))

### Refactor

* refactor(planner+web): ORDINARY_LOSS_CAP constant + pass tax_saved as dict

Extract the §1211 $3,000 ordinary-income-offset cap into a named constant
ORDINARY_LOSS_CAP (replacing 4 bare Decimal(&#34;3000&#34;) literals). Replace the
fragile Pydantic __dict__ smuggle in the harvest_plan endpoint with a plain
parallel dict passed explicitly to the template context.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`3b99eeb`](https://github.com/chen-star/net_alpha/commit/3b99eebda54cb71058551cd7cb020206ae1fa42d))


## v0.39.0 (2026-04-30)

### Feature

* feat(web): collapsible data-quality groups on Imports page

Groups hygiene warnings into Missing basis / No price quote / Missing dates
buckets rendered as &lt;details&gt; elements; the first non-empty bucket is open
by default, replacing the previous flat list in _data_hygiene.html.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`f8acc78`](https://github.com/chen-star/net_alpha/commit/f8acc786b30f2ce59fcc633fbc99be50e8ea665a))

* feat(web): per-row &#39;Simulate sale&#39; action on Holdings

Adds a visible ↗ Sim link to each row in the Holdings table that opens
the Sim page prefilled with ticker, qty, action=sell, and derived price
(market_value ÷ qty when available), making Sim discoverable from the
table without opening the row-actions menu.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`b0c2846`](https://github.com/chen-star/net_alpha/commit/b0c2846d669b3fda0f170aa41a07cf1bc9fe5eb1))

* feat(web): /sim/suggestions chip strip on Sim page

Add GET /sim/suggestions HTMX fragment endpoint that surfaces up to 3
one-click prefill chips (largest loss, wash-sale risk, largest gain)
above the sim form; falls back to a demo TSLA chip on empty DB.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`4d11259`](https://github.com/chen-star/net_alpha/commit/4d1125947c8a5564023c711f2a2aa82a92071e8c))

* feat(sim): top_suggestions pure-function chip picker

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`e69c385`](https://github.com/chen-star/net_alpha/commit/e69c38594d40ca6d0be4eb96274c39f946400fc1))


## v0.38.0 (2026-04-30)

### Feature

* feat(web): /tax/harvest/plan endpoint + lazy-load on at-loss view

Wire up GET /tax/harvest/plan that builds a HarvestPlan and renders the
_harvest_plan.html fragment. Embed it into _positions_view_at_loss.html
via an HTMX loading shell replacing the old inline table. Updated two
compat test files (test_phase1_positions_tabs, test_phase2_at_loss_columns)
to hit the new fragment endpoint directly since headers are now lazy-loaded.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ad4fa93`](https://github.com/chen-star/net_alpha/commit/ad4fa933f2fc9dfb2695335a9b5650e6fe00933f))

* feat(web): _harvest_plan.html fragment skeleton ([`79bc178`](https://github.com/chen-star/net_alpha/commit/79bc178afd9a3bd1123d8a04185be9b1ed6f7021))

* feat(planner): summarize_manual_picks for user-edited selection

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`b0fa656`](https://github.com/chen-star/net_alpha/commit/b0fa6569ec25728b264f2c987e151f69c391f6b4))

* feat(planner): build_plan greedy harvest planner with tax-saved ranking

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ccb4e7a`](https://github.com/chen-star/net_alpha/commit/ccb4e7ac6bc0841a53e7a0db4091c4df19429b69))


## v0.37.1 (2026-04-30)

### Feature

* feat(web): aria-label and tooltip on top-nav imports badge ([`d6de0d3`](https://github.com/chen-star/net_alpha/commit/d6de0d34e943264be8682470765e9ed5bdd3fae3))

* feat(web): per-input helper text and missing-quote message on Sim form ([`b17349a`](https://github.com/chen-star/net_alpha/commit/b17349ab17cde3093cace76cdce6141bfc6c4bc2))

* feat(web): inline tax-projection setup form replacing YAML snippet

Replaces the legacy YAML-snippet copy-paste flow with a styled inline
form using the app&#39;s standard classes (panel, label-uc, bg-surface,
btn). Makes state and state_marginal_rate optional (blank = federal-only)
to match TaxConfig defaults. Route handler updated to use Form(&#34;&#34;) /
Form(0.0) defaults accordingly.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`de22f61`](https://github.com/chen-star/net_alpha/commit/de22f61eb940e21254fe3e6cecce7210342c7a0e))

* feat(web): default Harvest queue to &#39;currently harvestable only&#39;

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`19a8cc7`](https://github.com/chen-star/net_alpha/commit/19a8cc7dcf9d0f3f9f0dd5b91e93811dc8779321))

* feat(web): tooltip on wash-sale &#39;to safe&#39; countdown ([`a1e8421`](https://github.com/chen-star/net_alpha/commit/a1e8421dfa7321e0eefd3571ad7d345415af4a37))

* feat(web): explain Watch vs Violations on the Tax wash-sales tab

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`38f14de`](https://github.com/chen-star/net_alpha/commit/38f14de7a09aeb991d5f00b5132438cf038d8cc2))

* feat(web): collapse wash-sales filter behind a Filter summary

Wraps the filter form in a &lt;details&gt; element that is collapsed by default and auto-opens only when at least one filter is explicitly set by the user (ticker, account, confidence, or year). Adds filter_year_explicit context flag to _wash_sales_context so the template can distinguish a user-chosen year from the default current-year preset. Applied to both wash_sales.html and _tax_wash_sales_tab.html (the live route).

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`8061aa0`](https://github.com/chen-star/net_alpha/commit/8061aa0b4afe4c7f52ed5de4ef185bbaf2d726fe))

* feat(web): sticky thead on remaining Holdings/Portfolio table fragments ([`dd74df0`](https://github.com/chen-star/net_alpha/commit/dd74df056c3e216225f4cd6776c952019f55ec32))

* feat(web): sticky thead on Positions table views

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`2c0efa2`](https://github.com/chen-star/net_alpha/commit/2c0efa2f1ebc198ad914bf190d2cee1180b80851))

* feat(web): add tooltips to remaining four portfolio KPI tiles

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`639edd2`](https://github.com/chen-star/net_alpha/commit/639edd29b739e4ec0eb8264c53bb7b8e849d5979))

### Test

* test(web): pin Holdings tab links to Options and Stocks views

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`5ae21e8`](https://github.com/chen-star/net_alpha/commit/5ae21e84ea43279eca6ce24c9537f66f888b6fd6))


## v0.37.0 (2026-04-30)

### Feature

* feat(web): add cash-vs-positions deployment chart on Overview

Splits the allocation row into a 2/3 + 1/3 grid. The new right-hand
panel shows a hero &#34;% deployed&#34; stat, a stacked bar (Positions ·
Cash·free · Cash·pledged), and a per-segment legend so the user can
read deployment ratio at a glance — the data already lived on
AllocationView, so no compute changes.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`3e6598e`](https://github.com/chen-star/net_alpha/commit/3e6598e6719c36a70d787dbbdd9308331eebc133))


## v0.36.0 (2026-04-30)

### Feature

* feat(web): show Total planned in Plan tab footer

Adds a Total planned figure (sum of every target&#39;s USD equivalent) to
the Plan tab footer alongside Total to fill / Free cash / Coverage so
the user can see the full target portfolio size at a glance.

Share-unit targets without a quote are excluded from the sum; the
footer indicates partial coverage as &#34;(X/Y priced)&#34; when that happens.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`4339a71`](https://github.com/chen-star/net_alpha/commit/4339a71fd58e9dd07153651ec21397464edb1d07))


## v0.35.1 (2026-04-30)

### Fix

* fix(web): positions UI fixes — overshoot bar, table styling, options dup

- Plan tab + Positions Target column: when % filled &gt; 100, render the bar
  fully filled with a green &#34;target met&#34; segment + red &#34;over&#34; segment
  proportional to the overshoot, instead of capping width at 100%.
- Closed and At-loss tables: replace undefined `data-table` class with the
  existing `panel`+`net-table` styling so rows have proper padding,
  hairlines, and aligned numeric columns.
- Options tab: drop the duplicate `holdings-short-options` panel —
  `/holdings/short-options` is a backwards-compat alias for
  `/holdings/options`, so the same long+short table was rendering twice.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`11006d9`](https://github.com/chen-star/net_alpha/commit/11006d9804cbc6286145be7f96a2a47adfab1c6c))


## v0.35.0 (2026-04-30)

### Feature

* feat(web): add Target column to Positions table when targets exist

Injects a target column into the shared _portfolio_table.html partial
whenever any PositionTarget rows exist; shows current vs target with a
progress bar and color-coded fill percentage.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`d3e4d5c`](https://github.com/chen-star/net_alpha/commit/d3e4d5c904a88f4d5e785166ac6c6a48f0efac35))

* feat(web): plan target delete endpoint

Adds DELETE /positions/plan/target/{symbol} that removes a target and returns
the updated plan body fragment; idempotent for unknown symbols (200 in both cases). ([`a645853`](https://github.com/chen-star/net_alpha/commit/a6458532098bfd2b1fa33974b6511429011dba49))

* feat(web): plan target add/edit modal + upsert endpoint

Extracts _build_plan_view_for_request helper from the inline plan branch so
GET ?view=plan, POST /positions/plan/target, and future DELETE can all share
the same view-model assembly. Adds modal template and two new routes.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`f3e56fd`](https://github.com/chen-star/net_alpha/commit/f3e56fde2320926b789929806cab2bea4af075cb))

* feat(web): full Plan tab render with progress bars + footer

Replace placeholder div with a complete position-targets table: per-row progress bars (blue/yellow/green/red by fill %), target/current/gap cells, and a footer summarising Total to fill, Free cash, and coverage. Free-cash calculation is now CSP-aware (subtracts cash_secured_total from open short-option positions).

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`91ce04c`](https://github.com/chen-star/net_alpha/commit/91ce04ca0a20e028d3df765ff25d6f691441a55d))

* feat(web): add Plan tab navigation + empty state

Wires the Plan tab into the Positions page: adds the tab link to
_positions_tabs.html, branches the positions.html include chain, creates
the _positions_view_plan.html empty-state template, and extends the
positions_page route to validate &#34;plan&#34; as a view, fetch targets for
the tab badge count, and build + inject plan_view for the plan branch
(with a bare cash-balance free_cash that Task 13 will upgrade).

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`306e941`](https://github.com/chen-star/net_alpha/commit/306e941850ce37ec75d79b777f233d28165a6789))

* feat(targets): add build_plan_view pure function

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`cd22cf7`](https://github.com/chen-star/net_alpha/commit/cd22cf7aa73ff6416e43986082af6aec98745608))

* feat(db): add Repository.{list,get,upsert,delete}_target

Adds four public methods and one static helper to Repository for
reading and writing position_targets rows, using the existing ORM
select() pattern. Uppercases symbols on write and lookup.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ab9b988`](https://github.com/chen-star/net_alpha/commit/ab9b988eb41968532f29605354749b84b4a25697))

* feat(targets): add PositionTarget domain model + SQLModel row

Introduces TargetUnit enum (usd/shares), frozen PositionTarget dataclass,
and PositionTargetRow SQLModel mapped to the existing position_targets table.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`36d8939`](https://github.com/chen-star/net_alpha/commit/36d89393bc3f2b9007bb246694e508d091e2e1b4))

* feat(web): add SPY benchmark line on equity curve

Wire benchmark_symbol from PricingConfig into the equity-curve chart as a
dashed gray ApexCharts series; gracefully degrades to empty list on any
failure or when remote pricing is disabled.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`fc097ae`](https://github.com/chen-star/net_alpha/commit/fc097aee0ccc88c5f072d08df5e1e71985812c51))

* feat(portfolio): add benchmark shadow-account series builder

Adds BenchmarkPoint dataclass to models.py and build_benchmark_series()
pure function in portfolio/benchmark.py that simulates a shadow account
buying the benchmark index with the same cash flows as the user&#39;s account.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`c13f155`](https://github.com/chen-star/net_alpha/commit/c13f1551507b4d7ddff2d17fafc755f610cd402b))

* feat(pricing): add cached get_historical_close on PricingService

Read-through historical close cache: _MISS sentinel distinguishes
no-row from negative-cache (None), so Yahoo is hit at most once per
(symbol, date) pair even when the close is unavailable.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`e07c81c`](https://github.com/chen-star/net_alpha/commit/e07c81c20beb3572eb284b9f7acfe06f2ac9745f))

* feat(db): add v11→v12 migration (position_targets + historical_price_cache)

Introduces two new tables: position_targets (user-managed per-symbol
allocation targets with USD/share unit) and historical_price_cache
(read-through cache for benchmark daily closes). Fixes the migration
chain so v10→v11 assigns current=11 instead of early-returning,
allowing v12 to run. Bumps CURRENT_SCHEMA_VERSION to 12 and updates
all tests that hardcoded version &#34;11&#34;.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`7156f10`](https://github.com/chen-star/net_alpha/commit/7156f1020883ae597feef9fdd04bfff6021919dc))

* feat(pricing): add get_historical_close to provider + Yahoo impl

Adds a default no-op get_historical_close(symbol, on) to the PriceProvider
ABC and a full yfinance implementation on YahooPriceProvider that fetches a
single-day window and returns Decimal or None on any failure.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`15a8d0d`](https://github.com/chen-star/net_alpha/commit/15a8d0d09757842ce9fb184723bae3fbc273432a))

* feat(web): add Top Movers panel on Overview

Renders the top-3 unrealized $ winners and losers among open positions
on the portfolio body fragment; panel is caller-gated and omitted when
no priced positions exist.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`17219ac`](https://github.com/chen-star/net_alpha/commit/17219ac2be77d4441581cc9a8e1e2d79835072ff))

* feat(portfolio): add top_movers pure function ([`4b7410f`](https://github.com/chen-star/net_alpha/commit/4b7410f90fe09a59884a7c801c91de00fc0cffc7))

* feat(web): drop TODAY tile, promote Total Return to top row

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`56e8a80`](https://github.com/chen-star/net_alpha/commit/56e8a805cd3f634dc46e2e462c89bb7d65f9c759))

### Fix

* fix(web): address final code review (modal swap header, dt.UTC, target column, dead guards)

- Fix 1: _modal_error now sets HX-Retarget/#plan-modal-backdrop + HX-Reswap/outerHTML
  so validation errors re-render the modal in-place instead of replacing the plan body
- Fix 2: standalone /portfolio/equity-curve passes benchmark_points/benchmark_symbol defaults
- Fix 3: replace datetime.utcnow() with datetime.now(UTC) in repository.upsert_target
- Fix 4: fallback branch in _portfolio_table Target cell appends &#39; sh&#39; for share count clarity
- Fix 5: remove dead _quantize(...) or Decimal(&#34;0&#34;) guards; call .quantize directly
- Fix 6: _render_plan_body forwards selected_account/selected_period context keys
- Tests: strengthen test_post_rejects_empty_symbol/zero_amount with HX-Retarget assertion;
  drop unused dt import from test_positions_pane_context.py (pre-existing ruff F401)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`63b0e14`](https://github.com/chen-star/net_alpha/commit/63b0e14f31ca72a1a7a2d4db9c92e1c6a0492697))

### Style

* style: ruff format + StrEnum upgrade for new feature files

Apply ruff format/check across files added or modified during the
Overview redesign + position targets feature. The functional change is
TargetUnit (enum.Enum) → StrEnum, eliminating the redundant str+Enum
inheritance. Pre-existing ruff issues outside the feature scope are not
touched.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`980f715`](https://github.com/chen-star/net_alpha/commit/980f715a3db1d775bc44d79382968e5773496f94))

* style(web): rename misleading test, remove blank-line artifact

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`3d59a28`](https://github.com/chen-star/net_alpha/commit/3d59a289a63782f5971b2d06b60df4fec5e07394))

### Unknown

* Merge branch &#39;feature/overview-redesign-and-position-targets&#39;

Overview page redesign + position targets (17 tasks):
- Drop TODAY tile, promote Total Return to top row
- SPY benchmark line on equity curve (graceful degrade)
- Top Movers panel (3 winners + 3 losers by unrealized $)
- Plan tab on Positions with full CRUD via HTMX modal
- Target column on shared positions table
- New SQLite tables: position_targets, historical_price_cache (v11→v12)
- 31 new tests, full suite 1095 passing ([`6207632`](https://github.com/chen-star/net_alpha/commit/6207632eec564a1326a9845916dc6932993d66e7))


## v0.34.0 (2026-04-29)

### Feature

* feat(web): POST /audit/set-basis/multi for split-into-N-lots

Validates per-row date / qty / basis bounds, qty-sum equality against
the transferred quantity, and acquisition_date &lt;= transfer_date. Calls
Repository.split_imported_transfer which persists N siblings sharing a
transfer_group_id and triggers wash-sale recompute. Adds repo helper
get_trades_in_transfer_group used by tests. Extracts a shared
_post_basis_save_recompute helper to avoid a third copy of the
post-save side effects.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`8c1712e`](https://github.com/chen-star/net_alpha/commit/8c1712e26f7e7641062f8b255ff81c28fe390500))

* feat(web): split-into-multiple-lots link on single-lot form

HTMX-driven swap: clicking the link replaces the single-lot panel
with the multi-lot row table fragment. Removes the Task 4 xfail.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`e1d1c45`](https://github.com/chen-star/net_alpha/commit/e1d1c4570e89159e2f1a02d95814d6be01daf4c5))

* feat(web): multi-lot set-basis fragment and HTMX swap routes

Add GET /audit/set-basis/multi/{trade_id} (multi-lot row-table) and
GET /audit/set-basis/single/{trade_id} (back-to-single swap target).
Multi-lot fragment uses Alpine for live qty-sum validation against
the transferred quantity.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`522258c`](https://github.com/chen-star/net_alpha/commit/522258c4ad961b10c4874b13abfe2ea0637755f6))

* feat(web): single-lot set-basis form takes acquisition date

Add POST /audit/set-basis/single with date + basis fields and
server-side validation (date format, future date, date &gt; transfer
date, negative basis). Legacy POST /audit/set-basis remains for
timeline-cell and imports-drawer callers.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`1dfdce3`](https://github.com/chen-star/net_alpha/commit/1dfdce333e978c4d476763ec84297b35825ee7a4))

* feat(web): expose transfer qty + date on positions pane

Wire transfer_qty and transfer_date through positions_pane render
context so the upcoming multi-lot split UI can validate qty-sum and
enforce acquisition_date &lt;= transfer_date.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`f520dd1`](https://github.com/chen-star/net_alpha/commit/f520dd1651700c1b04d0d9788ed86ff79f18bf78))

* feat(db): update_trade_basis accepts optional trade_date

The &#34;Set basis &amp; date&#34; inline form on the positions pane needs to set
acquisition date alongside cost_basis on transfer-in trades. Add an
optional trade_date parameter; when None, behavior is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`7d7a9fc`](https://github.com/chen-star/net_alpha/commit/7d7a9fc0a7f673c3e72d35b3a0c04acb879a70c9))

### Fix

* fix(web): correct single-form swap target, cancel propagation, and 4xx swap

Three browser-side bugs found in final review:
- Single-lot form used hx-target=&#34;this&#34;, producing a panel-inside-a-
  panel after save. Now matches the multi flow&#39;s hx-target=&#34;closest
  .panel&#34;.
- Back-to-single cancel handler used stopPropagation, which doesn&#39;t
  block HTMX&#39;s same-element listener. Switch to
  stopImmediatePropagation so cancel actually cancels.
- HTMX 1.9 silently drops 4xx responses by default. Add a global
  htmx:beforeSwap handler so validation error fragments render in
  the form panel as the spec calls for.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`6d585cf`](https://github.com/chen-star/net_alpha/commit/6d585cf2e6f1331671d7287f944d621deb242503))

* fix(db): clear basis_unknown when transfer rows get user-supplied basis

split_imported_transfer and update_imported_transfer were setting
transfer_basis_user_set=True on the parent row but leaving
basis_unknown=True, so the audit/hygiene checker kept flagging rows
the user had already reconciled. Clear the flag in both methods and
add a regression assertion to the integration test.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`9a0cea6`](https://github.com/chen-star/net_alpha/commit/9a0cea6ce619140ba0feb7e7e63aa46de040ce11))

* fix(web): don&#39;t echo raw user input in multi-lot date error

Mirror the wording from set_basis_single so a malformed date doesn&#39;t
get reflected back into the error fragment.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`c229387`](https://github.com/chen-star/net_alpha/commit/c2293877b1b38165f18b2113b7cdee8eac8c8bb3))

* fix(web): tighten multi-lot fragment JS — Alpine clone + float tolerance

The &#34;+ Add lot&#34; button cloned a row and tried to wire @click via
setAttribute, which Alpine doesn&#39;t process on cloned nodes. Switch to
addEventListener so the remove button works. Replace == float
comparisons in qty-sum validation with a 1e-4 tolerance consistent
with the server-side check; otherwise fractional shares
(e.g., 33.33+33.33+33.34) leave Save permanently disabled. Tidy the
back-to-single confirm handler.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`81af69c`](https://github.com/chen-star/net_alpha/commit/81af69ce6352f1d25c19a3aa7ae4d9151954e5a1))

* fix(web): test the existing /positions/pane route (no shim)

The test URL was wrong; remove the redundant /portfolio/positions-pane/{sym}
shim that was added to match it, and call the production route directly.
The transfer-context behavior verified by the test is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`1d5e4e1`](https://github.com/chen-star/net_alpha/commit/1d5e4e1ed95b43d99e8178ecd2489d8268b52080))

* fix(portfolio): YTD Net Contributed card now matches its provenance modal

The KPI card on the Overview page read $54,840.64 (lifetime cumulative
transfers) while the provenance modal opened from it read $23,475.53 (YTD
2026 only). compute_cash_kpis returned series[-1].cumulative_contributions,
which is a running total that folds pre-period transfers into the opening
balance and never resets at period_start.

Add a separate `period_net_contributions` field on CashFlowKPIs computed
from in-period events only, and bind the card to it. Lifetime
`net_contributions` is preserved for the growth math (current value − total
money in is correctly lifetime-bounded). The cash-balance series semantics
used by the cash chart are unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`4c83c97`](https://github.com/chen-star/net_alpha/commit/4c83c97ade371a2d0ffa0030145ad4d9808c9a73))

### Refactor

* refactor(web): tighten set-basis/single — top-level import + id guard + tests

Move the datetime import to module top to match other route files.
Guard int(trade_id) so a non-numeric id returns 400 instead of 500.
Add tests for the invalid-date-format and trade-not-found branches
that were uncovered.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`0585493`](https://github.com/chen-star/net_alpha/commit/0585493e9b4a5d68bb96ebedb0600a1ecc0816a0))

* refactor(web): remove duplicate imports in positions.py + conftest.py

Single `import datetime as dt` in positions.py replaces the dual
import; same for the inlined Trade import in seed_transfer_in.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`fb79689`](https://github.com/chen-star/net_alpha/commit/fb796895753ec30b61ac1a5782cfc1b5fb06a47a))

### Test

* test(integration): end-to-end multi-lot transfer basis split

Imports a transfer-in row with no basis, walks through the HTMX swap
to the multi-lot fragment, submits a 3-lot split, and verifies the
basis-missing warning clears on re-render.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`79256e8`](https://github.com/chen-star/net_alpha/commit/79256e8e0fb330668eb38a65e34aba27efa3cc89))

### Unknown

* Merge branch &#39;feature/multi-lot-transfer-basis&#39; — Multi-lot transfer basis + YTD net-contributed fix

YTD Overview Net Contributed card now matches its provenance modal
(period-bounded transfers, not lifetime cumulative). Inline Set basis
panel on the positions pane gains a date input plus a tiered Split
Into Multiple Lots flow that calls Repository.split_imported_transfer
under the hood — no schema change. End-to-end integration test covers
the full flow.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`86aefd0`](https://github.com/chen-star/net_alpha/commit/86aefd04f961d058f9035ff27dfd4113a73eefa1))


## v0.33.0 (2026-04-29)

### Documentation

* docs: tax correctness 1.0 — §1256 + explainability + after-tax performance

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`045dc9f`](https://github.com/chen-star/net_alpha/commit/045dc9f9809503fc236a05eb49c0df3790d9aa2d))

### Feature

* feat(web): tax/performance.html — 4 KPIs + ST/LT/§1256 mix + wash cost + caveats

Replaces Task 18 placeholder with full Tier-2 panel: pre-tax/tax-bill/
after-tax/drag KPI cards, stacked mix bar with legend, wash-sale impact
row, effective tax rate, expandable caveats, and disclaimer footer.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`63c49bd`](https://github.com/chen-star/net_alpha/commit/63c49bd504f5b569ff6cbc3bf5d27531e5f58ba1))

* feat(web): /tax?view=performance route uses compute_after_tax

Wire the performance view into the /tax handler: reads tax_brackets_cfg
from app state, builds a Period from the year param (or YTD), calls
compute_after_tax, and renders _tax_performance_panel.html. Adds the
Performance tab to tax.html. Minimal placeholder template satisfies
the pre-tax / after-tax text assertions; Task 19 will flesh it out.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`132c9b9`](https://github.com/chen-star/net_alpha/commit/132c9b96526c3ae6d06df22da95dcf1de38dd0bb))

* feat(portfolio): compute_after_tax + AfterTaxBreakdown (M&#39;s pure compute)

Add Period dataclass, AfterTaxBreakdown Pydantic model, and compute_after_tax
pure function with 60/40 §1256 split, NIIT toggle, state tax layer, and
wash-sale marginal-cost calculation. Add three Repository helpers
(realized_pnl_split, section_1256_pnl, wash_sale_disallowed_total) backed
by RealizedGLLotRow, Section1256ClassificationRow, and WashSaleViolationRow.
12/12 unit tests pass; full suite 1013 passed.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`805d7fb`](https://github.com/chen-star/net_alpha/commit/805d7fb35b37b3cf49efe63765dd99108e73d614))

* feat(tax_planner): add niit_enabled field to TaxBrackets (default True) ([`6875926`](https://github.com/chen-star/net_alpha/commit/687592628e78f18d593c95e922f5da9284bd435f))

* feat(cli): --detail prints per-violation explanations + §1256 exempt matches

Adds cli_renderer.py with render_explanation() that formats an ExplanationModel
as a printable ASCII block; wires _print_detail() into the default command&#39;s
--detail branch to render each WashSaleViolation and §1256 exempt match.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`028453e`](https://github.com/chen-star/net_alpha/commit/028453ef37fcbc1358c629542250233055b86bb4))

* feat(web): wire HTMX inline-expand trigger on wash-sale rows

Each violation row in _detail_table.html now carries hx-get, hx-trigger=&#34;click once&#34;,
hx-target, hx-swap, and an Alpine @click toggle. A sibling explain &lt;tr&gt; with x-show
provides the lazy-load target for the /tax/violation/{id}/explain fragment. Rows are
wrapped per-violation in their own &lt;tbody x-data&gt; so Alpine scope covers both rows.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`bf3571d`](https://github.com/chen-star/net_alpha/commit/bf3571de1d4579f04319c7a3efb0f63c88c19f13))

* feat(web): HTMX inline-expand explain fragments for violations + exempt matches

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ade0792`](https://github.com/chen-star/net_alpha/commit/ade0792cf148b4e4534922beb4a817a0892138cb))

* feat(explain): explain_exempt mirrors explain_violation for ExemptMatch

Pure function explain_exempt() builds ExplanationModel with is_exempt=True
and adjusted_basis_target=None for §1256 exempt matches; re-exported from
net_alpha.explain.__init__.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`0dfe3e7`](https://github.com/chen-star/net_alpha/commit/0dfe3e745926ae531bddb6dfbd842ee3ee49136e))

* feat(explain): explain_violation + ExplanationModel

Pure function that builds a structured ExplanationModel from a
WashSaleViolationRow, covering exact-ticker, ETF-pair, and option-chain
matches with cross-account detection and partial-wash-sale math strings.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`e0747f2`](https://github.com/chen-star/net_alpha/commit/e0747f20e431c08d037836b988904abc066e1be4))

* feat(explain): rule citations, match-reason, disallowed-math, confidence-reason templates

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`cb1f5a5`](https://github.com/chen-star/net_alpha/commit/cb1f5a59c499cfd414b666db7cb616f0d7f86c48))

* feat(engine): one-shot migration recompute reclassifies stale §1256 violations

Adds migrate_existing_violations() to engine/recompute.py: backfills
trades.is_section_1256 for legacy rows with DEFAULT 0, converts any
WashSaleViolationRows referencing §1256 contracts into ExemptMatch
records, runs the §1256 60/40 classifier, and returns a
MigrationRecomputeSummary with counts for a user-facing banner.

Wires the one-shot pass into init_db() (connection.py) behind a meta
key guard (section_1256_migration_done) so it runs exactly once per
DB after upgrade from v10. Adds repository helpers delete_violations_by_id
and set_section_1256_flag. Six new integration tests cover reclassification,
no-op on equity violations, flag backfill, idempotency, and classifier output.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`1616137`](https://github.com/chen-star/net_alpha/commit/16161372fe2dfa0bfb4018c5717e5260a59af926))

* feat(engine): recompute persists ExemptMatch + §1256 classifications; universe-hash trigger

- Add recompute_all(repo) orchestrator: runs wash-sale detection, persists
  ExemptMatch records, runs §1256 classifier and persists classifications,
  then stamps the universe hash.
- Add should_full_recompute(repo) -&gt; bool: compares stored meta hash against
  current bundled+user universe hash; returns True when stale.
- Add _stamp_universe_hash(repo) private helper.
- Fix add_import and _row_to_trade to round-trip is_section_1256 through
  the DB (was missing, causing trades loaded from DB to always have
  is_section_1256=False, silently breaking exempt-match detection).

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`bf0bf85`](https://github.com/chen-star/net_alpha/commit/bf0bf85b110160525dd26de827917a4aa343772f))

* feat(section_1256): 60/40 classifier for closed §1256 trades

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`a466988`](https://github.com/chen-star/net_alpha/commit/a466988f2d54091b7bb4b404ea78aabe58aff3b1))

* feat(engine): emit ExemptMatch for §1256 contracts (Approach 2)

When either side of a candidate wash-sale match is a §1256 contract
(trade.is_section_1256 == True), the detector now emits ExemptMatch
instead of WashSaleViolation and does not adjust replacement-lot basis.
Applies to both detect_wash_sales and detect_in_window.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`37321d4`](https://github.com/chen-star/net_alpha/commit/37321d46f76a1a74340ede3d6ee0ea7873468321))

* feat(db): repository methods for ExemptMatch + Section1256Classification

Add save/clear/list methods for ExemptMatch and Section1256Classification
to Repository, with int() FK conversions mirroring _violation_to_row.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`d814d58`](https://github.com/chen-star/net_alpha/commit/d814d58a05a122675752c1288609465777e656c1))

* feat(db): migration v10→v11 — exempt_matches + section_1256_classifications + is_section_1256

- Add _migrate_v10_to_v11: creates exempt_matches table (INTEGER FKs),
  section_1256_classifications table, adds trades.is_section_1256 column,
  stamps section_1256_universe_hash and wash_sale_engine_version meta rows.
- Add preflight column-ensure at top of migrate() so SQLModel ORM SELECTs
  (used in intermediate migration steps like v1→v2 backfill) always work
  even when DB hasn&#39;t reached v11 yet — fixes 6 pre-existing test failures.
- Bump CURRENT_SCHEMA_VERSION 10 → 11.
- Add tests/db/test_migration_v11.py (5 new tests, all passing).
- Update version assertions in test_migration_v1_v2.py and test_migrations.py.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`4e4c9fa`](https://github.com/chen-star/net_alpha/commit/4e4c9fa0df8e5011bf8893d9a78a41d801598e36))

* feat(db): row tables for ExemptMatch + Section1256Classification + TradeRow.is_section_1256

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`d20b2c3`](https://github.com/chen-star/net_alpha/commit/d20b2c3880f6769873cd453b5d227550939a3dc6))

* feat(models): ExemptMatch + Section1256Classification + Trade.is_section_1256

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`02b42b0`](https://github.com/chen-star/net_alpha/commit/02b42b0e7aa7fac3c76e95f18a2e95fcf0a1beaa))

* feat(section_1256): bundled universe + is_section_1256 detection ([`fcee2f4`](https://github.com/chen-star/net_alpha/commit/fcee2f4404f3cc63b7f7d64d4315a04c01c37a1a))

### Fix

* fix(web): surface ExemptMatch on wash-sales tab + valid HTMX target (C4, I1)

C4: _detail_table.html renders a §1256 Exempt Matches section after the
violations table when exempt_matches is non-empty. wash_sales.py passes
exempt_matches into the table-view context (filtered by same ticker/account/year).

I1: Move explain-row id from &lt;tr&gt; to &lt;td colspan=&#34;8&#34;&gt; so HTMX innerHTML
swap inserts into a valid container — a &lt;div&gt; inside a bare &lt;tr&gt; is
hoisted out of the table by browsers. Applied to both violation and exempt
match rows. hx-target updated to point directly to the &lt;td id&gt;.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`6982045`](https://github.com/chen-star/net_alpha/commit/6982045dd6baff9d4a9b10010365bc777d6bc93d))

* fix(engine): persist ExemptMatch + classifications in recompute_all_violations (C2, C3)

C2: Fold exempt-match persistence and §1256 classifier invocation into
recompute_all_violations so the production recompute path is complete.
recompute_all() becomes a thin wrapper that auto-loads ETF pairs.

C3: should_full_recompute() now checks both the universe hash AND the
wash_sale_engine_version meta key, triggering a full recompute on binary
upgrades. _stamp_universe_hash() also stamps the engine version.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`a86df73`](https://github.com/chen-star/net_alpha/commit/a86df73c21620e7b283448f9d9ffb7feeb9eac78))

* fix(db): set Trade.is_section_1256 in add_import (C1: was always False on import)

Auto-detect §1256 status via is_section_1256() when the broker parser
does not set the flag explicitly. Respects any explicit True already on
the trade object.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`afc0bf8`](https://github.com/chen-star/net_alpha/commit/afc0bf88b66f7a651da64213faec0e83e0b286aa))

* fix(db): exclude §1256 from realized_pnl_split to avoid double-count with section_1256_pnl ([`3559fee`](https://github.com/chen-star/net_alpha/commit/3559feeb81a345842163ef790e5599110423ebbd))

* fix(explain): use Repository.get_trade_by_id (was nonexistent get_trade) ([`b683596`](https://github.com/chen-star/net_alpha/commit/b683596ffc48762f47e14603153a34abdab3e34a))

* fix(db,engine): use bindparams + ORM update; replace typer.echo with print in db layer

- connection.py: convert f-string SQL in _migration_1256_done and _stamp_migration_1256_done to bindparams; remove typer import and replace typer.echo with plain print
- repository.py: rewrite set_section_1256_flag to use ORM table.update().where().values() pattern matching delete_violations_by_id; signature changed to list[int]
- recompute.py: cast Trade.id to int at set_section_1256_flag call site; add idempotency invariant comments for exempt-match save and classifier save steps; ruff-fixed import sort

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`575ffd8`](https://github.com/chen-star/net_alpha/commit/575ffd89a22f533589c6450e8cd38f7568805928))

* fix(engine): stamp universe hash only when recompute ran; add is_section_1256 round-trip test

Move _stamp_universe_hash inside the `if all_dates:` guard so an empty-DB
call to recompute_all does not lock out should_full_recompute when trades
are added later. Add focused add_import → all_trades round-trip test for
the is_section_1256 field to give direct coverage of the repository fix.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`1a14355`](https://github.com/chen-star/net_alpha/commit/1a1435589a33e2872d56ecdaa312259ee857e05e))

* fix(section_1256): classifier uses is_sell() predicate (was lowercase string compare)

Real broker-imported trades have action=&#34;Sell&#34; (title case); the hard-coded
`!= &#34;sell&#34;` guard silently skipped every real §1256 sell. Switch to the
existing `Trade.is_sell()` casing-insensitive predicate. Update test fixtures
to use title-case actions matching production broker output; remove unused
`import pytest`.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`d4b6c1f`](https://github.com/chen-star/net_alpha/commit/d4b6c1febffa2b33c7adadf8b0b732fb6c060e3a))

* fix(db): repository fixups — account filter consistency, RuntimeError catch, cascade delete on remove_import

- list_exempt_matches: `if account:` → `if account is not None:` to match rest of file
- list_section_1256_classifications: replace dead `acct_id is not None` branch with try/except RuntimeError, returning [] on unknown account (safe for user-controlled filter values in web layer)
- remove_import: cascade-delete ExemptMatchRow (loss_trade_id OR triggering_buy_id) and Section1256ClassificationRow (trade_id) so no orphaned rows persist after import removal
- tests: 2 new filter-path tests (account subset + unknown account → []) bringing file to 6 tests

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`8a4b902`](https://github.com/chen-star/net_alpha/commit/8a4b9029969701ffcf6a34a03106ca0e226d4bff))

* fix(db): stamp §1256 meta keys on fresh DB init (was upgrade-only)

Extract _stamp_section_1256_meta() helper and call it from both the
fresh-DB branch of migrate() and _migrate_v10_to_v11, so a new install
gets section_1256_universe_hash + wash_sale_engine_version in meta.
Also changes hardcoded &#39;11&#39; to str(CURRENT_SCHEMA_VERSION) for
extensibility. Adds regression test test_fresh_db_stamps_section_1256_meta_keys.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`f91f003`](https://github.com/chen-star/net_alpha/commit/f91f003b45797947252c75c160464e708e551978))

* fix(test): remove unused imports flagged by ruff F401 ([`5e5d23d`](https://github.com/chen-star/net_alpha/commit/5e5d23db2913546ebda7c1a8c9f0c71337d52770))

### Test

* test(integration): real production-path test for §1256 auto-detection + persistence

3 tests covering:
- add_import auto-detects is_section_1256 (no explicit flag set)
- recompute_all_violations persists exempt matches and classifications
- recompute_all_violations stamps universe hash + engine version in meta

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`42fad23`](https://github.com/chen-star/net_alpha/commit/42fad233dfe7032e956ced45f27ab7ff8ddf0642))

* test(integration): end-to-end §1256 recognition + ETF pair preservation

Adds a golden end-to-end integration test that seeds synthetic trades, runs
recompute_all, and asserts the full expected mix: 2 regular wash-sale violations
(TSLA confirmed, SPY/VOO ETF-pair unclear), 1 ExemptMatch for the §1256 SPX
call pair, and 1 Section1256Classification with correct 60/40 LT/ST split on
the closed SPX 5000C profit trade.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`4e0dc25`](https://github.com/chen-star/net_alpha/commit/4e0dc25f69b425a2112272d90a4f6fb4ab0bdeec))

* test(explain): cover fallback branches in templates ([`ec27f63`](https://github.com/chen-star/net_alpha/commit/ec27f635ac6e8eabda352d5d0745a28c9cde02cf))

* test(engine): cover §1256 candidate-only flag + detect_in_window exempt path

Replace misnamed test_mixed_spx_loss_spy_buy_emits_exempt_match (which
was a SPX→SPX duplicate) with test_candidate_only_section_1256_still_emits_exempt,
exercising the candidate-arm of the `loss_sale.is_section_1256 or candidate.is_section_1256`
branch with asymmetric flags. Add two detect_in_window tests: one asserting
the §1256 exempt match is included when both trades fall inside the window,
and one asserting it is excluded when both fall outside.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`775f768`](https://github.com/chen-star/net_alpha/commit/775f7686e565cc09ccb2f7b3ae1adf869fe717af))

### Unknown

* Merge branch &#39;feature/tax-correctness-pmo3&#39; — Tax Correctness 1.0 (P+M+O3) ([`9058a3d`](https://github.com/chen-star/net_alpha/commit/9058a3dda109e07717813edfadc1b405ba295454))


## v0.32.1 (2026-04-29)

### Fix

* fix(web): Closed positions tab renders historical realized lots

The Positions → Closed tab was a hard-coded &#34;Coming in Phase 2&#34; placeholder.
The data layer (RealizedGLLot model, get_gl_lots_for_account) was already
in place; the view, route branch, and aggregator were never wired up.

- Add Repository.list_all_gl_lots() returning every lot across accounts,
  sorted by close date desc.
- Add ClosedLotRow dataclass + compute_closed_lots() aggregator with
  period (YTD/year/lifetime) + account filtering. Realized P/L =
  proceeds − cost_basis.
- Branch routes/positions.py on selected_view == &#34;closed&#34; mirroring the
  at-loss pattern, including HTMX fragment response.
- Replace _positions_view_closed.html placeholder with a real table:
  Symbol · Acct · Qty · Basis · Proceeds · Realized · Opened · Closed ·
  Holding (LT/ST). Wash-sale lots show a &#34;W&#34; chip with disallowed-loss
  tooltip. Empty state nudges the user toward Settings → Imports.

Tests: 14 new (11 aggregator unit + 3 route smoke) covering realized P/L
sign, period filter, account filter, sort order, term passthrough, wash-sale
fields, option vs equity display_symbol, empty input.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`464e4ba`](https://github.com/chen-star/net_alpha/commit/464e4badc6acd4b902793dcd6978908891f5c3e5))


## v0.32.0 (2026-04-29)

### Feature

* feat(snapshots): expand WIDTHS to 4 viewports; recapture 32 baselines (§F1)

Add laptop (1024×768) and desktop-wide (1440×900); refit desktop to
1280×800.  Rewrite tablet/desktop baselines in place; add 16 new
laptop/desktop-wide PNGs.  32 pass in verify mode, 0 diff leaks.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`d2351e9`](https://github.com/chen-star/net_alpha/commit/d2351e9edc437832832bc0cb1da9e3aaa9786076))

* feat(web): ticker tabs gain aria-controls + role=tabpanel (I4)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`499ab7e`](https://github.com/chen-star/net_alpha/commit/499ab7ebafe16ee847094c818b76bac7a5fc0418))

* feat(web): side pane + Settings drawer go full-width at narrow widths (§3.9)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`5588db0`](https://github.com/chen-star/net_alpha/commit/5588db05d3de1790efbe1742a42ed10d43f78517))

* feat(web): wide tables get overflow-x-auto wrapper at narrow widths (§3.9)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`e67193a`](https://github.com/chen-star/net_alpha/commit/e67193a837d77ee1ea67f391460730a214d1ce76))

* feat(web): KPI grids reflow at 768/1024 (§3.9)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`3a5a24f`](https://github.com/chen-star/net_alpha/commit/3a5a24f29220403602f786e53131d74df5d8f022))

* feat(web): drop width=1024 viewport, add &lt; 768 banner (§3.9) ([`eb66911`](https://github.com/chen-star/net_alpha/commit/eb669112797637eed9f9efc3452252812ed2a3b7))

### Fix

* fix(web): Phase 5 review fix-pack

- Wrap 4 missed tables in overflow-x-auto (_schwab_lot_detail,
  _reconciliation_diff, _detail_table, _provenance_modal)
- Hide topbar below md (banner-only at &lt; 768 per spec §3.9)
- Update test_baseline_screens.py docstring for 4-width matrix
- Recapture tablet snapshots affected by topbar visibility

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`29a1c25`](https://github.com/chen-star/net_alpha/commit/29a1c2588ccceb7ea663bad322a194b2cb8a6821))

### Test

* test(web): smoke every page returns 200 (Phase 5) ([`37a5237`](https://github.com/chen-star/net_alpha/commit/37a5237e7c0c3aa75d75800e06e4f541dc2fc975))

### Unknown

* Merge branch &#39;phase5-responsive&#39; — Phase 5 Responsive (iPad-width usable)

Phase 5 of the UI/UX redesign (spec §3.9, §7).

- Drop width=1024 viewport, add &lt; 768 banner
- KPI grids reflow at 768/1024 (12-col → stacked → 4-col)
- Wide tables get overflow-x-auto wrappers
- Side pane and Settings drawer go full-width below md/lg
- Ticker tabs gain aria-controls + role=tabpanel (I4 carry-over)
- Hide topbar below md (banner-only at &lt; 768)
- Snapshot suite expanded to 768/1024/1280/1440 (32 baselines)
- 768 smoke tests for every page

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`6d64635`](https://github.com/chen-star/net_alpha/commit/6d6463556338f3d8616ebf37d26db6453c0ccbde))


## v0.31.0 (2026-04-29)

### Feature

* feat(web): keyboard shortcut layer (g o/p/t/s, ?, ,) + cheatsheet (§3.7)

Adds a small self-contained keydown listener (keyboard.js) that wires:
- g o / g p / g t / g s — navigate to Overview / Positions / Tax / Sim
- ? — open the keyboard cheatsheet modal
- , — open the settings drawer (event already wired in Phase 1)

The handler ignores events from inputs/textareas/selects/contenteditable
so typing inside forms is unaffected. Esc-close for drawers/panes is
already handled by Alpine and is not duplicated here.

Adds nav-link aria-keyshortcuts attributes for assistive tech.
Adds a .kbd token style and the cheatsheet modal (Alpine x-data).

Section G3 of Phase 4 plan. ([`ea6d98e`](https://github.com/chen-star/net_alpha/commit/ea6d98ec6f1b3f6ed65c4ba990a8eed6a71190f6))

* feat(web): apply --ring-focus to all interactive elements (§5.14)

Adds a single :focus-visible block that applies the --ring-focus token
(0 0 0 2px rgba(94,92,230,0.6)) to every interactive primitive:
.btn, .btn-ghost, .tab, .chip, .nav-link, native button, and form
inputs. Outlines are nullified so the indigo halo is the canonical
keyboard-focus indicator.

Section G2 of Phase 4 plan. ([`7be9de6`](https://github.com/chen-star/net_alpha/commit/7be9de6c3e3f78ff970ac22c4ab3d7c021963d3f))

* feat(web): main container 1280px → 1536px (max-w-screen-2xl) (H8)

Bumps the main and footer containers from max-w-[1280px] to
max-w-screen-2xl (1536px) to give the wider Positions table and
Tax view more horizontal room. Snapshot baselines will be
re-captured in Section H. ([`d6dde36`](https://github.com/chen-star/net_alpha/commit/d6dde36c30eae9a07e2ddda1cdcb5dd27db4028b))

* feat(web): options panel header is a 3-card mini-summary (H7)

/holdings/options now passes options_summary (open_contracts, net_premium,
avg_dte) to _portfolio_open_options.html. Net premium signs short premium
received as a credit and long cost paid as a debit; avg DTE is qty-weighted.
The 3-card grid renders above the row list, mirroring the kpi-numeric
pattern used elsewhere. ([`7635012`](https://github.com/chen-star/net_alpha/commit/76350123f9b3999a1a920202443608fe4d90079e))

* feat(web): LT/ST mixed shows as single &#39;lt+st&#39; chip in Account column (H5)

Adds account_chip (joined sub-account suffixes) and account_displays
(full labels) to PositionRow. Single-account rows render the label in
mono; multi-account rows render a single chip whose tooltip lists
every full label. ([`c1da698`](https://github.com/chen-star/net_alpha/commit/c1da698218bde389ef34cc86ef9ada531c620af2))

* feat(web): all quantity cells use fmt_quantity (H2)

Replaces &#34;%.4f&#34;|format(r.qty) and &#34;%g&#34;|format(r.lt_qty|float) ST splits
with fmt_quantity, so whole shares render as integers and fractional
quantities trim trailing zeros consistently with other tables. ([`64abe94`](https://github.com/chen-star/net_alpha/commit/64abe949351ecf7744d3b385108c3b9dfdeaf744))

* feat(web): missing-basis chip + em-dash empties on Positions table (H1)

Adds PositionRow.basis_known derived from any open lot having non-null,
non-zero cost_basis. The Positions table now renders the new chip
&#39;⚠ basis missing&#39; when basis is provably missing on an open position
instead of showing $0.00, and falls back to fmt_currency (em-dash for
None) elsewhere. ([`da5490f`](https://github.com/chen-star/net_alpha/commit/da5490fbc1a5dd9201a5935d10ef876bcaaca0be))

* feat(web): inline Set-basis chip on Timeline rows missing basis (Tk5)

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`c9227ea`](https://github.com/chen-star/net_alpha/commit/c9227ea8c4ea335ac852b3ac7c8107ebc4f3411f))

* feat(web): /ticker accepts ?view=timeline|lots|recon and serves fragments (Tk4) ([`8fbba8e`](https://github.com/chen-star/net_alpha/commit/8fbba8e35087af8d31ab57010af7093cb5b2d1a2))

* feat(web): /ticker uses Timeline / Open lots / Broker reconciliation tabs (Tk4) ([`a9684c2`](https://github.com/chen-star/net_alpha/commit/a9684c2b2f2c35766f15ce3af7c83890a53cb99d))

* feat(web): reconciliation /reconciliation accepts variant=badge for Ticker KPI (Tk3)

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`82d11ec`](https://github.com/chen-star/net_alpha/commit/82d11ec6e12d4a773de6a463902a1af9254fd543))

* feat(web): ticker KPIs use sans + fmt_currency; mono only on identifiers (Tk1)

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`b6edace`](https://github.com/chen-star/net_alpha/commit/b6edaced71f74817f9525a16db03c6729a011594))

* feat(web): ticker page h1 is sans Inter, white (Tk1, Tk2)

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`dd49f11`](https://github.com/chen-star/net_alpha/commit/dd49f117ba7717d6c7f71157eda5ccae131aa8e5))

* feat(web): calendar strip shows N events YTD (N3) ([`983d5e2`](https://github.com/chen-star/net_alpha/commit/983d5e235b1de0709a2df8d1981127f41e44be83))

### Fix

* fix(web): keyboard.js state machine + Realized P/L $0 color

keyboard.js: prior matcher used pending.endsWith(seq.replace(&#39; &#39;,&#39;&#39;)),
so typing &#39;good&#39; or &#39;tags&#39; triggered nav. Replace with an explicit
&#39;awaiting g&#39; state machine so only true g-prefixed chords fire.

ticker.html: Realized P/L (YTD and Lifetime) used &gt;= 0 as the green
threshold, painting $0.00 green. Mirror the Disallowed pattern: &gt; 0
positive, &lt; 0 negative, else neutral text-label-2.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`00b3f47`](https://github.com/chen-star/net_alpha/commit/00b3f47d97acfc28c1b5a3d01d68fd617e1e93bf))

* fix(web): inline Set basis chip swaps the basis cell, not affordance cell

The chip lived in the BASIS td but its hx-target pointed at the
affordance cell two columns over, leaving the warning chip stuck after
save. Add id=&#34;trade-basis-{id}&#34; to the basis td, retarget the chip
form, and update the timeline-caller branch of /audit/set-basis to
return matching markup including the saved cost-basis value.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`5abe8be`](https://github.com/chen-star/net_alpha/commit/5abe8bee87b068640a0d690518fe79e065fb8953))

* fix(web): drop HTMX swap on ticker tabs to preserve active state

Tabs server-render the active class from selected_view, but HTMX swap
only replaced inner content, leaving the highlight stuck on the old tab.
Convert to plain &lt;a&gt; nav matching the _positions_tabs.html pattern. The
route already returns full HTML for non-HX requests.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`41bab74`](https://github.com/chen-star/net_alpha/commit/41bab743ff97eaee1c01c15175ab22086a24dbe9))

* fix(web): wash-sale filter chips × is clickable, drops that filter (N2) ([`3b41b59`](https://github.com/chen-star/net_alpha/commit/3b41b59ed8399913ce4d0527671e16a3fe463c8d))

* fix(web): sim Account label flips to (required) when Sell selected (N1) ([`00290c6`](https://github.com/chen-star/net_alpha/commit/00290c6c6887f525293c0f21a20d9edd3a490804))

* fix(web): projection form swap is outerHTML to avoid self-nesting (I1) ([`c236f57`](https://github.com/chen-star/net_alpha/commit/c236f5769c7f1f623791030adecbf2cb0aa590a6))

* fix(web): hero subhead &#39;vs contributed&#39; now equals total − net_contributed (I2)

Adapted to actual codebase field name: cash_kpis.net_contributions (not
net_contributed). Both kpis_fragment and body call sites now compute
total_account_value - cash_kpis.net_contributions instead of
period_realized + period_unrealized. ([`cdf19c1`](https://github.com/chen-star/net_alpha/commit/cdf19c10a6592984bf7c642a0054673a95d79770))

### Refactor

* refactor(web): promote text-label-3 → text-label-2 on load-bearing copy (§5.14)

Per §5.14, label-3 is reserved for decorative dividers and disabled
affordances. Bumps load-bearing copy (KPI sub-lines, panel sub-headers,
source labels, &#34;Loading…&#34; placeholders, event-count spans, harvest
queue origin lines, profile descriptions, etc.) to label-2.

Decorative `·` separators, em-dash placeholders for missing values,
disabled pagination buttons, and chevron affordances stay at label-3.

Section G1 of Phase 4 plan. ([`78426ec`](https://github.com/chen-star/net_alpha/commit/78426ecb54bf54fa2709881f5fd29ca41d7e266c))

* refactor(web): open-options bar shows P/L only; DTE is a separate badge (H6)

The time-elapsed bar already represents only one metric (not P/L,
which would require live option quotes we don&#39;t fetch). Per the
H6 split, DTE becomes a discrete badge-muted with the existing
text-warn / text-label-1 / text-label-2 colorization preserved
based on time-to-expiry, so the row&#39;s right column reads as a
standalone badge instead of a number with inline subscript. ([`78134e9`](https://github.com/chen-star/net_alpha/commit/78134e97d4efcb311e47be36a05bd9ecb9e65bfe))

* refactor(web): rename CASH SUNK/SH → &#39;Cash invested / sh&#39; + tooltip (H3)

Header now uses Title Case, plain English, with a clarifying tooltip on
how the per-share number is derived (and that wash adjustments are
included). ([`220bb81`](https://github.com/chen-star/net_alpha/commit/220bb81c07a468ebc0dacb15cb8e9fe2a8f7531a))

* refactor(web): drop above-table recon strips; badge + tab replace them (Tk3)

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`7936d4d`](https://github.com/chen-star/net_alpha/commit/7936d4d9bb1306a695cf0d2bc81b67a5dab305e0))

### Test

* test(web): smoke tests for Phase 4 ticker tabs + recon badge variant

Cover the new query-param round-trip on /ticker/&lt;sym&gt;?view=, the
HX-Request fragment-only response, and the variant=badge branch on
/reconciliation/&lt;sym&gt;. Tests are tolerant of unseeded data so they pass
in the default conftest.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`52eba36`](https://github.com/chen-star/net_alpha/commit/52eba36008e4fce1daadc3d957546cdafebefe39))

* test(web): re-capture snapshot baselines after Phase 4 visual sweep ([`3c5aa0f`](https://github.com/chen-star/net_alpha/commit/3c5aa0f9ca089fc33146278dda14f11b5eb5342d))

### Unknown

* Merge branch &#39;phase4-ticker-visual&#39; — Phase 4 Ticker page + visual sweep ([`62187f9`](https://github.com/chen-star/net_alpha/commit/62187f976a7ae2486c2243d4a1d3a45ee046c315))


## v0.30.0 (2026-04-29)

### Chore

* chore(web): delete dead _harvest_tab.html (Phase 1 redirected away; Phase 2 review #I6)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`0431899`](https://github.com/chen-star/net_alpha/commit/043189918b93f99b4758947eb157dbef4b60f69b))

### Feature

* feat(web): /sim — recent sims this-session panel (S2)

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`4101746`](https://github.com/chen-star/net_alpha/commit/410174648a17ff8747a9fb4f4ea39838a69fd9ff))

* feat(web): /sim — account required for action=Sell with inline error (S3)

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`b395c6f`](https://github.com/chen-star/net_alpha/commit/b395c6ff1246b60dbcf973d34284d2fb58142650))

* feat(web): drop-zone preview on drag-over (I4)

Adds drop_zone.js (vanilla JS) that listens to dragenter/dragover on any
element wrapping a [data-drop-zone] file input and shows a sibling
[data-testid=&#34;drop-zone-preview&#34;] element with the incoming file count.
Wires data-drop-zone onto the CSV upload input and adds the preview div
to _drop_zone.html; loads the script via base.html.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`73fcb76`](https://github.com/chen-star/net_alpha/commit/73fcb76a72fbba6e83c82d902f6f07d24f1326df))

* feat(web): drawer Imports — one-explanation card + per-row inline form (I1, I2)

Restructures the data-hygiene section: basis_unknown rows now render a
single shared explanation card (I1) and one compact HTMX inline form each
(I2, caller=drawer).  Adds MissingBasisRow helper to hygiene.py and wires
collect_missing_basis_rows into the imports route context.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`0650385`](https://github.com/chen-star/net_alpha/commit/065038541c328ebadcf078f1661231c43430a1d8))

* feat(web): inline tax-projection form replaces YAML snippet (Pr1, Pr2)

Add write_tax_config() to config.py, POST /tax/projection-config route
that persists to config.yaml and hot-reloads app.state, _projection_form.html
with HTMX-wired form, and updated _projection_tab.html / _projection_card.html
to remove the manual YAML-snippet copy.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`006b38b`](https://github.com/chen-star/net_alpha/commit/006b38bcce3ae7429a7632c59f533b278838c617))

* feat(web): tax filter chips with reset (W2); calendar strip always visible (W3)

- _tax_wash_sales_tab.html: add filter chip summary row with
  data-testid=&#34;filter-reset&#34; (W2) and always-visible compact month-header
  strip with data-testid=&#34;calendar-strip&#34; (W3)
- app.src.css: add .chip utility class for filter-bar chips
- app.css: rebuilt

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`bb910a5`](https://github.com/chen-star/net_alpha/commit/bb910a51ac729ae602bbfaa17a32eb100e2ccb84))

* feat(web): wash-watch labeled forward; violations labeled backward, affirmative empty (W1, W1b)

- _portfolio_wash_watch.html: heading now reads &#34;Wash-sale watch ·
  forward-looking 30d&#34;
- _tax_wash_sales_tab.html: violations section gets &#34;Violations ·
  backward-looking detected&#34; heading before the detail table
- _detail_table.html: empty state replaced with affirmative &#34;✓ No
  wash-sale violations detected&#34; copy instead of generic &#34;No violations
  match these filters.&#34;
- test_detail_routes.py: updated to match new empty-state copy

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`a9202d3`](https://github.com/chen-star/net_alpha/commit/a9202d3413a52132a9bef7fe5e152b4582e1f21e))

* feat(web): tax realized-P/L stacked mini-bar (T5)

Add realized_kpis (OffsetBudget) to _wash_sales_context so the tax
wash-sales tab can render a split loss/gain mini-bar. The bar shows
realized_losses_ytd vs realized_gains_ytd; degrades to an empty-state
message when no P/L has been realized this period.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`cb1e530`](https://github.com/chen-star/net_alpha/commit/cb1e5302d757354bb655c11a5afe8705f93f06f1))

* feat(web): loss-harvest budget bar (T4)

Add data-testid=&#34;offset-budget&#34; to the tile wrapper and
data-testid=&#34;offset-budget-bar&#34; to the existing progress bar in
_offset_budget_tile.html so tests can assert the bar is present.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`bc5315a`](https://github.com/chen-star/net_alpha/commit/bc5315aeb0d4f3dfe2d890b7d9ab7abb742e56ef))

* feat(web): Overview KPI grid — hero + today + cash (3 large) over 4 small (P2/P4/NEW)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`d9a16cc`](https://github.com/chen-star/net_alpha/commit/d9a16cc771161a68f12714bc7abad41dd2066b76))

* feat(portfolio): compute_today_change for the Overview Today tile (P3 prep)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`a06cdd0`](https://github.com/chen-star/net_alpha/commit/a06cdd0aaf015c9132d794fdca4248208c006e55))

* feat(pricing): Quote exposes previous_close for the Today tile (Phase 3 prep)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`80362ab`](https://github.com/chen-star/net_alpha/commit/80362ab1613a5d08dce7041bb8e573330cffc3e9))

* feat(web): freshness chip in toolbar — drop in-tile cached-prices copy (P5)

Add compute_price_freshness helper (portfolio/freshness.py) that maps a
PricingSnapshot to a green/amber/red tier and label (&lt; 15m / 15m–24h /
&gt; 24h). Wire it into the / route and render a data-testid=&#34;freshness-chip&#34;
button in the toolbar. Remove the inline &#34;Cached prices…refresh&#34; copy from
the KPI tile footer — the chip is now the single freshness surface.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`035cd2d`](https://github.com/chen-star/net_alpha/commit/035cd2db35a154f5c28d16c0ec3352e53aacb544))

* feat(web): drop Portfolio section header and Tax planning footer (P1, P9)

Remove redundant &#34;Portfolio&#34; section-header div from the body fragment
and drop the Tax planning footer panel (offset budget + year-end
projection) from the Overview page — those panels belong on /tax.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`9fc2145`](https://github.com/chen-star/net_alpha/commit/9fc2145fdb00783ea6a215aa4947c301c3462f3f))

* feat(web): at-loss summary strip — total unrealized, harvestable count, replacements count (Phase 2 review #I3)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`b4d8d8d`](https://github.com/chen-star/net_alpha/commit/b4d8d8de58e06495f16360ad4ed253f34e1b0baf))

### Fix

* fix(web): sim error swap targets sibling div, not #sim-result; refresh stale hygiene copy

C3: G1&#39;s account-required validation re-rendered the entire sim.html
into HTMX&#39;s #sim-result target, producing nested &lt;html&gt;/&lt;body&gt; and
double navbars. Switched to an OOB swap into a new #sim-form-error
sibling of the account select; #sim-result clears via the empty
main-response. Removed the old {% if form_error %} block from inside
the form that is now superseded by the OOB swap.

I3: hygiene.py&#39;s _check_tax_config_missing still pointed users to a
&#34;copy-paste config snippet&#34; — the YAML flow Phase 3 replaced with the
inline form. Updated the copy.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`7cb30d9`](https://github.com/chen-star/net_alpha/commit/7cb30d97271e0111b1796cec0b38fc6730a958b3))

* fix(web): negative-sign rendering on Today tile + hero, plus text-loss/gain/success aliases

C1: Today tile and the hero tile&#39;s vs-contributed subhead were stripping
the leading minus on negative values via |abs in the template, so
loss days rendered as gains. Removed the |abs and let fmt_currency emit
the sign. The + prefix now fires only on strict positives.

C2: text-loss / text-gain / text-success classes were used across at
least 8 templates (T5 mini-bar, at-loss summary, sim form_error,
harvest-clear status, etc.) but never defined in app.src.css — the
compiled bundle had only .text-neg / .text-pos / .text-warn /
.text-info. Added aliases so the existing markup colors correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`be909a8`](https://github.com/chen-star/net_alpha/commit/be909a8a0691d5d82fc9c7e01e2345c1a400a3a7))

* fix(web): equity x-axis tick density (P6); cash chart line semantics (P7)

Add hideOverlappingLabels: true + tickAmount: 12 to the equity-curve x-axis
so month labels don&#39;t pile up on dense date ranges.

Add data-series-solid=&#34;cash_balance&#34; and data-series-dashed=&#34;net_contributed&#34;
attributes to the cash chart container — stable semantic hooks that confirm
series ordering (cash balance = solid, net contributed = dashed, per §5.12).

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`34f4d7c`](https://github.com/chen-star/net_alpha/commit/34f4d7c8f10d77f7106504fdecbc25feb563c210))

* fix(web): CASH KPI rendered exactly once on Overview (P3 bug fix)

Remove the duplicate Cash balance tile from the secondary cash-flow row
in _portfolio_kpis.html. Cash is already shown via the slot_cash macro
in the hero KPI grid; the second block now shows only Net contributed
and Growth (unique data). Resize those two tiles from col-span-4 to
col-span-6 to fill the 12-column grid.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`836911c`](https://github.com/chen-star/net_alpha/commit/836911cabb642a32732e164c74c416648446ad4f))

* fix(web): positions_pane logs lookup failures instead of silent swallow (Phase 2 review #I4)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`5db9481`](https://github.com/chen-star/net_alpha/commit/5db9481656f6900ecf38cdf84813e10a4655f87b))

### Refactor

* refactor(web): realized_delta is loss — drop the duplicate compute (Phase 2 review #I5)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`66c383b`](https://github.com/chen-star/net_alpha/commit/66c383b379feaf5c488191696e1369afd923d433))

### Style

* style: fix import block sort order in positions.py (ruff I001)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ffd92b9`](https://github.com/chen-star/net_alpha/commit/ffd92b9c950061bc7709560b51929afd027cb513))

### Test

* test(web): re-capture snapshot baselines after C1/C2 color fixes

The C1 negative-sign fix and C2 .text-loss/.text-gain/.text-success
class definitions land real color on previously-uncolored markup:
- positions-at-loss: per-row loss column + summary unrealized now red
- settings-imports: triangle-alert tone change
- tax-wash: T5 mini-bar totals now red
- overview: Today tile + hero negative-vs-contributed render correctly

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`563ec3e`](https://github.com/chen-star/net_alpha/commit/563ec3edfd623c9270214738c369464d5748f76c))

* test(web): re-capture snapshot baselines for Phase 3 page polish

Phase 3 changes:
- overview: KPI grid restructured (1 hero + 2 large + 4 small),
  Today tile added, freshness chip in toolbar, Portfolio H2 +
  Tax planning footer dropped, CASH dedup
- tax-wash: realized-P/L stacked mini-bar, watch/violations panel
  labels, affirmative empty copy, filter chips with reset, calendar
  strip
- tax-proj: inline projection form replacing the YAML snippet
- sim: action-required-for-Sell inline error layout, Recent sims
  this-session panel
- settings-imports: one-explanation card + per-row inline form
- positions-* / ticker-nvda: minor pixel diffs from app.css regeneration

Plus ruff-format cleanups in pnl.py + test_provider.py picked up
during the snapshot run.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`0c992bd`](https://github.com/chen-star/net_alpha/commit/0c992bd4cf92210a45024eb8a24e8243c3bbb132))

### Unknown

* Merge branch &#39;phase3-page-polish&#39; — Phase 3 Page Polish

The §4.1, §4.3, §4.4, §4.6 page-interior polish lands. Overview gets
the new KPI grid (1 hero + 2 large + 4 small) with Total Account Value
as the hero, the Today tile (NEW), single-emit CASH (P3 bug fix),
corrected chart semantics (P6, P7), a freshness chip in the toolbar,
and the dropped Portfolio H2 + Tax planning footer. Tax gets a
realized-P/L stacked mini-bar, loss-harvest budget bar, explicit
forward/backward panel labels, affirmative empty-state copy, filter
chips with reset, and an always-visible calendar strip. The YAML-
snippet projection UI is replaced by an inline form that POSTs to
/tax/projection-config and persists to ~/.net_alpha/config.yaml. The
Settings drawer&#39;s Imports tab gets a one-explanation card + per-row
inline forms (caller=drawer) and a drop-zone preview on drag-over.
Sim grows the recent-sims-this-session panel and account-required
validation that returns an OOB-swap fragment instead of a full-page
clobber.

25 commits across sections A (Phase 2 review backlog), B (Overview
clutter + charts), C (KPI restructure + Today tile), D (Tax polish),
E (inline projection form), F (Imports drawer), G (Sim recents +
validation), H (verification), plus review-feedback fixes for
negative-sign rendering, .text-loss/gain/success CSS aliases, sim
error response shape, and stale hygiene copy.

Phase 3 polishes page interiors. Phase 4 is the visual sweep
(Ticker page mono → sans, KPI variants, label-3 → label-2 contrast,
keyboard shortcuts). Phase 5 is responsive (drop viewport=1024). ([`40a9848`](https://github.com/chen-star/net_alpha/commit/40a98489ac94229542ff6c70de71298abecfdfb7))


## v0.29.0 (2026-04-28)

### Documentation

* docs(web): document /imports/_legacy_page is drawer-fetched only (review nit #12) ([`430a4ab`](https://github.com/chen-star/net_alpha/commit/430a4ab6d942658157a51f078cff1b9c53a2e7aa))

* docs(web): _density_toggle docstring no longer references /holdings (review nit #11) ([`2f2f02d`](https://github.com/chen-star/net_alpha/commit/2f2f02dfe33d47999371a179af0f7fb5b58092a5))

### Feature

* feat(web): /sim accepts ?account= and ?action= for row-action pre-fill

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`eeaee79`](https://github.com/chen-star/net_alpha/commit/eeaee7928ab04a4ad850e9076b375779d871c51a))

* feat(web): pane set-basis form block (single-lot inline; multi-lot links out) ([`adce4a0`](https://github.com/chen-star/net_alpha/commit/adce4a0cce336d9d2c7b3d8e8b069c6e48e98e34))

* feat(web): pane sim-sell preview block + run-full-sim deep link ([`de6a550`](https://github.com/chen-star/net_alpha/commit/de6a550a5398907a05e548d2542c7992bfb8c3e3))

* feat(web): pane header — qty · account · last · basis · loss ([`e239767`](https://github.com/chen-star/net_alpha/commit/e2397678e5d31058550bda2087fbc1572fdd2901))

* feat(web): row click opens positions side pane (Alpine \$dispatch)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`2ac41c1`](https://github.com/chen-star/net_alpha/commit/2ac41c1ad9207c3f5b7e656804a4025a3021f60a))

* feat(web): /positions/pane returns side-pane body fragment

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`b14d4b1`](https://github.com/chen-star/net_alpha/commit/b14d4b1a52d59072c1f63fee0f130b3b455cd5e5))

* feat(web): mount positions side pane skeleton (Alpine + HTMX)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`9252240`](https://github.com/chen-star/net_alpha/commit/9252240e68a3d876e09052eb9411bbb042595af7))

* feat(web): drop &#39;click row to drill down&#39; hint (audit H9)

The row action menu (§3.4) now provides explicit affordance for row
interaction; the inline hint copy is redundant and adds visual noise.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`fcec6f2`](https://github.com/chen-star/net_alpha/commit/fcec6f28553b09205018f4aefb5a7157b2b9a983))

* feat(web): mount row action menu on All / Stocks tab rows

Adds data-row=&#34;position&#34; + group class to each portfolio table row,
appends a matching w-10 header cell, and includes _row_actions.html
in the trailing cell. Uses r.accounts[0] as account label and none
for account_id (PositionRow is account-aggregated, not lot-scoped).

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ac762ec`](https://github.com/chen-star/net_alpha/commit/ac762ecaa77388fb64d1ca426e35f2aac229fcae))

* feat(web): row action menu — Open ticker / Sim sell / Set basis / Copy

Adds _row_actions.html partial with four-item Alpine dropdown, hover-
revealed ⋯ button, and aria-keyshortcuts for future Phase 4 bindings.
Mounts on at-loss table rows. Vendors external-link.svg (Lucide 0.469.0).
Adds .row-actions CSS utility. Adds test_phase2_row_actions.py.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`da504eb`](https://github.com/chen-star/net_alpha/commit/da504eb48bcd5075559cbf41915ca97ebca841bf))

* feat(web): at-loss sorts clear rows first; renders &#39;clear&#39; for past dates

Adds _lockout_sort_key so rows with lockout_clear=None or in the past
sort before future-locked rows (ascending date within each group). Passes
`today` into template context so the lockout cell can compare
`row.lockout_clear &gt; today` and display &#39;clear&#39; for past/None dates.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`5dc8613`](https://github.com/chen-star/net_alpha/commit/5dc8613bf36985eb0ed29b5c47746f97b9b0a233))

* feat(portfolio): HarvestOpportunity exposes open_basis for at-loss UI

Adds required `open_basis: Decimal` field between `qty` and `loss`.
Wires it at the single construction site in compute_harvest_queue using
the loop variable `basis`. Tightens the MKT/BASIS template guards from
`is defined` to direct access. Updates 4 tests that construct
HarvestOpportunity directly (test_harvest_opportunity_minimal ×1,
test_harvest_queue_render.py ×3).

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`3190943`](https://github.com/chen-star/net_alpha/commit/319094321ce26f865e8fc934e641223a0f8230f6))

* feat(web): at-loss table — new Lockout-clear + Replacement columns

Replaces the _harvest_queue.html include with a dedicated table that
always renders column headers (SYM/ACCT/QTY/MKT/BASIS/UNREAL/
LOCKOUT-CLEAR/REPLACEMENT). Also fixes budget field names (net_realized,
cap_against_ordinary) and updates the stale Phase 1 test that expected
the old &#34;Harvest queue&#34; heading.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`b127a14`](https://github.com/chen-star/net_alpha/commit/b127a14dc15cf05c9f50243feb0f3263c8159a74))

### Fix

* fix(web): set-basis form success state stays inside the pane

The pane&#39;s set-basis form had hx-target=&#34;#positions-pane-body&#34; with
hx-swap=&#34;innerHTML&#34;, which replaced the entire pane body with the
data-hygiene endpoint&#39;s bare &lt;li&gt; response (&#34;Reload the page to recompute
totals&#34;). Free-floating &lt;li&gt; outside a &lt;ul&gt; is invalid HTML and the
copy directs the user out of the pane.

Form now uses hx-target=&#34;this&#34; / hx-swap=&#34;outerHTML&#34; and posts to
/audit/set-basis?caller=pane. The route branches on the caller param
to return _positions_pane_set_basis_saved.html — an inline confirmation
with a &#34;Refresh pane&#34; button that fetches /positions/pane to surface
the updated totals.

Existing data-hygiene callers don&#39;t pass ?caller=pane, so they continue
to receive the legacy _data_hygiene_set_basis.html response unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`2d8ccb3`](https://github.com/chen-star/net_alpha/commit/2d8ccb31be41025f07ff343ab4a72f751264adf2))

* fix(web): row action menu — Alpine binding + None handling

C1: aria-expanded was rendered as literal &#34;open ? &#39;true&#39; : &#39;false&#39;&#34;
because the attribute wasn&#39;t prefixed with `:` (or x-bind:). Screen
readers got the source expression; WCAG 4.1.2 violation. Switched to
`:aria-expanded=&#34;open&#34;` so Alpine evaluates and stringifies correctly.

C2: account_id from the All-view&#39;s _portfolio_table.html arrives as
Jinja&#39;s None (the table is symbol-aggregated, no per-row account_id).
Jinja&#39;s |default(&#39;null&#39;) filter only fires when undefined, not None,
so the rendered Alpine handler emitted `account_id: None` — a
ReferenceError that broke the Set-basis menu item silently. Switched
to explicit `account_id is not none` check.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`1547280`](https://github.com/chen-star/net_alpha/commit/15472806f4360451c9af91453210887dceff054a))

* fix(web): /settings/* shows polite hint when drawer closed (review nit #6) ([`1b7ed78`](https://github.com/chen-star/net_alpha/commit/1b7ed78af0fad69e31668de2d2e97aadeeed980e))

* fix(web): drawer placeholders say &#39;Coming soon&#39; not specific phase (review nit #9) ([`6c78e6b`](https://github.com/chen-star/net_alpha/commit/6c78e6bc17cb3157546acd573f35c3078fd02579))

* fix(web): legacy imports page does not highlight Overview nav (review nit #7) ([`ba1857e`](https://github.com/chen-star/net_alpha/commit/ba1857e39464a46c92189f682ff481091b00e086))

* fix(web): empty-state CTA targets /settings/imports directly (review nit #8) ([`78f9927`](https://github.com/chen-star/net_alpha/commit/78f9927feb8c3e4c8fee6df7a903e07a851f307e))

### Style

* style: ruff format fixes for B1/B2 test files

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`1a1edb5`](https://github.com/chen-star/net_alpha/commit/1a1edb5d81f4c8039d3cdebc88c4276ae6d120b3))

* style: ruff format test_phase2_review_backlog.py ([`5d0831a`](https://github.com/chen-star/net_alpha/commit/5d0831a8356cdb819981952cd2717ada835f69b8))

### Test

* test(web): re-capture snapshot baselines for Phase 2 canvas

Phase 2 changes:
- positions-all: row-action menu (⋯) column added; row-click semantics
  changed from window.location to side pane $dispatch
- positions-at-loss: complete rewrite — Lockout-clear + Replacement
  columns, sorted with clear lots first
- sim: hidden &lt;select name=&#34;action&#34;&gt; added so the segmented control&#39;s
  value participates in the form contract
- settings-imports: &#39;polite hint&#39; copy added when drawer is closed
- minor pixel diffs across other pages from .row-actions CSS additions

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`44fed8c`](https://github.com/chen-star/net_alpha/commit/44fed8c8d0dcd36e00d3b628cd5b5842bb6ea12d))

### Unknown

* Merge branch &#39;phase2-positions-canvas&#39; — Phase 2 Positions Canvas

The §3.3/§3.4/§3.5 Positions canvas lands: at-loss tab gets dedicated
Lockout-clear and Replacement columns; every positions row gains a
hover-revealed action menu (⋯) with Open ticker / Sim sell / Set basis /
Copy ticker; a non-modal #positions-pane side pane opens on row click
with sim-sell preview, set-basis form, and open-ticker link; the Sim
page pre-fills from row-action URL params.

24 commits across sections A (Phase 1 review backlog cleanup), B
(at-loss column redesign + HarvestOpportunity.open_basis), C (row
action menu), D (side pane skeleton + /positions/pane route), E
(side pane content), F (sim pre-fill), G (snapshot baselines), plus
review-feedback fixes for the aria-expanded Alpine binding, the
None-handling JS ReferenceError on All-view Set-basis, and the
set-basis form success state.

Phase 2 stays structural — page interiors of Overview, Tax, Imports,
and Ticker are untouched. Phase 3 polishes those; Phase 5 makes the
side pane and drawer responsive at &lt;1024px. ([`ddece0a`](https://github.com/chen-star/net_alpha/commit/ddece0a09f04792b5a5d7b9c3e45916ee8b2a3cb))


## v0.28.0 (2026-04-28)

### Feature

* feat(web): drop Harvest tab from /tax (moved to /positions?view=at-loss)

Removes the Harvest nav tab from tax.html. The route still processes the
harvest view for profile-default routing, but the tab link is gone. Updates
test_tax_default_tab to assert new behaviour (no nav link, content still
renders).

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`cf66f12`](https://github.com/chen-star/net_alpha/commit/cf66f1294335b6ced5900f98739419ade20d4d2d))

* feat(web): positions tab views — at-loss serves harvest queue

Wires harvest queue context (rows, only_harvestable, budget) into
positions_page when selected_view == &#39;at-loss&#39;, replicating the context
that /tax?view=harvest used to build before it became a redirect.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`459a8ff`](https://github.com/chen-star/net_alpha/commit/459a8ffef08f67c62808889fafdfd59840e1d0c0))

* feat(web): positions tab strip — All/Stocks/Options/At-a-loss/Closed

Renames holdings.html → positions.html, updates route to render positions.html
and accept ?view= param. Adds _positions_tabs.html with 5-tab strip wired into
positions.html with view-based partial switching.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`458e715`](https://github.com/chen-star/net_alpha/commit/458e715f23d9247fc986799979714d2ab1cf1559))

* feat(web): remove inline density toggle from page chrome (audit H4/T1)

Per-page density toggles removed from holdings.html, tax.html, and
imports.html; the toggle now lives exclusively in the Settings drawer&#39;s
Density tab. Updated test_density_toggle_in_pages.py to assert drawer
presence instead of per-page chrome, updated test_phase3_smoke.py&#39;s
stale page-key assertion, and added test_phase1_density_relocation.py
to guard against per-page regression.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`8d6f011`](https://github.com/chen-star/net_alpha/commit/8d6f01162e8d5ccca9e15a2896574190896fa00c))

* feat(web): drawer Density tab — global preference

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`b43f89d`](https://github.com/chen-star/net_alpha/commit/b43f89d70963fe2654623ea1654b278a0b2aa4ef))

* feat(web): drawer Imports tab — lazy-load content from legacy page

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`2873c07`](https://github.com/chen-star/net_alpha/commit/2873c0717cb2f54d981c5745b9cb7ed0c40b6d82))

* feat(web): settings drawer tab strip + placeholders

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`494c477`](https://github.com/chen-star/net_alpha/commit/494c477acd75988bffb8ef43304f2f0f809776ee))

* feat(web): auto-open settings drawer on /settings/imports load

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ee94a24`](https://github.com/chen-star/net_alpha/commit/ee94a246845269daa907edc3dd6d24054273e4f5))

* feat(web): add gear icon to topbar with drawer-open dispatch

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`af02141`](https://github.com/chen-star/net_alpha/commit/af021419d2fdfbc4578730d1d46a49fea72b8ee6))

* feat(web): drop redundant topbar pills (audit P10)

Remove account-count and period pills from portfolio and holdings topbar_right
blocks — they duplicate info already shown in the page subhead. Update
test_positions_routes and test_phase1_topnav to match new nav labels.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`21314f8`](https://github.com/chen-star/net_alpha/commit/21314f8d9a1983edc26e33b3bcde5d68f2a07937))

* feat(web): update active_page values for new nav (overview/positions)

portfolio→overview, holdings→positions, wash_sales→tax, imports→overview.
Also updates deprecated nav-badge test assertions to match Phase 1 design
(badge removed from nav, will move to gear icon in Section C).

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`de09100`](https://github.com/chen-star/net_alpha/commit/de0910059ab2a8f3a1d6c1186f771abee8e2e77a))

* feat(web): rewrite top nav — Overview · Positions · Tax · Sim

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`b34ffa8`](https://github.com/chen-star/net_alpha/commit/b34ffa8dcf84bdf6e601dfb286f01e76b5389a2c))

* feat(web): 301 /imports → /settings/imports; add settings drawer entry routes ([`4918311`](https://github.com/chen-star/net_alpha/commit/4918311728ba1d83b008b4223ccda507e2d4487d))

* feat(web): 301 /tax?view=harvest → /positions?view=at-loss ([`e6dd5f0`](https://github.com/chen-star/net_alpha/commit/e6dd5f03033abb7eaa953517b9d1bdf8285a33cb))

* feat(web): 301 /holdings → /positions (preserves query string) ([`a655fdf`](https://github.com/chen-star/net_alpha/commit/a655fdfb6220e7278afdf37d9b6e302a208bde95))

### Fix

* fix(web): CSV upload now redirects to Settings drawer instead of /imports

Critical #3: POST /imports returned 303 to /imports?flash=..., which
then hit the Phase 1 301 to /settings/imports — dropping the flash
param. Flash was already cosmetic (template never displayed it), so
removed the dead plumbing and changed the redirect target to
/settings/imports so the user lands on the drawer&#39;s Imports tab where
the new import is visible in the past-imports table.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`9cb5c63`](https://github.com/chen-star/net_alpha/commit/9cb5c63f4382df881f390a6f89020179ab59d77e))

* fix(web): harvest queue routing — at-loss owns the toggle, /tax fully redirects

Critical #1: the &#39;currently harvestable only&#39; checkbox on
/positions?view=at-loss had hardcoded hx-get=&#39;/tax?view=harvest&#39; and
hx-target=&#39;#tab-content&#39;, neither of which works on the new home.
Parameterized via harvest_form_action / harvest_form_target context vars
and a #positions-tab-content wrapper. HX-Request to /positions?view=at-loss
now returns the panel partial only.

Critical #2: /tax with active/options profile defaults (or ?view=budget)
rendered harvest content with no visible tab. Resolved /budget alias
before the redirect so it 301s to /positions?view=at-loss; updated
profile.default_tax_tab() so &#39;active&#39;/&#39;options&#39; default to &#39;wash-sales&#39;;
deleted the dead harvest-render branch in routes/tax.py and its include
in tax.html.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`0438dc8`](https://github.com/chen-star/net_alpha/commit/0438dc846745dd913a873f4fa21c6629e9d7d1d6))

### Refactor

* refactor(web): /holdings → /positions (redirects come in next commit) ([`b7a8aa5`](https://github.com/chen-star/net_alpha/commit/b7a8aa5604f048835584a886df7ae6e382627afe))

* refactor(web): rename holdings router to positions (no path change yet) ([`e3a5e44`](https://github.com/chen-star/net_alpha/commit/e3a5e4419531bc8e2082b02f777eb303f4389c3b))

### Style

* style(web): ruff format Phase 1 touchpoints

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`ad6da8c`](https://github.com/chen-star/net_alpha/commit/ad6da8cadbd53ab4a33c99c880854e9ff5f38f0e))

### Test

* test(web): re-capture snapshot baselines for Phase 1 IA

PAGES list updated to reflect the new IA: overview / positions-all /
positions-at-loss / tax-wash / tax-proj / settings-imports / sim /
ticker-nvda. The old `holdings`, `tax-harvest`, `imports`, and `portfolio`
baseline directories are gone — those URLs now 301 to the new locations.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`8a676bb`](https://github.com/chen-star/net_alpha/commit/8a676bb36042514b08e3ac36231bc2e015bcfbde))

* test(web): nav-link round-trip smoke test

Adds docstring clarifying the B4 round-trip parametrize purpose: each of
the four nav destinations (/, /positions, /tax, /sim) must appear as an
href on the home page and return HTTP 200 with the label in the HTML.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`7038442`](https://github.com/chen-star/net_alpha/commit/70384427ae4d81ea4d21f951e098caed13518729))

### Unknown

* Merge branch &#39;phase1-ia-migration&#39; — Phase 1 IA Migration

The IA shift from the §3 design spec lands: top nav becomes
Overview · Positions · Tax · Sim plus a settings gear; /holdings becomes
/positions (with 301); /tax?view=harvest 301-redirects to a new
/positions?view=at-loss tab; the Imports page content lifts into a
Settings drawer triggered by the gear; the per-page Density toggle
moves to that drawer as a global preference.

22 commits across sections A (routing &amp; redirects), B (top nav rewrite),
C (gear icon + drawer trigger), D (drawer content — Imports + Density
tabs), E (density toggle removal from page chrome), F (positions tabs
scaffold), G (final verification), plus review-feedback fixes for the
harvest queue toggle wiring and CSV upload redirect target.

Phase 1 is structural only — page-interior content stays untouched.
Phase 2 rebuilds the at-loss tab; Phase 3 polishes page interiors. ([`6d00c2f`](https://github.com/chen-star/net_alpha/commit/6d00c2ffdd8535a475547fcd9a5652739e0432c2))


## v0.27.0 (2026-04-28)

### Build

* build: add snapshot-test/-update targets; exclude from default suite

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`5946130`](https://github.com/chen-star/net_alpha/commit/59461304581af5b56abb6e491086fd64b35fa5b1))

* build: add pytest-playwright + playwright to [dev] extras

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`47e5de2`](https://github.com/chen-star/net_alpha/commit/47e5de2278421d71e2642b7b97527936773748a3))

* build: add vendor-lucide Makefile target (§5.4) ([`1a1f395`](https://github.com/chen-star/net_alpha/commit/1a1f3952ce4e2f07fa2ce999ed6f443317981eca))

* build(css): rebuild app.css after Phase 0 token additions ([`01ae997`](https://github.com/chen-star/net_alpha/commit/01ae9973af8c1e2f597191018f400b332f8b7ebb))

### Documentation

* docs: UI/UX evaluation &amp; redesign spec (Approach B)

Whole-product audit of the web UI across Portfolio, Holdings, Tax, Imports,
Sim, and Ticker pages, plus a multi-phase redesign that reorganizes the IA
(Overview/Positions/Tax/Sim + Settings drawer), promotes the Sim page,
absorbs the harvest queue into Positions, replaces the YAML tax-projection
setup with an inline form, and lays out a polish pass against ~50 audit
findings. Stack and visual tokens kept; five-phase sequencing with snapshot
tests as part of the work.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`099d54b`](https://github.com/chen-star/net_alpha/commit/099d54b2e121ec95b6941aa726fc552e28b84aaf))

### Feature

* feat(web): mount settings drawer skeleton in base.html ([`4ce1ec4`](https://github.com/chen-star/net_alpha/commit/4ce1ec49c4dba045d85d4e6d4985d4ec7968d558))

* feat(web): add empty settings drawer skeleton (§3.6) ([`d6ca51d`](https://github.com/chen-star/net_alpha/commit/d6ca51d0eb105d20c0c294a28b5ddbf20205b0aa))

* feat(web/static): vendor Lucide icons (§5.4) ([`43e71fa`](https://github.com/chen-star/net_alpha/commit/43e71fa646aa7408ebc9c6613be321c1ce2549c0))

* feat(web/css): add Phase 0 design tokens (§5.1) ([`46e8a95`](https://github.com/chen-star/net_alpha/commit/46e8a95ce4d0a1cc549dafca3e9c67a06f262e88))

* feat(web): register fmt_* helpers as Jinja globals ([`4ba38d7`](https://github.com/chen-star/net_alpha/commit/4ba38d7360b1d7310b67cc255560523753b8804e))

* feat(web/format): add fmt_date (§5.9) ([`1e89922`](https://github.com/chen-star/net_alpha/commit/1e89922ae9ae52de74a026487578cc7411b18ba2))

* feat(web/format): add fmt_percent (§5.9) ([`514e1f7`](https://github.com/chen-star/net_alpha/commit/514e1f7be20861877c17dc00b73594c5371f41bb))

* feat(web/format): add density-aware fmt_currency (§5.9) ([`7cb0546`](https://github.com/chen-star/net_alpha/commit/7cb0546b8a4e9506b3678a3f2df9d65dd9e2d385))

* feat(web/format): add fmt_quantity (§5.9) ([`baaf815`](https://github.com/chen-star/net_alpha/commit/baaf8152350e7bcaff7cd959daf53b0b494f3e45))

### Fix

* fix(web): code-review feedback for Phase 0

- Restore the vendor-lucide recipe body and the missing tail of
  LUCIDE_ICONS (refresh-cw, database, download) — earlier rebase
  conflict resolution had eaten both. `make vendor-lucide` is now
  functional and reproducibly fetches all 23 icons.
- Drop the dead `Density = Literal[...]` alias from web/format.py.
  A real `Density` literal already exists in models/preferences.py
  with a different value set (&#34;tax&#34; vs &#34;tax-view&#34;); keeping a second
  one was a future-confusion trap and the alias was unused.
- Settings drawer: drop the redundant static `hidden` Tailwind class
  (Alpine `x-show` + `x-cloak` already handle pre-hydration hide),
  add a window-level `open-settings-drawer` listener so Phase 1&#39;s
  gear-icon trigger can $dispatch from outside the drawer&#39;s Alpine
  scope, and an esc-to-close handler.
- Add the load-bearing `[x-cloak] { display: none !important; }`
  rule to app.src.css that four existing templates rely on.

237 tests pass, 16 snapshots pass, lint clean.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`a184483`](https://github.com/chen-star/net_alpha/commit/a184483e629ce1de7123e8930aad33a8a83dbb64))

### Style

* style: apply ruff format to Phase 0 test files

Trailing pass over tests touched by Section B/C/E to satisfy
`ruff format --check` (re-flowed long Path expressions, set literals,
and an f-string that fit on one line).

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`be29b80`](https://github.com/chen-star/net_alpha/commit/be29b80223360f0660016497aed6a297420c2613))

### Test

* test(web): capture Phase 0 baseline page snapshots

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`9f23727`](https://github.com/chen-star/net_alpha/commit/9f237272aa1d633605c87acce8c7b8dd21a99e3b))

* test(web): baseline-snapshot test scaffolding (no baselines yet)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`6e8ba63`](https://github.com/chen-star/net_alpha/commit/6e8ba6356f0591bf911d0ff6a046046f3eb36ad5))

* test(web): add Playwright snapshot test scaffolding

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`495a468`](https://github.com/chen-star/net_alpha/commit/495a4685cefae9f8b2586a2a9814d76fa1a19f18))

* test(web): assert vendored Lucide icons present ([`08b60b9`](https://github.com/chen-star/net_alpha/commit/08b60b902403a80b04dda7eb86b658ddd4e02315))

### Unknown

* Merge branch &#39;phase0-foundations&#39; — Phase 0 Foundations ([`ff3fe65`](https://github.com/chen-star/net_alpha/commit/ff3fe654a6c978de937406a0d1e2309662588de7))

* Merge branch &#39;master&#39; of https://github.com/chen-star/net_alpha ([`cd9675b`](https://github.com/chen-star/net_alpha/commit/cd9675b671980e7c88e4718a6bd8c61634b45ddb))


## v0.26.0 (2026-04-28)

### Feature

* feat(web): instant page loads, move wash UI to Tax, filterable allocation modal

- Pricing: stale-while-revalidate read path. PricingService.get_prices
  now serves cached entries (fresh OR stale) immediately and only
  network-fetches symbols with no cache row at all. Stale entries surface
  via snapshot.stale_symbols so the Portfolio KPIs panel shows a soft
  refresh hint. Eliminates the multi-second blocking Yahoo fetch on every
  cold load past the 15-min TTL — Portfolio/Holdings/Tax pages now render
  from cache in under a second after the first ever load.

- Tax-related UI off Portfolio: drop wash_impact slot from default
  active/options profile orderings, remove the Wash-sale watch panel
  from the bundled /portfolio/body fragment, and add the watch panel
  to the Tax page wash-sales tab (which already showed the wash-impact
  summary). Wash-watch is now scoped by the same account filter as the
  rest of that tab.

- Allocation modal: was a wall of low-density rows with no way to find
  a symbol. Adds a typeahead filter (Alpine), sticky table header, and
  compact row padding. Two keystrokes to find any holding. ([`a8e7363`](https://github.com/chen-star/net_alpha/commit/a8e7363371a12189c75961c2b2cf477922c496d4))


## v0.25.0 (2026-04-28)

### Feature

* feat(transfers): multi-segment split for one-to-many transfer rows

A single broker-reported transfer often represents shares acquired across
multiple lots/dates. The &#34;Set basis &amp; date&#34; form now accepts N segments
(acquisition_date, qty, basis) per transfer; quantities must sum to the
parent&#39;s total.

Repository:
- New split_imported_transfer() splits the parent row into N siblings that
  share transfer_group_id and transfer_date. The first segment is written
  in place to preserve the original natural_key (re-import dedup still
  works); siblings get derived &#34;&lt;parent_nk&gt;:seg&lt;i&gt;&#34; keys.
- The legacy update_imported_transfer is kept for any direct callers.

Form:
- Alpine-driven add/remove segment rows; running total of segment qtys
  rendered next to the parent total so the user can validate the split
  before submit.
- The form GET now loads existing siblings (when transfer_group_id is set),
  so re-opening a previously-split row shows every segment populated.

Route:
- /trades/{id}/edit-transfer accepts seg_date[], seg_qty[], seg_basis[]
  parallel arrays and validates them as a unit.

Tests cover single-segment edits (legacy behavior), 3-segment splits, and
sum-mismatch rejection.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`493654e`](https://github.com/chen-star/net_alpha/commit/493654eb92e374314ffda7b7784958ff6027f7c7))

* feat(web): portfolio reorg, allocation modal, sortable holdings, all-options panel

- Portfolio page: split into Portfolio (top) and Tax planning (bottom) sections.
- Allocation donut: clickable; opens modal with full ranked breakdown including
  positions normally rolled into &#39;OTHER&#39;. Pledged-cash slice recolored to the
  orange used in the Cash KPI bar so it&#39;s visually distinct from free cash.
- Holdings: sortable column headers (server-side, preserves filters/paging).
- Holdings: &#39;Open short options&#39; panel replaced with &#39;Open options&#39; (long+short),
  sorted by expiry. New compute_open_option_positions function unifies long lots
  with short option chains.
- Ticker page: &#39;accounts&#39; KPI now derives from trades ∪ lots, so symbols with
  only short-option exposure (e.g. UUUU) no longer show &#39;-&#39;.
- Ticker timeline: transfer rows show both &#39;Acq&#39; (acquisition date) and
  &#39;Xferred&#39; (broker-statement date) when they differ.
- Schema v10: trades.transfer_date and trades.transfer_group_id columns added.
  Initial import populates transfer_date from the broker date for transfer rows;
  user edits modify only the acquisition date, preserving the transfer date for
  audit. transfer_group_id reserved for the upcoming multi-segment split.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`6673ba7`](https://github.com/chen-star/net_alpha/commit/6673ba72bf41acd5f22dd6c1840ceeda109a2ee1))


## v0.24.4 (2026-04-28)

### Fix

* fix(ticker): correct realized P&amp;L for short options + Timeline G/L column

Five issues, all on the ticker drilldown page:

#1 (Open lots empty for UUUU): when only short options are open, the long-lots
   table is hidden and replaced with a brief &#34;see Open short options below&#34;
   pointer so the user isn&#39;t staring at an empty table after a CSP open.

#2 (Timeline gain/loss column): added a G/L column showing realized P&amp;L per
   close row. Long-lot Sells show proceeds-basis directly; short-option
   closes (BTC, expiry-synthetic) pair the close cost against the matching
   STO premium so the user sees per-cycle profit on the close row.

#3 (UUUU realized P&amp;L wrong): the legacy realized helper summed
   proceeds-basis for every Sell, which counted STO premiums as realized at
   open and silently dropped BTC closes (which are Buys). Replaced with
   realized_pl_from_trades, which skips STOs and pairs BTCs against their
   matching opens. Wired through ticker.py, compute_open_positions
   (per-symbol realized), and the realized-P&amp;L provenance trace. UUUU YTD
   2026 now reads $826.04 (was $750.02 inflated by un-paired STO premium).

#4 (Schwab Lot Detail title style): heading lifted out of the panel header
   bar and rendered as &lt;h2 class=&#34;text-xl mt-6 mb-2&#34;&gt; to match the Timeline /
   Open lots / Open short options sections.

#5 (sell-put basis/proceeds): same root cause as #3 — the bad realized
   number propagated to FRMI as well. FRMI YTD 2026 now reads -$157.32
   (one closed cycle: STO +129.34 paired with BTC -286.66).

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`af8fb2d`](https://github.com/chen-star/net_alpha/commit/af8fb2d5ccbfef17375ddc54bd59d071b3312226))


## v0.24.3 (2026-04-28)

### Fix

* fix(web): batch-2 — 9 issues (rename hygiene, tax merge, allocation, sim, etc.)

#1 hygiene orphan-sell: match option closes by digit-stripped ticker base so
   Schwab corp-action renames (GME → GME1) don&#39;t get falsely flagged.

#2 nav: drop standalone &#34;Sim&#34; entry from top nav. /sim still reachable from
   Holdings/Ticker &#34;Simulate sale&#34; + Tax/Harvest &#34;Simulate harvest&#34; links.

#3 tax route: read only_harvestable from query string and thread it through
   compute_harvest_queue. Previous code hardcoded False so the checkbox was
   visually toggleable but functionally a no-op.

#4 sim form: pre-fill qty + current Yahoo quote when invoked with
   ?harvest=1, and render a &#34;Harvest mode&#34; banner explaining the pre-fill.

#5 projection placeholder: replace cryptic &#34;tax: section in config.yaml&#34;
   copy with a one-liner pointing at the Tax → Projection tab. The tab
   itself now embeds a copy-paste YAML setup snippet with comments.

#6 tax page: collapse &#34;Offset budget&#34; tab into &#34;Harvest&#34; tab (3 tabs total).
   Budget tile + realized-P/L breakdown render above the harvest queue.
   ?view=budget aliased to ?view=harvest for back-compat.

#7 hygiene unpriced: drop misleading &#34;go fix → /holdings&#34; link and downgrade
   severity to info — there&#39;s no in-app price-override UI, so the link was a
   dead end. Rephrase detail to explain the cause is environmental.

#8 allocation donut: split cash slice into free + pledged with subtly
   different shades (CSP collateral shown muted vs free cash). New
   build_allocation cash_pledged param + AllocationSlice.is_pledged_cash.

#9 short options: move panel from Portfolio body to Holdings page (lazy
   /holdings/short-options fragment). Holdings is the inventory page;
   Portfolio stays focused on KPIs / allocation / wash watch / cash.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`1171620`](https://github.com/chen-star/net_alpha/commit/1171620da98d413d8027fee2b9bd3c44c5038427))


## v0.24.2 (2026-04-28)

### Fix

* fix(web): address 9 UX issues across portfolio/holdings/tax/imports

- portfolio: replace bleed-out templates with .kpi panels (offset budget tile, projection card)
- portfolio: add CSP free/pledged stacked progress bar, csp_count to body ctx
- portfolio: hoist gl_option_closures to single repo call (perf)
- portfolio: rewrite offset_budget_tab and projection_tab as .kpi panels
- holdings: add column-header tooltips and tax-view footnote in _portfolio_table
- tax: rewrite _harvest_queue with intro paragraph + tooltips on Lockout/Premium/Simulate
- tax: wash sales default year = current year; All years opt-in (value=0)
- imports: hygiene basis_unknown now links to /ticker/&lt;sym&gt;#trade-affordance-&lt;id&gt;
- imports: skip orphan-sell warning when basis_source startswith option_short_open
- imports: dup-cluster key includes price-per-share to allow average-down trades
- repo: add _acct_display_cache to Repository __init__ to eliminate N+1

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`2e0abd2`](https://github.com/chen-star/net_alpha/commit/2e0abd2e0cda196c52084d4d6241a2e71f9b951f))


## v0.24.1 (2026-04-28)

### Fix

* fix(web): open-short visualization, lifetime KPIs, density HTMX, tax CSS

- portfolio: skip None proceeds/cost_basis in tax_planner (fixes 500)
- positions: add compute_open_short_option_positions + OpenShortOptionRow
- ticker: KPIs show YTD + Lifetime; timeline merges put_assignment Buy and
  synthesizes &#34;Closed by Expiry&#34; rows; drops duplicate option_short_close_assigned
- portfolio body: new &#34;Open short options&#34; panel (CSP/CC counts, premium, cash
  secured) shown when shorts exist
- density/profile forms switched to hx-post so HX-Refresh response actually
  refreshes the page
- tax: add .tabs/.tab/.tab--active styles, _harvest_queue uses .net-table ([`077694b`](https://github.com/chen-star/net_alpha/commit/077694b1a7fe8ae26097259eb180a9ca0e3d128c))

### Unknown

* Merge branch &#39;master&#39; of https://github.com/chen-star/net_alpha ([`4c1e0fb`](https://github.com/chen-star/net_alpha/commit/4c1e0fb3c5eab131a64fe0e98e36ce9216083b44))


## v0.24.0 (2026-04-28)

### Chore

* chore: ruff format fixes on Section F test files ([`aebb5cd`](https://github.com/chen-star/net_alpha/commit/aebb5cdf93f31fc03118e89e3ee6597e482e4ee4))

* chore(web): ruff UP017 fix — use datetime.UTC alias in Section D files

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`0e66192`](https://github.com/chen-star/net_alpha/commit/0e66192c8226d10db510395bcaf799f3100f0ca8))

### Feature

* feat(web): switcher label reflects current request&#39;s account filter

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`f87cb33`](https://github.com/chen-star/net_alpha/commit/f87cb3396edc64bcf860961c37949d2f02ed0674))

* feat(web): /tax default tab from ProfileSettings.default_tax_tab

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`8edabc6`](https://github.com/chen-star/net_alpha/commit/8edabc64bbd41045dfd8117b786cd73e25174295))

* feat(web): density toggle on /holdings, /tax, /imports ([`687f085`](https://github.com/chen-star/net_alpha/commit/687f085ea570b5339e001ba575643044ffa070d1))

* feat(web): localStorage density override shim ([`eafcec1`](https://github.com/chen-star/net_alpha/commit/eafcec17daef8af85d687fd4877ed9cc20553e76))

* feat(web): density toggle template (Compact / Comfortable / Tax-view) ([`8ea6b14`](https://github.com/chen-star/net_alpha/commit/8ea6b14763e5dcf74cd6ab077e2fd8b6f62aaf9b))

* feat(web): profile-driven extra columns in holdings table ([`6f139b1`](https://github.com/chen-star/net_alpha/commit/6f139b1f8f12155e1c28f2e6be394c46e5aec2bd))

* feat(portfolio): premium_received per position for options profile ([`7ddf561`](https://github.com/chen-star/net_alpha/commit/7ddf5615d0e6d97941bf7308965e5d58e1d6ad96))

* feat(portfolio): position rows expose days_held + lt/st split ([`38aab14`](https://github.com/chen-star/net_alpha/commit/38aab14b9e02d5cb8d04a0d3887a6fb3f7f50849))

* feat(web): KPI hero ordering driven by ProfileSettings.order

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`f7a4265`](https://github.com/chen-star/net_alpha/commit/f7a4265d7433078bdcc478017c79dda303c3c905))

* feat(web): conservative profile collapses wash-watch by default

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ce240a3`](https://github.com/chen-star/net_alpha/commit/ce240a340c6f97751fe36135d4b44734c34660b1))

* feat(web): pass ProfileSettings into /portfolio context

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ffedb56`](https://github.com/chen-star/net_alpha/commit/ffedb567059b1c552b59b32388aca45d37a8883c))

* feat(web): first-visit profile picker modal

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`0505c5b`](https://github.com/chen-star/net_alpha/commit/0505c5b030dd2688f107cf22bdad3832a08a964d))

* feat(web): render profile switcher in base topbar

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`84f27f6`](https://github.com/chen-star/net_alpha/commit/84f27f6d492d0f19c8fb78d70bf5bf8923017fe1))

* feat(web): toolbar profile switcher template

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`fd7b732`](https://github.com/chen-star/net_alpha/commit/fd7b7326221347576b36853f4dddf40412f63748))

* feat(web): POST /preferences writes per-account or all-account prefs

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`b3d79aa`](https://github.com/chen-star/net_alpha/commit/b3d79aa141533fc44f27337e4371425f6dfea7a6))

* feat(web): get_profile_settings FastAPI dependency

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`3cb25ce`](https://github.com/chen-star/net_alpha/commit/3cb25ceb9f8378ca6d85b21b10e5835f6b49d9a2))

* feat(prefs): resolve_effective_profile across single/all-account views

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`9a7fee9`](https://github.com/chen-star/net_alpha/commit/9a7fee9ef7fbe0ed3ad39d0d858fab3a788a298f))

* feat(prefs): default_columns() and default_tax_tab() ([`e758b53`](https://github.com/chen-star/net_alpha/commit/e758b531939d59f40717396e7cd8f623ed26e3b6))

* feat(prefs): ProfileSettings.order() for KPI hero slots ([`3b397ca`](https://github.com/chen-star/net_alpha/commit/3b397caf1559a4adf6fa1c8102a1574c7ffade4a))

* feat(prefs): ProfileSettings.shows() rule table ([`a586965`](https://github.com/chen-star/net_alpha/commit/a5869652d323ef7b8f39a272e461bdef89a01683))

* feat(db): repository methods for user preferences

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`1e8ed24`](https://github.com/chen-star/net_alpha/commit/1e8ed249015f7f90e84a5fcb7445116dada606a3))

* feat(db): v8 -&gt; v9 migration adds user_preferences

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`e3a9cbb`](https://github.com/chen-star/net_alpha/commit/e3a9cbb0a309efda6639bb7123285b2a13afd495))

* feat(db): add UserPreferenceRow for v9 schema

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`9062bca`](https://github.com/chen-star/net_alpha/commit/9062bcae14827dbb25fc64a47be5d75a6b6b645d))

### Fix

* fix(portfolio): premium_received skips assigned-put chain to avoid double-count

Assigned-put STO/synthetic-close trade pairs (basis_source in
{option_short_open_assigned, option_short_close_assigned}) already fold
their premium into the underlying stock&#39;s adjusted basis. Counting them
again in premium_received would double-credit the user-visible figure.

Move the premium accumulator below the existing _SKIP_AGG_SOURCES guard
so the same skip applies. Adds a regression test exercising the
assigned-put chain.

Caught in Phase 3 final code review. ([`21b4ad5`](https://github.com/chen-star/net_alpha/commit/21b4ad5510008bf48eb515f6e4ab542c6a7fabbf))

* fix: emit data-col markers in holdings.html wrapper for empty-state path

The _portfolio_table.html cols-meta span only renders when rows exist.
In the no-imports (empty-state) path the holdings page skips the table
fragment entirely, so data-col attributes never appeared in the HTML.

Add a hidden cols-meta span directly in holdings.html so column markers
are always present in the page regardless of import state.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`713ed6b`](https://github.com/chen-star/net_alpha/commit/713ed6b9eab2cf1612a2bf7679b927e2ed233078))

### Style

* style: ruff format

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ecc7be1`](https://github.com/chen-star/net_alpha/commit/ecc7be14c1050c985899d7e6d750ef2bec5e4e14))

### Unknown

* Merge branch &#39;phase3-density&#39; — Phase 3 Style-Aware Density

Adds the per-account profile (Conservative/Active/Options) and per-page
density toggle (Compact/Comfortable/Tax-view) per spec Section 3.

- v8 → v9 schema migration adds user_preferences table.
- prefs/profile.py encapsulates the visibility/ordering/columns rule
  table (Section 3b) so templates ask profile.shows() / order() /
  default_columns() / default_tax_tab() instead of branching on profile
  strings.
- POST /preferences route + toolbar switcher (request-aware) +
  first-visit modal (3a).
- /portfolio: KPI hero ordering by profile, wash-watch
  collapsed-by-default for Conservative.
- /holdings: profile-driven extra columns (days_held, lt_st_split,
  premium_received, origin_event placeholders) + density toggle.
- /tax: default tab from profile.default_tax_tab().
- /imports: density toggle.
- density.js localStorage transient override.

Phase 3d (smart suggestion) is explicitly deferred per spec.

29 commits, +78 tests (672 → 750 pass + 1 skip). ([`198afa1`](https://github.com/chen-star/net_alpha/commit/198afa1ce22a59960577fbd6d890d310dc34c041))


## v0.23.0 (2026-04-28)

### Feature

* feat(web): rename nav &#39;Wash sales&#39; to &#39;Tax&#39;

Update base.html nav link from /wash-sales (active_page=&#39;wash_sales&#39;)
to /tax (active_page=&#39;tax&#39;). The /tax route sets active_page=&#39;tax&#39; in
context so the nav link highlights correctly.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ddd6f1a`](https://github.com/chen-star/net_alpha/commit/ddd6f1ade0ce35bb480834978f608de10e86ea11))

* feat(web): /wash-sales -&gt; /tax 301 redirect (preserves query string)

The wash_sales_legacy handler redirects all /wash-sales requests to /tax.
Old sub-views ?view=table|calendar are normalised to view=wash-sales.
Update test_calendar.py and test_wash_sales_route.py to follow redirects
or target /tax directly. Add test_tax_redirects.py (3 tests).

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`074ce26`](https://github.com/chen-star/net_alpha/commit/074ce26d7684b8e7b68dbd15a987558895466c7a))

* feat(web): tax.html 4-tab page (wash-sales | harvest | budget | projection)

Create tax.html wrapper template extending base.html with tab nav.
Extract wash-sales tab inner content into _tax_wash_sales_tab.html
(mirror of wash_sales.html inner body, links updated to /tax).
Add _offset_budget_tab.html and _projection_tab.html for budget and
projection tabs.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`6d26543`](https://github.com/chen-star/net_alpha/commit/6d26543623fa537f907b6048c61d4f0a4846f0c2))

* feat(web): add /tax route with tabbed view dispatcher

Extract _wash_sales_context helper from wash_sales.py and wire up new
/tax route supporting wash-sales | harvest | budget | projection tabs.
Register tax_routes.router in app.py. Add test_tax_route.py (4 tests).

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`78c597e`](https://github.com/chen-star/net_alpha/commit/78c597ed5e8a672f15e09978921494140ad47fca))

* feat(web): load etf_replacements into app.state at startup

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`c4a5c8e`](https://github.com/chen-star/net_alpha/commit/c4a5c8edfe80f136f3eba11492fba9b4c90448f5))

* feat(web): pre-trade traffic-light on /sim result

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`2ab3abc`](https://github.com/chen-star/net_alpha/commit/2ab3abc0c15568b15683784bbdda5c2c3749007f))

* feat(tax): assess_trade — bracket-push yellow + lot-method hint

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`e77e7de`](https://github.com/chen-star/net_alpha/commit/e77e7de4ac738c824d7d1996f2178fb8864d893d))

* feat(tax): assess_trade — wash-sale red verdict

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`b736e20`](https://github.com/chen-star/net_alpha/commit/b736e2000a795270fec50a34e423869adf57f499))

* feat(tax): TaxLightSignal model

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`094cb06`](https://github.com/chen-star/net_alpha/commit/094cb068ceb11c058b4fa064b07e8a3eaf4a5830))

* feat(web): year-end projection card on portfolio (with config-missing placeholder)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`4466164`](https://github.com/chen-star/net_alpha/commit/4466164cf053a15d7339ec464a6594ca8f3c2b70))

* feat(tax): year-end tax projection (single marginal rate) + planned trades + bracket-push warnings

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`a72dca4`](https://github.com/chen-star/net_alpha/commit/a72dca4326679fa0a2ab40f11d3031fc1f1ea60b))

* feat(tax): TaxBrackets, TaxProjection, MissingTaxConfig

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`25782af`](https://github.com/chen-star/net_alpha/commit/25782af5ad8ffdd4fe0fd06493b189e8db02b9ee))

* feat(web): offset-budget tile on portfolio KPI strip

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`1b10e6a`](https://github.com/chen-star/net_alpha/commit/1b10e6a255ede31c47a614745bb843fd01eed0de))

* feat(tax): compute_offset_budget with $3K cap + carryforward + planned delta

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`8011099`](https://github.com/chen-star/net_alpha/commit/801109931ded21b2afd6a283a4a3cc2c53aef35d))

* feat(tax): OffsetBudget and PlannedTrade models

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`c8405b4`](https://github.com/chen-star/net_alpha/commit/c8405b4e8205606c68f0e4ca4f4d79cea57f913d))

* feat(web): _harvest_queue.html template

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`9d126a8`](https://github.com/chen-star/net_alpha/commit/9d126a8d38ab8e5d795bf10b2a2818e4e747bd8b))

* feat(tax): compute_harvest_queue with LT/ST split + account filter

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`0d1c0f4`](https://github.com/chen-star/net_alpha/commit/0d1c0f432eefc2b45e202ec76ac8afd288ffe2c7))

* feat(tax): HarvestOpportunity model + portfolio test conftest ([`73cf160`](https://github.com/chen-star/net_alpha/commit/73cf16024b048c5c0209131eb1d2f306e3d6e412))

* feat(engine): cross-asset lockout — open CSP locks out underlying

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`3c55520`](https://github.com/chen-star/net_alpha/commit/3c55520625d915e96c8616a3abec5458c74050f9))

* feat(engine): same-symbol lockout-clear date computation

Adds compute_lockout_clear_date in engine/lockout.py: given a symbol, all trades,
and an as-of date, returns the first wash-sale-safe sale date (most recent buy + 31
days) or None when no buy is in the 30-day window. Handles cross-account buys and
substantially-identical ETF pairs. Structured for Task 6 cross-asset extension.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`bb0b90b`](https://github.com/chen-star/net_alpha/commit/bb0b90b3a4520251d763e0aead2a9d9e58aebfda))

* feat(tax): premium origin extraction for CSP-assigned lots

Adds CSPAssigned / CCAssigned / PremiumOriginEvent models and
extract_premium_origin() to portfolio/tax_planner.py; recovers the
put premium from the STO→BTC-assigned chain for wheel-strategy lots.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`e93a701`](https://github.com/chen-star/net_alpha/commit/e93a7013ea433ccff2dd9fcf387de1b09f176748))

* feat(engine): bundled etf_replacements.yaml + loader with consistency check

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`d95febf`](https://github.com/chen-star/net_alpha/commit/d95febf3415a0da265a3eca54ff9345358fd0445))

* feat(audit): hygiene category &#39;tax_config_missing&#39;

Adds a new `tax_config_missing` info-level hygiene issue that surfaces
when no `tax:` section exists in config.yaml. Threads `settings` through
`collect_issues` and `get_imports_badge_count` as an optional kwarg so
all existing callers remain backwards-compatible.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`c7e6212`](https://github.com/chen-star/net_alpha/commit/c7e62129d22ec3b4f7f6eb1c309085c183c52261))

* feat(config): add TaxConfig model and loader

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`20c756f`](https://github.com/chen-star/net_alpha/commit/20c756fa277cffe59a506a2bb032ad50f67d60f7))

### Fix

* fix(engine): bundled SCHD replacement DGRO to avoid etf_pairs conflict

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`564dc1c`](https://github.com/chen-star/net_alpha/commit/564dc1cfb5ca8c308fe344fed22c780b85646dc1))

### Refactor

* refactor(test): promote seed_lots to portfolio conftest fixture

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`3b32bc3`](https://github.com/chen-star/net_alpha/commit/3b32bc38d96f41c66092c2f65cfffb7ed26b95d7))

### Style

* style: ruff import sort + drop unused TaxProjection test import

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`876cdf9`](https://github.com/chen-star/net_alpha/commit/876cdf9e8fd4ea058a3bc1dfd65a661b8574b179))

### Test

* test(tax): phase-2 smoke — /tax tabs, portfolio embeds, /sim traffic light

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`3180be9`](https://github.com/chen-star/net_alpha/commit/3180be9ad0ed25ce2891f9b48ad77ecd6a1d4f50))

* test(tax): CSP origin round-trips through Repository

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ad1a1fd`](https://github.com/chen-star/net_alpha/commit/ad1a1fd1ba5aa34cfb595a893b12882ac9e78cef))

* test(tax): cross-asset wheel-strategy lockout coverage

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`2f03414`](https://github.com/chen-star/net_alpha/commit/2f03414eea006f3d3905b000e4bf2b0e18719183))

* test(tax): replacement-suggestion wiring in harvest queue

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`390ce2e`](https://github.com/chen-star/net_alpha/commit/390ce2e639e154bc92a3e4313974afbd44a84fe0))

* test(tax): premium offset and only_harvestable filter coverage

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`5481d49`](https://github.com/chen-star/net_alpha/commit/5481d49a7f77bb0d51caf69059ddb50091cec1f4))

### Unknown

* Merge branch &#39;phase2-tax-planner&#39; — Phase 2 Forward Tax Planner

Adds a forward-looking tax planner: harvest queue, $3K offset budget gauge,
single-marginal-rate year-end projection, and pre-trade traffic light. Renames
/wash-sales to /tax with a 4-tab page (Wash sales | Harvest queue | Offset
budget | Projection); /wash-sales returns 301 to /tax. Wheel-strategy
awareness: extracts CSP-assigned premium offsets from existing synthetic
trade chains, and treats open short puts as substantially identical to the
underlying for cross-asset wash-sale lockout. Bundled etf_replacements.yaml
suggests harvest replacements with consistency check against etf_pairs.yaml.

Read-only — no DB schema changes, no migrations. New optional `tax:` config
section in ~/.net_alpha/config.yaml; absence renders a placeholder card on
/portfolio. Tax-bracket data never leaves the box.

30 TDD tasks, 31 commits + plan + 2 small fixups. 672 tests pass (up from
603 on master); 1 skipped; ruff clean.

Plan: docs/superpowers/plans/2026-04-27-tax-planner.md
Spec: docs/superpowers/specs/2026-04-27-provenance-tax-planner-density-design.md (Section 2)

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`3510997`](https://github.com/chen-star/net_alpha/commit/351099755155d2c8f8ee6099a63027b3b606d89c))


## v0.22.0 (2026-04-28)

### Feature

* feat(audit): nav-bar Imports badge with 30s TTL cache

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`812bc22`](https://github.com/chen-star/net_alpha/commit/812bc2215f0a3336f92125784209100898cde669))

* feat(audit): POST /audit/set-basis updates trade basis with HTMX swap

Also clears basis_unknown=False in update_trade_basis when cost_basis is set.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`6417d46`](https://github.com/chen-star/net_alpha/commit/6417d468fffe14cf1b92b40954997346751ecb2c))

* feat(audit): embed data-hygiene section on /imports

Adds _data_hygiene.html partial with severity badges and inline HTMX
fix-forms; wires collect_issues(repo) into the imports_page route so
the section appears when issues exist and is hidden when the DB is clean.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`1c29466`](https://github.com/chen-star/net_alpha/commit/1c29466e94fbcbe8bcca3e6aec345177ef2c3d45))

* feat(audit): hygiene check — duplicate natural-key clusters ([`943abc2`](https://github.com/chen-star/net_alpha/commit/943abc2d4eb2a07614951d713fcb7b4ed56e50e6))

* feat(audit): hygiene check — orphan sells ([`bd44ad8`](https://github.com/chen-star/net_alpha/commit/bd44ad8cb7bb454d7c589cc456891d7e48ad7ace))

* feat(audit): hygiene check — basis-unknown buys with inline fix form

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ed37ce2`](https://github.com/chen-star/net_alpha/commit/ed37ce272ea515584651178f8bdf2401a81cf36f))

* feat(audit): hygiene check — unpriced symbols

Add _get_unpriced_symbols helper (monkeypatchable seam) and implement
_check_unpriced to emit a warn-severity HygieneIssue per equity symbol
with no cached price quote, with fix_url pointing to /holdings?symbol=X.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`7195d8d`](https://github.com/chen-star/net_alpha/commit/7195d8dcfb5c8c8c3e9a181120e5dee4fb73dcc8))

* feat(audit): hygiene scaffold with HygieneIssue model + collect_issues dispatcher ([`76ba3d6`](https://github.com/chen-star/net_alpha/commit/76ba3d6e9f54d9ad39eaca3d806c19af6893d0aa))

* feat(audit): embed reconciliation strip on ticker page (lazy HTMX load)

Passes account_ids (derived from all accounts with trades for the symbol)
to the ticker template; the template renders one hx-get=&#34;load&#34; div per
account that triggers the /reconciliation/{symbol}?account_id= fragment.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ec7a5cd`](https://github.com/chen-star/net_alpha/commit/ec7a5cdcaa58e1688d352275613ddc1c9e45c9b6))

* feat(audit): /reconciliation/{symbol} route + strip + diff fragments

Appends GET /reconciliation/{symbol}?account_id=&amp;expanded= to audit_routes,
renders _reconciliation_strip.html (match/near_match/diff states with HTMX
investigate button) or _reconciliation_diff.html (per-lot table with collapse).

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`d13fd71`](https://github.com/chen-star/net_alpha/commit/d13fd71c26145baad6bc37e6311cdbf366caf522))

* feat(audit): per_lot_diffs() with cause hints

Appends LotDiff model, per_lot_diffs(), and _cause_hint() to
reconciliation.py; pairs broker G/L lots against net-alpha sell
trades by close date and returns deltas with heuristic cause labels.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`1921056`](https://github.com/chen-star/net_alpha/commit/192105675ccee6375d1a3a832382a592686ac73a))

* feat(audit): reconcile() with tolerance + status enum

Adds ReconciliationResult + reconcile() comparing net_alpha realized P/L
against broker G/L lots, classifying results as MATCH / NEAR_MATCH / DIFF /
UNAVAILABLE based on a configurable tolerance threshold (default $0.50).

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`1f32e96`](https://github.com/chen-star/net_alpha/commit/1f32e96e15cb3782c09c476b24f576e071ed8312))

* feat(audit): broker provider registry ([`15b2628`](https://github.com/chen-star/net_alpha/commit/15b262803494cd2dc7764777c8f2a65b90031b92))

* feat(audit): SchwabGLProvider over existing get_gl_lots_for_ticker

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`30cb22d`](https://github.com/chen-star/net_alpha/commit/30cb22d89b17c7ce2227bc0c53ab9c6fc7846110))

* feat(audit): BrokerLot + BrokerGLProvider ABC

Define normalized broker lot row and abstract provider interface for
reconciliation. Supports flexibility across broker GL formats.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`51efb7d`](https://github.com/chen-star/net_alpha/commit/51efb7d9291f3e400f30d9d94d672e62c96e92e6))

* feat(audit): wire provenance triggers into Portfolio KPIs and Ticker page

- Add &lt;dialog id=&#34;provenance-dialog&#34;&gt; mount point to base.html (all pages)
- Add _resolve_account_id + _build_metric_refs helpers to portfolio route
- Pass metric_refs to _portfolio_kpis.html via portfolio_kpis and portfolio_body handlers
- Decorate Realized P/L (period + lifetime), Unrealized P/L (period + lifetime), Wash Impact, Cash, and Net Contributed KPIs with provenance_link macro; skip Open Position $ (market snapshot)
- Build RealizedPLRef per symbol in ticker_drilldown, decorate YTD Realized P/L
- Add integration tests (TDD: 3 fail → 3 pass)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`6b91ec9`](https://github.com/chen-star/net_alpha/commit/6b91ec9b6dc567b88001ec46dfb1dfa46333ed16))

* feat(audit): provenance_link Jinja macro with HTMX trigger

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`44c4899`](https://github.com/chen-star/net_alpha/commit/44c489966bf7ee39871b8b112b6b15bc23b3d900))

* feat(audit): full provenance modal template with three sections

Replace placeholder _provenance_modal.html with the complete template
rendering contributing trades, applied wash-sale adjustments (with rule
citation), and contributing cash events in styled tables.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`610b8f6`](https://github.com/chen-star/net_alpha/commit/610b8f6a250ca1193b94ae83eecdd72316e63e9d))

* feat(audit): GET /provenance/{encoded} returns trace fragment with error fallback

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`4745359`](https://github.com/chen-star/net_alpha/commit/4745359cf4e75e7a905a974c073dae4ff3ae1a84))

* feat(audit): re-export public API from package root ([`1537fc9`](https://github.com/chen-star/net_alpha/commit/1537fc985cc04edc88f4c100537bb26c48507070))

* feat(audit): provenance_for handles CashRef and NetContributedRef variants

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`4004180`](https://github.com/chen-star/net_alpha/commit/4004180d380781ffc9bd4f4bd33e9cfe1d1d1c31))

* feat(audit): provenance_for handles WashImpactRef variant

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`53b0417`](https://github.com/chen-star/net_alpha/commit/53b041713c9b5ea59b5264b280c870e82c7423bd))

* feat(audit): provenance_for handles UnrealizedPLRef variant

Adds _unrealized_pl dispatcher and _account_id_match helper; refactors
_trade_account_match to delegate to the new shared helper (DRY).

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`50c0750`](https://github.com/chen-star/net_alpha/commit/50c0750b260dac64f06c639ab8171fbaee568bff))

* feat(audit): provenance_for handles RealizedPLRef variant

Adds the provenance_for dispatcher to provenance.py with the first
variant _realized_pl, which filters repo.all_trades() by period,
symbol, and account_id to build a ProvenanceTrace with signed amounts
and a human-readable metric_label.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`c7c7388`](https://github.com/chen-star/net_alpha/commit/c7c738871c5e8400b02235a2cdcbf95b3d0f3258))

* feat(audit): add ProvenanceTrace, ContributingTrade, AppliedAdjustment types

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`76fb429`](https://github.com/chen-star/net_alpha/commit/76fb42946c650498129f5615aa892a1f2ac19834))

* feat(audit): add MetricRef discriminated union + base64 encoding

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`9bfcff5`](https://github.com/chen-star/net_alpha/commit/9bfcff531a8eaf7ba1b48919768b77bf631b5adf))

* feat(audit): scaffold audit package with shared test fixture ([`2f3ea76`](https://github.com/chen-star/net_alpha/commit/2f3ea76030950cec3aa9111d8f6460d0f16a1843))

### Fix

* fix(audit): address final review findings

- Hoist repo.list_accounts() out of per-trade and per-violation loops in
  _realized_pl and _wash_impact (matches existing _unrealized_pl pattern).
- POST /audit/set-basis now triggers stitch + recompute_all_violations and
  invalidates the badge cache so engine state matches the DB immediately.
- Export reconcile, collect_issues, and related types from audit/__init__.py
  per the plan&#39;s stable-import-path contract. ([`d4932c9`](https://github.com/chen-star/net_alpha/commit/d4932c953c26162c01bd886e9e97c00a81c7a45c))

### Refactor

* refactor(audit): DRY account-display dict construction via _accounts_by_id

Eliminates the last N+1 in _unrealized_pl (was iterating list_accounts() per
open lot). All three dispatcher branches now share one helper.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`75e6414`](https://github.com/chen-star/net_alpha/commit/75e6414a9ae71e2573ff44df881d1636dcfd74de))

* refactor(audit): hoist all_trades lookup + lift Repository/Trade imports to top

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`08dd71b`](https://github.com/chen-star/net_alpha/commit/08dd71b8c0b0152b75f5d578741fe48dacfa9875))

* refactor(audit): lift seed_import helper into conftest for reuse ([`1dc613f`](https://github.com/chen-star/net_alpha/commit/1dc613f548c8bac2735d46b213c35d27be4f71ac))

### Style

* style(audit): move pytest import to module top in test_metric_ref_encoding ([`6322e1a`](https://github.com/chen-star/net_alpha/commit/6322e1a90e80fdf7c0ccb254ee8985a7f8e82467))

### Test

* test(audit): phase-1 smoke test — provenance + reconciliation + hygiene

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`9be039d`](https://github.com/chen-star/net_alpha/commit/9be039dfd73bd5b98e7e5353f8b1c38f82f4e925))

### Unknown

* Merge branch &#39;phase1-provenance&#39; — Phase 1 Provenance Layer

Three sub-features delivered behind the audit/ subpackage:

Section 1a — Drillable provenance:
  audit/provenance.py with MetricRef discriminated union, ProvenanceTrace,
  provenance_for() dispatcher (5 variants). HTMX route /provenance/{encoded}
  feeds a 3-section modal mounted in base.html. Provenance triggers wired
  into Portfolio KPIs and per-symbol Realized P/L on the Ticker page.

Section 1b — Reconciliation strip:
  audit/brokers/ with BrokerGLProvider ABC + SchwabGLProvider + registry.
  audit/reconciliation.py with reconcile() (MATCH/NEAR_MATCH/DIFF/UNAVAILABLE
  + configurable tolerance) and per_lot_diffs() with cause hints. Lazy HTMX
  strip embedded on the Ticker page.

Section 1c — Data hygiene queue:
  audit/hygiene.py with 4 category checks (unpriced, basis_unknown,
  orphan_sell, dup_key). Embedded section on /imports plus inline fix form
  for basis_unknown. POST /audit/set-basis triggers stitch + recompute
  immediately. Nav-bar Imports badge with 30s TTL cache.

Smoke test exercises all three end-to-end. 603 passing, 1 skipped, zero
regressions.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`2b9bd7f`](https://github.com/chen-star/net_alpha/commit/2b9bd7f5a419a87da89df658776c4209308df341))


## v0.21.0 (2026-04-27)

### Feature

* feat: short-option lifecycle, assigned-put handling, lot-view correctness

The largest change is treating Sell-to-Open / Buy-to-Close as first-class
trades rather than dropping them. The v1 parser silently ignored both,
which made premium income invisible and hid open short-option positions
on tickers like UUUU/HIMS where the user only sells puts. Now:

* SchwabParser emits STO as a Sell and BTC as a Buy, tagged with
  basis_source markers (option_short_open / option_short_close) so
  downstream code can recognise them. Assigned-put STOs are tagged
  separately (option_short_open_assigned) and paired with a synthetic
  closing trade on the assignment date — this keeps the open-option
  counter honest without double-counting the premium (which is also
  folded into the assigned underlying&#39;s cost basis).
* engine/detector skips synthetic STO/BTC pairs from wash-sale scans
  and excludes BTC trades from seeding new long lots.
* engine/stitch skips STO sells from FIFO matching (an STO has no prior
  long lot — falling through to FIFO mis-matched STO premiums against
  unrelated equity buys, producing nonsense realised P/L).
* portfolio/positions now consumes long-option lots by their STC trades
  AND by GL-only closures (Schwab does not write a transaction row for
  options that expire worthless, only a GL row), and surfaces a
  per-underlying &#34;open option contracts&#34; count so tickers with
  option-only exposure show up on the holdings page.
* web ticker drilldown uses the new open_lots_view so long-closed BTOs
  no longer linger in the &#34;Open lots&#34; table forever.

Other changes:

* Option-symbol regexes accept tickers with trailing digits (Schwab
  appends &#34;1&#34; after corp actions: &#34;GME&#34; → &#34;GME1&#34;). A new
  _opt_ticker_base() helper strips the suffix when matching events so
  pre/post-action option pairs net out cleanly.
* Trade gains an occurrence_index so byte-identical Schwab fill rows
  (split fills on the same day at the same price) get distinct natural
  keys instead of one being silently dropped on import. The suffix only
  appears when &gt; 0, so existing DB rows continue to dedup against
  re-imports.
* Repository.add_import replaces a per-trade try/rollback dedup loop
  (which on a duplicate would unwind the entire session, silently
  losing every trade flushed earlier in the call) with a pre-filter
  that mirrors how cash events are already handled.
* PricingService skips split-history lookups for option-shaped symbols
  to silence noisy 404s; YahooPriceProvider drops &#34;no price&#34; warnings
  to debug.
* web/app.py adds an asset_v cache-buster (server-start timestamp) on
  every static asset reference so a dev restart actually refreshes
  CSS/JS in the browser.
* holdings_filter.js handles the case where Alpine has already
  initialised before the script runs (alpine:init never fires).

Tests: ~750 LOC of new coverage across brokers, db, detector, stitch,
positions, holdings routes, plus a new tests/ingest/test_option_parser.py
for the corp-action ticker regression.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`67282c0`](https://github.com/chen-star/net_alpha/commit/67282c06be3d6b2f00ef3fb190c32b44886f18cb))


## v0.20.1 (2026-04-27)

### Chore

* chore: update agent instructions and coverage ([`11c82a2`](https://github.com/chen-star/net_alpha/commit/11c82a2ec4802b001d3e9b10aa7b3d02b404a169))

### Ci

* ci: fix tests by installing all extras ([`0915b39`](https://github.com/chen-star/net_alpha/commit/0915b39a3d839cd5cc470930e3ac34769f98f745))

### Fix

* fix(web): single-pick import — reuse drop-zone file input via form attribute

The import flow forced users to select files twice: once into the drop
zone for preview, then again in the modal because a separate file input
required re-attachment (&#34;Browser security requires re-attaching the
files at submit time.&#34;). That second pick wasn&#39;t actually required —
HTML&#39;s `form=&#34;...&#34;` attribute lets a control submit with a form that&#39;s
elsewhere in the DOM.

The drop-zone&#39;s `#csv-input` now declares `form=&#34;import-form&#34;` and the
modal form carries that id. The modal&#39;s redundant file picker, &#34;Choose
files&#34; button, file-chip, and re-attach hint are removed.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`1a7d4c0`](https://github.com/chen-star/net_alpha/commit/1a7d4c022d1dd246d9b04706abe541b26386b6cc))


## v0.20.0 (2026-04-27)

### Chore

* chore: ruff format + lift in-function imports to module top ([`4331f86`](https://github.com/chen-star/net_alpha/commit/4331f867f8aa7c5648e16c24ba62153def5c69b3))

### Feature

* feat(web): show cash_event_count on imports list summary and detail row ([`1a56a47`](https://github.com/chen-star/net_alpha/commit/1a56a47a7c5d9c55bb656954e2bc06ed234a4164))

* feat(web): portfolio body — equity + cash side by side, allocation full width ([`5e3d60c`](https://github.com/chen-star/net_alpha/commit/5e3d60ca4fee504746edb805165e04e6dfc6a5be))

* feat(portfolio): allocation donut shows Cash slice

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`1a47e3e`](https://github.com/chen-star/net_alpha/commit/1a47e3e5473f1c9f0cf6917e3f9655290e7f2300))

* feat(web): add Cash / Net contributed / Growth KPI tiles ([`945e162`](https://github.com/chen-star/net_alpha/commit/945e16252845957bc2c452ec0d35bed6669cee44))

* feat(web): add _portfolio_cash_curve.html — ApexCharts balance + contributions ([`348c0ee`](https://github.com/chen-star/net_alpha/commit/348c0eeae1bcb29196e199824d7bb4613fb671b7))

* feat(web): wire cash KPIs/points/slice into /portfolio/body context

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`bc19806`](https://github.com/chen-star/net_alpha/commit/bc1980691fb6349216a6f0150f4e16014a7e5c30))

* feat(portfolio): cash_allocation_slice for donut integration ([`234c422`](https://github.com/chen-star/net_alpha/commit/234c422c5d91da81a8bf30a841226a60bc81835e))

* feat(portfolio): compute_cash_kpis ([`2ea7503`](https://github.com/chen-star/net_alpha/commit/2ea7503ab1c0cc432e2bb85833b586035501093b))

* feat(portfolio): add CashFlowKPIs dataclass alongside CashBalancePoint ([`dff17aa`](https://github.com/chen-star/net_alpha/commit/dff17aa41279cd8390741b3a9308adf47c024828))

* feat(portfolio): build_cash_balance_series with period and account scoping

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`25b768f`](https://github.com/chen-star/net_alpha/commit/25b768f198821efac8dbac993da2b522f4a3d9dd))

* feat(import): wire cash_events through add_import and Schwab callers ([`aa3dee0`](https://github.com/chen-star/net_alpha/commit/aa3dee08c2331a227befe41b15e445c7a431959a))

* feat(schwab): emit CashEvent for transfers, dividends, interest, fees, sweeps ([`134fe4b`](https://github.com/chen-star/net_alpha/commit/134fe4b2204043dec5084b69d2e5fb986a14be5f))

* feat(schwab): populate gross_cash_impact from CSV Amount on every trade ([`a629b19`](https://github.com/chen-star/net_alpha/commit/a629b19eb4981799dd8b55ed412483ca420d8f21))

* feat(models): add ImportResult for parser return value ([`bea7e36`](https://github.com/chen-star/net_alpha/commit/bea7e36ac15b77c11e4f0a4ac668877e97fe7f9b))

* feat(repo): list_cash_events with account/date scoping ([`832f870`](https://github.com/chen-star/net_alpha/commit/832f8708f9c8bc3d41f59c8a6c4cc45788daf699))

* feat(repo): add_cash_events with dedup on (account_id, natural_key) ([`2d682d3`](https://github.com/chen-star/net_alpha/commit/2d682d3c5da5f4a474cf9f19bb8536f5d5bc5946))

* feat(db): v7→v8 migration for cash_events + gross_cash_impact + cash_event_count ([`f63828b`](https://github.com/chen-star/net_alpha/commit/f63828b13d1f7c37427ee6fdeb1530c467e87e9d))

* feat(db): add cash_events table; add gross_cash_impact and cash_event_count columns ([`e401c91`](https://github.com/chen-star/net_alpha/commit/e401c914fb47fe9f8fb8fe309763e37d4dcee647))

* feat(models): add CashEvent domain model with natural_key dedup ([`5b0e645`](https://github.com/chen-star/net_alpha/commit/5b0e645bbebdf5aaf3c49bfc54cf90e778ae4308))

### Fix

* fix: review-found correctness bugs in cash-flow

1. (Critical) add_import pre-filters cash events by natural key in-session
   instead of relying on UNIQUE-constraint rollback, which was unwinding
   trades flushed earlier in the same session and silently dropping data.
2. (Important) _trade_cash_delta returns 0 for Security Transfer /
   Journaled Shares rows (basis_source=transfer_in/out). The legacy
   fallback to ±cost_basis was fabricating phantom cash debits/credits
   for share-only transfers.
3. (Important) Schwab parser maps Reinvest Dividend, Long Term Cap Gain,
   Short Term Cap Gain to dividend cash events (DRIP and capital-gain
   distributions are widely used; the previous mapping silently lost them
   as &#34;Unknown action&#34; warnings).

Adds regression tests for each. ([`7769970`](https://github.com/chen-star/net_alpha/commit/776997020345d98f38e832af48ae809f7ed755c2))

* fix: skip option-side actions in parse_full; clarify add_import commit ordering ([`72590a1`](https://github.com/chen-star/net_alpha/commit/72590a1e959ce4e17db4e4d088ba58dfe227f739))

* fix(repo): cascade-delete cash_events on remove_import ([`531175d`](https://github.com/chen-star/net_alpha/commit/531175d65f8bab4ee9fd618f9bee559bbc6e0f8a))

### Test

* test(integration): cash events round-trip through both Schwab CSV fixtures

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`a8e0667`](https://github.com/chen-star/net_alpha/commit/a8e06674a368efc08df4bbac3b288d37b2770e1d))

* test(fixtures): add anonymized Schwab Short/Long Term Transactions CSVs ([`406d8fc`](https://github.com/chen-star/net_alpha/commit/406d8fc4dfc9926717cbf219e26e29055731dbec))


## v0.19.0 (2026-04-27)

### Feature

* feat(splits): manual per-lot edit UI on /ticker/{sym} (item 5)

Inline edit form on each lot row writes a lot_overrides record with
reason=&#39;manual&#39;. apply_manual_overrides replays the latest edit per
(trade_id, field) at the end of every recompute, so manual edits survive
re-import / unimport / future recomputes.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`2257a5e`](https://github.com/chen-star/net_alpha/commit/2257a5ec44c256607ea8894f0176756428035b28))

* feat(splits): auto-sync on first import of a new symbol (item 5)

When a CSV brings in symbols never seen in any prior import, fire
sync_splits for those symbols so existing wash-sale data stays accurate
without manual intervention. Re-imports of known symbols do NOT trigger
fetch -- avoids burning network on every CSV upload.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`69062d2`](https://github.com/chen-star/net_alpha/commit/69062d2842cb490025487cfd6e19b94b1cd7c6b1))

* feat(splits): /splits/sync endpoint + toolbar Sync splits button (item 5)

PricingService.sync_splits orchestrates fetch -&gt; upsert -&gt; apply. Honors
the prices.enable_remote flag (returns error_symbols when disabled, no
network call). Toolbar button POSTs symbols=ALL and reloads the page.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`9b48931`](https://github.com/chen-star/net_alpha/commit/9b489312722570ad7b00d893a666e3b38ec635d2))

* feat(engine): apply_splits as final step of recompute (item 5)

apply_splits mutates regenerated lots whose date is before each known
split&#39;s ex-date. Multiplies qty by ratio, preserves total basis (basis is
dollar, not per-share). Idempotent via (trade_id, split_id) check in
lot_overrides. Wired into recompute_all_violations so re-imports and
unimports keep the split-adjustment intact. ([`e772201`](https://github.com/chen-star/net_alpha/commit/e7722013fa7ff953c83ce182aff06707fdc211cc))

* feat(pricing): YahooPriceProvider.fetch_splits (item 5)

Wraps yfinance.Ticker.splits with the same error-swallowing pattern as
get_quotes (per-symbol failures return empty list, never raise). Default
on the ABC is to return [] so providers without split data are unaffected.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`71251bf`](https://github.com/chen-star/net_alpha/commit/71251bf642033729e9ccf1181e5fc7a4db00d7f6))

* feat(db): repository methods for splits + lot_overrides (item 5)

add_split is idempotent on (symbol, split_date). lot_overrides keyed by
trade_id since lots are regenerated on every recompute.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`f2b8a37`](https://github.com/chen-star/net_alpha/commit/f2b8a370a8cf7e4dca809e693751d28b1f6d17e0))

* feat(db): schema v7 — splits + lot_overrides tables (item 5)

splits: keyed (symbol, split_date) UNIQUE, holds ratio + source + fetched_at.
lot_overrides: audit trail of qty/basis changes, keyed by trade_id (stable
across recompute since lots are regenerated). split_id FK lets apply_split
check idempotency. Also bumps hardcoded version assertions in older migration
tests from 6 → 7. ([`0fa6c37`](https://github.com/chen-star/net_alpha/commit/0fa6c37f57bde1deea89d0cd3e56deefaf31452b))

* feat(web): merge Detail+Calendar into /wash-sales (item 2)

New route /wash-sales with ?view=table|calendar toggle and a unified
filter bar (ticker, account, year, confidence). Old paths 301-redirect
preserving query string. Top-nav &#39;Detail&#39; becomes &#39;Wash sales&#39;;
&#39;Calendar&#39; link removed.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`be3dc16`](https://github.com/chen-star/net_alpha/commit/be3dc164c7a4d02c0da47d3f86430604bb36c86c))

* feat(web): drop Realized col, add % to Unrealized on Holdings (item 1)

The {period} Realized column was redundant with Portfolio KPIs and the
Timeline. Unrealized now stacks dollar and percent inline, color-coded
together. Percent is unrealized_pl / open_cost.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`62f545d`](https://github.com/chen-star/net_alpha/commit/62f545d66ae0ca020a17b2bc544d56b237f4aea0))

### Fix

* fix(splits): apply_splits idempotent via canonical formula

The override-existence skip was wrong — lots are regenerated by
replace_lots_in_window on every recompute, so a prior override row
left freshly-regenerated raw lots un-adjusted on the next recompute.
After Sync splits + any other import, SQQQ silently reverted from 10
shares back to 100 — exactly the user&#39;s original bug.

Replace the skip-list approach with a canonical-inputs formula:
lot.quantity = trade.quantity * cumulative_ratio (product of split
ratios with split_date &gt; trade.date). Idempotent by construction —
calling apply_splits any number of times produces the same result.
The lot_overrides table for splits is now purely an audit log.

Adds regression test test_split_survives_repeated_recompute that
fails without this fix.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`950f988`](https://github.com/chen-star/net_alpha/commit/950f988c166b5c8714ffef014b7d35c4adfe1501))

* fix: route CLI lot replacement through recompute_all_violations

Both cli/default.py:run and cli/imports.py:remove_cmd called
detect_in_window + replace_lots_in_window directly, bypassing apply_splits
and apply_manual_overrides. After CLI unimport or re-import of split-
affected symbols, lots were silently un-split-adjusted. Switch both paths
to recompute_all_violations which already handles the post-replace apply
steps. Also: PricingService.sync_splits now calls apply_manual_overrides
after apply_splits so manual edits retain precedence after a sync.
Polish: SplitRow imports lifted to module top, repository methods now
return list[Split]/list[LotOverride] instead of bare list.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`07be6ca`](https://github.com/chen-star/net_alpha/commit/07be6ca1e8e3a188eb0c8f30d3e199b89d5d269f))

* fix(web): extract holdings symbol filter to named Alpine component (item 4)

Inline 27-line x-data block was leaking its expression body as visible text
on the Holdings page when Alpine couldn&#39;t evaluate it. Move to a separate
JS file as a named Alpine.data(&#39;symbolFilter&#39;, ...) component, so the only
inline content is the call site. Also fixes the dropdown&#39;s mis-positioning
(secondary symptom of the same parse failure).

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`3aad68f`](https://github.com/chen-star/net_alpha/commit/3aad68fbb7dc8833dc599f9bc45b99d435be90d3))

* fix(web): toolbar form stays on the current page (item 6)

Previously the period/account toolbar always submitted to /, bouncing the
user from /holdings back to portfolio when they changed Account. Now each
page passes its own toolbar_action.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`8638369`](https://github.com/chen-star/net_alpha/commit/86383690b6cc1262af05d59799c766a7a7a11579))

### Test

* test(splits): e2e split survival across unimport/reimport (item 5)

Adds integration test verifying split adjustments persist through the
full lifecycle of import -&gt; sync -&gt; unimport -&gt; reimport. Includes fix
to remove_import to clean up lot_overrides for deleted trades, allowing
splits to be re-applied on reimport.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`0806438`](https://github.com/chen-star/net_alpha/commit/0806438cdd4dc7644bfee6ce3a2bf3026231fefa))


## v0.18.0 (2026-04-27)

### Chore

* chore: sync uv.lock to pyproject v0.17.0 wash-alpha version

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`cf192c4`](https://github.com/chen-star/net_alpha/commit/cf192c43fa5dca0d18088ef4034053efb1ba679a))

### Feature

* feat(web): Add-trade button + per-row affordances + form modals on ticker page ([`8b096bf`](https://github.com/chen-star/net_alpha/commit/8b096bf5e270e9b92ea11c0896073659d9feb7b6))

* feat(web): POST /trades/{id}/delete ([`055e9f6`](https://github.com/chen-star/net_alpha/commit/055e9f6a76e23fb0cba769fc68420c003b453b8c))

* feat(web): POST /trades/{id}/edit-manual ([`ee29587`](https://github.com/chen-star/net_alpha/commit/ee29587080d4068bbe0e6e9f67da3c1aa55050c8))

* feat(web): POST /trades/{id}/edit-transfer ([`dbc8066`](https://github.com/chen-star/net_alpha/commit/dbc80663cc10247963f1b97a9fecbb9daa7faa99))

* feat(web): POST /trades — create manual trade

Form-driven endpoint mapping Buy/Sell/Transfer In/Transfer Out to
(action, basis_source), validates account/date/quantity, calls
repo.create_manual_trade, and redirects to /ticker/{symbol}.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`b5bde27`](https://github.com/chen-star/net_alpha/commit/b5bde27dadcde8b0e55d521ddc0ed319c587a7a0))

* feat(web): Timeline shows Transfer In/Out + provenance badges

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`001b606`](https://github.com/chen-star/net_alpha/commit/001b606583e4fdf18c65d0eaac40b24e912ac577))

* feat(web): display_action helper for transfer-aware Timeline labels ([`76b03cc`](https://github.com/chen-star/net_alpha/commit/76b03ccc93465702aea6d7202dd52b9bf258165d))

* feat(db): Repository.delete_manual_trade ([`d7a26c6`](https://github.com/chen-star/net_alpha/commit/d7a26c6a73adb8d623a1077bcdef0eaeacc249d1))

* feat(db): Repository.update_manual_trade ([`b7bb8fb`](https://github.com/chen-star/net_alpha/commit/b7bb8fb8d6fa2f37c73d805f0ec1151270aa1cdf))

* feat(db): Repository.update_imported_transfer (date + basis|proceeds, immutable natural_key) ([`8111a2c`](https://github.com/chen-star/net_alpha/commit/8111a2c9623e76500bc9d6f5977a738d65654019))

* feat(db): Repository.create_manual_trade + manual: natural_key namespace ([`34451d8`](https://github.com/chen-star/net_alpha/commit/34451d8daed4280afac37edf0ff8e430d1744a1c))

* feat(models): Trade.is_manual + Trade.transfer_basis_user_set

Add is_manual and transfer_basis_user_set fields to the Pydantic Trade
model and TradeRow SQLModel, relax TradeRow.import_id to nullable, and
propagate both flags through _row_to_trade so all_trades() carries them.
Update v5→v6 migration tests to reflect completed Task 2 state.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`3ebc0e4`](https://github.com/chen-star/net_alpha/commit/3ebc0e4cb2d5c10768dec3d499fa29ef64983ed7))

* feat(db): v5→v6 — add trades.is_manual + transfer_basis_user_set; nullable import_id

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`4acec5f`](https://github.com/chen-star/net_alpha/commit/4acec5f3bfeea9ecc6a863a51da3edcbab1f9a6a))

* feat(web): multi-select symbol filter popover on holdings table

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`fc62c7a`](https://github.com/chen-star/net_alpha/commit/fc62c7aaa0a372c42e9bb5901b8ddf88495a0fb7))

* feat(web): replace ?q= with ?symbols= multi-select filter on /portfolio/positions ([`6da8634`](https://github.com/chen-star/net_alpha/commit/6da8634346e27606f31893e7b042e748f84a8d88))

* feat(web): /holdings page + nav link

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`de0467f`](https://github.com/chen-star/net_alpha/commit/de0467fb1adf05794f91d83da70c5cb5f8ee72c1))

* feat(web): single-fragment load on /portfolio (5x→1x request) ([`6d7806e`](https://github.com/chen-star/net_alpha/commit/6d7806e20bd1b18fe37770b229ba268312a8ca72))

* feat(web): /portfolio/body bundled fragment

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`04936bc`](https://github.com/chen-star/net_alpha/commit/04936bcaf79257a7dc0df296dab51a2b775c3a82))

* feat(web): styled Choose Files button + file-count chip in import modal ([`5ea9c5e`](https://github.com/chen-star/net_alpha/commit/5ea9c5ed6e6454faab93f37fae102d9813bd8100))

* feat(web): equalize equity-curve and allocation panel widths ([`4c94c9f`](https://github.com/chen-star/net_alpha/commit/4c94c9f76f649ee9d6d0948d944dc79308998991))

### Fix

* fix(web): account allow-list from list_accounts; add duplicate-count and violation-removal tests

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`fc4384f`](https://github.com/chen-star/net_alpha/commit/fc4384f8726dd58a1ba0e694144e1d4fa21e8864))

* fix(test): include is_manual + transfer_basis_user_set in raw trade insert ([`bc154bd`](https://github.com/chen-star/net_alpha/commit/bc154bd71a1d3408a8ab3f1a53bad6205c1ab018))

* fix(web): correct hx-target and smart-quote regressions in holdings table fragment

Replace legacy hx-target=&#34;#portfolio-positions&#34; with &#34;#holdings-positions&#34; across all Show/Pagesize/Pagination buttons; fix Unicode smart quotes (U+201C/201D) on the status-hint span class attribute; add two regression tests covering both issues.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`af8fb92`](https://github.com/chen-star/net_alpha/commit/af8fb92fed5d1b9d549e65b2050a6dd74cd96e4b))

### Test

* test(integration): re-import preserves user edits to transfer rows

End-to-end check that editing a transfer-in row&#39;s date+basis survives
a re-import of the same Schwab CSV (idempotent dedup via natural_key).

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`abb426b`](https://github.com/chen-star/net_alpha/commit/abb426befc281b28159cf1dc5aac0fd13b8df822))

### Unknown

* wip: pre-Plan-A/B checkpoint

Snapshot of in-progress work prior to executing the
2026-04-26 portfolio-polish-and-perf and manual-trade-crud plans.

Includes:
- schwab.py: transfer-in/out parsing + put-assignment basis offsets
- portfolio/positions.py: FIFO lot consumption with GL closure fallback
- portfolio/equity_curve.py: dual-series (realized + total) curve
- web templates: _portfolio_table + _portfolio_equity_curve restyle iterations
- repository/migrations/tables/domain: small alignment changes
- AGENTS.md / CLAUDE.md: stale GitNexus stat refresh

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`d7c56d8`](https://github.com/chen-star/net_alpha/commit/d7c56d84b286ead08ba75c13343b121cbeb2099b))


## v0.17.0 (2026-04-27)

### Chore

* chore: post-review cleanup — drop dead routes, add static-asset tests, polish

Following the cross-cutting code review, addresses five gaps:

- Delete `_portfolio_wash_impact.html` + `_portfolio_lot_aging.html` and the
  two now-unreachable route handlers `/portfolio/wash-impact` and
  `/portfolio/lot-aging`. Wash impact info is in the KPI strip; lot-aging
  detail lives on the ticker page. Drop the corresponding tests.
- `static/charts.js`: expose `warn` (#FF9F0A) in the theme `colors` table.
- `app.src.css`: add `font-feature-settings: &#34;tnum&#34;, &#34;ss01&#34;` on `.num`.
- `tests/web/test_static.py`: add file-existence smoke tests for the
  ApexCharts vendor JS+CSS and `charts.js`.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`c25aa76`](https://github.com/chen-star/net_alpha/commit/c25aa7675b1239afd4ac77958d9b8dbe246a0d35))

* chore: remove treemap module, template, tests, and TreemapTile model ([`73ec625`](https://github.com/chen-star/net_alpha/commit/73ec625df9227f37311531ff2fa3c9d19615939a))

### Documentation

* docs: update CLAUDE.md — treemap → allocation + add wash_watch in portfolio module list ([`b5777b4`](https://github.com/chen-star/net_alpha/commit/b5777b461c5ad7f245188dbaf6240052c339dcd6))

### Feature

* feat(web): restyle sim + detail + ticker + error pages

Apply Apple-dark design tokens across all remaining web pages:
- sim.html: seg/seg-active action picker, surface inputs, panel form
- _sim_buy_result / _sim_sell_result: panel cards, num cells, pos/warn labels
- detail.html: panel totals bar with chip-confirm/probable/unclear, surface inputs;
  adds {% set active_page = &#39;detail&#39; %}
- _detail_table.html: panel+net-table, chip-* confidence, info/warn source badges,
  hover:bg-surface-2 rows
- ticker.html: panel KPI grid, net-table timeline; adds panel wrappers
- _lots_table.html: net-table, text-label-2 empty state, bg-warn/5 wash-adj rows
- _schwab_lot_detail.html: panel+net-table, text-neg wash-sale Yes, text-label-3 No
- error.html: panel centered card, text-label-2 detail, bg-surface-2 traceback,
  btn-ghost &#34;← Back&#34; link

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`fa9daee`](https://github.com/chen-star/net_alpha/commit/fa9daeec51e8009270a2f15ff4490301eff90240))

* feat(web): restyle imports + drop zone + modal + toast

Apply Apple-dark design tokens to the full imports stack: panel/net-table
wrappers, dashed drop zone with rgba border, surface-variant modal,
pos/warn colours in detection cards, label-uc labels, and bg-pos toast.
Adds {% set active_page = &#39;imports&#39; %} for topbar highlight.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`b115a71`](https://github.com/chen-star/net_alpha/commit/b115a71816f4064546f1a0c5700b02692a3435be))

* feat(web): restyle calendar pages — Apple-system colors for ribbons + dots

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`4f9aade`](https://github.com/chen-star/net_alpha/commit/4f9aade9ee60037faf00febac19556ac4395127e))

* feat(web): restyle positions table, toolbar, empty state under new tokens

Replaces all slate/white/emerald Tailwind classes in the three portfolio
fragments with the new design-token components (net-table, panel, seg,
seg-active, label-uc, btn, text-pos/neg, text-label-*, bg-surface-*).
Adds ticker-initial icon block to the Symbol cell and hairline borders
on select and pagination controls via CSS variable.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`8e7e1e5`](https://github.com/chen-star/net_alpha/commit/8e7e1e590b3e896a0fd27376ab8758f84a1ea72a))

* feat(web): equity curve via ApexCharts area chart with violet &#39;+ unrealized&#39; marker ([`7efac86`](https://github.com/chen-star/net_alpha/commit/7efac865b5b1587e0f620f1ad7353e10a1bcb904))

* feat(web): shared ApexCharts dark theme + merge helper at /static/charts.js ([`952357f`](https://github.com/chen-star/net_alpha/commit/952357ff4c72c5962bad53241d1ba03495753159))

* feat(web): /portfolio/wash-watch fragment route

Wire recent_loss_closes() into a GET handler that renders the
_portfolio_wash_watch.html partial; supports ?account= and ?window_days=
query params.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`9a7dc8c`](https://github.com/chen-star/net_alpha/commit/9a7dc8cf307783094928bf8d0aa7ab8815bdafff))

* feat(web): wash-watch partial — countdown rows w/ red→amber→green safe-bar

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`a0b7266`](https://github.com/chen-star/net_alpha/commit/a0b72666792489168dd35d17ef4c3db33d77fc7e))

* feat(portfolio): recent_loss_closes — wash-sale watch aggregation

Add LossCloseRow model and recent_loss_closes() pure function that
aggregates sell trades with negative realized P/L in the last N days,
collapsed per symbol (most recent close, summed loss), sorted by
close_date desc.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`e1b5b4a`](https://github.com/chen-star/net_alpha/commit/e1b5b4a6365cdf9af379862b2dc67e21a7b0450a))

* feat(web): portfolio.html — equity+allocation row, wash-watch slot, drop treemap ([`31ac787`](https://github.com/chen-star/net_alpha/commit/31ac7879325f575116a1ae48bb3640c8ee431869))

* feat(web): replace /portfolio/treemap route with /portfolio/allocation ([`0bd2fed`](https://github.com/chen-star/net_alpha/commit/0bd2fedbfd9b7c02bf092d94aaabb2fc81a196e5))

* feat(web): allocation partial — donut + concentration stats + ranked chips ([`27ec5dd`](https://github.com/chen-star/net_alpha/commit/27ec5ddc6dd206c841912ce90e3e788ef2079044))

* feat(portfolio): build_allocation — donut/leaderboard view + concentration stats

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`063c7dc`](https://github.com/chen-star/net_alpha/commit/063c7dc4b9d1fa88f86a1043d580982f068b3ff5))

* feat(web): combined hero KPI cards (Realized/Unrealized) + Open$/Wash mini

Collapse 6-card KPI partial into 4-card grid-cols-12 layout: Realized hero (4 cols, kpi-hero halo) with YTD+Lifetime side-by-side, Unrealized hero (4 cols), Open position $ mini (2 cols), Wash impact mini (2 cols). Route now calls compute_wash_impact and passes wash_impact_total + wash_violations into template context.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`bf49788`](https://github.com/chen-star/net_alpha/commit/bf497883e6cb18c76bd28233ac186a197e30f831))

* feat(web): restyle base.html — vibrancy topbar, Inter+JBM, ApexCharts include

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`536640d`](https://github.com/chen-star/net_alpha/commit/536640dc9eb2242a28df83cf7e51dd6c34e3ed5a))

* feat(web): @font-face Inter+JBM, type scale, full component library

Add 6 @font-face declarations for Inter (400/500/600/700) and JetBrains
Mono (400/500), expand @layer base with dark color-scheme, Inter font
features/antialiasing, h1-h3 type scale, and .num tabular-nums rule.
Replace minimal @layer components with full design-system library:
.panel, .panel-head, .label-uc, .kpi, .kpi-hero, .seg, .pill, .topbar,
.nav-link, .chip-confirm/probable/unclear, .net-table, .brand-mark, plus
back-compat aliases; var(--color-*) used directly where @apply text-*
tokens conflict with Tailwind v4 reserved names.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`564feb2`](https://github.com/chen-star/net_alpha/commit/564feb211a02b8142ce3ac052d7a6eaa504e4ffd))

* feat(web): new Apple-dark color tokens, font stack, radii in tailwind config

Replaces the old small v3-style palette with the full Apple-dark design
system (canvas, hairline, label-tier text, P/L semantics, rank slots, brand
cyber). Updates app.src.css from Tailwind v3 directives to v4 @import/@theme
syntax required by pytailwindcss 0.1.4 (bundles Tailwind v4). Back-compat
aliases (confirmed, probable, unclear, primary, secondary, accent, ink) keep
existing templates rendering until they are restyled.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`804a90c`](https://github.com/chen-star/net_alpha/commit/804a90cc07f11b7a101ea84912747a77872005a6))

* feat(web): vendor ApexCharts 3.51 (no runtime npm)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ad44837`](https://github.com/chen-star/net_alpha/commit/ad448379992368cc5ef2fdc7949a32cbdb20771f))

* feat(web): vendor Inter and JetBrains Mono woff2 fonts

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`f60fb20`](https://github.com/chen-star/net_alpha/commit/f60fb200926ce980ab9bbb734eb89a1c7db12b33))


## v0.16.1 (2026-04-26)

### Fix

* fix(web): include current year and trade-activity years in Calendar YEAR dropdown

The dropdown derived its options solely from violation loss_sale_date years,
so a year with trade activity but no detected wash sales (e.g. the current
year on a fresh slate) was missing — the page header still selected it via
today.year, but the user couldn&#39;t pick it from the dropdown. Union trade
years and the current year into the option set, sorted newest-first.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`621fc36`](https://github.com/chen-star/net_alpha/commit/621fc36671c0f168a7ff4d480fe6041fe2e9f31c))


## v0.16.0 (2026-04-26)

### Chore

* chore: ruff format ([`9d7efb5`](https://github.com/chen-star/net_alpha/commit/9d7efb5efbc74385f8731d669f5079aa9034528c))

* chore: refresh gitnexus index stats and sync uv.lock to v0.15.1

- AGENTS.md / CLAUDE.md: gitnexus symbol/relationship/flow counts
  refreshed after the Phase 2 merge (1711 → 2436 symbols).
- uv.lock: wash-alpha version pinned to 0.15.1 to match the
  current pyproject.toml after the semantic-release bump (d5deb1a).

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`431e05f`](https://github.com/chen-star/net_alpha/commit/431e05f39ae3f762db02adde98430e1b752050cf))

### Feature

* feat(web): detail page totals bar + group-by-ticker + Lag/Source columns ([`887c676`](https://github.com/chen-star/net_alpha/commit/887c6767ac17d0284177db66f09e28bdaa9677c7))

* feat(web): wire detail summary, ticker grouping, and lag-sort into the route ([`bb2a9b8`](https://github.com/chen-star/net_alpha/commit/bb2a9b8c0f5d39eef19923a628d0dfa06dcd304b))

* feat(portfolio): add detail-page aggregations (summary, grouping, lag, source label) ([`889a422`](https://github.com/chen-star/net_alpha/commit/889a4225158557e010869df0e2fd04e7b3c75723))

* feat(web): POST /sim dispatches BUY/SELL with date and renders new result partials ([`d10180f`](https://github.com/chen-star/net_alpha/commit/d10180f37121c8497ddafe3ce0f049c0c72a7af9))

* feat(web): unified sim form with BUY/SELL toggle and date picker ([`4acd4e0`](https://github.com/chen-star/net_alpha/commit/4acd4e04e3747ba0e8e2e630e253aebe36e0f4a4))

* feat(engine): add simulate_buy() pure function with per-account FIFO loss matching

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`885de0f`](https://github.com/chen-star/net_alpha/commit/885de0f2004353bab348be50132580de06eb49ce))

### Test

* test(web): integration coverage for detail totals, grouping, lag sort, source ([`1c15594`](https://github.com/chen-star/net_alpha/commit/1c15594b5c701665f897bd8d733fc062352c71ad))

* test(web): cover BUY/SELL dispatch on POST /sim with date and account ([`38ebab6`](https://github.com/chen-star/net_alpha/commit/38ebab65dd542a4d56309c16e040a20e7049647c))

* test(engine): add ETF substantially-identical pair tests for simulate_buy ([`bb7d644`](https://github.com/chen-star/net_alpha/commit/bb7d644aeddd037f7f9ebe03f1ead9a517b689b4))

* test(engine): add day-0/30/31 boundary tests for simulate_buy ([`59a883a`](https://github.com/chen-star/net_alpha/commit/59a883a4075d4243b8b03b361f7ddd5c2dbb3c45))

### Unknown

* Merge branch &#39;feat/phase3-sim-and-detail&#39; — Phase 3 sim BUY support + detail enhancements ([`e5ac750`](https://github.com/chen-star/net_alpha/commit/e5ac7503938c6f47450cfa4f51068b7a222a3205))


## v0.15.1 (2026-04-26)

### Feature

* feat(domain): add SimBuyMatch and SimulationBuyOption models ([`d8afe49`](https://github.com/chen-star/net_alpha/commit/d8afe496301ef20a63b7f4f2a4482714a9bbe2f7))

### Fix

* fix(web): polish Portfolio page — treemap, equity axes, table paging, KPI

- Allocation treemap uses squarified algorithm for square-ish tiles;
  adds 220px height, 2px gaps, layered labels, hover-detail tooltip,
  and an &#34;OTHER&#34; bucket P/L that surfaces as unknown when every
  rolled-up position is itself unpriced.
- Equity curve gains $-labelled Y gridlines (always includes $0),
  Jan-Dec X axis, sell-event dots, a tethered &#34;+ unrealized&#34; dot,
  and per-point hover tooltips.
- Position table gains 25-row HTMX paging, an Open / All toggle
  (All adds period-closed positions tagged &#34;closed&#34;), and native
  per-column tooltips that explain each calculation.
- KPI grid gains &#34;Net P/L (Lifetime) = Lifetime Realized + Unrealized&#34;
  in slot 6 plus tooltips on every existing card.
- compute_kpis now returns partial sums when only some symbols are
  unpriced (priced_market - priced_basis is correctly subset-scoped),
  along with a missing_symbols list. The KPI fragment surfaces an
  asterisk on each affected card and an amber footer naming the
  unpriced tickers. Falls back to &#34;—&#34; only when every equity lot
  is unpriced (true no-data).

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`b166af6`](https://github.com/chen-star/net_alpha/commit/b166af6d83a318d434894147c78d585027b1774d))


## v0.15.0 (2026-04-26)

### Chore

* chore: ruff format

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`223b2b8`](https://github.com/chen-star/net_alpha/commit/223b2b8360aaf05d316a21f57ace4de2d5e1ec9c))

### Feature

* feat(web): embed drop zone on imports page

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`f6e7a0b`](https://github.com/chen-star/net_alpha/commit/f6e7a0b0e998eb58274e223123c557c99c71a396))

* feat(web): GET /imports/{id}/detail returns expandable detail panel

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`7d03ca5`](https://github.com/chen-star/net_alpha/commit/7d03ca5afb701468af474129ddb5dafb8ad8e363))

* feat(web): summary line and expand toggle on imports table

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`492c682`](https://github.com/chen-star/net_alpha/commit/492c68237b282f348a7dcc7b6a4ff0265b0268c2))

* feat(web): compute and persist import aggregates on upload

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`47d7fe8`](https://github.com/chen-star/net_alpha/commit/47d7fe88fc35cdfc2a367d5f9366bc78d034c124))

* feat(db): backfill import aggregates on init_db

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`3321bcb`](https://github.com/chen-star/net_alpha/commit/3321bcb68191d1a24da2231c83acdfd5bdd44e2a))

* feat(import): backfill aggregates for legacy import rows

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`068882c`](https://github.com/chen-star/net_alpha/commit/068882c962e48b74e15f1de0dbc8a688f776153a))

* feat(repo): persist and surface import aggregates; add get_import_detail

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`1086698`](https://github.com/chen-star/net_alpha/commit/1086698d9f285667c767d9593f79ceba5997200b))

* feat(import): add compute_import_aggregates pure function

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`b8fde73`](https://github.com/chen-star/net_alpha/commit/b8fde7324ab0b55480f0e6d129defd3fd72e79b1))

* feat(models): extend ImportRecord and ImportSummary with v4 aggregate fields

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`3fb4e4f`](https://github.com/chen-star/net_alpha/commit/3fb4e4f06aac96c4deb22de91232f9beaf64a71e))

* feat(db): schema v4 — add aggregate columns to imports

Adds 6 nullable columns to the imports table (min/max trade dates,
equity/option/expiry counts, parse warnings JSON) for the calendar
imports Phase 2 backfill. Updates existing migration tests to use
CURRENT_SCHEMA_VERSION instead of hard-coded version literals.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`7895223`](https://github.com/chen-star/net_alpha/commit/789522353f5be7f4a7fabcff3211727f2dde6285))

* feat(web): stack monthly P&amp;L ribbon above wash-sale dots

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`877ab57`](https://github.com/chen-star/net_alpha/commit/877ab5786bad699ab769f71f7b6af4ae499cbdb0))

* feat(web): add monthly P&amp;L ribbon partial

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`ddcc096`](https://github.com/chen-star/net_alpha/commit/ddcc0963050b4c6487133561761e9727738e18d1))

* feat(portfolio): add monthly_realized_pl aggregator

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`a12192a`](https://github.com/chen-star/net_alpha/commit/a12192aa05c7599ffb246acf8d41cd56611b9613))

* feat(portfolio): add MonthlyPnl model for calendar ribbon

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`deb2f5f`](https://github.com/chen-star/net_alpha/commit/deb2f5f446493181ac0d8dede10fb56933407bcd))

### Test

* test(db): cover v3 → v4 migration

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`769d491`](https://github.com/chen-star/net_alpha/commit/769d49178b7c135a6bfd977cb05d0c7d5ae7fd90))

* test(portfolio): add Dec-31/Jan-1 boundary case for monthly_realized_pl

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`7718274`](https://github.com/chen-star/net_alpha/commit/77182746d6a1e9d76570d374f80d35143ca5c851))

### Unknown

* Merge branch &#39;feat/calendar-imports-phase2&#39; — Phase 2 calendar dual-ribbon + imports notes

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`6de4f7f`](https://github.com/chen-star/net_alpha/commit/6de4f7fc4bcd16ff15543b0420ce2cba0a2f9e39))


## v0.14.0 (2026-04-26)

### Chore

* chore: sync uv.lock wash-alpha version to 0.13.1 ([`4cd3ef3`](https://github.com/chen-star/net_alpha/commit/4cd3ef3e5a900e459e456610dd687f2dccb1ea38))

* chore: apply ruff format to phase 1 files

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`faa7a6a`](https://github.com/chen-star/net_alpha/commit/faa7a6a915805ebe3fb0b7ef426406a19696ba8f))

* chore(web): remove obsolete dashboard route, templates, and tests ([`01608a4`](https://github.com/chen-star/net_alpha/commit/01608a43ca82b7ee713583e6cb55decfa1a78d9c))

* chore(deps): add yfinance to [ui] extras for portfolio pricing

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`259fe65`](https://github.com/chen-star/net_alpha/commit/259fe651dc4e13c2b6018d1b7337132212ec4b58))

### Documentation

* docs(spec): add UI/UX redesign design (portfolio + calendar + imports + sim + detail)

Three-phase plan: pricing foundation + portfolio rebuild, calendar dual-ribbon
+ imports relocation/notes, sim buy support + detail enhancements. Documents
the no-remote-prices policy relaxation (symbols only, configurable).

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`1b66473`](https://github.com/chen-star/net_alpha/commit/1b66473d12a60a5293f133cfe74cf53ba3c091a6))

* docs(claude.md): document price-data privacy + portfolio modules + UI conventions ([`804731a`](https://github.com/chen-star/net_alpha/commit/804731ad1086f1496b0b094aae8a44d8af7e1f28))

* docs(spec): add UI/UX redesign design (portfolio + calendar + imports + sim + detail)

Three-phase plan: pricing foundation + portfolio rebuild, calendar dual-ribbon
+ imports relocation/notes, sim buy support + detail enhancements. Documents
the no-remote-prices policy relaxation (symbols only, configurable).

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`eb5700a`](https://github.com/chen-star/net_alpha/commit/eb5700adca9ee802fc0d1481007ae7ef16e336ec))

### Feature

* feat(web): add &#39;Prices via Yahoo Finance&#39; footer line when remote prices enabled

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`d1cfdaa`](https://github.com/chen-star/net_alpha/commit/d1cfdaa6a29e4ba26153ea3a66d5fe0233072516))

* feat(web): treemap, equity curve, wash-impact, lot-aging fragments ([`036ed33`](https://github.com/chen-star/net_alpha/commit/036ed33bbffbeefd535c6619a54ab1dcd2ed6856))

* feat(web): /portfolio/positions fragment + per-symbol table

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`4a79442`](https://github.com/chen-star/net_alpha/commit/4a794420a6e904a41bf402ff0f9b17f99eb3756d))

* feat(web): /portfolio/kpis fragment + KPI partial

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`102ab71`](https://github.com/chen-star/net_alpha/commit/102ab7144fb6fcaede347b8cdfdd372d528064fd))

* feat(web): portfolio page shell with HTMX-loaded fragments + empty state

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`8851203`](https://github.com/chen-star/net_alpha/commit/8851203e0c0a621abd8bf7e80f4e0806ec917d5f))

* feat(portfolio): compute_wash_impact for portfolio mini-grid

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`56032e5`](https://github.com/chen-star/net_alpha/commit/56032e5f30f43e6808830410333f357fdec3804b))

* feat(portfolio): lot_aging — top-N lots crossing LTCG threshold ([`88d5571`](https://github.com/chen-star/net_alpha/commit/88d557144951c6a073e408a725fb837cc9394116))

* feat(portfolio): equity curve — realized cumulative + present-day point

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`cb383e2`](https://github.com/chen-star/net_alpha/commit/cb383e2ee9831b8a722be53e9344776190f6d161))

* feat(portfolio): slice-and-dice treemap layout

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ea3be67`](https://github.com/chen-star/net_alpha/commit/ea3be6775904dc0c9a6f13d2874d784d82d9c794))

* feat(portfolio): compute_kpis (period + lifetime, account-scoped)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`1babe96`](https://github.com/chen-star/net_alpha/commit/1babe966041fe1a8d61ea63b55c1d6325436f932))

* feat(portfolio): compute_open_positions with account/period scoping

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`2ffbfbc`](https://github.com/chen-star/net_alpha/commit/2ffbfbc5c4127fd896b4ed52afe193fa8ddd4088))

* feat(portfolio): view-model dataclasses for positions/KPIs/charts ([`8ab0120`](https://github.com/chen-star/net_alpha/commit/8ab01202af8a9ef534f206dc9698095fbeb130a0))

* feat(web): POST /prices/refresh — invalidate + refetch quotes

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`0b67abf`](https://github.com/chen-star/net_alpha/commit/0b67abfe69baeb92193a2ab375dcd362f9d3a510))

* feat(web): wire PricingService into FastAPI app state and DI

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`c5bddeb`](https://github.com/chen-star/net_alpha/commit/c5bddeb0302ab8dca72be1993c8cbee6730337b6))

* feat(pricing): PricingService orchestrating provider + cache

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`c26b74d`](https://github.com/chen-star/net_alpha/commit/c26b74d36dc1ddb0bfee9fbe320cc91b9fbfaa2c))

* feat(pricing): YahooPriceProvider via yfinance + network test marker

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`a1d4ff3`](https://github.com/chen-star/net_alpha/commit/a1d4ff34bbb3c7e94a8790337dc3542afac2402e))

* feat(pricing): SQLite-backed PriceCache with TTL + stale detection

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`de48a4d`](https://github.com/chen-star/net_alpha/commit/de48a4d878cd7ea4b8163cb9b537ebdefbb4ad3f))

* feat(pricing): Quote model and PriceProvider ABC

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`27f5010`](https://github.com/chen-star/net_alpha/commit/27f5010436c6a26898ffa34b3cc012551979f905))

* feat(db): schema v3 — add price_cache table for pricing subsystem

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`39f1fa7`](https://github.com/chen-star/net_alpha/commit/39f1fa7e706f55aa53e70cab0d1cd0d96e968c7e))

* feat(config): PricingConfig + YAML loader from ~/.net_alpha/config.yaml

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ab48329`](https://github.com/chen-star/net_alpha/commit/ab4832981164c71c075ed8a34f49960db757adc8))

### Fix

* fix(web): pre-merge polish — refresh ALL, dead dropdown, status text, equity curve scope

- /prices/refresh?symbols=ALL now resolves open-position tickers from the
  repository instead of passing the literal string to Yahoo (which returned
  Allstate); empty-lot edge case returns early with no error
- Remove the non-functional &#34;Show separately&#34; Options dropdown from the
  toolbar; group_options route param stays as a no-op default
- Replace the stuck &#34;Prices: loading…&#34; span with a static accurate label
  (&#34;Prices via Yahoo (~15 min delay)&#34;); id attribute dropped since nothing
  targets it
- Equity curve title changed to &#34;Equity curve · YEAR (YTD only)&#34; to make
  it visually clear the chart is year-scoped regardless of toolbar period
- Update test assertion to match new title-case heading text

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`540c091`](https://github.com/chen-star/net_alpha/commit/540c09158dfc581602dcc328fa93e61425be7ec0))

### Test

* test(integration): end-to-end portfolio page render with mocked prices

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`763f178`](https://github.com/chen-star/net_alpha/commit/763f17847301a9565ed4303e474e4e3b30a26fb1))

### Unknown

* Merge branch &#39;feat/portfolio-phase1&#39; — Phase 1 portfolio + pricing subsystem ([`f595359`](https://github.com/chen-star/net_alpha/commit/f595359a0ce3da500fecd34fdcfab71509f35621))


## v0.13.1 (2026-04-26)

### Fix

* fix(engine): trust Schwab when it explicitly clears a same-ticker lot

Previously, when the engine flagged a same-account exact-ticker wash sale
candidate that Schwab had already evaluated and cleared (Wash Sale=No),
we surfaced the engine&#39;s verdict as &#34;Unclear&#34;. This inflated the watch list
with cases the IRS would not disallow (e.g. a CRCL 06/18 150C loss whose
nearby buy was a different-strike CRCL contract that Schwab correctly does
not consider substantially identical), and counted them in YTD Disallowed
Loss totals.

Per the original brainstorm decision (&#34;trust Schwab for closed Schwab
positions&#34;), drop these contradictions instead of surfacing them. The one
exception preserved is when the engine&#39;s original confidence was already
&#34;Unclear&#34; — that label only comes from the substantially-identical/ETF-pair
matcher branch, which Schwab cannot model and which still needs human review.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`43be6c2`](https://github.com/chen-star/net_alpha/commit/43be6c2f5688e2e225036d4195aaf235f6bb313c))


## v0.13.0 (2026-04-26)

### Feature

* feat(web): redesign watch list columns and add period filter

Watch list columns are now Loss date · Ticker · Buy date · Account(s) ·
Disallowed · Confidence. The Account(s) column collapses to a single label
when both sides match and shows &#34;loss → buy&#34; only on cross-account violations,
removing the redundant duplication users were seeing.

Dashboard now defaults to the current year with a year dropdown above the
watch list (years derived from existing violations, plus the current year
and All time). KPI labels switch between YTD/FY&lt;year&gt;/All time accordingly.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`2cdd8ce`](https://github.com/chen-star/net_alpha/commit/2cdd8ce42d9a913729976f16e9bbd6dba16f902e))

### Fix

* fix(engine): correct stale violations and per-sell G/L allocation

Two bugs were producing wrong dashboard output:

1. Per-import incremental recompute leaves stale Confirmed/Probable labels.
   Each upload only re-evaluated the ±30-day window of its own affected dates,
   so when G/L data arrived in a later upload the merge step never re-checked
   engine violations from earlier uploads outside that narrow window.

   Add engine/recompute.py::recompute_all_violations which re-runs detection
   and merge over the union of all trade and G/L dates. Wire it into both
   the upload and delete routes in place of the per-window recompute block.

2. stitch._try_gl summed cost basis across every G/L lot sharing the same
   (account, symbol, closed_date) tuple, even when several distinct sells
   existed on that date. A user with two PL sells (proceeds $279.33 and
   $429.33) and two G/L lots (cost $150.66 + $185.66) saw both sells get
   cost_basis=$336.32, manufacturing a phantom $56.99 loss.

   When multiple lots match, narrow to lots whose proceeds match this
   specific sell&#39;s proceeds (within $0.01). Fall back to the full set if
   none match individually, preserving behavior for the case where Schwab
   splits a single sell across multiple cost-basis lots.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`3cc9558`](https://github.com/chen-star/net_alpha/commit/3cc955808608859e1d7be1d7da0489367f9e7bcc))


## v0.12.1 (2026-04-26)

### Chore

* chore: sync uv.lock to v0.12.0 ([`811817c`](https://github.com/chen-star/net_alpha/commit/811817cc7fe5c373fce9362303154e6192f9819a))

* chore: sync uv.lock with v0.11.0 release version bump

After pulling the v0.11.0 release commit, uv sync re-generated the lock
to reflect wash-alpha&#39;s new version.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`2ca03e0`](https://github.com/chen-star/net_alpha/commit/2ca03e01095c7f040289d6e6a5ea1bd2d8d5c059))

### Fix

* fix(web): drop zone — include #csv-input in HTMX request

The drop-zone div is not a &lt;form&gt;, so HTMX did not auto-include the file
input when posting to /imports/preview, causing FastAPI to return 422
Unprocessable Entity (missing &#39;files&#39; field). Adding hx-include=&#34;#csv-input&#34;
explicitly tells HTMX to serialize that input into the request body.

Web tests pass (42/42) — they don&#39;t catch this because they POST directly
without going through HTMX. ([`464408f`](https://github.com/chen-star/net_alpha/commit/464408f722d6baead6358f0adab18349fa6396ed))

### Unknown

* Merge branch &#39;master&#39; of https://github.com/chen-star/net_alpha ([`a6181f4`](https://github.com/chen-star/net_alpha/commit/a6181f4d7e74b50f451023521b0371a9b970dbd8))


## v0.12.0 (2026-04-26)

### Chore

* chore: gitignore private/, drop PRD.md, refresh GitNexus stats, bump uv.lock

- .gitignore: add private/ for local-only Schwab CSVs (not for distribution)
- PRD.md: remove (now superseded by docs/superpowers/specs/)
- AGENTS.md / CLAUDE.md: refresh auto-managed GitNexus stats
- uv.lock: pull in lock churn from feature branch dependencies

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`f34f843`](https://github.com/chen-star/net_alpha/commit/f34f8437eaef54deac7f1a4e7f4cf4bbfc6a673e))

* chore(web): rebuild Tailwind CSS for new G/L hydration UI elements

Rebuilt via &#39;npx tailwindcss@3&#39; to capture the new utility classes
introduced by the violation source badges, Schwab lot detail panel,
and imports table G/L lots column. (pytailwindcss 0.1.4 now downloads
a Tailwind v4 binary at runtime, which is incompatible with our v3
config. Using npx pins the v3 tooling explicitly.) ([`27357c2`](https://github.com/chen-star/net_alpha/commit/27357c2e26f83e362377f31789fc7a653a98f93b))

### Feature

* feat(cli): G/L hydration + merge in default import command

CLI accepts mixed Transaction History + Realized G/L files in a single
invocation. Same stitch + merge pipeline as the web UI. Reports
hydration counts and warnings on stdout. Now uses init_db() to ensure
schema migrations run.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`9ce28ad`](https://github.com/chen-star/net_alpha/commit/9ce28ad9c59a681f576640d834f92eb25b09dc63))

* feat(web): show G/L lot count on imports list

ImportSummary gains gl_lot_count. The imports table renders it as a
new column so G/L-only imports are no longer confusing zero-trade rows. ([`4f2b1d7`](https://github.com/chen-star/net_alpha/commit/4f2b1d7d92ec10ffd7102a802dc22840a2ce881d))

* feat(web): Schwab lot detail panel on ticker drilldown

Read-only table showing closed/opened dates, quantity, cost basis,
wash sale flag, and disallowed loss for each G/L lot in this ticker.
Lets users verify our hydrated cost basis against Schwab&#39;s source data.
Hidden when no G/L rows exist for the ticker.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`f332cd8`](https://github.com/chen-star/net_alpha/commit/f332cd880f2e60fa4ff63928f7d8a9694fed863b))

* feat(web): violation source badges (Schwab / Cross-account / Engine)

Renders next to the existing confidence pill so the user knows whether
a violation came from Schwab&#39;s 1099-B reporting, engine cross-account
detection, or engine-only substantially-identical inference.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`f106827`](https://github.com/chen-star/net_alpha/commit/f106827bebc681f743f283711cb0c4f5af4ea696))

* feat(web): multi-file upload with G/L hydration + merge

Drop zone accepts multiple CSVs (Schwab Transactions, Realized G/L,
or any combination). Per-file detection cards in preview modal.
Upload route runs each file through its parser, then stitch +
detect + merge end-to-end, scoped to the affected ±30-day window.
Flash message reports counts: trades, dups, G/L lots, hydrated sells,
warnings. ([`177d6ba`](https://github.com/chen-star/net_alpha/commit/177d6ba16204cbb092f336a52ce3d525d32042a6))

* feat(engine): merge_violations — combine engine output with Schwab verdicts

Rules:
  1. Schwab wash_sale=Yes  -&gt; Confirmed/schwab_g_l violation
  2. Engine + Schwab agree -&gt; keep Schwab&#39;s
  3. Engine same-acct cleared by Schwab -&gt; downgrade engine to Unclear
  4. Engine cross-account  -&gt; Probable/engine
  5. Engine same-acct without matching Schwab row (substantially identical)
                          -&gt; Unclear/engine
  6. No G/L for account    -&gt; engine output unchanged

WashSaleViolation gains a source field; round-tripped via repository.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`b56f373`](https://github.com/chen-star/net_alpha/commit/b56f373c9fa450e3455f667b531f6e7481fb32c2))

* feat(engine): stitch — hydrate Sell cost_basis from G/L or FIFO

stitch_account walks every Sell trade in an account and populates
cost_basis from realized_gl_lots (preferred, by symbol+closed_date)
or FIFO buy-lot consumption (fallback). Records basis_source on
each Sell so the UI can surface confidence/source. Returns a
StitchSummary with counts and any quantity-mismatch warnings. ([`3e59740`](https://github.com/chen-star/net_alpha/commit/3e5974090955832ac28c52a91006ac53e532b196))

* feat(brokers): SchwabRealizedGLParser produces RealizedGLLot rows

Recognizes Realized G/L CSV by headers (Symbol, Closed Date, Opened Date,
Quantity, Proceeds, Cost Basis (CB), Wash Sale?). Parses both stock and
option lots, money columns with \$/comma, Yes/No flags, and empty
Disallowed Loss as 0.0. Registered after SchwabParser in the registry.
BrokerParser Protocol relaxed to list[Any] since parsers may emit
different value-object types.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`23e9aa2`](https://github.com/chen-star/net_alpha/commit/23e9aa2940df384aab35173bfec667a8650b6288))

* feat(db): repository methods for G/L lots and stitch helpers

Adds add_gl_lots, get_gl_lots_for_match, get_gl_lots_for_ticker,
get_sells_for_account, get_buys_before_date, update_trade_basis.
Idempotent insert dedups on RealizedGLLot.compute_natural_key().
Trade Pydantic gains basis_source field; round-tripped via repository. ([`c773afa`](https://github.com/chen-star/net_alpha/commit/c773afa199cd388a00460648a5d1871e6c4671fd))

* feat(db): schema v2 — realized_gl_lots table + basis_source/source columns

Adds RealizedGLLotRow table, Trade.basis_source column (default &#39;unknown&#39;),
and WashSaleViolation.source column (default &#39;engine&#39;). Wires
migrate(session) into init_db() so v1 DBs upgrade in place. Migration
is additive and idempotent. ([`bf18031`](https://github.com/chen-star/net_alpha/commit/bf1803186efdf4d2642336a63c8827f3ce662c24))

* feat(models): add RealizedGLLot domain model

Pydantic value object for one tax-lot row from Schwab&#39;s Realized G/L
CSV. Includes Schwab&#39;s per-lot wash sale flag and disallowed loss for
later merge with engine output. compute_natural_key() supports
idempotent dedup on re-imports. ([`578b8c0`](https://github.com/chen-star/net_alpha/commit/578b8c086545dd29c07b1c720202def0bf301daa))

* feat(ingest): smart header detection in load_csv

Skips up to 5 preamble rows (title rows, blank rows) before the real
header row. Required for Schwab Realized G/L CSVs which have a
&#39;Realized Gain/Loss - Lot Details ...&#39; title above the column headers.
Falls back to row 0 when no plausible header row is found, preserving
backwards compat for files that already had headers on row 0. ([`eaf223e`](https://github.com/chen-star/net_alpha/commit/eaf223e238114daa22c31302c3cc2b047ac80669))

### Fix

* fix(db): cascade-delete G/L lots on remove_import + re-stitch

Without this, removing an import that contained G/L data leaves orphan
RealizedGLLotRow rows behind. Subsequent stitch_account calls would
silently hydrate sells using stale cost basis from those orphans,
corrupting wash-sale results.

The web DELETE /imports/{id} route now also runs stitch_account before
re-running detect_in_window, so sells that were hydrated from now-removed
G/L data get demoted to FIFO/unknown as appropriate. ([`e14196e`](https://github.com/chen-star/net_alpha/commit/e14196ef06867d3dc6cbe4a366dbe0adaf09d026))

* fix(db): resolve real trade IDs for Schwab G/L violations

Schwab G/L violations carry synthetic &#39;schwab_gl_&lt;hash&gt;&#39; trade IDs
that can&#39;t be int()-cast for the FK column. _violation_to_row now
detects source=&#39;schwab_g_l&#39; and resolves loss_trade_id by looking
up the matching Sell trade (account+ticker+date). When no matching
Sell trade exists, raises LookupError; replace_violations_in_window
catches and silently skips, supporting G/L-only imports without
Transaction History. ([`9755993`](https://github.com/chen-star/net_alpha/commit/975599397b78de04ee7c306b5d0f164b4cd1b8d8))

* fix(ingest): apply ruff format + clarify load_csv docstring

Address code-review feedback for Task 1:
  - ruff format collapsed implicit string concatenation in tests
    (same lint rule that blocked 79e7763 on master)
  - load_csv docstring references _HEADER_SCAN_LIMIT instead of
    hardcoding the value 5 ([`bb3a557`](https://github.com/chen-star/net_alpha/commit/bb3a5578366ef6cf085452b27131c7c243d24a4f))

### Unknown

* Merge branch &#39;master&#39; of https://github.com/chen-star/net_alpha ([`26ed5ba`](https://github.com/chen-star/net_alpha/commit/26ed5bab42411b79f7eeb009327a47164560386a))

* Merge feature/schwab-gl-hydration — Schwab Realized G/L hydration (v2.2)

Adds Schwab Realized G/L CSV ingestion alongside Transaction History so the
wash-sale engine can detect violations on Schwab data.

Architecture: a new realized_gl_lots table stores Schwab&#39;s per-lot G/L
verbatim. SchwabRealizedGLParser produces those rows from G/L CSVs. After
every import a stitch pass walks each Sell trade and populates cost_basis
from G/L (preferred) or FIFO buy lots (fallback). Engine output is then
merged with Schwab&#39;s per-lot wash-sale verdict — Schwab is authoritative
for closed Schwab positions, our engine adds cross-account and
substantially-identical detections.

UI: multi-file upload (any combination of CSVs), per-file detection cards,
violation source badges (Schwab / Cross-account / Engine), Schwab lot
detail panel on ticker drilldown, G/L lots column on imports list. CLI
gets the same pipeline. Schema bumped 1→2 with idempotent migration.

249 tests passing. Spec: docs/superpowers/specs/2026-04-25-schwab-gl-hydration-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`beaf76c`](https://github.com/chen-star/net_alpha/commit/beaf76cddee4ac90f5388d1004722571f11291eb))


## v0.11.0 (2026-04-26)

### Build

* build(web): tailwind config, source css, and built app.css; add make build-css ([`cb13f90`](https://github.com/chen-star/net_alpha/commit/cb13f90bedecc8a28d9ccda79116734d9ed9feaf))

### Documentation

* docs: document net-alpha ui command + web subsystem in README and CLAUDE.md ([`030acfc`](https://github.com/chen-star/net_alpha/commit/030acfccf28b2f5660dbcce889f454758911da9f))

### Feature

* feat(cli): net-alpha ui command (port picker, uvicorn boot, browser open) ([`08cc044`](https://github.com/chen-star/net_alpha/commit/08cc0446d132b53fb514ad814d547c5d6aba7cbb))

* feat(web): 404/500 handlers render error.html with traceback toggle ([`08d435d`](https://github.com/chen-star/net_alpha/commit/08d435d4d9c181ce84663065dab28dfe42f20414))

* feat(web): GET /ticker/{symbol} drilldown with KPIs, timeline, lots, violations ([`5e57bab`](https://github.com/chen-star/net_alpha/commit/5e57bab84c71d579462d56e97a50f58ef4f34661))

* feat(web): GET /calendar/focus/{id} renders ±30-day strip + violation card ([`48693b8`](https://github.com/chen-star/net_alpha/commit/48693b8bac2e3bb1456b73088df392b2e1ee576a))

* feat(web): GET /calendar with annual ribbon (per-year violation markers) ([`af1ec79`](https://github.com/chen-star/net_alpha/commit/af1ec79e6e7239de092902479d89ed7ac44128a4))

* feat(web): POST /imports/preview + POST /imports for drag-drop upload flow ([`50f3fae`](https://github.com/chen-star/net_alpha/commit/50f3fae5827591c231a29d69fe3325e05cdcd708))

* feat(web): drag-drop zone partial on dashboard with Alpine drag-over highlight ([`03a6363`](https://github.com/chen-star/net_alpha/commit/03a6363a55055dd40dcb08ccd0fcbf17114ffe99))

* feat(web): GET/POST /sim with HTMX-driven per-account result cards ([`66caad0`](https://github.com/chen-star/net_alpha/commit/66caad0b97173328d5d730a4a2123b1d0567c302))

* feat(web): GET /detail page with ticker/account/year/confidence filters ([`8a1e5a4`](https://github.com/chen-star/net_alpha/commit/8a1e5a485a40213175cfe8f576a4d59e2d8b9e53))

* feat(web): DELETE /imports/{id} removes import + recomputes wash sales ([`9037195`](https://github.com/chen-star/net_alpha/commit/9037195e72b61e75eb94718375d8453f322e5a19))

* feat(web): GET /imports management page with HTMX-ready remove button ([`cb1d2f9`](https://github.com/chen-star/net_alpha/commit/cb1d2f9e35324be0e02fa2c362f54c1700e9c3b9))

* feat(web): dashboard route with watch list + YTD KPI cards ([`2a24d82`](https://github.com/chen-star/net_alpha/commit/2a24d8233c782e5e4c6d54e02b773a74d3a248e1))

* feat(db): repository read methods for UI (list_distinct_tickers, get_*_for_ticker) ([`33bd930`](https://github.com/chen-star/net_alpha/commit/33bd930a08ba3445d72725d45d8976dabc4e2acf))

* feat(web): base.html with nav and disclaimer footer; jinja env + etf pairs in app state ([`4c99556`](https://github.com/chen-star/net_alpha/commit/4c99556c8c02a161f667e9ab648f60fa1d76919f))

* feat(web): vendor htmx + alpine static assets, mount /static via StaticFiles ([`ac0f843`](https://github.com/chen-star/net_alpha/commit/ac0f8439e7784bae6ea46ba73b28856d929f41d6))

* feat(web): create web package skeleton with FastAPI app factory ([`3159f4a`](https://github.com/chen-star/net_alpha/commit/3159f4a0cb2a3b4408f316ead0ec687013a7e3dc))

### Fix

* fix(web): correct tailwind palette to match design spec (primary, secondary, accent, bg) ([`ec737d3`](https://github.com/chen-star/net_alpha/commit/ec737d3c02a94b8430c585f01a56a5cc1472313a))

* fix(build): pin pytailwindcss to v3, restore @apply with custom color tokens

Downgrade pytailwindcss to v3.4.1 and restore v3-style CSS with @apply
directives using custom color tokens (primary, secondary, confirmed,
probable, unclear). Manually installed v3 via pytailwindcss.install(),
added safelist for utility classes and component classes to ensure all
needed styles are generated despite no templates yet.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`3777561`](https://github.com/chen-star/net_alpha/commit/37775619b3cf973ac16b95becf3103f9746e926e))

### Style

* style: ruff format + import sort across web subsystem ([`30fd46a`](https://github.com/chen-star/net_alpha/commit/30fd46a446f8dc419e8b4d5fcfcf1a8eebf352b4))

### Test

* test(web): add conftest with settings/engine/repo/client fixtures + trade builders ([`881b14c`](https://github.com/chen-star/net_alpha/commit/881b14cdfe68bdfc108f79db2a69cb85a844231a))

### Unknown

* Merge feature/local-ui — local web UI (v2.1)

20-task subagent-driven implementation of the local web UI:
FastAPI + Jinja + HTMX + Alpine + Tailwind v3 (vendored, no node/npm).
Drag-drop CSV import, watch list, YTD KPIs, sim, imports management,
detail, wash-sale calendar (annual ribbon + ±30-day focus), ticker
drilldown, error pages, and the `net-alpha ui` CLI command (port picker,
uvicorn boot, browser open, --port/--no-browser/--reload flags).

187 tests passing, ruff clean.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`07d8d92`](https://github.com/chen-star/net_alpha/commit/07d8d92a220d1a12de4e1a7878c48404798c440d))

* deps(ui): add optional ui group (fastapi, jinja, uvicorn, multipart) + pytailwindcss/httpx for dev ([`9d42901`](https://github.com/chen-star/net_alpha/commit/9d429016cdf19825023ae7ef3e575ee1efaa3d52))


## v0.10.0 (2026-04-25)

### Chore

* chore(deps): drop questionary, anthropic, pydantic-settings, textual; bump to 2.0.0

Runtime dep set is now: pydantic, sqlmodel, typer[all], loguru, pyyaml.
Description reflects the v2 simplified product. config.py migrated from
BaseSettings to plain pydantic BaseModel; LLM-related config fields removed.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`7b2752b`](https://github.com/chen-star/net_alpha/commit/7b2752bb2db70a03add7b5b4b4893fa9565ef47b))

* chore(v2): delete legacy import_/, cli/import_cmd, cli/simulate, unused models

v2 surfaces (default + sim + imports + migrate-from-v1) replace all
prior import/check/simulate paths. Repository stubs and unused
Pydantic models follow them out. MetaRepository remains for the
migration boot in cli/app.py.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ceef1c3`](https://github.com/chen-star/net_alpha/commit/ceef1c34da1f833477f15d8c14435401de6cbdf5))

* chore: move etf_pairs.yaml into package, wire loader through CLI

Bundled file now lives at src/net_alpha/etf_pairs.yaml so pip install
ships it. User override at ~/.net_alpha/etf_pairs.yaml extends bundled
pairs (does not replace). Both CLI recompute call sites now use the
real loader instead of an empty dict stub.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`17ad7cb`](https://github.com/chen-star/net_alpha/commit/17ad7cb648fd45bacd32e5333e94bf070994ba3d))

* chore(v2): remove TUI, agent, wizard, and dropped CLI commands

These surfaces are cut in v2. Engine, db, and import_ packages remain
in place pending replacement in later tasks.

SchemaMapping and anonymize_row were inlined into csv_reader.py and
importer.py respectively (both kept), since schema_detection.py and
anonymizer.py were their sole definitions but still consumed by kept modules.
Integration tests for the deleted commands were also removed.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`88d893c`](https://github.com/chen-star/net_alpha/commit/88d893c9cff302cab83e98dade6e53d86a35d7fb))

### Documentation

* docs: rewrite README, CLAUDE.md, AGENTS.md for v2 surface

Remove all v1-era references (AI import, LLM/Anthropic, questionary,
pydantic-settings, textual TUI, wizard, agent, check/report/rebuys
subcommands). Replace with v2 command surface (default import+check,
sim, imports, imports rm, migrate-from-v1) and bundled-Schwab-parser
description throughout.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`fa7344c`](https://github.com/chen-star/net_alpha/commit/fa7344cbf0afecc5e9bd90c9bc575527710701bd))

### Feature

* feat(cli): migrate-from-v1 helper (v2.0.x only)

Reads ~/.net_alpha/net_alpha.db (v1 schema) and writes a parallel
v2 DB at ~/.net_alpha/net_alpha.db.v2. User then moves it into
place. Refuses to overwrite an existing v2 file.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ac7ac24`](https://github.com/chen-star/net_alpha/commit/ac7ac24f4489145c7f045804a93c41662ab7beb5))

* feat(cli): v2 surface — default import/check, sim, imports, imports rm

Rewrites app.py with a _FileFirstGroup that routes file-path arguments to
a hidden &#39;run&#39; sub-command, enabling `net-alpha &lt;csv&gt; --account &lt;label&gt;`
as the default entry point alongside explicit `sim` and `imports` sub-commands.
Deletes test_simulate_lots.py (v1 simulate tests superseded by test_app_v2.py).

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`973973b`](https://github.com/chen-star/net_alpha/commit/973973bb011007b498a46972e3fdec75efeb323d))

* feat: thread ticker through WashSaleViolation for renderer enrichment

Add ticker field to WashSaleViolation domain model, WashSaleViolationRow
table, detector emission, repository read/write paths, and watch_list
renderer. Removes the hardcoded &#39;TKR&#39; placeholder.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`fb90b0e`](https://github.com/chen-star/net_alpha/commit/fb90b0e31e34cde9d8b8f16ae437aba5791295b1))

* feat(output): renderers — disclaimer, watch_list, ytd_impact, sim_result, imports_table, detail

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`09a3697`](https://github.com/chen-star/net_alpha/commit/09a36972a7dab1f602e22ad47249283d10375093))

* feat(brokers): protocol + registry + Schwab parser

Add BrokerParser Protocol, detect_broker registry, and SchwabParser
implementing buy/sell/reinvest/option action parsing for Schwab CSVs.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`121c2ce`](https://github.com/chen-star/net_alpha/commit/121c2ce16c11395e5bdfdad61f16b089c24e3b16))

* feat(ingest): csv_loader, option_parser port, dedup by natural_key

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`57fafea`](https://github.com/chen-star/net_alpha/commit/57fafeab775669521cf36393b9c3366f0df1acbc))

* feat(engine): simulate_sell — cross-account what-if planner

Implements Task 11: simulator.py with FIFO lot consumption, realized P&amp;L,
cross-account wash sale detection, insufficient-shares flagging, and
lookforward_block_until date. 7 new tests; full suite at 215.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`65f8328`](https://github.com/chen-star/net_alpha/commit/65f8328f9d5ebc304e398f6af8bcdc215cbf3d20))

* feat(engine): detect_in_window for incremental recompute

Append new function detect_in_window to detector.py that runs the full
detection algorithm but emits only violations whose loss_sale_date falls
within the supplied window. Caller is responsible for passing trades that
include ±30 days around the window for correct cross-window matching.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`0b12d2c`](https://github.com/chen-star/net_alpha/commit/0b12d2c83e227593777d451a51777d910055947c))

* feat(engine): violations carry loss_account, buy_account, sale/buy dates

Tightens the scaffolded _violation_to_row helper from Task 8 to use
the new typed fields. Wash sale violations now have full provenance
(which accounts, when) needed by the v2 watch list and YTD renderers.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`f8b10cc`](https://github.com/chen-star/net_alpha/commit/f8b10cc479ffdf14f1ebb876b050965436e5c9a1))

* feat(db): repository remove_import + replace_violations_in_window

Add three methods to v2 Repository: remove_import (cascading delete of
import/trades/lots/violations + recompute window), replace_violations_in_window
(atomic clear-and-rewrite of violations in date range), and _violation_to_row
(scaffolded with getattr defaults for Task-9 fields loss_account_id,
buy_account_id, loss_sale_date, triggering_buy_date).

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`350f8a6`](https://github.com/chen-star/net_alpha/commit/350f8a6c6f933049b5dcabcf66122370918d05c4))

* feat(db): repository reads — trades, lots, violations, windowed

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`d5697b5`](https://github.com/chen-star/net_alpha/commit/d5697b5787efff8dde93f146ae70b5219706820f))

* feat(db): add_import with dedup via natural_key UNIQUE constraint

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`4fb5837`](https://github.com/chen-star/net_alpha/commit/4fb583764ed6ccc20d1f265b2980ad78d0f10794))

* feat(db): repository v2 skeleton + account/import management methods

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`96c0cec`](https://github.com/chen-star/net_alpha/commit/96c0cec48acddf1b4d2045ba4af9e2012ebac1a9))

* feat(models): add Trade.compute_natural_key for v2 idempotent imports

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`aa53dd2`](https://github.com/chen-star/net_alpha/commit/aa53dd2237b8c5ee567216df0b5fc5458df6a369))

* feat(db): replace tables with v2 schema (Account, Import, FKs, natural_key)

v1 schema is dropped wholesale. Schema starts at version 1. v1 -&gt; v2
upgrade lives in a separate migrate-from-v1 helper (Task 17).

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`da4992d`](https://github.com/chen-star/net_alpha/commit/da4992dcf53e06b145f3ff0c67d28a40b2508321))

* feat(models): add v2 domain types — Account, ImportRecord, SimulationOption

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`e4635a8`](https://github.com/chen-star/net_alpha/commit/e4635a83a7fd8ae12454ff9ba140a857438cd176))

### Fix

* fix(cli,db): persist lots after wash-sale recompute

The default and imports-rm flows were calling detect_in_window then
discarding the result.lots half. As a consequence repo.all_lots() was
always empty and sim reported &#39;no holdings of &lt;ticker&gt;&#39; for any
import. Adds Repository.replace_lots_in_window mirroring the
violations method, and updates both CLI handlers to persist both
halves of the DetectionResult.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`87691bb`](https://github.com/chen-star/net_alpha/commit/87691bbe13657f948801fd07a254f70173bb8440))

* fix(cli,db): tighten violation_to_row session, account annotation, mark TODOs

- repository._violation_to_row now takes the parent session as a
  parameter, matching the row-helper pattern used elsewhere.
- cli/app.py callback&#39;s --account annotation is str | None (was str
  with None default).
- Both etf_pairs={} sites tagged with TODO(Task 16) so the loader
  wire-up isn&#39;t missed.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`3234a50`](https://github.com/chen-star/net_alpha/commit/3234a50fcf5a15e0ebd6904c7971470e26df1c70))

* fix(db): repository violation reads populate full field set

Both all_violations() and violations_for_year() were dropping
loss_sale_date, triggering_buy_date, loss_account, and buy_account
when reconstructing Pydantic WashSaleViolation from the row. This
silently broke watch_list filtering (predicate on triggering_buy_date)
and produced &#39;None ... on None&#39; garbage from --detail.

Also: drop hardcoded &#39;schwab&#39; from sim&#39;s no-such-account message.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`7158775`](https://github.com/chen-star/net_alpha/commit/71587752ab4aac2eb4887eafcf1d91de2980cb9c))

* fix(db): consolidate CURRENT_SCHEMA_VERSION; guard removed LLM path

- importer.py: replace SchemaCacheRow LLM branch with explicit
  NotImplementedError so the import path doesn&#39;t crash silently.
- connection.py: import CURRENT_SCHEMA_VERSION from migrations
  (eliminates duplicate constant).
- migrations.py: add scaffolding comment for future schema versions.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`f9410c6`](https://github.com/chen-star/net_alpha/commit/f9410c6d244bffecb70708c5c3e456755cc4d4db))

* fix(import_): rename ambiguous &#39;l&#39; var, drop dead _NUMERIC_PATTERN

Both regressions were introduced when inlining anonymizer.py and
schema_detection.py during Task 1. Will be deleted entirely in Task 18,
but fixing now to keep CI green.

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`adb0bc0`](https://github.com/chen-star/net_alpha/commit/adb0bc01d6f36ca9f560be8b5c27819e9f43f2b3))

### Unknown

* Merge v2 simplification rewrite into master

v2 collapses the surface from 10 CLI commands + wizard + TUI + agent
into 4 commands (sim, imports, imports rm, migrate-from-v1, plus the
default &lt;csv&gt; flow). Removes LLM CSV import, Robinhood, TUI, agent,
wizard, schema cache, and pydantic-settings/anthropic/questionary/
textual deps. New brokers/, ingest/, output/ packages. Repository
v2 with idempotent multi-account imports and windowed wash-sale
recompute. simulate_sell as a cross-account what-if planner.

22 tasks executed via subagent-driven-development with two-stage
review (spec compliance + code quality) per task. Final state:
150 passing tests, lint clean, ruff format clean, package builds
as wash_alpha-2.0.0.

See:
- docs/superpowers/specs/2026-04-25-v2-simplification-design.md
- docs/superpowers/plans/2026-04-25-v2-simplification.md

Co-Authored-By: Claude Opus 4.7 (1M context) &lt;noreply@anthropic.com&gt; ([`f17e185`](https://github.com/chen-star/net_alpha/commit/f17e18502721ef2579ff65c02c37f7120421fb3c))


## v0.9.1 (2026-04-19)

### Fix

* fix: resolve all linting and formatting errors to fix failing CI ([`79e7763`](https://github.com/chen-star/net_alpha/commit/79e7763339a88756e56f3d023ef52f10ab10de63))


## v0.9.0 (2026-04-18)

### Chore

* chore: update GitNexus index statistics in documentation files ([`4bce402`](https://github.com/chen-star/net_alpha/commit/4bce4027942a2a6528b10a561623e281045f517a))

### Documentation

* docs: sync PRD commands with current implementation and update GitNexus stats ([`ecdb8ab`](https://github.com/chen-star/net_alpha/commit/ecdb8ab0059f1b3785b3150b0c2d8c72589a0634))

* docs: modernize README and update agent configuration ([`ba80638`](https://github.com/chen-star/net_alpha/commit/ba80638260ddc713c6d625f373a9e906df98c798))

### Feature

* feat(cli): remove eager api key prompt from interactive wizard ([`26f11a5`](https://github.com/chen-star/net_alpha/commit/26f11a5a5d78c30e446d24c7442ee6d5a206af97))

* feat(cli): make API key optional for import command ([`389eb5a`](https://github.com/chen-star/net_alpha/commit/389eb5ac0e58767059d1d41295b5e415b17d2830))

* feat(import): run_import uses hardcoded schema if cache misses and no API key ([`59c7479`](https://github.com/chen-star/net_alpha/commit/59c747925332763fc2c232e97dc94ad7e20cb033))

* feat(import): add hardcoded schemas for schwab and robinhood ([`c0fda74`](https://github.com/chen-star/net_alpha/commit/c0fda74f0aa32d059659b9c22b1fe61ff9069d07))

### Test

* test: update tests to test LLM branch using unknown_broker instead of known brokers ([`9da464c`](https://github.com/chen-star/net_alpha/commit/9da464cce56a1bc08e045fffd82e4c7c7695eda7))


## v0.8.0 (2026-04-18)

### Chore

* chore: add textual dependency for TUI ([`57b6b64`](https://github.com/chen-star/net_alpha/commit/57b6b64fac7faf0232e19c7d01763a220d4b35e3))

* chore: install ui-ux-pro-max-skill for cluade and antigravity ([`f9c8919`](https://github.com/chen-star/net_alpha/commit/f9c8919ac63d12d1129950f318eae6d91686dce4))

### Documentation

* docs: sync architecture, cli, and readme with tui implementation and update model versions ([`188ab91`](https://github.com/chen-star/net_alpha/commit/188ab91971d9fd802ec7be040d5866186a0c8a89))

### Feature

* feat(tui): accurately track and display virtual trade wash sales ([`cd03d50`](https://github.com/chen-star/net_alpha/commit/cd03d5064dfbedec93332e8b52d2faf15fe15d95))

* feat(tui): wire reactive inputs to simulation engine ([`60eef00`](https://github.com/chen-star/net_alpha/commit/60eef0037f30a743cab0bfb69bbc6cf5865fe315))

* feat(tui): add simulation engine helper ([`6322c6c`](https://github.com/chen-star/net_alpha/commit/6322c6c73101dcef351550cd15c1c361d74dc134))

* feat(tui): load and display database trades in DataTable ([`c45cfa0`](https://github.com/chen-star/net_alpha/commit/c45cfa07e86ce5f577a06e7232cdce463e809f44))

* feat(tui): build split-pane dashboard layout ([`e2b4679`](https://github.com/chen-star/net_alpha/commit/e2b46794370f0c7e36d5c25f934424f0f67b59bd))

* feat(tui): scaffold base textual app and cli command ([`ecf5dfe`](https://github.com/chen-star/net_alpha/commit/ecf5dfe606632fc90a130a0fae86a178e7cc7db1))


## v0.7.0 (2026-04-18)

### Chore

* chore: remove tmp_skills from git ([`f8a8545`](https://github.com/chen-star/net_alpha/commit/f8a85450b3902ac712336a5d2cb5b93c76cd12ea))

### Documentation

* docs: fix verification failures and update codebase references

- Resolve gitnexus tool reference failures in CLAUDE.md and AGENTS.md
- Correct CI/CD workflow paths to .github/workflows/ in release plan
- Update CLI test path to integration test location in CLI plan
- Update project name to wash-alpha and fix SchemaCacheRow symbol ([`f8de4e7`](https://github.com/chen-star/net_alpha/commit/f8de4e719ec13ca32c2ccb0b54193ca0b1ddd501))

* docs: update project documentation ([`24dede1`](https://github.com/chen-star/net_alpha/commit/24dede1eff43605c33e8b428104e10a8a02da527))

### Feature

* feat(cli): add interactive wizard mode ([`aa73887`](https://github.com/chen-star/net_alpha/commit/aa7388793404532fdabfa1422e811ac053d5b205))

### Unknown

* merge: synchronize with origin/master and finalize v0.6.0 release ([`e431436`](https://github.com/chen-star/net_alpha/commit/e43143681a8e9f79aed4bac685b8dab0e3d89594))


## v0.6.0 (2026-04-16)

### Documentation

* docs: update PyPI badge to Shields.io and refresh GitNexus index ([`b3ed7af`](https://github.com/chen-star/net_alpha/commit/b3ed7afeeff5150229c9b8868d1cf5f1fc87be1a))

* docs: include design spec and implementation plan for README overhaul ([`52fa57a`](https://github.com/chen-star/net_alpha/commit/52fa57a085da7b1ae825b02fb120a273e540c978))

* docs: add summary for README overhaul plan ([`79ee9e2`](https://github.com/chen-star/net_alpha/commit/79ee9e2a50096f67e1b80fcab0b80f6b8b576575))

* docs: final polish of README overhaul ([`d02bf06`](https://github.com/chen-star/net_alpha/commit/d02bf06fe4187c505d3fa47f21eb9a6b4c789eda))

* docs: add technical deep-dive and privacy section to README ([`bd7cb81`](https://github.com/chen-star/net_alpha/commit/bd7cb81be103889e16eba379bb17032d291b16a1))

* docs: add modern workflow walkthrough to README ([`97a314a`](https://github.com/chen-star/net_alpha/commit/97a314a9dbe9fe1c1971d1b5077939bc1c31eba4))

* docs: add hero header and value prop to README ([`4dd472d`](https://github.com/chen-star/net_alpha/commit/4dd472d38d92c6bae640e6a75265f8ca3196c3bd))

### Feature

* feat: add crypto and common ETF pairs for tax loss harvesting ([`5546b33`](https://github.com/chen-star/net_alpha/commit/5546b3305178448f8c769fb88117671f4c613589))


## v0.5.0 (2026-04-16)

### Chore

* chore: update release script, Makefile, and local lockfile ([`5b31293`](https://github.com/chen-star/net_alpha/commit/5b312937fb9bc0e42dfef80a7f7b871c7ce048d7))

### Ci

* ci: fix automated release and add manual release fallback ([`93a16f6`](https://github.com/chen-star/net_alpha/commit/93a16f6cd6636b2b54441a5dbca18aef854c0192))

### Documentation

* docs: update codecov badge with private token ([`f1b19b2`](https://github.com/chen-star/net_alpha/commit/f1b19b2bc918e30ffcf8343f23b03b8579ad55ee))

### Feature

* feat: enhance README with AI agent and interactive TUI features ([`c0f97cc`](https://github.com/chen-star/net_alpha/commit/c0f97cc2e3fa8c93a62a0af2110077241493a8ac))


## v0.4.2 (2026-04-16)

### Ci

* ci: fix codecov badge and improve upload debugging ([`e2e4f6b`](https://github.com/chen-star/net_alpha/commit/e2e4f6bc949ebc02b70aceaa8b7801a2b83cc2b8))

* ci: remove [skip ci] from release commits to allow workflow triggering ([`2cbe312`](https://github.com/chen-star/net_alpha/commit/2cbe3125964476f9bb207dd14255b07baf74dcfe))

### Fix

* fix: sync internal version and trigger release automation ([`53739f4`](https://github.com/chen-star/net_alpha/commit/53739f445632405cc5e0a944c2f44417dde6cdca))

* fix: trigger release automation verification ([`1c68a14`](https://github.com/chen-star/net_alpha/commit/1c68a149376d6d97ba74a55fd93b87c79704ab28))


## v0.4.1 (2026-04-16)

### Chore

* chore: add MCP config and tax optimization suite plan ([`36b68a3`](https://github.com/chen-star/net_alpha/commit/36b68a34e092608e3f05df758620813b332138a3))

### Fix

* fix: resolve linting and formatting issues to fix CI ([`56e2588`](https://github.com/chen-star/net_alpha/commit/56e2588b69885a2757a348cafc5187e0e5cd3986))


## v0.4.0 (2026-04-16)

### Chore

* chore: update GitNexus metadata and bump version ([`18925c3`](https://github.com/chen-star/net_alpha/commit/18925c3a4036812c1629293e15af9fd4095fcd71))

### Feature

* feat: wire agent command into CLI app and add integration smoke test ([`8688b78`](https://github.com/chen-star/net_alpha/commit/8688b783a0e98b6a70846e944b3fdf9e421afc75))

* feat: add agent REPL with local routing and session-start scan ([`df5bca1`](https://github.com/chen-star/net_alpha/commit/df5bca182ccd5e7584f8772ad96966e1ae6e3c13))

* feat: add ReAct loop for Claude tool-use agent ([`ec3d0a0`](https://github.com/chen-star/net_alpha/commit/ec3d0a00258a09fafc70836530458084b18cca2d))

* feat: add agent system prompt assembly ([`84494a6`](https://github.com/chen-star/net_alpha/commit/84494a63b1a21b40a4318fe8fb6eda0cb78d5a31))

* feat: add agent tool executors and Claude tool schemas ([`9356288`](https://github.com/chen-star/net_alpha/commit/9356288660a881225382d6caf206ca17d5233d3a))

* feat: add agent_api_key, agent_model, resolved_agent_api_key to Settings ([`3293bc4`](https://github.com/chen-star/net_alpha/commit/3293bc45a52029c3a06b5c19bad60dfd44e87129))


## v0.3.0 (2026-04-16)

### Feature

* feat: show example values in schema confirmation and add post-import nudge ([`a74b75c`](https://github.com/chen-star/net_alpha/commit/a74b75c52dd88a4398b2c4f56033e9b8fedb50d8))

* feat: add broker autocomplete and what-to-do-next panel to wizard ([`91a3141`](https://github.com/chen-star/net_alpha/commit/91a3141e831efd1f71b562565b82a5c2f8dc5f23))

* feat: add ticker validation with close-match suggestion to simulate sell ([`146c0b9`](https://github.com/chen-star/net_alpha/commit/146c0b9995308400a8fa458a3fe06e5c12f3061e))

* feat: add --quiet flag to report command ([`9c71723`](https://github.com/chen-star/net_alpha/commit/9c717232ae14c278331c33cb9f04ad373ebfde18))

* feat: add --quiet flag, --type validation, hints, and last_check_at to check command ([`a1e5992`](https://github.com/chen-star/net_alpha/commit/a1e5992b14fabcbed96a688579907d065c97b7c4))

* feat: add urgency coloring and cross-command hint to rebuys ([`ba34bbb`](https://github.com/chen-star/net_alpha/commit/ba34bbbcf40d6f9fd3ca1fe57bc78a51365ae37d))

* feat: color-code tax-position monetary values and add cross-command hint ([`c819312`](https://github.com/chen-star/net_alpha/commit/c819312682caf189559b01e4b092728c2a6a9e61))

* feat: add progress spinners to check, report, and import commands ([`63a00d8`](https://github.com/chen-star/net_alpha/commit/63a00d8877f59079b9b206f0fb3edcf476345fca))

* feat: add net-alpha status dashboard command ([`ebdc415`](https://github.com/chen-star/net_alpha/commit/ebdc4159215eb2e3bc45327af4c0a21ff4433456))

* feat: add MetaRepository for reading and writing meta key-value pairs ([`56864bb`](https://github.com/chen-star/net_alpha/commit/56864bb673f044c45fd9f7be2f2a767cff32d8ef))

* feat: add print_hint and format_currency_colored output helpers

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`a376f45`](https://github.com/chen-star/net_alpha/commit/a376f45b1a9249f38b2e1cfa185d0b693c9920e6))

### Style

* style: normalize error message formatting across CLI commands ([`557b672`](https://github.com/chen-star/net_alpha/commit/557b672fc2859b1b73ee92f32582a8bcee84c1e7))


## v0.2.0 (2026-04-15)

### Feature

* feat: enhance simulate sell with lot selection comparison

When --price is given, shows FIFO/HIFO/LIFO comparison table with
ST/LT gain/loss split, wash sale risk flags, and tax-aware
recommendation. Reuses existing wash sale check logic. ([`590cb5b`](https://github.com/chen-star/net_alpha/commit/590cb5bf167ed0589fdf9203902da0bfb19754f1))

* feat: add tax-position CLI command

Shows YTD realized ST/LT gains and losses, net capital position,
loss-to-zero-st, carryforward, and open lots with holding period
tracker. Sorted by days-to-long-term ascending. ([`2393ce0`](https://github.com/chen-star/net_alpha/commit/2393ce02cb1bb6c55df2085bc1415bd3e59011c9))

* feat: implement tax position engine (Tasks 3-8)

- _allocate_lots: per-(account,ticker) FIFO with realized pairs + open lots
- compute_tax_position: YTD ST/LT aggregation, year-filtered, basis_unknown counted
- identify_open_lots: sorted by days_to_long_term asc, LT lots last
- select_lots: FIFO/HIFO/LIFO across accounts with ST/LT split
- recommend_lot_method: rule-based decision tree with wash risk + fallback
- 42 tests covering all edge cases: boundaries, per-account isolation,
  basis_unknown, option exclusion, holding period, tiebreaks ([`9b5d774`](https://github.com/chen-star/net_alpha/commit/9b5d774ed7edc4e957dc9c82d0c94fd775ce9274))

* feat: add OpenLotFactory and RealizedPairFactory test fixtures ([`b98443a`](https://github.com/chen-star/net_alpha/commit/b98443a192b644476dae1b2f4ae5d0854b5b4599))

* feat: add domain models for tax optimization suite

Add TaxPosition, OpenLot, LotSelection, LotRecommendation,
AllocationResult, and RealizedPair to models/domain.py.
Includes computed properties for net_st, net_lt, net_capital_gain,
loss_needed_to_zero_st, and carryforward ($3,000 cap). ([`10b93c7`](https://github.com/chen-star/net_alpha/commit/10b93c78447618345787bf92449e7f1382585657))

### Style

* style: fix lint and formatting for tax optimization suite ([`4302adc`](https://github.com/chen-star/net_alpha/commit/4302adca872a248b38feb67d9e8db763c461a41f))

### Test

* test: fix lint and add missing tests for domain models

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`7acd573`](https://github.com/chen-star/net_alpha/commit/7acd57373a61e799cc01b2645498ee6843f35a6e))


## v0.1.3 (2026-04-14)

### Fix

* fix: remove unused imports and fix import ordering in test files

Fixes CI lint failures (F401 unused imports, I001 unsorted imports) and
reformats all files to match ruff format standards.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`fde1085`](https://github.com/chen-star/net_alpha/commit/fde108584d18039bb2e2cd38d3d4771b7515172e))

### Unknown

* Update gitnexus index. ([`97cb253`](https://github.com/chen-star/net_alpha/commit/97cb253837342447ab09752d55607eb5d4648d24))


## v0.1.2 (2026-04-14)

### Chore

* chore: update GitNexus index stats ([`bcd9de8`](https://github.com/chen-star/net_alpha/commit/bcd9de82767c8bdce3006db5295f2c546dfcf5e3))

### Fix

* fix: set line-length to 120 and fix remaining lint errors ([`6abcae6`](https://github.com/chen-star/net_alpha/commit/6abcae62cd7ef5be20418ff8ef28d6eed330e24d))

* fix: use --extra dev to install optional dev dependencies in CI ([`deef5b9`](https://github.com/chen-star/net_alpha/commit/deef5b94b11a120cb6c30a18b3c44fddd8aaef53))


## v0.1.1 (2026-04-14)


## v0.1.0 (2026-04-14)

### Chore

* chore: rename PyPI package to wash-alpha ([`7189777`](https://github.com/chen-star/net_alpha/commit/7189777bcac4ee6a8351de7f7afddc795df2b3cb))

* chore: add python-semantic-release config ([`62c3a91`](https://github.com/chen-star/net_alpha/commit/62c3a9198e4d17cade4b6c8e5e8326436020c769))

* chore: add .gitignore with worktrees and Python artifacts ([`c77363c`](https://github.com/chen-star/net_alpha/commit/c77363c3dafad828c893b8c3af2e51dad26d8f32))

### Ci

* ci: add explicit tag push step; gate codecov upload to py3.11 ([`40c582f`](https://github.com/chen-star/net_alpha/commit/40c582fc5c5927d68b3a9fd26b46a8869f6c6d11))

* ci: add release workflow (hatch build, PyPI OIDC publish, GitHub Release) ([`617ec50`](https://github.com/chen-star/net_alpha/commit/617ec50c4a7885be7bf65e1580260ebec924d311))

* ci: pin python-semantic-release to v8 range ([`47eab42`](https://github.com/chen-star/net_alpha/commit/47eab4224f0bdab87e269bbdf376ec3add7009e5))

* ci: add version bump workflow (conventional commits + manual override) ([`ecf4d8c`](https://github.com/chen-star/net_alpha/commit/ecf4d8cb556eb739f64a71648e4219d2a7f0baee))

* ci: add CI workflow (lint, test, coverage on push/PR/nightly) ([`4f54c9b`](https://github.com/chen-star/net_alpha/commit/4f54c9bc8b2c8206b928b25ba04cd1740c6dc1b1))

### Documentation

* docs: add CI, PyPI, and coverage badges to README ([`c19d0fd`](https://github.com/chen-star/net_alpha/commit/c19d0fd8c87340c3abf89a57eb9ef889f74c9daa))

### Feature

* feat: add first-run interactive wizard

Implements the interactive wizard that runs on first launch, prompting
for an Anthropic API key, importing broker CSVs, and running an initial
wash sale check.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`1f3f191`](https://github.com/chen-star/net_alpha/commit/1f3f191b91c9e52e5515c9b02751e766ae5afc74))

* feat: add annual wash sale report command with CSV export

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`8ca38b6`](https://github.com/chen-star/net_alpha/commit/8ca38b686990a726e57c0e50aa456a80793eece0))

* feat: add safe-to-rebuy tracker command ([`25ee889`](https://github.com/chen-star/net_alpha/commit/25ee88958efd09fb8a5271bb8f5acb0a34c8078d))

* feat: add simulate sell command with look-back detection

Adds `net-alpha simulate sell &lt;ticker&gt; &lt;qty&gt; [--price P]` that checks
the 30-day look-back window for existing buys that would trigger a wash
sale, shows the triggering trade with confidence label and safe-to-sell
date, and estimates the disallowed loss when a price is provided.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`3d3a0af`](https://github.com/chen-star/net_alpha/commit/3d3a0afb82a50a3928dda83d43d5e4e8f086a587))

* feat: add check command with summary, detail, and staleness warnings

Implements `net-alpha check` with year/ticker/type filtering, per-account
staleness warnings, wash sale summary table, violation detail table,
rebuy hint, and basis-unknown/option-expiration caveats.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`65466a2`](https://github.com/chen-star/net_alpha/commit/65466a2d0b247d6ec8e860e38b2f84db041a7031))

* feat: add CSV import command with schema confirmation ([`e24fb59`](https://github.com/chen-star/net_alpha/commit/e24fb59408fd3a0b732df3eac73ddbde01c1ef16))

* feat: add Typer app entry point with DB bootstrap ([`12591e9`](https://github.com/chen-star/net_alpha/commit/12591e90ee95f7599d9425e5417e50e166a3149a))

* feat: add CLI output helpers and disclaimer ([`ba743ed`](https://github.com/chen-star/net_alpha/commit/ba743ed1287fd9f8325da0e73b00d88e90061e1d))

* feat: add main import orchestrator

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`88acb60`](https://github.com/chen-star/net_alpha/commit/88acb60a87c5dff65f5e1b6952b32a11967bbc59))

* feat: add trade deduplication with hash and semantic key signals

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`040eca4`](https://github.com/chen-star/net_alpha/commit/040eca4d5cf3b1731039c942e190de4180371457))

* feat: add CSV reader with schema mapping and option parsing

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`b144405`](https://github.com/chen-star/net_alpha/commit/b14440574d118ea01d1b1c19bc481f0e689483c3))

* feat: add LLM schema detection with retry and exponential backoff

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`d5156b1`](https://github.com/chen-star/net_alpha/commit/d5156b15724af9e1346a99bc1016660117946971))

* feat: add option symbol regex parsers (OCC, Schwab, Robinhood)

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`d48a098`](https://github.com/chen-star/net_alpha/commit/d48a098af9a509e6e7b4ed9f04fae6bff2bccd0e))

* feat: add row anonymizer for LLM schema detection

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`553d46b`](https://github.com/chen-star/net_alpha/commit/553d46b6ec548354d6df182a6a68d46a859109d0))

* feat: add repositories with domain ↔ table mapping

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`18e1bf5`](https://github.com/chen-star/net_alpha/commit/18e1bf5ee41d5f8a3cd0597bcaae235202fda403))

* feat: add schema migration framework with v0→v1

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`b3ef87f`](https://github.com/chen-star/net_alpha/commit/b3ef87fcdb7cc578b3d5f638f8743c10bda5c6f2))

* feat: add DB connection and init with schema versioning

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`e3ed6a1`](https://github.com/chen-star/net_alpha/commit/e3ed6a15f417d729391a470e4afdebf344bb7a34))

* feat: add SQLModel table classes for all entities

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`96af8a7`](https://github.com/chen-star/net_alpha/commit/96af8a7392a637b822a019c3c97d334ee71bebc7))

* feat: add Settings config via pydantic-settings

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`ec96913`](https://github.com/chen-star/net_alpha/commit/ec969132973f985e7b30dc78171e4fcfa2d227c2))

* feat: implement core wash sale detection engine ([`21e590e`](https://github.com/chen-star/net_alpha/commit/21e590eb09101c58126a5b1ca36b9e034f9c7429))

* feat: add ETF pairs loader with user override support ([`aa33a91`](https://github.com/chen-star/net_alpha/commit/aa33a912dc53f87d8698f6cd1d7ad240f6902b57))

* feat: implement equity match confidence with ETF pair support

- Add get_match_confidence for all equity/option/ETF scenarios
- Tests cover confirmed, probable, unclear, and no-match cases ([`d94c022`](https://github.com/chen-star/net_alpha/commit/d94c02258dd2ea1ca35ab290e2e62139c4d034ef))

* feat: implement 30-day wash sale window check ([`0d16b00`](https://github.com/chen-star/net_alpha/commit/0d16b0037127f69993b95a6b4186d19115ea8d9b))

* feat: add factory_boy test fixtures for Trade and Lot ([`2942c0d`](https://github.com/chen-star/net_alpha/commit/2942c0ddcf32d101c650ccb123190fab73ea4073))

* feat: add Lot, WashSaleViolation, and DetectionResult models ([`6c88d0a`](https://github.com/chen-star/net_alpha/commit/6c88d0a4ba7ae9f072775009c55866138166badc))

* feat: add Trade and OptionDetails domain models ([`77cb8cd`](https://github.com/chen-star/net_alpha/commit/77cb8cd87873d554a4eca415e0da2898c8531d9d))

* feat: initialize project structure with uv and hatch ([`c370488`](https://github.com/chen-star/net_alpha/commit/c3704880f9ede0833229a2edc825bbe3b85ebabb))

### Fix

* fix: resolve ruff linter warnings in Plan 1 source files

Replace Optional[X] with X | None, fix line-length violations,
and clean up unused imports across domain model, matcher, and tests.

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`25d778d`](https://github.com/chen-star/net_alpha/commit/25d778de009d04d9fb8935f85571fd3534f2b1dd))

### Test

* test: complete integration test suite — CLI and engine tiers ([`0c8a5dd`](https://github.com/chen-star/net_alpha/commit/0c8a5dd6942a6bc7cd808b5195952ce891ee6000))

* test: add CLI integration tests for report command ([`bd4223e`](https://github.com/chen-star/net_alpha/commit/bd4223e1c4c9b1961afd068dbeff7b9ea156b0dd))

* test: add CLI integration tests for rebuys command ([`ef484fa`](https://github.com/chen-star/net_alpha/commit/ef484fa8883754ba2e747d3c6a97231de564e30e))

* test: add CLI integration tests for simulate sell command ([`9c99f5f`](https://github.com/chen-star/net_alpha/commit/9c99f5fdd20b6a911308ebf1dd930de69b327472))

* test: add CLI integration tests for check command ([`f3a9012`](https://github.com/chen-star/net_alpha/commit/f3a9012f47247a02c573e424f330902f3d05c70f))

* test: add CLI integration tests for import command ([`2416c11`](https://github.com/chen-star/net_alpha/commit/2416c119de4cf61d070f1ea8375d0b8ec299b71b))

* test: add engine integration tests for wash sale detector ([`68e22ac`](https://github.com/chen-star/net_alpha/commit/68e22acf4c859802746ace0a8deda9a9827d9682))

* test: add engine integration tests for import pipeline ([`ffc49aa`](https://github.com/chen-star/net_alpha/commit/ffc49aab1ce17dce6dcd66f8a19783e8ffdbbea0))

* test: add engine integration tests for import pipeline

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`45ed76d`](https://github.com/chen-star/net_alpha/commit/45ed76de64d10281d6b88aac867b9c4ef716f4f3))

* test: add CLI integration conftest with patched _bootstrap ([`5f6e4e3`](https://github.com/chen-star/net_alpha/commit/5f6e4e310912321f5c189cd12d5576e9fe91e774))

* test: scaffold integration test directory and shared conftest ([`7ebd6ac`](https://github.com/chen-star/net_alpha/commit/7ebd6ac53f843e1a30c6570eae38e26c79fde66a))

* test: add golden file integration tests for Schwab and Robinhood CSV

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`f7a36ee`](https://github.com/chen-star/net_alpha/commit/f7a36ee12870164cbdec583c5aab6cc714a6d9da))

* test: add Lot, Violation, and SchemaCache repository tests

Co-Authored-By: Claude Sonnet 4.6 &lt;noreply@anthropic.com&gt; ([`645eb69`](https://github.com/chen-star/net_alpha/commit/645eb6956e4ce192cbbcfc28e1f3612e6f4d308e))

* test: options and ETF wash sale detection scenarios ([`4f9240b`](https://github.com/chen-star/net_alpha/commit/4f9240be4675fbf9a873eb2f75bb66e997aa1ecc))

* test: cross-account, cross-year, basis_unknown, and edge cases ([`d42cb0c`](https://github.com/chen-star/net_alpha/commit/d42cb0c9f1328ad4dc5ac8096d508512182bac7f))

* test: FIFO allocation, partial wash sales, and basis adjustment ([`a902aff`](https://github.com/chen-star/net_alpha/commit/a902aff5317dfb877821cf8b764a858ff6a10c9b))

### Unknown

* Delete liscense in README.md. ([`bbb78b5`](https://github.com/chen-star/net_alpha/commit/bbb78b5ff83232959391d3ac499da8713d9cce31))

* Add README.md. ([`d5152b5`](https://github.com/chen-star/net_alpha/commit/d5152b503a7385b81c5dffd9d5aeb7c94c0e40c7))

* Enable gitnexus ([`f127792`](https://github.com/chen-star/net_alpha/commit/f1277921a8e405b3e5c811c448d541b09f804eb0))

* Add v1 plan ([`2ed4e10`](https://github.com/chen-star/net_alpha/commit/2ed4e10bb1069412bc7a2e7cddb749316c5f15c7))

* Add spec for v1. ([`255a26c`](https://github.com/chen-star/net_alpha/commit/255a26c660fd00aca9799c67ba396ba9a574f59d))

* init PRD.md ([`9d87db8`](https://github.com/chen-star/net_alpha/commit/9d87db828384b6b034d80650111d1fb9a84e1bb3))
