11 Commits
Author SHA1 Message Date
vgearen 1669508fcf fix(tests): update RefreshSchedulerTests to match always-trading weekday behavior
v1.1.7 changed isTradingHour to always return true on weekdays (the US
four-session + overnight coverage spans 24h). Two tests still asserted
the pre-v1.1.7 lunch-break / after-close = not-trading behavior and
have been failing locally for months; CI's new Run Tests step caught
them and aborted the v1.3.5 release.

Update both tests to assert the current desired behavior (trading) and
add a comment explaining the rationale.
2026-05-14 11:34:53 +08:00
vgearen f1b6b72d8d fix(critical): isolate AppStateTests from user's real data directory
PRIOR BEHAVIOR (data-loss bug):
AppStateTests.setUp() constructed AppState() directly, which used the real
~/Library/Application Support/Stockbar/ for stocks.json. Each test method
that did 'sut.stocks = [s1, s2]' triggered didSet → saveStocks → silently
overwrote the user's actual portfolio file with 2 test entries. backupIfNeeded
then rolled all 10 backup snapshots forward, eventually erasing every
historical copy of real data.

This destroyed a user's 29-stock portfolio when xcodebuild test was run
during Korean stock feature development (2026-05-14 10:08).

FIX:
- AppState gains a static appSupportDirOverride: URL? hook
- AppStateTests.setUp() creates a unique tmpDir, sets the override, tearDown
  resets and removes the directory
- Verified: after running all 7 AppStateTests, the real stocks.json is
  byte-identical to before the run.

This must land before any further test run touches AppState.
2026-05-14 10:35:01 +08:00
vgearen a50a9c3d8c fix(tests): correct test_parseSina_multipleStocks assertions
Pre-existing test asserted quotes["159941"] != nil but raw data only
contains sh600000 and sz000001 — typo in original assertion. Replace with
the two real codes.

Note: two RefreshSchedulerTests (afterClose, lunchBreak) still fail; they
expose a long-standing 'return true; return false' bug in isTradingHour
(weekdays always trade). Out of scope for the Korean stocks change.
2026-05-14 10:14:17 +08:00
vgearen 8f568195d1 feat(korea): Korean intraday chart via Yahoo v8 chart
ChartService.fetchIntraday dispatches .krStock to new fetchKoreanIntraday,
which reuses the Yahoo v8 chart endpoint. parseKoreanChartPoints (exposed
static for tests) maps KST timestamps to indices: 09:00 → 0, 15:29 → 389
(KR session is 6.5h = 390 minutes, no pre/post market).

RefreshScheduler comment updated to note KR trading window (KST 09:00-15:30
= BJ 08:00-14:30) is already covered by the always-true weekday branch.
2026-05-14 10:07:10 +08:00
vgearen 6e63064207 feat(korea): add koreanMarketSession + wire KR into AppState.refresh
AppState.koreanMarketSession (nonisolated) returns '盘中' during KST 09:00-15:30
on weekdays, nil otherwise. Marked nonisolated so it can be called from sync
non-MainActor contexts (notably tests).

AppState.refresh now has a 5th parallel branch fetching Korean quotes via
DataService.fetchKoreanQuotes. KR is non-throwing (returns [:] on failure) so
it's awaited outside the try block — any KR network failure does not abort
A股/港股fetch results.
2026-05-14 10:05:47 +08:00
vgearen bddb608b76 feat(korea): fetch Korean quotes via Yahoo v8 chart endpoint
DataService.parseKoreanChartMeta parses Yahoo v8 chart 'meta' segment into
Quote (price/change/percent/updateTime). Yahoo v7 batch quote endpoint
returns 401 since 2024, so we fall back to v8 chart per-symbol.

DataService.fetchKoreanQuotes uses withTaskGroup to issue concurrent
requests for the watchlist; single-symbol failure does not affect others.

Fixtures captured from live API for KOSPI (005930.KS Samsung) and KOSDAQ
(293490.KQ Pearl Abyss); synthetic quote_empty.json covers null price.
2026-05-14 10:03:56 +08:00
vgearen 28eb6bd98c feat(korea): add Market.krStock + KRW currency + chart x-axis
Adding Market.krStock cascades into several exhaustive switches that must all
update together: ExchangeRates.convert, DisplayCurrency, StockChartView ranges
and labels. Bundles tasks 2/3/4/5/14 into one build-passing landing.

- Market enum: add krStock case + kr_ prefix routing in Market.from
- ExchangeRates: add usdToKrw (fallback 1380), krwToCny derived, KRW path in convert
- DisplayCurrency: add .krw (₩), display name 韩元
- CurrencyService.fetchRates: include fx_susdkrw in Sina FX batch URL
- StockChartView: KR fullDayRange 0...389 (KST 09:00-15:30, 390 min); KR axis labels
  09:00/10:30/12:00/13:30/15:30

Tests: +5 Stock cases for KR, +8 CurrencyKRWTests covering all KR↔CNY/USD/HKD/KRW
conversion paths and fallback constant.
2026-05-14 10:01:57 +08:00
vgearen 36ec419416 feat(korea): add KoreanStockID helpers for kr_<code>.ks/kq ↔ Yahoo symbol 2026-05-14 09:59:41 +08:00
vgearen 52c4c7d92a fix(tests): repair AppSettingsTests stale init-argument calls
Pre-existing tests called AppSettings(colorScheme:, refreshInterval:, statusBarStockId:)
which no longer compiles after AppSettings struct refactor. Switch to default
init + mutation pattern; behavior unchanged.
2026-05-14 09:59:32 +08:00
vgearenandClaude Sonnet 4.6 28cf10480b Rename app from StockMonitor to Stockbar
- Bundle ID: com.vgearen.StockMonitor → com.vgearen.Stockbar
- Display name: Stock Monitor → Stockbar
- App entry struct: StockMonitorApp → StockbarApp
- App Support path: ~/Library/Application Support/Stockbar
- Log path: ~/Library/Logs/Stockbar
- OSLog subsystem: com.vgearen.Stockbar
- Test module import: @testable import Stockbar
- GitHub Actions: scheme, DMG name updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 09:34:19 +08:00
vgearenandClaude Sonnet 4.6 6b1f565d7d Initial release: macOS menu bar stock monitor
- A-shares, HK, and US stock real-time quotes (Sina / Tencent)
- Intraday minute charts (Tencent / Yahoo Finance)
- US extended-hours session indicator with price and change%
- Portfolio P&L tracking (cost price × shares)
- Sort by change%, color themes, launch at login
- File-based persistence with 10-snapshot rolling backup
- Universal binary (Apple Silicon + Intel)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 17:41:52 +08:00