feat(market): equity detail page with per-asset-class routing#135
Merged
luokerenx4 merged 5 commits intomasterfrom Apr 21, 2026
Merged
feat(market): equity detail page with per-asset-class routing#135luokerenx4 merged 5 commits intomasterfrom
luokerenx4 merged 5 commits intomasterfrom
Conversation
Step 2 of the Market workbench. Splits /market into a landing (search only) and /market/:assetClass/:symbol for detail views, so the current asset lives in the URL — shareable, bookmarkable, and back/forward navigable instead of hidden in component state. Equity gets a bespoke layout: a quote header (last, change, day range, 52W, MA50/200), the existing K-line, side-by-side profile + key metrics, and a tabbed financial statements table (Balance / Income / Cash Flow) that lazy-loads each statement on first view and caches per symbol. Crypto / Currency / Commodity fall through to a GenericDetail with just the K-line — their data shapes are different enough that forcing them into the equity layout would mislead. Each will get its own detail page when the shape is figured out. The reusable surface is a single Card wrapper (title + right slot + content). Every panel owns its own fetch / loading / error / render. No shared Table / Tabs primitives — financial tables are row=item × column=period and don't generalize, so they live inside the financials panel as a one-off. The SearchBox navigates directly now; its onSelect prop is gone. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two things users hit after the equity pilot landed. The financial statements panel was rendering but getting squeezed out of visible space: EquityDetail's outer flex-col carried min-h-0 (allowing children to shrink below natural height) while Card's content used flex-1 min-h-0 for a shape that nobody actually needed. Combined, they let the browser collapse the bottom panel when content ran long. Dropped both — panels render at natural height, parent's overflow-y-auto owns the scroll like it always should have. K-line interval and range now persist in the URL as ?interval=1h&range=1M via useSearchParams, with replace so we don't spam browser history. Default values (1d / 1Y) are elided so shared links stay tidy. The SearchBox propagates the current search string when switching symbols, so picking MSFT after configuring AAPL at 1h/1M keeps the view settings instead of resetting them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Key Metrics and Financial Statements were rendering "—" for a bunch of
rows because the panels were keyed on guesses (priceToEarningsRatioTTM,
operating_income, total_equity, property_plant_equipment_net,
selling_general_and_administrative_expenses, grossProfitMarginTTM, …)
instead of the names the FMP fetcher actually emits after its alias
pass. The fetchers canonicalize to snake_case (price_to_earnings,
return_on_equity, gross_profit_margin, total_operating_income,
selling_general_and_admin_expense, plant_property_equipment_net,
total_common_equity, etc.) and drop the upstream camelCase — so the old
lookups never hit.
This is purely a UI correction — no opentypebb or FMP changes. Verified
against live /fundamental/{metrics,ratios,balance,income,cash}
responses. Also swapped balance's accountsReceivables (upstream raw,
escapes aliasing) for net_receivables (canonical).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
FMPEquityQuoteFetcher's ALIAS_DICT had maps for dayHigh/dayLow/price/ priceAvg50/priceAvg200/previousClose/changePercentage/timestamp but not yearHigh/yearLow/marketCap. The standard EquityQuote schema declares year_high/year_low/market_cap with default(null), so parsing left those canonical fields null while the upstream camelCase ones survived via passthrough — consumers reading the canonical snake_case contract saw nothing. Add the three missing aliases. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bid and ask are real-time L1 quote data whose usefulness lives at the execution / order-routing layer (UTA), not in an analysis workbench. Keeping them alongside 52W range and MA200 blurred the line between \"price to act on\" and \"price to think about\" — and since most providers don't populate bid/ask on their historical quote endpoints anyway, they just rendered as dashes. Drop them from the header. The remaining 10 metrics land evenly on a 5-column grid. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Step 2 of the Market workbench. Splits the single-component /market surface into a landing (search only) and /market/:assetClass/:symbol for per-asset-class layouts. Equity is the pilot — everything else falls through to a generic K-line view until each class's data semantics are figured out.
New: Equity detail page
?interval=1h&range=1M) via useSearchParams. SearchBox preserves those across symbol switches so user preferences carry over when jumping between AAPL → MSFT.Routing
/market— landing with search + empty state/market/:assetClass/:symbol— detail dispatcher; equity getsEquityDetail, others getGenericDetail(K-line only)useNavigate(), preserves existing search paramsopentypebb fixes along the way
getHistoricalOhlcwas passing the canonicalstart_date/end_datethrough to FMP which expectsfrom/to; requests were silently returning a fixed 5-year window regardless. Rename keys before serialising.apikey/api_key/token/access_token/key/secret/password(FMP URL auth was leaking the key into logs), and the caught cause is now included in the thrown message so fetch failures are diagnosable without debugger stepping.yearHigh/yearLow/marketCapweren't in the FMP ALIAS_DICT, so the canonical snake_case fields were always null despite data being there in camelCase. Added the three missing entries.Design decisions worth noting
[equity, crypto, currency, commodity]) buried a commoditygoldbelow 20 equity ticker hits. Now scored by match quality (exact / alias / prefix / word-boundary / substring) sogoldcommodity wins,xauhits gold via alias, and Goldman Sachs no longer outranks SPDR Gold Trust ongold.Test plan
npx tsc --noEmitcleanpnpm test— 1088 tests / 56 files pass/market/equity/AAPLrenders all 5 sections; switching Balance/Income/Cash Flow tabs loads each lazily/market/crypto/BTCUSDrenders K-line only (GenericDetail)/market/commodity/goldrenders K-line placeholdergold→ commodity gold at Add Star History chart to README #1;xau→ gold via alias;apple→ AAPL at Add Star History chart to README #1apikey=***and include the caught cause🤖 Generated with Claude Code