Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
6e33881
fix(ratewise): 修復 lint 基線
May 12, 2026
f8c814b
fix(ratewise): 收斂正式公開路由表面
May 12, 2026
9cf44bb
fix(ratewise): 改善全域錯誤可觀測性
May 12, 2026
8e3e16f
test(ratewise): 補強生產級 QA 閘門
May 12, 2026
1abab9c
build(ratewise): 收斂生成產物 SSOT
May 12, 2026
e7f76aa
refactor(ratewise): 建立幣別路由 registry
May 12, 2026
325ebff
test(ratewise): 對齊 internal route 預渲染契約
May 12, 2026
2529742
fix(ratewise): 收斂 fallback 匯率快照
May 12, 2026
4527f53
fix(ratewise): 修正多幣別匯率標籤
May 12, 2026
bfedf89
fix(ratewise): 修正多幣別無障礙對比
May 12, 2026
d358efd
test(ratewise): 修復 E2E 測試矩陣漂移
May 13, 2026
bb5eb9b
fix(ratewise): 補強遠端匯率失敗 fallback
May 13, 2026
5870651
fix(ratewise): 修復 SSG 金額頁快照來源
May 13, 2026
4569006
Merge remote-tracking branch 'origin/main' into chore/ratewise-produc…
May 13, 2026
75c46bf
fix(ratewise): 更新 fallback 快照與刷新輸出
May 13, 2026
146fd3f
ci(ratewise): 收斂資料 PR 合併治理
May 13, 2026
596426d
fix(ratewise): 補齊跨瀏覽器 fetch 失敗分類
May 14, 2026
ab61b65
refactor(ratewise): 收斂 Sentry beforeSend 與 errorClassification SSOT
May 14, 2026
f6a9298
perf(ratewise): 換錢所趨勢線收斂為 aggregate SSOT
May 15, 2026
3ff42f2
refactor(ratewise): 收斂 moneybox aggregate 註解與快取寫入點
May 15, 2026
e4c1b63
ci(ratewise): moneybox workflow 發布 aggregate 30-day history
May 15, 2026
56c7924
refactor(ratewise): 收斂 UIUX 與設計 Token SSOT
May 23, 2026
05b3e49
refactor(ratewise): 收斂設計 Token SSOT 至生產級別
May 30, 2026
07c7756
chore(ratewise): 生產治理同步與 PWA shell 品牌色收斂
May 30, 2026
cc67165
fix(ratewise): 收斂換錢所 rateMode 選價 SSOT 並移除內容頁 sticky header
s123104 Jun 10, 2026
3440a56
fix(ratewise): 補齊互動元件 44px 觸控目標與鍵盤焦點指示
s123104 Jun 10, 2026
7b1b8be
refactor(ratewise): 圓角 SSOT 終局收斂並新增防回歸守門
s123104 Jun 10, 2026
e31ab53
fix(ratewise): 修正 open-data 端點路徑 320px 水平溢出
s123104 Jun 10, 2026
6790b17
fix(ratewise): 移除 CDN If-None-Match 避免 jsDelivr preflight 失敗
Jun 13, 2026
065955e
docs(ratewise): 新增 2026 產品級 UX 規格與 Phase 1 計畫
Jun 13, 2026
83c793f
feat(split-meow): 新增 KRW 貨幣支援與換錢所匯率整合
Jun 18, 2026
6462118
fix(ratewise): 更新 sw.test.ts 對齊 precache-first 導覽策略
Jun 18, 2026
28dd0f2
fix(ratewise): 更新 pwa-offline.test.ts 對齊 precache-first 導覽策略
Jun 18, 2026
9e20410
fix(ratewise): 對齊 nitro 主題色票 SSOT(themes.ts ↔ index.css)
Jun 19, 2026
51066c5
feat(split-meow): 以固定底部面板取代 BottomSheet 解決鍵盤遮擋問題
Jun 19, 2026
86c77b4
fix(ratewise): 以 precache-first 取代 3s timeout 修復 iOS PWA eviction 假離線
Jun 19, 2026
cbd116c
fix(ratewise): 移除 moneybox CDN ETag 請求,更新 OpenData 與 SeoTech 說明
Jun 19, 2026
a9eae3d
fix(ratewise): 為導覽 case-3 加上有界網路 fallback (#426)
s123104 Jun 26, 2026
5ccd813
fix(ci): MoneyBox history 檔名改用資料的首爾 snapshot date
Jun 26, 2026
87fcbe3
feat(split-meow): 每筆支出保存幣別與匯率快照,KRW 顯示對應 TWD
Jun 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-cdn-preflight-if-none-match.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/ratewise': patch
---

修正匯率 CDN 請求因 preflight 失敗而過度降級至 GitHub Raw 的問題,提升主 CDN 命中率與載入穩定性
5 changes: 5 additions & 0 deletions .changeset/fix-moneybox-cdn-etag.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/ratewise': patch
---

移除 moneybox CDN fetchFromCDN 的 ETag/304 條件式請求,統一以 HTTP cache 與 client 端 TTL 管理重複請求;更新 OpenData 與 SeoTech 頁面說明文字以反映新行為
5 changes: 5 additions & 0 deletions .changeset/fix-nitro-theme-ssot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/ratewise': patch
---

對齊 nitro 主題色票 SSOT:themes.ts primary 從 #00D4FF 改為 #0096E6,與 index.css 保持一致
5 changes: 5 additions & 0 deletions .changeset/fix-ratewise-pwa-ios-eviction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/ratewise': patch
---

修復 iOS PWA precache 驅逐後 3s timeout 導致在線用戶看到 offline.html 的假離線問題,改用 precache-first 冷啟動策略
5 changes: 5 additions & 0 deletions .changeset/quiet-breadcrumb-token-refactor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/ratewise': patch
---

收斂內容頁返回導覽、麵包屑與核心 UI shell 的 design token,改善 PWA 與小螢幕顯示穩定性。
5 changes: 5 additions & 0 deletions .changeset/ratewise-a11y-touch-target-focus.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/ratewise': patch
---

無障礙與操作體驗改善:評分視窗星星按鈕、開放資料頁複製按鈕、Email 連結與計算機關閉鈕補齊 44px 觸控目標與鍵盤焦點指示;小型標籤與代碼區塊圓角統一為設計系統規格。
5 changes: 5 additions & 0 deletions .changeset/ratewise-artifact-ssot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/ratewise': patch
---

Clarify generated artifact buckets and remove local build report files from tracked source.
5 changes: 5 additions & 0 deletions .changeset/ratewise-currency-route-registry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/ratewise': patch
---

Consolidate currency landing route registration behind a registry with parity checks.
5 changes: 5 additions & 0 deletions .changeset/ratewise-data-pr-governance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/ratewise': patch
---

Strengthen scheduled rate data update governance by requiring generated data PRs to pass branch protection before merge.
5 changes: 5 additions & 0 deletions .changeset/ratewise-design-token-ssot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/ratewise': patch
---

收斂設計 Token 為語義化 SSOT:圓角與陰影全站統一為語義 token,恢復 Tailwind 標準預設值避免第三方整合風險
5 changes: 5 additions & 0 deletions .changeset/ratewise-error-classification-cross-browser.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/ratewise': patch
---

errorClassification 補上 Firefox 與 iOS / macOS Safari 多種 fetch 失敗訊息覆蓋(離線、連線中斷、DNS 失敗、無法連線),避免一般網路失敗在跨瀏覽器下被誤分類為 unknown;同步修正刷新腳本 cache 分支幣別數量輸出。
5 changes: 5 additions & 0 deletions .changeset/ratewise-error-observability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/ratewise': patch
---

改善正式版全域錯誤分類,避免一般網路錯誤被誤判為預期歷史匯率缺檔
5 changes: 5 additions & 0 deletions .changeset/ratewise-exchange-shop-ratemode-nav-header.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/ratewise': patch
---

修正換錢所匯率模式選價:選「中間價」時正確以(買入+賣出)÷2 計算、選「賣出」時雙向採用賣出價,趨勢圖與多幣別列同步跟隨所選模式;內容頁頂部「返回+麵包屑」導覽改為不固定的圓角按鈕樣式,修復部分 iOS PWA 用戶頂部內容被遮擋的問題。
5 changes: 5 additions & 0 deletions .changeset/ratewise-fallback-snapshot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/ratewise': patch
---

讓 build-time fallback 匯率快照由每日資料更新流程維護,避免一般 build 產生匯率資料漂移;同時在線上遠端匯率來源全失敗且本機無快取時,改用 build-time snapshot 維持換算器可用。
5 changes: 5 additions & 0 deletions .changeset/ratewise-moneybox-aggregate-cleanup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/ratewise': patch
---

換錢所 aggregate 趨勢線:移除說明性註解、抽出 daily fallback helper、收斂兩處 cache 寫入為單一寫入點,行為不變。
5 changes: 5 additions & 0 deletions .changeset/ratewise-moneybox-aggregate-trend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/ratewise': patch
---

換錢所(MoneyBox)趨勢線改用 aggregate endpoint:與台銀 history-30d.json SSOT 一致,命中時 30 個 daily fetch 收斂為 1 個(runtime AB 量到 50→1 requests、~5,049ms→~2ms)。aggregate 不存在時自動退回原本逐日 fetch 路徑,行為無回歸。
5 changes: 5 additions & 0 deletions .changeset/ratewise-multi-rate-label.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/ratewise': patch
---

修正多幣別頁面在換錢所反向匯率、現金不可用與小字低對比情境下的匯率來源標籤。
5 changes: 5 additions & 0 deletions .changeset/ratewise-opendata-mobile-overflow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/ratewise': patch
---

修正開放資料頁 API 端點路徑在小螢幕(320px 等窄視口)造成的水平溢出,長路徑現在會正確斷行。
5 changes: 5 additions & 0 deletions .changeset/ratewise-production-surface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/ratewise': patch
---

正式版不再輸出內部展示與測試頁面的預渲染路由
5 changes: 5 additions & 0 deletions .changeset/ratewise-pwa-shell-brand-sync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/ratewise': patch
---

PWA shell 品牌色同步至 Violet 主題,CDN 備援日誌降噪至 debug 等級
5 changes: 5 additions & 0 deletions .changeset/ratewise-radius-ssot-guard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/ratewise': patch
---

視覺一致性收斂:匯率卡趨勢圖底部圓角對齊卡片外框、骨架載入與小型元素圓角統一為設計系統規格,並新增圓角防回歸守門測試。
5 changes: 5 additions & 0 deletions .changeset/ratewise-sentry-fetch-filter-ssot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/ratewise': patch
---

Sentry `beforeSend` 改用 `classifyUnhandledRejection` 判斷 fetch 失敗,與 errorClassification SSOT 收斂;Firefox / Safari 的網路失敗訊息不再重複送到 Sentry。
5 changes: 5 additions & 0 deletions .changeset/ratewise-ssg-snapshot-source.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/ratewise': patch
---

修正金額頁 SSG 預渲染匯率來源,clean checkout 不再依賴 ignored 的 public/rates.json。
5 changes: 5 additions & 0 deletions .changeset/ratewise-sw-bounded-nav.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/ratewise': patch
---

離線/弱網下載入更穩定,不再可能卡白屏。
5 changes: 5 additions & 0 deletions .changeset/split-meow-expense-currency-snapshot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@app/split-meow': patch
---

每筆支出記錄記帳當下的幣別與匯率,切換顯示幣別後歷史金額不再被錯誤換算;以韓元(₩)記帳的支出會同時顯示對應的台幣參考金額。
53 changes: 53 additions & 0 deletions .github/workflows/ratewise-production-governance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: RateWise Production Governance

on:
workflow_dispatch:
schedule:
- cron: '17 20 * * *'

permissions:
contents: read

jobs:
production-governance:
name: Live Headers And Performance Gates
runs-on: ubuntu-latest
timeout-minutes: 20
env:
PLAYWRIGHT_BASE_URL: https://app.haotool.org
E2E_BASE_PATH: /ratewise

steps:
- name: Checkout code
uses: actions/checkout@v6

- name: Setup pnpm
uses: pnpm/action-setup@v6
with:
version: 9.10.0

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Install Playwright Chromium
run: pnpm --filter @app/ratewise exec playwright install --with-deps chromium

- name: Verify Cloudflare headers and cache policy
run: |
RUN_PRODUCTION_TESTS=true \
pnpm --filter @app/ratewise exec playwright test \
tests/e2e/cloudflare-cache.spec.ts \
--project=chromium-desktop

- name: Verify trend chart latency budget
run: |
RUN_RATEWISE_PERFORMANCE_TESTS=true \
pnpm --filter @app/ratewise exec playwright test \
tests/e2e/trend-chart-latency.spec.ts \
--project=chromium-desktop
82 changes: 73 additions & 9 deletions .github/workflows/update-moneybox-rates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ jobs:
MONEYBOX_FETCH_OUTPUT_FILE: .moneybox-current-fetch.json
MONEYBOX_LATEST_FILE: public/rates/providers/moneybox/latest.json
MONEYBOX_HISTORY_DIR: public/rates/providers/moneybox/history
MONEYBOX_AGGREGATE_FILE: public/rates/providers/moneybox/history-30d.json
MONEYBOX_RETIRED_LATEST_FILE: public/rates/moneybox.json
MONEYBOX_RETIRED_HISTORY_DIR: public/rates/moneybox-history
MONEYBOX_CDN_DATA_BASE: https://cdn.jsdelivr.net/gh/haotool/app@data
Expand Down Expand Up @@ -93,13 +94,26 @@ jobs:
id: save-history
if: steps.fetch-rates.outcome == 'success'
run: |
CURRENT_DATE=$(TZ=Asia/Taipei date +%Y-%m-%d)
MONEYBOX_HISTORY_FILE="${MONEYBOX_HISTORY_DIR}/${CURRENT_DATE}.json"
mkdir -p "$MONEYBOX_HISTORY_DIR"
if [[ ! -f "$MONEYBOX_FETCH_OUTPUT_FILE" ]]; then
echo "❌ Current MoneyBox fetch snapshot missing: ${MONEYBOX_FETCH_OUTPUT_FILE}"
exit 1
fi
# history 檔名 SSOT:直接採用資料本身宣告的首爾掛牌日(updateTime),
# 與 fetch script 的 date-rollover 判斷 (extractSeoulSnapshotDate) 同源,
# 避免 runner wall-clock 與資料日期在跨日視窗不一致。提取失敗時 fallback 首爾 wall-clock。
CURRENT_DATE=$(node --input-type=module -e "
import { readFileSync } from 'node:fs';
const { extractSeoulSnapshotDate } = await import('./scripts/fetch-moneybox-rates.js');
const data = JSON.parse(readFileSync(process.env.MONEYBOX_FETCH_OUTPUT_FILE, 'utf8'));
const d = extractSeoulSnapshotDate(data);
if (d) process.stdout.write(d);
")
if [[ ! "$CURRENT_DATE" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
CURRENT_DATE=$(TZ=Asia/Seoul date +%Y-%m-%d)
echo "⚠️ 無法從資料提取首爾 snapshot date,fallback 至首爾 wall-clock: ${CURRENT_DATE}"
fi
MONEYBOX_HISTORY_FILE="${MONEYBOX_HISTORY_DIR}/${CURRENT_DATE}.json"
mkdir -p "$MONEYBOX_HISTORY_DIR"
if [[ ! -f "$MONEYBOX_HISTORY_FILE" ]] || [[ -n "$(git status --short --untracked-files=all -- "$MONEYBOX_LATEST_FILE")" ]]; then
cp "$MONEYBOX_FETCH_OUTPUT_FILE" "$MONEYBOX_HISTORY_FILE"
echo "✅ MoneyBox history snapshot refreshed: ${MONEYBOX_HISTORY_FILE}"
Expand Down Expand Up @@ -134,21 +148,70 @@ jobs:
if [[ -n "$(git status --short --untracked-files=all -- "$MONEYBOX_HISTORY_DIR/")" ]]; then
echo "changed=true" >> $GITHUB_OUTPUT
fi
if [[ ! -f "$MONEYBOX_AGGREGATE_FILE" ]]; then
echo "changed=true" >> $GITHUB_OUTPUT
fi
if [[ "$RETIRED_ALIASES_CHANGED" == "true" ]]; then
echo "changed=true" >> $GITHUB_OUTPUT
fi

- name: Generate moneybox aggregate 30-day history
if: steps.fetch-rates.outcome == 'success' && steps.git-check.outputs.changed == 'true'
run: |
node --input-type=module << 'SCRIPT'
import { readdirSync, readFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';

const HISTORY_DIR = process.env.MONEYBOX_HISTORY_DIR;
const OUTPUT_PATH = process.env.MONEYBOX_AGGREGATE_FILE;
const MAX_DAYS = 30;

if (!existsSync(HISTORY_DIR)) {
console.error(`❌ History dir not found: ${HISTORY_DIR}`);
process.exit(1);
}

const files = readdirSync(HISTORY_DIR)
.filter((f) => /^\d{4}-\d{2}-\d{2}\.json$/.test(f))
.sort()
.reverse()
.slice(0, MAX_DAYS);

const snapshots = [];
for (const file of files) {
const date = file.replace(/\.json$/, '');
try {
const raw = JSON.parse(readFileSync(join(HISTORY_DIR, file), 'utf-8'));
snapshots.push({ date, raw });
} catch (e) {
console.warn(`⚠️ Skip ${file}: ${e.message}`);
}
}

const aggregate = {
providerId: 'moneybox',
generatedAt: new Date().toISOString(),
snapshots,
};

const outDir = dirname(OUTPUT_PATH);
if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
writeFileSync(OUTPUT_PATH, JSON.stringify(aggregate, null, 2) + '\n');

console.log(`✅ moneybox aggregate: ${snapshots.length} days → ${OUTPUT_PATH}`);
SCRIPT

- name: Commit and push changes
if: steps.fetch-rates.outcome == 'success' && steps.git-check.outputs.changed == 'true'
env:
TODAY_HISTORY_FILE: ${{ steps.save-history.outputs.history_file }}
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
# 只 add 當日 history ,避免 git checkout origin/main -- public/rates/
# data branch 既有的前幾日 history snapshot 一併 stage 成刪除(會清掉公開
# MoneyBox history API 與趨勢圖前日資料)。SSOT:每次只動當日 snapshot。
git add "$MONEYBOX_LATEST_FILE" "$TODAY_HISTORY_FILE"
# 只 add 當日 history 檔與 aggregate,避免 git checkout origin/main -- public/rates/
# 後把 data branch 既有的前幾日 history snapshot 一併 stage 成刪除(會清掉公開
# MoneyBox history API 與趨勢圖前日資料)。SSOT:每次只動當日 snapshot + aggregate
git add "$MONEYBOX_LATEST_FILE" "$TODAY_HISTORY_FILE" "$MONEYBOX_AGGREGATE_FILE"

# 取得更新資訊
UPDATE_TIME=$(node -p "JSON.parse(require('fs').readFileSync(process.env.MONEYBOX_LATEST_FILE)).updateTime")
Expand Down Expand Up @@ -205,8 +268,9 @@ jobs:
run: |
PURGE_URL="${MONEYBOX_PURGE_DATA_BASE}/${MONEYBOX_LATEST_FILE}"
HISTORY_PURGE_URL="${MONEYBOX_PURGE_DATA_BASE}/${MONEYBOX_HISTORY_DIR}/${CURRENT_DATE}.json"
AGGREGATE_PURGE_URL="${MONEYBOX_PURGE_DATA_BASE}/${MONEYBOX_AGGREGATE_FILE}"
echo "🧹 Purging jsDelivr CDN cache..."
for URL in "$PURGE_URL" "$HISTORY_PURGE_URL"; do
for URL in "$PURGE_URL" "$HISTORY_PURGE_URL" "$AGGREGATE_PURGE_URL"; do
for i in 1 2 3; do
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$URL")
if [ "$HTTP_STATUS" = "200" ]; then
Expand All @@ -228,7 +292,7 @@ jobs:
git rebase --abort 2>/dev/null || true
for i in 1 2 3; do
echo "🔄 Refreshing MoneyBox provider files from remote data branch (try $i/3)..."
if git fetch origin data && git checkout origin/data -- "$MONEYBOX_LATEST_FILE" "$MONEYBOX_HISTORY_DIR"; then
if git fetch origin data && git checkout origin/data -- "$MONEYBOX_LATEST_FILE" "$MONEYBOX_HISTORY_DIR" "$MONEYBOX_AGGREGATE_FILE"; then
echo "✅ Remote refresh successful"
exit 0
fi
Expand Down
Loading