# AXIS Web/WASM OPFS 页面临时缓存设计 ## 目标 页面刷新或 Worker 重启后,恢复用户需要的页面上下文,同时不把页面缓存误认为 LinuxCNC 运行时权威。所有 G-code、Task、TP、运动学、轴值、HAL、IO、工具、探针和错误状态仍由 LinuxCNC WASM checkpoint/recovery 恢复;OPFS 临时 JSON 只保存页面上下文和可重新提交给 WASM 的程序草稿。 ## 分层 ~~~mermaid flowchart LR UI[React 页面状态
页签 / 相机 / 图层 / 编辑器] -->|UI_STATE_SAVE| WC[ReactWorkerClient] WC -->|Worker message| W[Dedicated Worker] W --> OPFS[OPFS 双槽位临时 JSON] OPFS -->|UI_STATE_LOAD| W W --> WC WC --> UI P[POWER OFF response] --> W W --> CP[WASM power-off checkpoint] CP --> OPFS OPFS -->|LOAD_WORKSPACE| W W --> WASM[LinuxCNC WASM restore] WASM --> R[Task/TP/pose/IO snapshot] R --> UI ~~~ ## 缓存范围 允许写入 UI 临时 JSON 的字段: - selectedAxis、leftTab、rightTab; - view、zoom、rotateMode; - coordType、displayType、metric; - preview layers; - casePath、program.path、program.source; - cache schema version、context_id、generation、saved_at_ms、state_hash。 禁止由 UI JSON 直接恢复并驱动运动的字段: - task state/mode/program status; - TP queue、active segment、velocity、acceleration、jerk; - XYZAC pose、J0..J4 joints、RTCP/FK/IK state; - HAL、spindle、coolant、tool、probe、home/limit、fault; - operation ledger、event cursor 和已提交副作用。 这些字段必须来自 WASM checkpoint;缓存的 program.source 重新恢复时也必须再次发送 PROGRAM_LOAD,由 WASM interpreter 解析。 ## 文件布局 ```text OPFS root/ linuxcnc-web-wasm/ ui-state/ xyzac-trt/ state-a.json state-b.json manifest.json xyzac-trt-power-off/ state-a.json state-b.json manifest.json ``` `state-a.json` 和 `state-b.json` 轮换写入。写新槽位成功并 flush 后才更新 manifest;任一写入中断时,旧槽位仍可读取。读取时验证两个槽位,选择 generation 最大且 hash 正确的记录。 ## JSON 记录 ```json { "schema": "linuxcnc-ui-temp-json.v1", "context_id": "linuxcnc-2.10.0-pre0-wasm-m1-py-c1", "key": "xyzac-trt", "generation": 12, "saved_at_ms": 1770000000000, "state": { "version": 1, "leftTab": "manual", "rightTab": "preview", "view": "p", "zoom": 1, "program": { "path": "browser/demo.ngc", "source": "..." } }, "state_hash": "fnv1a64:...", "record_hash": "fnv1a64:..." } ``` 约束:单记录最大 1 MiB,程序缓存最大 900 KiB,缓存 TTL 为 7 天;超限、过期、JSON 损坏、schema/context/hash 不匹配时返回 empty/blocked,不能回退到旧 UI 假状态。 ## 生命周期 ~~~mermaid sequenceDiagram participant A as App.tsx participant C as WorkerClient participant W as Worker participant O as OPFS participant X as WASM A->>C: ensureSession C->>W: HELLO / SESSION_ATTACH A->>C: UI_STATE_LOAD C->>W: custom Worker cache request W->>O: scan slots + verify hash/TTL O-->>W: newest valid UI state W-->>C: UI_STATE_RESULT C-->>A: restore tabs/camera/editor draft A->>W: LOAD_WORKSPACE W->>O: load power-off checkpoint if present W->>X: restore WASM task/TP/program/IO X-->>W: authoritative snapshot W-->>A: state snapshot A->>C: debounced UI_STATE_SAVE C->>W: save state JSON W->>O: write inactive slot, flush, update manifest ~~~ 高频 SIM_STEP 不写 OPFS。页面状态变化使用 500ms 防抖;程序编辑器输入连续变化只保存最后一个稳定版本。POWER OFF 的 WASM checkpoint 由 Worker 在收到成功回执后写入专用 key;下一次 LOAD_WORKSPACE 由 Worker 自动读取并交给 WASM restore。 ## 安全和一致性 1. React 不访问 navigator.storage、WASM 指针或 OPFS 文件。 2. Worker 是 OPFS 唯一写者;所有消息串行处理,避免控制命令和缓存写入竞争。 3. UI_STATE_RESULT 不进入 CNC state reducer,不改变 protocol sequence、task tick 或运动状态。 4. UI cache 的 program.source 只能作为输入草稿;加载和预览仍必须由 WASM PROGRAM_LOAD 生成。 5. OPFS 不可用时,页面显示可诊断的 blocked/empty 状态,不使用 localStorage 或静默内存替代。 6. 清理只删除 UI 临时槽位;不会删除 WASM checkpoint、事件日志或程序运行状态。 ## 代码和测试 | 能力 | 文件 | | --- | --- | | 双槽位 JSON、TTL、hash、OPFS API | web/worker/opfs-ui-state.mjs | | Worker-only 路由、断电 checkpoint | web/worker/opfs-worker-bridge.mjs | | React UI 状态读写调用 | web/react/src/worker-client.ts、web/react/src/App.tsx | | 浏览器 Worker 入口 | web/react/src/sim-worker-entry.ts、web/worker/sim-worker.mjs | | 单元回归 | web/worker/test_opfs_ui_state.mjs | | 原生 OPFS 能力探针 | web/workspace/opfs-runtime.mjs、web/app/browser-opfs-probe.html | 验证命令: ```text node web/worker/test_opfs_ui_state.mjs npm run typecheck --prefix web/react npm run build --prefix web/react ``` 真实浏览器还需验证 OPFS quota、Worker reload、坏槽位、过期记录和离线启动;这些属于独立的浏览器恢复 oracle,不等同于 UI cache 单元测试通过。 已完成的线上浏览器样例:`scripts/test_xyzac_opfs_browser.mjs` 在 `20260903-linuxcnc-wasm-opfs-r30` 上验证视角、DRO 页签、A 轴选择刷新后恢复;实际 OPFS 文件为 `state-b.json`/`manifest.json`,`localStorage` 为空,浏览器错误为 0。quota、崩溃和离线 oracle 仍独立保留为 pending。