| Files | Last commit | Last update |
|---|---|---|
feat(ui): pick transport at startup and keep the mode visible The frontend called invoke() directly, so outside Tauri it silently fell back to demoBundle() — a browser could only ever see fake processes. Now transport.ts resolves IPC vs REST vs demo once at boot, shares one probe across concurrent callers, and broadcasts the result so the badge can switch from its placeholder. isTauri() stays a synchronous check (no network), because the first frame must not flicker into the wrong mode; isBackend() is the "do I have real data" question that pages ask. Header and sidebar show Live / Web · live data / Demo so a number on screen is never ambiguous. Three defects found while verifying the result visually: - port "open" was hardcoded to http://localhost:PORT — wrong for IPv6 listeners, HTTPS services and UDP ports; now derived from the actual listen address with UDP dropped. - every overlay used bg-black/55, which in the (now default) light theme turned the page into grey mud under a 2px blur; scrim colour follows data-chart-theme. - the cost trend axis mixed 2026-06 (monthly bucket) with 10-25 (daily), so readers could not tell which points were months; unified to 26/6月 vs 10-25, and the parser now checks shape instead of length so "unknown" no longer becomes un/NaN月. | 1 day ago | |
feat(theme,motion): 视觉 2.0 — 动效三档、卡片深度、渐变强调色、16 套主题与首启引导 做了什么: - 动效系统:store 新增 motion/motionExplicit/onboardingDone(persist v3→4, 迁移只补默认值),生效档位集中在 effectiveMotion()(未显式选择时系统 reduced-motion 自动降为 off,显式选择可覆盖);生效档写 <html data-motion>, global.css 末尾集中断闸,废除散落的 prefers-reduced-motion 块;新增 shimmer 骨架屏与 View Transitions(off 档/不支持的 WebView 瞬时切换); useCountUp 按档位调时长(off=0)。 - 卡片深度:--shadow-card 改由 elevation(0-3) 求解,废除「flat 一律无阴影」 旧规则;暗色高程配 1px 内描边补偿;--shadow-card-hover 供 hover 微抬升 (translateY -2px,只动 transform/box-shadow)。 - 渐变强调色:ThemeInput 新增 accent2(缺省 = accent 色相 +36°,过同一道 4.6 AA 求解)、gradientAccent(默认开,关档退化为零跨度渐变,消费方 无感知);应用于主按钮、统计大数字(--gradient-accent + background-clip)。 - 主题 8→16:新增 midnight-aurora / ember / matcha / grape-soda(暗)与 nordic-light / sakura / mono-brutal / sky(亮),全部过 buildTheme AA 求解; Terminal/Ink/Void/Mono Brutal 显式 elevation:0;parseTheme 对新字段只 clamp/落默认,旧 JSON 零迁移成本。 - 统计卡重做:图标 accent-soft 圆角底座、accent 档数字渐变,对外 props 不变。 - 首启 3 步引导:OnboardingOverlay(复用 Modal 焦点圈定),功能一览→主题 选择→动效档位,完成或跳过都持久化 onboardingDone 永不复弹;off 档用 纯 opacity 淡入(.motion-safe-fade 豁免类)。 - 外观面板新增动效三档设置;主题编辑器新增 accent2/渐变开关/氛围光/层级控件; i18n 三语同 key 同全。 为什么:对应 PRD docs/prd/2026-11-03-visual-2.0.md 与设计 docs/arch/2026-11-03-visual-2.0.md 的 T01–T04。全部动画只走 transform/opacity,新用色全部过 AA 求解器,无新运行时依赖。 测试:473 单测全绿(新增动效档位解析/迁移/accent2 派生/glow 与 elevation clamp/16 主题 AA 断言);e2e 断言文件的主题矩阵扩到 16 套并预置 onboardingDone(基线重生成留给 QA 阶段)。 | 4 days ago | |
fix: close the gaps found by a line-by-line pass Second review round, this time reading the code rather than trusting the commit messages. Several of the fixes below are corrections to my own previous "fixes". Data integrity - data::save_to_disk did not take the lock it was introduced to share, so save_data could still interleave with a mutate and overwrite it. Write path now locks; mutate uses an unlocked inner call to avoid self-deadlock. - agent scan_costs built its own PricingTable::default(), ignoring pricing_overrides.json. The same ledger produced two different totals depending on who scanned it. Both paths now share build_pricing_table. Contract correctness - AgentStep/AgentRunResult elapsed_ms were u64, which ts-rs rendered as bigint while IPC actually sends a number; the frontend then wrote 0n and rendered raw bigint. Marked #[ts(type = "number")] like every other u64. - AgentTurn was declared and never used; removed. approved_call_names and ToolCall were dead too. Assistant behaviour - the conversation was thrown away on every page switch (App unmounts pages via key={view}). Turns and pending approvals now live in the store, with a clear button. - tool history was not sent back to the model, so after approving a call and saying "continue" the model had no memory of what it had just done and redid it. buildMessages now rebuilds assistant(tool_calls) plus tool results in protocol order. - the run could not be stopped: added a cancel flag checked at each step and before each tool, plus a stop button. - provider settings opened blank in browser preview (provider was null and the body was gated on it). Initialised to an empty shell and the saved fallbacks are now filled back in. - JSON tool output was truncated mid-object, so the model saw broken JSON and retried. Arrays are now truncated by item with an explicit "showing X of Y". Cross-platform / packaging - productName was the Chinese word for Workbench; deb package names only allow [a-z0-9.+-]. Set to "Workbench" (window title still shows both languages). - Modal had a fixed p-6 outer margin, leaving 272px of content on a 320px screen, and could overflow vertically; now p-3 sm:p-6 with max-h-[90vh]. - main content and header padding now shrink on the compact breakpoint. Charts - isPieWorthRendering only counted categories, so an all-zero cost breakdown (every record unpriced) still drew a meaningless equal-slice circle. It now requires at least two non-zero values. Also: the data-changed event that tells the UI to reload after an agent tool writes data.json was never wired up; it is now emitted and listened for, with the listener-leak race on late promises handled. | 18 days ago | |
fix: close the gaps found by a line-by-line pass Second review round, this time reading the code rather than trusting the commit messages. Several of the fixes below are corrections to my own previous "fixes". Data integrity - data::save_to_disk did not take the lock it was introduced to share, so save_data could still interleave with a mutate and overwrite it. Write path now locks; mutate uses an unlocked inner call to avoid self-deadlock. - agent scan_costs built its own PricingTable::default(), ignoring pricing_overrides.json. The same ledger produced two different totals depending on who scanned it. Both paths now share build_pricing_table. Contract correctness - AgentStep/AgentRunResult elapsed_ms were u64, which ts-rs rendered as bigint while IPC actually sends a number; the frontend then wrote 0n and rendered raw bigint. Marked #[ts(type = "number")] like every other u64. - AgentTurn was declared and never used; removed. approved_call_names and ToolCall were dead too. Assistant behaviour - the conversation was thrown away on every page switch (App unmounts pages via key={view}). Turns and pending approvals now live in the store, with a clear button. - tool history was not sent back to the model, so after approving a call and saying "continue" the model had no memory of what it had just done and redid it. buildMessages now rebuilds assistant(tool_calls) plus tool results in protocol order. - the run could not be stopped: added a cancel flag checked at each step and before each tool, plus a stop button. - provider settings opened blank in browser preview (provider was null and the body was gated on it). Initialised to an empty shell and the saved fallbacks are now filled back in. - JSON tool output was truncated mid-object, so the model saw broken JSON and retried. Arrays are now truncated by item with an explicit "showing X of Y". Cross-platform / packaging - productName was the Chinese word for Workbench; deb package names only allow [a-z0-9.+-]. Set to "Workbench" (window title still shows both languages). - Modal had a fixed p-6 outer margin, leaving 272px of content on a 320px screen, and could overflow vertically; now p-3 sm:p-6 with max-h-[90vh]. - main content and header padding now shrink on the compact breakpoint. Charts - isPieWorthRendering only counted categories, so an all-zero cost breakdown (every record unpriced) still drew a meaningless equal-slice circle. It now requires at least two non-zero values. Also: the data-changed event that tells the UI to reload after an agent tool writes data.json was never wired up; it is now emitted and listened for, with the listener-leak race on late promises handled. | 18 days ago | |
feat(ui): pick transport at startup and keep the mode visible The frontend called invoke() directly, so outside Tauri it silently fell back to demoBundle() — a browser could only ever see fake processes. Now transport.ts resolves IPC vs REST vs demo once at boot, shares one probe across concurrent callers, and broadcasts the result so the badge can switch from its placeholder. isTauri() stays a synchronous check (no network), because the first frame must not flicker into the wrong mode; isBackend() is the "do I have real data" question that pages ask. Header and sidebar show Live / Web · live data / Demo so a number on screen is never ambiguous. Three defects found while verifying the result visually: - port "open" was hardcoded to http://localhost:PORT — wrong for IPv6 listeners, HTTPS services and UDP ports; now derived from the actual listen address with UDP dropped. - every overlay used bg-black/55, which in the (now default) light theme turned the page into grey mud under a 2px blur; scrim colour follows data-chart-theme. - the cost trend axis mixed 2026-06 (monthly bucket) with 10-25 (daily), so readers could not tell which points were months; unified to 26/6月 vs 10-25, and the parser now checks shape instead of length so "unknown" no longer becomes un/NaN月. | 1 day ago | |
fix(release): make the installed build actually usable — 0.7.1 Acceptance testing followed the real user path (silent install → launch → page-by-page walkthrough) and found two defects that only exist on an end-user machine, both invisible to every prior gate: - **Installed Web mode returned 404 for the page.** The install dir holds one exe and no dist/; the static-dir fallback chain ended at CARGO_MANIFEST_DIR/../dist — the build machine's source tree. Verified by moving dist away: GET / went 404 while /api stayed fine. Frontend assets are now embedded at compile time (rust-embed, 34 files, ~1.3 MB), so workbench serve is self-contained; a disk dist still wins so frontend-only iteration doesn't need a Rust rebuild. /api misses still return 404 JSON instead of falling through to the SPA. - **Pricing editor showed "desktop-only" in the browser.** Its load gate was isTauri() and the HTTP side had no pricing endpoint at all. Added GET/PUT /api/cost/pricing sharing the desktop validation (all-or-nothing), gate changed to isBackend(). Verified PUT → pricing_overrides.json → read-back identical. Acceptance walkthrough also confirmed working: project/task creation with restart persistence, process search + virtual scrolling, command palette jump, diagnostics (7 checks) in web mode, honest log-page degradation, theme badge "Web · live data". Uninstaller removes dir and registry key. | 23 hours ago | |
feat(ui): pick transport at startup and keep the mode visible The frontend called invoke() directly, so outside Tauri it silently fell back to demoBundle() — a browser could only ever see fake processes. Now transport.ts resolves IPC vs REST vs demo once at boot, shares one probe across concurrent callers, and broadcasts the result so the badge can switch from its placeholder. isTauri() stays a synchronous check (no network), because the first frame must not flicker into the wrong mode; isBackend() is the "do I have real data" question that pages ask. Header and sidebar show Live / Web · live data / Demo so a number on screen is never ambiguous. Three defects found while verifying the result visually: - port "open" was hardcoded to http://localhost:PORT — wrong for IPv6 listeners, HTTPS services and UDP ports; now derived from the actual listen address with UDP dropped. - every overlay used bg-black/55, which in the (now default) light theme turned the page into grey mud under a 2px blur; scrim colour follows data-chart-theme. - the cost trend axis mixed 2026-06 (monthly bucket) with 10-25 (daily), so readers could not tell which points were months; unified to 26/6月 vs 10-25, and the parser now checks shape instead of length so "unknown" no longer becomes un/NaN月. | 1 day ago | |
feat(states,autostart,perf): page-level states, real start-at-login, and a complexity pass Page states: ErrorState (role=alert + retry), Skeleton/SkeletonCard and Empty.action are now used by Processes, Servers, Projects, Tasks, the cost dashboard and the overview widgets. A backend failure shows an in-page error instead of a toast that disappears; a first load shows the shape of the data instead of four zeroed cards. Start at login: tauri-plugin-autostart registers the real OS entry (HKCU Run on Windows, ~/.config/autostart on Linux, LaunchAgent on macOS). The toggle reads is_enabled on every mount, so an entry removed in Task Manager shows as off. Browser preview never calls IPC. Verified against the packaged exe: enable / disable / UI state (3 checks in scripts/e2e-desktop.mjs). Charts: ECharts moved to its own chunk behind a lazy wrapper. Settings, Logs, Tasks and Projects no longer download 540 KB they never draw. Production measurement: FCP 180 ms, chart block requested after the overview paints. Complexity: every function in src/ is now <= 10 (was App 43, CostDashboard 32, parseTheme 22, buildTheme 19, Agent 17, Processes 14). New modules: useAppRuntime, useShellLayout, pages/processFilter, pages/processes/ProcessTable, pages/servers/DevServerList, pages/ai/costCharts. Fixes found while doing the above: - readableOn blended towards the background, so it could never reach 4.5:1 on light themes; it returned the original 2.78:1 grey. Now solves towards black/white first, then fades back (4 tests measure the ratio). - Editing a built-in theme wrote its id into the custom list, where findTheme kept returning the built-in: every edit looked like it did nothing. Editing now duplicates first (editableTheme + 2 tests). - Ledger layout laid a 168 px donut across a 1146 px row; small panels are now paired, large ones stay full width. - Three READMEs still described pricing overrides as read-only and port collection as Windows-only, two releases after both changed. Verification: tsc, eslint, prettier clean; 324 unit tests; coverage 93.66% lines / 85.81% branches (store 95.52%, api 94.73%, ui 89.88%); 16 Playwright visual + contrast tests; 16 desktop E2E checks against the packaged exe. | 16 days ago | |
feat(states,autostart,perf): page-level states, real start-at-login, and a complexity pass Page states: ErrorState (role=alert + retry), Skeleton/SkeletonCard and Empty.action are now used by Processes, Servers, Projects, Tasks, the cost dashboard and the overview widgets. A backend failure shows an in-page error instead of a toast that disappears; a first load shows the shape of the data instead of four zeroed cards. Start at login: tauri-plugin-autostart registers the real OS entry (HKCU Run on Windows, ~/.config/autostart on Linux, LaunchAgent on macOS). The toggle reads is_enabled on every mount, so an entry removed in Task Manager shows as off. Browser preview never calls IPC. Verified against the packaged exe: enable / disable / UI state (3 checks in scripts/e2e-desktop.mjs). Charts: ECharts moved to its own chunk behind a lazy wrapper. Settings, Logs, Tasks and Projects no longer download 540 KB they never draw. Production measurement: FCP 180 ms, chart block requested after the overview paints. Complexity: every function in src/ is now <= 10 (was App 43, CostDashboard 32, parseTheme 22, buildTheme 19, Agent 17, Processes 14). New modules: useAppRuntime, useShellLayout, pages/processFilter, pages/processes/ProcessTable, pages/servers/DevServerList, pages/ai/costCharts. Fixes found while doing the above: - readableOn blended towards the background, so it could never reach 4.5:1 on light themes; it returned the original 2.78:1 grey. Now solves towards black/white first, then fades back (4 tests measure the ratio). - Editing a built-in theme wrote its id into the custom list, where findTheme kept returning the built-in: every edit looked like it did nothing. Editing now duplicates first (editableTheme + 2 tests). - Ledger layout laid a 168 px donut across a 1146 px row; small panels are now paired, large ones stay full width. - Three READMEs still described pricing overrides as read-only and port collection as Windows-only, two releases after both changed. Verification: tsc, eslint, prettier clean; 324 unit tests; coverage 93.66% lines / 85.81% branches (store 95.52%, api 94.73%, ui 89.88%); 16 Playwright visual + contrast tests; 16 desktop E2E checks against the packaged exe. | 16 days ago | |
chore(release): cut 0.4.0, add version gate and fix chart label contrast The version number lived in four files plus a fifth hand-written copy in the UI. Bump to 0.4.0, move the single source to package.json, inject it at build time, and add npm run version:check to CI so a half-finished bump cannot ship. Three chart regressions found by reading the rendered screenshots: - Horizontal bar value labels inherited the series gradient colour and became near-invisible on dark themes (the "3" in the port distribution read as an empty cell). Labels now use the theme text colour. - Bar-top labels in the process histograms collided with the top gridline; the value axis gets one extra step and no split line. - The AI memory chart put its "NNN MB" label on top of the axis tick; the axis now leaves 30% headroom and the grid widened to fit the label. Also: unify the workflow branch filters on main, document the branch and versioning policy in CONTRIBUTING, add docs/ROADMAP.md with an explicit not-planned section, replace GitHub-only badges with platform-neutral ones, and regenerate the screenshots and the four affected visual baselines. | 5 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(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(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(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(diagnostics,theme): frontend diagnostics buffer, logs filtering, and a light default that does not flash Frontend diagnostics: - src/lib/diagnostics.ts: capture global error / unhandledrejection (beyond what ErrorBoundary sees), keep the last 50 with id, kind, stack and page; consecutive duplicates collapse; localStorage only. - ErrorBoundary: expandable stack + one-click copy for issue reports, crashes also land in the buffer. - Settings -> Diagnostics: frontend error list, clear button, appended to the copyable report. Theme: - default is light Frost; index.html pins first-paint tokens and syncs saved polarity before paint, so neither polarity flashes. - accentsoft is solved from the 4.6:1 target per theme instead of a fixed mix ratio (Frost measured 4.45:1 before); test covers all eight built-ins. - theme editor shows an auto-adjusted badge when the contrast solve will change the user's color on save. Logs page: - finish the interrupted edit: split("\n") was broken across lines, LogLevel / levelOf were referenced but missing; add keyword search, level filter with counts, shown/total line counter; log pane uses theme-derived panel2 instead of a hardcoded dark mix (timestamps measured 4.02:1 on Frost). E2E: custom-theme accent asserts hue preserved + AA after the solve; visual baselines regenerated for the light default. | 4 days ago | |
feat(theme,motion): 视觉 2.0 — 动效三档、卡片深度、渐变强调色、16 套主题与首启引导 做了什么: - 动效系统:store 新增 motion/motionExplicit/onboardingDone(persist v3→4, 迁移只补默认值),生效档位集中在 effectiveMotion()(未显式选择时系统 reduced-motion 自动降为 off,显式选择可覆盖);生效档写 <html data-motion>, global.css 末尾集中断闸,废除散落的 prefers-reduced-motion 块;新增 shimmer 骨架屏与 View Transitions(off 档/不支持的 WebView 瞬时切换); useCountUp 按档位调时长(off=0)。 - 卡片深度:--shadow-card 改由 elevation(0-3) 求解,废除「flat 一律无阴影」 旧规则;暗色高程配 1px 内描边补偿;--shadow-card-hover 供 hover 微抬升 (translateY -2px,只动 transform/box-shadow)。 - 渐变强调色:ThemeInput 新增 accent2(缺省 = accent 色相 +36°,过同一道 4.6 AA 求解)、gradientAccent(默认开,关档退化为零跨度渐变,消费方 无感知);应用于主按钮、统计大数字(--gradient-accent + background-clip)。 - 主题 8→16:新增 midnight-aurora / ember / matcha / grape-soda(暗)与 nordic-light / sakura / mono-brutal / sky(亮),全部过 buildTheme AA 求解; Terminal/Ink/Void/Mono Brutal 显式 elevation:0;parseTheme 对新字段只 clamp/落默认,旧 JSON 零迁移成本。 - 统计卡重做:图标 accent-soft 圆角底座、accent 档数字渐变,对外 props 不变。 - 首启 3 步引导:OnboardingOverlay(复用 Modal 焦点圈定),功能一览→主题 选择→动效档位,完成或跳过都持久化 onboardingDone 永不复弹;off 档用 纯 opacity 淡入(.motion-safe-fade 豁免类)。 - 外观面板新增动效三档设置;主题编辑器新增 accent2/渐变开关/氛围光/层级控件; i18n 三语同 key 同全。 为什么:对应 PRD docs/prd/2026-11-03-visual-2.0.md 与设计 docs/arch/2026-11-03-visual-2.0.md 的 T01–T04。全部动画只走 transform/opacity,新用色全部过 AA 求解器,无新运行时依赖。 测试:473 单测全绿(新增动效档位解析/迁移/accent2 派生/glow 与 elevation clamp/16 主题 AA 断言);e2e 断言文件的主题矩阵扩到 16 套并预置 onboardingDone(基线重生成留给 QA 阶段)。 | 4 days ago | |
feat(states,autostart,perf): page-level states, real start-at-login, and a complexity pass Page states: ErrorState (role=alert + retry), Skeleton/SkeletonCard and Empty.action are now used by Processes, Servers, Projects, Tasks, the cost dashboard and the overview widgets. A backend failure shows an in-page error instead of a toast that disappears; a first load shows the shape of the data instead of four zeroed cards. Start at login: tauri-plugin-autostart registers the real OS entry (HKCU Run on Windows, ~/.config/autostart on Linux, LaunchAgent on macOS). The toggle reads is_enabled on every mount, so an entry removed in Task Manager shows as off. Browser preview never calls IPC. Verified against the packaged exe: enable / disable / UI state (3 checks in scripts/e2e-desktop.mjs). Charts: ECharts moved to its own chunk behind a lazy wrapper. Settings, Logs, Tasks and Projects no longer download 540 KB they never draw. Production measurement: FCP 180 ms, chart block requested after the overview paints. Complexity: every function in src/ is now <= 10 (was App 43, CostDashboard 32, parseTheme 22, buildTheme 19, Agent 17, Processes 14). New modules: useAppRuntime, useShellLayout, pages/processFilter, pages/processes/ProcessTable, pages/servers/DevServerList, pages/ai/costCharts. Fixes found while doing the above: - readableOn blended towards the background, so it could never reach 4.5:1 on light themes; it returned the original 2.78:1 grey. Now solves towards black/white first, then fades back (4 tests measure the ratio). - Editing a built-in theme wrote its id into the custom list, where findTheme kept returning the built-in: every edit looked like it did nothing. Editing now duplicates first (editableTheme + 2 tests). - Ledger layout laid a 168 px donut across a 1146 px row; small panels are now paired, large ones stay full width. - Three READMEs still described pricing overrides as read-only and port collection as Windows-only, two releases after both changed. Verification: tsc, eslint, prettier clean; 324 unit tests; coverage 93.66% lines / 85.81% branches (store 95.52%, api 94.73%, ui 89.88%); 16 Playwright visual + contrast tests; 16 desktop E2E checks against the packaged exe. | 16 days ago | |
test(coverage): 补 particles 生命周期与 downloadText —— 函数覆盖回 90% 线上 依赖合并(vitest/coverage-v8 小版本更新)改变了函数计数口径, 全局函数覆盖跌到 89.69% < 90% 门禁。补 3 个真实行为测试: - particles:ResizeObserver 触发 resize、visibility 回调隐藏暂停/ 恢复重调度(此前内部 start/stop/resize 从未被测试驱动)。 - export.downloadText:Blob 链接创建、a.click 触发、URL 回收。 492 测试全绿,函数覆盖 90.63%。 | 3 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(visual): 粒子强度滑杆、自定义背景图、空态插画 用户批准的 P1 三项全量落地: 粒子强度(0-100): - AppearancePanel 滑杆(开启粒子时显示),store 持久化并钳制。 - ParticleBackdrop 改读用户强度,内部仍封顶 0.55 透明度。 自定义背景图: - AppearancePanel 选图 → FileReader → Image 解码 → 等比压缩最长边 1920 → JPEG 原样/其余转 PNG(canvas 画 GIF 只取首帧)→ 2.5MB 容量守卫 → setBackground。全链路本机,绝不上传。 - BackgroundImage 组件:78% 主题底色遮罩保证任何壁纸下可读; z-0 与光晕/粒子同层。store 白名单只收 data:image/*,在写入 源头拒收其它 URL(注入面在入口消灭)。 空态插画: - 共享 Empty 组件升级:虚线托盘 + 悬浮盒 + 语义图标嵌盒面 + accent/accent2 光点,1.5px 缓浮(data-motion=off 集中断闸)。 全站 10+ 空态一处升级全部受益;任务页手搓空态换用 Empty。 测试 +9(缩放计算/格式选择/容量守卫/强度钳制/背景图白名单/ v5→v6 迁移),489 全绿。设置页基线合法漂移已重生成,e2e 连跑 两遍 23 全过。截图自检:空态插画与设置页新控件人审通过。 | 3 days ago | |
feat(visual): 粒子强度滑杆、自定义背景图、空态插画 用户批准的 P1 三项全量落地: 粒子强度(0-100): - AppearancePanel 滑杆(开启粒子时显示),store 持久化并钳制。 - ParticleBackdrop 改读用户强度,内部仍封顶 0.55 透明度。 自定义背景图: - AppearancePanel 选图 → FileReader → Image 解码 → 等比压缩最长边 1920 → JPEG 原样/其余转 PNG(canvas 画 GIF 只取首帧)→ 2.5MB 容量守卫 → setBackground。全链路本机,绝不上传。 - BackgroundImage 组件:78% 主题底色遮罩保证任何壁纸下可读; z-0 与光晕/粒子同层。store 白名单只收 data:image/*,在写入 源头拒收其它 URL(注入面在入口消灭)。 空态插画: - 共享 Empty 组件升级:虚线托盘 + 悬浮盒 + 语义图标嵌盒面 + accent/accent2 光点,1.5px 缓浮(data-motion=off 集中断闸)。 全站 10+ 空态一处升级全部受益;任务页手搓空态换用 Empty。 测试 +9(缩放计算/格式选择/容量守卫/强度钳制/背景图白名单/ v5→v6 迁移),489 全绿。设置页基线合法漂移已重生成,e2e 连跑 两遍 23 全过。截图自检:空态插画与设置页新控件人审通过。 | 3 days ago | |
refactor!: remove AI chat module, add UAC-elevated process kill, showcase v0.2.0 Utility audit verdict (tech-debt + architect, dual-evidenced): every module kept except the AI chat + tool-approval module - its 13 tools were wrappers around existing page commands (zero exclusive capability), it required a user-configured OpenAI-compatible key to do anything, and it was outside the regularly-run test chain. AI session cost metering (ai.rs, session file scanning, SQLite ledger) is untouched: local-file parsing, no model dependency, unique value. Removed (-~4200 lines): - src-tauri/src/agent/ (mod/llm/tools/executor, 2072 lines, 8 IPC commands) - src/pages/Agent.tsx, agentApi(+test), i18n agent dict, 12 generated contract files, data-changed event chain, e2e-agent.mjs, docs references Added: - terminate_process_elevated(pid: u32): shared protected-list validation (lsass/svchost/... can never be elevated-killed), live PID+name re-check before spawn (TOCTOU), PowerShell Start-Process taskkill -Verb RunAs behind CREATE_NO_WINDOW; non-Windows fails closed - KillDialog: "End as administrator" appears only after a normal kill fails and only on Windows; errors surface via existing toast i18n - 5 new Rust unit tests + platform-gated UI; cargo test --lib 83/83 Quality gates (independently re-run by QA + lead): tsc 0, eslint 0, vitest 346/346, prettier clean, vite build ok, cargo check/clippy 0/0, cargo test 83/83, Playwright 16/16 with 23 visual baselines regenerated (--update-snapshots=all; 0.5% budget restored after audit-era drift had consumed 90% of it). Security review: 6/6 checks pass (IPC surface shrank by 8 commands; no secret ever touched disk). Docs: README x3 rebuilt as a screenshot-first showcase (hero, 2x3 grid, feature table, download, minimal build); long-form moved to docs/ARCHITECTURE{,.zh-CN,.ja}.md; 9 page screenshots re-shot without the removed nav item; CHANGELOG cut as [0.2.0] with a Removed section; version 0.2.0 in package.json / tauri.conf.json / Cargo.toml / APP_VERSION. AQG: plan-tier ledger .aqg/code-construction/agent-removal-and-elevated-kill.md (construction check PASSED); review verdict APPROVE-WITH-NITPICKS with all 9 findings fixed pre-commit; hosted cross-LLM rerun still open. | 15 days ago | |
feat(cost,theme): SQLite cost ledger, theme-driven charts, editable widget widths Cost ledger: replace the single cost_data.json with SQLite (rusqlite bundled). Records get indexes on day / model / file, the scan writes incrementally inside one transaction, and overview / trend / breakdown are SQL aggregates instead of "parse the whole file, then loop". 100k records: 246 ms for all three. The old cost_data.json is imported once and renamed .migrated (never deleted); a parse failure leaves both files alone and retries next launch. Verified on the packaged exe: migrated settings (7.0 budget / 75% / estimates off) and a record came through, and survived a restart. Backups now snapshot with VACUUM INTO (a plain copy can catch a half-merged journal); restore validates PRAGMA integrity_check first and clears -wal/-shm/-journal afterwards. Theme: the chart palette is derived from the accent hue instead of a fixed purple/blue/pink list, so paper and ink themes stop growing purple charts. The editor now exposes the ok/warn/danger colours that parseTheme always accepted but no UI could reach, shows a live WCAG contrast ratio beside each colour, and adds an UI scale slider (85-120%) that zooms the whole WebView rather than the handful of rem-based sizes. Layout: widgetSpans stores per-widget column overrides (4/6/8/12) with a picker in Settings; bento and ledger honour them. "Custom layout" previously meant order and visibility only. Tooling: scripts/ci-local.mjs runs the same 11 steps as ci.yml on this machine (icon contract, tsc, eslint, prettier, unit, build, Playwright, cargo fmt/test/clippy, IPC drift) so the remote run confirms rather than discovers. --linux tries the Linux job in Docker and states plainly when it cannot. Verification: ci-local 11/11; coverage 93.90% lines / 86.40% branches; 342 unit tests; 16 Playwright; 104 Rust; 16 desktop E2E against the packaged exe. | 15 days ago | |
feat(theme,overview): theme share links and two more overview layouts Completes the two doable items left in docs/plan-audit.md §4. Theme share links (URL fragment, no backend): - new src/lib/theme/share.ts packs a ThemeInput into #theme= (base64url of the same JSON the export button writes) and decodes it back through the exact parseTheme validator the JSON import uses — fail-closed, hex/enum whitelists, numeric clamps; hash is consumed once and stripped from the address bar whether the payload is valid or not, so reloading cannot import twice; id collisions rename instead of overwriting - appearance panel gains a Share link button (clipboard, toast feedback); main.tsx consumes the hash before the boot applyTheme so the imported theme is what gets applied - 12 unit tests: round-trips (unicode names, all 8 built-ins), URL-safe alphabet, fail-closed decode, hash consume/clear semantics Two more overview layouts (now seven): - timeline: every visible widget as a numbered stop on one vertical rail, in the user's order; accent-ringed markers stay legible on dark themes - board: three fixed lanes (signals / resources / work) grouped by purpose; in-lane order follows the user's order (bucketed from ids, locked by a two-widgets-per-lane regression test), empty lanes are not rendered - wireframe previews for both; i18n keys x3 languages (dict.test parity) Tests/gates: vitest 359/359, tsc strict clean, eslint clean, prettier clean, vite build ok, Playwright 16/16 (visual suite now covers seven layouts; button[data-layout] pinned to 7). New baselines for the two layouts; bento/dense/ledger baselines refreshed by --update-snapshots=all resolve pre-existing chart-palette drift from ad709ee that the 0.5% diff tolerance had been silently tolerating (verified pixel-wise, not introduced here). AQG: plan-tier ledger .aqg/code-construction/theme-share-and-more-layouts.md; Gate A (trust-boundary hash input) routed deep, but hosted Decision Engine MCP tools were unavailable pre-dispatch, so the round ran the DE Lite local five-dimension advisory (advisory_only, audit_id=null, decision needs-cross-llm-rerun, ledger VALID). The advisory's one MEDIUM logic finding (boardLanes in-lane order) is fixed in this commit; two LOW findings rejected with verified reasons. The cross-LLM rerun stays open. Docs: README x3 roadmaps (seven layouts, share-link item), CHANGELOG Unreleased, plan-audit §4 items 3+4 marked done with evidence. Code signing (paid certificate) and auto-update (signed release channel) remain blocked exactly as documented. | 15 days ago | |
feat(theme,overview): theme share links and two more overview layouts Completes the two doable items left in docs/plan-audit.md §4. Theme share links (URL fragment, no backend): - new src/lib/theme/share.ts packs a ThemeInput into #theme= (base64url of the same JSON the export button writes) and decodes it back through the exact parseTheme validator the JSON import uses — fail-closed, hex/enum whitelists, numeric clamps; hash is consumed once and stripped from the address bar whether the payload is valid or not, so reloading cannot import twice; id collisions rename instead of overwriting - appearance panel gains a Share link button (clipboard, toast feedback); main.tsx consumes the hash before the boot applyTheme so the imported theme is what gets applied - 12 unit tests: round-trips (unicode names, all 8 built-ins), URL-safe alphabet, fail-closed decode, hash consume/clear semantics Two more overview layouts (now seven): - timeline: every visible widget as a numbered stop on one vertical rail, in the user's order; accent-ringed markers stay legible on dark themes - board: three fixed lanes (signals / resources / work) grouped by purpose; in-lane order follows the user's order (bucketed from ids, locked by a two-widgets-per-lane regression test), empty lanes are not rendered - wireframe previews for both; i18n keys x3 languages (dict.test parity) Tests/gates: vitest 359/359, tsc strict clean, eslint clean, prettier clean, vite build ok, Playwright 16/16 (visual suite now covers seven layouts; button[data-layout] pinned to 7). New baselines for the two layouts; bento/dense/ledger baselines refreshed by --update-snapshots=all resolve pre-existing chart-palette drift from ad709ee that the 0.5% diff tolerance had been silently tolerating (verified pixel-wise, not introduced here). AQG: plan-tier ledger .aqg/code-construction/theme-share-and-more-layouts.md; Gate A (trust-boundary hash input) routed deep, but hosted Decision Engine MCP tools were unavailable pre-dispatch, so the round ran the DE Lite local five-dimension advisory (advisory_only, audit_id=null, decision needs-cross-llm-rerun, ledger VALID). The advisory's one MEDIUM logic finding (boardLanes in-lane order) is fixed in this commit; two LOW findings rejected with verified reasons. The cross-LLM rerun stays open. Docs: README x3 roadmaps (seven layouts, share-link item), CHANGELOG Unreleased, plan-audit §4 items 3+4 marked done with evidence. Code signing (paid certificate) and auto-update (signed release channel) remain blocked exactly as documented. | 15 days ago | |
feat(ui): pick transport at startup and keep the mode visible The frontend called invoke() directly, so outside Tauri it silently fell back to demoBundle() — a browser could only ever see fake processes. Now transport.ts resolves IPC vs REST vs demo once at boot, shares one probe across concurrent callers, and broadcasts the result so the badge can switch from its placeholder. isTauri() stays a synchronous check (no network), because the first frame must not flicker into the wrong mode; isBackend() is the "do I have real data" question that pages ask. Header and sidebar show Live / Web · live data / Demo so a number on screen is never ambiguous. Three defects found while verifying the result visually: - port "open" was hardcoded to http://localhost:PORT — wrong for IPv6 listeners, HTTPS services and UDP ports; now derived from the actual listen address with UDP dropped. - every overlay used bg-black/55, which in the (now default) light theme turned the page into grey mud under a 2px blur; scrim colour follows data-chart-theme. - the cost trend axis mixed 2026-06 (monthly bucket) with 10-25 (daily), so readers could not tell which points were months; unified to 26/6月 vs 10-25, and the parser now checks shape instead of length so "unknown" no longer becomes un/NaN月. | 1 day ago | |
feat(ui): pick transport at startup and keep the mode visible The frontend called invoke() directly, so outside Tauri it silently fell back to demoBundle() — a browser could only ever see fake processes. Now transport.ts resolves IPC vs REST vs demo once at boot, shares one probe across concurrent callers, and broadcasts the result so the badge can switch from its placeholder. isTauri() stays a synchronous check (no network), because the first frame must not flicker into the wrong mode; isBackend() is the "do I have real data" question that pages ask. Header and sidebar show Live / Web · live data / Demo so a number on screen is never ambiguous. Three defects found while verifying the result visually: - port "open" was hardcoded to http://localhost:PORT — wrong for IPv6 listeners, HTTPS services and UDP ports; now derived from the actual listen address with UDP dropped. - every overlay used bg-black/55, which in the (now default) light theme turned the page into grey mud under a 2px blur; scrim colour follows data-chart-theme. - the cost trend axis mixed 2026-06 (monthly bucket) with 10-25 (daily), so readers could not tell which points were months; unified to 26/6月 vs 10-25, and the parser now checks shape instead of length so "unknown" no longer becomes un/NaN月. | 1 day ago | |
test(coverage): 补 particles 生命周期与 downloadText —— 函数覆盖回 90% 线上 依赖合并(vitest/coverage-v8 小版本更新)改变了函数计数口径, 全局函数覆盖跌到 89.69% < 90% 门禁。补 3 个真实行为测试: - particles:ResizeObserver 触发 resize、visibility 回调隐藏暂停/ 恢复重调度(此前内部 start/stop/resize 从未被测试驱动)。 - export.downloadText:Blob 链接创建、a.click 触发、URL 回收。 492 测试全绿,函数覆盖 90.63%。 | 3 days ago | |
feat(theme): 氛围粒子背景 — 主题取色、三重省电、默认关 用户拍板:粒子背景做成开关,选择权交给用户。 - src/lib/particles.ts: Canvas 2D 粒子场,零依赖。颜色取自 --color-accent/accent2/text(每秒刷新,换主题自动跟随); 粒子数按面积钳制 24-72;上浮+正弦摆动,整体透明度封顶 0.55 ——定位是氛围,不是屏保。 - ParticleBackdrop: fixed 全屏 z-0,pointer-events-none;默认关; 生效动效档 off 时停用;页面隐藏暂停 rAF。 - store: particles 偏好,persist v4→v5(迁移只补默认 false)。 - AppearancePanel: 氛围粒子 开启/关闭;i18n 三语。 - 测试 +11:数量钳制、粒子推进/环绕、rAF 生命周期、隐藏不启动、 无 2D 上下文降级、v4→v5 迁移。482 单测全绿。 - e2e: 外壳/设置基线因新增粒子卡合法漂移,重生成后连跑两遍 23 全过。 门禁:tsc/lint/format/dup(0.31%)/vitest/build 全绿。 | 3 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(visual): 粒子强度滑杆、自定义背景图、空态插画 用户批准的 P1 三项全量落地: 粒子强度(0-100): - AppearancePanel 滑杆(开启粒子时显示),store 持久化并钳制。 - ParticleBackdrop 改读用户强度,内部仍封顶 0.55 透明度。 自定义背景图: - AppearancePanel 选图 → FileReader → Image 解码 → 等比压缩最长边 1920 → JPEG 原样/其余转 PNG(canvas 画 GIF 只取首帧)→ 2.5MB 容量守卫 → setBackground。全链路本机,绝不上传。 - BackgroundImage 组件:78% 主题底色遮罩保证任何壁纸下可读; z-0 与光晕/粒子同层。store 白名单只收 data:image/*,在写入 源头拒收其它 URL(注入面在入口消灭)。 空态插画: - 共享 Empty 组件升级:虚线托盘 + 悬浮盒 + 语义图标嵌盒面 + accent/accent2 光点,1.5px 缓浮(data-motion=off 集中断闸)。 全站 10+ 空态一处升级全部受益;任务页手搓空态换用 Empty。 测试 +9(缩放计算/格式选择/容量守卫/强度钳制/背景图白名单/ v5→v6 迁移),489 全绿。设置页基线合法漂移已重生成,e2e 连跑 两遍 23 全过。截图自检:空态插画与设置页新控件人审通过。 | 3 days ago | |
feat(ui): pick transport at startup and keep the mode visible The frontend called invoke() directly, so outside Tauri it silently fell back to demoBundle() — a browser could only ever see fake processes. Now transport.ts resolves IPC vs REST vs demo once at boot, shares one probe across concurrent callers, and broadcasts the result so the badge can switch from its placeholder. isTauri() stays a synchronous check (no network), because the first frame must not flicker into the wrong mode; isBackend() is the "do I have real data" question that pages ask. Header and sidebar show Live / Web · live data / Demo so a number on screen is never ambiguous. Three defects found while verifying the result visually: - port "open" was hardcoded to http://localhost:PORT — wrong for IPv6 listeners, HTTPS services and UDP ports; now derived from the actual listen address with UDP dropped. - every overlay used bg-black/55, which in the (now default) light theme turned the page into grey mud under a 2px blur; scrim colour follows data-chart-theme. - the cost trend axis mixed 2026-06 (monthly bucket) with 10-25 (daily), so readers could not tell which points were months; unified to 26/6月 vs 10-25, and the parser now checks shape instead of length so "unknown" no longer becomes un/NaN月. | 1 day ago | |
feat(ui): pick transport at startup and keep the mode visible The frontend called invoke() directly, so outside Tauri it silently fell back to demoBundle() — a browser could only ever see fake processes. Now transport.ts resolves IPC vs REST vs demo once at boot, shares one probe across concurrent callers, and broadcasts the result so the badge can switch from its placeholder. isTauri() stays a synchronous check (no network), because the first frame must not flicker into the wrong mode; isBackend() is the "do I have real data" question that pages ask. Header and sidebar show Live / Web · live data / Demo so a number on screen is never ambiguous. Three defects found while verifying the result visually: - port "open" was hardcoded to http://localhost:PORT — wrong for IPv6 listeners, HTTPS services and UDP ports; now derived from the actual listen address with UDP dropped. - every overlay used bg-black/55, which in the (now default) light theme turned the page into grey mud under a 2px blur; scrim colour follows data-chart-theme. - the cost trend axis mixed 2026-06 (monthly bucket) with 10-25 (daily), so readers could not tell which points were months; unified to 26/6月 vs 10-25, and the parser now checks shape instead of length so "unknown" no longer becomes un/NaN月. | 1 day ago | |
feat(ui): pick transport at startup and keep the mode visible The frontend called invoke() directly, so outside Tauri it silently fell back to demoBundle() — a browser could only ever see fake processes. Now transport.ts resolves IPC vs REST vs demo once at boot, shares one probe across concurrent callers, and broadcasts the result so the badge can switch from its placeholder. isTauri() stays a synchronous check (no network), because the first frame must not flicker into the wrong mode; isBackend() is the "do I have real data" question that pages ask. Header and sidebar show Live / Web · live data / Demo so a number on screen is never ambiguous. Three defects found while verifying the result visually: - port "open" was hardcoded to http://localhost:PORT — wrong for IPv6 listeners, HTTPS services and UDP ports; now derived from the actual listen address with UDP dropped. - every overlay used bg-black/55, which in the (now default) light theme turned the page into grey mud under a 2px blur; scrim colour follows data-chart-theme. - the cost trend axis mixed 2026-06 (monthly bucket) with 10-25 (daily), so readers could not tell which points were months; unified to 26/6月 vs 10-25, and the parser now checks shape instead of length so "unknown" no longer becomes un/NaN月. | 1 day ago | |
feat(ui): pick transport at startup and keep the mode visible The frontend called invoke() directly, so outside Tauri it silently fell back to demoBundle() — a browser could only ever see fake processes. Now transport.ts resolves IPC vs REST vs demo once at boot, shares one probe across concurrent callers, and broadcasts the result so the badge can switch from its placeholder. isTauri() stays a synchronous check (no network), because the first frame must not flicker into the wrong mode; isBackend() is the "do I have real data" question that pages ask. Header and sidebar show Live / Web · live data / Demo so a number on screen is never ambiguous. Three defects found while verifying the result visually: - port "open" was hardcoded to http://localhost:PORT — wrong for IPv6 listeners, HTTPS services and UDP ports; now derived from the actual listen address with UDP dropped. - every overlay used bg-black/55, which in the (now default) light theme turned the page into grey mud under a 2px blur; scrim colour follows data-chart-theme. - the cost trend axis mixed 2026-06 (monthly bucket) with 10-25 (daily), so readers could not tell which points were months; unified to 26/6月 vs 10-25, and the parser now checks shape instead of length so "unknown" no longer becomes un/NaN月. | 1 day ago | |
feat(ui): pick transport at startup and keep the mode visible The frontend called invoke() directly, so outside Tauri it silently fell back to demoBundle() — a browser could only ever see fake processes. Now transport.ts resolves IPC vs REST vs demo once at boot, shares one probe across concurrent callers, and broadcasts the result so the badge can switch from its placeholder. isTauri() stays a synchronous check (no network), because the first frame must not flicker into the wrong mode; isBackend() is the "do I have real data" question that pages ask. Header and sidebar show Live / Web · live data / Demo so a number on screen is never ambiguous. Three defects found while verifying the result visually: - port "open" was hardcoded to http://localhost:PORT — wrong for IPv6 listeners, HTTPS services and UDP ports; now derived from the actual listen address with UDP dropped. - every overlay used bg-black/55, which in the (now default) light theme turned the page into grey mud under a 2px blur; scrim colour follows data-chart-theme. - the cost trend axis mixed 2026-06 (monthly bucket) with 10-25 (daily), so readers could not tell which points were months; unified to 26/6月 vs 10-25, and the parser now checks shape instead of length so "unknown" no longer becomes un/NaN月. | 1 day ago | |
feat(ui): pick transport at startup and keep the mode visible The frontend called invoke() directly, so outside Tauri it silently fell back to demoBundle() — a browser could only ever see fake processes. Now transport.ts resolves IPC vs REST vs demo once at boot, shares one probe across concurrent callers, and broadcasts the result so the badge can switch from its placeholder. isTauri() stays a synchronous check (no network), because the first frame must not flicker into the wrong mode; isBackend() is the "do I have real data" question that pages ask. Header and sidebar show Live / Web · live data / Demo so a number on screen is never ambiguous. Three defects found while verifying the result visually: - port "open" was hardcoded to http://localhost:PORT — wrong for IPv6 listeners, HTTPS services and UDP ports; now derived from the actual listen address with UDP dropped. - every overlay used bg-black/55, which in the (now default) light theme turned the page into grey mud under a 2px blur; scrim colour follows data-chart-theme. - the cost trend axis mixed 2026-06 (monthly bucket) with 10-25 (daily), so readers could not tell which points were months; unified to 26/6月 vs 10-25, and the parser now checks shape instead of length so "unknown" no longer becomes un/NaN月. | 1 day ago | |
feat(ui): pick transport at startup and keep the mode visible The frontend called invoke() directly, so outside Tauri it silently fell back to demoBundle() — a browser could only ever see fake processes. Now transport.ts resolves IPC vs REST vs demo once at boot, shares one probe across concurrent callers, and broadcasts the result so the badge can switch from its placeholder. isTauri() stays a synchronous check (no network), because the first frame must not flicker into the wrong mode; isBackend() is the "do I have real data" question that pages ask. Header and sidebar show Live / Web · live data / Demo so a number on screen is never ambiguous. Three defects found while verifying the result visually: - port "open" was hardcoded to http://localhost:PORT — wrong for IPv6 listeners, HTTPS services and UDP ports; now derived from the actual listen address with UDP dropped. - every overlay used bg-black/55, which in the (now default) light theme turned the page into grey mud under a 2px blur; scrim colour follows data-chart-theme. - the cost trend axis mixed 2026-06 (monthly bucket) with 10-25 (daily), so readers could not tell which points were months; unified to 26/6月 vs 10-25, and the parser now checks shape instead of length so "unknown" no longer becomes un/NaN月. | 1 day ago | |
feat(cost,theme): SQLite cost ledger, theme-driven charts, editable widget widths Cost ledger: replace the single cost_data.json with SQLite (rusqlite bundled). Records get indexes on day / model / file, the scan writes incrementally inside one transaction, and overview / trend / breakdown are SQL aggregates instead of "parse the whole file, then loop". 100k records: 246 ms for all three. The old cost_data.json is imported once and renamed .migrated (never deleted); a parse failure leaves both files alone and retries next launch. Verified on the packaged exe: migrated settings (7.0 budget / 75% / estimates off) and a record came through, and survived a restart. Backups now snapshot with VACUUM INTO (a plain copy can catch a half-merged journal); restore validates PRAGMA integrity_check first and clears -wal/-shm/-journal afterwards. Theme: the chart palette is derived from the accent hue instead of a fixed purple/blue/pink list, so paper and ink themes stop growing purple charts. The editor now exposes the ok/warn/danger colours that parseTheme always accepted but no UI could reach, shows a live WCAG contrast ratio beside each colour, and adds an UI scale slider (85-120%) that zooms the whole WebView rather than the handful of rem-based sizes. Layout: widgetSpans stores per-widget column overrides (4/6/8/12) with a picker in Settings; bento and ledger honour them. "Custom layout" previously meant order and visibility only. Tooling: scripts/ci-local.mjs runs the same 11 steps as ci.yml on this machine (icon contract, tsc, eslint, prettier, unit, build, Playwright, cargo fmt/test/clippy, IPC drift) so the remote run confirms rather than discovers. --linux tries the Linux job in Docker and states plainly when it cannot. Verification: ci-local 11/11; coverage 93.90% lines / 86.40% branches; 342 unit tests; 16 Playwright; 104 Rust; 16 desktop E2E against the packaged exe. | 15 days ago | |
feat(states,autostart,perf): page-level states, real start-at-login, and a complexity pass Page states: ErrorState (role=alert + retry), Skeleton/SkeletonCard and Empty.action are now used by Processes, Servers, Projects, Tasks, the cost dashboard and the overview widgets. A backend failure shows an in-page error instead of a toast that disappears; a first load shows the shape of the data instead of four zeroed cards. Start at login: tauri-plugin-autostart registers the real OS entry (HKCU Run on Windows, ~/.config/autostart on Linux, LaunchAgent on macOS). The toggle reads is_enabled on every mount, so an entry removed in Task Manager shows as off. Browser preview never calls IPC. Verified against the packaged exe: enable / disable / UI state (3 checks in scripts/e2e-desktop.mjs). Charts: ECharts moved to its own chunk behind a lazy wrapper. Settings, Logs, Tasks and Projects no longer download 540 KB they never draw. Production measurement: FCP 180 ms, chart block requested after the overview paints. Complexity: every function in src/ is now <= 10 (was App 43, CostDashboard 32, parseTheme 22, buildTheme 19, Agent 17, Processes 14). New modules: useAppRuntime, useShellLayout, pages/processFilter, pages/processes/ProcessTable, pages/servers/DevServerList, pages/ai/costCharts. Fixes found while doing the above: - readableOn blended towards the background, so it could never reach 4.5:1 on light themes; it returned the original 2.78:1 grey. Now solves towards black/white first, then fades back (4 tests measure the ratio). - Editing a built-in theme wrote its id into the custom list, where findTheme kept returning the built-in: every edit looked like it did nothing. Editing now duplicates first (editableTheme + 2 tests). - Ledger layout laid a 168 px donut across a 1146 px row; small panels are now paired, large ones stay full width. - Three READMEs still described pricing overrides as read-only and port collection as Windows-only, two releases after both changed. Verification: tsc, eslint, prettier clean; 324 unit tests; coverage 93.66% lines / 85.81% branches (store 95.52%, api 94.73%, ui 89.88%); 16 Playwright visual + contrast tests; 16 desktop E2E checks against the packaged exe. | 16 days ago | |
feat(ui): pick transport at startup and keep the mode visible The frontend called invoke() directly, so outside Tauri it silently fell back to demoBundle() — a browser could only ever see fake processes. Now transport.ts resolves IPC vs REST vs demo once at boot, shares one probe across concurrent callers, and broadcasts the result so the badge can switch from its placeholder. isTauri() stays a synchronous check (no network), because the first frame must not flicker into the wrong mode; isBackend() is the "do I have real data" question that pages ask. Header and sidebar show Live / Web · live data / Demo so a number on screen is never ambiguous. Three defects found while verifying the result visually: - port "open" was hardcoded to http://localhost:PORT — wrong for IPv6 listeners, HTTPS services and UDP ports; now derived from the actual listen address with UDP dropped. - every overlay used bg-black/55, which in the (now default) light theme turned the page into grey mud under a 2px blur; scrim colour follows data-chart-theme. - the cost trend axis mixed 2026-06 (monthly bucket) with 10-25 (daily), so readers could not tell which points were months; unified to 26/6月 vs 10-25, and the parser now checks shape instead of length so "unknown" no longer becomes un/NaN月. | 1 day ago | |
feat(ui): pick transport at startup and keep the mode visible The frontend called invoke() directly, so outside Tauri it silently fell back to demoBundle() — a browser could only ever see fake processes. Now transport.ts resolves IPC vs REST vs demo once at boot, shares one probe across concurrent callers, and broadcasts the result so the badge can switch from its placeholder. isTauri() stays a synchronous check (no network), because the first frame must not flicker into the wrong mode; isBackend() is the "do I have real data" question that pages ask. Header and sidebar show Live / Web · live data / Demo so a number on screen is never ambiguous. Three defects found while verifying the result visually: - port "open" was hardcoded to http://localhost:PORT — wrong for IPv6 listeners, HTTPS services and UDP ports; now derived from the actual listen address with UDP dropped. - every overlay used bg-black/55, which in the (now default) light theme turned the page into grey mud under a 2px blur; scrim colour follows data-chart-theme. - the cost trend axis mixed 2026-06 (monthly bucket) with 10-25 (daily), so readers could not tell which points were months; unified to 26/6月 vs 10-25, and the parser now checks shape instead of length so "unknown" no longer becomes un/NaN月. | 1 day ago | |
fix(a11y,security,data): WCAG AA across all themes, escaped chart tooltips, and a real backup path Audit round: correctness, security, reliability, performance, test quality. Fixed — security - ECharts tooltips render HTML by default (echarts/lib/util/model.js:673 returns 'html', TooltipHTMLContent.js:316 assigns innerHTML) and the disk widget interpolated the volume label straight into the markup. Volume labels are arbitrary local strings. Added escapeHtml() and used it in both HTML formatters; tests assert that an escaped payload parses to zero elements. - cargo audit found 6 vulnerabilities (4 rustls, rustls-webpki, webpki-root-certs) reached through reqwest -> hyper-rustls. Upgraded all three; audit is now clean apart from 5 "unmaintained" warnings on transitive crates. Fixed — readability - --color-faint was a fixed 62% mix of text toward bg: 2.78:1 on light themes, well under WCAG AA, and invisible as a defect in review. dim/faint/accent and the semantic colours are now solved against a contrast target instead of a mix ratio, and text itself is repaired when a user picks a low-contrast colour. Measured: 8 themes x 10 pages, 0 failures, worst margin +0.13. - Primary button text used a hard-coded dark colour; it is now --color-on-accent, derived from the accent it sits on. - Added e2e/contrast.spec.ts so this cannot silently regress. Fixed — state and i18n - setLayout updated the store but never wrote data-layout, so CSS and tests always read the initial value. - The orbit layout rendered the KPI block unconditionally, re-showing a widget the user had hidden. - The appearance panel hard-coded name.zh, so switching to English left the shell/layout/widget labels in Chinese. - upsertTheme could leave theme pointing at an id absent from the theme list; unknown built-in ids now keep the current selection. Added — backup - backup_data copies data.json, cost_data.json and pricing_overrides.json into a timestamped folder under the app data dir, plus open_backup_dir (fixed path, not an arbitrary open_path). Verified on the real exe: 2 files copied, missing one reported as skipped. Tests - 229 unit (was 190), 16 Playwright (was 14), 101 Rust. - New: contrast contract, chart-theme registry, API/agent IPC argument contracts, breakpoint hook, store theme/shell/layout state. - docs/audit-2026-10-22.md records the full review with measured numbers, competitor matrix, and the remaining gaps (store/api coverage, error and loading states, restore-from-backup, complexity). | 16 days ago | |
feat(ui): grouped collapsible navigation and a responsive shell Nine pages in a flat sidebar meant re-scanning the whole list every time. Pages now belong to a group (monitor / AI / work / system), so users navigate by group first. On wide screens the sidebar collapses to icons; below 768px it becomes a drawer opened from the header. - new Assistant page (chat, tool trace, approval prompts) and Cost ledger page (cost separated from AI sessions, which keeps processes and sessions) - breakpoint hook subscribes to matchMedia change events rather than resize, so dragging a window does not re-render every frame - collapse state and drawer state are separate booleans: mixing them makes behaviour wrong when crossing breakpoints - the i18n gate now also asserts every registry page has nav and title copy in all three languages; all 368 literal t() keys resolve (verified) | 18 days ago | |
feat(states,autostart,perf): page-level states, real start-at-login, and a complexity pass Page states: ErrorState (role=alert + retry), Skeleton/SkeletonCard and Empty.action are now used by Processes, Servers, Projects, Tasks, the cost dashboard and the overview widgets. A backend failure shows an in-page error instead of a toast that disappears; a first load shows the shape of the data instead of four zeroed cards. Start at login: tauri-plugin-autostart registers the real OS entry (HKCU Run on Windows, ~/.config/autostart on Linux, LaunchAgent on macOS). The toggle reads is_enabled on every mount, so an entry removed in Task Manager shows as off. Browser preview never calls IPC. Verified against the packaged exe: enable / disable / UI state (3 checks in scripts/e2e-desktop.mjs). Charts: ECharts moved to its own chunk behind a lazy wrapper. Settings, Logs, Tasks and Projects no longer download 540 KB they never draw. Production measurement: FCP 180 ms, chart block requested after the overview paints. Complexity: every function in src/ is now <= 10 (was App 43, CostDashboard 32, parseTheme 22, buildTheme 19, Agent 17, Processes 14). New modules: useAppRuntime, useShellLayout, pages/processFilter, pages/processes/ProcessTable, pages/servers/DevServerList, pages/ai/costCharts. Fixes found while doing the above: - readableOn blended towards the background, so it could never reach 4.5:1 on light themes; it returned the original 2.78:1 grey. Now solves towards black/white first, then fades back (4 tests measure the ratio). - Editing a built-in theme wrote its id into the custom list, where findTheme kept returning the built-in: every edit looked like it did nothing. Editing now duplicates first (editableTheme + 2 tests). - Ledger layout laid a 168 px donut across a 1146 px row; small panels are now paired, large ones stay full width. - Three READMEs still described pricing overrides as read-only and port collection as Windows-only, two releases after both changed. Verification: tsc, eslint, prettier clean; 324 unit tests; coverage 93.66% lines / 85.81% branches (store 95.52%, api 94.73%, ui 89.88%); 16 Playwright visual + contrast tests; 16 desktop E2E checks against the packaged exe. | 16 days ago | |
feat(states,autostart,perf): page-level states, real start-at-login, and a complexity pass Page states: ErrorState (role=alert + retry), Skeleton/SkeletonCard and Empty.action are now used by Processes, Servers, Projects, Tasks, the cost dashboard and the overview widgets. A backend failure shows an in-page error instead of a toast that disappears; a first load shows the shape of the data instead of four zeroed cards. Start at login: tauri-plugin-autostart registers the real OS entry (HKCU Run on Windows, ~/.config/autostart on Linux, LaunchAgent on macOS). The toggle reads is_enabled on every mount, so an entry removed in Task Manager shows as off. Browser preview never calls IPC. Verified against the packaged exe: enable / disable / UI state (3 checks in scripts/e2e-desktop.mjs). Charts: ECharts moved to its own chunk behind a lazy wrapper. Settings, Logs, Tasks and Projects no longer download 540 KB they never draw. Production measurement: FCP 180 ms, chart block requested after the overview paints. Complexity: every function in src/ is now <= 10 (was App 43, CostDashboard 32, parseTheme 22, buildTheme 19, Agent 17, Processes 14). New modules: useAppRuntime, useShellLayout, pages/processFilter, pages/processes/ProcessTable, pages/servers/DevServerList, pages/ai/costCharts. Fixes found while doing the above: - readableOn blended towards the background, so it could never reach 4.5:1 on light themes; it returned the original 2.78:1 grey. Now solves towards black/white first, then fades back (4 tests measure the ratio). - Editing a built-in theme wrote its id into the custom list, where findTheme kept returning the built-in: every edit looked like it did nothing. Editing now duplicates first (editableTheme + 2 tests). - Ledger layout laid a 168 px donut across a 1146 px row; small panels are now paired, large ones stay full width. - Three READMEs still described pricing overrides as read-only and port collection as Windows-only, two releases after both changed. Verification: tsc, eslint, prettier clean; 324 unit tests; coverage 93.66% lines / 85.81% branches (store 95.52%, api 94.73%, ui 89.88%); 16 Playwright visual + contrast tests; 16 desktop E2E checks against the packaged exe. | 16 days ago | |
fix(a11y,security,data): WCAG AA across all themes, escaped chart tooltips, and a real backup path Audit round: correctness, security, reliability, performance, test quality. Fixed — security - ECharts tooltips render HTML by default (echarts/lib/util/model.js:673 returns 'html', TooltipHTMLContent.js:316 assigns innerHTML) and the disk widget interpolated the volume label straight into the markup. Volume labels are arbitrary local strings. Added escapeHtml() and used it in both HTML formatters; tests assert that an escaped payload parses to zero elements. - cargo audit found 6 vulnerabilities (4 rustls, rustls-webpki, webpki-root-certs) reached through reqwest -> hyper-rustls. Upgraded all three; audit is now clean apart from 5 "unmaintained" warnings on transitive crates. Fixed — readability - --color-faint was a fixed 62% mix of text toward bg: 2.78:1 on light themes, well under WCAG AA, and invisible as a defect in review. dim/faint/accent and the semantic colours are now solved against a contrast target instead of a mix ratio, and text itself is repaired when a user picks a low-contrast colour. Measured: 8 themes x 10 pages, 0 failures, worst margin +0.13. - Primary button text used a hard-coded dark colour; it is now --color-on-accent, derived from the accent it sits on. - Added e2e/contrast.spec.ts so this cannot silently regress. Fixed — state and i18n - setLayout updated the store but never wrote data-layout, so CSS and tests always read the initial value. - The orbit layout rendered the KPI block unconditionally, re-showing a widget the user had hidden. - The appearance panel hard-coded name.zh, so switching to English left the shell/layout/widget labels in Chinese. - upsertTheme could leave theme pointing at an id absent from the theme list; unknown built-in ids now keep the current selection. Added — backup - backup_data copies data.json, cost_data.json and pricing_overrides.json into a timestamped folder under the app data dir, plus open_backup_dir (fixed path, not an arbitrary open_path). Verified on the real exe: 2 files copied, missing one reported as skipped. Tests - 229 unit (was 190), 16 Playwright (was 14), 101 Rust. - New: contrast contract, chart-theme registry, API/agent IPC argument contracts, breakpoint hook, store theme/shell/layout state. - docs/audit-2026-10-22.md records the full review with measured numbers, competitor matrix, and the remaining gaps (store/api coverage, error and loading states, restore-from-backup, complexity). | 16 days ago | |
fix(util): compare due dates by local calendar day dueLabel mixed a UTC-generated date with a local-time parse, so in UTC+8 the deadline for today was reported as overdue for the first eight hours after midnight. Both sides are now normalised to local start-of-day. demo trend rows also violated the YYYY-MM-DD contract (hardcoded MM-DD), which blanked the cost trend x-axis in the browser preview; they are now generated relative to today. | 19 days ago | |
fix(a11y,security,data): WCAG AA across all themes, escaped chart tooltips, and a real backup path Audit round: correctness, security, reliability, performance, test quality. Fixed — security - ECharts tooltips render HTML by default (echarts/lib/util/model.js:673 returns 'html', TooltipHTMLContent.js:316 assigns innerHTML) and the disk widget interpolated the volume label straight into the markup. Volume labels are arbitrary local strings. Added escapeHtml() and used it in both HTML formatters; tests assert that an escaped payload parses to zero elements. - cargo audit found 6 vulnerabilities (4 rustls, rustls-webpki, webpki-root-certs) reached through reqwest -> hyper-rustls. Upgraded all three; audit is now clean apart from 5 "unmaintained" warnings on transitive crates. Fixed — readability - --color-faint was a fixed 62% mix of text toward bg: 2.78:1 on light themes, well under WCAG AA, and invisible as a defect in review. dim/faint/accent and the semantic colours are now solved against a contrast target instead of a mix ratio, and text itself is repaired when a user picks a low-contrast colour. Measured: 8 themes x 10 pages, 0 failures, worst margin +0.13. - Primary button text used a hard-coded dark colour; it is now --color-on-accent, derived from the accent it sits on. - Added e2e/contrast.spec.ts so this cannot silently regress. Fixed — state and i18n - setLayout updated the store but never wrote data-layout, so CSS and tests always read the initial value. - The orbit layout rendered the KPI block unconditionally, re-showing a widget the user had hidden. - The appearance panel hard-coded name.zh, so switching to English left the shell/layout/widget labels in Chinese. - upsertTheme could leave theme pointing at an id absent from the theme list; unknown built-in ids now keep the current selection. Added — backup - backup_data copies data.json, cost_data.json and pricing_overrides.json into a timestamped folder under the app data dir, plus open_backup_dir (fixed path, not an arbitrary open_path). Verified on the real exe: 2 files copied, missing one reported as skipped. Tests - 229 unit (was 190), 16 Playwright (was 14), 101 Rust. - New: contrast contract, chart-theme registry, API/agent IPC argument contracts, breakpoint hook, store theme/shell/layout state. - docs/audit-2026-10-22.md records the full review with measured numbers, competitor matrix, and the remaining gaps (store/api coverage, error and loading states, restore-from-backup, complexity). | 16 days ago |
| Files | Last commit | Last update |
|---|---|---|
| 1 day ago | ||
| 4 days ago | ||
| 18 days ago | ||
| 18 days ago | ||
| 1 day ago | ||
| 23 hours ago | ||
| 1 day ago | ||
| 16 days ago | ||
| 16 days ago | ||
| 5 days ago | ||
| 14 days ago | ||
| 14 days ago | ||
| 4 days ago | ||
| 4 days ago | ||
| 4 days ago | ||
| 4 days ago | ||
| 4 days ago | ||
| 4 days ago | ||
| 4 days ago | ||
| 16 days ago | ||
| 3 days ago | ||
| 14 days ago | ||
| 3 days ago | ||
| 3 days ago | ||
| 15 days ago | ||
| 15 days ago | ||
| 15 days ago | ||
| 15 days ago | ||
| 1 day ago | ||
| 1 day ago | ||
| 3 days ago | ||
| 3 days ago | ||
| 14 days ago | ||
| 14 days ago | ||
| 3 days ago | ||
| 1 day ago | ||
| 1 day ago | ||
| 1 day ago | ||
| 1 day ago | ||
| 1 day ago | ||
| 1 day ago | ||
| 1 day ago | ||
| 15 days ago | ||
| 16 days ago | ||
| 1 day ago | ||
| 1 day ago | ||
| 16 days ago | ||
| 18 days ago | ||
| 16 days ago | ||
| 16 days ago | ||
| 16 days ago | ||
| 19 days ago | ||
| 16 days ago |