Skip to content

feat: vested/upcoming RSU grouping with vest-date valuation#204

Merged
Sagargupta16 merged 4 commits into
mainfrom
feat/rsu-vested-upcoming
Jul 9, 2026
Merged

feat: vested/upcoming RSU grouping with vest-date valuation#204
Sagargupta16 merged 4 commits into
mainfrom
feat/rsu-vested-upcoming

Conversation

@Sagargupta16

Copy link
Copy Markdown
Owner

What

RSU vesting schedules in Settings were rendered in raw insertion order (a 2025 vesting added last sat below 2028 rows) and every vesting -- past or future -- was valued at the grant's current stock price. This PR fixes both.

  • Vested / Upcoming grouping. Each grant's vesting table now splits into a dimmed "Vested" section (date <= today) and an "Upcoming" section, both sorted chronologically. Rows re-sort on date-input blur, not per keystroke, so rows don't jump while typing. Grants saved before this change are normalized on load. The summary line splits into "Vested: N shares (value)" and "Upcoming: N shares (value)".
  • Vest-date price locking. Once a vesting date passes, the historical close for that date is fetched once and stored as price_at_vest, so realized RSU income stops drifting with the current price. Falls back to current price when the lookup fails; editing a vested row's date clears the lock so it re-fetches.
  • Backend: GET /api/stock-price/{symbol}?on_date=YYYY-MM-DD returns the close on that date (nearest prior trading day within a week covers weekends/market holidays) plus an as_of field. RsuVesting schema gains optional price_at_vest (Decimal, > 0).
  • Projection math: getRsuVestingsByFY treats vested rows as realized income -- locked vest-date price, no stock-appreciation applied. Upcoming vestings keep the projection behavior. Same for the TDS schedule's rsuExtrasByFyMonth.
  • Structure: new lib/rsuVesting.ts is the single source of truth for isVested / sortVestings / vestingPrice / splitRsuTotals, shared by settings UI, tax projection, and TDS schedule. Grant state handlers extracted into a useRsuGrants hook; vesting table into VestingTable.tsx.
  • Housekeeping: CHANGELOG backfilled (stale "Unreleased" heading renamed to 2.20.0, missing 2.21.0 for PR feat: rule-based auto-categorization, transaction tags, saved filter views #203 added, 2.22.0 for this work). Versions aligned to 2.22.0 across root/frontend package.json, backend pyproject/uv.lock, and APP_VERSION (the health endpoint was reporting a hardcoded 1.0.0).

Testing

  • Backend: 328 pytest tests pass, including 7 new ones for the date-aware price endpoint (exact day, weekend fallback, null-close skip, no-data 502, future-date 400) and 3 for the price_at_vest schema.
  • Frontend: 286 vitest tests pass, including a new rsuVesting.test.ts suite (vested/sort/price/split-totals) and projection tests pinned to a fixed "today" proving vested rows never get appreciation.
  • Live UI (demo mode, Vite dev server): verified the vesting table renders Vested (1) / Upcoming (3) groups sorted by date; edited the last row's date to a past date and confirmed on blur it re-sorted to the top of the Vested group and the split totals updated. Checked at mobile and desktop widths; no new console errors.
  • pnpm run type-check, eslint, ruff check, ruff format --check, mypy, and production build all clean.

…stings

GET /api/stock-price/{symbol} accepts optional on_date and returns the
close on that date (nearest prior trading day within a week for
weekends/holidays) plus an as_of field. RsuVesting schema gains optional
price_at_vest so vested income can be locked at the vest-date price.
Vesting tables split into Vested (date <= today, dimmed) and Upcoming
sections, sorted chronologically; rows re-sort on date-input blur and
pre-existing grants are normalized on load. Summary line splits into
vested vs upcoming shares/value.

New lib/rsuVesting.ts is the single source of truth for isVested /
sortVestings / vestingPrice / splitRsuTotals, shared by the settings UI,
projectionCalculator (vested rows use locked vest-date price, never
appreciated) and tdsScheduleCalculator. useRsuGrants hook extracts grant
state handling from SalaryStructureSection and auto-fetches the
historical close once when a vesting passes, storing price_at_vest;
editing a vested date clears the lock so it re-fetches.
CHANGELOG gains 2.22.0 (this work), 2.21.0 (PR #203, was missing) and
renames the stale Unreleased heading to 2.20.0 (PR #202, merged
2026-07-04). Versions aligned across root/frontend package.json,
backend pyproject + uv.lock, and APP_VERSION (health endpoint reported
a hardcoded 1.0.0). HANDBOOK RSU section updated for vested/upcoming
grouping.
pytest.approx for float assertions, single throwing call in the
exception test, Annotated Query param, and extract applyVestPrices to
cut function nesting below 4 levels.
@sonarqubecloud

sonarqubecloud Bot commented Jul 9, 2026

Copy link
Copy Markdown

@Sagargupta16 Sagargupta16 merged commit efb16d9 into main Jul 9, 2026
8 checks passed
@Sagargupta16 Sagargupta16 deleted the feat/rsu-vested-upcoming branch July 9, 2026 02:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant