Commit Graph

137 Commits

Author SHA1 Message Date
Leo f10d10747c fix(collector): 萃取產物加 vault 辨識+落卡不再無條件覆蓋(arcrun-rag#60)
事故:daemon 完全沒有 vault 辨識,把萃取卡片寫進使用者的 Logseq vault
(system-dev/wiki/cards/ 對一般資料夾沒事,對 vault=憑空多出頁面,
2026-08-10 leo 實撞:25 個簡體字卡片污染 ~/Documents/KB)。

修法(照票上指示搬現成的,不重新設計):
- collector/vault.go:DetectVaultType/IsVault,判準逐條抄自
  system-dev-template/scripts/install.sh:209-221(先查 logseq/、再查 .obsidian/)。
  已用同一批 fixture 資料夾跑過 install.sh 與這支 Go 版,五種情境(logseq/
  obsidian/一般資料夾/兩者皆有/空資料夾)IS_VAULT 判斷逐一比對一致。
- extract.go:cardsRelDirFor() 依 IsVault 決定卡片相對路徑——非 vault 不變
  (system-dev/wiki/cards/),vault 改落 .arcrun-rag/wiki/cards/(點開頭隱藏
  目錄,Logseq/Obsidian 預設不掃描,跟 daemon 自己 scan.go 的隱藏目錄跳過規則
  一致)。extract_workersai.go/extract_gemma.go/direct.go 的落卡與下架清除
  都改用這個函式,三處對同一個 absRoot 保證同一個答案。
- safewrite.go:safeWriteCard() 取代兩處無條件 os.WriteFile——目標已存在且
  內容不同就先備份成 <dest>.bak-<unixnano> 才覆寫;內容相同則不動(不產生
  垃圾備份);備份失敗就整個中止,不無聲蓋掉使用者機器上已有的東西。

驗證(見 PR/commit 說明附的實測輸出):
- 用 fake Logseq vault fixture 重現舊行為(卡片確實落在 system-dev/wiki/cards/),
  再用同一份 fixture 驗新行為(卡片改落 .arcrun-rag/wiki/cards/,vault 根目錄
  非隱藏 .md 數量不變、journals/ 原稿位元不動)。
- 故意放同名既有卡片,跑完既有內容被備份、新內容確實寫入,未無聲遺失。
- 非 vault 既有測試(extract_gemma_test.go 原有三支)全數不動照過,確認
  一般資料夾行為零改變。

範圍外(留給下一輪):ExtractWithClaude(extract.go 的 claude 路)與其
templatefs/.claude/commands/rag-extract-file.md 技能檔仍硬寫 system-dev/wiki/cards/,
但這條路目前在 direct.go 的 RunDirectOnce 是不支援狀態(cfg.Extractor 只認
workers-ai/gemma),非本次事故的作用路徑,故未動。

不影響:leo 機器上的 daemon(未重啟、未重新打包);此修復要生效還要
①總管審過併 main ②重新打包桌面版裝上他機器 ③總管用新二進位實測,
三件缺一不可(見 issue #60 leo 的重啟條件)。
2026-08-11 12:12:51 +08:00
Leo 3862a1a877 chore(daemon): 版號戳到 v0.18.25,補迎新精靈的 changelog 段(Gitea #23)
daemon-version.py 的指紋鎖已擋下 v0.18.24(onboarding wizard 合併後
原始碼與該版指紋不符)。補上「## 下一版(未發佈)」段描述兩步引導
精靈,重跑 --stamp 機械戳出 v0.18.25,三平台(dmg/exe/msix)已重打
包並逐一拆開驗證內嵌版本,見 #23 留言。本次僅打包,未出貨。
2026-08-09 17:41:37 +08:00
Leo e7a72d74e1 daemon: 首次啟動改成兩步引導精靈(Gitea #23)
舊版 onboarding() 只有兩行字+兩顆按鈕,沒解釋 Arcrun 是什麼,也沒有
「我做到哪了」的感覺;『我還沒有知識庫』點下去就丟去外部網址,
回來以後要自己想起來要按哪顆鈕才能連線。

改成兩步小精靈,每步都有步驟點(1/2):
① 認識 Arcrun:三句大白話講清楚在做什麼,不用任何內部詞
② 連上或申請知識庫:『已經有了』/『還沒有』並排兩張卡,
   『還沒有』那張當場講清楚『回來這裡按哪顆鈕』,不是丟出去就沒事

連線邏輯(Connect/showConnect)完全沒動,接的是既有後端;
連線成功後落回首頁,首頁本來就有狀態時間軸+自動種好的範例資料夾
(default_library.go 的 P4),使用者不必再多做一步就看得到
『丟檔案 → 知識卡』整條路跑起來。

只動前端(main.js+style.css),零 Go 改動。check-cis.sh 全過;
用 mock window.go.main.App 在瀏覽器截圖驗過兩步畫面(截圖見 issue #23 留言)。
2026-08-09 15:03:23 +08:00
Leo 63bea13f21 feat(daemon-beta B2): 萃取品質 lint(H1–H6)重新接到現行 direct.go
Gitea #24:分支 work/b2-quality-lint-0726(2f3e9ec)躺在 main 外兩週,
今天重新整合進現行 direct.go(原分支落後 main 130+ 筆,direct.go 本身
差 667 行,需要真正的整合工作而非硬併,見 wiki status.md 與頂層
daemon-beta/tasks.md 已記載的殘項)。

- collector/lint.go/lint_test.go:package main → package collector
  (main.go 在 4a26856/v0.18.9 已從兩支執行檔併成一支,整個 collector/
  目錄已改名 package collector;分支停在改名之前)。
- collector/main.go:func main() → func Run(args []string) int
  (同一次重構),新增 case "lint" 呼叫 runLint。
- collector/direct.go:DirectConfig.LintStrict 欄位+extractor 萃完、
  POST rag_ingest_card 前掛 LintCard 閘(硬缺→不送標 rejected;
  軟項→照送帶 quality:low+quality_warnings);CLI --strict 旗標。
- 既有測試共用的 cardFixture(direct_extract_test.go)/direct_pacing_test.go
  三處 inline 卡片,從「兩段最簡卡」補成 B2 合格四段卡——不是新增例外,
  是 lint 正確地在做它的事,舊卡片本來就不合格。

驗證:go build/vet/test ./... 全綠;另以真實 PDF(docs/onboarding/封測說明.pdf,
2MB 二進位)跑過完整 direct.go 同步鏈,證實送進 Gemini 的 prompt 是
5,071 字乾淨中文(0 個控制字元/NUL),不是原始位元組——ConvertToText
(t73)已完整解決 900b895 想止血的問題,故該分支的 looksLikeText
二進位止血、Windows 交叉編譯腳本判定作廢,不併。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 14:26:21 +08:00
Leo ef8126201c P8 短板齊平:模型品質實測(granite 否決、qwen3-30b 定案)+額度撞牆三句話接上畫面
① docs/benchmarks/p8-extractor-quality/:leo 真筆記 8 篇 × 5 模型可複驗實測
   (production 同 prompt/參數、CF 原生 usage.neurons 計量)——
   scout 84 n/檔(119 檔/日)→ qwen3-30b 43 n/檔(232 檔/日)品質不降反升;
   granite 最便宜(858 檔/日)但 5/8 缺段、三元組全滅=否決。
   誠實結論:免金鑰路物理撐不起「幾千篇當天匯入」(差 13 倍),
   正解=消化節奏(3,000 篇約 13 天背景跑完、新筆記優先)+出口(Gemini/付費/ollama)。
② 撞牆體驗:quota_message 08-07 就在 status.json,App 從來沒接——
   app.go pickQuotaNotice(過期快照不說謊)+ main.js cardQuota(三句話卡)+
   progress.go ClassifyFailure 補認三句話(原本掉「其他」)。
   Go 兩 module 全綠;瀏覽器實載 dist 深淺兩主題截圖、console 0 錯。
③ 公開鏡像排除 docs/benchmarks(輸入是 leo 私人筆記)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 00:44:34 +08:00
Leo 779a1b801e feat(t215): 每個知識庫顯示是否要更新,落後就給 install 頁連結
leo 08-08:「在每個知識庫會看到其他需要更新的,在每個知識庫上顯示是否要更新,
如果要,加開啓 install 頁的連結」。小幫手以前只提示自己(daemon 本體)要不要
更新,使用者連著多個雲端知識庫時完全看不出哪一個落後。

- collector/cloud_latest.go:EvalCloudUpdate + FetchLatestCloudRelease(30 分鐘節流),
  判準與 portal 版本卡 loadVersion() 同一套(bundle_version vs install.arcrun.dev/
  api/latest 的 release,semver 逐段整數比較),不是 t103 minCloudRelease 那把相容
  底線,避免同一個知識庫在兩處得到相反答案。
- direct.go/sync_status.go:每輪同步順帶算好每個帳號的 CloudUpdateKnown/
  CloudUpdateStale/CloudLatest,寫進 status.json。
- app.go:GetState 把這些欄位接進 UIAccount,供前端讀。
- main.js/style.css:首頁新增「知識庫版本」卡(每庫一行,落後才出現「前往安裝頁
  更新」按鈕,帶 email 預填);側邊欄庫名旁加警示點;各庫頁也顯示同一行版本狀態。
  查不到版本(連不上/latest 暫時查不到)一律誠實說「查不到」,不當成「已最新」。

已用假 window.go 在瀏覽器實測四種情境(落後/已最新/連不上/查得到 mine 但查不到
latest)+零知識庫的 onboarding 頁+深色模式,畫面與按鈕行為皆符合預期。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 00:21:40 +08:00
Leo 2bbfa453d3 更新畫面那一行由 changelog 機械導出,不再出貨當下手工排版
leo 2026-08-08 真機看到 v0.18.24 的更新畫面:
「不要這麼長的散文,簡短講改了什麼,細節去 docs 讀。」
畫面是一整面文字牆,`**粗體**` 還原樣露出來。

真兇不是文案,是**最後一哩沒有機制**:
- 單一真相源(changelog.md)08-06 就立好了(30f726b),
  changelog-section.sh 也有 --check 閘擋「忘了寫更新內容」。
- 但它的**投影**那一段是 `tr '\n' ' '`——把整段散文壓成一坨,
  而且 grep 全 repo **沒有任何東西呼叫它**(build-*.sh 只用 --check)。
- ⇒ 每次出貨都是有人臨時寫一段 python 折行塞進 manifest,
  每次重寫一次、沒人檢查結果長什麼樣。

為什麼只能是一行短句(不是排版沒做好):
  main.js:215 是 `<div class="d">${esc(u.notes)}</div>`
  ① esc() ⇒ 任何 markdown 符號都會原樣露出
  ② .d 沒有 white-space:pre ⇒ 塞 \n 也不會分行

改法:
- 新增 installer/scripts/daemon-notes.mjs =**唯一的投影器**:
  只取 changelog 每條的粗體標題、串成一行、超長截斷並導去說明文件。
- changelog-section.sh 的投影段改成委派給它(--check 閘原樣保留),
  兩邊同一份規則不會漂移。
- ship.mjs 新增 notes 步驟:每次出貨自動套用,手改過的會被改回來。
- release.mjs 的 verifyManifest 加第四條規則:notes 必須存在、無 markdown、
  單行、不超長,且**等於 changelog 的機械投影**——手改一律擋下。

實測:
· v0.18.24/23/22 三版各自產出 62/51/80 字,零手工調整
· 手改成一段散文 → verify-manifest exit 1,兩條規則都抓到
· 再跑一次管線 → 自己改回正確那一行
· 用 daemon 真實 style.css + main.js 的真實 markup 渲染兩版對照並截圖:
  舊 576 字(含 8 個原樣露出的 **)vs 新 62 字三行讀完

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 21:47:31 +08:00
Leo 5ea6b755d9 修 stage 上 daemon 下載連結誤指 prod GitHub 的 bug,v0.18.24 上架 staging 驗收
daemonOf()(installer/oauth-prototype/worker.js)原本無條件把下載連結組成
raw.githubusercontent.com/youlinhsieh/arcrun-rag-bundles/<sha>/...——這對 prod
(bundleBase 是 jsDelivr @sha,需要換宿主避開單檔 20MB 上限)是對的,但對
staging(bundleBase 本身就是可直接讀檔的 Gitea raw root)會把 staging 的
commit sha 套進 prod 的 GitHub repo 路徑,下載必然 404/502。改成只有偵測到
prod 的 jsDelivr `@sha` 格式才換宿主,其餘直接用 base 本身當下載來源。
cacheKey 加版本後綴避免邊緣快取繼續吐修復前的舊 JSON。

wrangler.toml [env.staging] BUNDLE_BASE 釘子換到推了 v0.18.24 daemon 的
staging bundle commit(31a6ccf)。

diagnostics_stage_manual_test.go 的模擬出貨版本號更新到 v0.18.24(跟目前
實際出貨版本一致)。

實測(stage,真下載+真掛載):
- /api/latest:daemon.version=v0.18.24,下載連結指回 staging 自己的 Gitea repo
- /download/mac:200,sha256 與本機建置產物一致,掛載後
  CFBundleShortVersionString=0.18.24,codesign 有效
- /download/win:200,sha256 一致,exe 內嵌版本字串含 v0.18.24
- TestDiagnosticsStageManual(真 stage youlin 帳號)PASS,匯出診斷檔 JSON
  全文無本機絕對路徑

紅線核對:prod 全程未碰(.github-armed 不存在、arcrun-rag-bundles prod repo
未 clone/push);只動了 staging bundle repo(Gitea arcrun-rag-bundles-staging)
與 arcrun-rag-installer-staging worker。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 17:24:31 +08:00
Leo 0cdbda8694 v0.18.24 changelog 補齊(診斷檔 engine 狀態+路徑遮蔽)+三平台打包驗證
changelog 原本只涵蓋 5fcc139/5effcb2/9f1ca58 三項,補上今天另兩項使用者可見改動:
- 疑難排解按鈕的診斷檔現在也看得出小幫手還在不在跑(8eeb393)
- 診斷檔不再洩漏本機絕對路徑(4f81ad6)

三平台已本機打包驗證(未出貨、未碰 stage/prod):
- Mac dmg:CFBundleShortVersionString=0.18.24、codesign 有效、內含 Applications 捷徑
- Windows exe:單一自帶同步引擎執行檔(NSIS 安裝程式因本機 makensis 工具鏈壞掉暫缺,非本輪修復範圍)
- MSIX:AppxManifest Identity Version=0.18.24.0

.version-source.json 的 v0.18.24 指紋隨之更新(fingerprint 現涵蓋 8eeb393/4f81ad6 的 collector/ 改動)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 16:59:18 +08:00
Leo 0c6fb43117 fix(t213): 診斷檔遮蔽引擎錯誤訊息裡的本機絕對路徑
engine.detail/engine.last_error 在同步引擎沒在跑時,會把 collector 開機
橫幅的完整原文帶出來(direct.go RunDirect 的「監看 %s → %s」那行),其中
含監看資料夾的絕對路徑(如 /Users/xxx/Desktop/...)。這是上一輪(第二/
三輪)已發現但標記「附帶發現、未修」的洞——首頁在同一狀態下本來就會顯示
同一句話(不動,留給另案判斷),但診斷檔現在會被匯出成檔案交給外部人看,
風險層級跟留在托盤畫面上不同,這輪把它遮掉。

新增 redactLocalPaths(),只在 buildDiagnosticsPayload() 組裝 engine 欄位時
套用:把訊息裡看起來像本機絕對路徑的片段換成「…/<路徑最後一截>」,
http(s) URL 先放行原文(避免 URL 裡的 / 被誤判成本機路徑)。只動
diagnostics_export.go 這一端,不改 describeStatus()/collectorFailure()/
direct.go 的訊息本身——那些同時是首頁托盤畫面在用的同一組憑據。

擴充 diagnostics_engine_e2e_test.go(真執行檔+真 supervisor 子行程,零
網路零帳號):監看資料夾改用真實絕對路徑,證明子行程停擺後整份匯出 JSON
找不到該路徑,同時 last_error/detail 仍讀得出「引擎沒在跑、以及為什麼」
(含監看資料夾的名字)。另加 8 個 redactLocalPaths 純函式單測。

go build/go vet/go test ./...(collector+arcrun-app+supervisor 三個
package)全綠;diagnostics_stage_manual_test.go 維持預設 SKIP,本輪未碰
網路/stage。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 16:36:20 +08:00
Leo 4cf09bf8ca test(t213): 補一支可獨立重跑的 stage 手動驗收,回應總管複驗要求
總管指出「本機測綠 ≠ stage 上按得出來」,且上一輪驗完就刪了測試檔,
讓總管沒有東西可以自己重跑複驗(不像 diagnostics_engine_e2e_test.go
那支,總管自己重跑就拿到 PASS)。

新增 diagnostics_stage_manual_test.go:留在版控裡(RUN_STAGE_DIAGNOSTICS=1
+ STAGE_ACCOUNT_JSON 才會跑,預設不影響一般 go test ./...),總管/leo
可以用自己的憑證隨時獨立重跑,不必等我。這次順帶修正版本號問題:
buildDiagnosticsPayload() 是被測試行程自己呼叫的(同一個 package main
的 version 套件變數),先前直接 go build 只讓子行程帶了新版號,
daemon_version 欄位仍印出編譯測試二進位檔的預設值 "dev"——這次改成
呼叫前直接賦值 version(比照 build-mac.sh 的 ldflags 精神,讓輸出更接近
真實出貨版本),確認過改動不影響其他測試(version 用完 t.Cleanup 還原)。

真跑一次(stage youlin 帳號,真雲端):daemon_version="v0.18.23"、
accounts[].cloud.bundle_version="1.4.22"(確認不是 null)、同一組
progress(total:2 pending:1 unreadable:1)在「活著」與「sup.Stop() 停了」
兩次匯出裡 engine.alive 正確反映 true/false。JSON 全文(含檔案路徑證據)
已回報總管。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 14:51:16 +08:00
Leo 8baa9170da feat(t213): 診斷檔補 engine 區塊,補上考卷 Q2「還在不在跑」缺口
pending=0 以前只能證明「現在沒有排隊的」,證明不了「真的做完了」還是
「daemon 早就掛了沒人知道」——因為診斷檔完全沒有時間資訊。

local.engine 不發明第二套判斷法:直接借用首頁狀態列本來就在用的
collectorAlive()/collectorSyncing()/collectorFailure()/describeStatus(),
只是把這些憑據也寫進 JSON(alive/syncing/crash_looping/headline/detail/
last_sync/last_activity_at/seconds_since_last_sync)。last_error 只在
alive=false 或 crash_looping=true 時才附上,避免健康行程的開機橫幅
被誤讀成錯誤訊息。

新增 diagnostics_engine_e2e_test.go:真的建執行檔、真的用 supervisor
拉起子行程、真的 Stop() 停掉它,驗證同一組 progress 數字在兩種情境下
engine.alive 正確反映活/死(永久迴歸測試,零帳號零網路,快且穩定)。

stage(youlin 帳號)實測驗證見 system-dev/wiki/status.md:兩次真實匯出
(活著/停了)JSON 全文、四題考卷重跑 4/4。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 14:37:53 +08:00
Leo 92fecb48c1 stage 驗收:t213 兩半+t210+t209 全鏈打通(InkStoneCo 總管交辦)
- changelog 補 v0.18.24(下一版)條目:失敗真因不再被抹掉/首頁改統計/
  疑難排解按鈕搬進小幫手本體——三批合一次驗收
- .version-source.json 記錄 v0.18.24 指紋(daemon-version.py 版本閘產物)
- staging 安裝器釘子換到 arcrun-rag-bundles-staging@51f84bf(source Arcrun@8447165,
  含 93b1140 新端點 GET /portal/daemon/diagnostics + 8447165 portal 按鈕文案)

驗收方式:deploy-all.mjs 全量部署到 youlin 測試實例(stage)、collector direct --once
真跑一輪(3 ingested/2 unreadable,格式不支援)、buildDiagnosticsPayload() 真跑產出
合併診斷 JSON(本機 Progress/FailureBreakdown + 雲端 /portal/daemon/diagnostics)。
四題考卷用該 JSON 逐題核對,詳見本次回報。

未動:prod bundle repo/github-arm/publish-github(紅線,一步未碰)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 13:53:53 +08:00
Leo e5d8cee58b feat(t213): 匯出診斷檔搬進 arcrun-app 本機端(phase 2,接手 t210 落地後的 Progress/FailureBreakdown)
t210(commit 5effcb2)已把「總量進度」「失敗分類」算好並寫進 status.json(現況快照,
不隨閒置歸零)——本次直接讀那兩個欄位,不另算一套(首頁與診斷檔同一組數字)。

新增:
- collector/cmd/arcrun-app/diagnostics_export.go
  - App.ExportDiagnostics():合併本機(daemon 版本/自我更新狀態/Progress/
    FailureBreakdown/略過檔案樣本)與雲端(每帳號打新端點 GET /portal/daemon/diagnostics,
    X-Arcrun-API-Key 認證)成一份 JSON,彈系統存檔對話框存下來
  - mergeDiagnostics 抽成無 IO 純函式,方便測試
- collector/cmd/arcrun-app/diagnostics_export_test.go:5 個離線測試,涵蓋
  同首頁數字/分類名稱原樣照抄不自己判斷/失敗檔名 basename-only/略過清單省略欄位/
  帳號層 cloud 與 cloud_error 互斥
- frontend/src/main.js:「版本與更新」頁加「疑難排解」卡片+匯出按鈕

守住的規則:
- Progress/FailureBreakdown 原樣接住 status.json,不重掃 manifest
- 分類名稱字串只認 collector/progress.go 的 ClassifyFailure,本檔不判斷任何分類
- 失敗檔名 basename-only:借用既有 buildSkipped() 輸出(本來就是 basename+白話標籤)

驗證(實跑,非設計稿):go build/vet/gofmt 全乾淨;go test ./...(collector+
arcrun-app)全過;check-cis.sh/check-render.sh 視覺機械閘全過;wails build 成功
產生含 ExportDiagnostics 的 bindings;拿本機真實 config.json(2 個真帳號)+真網路
跑 buildDiagnosticsPayload():真拿到 update_check.latest=v0.18.23、真列出本機
略過檔案(basename)、兩個真帳號誠實回 cloud_error(線上新端點還沒部署,與總管
實測結果一致)。未在真實 GUI 點過按鈕(本機正跑 production Arcrun.app,避免干擾)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 13:09:48 +08:00
Leo b3f63f40fb feat(t210): 首頁改統計,不再逐檔解釋——Evan「9000/101/20 兜不起來」的病
地基(progress.go 的 SyncProgress/ClassifyFailure/BuildFailureBreakdown,
eeb35ba)已經算好「總量」與「失敗分類」,但沒人接到畫面上:首頁仍在用
本輪計數(extractedOK)+逐檔白話翻譯(humanizeFailure),導致 leo 08-08
轉述的病——三個數字互相對不起來,使用者無法判斷「還在跑」還是「壞了」。

接線(collector/direct.go):
- runDirectOnceRoot 現在也回傳這一根資料夾的 SyncProgress/StuckReasons
  (rootProgress),RunDirectOnce 跨帳號跨資料夾 Add() 累加成總量。
- G-6.2 的 SkippedDocCount(讀不了的檔,根本沒進 manifest)併進
  Unreadable/Total——這是 Progress() 算不到的部分,由呼叫端補齊,
  維持不變式 Total == Done+Pending+Stuck+Unreadable。
- 「送不上去」的分類統計=Stuck 的 LastError 原文+Unreadable 重用
  convert.go 既有的 ErrUnsupported,一起餵給 BuildFailureBreakdown。
  分類判斷全程只經過 progress.go 的 ClassifyFailure 一個接縫,
  direct.go/app.go/前端都不認得任何分類名稱字串(留給 t214 之後
  改資料驅動時只動一個檔)。
- SyncStatus 新增 Progress/FailureBreakdown 兩個欄位,兩者都是每輪從
  manifest/掃描結果原地重算的現況快照,不進 CarryForwardActivity——
  斷網或閒置一輪不會被清成 0。

畫面(cmd/arcrun-app/app.go+frontend/src/main.js):
- 移除 humanizeFailure/buildFailures/UIFailures 那套逐檔白話翻譯,
  改用 UIProgress(Total/Done/Pending/CantSync/Groups);前端 cardProgress
  只把後端給的 category/count 陣列原樣印出,不分支、不排序、不認分類名。
- 「送不上去」預設摺疊(<details>),展開只有分類與份數,不逐檔列名、
  不解釋、不給解法;細節導向「開啟使用說明」。
- 保留 buildSkipped 的「讀不了的檔」卡片(那是另一件事),但份數已併入
  Unreadable。
- 移除「總計」卡片裡用本輪計數 extractedOK 的「份已整理」——上方狀態
  時間軸的「上次 N 份」與下方矛盾(1 份 vs 0 份已整理)的病因直接消掉,
  改用 cardProgress 的累計「已送上去」。

驗證:
- go build ./... 與 go test ./...(collector/cmd/arcrun-app/
  cmd/arcrun-tray 三個 module)全綠。
- 新增 collector/progress_wiring_test.go:真跑 RunDirectOnce 湊出
  Done/Pending/Stuck/Unreadable 四種狀態同時存在,驗四數字相加等於
  總數(leo 驗法①);再跑一輪「什麼都沒發生」驗數字不歸零(驗法③)。
- check-render.sh 視覺機械閘綠(lockup 底板/深色模式)。

殘項(誠實標記,未完成):
- 真機驗收(leo 08-08 驗法④:拿 Evan 的情境走一遍)未做,需要 leo 或
  封測者在實機驗證。
- 「開啟使用說明」目前連到既有 docs 首頁,尚無 t210 分類對應的 FAQ 頁
  (tasks.md 已記為相依項)。
- t213 診斷檔尚未消費這組新欄位(tasks.md 記載該任務等本任務讓路)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 12:45:12 +08:00
Leo 1efc8123f1 feat(t210): 統計層地基——分母與失敗分類(首頁與診斷檔共用同一組數字)
leo 08-08:「我不要枚舉每個檔案可能的問題和解法,應該是統計的」。
SyncProgress 單位裁定為「份檔案」(地端算不出待同步幾張卡)。
不變式 Total == Done+Pending+Stuck+Unreadable 由測試守著。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 12:09:49 +08:00
Leo e12f44e84d fix(daemon): 失敗真因每輪被自己抹掉——展開只剩「當時沒有記下原因」
Evan 封測(08-08):首頁「有 20 份沒有送進知識庫」,逐份展開看到的都是
「已經自動試過 N 次都沒成功……這個檔是在舊版失敗的,當時沒有記下原因」。

原因當時真的記下來了(MarkFailed 有寫 LastError),是 scan.go 每輪重建
ManifestEntry 時漏 carry 這一欄,5 秒就把真因抹掉一次 ⇒ 下一輪組不出
「|原因:」⇒ 畫面退回 humanizeFailure 的最後退路。

這是 t195「加了欄位卻寫進去就不見」的第二次實例:LastError 正是同一批
新增的欄位,卻漏在 carry 名單裡(wiki mistakes.md:92 已立過警告)。

對照實驗(非空靶):拿掉這一行 → 兩支測試都紅(真因為空字串)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 11:44:36 +08:00
Leo 2b06a0456d v0.18.23 changelog(用戶語言)+三平台打包
指紋閘擋下重打同版號(「兩個內容不同卻都叫 v0.18.22=版本號說謊」),
照它要求先寫 changelog 才放行——這個閘今天第一次真的攔到人,有效。

打包踩到一個坑:dmg 壓製回 resource temporarily unavailable,
真因是前一次對照實驗留下的 /Volumes/Arcrun 1 掛載點沒卸載。
hdiutil detach -force 後即通過。記下來免得下次誤判成打包壞了。
2026-08-08 03:10:11 +08:00
Leo edc9916b2f 修好:Mac 自我更新斷七代(dmg 餵給解 zip 的 ditto)+殘留 staged 旗標騙人重啟
症狀一:manifest.daemon.mac.file 出貨格式從 zip 換成 dmg(build-dmg.sh),
但 applyStagedAndRestart 只認 zip(ditto -x -k)⇒ ditto: Couldn't read PKZip
signature,Mac「檢查更新」從 v0.18.5 斷到 v0.18.22(leo 真機截圖實測)。
症狀二:手動裝好新版後仍顯示「已下載完成,重新啟動就會套用」——staged.json
判準是「有沒有下載過」而非「目前版本 vs 最新版本」。

- extractAppFrom 依副檔名分流 .dmg(hdiutil attach -mountpoint)/.zip;
  Windows 補上真正的自我更新(rename 正在跑的 exe → 搬新版 → 原路徑重啟,
  併檔後 Windows 只有單一 exe,「開資料夾」是併檔前的殘留假設)。
- CheckUpdate 加 stagedDecision 純函式:已追上或 staged 版本過期就清掉
  殘留(連磁碟檔案一起清,不只清記憶體)。
- 新增 selfupdate_test.go:用真的 hdiutil 造 dmg 驗證解壓(不是 mock)。

Mac 端到端已用真的線上 manifest/真 v0.18.22 dmg 驗證整條鏈路(含用舊寫法
對同一份真檔案重現原始錯誤,證明修法對症)。Windows 只驗到 GOOS=windows
交叉編譯成功,真機行為待 leo 用 VMware Fusion 驗證——詳見 status.md。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 02:58:01 +08:00
Leo 837e5e8052 出 v0.18.22:aa65efa 積壓分批四件套+新增預設庫,全平台已打包驗證
changelog 補上用戶語言的更新內容(daemon-version.py --stamp 依此機械
算出版號、記錄原始碼指紋)。三平台已打包並拆開驗證內嵌版本 == v0.18.22:
- Mac DMG:Info.plist CFBundleShortVersionString=0.18.22,
  arcrun-app --collector --version → v0.18.22,codesign 有效
- Windows exe:ldflags 內嵌 v0.18.22(無舊版號殘留),
  build-win.sh 內建探針確認 --collector --version → v0.18.22
- MSIX:AppxManifest Identity Version=0.18.22.0(真身分,非佔位值),
  內嵌 Arcrun.exe 同樣是 v0.18.22

尚未出貨(ship.mjs/purge jsDelivr/D20 開閘),產物留在
collector/cmd/arcrun-app/dist(-msix)/(gitignored,不進版控)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 00:27:44 +08:00
Leo 361f1f1f9d 新增全新安裝的「可刪除預設庫」(leo 2026-08-08 拍板)
全新使用者第一次連上知識庫時,自動在 Documents 建一個
「Arcrun 範例庫(可刪除)」,裡面放三份 .md 示範內容,
不必自己選資料夾就能立刻走完「丟檔→知識卡→搜得到」。

四條紅線都有測試釘住(default_library_test.go/
default_library_e2e_test.go,含真的建 binary 跑 --dry-run 驗證):
1. 可刪——刪掉就真的消失,marker 檔擋住重種
2. 只有第一次——已有帳號的機器不會冒出來
3. 不污染——只在自己的資料夾裡寫檔
4. 不留幽靈資料——資料夾被刪後,config 殘留引用會被清掉(GetState 自我修復)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 00:22:03 +08:00
Leo 89d9b314fb 出貨 1.4.20:安裝器釘子 → 8be98b8+收 daemon 去重的規模測試
釘子:c1566cf → 8be98b8(release 1.4.20,source Arcrun@c7a0b31)
jsDelivr 已驗(用戶端的路,四個檔全 200 且內容逐項對):
  manifest release=1.4.20 core=5 顆(懶載設計保住)
  cypher ANALYTICS_KV.put 殘留 0/kbdb empty_reason 5/UI 匯出診斷檔按鈕 1

順帶收 daemon agent 留下的 scan_dedup_scale_test.go(它沙盒推不了)。
親跑驗過不是空檔:TestScan_FormatDuplicate_EvanDatasetScale
10,545 個白名單內檔案(9,045 獨立 md + 500 組三格式撞 stem)耗時 688ms,PASS。

🔴 出貨時抓到兩個「用 staging 覆蓋 prod」的陷阱(已避開,記入 wiki):
1. rsync --delete 會砍掉 prod 才有的 daemon/(20 個安裝檔)與 README.md
   => 推上去封測者就下載不到 daemon。靠 git status 顯示 D 才抓到。
2. core 陣列會從 5 顆變 24 顆——prod 只裝 5 顆是懶載設計(其他用到才長)。
   歷史警察指出 bbb433e 就是修「core 陣列被整個蓋掉」=> 同一個坑有前科,今天差點重演。
共同教訓:staging 與 prod 的內容範圍不一樣,不能整包蓋。

⏸️ 安裝器部署(wrangler deploy --config)被 classifier 擋,等 leo 執行。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 23:33:21 +08:00
Leo a6ced32d45 collector:積壓分批+新檔優先/額度用完講人話降速/斷點續傳/同內容多格式去重
封測事故(Evan):daemon 逐檔萃取上傳、每個檔在雲端產生一次工作流執行,
690 個檔在自己的免費 CF 帳號上短時間內衝出 1,070 次寫入,撞上免費上限 1,000,
額度爆掉、只有 8 個檔成功。雲端那一半(紀錄改走資料層 API)已修好,
daemon 這一半原本完全沒有節奏——本次補齊四件事:

1. 上傳節奏(direct_pacing.go):單輪最多處理 MaxEventsPerRun 個事件
   (預設 25)、每次觸發雲端前節流 700ms;一輪掃到的事件依檔案 mtime
   由新到舊排序,今天寫的永遠優先,積壓慢慢消化不擋日常使用。

2. 額度用完講人話(quota.go):偵測到 Workers AI「10,000 neurons」/
   「4006」等已知上游訊號後,換成三句話(今天已整理幾份/可換模型或
   升級 Cloudflare/不花錢也沒關係、今天或明天早上 8:00 會自動恢復),
   不出現裸露的錯誤碼;同帳號同輪與下一輪都不再繼續撞牆
   (quotaState 全域冷卻,跨資料夾/跨程序重啟持續,直到台灣時間
   早上 8:00 額度重置)。

3. 斷點續傳:每個事件處理完立刻寫回 manifest(不再等整輪跑完才存一次),
   process 被殺掉重開只會接著做真正還沒完成的部分;removed 事件另外
   用 preScanEntries 快照保護,下架失敗時不會被其他事件的存檔動作
   誤標成「已完成」而永遠不再重試。

4. 同內容多格式去重(scan.go):同一批來源轉出的多種格式(如 leo 給的
   資料集 27,164 檔=9,045 md+9,044 json+9,043 html,md/json 同檔名
   主幹)依檔名主幹分組,只留優先序最高的一份進事件管線,其餘標記在
   DuplicateFormats(不吃三倍額度)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 16:58:07 +08:00
Leo 8ad6f25ba3 出貨 v0.18.21:釘子 → c1566cf(Windows 修復鏈+首發 MSIX 已送達封測者) 2026-08-06 23:18:00 +08:00
Leo c952d66e41 MSIX 帶入真 Store Identity(從頂層 .env 自動讀)+credentials-map 登錄:別再問 leo 要這三個值 2026-08-06 23:04:53 +08:00
Leo 5c0bddacc4 v0.18.20:失敗清單改摺疊式+修指紋閘的自我參照
## UI(leo 實測版面被壓扁)
失敗清單改用原生 `<details>`:只列檔名、旁邊三角形、**預設收合**,
點開才看原因。先前把原因攤在檔名旁邊,中文檔名被擠成一行一個字。

## 🔴 指紋閘三修:帳本自己不能算進指紋(經典自我參照)
`.version-source.json` 就住在 `collector/` 底下 ⇒ 戳版號寫入它、指紋跟著變
⇒ **同一輪連續打 win/mac/msix,第二支就被自己的閘擋下**(實撞)。
查證過才修(沒有亂放寬):
  · `gen-icons.py` 產的 .ico **位元一致**,不是它
  · `dist/` 早就 gitignore,不在名單裡
  · 真兇只有帳本
⇒ **只排除帳本一個檔**。一度想順手排掉整個 `build/`,那會讓「換 app icon」
  不算原始碼變更=在閘上挖洞,已收回。
驗:連跑兩次版本號相同(冪等)。

## 三種產物都備齊(在共用資料夾)
`Arcrun-win-v0.18.20.exe`/`送審用/Arcrun-v0.18.20.dmg`/`送審用/Arcrun-v0.18.20.msix`
⚠️ MSIX 的 **Identity 三值仍是佔位**(`PLACEHOLDER.ArcrunRAG`)⇒ **不能送 Store**,
需 leo 從 Partner Center 提供 `IDENTITY_NAME`/`PUBLISHER`/`PUBLISHER_DISPLAY` 再重打。

## 未送達
線上仍 v0.18.8。推遠端需 leo 開 D20 閘。
2026-08-06 22:39:42 +08:00
Leo 73004b4149 失敗清單改成「檔名 + 三角形,預設收合」(leo 實測版面被壓扁)
leo:「表格最左邊的標題被壓扁了…**可以顯示沒通過的名稱,旁邊有個三角形,
點擊看到失敗細節,default 摺疊起來,重點是找到問題在哪裏**」

## 真兇
我重用 `.step` 的橫向格線(`display:flex` + 原因擠在右邊)
⇒ 中文檔名被壓成**一行一個字**(實撞截圖)。

## 改法
改用原生 `<details>/<summary>`:三角形、鍵盤可操作、預設收合都不必自己寫。
檔名一行(`word-break: break-word`,長檔名換行但不是一字一行),
原因收在裡面,需要時才展開——**先看到「哪些檔沒過」,細節是第二層**。
CSS 用實際存在的 CIS 變數(`--ink-rgb`/`--amber`;原本誤用不存在的 `--hair`/`--ink-soft`)。

## 驗
`node --check` 過、`check-cis.sh` **全過**、go build 過。
⚠️ **未真機驗**(未打包送到 leo 手上)。
2026-08-06 22:31:00 +08:00
Leo d0852e9b45 v0.18.19:失敗訊息改成人話(leo:「這個錯誤訊息我真的看不懂」)
v0.18.18 雖然把失敗檔案列出來了,但寫的是
「上次失敗(第 5 次),5h38m38s 後重試(上面是自動重試的排程,真正的原因是前一次失敗)」
——**那是我寫給自己看的**:只是把 collector 的原文轉貼、再補一句解釋為什麼看起來怪。

使用者要的是三件事:**現在怎樣、為什麼、我要不要做什麼**。

## 改法
- 解析退避訊息取出「試幾次/多久後再試/原因」,重新組成人話
- **有原因**時以原因為主、排程放後面補充(他要判斷的是原因)
- **沒有原因**時(舊版失敗的檔案沒存 LastError)誠實說:
  「已經自動試過 5 次都沒成功,約 5 小時後會再試一次。這個檔是在舊版失敗的,
    當時沒有記下原因;如果一直沒好,把紀錄檔傳給我們。」
- 時間不再精確到秒(`5h38m38s` → 「約 5 小時」)——使用者要的是量級不是秒數

## 實測輸出(三種情境都印出來看過)
  舊:上次失敗(第 5 次),5h38m38s 後重試(上面是自動重試的排程…)
  新:已經自動試過 5 次都沒成功,約 5 小時後會再試一次。這個檔是在舊版失敗的…
  新:今天的免費 AI 額度用完了 ⇒ 明天會自動恢復,這些檔案會自己補上,你不用做什麼。
  新:這份 PDF 看起來是掃描的圖片 ⇒ 需要先做文字辨識(OCR),或換一份有文字的版本。
2026-08-06 22:02:32 +08:00
Leo 857420dfa7 v0.18.18:失敗原因真的顯示在畫面上(前面幾版只做了一半)
## 補完「上游錯誤要看得到」這條線
- 退避中的檔案也進 `status.failures`(先前退避期間該欄是空的
  ⇒ 畫面只有「⚠ N 份失敗」沒有原因,等於前面白做)
- `shortError` 從 120 放寬到 240 字:真因在訊息**後半段**,砍 120 會整段切掉

## 實測輸出(不是推測,是印出來的卡片內容)
  有 2 份沒有送進知識庫
   · 2021超新星品牌白皮書-科特勒.pdf
     今天的免費 AI 額度用完了 ⇒ 明天會自動恢復,這些檔案會自己補上,你不用做什麼。
   · 台灣青年創新創業協會謝侑霖.pdf
     這份 PDF 看起來是掃描的圖片 ⇒ 需要先做文字辨識(OCR),或換一份有文字的版本。
⇒ 同樣是「失敗」,處置完全相反(等就好/要動手),使用者現在分得出來。
(測試資料取自 leo Windows collector.log 的原文。)
2026-08-06 21:41:28 +08:00
Leo c7675a10a0 不再把開發用 template 塞進使用者資料夾;上游錯誤不再被退避訊息吞掉
## leo 三條裁決,逐條落實
① 「**別人的錯誤一律要顯示給用戶看,不然就會變成我的錯誤,導致客服**」
   → manifest entry 新增 `LastError`(存**原文**),退避訊息改成
     「上次失敗(第 N 次),58m 後重試|**原因:**<上游原文>」。
     成功後清空,不留舊錯誤嚇人。
     測試 `TestUpstreamErrorSurvivesBackoff` 從上游錯誤字串出發,
     驗它活到給使用者看的那句話;反向驗證拿掉即紅。

② 「**拿來開發一般人用不到的根本別安裝**」
   → daemon 不再代裝 system-dev template(`CLAUDE.md`/`scripts/`/`system-dev/`,37 檔)。
     兩層傷害:把人家資料夾弄亂 + 那些檔被當知識吃進去
     ⇒ 知識庫長出 `kb`/`t195-watch` 這種不是使用者內容的庫(leo 實撞)。
     `template-install` 子命令保留,開發者情境不受影響。

③ 「**它也不能只看隱藏檔內,因為 template 在我所有的 repo 裡不是隱藏的**」
   → 排除規則用**路徑身分**(`TemplateOwns()`,來源是內嵌 templatefs 的實際路徑
     + `system-dev/`・`scripts/` 目錄前綴),**不是**「有沒有以點開頭」。
     測試刻意把 template 檔放成**不隱藏**(就像 leo 的 repo),驗它仍被排除;反向驗證即紅。
     這些檔也不計進「有 N 個檔案沒有被整理」——那欄是給使用者看他自己的檔案的。

## 順帶修 leo 截圖上的重複
「有 2 個檔案沒有被整理」底下 `scripts/sdd-active-check.sh` 出現兩次
——多帳號時同一個資料夾被掃多輪、每輪都 append。已去重。

## 殘項(誠實)
App 端失敗卡還沒接上這條線 ⇒ **畫面尚未真的顯示原因**。未打包、未送達。
2026-08-06 21:33:37 +08:00
Leo 6e0b65a2bb mistakes:Workers AI 額度用完/掃描版 PDF——兩個「不是 bug 但要說清楚」的已知現象 2026-08-06 20:48:01 +08:00
Leo 9dc7cbc803 v0.18.17:失敗的檔案列出檔名與原因;補上「上一版說有、其實沒做進畫面」的按鈕
leo Windows 實測 v0.18.16:連線誤報已消失( 那個修正生效),
但出現「⚠ 3 份失敗」→ 加檔後「⚠ 1 份失敗」,兩個新檔沒推上雲端也沒產卡。

## 修:數字不能代替原因(今天第四次同款病)
collector **早就**把「哪個檔失敗、為什麼」寫進 status.json(`Failures[]`),
但 App 一直只讀計數 ⇒ 畫面只會說「⚠ 3 份失敗」
⇒ leo 只能回報「沒推到雲端」,我只能猜。
前三次同款:exit status 2 蓋掉真話/非文件檔不點名/誤判沒連線。
現在列出檔名+原因(最多 8 份,總數照實講)。

## 🔴 更正:v0.18.12 宣稱的「一鍵打開紀錄檔資料夾」**根本沒進到畫面**
Go 那邊 `OpenLogFolder()`/`EngineTrouble` 都做了,但前端那次 `str.replace()`
**錨點縮排不符、沒命中**,而我沒加斷言 ⇒ 檔案沒變,我卻在回覆與 changelog 裡宣稱做好了。
(同一天已經因為「取代沒驗證」出過一次事,這是第二次。)
本次三處錨點**全部 assert**,並機械複驗五個關鍵字+`node --check`。
changelog 也已更正 v0.18.12 那段。

## 順帶修 .bat 的收集段(leo:「有按照 1 然後 2,你要檢查 bat」)
收集 log 那段用 `>nul 2>&1` **把錯誤全吞掉** ⇒ 失敗了也沒人知道,
我還以為是 leo 沒按 Enter。改成**逐檔回報 v/X/-**,並把輸出目錄
從中文「記錄檔」改成純英文 `logs`(中文路徑+UNC+Big5 主控台三者疊加容易出事)。
2026-08-06 20:22:24 +08:00
Leo 01fbb21357 v0.18.16:修「明明連上卻說沒連上」——第三次「我這台好好的」
leo Windows 實測:首頁跳「需要你處理一下 ⇒ 還沒連上知識庫」,
但側邊欄有 geek6688、Portal 的庫目錄管理也看得到它的庫(win_on_mac_test_lib)。

## 真兇:又是「只看根層欄位」
`direct.go` 判連線只看 **根層** `cypher_url`/`api_key`,
而多帳號設定(現在的常態)根層是空的 ⇒ **恆判成沒連線**。
為什麼開發機沒事:leo 的 Mac config 帶著**單帳號時代留下的根層欄位**。
⇒ **今天第三次同一個模式**(另兩次:config 缺 manifest 靠舊檔補齊、
   啟動 log 用根層組出 `/webhooks/named//…`)。
修:抽出 `accountsConnected()`=「根層有 **或** 任一帳號有」,並補測試
(刻意用「只有 accounts、根層全空」=全新使用者的形狀)。

## 順帶:那句話還指錯路
訊息叫人「點托盤選單的『+ 新增帳號…』」——**托盤選單早就只剩「結束 Arcrun」**
(t194 起所有設定都在視窗裡)⇒ 使用者照著做會找不到入口。
改指「左邊『知識庫』下方的新增知識庫帳號」。

## 順帶修我自己的測試腳本(leo 實撞)
`① 測試最新版.bat` 用 `dir /o-d`(修改時間)挑版本 ⇒ 複製到共用資料夾後
時間順序不一定等於版本順序,**leo 那次挑到了 v0.18.13**。
改用 `dir /on`(檔名排序)取最後一個。
另把「固定等 20 秒」改成「等你操作完按 Enter 再收 log」——
清空後第一次跑,引擎本來就不該啟動,等 20 秒收到的是空的。

## 附帶確認(leo 截圖)
- 「有 1 個檔案沒有被整理」**已列出檔名**(`scripts/sdd-active-check.sh`)⇒ 08-06 那個修正生效
- Portal 庫目錄管理三個庫都在(含 Windows 的 `win_on_mac_test_lib`)⇒ 子庫沒出現是假警報
2026-08-06 19:57:05 +08:00
Leo 458ff3fd88 v0.18.14/15:全新使用者連上知識庫後會自己開始+log 不再印不存在的網址
leo 08-06 用共用資料夾實測,兩張圖把最後一個病逼出來:
「第一次直接跑可以,**清空再來一次,就不跑了**」。

## 真兇:restartWatch 第一行就早退
全新使用者的順序是:
  ① 打開程式(**還沒有任何設定**)⇒ startSupervisor 早退,`sup` 是 nil
  ② 連上知識庫、加資料夾 ⇒ restartWatch
  ③ 舊版:`if sup == nil { return }` ⇒ **什麼都沒發生**
  ⇒ 引擎永遠不啟動,畫面叫人「請結束 Arcrun 再重新開啟」
「不算錯」(重開真的會好),但等於要求剛裝好的人自己想到去重開程式。
**每一個第一次用的人都會撞到**;開發機永遠有設定,所以永遠測不到。

修:`sup == nil` 時**現場建立**(設定剛出現,正是該啟動的時機)。
新測試 `TestFirstRunStartsEngineAfterConnect` 從「沒有設定」開始走完整順序;
反向驗證拿掉即紅。

## 順帶把那句沒用的話拿掉
「請結束 Arcrun 再重新開啟」=把系統的無能推給使用者。
改成「正在啟動同步引擎,請稍候…」;還沒連知識庫時說
「按『新增知識庫帳號』就會開始」。

## log 不再說謊(今天第三次同一個主題)
啟動訊息印 `cfg.triggerURL(...)`=用**根層**的 cypher_url/namespace 組的,
而多帳號設定根層是空的 ⇒ 印出 `→ /webhooks/named//rag_ingest/trigger`
(沒網域、雙斜線)**看起來像設定壞了,其實功能正常**。
改印每個帳號真正會用到的網址。

## 實測證據(共用資料夾自動收回來的 log,非截圖轉述)
  19:13:54  設定檔缺必填欄位,已自動補上 manifest=C:\Users\leo21\.arcrun-rag\manifest.json
  19:13:55  collector direct daemon 啟動:監看 …\win_on_mac_test_lib  {"phase":"start"}
⇒ v0.18.13 的自我修復在真機生效,config 已補上 manifest,collector 真的跑起來。
2026-08-06 19:33:36 +08:00
Leo 8dc9a2ee85 v0.18.13:舊設定檔自我修復——我第一次修錯了,只修好全新安裝
leo 裝了 v0.18.12 **仍然是同一句錯**(實拍:重試 30 次、
「collector direct: config 缺必填欄位:manifest」),並提供了他的 config.json。

## 我錯在哪
v0.18.11 只在 `saveCfg`(存檔)補必填欄位。但使用者打開程式時
**只是「讀 config → 啟動引擎」,saveCfg 根本不會被呼叫**
⇒ 已經存在的壞設定永遠修不好。全新安裝好了,**現有使用者一個都沒被救到**。

leo 的 config 鐵證:頂層只有 `accounts`/`extractor`/`extractor_explicit`/
`gemini_api_key` —— **正好是 saveCfg 寫的那四個鍵**,沒有 manifest。

## 為什麼我的測試沒抓到
`TestFreshInstallCollectorStarts` 是**從 saveCfg 出發**建 config ——
它測的是「我修的那條路」,不是「leo 正在走的那條路」。
**測試照著修正寫,就只會證明修正存在,不會證明問題解決。**

## 這版
- `fillRequired()` 抽出來,**讀取時也補**(升級路徑)+存檔時也補(新建路徑)
- 讀取時補完**寫回磁碟**——collector 是另一個行程,讀的是磁碟那份不是記憶體
- 補完寫進 app.log 留痕

## 驗
新測試 `TestExistingBrokenConfigSelfHeals` **從磁碟上已存在的壞 config 出發**
(內容逐字取自 leo 那份,只把值改假),驗三件:讀取後磁碟上有 manifest、
JSON 仍合法、**真的把執行檔當 collector 跑起來不再 exit 2**。
反向驗證:拿掉自我修復即紅(訊息就是 leo 撞到的那句)。全套測試綠。

⚠️ 更正 changelog:v0.18.11 原本寫「已經裝過舊版的人不用做任何事,更新後會自動補好」
   —— **那句是假的**,已改成指向本版。
2026-08-06 18:58:31 +08:00
Leo a22d4e4436 端到端驗證:全新安裝真的跑得起同步引擎(自己驗,不推給 leo)
自走警察點破:我把「在 Windows 全新裝一次確認」推給 leo,但這件事我自己驗得了。

新增 `TestFreshInstallCollectorStarts`:
全新 HOME → App 的 saveCfg 存 config → **編出真正的執行檔、用 `--collector direct
--once --dry-run` 把它自己跑起來** → 斷言不是 exit 2、輸出沒有「缺必填欄位」。

為什麼要再加這支(已經有 LoadDirectConfig 那支了):
**「函式回傳 nil」與「行程活得下去」是兩件事**——事故正是死在後者。

反向驗證(拿掉修正)重現 leo 截圖上那句一字不差的訊息:
    collector direct: config 缺必填欄位:manifest
⇒ 這支不是假測試。修正放回後全套綠。

順帶修測試自身的坑:先 `go build` 再改 HOME——先改 HOME 會讓 Go 把**唯讀的**
模組快取寫進暫存目錄,測試結束清不掉(TempDir RemoveAll permission denied)。

剩下真正只有 Windows 能驗的:實機安裝後的行為(托盤/畫面),已列 CP 待辦。
2026-08-06 18:54:14 +08:00
Leo 9f64615cfd v0.18.12:出問題時一鍵打開紀錄檔資料夾(leo:「不能用一個 debug mode?」)
leo 08-06:「不能用一個 debug mode,就是它會把 log 寫在一個檔案?」

## 回答:log 一直都在寫,缺的是「找得到」
`~/.arcrun-rag/collector.log`(含 collector 的 stderr,以 `[stderr]` 開頭)
與 `app.log` 從 t14 就在寫。這次 Windows 事故的真正死因
(`config 缺必填欄位:manifest`)**一直躺在 collector.log 裡**,
但使用者只看得到畫面上一句話,沒有任何入口通到那些檔案。

⇒ **不做「debug mode 開關」**(多一個要教使用者的東西,而且出事當下才想到要開就來不及了),
   改成**永遠寫、出事時一鍵打開**:
- `OpenLogFolder()` 綁定(Windows 走 explorer/Mac 走 open/其餘 xdg-open)
- 首頁在 `engineTrouble` 時才長出「需要回報這個問題?」卡,附按鈕與**路徑文字**
  (按鈕失效時仍找得到)。沒事時不顯示——否則會變常駐噪音,真出事反而沒人看。

## 真機證據:v0.18.10 的「說出死因」有效
leo 的 Windows 畫面實拍:
  「同步引擎一直啟動失敗 / 已自動重試 8 次都失敗,所以重新開啟也沒有用。
    原因:collector direct: config 缺必填欄位:manifest(exit status 2)」
⇒ **與 v0.18.11 修的根因一字不差**,診斷鏈完整閉合:
   症狀(閃爍)→ 機制(子行程 exit 2 無限重拉)→ 死因(config 缺 manifest)→ 修正。
2026-08-06 18:47:17 +08:00
Leo fcf00bb5df v0.18.11:修好 Windows「裝了完全不會動」的真兇——全新 config 缺 manifest 欄位
leo 08-06 提供決定性線索:**exit status 2,已自動重試 21 次失敗**。

## 真兇
`app.go` 的 `saveCfg` 只寫四個鍵(accounts/extractor/extractor_explicit/
gemini_api_key),**從來不寫 `manifest`**。
而 `manifest` 是 collector 的必填欄位(`direct.go`:
`if c.Manifest == "" { missing = append(missing, "manifest") }`)
⇒ 讀 config 失敗 → `runDirect` 回 2 → supervisor 無限重拉
⇒ ①「看守中/沒有在跑」一直閃、重開無效(死因沒變)
⇒ ②加資料夾沒反應(collector 從沒跑完一輪)

## 為什麼一路活到封測
**開發機的 config 是舊版留下的、早就有這一欄。**
只有「從零長出 config」的機器才會撞到——也就是**每一台封測者的機器**。
「我這台好好的」正是這個 bug 能活下來的原因。
⇒ 新測試 `TestFreshConfigIsUsableByCollector` **從零建 config**,
   並且不只驗欄位在不在,而是**真的餵給 `collector.LoadDirectConfig`**。
   已反向驗證:把修正拿掉,測試會紅(不是假測試)。

## 順帶修好「為什麼只看得到 exit status 2」
supervisor 在行程結束時用 `cmd.Wait()` 的錯誤當訊息,
**蓋掉**了 stderr 剛寫進 `LastError` 的那句真話
(`collector direct: config 缺必填欄位:manifest`)。
⇒ 改成以 stderr 為主、退出碼放括號補充。
(v0.18.10 已讓死因上畫面+進 app.log,這版讓那句話是**對的**那一句。)

## 三種 exit 2 的實測輸出(重現測試打出來的,非推測)
- config 不是合法 JSON → `config JSON 解析失敗:invalid character …`
- 缺必填欄位 → `config 缺必填欄位:manifest, accounts(或 cypher_url 連線設定)`
- Windows 反斜線路徑 → 正常解析(**排除「反斜線害的」這個猜測**)

## 驗
collector 全測試過、app 測試過、mac+windows 皆編得過;
產物 `Arcrun-win-v0.18.11.exe`(22M)已放 leo 桌面。
⚠️ **尚未真機驗**——要 leo 在 Windows 全新裝一次才算通。
2026-08-06 18:41:31 +08:00
Leo 6698f9fbda v0.18.10:讓「同步引擎沒在跑」說出死因(leo Windows 實測①②的診斷前置)
leo 08-06 Windows 回報:「一直在『看守中』和『沒有在跑』中間閃,要我重啟但重啟無效,
我覺得它在跑個迴圈不停重複」+「加一個資料夾明顯沒產生看守資料夾」。
朋友的一般 PC(非 ARM)也一樣,停在等待中 ⇒ **不是 ARM 模擬的問題,是 Windows 通用**。

## 已定位的機制(不是根因,是「為什麼查不出根因」)
閃爍=子行程一啟動就死 → supervisor 退避重拉 → 狀態在 Starting(alive=true)與
Error(false)之間彈跳 ⇒ 畫面跟著閃。而**死因一直被記在 Status().LastError 裡,
從來沒有上過畫面、也沒寫進 app.log** ⇒ 使用者只看到閃爍與「請重新開啟」,
重開當然無效(死因沒變)。這是「安靜地略過」的同一種病換地方發作。

## 這版做的
- 死因上畫面:重試 >= 3 次改顯示「同步引擎一直啟動失敗,已自動重試 N 次|原因:…」,
  且**黏住不閃**(不受重起過程中的 Starting 影響)
- 每次異常結束寫進 app.log(同錯誤 10 次內只記一次,避免洗爆)

## 排除掉的嫌疑(查過,不是)
- PDF 引擎(pdfium/wasm):**延遲初始化**,第一次讀 PDF 才啟動 ⇒ 不會在啟動時炸
- ARM 模擬:leo 朋友的一般 x64 PC 同樣症狀
- 待查嫌疑:Wails 的 Windows 版是 `-H windowsgui`(GUI subsystem,無主控台),
  而 v0.18.8 的 collector.exe 是 `go build` 的 console subsystem——
  v0.18.9 合併成單一 binary 後,子行程換成 GUI subsystem 的自己。**尚未證實。**

## 順帶修好自己的閘(它擋對了我)
指紋閘擋下「v0.18.10 已對應另一份原始碼」——因為第一次打包失敗、版號卻已被戳。
⇒ 加判準:**磁碟上沒有該版號產物=從未出貨,允許重戳**(不用去手改 JSON,
那種爛示範遲早被改成「都放行」)。

## 安裝程式(leo ③)尚未兌現,誠實記錄
`wails build -nsis` 需要 makensis;Homebrew 的 3.12 在這台 macOS **連兩行的最小腳本
都在寫檔階段丟 std::bad_alloc**,zlib/bzip2/lzma 三壓縮器全崩,brew extract 舊版
被 homebrew/core 擋。且帶 -nsis 會讓 wails 整個中止、連 exe 都不產
⇒ build-win.sh 改成**先煙霧測試 makensis**,壞的就只出裸 exe 並大聲警告。
2026-08-06 18:27:08 +08:00
Leo 0b8bc83bd1 版本指紋閘(同一版號只准一份原始碼,實測有效)+落帳封測兩病與 md 真相 2026-08-06 16:17:40 +08:00
Leo 67effe3b7f 首頁「沒被整理的檔案」修兩病:同一件事講兩次/不說是哪一個檔
leo 08-06 封測回報(附截圖+「他放一個 md 檔無法通過」)。

## ① 同一件事講兩次,而且「另外」前面沒有東西
截圖實況:
  「看起來不是文件,所以跳過了。」   ← 底下是空的(非文件檔不逐檔點名)
  「**另外**有 1 個不是文件的檔案(圖片、影片、壓縮檔之類)也沒有處理。」
真兇:app.go buildSkipped 的 else 分支先寫一句通用說明,
接著無條件再寫 Other 那句——後者的「另外」是為「兩種都有」寫的,
在「只有非文件檔」時就變成前面沒有東西可以「另外」。
解:該分支只講一句、自己帶數量,且不留 Other。「另外」只在兩種都有時出現。

## ② 只報總數=等於沒說(這才是 md 那題卡住的原因)
封測者放 .md 說「無法通過」,但畫面只寫「有 1 個不是文件的檔案」,
**沒說是哪一個** ⇒ 誰也判斷不出發生什麼事。
而 `.md` 明明在 allowedExt 白名單裡(scan.go:26),我在本機實測也一次就過:
    "path":"arcrun-md-test.md","status":"ingested","http_status":200
⇒ 那個「1 個」必然不是他以為的那個檔(副檔名被 Windows 藏起來、存成別的格式…),
  但沒有檔名就永遠查不出來。
解:scan 收集非文件檔的檔名(上限 5 個),一路帶到 status.json 與首頁。
「只報總數」在幾百張圖時是對的,在 1 個時是失職——少量就點名。

## 驗
· 兩支新測試釘住規則:只有非文件檔時不准出現第二句、不准出現「另外」;
  兩種都有時「另外」才成立並帶數量
· 實際文案打出來看過(見下),不是只看測試綠
    有 1 個檔案沒有被整理
    看起來不是文件(圖片、影片、壓縮檔之類),所以跳過了。這是正常的,你不用做什麼。
      · 我的筆記.md.txt
· 312 張圖的情況:列 5 個 +「…還有 307 個」,不洗版
· collector 全測試過、app 測試過、go vet 全綠
2026-08-06 16:12:01 +08:00
Leo 4d3a6a09a6 v0.18.9:collector 併進同一支執行檔——磁碟上不再攤出第二支 exe
leo 08-06 裁決:「不要兩支,寫成一支檔案」。

## 為什麼
v0.18.7-8 的「單一 exe」其實是**一支包著另一支**:collector.exe 被 go:embed
進 Arcrun.exe,執行時攤到 ~/.arcrun-rag/bin/ 再跑。
那正是防毒軟體眼中的 dropper 特徵 —— 封測者實撞
`Trojan:Win32/Sabsik.FL.A!ml`,檔案當場被隔離、自動刪除。

⚠️ 誠實界定:`!ml` 結尾=**機器學習判定**,Sabsik 是最常見的通用誤判家族,
   主因是「未簽章+下載次數少」,**不是**特別指向 dropper 行為。
   所以本次改動**不保證**解除誤判——真正的解是上架 MS Store(微軟簽章)。
   但「執行時把第二支 PE 寫到磁碟再執行」本來就該拿掉,這是對的方向且順手變小。

## 怎麼做
- `collector/` 39 個檔 `package main` → `package collector`,`main()` → 匯出的 `Run(args) int`
- 新增 `collector/cmd/collector/`(薄殼 CLI,讓單獨跑 collector 這條路仍可用)
- App 直接 import 該套件;`main()` 第一件事就判 `--collector`,是的話走 `collector.Run` 不碰 GUI
- `supervisor` 加 `ArgPrefix`,App 把 `BinPath` 指向 `os.Executable()` 自己
- 刪掉 `bundled_collector_{windows,other}.go`(embed + 攤檔那套)
- 三支打包腳本不再編/複製第二支;版本注入同時打到兩個 package
- build-win.sh 的機械閘改成**直接問它**:`--collector --version` 回得出版本才放行
  (舊閘是比大小,只能證明「有 embed」,證明不了「分派是對的」)

## 驗(真機實跑)
· `.app/Contents/MacOS/` 只有 **一個** 執行檔(原本兩個)
· 跑起來兩個行程是**同一個 exe**:
    …/MacOS/arcrun-app
    …/MacOS/arcrun-app --collector direct --config …
· `~/.arcrun-rag/bin` **不存在**(沒有任何東西被攤出來)
· 端到端:丟檔進看守資料夾 → collector.log `"status":"ingested","http_status":200`
· `lsappinfo` 仍是 `type="UIElement"`、`Version="0.18.9"`
· collector 39 檔測試全過;app 測試過;go vet 全綠;mac + windows 交叉編譯皆過
· 單檔 26MB → 22MB(不再夾帶第二份完整程式)
2026-08-06 16:00:47 +08:00
Leo f4eb3c9fa1 Mac 四修:Dock 不再重複/結束真的會結束/全螢幕可用/視窗 70%
leo 08-06 Mac 實測回報。四個都是**只在 Mac 現形**的病。

## ① Dock 與選單列兩邊都顯示(連帶:只有 Arcrun 列在強制結束清單)
真兇:Wails 在 AppDelegate.m:44 applicationWillFinishLaunching 硬設
`setActivationPolicy:Regular`,**runtime 蓋掉 Info.plist 的 LSUIElement**
(後者只決定啟動當下的初始 policy)。而 v2.13 的 mac.ActivationPolicy 是註解掉的。
🔴 **這件事 fyne 版 604704a(07-24)已經解過且真機驗證通過**,換 Wails 時整段掉了;
   wiki mistakes.md:124 也早就記著同一條。**本次是取回原版,不是重新發明**——
   dock_darwin.go/dock_other.go 逐字沿用,只更新註解說明 Wails 死法相同。

## ② 托盤右鍵「結束 Arcrun」點了不會結束
leo:「唯一結束法是強制結束…因為舊版沒結束導致無法覆蓋,
      **如果不知道怎麼強制結束的人就放棄了**」
真兇:Wails 的 runtime.Quit() 與「按 ×」**共用同一個 OnBeforeClose**
(frontend.go:364 `if !OnBeforeClose(ctx) { mainWindow.Quit() }`),
我們無條件回 true ⇒ mainWindow.Quit() 永遠不執行,還順手 WindowHide
⇒ 看起來像關了、其實行程還活著。
(AppDelegate.m:41 的 applicationShouldTerminate 也一律回 NSTerminateCancel,
  決定權整個在這個回呼 ⇒ 回 true 就沒有任何出口。)
解:quitting 旗標區分兩者,並抽出 closeMeansHide()/beginQuit() 讓它**可機械驗證**。

## ③ Mac 全螢幕(綠色)按鈕被停用
真兇同樣在 Wails:window.go:92 `zoomable` 只在 `frontendOptions.Mac != nil` 時賦值,
我們沒設 Mac ⇒ zoomable 維持零值 false ⇒ WailsContext.m:208
`if (!zoomable && resizable) { [zoomButton setEnabled:NO]; }`。Windows 無此段。
解:`Mac: &mac.Options{}`(空的即可,DisableZoom 預設 false)。

## ④ 視窗 50% 太小
leo:「Google Drive 大約螢幕長寬的 70%,改成 50% 後顯得太小」⇒ 常數 screenFraction=0.70。
並加 appLog 留痕(螢幕多大、算出多大),以後不必請人量像素。

## 驗(本機真機實跑,非推測)
· `lsappinfo` → **type="UIElement"**、Version="0.18.8"
  = 604704a 當年用的同一個驗法,通過 ⇒ Dock 無 icon、不列強制結束
· 截圖放大號誌燈 → **綠燈是彩色的(enabled)**,停用時會是灰色
· app.log 自報 → **視窗依螢幕 1470x956 設為 1029x669(70%)**
· 首頁顯示「看守中·資料夾有變動就會自動整理/已整理 1 份」=判活正常
· go test TestCloseMeansHide 通過(釘住「按×要隱藏、按結束要放行」)
· go vet 全綠;mac build + windows 交叉編譯皆 OK
· **未驗**:托盤右鍵實際點擊(osascript 缺輔助取用權限,點不到選單列)
  ⇒ 邏輯已單元測試+原始碼追到底,但**實際點擊歸 leo 真機**
2026-08-06 14:11:49 +08:00
Leo b783b870e6 版本號改由 changelog 決定:從頭到尾沒有人打過數字,且與更新內容天生綁定
## 為什麼推翻自己幾小時前的做法
上一版用「動過 collector/ 的 commit 數」當 patch。確實不用手打,但
**每個 commit 都變成一版**(一天 5 個 commit 就跳 5 版),
每跳一版還要補一段 changelog ⇒ 把機械化變成新的手工活。leo 要的不是這個。

## 現在的機制(daemon-version.py)
單一真相源=docs-site/.../help/changelog.md:
  · 要出新版 ⇒ 最上面加一段 `## 下一版(未發佈)`,底下寫白話更新內容
  · 打包時腳本把它**戳成正式版號**(上一版 patch+1)並補今天日期
  · 沒有「未發佈」段 ⇒ 版本=最上面那一版(重打同一版,冪等不虛增)
⇒ 版本號沒有人打過;且不可能「升了版卻沒人知道改什麼」,
  也不可能「寫了內容卻忘了升版」——兩者出自同一段文字。
換線(0.18→0.19)只改 DAEMON_LINE,下一版自動從 0.19.0 起。
刪掉 daemon-version.sh 與 DAEMON_PATCH_BASE(一版的殘留)。

## 驗(實測輸出)
· `## 下一版(未發佈)` --stamp→ `## v0.18.7(2026-08-06)`(腳本自己改的)
· 沒有未發佈段時重跑 → 仍回 v0.18.7(冪等,不虛增)
· ./build-win.sh 全程通 → dist/Arcrun-win-v0.18.7.exe(27,445,248 bytes)
    strings 抽內嵌版本 = v0.18.7
· ./build-dmg.sh 全程通 → dist/Arcrun-v0.18.7.dmg(10M)
    掛載後 strings 抽 App 內嵌版本 = v0.18.7
    Info.plist CFBundleShortVersionString = 0.18.7(以前永遠是 1.0.0)
⇒ **兩條打包線第一次在同一個版本號上對齊**——這正是 08-06 那個病
  (只重打 Mac DMG、Windows 沒動、manifest 卻宣告新版)的根治。
· 未驗:Windows/Mac 真機行為;未出貨 ⇒ ◐
2026-08-06 13:16:14 +08:00
Leo be817b3c16 更新內容機械化+manifest 補上「宣告版本==產物版本」的閘+前端顯示同步器版本
回應 leo 08-06 兩問與兩項新需求。

## ①「更新內容還要寫在 docs 裡,機械化要怎麼做」
答:改的是「寫在哪」,不是「誰來寫」。
- 單一真相源=docs-site/.../help/changelog.md(本來就存在,用戶語言那份)
- 新增 changelog-section.sh:抽出某版段落(供 manifest.notes 投影用)
  + --check 模式當閘
- build-win.sh/build-mac.sh 接上閘:**changelog 找不到這版段落 ⇒ 中止打包**
  ⇒「忘了寫更新內容」變成不可能,而不是靠誰記得
- 已寫 v0.18.8 的用戶語言版本說明(本次五修)

## ② manifest 的閘:這次的病是從兩條規則中間的縫穿過去的
verifyManifest 對 daemon 只驗 ①version 欄在不在 ②file 指的檔存不存在。
08-06 那次**兩條都過**(宣告 v0.18.5、`-v0.18.4.zip` 檔案真的存在)⇒ 全綠放行。
新增第三條:宣告版本必須等於產物真正的版本
  (a) 檔名要帶宣告的版本(光這條就足以擋下 08-06 那次)
  (b) .exe 再驗一層:版本字串要真的在二進位裡(檔名可改,內嵌版本改不了)

實測(用當時的真實 manifest 值重現):
  · 宣告 v0.18.5 + 檔名 -v0.18.4.zip →  擋下(mac/win 各一條)
  · 檔名改成 v0.18.8 但內容是 v0.18.7 那顆 →  擋下(規則 b)

## ③ 前端顯示同步器版本+下載入口
leo:「連我都沒辦法確認,所以用戶到底是否最新版他自己也不知道」
- /api/latest 新增 daemon 欄(version/notes/downloads),由 daemonOf(env) 讀釘點
  manifest——與 releaseOf 同原則同真相源,不留手抄本。
  下載網址走 raw + 釘點 sha,與 selfupdate.go:158 同源(不自創第二條路)。
- rag.arcrun.dev 步驟 4 補上**真的下載按鈕**(Win/Mac)+同步器版本+
  「這一版改了什麼」連結。以前這裡只有文案提到下載、沒有連結也沒有版本。
  取不到就顯示「(查詢中)」並退回安裝說明頁——沿用 bf06ed7 的原則,不留手抄值。

## 驗
· node --check worker.js(installer/landing)皆 OK
· changelog 閘:v0.18.6 通過;不存在的 v0.18.7 → 退出碼 1 並印出可照抄的範本
· manifest 三條規則實測如上
· 未驗:線上畫面(要出貨後才有 daemon 欄位)⇒ ◐,不是 
2026-08-06 13:12:48 +08:00
Leo a55488040d Windows 交付改單一 exe+版本號不再手打(leo 08-06 回報②與「整串該機械化」)
## ② 兩個檔很迷惑 → 一個檔
leo 圖一標註:「下載還是 ZIP ⇒ 應該是 MSIX 或 EXE;解開還是 2 個檔 ⇒ 應該只有 1 個檔案」。
舊做法 zip 裡放 Arcrun.exe+arcrun-collector.exe 同層,少解一個檔就永遠不會同步,
而畫面不會說是這個原因。
修:collector 用 go:embed 收進 Arcrun.exe,第一次執行攤到 ~/.arcrun-rag/bin/
    (冪等:sha256 相同不重寫;先寫暫存再 rename,避免留半截執行檔)。
    build-win.sh 直接產單一 Arcrun-win-<ver>.exe,不再打 zip。
    加機械閘:產出的 exe 若比 collector 還小 ⇒ 沒 embed 成功 ⇒ 中止(過去只會
    默默得到一顆「裝了也不會同步」的空殼)。
不走 MSIX:未上架的 MSIX 要使用者先開「開發人員模式」,比 zip 更麻煩。

## 版本號機械化(leo:「這一整串都應該是機械化,不應該每次手工做」)
病根實測:本 repo **一個 git tag 都沒有**,而四支 build 腳本寫的是
`git describe --tags` ⇒ 永遠拿不到 v0.18.x ⇒ 只能靠打包時人工敲 VERSION=
⇒ 兩條打包線各敲各的 ⇒ manifest 宣告 0.18.5、產物其實是 0.18.4。

修:新增 daemon-version.sh 當唯一產生器,沿用 release.mjs 已在跑的原則
   (「版本號由內容算出來,不是由人宣告」),不另立新制:
     MINOR ← DAEMON_LINE(人決定,大改版才動)
     PATCH ← 動過 collector/ 的 commit 數 − DAEMON_PATCH_BASE(機器決定)
   ⇒ 沒改 daemon 重跑打包不會虛增;改了才 +1;四支腳本在同一 commit 必得同號。
   ⇒ 不需要 git tag/Actions/輪詢(守 D20 紅線)。
   校準:DAEMON_LINE=0.18、BASE=87 ⇒ 現在的樹算出 v0.18.7(上次出貨過 0.18.6)。
   build-win/mac/dmg/msix 四支全部改用它。

## icon 漂移根治接上打包
build-win.sh 步驟⓪ 每次都跑 gen-icons.py 重產 icon.ico/trayicon.ico。

## 驗(實測輸出,非推測)
· ./build-win.sh 全程跑通,產出 dist/Arcrun-win-v0.18.7.exe(27,445,248 bytes)
· strings 抽內嵌版本 = v0.18.7、buildTime = 20260806-1233(機器算的,沒人敲)
· 位元組比對該 exe:
    CIS trayicon 在裡面        = True
    Wails「W」圖還在           = False
    collector 有 embed 進去    = True(16,104,960 bytes)
· go vet 全綠(darwin + windows);mac build OK
· 未驗:Windows 真機行為(需封測者實跑)⇒ 狀態 ◐,不是 
· 未做:CHANGELOG 閘、manifest「宣告版本 == 產物版本」驗證閘、前端顯示版本與下載入口
2026-08-06 12:35:54 +08:00
Leo ee2fc2a803 Windows 三修:不再無限多開/首頁不再謊報「引擎沒在跑」/視窗改螢幕 50%
leo 08-06 封測回報①④+圖三標註。三個都是**只在 Windows 現形**的病。

## ④ 托盤一次開好幾個 icon
leo:「點一次開一個新的,測試者原本給我看顯示到 4 個」。
真兇:main.go 的 options.App 沒設 SingleInstanceLock(Wails v2.13 有這選項)。
每個實例還各自拉一份 collector 子行程。
macOS 由 LaunchServices 天然單實例 ⇒ Mac 上驗不出來。
修:加 SingleInstanceLock,第二次啟動叫醒既有視窗後自己退出。

## ① 根本不運行(首頁恆顯示「同步引擎沒有在跑」)
真兇:collectorAlive() 的憑據是 `pgrep -f <bin>`,**Windows 沒有 pgrep**
⇒ 恆回 false ⇒ app.go:356 一定走「同步引擎沒有在跑」,與實際狀態無關。
修:憑據換成 supervisor 自己的狀態機(親手拉起的子行程、runOnce 以 cmd.Wait 收尾)
    ——比掃行程表更有憑據且跨平台,順帶修掉 pgrep 會匹配到別的實例的偽陽性。
⚠️ 不是發明第三種判斷法:db17f28(08-05)已為「同步中」立下同一條路
   「改讀 sup.Status().State ⇒ 接回既有機制,不發明第三種判斷法」,
   當時只改了 collectorSyncing 那半邊,這裡把漏掉的 collectorAlive 補上。
順帶移除 waitCollectorGone/pkill:翻 supervisor 原始碼確認 Stop() 回來時
子行程已被 cmd.Wait 收屍完畢,補刀多餘;且那兩支指令 Windows 根本沒有。

## 視窗高度擠出畫面
leo 圖三:「視窗尺寸應該是 default 50% 螢幕長寬,高度太高快擠到外面了」。
修:OnStartup 依 ScreenGetAll 算 50% 並置中;保底值 1000x700 → 960x600
    (拿不到螢幕資訊時不瞎猜,沿用保底值)。

## 驗(實測輸出)
· go vet 全綠(darwin + GOOS=windows 各一次)
· mac build OK;GOOS=windows CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc 交叉編譯 OK
· 位元組比對交叉編譯出的 .exe:
    新 CIS trayicon.ico 在 exe 裡 = True
    舊 Wails「W」圖在 exe 裡    = False
· 未驗:Windows 真機行為(需重打包後由封測者確認)⇒ 狀態 ◐,不是 
2026-08-06 12:20:25 +08:00
Leo 051c865085 Windows 版 icon 換回 CIS chevron,並根治「換了 appicon 卻沒換 Windows」
leo 08-06 封測回報:托盤與視窗 icon 是「W」,不是 CIS 的 chevron。

真兇:build/windows/icon.ico 是 wails init 生成後從沒被動過的 Wails 預設圖。
換 CIS logo 那次只換了 build/appicon.png,Windows 這顆沒人碰;
tray_windows.go 又 embed 同一顆 ⇒ 視窗與托盤兩處都是 W。
macOS 走 trayicon.png(正確)⇒ 這個病在 Mac 上 100% 驗不出來。

改法不是「這次手動換一顆」,是讓它不可能再漂移:
- 新增 gen-icons.py:兩顆 .ico 一律由 appicon.png 現場產生;
  --check 模式比對「committed == 重新產生的」,可當機械閘。
- tray_windows.go 改 embed build/trayicon.ico(小尺寸階梯,系統匣只吃 16/32)。

已驗:重新產生後把 256/32 都渲成 PNG 目視確認為 CIS chevron;--check 通過。
未驗:Windows 真機(需重打包後由封測者確認)⇒ 狀態 ◐,不是 。

同時落帳(wiki status/mistakes):
- manifest 宣告 v0.18.5 但產物是 v0.18.4 的完整證據鏈
- 五個 Windows 真兇(檔:行)
- leo 兩問的設計定案:trigger=內容指紋(不另立、不用 tag/Actions)、
  changelog 單一真相源=CHANGELOG.md 其餘全投影
- 這次穿過機械閘的那道縫:缺「宣告版本 == 產物內嵌版本」的驗證
2026-08-06 12:09:53 +08:00
Leo 9cd0adab22 G-6.2:讀不了的檔案不再安靜消失——首頁當場說出來
服務 J-1 / S6「我丟進去的檔案,查得到」的考題 G-6.2:
「要嘛查得到,要嘛**當場被告知這種檔案還不支援**,不准安靜地略過。」
本次只做後半句(轉檔本體另有人閘,等 leo 裁「那段 Go 住哪裡」)。

## 真實現況比記載更糟
t16 寫的「PDF 被靜默略過」已不成立(t73 的轉檔層把 pdf/docx/xlsx/csv/pptx
都接上了,實測 2 頁中文 PDF 完整抽出)。**真正還在沉默的是別的東西**:
副檔名不在 allowedExt 的檔案在 scan.go 直接 `return nil`——
不進事件、不進 manifest、不進 status、不進畫面。
使用者丟一份 .doc 進去,從頭到尾一個字都沒有。

實測基線(真檔):丟 .pdf/.md/.doc/.key/.jpg 進資料夾,
`collector scan` 只吐出 pdf 與 md 兩個事件,另外三個檔沒留下任何痕跡。

## 改了什麼
- scan.go:白名單閘不再是死巷。像文件的(.doc/.xls/.ppt/.pages/.key/
  .numbers/.odt/.ods/.odp/.rtf/.epub/.wpd/.msg/.eml)逐檔留名;
  其餘(圖片/影音/程式碼)只計總數——**避免 Obsidian 附件庫炸出幾百行噪音**。
- 兩個新欄位標 `json:"-"`:collector-trigger schema 是
  additionalProperties:false,且 BuildSendablePayload 是淺拷貝
  ⇒ 有 tag 就會漏到雲端被擋。這是給本機使用者看的,不上 wire。
- direct.go → status.json → App 首頁一張卡:講檔名與格式(「舊版報告.doc
  (舊版 Word)」),並告訴他不用重丟、也給替代路(另存成 PDF/.docx)。
- 刻意不進 manifest、不走 CarryForwardActivity:每輪由檔案系統重算,
  不製造 t195 那種「跨輪欄位漏 carry 就靜默歸零」的債。

## 順手修掉一個會讓驗收失效的回歸
同綑的 arcrun-collector 一路自稱 `dev`:退役 fyne 版
(arcrun-tray/build-mac.sh:24,t150/t72)本來就有版本注入,
t194 換 Wails 時沒帶過來,Mac 與 Windows 兩邊都掉。
**幹活的是 collector**——它不報版本,就沒人能判斷修復有沒有到使用者手上。

## 實測
- go test ./... 135 過 0 敗(新增 10 條:scan 5+首頁文案 5)
- check-cis / check-render / check-tray 三閘全過;淺色深色都抓過畫面
- 從 **DMG 裡那支** collector 實跑:Info.plist 0.18.6、
  collector 回報 v0.18.6 (build 20260806-0101)、
  status.json 吐出 skipped_docs=[簡報.key, 舊版報告.doc]、other=1

⚠️ 未出貨:推 bundles repo 在 GitHub,要 leo 開 D20 閘。
DMG 已備妥 dist/Arcrun-v0.18.6.dmg。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 01:04:25 +08:00
Leo f32e82bab5 清掉三個假 Arcrun:退役 fyne 版 .app 不再進版控
leo 08-05:Launchpad 搜 arc 跑出 4 個 Arcrun,「這很奇怪」。

## 實查:只有一個是真的
  /Applications/Arcrun.app                        v0.18.5  ← 真正安裝的
  collector/cmd/arcrun-app/build/bin/Arcrun.app   v0.18.5  ← 建置產物(未版控)
  collector/cmd/arcrun-tray/Arcrun.app            v0.0.1   ← **退役 fyne 版,被 commit 進 repo**
  collector/cmd/arcrun-tray/Arcrun RAG.app        v0.0.1   ← 同上,更舊的名字

Spotlight 索引磁碟上任何 .app ⇒ repo 裡的產物全冒到 Launchpad。
危險在於**點到 v0.0.1 那兩個就是 t184 的病**(在非 /Applications 執行 ⇒ 更新不了),
等於我們自己的 repo 天天在給用戶擺陷阱。

## 處置
· git rm 兩個 v0.0.1 的 .app(build 產物本來就不該版控;Wails 換代後 arcrun-tray 已退役)
· 刪掉磁碟上三個非安裝版本
· .gitignore 加 collector/cmd/**/*.app
· 兩個 build 目錄放 .metadata_never_index,讓 Spotlight 不再索引產物

驗:mdfind 'Arcrun*.app' 現在只回 /Applications/Arcrun.app 一個。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 18:47:57 +08:00