All notable changes to JD2-Logbook will be documented in this file.
Format: [vX.Y.Z] — YYYY-MM-DD
dive log,scuba,logbook,
Shearwater,UDDF,Garmin,Suunto,Subsurface,nitrox,dive computer,freediving)
被電腦錶品牌名稱佔掉大半版位,一般用戶的「潛水日誌」核心搜尋意圖詞沒有
被覆蓋到。已記入 V1_RELEASE_CHECKLIST.md「下一版重點工作」列為首要
任務,下次改版送審前需重新設計三語各自的關鍵字策略。V1_2_BACKLOG.md #1 先前記錄 2026-07-25 已在 ASC 後台將 Coarse
Location/Device ID/Usage Data 的追蹤用途改為 No,但本次直接在 ASC 網頁
逐格點開確認,三格的「Purpose」問卷實際仍勾選著「Third-Party
Advertising」(Apple 定義下這個勾選即等同宣告追蹤,與先前記錄的「used to
track: No」是不同的問卷欄位,先前的操作大概率是改到後面的追蹤問題但漏了
前面的用途勾選)。程式碼面複查結果不變(全專案無 ATT 實作、無 IDFA 存取、
AdMob 用預設 Request() 未傳遞定位/裝置資料、CoreLocation 僅本機一次性
requestLocation()、無任何網路請求程式碼),確認 App 本身沒有追蹤行為,
問題出在宣告本身。三格皆改為僅勾選「App Functionality」,逐一走完
Linked-to-identity(No)與 Tracking(No)兩個子問題後 Publish,並重新整理
頁面截圖驗證三格摘要皆顯示「Used for App Functionality」。docs/APPSTORE_COPY.md 最終版本,將 Description/Keywords/
Promotional Text 套用到 iOS App 1.2 三語頁面;套用日文時一度誤操作
(語言下拉選單點擊未即時生效,文字打進了尚未真正切換過去的繁中頁面),
幸好尚未存檔即發現並改用重新整理捨棄,改以逐次確認下拉選單勾選狀態後才
輸入文字的方式修正流程。macOS App 1.2 另建版本後,比照套用同一份三語
文案+新版 Screenshots(含使用者自行調整過的正確尺寸)+What’s New
文案。String(localized:) 讀系統
Locale.current,語言切換後不重開 App 不會反映;Text(LocalizedStringKey)/
languageManager.localized(_:) 才會即時跟隨。全專案掃過 SettingsView/
DiveLogEditSheet/DiveLogDetailView/DiveCalendarView/MapView/
DiveAnalysisView/DiveLogListView/MainTabView/LogbookContainerView/
DiveMapRepresentable/DiveSiteAnnotation(非 View 型別,改吃呼叫端已解析
字串)/GasMix+LocalizedDisplay.swift,全部改對,實際呼叫 0 處殘留。ImportWizardView 進度/結果畫面完全未走在地化機制(v1.1 port 就存在的舊
bug,非本次回歸):"File X of Y"/"N dive(s) imported"/"N skipped
(duplicates)" 原本是純字串插值,永遠顯示英文;旁邊早已有 18 語言完整翻譯的
key 閒置沒被呼叫。已重新接回既有 key,不需要新翻譯。DiveLogEditSheet
的 Gas picker 不支援編輯 trimix,但 save() 原本無條件用 picker 顯示值(固定
降級成 Air)覆寫 dive.gasMixJSON——只要對任何 trimix 潛水按 Edit 再 Save
(哪怕只改備註),氣體資料就不可逆遺失成 Air。已修:記住原始 trimix JSON,
save() 時維持不動;Gas picker 對 trimix 潛水加 .disabled() + 說明文字避免
誤導。UnitSystem 新增 convertWeight/
convertPressure(kg↔lbs、bar↔psi),修復 DiveLogEditSheet(v1.2 #4 的
漏網之魚)與獨立寫死的 DiveLogDetailView(唯讀詳情頁自己另外硬編碼
"%.1f kg"/"%.0f bar",跟編輯表單的問題各自獨立、互不相關);DiveLogListView
的「Deepest」統計卡片同樣硬編碼 %.1fm 一併修掉。airTemperature/visibility
的「nil = 未記錄」寫法統一處理。maxDepth 因是必填欄位改用 0 當「不可能值」
哨兵(借用既有 isSaveEnabled 驗證關卡);waterTemperature 因沒有安全的
「不可能值」可借用,維持原預設(PM 確認)。calloutCell label 加 .minimumScaleFactor 當版面保底(過長
文字縮小字級而非省略號截斷成看不懂的內容);能在同檔案內找到既有先例的(如
西班牙文 Ceiling 比照葡萄牙文「Teto」、法文 No Deco 比照西/葡/義的
「[without]+deco」模式)直接移除冗餘用詞,不硬猜的留給母語審核(僅剩希臘文/
克羅埃西亞文 No Deco)。JD2Core/Utilities/Extensions.swift 整個檔案(21 個符號,逐一
驗證含測試目標皆 0 呼叫端後確認是 v1.0 舊架構遺留)連同 DiveLog.averageAscentRate
一併移除,不留 // removed 註解。DiveReplayEngine.tissueLoadPercent 效能修正:原本每次呼叫都重建一個
Buhlmann 實例,只為了讀兩個跟 environment 無關的常數欄位(gfHigh/
compartments[].aN2/bN2);互動拖曳剖面時每個拖曳幀都觸發,等於每幀都重新
配置一次 16 隔室陣列。改為快取,只有 environment 真的變了才重建。code_audit_report-0726.md)主要問題屬實,如上已修復;第二輪
(audit_report-0726-R2.md 及其修訂版 -rev3.md)發現多項報告本身的幻覺
內容(例如宣稱只支援「6 種語言」,實際 18 種;多個「未本地化」的具體指控
查證後其實是透過 SwiftUI 自動 Text(LocalizedStringKey) 正確運作,報告作者
不理解這個機制;部分程式碼片段直接編造、檔案裡查無此行),逐項記錄查證結果
於 V1_2_BACKLOG.md,不照單全收。DiveRowView/DiveLogDetailView/DiveSiteSheetView/
DiveAnalysisView 的 calloutRow/DiveSiteAnnotation/DiveMapRepresentable
(非 SwiftUI View context,改直讀 UserDefaults)/DiveLogEditSheet(新增
4 個雙向 Binding computed property供輸入欄位換算)。calloutRow 固定 5 欄排版,缺資料的 Ceiling/No Deco 才用「—」佔位,維持互動;
組織艙圖用帶 info.circle 圖示的說明文字取代,明確標示是已知限制非程式錯誤。
順便修好狀態列文字大小忽大忽小的問題(minimumScaleFactor 導致 5 欄各自獨立
縮放不一致,改用固定不縮放字級+關閉插入動畫)。Info.plist 的 LSApplicationCategoryType
從 public.app-category.sports-games(Apple 分類系統裡其實是「Games→Sports」
子分類,非獨立的「Sports」)改為 public.app-category.healthcare-fitness;
App Store Connect 的 Category 欄位同步更新。iOS + macOS 皆已用真機/直接雙擊
安裝版驗證修復生效(排除是 Xcode cmd+R 偵錯階段特有現象後確認)。AdUnitID 的 DEBUG 分支改用 Google 官方測試 Ad Unit ID(不綁裝置),
取代原本註冊 testDeviceIdentifiers 的做法(裝置 identifierForVendor 在整個
刪除重裝 App 後會重新產生,測 Restore Purchase 這類情境時裝置 ID 一直變)。refreshPurchaseStatus() 每次啟動
都會覆蓋回 true,開關永遠關不掉)。_JD2-family/00_Import_test_scenes/
建 5 個情境資料夾,涵蓋黃金路徑、單檔多潛水、8 格式批次混合、去重/錯誤處理、
trimix/ZIP 包裝/跨品牌相容性等特殊情況,全數通過。過程發現 DiveImportKit
的匯入失敗錯誤訊息全數寫死繁體中文(跨三個 App),依家族鐵律回報而非自行
修改,記錄於 _JD2-family/reports/R-2026-07-25-DiveImportKit錯誤訊息未本地化.md;
家族總管已處理第一階段(6 個錯誤樣板改英文,DiveImportKit v0.4.0→v0.4.1)。
同時補上 F-07-IMPORT_FORMAT_COVERAGE.md 的 Garmin Connect JSON(首次有資料
驗證過)、DAN DL7/Divesoft DLF v2/Garmin FIT 跨廠牌拒收的 E2E 驗證紀錄。SettingsView.swift 加 showBackupSection = false feature flag,UI 隱藏、
程式碼保留,下一版驗證後開放。MARKETING_VERSION 1.0 → 1.2,CURRENT_PROJECT_VERSION 2 → 3。iosApp/JoyDive 舊專案、JD2-ultra/JD2-immersion 的舊 DerivedData)。DiveReplayEngine.replay()
新增上升速度追蹤,門檻與文案數值比照統一 DiveKit AlgorithmConstants
(10 m/min、連續 5s→Ascent Rate Alert、連續 10s→Mandatory Safety Stop),
新增 ReplayWarning/ReplayWarningKind,不綁在既有 ReplayPoint 上(獨立
清單,避免兩種警示落在同一樣本區間互相覆蓋)。DiveAnalysisView 新增:
①曲線上的彩色圓點標記(chartOverlay 疊加,沿用時間軸偏移修復的
plotAreaFrame 校正手法)②狀態資訊列下方第二列——選取點命中警示事件時顯示
事件卡片(圖示+標題+描述+右側深度/時間)。4 個新翻譯 key 補齊 18 語言。ImportWizardView.swift
用 String(localized:)(純 Foundation API,只認系統語言)而非
languageManager.localized(_:)(吃 App 內語言切換器)——v1.1 #8 修復當時這批
Import 格式清單程式碼還沒寫,沒被涵蓋到。全面替換,新增缺漏的
“Import Completed with Issues” 翻譯 key。DiveAnalysisView 的 chartOverlay 把 Swift
Charts 的 proxy.position(forX:)/proxy.value(atX:)(相對繪圖區域座標,
不含左側 Y 軸刻度標籤寬度)直接當成 GeometryReader 的座標系使用,導致選取線
與拖曳命中整體往左偏移一個刻度標籤寬度(使用者截圖佐證:選取線飄移到 0 分鐘
外面)。改用 geo[proxy.plotAreaFrame] 校正 origin。JD2-Ultra companion 的
同款程式碼有一樣的 bug,已記錄到 SYNC_TO_JD2-ULTRA.md #6。JD2Core/Models/UnitSystem.swift(metric/imperial 列舉+深度/溫度轉換函式)
+ Settings 頁單位系統切換,依指示不用文字、直接用符號表示(m / °C vs
ft / °F),@AppStorage 持久化。⚠️ 本次只做設定頁選項本身,尚未展開到全
App 各處顯示/輸入欄位套用,詳見 V1_2_BACKLOG.md #4。iOS 1.0 (Build 2) 送審遭 Apple 駁回,兩項理由:
AppTrackingTransparency
引用、Info.plist 無 NSUserTrackingUsageDescription、AdMob 用預設
Request()(無 IDFA/個人化廣告設定)、CoreLocation 僅供地圖「回到我的
位置」一次性定位、資料不外傳。確認 App 實際上沒有追蹤行為,ASC 隱私標籤
應是誤填(很可能是照抄 logbook/privacy.md 舊文案填的,該文案本身就寫了
「IDFA 僅在授權後」「可透過 ATT 重設追蹤授權」等從未真正實作的敘述)。
修正 logbook/privacy.md 三語版本(EN/繁中/日文),移除與實際行為不符的
IDFA/ATT 敘述,改為明確聲明不使用 IDFA、不請求 ATT 授權、廣告非個人化投放。
待使用者執行:ASC App Privacy 問卷重新填寫(Location/Device ID/Usage
Data 取消「used to track」)+ Review Notes 註明已修正。詳見 V1_2_BACKLOG.md #1。Deepblu 公司已停業,官方 App 從未支援直接匯出 .json/.csv(2026-07-19 網路
查證確認),唯一取得結構化資料的路徑是社群第三方工具 deepblu-tools,但需要
使用者自己的 Deepblu 帳號——確認沒有帳號,此路徑不可行。加上現有樣本自建立
以來就是模擬資料,DeepbluCOSMIQParser 從未被真實資料驗證過,PM 決定直接
移除格式支援,不再長期掛著一個無法驗證、公司已消失的格式。
移除範圍:DiveLogFormat.deepblu case(含 supportedExtensions/priority)、
DeepbluCOSMIQParser.swift、DeepbluCOSMIQParserTests.swift、匯入畫面選單裡
的「Deepblu COSMIQ+」項目、DiveLogDetailView 的 sourceFormat 顯示標籤。
ARCHITECTURE.md 同步移除對應解析器列(該列原本宣稱「已對真實樣本驗證」,
本來就是錯的)。JD2-LogbookTests 全套件 205 通過/15 略過/0 失敗。詳見
_JD2-family/decisions/2026-07-19_移除Deepblu支援.md。
Dive_2026-06-04-0819.xmlF-07 待辦 6 原本列了 2 個確認遺失的 D4i 檔案(0948/0819),0948 已於同日
稍早補齊,0819 使用者補上傳後也已歸檔驗證(SuuntoDM5XMLParserTests 新增
1 個真實樣本斷言,全綠),待辦 6 全數銷項。另外,4 個 .sde 匯出重新下載後
依然是 0 bytes 空檔,非傳輸偶發問題,疑似 DM5 軟體端 .sde 匯出功能本身有
問題,使用者決定暫緩擱置,已記錄於 F-07 待辦表。
使用者提供同一支錶(序號 99723006)4 次潛水的多格式匯出:4 個 SML、3 個新
DM5 XML(其中 Dive_2026-06-03-0948.xml 正是先前 F-07 確認遺失、待辦要 PM
重新匯出的那個檔案,本次補齊)、2 個真實 Suunto App JSON、2 個 Suunto FIT;
4 個 .sde 匯出皆為 0 bytes 空檔,未歸檔(待使用者確認匯出流程問題)。三方
(SML/DM5 XML/JSON)交叉核對同一批潛水的深度/時長/氣體數值一致。
修復 1:SuuntoJSONParser 樣本剖面靜默遺失——真機的 Suunto App JSON 匯出
樣本點只有絕對時間戳 TimeISO8601,從未出現解析器原本唯一支援的相對秒數
Time 欄位,導致 profileSamples 永遠是空陣列(dive 匯入成功但深度剖面圖是
空的,不報錯)。已修復:改用 TimeISO8601 - Header.DateTime 反推相對秒數,
Time 欄位保留作 fallback。這也是本次順便補齊的最急迫格式缺口——Suunto
JSON 先前連假資料都沒有,是全格式中驗證狀態最差者,SuuntoJSONParserTests
新增 3 個測試(2 真實樣本+1 迴歸測試)全綠。
修復 2:GarminDescentParser 誤接受非 Garmin 廠牌的 FIT 檔案——用 2 個真實
Suunto FIT 匯出驗證時發現,canHandle 原本只驗證 FIT magic bytes(通用容器
格式),會誤判非 Garmin 廠牌的 .fit 為可解析;解析時因缺少 Garmin 專屬的
dive_gas(GMN 269)訊息,gasMixJSON 靜默退回預設值 "air",但深度/時長
(來自通用的 session GMN 18)看起來完全正常、不會報錯——實測兩筆皆為
Nitrox 30%,被誤判成 Air,是最危險的一種靜默資料錯誤。已修復:新增
file_id.manufacturer 檢查,非 Garmin 廠牌在 canHandle/validateContent
階段明確拒絕,parse 拋出寫明實際廠牌的 unsupportedFormat 錯誤。
GarminFITParserTests 新增 2 個迴歸測試全綠。Suunto FIT 樣本已歸檔為負向
測試 fixture,不會被任何解析器誤用。
真實樣本已歸檔至 _JD2-family/dive-log-samples/Suunto/(原始檔,SML/DM5/JSON/
FIT 各自子目錄)與 _JD2-family/00_Import_samples/(Suunto_SML 更新、
Suunto_DM5 新增第 3 筆、新增 Suunto_JSON/ 首次涵蓋此格式)。
JD2-LogbookTests 全套件 209 通過/15 略過/0 失敗。詳見
_JD2-family/F-07-IMPORT_FORMAT_COVERAGE.md Suunto 各行。
使用者提供 2 個真實 Moveslink 裝置匯出(序號 99723006,2021-09-01/2021-09-04,
共 141/156 筆採樣),取代先前 F-07 稽核確認的模擬樣本。實測發現
SuuntoSMLParser.parseISO8601 對真機常見的無時區 DateTime(如
2021-09-01T15:14:26,不含 Z/offset)一律回傳 nil,導致整筆解析失敗——
舊的模擬樣本因誤植了 Z 後綴而長期未曝光此問題。已修復(加無時區格式
fallback,與 ShearwaterXMLParser/UDDFParser 既有慣例一致);另外發現真機
Header 其實有 <Depth><Max> 欄位(舊文件誤記「Header 沒有 MaxDepth」),已更正
檔頭註解,解析行為本身不變(仍從樣本點推算 maxDepth,較穩健)。
SuuntoSMLParserTests 新增 3 個測試(2 個真實樣本斷言+1 個無時區迴歸測試)
全綠。真實樣本已歸檔至 _JD2-family/dive-log-samples/Suunto/SML/(原始檔)與
_JD2-family/00_Import_samples/Suunto_SML/(改日期版,取代原模擬樣本)。詳見
_JD2-family/F-07-IMPORT_FORMAT_COVERAGE.md Suunto 表格。
家族樣本庫 DL7.zxu 從截斷版(誤植,只有 1 筆採樣點)補回 Subsurface 官方完整
內容(3 組潛水記錄+1 個真實 ZDP{...} 剖面區塊)後,發現解析器完全無法正確
處理:① ZDH/ZDT 配對用錯欄位(fields[1] 應為 fields[2]),導致唯一帶
剖面資料的那筆潛水配對失敗被靜默丟棄;② ZDP{...ZDP} 多行區塊語法完全不被
支援,只認合成測試用的單行格式。依 PyDL7 開源實作核實欄位語意後修正兩者,
新增區塊語法解析(與既有單行語法並存)。修復後正確解析出 3 筆潛水,ZDP
剖面路徑首次被真實資料驗證通過。iOS+macOS build 成功、DANDL7ParserTests
全綠、全套件無回歸。詳見 _JD2-family/F-07-IMPORT_FORMAT_COVERAGE.md 第七節。
實測 00_Import_samples(20 個真實樣本)批次匯入時發現 UI 只顯示
「Import Successful」,完全不顯示哪些檔案失敗。追查根因:
ImportWizardView.runBatchImport 逐檔錯誤只記 firstError(第 2 個以後的失敗
直接丟棄),只要批次裡有一筆成功就跳到 .success;skipped 也永遠寫死 0,
即使 ImportCoordinator 內部確實算出 dedup 略過筆數,只印到 console 沒回傳。
ImportCoordinator.importFile 回傳型別改 ImportFileResult(dives +
skippedDuplicates),把原本只印 console 的 dedup 筆數一併回傳。ImportWizardView:新增 ImportFailure(檔名+原因);ImportStep.success
加 failures 參數;runBatchImport 改單一 catch block 蒐集全部失敗
(含原本被特殊跳過的 emptyFile),不再只留第一個;成功畫面依
failures/count 三態呈現(全成功綠勾勾/部分完成橘色警示+失敗清單/
全失敗紅色 X+失敗清單),不再有任何一筆錯誤資訊被靜默丟棄。00_Import_samples 全部 20 個
真實檔案批次匯入 100% 成功(0 失敗——搭配同日 DiveImportKit v0.2.1 的
Seabear/SubsurfaceCSV 格式覆蓋補強後,先前的 4 個失敗樣本已全部修正)。分支 feature/import-failure-visibility(總指揮驗收後 merge)。
家族 F6(Importers 合流)第一步:Logbook 端採用新建的 ../DiveImportKit
(獨立 git repo,v0.1.0,130 tests 全綠),5 個已抽取的格式解析器
(UDDF/Subsurface XML/Subsurface CSV/Shearwater/Seabear CSV)改為
Kit 引用+App 端薄包裝,App 內對應實作刪除。分支
feature/F6-shared-import-kit,三個 commit(總指揮驗收後 merge):
project.pbxproj 新增 ../DiveImportKit local package(主 target +
Tests target,比照 F5 DiveKit 手法;plutil -lint + resolvePackageDependencies
兩道驗證通過)。JD2Core/Importers/DiveImportKitAdapter.swift——全 App 唯一
import DiveImportKit 的檔案(Kit 與本地型別同名,只在 adapter 內以
DiveImportKit. 前綴限定,避免全面歧義):
ParsedDiveLog → DiveLog 逐欄位對映;profileSamples 陣列編回
profileSamplesJSON(短鍵 t/d/w)、importExtras 陣列走既有
buildImportExtrasJSON 編回 importExtrasJSON,SwiftData schema 零變動。DiveLogImportError 逐 case 轉換(ImportWizardView
以本地 case 逐一 catch,不轉換 UI 錯誤提示會劣化)。UDDFParser 等),DiveLogImporterFactory
清單與 priority 順序、既有測試(F5 E2E 等)零改動。MinimalZipReader 本地薄轉發 enum(SuuntoSDEParser 仍在用;因專案啟用
MemberImportVisibility,typealias 再匯出行不通,Ultra 端採用時同樣要注意)。
刪除已搬遷實作:monolith 內 4 個解析器+私有 delegate/資料結構
(DiveLogImporter.swift 2483→911 行)、ShearwaterXMLParser.swift、
MinimalZipReader.swift 整檔。保留:protocol/DiveLogFormat/
DiveLogImportError/Factory/Peregrine·Oceanic 本地 stub/其餘 Logbook
專屬解析器/ImportCoordinator(去重/並發邏輯不動,SYNC #2/#3 另案)。DiveImportKitAdapterTests(test42.uddf 走 factory→包裝→DiveLog 全流程,
關鍵欄位斷言與搬遷前期望值一致)。驗證:iOS+macOS build 成功;iOS 模擬器與 macOS 本機均實際啟動;測試套件
(排除既有已知崩潰的 ImportCoordinatorTests)0 failures(SuuntoJSON 樣本
缺檔 skip 為既有現象)。UI 完全未動,匯入流程仍走原 factory 入口。
三個 JD2 家族 App 中 ultra/immersion 已於 F3a/F3b 改用統一 DiveKit(SPM 引用),
本次 Logbook 跟進,家族「共用演算法只有一份」目標達成。分支
feature/F5-divekit-migration,三個 commit:
project.pbxproj 新增 ../DiveKit local package(主 target + Tests target)。DiveReplayEngine.swift(Logbook 專屬回放
引擎,語意不同不遷移)與 21 個消費檔補 import DiveKit。刪除
DiveEngineTests.swift(測試已刪除的死碼,其回歸場景已在 DiveKit 自己的測試
套件覆蓋)。根因排查:DiveReplayEngineTests 先前的 malloc 崩潰證實是被
DiveEngineTests 汙染共享測試行程,刪除後單獨執行 4/4 全過、無崩潰。Buhlmann 只追蹤氮氣(Compartment 無 pHe
欄位),trimix 的 ndlSeconds() 會 assertionFailure(v2.0 項目,尚未實作);
用真實 trimix 樣本(00_Import_samples/)走完整匯入→剖面分析流程時首次踩到。
PM 決策:F5 繞過(不動 DiveKit 演算法本體),DiveReplayEngine.replay() 偵測
trimix 即跳過生理計算、只給深度/時間/溫度剖面(decoDataUnavailable),
DiveAnalysisView 對應隱藏 Ceiling/NDL/組織艙 UI。真正的 trimix 氦氣支援
另排(家族層追蹤)。新增 F5DiveKitMigrationE2ETests.swift:真實 trimix 樣本驗證短路路徑正確、合成
空氣潛水驗證完整 DiveKit 重放路徑正常。驗證:iOS+macOS build 成功;測試套件
(排除既有已知崩潰的 ImportCoordinatorTests,與本次無關)0 failures。
外部稽核報告 docs/reports/R-2026-07-17-audit_report.md(原 audit_report-0717.md,2026-07-18 歸檔更名)針對核心演算法與匯入流程提出 4 項風險,
逐項核對程式碼後確認全數屬實(非誤報),並全數修復:
Buhlmann chunking 迴圈 pRate 歸零 bug(DiveEngine.swift)
:時間補償的 chunk 迴圈原本每一步都傳最終深度給 buhlmann.update(),導致
Buhlmann 內部 prevDepth 在第一個 chunk 後就等於最終深度,depthDelta 恆為
0、壓力變化率被誤判為 0,等同把補算期間全當恆深處理。改為依已耗用時間比例
在「tick 開始前深度」→「本次 tick 深度」間線性插值,與同一份程式碼庫內
DiveReplayEngine.swift(本 session 較早修復)已驗證過的手法一致。deduplicateDives 原本只比對資料庫既有記錄的靜態快照,同一批次(甚至單一
檔案)內部彼此重複的日誌會互相漏檢、全數寫入。改為逐筆比對+動態把已確認
非重複的日誌併入比對陣列,抽成可獨立單元測試的 Self.dedupe(_:against:),
與 DiveLogDatabase.importFromJSON 既有正確做法一致。ImportCoordinator 為 @MainActor,importer.parse()
為同步 CPU 密集操作,大檔案/批次匯入會讓 UI 卡住甚至觸發 Watchdog 強制關閉。
改用 Task.detached 包住選格式+解析。⚠️ 已知限制:專案預設
-default-isolation=MainActor、DiveLogImporter/DiveLog 皆非 nonisolated/
Sendable,此修復在目前編譯設定下僅為 warning(非 error),警告訊息明確標註
「this is an error in the Swift 6 language mode」;徹底解法需將協定三方法與
全部 20 個解析器實作標記 nonisolated+處理 DiveLog 跨 actor 傳遞,規模較大,
本次未一併處理,已記錄於 SYNC_TO_JD2-ULTRA.md。beginDive() 觸發時檢查,若潛水員完成潛水後在水面
停留超過 24 小時卻未再下潛,UI/Widget 顯示的 OTU 會卡在舊值。抽成共用的
resetStaleOTUIfNeeded(now:),同時掛在 beginDive()、水面 tick()、
restore()(App 重啟還原)三處呼叫。測試:新增 DiveEngineTests.swift(4 項,涵蓋風險 #1 的 chunking 插值正確性
與風險 #4 的三種歸零情境)+ ImportCoordinatorTests.swift 新增 4 項純邏輯去重
測試(不碰資料庫)。全數通過,iOS/macOS 雙平台建置成功。
Ultra companion 風險評估:逐一核對 JD2-ultra 對應檔案
(DiveKit/Sources/DiveKit/Algorithm/DiveEngine.swift、
JD2UltraPhone/Import/ImportCoordinator.swift),確認 4 項風險全數同樣存在、
尚未修復(DiveKit 演算法程式碼為早期整包 port 自 Ultra,ImportCoordinator 架構
高度相似)。本次僅評估、未修改 Ultra 程式碼(不同專案,需另行決定是否同步)。
新增 SYNC_TO_JD2-ULTRA.md 作為長期追蹤文件,記錄「JD2-Logbook 發現且可能同樣
影響 Ultra」的問題,供 Ultra 端未來參考同步。
Import tab「Supported Formats」:格式數擴充到 16 種後,原本無分類的 2 欄
卡片格線難以掃視,改為依品牌/來源分 4 組(Universal / Suunto / Garmin /
Other Brands)+單欄列表列。列的視覺語彙 port 自 JD2-Ultra companion
DiveComponents.swift 的 SectionHeader/ValueRow 慣例(圖示+標題置左、
次要資訊置右,grouped 卡片背景+列間 Divider),對齊 iOS 原生
List(.insetGrouped) 視覺語言,而非沿用舊版無來源依據的卡片格線設計。
Dive Profile 互動剖面圖/組織艙負荷資訊列:原本的圖示膠囊列(icon+text
pill)改為與 JD2-Ultra companion DiveAnalysisView.calloutRow 完全一致的
五欄等寬排版(Time / Depth / Temp / Ceiling / No Deco,label 在上、數值在
下);新增「安全語意數值才用填色膠囊強調」規則——一般狀態為純深色文字,
只有真的減壓中(紅底白字)或免減壓時間逼近 10 分鐘(黃底黑字)時膠囊才
亮起,其餘與 Ultra 邏輯(PlanModel.ndlText:99+ / 分鐘)一致。
新增 4 個 xcstrings 翻譯 key(”Temp”/”No Deco”/”Universal”/”Other Brands”), 18 種語言全數補齊,優先沿用 Ultra 既有翻譯值。
PM 指示全面查證 /file_format_research 盤點的 18 種潛水電腦/軟體格式,不接受
「叫使用者自己轉檔」的退讓方案。逐一重新驗證研究文件的假設(多處與真實樣本
不符,例如 Suunto SDE 內部其實是舊版 DM3 格式而非 DM5、DAN DL7 的 ZDT 記錄
語意與初版猜測相反),並在可能時搜尋開源參考實作交叉驗證byte-level正確性。
新增 10 個格式解析器:
SuuntoDM5XMLParser:Suunto DM4/DM5 WCF XML(D4i 等錶款直傳),真實樣本逐欄位驗證SHEARWATERParser:從空 stub 改為真實實作,同時修正原本用預設 canHandle 誤攔截所有 .xml(含 D4i 檔)的根因 bugSuuntoSMLParser:Moveslink XML,含 Kelvin 水溫轉換DANDL7Parser:業界標準交換格式,欄位對照依開源 PyDL7 校正(研究文件誤判 ZDT 為逐樣本剖面,實為 dive trailer)DivesoftDLFParser:二進位格式,欄位偏移依開源 divesoft-parser 逐位元核對,並用真實樣本 3 個獨立欄位(start_time/max_depth/min_temperature)精確驗證吻合;v2 header(”DiVE” magic)明確拒絕而非臆測SuuntoSDEParser:ZIP 包裝的舊版 DM3 XML(非原研究猜測的 DM5 格式),歐式逗號小數處理ReefnetSensusParser:CSV,壓力→深度公式依 ReefNet 官方換算說明驗證;水溫欄位因無法可靠確認編碼,刻意不猜測轉換DivingLogSQLiteParser:原生 SQLite3(無需第三方套件),RTF 備註欄位手寫剝除器(避免引入 UIKit 依賴破壞 JD2Core 跨平台界線)GarminConnectJSONParser、DeepbluCOSMIQParser:格式假設(無公開 API 文件),待真實樣本驗證新增 MinimalZipReader.swift:純 Swift 跨平台 ZIP 讀取器(PKWARE 公開規格,
支援 stored/deflate),取代原本只在 macOS 用 /usr/bin/unzip 的做法,順便修正
UDDF 的 ZIP 包裝格式在 iOS 原本完全無法匯入的既有缺口。
確認 8 個格式目前無法安全實作(Scubapro LogTRAK、Mares Dive Organizer、
Heinrichs Weikamp OSTC、Cressi PC Interface、Ratio iDive、Cochran CAN、
Aqualung i-Trak、APD LogViewer):逐一檢查後證實為 Microsoft Access/SQL Server
Compact 等專有二進位資料庫無公開規格、或研究樣本僅為文字佔位符(無真實資料
可驗證),非偷懶跳過。具體理由見 file_format_research/format_inventory.md。
功能:
importExtrasJSON 欄位:buddy / 裝置序號 / 韌體不再塞進 notes 文字,改結構化存儲;Detail 頁新增可折疊「原始資料」區塊avgDepth 欄位:來源值優先,無則以剖面樣本梯形近似重建DiveLogDatabase.exportAsJSON/importFromJSON 從拋錯 stub 改為真正可用,Settings 頁新增入口DiveKit:取代本地死碼 Buhlmann.swift/DiveEngine.swift/AlgorithmConstants.swift(9 項已知安全問題);新增互動剖面圖(拖曳查看深度/水溫/ceiling/NDL,放開後保留選取)+組織艙飽和度長條圖(預設收合,互動後才顯示)DiveLogImporter.swift 82.2% → 89.1%技術債(順手修復,與今日改動無關的舊問題):
project.pbxproj 測試 target TEST_HOST 殘留改名前的 JD2-Logbook.app(應為 JoyDive².app),導致 xcodebuild test 完全無法建置@testable import JD2_Logbook 未隨模組改名同步(實際模組為 JoyDive_)架構重點:互動剖面圖與組織艙圖的選取狀態統一由 DiveAnalysisView 管理(非各自為政),重放引擎改為直接驅動 Buhlmann + 樣本間 ≤10s 線性內插(比對 JD2-Ultra companion DiveReplay.swift 對齊,取代原本用 DiveEngine.tick() 逐樣本呼叫、樣本間隔大時深度會瞬間跳變的失真做法)。
待決策(下次上架前):macOS Info.plist 的 LSApplicationCategoryType = public.app-category.sports-games 會觸發系統誤判為遊戲、自動開啟 macOS 遊戲模式(gamepolicyd 只檢查分類值是否以 games 結尾);需決定改為 public.app-category.sports 或 public.app-category.healthcare-fitness,同時要對齊 App Store Connect 的上架分類。
buddy 欄位