Two bugs in StockChartView triggered by opening a Korean stock's chart:
1. Top whitespace: DropdownView used ZStack to overlay stockListView
(opacity 0 when chart shown) and StockChartView. ZStack sized to
max(list, chart) — list at ~720 tall pushed the ~500-tall chart to
vertical-center, leaving ~200 of blank above the chart. Replace
ZStack with plain if-let so list and chart are mutually exclusive
in the layout tree.
2. Stat-row line wrap: fmt used %.3f always, producing 'KRW 284000.000'
which overflowed the narrow stat columns and wrapped to two lines
('284000.0' / '00'). Scale precision by magnitude: ≥1000 → integer,
≥10 → 2 decimals, else 3 decimals.
UI fix (SDK-agnostic):
- SettingsView height 480 → 620 so the settings mode panel size matches
list mode at maximum. Avoids MenuBarExtra's NSPanel not shrinking when
switching between list and settings views.
- Drop the .id(showSettings) hack — it forced view rebuild but the panel
is managed by AppKit and does not respond to SwiftUI view recreation.
CI hardening:
- Use maxim-lobanov/setup-xcode@v1 with latest-stable so the runner Xcode
is as close to local (Xcode 26.5) as available, minimizing SwiftUI SDK
behavior drift.
- Run xcodebuild test before building the DMG; release will fail-fast on
test regressions instead of shipping broken binaries.
Root cause analysis of v1.3.4 regression:
Local Xcode 26.5 produced a 5.2MB binary linking to macOS 26 SwiftUI
symbols; GH macos-latest with default Xcode 16 produced a 5.0MB binary
linking to macOS 15 SwiftUI. The .id() hack happened to look correct in
the SDK 26 build but not in SDK 15 — same source, different runtime
behavior. The height:620 approach works in both.
MenuBarExtra(.window) caches the NSPanel size from list mode (~720 with
many stocks) and does not auto-shrink when switching to the 480-tall
settings view, leaving a large blank gap. Adding .id(showSettings) tells
SwiftUI to rebuild the view subtree on mode change, prompting the panel
to recompute its size from the new content.
release: 1.3.3 → 1.3.4
The opaque background added in v1.3.2 exposed extra empty space below
the toolbar — the panel was sized larger than the VStack content.
Adding .fixedSize(horizontal: false, vertical: true) before the
background makes the panel size to its actual content.
release: 1.3.2 → 1.3.3
MenuBarExtra(.window) on the new SDK defaults to a vibrant translucent
material, letting whatever is behind bleed through. Apply
Color(.windowBackgroundColor) to the root VStack so the panel reads as
a normal opaque window in both light and dark mode.
release: 1.3.1 → 1.3.2
Stocks with both costPrice and holdingShares set (effective values from
real portfolio or active watchlist) appear at the top; the rest follow in
original order. Makes managing larger watchlists easier.
When user types a 6-digit code like '000660', Tencent returns A股 sz000660
(*ST 南华) while the same code is also a valid Korean ticker (000660.KS SK
hynix). Previous logic only fell back to Yahoo when Tencent returned empty,
hiding the Korean match.
Now: fetchSuggestions runs tencentSearch and yahooKoreanSearch concurrently
and merges results with dedup. Yahoo is queried whenever the input is
plausibly Korean: 6-digit number, ≥3 letters, Hangul, .ks/.kq suffix, kr_
prefix. Short numeric inputs skip Yahoo to save a request.
DropdownView: extend market iteration arrays to include .krStock so Korean
stocks appear as a 4th group after A股/港股/美股.
SettingsView search overhaul:
- looksKorean(): heuristic to route KR-like keywords (.ks/.kq suffix, kr_
prefix, Hangul characters) directly to Yahoo
- yahooKoreanSearch(): /v1/finance/search filtered to symbols ending in
.KS or .KQ, returns SearchResult with market=.krStock
- directResult(): explicit kr_<code>.ks and bare 005930.ks/kq forms supported
- Placeholder text updated to suggest Korean inputs
Tencent/Sina searches unchanged for A股/港股/美股.
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.
- 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>
- Use ZStack overlay for chart instead of if/else branch swap
- ScrollView stays mounted while chart is shown (opacity toggle)
- Scroll position preserved when returning from chart view
- Panel reopen naturally starts from top
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New setting: "持仓股单独分组置顶" toggle
- When enabled, stocks with holdings are grouped into a "持仓" section at top
- Remaining non-held stocks still grouped by A/HK/US market as usual
- StockGroupView supports custom title for holdings group
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>
- Add setting to choose status bar PnL: none / daily / total / both
- Fix launch-at-login: remove unconditional register() on every launch
- Add error logging for SMAppService register/unregister
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>
- Replace README screenshot with latest UI
- StockRowView: use Color.primary.opacity(0.04) to match ProfitSummaryView style
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- StockChartView: mark 盘前/盘中/盘后 with colored background zones and text labels for US stocks
- UpdateChecker: check latest release via GitHub API, prompt user to download if newer version found; semantic version comparison to avoid false positives
- SettingsView: add 关于 section with version and 检查更新 button; hide scrollbar (.never)
- DropdownView: hide scrollbar (.never)
- ProfitSummaryView: replace opaque white background with subtle transparent tint
- project.pbxproj: normalize MARKETING_VERSION to 1.0.0 (three-part semver)
- README/README_CN: add main screenshot
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>