19 Commits
Author SHA1 Message Date
JianMiauandClaude Fable 5 eeba11e643 修正解鎖螢幕後不能拖曳、不能右鍵:互動不再依賴 mousedown
根本原因:
2.0.13 的事件紀錄證實:鎖定螢幕再解鎖後,renderer 只收得到 mouseup 與
mousemove,左右鍵的 mousedown 全部被吃掉,直到重啟才恢復(符合 Windows 對
永不啟用視窗的 WM_MOUSEACTIVATE 回傳 MA_NOACTIVATEANDEAT 的行為,只丟掉按下
那一則訊息)。而左鍵靠 mousedown 設 pressed、右鍵靠 mousedown 開選單,兩者
因此同時失效。

影響:
每次鎖定/解鎖後寵物就不能拖曳、不能右鍵,只能重啟。

修法:
右鍵選單改在 mouseup 開(也是 Windows 慣例);mousemove 的 buttons 位元帶著
「左鍵按著」而沒有 pressed 時補一個 synthetic pressed,拖曳照常;沒有
mousedown 的左鍵 mouseup 當成單擊。補上對應單元測試與 README 說明。
版號 2.0.14。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 15:24:12 +08:00
JianMiauandClaude Fable 5 35865b9e17 右鍵選單新增「重啟」
根本原因:
使用者要求選單能直接重啟寵物,不必手動結束再開。

影響:
更新或狀態異常時需要到系統匣結束再找 exe 重開。

修法:
選單加「重啟」。安裝版/開發模式用 app.relaunch();portable 版不能這樣:
relaunch 會重跑解壓到 %TEMP% 的那顆 exe,而 portable 外殼在程式結束時會刪掉
整個解壓目錄、舊程序還在跑時重新解壓也會被檔案鎖住,所以改成延遲兩秒後
重新執行原本的 -portable.exe(PORTABLE_EXECUTABLE_FILE)再結束自己。另加
本機專用的 POST /restart 端點供自動化驗證。版號 2.0.13。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 15:04:41 +08:00
JianMiauandClaude Fable 5 30d3e481e4 加入滑鼠事件紀錄與鎖定/解鎖處理,追查解鎖後點不到寵物
根本原因:
使用者回報登出/鎖定螢幕再回來後就不能拖曳、不能右鍵。現場量測顯示 OS
這一層正常(點穿位元會切、WindowFromPoint 打到寵物視窗),但事件紀錄裡
完全沒有拖曳或選單的痕跡,代表問題在 renderer 有沒有收到滑鼠事件這一層,
而這一層目前沒有任何可觀測資料,無法判斷。

影響:
解鎖後偶發點不到寵物,且發生時查不出卡在哪裡。

修法:
1. renderer 把 mousedown/mouseup(含按鍵與座標、當時的 pressed/drag)與
   hover 進出寫進事件紀錄;pet:state 帶 rendererMouse(pressed/drag/hovered
   /overlay),/state 可直接查。
2. 主程序監聽 powerMonitor 的 lock-screen/unlock-screen/suspend/resume:
   記錄到事件紀錄與 /state.mouse.sessionEvents,解鎖或喚醒後重新套用目前的
   點穿狀態、showInactive 並重宣告置頂。
版號 2.0.12。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 14:56:59 +08:00
JianMiauandClaude Fable 5 bf059b19cf 點穿改由主程序決定並加入滑鼠診斷:修不能拖曳、不能右鍵
根本原因:
點穿狀態一直是 renderer 判定後再用 IPC 叫主程序切換。renderer 只要例外、
卡住或狀態錯亂(例如視窗永遠不取得焦點、Windows 對背景視窗的 capture 有限制,
放開的那一下落在視窗外就收不到 mouseup,pressed 從此卡在 true),主程序就
再也收不到切換指令,寵物永遠點不到。另外 2.0.8 起每 2 秒的置頂重宣告會在
右鍵選單開著時把寵物視窗抬到選單上面,2.0.10 改成整個方框接滑鼠後,被蓋住
的選單項目就點不到了。

影響:
偶發「不能拖曳、不能右鍵」,而且發生時沒有任何可查的狀態或紀錄。

修法:
1. 主程序每 50 ms 自己輪詢游標,對照 renderer 回報的人物方框與氣泡矩形
   (pet:hit-rects)直接呼叫 setIgnoreMouseEvents;決策邏輯抽成 lib/hit-test.js。
   renderer 沒回報或掛掉時用版面公式算方框當備援,並自動重載。
2. 拖曳不管因 mouseup、逾時(20 秒)或其他原因結束,都送 pet:drag-ended
   讓 renderer 清掉按住/拖曳狀態。
3. 右鍵選單開著時暫停置頂重宣告。
4. /state 回傳 bounds 與 mouse 診斷(是否點穿、游標相對座標、是否在方框/
   氣泡、矩形來源、切換與輪詢次數、renderer 錯誤、拖曳結束原因);renderer
   例外、拖曳開始/結束、選單開關寫入事件紀錄。
版號 2.0.11。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 14:15:29 +08:00
JianMiauandClaude Fable 5 a9497cc123 修正又被穿透、不能拖曳也不能右鍵:點穿改用人物方框判定
根本原因:
點穿是逐像素讀 canvas 該點的 alpha 來決定,而 2.0.7 起每一幀都會重驗。
人物的手腳與邊緣會隨動畫在透明/不透明之間切換(實測小念 hover 時
idle+jumping 共 11 幀,只有 44% 的人物像素每一幀都不透明;running 也只有
56%),游標停在邊緣時點穿位元就跟著動畫高速切換,按下去那一瞬間剛好是
透明幀,滑鼠事件就穿到底下的視窗去了。hover 會觸發跳三下,正好是使用者
伸手要抓寵物的時候,最容易中。

影響:
游標明明在寵物身上,左鍵拖不動、右鍵沒有選單,點擊落到底下的視窗。

修法:
點穿與 hover 一樣改用人物方框(canvas 矩形)或氣泡矩形判定,狀態只取決於
游標位置,不再與動畫幀連動;移除 overSprite 與 tick 內的每幀重驗。這也是
Codex 的做法。README 對應段落與對照表同步更新。版號 2.0.10。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 10:06:32 +08:00
JianMiauandClaude Fable 5 4e4563e861 完成時氣泡改顯示最後一句回覆,不再延到下一輪才出現
根本原因:
Stop 事件時 main.js 已從 transcript 附上最後一句助理訊息(narration),
但 renderer 的 Stop 分支寫死顯示「完成!」沒使用它,也沒把 narrationId
標記為已顯示;下一輪第一個工具事件呼叫 sayActivity 時,transcript 裡
最新的助理訊息仍是上一輪結尾、id 又沒被消費過,就被當成新話顯示出來。

影響:
對話收尾只看得到「完成!」,看不到 AI 的最後一句回覆;等使用者送出
下一句指令後,寵物反而冒出上一輪的舊訊息,時序錯亂。

修法:
Stop 時若帶有 narration,氣泡直接顯示「 最後一句」並消費 narrationId,
沒有才退回「完成!」;UserPromptSubmit 也先把當下 narrationId 標記為已
顯示,雙重保險。另修 release.js 只挑檔名含當前版本的產物,避免 dist/
殘留的舊版檔案混進新 release。版號 2.0.9。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 16:40:32 +08:00
JianMiauandClaude Fable 5 7060571df1 修正被 VS Code 蓋住:定期重新宣告置頂
摘要:
每 2 秒重新宣告一次置頂,並在螢幕配置變動與啟動後各補一次。

根本原因:
Windows 會把視窗踢出 topmost band,卻留著 WS_EX_TOPMOST 樣式位元。
以 Win32 檢查實際情況:寵物視窗的 exStyle 是 0x08280028(含 WS_EX_TOPMOST),
但 z-order 排在 Z=4,而兩個 TOPMOST=false 的 VS Code 視窗排在 Z=1 與 Z=2,
也就是它們蓋在寵物上面。Electron 這邊仍以為自己是置頂的,
setAlwaysOnTop 已經在建立視窗時與勾選選單時都設過,所以不會自己修好。

影響:
寵物被 VS Code 之類的一般視窗蓋住,只能重啟或重新勾一次選單的「永遠置頂」。

修法:
- 新增 reassertTopmost():setAlwaysOnTop(true, "screen-saver") 之後再呼叫 moveTop()。
  單靠 setAlwaysOnTop 不夠——狀態沒變時它可能不會真的呼叫 SetWindowPos;
  moveTop() 會強制 SetWindowPos,對 WS_EX_TOPMOST 的視窗即為插回 topmost band 最上面。
  視窗是 WS_EX_NOACTIVATE + focusable:false(已用 Win32 確認),不會搶焦點。
- 每 TOPMOST_REASSERT_MS(2000)呼叫一次;display-metrics-changed / added / removed
  也各接一次,因為螢幕配置變動最容易觸發降級。
- ready-to-show 的 showInactive 後補一次;選單勾選置頂時也補一次。
- 拖曳中(dragTimer 存在)跳過,避免和每 16 ms 的位置更新打架。
- 結束時清掉 timer。

驗證:
- 先從外部對執行中的視窗呼叫 SetWindowPos(HWND_TOPMOST) 確認這正是有效解:
  寵物由 Z=4 移到 Z=0,兩個 VS Code 視窗退到 Z=5、Z=6。
- 再以改好的版本實測守護行為,用 SetWindowPos(HWND_BOTTOM) 模擬 Windows 的降級:
  基準 petZ=0 TOPMOST=true 上方 0 個;強制降級後 petZ=17 TOPMOST=false 上方 4 個
  (VS Code 兩個、CM 等);1 秒後回到 petZ=0 TOPMOST=true 上方 0 個;3 秒後維持。
- 以 ELECTRON_RUN_AS_NODE 讀打包後 app.asar,確認版本 2.0.8 與八項改動都在出貨檔案裡。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 14:36:36 +08:00
JianMiauandClaude Fable 5 cd4b618be0 補上遺漏的氣泡標題列
摘要:
整理記憶中、需要你授權、工具失敗、錯誤訊息、一般通知、掰掰這六個氣泡補上第一行標題。

根本原因:
上一版把氣泡改成兩行時,是以「原本帶(專案名)尾綴」為線索去改呼叫點,
但這六個訊息本來就沒有尾綴,所以整批漏掉,顯示出來只有單行、沒有專案或對話名稱。

影響:
壓縮記憶、等授權、出錯這些最需要知道是哪個 session 的時候,反而看不到來源。

修法:
- handleEvent 內六個 say() 補上 title 參數。
- 選單「動作測試」的氣泡給標題「動作測試」,才分得出是測試不是真的事件。
- first-awake 的「嗨,我是〈寵物名〉」維持沒有標題:它不屬於任何 session,
  訊息本身已含寵物名,CSS 的 :empty 會把空標題整行收起來,不會留空白。

驗證:
以 DOM stub 載入完整 renderer.js 驅動 handleEvent,11 項全過:
PreCompact 的標題與訊息、其餘五個補上的事件都帶標題、原本就有的「完成」仍正常、
對話名稱優先於專案名、無 cwd 也無對話名時標題為空字串、滑鼠停留提示為兩行。
另以靜態掃描確認 handleEvent 內全部 13 個 say() 都帶 title,
並在打包後的 app.asar 上重跑同一項掃描。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 14:06:50 +08:00
JianMiauandClaude Fable 5 42083acec5 修正有時候卡住無法拖曳:點穿狀態改由主程序輪詢驅動
摘要:
點穿的命中判定不再只依賴 renderer 的 mousemove,改由主程序每 50 ms 的游標輪詢一起驅動,
並在每格畫完後用最後已知座標重驗一次。

根本原因:
視窗預設 setIgnoreMouseEvents(true, { forward: true }),而唯一會把它切回可互動的地方
是 renderer 的 mousemove。問題是視窗處於點穿狀態時,Electron 的 forward 不保證會把
mousemove 轉發進來——本次開發過程中已實測到:游標直接跳到寵物上收不到事件,
慢慢掃過去才收得到。
於是只要在「游標還停在寵物身上」時進入點穿(例如游標停在某一點,動畫換格後
該點的像素變成透明),就再也收不到 mousemove,ignoringMouse 永遠是 true,
滑鼠按下也傳不進視窗,就變成按不動也拖不動,而且不會自己好。
另外 startCursorPolling 的內容原本被 `!config.followCursor` 整段擋掉,
而追視預設為關,所以主程序根本沒有在追游標,沒有任何備援路徑。

影響:
寵物有時候完全無法拖曳或點擊,只能重啟;游標停在人物邊緣或半透明處時特別容易發生。

修法:
- startCursorPolling 的守衛從 `!win || !config.followCursor || dragTimer` 改為
  `!win || dragTimer`,輪詢一律執行;只有送 pet:cursor(追視用)那一行仍看 followCursor。
- 主程序新增 pet:cursor-pos,送出視窗相對座標;preload 開放該頻道。
- renderer 把命中判定集中成 refreshHitTest(),mousemove 與 pet:cursor-pos 走同一條路,
  並在 pressed / drag 期間不動點穿狀態,避免拖曳中途鬆手。
- tick 畫完後若最後已知座標落在人物方框內就重驗一次,處理「游標沒動但換格導致
  該點像素改變」的情況。
- mouseout 時清掉 lastPoint。
- README 的設計重點補上為何不能只靠 mousemove。

驗證:
- 實機加暫時診斷確認 pet:cursor-pos 確實送達 renderer,且回報的 canvasRect
  (57,72,183,209)與依視窗尺寸手算的結果一致;確認後移除診斷碼。
- 以 DOM stub 載入完整 renderer.js 並讓 getImageData 的 alpha 可切換,14 項全過:
  壓在人物上會解除點穿並觸發 hover 跳躍、有通知主程序、方框內但透明維持點穿、
  方框外點穿且非 hover、拖曳中與按住中都不改點穿、pet:cursor-pos 有註冊且
  直接跳到人物上也能解除點穿、以及最關鍵的一組——先進入點穿狀態,
  再由 pet:cursor-pos 在同一點換格後把它救回來,這正是原本卡死的情境。
- 以 ELECTRON_RUN_AS_NODE 讀打包後 app.asar,確認版本 2.0.6 與九項改動都在出貨檔案裡。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 13:51:28 +08:00
JianMiauandClaude Fable 5 93f635dd01 氣泡改成兩行,第一行顯示對話名稱或專案名
摘要:
氣泡上方加一行標題(/rename 設的對話名稱,沒設就用專案資料夾名),訊息移到第二行。

根本原因:
原本把專案名以「(專案名)」的形式接在訊息尾巴,訊息本身就短,
加了括號後更容易被氣泡的單行省略號截掉,而且同時開多個 session 時
要看到最後才知道是哪一個在叫你。

影響:
多 session 並行時不容易分辨氣泡是哪個專案發出的;訊息也被括號擠掉。

修法:
- index.html 氣泡內加 #bubble-title;style.css 把單行限制從 #bubble 移到
  #bubble-title 與 #bubble-text 各自身上,兩行各自省略,
  並以 :empty 讓沒有標題時整行收起來,不留空白。
- say() 增加第四個參數 title,事件處理處的八個呼叫點改傳標題、移除 suffix。
- main.js 的 latestNarration 改名為 readTranscript,同一次反向掃描同時取出
  助理說明與 custom-title(/rename 會把它寫進 transcript),兩者都拿到就提早結束;
  收到事件時把 sessionTitle 一併併進 payload。
- 標題優先序:對話名稱 > 專案資料夾名 > 不顯示。

驗證:
- 單元測試以真實 transcript 執行 readTranscript:取到對話名稱 "claude-pet"、
  同時取到助理說明、快取一致、路徑不存在或為 null 時回 null,6 項全過。
- 實機截圖確認兩行版面正確。截圖當下剛好收到另一個 session 的真實事件,
  標題顯示 "telegram-codex-bot"、第二行是該 session 的助理說明,
  等於用實際流量驗證了多 session 下的標題辨識。
- 以 ELECTRON_RUN_AS_NODE 讀打包後 app.asar,確認版本 2.0.5 與十項改動都在出貨檔案裡,
  包含舊的 suffix 已完全移除。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 12:09:01 +08:00
JianMiauandClaude Fable 5 95c9b97eb7 工作中氣泡顯示工具動作與助理說明
摘要:
running 狀態的氣泡改成有東西就播報:優先顯示助理剛說那句話,否則顯示正在用的工具與對象。

根本原因:
使用者希望氣泡能像 telegram bot 那樣顯示 AI 的思考過程。
實際查證後思考文字拿不到——Claude Code 從 2.1.238 起不再把 thinking 寫進 transcript,
只留加密簽章(本 session 169 個 thinking 區塊文字全為空字串);
比對同樣用 claude-opus-5 但版本為 2.1.237 的舊 session 則存得到,
確認是 Claude Code 版本差異而非模型差異。Claude Code 也沒有對外事件串流,
所以 hook 拿不到思考內容,這點與 Codex CLI 的 --json reasoning 事件不同。
而參考的 cluemarket-tg-bot 其實也沒顯示思考:它的變數雖名為 lastThinkingText,
抓的卻是 text 區塊,另外兩個是寫死的「思考中」字串與工具標籤。
原本 running 狀態除了關閉舊氣泡外沒有任何資訊。

影響:
工作期間看不出 Claude Code 正在做什麼,只知道她在忙。

修法:
- hook 補送 toolTarget(依 command / file_path / pattern / url 等順序取一個欄位並截斷,
  避免 Write 這類工具把整份檔案內容塞進 payload)、agentType 與 transcriptPath。
- main.js 新增 latestNarration:由 transcript 尾端 192 KB 反向找最後一個非空的
  assistant text 區塊,依檔案大小快取,transcript 長到數 MB 也不會變慢;
  收到事件時把 narration 與其 uuid 併進 payload 再轉給 renderer。
- renderer 新增 sayActivity:有新的助理說明就顯示第一句,否則顯示工具標籤加對象。
  同一段說明只播報一次;waiting 時不播報以免蓋掉固定氣泡。
  另加 stripMarkdown,否則助理文字的 **粗體**、反引號、標題與連結語法會直接出現在氣泡裡。
- README 新增「能顯示什麼、不能顯示什麼」一節說明思考文字為何拿不到。

驗證:
- 單元測試以 DOM stub 載入完整 renderer.js 並抽出 main.js 的 latestNarration,15 項全過:
  對真實 transcript 取得助理說明與 uuid、快取一致、路徑不存在或為 null 時回 null 不拋錯、
  firstSentence 取第一句與忽略空行、Bash 取指令前段、檔案路徑只取檔名、過長截斷、
  新說明更新 id、同段不重複、waiting 時不播報、空 payload 不拋錯。
- markdown 清理另 7 項全過:粗體、反引號、標題、清單、連結、程式碼區塊、無標記純文字。
- 實機啟動並以真實事件驗證:events.log 顯示 hook 確實帶出 toolTarget 與 transcriptPath;
  截圖確認氣泡顯示「⚙️ 執行 npm run dist」,以及傳入真實 transcript 後顯示「💬 助理說明」。
  markdown 殘留問題就是在這一步的截圖中發現並修掉的。
- 以 ELECTRON_RUN_AS_NODE 讀打包後 app.asar 與 app.asar.unpacked,確認版本 2.0.4
  與九項改動都在出貨檔案裡。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 10:16:43 +08:00
JianMiauandClaude Fable 5 f1e7aca797 新增發佈到 Gitea Releases 的腳本
摘要:
scripts/release.js 建立 tag v<版本> 的 release 並上傳 dist 的 .exe / .zip,
附帶 --dry-run 與伺服器設定預檢。

根本原因:
產物有 345 MB 且不進版控,需要一個散布管道。Gitea 有 Releases 與附件 API,
但目前伺服器設定會擋下全部三個檔:
  - [attachment] max_size 為 100 MB,portable 101.7 / setup 102.0 / zip 141.3 都超過
  - allowed_types 白名單只有文件與圖片類,沒有 .exe
另外 .npmrc 裡現有的 token 只有套件庫權限,呼叫 repo API 會回 403。

影響:
沒有腳本就得手動上傳三個上百 MB 的檔;直接上傳也會因上述設定而失敗。

修法:
- 新增 scripts/release.js 與 npm script release:
  owner/repo 由 git remote 推導,網址可用 GITEA_URL 覆寫(SSH 埠與網頁埠不同時)。
  release 說明取自上一個 tag 之後的 commit 標題;沒有舊 tag 就取最近 30 筆。
  release 已存在則重用,同名附件先刪再傳,可重複執行。
- 上傳採用手動組裝的 multipart 並以串流送出,不把上百 MB 的檔案讀進記憶體。
- 發佈前先讀 /api/v1/settings/attachment 做預檢,超過上限或副檔名不允許時
  直接列出是哪個檔案卡在哪一條並中止,不會傳到一半才失敗。
- --dry-run 只做讀取,印出將建立或重用的 release 與變更說明。
- git() 關閉 stderr:releaseNotes 會刻意 describe 一個可能不存在的舊 tag,
  否則 git 的 fatal 訊息會被誤認成發佈失敗。
- README 新增「發佈到 Gitea Releases」一節,寫明所需的 app.ini 設定與 token 權限。

驗證:
- 對真實伺服器執行 --dry-run:正確推導出 AI/claude-pet 與 tag v2.0.3,
  並列出三個檔各自違反的限制後中止,全程只有 GET。
- 另寫一個假的 Gitea(本機 http)跑完整流程:建立 release 的 tag、名稱、
  target_commitish 與說明皆正確;三個附件的 Content-Length 與實收位元組相符、
  multipart 邊界與 Content-Disposition 正確、結尾正確,
  且收到的檔案內容 SHA256 與磁碟上的完全一致(共 345 MB)。

未執行:
實際發佈需要伺服器調整設定與一個有 write:repository 權限的 token,兩者都在使用者手上。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 08:30:07 +08:00
JianMiauandClaude Fable 5 d78f99fcc7 游標離開人物時立刻中斷跳躍,不再播完三次
摘要:
hover 跳躍改為「游標一離開就停」,而不是不管游標在哪都把 3 次播完。

根本原因:
把 hover 跳躍實作成一次性 overlay,播放長度固定為 STATE_REPEATS 輪,
setHovered(false) 只更新 hovered 旗標、不動 overlay,所以游標即使馬上移開,
跳躍仍會自顧自播完約 2.5 秒才回到基礎狀態。
這其實也偏離 Codex:Codex 的顯示狀態是 `hovered ? "jumping" : state`,
游標一離開就翻回原狀態並重跑動畫,本來就不會把跳躍播完。

影響:
只是游標經過或很快移開時,寵物還會繼續跳完,反應顯得遲滯、不跟手。

修法:
- play() 之後把該 overlay 標記 fromHover。
- setHovered(false) 時若目前 overlay 是 hover 觸發的就直接清掉,
  下一個 tick 會依 resolveAnim 回到當下的基礎狀態。
  只清 fromHover 的,初次問候揮手等其他一次性動作不受影響。
- README 的日常使用、行為對照表、與 Codex 的對照三處同步更新,
  對照表新增「hover 離開」一列。

保持不變:
進入時最多跳 3 次、游標持續停留不重複觸發(使用者已確認要這個行為)。

驗證:
以 DOM stub 載入完整 renderer.js 執行 13 項測試全過,涵蓋:
進入觸發 jumping 且序列為 15 幀、標記 fromHover、離開後 overlay 立刻為 null、
resolveAnim 立刻回 idle、工作中離開回到 running 而非 idle、
問候揮手不會被離開中斷、既有 overlay 期間不觸發跳躍、
持續停留不重複觸發、拖曳中不觸發。
另以 ELECTRON_RUN_AS_NODE 讀打包後 app.asar 確認版本 2.0.3 與六項改動都在出貨檔案裡,
exe FileVersion 為 2.0.3。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 16:58:57 +08:00
JianMiauandClaude Fable 5 7e2f689f26 修正開啟追視後 renderer 崩潰,追視改為只在待機時生效
摘要:
startAnim 對 look 會丟 TypeError 導致動畫迴圈整個死掉;另把追視改成只在 idle 時覆蓋。

根本原因:
1. 追視是單一靜態擺姿,TIMINGS 裡沒有 look 這個動作。但序列模型改寫後,
   tick 在動畫改變時無條件呼叫 startAnim(anim),傳入 "look" 時會走到
   cycleOf("look") → TIMINGS["look"].map → TypeError。
   例外從 requestAnimationFrame 的 callback 拋出,迴圈就此中止,
   整隻寵物凍結,也不再回應任何 hook 事件——就是使用者回報的「直接沒反應」。
2. 對照 Codex 時把 lookFrame 改成覆蓋所有狀態。但 Codex 的追視來源是 quick chat 的
   文字游標,只有使用者真的在打字時才存在;滑鼠卻是一直都在,照搬會讓工作中的
   running / waiting / review 動畫永遠被靜態擺姿蓋掉,寵物失去反映狀態的作用。

影響:
選單一勾「跟著游標看」,游標一動寵物就完全凍結,且不會恢復。

修法:
- startAnim 開頭對 look 早退,直接設成空的幀序列(靜態擺姿),
  不進入 cycleOf / baseSequence。draw 本來就單獨處理 look,tick 的推幀也已排除 look。
- resolveAnim 先取 baseState(),只有 base 為 idle 時才回傳 look;
  一併避免原本重複呼叫 baseState() 的副作用。
- README 的特色、行為對照表、與 Codex 的對照三處同步更新,
  對照表把「追視」拆成來源與優先序兩列並說明為何刻意不同。

不動的部分:
hover 跳躍維持進入時播 3 次、之後停住不再跳(使用者確認就是要這個行為)。
另已確認跳躍結束後會回到當下的基礎狀態而非一律 idle。

驗證:
- 單元測試(抽出實際程式碼執行):修正前 startAnim("look") 拋
  TypeError: Cannot read properties of undefined (reading 'map'),修正後回傳空序列;
  idle 6 幀、running 24 幀正常;resolveAnim 在追視開啟下
  idle→look、running→running、waiting→waiting、review→review,
  hover 跳躍優先於 look 與 running,跳躍結束回到 running 而非 idle,共 11 項全過。
- 實機開啟 followCursor 啟動:/state 持續回應且 anim 為 look(修正前此處即崩潰),
  log 無任何例外;灌入事件後 running / waiting 正確蓋過追視。
- 以 ELECTRON_RUN_AS_NODE 讀打包後 app.asar:版本 2.0.2、崩潰修正與追視優先序都在,
  hover 跳躍、矩形判定、右鍵選單未受影響;exe FileVersion 為 2.0.2。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 16:48:43 +08:00
JianMiauandClaude Fable 5 3f92e102cc 打包時自動遞增版號,避免同版號重包
摘要:
dist 與 dist:portable 掛上 pre 腳本自動執行 npm version patch,版本 2.0.0 → 2.0.1。

根本原因:
產物檔名帶版號(Claude Pet-<版本>-setup.exe),但版號要靠人記得改。
實際上 2.0.0 被重包了兩次而版號沒動:一次沒有 hover 跳躍、一次有,
使用者回報「滑鼠停上面沒有 jump」時,無法從檔名判斷手上是哪一份,
必須另外比對檔案時間才能確認。

影響:
同一個版號可能對應內容不同的散布檔,回報問題與確認修正都會失準。

修法:
- package.json 新增 predist 與 predist:portable,內容為
  `npm version patch --no-git-tag-version`;npm 會在對應的 dist 腳本前自動執行。
- pack 不掛(它只產 win-unpacked 供開發驗證,不是散布檔)。
- README 的「打包成 exe」補上說明:版號自動遞增、不要繞過、
  要升 minor/major 就先手動 npm version。

驗證:
執行 npm run dist,package.json 由 2.0.0 自動變成 2.0.1,
產物檔名同步為 Claude Pet-2.0.1-*;以 ELECTRON_RUN_AS_NODE 讀打包後的 app.asar
確認內含版本為 2.0.1,exe 的 FileVersion 亦為 2.0.1,
且 hover 跳躍、矩形判定、右鍵選單都還在。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 16:29:15 +08:00
JianMiauandClaude Fable 5 6da4775867 移除 hover 浮出的選單鈕,版本升到 2.0.0
摘要:
拿掉游標靠近時人物縮小讓位、上方浮出圓形選單鈕的整套 hover 版面,選單只留右鍵與系統匣。

根本原因:
上一版照 Codex 的 petControlsAppearance 做了 proximity 版面,但那顆控制鈕的作用就是開選單,
而本程式的右鍵與系統匣已經有同樣的選單,多一顆按鈕沒有增加任何功能,
反而讓游標經過時人物會縮小位移。

影響:
滑鼠靠近時人物無謂地縮小、下移,並蓋出一顆重複功能的按鈕。

修法:
- renderer:移除 HOVER_SCALE / HOVER_OFFSET_Y / PROXIMITY_ENTER / PROXIMITY_EXIT 常數,
  以及 updateProximity / setNear / overControls / distanceToRect 與 near 狀態、
  applyScale 裡的控制鈕定位、menu-btn 的事件繫結;mousemove 的點穿判定回到人物與氣泡兩者。
- index.html 移除 #controls;style.css 移除 body.near 的縮放位移與 #controls 樣式。
- package.json 版本 0.1.0 → 2.0.0。
- README 的日常使用、行為對照表、與 Codex 的對照三處同步更新,
  對照表標明此項未採用及原因。

保留不變:
右鍵與系統匣選單、idle 放慢 6 倍、狀態優先序與到期、look 列縮放補正、
first-awake 問候、拖曳 4 px 方向閾值、reduced motion。

驗證:
- 三個 js 檔語法檢查通過,全專案 grep 確認 controls / menu-btn / HOVER_ / PROXIMITY_ /
  body.near / setNear 等皆無殘留。
- 實機啟動:/health 正常,events.log 顯示 look rows rescaled 8 cells 仍正常觸發;
  把游標移到人物上截圖,人物維持原尺寸原位置、無按鈕浮出(測後游標已復位)。
- 以 ELECTRON_RUN_AS_NODE 讀打包後 app.asar:版本 2.0.0、html 無 menu-btn、
  css 無 body.near、js 無 proximity 邏輯,且右鍵選單、idle 放慢、look 補正、
  狀態優先序都還在;exe 版本資訊為 2.0.0。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 15:18:30 +08:00
JianMiauandClaude Fable 5 e591475d4c 加上免安裝版,寵物來源改成執行目錄的 pets
摘要:
新增 portable 單檔與 zip 資料夾兩種免安裝產物;寵物一律從執行目錄的 pets/ 掃,設定檔不再累積程式自己算出來的路徑。

根本原因:
1. 只有 NSIS 安裝檔,沒有點兩下就能跑的版本。
2. 內附寵物被 asarUnpack 埋在 resources/app.asar.unpacked/pets,使用者要新增寵物得先鑽進 resources 資料夾。
3. loadConfig 會把算出來的來源路徑寫回 config.json,程式一搬家或在安裝版與 portable 之間切換,petSources 就越積越多失效的絕對路徑(實際已累積出 dist/win-unpacked/resources/app.asar.unpacked/pets 這種殘留)。
4. CODEX_PETS_DIR 用到 HOME,但 const HOME 宣告在它下面,會在啟動時就 TDZ ReferenceError。

影響:
沒有免安裝選項;新增寵物的路徑不直覺;設定檔會長出一堆死路徑;且上一版打包後的程式其實一啟動就會崩潰。

修法:
- win target 加上 portable 與 zip;portable 設 unpackDirName=ClaudePet、requestExecutionLevel=user。
- pets/ 改用 extraFiles 放到 exe 旁邊(不再進 asar),APP_ROOT 打包後取 process.execPath 的目錄。
- portable 的 NSIS 外殼每次啟動都重解壓、結束就刪整個目錄,因此:hook 腳本另外複製到 ~/.claude-pet/hook/ 並註冊該穩定路徑(否則寵物沒開時 Claude Code 每次觸發 hook 都找不到檔案);使用者寵物放那顆 exe 旁邊;開機自動啟動改用 PORTABLE_EXECUTABLE_FILE。
- petSources 語意改為「只存使用者自己加的額外路徑」;執行目錄的 pets 與 ~/.codex/pets 為隱含來源,由 petSearchDirs() 組出實際掃描清單,載入時剔除隱含項與不存在的路徑。同 id 以使用者自己放的優先。
- 把 HOME/CONFIG_DIR 移到常數區最前面,修掉 TDZ。
- 內附第二隻寵物 xiao-nian-realistic。

驗證:
以 ELECTRON_RUN_AS_NODE 執行打包後的 exe(不開視窗):
- 一般模式與模擬 portable 模式下的 pets 路徑、hook 路徑、autostart 指令皆正確。
- 用假的 USERPROFILE 實跑 install/uninstall:hook 腳本確實複製、指令含空白路徑會加引號、其他人的 Stop/PreToolUse hook 與 model 設定在安裝與移除後都完整保留、備份有產生。
- 帶殘留路徑的設定檔會被正確清理,掃描順序為「使用者 pets → 內附 pets → ~/.codex/pets → 額外路徑」。
- 從 app.asar 讀回 main.js 確認出貨的程式碼順序正確。
- 以純 node 載入 main.js,確認常數區與 loadConfig 不再有 TDZ。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 14:11:20 +08:00
JianMiauandClaude Fable 5 56fd8448ba 用 electron-builder 打包成 Windows exe
摘要:
加上 electron-builder 設定,`npm run dist` 產出 NSIS 安裝檔與免安裝版,並修正打包後才會出現的路徑問題。

根本原因:
原本三處路徑都假設程式是攤在資料夾裡跑的,打包成 asar 後會壞:
1. hook 腳本路徑會落在 app.asar 內,而 Claude Code 是用 `node <路徑>` 執行它,node 讀不到 asar 裡的檔案,hook 會直接失敗。
2. pets 資料夾同樣在 asar 內,使用者無法自己丟寵物進去,選單「開啟寵物資料夾」也開不起來。
3. 開機自動啟動寫入的是 `electron.exe + 專案路徑`,打包後沒有 node_modules,登錄值會指向不存在的檔案。

影響:
沒有可散佈的執行檔;直接打包的話 hook、換寵物、開機啟動三個功能都會壞掉。

修法:
- 新增 lib/paths.js:unpacked() 把 app.asar 換成 app.asar.unpacked,isPackaged() 以 process.defaultApp 判斷是否為打包版。
- build.asarUnpack 把 hook/ 與 pets/ 解到 asar 外,hooks-installer 與 main.js 的 PETS_DIR 都改走 unpacked()。
- autostart 打包後改用 process.execPath,並跳過開發版才需要的 electron.exe 存在檢查。
- NSIS 設為 per-user、可改安裝路徑、建立桌面與開始選單捷徑,解除安裝不刪 ~/.claude-pet。
- 新增 build/icon.ico(16–256,七種尺寸),取自小念 spritesheet 第 0 列第 6 欄的頭肩方形裁切。
- dist/ 加入 .gitignore;README 補上「打包成 exe」一節與安裝檔的使用方式。

驗證:
以 ELECTRON_RUN_AS_NODE 執行打包後的 exe(不開視窗)載入打包內的 lib,確認 isPackaged 為 true、hook 路徑指到 app.asar.unpacked 且檔案存在、autostart 指到 exe 本身、pets 掃得到 xiao-nian、renderer 讀得到 asar 內的 index.html;另確認 exe 版本資訊與七種尺寸圖示皆已嵌入。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 11:03:11 +08:00
JianMiauandClaude Fable 5 b1d57249f8 初始提交:Codex Pets 相容的 Claude Code 桌面寵物
摘要:
Electron 桌面寵物,透過 Claude Code hooks 即時反映工作狀態(思考、等待授權、失敗、完成),並支援 16 方向追視游標。

根本原因:
專案尚未納入版控,需要建立初始 repo 以便後續追蹤與協作。

影響:
建立 main 分支的第一個版本,包含完整可執行的程式碼與內附寵物「小念」。

修法:
納入以下內容:
- main.js / preload.js:Electron 主程序、透明置頂視窗、系統匣選單、HTTP 事件伺服器、游標輪詢與拖曳
- renderer/:Codex V2 spritesheet 幀動畫播放器、session 狀態機、氣泡、alpha 點穿判定
- hook/claude-pet-hook.js:Claude Code hook 端,stdin → POST /event,永遠 exit 0
- lib/、scripts/:hooks 安裝/移除、Windows 開機自動啟動
- pets/xiao-nian:內附寵物包(pet.json + spritesheet.webp)
- README.md、docs/states.png:使用說明與狀態總覽圖

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 10:32:39 +08:00