<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Cairn</title>
<link>https://cairn.iamnbutler.workers.dev/</link>
<description>Research into user interfaces where every action a person can take is a claim with a durable id and machine-checked evidence. The blog and progress videos.</description>
<language>en</language>
<atom:link href="https://cairn.iamnbutler.workers.dev/feed.xml" rel="self" type="application/rss+xml"/>
<image><url>https://cairn.iamnbutler.workers.dev/apple-touch-icon.png</url><title>Cairn</title><link>https://cairn.iamnbutler.workers.dev/</link></image>
<lastBuildDate>Mon, 26 Oct 2026 14:46:47 GMT</lastBuildDate>
<item>
<title>Try it, back in the browser</title>
<link>https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/#try-it-back-in-the-browser</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/images/7a91994661a0e8c8.png</guid>
<pubDate>Mon, 26 Oct 2026 14:46:47 GMT</pubDate>
<description>&lt;figure&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/images/7a91994661a0e8c8.png&quot; width=&quot;2296&quot; height=&quot;1392&quot; alt=&quot;Try it, back in the browser&quot;&gt;&lt;figcaption&gt;TodoMVC from cairn_examples on /try/todo/: every frame one its Lean code drew, captured headless by make try. The pointer is on a todo's checkbox, cairn_examples^204, proved, with its claim page a click away.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;A screenshot at commit dccde9a0, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/&quot;&gt;Likes that show at once and end up true&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Sky: the notifications list</title>
<link>https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/#sky-the-notifications-list</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/images/316bbff1e81f0fae.png</guid>
<pubDate>Mon, 26 Oct 2026 14:35:19 GMT</pubDate>
<description>&lt;figure&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/images/316bbff1e81f0fae.png&quot; width=&quot;1200&quot; height=&quot;1640&quot; alt=&quot;Sky: the notifications list&quot;&gt;&lt;figcaption&gt;Grouped likes, follows with Follow back, replies and mentions as posts with Like, Repost and ⋯; the unread count on the tab.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;A screenshot at commit a85a11f0, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/&quot;&gt;Likes that show at once and end up true&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Sky: notifications on screen</title>
<link>https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/#sky-notifications-on-screen</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/videos/7640748f220e2658.mp4</guid>
<pubDate>Mon, 26 Oct 2026 14:35:17 GMT</pubDate>
<description>&lt;figure&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/7640748f220e2658.mp4&quot;&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/videos/7640748f220e2658.jpg&quot; width=&quot;1200&quot; height=&quot;1640&quot; alt=&quot;Sky: notifications on screen&quot;&gt;&lt;/a&gt;&lt;figcaption&gt;Others like, repost and follow while the reader is on Home: the count comes onto the Notifications tab (and the Dock badge). The tab opens the list, &amp;quot;3 new&amp;quot; takes it to the new rows, which are marked seen; Follow back, a like from a mention, then Home again at the same place. Rendered offscreen against the fake PDS.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/7640748f220e2658.mp4&quot;&gt;Watch the video&lt;/a&gt; (16 s), recorded at commit a85a11f0, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/&quot;&gt;Likes that show at once and end up true&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Cairn 0.5.2</title>
<link>https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/#cairn-0-5-2</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#cairn-0-5-2</guid>
<pubDate>Mon, 26 Oct 2026 14:08:40 GMT</pubDate>
<description>&lt;p&gt;Cairn 0.5.2 is out on the public registry with two virtual-list fixes Sky turned up: a list now settles however many layouts it takes when many rows it knows change height at once (Sky's mute hiding many posts), and a clamp within a batch no longer moves the reader's row when a later step of the same batch lengthens the list.&lt;/p&gt;&lt;p&gt;No claim is new, retired, moved or changed. The registry's ledger check accepted 0.5.1 to 0.5.2 and serves its claims export for the claim pages.&lt;/p&gt;&lt;p&gt;As of commit 23f0836, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/&quot;&gt;Likes that show at once and end up true&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Sky: muting an author mid-feed</title>
<link>https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/#sky-muting-an-author-mid-feed</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/videos/ed6f79cd26cde170.mp4</guid>
<pubDate>Mon, 26 Oct 2026 12:56:55 GMT</pubDate>
<description>&lt;figure&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/ed6f79cd26cde170.mp4&quot;&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/videos/ed6f79cd26cde170.jpg&quot; width=&quot;1200&quot; height=&quot;1640&quot; alt=&quot;Sky: muting an author mid-feed&quot;&gt;&lt;/a&gt;&lt;figcaption&gt;⋯ on a post, Mute: the author's posts leave the feed and the post being read stays where it is; the status line offers Undo, which brings them back (cairn#254). The ⋯ menu is AppKit's and isn't in the offscreen render: the button shows pressed while it is open.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/ed6f79cd26cde170.mp4&quot;&gt;Watch the video&lt;/a&gt; (8 s), recorded at commit f9a66b65, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/&quot;&gt;Likes that show at once and end up true&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Sky: Like and Repost, one refused</title>
<link>https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/#sky-like-and-repost-one-refused</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/videos/ccbf74382a6fcbcc.mp4</guid>
<pubDate>Mon, 26 Oct 2026 12:56:50 GMT</pubDate>
<description>&lt;figure&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/ccbf74382a6fcbcc.mp4&quot;&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/videos/ccbf74382a6fcbcc.jpg&quot; width=&quot;1200&quot; height=&quot;1640&quot; alt=&quot;Sky: Like and Repost, one refused&quot;&gt;&lt;/a&gt;&lt;figcaption&gt;Sky against the fake PDS with its replies slowed by 1.4 s: Like shows at once and greys until the server confirms it, then Repost; the next Like is refused and rolls back, with the reason in the status line (cairn#254). Rendered offscreen from the engine's frames.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/ccbf74382a6fcbcc.mp4&quot;&gt;Watch the video&lt;/a&gt; (11 s), recorded at commit f9a66b65, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/&quot;&gt;Likes that show at once and end up true&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Cairn 0.5.1</title>
<link>https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/#cairn-0-5-1</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#cairn-0-5-1</guid>
<pubDate>Mon, 26 Oct 2026 12:06:37 GMT</pubDate>
<description>&lt;p&gt;Cairn 0.5.1 is out on the public registry: cairn conformance now holds the world (the debug socket's holdWorld stops the host's own clock, wake and requests, and the driver decides the time and every reply), benchmarks for Cairn.Optimistic up to 1,000,000 keys, and two fixes Sky needs: cairn fuzz no longer treats controls as hidden after a virtual list is replaced, and a held clock never steps an app with an empty batch.&lt;/p&gt;&lt;p&gt;One claim is new, ^1032 (under holdWorld the host sends no clock, wake or reply of its own), fuzzed by world.hold. The registry's ledger check accepted 0.5.0 to 0.5.1 and serves its claims export for the claim pages.&lt;/p&gt;&lt;p&gt;As of commit 3616efd, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/&quot;&gt;Likes that show at once and end up true&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Sky: notifications, grouped</title>
<link>https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/#sky-notifications-grouped</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/images/8d8c5c0838845866.png</guid>
<pubDate>Mon, 26 Oct 2026 11:44:43 GMT</pubDate>
<description>&lt;figure&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/images/8d8c5c0838845866.png&quot; width=&quot;1200&quot; height=&quot;1640&quot; alt=&quot;Sky: notifications, grouped&quot;&gt;&lt;figcaption&gt;Sky's notifications: likes of one post in one row, marked seen once on screen (cairn#253).&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;A screenshot at commit 7a69f8b, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/&quot;&gt;Likes that show at once and end up true&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Sky: unread notifications</title>
<link>https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/#sky-unread-notifications</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/images/84ca80ebe0308a20.png</guid>
<pubDate>Mon, 26 Oct 2026 11:44:41 GMT</pubDate>
<description>&lt;figure&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/images/84ca80ebe0308a20.png&quot; width=&quot;1200&quot; height=&quot;1640&quot; alt=&quot;Sky: unread notifications&quot;&gt;&lt;figcaption&gt;The unread count, polled from the fake PDS, on Sky's Notifications tab (cairn#253).&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;A screenshot at commit 7a69f8b, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/&quot;&gt;Likes that show at once and end up true&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Sky: like, repost, follow and mute</title>
<link>https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/#sky-like-repost-follow-and-mute</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/images/bd72968d90c4fd23.png</guid>
<pubDate>Mon, 26 Oct 2026 11:42:38 GMT</pubDate>
<description>&lt;figure&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/images/bd72968d90c4fd23.png&quot; width=&quot;1200&quot; height=&quot;1640&quot; alt=&quot;Sky: like, repost, follow and mute&quot;&gt;&lt;figcaption&gt;Sky's feed with the acts row under each post: Like, Repost, Follow and Mute, built on Cairn.Optimistic and cairn_atproto's writes. An offscreen render against the fake server.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;A screenshot at commit 7a69f8b, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/&quot;&gt;Likes that show at once and end up true&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Cairn 0.5.0 and cairn_atproto 0.3.0, early in M5</title>
<link>https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/#cairn-0-5-0-and-cairn-atproto-0-3-0-early-in-m5</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#cairn-0-5-0-and-cairn-atproto-0-3-0-early-in-m5</guid>
<pubDate>Mon, 26 Oct 2026 10:06:12 GMT</pubDate>
<description>&lt;p&gt;Cairn 0.5.0 is out on the public registry as iamnbutler/cairn, so Sky's M5 work builds on it from the registry: Cairn.Optimistic (values shown at once, confirmed or rolled back, ^1100–^1114), a fuzz server with a world that fails requests before it acts and loses replies after, menus on controls and in the menu bar, Effect.scrollTo, kept scroll boxes, the Dock badge and announcements. Frames are v13, and every app now links Lean's libStd.&lt;/p&gt;&lt;p&gt;cairn_atproto 0.3.0 follows on Cairn ^0.5.0, with Fake.server: the fake PDS as a cairn fuzz server whose world is the whole PDS and the host's Keychain. A small app in the package, FakeApp, runs under cairn fuzz with it and checks that the server holds every like it acknowledged while replies are lost and tokens rotate.&lt;/p&gt;&lt;p&gt;The registry's ledger check accepted 0.4.1 to 0.5.0 (23 new claims, none retired) and cairn_atproto 0.2.0 to 0.3.0, and serves both claims exports for the claim pages.&lt;/p&gt;&lt;p&gt;As of commit 486e8ce, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/&quot;&gt;Likes that show at once and end up true&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Likes that roll back</title>
<link>https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/#likes-that-roll-back</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/videos/dc420e524d898d55.mp4</guid>
<pubDate>Mon, 26 Oct 2026 08:35:19 GMT</pubDate>
<description>&lt;figure&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/dc420e524d898d55.mp4&quot;&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/videos/dc420e524d898d55.jpg&quot; width=&quot;960&quot; height=&quot;1200&quot; alt=&quot;Likes that roll back&quot;&gt;&lt;/a&gt;&lt;figcaption&gt;The fixture's Like buttons on Cairn.Optimistic, against a scripted server: each press shows at once with its count. Post 1's like is accepted. Post 3's is refused and rolled back in the step the refusal arrives, with a line saying so. Post 4's reply is lost after the server applied it, so the post stays liked, dimmed, until the read back confirms it. Rendered offscreen (Fixture --like-frames).&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/dc420e524d898d55.mp4&quot;&gt;Watch the video&lt;/a&gt; (13 s), recorded at commit a8d0310, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/&quot;&gt;Likes that show at once and end up true&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Likes that show at once and end up true</title>
<link>https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#fifth-milestone-sky-acts-on-bluesky</guid>
<pubDate>Mon, 26 Oct 2026 08:18:05 GMT</pubDate>
<description>&lt;p&gt;Sky, the Bluesky client I'm building on Cairn, could read a feed. Now it can act on one: like,
repost, follow and mute, with a notifications tab beside Home. This is the fifth milestone, and
it's still in progress.&lt;/p&gt;
&lt;p&gt;The hard part of a like button isn't drawing it. The button has to change the instant you press
it, before the server has answered, and the server may refuse, answer late, answer out of order,
or act and lose the reply. Most apps handle that with care and hope. I wanted a claim:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Once every reply is in, whatever order the replies came in, each post shows what the server
holds, and its count is the server's.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;What Cairn gained&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Optimistic values&lt;/strong&gt; (&lt;code&gt;Cairn.Optimistic&lt;/code&gt;): keyed values shown at once, with one write in
flight per key. A refused write rolls back in the step its answer arrives, and a lost reply is
read back. The convergence proof is about the framework, so every app gets it, not just Sky.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Menus&lt;/strong&gt; on controls and on the secondary click, and an app's own items in the menu bar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;scrollTo&lt;/code&gt;, the Dock badge and spoken announcements&lt;/strong&gt;, as effects the host runs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A server with a world of its own in &lt;code&gt;cairn fuzz&lt;/code&gt;.&lt;/strong&gt; It keeps the likes and follows, loses
replies after acting on them, and lets the fuzzer check claims against what it holds.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;cairn_atproto&lt;/code&gt; 0.3.0&lt;/strong&gt;: record keys, idempotent writes, the authoritative read, viewer state
and notifications.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What it looks like&lt;/h2&gt;
&lt;p&gt;When the server refuses a like, Sky rolls it back, says so in a line and announces it to
VoiceOver. Muting an author takes their posts out of the feed at once without moving the post
you're reading, and the status line offers Undo. Notifications are grouped (&amp;quot;Alice, Bob and 3
others liked your post&amp;quot;), counted on the tab and the Dock, and marked seen once they're on
screen.&lt;/p&gt;
&lt;h2&gt;The catch&lt;/h2&gt;
&lt;p&gt;The proof is about a model of the server: one register per key, with idempotent writes.
Bluesky's real servers lag, so a count other people are changing can be briefly behind; the
count claim holds when no one else has acted since the last view. Posting, replies and quotes
are the next milestone.&lt;/p&gt;&lt;p&gt;Progress so far:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/#try-it-back-in-the-browser&quot;&gt;Try it, back in the browser&lt;/a&gt; (2026-10-26)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/#sky-the-notifications-list&quot;&gt;Sky: the notifications list&lt;/a&gt; (2026-10-26)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/#sky-notifications-on-screen&quot;&gt;Sky: notifications on screen&lt;/a&gt; (2026-10-26)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/#cairn-0-5-2&quot;&gt;Cairn 0.5.2&lt;/a&gt; (2026-10-26)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/#sky-muting-an-author-mid-feed&quot;&gt;Sky: muting an author mid-feed&lt;/a&gt; (2026-10-26)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/#sky-like-and-repost-one-refused&quot;&gt;Sky: Like and Repost, one refused&lt;/a&gt; (2026-10-26)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/#cairn-0-5-1&quot;&gt;Cairn 0.5.1&lt;/a&gt; (2026-10-26)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/#sky-like-repost-follow-and-mute&quot;&gt;Sky: like, repost, follow and mute&lt;/a&gt; (2026-10-26)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/#sky-notifications-grouped&quot;&gt;Sky: notifications, grouped&lt;/a&gt; (2026-10-26)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/#sky-unread-notifications&quot;&gt;Sky: unread notifications&lt;/a&gt; (2026-10-26)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/#cairn-0-5-0-and-cairn-atproto-0-3-0-early-in-m5&quot;&gt;Cairn 0.5.0 and cairn_atproto 0.3.0, early in M5&lt;/a&gt; (2026-10-26)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/likes-that-end-up-true/#likes-that-roll-back&quot;&gt;Likes that roll back&lt;/a&gt; (2026-10-26)&lt;/li&gt;&lt;/ul&gt;</description>
</item>
<item>
<title>Sky 0.3.1, signed and installed</title>
<link>https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#sky-0-3-1-signed-and-installed</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#sky-0-3-1-signed-and-installed</guid>
<pubDate>Mon, 26 Oct 2026 07:22:04 GMT</pubDate>
<description>&lt;p&gt;Sky 0.3.1 is on the public registry as iamnbutler/sky, built on Cairn 0.4.1 with cairn_json and cairn_atproto, all from the registry, and so is its CI in iamnbutler/sky. Its claims export, 26 active claims, is on the registry for its claim pages.&lt;/p&gt;&lt;p&gt;cairn bundle --sign made Sky.app, signed with a Developer ID under the hardened runtime, and it is installed in ~/Applications. Signing in with a real account is the next step, by a person, not by a test: every test signs in to the fake server.&lt;/p&gt;&lt;p&gt;As of commit sky@bd64e76, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/&quot;&gt;A feed that never ends&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Cairn 0.4.1: the trusted base for M4, fuzzed</title>
<link>https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#cairn-0-4-1-the-trusted-base-for-m4-fuzzed</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#cairn-0-4-1-the-trusted-base-for-m4-fuzzed</guid>
<pubDate>Mon, 26 Oct 2026 07:05:04 GMT</pubDate>
<description>&lt;p&gt;Cairn 0.4.1 is out on the public registry as iamnbutler/cairn. Every Keychain call now goes through one serial queue for the process, so a forgetSecret racing a read of the same app's items no longer deadlocks inside Security's file-based keychain (#237), and a value that starts with a byte order mark reads back with it.&lt;/p&gt;&lt;p&gt;What VoiceOver and other assistive apps see of a scrolled list or a secure field was reviewed by hand; it is now two assume claims with a fuzz check each. ^1021: the accessibility tree holds exactly what a scroll area shows, walked through AppKit's own accessibility API on a real view. ^1022: a secure field shows nothing typed, and the debug socket's request log shows no secret. docs/trusted.md lists every assumption M4 added with its check; the full trusted fuzz run passes all 54 checks.&lt;/p&gt;&lt;p&gt;The registry's ledger check accepted 0.4.0 to 0.4.1, a patch release: two new claims and nothing retired. It serves the claims export, 54 active claims, for the claim pages.&lt;/p&gt;&lt;p&gt;As of commit a6a35fb, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/&quot;&gt;A feed that never ends&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Sky: refresh keeps the reader</title>
<link>https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#sky-refresh-keeps-the-reader</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/images/86036cc2ce33030e.png</guid>
<pubDate>Mon, 26 Oct 2026 01:39:56 GMT</pubDate>
<description>&lt;figure&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/images/86036cc2ce33030e.png&quot; width=&quot;1200&quot; height=&quot;1640&quot; alt=&quot;Sky: refresh keeps the reader&quot;&gt;&lt;figcaption&gt;Three posts arrived and Refresh put them above: the post at the top of the view didn't move, and Sky offers ↑ 3 new posts to go back to the top. Offscreen render, fake PDS.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;A screenshot at commit 96355c0, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/&quot;&gt;A feed that never ends&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Sky: scrolled to the end</title>
<link>https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#sky-scrolled-to-the-end</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/images/3b7463082057dda1.png</guid>
<pubDate>Mon, 26 Oct 2026 01:39:54 GMT</pubDate>
<description>&lt;figure&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/images/3b7463082057dda1.png&quot; width=&quot;1200&quot; height=&quot;1640&quot; alt=&quot;Sky: scrolled to the end&quot;&gt;&lt;figcaption&gt;Scrolling to the end shows the list's end row; its appear loads the next page (30 → 43 posts). Reposts say who reposted; link cards are words for now. Offscreen render, fake PDS.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;A screenshot at commit 96355c0, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/&quot;&gt;A feed that never ends&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Sky: the home feed</title>
<link>https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#sky-the-home-feed</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/images/3615ff776ae0a502.png</guid>
<pubDate>Mon, 26 Oct 2026 01:39:52 GMT</pubDate>
<description>&lt;figure&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/images/3615ff776ae0a502.png&quot; width=&quot;1200&quot; height=&quot;1640&quot; alt=&quot;Sky: the home feed&quot;&gt;&lt;figcaption&gt;Posts in Cairn's virtual list: avatars and images load from the fake PDS into fixed-size placeholders, text wraps, counts under each post. Offscreen render, fake PDS.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;A screenshot at commit 96355c0, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/&quot;&gt;A feed that never ends&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Cairn 0.4.0: runtime scrolling, Action.appear, virtual lists and package-scoped fuzzing</title>
<link>https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#cairn-0-4-0-runtime-scrolling-action-appear-virtual-lists-and-package-scoped-fuzzing</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#cairn-0-4-0-runtime-scrolling-action-appear-virtual-lists-and-package-scoped-fuzzing</guid>
<pubDate>Mon, 26 Oct 2026 00:19:46 GMT</pubDate>
<description>&lt;p&gt;Cairn 0.4.0 is out on the public registry as iamnbutler/cairn. Scroll offsets belong to the runtime: a box names itself with Style.scrollRef, the runtime keeps its offset in UI.scroll, moves it for the wheel, trackpad and scroll keys, and clamps it after every step, and a scroll never reaches update (#225). A control with appear := true is told when it comes on screen, through Action.appear. The host's scroll sign is a new assume claim, ^1012.&lt;/p&gt;&lt;p&gt;VList.node lays out only the rows of a list that meet its box, so a runtime step costs about the same at 1,000 rows as at 100,000, with proofs that the rows built are exactly those in view and that the first row showing stays still when rows are inserted above it (#231). Fuzz targets are scoped by package: cairn fuzz runs only a package's own machines unless given --deps, and two targets with one name fail to compile (#224).&lt;/p&gt;&lt;p&gt;The registry's ledger check accepted 0.3.0 to 0.4.0: the only change to the ledger is ^1012. It is a minor release because Event, Action and Trigger gain constructors, so exhaustive matches need new cases; Style and UI gain defaulted fields, and settleFuel is 4. The registry checked the claims export, 54 claims, against the ledger and serves it for the claim pages.&lt;/p&gt;&lt;p&gt;As of commit f107007, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/&quot;&gt;A feed that never ends&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Sky: a slow sign-in</title>
<link>https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#sky-a-slow-sign-in</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/images/990f98ad7fd212e4.png</guid>
<pubDate>Sun, 25 Oct 2026 23:44:51 GMT</pubDate>
<description>&lt;figure&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/images/990f98ad7fd212e4.png&quot; width=&quot;920&quot; height=&quot;1120&quot; alt=&quot;Sky: a slow sign-in&quot;&gt;&lt;figcaption&gt;After a few seconds Sky says it's still signing in and offers Cancel. Offscreen render, fake PDS.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;A screenshot at commit 6f0aa14, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/&quot;&gt;A feed that never ends&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Sky: offline</title>
<link>https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#sky-offline</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/images/e6c13baba13e7bfe.png</guid>
<pubDate>Sun, 25 Oct 2026 23:44:49 GMT</pubDate>
<description>&lt;figure&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/images/e6c13baba13e7bfe.png&quot; width=&quot;920&quot; height=&quot;1120&quot; alt=&quot;Sky: offline&quot;&gt;&lt;figcaption&gt;Sign-in errors are words, not codes: here the service can't be reached. Offscreen render, fake PDS.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;A screenshot at commit 6f0aa14, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/&quot;&gt;A feed that never ends&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Sky: a wrong app password</title>
<link>https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#sky-a-wrong-app-password</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/images/e20419d09bd36cef.png</guid>
<pubDate>Sun, 25 Oct 2026 23:44:47 GMT</pubDate>
<description>&lt;figure&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/images/e20419d09bd36cef.png&quot; width=&quot;920&quot; height=&quot;1120&quot; alt=&quot;Sky: a wrong app password&quot;&gt;&lt;figcaption&gt;A wrong password, said in words; the field is emptied as the request is sent. Offscreen render, fake PDS.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;A screenshot at commit 6f0aa14, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/&quot;&gt;A feed that never ends&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Sky: sign in</title>
<link>https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#sky-sign-in</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/images/e2f41e19d6b35be9.png</guid>
<pubDate>Sun, 25 Oct 2026 23:44:46 GMT</pubDate>
<description>&lt;figure&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/images/e2f41e19d6b35be9.png&quot; width=&quot;920&quot; height=&quot;1120&quot; alt=&quot;Sky: sign in&quot;&gt;&lt;figcaption&gt;Sky's sign-in screen (cairn#210): handle or email, the app password in a secure field (bullets only), the service. An offscreen render from the selftest against the fake PDS.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;A screenshot at commit 6f0aa14, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/&quot;&gt;A feed that never ends&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>The runtime scrolls the fixture's rows</title>
<link>https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#the-runtime-scrolls-the-fixture-s-rows</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/images/a2ff42128e730c36.png</guid>
<pubDate>Sun, 25 Oct 2026 22:51:11 GMT</pubDate>
<description>&lt;figure&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/images/a2ff42128e730c36.png&quot; width=&quot;960&quot; height=&quot;1200&quot; alt=&quot;The runtime scrolls the fixture's rows&quot;&gt;&lt;figcaption&gt;Cairn's runtime now owns scroll offsets (#203). A scroll of 100,000 points over the fixture's 200 rows is clamped to the end, the app's state never sees it, and the end line, marked appear, hears that it came on screen: an Action.appear move counted in the app. Offscreen render through the real engine (Fixture --scroll-shots).&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;A screenshot at commit bed3b86, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/&quot;&gt;A feed that never ends&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Cairn 0.3.0: the example apps leave, Keychain secrets, secure fields and scroll boxes</title>
<link>https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#cairn-0-3-0-the-example-apps-leave-keychain-secrets-secure-fields-and-scroll-boxes</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#cairn-0-3-0-the-example-apps-leave-keychain-secrets-secure-fields-and-scroll-boxes</guid>
<pubDate>Sun, 25 Oct 2026 22:40:44 GMT</pubDate>
<description>&lt;p&gt;Cairn 0.3.0 is out on the public registry as iamnbutler/cairn. The example apps (Counter, Basics, Todo, Clock, Tray, Gallery) live in their own package, cairn_examples, and their 71 claims keep their numbers there: cairn^202 is now cairn_examples^202, marked moved:cairn_examples in Cairn's ledger, which now holds only framework claims (#219).&lt;/p&gt;&lt;p&gt;A secret can live in the Keychain, and http can capture reply fields into it before Lean sees the body (#216). Text.SecureField shows bullets, never puts its text on the clipboard, in undo history or in any frame dump, and the host turns on secure event input while it has focus (#217). Style.scroll lays out scroll boxes, with proofs that what shows stays inside the box and a control is pressed only where it shows (#218). The frame format is version 11, and the 0.1.3 shims are gone.&lt;/p&gt;&lt;p&gt;The registry's ledger check accepted 0.2.5 to 0.3.0: moving and retiring released claims needs a new minor while at 0.x, and 0.2.6 would have been refused. It checked the claims export, 53 claims, against the ledger and serves it for the claim pages. Apps upgrade by naming an environment secret's source, matching http with eight arguments and importing Cairn.Abi for Cairn.Frame.Boundary.&lt;/p&gt;&lt;p&gt;As of commit 60c4483, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/&quot;&gt;A feed that never ends&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Secure text field</title>
<link>https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#secure-text-field</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/images/8e152cdadfa259a1.png</guid>
<pubDate>Sun, 25 Oct 2026 21:51:15 GMT</pubDate>
<description>&lt;figure&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/images/8e152cdadfa259a1.png&quot; width=&quot;960&quot; height=&quot;1200&quot; alt=&quot;Secure text field&quot;&gt;&lt;figcaption&gt;The fixture's Passcode field after typing seven characters: the frame holds seven bullets, never the text, and the host gives it the AXSecureTextField role with no value. An offscreen render of the frame (Renderer.image).&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;A screenshot at commit 1968755, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/&quot;&gt;A feed that never ends&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Yap on cairn_json</title>
<link>https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#yap-on-cairn-json</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/images/bc48859669cf1995.png</guid>
<pubDate>Sun, 25 Oct 2026 21:14:20 GMT</pubDate>
<description>&lt;figure&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/images/bc48859669cf1995.png&quot; width=&quot;1112&quot; height=&quot;428&quot; alt=&quot;Yap on cairn_json&quot;&gt;&lt;figcaption&gt;Yap's overlay mid-dictation, now built on Cairn 0.2.5 with JSON from the cairn_json package instead of its own parser. Yap 0.1.0 is on the registry.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;A screenshot at commit bdf391d, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/&quot;&gt;An app built from packages alone&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>A feed that never ends</title>
<link>https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#fourth-milestone-sky-reads-bluesky</guid>
<pubDate>Sun, 25 Oct 2026 20:57:29 GMT</pubDate>
<description>&lt;p&gt;Sky is the reading half of a Bluesky client, built on Cairn: sign in with an app password, keep
the session, and read the home feed for as long as you scroll. It was the fourth milestone and
the first of four on Bluesky, which needs nearly everything a small app doesn't: long lists,
authentication, paging and requests that race.&lt;/p&gt;
&lt;p&gt;Sky lives in its own repository, &lt;a href=&quot;https://github.com/iamnbutler/sky&quot;&gt;iamnbutler/sky&lt;/a&gt;, as the
package &lt;code&gt;sky&lt;/code&gt;, built only from registry packages. Its &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/packages/sky/claims/&quot;&gt;claims are on the
registry&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;What Cairn gained&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scrolling owned by the runtime.&lt;/strong&gt; Every scroll offset belongs to Cairn, not the app, and is
clamped after every step. A virtual list builds and lays out only the rows on screen: a step
costs about 0.17 ms at 1,000 rows and at 100,000.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secrets.&lt;/strong&gt; The host captures the session's tokens from the server's replies straight into
the Keychain, so they never reach Lean. The password goes in a secure field that shows
bullets, keeps no undo and copies nothing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;cairn_atproto&lt;/code&gt;&lt;/strong&gt;, a package for XRPC, the session calls and the timeline, with proofs that
only one token refresh runs at a time and each call is sent at most twice.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What's proved about the feed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;No duplicates and no gaps.&lt;/li&gt;
&lt;li&gt;A late page for an old cursor never changes the feed.&lt;/li&gt;
&lt;li&gt;A refresh racing a load-more ends as one of the two would have, in order.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Refresh puts new posts above without moving the post you're reading, and offers a way back to
the top. Flinging through 1,102 posts with images, Sky's Lean thread takes a median of 1.5 to
1.7 ms a frame.&lt;/p&gt;
&lt;h2&gt;The catch&lt;/h2&gt;
&lt;p&gt;Everything the host is trusted with, from the Keychain to what VoiceOver sees of a scrolled
list, is an assumption with a fuzz check of its own rather than a proof. Every path runs against
a fake server with no account and no network; reading a real timeline is something I do by hand
with the installed app.&lt;/p&gt;&lt;p&gt;Progress so far:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#sky-0-3-1-signed-and-installed&quot;&gt;Sky 0.3.1, signed and installed&lt;/a&gt; (2026-10-26)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#cairn-0-4-1-the-trusted-base-for-m4-fuzzed&quot;&gt;Cairn 0.4.1: the trusted base for M4, fuzzed&lt;/a&gt; (2026-10-26)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#sky-refresh-keeps-the-reader&quot;&gt;Sky: refresh keeps the reader&lt;/a&gt; (2026-10-25)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#sky-scrolled-to-the-end&quot;&gt;Sky: scrolled to the end&lt;/a&gt; (2026-10-25)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#sky-the-home-feed&quot;&gt;Sky: the home feed&lt;/a&gt; (2026-10-25)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#cairn-0-4-0-runtime-scrolling-action-appear-virtual-lists-and-package-scoped-fuzzing&quot;&gt;Cairn 0.4.0: runtime scrolling, Action.appear, virtual lists and package-scoped fuzzing&lt;/a&gt; (2026-10-25)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#sky-a-slow-sign-in&quot;&gt;Sky: a slow sign-in&lt;/a&gt; (2026-10-25)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#sky-offline&quot;&gt;Sky: offline&lt;/a&gt; (2026-10-25)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#sky-a-wrong-app-password&quot;&gt;Sky: a wrong app password&lt;/a&gt; (2026-10-25)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#sky-sign-in&quot;&gt;Sky: sign in&lt;/a&gt; (2026-10-25)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#the-runtime-scrolls-the-fixture-s-rows&quot;&gt;The runtime scrolls the fixture's rows&lt;/a&gt; (2026-10-25)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#cairn-0-3-0-the-example-apps-leave-keychain-secrets-secure-fields-and-scroll-boxes&quot;&gt;Cairn 0.3.0: the example apps leave, Keychain secrets, secure fields and scroll boxes&lt;/a&gt; (2026-10-25)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-feed-that-never-ends/#secure-text-field&quot;&gt;Secure text field&lt;/a&gt; (2026-10-25)&lt;/li&gt;&lt;/ul&gt;</description>
</item>
<item>
<title>Cairn 0.2.5: closing the window quits cleanly</title>
<link>https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#cairn-0-2-5-closing-the-window-quits-cleanly</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#cairn-0-2-5-closing-the-window-quits-cleanly</guid>
<pubDate>Sun, 25 Oct 2026 12:14:23 GMT</pubDate>
<description>&lt;p&gt;Cairn 0.2.5 is out on the public registry as iamnbutler/cairn. Closing an app's window now quits it cleanly: the window delegate owns the window and shuts the view down before the app terminates. Before, AppKit also released the window on close, and a frame landing afterwards reached the freed window; Pairs 0.1.2 crashed that way. make selftest closes a real window with input, a timer and frames in flight (#199).&lt;/p&gt;&lt;p&gt;cairn builds fetch registry dependencies at the commit the manifest pins and turn Lake's lazy fetches off when every git dependency is on a registry: a stale checkout on Pairs went from 92 s to 4 s (#198). The tray's tick-rate selftest asserts the wall-clock rate on real hardware only (#195).&lt;/p&gt;&lt;p&gt;The registry checked its claims export, 122 claims, against the ledger and serves it for the claim pages. claims.lock is the same as 0.2.4's: no claim was retired, moved, changed or added, so it is a patch release.&lt;/p&gt;&lt;p&gt;As of commit ea05c5e, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/&quot;&gt;An app built from packages alone&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Cairn 0.2.4, with a fuzzer that plays the host</title>
<link>https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#cairn-0-2-4-with-a-fuzzer-that-plays-the-host</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#cairn-0-2-4-with-a-fuzzer-that-plays-the-host</guid>
<pubDate>Sun, 25 Oct 2026 08:41:47 GMT</pubDate>
<description>&lt;p&gt;Cairn 0.2.4 is out on the public registry as iamnbutler/cairn. cairn fuzz now plays the host's side: it answers requests late and in any order, replies to cancelled ones, fires timers, moves the clock, sends subscription events and evicts images, within what the trusted claims allow, and checks that every image a frame draws is one the host holds (#189).&lt;/p&gt;&lt;p&gt;An app's Custom fuzz hooks take replies and subEvents generators, relations checked on every step, drained checks once the host has answered everything, and outstanding checks against the requests the runtime still holds (#189, #192).&lt;/p&gt;&lt;p&gt;The registry checked its claims export, 122 claims, against the ledger and serves it for the claim pages. claims.lock is the same as 0.2.3's: no claim was retired, moved, changed or added, so it is a patch release.&lt;/p&gt;&lt;p&gt;As of commit 654c669, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/&quot;&gt;An app built from packages alone&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Cairn 0.2.3, with several apps a package and cairn bundle</title>
<link>https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#cairn-0-2-3-with-several-apps-a-package-and-cairn-bundle</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#cairn-0-2-3-with-several-apps-a-package-and-cairn-bundle</guid>
<pubDate>Sun, 25 Oct 2026 07:30:22 GMT</pubDate>
<description>&lt;p&gt;Cairn 0.2.3 is out on the public registry as iamnbutler/cairn. A package can hold several apps, and cairn build, size, bench and conformance take --app to link one (#177), as iamnbutler/cairn_examples does. cairn bundle wraps an app package's release build in a .app with the SwiftPM resource bundles it uses (#185).&lt;/p&gt;&lt;p&gt;The fuzzer now presses status-item buttons and menu items and takes App.minSize as the window's minimum, so Clock, Tray and Gallery are fuzzed (#178, #182). The click assumptions ^7 and ^214 moved from the examples into Cairn's trusted base, with pointer fuzz checks (#184).&lt;/p&gt;&lt;p&gt;The registry checked its claims export, 122 claims, against the ledger and serves it for the claim pages. claims.lock is the same as 0.2.2's: no claim was retired, moved, changed or added, so it is a patch release.&lt;/p&gt;&lt;p&gt;As of commit 8ecf96e, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/&quot;&gt;An app built from packages alone&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Pairs: the win screen</title>
<link>https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#pairs-the-win-screen</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/images/ce23b28c3df48597.png</guid>
<pubDate>Sun, 25 Oct 2026 07:08:54 GMT</pubDate>
<description>&lt;figure&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/images/ce23b28c3df48597.png&quot; width=&quot;1120&quot; height=&quot;1280&quot; alt=&quot;Pairs: the win screen&quot;&gt;&lt;figcaption&gt;All eight pairs of Patterns matched: the board gives way to the result, and the best moves and time for the pack are saved (they only ever improve, pairs^26).&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;A screenshot at commit e3d4416, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/&quot;&gt;An app built from packages alone&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Pairs: a pack that fails</title>
<link>https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#pairs-a-pack-that-fails</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/images/9b697b4fa1908202.png</guid>
<pubDate>Sun, 25 Oct 2026 07:08:52 GMT</pubDate>
<description>&lt;figure&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/images/9b697b4fa1908202.png&quot; width=&quot;1120&quot; height=&quot;1280&quot; alt=&quot;Pairs: a pack that fails&quot;&gt;&lt;figcaption&gt;A pack with a missing image: Pairs says which face didn't load and leaves the game exactly as it was. A 404 manifest and a pack.json that isn't JSON are refused the same way.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;A screenshot at commit e3d4416, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/&quot;&gt;An app built from packages alone&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Pairs: a pack loading over http</title>
<link>https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#pairs-a-pack-loading-over-http</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/images/3a1f184c6101e886.png</guid>
<pubDate>Sun, 25 Oct 2026 07:08:50 GMT</pubDate>
<description>&lt;figure&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/images/3a1f184c6101e886.png&quot; width=&quot;1120&quot; height=&quot;1280&quot; alt=&quot;Pairs: a pack loading over http&quot;&gt;&lt;figcaption&gt;A pack typed into the field loads beside the game from the URL tests' stub server, whose images answer after three seconds: the game goes on, with Cancel and a count of the images loaded. Choosing another pack or Cancel stops it; its late replies change nothing (pairs^28, ^29).&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;A screenshot at commit e3d4416, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/&quot;&gt;An app built from packages alone&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Pairs: the grid follows the window</title>
<link>https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#pairs-the-grid-follows-the-window</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/images/32893dece0647bb6.png</guid>
<pubDate>Sun, 25 Oct 2026 07:08:48 GMT</pubDate>
<description>&lt;figure&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/images/32893dece0647bb6.png&quot; width=&quot;1840&quot; height=&quot;1280&quot; alt=&quot;Pairs: the grid follows the window&quot;&gt;&lt;figcaption&gt;The same game in a wider, shorter window: Grid keeps four columns and shrinks the cards to fit the height; nothing scrolls. App.minSize from Grid.minBox keeps every card at least 64 points wide.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;A screenshot at commit e3d4416, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/&quot;&gt;An app built from packages alone&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Pairs: a game in progress</title>
<link>https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#pairs-a-game-in-progress</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/images/f4be29e0a16b0cc3.png</guid>
<pubDate>Sun, 25 Oct 2026 07:08:46 GMT</pubDate>
<description>&lt;figure&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/images/f4be29e0a16b0cc3.png&quot; width=&quot;1120&quot; height=&quot;1280&quot; alt=&quot;Pairs: a game in progress&quot;&gt;&lt;figcaption&gt;Pairs, the M3 memory game, built only from registry packages (cairn 0.2.2, cairn_json). Three pairs of Shapes are matched (green edges); the arrow and the heart don't match and turn back after a second. Rendered offscreen from the app's own frame.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;A screenshot at commit e3d4416, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/&quot;&gt;An app built from packages alone&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Cairn 0.2.2, with a window minimum and an image budget</title>
<link>https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#cairn-0-2-2-with-a-window-minimum-and-an-image-budget</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#cairn-0-2-2-with-a-window-minimum-and-an-image-budget</guid>
<pubDate>Sun, 25 Oct 2026 05:38:57 GMT</pubDate>
<description>&lt;p&gt;Cairn 0.2.2 is out on the public registry as iamnbutler/cairn. An app declares its window's smallest size with App.minSize, and Grid.minBox gives the smallest box in which every card is at least its minimum width, proved for every size; the host keeps the window at least that big (#169, frame format 7). Sub.imageCache keeps the host's images within a byte budget, and the host tells the app with an evicted notice for every image it drops, so ready always means the pixels are there (#170, frame format 8).&lt;/p&gt;&lt;p&gt;The registry checked its claims export, 122 claims, against the ledger and serves it for the claim pages. No claim was retired, moved or changed, so it is a patch release; five are new, ^511–^513, ^1010 and ^1011.&lt;/p&gt;&lt;p&gt;As of commit 885b585, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/&quot;&gt;An app built from packages alone&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Image cache under a budget</title>
<link>https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#image-cache-under-a-budget</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/videos/44efac4eb00e6da0.mp4</guid>
<pubDate>Sun, 25 Oct 2026 04:52:42 GMT</pubDate>
<description>&lt;figure&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/44efac4eb00e6da0.mp4&quot;&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/videos/44efac4eb00e6da0.jpg&quot; width=&quot;2560&quot; height=&quot;1584&quot; alt=&quot;Image cache under a budget&quot;&gt;&lt;/a&gt;&lt;figcaption&gt;Gallery runs an imageCache subscription with an 8 MiB budget. Load 24 fetches all 24 thumbnails; the host keeps the twelve on screen and drops the other page's, telling the app, which counts them as evicted and no longer takes them for loaded. Next page loads the dropped ones again, and the host drops the first page's in turn.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/44efac4eb00e6da0.mp4&quot;&gt;Watch the video&lt;/a&gt; (25 s), recorded at commit ecdc04f, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/&quot;&gt;An app built from packages alone&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Cairn 0.2.1, with claim pages</title>
<link>https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#cairn-0-2-1-with-claim-pages</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#cairn-0-2-1-with-claim-pages</guid>
<pubDate>Sun, 25 Oct 2026 04:41:35 GMT</pubDate>
<description>&lt;p&gt;Cairn 0.2.1 is out on the public registry as iamnbutler/cairn. Requests in flight are capped at 6, with a queue and cancelRequest, frame format 6 (#151). Cairn.UI.Grid lays cards out in as many columns as the window fits, proved for every size (#160). cairn fuzz, claims, bench, size and conformance run on any app, and test/fixture is Cairn's own fixture app (#149).&lt;/p&gt;&lt;p&gt;cairn publish now sends the claims export as well as claims.lock. The registry checked its 117 claims against the ledger and serves it, so Cairn's claim pages group its moves and flows by place instead of showing only ledger rows (#156). No claim was retired, moved or changed, so it is a patch release; six are new, ^506–^510 and ^1009.&lt;/p&gt;&lt;p&gt;As of commit d08381d, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/&quot;&gt;An app built from packages alone&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Try it: TodoMVC in the browser</title>
<link>https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/#try-it-todomvc-in-the-browser</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/videos/b4346f892137dc00.mp4</guid>
<pubDate>Sun, 25 Oct 2026 04:30:29 GMT</pubDate>
<description>&lt;figure&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/b4346f892137dc00.mp4&quot;&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/videos/b4346f892137dc00.jpg&quot; width=&quot;1920&quot; height=&quot;1200&quot; alt=&quot;Try it: TodoMVC in the browser&quot;&gt;&lt;/a&gt;&lt;figcaption&gt;Every frame is one the app's Lean code drew, explored ahead of time; each control shows its claim id, its reading and a link to its claim page.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/b4346f892137dc00.mp4&quot;&gt;Watch the video&lt;/a&gt; (23 s), recorded at commit a9813e5, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/&quot;&gt;Claims on an app I use every day&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>A floor on tile size</title>
<link>https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#a-floor-on-tile-size</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/videos/5c8ee160005c3fbc.mp4</guid>
<pubDate>Sun, 25 Oct 2026 04:21:06 GMT</pubDate>
<description>&lt;figure&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/5c8ee160005c3fbc.mp4&quot;&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/videos/5c8ee160005c3fbc.jpg&quot; width=&quot;2560&quot; height=&quot;1584&quot; alt=&quot;A floor on tile size&quot;&gt;&lt;/a&gt;&lt;figcaption&gt;Gallery asks the host to keep its window at least 1020 × 536: the smallest size, proved with Grid.minBox, at which every tile is 180 points wide. The demo drags the window narrower, shorter, then both, through the window's resize delegate as a person's drag goes; it stops at the minimum each time and the tiles stay 180 points wide.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/5c8ee160005c3fbc.mp4&quot;&gt;Watch the video&lt;/a&gt; (22 s), recorded at commit f6e6ad2, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/&quot;&gt;An app built from packages alone&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Gallery's grid follows the window</title>
<link>https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#gallery-s-grid-follows-the-window</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/videos/f059cdc41ffb2079.mp4</guid>
<pubDate>Sun, 25 Oct 2026 03:38:17 GMT</pubDate>
<description>&lt;figure&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/f059cdc41ffb2079.mp4&quot;&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/videos/f059cdc41ffb2079.jpg&quot; width=&quot;3280&quot; height=&quot;1464&quot; alt=&quot;Gallery's grid follows the window&quot;&gt;&lt;/a&gt;&lt;figcaption&gt;Gallery's six tiles in a grid whose column count Lean computes from the window size: three columns at 1120 points, six at 1640, two at 860, in reading order throughout. The host only reports resize events; Lean proves the cells never overlap, stay inside the grid's box, take the most columns that fit, and that a click in a tile reaches that tile.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/f059cdc41ffb2079.mp4&quot;&gt;Watch the video&lt;/a&gt; (15 s), recorded at commit ee9ecbf, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/&quot;&gt;An app built from packages alone&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Claim pages</title>
<link>https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#claim-pages</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/videos/d3e89acd24cf2229.mp4</guid>
<pubDate>Sun, 25 Oct 2026 03:25:50 GMT</pubDate>
<description>&lt;figure&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/d3e89acd24cf2229.mp4&quot;&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/videos/d3e89acd24cf2229.jpg&quot; width=&quot;1280&quot; height=&quot;900&quot; alt=&quot;Claim pages&quot;&gt;&lt;/a&gt;&lt;figcaption&gt;Every package on the registry gets claim pages, rendered by the site from the claims export cairn publish now sends: the overview grouped by place, then the Todos window, a flow's steps, a move, and one of the trusted base's assumptions. No agent and no screenshots of the apps: proofs, witnesses and where each claim is defined come straight from Lean. Shown here with Cairn's export from this branch; the live pages fill in once Cairn is published again. These replace the maps, and /map/ redirects here.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/d3e89acd24cf2229.mp4&quot;&gt;Watch the video&lt;/a&gt; (15 s), recorded at commit f45df9b, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/&quot;&gt;An app built from packages alone&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Many requests in flight, and Cancel</title>
<link>https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#many-requests-in-flight-and-cancel</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/videos/acae0dd6c7806322.mp4</guid>
<pubDate>Sun, 25 Oct 2026 02:41:38 GMT</pubDate>
<description>&lt;figure&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/acae0dd6c7806322.mp4&quot;&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/videos/acae0dd6c7806322.jpg&quot; width=&quot;2240&quot; height=&quot;1184&quot; alt=&quot;Many requests in flight, and Cancel&quot;&gt;&lt;/a&gt;&lt;figcaption&gt;Gallery asks for 24 thumbnails from a slow server. Lean's runtime sends six at a time and queues the rest; Cancel drops the queued ones and stops the six being fetched, and a late answer for a cancelled one changes nothing.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/acae0dd6c7806322.mp4&quot;&gt;Watch the video&lt;/a&gt; (23 s), recorded at commit ebac002, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/&quot;&gt;An app built from packages alone&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Cairn 0.2.0, with images</title>
<link>https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#cairn-0-2-0-with-images</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#cairn-0-2-0-with-images</guid>
<pubDate>Sun, 25 Oct 2026 02:05:01 GMT</pubDate>
<description>&lt;p&gt;Cairn 0.2.0 is out, on the public registry as iamnbutler/cairn next to 0.1.3. It has images: Node.image, the loadImage effect, frame format 5 and the Gallery example (#132). import Cairn now links, the old everything-umbrella is CairnAll, and cairn new --bin makes a working app (#134). cairn-link links each dependency's libs from its cairn.toml, and Cairn's says libs = [&amp;quot;Cairn&amp;quot;] (#127).&lt;/p&gt;&lt;p&gt;It was going to be 0.1.4, and v0.1.4 is tagged, but the registry refused it: claim ^213's statement hash changed when Style gained its image field, and a published claim keeps its statement. Its meaning didn't change, so the same invariant is now ^225 and ^213 is retired. Retiring a released claim needs a new minor version while at 0.x, hence 0.2.0.&lt;/p&gt;&lt;p&gt;As of commit 16b70b8, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/&quot;&gt;An app built from packages alone&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>cairn new makes an app that links</title>
<link>https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#cairn-new-makes-an-app-that-links</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#cairn-new-makes-an-app-that-links</guid>
<pubDate>Sun, 25 Oct 2026 01:17:40 GMT</pubDate>
<description>&lt;p&gt;Since the library split, the Cairn umbrella imported every proof and tool, none of which an app links, so an app that wrote import Cairn, as cairn new's template did, failed at the host's link on initialize_cairn_Cairn (#128). Now Cairn is the linked library's umbrella, with the claim commands at compile time only, and the everything-umbrella the ledger, the audit and the docs read is CairnAll. cairn new --bin writes a working app: a place, a move, its exports, the C declaration of its initializer and a host with a selftest. make check now builds one against the checkout, runs its selftest and locks its two claims, which takes about a minute.&lt;/p&gt;&lt;p&gt;As of commit a2d3ff9, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/&quot;&gt;An app built from packages alone&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>cairn_json, the first package outside the cairn repository</title>
<link>https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#cairn-json-the-first-package-outside-the-cairn-repository</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#cairn-json-the-first-package-outside-the-cairn-repository</guid>
<pubDate>Sun, 25 Oct 2026 01:01:40 GMT</pubDate>
<description>&lt;p&gt;Yap's JSON parser and encoder is now its own registry package, cairn_json 0.1.0, in iamnbutler/cairn_json. It requires cairn ^0.1.2 from the registry, is pure Lean with no Swift and no extern, and leaves Yap's date arithmetic for a later cairn_date.&lt;/p&gt;&lt;p&gt;The library is 491 lines. 793 lines of proofs hold 49 theorems: decode (encode v) = some v and decode (pretty v) = some v for every value nested at most 512 deep, and that more fuel never changes what the parser answers. decode is total by construction, with no partial def. The axiom audit allows only propext, Classical.choice and Quot.sound, and the kernel replays every declaration.&lt;/p&gt;&lt;p&gt;A trusted fuzz check compares it with Foundation's JSONSerialization: 220,164 documents per seed (valid, mutated, UTF-16 and UTF-32, random bytes and 164 edge cases), 5 seeds, 0 unexplained differences. The known ones are listed in its docs/trusted.md: numbers Foundation can't hold, a leading U+FEFF Foundation drops from strings, short UTF-16 and UTF-32 documents without a byte order mark, invalid UTF-8, and nesting 513 deep.&lt;/p&gt;&lt;p&gt;The commit is cairn_json's.&lt;/p&gt;&lt;p&gt;As of commit bf6a0e7, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/&quot;&gt;An app built from packages alone&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Gallery: images</title>
<link>https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#gallery-images</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/videos/e7538d8c5c746b46.mp4</guid>
<pubDate>Sun, 25 Oct 2026 01:01:25 GMT</pubDate>
<description>&lt;figure&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/e7538d8c5c746b46.mp4&quot;&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/videos/e7538d8c5c746b46.jpg&quot; width=&quot;1440&quot; height=&quot;1184&quot; alt=&quot;Gallery: images&quot;&gt;&lt;/a&gt;&lt;figcaption&gt;Six image tiles, each a 200 × 150 Node.image that Lean lays out before any pixels exist: a PNG, a JPEG turned by its EXIF orientation, a GIF, a poster over https, a text file and a missing file. The host decodes with ImageIO and paints with Metal; Lean only learns the pixel size, or the fault, from the loadImage reply. Reload (^501) sends every tile back to its grey placeholder and reads each source again; Fit (^502) cycles cover, contain and stretch. Nothing moves when a picture arrives or fails. In the self test every tile was answered 6.8 ms after launch.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/e7538d8c5c746b46.mp4&quot;&gt;Watch the video&lt;/a&gt; (31 s), recorded at commit 8254f50, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/&quot;&gt;An app built from packages alone&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>cairn-link links each dependency's libraries</title>
<link>https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#cairn-link-links-each-dependency-s-libraries</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#cairn-link-links-each-dependency-s-libraries</guid>
<pubDate>Sun, 25 Oct 2026 00:53:44 GMT</pubDate>
<description>&lt;p&gt;bin/cairn-link no longer links Cairn's library by name (#118). It reads lake-manifest.json, finds the cairn.toml beside each dependency, transitive ones included, and links the Lean libraries that package lists under libs. Cairn's own cairn.toml now lists Cairn, so an app gets the same archive as before, and a library like cairn_json can be linked the same way.&lt;/p&gt;&lt;p&gt;It refuses a Lean module that ends up in two archives, which would be a duplicate symbol, and a dependency that is an app. A dependency with no cairn.toml is linked not at all; if linked code imports it, cairn-link names the package and says to give it one.&lt;/p&gt;&lt;p&gt;Checked with a throwaway app that depends on Cairn and on a second library package with its own libs: it links, and a small C program draws a frame through both. Yap builds against it with its existing --lib Yap and passes its selftest.&lt;/p&gt;&lt;p&gt;As of commit 1e1b640, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/&quot;&gt;An app built from packages alone&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>An app built from packages alone</title>
<link>https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#third-milestone-pairs</guid>
<pubDate>Sun, 25 Oct 2026 00:43:00 GMT</pubDate>
<description>&lt;p&gt;Pairs is a memory game: a grid of face-down cards, flip two, keep them if they match. Decks are
theme packs, a JSON manifest and a set of images, loaded from a folder or a URL. It was the
third milestone, small enough to finish, and it needs what a Bluesky client needs: images, JSON
and many requests in flight at once.&lt;/p&gt;
&lt;p&gt;It's also the first app built only through the package path, &lt;code&gt;cairn new pairs --bin&lt;/code&gt;,
&lt;code&gt;cairn add&lt;/code&gt; and &lt;code&gt;cairn build&lt;/code&gt;, in a repository of its own. Nothing in it reaches into Cairn's.&lt;/p&gt;
&lt;h2&gt;What Cairn gained&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Images.&lt;/strong&gt; An image node whose size Lean knows before the pixels arrive, so the layout never
jumps, and an effect that loads one. The host decodes and paints; Lean never holds pixels.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JSON as its own package&lt;/strong&gt;, &lt;code&gt;cairn_json&lt;/code&gt;, with a proof that decoding what was encoded gives it
back.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Requests in flight&lt;/strong&gt;: at most six at once, with the rest queued. Switching packs cancels
them, and a late reply for an old pack never changes the board.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What's proved&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The grid follows the window, and for every width no two cards overlap and every card is inside
it.&lt;/li&gt;
&lt;li&gt;The deal has every face exactly twice, at most two unmatched cards are face up, and every game
can be won.&lt;/li&gt;
&lt;li&gt;A best score only improves.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The catch&lt;/h2&gt;
&lt;p&gt;Decoding images, caching them and the network belong to the host: they're recorded as
assumptions and fuzzed, not proved. The grid always fits the window, so nothing in Pairs
scrolls; scrolling waited for Sky.&lt;/p&gt;&lt;p&gt;Progress so far:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#yap-on-cairn-json&quot;&gt;Yap on cairn_json&lt;/a&gt; (2026-10-25)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#cairn-0-2-5-closing-the-window-quits-cleanly&quot;&gt;Cairn 0.2.5: closing the window quits cleanly&lt;/a&gt; (2026-10-25)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#cairn-0-2-4-with-a-fuzzer-that-plays-the-host&quot;&gt;Cairn 0.2.4, with a fuzzer that plays the host&lt;/a&gt; (2026-10-25)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#cairn-0-2-3-with-several-apps-a-package-and-cairn-bundle&quot;&gt;Cairn 0.2.3, with several apps a package and cairn bundle&lt;/a&gt; (2026-10-25)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#pairs-the-win-screen&quot;&gt;Pairs: the win screen&lt;/a&gt; (2026-10-25)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#pairs-a-pack-that-fails&quot;&gt;Pairs: a pack that fails&lt;/a&gt; (2026-10-25)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#pairs-a-pack-loading-over-http&quot;&gt;Pairs: a pack loading over http&lt;/a&gt; (2026-10-25)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#pairs-the-grid-follows-the-window&quot;&gt;Pairs: the grid follows the window&lt;/a&gt; (2026-10-25)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#pairs-a-game-in-progress&quot;&gt;Pairs: a game in progress&lt;/a&gt; (2026-10-25)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#cairn-0-2-2-with-a-window-minimum-and-an-image-budget&quot;&gt;Cairn 0.2.2, with a window minimum and an image budget&lt;/a&gt; (2026-10-25)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#image-cache-under-a-budget&quot;&gt;Image cache under a budget&lt;/a&gt; (2026-10-25)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#cairn-0-2-1-with-claim-pages&quot;&gt;Cairn 0.2.1, with claim pages&lt;/a&gt; (2026-10-25)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#a-floor-on-tile-size&quot;&gt;A floor on tile size&lt;/a&gt; (2026-10-25)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#claim-pages&quot;&gt;Claim pages&lt;/a&gt; (2026-10-24)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#gallery-s-grid-follows-the-window&quot;&gt;Gallery's grid follows the window&lt;/a&gt; (2026-10-24)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#many-requests-in-flight-and-cancel&quot;&gt;Many requests in flight, and Cancel&lt;/a&gt; (2026-10-24)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#cairn-0-2-0-with-images&quot;&gt;Cairn 0.2.0, with images&lt;/a&gt; (2026-10-24)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#cairn-new-makes-an-app-that-links&quot;&gt;cairn new makes an app that links&lt;/a&gt; (2026-10-24)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#gallery-images&quot;&gt;Gallery: images&lt;/a&gt; (2026-10-24)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#cairn-json-the-first-package-outside-the-cairn-repository&quot;&gt;cairn_json, the first package outside the cairn repository&lt;/a&gt; (2026-10-24)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/an-app-from-packages-alone/#cairn-link-links-each-dependency-s-libraries&quot;&gt;cairn-link links each dependency's libraries&lt;/a&gt; (2026-10-24)&lt;/li&gt;&lt;/ul&gt;</description>
</item>
<item>
<title>Cairn 0.1.3, on the public registry</title>
<link>https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/#cairn-0-1-3-on-the-public-registry</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#cairn-0-1-3-on-the-public-registry</guid>
<pubDate>Sun, 25 Oct 2026 00:39:27 GMT</pubDate>
<description>&lt;p&gt;Cairn 0.1.3 is out, and it is on the public registry at cairn.registry.nexthub.sh as iamnbutler/cairn, next to 0.1.2. Reading it needs no login: cairn add cairn finds the owner by name and writes the version range, and Lake fetches Cairn from the registry's git (#107, #109). /packages/ now reads the registry itself instead of sample data (#111).&lt;/p&gt;&lt;p&gt;The Cairn library is now only what an app links: 31 modules. Proofs, claims, fuzzing and tools are their own Lake libraries, so libcairn_Cairn.a went from 3.67 to 2.78 MB (#114). Three modules moved so the directories in lean/ have no cycles, and make lint now fails on one; the old names stay as shims until 0.2.0 (#110).&lt;/p&gt;&lt;p&gt;No claim changed, so it is a patch release. Yap moved to it with only its tag changed.&lt;/p&gt;&lt;p&gt;As of commit aea142c, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/&quot;&gt;Claims on an app I use every day&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Cairn 0.1.2: libraries and apps</title>
<link>https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/#cairn-0-1-2-libraries-and-apps</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#cairn-0-1-2-libraries-and-apps</guid>
<pubDate>Sat, 24 Oct 2026 21:11:09 GMT</pubDate>
<description>&lt;p&gt;Cairn 0.1.2 is out. A package is now a library or an app, as in Cargo: cairn.toml has kind = &amp;quot;lib&amp;quot; or &amp;quot;bin&amp;quot;, cairn new asks which, and /packages/ lists apps under their own tab (#103). Nothing can depend on an app.&lt;/p&gt;&lt;p&gt;cairn publish sends no docs. HTML from a publisher would run as the registry's site, so docs will be built from the source instead, as docs.rs does (#98).&lt;/p&gt;&lt;p&gt;The ledger now treats retiring a released claim as a breaking change: it needs a new major version, or a new minor one while at 0.x (#100). No claim changed in 0.1.2, so it is a patch release.&lt;/p&gt;&lt;p&gt;As of commit 597dea8, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/&quot;&gt;Claims on an app I use every day&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Cairn is a package, and Yap moved out</title>
<link>https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/#cairn-is-a-package-and-yap-moved-out</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#cairn-is-a-package-and-yap-moved-out</guid>
<pubDate>Sat, 24 Oct 2026 19:35:06 GMT</pubDate>
<description>&lt;p&gt;Cairn is now a package. An app lives in its own repository and depends on a tagged release, and one tag pins both halves: the Lean package and the Swift libraries. v0.1.0 is the first release (#80). v0.1.1 fixes cairn doc, which failed in apps because it read its stylesheet from the app (#91).&lt;/p&gt;&lt;p&gt;Yap moved out first. It lives in its own repository, yap-cairn, and depends on Cairn v0.1.0 like any other app would. Its Lean, proofs, fuzzers, Swift host and benches left this repository with it.&lt;/p&gt;&lt;p&gt;Claim ids are now scoped by package. Cairn's are cairn^N and Yap's are yap^N, so “a shortcut never sends” is yap^311. Cairn's claims.lock still lists Yap's old ids, marked moved:yap, so they can never be issued here again.&lt;/p&gt;&lt;p&gt;As of commit ea72e75, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/&quot;&gt;Claims on an app I use every day&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>The cairn command and packages</title>
<link>https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/#the-cairn-command-and-packages</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#the-cairn-command-and-packages</guid>
<pubDate>Sat, 24 Oct 2026 19:29:01 GMT</pubDate>
<description>&lt;p&gt;There is now a cairn command (#79). cairn new starts a package in its own repository with its claim scope set, cairn add depends on another package, and cairn build runs Lake, links the Lean into one library and builds the Swift host. cairn doc writes the package's docs, and cairn publish and cairn login talk to a registry. It has unit tests, and it has been run against a stand-in registry but not a deployed one yet.&lt;/p&gt;&lt;p&gt;The registry is nextpm, a package registry, with a new cairn kind (nextpm #22). It has an index Lake reads natively, the source served over git, and a build cache, so that lake cache get downloads prebuilt Lean. Its claim ledger refuses a version that drops, renumbers or redefines an id. /packages/ shows a registry's packages, and until one is running it shows sample data (#78).&lt;/p&gt;&lt;p&gt;Publishers no longer upload docs. A registry now refuses HTML from a publisher (nextpm #23), and docs will be built from the source by a builder we run, the way docs.rs works (#94).&lt;/p&gt;&lt;p&gt;As of commit fb902c0, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/&quot;&gt;Claims on an app I use every day&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Developer docs and the API reference</title>
<link>https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/#developer-docs-and-the-api-reference</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#developer-docs-and-the-api-reference</guid>
<pubDate>Sat, 24 Oct 2026 18:36:08 GMT</pubDate>
<description>&lt;p&gt;Developer docs are up at /docs, laid out like Rust's bookshelf: the Cairn Book, Cairn by Example, reference pages, and guides to working on Cairn itself (#76). Every code sample is included from a source file that make check compiles, so a sample can't drift from the code it shows. Moving an included region fails the build until the page is fixed.&lt;/p&gt;&lt;p&gt;The API reference at /docs/api/ is generated by cairn doc, Cairn's answer to rustdoc (#77). It reads the Lean environment directly instead of using doc-gen4. On TodoMVC, doc-gen4 took 185 s and wrote 227 MB, almost all of it Lean's own library. Each claim appears on its module's page with its statement, evidence and proofs. A Lean block in a doc comment is a doc test that make check runs.&lt;/p&gt;&lt;p&gt;Every heading on the site also has an id now, and a # that copies a link to it (#72).&lt;/p&gt;&lt;p&gt;As of commit d04416c, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/&quot;&gt;Claims on an app I use every day&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Yap against the original under Instruments</title>
<link>https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/#yap-against-the-original-under-instruments</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#yap-against-the-original-under-instruments</guid>
<pubDate>Sat, 24 Oct 2026 13:14:51 GMT</pubDate>
<description>&lt;p&gt;Yap on Cairn was measured against the original Yap with Apple's Instruments rather than Cairn's own tools (#65). At 60 Hz the two overlays now pace the same, at 59.3 and 59.9 frames a second, with no hitches in either.&lt;/p&gt;&lt;p&gt;While recording, Cairn used 205 ms of CPU a second, against the original's 82. Most of that went to redrawing the menu bar's status item on every frame. Once its properties were written only when they change, an A/B run went from 197 to 98 ms/s.&lt;/p&gt;&lt;p&gt;The overlay now appears only once the microphone is running, as in the original, so a microphone that fails no longer flashes it (#68, yap^349). On a 120 Hz display, Cairn drew 118 frames a second where the original draws 60. It's now capped at 60, and it checks files every 2 s, as the original does (#69). The idle runs and a check on a 60 Hz display are still to do (#70).&lt;/p&gt;&lt;p&gt;As of commit 40b6863, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/&quot;&gt;Claims on an app I use every day&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Yap on Cairn: the overlay</title>
<link>https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/#yap-on-cairn-the-overlay</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/videos/796054c4982eb3e6.mp4</guid>
<pubDate>Sat, 24 Oct 2026 07:16:39 GMT</pubDate>
<description>&lt;figure&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/796054c4982eb3e6.mp4&quot;&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/videos/796054c4982eb3e6.jpg&quot; width=&quot;1112&quot; height=&quot;428&quot; alt=&quot;Yap on Cairn: the overlay&quot;&gt;&lt;/a&gt;&lt;figcaption&gt;Yap's dictation overlay on Cairn, driven by a synthetic voice: recording with a moving tape waveform and a two-line live preview, then transcribing, then typed. The state, view and layout are Lean; the waveform is a host-drawn paint node running the original's Metal shader.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/796054c4982eb3e6.mp4&quot;&gt;Watch the video&lt;/a&gt; (11 s), recorded at commit a83d447, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/&quot;&gt;Claims on an app I use every day&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Claims on an app I use every day</title>
<link>https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#second-milestone-yap-on-cairn</guid>
<pubDate>Sat, 24 Oct 2026 01:04:46 GMT</pubDate>
<description>&lt;p&gt;Yap is a small dictation app for the Mac: hold a key, talk, and the transcript is typed into the
app in front. I use it every day. TodoMVC lived in one window; almost all of Yap lives outside
its window, in a global key, the microphone, timers, a network request, files, sounds and typing
into another app. The second milestone asked whether claims hold up there.&lt;/p&gt;
&lt;p&gt;I rebuilt it on Cairn, signed it, installed it and used it for a day of real work.&lt;/p&gt;
&lt;h2&gt;What Cairn gained&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Effects.&lt;/strong&gt; The clock and timers, requests the host performs and answers, and outside inputs
like a global key and the microphone. Lean decides; Swift captures audio, hears the key, makes
the request and types the text, each because Lean asked.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;New surfaces&lt;/strong&gt;: a native menu bar menu built from Lean's view, and an overlay that never
takes focus.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Packages.&lt;/strong&gt; Moving Yap into its own repository made Cairn a package, with a &lt;code&gt;cairn&lt;/code&gt; command
and claim ids scoped by package. Yap's are &lt;code&gt;yap^N&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What's proved about dictation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;A key used in a shortcut never sends.&lt;/li&gt;
&lt;li&gt;A transcript is saved before it's typed.&lt;/li&gt;
&lt;li&gt;A late reply for an old clip never types.&lt;/li&gt;
&lt;li&gt;Every recording ends.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The catch&lt;/h2&gt;
&lt;p&gt;Each new effect is an assumption about the host, fuzzed with slow, failed, duplicated and
out-of-order replies, not proved. Audio and HTTP stay in Swift; Lean only decides what to do with
the results.&lt;/p&gt;&lt;p&gt;Progress so far:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/#try-it-todomvc-in-the-browser&quot;&gt;Try it: TodoMVC in the browser&lt;/a&gt; (2026-10-25)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/#cairn-0-1-3-on-the-public-registry&quot;&gt;Cairn 0.1.3, on the public registry&lt;/a&gt; (2026-10-24)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/#cairn-0-1-2-libraries-and-apps&quot;&gt;Cairn 0.1.2: libraries and apps&lt;/a&gt; (2026-10-24)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/#cairn-is-a-package-and-yap-moved-out&quot;&gt;Cairn is a package, and Yap moved out&lt;/a&gt; (2026-10-24)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/#the-cairn-command-and-packages&quot;&gt;The cairn command and packages&lt;/a&gt; (2026-10-24)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/#developer-docs-and-the-api-reference&quot;&gt;Developer docs and the API reference&lt;/a&gt; (2026-10-24)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/#yap-against-the-original-under-instruments&quot;&gt;Yap against the original under Instruments&lt;/a&gt; (2026-10-24)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/#yap-on-cairn-the-overlay&quot;&gt;Yap on Cairn: the overlay&lt;/a&gt; (2026-10-24)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/#clock-effects&quot;&gt;Clock: effects&lt;/a&gt; (2026-10-23)&lt;/li&gt;&lt;/ul&gt;</description>
</item>
<item>
<title>Clock: effects</title>
<link>https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/#clock-effects</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/videos/a79d52b1bab3288e.mp4</guid>
<pubDate>Fri, 23 Oct 2026 21:55:36 GMT</pubDate>
<description>&lt;figure&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/a79d52b1bab3288e.mp4&quot;&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/videos/a79d52b1bab3288e.jpg&quot; width=&quot;960&quot; height=&quot;1104&quot; alt=&quot;Clock: effects&quot;&gt;&lt;/a&gt;&lt;figcaption&gt;Cairn's first effects. A stopwatch whose time, ticks, sounds and lap file come from the host as replies to requests Lean makes: Lap plays a sound, Save writes the laps to a file, Reset clears them and Load reads them back. Timers fire within 1–2 ms; a click → read file → frame round trip is 1.5 ms (p50).&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/a79d52b1bab3288e.mp4&quot;&gt;Watch the video&lt;/a&gt; (16 s), recorded at commit 4fc3970, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/claims-on-an-app-i-use-every-day/&quot;&gt;Claims on an app I use every day&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>TodoMVC in Lean</title>
<link>https://cairn.iamnbutler.workers.dev/blog/a-proved-todomvc/#todomvc-in-lean</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/videos/5cf436dca2cd5034.mp4</guid>
<pubDate>Fri, 23 Oct 2026 17:16:53 GMT</pubDate>
<description>&lt;figure&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/5cf436dca2cd5034.mp4&quot;&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/videos/5cf436dca2cd5034.jpg&quot; width=&quot;1120&quot; height=&quot;840&quot; alt=&quot;TodoMVC in Lean&quot;&gt;&lt;/a&gt;&lt;figcaption&gt;The full TodoMVC, written in Lean and running natively on the Mac. Add, complete, double-click to edit, filter, clear completed, and toggle all. Every control is a claim (^202 to ^211). On the real app, 999 of 1,000 generated conformance sequences matched the Lean model. The one miss is a known edge case: a click that ends an edit can be dropped, as in a browser.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/5cf436dca2cd5034.mp4&quot;&gt;Watch the video&lt;/a&gt; (28 s), recorded at commit 9224599, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-proved-todomvc/&quot;&gt;A proved TodoMVC&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Basics: the first Lean app</title>
<link>https://cairn.iamnbutler.workers.dev/blog/a-proved-todomvc/#basics-the-first-lean-app</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/videos/c8a7c8d6ab57ac2f.mp4</guid>
<pubDate>Fri, 23 Oct 2026 15:57:47 GMT</pubDate>
<description>&lt;figure&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/c8a7c8d6ab57ac2f.mp4&quot;&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/videos/c8a7c8d6ab57ac2f.jpg&quot; width=&quot;960&quot; height=&quot;1264&quot; alt=&quot;Basics: the first Lean app&quot;&gt;&lt;/a&gt;&lt;figcaption&gt;A whole app written in Lean: adding, ticking and deleting items. Lean decides the layout, focus, text editing and hit testing; Swift only draws. Every control is a claim (^101 to ^104). Lean proves that an item's Delete button exists exactly when the item does, and that the 'items left' count is right.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/c8a7c8d6ab57ac2f.mp4&quot;&gt;Watch the video&lt;/a&gt; (17 s), recorded at commit 733224a, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-proved-todomvc/&quot;&gt;A proved TodoMVC&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>The host, with text</title>
<link>https://cairn.iamnbutler.workers.dev/blog/a-proved-todomvc/#the-host-with-text</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/videos/9cb67df1092eb561.mp4</guid>
<pubDate>Fri, 23 Oct 2026 15:18:26 GMT</pubDate>
<description>&lt;figure&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/9cb67df1092eb561.mp4&quot;&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/videos/9cb67df1092eb561.jpg&quot; width=&quot;1040&quot; height=&quot;904&quot; alt=&quot;The host, with text&quot;&gt;&lt;/a&gt;&lt;figcaption&gt;The new AppKit host painting through the Metal renderer: Core Text glyphs from an atlas, a caret, a focused field, a checkbox and a list. Typing goes through the Mac's text-input path, and every control in the accessibility tree is named by its claim id (^1 field, ^2 checkbox, ^3 Add, ^4#1 the new item). This app is the host's test fixture, with its frames built in Swift; the next video is the same host running a Lean app.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/9cb67df1092eb561.mp4&quot;&gt;Watch the video&lt;/a&gt; (18 s), recorded at commit 3ff02ad, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-proved-todomvc/&quot;&gt;A proved TodoMVC&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>Boundary spike</title>
<link>https://cairn.iamnbutler.workers.dev/blog/a-proved-todomvc/#boundary-spike</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/m/videos/4dff72519883d7d3.mp4</guid>
<pubDate>Fri, 23 Oct 2026 14:57:34 GMT</pubDate>
<description>&lt;figure&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/4dff72519883d7d3.mp4&quot;&gt;&lt;img src=&quot;https://cairn.iamnbutler.workers.dev/m/videos/4dff72519883d7d3.jpg&quot; width=&quot;960&quot; height=&quot;704&quot; alt=&quot;Boundary spike&quot;&gt;&lt;/a&gt;&lt;figcaption&gt;The first thing on screen. Lean owns the state, the layout and the hit test; Swift forwards clicks and paints Lean's rectangles with Metal. The bar is the count (no text rendering yet). Reset (red) exists only while the count is above zero, which is proved in Lean, and here it's pressed through the accessibility tree.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/m/videos/4dff72519883d7d3.mp4&quot;&gt;Watch the video&lt;/a&gt; (9 s), recorded at commit a6b45bc, in the progress log of &lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-proved-todomvc/&quot;&gt;A proved TodoMVC&lt;/a&gt;.&lt;/p&gt;</description>
</item>
<item>
<title>A proved TodoMVC</title>
<link>https://cairn.iamnbutler.workers.dev/blog/a-proved-todomvc/</link>
<guid isPermaLink="false">https://cairn.iamnbutler.workers.dev/milestones/#first-milestone-a-proved-todomvc</guid>
<pubDate>Fri, 23 Oct 2026 14:57:34 GMT</pubDate>
<description>&lt;p&gt;The first milestone was TodoMVC, running natively on the Mac. TodoMVC is small, completely
specified and has text input, and formal UI testing already uses it as a benchmark. It needs
every layer of a UI framework and is small enough to finish.&lt;/p&gt;
&lt;p&gt;Its state, view, layout, hit testing, focus and text editing are written in Lean. A thin Swift
host passes events in and paints Lean's frames with Metal. The text fields do typing, selection,
copy and paste, and input methods.&lt;/p&gt;
&lt;h2&gt;What's proved&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Every action is a claim with an id, and the build won't let an id drift.&lt;/li&gt;
&lt;li&gt;Each action does what its claim says, and the counts and filters stay right in every state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Presence&lt;/strong&gt;: a control is on screen exactly when its action is enabled.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pressable&lt;/strong&gt;: at any window size from the minimum up, a click at an enabled control's centre
reaches that control, so it's visible, unclipped and on top.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Checked another way&lt;/h2&gt;
&lt;p&gt;The real app is driven through a thousand generated sequences of real pointer and key events,
and must agree with the model after every step. A map of the app is generated from the claims,
with the evidence behind each one, and everything the proofs trust about the host is written
down in the trusted base.&lt;/p&gt;&lt;p&gt;Progress so far:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-proved-todomvc/#todomvc-in-lean&quot;&gt;TodoMVC in Lean&lt;/a&gt; (2026-10-23)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-proved-todomvc/#basics-the-first-lean-app&quot;&gt;Basics: the first Lean app&lt;/a&gt; (2026-10-23)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-proved-todomvc/#the-host-with-text&quot;&gt;The host, with text&lt;/a&gt; (2026-10-23)&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cairn.iamnbutler.workers.dev/blog/a-proved-todomvc/#boundary-spike&quot;&gt;Boundary spike&lt;/a&gt; (2026-10-23)&lt;/li&gt;&lt;/ul&gt;</description>
</item>
</channel>
</rss>
