* feat(website): serve paseo.sh in Simplified Chinese
Every page of the website now has a Simplified Chinese version under
/zh-CN: the interface, docs, blog posts, comparison pages, legal pages,
all changelog releases and the homepage mockup. English URLs are unchanged.
The URL alone decides the language. There is no browser-language redirect
and no stored preference, because either would have to redirect the
unprefixed English URLs that people share and search engines index.
Internal links carry the page's language instead, and hreflang alternates
point Chinese search results at /zh-CN.
Translated headings pin the English anchor with {#id}, so section links
work in both languages. content-translations.test.ts fails when a page
has no translation or a translation's headings drift from the English.
Changelog releases and plugin listings without a translation render
whole in English under a notice, so a release never waits on one.
Plugin manifests and the registry gain optional translations and
OVERVIEW.zh-CN.md fields; older clients and registries ignore them.
* feat(website): replace the native language select with an EN/ZH menu
The header showed an OS-native select whose popup did not match the site.
The trigger now shows the language abbreviated (EN, ZH) and opens a menu in
the app's dropdown style that names each language in itself, with its name
in the page language underneath. Rows are real links to the same page in
that language; arrows, Escape, outside presses and taps behave as a menu
button, and the menu keeps itself on screen when a narrow header pushes the
trigger toward an edge.
Also drops the stray space after the full-width comma in the Chinese
homepage subtitle.
* docs(website): translate main's new docs and 0.12.0 release notes
Main added Keep run workspaces to the schedules docs, rewrote plugin icons
around inline SVG, and promoted 0.12.0 with new entries. Bring the Chinese
pages and changelog in line so no section falls back to English.
* fix(server): leave plugin listing translations to the registry
The daemon rejected manifests whose translations the registry would refuse,
so a plugin main installs could fail to load here. Paseo never reads
translations; the registry validates them at publish. Docs now say which
checks are automated at publish and which are human review.
* docs(plugins): describe the registry's final overview translation discovery
* style(server): format manifest test
* test(app): measure sidebar rows one at a time
After a reload the developer error toast appears while expectVerticalOrder
measures every row at once. Each concurrent boundingBox runs the toast
locator handler, and only one of them resumes, so the poll timed out on
every attempt since #7424. Measuring sequentially keeps one action in flight.
* test(app): return sidebar row measuring to main
#7480 hides the developer toast instead of running a locator handler, so
measuring the rows concurrently no longer races it. Reverts 41ac23016.
* test(app): wait for the compact shell before opening compact Changes
The shell survives the breakpoint, so right after the resize the wide
Explorer could still be visible: the test skipped the Explorer toggle and
the tab then disappeared, or toggled at the wrong time. Wait for the
compact-shell marker, as the composer helpers do since #7424.
* docs(website): translate main's plugin file panel docs
* test(app): edit the code font size after the font family remount
A committed appearance change remounts the settings screen through
AppearanceStyleBoundary, and the new form starts from the stored size.
Filling the size before that remount lost the edit in CI.
* docs(website): translate main's client/ui import docs
Follows #7474: one /client/ui entry, StreamedText instead of useRevealedText,
SettingsGroup dropped from the settings table, and the new
'UI imports in v0.13' migration section.
* test(app): wait for the previous layout's file panel picker to leave
After a resize the old layout's picker animates out while the new one is
already mounted, so the Use button matched two elements and the strict
locator failed. Wait for the shell's layout and a single picker first.
4.0 KiB
title, description, date, draft
| title | description | date | draft |
|---|---|---|---|
| 我对 Electron 的看法错了 | 当小体积安装包不再比渲染、通知和打包 Node daemon 更重要时,我为什么把 Paseo 从 Tauri 迁移到了 Electron。 | 2026-05-28 | false |
开始构建 Paseo 时,我选择了 Tauri。
当时的理由看起来显而易见。我在做一个使用 Web 界面的桌面应用,希望交付的东西不显得臃肿。和很多开发者一样,我也早已默认 Electron 是那个不好的选项。
Tauri 具备所有对的东西:Rust、小体积二进制、原生 WebView、更低的内存占用。它看起来是更好的选择。
我还经常在产品落地页上看到 “Built with Tauri”,我想这对我的影响比我意识到的更大。它让 Tauri 显得像是酷小孩们都在用的东西。
我以为自己比那些 Electron 应用更懂行。一开始,我似乎也确实是对的。在 macOS 上,应用体积很小,界面运行良好,安装包的大小让我自我感觉良好。
让应用在 Windows 上运行起来也没什么问题。Tauri 在那里使用基于 Chromium 的 WebView2,所以应用的表现与我的预期足够接近。确实有一些平台差异要处理,但都不是 Tauri 特有的。
麻烦是从 Linux 开始的。
Tauri 不会在各个平台上捆绑同一个浏览器引擎,这正是它的设计初衷。在 Linux 上,它依赖 WebKitGTK。这听起来很优雅,直到你开始在不同发行版、不同 GPU 配置以及 Wayland/X11 的差异之间调试渲染行为。
对 Paseo 来说,这变成了大量我并不想做的产品工作。Tauri 使用的 WebKitGTK 绑定太旧了。Wayland 有问题。等我终于让应用跑起来,它看起来就是不一样。不只是字重或抗锯齿这类小事,有些页面的布局确实存在差异。
这些我也许都能忍,但随后我开始实现通知功能。
对 Paseo 来说,通知不是锦上添花。当一个 Agent 完成、失败或需要关注时,我希望用户点击通知就能跳转到正确的位置。
Tauri 的通知插件可以显示通知,但桌面端的点击处理并没有以我需要的方式提供。有一个叫 Actions API 的东西可以给通知附加回调,但文档把它标注为仅限移动端,这很奇怪。
这让我注意到 Tauri 正在向移动应用扩展,很难不产生一些疑虑。我还在和最基本的桌面产品问题较劲,而这个框架已经转向了完全不同的方向。
我尝试了一堆 Rust crate,但没有一个能按我想要的方式工作,最后只好为每个平台分别编写通知处理逻辑。这并不简单,而且现在我还得维护它。
还有 daemon 的问题。这部分其实不能怪 Tauri,但它让我进一步倒向了 Electron。
Paseo 有一个 Node.js daemon,而我想要一键式的体验:下载应用、打开、开始使用。我不想告诉用户“先去运行这条命令”,也不想向他们解释什么是 “daemon”。
Tauri 支持 sidecar,所以我把 daemon 打包了进去,它也能工作。但这本身就成了一个项目:不同平台和目标三元组需要不同的二进制文件,还有打包细节、权限、进程启动、路径和升级。
某一刻我意识到,我其实是在多绕几步重新造一个 Electron。
于是我决定试试 Electron。迁移过程有点痛苦,但我出乎意料地很快就让一切跑了起来。经过一周扎实的工作,Paseo 反而变得更轻、更简单了。界面在各个平台上看起来都一样。通知的表现符合我的需要。daemon 也更简单了,因为 Electron 已经内置了 Node。
总的来说,从那以后我对 Electron 一直很满意。它大多数时候不会碍事。出现性能问题时,原因往往在我的应用代码,而不是 Electron 本身。我想这也是 Electron 名声不好的原因:做出一个臃肿的 Electron 应用实在太容易了。
我的结论并不是 Tauri 不好。它只是不适合 Paseo,而我做出那个选择更多是凭感觉,而不是客观地权衡利弊。