架构

双世界桌面模型——特权 Rust 核心与沙箱 webview,由类型化、能力受限的 IPC 相连。

心智模型是由一条狭窄、类型化边界相连的两个世界。

两个世界

世界是什么做什么
Rust 核心(特权)src-tauri/——原生二进制、插件与你的 #[tauri::command]触碰 OS:钥匙串、文件系统、通知、更新器、托盘、日志。
webview(沙箱)src/——系统 webview 中的 React SPA,HashRouter,懒加载路由渲染 UI 并调用核心。无 Node、无 preload 脚本,不直接访问 OS。

两者之间的边界是 invoke 加上能力 ACL。UI 代码从不直接触碰 OS——它向核心 请求,而核心只对某个能力已授予的权限作答。这正是相对 Web 模板和 Electron 版的 安全差异:不是 preload 桥,而是每个窗口携带一份枚举好的权限集,Rust 核心会在你的 代码运行前拒绝其外的一切。

一屏看懂 IPC

组件  →  tauri.<domain>.<method>()  →  生成绑定           →  #[tauri::command](Rust)
        (或 useTauri('<domain>'))    src/lib/bindings.ts     src-tauri/src/commands/
  • 类型由 tauri-specta 编译期推导——没有需要同步维护的手写 schema。生成的 src/lib/bindings.ts 已提交,任何漂移都会使 CI 失败。
  • ACL 在命令体运行之前检查权限。
  • Zod 保留给设置与表单,而非 IPC 参数。

完整的添加命令流程见 IPC 页

一个 use-conveyor.ts shim 与 src/lib/conveyor/schemas.ts 从 Electron 移植 中保留下来,仍被部分组件引用。请把 tauri / useTauri 视为真实 API,把 conveyor 层视为移植兼容——其中一些方法是空操作 stub。见 IPC

中心化配置

所有前端配置在 shared/config.ts只读一次;其他地方都不触碰 import.meta.env。它暴露 featuresbillingConfigshowcaseConfig 与 分析/Sentry 插槽。

开关(featuresEnv默认
authVITE_AUTH_ENABLED
autoUpdateVITE_AUTO_UPDATE_ENABLED
showcaseVITE_SHOWCASE_ENABLED
billingVITE_BILLING_ENABLED

一个开关在构建期剥离其整个子系统。关键在于每个子系统只有一个开关——没有 Electron 那样的 MAIN_VITE_* 孪生。原生菜单与托盘用 TypeScript 构建 (src/lib/menu/src/lib/tray/)并直接读取同一个 features 对象,因此单个 VITE_SHOWCASE_ENABLED 就能触及路由、侧边栏、命令面板以及原生 Template 菜单。

部分模板注释与 README 描述前端在启动时通过 app::set_feature_flags 命令把开关 "推送"给 Rust。该命令并不存在getNativeFeatureFlags() 也没有任何调用者。 单开关行为是真实的,但原因更简单:菜单/托盘由 JS 构建并直接读取 features。 不存在配置推送。

状态分层

持有支撑
zustand store会话与权益状态(auth-storesubscription-storesupabase-js
TanStack Query服务端数据(todos、订阅读取)src/lib/query/
设置存储用户偏好tauri-plugin-store + TS 侧版本化迁移

启动流程

Rust 入口(main.rslib.rs run())以刻意的顺序装配应用:

  1. single-instance 插件最先——使第二次启动在任何窗口存在前被短路;其回调会 聚焦运行中的窗口,并把新进程的 argv 转发给前端意图分发器。
  2. 插件——opener、store、dialog、fs、autostart、log、notification、 global-shortcut、os、process、updater、window-state 与 deep-link。
  3. setup 钩子——仅在 debug 构建中于运行时注册 soar-tauri:// 协议 (deep_link().register_all()),使深链在 tauri dev 期间可测;并初始化可选的 Sentry guard。
  4. 窗口——由 tauri.conf.jsondecorations: false(无边框自定义标题栏)创建。
  5. 前端——main.tsx 校验 Supabase env,再挂载 AppOnboardingGate → 路由。

features.auth 开启且 Supabase env 缺失或无效时,main.tsx 渲染 SupabaseConfigErrorScreen,且从不导入 App(否则其 store 导入会构造 Supabase 客户端)。关闭认证时,应用照常挂载且从不触碰该客户端。

第二道防线:CSP

在 ACL 之外,tauri.conf.jsonapp.security 设置了严格的内容安全策略。默认 锚定于 'self';唯一显著放宽的是 connect-src,它允许 IPC 传输以及对 Supabase 和更新源的出站 HTTPS/WSS:

"connect-src 'self' ipc: http://ipc.localhost https: wss:"

ipc:http://ipc.localhost 是 webview 触达 Rust 核心的方式;script-src 'self' (开发环境为 HMR 追加 'unsafe-inline'/'unsafe-eval')意味着应用内无法注入或 加载外来脚本。CSP 与 ACL 一道,把 webview 钉在 self——外部 URL 被强制经由受审计的 opener 收口流出(见 能力)。

相关页面

On this page