2026年5月29日 星期五

一段用 Claude Code 對話式開發的記帳 App 建置紀錄。

緣起

我家人多年來都在用一個 Android 記帳 App「帳務小管家 ZERO v4.7」記帳。資料完整、分類細緻、運作穩定,但有兩個痛點:

  1. 版本停止更新:手機壞了或換手機,只能綁在 Android 手機上
  2. 無法多人共用:家庭開支想一起記就尷尬

15,000+ 筆累積的交易、十幾種帳戶、四十多個專案分類 — 捨不得換、也不想重來。所以決定:自己做一個能完整繼承原資料、加上雲端同步的版本。

App 取名 Song 帳本

設計取捨

幾個關鍵決定:

1. PWA(漸進式網頁應用)而不是原生 App

  • 一個網址就能開,不用上架商店、不用簽名
  • iOS / Android / 桌機平板通吃
  • 「加入主畫面」就有 App 般的全螢幕體驗

2. Offline-first(離線優先)

未登入時 = 純本機記帳,跟以前一樣;登入後 = 雲端同步「疊加」上去,可選哪本帳要同步。這樣家人不熟雲端也照樣能用,熟的可以多裝置同步、跟家人共用一本帳。

3. 單一 HTML 檔部署

最終打包成一個 1.1 MB 的 index.html — 內嵌 SQLite WASM 引擎、所有邏輯、CSS、字型。Cloudflare Pages 拖一下就更新,沒有 build 鏈、沒有 npm 依賴。

4. 後端用 Supabase 免費方案

  • Postgres + Auth + Realtime + Row Level Security 一站搞定
  • 免費額度 500 MB / 月,目前用不到 2%
  • 配 Gmail 自訂 SMTP 寄登入碼

完成的功能

類別 功能
記帳支出 / 收入 / 轉帳、兩層分類(大類 / 子項)、專案歸戶、計算機輸入
多帳本每本帳獨立儲存、互不干擾
多幣別外幣帳戶用原幣記、報表自動換算成台幣
雲端同步Email OTP 登入、Realtime 即時推播、Last-Write-Wins 衝突處理
離線佇列沒網路時照記、連上網自動補上傳
帳本分享擁有者 / 可編輯 / 唯讀三種權限
資料匯入可直接吃原 App 的 .db 檔案
報表月報、年報、趨勢、專案、行為分析(含 MoM / YoY)
雙版面手機單欄、桌機/平板自動切儀表板(左欄首頁常駐、右欄切換)

用 Claude Code 對話式開發

整個專案我幾乎沒有手寫一行程式碼 — 全程透過自然語言對話、由 Claude Code 動手實作。流程大致是:

  1. 我描述問題或丟一張截圖
  2. Claude 定位到對應程式碼、解釋現況、提方案
  3. 我確認方向 / 微調 / 反問
  4. Claude 動手改、跑語法檢查、必要時用瀏覽器 preview 看效果
  5. 我看一下截圖、決定要不要 commit
  6. Claude commit,留下精準的歷史

幾個讓我印象深刻的時刻:

  • 我發現「現金-美金餘額是負的」,貼了原 App 截圖。Claude 看了 .db 結構,發現匯入器把原幣金額跟歷史台幣換算混淆了,重寫成「存原幣 + 當筆匯率,報表用兩者相乘」,最後把基準數對到原 App 截圖到位數(美金 762.76 × 30 = 22,882.80)。
  • 月報「淨退」大類(退款 > 支出)的長條跑出畫面外,數字顯示「-0%」。Claude 找到根因(百分比沒鉗位),順手把其他三條長條也加上防禦性 Math.max(0,…)
  • 凌晨 0–8 點「複製交易到今天」會變成前一天的日期,因為 toISOString() 回的是 UTC 而台灣是 UTC+8。Claude 找到 todayStr() helper,改用本地時區,順便把另一處一樣 bug 的 wsStr 一起修。
  • 我抱怨「手機 vs 桌機螢幕差異很大、桌機兩側空空的」,Claude 幫我做了一個寬螢幕儀表板:CSS Grid 切兩欄、左欄首頁常駐、右欄跟著 tab 切換。實作過程中還抓到一個 bug — boot 時的 inline style display:block 蓋過了 media query 的 display:grid,整個 grid 沒生效。
  • 我問「原 App 的專案有 ID 和說明文字,你的匯入器抓了嗎?」Claude 去 query 了 mymoney.dbPROJECT_SET 表,發現確實有 PROJECT_NOTE 欄位被忽略了,補上之後還做了一個「從原 .db 單獨匯入專案說明」按鈕 — 不洗交易資料就能補上。

整個過程約 30+ commits、跨 4 天集中開發。 我幾乎不用碰程式碼,但每一個技術決策都掌握在自己手上。

一些有趣的細節

回沖(負數金額的支出)

原 App 允許「-300 元支出」當作沖回(例:同事退款)。匯入時數值處理正確、但顯示和正常支出沒區別 — 我以為被當成普通支出了。Claude 確認資料層面正確,視覺上改成:

  • 一般支出:-$300 紅色
  • 回沖:+$300 綠色(同一筆支出、用相反符號和顏色標示)

月支出合計自動扣回。

報表 MoM/YoY 邊界

當上月為負數(淨退)時,(curr - prev) / prev × 100 的公式會因為除以負數而符號翻轉,顯示「下降 3728%」之類的鬼話。改成:

  • prev ≤ 0 → 顯示「—」(基期無意義)
  • curr < 0 → 顯示「淨退」(當期是退款月)
  • 其他 → 正常 ▲/▼ 百分比

災難復原?

不需要設定自動備份。多裝置同步本身就是分散式備份 — 每台裝置的本地 IndexedDB 都是一份完整副本,Supabase 真的壞掉,任何一台同步中的裝置「啟用同步」就會 re-push 回去。

加上偶爾手動匯出 JSON 存到 OneDrive,就是雙保險。

結果

  • 主網址:https://song-ledger.song.tw/(自訂網域)
  • 部署:Cloudflare Pages(免費額度日 10 萬請求,用很少)
  • 後端:Supabase 免費方案(用不到 2%)
  • 家人在用、我自己也在用、跨裝置即時同步

順帶生了兩本手冊

  • 家庭使用手冊(PDF,9 頁,附截圖)
  • 管理者維運手冊(PDF + Markdown,記錄部署流程、雲端服務位置、疑難排解)

反思

  1. Offline-first 是正確選擇。家人不一定每天有網路、不一定想登入;不強迫雲端讓 App 對所有人都好用。
  2. 少即是多。沒有用任何 framework — React / Vue / Tailwind 一個都沒有,純手寫 vanilla JS / CSS。1.1 MB 一檔搞定,啟動快、改起來也快。
  3. AI 不會取代你的判斷。所有關鍵決定(要不要做 keep-alive、UI 走向、哪些 bug 先修、要不要寫自動備份)都是我做的;Claude 是執行 + 細節驗證的合作者。決策權永遠在你手上,這也是這套工作流最舒服的地方。
  4. 資料才是核心資產。花最多力氣的不是 UI、不是同步,是「正確匯入 15,000 筆歷史資料」這件事 — 對到位數、處理多幣別、回沖、專案說明,每一項都要驗證。

如果要再做一次,我會更早開始用 Claude Code 對話式開發 — 一開始我自己手寫了一些 code、改了又改、效率反而不高。


Stack 速查

技術
前端vanilla JS / CSS / HTML,單檔 1.1 MB
內嵌sql.js(SQLite WASM)讀原 App .db
後端Supabase(Postgres + Auth + Realtime + RLS)
部署Cloudflare Pages(自訂網域 song.tw)
同步Supabase Realtime postgres_changes + 離線 outbox
開發Claude Code 對話式
文件reportlab 產 PDF、Microsoft JhengHei 中文字型內嵌

寫於 2026 年 5 月底