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.
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.
- Add Quote.isToday: compare updateTime date with today in Beijing timezone
- effectiveDailyPnl returns nil when quote data is stale (not from today)
- Prevents showing yesterday's change as today's daily PnL on closed markets
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New Watchlist model: named virtual portfolios with per-stock cost/shares
- Watchlists persisted to watchlists.json, active ID in settings
- AppState: effective cost/shares/PnL methods that respect active watchlist
- Settings: create/delete/switch watchlists, edit writes to active watchlist
- ProfitSummaryView shows active watchlist name
- UI alignment fixes for toggle and text field padding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ET 20:00-24:00: allow Sunday (wd=1) as Monday overnight start
- ET 00:00-04:00: allow Saturday (wd=7) as Friday overnight continuation
- Fixes no data shown on Sunday evening when overnight API has live data
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add PythService.swift: fetch overnight prices from us-overnight-stock-api.vercel.app
- Restore "夜盘" session in usMarketSession() (ET 20:00-04:00)
- Parallel fetch Sina + Pyth during overnight, merge once to avoid UI flicker
- Clear stale after-hours extendedPrice before applying overnight data
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove "夜盘" from usMarketSession(), only keep 盘前/盘中/盘后
- Night session (ET 20:00-04:00) has no free real-time data API
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add SHA-256 hash verification for downloaded DMG in auto-update
- Fix shell command injection: use positional args instead of path interpolation
- Add log rotation (truncate to 500KB when exceeding 1MB)
- Replace critical try? with do/catch + logToFile for data persistence
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix "daily + total" PnL mode only showing daily: use single Text concatenation
- Move PnL options into status bar picker (daily / total / both), hide icon in PnL mode
- Add rounded corners to app icon for macOS consistency
- Add LaunchAgent plist fallback for launch-at-login on Intel Macs
- Remove StatusBarPnL enum, use statusBarStockId special values instead
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Show floating PnL percentage for each stock and portfolio summary
- Show daily/total PnL percentage in profit summary view
- Replace manual GitHub download with in-app auto-update (download DMG, mount, replace, relaunch)
- Disable app sandbox for Process support (non-App Store distribution)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- AppState.init: use _stocks = Published(wrappedValue:) to bypass didSet,
preventing empty array from overwriting disk on decode failure
- AppState.saveStocks: guard against overwriting non-empty file with []
- Stock: explicit Codable extension using decodeIfPresent for optional fields,
so missing fields in old JSON use defaults instead of failing the whole decode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CurrencyService: fetch USD/CNY and USD/HKD from Sina Finance forex
- DisplayCurrency enum (CNY/HKD/USD) with symbol and display name
- AppState: fetch rates on every refresh, convert totalPnL/totalDailyPnL
- Settings: add 持仓汇总货币 segmented picker
- ProfitSummaryView: show currency symbol in P&L display
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>