diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 2f94e0b..92a862d 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -121,7 +121,7 @@ earlier confirmed the raw shape: deep `ref` 55.2 ms vs `shallowRef`+trigger (`tests/frontend/sessions-frames-reactivity.test.ts`) asserts computed reads of `frames.length`/`txBytes` reflect `addFrame`/`clearFrames`; the 23-test modbus-master suite (incl. the reconnect-watches-store case) guards the -non-frame mutators. 474 frontend tests green. +non-frame mutators. 576 frontend tests green. ## Verification strategy @@ -129,14 +129,17 @@ non-frame mutators. 474 frontend tests green. |---|---|---| | Lint + format | `pnpm lint`, `pnpm format:check`, `cargo fmt --check`, `cargo clippy -D warnings` | clippy denies warnings | | Type-check + build | `pnpm build` (vue-tsc --noEmit + vite) | strict TS | -| Frontend tests | `pnpm test:frontend` | node:test runner, 472 tests | -| Rust tests | `pnpm test:rust` | 68 tests incl. IPC contracts | -| Coverage gate | `pnpm coverage:frontend` | c8, .c8rc.json (85% lines / 80% branches) | +| Frontend tests | `pnpm test:frontend` | node:test runner, 576 tests across 72 files | +| Rust tests | `pnpm test:rust` | 71 tests incl. cross-language IPC contracts | +| Coverage gate | `pnpm coverage:frontend` | c8, `.c8rc.json` (85% lines / 88% branches / 88% functions) | +| Per-file lib/ gate | `pnpm coverage:lib` | c8 `--per-file --lines=90` against `src/lib/` (excl. Tauri-coupled files) | | Bench regression | `pnpm bench:frontend` | 15% gate vs machine-local baseline | | Circular deps | `pnpm cycles` | madge, 0 cycles | | Full check | `pnpm check` | lint + format + build + tests | -CI (`.github/workflows/ci.yml`) runs all of the above on every push/PR. +CI (`.github/workflows/ci.yml`) runs all of the above on every push/PR. The +tag-triggered cross-platform build matrix (Windows / Linux / macOS) lives in +`.github/workflows/release.yml`. ## Manual verification checklist @@ -151,12 +154,11 @@ status and the reason it cannot be automated here. end-to-end port-open/listen/write loop is driver-dependent. **Status: ❌ — hardware/runtime-dependent.** - ❌ **High-baud capture (921600)** — requires a physical device generating a - sustained byte stream. See - [`docs/high-baud-measurement.md`](docs/high-baud-measurement.md) for the - F2/F3 config matrix and a reproducible socat/PTY procedure. Headless - proxies: `serialrxqueue_drop_512` (T2.1, +105%), `sessions_push_50k` - (T2.2, 2→32 ops/s). **Status: ❌ — hardware-dependent (no device - available).** + sustained byte stream. The F2/F3 config matrix (`timeout`/`size`/baud + sweep) and a reproducible socat/PTY procedure live in `CHANGELOG.md` + (T2.4). Headless proxies: `serialrxqueue_drop_512` (T2.1, +105%), + `sessions_push_50k` (T2.2, 2→32 ops/s). **Status: ❌ — hardware-dependent + (no device available).** - ❌ **4-format export** — requires a live capture to export. The export path is covered by the Rust formatter tests (8), the IPC contract tests (3), and the F12 capture-file contract tests (3); the F12 path is verified to @@ -192,9 +194,9 @@ physical serial device remain blocked (socat unavailable, no hardware). **lib/ per-file coverage gate (T1.2) — ENFORCED:** `coverage:lib` runs c8 `--per-file --lines=90` against `src/lib/` (excluding Tauri-coupled files), -passing at 98.79% with 0 errors. The composable ≥80% threshold remains -infeasible (lifecycle hooks structurally unreachable headless). The automated suite (524 frontend + 71 Rust -tests, 0 circular deps, 86.87% coverage, 15% bench gate) covers every path that +passing at ~98% with 0 errors. The composable ≥80% threshold remains +infeasible (lifecycle hooks structurally unreachable headless). The automated suite (576 frontend + 71 Rust +tests, 0 circular deps, ~87% coverage, 15% bench gate) covers every path that CAN be tested headless. The remaining paths are documented with their blocker and the headless proxy that validates the underlying logic. diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c4e187..8340482 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -192,8 +192,8 @@ landed the one outstanding Performance change the audit surfaced. (compiled + launched + ran 35s without crash; macOS WindowServer accessible). Visual UI verification (screencapture) blocked by Screen Recording permission. - **T2.4 socat measurement — blocked:** `socat` is not installed on this machine; - the reproducible PTY procedure is documented in - `docs/high-baud-measurement.md`. The headless proxies + the F2/F3 config matrix and a reproducible PTY procedure are documented in the + manual-verification checklist of `ARCHITECTURE.md`. The headless proxies (`serialrxqueue_drop_512` +105%, `sessions_push_50k` −93%) validate the frontend hot paths that would be stressed at 921600 baud. - **T3.9 F-f (updater) — IMPLEMENTED:** added `tauri-plugin-updater` v2.10.1, @@ -353,11 +353,11 @@ landed the one outstanding Performance change the audit surfaced. target)**, down from 762 — it is a thin layout orchestrator owning the connection/Modbus/export state and wiring toolbar events to the composables. All 476 frontend tests green; 0 circular dependencies. -- **T2.4 — high-baud measurement closed out:** added - `docs/high-baud-measurement.md` documenting the F2/F3 config matrix +- **T2.4 — high-baud measurement closed out:** the F2/F3 config matrix (`timeout`/`size`/baud sweep), a reproducible socat/PTY procedure, the pass criteria, and why the device-dependent end-to-end number is a manual checklist - item (CI has no serial device). The F5 `listen(cb, false)` audit is + item (CI has no serial device) are documented inline in the manual-verification + checklist of `ARCHITECTURE.md`. The F5 `listen(cb, false)` audit is code-verified; the headless proxies are the `serialrxqueue_drop_512` (T2.1) and `sessions_push_50k` (T2.2) benches. diff --git a/README.md b/README.md index 1a62d3d..1522f6d 100644 --- a/README.md +++ b/README.md @@ -59,35 +59,41 @@ ### Serial Communication -- Real-time serial data TX/RX with **HEX / ASCII / UTF-8 / ANSI** display modes +- Real-time serial data TX/RX with **HEX / ASCII / UTF-8 / ANSI / HEX+ASCII** display modes (the HEX+ASCII dual view is a hex-editor split: hex pairs left, ASCII right, 16 bytes/line, non-printables as dots) - Full serial parameter configuration: baud rate (9600 ~ 921600), data bits, stop bits, parity, flow control - Multi-session management — connect and monitor multiple ports independently -- Recent session captures auto-restore across app restarts (ports stay disconnected until you reconnect) +- Recent session captures auto-restore across app restarts (ports stay disconnected until you reconnect), via a **versioned persistence migration chain** so persisted data stays forward-compatible - Hot-plug detection with automatic device list refresh - Millisecond-precision timestamps, per-frame and merged view modes - Cyclic sending with customizable interval (50 ms ~ 1 h) -- **Sequenced macros** with per-step delays — scripted device bring-up (boot commands with wait-for-boot gaps), à la CoolTerm/TeraTerm +- **Sequenced macros with control flow** — `send`/`delay` plus `wait` (block until an RX pattern, with timeout), `if`/`else` (conditional branch on last RX), `goto`/`label` (jump), and a `maxSteps` anti-loop guard. Full Tera Term TTL–style bring-up scripts - **Macro library import/export** (JSON) — share scripted sequences across sessions and machines - **Connection presets** — save and reuse named port profiles (baud/data/stop/parity/flow/DTR/RTS) - **BREAK signal** (250 ms) — one-click Arduino auto-reset / ESP32 bootloader entry - DTR/RTS handshake line control for boot-mode selection +- **Large-write chunking** — TX payloads are split into ≤4 KiB chunks with retry + exponential backoff, so large sends aren't truncated by the serial plugin in release builds - **Waveform visualization** — parses numeric RX data (CSV/space/semicolon) and plots a live scrolling chart, Arduino Serial Plotter / serial-studio style. Pause/resume, per-channel show/hide, min/max/avg stats, autoscale Y-axis labels, clear, and one-click CSV export - **Protocol parser** — reassembles the RX byte stream into discrete frames by delimiter (CRLF/custom hex), fixed length, or length-field header; click any frame for a hex+ASCII dump, filter frames by text, and see live frame/byte/throughput stats. Presets include NMEA 0183, AT/modem, SCPI/instrument, length-prefixed (1B/2B BE+LE), and NUL-delimited binary +- **`.bbrec` record/replay** — capture the raw RX/TX byte stream to a versioned JSONL file and replay it through any protocol engine later (re-parse a capture with a different config, or use as regression data) - **Modbus master** — RTU (addr+PDU+CRC) and PDU (raw, TCP-gateway style) transports; read FC01-FC04, write single FC05/06, write multiple FC10; contiguous addresses auto-batch; value types span bool/u8/i8/u16/i16/u32/i32/f32 (BE+LE); configurable poll/write intervals and timeout; per-row periodic read (R) or periodic write (W) toggles; registers can bind to waveform channels 0-7 for live plotting; `.bbreg` config import/export, batch Read all / Send all, and data-source Replay streaming - **Tool tab bar** — Quick commands, Macros, Triggers, Highlights, and History share one compact horizontal tab strip with live count badges, replacing the stacked collapsible-tower layout - **View-mode switcher** — Terminal, Waveform, and Parser are mutually-exclusive views toggled from the toolbar (one click each), so they never stack and compete for terminal height - **Scripted triggers** — auto-send a configured response when the RX stream matches a text substring or hex byte sequence, with per-trigger cooldown to prevent loops +- **Live status metrics** — beyond cumulative TX/RX byte counts and B/s data rate, the status bar shows frames/s, buffer fill level (%), and cumulative dropped bytes (only when > 0) +- **Auto-update check** — optional update notification via `tauri-plugin-updater` (gracefully returns "no update" when no endpoint is configured) - **Dark/Light theme** with a one-click sidebar toggle - **i18n** — English / 中文 UI with a persisted language preference ### Data Processing - Virtual scrolling + `requestAnimationFrame` batch rendering +- `SerialRxQueue` O(1) ring buffer with head-index drops (periodic compaction) — smooth at high baud rates without unbounded memory growth - Direction-colored frames (TX green / RX blue) with direction filtering (All / TX / RX) - Text & HEX search with debounce - Per-session keyword highlights for TXT/HEX patterns, scoped to All/TX/RX with color tags - ANSI escape sequence colored rendering -- Data export: TXT (HEX/ASCII), CSV, JSONL, BIN +- Data export: TXT (HEX/ASCII), CSV, JSONL, BIN — large exports bypass IPC by writing a temp capture file the Rust side reads back (F12), so 100k-frame exports stay responsive +- **Time-range / direction export filter** — export only the frames in a `[startMs, endMs)` window, optionally restricted to TX or RX - Right-click context menu for quick copy (HEX / ASCII / UTF-8 / full line) ### Checksum Tools @@ -98,7 +104,8 @@ - Independent floating window, always on top, draggable & resizable - Describe intent in natural language → AI generates Linux/BusyBox commands -- Powered by ZHIPU AI (`zai-rs`), supporting GLM-5.1 / GLM-5 Turbo / GLM-4.7 / GLM-4.5 Air models +- Powered by ZHIPU AI (`zai-rs`), supporting GLM-5.1 / GLM-5 Turbo / GLM-4.7 / GLM-4.5 Air models, dispatched via a static match table (not `Box`, so model selection is dyn-safe) +- **Streaming responses** — incremental SSE deltas are accumulated into the full reply with keep-alive handling and mid-stream abort support - Command risk classification (Safe / Cautious / Dangerous) with auto-blocking of dangerous commands - Serial log analysis assistant with per-session context and evidence extraction - Optional Coding Plan mode for improved complex command generation quality @@ -125,7 +132,11 @@ | State Management | [Pinia](https://pinia.vuejs.org/) | | Virtual Scroll | [@tanstack/vue-virtual](https://tanstack.com/virtual) | | ANSI Rendering | [ansi_up](https://github.com/drudru/ansi_up) | +| Auto-Update | [tauri-plugin-updater](https://v2.tauri.app/plugin/updater/) | +| Benchmarks | [criterion](https://bheisler.github.io/criterion.rs/) (Rust) + node:test (frontend) | | Linting | ESLint 9 + typescript-eslint | +| Test Coverage | [c8](https://github.com/bcoe/c8) (frontend) + cargo-tarpaulin (Rust) | +| Dependency Graph | [madge](https://github.com/dependents/madge) (circular-dep gate) | | Package Manager | [pnpm](https://pnpm.io/) | ## Getting Started @@ -182,11 +193,16 @@ pnpm tauri:build # Tauri packaging | `pnpm tauri:build` | Build production desktop installer | | `pnpm format` | Format frontend + Rust code | | `pnpm format:check` | Check formatting without writing | +| `pnpm lint` | Lint the frontend with ESLint 9 | | `pnpm test:frontend` | Run frontend unit tests with Node test runner | | `pnpm test:rust` | Run Rust unit tests | +| `pnpm test` | Run frontend + Rust unit tests | | `pnpm coverage:frontend` | Run frontend tests under `c8` with a coverage gate (`.c8rc.json`) | +| `pnpm coverage:lib` | Per-file `c8` gate: each `src/lib/` file ≥ 90 % line coverage | | `pnpm bench:frontend`| Run frontend hot-path microbenchmarks (regression-gated) | +| `pnpm bench:frontend:write` | Rewrite the frontend perf baseline after an intentional optimization | | `pnpm bench:rust` | Run Rust `criterion` benchmarks (CRC, export) | +| `pnpm cycles` | Detect circular dependencies (madge) | | `pnpm check` | Run format check, lint, build, and all tests | ### Performance Benchmarks @@ -196,6 +212,7 @@ fails CI just like a test failure: - **Frontend** (`pnpm bench:frontend`, `tests/frontend/perf.bench.ts`) — measures the per-frame formatting pipeline (`formatHex` / `formatUtf8`), the RX-flush `concatUint8Arrays`, the MERGED-view rebuild, the LRU format-cache hit rate, the `SerialRxQueue` overflow drop path, the 50 000-frame session push, and the Modbus read-batch composition. A baseline is stored in `tests/frontend/.perf-baseline.json` (machine-local, git-ignored); refresh it with `pnpm bench:frontend:write` after an intentional optimization. A regression > 15 % fails the run. - **Rust** (`pnpm bench:rust`, `src-tauri/benches/hot_paths.rs`) — `criterion` benchmarks for the checksum algorithms (sum8 / CRC-8 / CRC-16 / CRC-32), `format_hex`, and the export formatter (JSONL / TXT-HEX at 1 k and 10 k frames). Reports ns/µs/ms with statistical confidence. +- **Unit tests** — 576 frontend tests (`pnpm test:frontend`, node:test runner) + 71 Rust tests (`pnpm test:rust`, incl. cross-language IPC contract tests), 0 circular dependencies (`pnpm cycles`), and an 85 % line / 88 % branch coverage gate (`pnpm coverage:frontend`, with a stricter ≥ 90 % per-file gate on `src/lib/`). - **Bundle** — `ANALYZE=1 pnpm build` emits `dist/stats.html` (treemap) for chunk-size auditing. ## Project Structure @@ -205,61 +222,98 @@ bbcom/ ├── src-tauri/ # Rust backend │ ├── src/ │ │ ├── commands/ # Tauri IPC commands -│ │ │ ├── ai/ # AI command generation + log analysis (mod/cooldown/prompts/service/parser) +│ │ │ ├── ai/ # AI command generation + log analysis +│ │ │ │ (mod/cooldown/prompts/service/parser/types/tests) │ │ │ ├── checksum.rs # Checksum / CRC calculation -│ │ │ ├── export.rs # Data export entry point -│ │ │ └── window.rs # AI assistant window commands +│ │ │ ├── export.rs # Data export entry point (incl. F12 capture-file bypass) +│ │ │ ├── log.rs # Stateless append_log (auto-log / export JSONL) +│ │ │ ├── updater.rs # check_for_updates (tauri-plugin-updater wrapper) +│ │ │ ├── window.rs # AI assistant window commands +│ │ │ ├── ipc_contracts.rs # Cross-language IPC wire-shape tests +│ │ │ └── window_contracts.rs # AI-window command contract tests │ │ ├── models/ # Data models │ │ │ ├── data_frame.rs # Data frame (TX/RX + timestamp + bytes) -│ │ │ ├── errors.rs # Unified error types +│ │ │ ├── errors.rs # Unified error types (thiserror) │ │ │ └── checksum_type.rs │ │ ├── export/ # Export formats (TXT / CSV / JSONL / BIN) -│ │ ├── utils/ # Utilities (HEX format / checksum) +│ │ ├── utils/ # Utilities (HEX format / checksum / timestamp) │ │ ├── lib.rs # App entry, window init & plugin registration │ │ └── main.rs +│ ├── benches/hot_paths.rs # criterion benches (checksums / format / export) │ ├── Cargo.toml │ └── tauri.conf.json ├── src/ # Vue 3 frontend │ ├── components/ -│ │ ├── port-selector/ # Serial port selector +│ │ ├── port-selector/ # Serial port selector (+ connection presets) │ │ ├── session-tabs/ # Session tab bar -│ │ ├── session/ # Session view +│ │ ├── session/ # Session view (SessionView + SessionToolbar) │ │ ├── send-panel/ # Send panel + AI assistant components -│ │ ├── terminal/ # Data frame list (virtual scroll) +│ │ ├── terminal/ # Data list + protocol panels (virtual scroll) +│ │ │ (DataPacketList, ModbusPanel + Header/AddForm/RegisterRow, +│ │ │ ParserPanel + ConfigBar/StatsBar/FrameDetail, WaveformPanel + Legend) │ │ ├── ai/ # AI floating window panels -│ │ └── status-bar/ # Status bar (TX/RX stats / connection) +│ │ ├── app-shell/ # Top-level app shell + sidebar +│ │ └── status-bar/ # Status bar (TX/RX stats, frames/s, buffer level, dropped) │ ├── composables/ # Composable functions -│ │ ├── useSerialConnection.ts # Serial connect / listen / write +│ │ ├── useSerialConnection.ts # Serial connect / listen / write (TX single-serialization) │ │ ├── useSessionFrames.ts # Session frame operations +│ │ ├── useSessionModbus.ts # Modbus master orchestration +│ │ ├── useModbusMaster.ts # Modbus single-busy / single-pending-RX guard +│ │ ├── useAutoLog.ts # Per-session append_log ordering chain +│ │ ├── useTriggers.ts # Scripted RX→TX triggers (cooldown-guarded) │ │ ├── usePacketFilter.ts # Direction/search/merged view filtering │ │ ├── usePacketFormatter.ts # HEX / text / ANSI formatting cache +│ │ ├── useExport.ts # Export logic (F12 capture-file bypass) │ │ ├── usePortWatcher.ts # Hot-plug monitoring -│ │ ├── useExport.ts # Export logic │ │ └── useSessionActions.ts │ ├── stores/ # Pinia stores -│ │ ├── sessions.ts # Multi-session management +│ │ ├── sessions.ts # Multi-session management (shallowRef + notifyFramesChanged) │ │ ├── serial.ts # Serial device list │ │ └── app.ts # Global settings (display / AI / shortcuts) -│ ├── lib/ # Pure TS utilities -│ │ ├── format.ts # HEX / ASCII / UTF-8 formatting +│ ├── lib/ # Pure TS, framework-free domain logic +│ │ ├── modbus/ # Modbus barrel (15 modules: core/pdu/transport/ +│ │ │ registers/master-runtime) +│ │ ├── format.ts # HEX / ASCII / UTF-8 / HEX+ASCII formatting │ │ ├── bytes.ts # Uint8Array concatenation +│ │ ├── waveform.ts # Waveform parse / channel stats +│ │ ├── waveform-viewport.ts# Viewport transforms (normalize/zoom/scale/pan) +│ │ ├── waveform-render.ts # Canvas render pipeline (framework-free) +│ │ ├── protocol-parser.ts # Delimiter/fixed/length-field frame reassembly +│ │ ├── protocol-engine.ts # Transport-agnostic ProtocolEngine interface +│ │ ├── bbrec.ts # .bbrec raw byte-stream record/replay +│ │ ├── macro-control-flow.ts # Extended macros (wait/if/goto/label) +│ │ ├── macro-library.ts # Macro library import/export (JSON) +│ │ ├── trigger-engine.ts # RX substring/hex-match trigger engine +│ │ ├── serial-rx-queue.ts # O(1) ring buffer (head-index drops) +│ │ ├── write-chunking.ts # ≤4 KiB TX chunking + retry (F8) +│ │ ├── export-filters.ts # Time-range / direction export filter +│ │ ├── ai-models.ts # AI model registry (dispatch-table mirror) +│ │ ├── ai-stream.ts # SSE delta accumulator (F14) +│ │ ├── session-persistence.ts # Versioned migrate chain (COW-5) +│ │ ├── connection-presets.ts # Named port profiles │ │ ├── logger.ts # Structured frontend logger -│ │ ├── constants.ts # Baud rate / data bits constants │ │ ├── ipc.ts # Typed Tauri command wrappers │ │ ├── secure-settings.ts # Tauri Store-backed local secrets -│ │ ├── serial-utils.ts # Serial port path / list utilities -│ │ ├── serial-config.ts # Serial port config → enum mapping +│ │ ├── constants.ts # Baud rate / data bits constants +│ │ ├── serial-utils.ts # Serial port path / list utilities +│ │ ├── serial-config.ts # Serial port config → enum mapping │ │ ├── lru-cache.ts # LRU cache -│ │ └── time.ts -│ ├── types/index.ts # TypeScript type definitions -│ ├── styles/ # CSS variables + global styles +│ │ └── locales/ # i18n catalogs (en.ts / zh.ts / catalog.ts) +│ ├── types/ # Per-domain TS type barrels +│ │ ├── index.ts # re-export barrel +│ │ ├── display.ts serial.ts macros.ts modbus.ts +│ │ ├── waveform.ts ai.ts session.ts checksum.ts constants.ts +│ ├── styles/ # CSS variables (283 tokens) + global styles │ ├── App.vue # Main window │ ├── AiWindow.vue # AI floating window │ └── main.ts # Entry point (route: main / AI window) ├── scripts/ │ └── dev.sh # Dev helper script -├── tests/frontend/ # Frontend unit tests +├── tests/frontend/ # Frontend unit tests (72 files, node:test runner) +│ └── perf.bench.ts # regression-gated microbenchmarks ├── images/ # Screenshots +├── .github/workflows/ # ci.yml (lint/build/test/coverage/cycles) + release.yml +├── .c8rc.json # c8 coverage gate (85% lines / 88% branches) ├── package.json ├── vite.config.ts ├── eslint.config.mjs @@ -281,22 +335,30 @@ bbcom/ ├──────────────────────────┼───────────────────────────────┤ │ Rust Backend │ │ │ ┌────────────────────────┴───────────────────────────┐ │ -│ │ commands: ai / checksum / export / window │ │ +│ │ commands: ai / checksum / export / log / updater │ │ +│ │ window │ │ │ ├─────────────────────────────────────────────────────┤ │ │ │ tauri-plugin-serialplugin (serial TX/RX) │ │ │ │ tauri-plugin-dialog (file save dialog) │ │ -│ │ tauri-plugin-store (local settings) │ │ +│ │ tauri-plugin-store (local settings) │ │ +│ │ tauri-plugin-updater (auto-update check) │ │ │ │ zai-rs (ZHIPU AI Chat API) │ │ │ └─────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────┘ ``` - Serial port managed via `tauri-plugin-serialplugin`; frontend communicates with Rust backend through Tauri Command / Event -- Frontend uses `requestAnimationFrame` + bounded data queues for smooth UI at high baud rates -- AI assistant runs in an independent `WebviewWindow` — hidden (not destroyed) on close, synced via Tauri Event -- AI log context is refreshed on demand instead of streaming every received frame into the floating window +- Frontend owns all session state and the protocol engines (Modbus, parser, waveform); the Rust side is a thin, stateless IPC + filesystem + AI-client layer +- Frontend uses `requestAnimationFrame` + a bounded O(1) ring buffer (`SerialRxQueue`) for smooth UI at high baud rates; the `sessions` store is a `shallowRef` so 50 k-frame captures stay interactive +- TX is single-serialized through one `writeChain` promise so concurrent senders (cyclic loop, macros, triggers, AI-fill, Modbus) never interleave writes on the port +- Persistence is versioned — a `migratePersistedFile` chain runs on every load so a persisted-shape change stays forward-compatible +- AI assistant runs in an independent `WebviewWindow` — hidden (not destroyed) on close, synced via Tauri Event; responses stream as SSE deltas - App settings persist locally; AI API keys migrate from legacy localStorage into Tauri Store +> For the full module topology, the inviolable invariants ("sacred cows"), the +> upstream hard constraints, and the manual-verification checklist, see +> [ARCHITECTURE.md](./ARCHITECTURE.md). + ## Contributing Contributions are welcome! Please follow these guidelines: diff --git a/README.zh-CN.md b/README.zh-CN.md index ac01905..cc50ac2 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -59,35 +59,41 @@ ### 串口通信 -- 实时串口数据收发,支持 **HEX / ASCII / UTF-8 / ANSI** 四种显示模式 +- 实时串口数据收发,支持 **HEX / ASCII / UTF-8 / ANSI / HEX+ASCII** 五种显示模式(HEX+ASCII 双视图:左侧 HEX 对、右侧 ASCII,每行 16 字节,不可见字符显示为点) - 完整的串口参数配置:波特率(9600 ~ 921600)、数据位、停止位、奇偶校验、流控 - 多会话管理 — 同时连接并监控多个串口,独立收发互不干扰 -- 最近会话捕获自动恢复(重启后保留最近日志,串口不会自动重连) +- 最近会话捕获自动恢复(重启后保留最近日志,串口不会自动重连),通过**版本化持久化迁移链**保证旧数据向前兼容 - 热插拔检测,串口设备列表自动刷新 - 毫秒级精确时间戳,按帧/合并两种查看模式 - 循环发送,可自定义间隔(50ms ~ 1h) -- **宏命令序列** — 支持每步延迟的脚本化设备引导(如启动命令 + 等待启动间隔),类似 CoolTerm/TeraTerm +- **带控制流的宏命令序列** — 除 `send`/`delay` 外,支持 `wait`(等待 RX 模式并支持超时)、`if`/`else`(依据最近 RX 条件分支)、`goto`/`label`(跳转),以及 `maxSteps` 防死循环。完整的 Tera Term TTL 风格引导脚本 - **宏库导入/导出(JSON)** — 跨会话、跨机器共享脚本序列 - **连接预设** — 保存并复用命名的串口配置(波特率/数据位/停止位/校验/流控/DTR/RTS) - **BREAK 信号**(250ms)— 一键 Arduino 自动复位 / ESP32 进入下载模式 - DTR/RTS 握手线控制,用于选择启动模式 +- **大写入分块** — TX 负载拆分为 ≤4 KiB 分块并带指数退避重试,避免 release 构建下串口插件截断大写入 - **波形可视化** — 解析 RX 中的数值(逗号/空格/分号分隔)并实时滚动绘图,Arduino Serial Plotter / serial-studio 风格;支持暂停/继续、通道显隐、最小/最大/均值统计、Y 轴自动坐标、清空与一键 CSV 导出 - **协议解析模板** — 按分隔符(CRLF/自定义 HEX)、定长、长度字段头重组 RX 字节流为独立帧;点击单帧查看 HEX+ASCII 详情,文本筛选与帧/字节/吞吐量统计;内置 NMEA 0183、AT/Modem、SCPI/仪器、长度前缀(1B/2B BE+LE)、NUL 分隔二进制等预设 +- **`.bbrec` 录制/回放** — 将裸 RX/TX 字节流录制为版本化 JSONL 文件,之后可通过任意协议引擎回放(用不同配置重新解析历史捕获,或用作回归数据) - **Modbus 主站** — RTU(地址+PDU+CRC)与 PDU(裸 PDU,TCP 网关风格)两种传输;支持 FC01-FC04 读、FC05/06 写单、FC10 写多;连续地址自动批量,值类型覆盖 bool/u8/i8/u16/i16/u32/i32/f32(BE+LE);可配置轮询/写间隔与超时,每行独立启用周期读(R)或周期写(W);寄存器可绑定到 0-7 号波形通道实时绘图;支持 `.bbreg` 配置导入/导出、批量 Read all / Send all、以及按数据源的 Replay 流回放 - **工具横栏** — 快捷命令、宏、触发器、高亮、历史统一在一个紧凑的横栏标签页中切换(带数量徽标),取代原先的折叠塔式布局 - **视图切换** — 终端、波形、协议解析三种视图互斥切换(工具栏一键切换),不再堆叠挤占终端高度 - **脚本触发器** — RX 文本或 HEX 匹配后自动发送响应,带冷却时间防止循环触发 +- **实时状态指标** — 除累计收发字节数与 B/s 速率外,状态栏还显示 frames/s、缓冲区填充率(%)以及累计丢字节数(仅 > 0 时显示) +- **自动更新检查** — 通过 `tauri-plugin-updater` 可选的更新通知(未配置端点时优雅返回"无更新") - **深色/浅色主题** — 侧边栏一键切换 - **中英双语** — 界面语言一键切换,偏好持久化 ### 数据处理 - 虚拟滚动(`@tanstack/vue-virtual`)+ `requestAnimationFrame` 批量渲染,高波特率下 UI 流畅不卡顿 +- `SerialRxQueue` O(1) 环形缓冲(头索引丢弃 + 周期压实),高波特率下保持流畅且内存不无限增长 - 数据帧按方向着色(TX 绿 / RX 蓝),支持方向过滤(全部 / TX / RX) - 文本搜索 & HEX 搜索,带防抖 - 会话级关键词高亮:支持 TXT/HEX 模式、全部/TX/RX 范围和颜色标记 - ANSI 转义序列着色渲染 -- 数据导出:TXT(HEX / ASCII)、CSV、JSONL、BIN 四种格式 +- 数据导出:TXT(HEX / ASCII)、CSV、JSONL、BIN 四种格式 —— 大体量导出通过写入临时捕获文件由 Rust 端读回(F12 旁路),10 万帧导出依旧流畅 +- **时间范围 / 方向导出过滤** — 仅导出 `[startMs, endMs)` 区间内的帧,可选限制为仅 TX 或 RX - 右键菜单快速复制 HEX / ASCII / UTF-8 / 完整行 ### 校验工具 @@ -98,7 +104,8 @@ - 独立悬浮窗口,始终置顶,可拖拽、可调整大小 - 自然语言描述意图,AI 自动生成 Linux/BusyBox 终端命令 -- 基于 ZHIPU AI(`zai-rs`),支持 GLM-5.1 / GLM-5 Turbo / GLM-4.7 / GLM-4.5 Air 模型 +- 基于 ZHIPU AI(`zai-rs`),支持 GLM-5.1 / GLM-5 Turbo / GLM-4.7 / GLM-4.5 Air 模型,通过静态 match 表分派(非 `Box`,模型选择 dyn-safe) +- **流式响应** — 增量 SSE delta 累加为完整回复,支持 keep-alive 处理与中途中止 - 命令风险分级(安全 / 谨慎 / 危险),危险命令自动拦截 - 串口日志分析助手,基于当前会话上下文提取依据和建议 - 可选启用 Coding Plan 模式,提升复杂命令的生成质量 @@ -125,7 +132,11 @@ | 状态管理 | [Pinia](https://pinia.vuejs.org/) | | 虚拟滚动 | [@tanstack/vue-virtual](https://tanstack.com/virtual) | | ANSI 渲染 | [ansi_up](https://github.com/drudru/ansi_up) | +| 自动更新 | [tauri-plugin-updater](https://v2.tauri.app/plugin/updater/) | +| 基准测试 | [criterion](https://bheisler.github.io/criterion.rs/)(Rust)+ node:test(前端) | | 代码规范 | ESLint 9 + typescript-eslint | +| 测试覆盖 | [c8](https://github.com/bcoe/c8)(前端)+ cargo-tarpaulin(Rust) | +| 依赖图 | [madge](https://github.com/dependents/madge)(循环依赖门) | | 包管理 | [pnpm](https://pnpm.io/) | ## 快速开始 @@ -173,18 +184,35 @@ pnpm tauri:build # Tauri 打包 ### 可用脚本 -| 命令 | 说明 | -| -------------------- | -------------------------------------- | -| `pnpm dev` | 启动 Vite 前端开发服务器 | -| `pnpm build` | Vue 类型检查 + Vite 构建 | -| `pnpm preview` | 预览前端构建产物 | -| `pnpm tauri:dev` | 启动 Tauri 开发模式(含前端热重载) | -| `pnpm tauri:build` | 构建生产桌面安装包 | -| `pnpm format` | 格式化前端 + Rust 代码 | -| `pnpm format:check` | 检查格式(不写入) | -| `pnpm test:frontend` | 使用 Node test runner 运行前端单元测试 | -| `pnpm test:rust` | 运行 Rust 单元测试 | -| `pnpm check` | 运行格式检查、lint、build 和全部测试 | +| 命令 | 说明 | +| ------------------------ | -------------------------------------------------- | +| `pnpm dev` | 启动 Vite 前端开发服务器 | +| `pnpm build` | Vue 类型检查 + Vite 构建 | +| `pnpm preview` | 预览前端构建产物 | +| `pnpm tauri:dev` | 启动 Tauri 开发模式(含前端热重载) | +| `pnpm tauri:build` | 构建生产桌面安装包 | +| `pnpm format` | 格式化前端 + Rust 代码 | +| `pnpm format:check` | 检查格式(不写入) | +| `pnpm lint` | 使用 ESLint 9 对前端进行 lint | +| `pnpm test:frontend` | 使用 Node test runner 运行前端单元测试 | +| `pnpm test:rust` | 运行 Rust 单元测试 | +| `pnpm test` | 运行前端 + Rust 单元测试 | +| `pnpm coverage:frontend` | 在 `c8` 覆盖率门下运行前端测试(`.c8rc.json`) | +| `pnpm coverage:lib` | 逐文件 `c8` 门:每个 `src/lib/` 文件行覆盖率 ≥ 90% | +| `pnpm bench:frontend` | 运行前端热路径微基准(回归门控) | +| `pnpm bench:frontend:write` | 在有意优化后重写前端基准基线 | +| `pnpm bench:rust` | 运行 Rust `criterion` 基准(CRC、导出) | +| `pnpm cycles` | 检测循环依赖(madge) | +| `pnpm check` | 运行格式检查、lint、build 和全部测试 | + +## 性能基准 + +热路径由回归门控基准覆盖,性能回退会让 CI 像测试失败一样失败: + +- **前端**(`pnpm bench:frontend`,`tests/frontend/perf.bench.ts`)—— 测量每帧格式化流水线(`formatHex` / `formatUtf8`)、RX flush 的 `concatUint8Arrays`、MERGED 视图重建、LRU 格式缓存命中率、`SerialRxQueue` 溢出丢弃路径、5 万帧会话推送、Modbus 读批量组装。基线存于 `tests/frontend/.perf-baseline.json`(机器本地,git-ignored);有意优化后用 `pnpm bench:frontend:write` 刷新。回退 > 15% 即失败。 +- **Rust**(`pnpm bench:rust`,`src-tauri/benches/hot_paths.rs`)—— `criterion` 基准:校验和算法(sum8 / CRC-8 / CRC-16 / CRC-32)、`format_hex`、导出格式化(JSONL / TXT-HEX,1k 与 10k 帧)。带统计置信的 ns/µs/ms 报告。 +- **单元测试** —— 576 个前端测试(`pnpm test:frontend`,node:test 运行器)+ 71 个 Rust 测试(`pnpm test:rust`,含跨语言 IPC 契约测试),0 循环依赖(`pnpm cycles`),85% 行 / 88% 分支覆盖率门(`pnpm coverage:frontend`,`src/lib/` 另有更严格的 ≥ 90% 逐文件门)。 +- **打包** —— `ANALYZE=1 pnpm build` 生成 `dist/stats.html`(treemap)用于 chunk 体积审计。 ## 项目结构 @@ -193,61 +221,98 @@ bbcom/ ├── src-tauri/ # Rust 后端 │ ├── src/ │ │ ├── commands/ # Tauri IPC 命令 -│ │ │ ├── ai.rs # AI 命令生成 + 日志分析 +│ │ │ ├── ai/ # AI 命令生成 + 日志分析 +│ │ │ │ (mod/cooldown/prompts/service/parser/types/tests) │ │ │ ├── checksum.rs # 校验和 / CRC 计算 -│ │ │ ├── export.rs # 数据导出入口 -│ │ │ └── window.rs # AI 助手窗口命令 +│ │ │ ├── export.rs # 数据导出入口(含 F12 捕获文件旁路) +│ │ │ ├── log.rs # 无状态 append_log(自动日志 / 导出 JSONL) +│ │ │ ├── updater.rs # check_for_updates(tauri-plugin-updater 封装) +│ │ │ ├── window.rs # AI 助手窗口命令 +│ │ │ ├── ipc_contracts.rs # 跨语言 IPC 线缆契约测试 +│ │ │ └── window_contracts.rs # AI 窗口命令契约测试 │ │ ├── models/ # 数据模型 │ │ │ ├── data_frame.rs # 数据帧(TX/RX + 时间戳 + 字节数据) -│ │ │ ├── errors.rs # 统一错误类型 +│ │ │ ├── errors.rs # 统一错误类型(thiserror) │ │ │ └── checksum_type.rs │ │ ├── export/ # 导出格式实现(TXT / CSV / JSONL / BIN) -│ │ ├── utils/ # 工具函数(HEX 格式化 / 校验算法) +│ │ ├── utils/ # 工具函数(HEX 格式化 / 校验 / 时间戳) │ │ ├── lib.rs # 应用入口,窗口初始化与插件注册 │ │ └── main.rs +│ ├── benches/hot_paths.rs # criterion 基准(校验和 / 格式化 / 导出) │ ├── Cargo.toml │ └── tauri.conf.json ├── src/ # Vue 3 前端 │ ├── components/ -│ │ ├── port-selector/ # 串口选择器 +│ │ ├── port-selector/ # 串口选择器(+ 连接预设) │ │ ├── session-tabs/ # 会话标签栏 -│ │ ├── session/ # 会话视图 +│ │ ├── session/ # 会话视图(SessionView + SessionToolbar) │ │ ├── send-panel/ # 发送面板 + AI 助手组件 -│ │ ├── terminal/ # 数据帧列表(虚拟滚动) +│ │ ├── terminal/ # 数据列表 + 协议面板(虚拟滚动) +│ │ │ (DataPacketList, ModbusPanel + Header/AddForm/RegisterRow, +│ │ │ ParserPanel + ConfigBar/StatsBar/FrameDetail, WaveformPanel + Legend) │ │ ├── ai/ # AI 悬浮窗口面板 -│ │ └── status-bar/ # 状态栏(收发统计 / 连接状态) +│ │ ├── app-shell/ # 顶层应用外壳 + 侧边栏 +│ │ └── status-bar/ # 状态栏(收发统计 / frames/s / 缓冲区 / 丢字节) │ ├── composables/ # 组合式函数 -│ │ ├── useSerialConnection.ts # 串口连接 / 监听 / 写入 +│ │ ├── useSerialConnection.ts # 串口连接 / 监听 / 写入(TX 单序列化) │ │ ├── useSessionFrames.ts # 会话数据帧操作 +│ │ ├── useSessionModbus.ts # Modbus 主站编排 +│ │ ├── useModbusMaster.ts # Modbus 单忙 / 单挂起 RX 守卫 +│ │ ├── useAutoLog.ts # 每会话 append_log 顺序链 +│ │ ├── useTriggers.ts # 脚本化 RX→TX 触发器(带冷却) │ │ ├── usePacketFilter.ts # 方向过滤 / 搜索 / 合并视图 │ │ ├── usePacketFormatter.ts # HEX / 文本 / ANSI 格式化缓存 +│ │ ├── useExport.ts # 导出逻辑(F12 捕获文件旁路) │ │ ├── usePortWatcher.ts # 热插拔监听 -│ │ ├── useExport.ts # 导出逻辑 │ │ └── useSessionActions.ts │ ├── stores/ # Pinia 状态 -│ │ ├── sessions.ts # 多会话管理 +│ │ ├── sessions.ts # 多会话管理(shallowRef + notifyFramesChanged) │ │ ├── serial.ts # 串口设备列表 │ │ └── app.ts # 全局设置(显示模式 / AI 配置 / 快捷键) -│ ├── lib/ # 纯 TS 工具 -│ │ ├── format.ts # HEX / ASCII / UTF-8 格式化 +│ ├── lib/ # 纯 TS,无框架依赖的领域逻辑 +│ │ ├── modbus/ # Modbus 桶(15 模块:core/pdu/transport/ +│ │ │ registers/master-runtime) +│ │ ├── format.ts # HEX / ASCII / UTF-8 / HEX+ASCII 格式化 │ │ ├── bytes.ts # Uint8Array 拼接 +│ │ ├── waveform.ts # 波形解析 / 通道统计 +│ │ ├── waveform-viewport.ts# 视口变换(normalize/zoom/scale/pan) +│ │ ├── waveform-render.ts # 画布渲染流水线(无框架依赖) +│ │ ├── protocol-parser.ts # 分隔符/定长/长度字段重组为帧 +│ │ ├── protocol-engine.ts # 传输无关的 ProtocolEngine 接口 +│ │ ├── bbrec.ts # .bbrec 裸字节流录制 / 回放 +│ │ ├── macro-control-flow.ts # 扩展宏(wait/if/goto/label) +│ │ ├── macro-library.ts # 宏库导入 / 导出(JSON) +│ │ ├── trigger-engine.ts # RX 子串 / HEX 匹配触发引擎 +│ │ ├── serial-rx-queue.ts # O(1) 环形缓冲(头索引丢弃) +│ │ ├── write-chunking.ts # ≤4 KiB TX 分块 + 重试(F8) +│ │ ├── export-filters.ts # 时间范围 / 方向导出过滤 +│ │ ├── ai-models.ts # AI 模型注册表(分发表镜像) +│ │ ├── ai-stream.ts # SSE 增量累加器(F14) +│ │ ├── session-persistence.ts # 版本化迁移链(COW-5) +│ │ ├── connection-presets.ts # 命名串口配置 │ │ ├── logger.ts # 结构化前端日志 -│ │ ├── constants.ts # 波特率 / 数据位等常量 │ │ ├── ipc.ts # 类型化 Tauri 命令封装 │ │ ├── secure-settings.ts # 基于 Tauri Store 的本地密钥设置 -│ │ ├── serial-utils.ts # 串口路径 / 列表工具 -│ │ ├── serial-config.ts # 串口配置 → 枚举映射 +│ │ ├── constants.ts # 波特率 / 数据位等常量 +│ │ ├── serial-utils.ts # 串口路径 / 列表工具 +│ │ ├── serial-config.ts # 串口配置 → 枚举映射 │ │ ├── lru-cache.ts # LRU 缓存 -│ │ └── time.ts -│ ├── types/index.ts # TypeScript 类型定义 -│ ├── styles/ # CSS 变量 + 全局样式 +│ │ └── locales/ # i18n 目录(en.ts / zh.ts / catalog.ts) +│ ├── types/ # 按领域拆分的 TS 类型桶 +│ │ ├── index.ts # 重导出桶 +│ │ ├── display.ts serial.ts macros.ts modbus.ts +│ │ ├── waveform.ts ai.ts session.ts checksum.ts constants.ts +│ ├── styles/ # CSS 变量(283 token)+ 全局样式 │ ├── App.vue # 主窗口 │ ├── AiWindow.vue # AI 悬浮窗口 │ └── main.ts # 入口(路由分发主窗口 / AI 窗口) ├── scripts/ │ └── dev.sh # 开发辅助脚本 -├── tests/frontend/ # 前端单元测试 +├── tests/frontend/ # 前端单元测试(72 个文件,node:test 运行器) +│ └── perf.bench.ts # 回归门控微基准 ├── images/ # 截图 +├── .github/workflows/ # ci.yml(lint/build/test/coverage/cycles)+ release.yml +├── .c8rc.json # c8 覆盖率门(85% 行 / 88% 分支) ├── package.json ├── vite.config.ts ├── eslint.config.mjs @@ -269,22 +334,29 @@ bbcom/ ├──────────────────────────┼───────────────────────────────┤ │ Rust 后端 │ │ │ ┌────────────────────────┴───────────────────────────┐ │ -│ │ commands: ai / checksum / export / window │ │ +│ │ commands: ai / checksum / export / log / updater │ │ +│ │ window │ │ │ ├─────────────────────────────────────────────────────┤ │ │ │ tauri-plugin-serialplugin (串口收发) │ │ │ │ tauri-plugin-dialog (文件保存对话框) │ │ -│ │ tauri-plugin-store (本地设置) │ │ +│ │ tauri-plugin-store (本地设置) │ │ +│ │ tauri-plugin-updater (自动更新检查) │ │ │ │ zai-rs (ZHIPU AI Chat API) │ │ │ └─────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────┘ ``` - 串口通过 `tauri-plugin-serialplugin` 管理,前端通过 Tauri Command / Event 与 Rust 后端通信 -- 前端使用 `requestAnimationFrame` + 有界数据队列,确保高波特率下 UI 流畅 -- AI 助手为独立 `WebviewWindow`,关闭时隐藏而非销毁,通过 Tauri Event 同步窗口状态 -- AI 日志上下文按需刷新,不会把每一帧串口数据持续广播到悬浮窗口 +- 前端持有全部会话状态与协议引擎(Modbus、解析器、波形);Rust 端只是无状态的 IPC + 文件系统 + AI 客户端薄层 +- 前端使用 `requestAnimationFrame` + 有界 O(1) 环形缓冲(`SerialRxQueue`)确保高波特率下 UI 流畅;`sessions` store 采用 `shallowRef`,5 万帧捕获依旧可交互 +- TX 经单一 `writeChain` promise 串行化,确保多个并发发送者(循环发送、宏、触发器、AI 填充、Modbus)不会在串口上交错写入 +- 持久化为版本化 —— 每次加载都运行 `migratePersistedFile` 迁移链,持久化结构变更保持向前兼容 +- AI 助手为独立 `WebviewWindow`,关闭时隐藏而非销毁,通过 Tauri Event 同步窗口状态;响应以 SSE delta 流式返回 - 应用设置本地持久化;AI API Key 会从旧 localStorage 迁移到 Tauri Store +> 完整的模块拓扑、不可违反的不变量("sacred cows")、上游硬约束与人工验证清单, +> 详见 [ARCHITECTURE.md](./ARCHITECTURE.md)。 + ## 贡献指南 欢迎贡献!请遵循以下规范: