3 Commits

Author SHA1 Message Date
claude-owner
d53ff7a745 feat(sidebar): RelatedStocks defaults collapsed + readOpen garbage-value fallback
readOpen() (reviewer f9027f5 non-blocking note):
- Previously returned v === '1' which collapsed '0' AND any garbage
  value (legacy markers, manual edits) into the same false branch,
  contradicting the 'fallback to default' comment. Split into three
  explicit cases: '1' → true, '0' → false, anything else →
  defaultOpen. Behavior unchanged for the writeOpen-only path; only
  garbage input is corrected.

RelatedStocks defaultOpen=false:
- Of the three collapsible sidebar panels (AlertsPanel,
  InvestmentNote, RelatedStocks), RelatedStocks has the lowest
  information density per row and the weakest direct tie to 'this
  symbol' (it's other symbols in the same market). Starting collapsed
  cuts initial mobile scroll height for first-time visitors; once the
  user toggles, localStorage pins their preference.
2026-06-01 10:31:48 +09:00
claude-owner
f9027f5365 feat(sidebar): collapsible AlertsPanel / InvestmentNote / RelatedStocks
Three lower-sidebar panels are now wrapped in a shared Collapsible so
mobile readers can fold away sections they're not actively using.

New shared pieces:
- lib/collapsible.ts — readOpen/writeOpen helpers backed by
  localStorage under 'panel-open:<name>'. Per-panel-name (not
  per-code) because the preference is a panel-level habit; users
  don't want different fold state for every symbol.
- components/Collapsible.tsx — button-as-header + chevron + body.
  aria-expanded for AT. Body uses 'hidden' instead of conditional
  unmount so textarea text, partial alert form input, and
  RelatedStocks fetch results all survive collapse cycles.
  SSR-safe: useState(defaultOpen) then useEffect-sync to avoid React
  hydration mismatch when localStorage diverges from default.

Panel refactors:
- RelatedStocks: outer rounded div + header replaced with Collapsible;
  loading/error/empty/data states all rendered inside body (header
  stays visible so user can still toggle/recognize the section even
  while loading).
- InvestmentNote: same swap. saved.updatedAt drives the subtitle.
- AlertsPanel: same swap. current price drives the subtitle.

Defaults are open=true everywhere; the surface area is the toggle
itself, not a forced collapse. User's first interaction sets the
sticky preference.
2026-06-01 10:26:33 +09:00
claude
21db09f65f feat(markets): /api/markets/movers + /related/{code} + 홈/종목 페이지 연동
backend:
  - 신규 라우터 app/api/markets.py
  - GET /api/markets/movers?market=ALL|KOSPI|KOSDAQ&limit=10
    → gainers / losers / by_volume 3 카테고리. ohlcv_daily 최신 2 거래일 비교.
  - GET /api/markets/related/{code}?limit=8
    → 같은 market 내 거래량 상위 (sector 컬럼은 시드 단계 NULL 다수라 제외).
  - main.py 에 라우터 include.

frontend:
  - lib/api.ts 에 Mover/MoversResponse/RelatedResponse 타입 + .movers/.related 추가.
  - components/MarketsOverview.tsx: 등락 상위 / 등락 하위 / 거래량 상위 3 컬럼.
  - components/RelatedStocks.tsx: 같은 시장 내 관련 종목 8 개.
  - 홈 페이지에 MarketsOverview 배치 (SeedTiles 아래).
  - 종목 페이지 우측 사이드바 컬럼에 RelatedStocks 추가 (SymbolSidebar 아래).

verify: py_compile clean, tsc --noEmit clean, next lint clean.
2026-05-28 01:36:13 +09:00