<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Bruce 博客</title><link>https://flydogai.com/bruce/blog?lang=zh</link><description>设计、产品、交互、工程探索与决策。把创造的过程，写给开发者和与他们协作的 Agent。</description><language>zh-CN</language><atom:link href="https://flydogai.com/bruce/blog/feed.xml" rel="self" type="application/rss+xml"/><item><title>把我们走过的路，留给下一位开发者</title><link>https://flydogai.com/bruce/blog/why-we-build-in-public?lang=zh</link><guid isPermaLink="true">https://flydogai.com/bruce/blog/why-we-build-in-public</guid><description>Bruce 博客创刊词：记录设计、产品、交互、技术与决策，让开发的过程成为开发者和他们的 Agent 都能继续使用的知识。</description><dc:creator>Bruce 团队</dc:creator><category>editorial</category><pubDate>Thu, 05 Nov 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;把我们走过的路，留给下一位开发者&lt;/h1&gt;&lt;p&gt;Bruce 博客创刊词：记录设计、产品、交互、技术与决策，让开发的过程成为开发者和他们的 Agent 都能继续使用的知识。&lt;/p&gt;&lt;p&gt;Bruce 团队 · 发布于 &lt;time datetime=&quot;2026-11-05&quot;&gt;2026-11-05&lt;/time&gt;&lt;/p&gt;&lt;section id=&quot;everyone-who-builds&quot;&gt;&lt;h2&gt;使用工具的人，也在成为开发者&lt;/h2&gt;&lt;p&gt;我们做 Bruce，是希望更多人能把想法变成可以使用的作品。独立开发者、第一次尝试做产品的人，以及专业团队，走进来时可能有不同的经验。只要开始提出问题、验证想法、判断结果，他们就在参与开发。&lt;/p&gt;&lt;p&gt;AI 能替我们完成越来越多的代码，但一个产品为什么这样做、什么地方不顺手、哪次尝试没有奏效，仍然需要人的判断。这样的判断经常藏在会话、改动和提交里。项目向前走，它们却很容易散掉。&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;leave-a-trail&quot;&gt;&lt;h2&gt;把做过的事，写成别人能继续走的路&lt;/h2&gt;&lt;p&gt;这个栏目从这里开始：把 Bruce 的设计、产品、交互、工程探索和关键决策持续记录下来。我们希望文章能交代最初的问题、观察到的现象、尝试过的方向、最后的选择，以及还没有解决的部分。&lt;/p&gt;&lt;p&gt;这也是一种 Build in Public。公开的不必只有漂亮的结果。一次绕路、一个被推翻的猜测、一组放回真实使用环境才有意义的数据，都可能帮下一位开发者少走一段弯路。文章面对人，也面对帮助他们工作的 Agent：背景、约束和验证方法越清楚，知识越容易被接着使用。&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;from-work-to-writing&quot;&gt;&lt;h2&gt;让开发过程，长出自己的开发日志&lt;/h2&gt;&lt;p&gt;这不只是我们给自己的写作要求，也是 Bruce 希望帮助使用者做到的事。现有的内置写作提示可以围绕一个主题检索记忆、里程碑和相关记录，并结合可用的 Git 历史，整理博客、开发日志、进展回顾和 Build-in-Public 草稿。&lt;/p&gt;&lt;p&gt;找到素材与写出草稿之后，还需要开发者核对事实、补充解释、删去不适合公开的信息，再决定是否保存和发布。我们重视这个决定权。文章应该帮助你表达自己的工作，而不是让工具替你宣告没有核实的成果。&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;a-place-to-talk&quot;&gt;&lt;h2&gt;让技术交流有一个具体的落点&lt;/h2&gt;&lt;p&gt;我们不期待每篇文章都给出唯一答案。我们更希望它能把讨论落到具体情境里：你为什么选择另一条路？同样的交互在你的设备上有什么不同？你的 Agent 从这些记录里发现了什么遗漏？&lt;/p&gt;&lt;p&gt;文章会有稳定的地址、可引用的小节和便于保存的文字版本。你可以把它交给同伴或 Agent，也可以带着具体文章来信讨论。交流从已经发生的事情开始，比重新解释整段背景更容易。&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;explain-the-product&quot;&gt;&lt;h2&gt;让一篇文章，支持更多次清楚的表达&lt;/h2&gt;&lt;p&gt;官网上的一句特性介绍，背后应该有足够扎实的理由。博客为这些理由留下空间：交互为什么这样安排、一个技术选择换来了什么、轻量和完整体验之间怎样取舍。需要深入了解时，产品页面可以直接引用对应的文章。&lt;/p&gt;&lt;p&gt;同一份经过核对的内容，也能成为对外供稿、产品介绍和媒体交流的基础素材。我们会保留完整解释，再按场景整理摘要；让宣传与实际工作有共同的依据，也让后来回看项目时能理解当时的判断。&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;start-here&quot;&gt;&lt;h2&gt;从创刊号开始，逐步补上来路&lt;/h2&gt;&lt;p&gt;第一批内容包括这篇创刊词、小狗标志的设计记录、两次交互排查，以及关于 Agent 运行边界的工程说明。旧记录会标出发生时间与本次整理时间，历史实验中的数字也会保留它们的测试条件。&lt;/p&gt;&lt;p&gt;接下来，我们会逐步从项目沉淀的文档和提交记录中整理更多值得讲清楚的主题。设计、产品、交互、技术和决策，都有自己的故事。希望你在这里读到的经验，能成为你的下一次尝试，也希望我们能由此认识更多正在创造的人。&lt;/p&gt;&lt;/section&gt;</content:encoded></item>
<item><title>好样的布鲁斯：名字、伙伴与小狗的来路</title><link>https://flydogai.com/bruce/blog/the-puppy-we-kept?lang=zh</link><guid isPermaLink="true">https://flydogai.com/bruce/blog/the-puppy-we-kept</guid><description>飞狗为何飞翔，布鲁斯为何握住方向盘。从名字里的玩笑与信任，到 Grok Bot 参与的早期探索、冰蓝 C 和原生 SVG，留下伙伴的来路。</description><dc:creator>Bruce 团队</dc:creator><category>design</category><pubDate>Thu, 05 Nov 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;好样的布鲁斯：名字、伙伴与小狗的来路&lt;/h1&gt;&lt;p&gt;飞狗为何飞翔，布鲁斯为何握住方向盘。从名字里的玩笑与信任，到 Grok Bot 参与的早期探索、冰蓝 C 和原生 SVG，留下伙伴的来路。&lt;/p&gt;&lt;p&gt;Bruce 团队 · 发布于 &lt;time datetime=&quot;2026-11-05&quot;&gt;2026-11-05&lt;/time&gt;&lt;/p&gt;&lt;section id=&quot;names&quot;&gt;&lt;h2&gt;飞得自由，跑得快，也一直在你身边。&lt;/h2&gt;&lt;p&gt;FlyDog / 飞狗里的飞翔，是自由，也是迅速。狗狗，则是一个忠诚、亲切、愿意陪你一起出发的伙伴。我们希望做出来的工具，也有这样的性格：行动利落，相处轻松，需要的时候就在身边。&lt;/p&gt;&lt;p&gt;飞狗和 FlyDog 都简单顺口。中英文用的是熟悉的字词，容易念，也容易记。我们希望大家能自然地叫出这个名字。&lt;/p&gt;&lt;p&gt;FlyDog 是母品牌，Bruce / 布鲁斯是陪你把想法做出来的开发伙伴。至于飞狗这个名字最初是怎么想到的，我们留到以后再聊。&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;bruce-c1&quot;&gt;&lt;h2&gt;布鲁斯考的是 C1。&lt;/h2&gt;&lt;p&gt;Bruce 让很多人想到李小龙的英文名 Bruce Lee。身手敏捷、功夫了得，是我们喜欢的巧合，也是对它的期待。让我们记住这个名字的，是中文互联网里那只被网友喊作“布鲁斯”的驾驶狗，以及“布鲁斯考的是 C1”这个梗。看见狗狗一本正经地坐在方向盘前，旁边的人却很放松，那个画面实在让人难忘。&lt;/p&gt;&lt;p&gt;这里的 C1 是网友的玩笑。我们喜欢的是其中的信任感：复杂的操作有人接手，你终于可以松一口气。创始人留下了当时看到的传播截图，我们也把这份联想记在这里。&lt;/p&gt;&lt;ul class=&quot;article-sources&quot;&gt;&lt;li&gt;&lt;a href=&quot;https://www.bilibili.com/video/BV1nN411G7V9/&quot; rel=&quot;noopener noreferrer&quot;&gt;驾考梗的中文投稿记录 · 2023&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.bilibili.com/video/BV1wD4y1g7rF/&quot; rel=&quot;noopener noreferrer&quot;&gt;“加油布鲁斯，你可以的！”投稿记录 · 2023&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://www.sina.cn/news/detail/5254032233070765.html&quot; rel=&quot;noopener noreferrer&quot;&gt;“好样的布鲁斯”用于陪伴冒险的传播记录 · 2026&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/section&gt;&lt;section id=&quot;good-boy-bruce&quot;&gt;&lt;h2&gt;好样的布鲁斯。&lt;/h2&gt;&lt;p&gt;“好样的布鲁斯。”“布鲁斯，你可以的。”两句很简单的话，有鼓励，也有把事情交给伙伴时的放心。类似的表达也出现在狗狗驾驶摩托、陪伴冒险的短视频里。画面各不相同，这份信任倒是很容易懂。&lt;/p&gt;&lt;p&gt;我们希望它是一只勇往直前、不怕复杂挑战的小狗，能接过开发里的“手动挡”：安装 Agent、配置环境、协调会话、启动脚本，还有那些把人从想法里拉走的零碎操作。人来决定往哪里走，让工具逐步承担怎么抵达的繁琐过程，享受更轻松的“自动挡”体验。&lt;/p&gt;&lt;p&gt;这是我们想走的方向。每做顺一件小事，就离那个值得托付的伙伴更近一点。到时，再说一句：好样的布鲁斯。&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;first-drafts&quot;&gt;&lt;h2&gt;这只小狗，早于今天的 SVG。&lt;/h2&gt;&lt;p&gt;九月的品牌与图标探索有 Grok Bot 参与，Cursor 等工具也参与了图稿落地和应用接入。我们在 V、播放键、代码尖括号之间来回比较，后来留下了戴护目镜的侧脸小狗。选图、重绘、接入应用，是几件不同的工作。&lt;/p&gt;&lt;p&gt;下面这八张，是重新找回的早期图稿。它们把那段摸索补了回来：有短暂用过的方向，也有留在档案里的备选。编号方便翻阅，并不代表每张都按这个顺序诞生。&lt;/p&gt;&lt;p&gt;后来选定的冰蓝 C，解决的是已有小狗在小尺寸下不够显眼的问题。Codex 再把选定的形象重建成原生 SVG。这次矢量精修延续了前面的创作，也让我们更想把那些早期尝试一起留下。&lt;/p&gt;&lt;ul&gt;&lt;li&gt;2026 年 9 月 6 日：V、播放键与代码尖括号是同日来回比较的方向。&lt;/li&gt;&lt;li&gt;2026 年 9 月 9 日：护目镜侧脸进入应用，修正轮廓连接后，我们选定了更细腻的小狗图稿。&lt;/li&gt;&lt;li&gt;2026 年 9 月 12 日：保留已经认可的小狗主体，清理圆角底板外的边界。&lt;/li&gt;&lt;li&gt;2026 年 11 月 5 日：选定冰蓝 C，随后由 Codex 完成这一轮原生 SVG 重建。&lt;/li&gt;&lt;/ul&gt;&lt;/section&gt;&lt;section id=&quot;explore&quot;&gt;&lt;h2&gt;先把可能性摆出来&lt;/h2&gt;&lt;p&gt;本轮精修从一个开放的问题开始：参考 FlyDog 的几何语言，Bruce 是否也适合一个新的符号？我们并排探索了折纸 B、双翼、负形 B 和飞行员意象。它们各有表达方式，但没有成为最终标志。&lt;/p&gt;&lt;p&gt;这轮探索的价值，是让我们重新看清已有形象。Bruce 的小狗已经有自己的表情：朝左的侧脸、双耳、歪头、护目镜和紫色徽章，共同构成了一种熟悉感。新符号看起来完整，不等于值得放弃这些识别线索。&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;keep&quot;&gt;&lt;h2&gt;把方向收回到熟悉的小狗&lt;/h2&gt;&lt;p&gt;反馈把任务重新说明白了：“还是参考我们现有的小狗，基于现有的小狗去优化一下吧。”我们喜欢的形象可以留下，需要解决的是它在使用时不够显眼。&lt;/p&gt;&lt;p&gt;原有深色毛发与底板比较接近，缩小之后，一些细节会混在一起。于是目标从重新建立身份，收敛到前后景分离、脸部占比和小尺寸辨识。双耳、护目镜、神态与徽章继续作为约束，而不是可以随意替换的装饰。&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;choose&quot;&gt;&lt;h2&gt;把 A、B、C 放到同一把尺子上&lt;/h2&gt;&lt;p&gt;我们同时比较大图和 16、24、32、48px 的使用尺寸，并查看明暗界面背景。大图里的精细感，不能直接代替小图标里的清楚程度。三个方向分别检验延续原色、改变底色，以及拉开前后景的效果。&lt;/p&gt;&lt;p&gt;最终选定 C。浅冰蓝底板托起深色轮廓，让面部、护目镜和徽章形成更清楚的层次。这是一次基于实际使用问题的取舍；其它原稿作为探索证据保留，不再与正式标志并列使用。&lt;/p&gt;&lt;ul&gt;&lt;li&gt;A / 青色延续：保留原有青色气质，增强轮廓与头部占比；延续性好，深色毛发与底板仍较接近。&lt;/li&gt;&lt;li&gt;B / 靛蓝探索：靛蓝与青色组合更醒目，深色底板里的结构仍较依赖亮边。&lt;/li&gt;&lt;li&gt;C / 冰蓝分层：浅底与深色结构线分离，让熟悉的小狗在小尺寸下更容易被看见。&lt;/li&gt;&lt;/ul&gt;&lt;/section&gt;&lt;section id=&quot;vector&quot;&gt;&lt;h2&gt;把每一笔真正变成曲线&lt;/h2&gt;&lt;p&gt;选定 C 后，接着要解决文件本身：“注意，不是直接把图片塞进去。”给 PNG 套一个 SVG 外壳，并不会得到可编辑、可持续放大的矢量形状。&lt;/p&gt;&lt;p&gt;我们按原图的轮廓与颜色区域分层拟合，保留脸、耳朵、护目镜、项圈和双箭头，清理边缘杂点与细碎纹理。完整标志由十八条可编辑路径和六组原生渐变组成，文件约 14KB，没有位图嵌入。&lt;/p&gt;&lt;p&gt;底板采用统一的冰蓝渐变与圆角。网站、共享界面组件和支持矢量的平台使用同一原生 SVG；平台需要的 PNG、ICO、ICNS 则由这份主图导出，并遵守各自的安全区、透明度和遮罩要求。这样修改身份时，有一个明确的源头。&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;sizes&quot;&gt;&lt;h2&gt;完整标志与侧栏字形，各自回到实际尺寸&lt;/h2&gt;&lt;p&gt;完整标志在明暗背景下按 16、24、32、48、64px 直接渲染检查。矢量的意义不仅是能放大，还包括在目标尺寸下保留清楚的结构。图文页提供原图与矢量的滑动叠加，使用相同画布和比例查看轮廓。&lt;/p&gt;&lt;p&gt;Agent 侧栏需要另一种密度。我们从同一只小狗提取双耳、护目镜和友好的侧脸，制作六条路径的单色伴随字形：普通工具栏使用 20px，紧凑模式使用 16px。颜色跟随界面状态，保留与相邻工具图标相称的笔画。&lt;/p&gt;&lt;p&gt;这枚字形与完整彩色标志承担不同的呈现任务，但指向同一个 Bruce。共享组件和官网工作台演示使用相同几何数据，避免同一含义在不同入口重新长出一套符号。&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;materials&quot;&gt;&lt;h2&gt;把结果与选择的理由一起留下&lt;/h2&gt;&lt;p&gt;正式完整标志是 bruce-mark.svg，Agent 伴随字形是 bruce-agent.svg。资料包同时保留四个早期符号、原有小狗、A/B/C 原始图片、大小尺寸对比、设计说明与文件清单。原稿保持历史身份，正式资源以当前 SVG 为准。&lt;/p&gt;&lt;p&gt;保留过程，是为了让后来的人能理解为什么留下这只小狗、为什么选择浅底，以及为什么为小尺寸另做字形。开发者和他们的 Agent 可以沿着问题、约束、对照与选择复用方法，媒体介绍也有完整解释可供引用。&lt;/p&gt;&lt;p&gt;FlyDog / 飞狗是母品牌，Bruce / 布鲁斯是子产品。我们继续使用熟悉的形象，同时让它在网站、应用和工具栏里更清楚地表达自己的身份。&lt;/p&gt;&lt;/section&gt;&lt;p&gt;&lt;a href=&quot;https://flydogai.com/bruce/brand?lang=zh&quot;&gt;查看完整品牌故事与设计档案&lt;/a&gt;&lt;/p&gt;</content:encoded></item>
<item><title>距离，不是时间：一次终端滚动排查</title><link>https://flydogai.com/bruce/blog/distance-not-time?lang=zh</link><guid isPermaLink="true">https://flydogai.com/bruce/blog/distance-not-time</guid><description>七轮排查都没有解释“不跟手”。直到我们开始比较，同一段手指位移究竟让画面走了多远。</description><dc:creator>杨永安 + Claude Code</dc:creator><category>interaction</category><pubDate>Thu, 05 Nov 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;距离，不是时间：一次终端滚动排查&lt;/h1&gt;&lt;p&gt;七轮排查都没有解释“不跟手”。直到我们开始比较，同一段手指位移究竟让画面走了多远。&lt;/p&gt;&lt;p&gt;杨永安 + Claude Code · 发布于 &lt;time datetime=&quot;2026-11-05&quot;&gt;2026-11-05&lt;/time&gt; · 原始记录 &lt;time datetime=&quot;2026-09-21&quot;&gt;2026-09-21&lt;/time&gt;&lt;/p&gt;&lt;section id=&quot;the-feeling&quot;&gt;&lt;h2&gt;“不跟手”还不是一个诊断&lt;/h2&gt;&lt;p&gt;最初的反馈是：在 Bruce 终端里滚动全屏 TUI，惯性滚动很流畅，手指频繁上下往复时却觉得沉。对照是熟悉的 IDE 终端。我们很快把问题理解成了响应慢，于是开始量延迟。&lt;/p&gt;&lt;p&gt;这篇整理自 2026 年 9 月的开发记录。文中的数值属于当时的设备、版本和测试场景，不是对所有终端或机器的性能承诺。真正值得留下的是，如何把一个准确的体感描述，变成可以区分原因的测量。&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;good-numbers&quot;&gt;&lt;h2&gt;数字都不错，为什么手感仍然不对&lt;/h2&gt;&lt;p&gt;我们依次检查写入、传输、程序回包、终端解析、渲染帧率和字形图集。确实找到了可以改进的地方：更短的合并窗口、更合适的调度方式，以及不让阻塞写入拖住其它工作的处理。但这些改动都没有解释那个稳定的“沉”。&lt;/p&gt;&lt;p&gt;另一个问题是测量对象。浏览器里的最小页面、刚启动而没有内容的 TUI，和用户正在操作的原生窗口，是不同的现场。我们给实际窗口补上了可请求求值、返回结果的开发调试通道，才看清一次手势到底走了哪条路径。&lt;/p&gt;&lt;p&gt;那次追踪发现，眼前滚动的是终端自己的回滚缓冲，事件根本没有经过我们一直优化的“把滚轮报告交给程序”那条路。先确认手势归谁处理，应该发生在优化之前。&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;travel&quot;&gt;&lt;h2&gt;转折来自一个更朴素的对照&lt;/h2&gt;&lt;p&gt;用户在另一款终端里做了同样的动作，给出更精确的描述：不是等得太久，是走得不够远。时间和距离终于被分开了。一个没有明显延迟的系统，也能因为位移增益不对而显得迟钝。&lt;/p&gt;&lt;p&gt;当时记录的一次三秒手势有 165 个事件，总位移约 1002px，平均每个事件约 6.1px。相关路径在把像素换算为行数时，对小于 50px 的事件额外乘了 0.3。触控板把手势切成许多小步，几乎每一步都被缩短。按当时约 15px 的行高估算，本该产生约 66 份报告，实际只有约 20 份。&lt;/p&gt;&lt;p&gt;影响的是交给全屏程序的滚轮报告，而不是所有滚动。两条路径混在一起测，延迟曲线再漂亮，也无法说明行程是否正确。&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;fix-the-path&quot;&gt;&lt;h2&gt;修复必须落在对应的路径上&lt;/h2&gt;&lt;p&gt;第一版补偿用错了地方：把本来正常的回滚缓冲也放大，反而产生过冲。后来才把判断收窄到三个条件：事件会送给程序、使用像素模式、原始位移落在会被缩短的范围内。下面只保留当时判断的核心。&lt;/p&gt;&lt;p&gt;这不是把灵敏度统一调大。大步进的鼠标事件没有经过相同的缩短，不能一起放大。我们也把相同总位移、不同事件分片方式的结果整理为回归用例，并在当时向上游提交了问题与修复建议；本地行为仍需要独立验证。&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;const sensitivity = toProgram &amp;amp;&amp;amp; pixelMode &amp;amp;&amp;amp; Math.abs(deltaY) &amp;lt; 50
  ? configured / 0.3
  : configured;&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;section id=&quot;takeaways&quot;&gt;&lt;h2&gt;让下一次排查少绕一点路&lt;/h2&gt;&lt;p&gt;这次排查留下的，不只是滚动补偿。能进入实际窗口的调试通道、会报告失败的测量脚本，以及对照同一手势的习惯，都能帮助后来的开发者和 Agent 避免把正确的数字用在错误的对象上。&lt;/p&gt;&lt;ul&gt;&lt;li&gt;先确认事件属于哪块表面、哪条处理路径。&lt;/li&gt;&lt;li&gt;“跟手”同时涉及时间和位移，两个量都要检查。&lt;/li&gt;&lt;li&gt;记录设备、版本、内容和前台状态，让数字有明确出处。&lt;/li&gt;&lt;li&gt;把决定性的对照写成回归用例，避免只留下“感觉好了”。&lt;/li&gt;&lt;/ul&gt;&lt;/section&gt;</content:encoded></item>
<item><title>抬起去哪了：一次点击丢失排查</title><link>https://flydogai.com/bruce/blog/the-missing-release?lang=zh</link><guid isPermaLink="true">https://flydogai.com/bruce/blog/the-missing-release</guid><description>轻点失灵、用力按一下又恢复。沿事件链逐层检查后，我们发现问题发生在网页收到事件之前，恢复策略却决定了后续体验。</description><dc:creator>杨永安 + Claude Code</dc:creator><category>interaction</category><pubDate>Thu, 05 Nov 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;抬起去哪了：一次点击丢失排查&lt;/h1&gt;&lt;p&gt;轻点失灵、用力按一下又恢复。沿事件链逐层检查后，我们发现问题发生在网页收到事件之前，恢复策略却决定了后续体验。&lt;/p&gt;&lt;p&gt;杨永安 + Claude Code · 发布于 &lt;time datetime=&quot;2026-11-05&quot;&gt;2026-11-05&lt;/time&gt; · 原始记录 &lt;time datetime=&quot;2026-10-02&quot;&gt;2026-10-02&lt;/time&gt;&lt;/p&gt;&lt;section id=&quot;an-incomplete-click&quot;&gt;&lt;h2&gt;一次点击，少了一半&lt;/h2&gt;&lt;p&gt;反馈很像一个前端问题：设置侧边栏轻点时有些栏目没有反应，用力按一下触摸板，后面的轻点又正常了。早先我们曾把类似现象归因于 React 的点击兼容性，并把一些操作提前到按下时执行。但那个解释没有经过完整测量。&lt;/p&gt;&lt;p&gt;这次直接在实际窗口记录事件。失败时有 pointerdown 和 mousedown，随后没有抬起。下一次操作又少了新的 pointerdown。WebKit 仍以为上一轮按键没有松开，后续判断也跟着错了。问题从“点击不灵”变成了“释放事件在哪里丢了”。&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;pointerdown → mousedown → …
下一次：mousedown → pointerup → mouseup → click&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;section id=&quot;follow-the-event&quot;&gt;&lt;h2&gt;沿着事件链，一层一层问&lt;/h2&gt;&lt;p&gt;我们先确认命中的确实是按钮，没有遮罩或拖拽区域挡住它。然后检查 WebView、窗口、应用，以及系统投递给进程的事件。那个丢掉的 mouseUp 在这些层里都没有出现，连系统查询仍把左键记作按下。继续调整页面处理函数，已经无法解释这一段事实。&lt;/p&gt;&lt;p&gt;当时测试机上的窗口管理工具 Magnet 有一个监听鼠标按下、抬起的主动事件拦截器。退出它再复测，结果发生了变化：记录中的两组八次点击分别丢了两到三次抬起；退出后，只使用有效时长的十二次点击没有再丢失。这是当时设备与工具状态下的对照，不能外推为所有安装环境的结论。&lt;/p&gt;&lt;p&gt;合成事件也必须有对照。按下和抬起同一瞬间发出的 0ms 测试，在最小原生窗口中也会丢失，不能拿它代表真实轻点。仪器自己的异常，差点成了我们的产品结论。&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;recovery&quot;&gt;&lt;h2&gt;同样的缺失，也可能有不同的体感&lt;/h2&gt;&lt;p&gt;接下来，我们并排比较 Chromium 与 WKWebView 收到的同一类事件。两边都可能收到不完整的序列，差别在于丢失之后如何恢复。测量中，Chromium 会在下一次按下前先结束旧的按下状态；WebKit 则可能继续沿用错误状态。&lt;/p&gt;&lt;p&gt;标题栏归谁处理也重要：原生标题栏上的点击不一定进入网页，而 Bruce 的网页拖拽区域会让 WebView 参与。同样一句“Electron 手感好”，背后可能是恢复策略与事件边界的差异，不能直接简化成某个框架的点击函数更可靠。&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;repair-with-boundaries&quot;&gt;&lt;h2&gt;修复状态，不能制造新的意图&lt;/h2&gt;&lt;p&gt;Bruce 无法替别的程序修复事件拦截器，但可以在自己的 WebView 入口维护按下与抬起的配对。实现会结合系统按键变化、事件队列和下一次真实按下，判断何时需要修复。&lt;/p&gt;&lt;p&gt;补发的位置和时间一样重要。早期且目标仍可信时，可以落回按下处完成轻点；太迟的补发只复位，不执行用户已经放弃的操作。把抬起放到指针当前所在的新目标上，可能让共同祖先收到一次幽灵点击。&lt;/p&gt;&lt;p&gt;后续实现还区分了指针拖动与原生拖拽会话，并跟踪迟到的真实释放，避免一次动作结算两次。修复一个丢事件的问题，不应该靠向所有目标无差别地合成点击。&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;make-the-evidence-useful&quot;&gt;&lt;h2&gt;把排查方法留给下一位开发者&lt;/h2&gt;&lt;p&gt;这些记录能帮助使用 WebView、原生窗口或混合界面的开发者，也能给代为排查的 Agent 一条更稳妥的路线。具体修复会随平台与版本变化，但可观测性和对照的方法可以复用。&lt;/p&gt;&lt;ul&gt;&lt;li&gt;把“没响应”拆成命中、按下、抬起、派发与状态恢复。&lt;/li&gt;&lt;li&gt;逐层确认事件是否到达，越过已排除的边界后就停止反复猜测。&lt;/li&gt;&lt;li&gt;保留失败与成功两组对照，并注明合成事件的条件。&lt;/li&gt;&lt;li&gt;仪器先确认目标窗口，避免合成输入落到别的应用。&lt;/li&gt;&lt;li&gt;恢复过期状态时，不补做用户已经放弃的意图。&lt;/li&gt;&lt;/ul&gt;&lt;/section&gt;</content:encoded></item>
<item><title>为什么一个数据目录只运行一个 Agent</title><link>https://flydogai.com/bruce/blog/one-store-one-agent?lang=zh</link><guid isPermaLink="true">https://flydogai.com/bruce/blog/one-store-one-agent</guid><description>桌面里有会话，手机却看不到。一次双服务并行的问题，让我们重新明确服务的所有权、发现方式和客户端上下文。</description><dc:creator>Bruce</dc:creator><category>engineering</category><pubDate>Thu, 05 Nov 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;为什么一个数据目录只运行一个 Agent&lt;/h1&gt;&lt;p&gt;桌面里有会话，手机却看不到。一次双服务并行的问题，让我们重新明确服务的所有权、发现方式和客户端上下文。&lt;/p&gt;&lt;p&gt;Bruce · 发布于 &lt;time datetime=&quot;2026-11-05&quot;&gt;2026-11-05&lt;/time&gt; · 原始记录 &lt;time datetime=&quot;2026-10-28&quot;&gt;2026-10-28&lt;/time&gt;&lt;/p&gt;&lt;section id=&quot;two-different-answers&quot;&gt;&lt;h2&gt;两个入口，各自看起来都正常&lt;/h2&gt;&lt;p&gt;一次排查中，驻留 Agent 与开发窗口启动的 Agent 同时服务于同一个数据目录。窗口只去一个预想端口寻找服务，没找到就启动了第二个；手机连接的入口则被先到的服务占用。桌面里刚出现的会话，手机上没有，两边也没有明显报错。&lt;/p&gt;&lt;p&gt;这不是先增加一次刷新就能说明的问题。两个入口实际上找到了不同的服务实例。2026 年 10 月的修复，从一条更基础的规则开始：同一份状态，需要明确谁拥有它。&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;store-identity&quot;&gt;&lt;h2&gt;边界是数据目录，不是整个设备&lt;/h2&gt;&lt;p&gt;Bruce 现在以数据目录为服务所有权的边界。同一个目录只能由一个 Agent 持有；第二个启动请求应该找到已有服务，说明谁在服务，然后退出。客户端也因此不需要各自猜测固定端口。&lt;/p&gt;&lt;p&gt;独立数据目录可以有独立 Agent，这是隔离环境的正常用法。测试需要自己的存储，不能和人的任务列表混在一起。“一台机器只能有一个进程”会把这个有用的边界也抹掉。&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;lock-and-record&quot;&gt;&lt;h2&gt;锁负责所有权，记录负责发现&lt;/h2&gt;&lt;p&gt;实现分成两件事。操作系统文件锁声明谁可以服务这份状态，Agent 在接触存储前先取得它。持有者退出后，锁会释放；即使文件还在，也不能把文件存在误读为锁仍被占用。&lt;/p&gt;&lt;p&gt;获得所有权后，Agent 写下自己的进程、监听位置和启动信息。记录先完整写到临时文件，再改名，避免读到半份内容。其它入口读取记录，寻找正在服务的实例。&lt;/p&gt;&lt;p&gt;记录可能比进程活得更久，所以读到记录还不等于服务在线。调用方仍要确认记录中的地址能够应答。所有权、发现和可用性各自说明，故障才更容易定位。&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;同一个数据目录
  存储锁   → 谁可以服务这份状态
  发现记录 → 去哪里寻找它
  实际应答 → 它现在是否可用&lt;/code&gt;&lt;/pre&gt;&lt;/section&gt;&lt;section id=&quot;context&quot;&gt;&lt;h2&gt;共享服务，仍然要明确访问哪个项目&lt;/h2&gt;&lt;p&gt;找对服务只是第一步。窗口、手机和其它客户端还需要说明项目、会话与请求身份。服务不能根据最近活跃的窗口，猜测手机此刻想访问哪个目录。&lt;/p&gt;&lt;p&gt;这样的划分把界面与工作状态分开：客户端负责表达当前上下文，服务负责它持有的任务、终端和记录。换一个入口不应无意中换成另一套工作；是否允许访问，则由对应的权限与连接配置决定。&lt;/p&gt;&lt;/section&gt;&lt;section id=&quot;regression-and-lifetime&quot;&gt;&lt;h2&gt;把边界写进测试，也把生命周期讲清楚&lt;/h2&gt;&lt;p&gt;回归测试覆盖了同目录的第二次持有会被拒绝、释放后可以再次取得所有权，以及不同目录互相独立。这些条件比一次“看起来连上了”更具体。&lt;/p&gt;&lt;p&gt;关闭一个客户端和重启 Agent 是不同操作。存储锁与发现记录不会让终端进程跨 Agent 重启存活。持续工作仍依赖主机在线、服务设置和实际进程生命周期；无损交接是需要另外设计与验证的能力。&lt;/p&gt;&lt;ul&gt;&lt;li&gt;先确认两端是不是同一份数据、同一个服务，再讨论同步。&lt;/li&gt;&lt;li&gt;用明确的所有权避免启动竞争，用发现记录避免端口猜测。&lt;/li&gt;&lt;li&gt;把项目、会话和身份作为上下文说明，不靠最近一次界面活动推断。&lt;/li&gt;&lt;li&gt;隔离测试存储，保留人的任务与会话现场。&lt;/li&gt;&lt;/ul&gt;&lt;/section&gt;</content:encoded></item></channel></rss>
