<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Compose Multiplatform on Trevor's Blog</title><link>http://mings.in/tags/compose-multiplatform/</link><description>Recent content in Compose Multiplatform on Trevor's Blog</description><generator>Hugo</generator><language>zh-CN</language><lastBuildDate>Sat, 24 Oct 2026 16:26:16 +0800</lastBuildDate><atom:link href="http://mings.in/tags/compose-multiplatform/index.xml" rel="self" type="application/rss+xml"/><item><title>Compose 1.13 发布了，但真正值得写的是 1.12</title><link>http://mings.in/posts/compose-113/</link><pubDate>Sat, 24 Oct 2026 16:26:16 +0800</pubDate><guid>http://mings.in/posts/compose-113/</guid><description>1.13 这版只有一个新功能，其余全是修问题。真正重的东西藏在 1.12 里，热重载接上了 MCP。</description><content:encoded><![CDATA[<p>10 月 23 日，Compose Multiplatform 1.13.0-beta01 发了。</p>
<p>标题里这句话不是标题党。这一版改的东西不少，但没一个是新方向，真正能把跨端这件事往前推一步的东西，两个月前就已经悄悄上过了。</p>
<p>先说结论，1.13 只有一处算新功能，剩下全是修问题。一句话带过就够了。</p>
<h2 id="113-就这点东西">1.13 就这点东西</h2>
<p>桌面端 ComposePanel 在 SwingGraphics 渲染模式下，现在能跟着显示器刷新率走帧。默认关，要开就设这个属性。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="nc">System</span><span class="p">.</span><span class="n">setProperty</span><span class="p">(</span><span class="s2">&#34;skiko.swing.frame.pacing&#34;</span><span class="p">,</span> <span class="s2">&#34;true&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>iOS 加了 UIKitInteropSafeAreaInsetsPolicy，能控制嵌进来的 UIKit 组件感知多少安全区。</p>
<p>多端通用只有一条，AnnotatedString 能渲染 Bullet 注解了。</p>
<p>其余全是修 bug。iOS 四个，Web 集中在 Safari 的选中和焦点，桌面端只修了一个 Windows 的缩放卡顿。</p>
<p>好，1.13 的部分到此结束。</p>
<h2 id="真正该看的是两个月前那行字">真正该看的是两个月前那行字</h2>
<p>8 月 25 日，1.12.0 发布。Highlight 只有一行。</p>
<p>Compose Hot Reload 加了个 MCP server，AI agent 可以连到正在运行的 Compose 应用上实时操作。</p>
<p>这行字当时几乎没引起讨论。但把它的工具清单摊开看，就知道这不是个小更新。</p>
<h2 id="一个-agent-拿到手的到底是什么">一个 agent 拿到手的到底是什么</h2>
<p>工程里跑一个 Gradle 任务 hotMcpServer，服务起来，连上应用，等启动，检测关闭，断了自己重连。</p>
<p>接进来的 agent，能调这些工具。</p>
<p>状态和重载这一组，status 看应用和重载状态，连带上一次报错的内容一起给。reload 和 await_reload 触发热重载并等它完成。restart 和 reset_ui 重启应用或者重置界面。</p>
<p>观察这一组，take_screenshot 截图，能指定保存路径。list_windows 和 resize_window 看窗口、改窗口大小。get_semantic_tree 读语义树，弹窗、底部弹层、popup 都算在里面。get_ui_error 和 get_logs 读当前错误和应用日志。</p>
<p>操作这一组，click、type_text、scroll，直接模拟用户输入。</p>
<p><img alt="Compose Hot Reload 暴露给 agent 的工具清单" loading="lazy" src="/images/compose-113/cmp113-tools.png"></p>
<p>这三组拼起来，第一次让 agent 在 Compose 里有了完整的闭环。</p>
<h2 id="闭环是怎么闭上的">闭环是怎么闭上的</h2>
<p>以前 agent 写 Compose 代码，写到界面上就断了。它能改文件，能编译，但看不到界面长什么样，点不了按钮，也不知道这次改动到底对不对。</p>
<p>人得接手。这就是那一大段等待。</p>
<p>现在这个循环是这样跑的。agent 改代码，调 reload 触发热重载，take_screenshot 看一眼，get_semantic_tree 读一遍结构确认布局没塌，get_logs 确认没抛异常。不对就再改一轮。</p>
<p>整个过程人不用进去。</p>
<p>一个 agent 自己把这一圈走完，这就是闭环。</p>
<p>1.3.0-beta02 又补上了缺的那块。可滚动的节点现在带 scrollX 或 scrollY，给出当前偏移、最大偏移，还有内容反向滚动时的 reversed 标记。</p>
<p>这条看着像管道工活。但对 agent 很关键。以前它只知道这里有个可滚动容器，现在知道滚到哪了、还能不能继续往下滚。不然它截图截到的永远是第一屏。</p>
<h2 id="这跟-flutter-是两条路">这跟 Flutter 是两条路</h2>
<p>上个月 Flutter 团队那篇官方文章，核心判断是代码生成早就不是瓶颈，验证才是。</p>
<p>他们的解法是框架天生就轻。热重载 200 毫秒，widget test 500 毫秒，状态检查直接读 Widget 树，全是确定的、结构化的数据。</p>
<p>JetBrains 没走这条路。它不去优化框架本身的速度，而是把运行中的应用整个打开给 agent，让 agent 自己动手验。</p>
<p>一个靠轻，一个靠开门。</p>
<p>我倾向后者更通用。它跟框架无关，只要求运行时能被程序访问。但这也意味着天花板很明确，取决于那个 MCP server 能暴露多少东西。现在十几个工具够用，往后要处理的界面更复杂，肯定不够。</p>
<h2 id="所以现在能做什么">所以现在能做什么</h2>
<p>如果你手上有 Compose 桌面工程，今晚就能试。</p>
<p>前提是 Compose Hot Reload 1.2.0 以上。工程里跑 gradlew hotMcpServer，把这个端点接到你的 agent 客户端里。让它改一个 Composable，重载，读语义树确认按钮还在原位。</p>
<p>上面这些工具清单和闭环路径，是 1.12 和 Hot Reload 1.3.0-beta02 的 release notes 加上仓库源码读出来的，不是我在自己机器上跑过一遍。</p>
<p>这点说清楚，免得你以为是实测。</p>
<p>按我的经验，这条路第一次接最容易卡在两个地方。MCP server 只在 dev 模式下起，你的 client 得连对端口。还有一个坑，get_semantic_tree 在弹层上的表现，官方文档写得很简，实际用之前先单独试一次。</p>
<p>第一次看到它自己把这一圈走完，感受跟看它生成代码完全不同。</p>
<p>问题给到你们。你手上的工程，试过让 agent 直接接管热重载和验证吗？还是仍然自己改完自己点？</p>
<p>再问一句，你觉得这种把运行时开放给 agent 的路子，Web 端和移动端会不会比桌面走得慢？桌面有 JVM 能直接接，移动那边得另想办法。</p>
]]></content:encoded></item></channel></rss>