Work / Chitragupt / Wiki / Decisions
2026-09-07-portfolio-observation-panels-belong-in-aside
Decisioncanonicalverified 2026-09-07
DECISION.2026-09-07.PORTFOLIO-OBSERVATION-PANELS-BELONG-IN-ASIDEPortfolio observation panels live in the sticky aside, and rank by rupees
Decision
The three observation panels — "What lost you money", "What made you money", "Gaps in your safety net" — render stacked vertically inside the sticky right aside, under "Goals · funding pace", on both /portfolio and /portfolio/holdings. They are not a full-width three-column grid in the main column.
Separately, they rank by rupees gained or lost, not by percentage return.
Why
Placement. PILLAR.PORTFOLIO.REVIEW.BLOCK-ORDER on pillar-portfolio listed "(4) Observation panels · (5) Goals · funding pace aside" without naming a column for (4), and the implementation read that as "(4) goes in main". Every other source disagreed:
portfolio-review.htmlputs the range switcher, Goals, and all three panels inside one<aside class="lg:sticky lg:top-20 lg:self-start space-y-3">.portfolio-holdings.htmldoes the same.PILLAR.PORTFOLIO.PATTERN-PANELSon the same entity page already described them as "three narrative-list asides"./portfolio/holdingswas already rendering them in the aside, using a[&>section]:!grid-cols-1CSS override to force the three-column grid back into one column — a workaround that only exists because the component assumed the wrong container.
So one line of one entity page said main; the wireframes, the neighbouring line of the same entity page, and one of the two real call sites all said aside. The entity line is corrected rather than the four sources that agreed.
This also fixed two user-visible complaints at once. The right column held only Goals and an upload button — "the right panel is useless" — because three of its four intended blocks were somewhere else. And side by side in a grid, a 6-row winners panel next to a 2-row safety-net panel left a visible hole; stacked, they simply run on.
Ranking. The panels ask what made or lost the user money, and were sorted by percentage. On a real 111-holding account that put RELINFRA (−79% of ₹5,816 = −₹22k) above RVNL (−39.9% of ₹83k = −₹33k), so the positions that actually cost the most money never appeared. PortfolioTopHolding gained an unrealised_paise field and both lists now sort on it. Rows lead with the rupee figure, matching the wireframe's own phrasing ("Net −₹10.7L across two brokers").
Impact
- pillar-portfolio —
REVIEW.BLOCK-ORDERrewritten to name the column each block belongs to;PATTERN-PANELSnotes the rupee ranking. PortfolioPage.tsx—PortfolioShelltakes anobservationsslot rendered in the aside;ObservationPanelsstacks (space-y-3) instead of gridding.portfolio/holdings/page.tsx— the[&>section]:!grid-cols-1override deleted, no longer needed.portfolio-review.ts/portfolio-review-engine.ts—unrealised_paiseper holding;top_winners/top_losersranked by it.
The same bug, found at three call sites
unrealised = nav − invested computed on the whole portfolio counts EPF/PPF/NPS balances — which report a value against a cost basis of zero — as pure gain. It read ₹14.16L on an account whose real unrealised gain was ₹13.3k. Fixed in:
portfolio-review-engine.ts— the storedunrealised_paise.InvestedKpiCard— recomputed locally, ignoring the engine's now-correct field.portfolio-holdings-rows.ts— the Holdings totals strip, which computedytdcorrectly from the cost-basis-scoped pair andunrealisedfrom the unscoped one, two lines apart.
A fourth instance sat in PortfolioVsNiftyChart's callout, which claimed the portfolio beat Nifty by ₹14.16L for the same reason. All four now use the cost-basis-scoped pair, which is what nav_with_cost_basis_paise exists for.
Second pass — the rest of the variant audit
Read all 9 wireframes under .context/designs/web/portfolio/. Three more divergences, all resolved toward the design:
- "Capital gains booked · per AY" was three KPI tiles, not a chart. The design draws a per-AY timeline: one bar per assessment year, value above, AY and tax below, a baseline dot, and a dashed "₹0 · so far" outline for the year in progress.
PILLAR.PORTFOLIO.CHART.CG-BOOKED-PER-AYon pillar-portfolio already said "per-AY LTCG + STCG timeline" — the code had drifted, the entity had not. Built asCapitalGainsTimeline; the current AY's figures moved to a one-line summary beneath it.getPortfolioHistorySeriesgainedrealised_tax_paiseandltcg_exemption_unused_paiseto feed the per-bar sub-labels. - The Top Holdings card is in no wireframe. Deleted, along with
CgStat,DONUT_PALETTEand thereadOnlyprop that existed only for it.BLOCK-ORDERitem (6) is a holdings drill link, which the allocation card's "111 holdings · See all →" already provides. The holdings TABLE on/portfolio/holdingsis a different thing and is untouched. - The Goals empty state described an action and offered no way to take it. Added a "Set a goal" button, gated to non-
ca_clientlenses like the populated card's "Map holdings" link.
Alignment fixes: the status banner is emerald (border-emerald-500/30 bg-emerald-500/5 text-[11.5px]) per the design, not zinc; its trailing copy now reads "see right column" rather than "see below", which stopped being true when the panels moved; the chart title carries the resolved range inline ("· FY 2021-22 → FY 2026-27").
Kept against the design, deliberately: the four-KPI strip (NAV / YTD / Invested / Realised CG) appears in no wireframe, and the design instead surfaces NAV in the donut centre. It is left in place because it is the one part of the page the product owner singled out as working. Flagged rather than deleted.
Still divergent: the design puts the range switcher in the aside as #pf-period-switcher with 1M · 3M · 6M · 1Y · Custom; ours sits in the chart header with 1Y · 3Y · 5Y · MAX. PILLAR.PORTFOLIO.RANGE-SWITCHER explicitly records the implementation's set as intentional ("chart-level; JS supports 1Y · 3Y · 5Y · MAX"), so this is left alone as a recorded divergence rather than silently changed.
Third pass — surplus-component sweep across all variants
Compared every wireframe's component inventory against the implementation and deleted what the designs don't have. Removed:
- The four KPI cards (NAV / YTD return / Invested / Realised CG) on the review page, plus their seven supporting components. In no wireframe — the design surfaces NAV in the donut centre instead.
- The Holdings totals strip (Showing / NAV / YTD / Unrealised / Unmapped).
portfolio-holdings.htmlhas no tile strip; "Showing 9 of 42" is pagination text and "YTD" is a table column header. - "Upload broker statement" from the aside. Every wireframe's aside ends Goals → panels → footnote.
- The full-width SEBI strip. The disclaimer itself is mandatory and verbatim per 2026-06-26-portfolio-sebi-strip, so it is RELOCATED, never dropped: every wireframe closes its
<aside>with the same sentence as a centredtext-[10px]footnote prefixed by "Statements land in Inbox". NowPortfolioAsideFootnote, on both the review and holdings pages. A placement and weight change, not a content change.
Font: ObservationPanel's list had no size set and inherited the page base (~16px); the design specifies text-[11.5px].
Placement corrections:
PerMemberAssetClassTablewas a standalone full-width section.portfolio-family.htmlnests it INSIDE the household-NAV card under aborder-tdivider with a 9px eyebrow and a 10px table — the same shape "vs balanced ideal" has under the allocation donut. Re-nested and stripped of its own card border, which would otherwise have produced a card inside a card.- View-only members had no exit.
portfolio-view-only.htmlends its banner with "Open your Personal portfolio"; the implementation had no such link, leaving a member stuck in the household lens.
Checked and confirmed already matching: the Holdings filter bar (search + 3 selects + sort) is exactly the design's; the Pro Family "Upgrade · ₹550" in portfolio-family-locked.html is the top-up from a paid plan, not a price drift — the canonical TIER.PRO-FAMILY.PRICE.AY2026 (₹799) is what the code renders via PRO_FAMILY_PRICE_LABEL.
Status
Active.
Sources
- .context/designs/web/portfolio/portfolio.html
- .context/designs/web/portfolio/holdings.html
- 2026-08-07-expense-portfolio-wireframe-alignment — the prior full-variant audit, which did not catch this
Every project of mine is written down like this.
Read the résumé