| Files | Last commit | Last update |
|---|---|---|
feat: IPC 契约类型由 ts-rs 生成,补齐前端测试与 CI 门禁 - Rust 结构体加 ts_rs::TS,统一由 cargo test --test export_bindings 导出 到 src/generated/(19 个类型),前端 src/types.ts 改为 re-export, 消除人肉双维护 - 补齐 u64/i64 字段的 #[ts(type = "number")]:IPC 走 serde_json, 前端拿到的始终是 number,避免生成 bigint 与实际运行时不一致 - 新增 OpencodeSessionsResult 契约并纳入导出清单 - 移除 #[ts(export)]:原先每次 cargo test 会在 src-tauri/generated/ 落一份无人引用的副本 - 前端新增 vitest 单测 47 例(util / store / 共享组件 / 错误边界) - CI:前端跑测试、clippy 提到 -D warnings、新增契约漂移门禁 (内容级比对,不受 CRLF 与 stat 缓存干扰) - 修复 scripts/make_icons.py 源图路径(源图已移入 scripts/,原路径 指向根目录导致脚本跑不通) - 修复 tauri.conf.json 的 \ 指向仓库外路径,改用官方 config schema - .gitignore 补充 coverage | 26 days ago | |
feat(backup): restore from a snapshot, and fix a restore that silently did nothing Restore - New list_backups / restore_backup alongside backup_data. The panel in Settings lists every snapshot with its timestamp and file list; restoring asks for confirmation, reports the rollback point, and refreshes. - Restore validates every file as parseable JSON before touching anything, creates a safety snapshot of the current state, then replaces files through temp-file + rename. It never deserializes into the concrete structs, because an older backup may lack newer fields and would otherwise be rejected. - The backup id is a timestamp, never a path; anything else is refused. Covered by a Rust test that feeds traversal strings and Windows paths. Fixed — restore was a no-op - Backup directories are named to the second, and restore creates its safety snapshot in the same second as the snapshot it reads. Both resolved to the same directory, so the safety copy overwrote the source, restore copied the unchanged data back, and the backup the user was restoring from was gone. Measured on the real exe: safety_backup == source id, data still "CHANGED". The directory is now claimed with an atomic create_dir and a collision waits for the next second; re-measured: both directories exist, data returns to the pre-backup value. - Restore used to create the safety snapshot before validating the source, so a corrupt backup left a useless rollback point behind. Validation now runs first. UI - The two appearance-panel cards were stretched to equal height by the grid, leaving a large blank block under the shorter one. The layout picker now renders a wireframe of the selected layout, so the five arrangements are distinguishable before clicking, and the grid uses items-start. - Settings columns are independent flex stacks rather than one grid, so a tall card no longer forces blank space into its neighbour. Desktop E2E now also covers backup → modify → restore → verify the data came back, plus rejection of a traversal id. All 14 checks pass on the packaged exe. | 16 days ago | |
feat(backup): restore from a snapshot, and fix a restore that silently did nothing Restore - New list_backups / restore_backup alongside backup_data. The panel in Settings lists every snapshot with its timestamp and file list; restoring asks for confirmation, reports the rollback point, and refreshes. - Restore validates every file as parseable JSON before touching anything, creates a safety snapshot of the current state, then replaces files through temp-file + rename. It never deserializes into the concrete structs, because an older backup may lack newer fields and would otherwise be rejected. - The backup id is a timestamp, never a path; anything else is refused. Covered by a Rust test that feeds traversal strings and Windows paths. Fixed — restore was a no-op - Backup directories are named to the second, and restore creates its safety snapshot in the same second as the snapshot it reads. Both resolved to the same directory, so the safety copy overwrote the source, restore copied the unchanged data back, and the backup the user was restoring from was gone. Measured on the real exe: safety_backup == source id, data still "CHANGED". The directory is now claimed with an atomic create_dir and a collision waits for the next second; re-measured: both directories exist, data returns to the pre-backup value. - Restore used to create the safety snapshot before validating the source, so a corrupt backup left a useless rollback point behind. Validation now runs first. UI - The two appearance-panel cards were stretched to equal height by the grid, leaving a large blank block under the shorter one. The layout picker now renders a wireframe of the selected layout, so the five arrangements are distinguishable before clicking, and the grid uses items-start. - Settings columns are independent flex stacks rather than one grid, so a tall card no longer forces blank space into its neighbour. Desktop E2E now also covers backup → modify → restore → verify the data came back, plus rejection of a traversal id. All 14 checks pass on the packaged exe. | 16 days ago | |
feat(cost): 0.5.0 — per-project attribution, ledger export, extended trend Per-project attribution: - project_breakdown_sql groups the ledger by project directory, ordered by spend; dashboard card with per-project bars. - estimated rows follow the include-estimated switch like every other money view. Ledger export: - get_cost_export returns raw records (bounded 200k, day+path ordered, estimated rows included - cost_source states itself). - CSV reuses RFC 4180 escaping + formula-injection guard, full float precision; JSON export with matching field names. Extended trend: - get_cost_trend_extended: daily window + monthly buckets strictly before it (no overlap by construction); 14/30/90-day switcher. - trend chart labels months as YYYY-MM, days as MM-DD. Tests: +5 Rust (ordering, estimated filter, bucket boundaries, export limits), +10 frontend (store loadCostTrend paths, demo bundle contract, diagnostics persistence, export escaping). 441 unit + 136 backend, coverage 85.7% branches, clippy clean, e2e 23 passed (costs baseline regenerated for the new cards). Chore: version 0.5.0 across six places; roadmap marks 0.5.0 done and corrects the budget-alert status (it shipped in 0.4.0). | 4 days ago | |
feat(cost): 0.5.0 — per-project attribution, ledger export, extended trend Per-project attribution: - project_breakdown_sql groups the ledger by project directory, ordered by spend; dashboard card with per-project bars. - estimated rows follow the include-estimated switch like every other money view. Ledger export: - get_cost_export returns raw records (bounded 200k, day+path ordered, estimated rows included - cost_source states itself). - CSV reuses RFC 4180 escaping + formula-injection guard, full float precision; JSON export with matching field names. Extended trend: - get_cost_trend_extended: daily window + monthly buckets strictly before it (no overlap by construction); 14/30/90-day switcher. - trend chart labels months as YYYY-MM, days as MM-DD. Tests: +5 Rust (ordering, estimated filter, bucket boundaries, export limits), +10 frontend (store loadCostTrend paths, demo bundle contract, diagnostics persistence, export escaping). 441 unit + 136 backend, coverage 85.7% branches, clippy clean, e2e 23 passed (costs baseline regenerated for the new cards). Chore: version 0.5.0 across six places; roadmap marks 0.5.0 done and corrects the budget-alert status (it shipped in 0.4.0). | 4 days ago | |
feat: IPC 契约类型由 ts-rs 生成,补齐前端测试与 CI 门禁 - Rust 结构体加 ts_rs::TS,统一由 cargo test --test export_bindings 导出 到 src/generated/(19 个类型),前端 src/types.ts 改为 re-export, 消除人肉双维护 - 补齐 u64/i64 字段的 #[ts(type = "number")]:IPC 走 serde_json, 前端拿到的始终是 number,避免生成 bigint 与实际运行时不一致 - 新增 OpencodeSessionsResult 契约并纳入导出清单 - 移除 #[ts(export)]:原先每次 cargo test 会在 src-tauri/generated/ 落一份无人引用的副本 - 前端新增 vitest 单测 47 例(util / store / 共享组件 / 错误边界) - CI:前端跑测试、clippy 提到 -D warnings、新增契约漂移门禁 (内容级比对,不受 CRLF 与 stat 缓存干扰) - 修复 scripts/make_icons.py 源图路径(源图已移入 scripts/,原路径 指向根目录导致脚本跑不通) - 修复 tauri.conf.json 的 \ 指向仓库外路径,改用官方 config schema - .gitignore 补充 coverage | 26 days ago | |
feat: IPC 契约类型由 ts-rs 生成,补齐前端测试与 CI 门禁 - Rust 结构体加 ts_rs::TS,统一由 cargo test --test export_bindings 导出 到 src/generated/(19 个类型),前端 src/types.ts 改为 re-export, 消除人肉双维护 - 补齐 u64/i64 字段的 #[ts(type = "number")]:IPC 走 serde_json, 前端拿到的始终是 number,避免生成 bigint 与实际运行时不一致 - 新增 OpencodeSessionsResult 契约并纳入导出清单 - 移除 #[ts(export)]:原先每次 cargo test 会在 src-tauri/generated/ 落一份无人引用的副本 - 前端新增 vitest 单测 47 例(util / store / 共享组件 / 错误边界) - CI:前端跑测试、clippy 提到 -D warnings、新增契约漂移门禁 (内容级比对,不受 CRLF 与 stat 缓存干扰) - 修复 scripts/make_icons.py 源图路径(源图已移入 scripts/,原路径 指向根目录导致脚本跑不通) - 修复 tauri.conf.json 的 \ 指向仓库外路径,改用官方 config schema - .gitignore 补充 coverage | 26 days ago | |
feat: IPC 契约类型由 ts-rs 生成,补齐前端测试与 CI 门禁 - Rust 结构体加 ts_rs::TS,统一由 cargo test --test export_bindings 导出 到 src/generated/(19 个类型),前端 src/types.ts 改为 re-export, 消除人肉双维护 - 补齐 u64/i64 字段的 #[ts(type = "number")]:IPC 走 serde_json, 前端拿到的始终是 number,避免生成 bigint 与实际运行时不一致 - 新增 OpencodeSessionsResult 契约并纳入导出清单 - 移除 #[ts(export)]:原先每次 cargo test 会在 src-tauri/generated/ 落一份无人引用的副本 - 前端新增 vitest 单测 47 例(util / store / 共享组件 / 错误边界) - CI:前端跑测试、clippy 提到 -D warnings、新增契约漂移门禁 (内容级比对,不受 CRLF 与 stat 缓存干扰) - 修复 scripts/make_icons.py 源图路径(源图已移入 scripts/,原路径 指向根目录导致脚本跑不通) - 修复 tauri.conf.json 的 \ 指向仓库外路径,改用官方 config schema - .gitignore 补充 coverage | 26 days ago | |
feat: IPC 契约类型由 ts-rs 生成,补齐前端测试与 CI 门禁 - Rust 结构体加 ts_rs::TS,统一由 cargo test --test export_bindings 导出 到 src/generated/(19 个类型),前端 src/types.ts 改为 re-export, 消除人肉双维护 - 补齐 u64/i64 字段的 #[ts(type = "number")]:IPC 走 serde_json, 前端拿到的始终是 number,避免生成 bigint 与实际运行时不一致 - 新增 OpencodeSessionsResult 契约并纳入导出清单 - 移除 #[ts(export)]:原先每次 cargo test 会在 src-tauri/generated/ 落一份无人引用的副本 - 前端新增 vitest 单测 47 例(util / store / 共享组件 / 错误边界) - CI:前端跑测试、clippy 提到 -D warnings、新增契约漂移门禁 (内容级比对,不受 CRLF 与 stat 缓存干扰) - 修复 scripts/make_icons.py 源图路径(源图已移入 scripts/,原路径 指向根目录导致脚本跑不通) - 修复 tauri.conf.json 的 \ 指向仓库外路径,改用官方 config schema - .gitignore 补充 coverage | 26 days ago | |
feat: v0.3.0 — process tree, command palette, CLI, diagnostics, real showcase Six new capabilities, all with tests and real verification: - Process tree: buildForest/flattenTree over sysinfo parent_pid, cycle- and orphan-safe (a 2-node cycle and a self-parent both render exactly once), virtualised, per-row detail listing that process's own listening ports, CSV export with RFC 4180 quoting plus formula-injection neutralisation. - Command palette (Ctrl/Cmd+K): fuzzy search over pages, processes, ports, projects and tasks; Enter runs, Esc closes; process/port hits jump to the process page pre-filtered on that PID. - Port -> process jump both ways: every listening port has "show owning process", the process detail lists the reverse mapping. - Environment self-check: platform, data directory writability, cost DB, Claude/Codex/opencode session folders and the real port command, each reported ok/warn/fail with a hint and a copyable summary. - CLI mode on the same binary: workbench ps|ports|diag [--json], read-only, no window. Windows stdout works when redirected to a file or a pipe (the first implementation produced an empty file — caught by e2e/cli.spec.ts). - Feature E2E suite (7) + CLI E2E (5) + desktop E2E now restores the data it overwrites. Fixes found while auditing: - Process status was localized Chinese from Rust, so English/Japanese UIs showed 运行中. Backend now emits stable keys; processStatus.ts translates and still accepts the legacy labels. - Resource chart had hard-coded cyan/amber series; now uses the theme accent and warn colours. - alwaysOnTop was persisted but never re-applied after a restart. - Landing page referenced four screenshots that never existed, still said v0.1.1, and its Pages/Release workflows had been deleted from main by the mirror clean-up commits. All three fixed; Pages now fails when a referenced asset is missing, Release builds all three platforms with SHA-256 sums. - Demo mode had no process hierarchy and an empty log page, so the new tree and the log viewer looked broken in every screenshot. Showcase: README x3 rewritten (positioning, screenshots, 32-second demo video, feature matrix), docs/COMPARISON{,.zh-CN,.ja}.md against Mission Center, System Informer, btop++, Glances, Stats, ccusage and Claude Code Usage Monitor. Verified: tsc 0, eslint 0, prettier clean, vitest 370/370 (34 files), cargo test 91/91 + binding export, clippy -D warnings 0, jscpd 0.18%, Playwright 35/35 (23 visual/contrast + 7 features + 5 CLI), packaged-exe desktop E2E 16 checks, io E2E 5 checks, NSIS 4.95 MiB at 0.3.0. | 14 days ago | |
feat: v0.3.0 — process tree, command palette, CLI, diagnostics, real showcase Six new capabilities, all with tests and real verification: - Process tree: buildForest/flattenTree over sysinfo parent_pid, cycle- and orphan-safe (a 2-node cycle and a self-parent both render exactly once), virtualised, per-row detail listing that process's own listening ports, CSV export with RFC 4180 quoting plus formula-injection neutralisation. - Command palette (Ctrl/Cmd+K): fuzzy search over pages, processes, ports, projects and tasks; Enter runs, Esc closes; process/port hits jump to the process page pre-filtered on that PID. - Port -> process jump both ways: every listening port has "show owning process", the process detail lists the reverse mapping. - Environment self-check: platform, data directory writability, cost DB, Claude/Codex/opencode session folders and the real port command, each reported ok/warn/fail with a hint and a copyable summary. - CLI mode on the same binary: workbench ps|ports|diag [--json], read-only, no window. Windows stdout works when redirected to a file or a pipe (the first implementation produced an empty file — caught by e2e/cli.spec.ts). - Feature E2E suite (7) + CLI E2E (5) + desktop E2E now restores the data it overwrites. Fixes found while auditing: - Process status was localized Chinese from Rust, so English/Japanese UIs showed 运行中. Backend now emits stable keys; processStatus.ts translates and still accepts the legacy labels. - Resource chart had hard-coded cyan/amber series; now uses the theme accent and warn colours. - alwaysOnTop was persisted but never re-applied after a restart. - Landing page referenced four screenshots that never existed, still said v0.1.1, and its Pages/Release workflows had been deleted from main by the mirror clean-up commits. All three fixed; Pages now fails when a referenced asset is missing, Release builds all three platforms with SHA-256 sums. - Demo mode had no process hierarchy and an empty log page, so the new tree and the log viewer looked broken in every screenshot. Showcase: README x3 rewritten (positioning, screenshots, 32-second demo video, feature matrix), docs/COMPARISON{,.zh-CN,.ja}.md against Mission Center, System Informer, btop++, Glances, Stats, ccusage and Claude Code Usage Monitor. Verified: tsc 0, eslint 0, prettier clean, vitest 370/370 (34 files), cargo test 91/91 + binding export, clippy -D warnings 0, jscpd 0.18%, Playwright 35/35 (23 visual/contrast + 7 features + 5 CLI), packaged-exe desktop E2E 16 checks, io E2E 5 checks, NSIS 4.95 MiB at 0.3.0. | 14 days ago | |
feat: IPC 契约类型由 ts-rs 生成,补齐前端测试与 CI 门禁 - Rust 结构体加 ts_rs::TS,统一由 cargo test --test export_bindings 导出 到 src/generated/(19 个类型),前端 src/types.ts 改为 re-export, 消除人肉双维护 - 补齐 u64/i64 字段的 #[ts(type = "number")]:IPC 走 serde_json, 前端拿到的始终是 number,避免生成 bigint 与实际运行时不一致 - 新增 OpencodeSessionsResult 契约并纳入导出清单 - 移除 #[ts(export)]:原先每次 cargo test 会在 src-tauri/generated/ 落一份无人引用的副本 - 前端新增 vitest 单测 47 例(util / store / 共享组件 / 错误边界) - CI:前端跑测试、clippy 提到 -D warnings、新增契约漂移门禁 (内容级比对,不受 CRLF 与 stat 缓存干扰) - 修复 scripts/make_icons.py 源图路径(源图已移入 scripts/,原路径 指向根目录导致脚本跑不通) - 修复 tauri.conf.json 的 \ 指向仓库外路径,改用官方 config schema - .gitignore 补充 coverage | 26 days ago | |
feat(cost): 0.5.0 — per-project attribution, ledger export, extended trend Per-project attribution: - project_breakdown_sql groups the ledger by project directory, ordered by spend; dashboard card with per-project bars. - estimated rows follow the include-estimated switch like every other money view. Ledger export: - get_cost_export returns raw records (bounded 200k, day+path ordered, estimated rows included - cost_source states itself). - CSV reuses RFC 4180 escaping + formula-injection guard, full float precision; JSON export with matching field names. Extended trend: - get_cost_trend_extended: daily window + monthly buckets strictly before it (no overlap by construction); 14/30/90-day switcher. - trend chart labels months as YYYY-MM, days as MM-DD. Tests: +5 Rust (ordering, estimated filter, bucket boundaries, export limits), +10 frontend (store loadCostTrend paths, demo bundle contract, diagnostics persistence, export escaping). 441 unit + 136 backend, coverage 85.7% branches, clippy clean, e2e 23 passed (costs baseline regenerated for the new cards). Chore: version 0.5.0 across six places; roadmap marks 0.5.0 done and corrects the budget-alert status (it shipped in 0.4.0). | 4 days ago | |
feat: IPC 契约类型由 ts-rs 生成,补齐前端测试与 CI 门禁 - Rust 结构体加 ts_rs::TS,统一由 cargo test --test export_bindings 导出 到 src/generated/(19 个类型),前端 src/types.ts 改为 re-export, 消除人肉双维护 - 补齐 u64/i64 字段的 #[ts(type = "number")]:IPC 走 serde_json, 前端拿到的始终是 number,避免生成 bigint 与实际运行时不一致 - 新增 OpencodeSessionsResult 契约并纳入导出清单 - 移除 #[ts(export)]:原先每次 cargo test 会在 src-tauri/generated/ 落一份无人引用的副本 - 前端新增 vitest 单测 47 例(util / store / 共享组件 / 错误边界) - CI:前端跑测试、clippy 提到 -D warnings、新增契约漂移门禁 (内容级比对,不受 CRLF 与 stat 缓存干扰) - 修复 scripts/make_icons.py 源图路径(源图已移入 scripts/,原路径 指向根目录导致脚本跑不通) - 修复 tauri.conf.json 的 \ 指向仓库外路径,改用官方 config schema - .gitignore 补充 coverage | 26 days ago | |
feat: IPC 契约类型由 ts-rs 生成,补齐前端测试与 CI 门禁 - Rust 结构体加 ts_rs::TS,统一由 cargo test --test export_bindings 导出 到 src/generated/(19 个类型),前端 src/types.ts 改为 re-export, 消除人肉双维护 - 补齐 u64/i64 字段的 #[ts(type = "number")]:IPC 走 serde_json, 前端拿到的始终是 number,避免生成 bigint 与实际运行时不一致 - 新增 OpencodeSessionsResult 契约并纳入导出清单 - 移除 #[ts(export)]:原先每次 cargo test 会在 src-tauri/generated/ 落一份无人引用的副本 - 前端新增 vitest 单测 47 例(util / store / 共享组件 / 错误边界) - CI:前端跑测试、clippy 提到 -D warnings、新增契约漂移门禁 (内容级比对,不受 CRLF 与 stat 缓存干扰) - 修复 scripts/make_icons.py 源图路径(源图已移入 scripts/,原路径 指向根目录导致脚本跑不通) - 修复 tauri.conf.json 的 \ 指向仓库外路径,改用官方 config schema - .gitignore 补充 coverage | 26 days ago | |
feat: IPC 契约类型由 ts-rs 生成,补齐前端测试与 CI 门禁 - Rust 结构体加 ts_rs::TS,统一由 cargo test --test export_bindings 导出 到 src/generated/(19 个类型),前端 src/types.ts 改为 re-export, 消除人肉双维护 - 补齐 u64/i64 字段的 #[ts(type = "number")]:IPC 走 serde_json, 前端拿到的始终是 number,避免生成 bigint 与实际运行时不一致 - 新增 OpencodeSessionsResult 契约并纳入导出清单 - 移除 #[ts(export)]:原先每次 cargo test 会在 src-tauri/generated/ 落一份无人引用的副本 - 前端新增 vitest 单测 47 例(util / store / 共享组件 / 错误边界) - CI:前端跑测试、clippy 提到 -D warnings、新增契约漂移门禁 (内容级比对,不受 CRLF 与 stat 缓存干扰) - 修复 scripts/make_icons.py 源图路径(源图已移入 scripts/,原路径 指向根目录导致脚本跑不通) - 修复 tauri.conf.json 的 \ 指向仓库外路径,改用官方 config schema - .gitignore 补充 coverage | 26 days ago | |
feat: IPC 契约类型由 ts-rs 生成,补齐前端测试与 CI 门禁 - Rust 结构体加 ts_rs::TS,统一由 cargo test --test export_bindings 导出 到 src/generated/(19 个类型),前端 src/types.ts 改为 re-export, 消除人肉双维护 - 补齐 u64/i64 字段的 #[ts(type = "number")]:IPC 走 serde_json, 前端拿到的始终是 number,避免生成 bigint 与实际运行时不一致 - 新增 OpencodeSessionsResult 契约并纳入导出清单 - 移除 #[ts(export)]:原先每次 cargo test 会在 src-tauri/generated/ 落一份无人引用的副本 - 前端新增 vitest 单测 47 例(util / store / 共享组件 / 错误边界) - CI:前端跑测试、clippy 提到 -D warnings、新增契约漂移门禁 (内容级比对,不受 CRLF 与 stat 缓存干扰) - 修复 scripts/make_icons.py 源图路径(源图已移入 scripts/,原路径 指向根目录导致脚本跑不通) - 修复 tauri.conf.json 的 \ 指向仓库外路径,改用官方 config schema - .gitignore 补充 coverage | 26 days ago | |
feat(pricing): a real price editor, and two dev-server detection bugs found on the real machine Pricing editor - New IPC: get_pricing / save_pricing, with ts-rs generated PricingRow and PricingView. Built-in table is 56 models; any model can be overridden, new models added, and rows reset to the built-in price. - Saving validates the whole table: one bad entry rejects the write and reports every reason. Partial success would make the displayed table and the file disagree, and price errors are exactly the kind of thing a user cannot discover later. - Bad entries already present in pricing_overrides.json are surfaced in the UI instead of being silently ignored (the old loader only logged them). - Empty override table deletes the file rather than leaving {}, so "no overrides" and "I forgot I set these" stay distinguishable. Two dev-server detection bugs (found by running the packaged exe) - serve was matched as a substring, and server contains serve: this machine reported 42 dev servers, all node processes whose entry point was dist-server/server/main.js or apps/server/main.js. Short words now require a standalone command token, plus a node_modules/serve/ path rule. 42 -> 29. - opencode-cli.exe serve --service is opencode's own local API, not a user project. Commands carrying --service / --daemon / --background are excluded. The remaining 29 are all genuine vite / uvicorn processes. - Both are covered by regression tests that encode the real command lines. Real desktop E2E - scripts/e2e-desktop.mjs connects to the packaged exe over the WebView2 debugging port and drives real Tauri commands: pricing read, write, invalid rejection, reset, plus the UI actually rendering the editor. This is the first test that runs against real IPC instead of the browser demo build, and it is what exposed both detection bugs above. Also: removed two unused theme variables; added the two i18n keys the widget manager already referenced (they were rendering raw keys); IPC command-name contract test that cross-checks invoke() strings against generate_handler, with a guard so an empty glob cannot pass silently. | 17 days ago | |
feat(pricing): a real price editor, and two dev-server detection bugs found on the real machine Pricing editor - New IPC: get_pricing / save_pricing, with ts-rs generated PricingRow and PricingView. Built-in table is 56 models; any model can be overridden, new models added, and rows reset to the built-in price. - Saving validates the whole table: one bad entry rejects the write and reports every reason. Partial success would make the displayed table and the file disagree, and price errors are exactly the kind of thing a user cannot discover later. - Bad entries already present in pricing_overrides.json are surfaced in the UI instead of being silently ignored (the old loader only logged them). - Empty override table deletes the file rather than leaving {}, so "no overrides" and "I forgot I set these" stay distinguishable. Two dev-server detection bugs (found by running the packaged exe) - serve was matched as a substring, and server contains serve: this machine reported 42 dev servers, all node processes whose entry point was dist-server/server/main.js or apps/server/main.js. Short words now require a standalone command token, plus a node_modules/serve/ path rule. 42 -> 29. - opencode-cli.exe serve --service is opencode's own local API, not a user project. Commands carrying --service / --daemon / --background are excluded. The remaining 29 are all genuine vite / uvicorn processes. - Both are covered by regression tests that encode the real command lines. Real desktop E2E - scripts/e2e-desktop.mjs connects to the packaged exe over the WebView2 debugging port and drives real Tauri commands: pricing read, write, invalid rejection, reset, plus the UI actually rendering the editor. This is the first test that runs against real IPC instead of the browser demo build, and it is what exposed both detection bugs above. Also: removed two unused theme variables; added the two i18n keys the widget manager already referenced (they were rendering raw keys); IPC command-name contract test that cross-checks invoke() strings against generate_handler, with a guard so an empty glob cannot pass silently. | 17 days ago | |
fix(cost): read the model where the tools actually put it This is the most consequential defect found so far: the cost panel reported $0 for every real session. Claude Code writes the model at message.model and Codex at turn_context.payload.model. model_from_value only looked at top-level "model" and manifest.model, so for real transcripts it always returned "unknown"; is_priced("unknown") is false, so every record was rewritten as zero_pricing and every total came out as $0.00. The existing tests all used a synthetic top-level model field, which is why the suite stayed green. - model_from_value reads model / message.model / payload.model / manifest.model and ignores <synthetic> - usage is now aggregated per (day, model) instead of one model per file. A session that mixes opus with haiku subagents used to price the whole file at the first model seen; each row's own model is now used - unreadable and empty files produce no records. They used to emit a zero_pricing row keyed by the real (path, mtime, size), which made existing_keys skip that file forever even after it became readable - pricing.rs::infer_model is removed rather than fixed: it was a second, diverging implementation of the same lookup - regressions: nested model is priced, Codex payload model, synthetic ignored, mixed models priced separately, unreadable/empty produce nothing | 18 days ago | |
feat: IPC 契约类型由 ts-rs 生成,补齐前端测试与 CI 门禁 - Rust 结构体加 ts_rs::TS,统一由 cargo test --test export_bindings 导出 到 src/generated/(19 个类型),前端 src/types.ts 改为 re-export, 消除人肉双维护 - 补齐 u64/i64 字段的 #[ts(type = "number")]:IPC 走 serde_json, 前端拿到的始终是 number,避免生成 bigint 与实际运行时不一致 - 新增 OpencodeSessionsResult 契约并纳入导出清单 - 移除 #[ts(export)]:原先每次 cargo test 会在 src-tauri/generated/ 落一份无人引用的副本 - 前端新增 vitest 单测 47 例(util / store / 共享组件 / 错误边界) - CI:前端跑测试、clippy 提到 -D warnings、新增契约漂移门禁 (内容级比对,不受 CRLF 与 stat 缓存干扰) - 修复 scripts/make_icons.py 源图路径(源图已移入 scripts/,原路径 指向根目录导致脚本跑不通) - 修复 tauri.conf.json 的 \ 指向仓库外路径,改用官方 config schema - .gitignore 补充 coverage | 26 days ago | |
feat(cost): 0.5.0 — per-project attribution, ledger export, extended trend Per-project attribution: - project_breakdown_sql groups the ledger by project directory, ordered by spend; dashboard card with per-project bars. - estimated rows follow the include-estimated switch like every other money view. Ledger export: - get_cost_export returns raw records (bounded 200k, day+path ordered, estimated rows included - cost_source states itself). - CSV reuses RFC 4180 escaping + formula-injection guard, full float precision; JSON export with matching field names. Extended trend: - get_cost_trend_extended: daily window + monthly buckets strictly before it (no overlap by construction); 14/30/90-day switcher. - trend chart labels months as YYYY-MM, days as MM-DD. Tests: +5 Rust (ordering, estimated filter, bucket boundaries, export limits), +10 frontend (store loadCostTrend paths, demo bundle contract, diagnostics persistence, export escaping). 441 unit + 136 backend, coverage 85.7% branches, clippy clean, e2e 23 passed (costs baseline regenerated for the new cards). Chore: version 0.5.0 across six places; roadmap marks 0.5.0 done and corrects the budget-alert status (it shipped in 0.4.0). | 4 days ago | |
feat(backup): restore from a snapshot, and fix a restore that silently did nothing Restore - New list_backups / restore_backup alongside backup_data. The panel in Settings lists every snapshot with its timestamp and file list; restoring asks for confirmation, reports the rollback point, and refreshes. - Restore validates every file as parseable JSON before touching anything, creates a safety snapshot of the current state, then replaces files through temp-file + rename. It never deserializes into the concrete structs, because an older backup may lack newer fields and would otherwise be rejected. - The backup id is a timestamp, never a path; anything else is refused. Covered by a Rust test that feeds traversal strings and Windows paths. Fixed — restore was a no-op - Backup directories are named to the second, and restore creates its safety snapshot in the same second as the snapshot it reads. Both resolved to the same directory, so the safety copy overwrote the source, restore copied the unchanged data back, and the backup the user was restoring from was gone. Measured on the real exe: safety_backup == source id, data still "CHANGED". The directory is now claimed with an atomic create_dir and a collision waits for the next second; re-measured: both directories exist, data returns to the pre-backup value. - Restore used to create the safety snapshot before validating the source, so a corrupt backup left a useless rollback point behind. Validation now runs first. UI - The two appearance-panel cards were stretched to equal height by the grid, leaving a large blank block under the shorter one. The layout picker now renders a wireframe of the selected layout, so the five arrangements are distinguishable before clicking, and the grid uses items-start. - Settings columns are independent flex stacks rather than one grid, so a tall card no longer forces blank space into its neighbour. Desktop E2E now also covers backup → modify → restore → verify the data came back, plus rejection of a traversal id. All 14 checks pass on the packaged exe. | 16 days ago | |
feat: IPC 契约类型由 ts-rs 生成,补齐前端测试与 CI 门禁 - Rust 结构体加 ts_rs::TS,统一由 cargo test --test export_bindings 导出 到 src/generated/(19 个类型),前端 src/types.ts 改为 re-export, 消除人肉双维护 - 补齐 u64/i64 字段的 #[ts(type = "number")]:IPC 走 serde_json, 前端拿到的始终是 number,避免生成 bigint 与实际运行时不一致 - 新增 OpencodeSessionsResult 契约并纳入导出清单 - 移除 #[ts(export)]:原先每次 cargo test 会在 src-tauri/generated/ 落一份无人引用的副本 - 前端新增 vitest 单测 47 例(util / store / 共享组件 / 错误边界) - CI:前端跑测试、clippy 提到 -D warnings、新增契约漂移门禁 (内容级比对,不受 CRLF 与 stat 缓存干扰) - 修复 scripts/make_icons.py 源图路径(源图已移入 scripts/,原路径 指向根目录导致脚本跑不通) - 修复 tauri.conf.json 的 \ 指向仓库外路径,改用官方 config schema - .gitignore 补充 coverage | 26 days ago | |
feat: IPC 契约类型由 ts-rs 生成,补齐前端测试与 CI 门禁 - Rust 结构体加 ts_rs::TS,统一由 cargo test --test export_bindings 导出 到 src/generated/(19 个类型),前端 src/types.ts 改为 re-export, 消除人肉双维护 - 补齐 u64/i64 字段的 #[ts(type = "number")]:IPC 走 serde_json, 前端拿到的始终是 number,避免生成 bigint 与实际运行时不一致 - 新增 OpencodeSessionsResult 契约并纳入导出清单 - 移除 #[ts(export)]:原先每次 cargo test 会在 src-tauri/generated/ 落一份无人引用的副本 - 前端新增 vitest 单测 47 例(util / store / 共享组件 / 错误边界) - CI:前端跑测试、clippy 提到 -D warnings、新增契约漂移门禁 (内容级比对,不受 CRLF 与 stat 缓存干扰) - 修复 scripts/make_icons.py 源图路径(源图已移入 scripts/,原路径 指向根目录导致脚本跑不通) - 修复 tauri.conf.json 的 \ 指向仓库外路径,改用官方 config schema - .gitignore 补充 coverage | 26 days ago | |
feat: IPC 契约类型由 ts-rs 生成,补齐前端测试与 CI 门禁 - Rust 结构体加 ts_rs::TS,统一由 cargo test --test export_bindings 导出 到 src/generated/(19 个类型),前端 src/types.ts 改为 re-export, 消除人肉双维护 - 补齐 u64/i64 字段的 #[ts(type = "number")]:IPC 走 serde_json, 前端拿到的始终是 number,避免生成 bigint 与实际运行时不一致 - 新增 OpencodeSessionsResult 契约并纳入导出清单 - 移除 #[ts(export)]:原先每次 cargo test 会在 src-tauri/generated/ 落一份无人引用的副本 - 前端新增 vitest 单测 47 例(util / store / 共享组件 / 错误边界) - CI:前端跑测试、clippy 提到 -D warnings、新增契约漂移门禁 (内容级比对,不受 CRLF 与 stat 缓存干扰) - 修复 scripts/make_icons.py 源图路径(源图已移入 scripts/,原路径 指向根目录导致脚本跑不通) - 修复 tauri.conf.json 的 \ 指向仓库外路径,改用官方 config schema - .gitignore 补充 coverage | 26 days ago | |
feat: IPC 契约类型由 ts-rs 生成,补齐前端测试与 CI 门禁 - Rust 结构体加 ts_rs::TS,统一由 cargo test --test export_bindings 导出 到 src/generated/(19 个类型),前端 src/types.ts 改为 re-export, 消除人肉双维护 - 补齐 u64/i64 字段的 #[ts(type = "number")]:IPC 走 serde_json, 前端拿到的始终是 number,避免生成 bigint 与实际运行时不一致 - 新增 OpencodeSessionsResult 契约并纳入导出清单 - 移除 #[ts(export)]:原先每次 cargo test 会在 src-tauri/generated/ 落一份无人引用的副本 - 前端新增 vitest 单测 47 例(util / store / 共享组件 / 错误边界) - CI:前端跑测试、clippy 提到 -D warnings、新增契约漂移门禁 (内容级比对,不受 CRLF 与 stat 缓存干扰) - 修复 scripts/make_icons.py 源图路径(源图已移入 scripts/,原路径 指向根目录导致脚本跑不通) - 修复 tauri.conf.json 的 \ 指向仓库外路径,改用官方 config schema - .gitignore 补充 coverage | 26 days ago | |
feat: IPC 契约类型由 ts-rs 生成,补齐前端测试与 CI 门禁 - Rust 结构体加 ts_rs::TS,统一由 cargo test --test export_bindings 导出 到 src/generated/(19 个类型),前端 src/types.ts 改为 re-export, 消除人肉双维护 - 补齐 u64/i64 字段的 #[ts(type = "number")]:IPC 走 serde_json, 前端拿到的始终是 number,避免生成 bigint 与实际运行时不一致 - 新增 OpencodeSessionsResult 契约并纳入导出清单 - 移除 #[ts(export)]:原先每次 cargo test 会在 src-tauri/generated/ 落一份无人引用的副本 - 前端新增 vitest 单测 47 例(util / store / 共享组件 / 错误边界) - CI:前端跑测试、clippy 提到 -D warnings、新增契约漂移门禁 (内容级比对,不受 CRLF 与 stat 缓存干扰) - 修复 scripts/make_icons.py 源图路径(源图已移入 scripts/,原路径 指向根目录导致脚本跑不通) - 修复 tauri.conf.json 的 \ 指向仓库外路径,改用官方 config schema - .gitignore 补充 coverage | 26 days ago | |
feat: IPC 契约类型由 ts-rs 生成,补齐前端测试与 CI 门禁 - Rust 结构体加 ts_rs::TS,统一由 cargo test --test export_bindings 导出 到 src/generated/(19 个类型),前端 src/types.ts 改为 re-export, 消除人肉双维护 - 补齐 u64/i64 字段的 #[ts(type = "number")]:IPC 走 serde_json, 前端拿到的始终是 number,避免生成 bigint 与实际运行时不一致 - 新增 OpencodeSessionsResult 契约并纳入导出清单 - 移除 #[ts(export)]:原先每次 cargo test 会在 src-tauri/generated/ 落一份无人引用的副本 - 前端新增 vitest 单测 47 例(util / store / 共享组件 / 错误边界) - CI:前端跑测试、clippy 提到 -D warnings、新增契约漂移门禁 (内容级比对,不受 CRLF 与 stat 缓存干扰) - 修复 scripts/make_icons.py 源图路径(源图已移入 scripts/,原路径 指向根目录导致脚本跑不通) - 修复 tauri.conf.json 的 \ 指向仓库外路径,改用官方 config schema - .gitignore 补充 coverage | 26 days ago | |
feat: IPC 契约类型由 ts-rs 生成,补齐前端测试与 CI 门禁 - Rust 结构体加 ts_rs::TS,统一由 cargo test --test export_bindings 导出 到 src/generated/(19 个类型),前端 src/types.ts 改为 re-export, 消除人肉双维护 - 补齐 u64/i64 字段的 #[ts(type = "number")]:IPC 走 serde_json, 前端拿到的始终是 number,避免生成 bigint 与实际运行时不一致 - 新增 OpencodeSessionsResult 契约并纳入导出清单 - 移除 #[ts(export)]:原先每次 cargo test 会在 src-tauri/generated/ 落一份无人引用的副本 - 前端新增 vitest 单测 47 例(util / store / 共享组件 / 错误边界) - CI:前端跑测试、clippy 提到 -D warnings、新增契约漂移门禁 (内容级比对,不受 CRLF 与 stat 缓存干扰) - 修复 scripts/make_icons.py 源图路径(源图已移入 scripts/,原路径 指向根目录导致脚本跑不通) - 修复 tauri.conf.json 的 \ 指向仓库外路径,改用官方 config schema - .gitignore 补充 coverage | 26 days ago |
| Files | Last commit | Last update |
|---|---|---|
| 26 days ago | ||
| 16 days ago | ||
| 16 days ago | ||
| 4 days ago | ||
| 4 days ago | ||
| 26 days ago | ||
| 26 days ago | ||
| 26 days ago | ||
| 26 days ago | ||
| 14 days ago | ||
| 14 days ago | ||
| 26 days ago | ||
| 4 days ago | ||
| 26 days ago | ||
| 26 days ago | ||
| 26 days ago | ||
| 26 days ago | ||
| 17 days ago | ||
| 17 days ago | ||
| 18 days ago | ||
| 26 days ago | ||
| 4 days ago | ||
| 16 days ago | ||
| 26 days ago | ||
| 26 days ago | ||
| 26 days ago | ||
| 26 days ago | ||
| 26 days ago | ||
| 26 days ago | ||
| 26 days ago |