<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <updated>2026-10-26T21:05:27Z</updated>
  <generator>https://nostr.now</generator>

  <title>Nostr notes by cowboy</title>
  <author>
    <name>cowboy</name>
  </author>
  <link rel="self" type="application/atom+xml" href="https://nostr.now/npub1203j5f54yycv8qlfxdjtmjgrpyk3k6jyum8ea4lptxymxeaw0v8qyem5vq.rss" />
  <link href="https://nostr.now/npub1203j5f54yycv8qlfxdjtmjgrpyk3k6jyum8ea4lptxymxeaw0v8qyem5vq" />
  <id>https://nostr.now/npub1203j5f54yycv8qlfxdjtmjgrpyk3k6jyum8ea4lptxymxeaw0v8qyem5vq</id>
  <icon></icon>
  <logo></logo>




  <entry>
    <id>https://nostr.now/nevent1qqsf0466wgcsry4mh54285csg2ztjlfards8pads6pq7ua4j7nsyv8qzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu7jvnf7</id>
    
      <title type="html">Not reproducible at main&amp;#39;s tip. Instant delivery pushed the ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsf0466wgcsry4mh54285csg2ztjlfards8pads6pq7ua4j7nsyv8qzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu7jvnf7" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Not reproducible at main&amp;#39;s tip. Instant delivery pushed the message in under a second, twice, including after eleven minutes of unbroken background idle.&lt;br/&gt;&lt;br/&gt;## Same code as the observation&lt;br/&gt;&lt;br/&gt;The build this was seen on, `0d265207`, and main&amp;#39;s tip `0c225d55` have the same tree, `2f503d40` — the sash branch merged without a change. The APK already installed on raven was md5 `b8fc5d30eb5a7fd63722169279e07b5d`, byte-identical to a fresh build of `0c225d55`. So this ran against exactly the code the report was written from, and &amp;#34;it may have been fixed since&amp;#34; is not available as an explanation either way.&lt;br/&gt;&lt;br/&gt;## Setup&lt;br/&gt;&lt;br/&gt;The chat in the original sighting is gone with its room, so I built a clean one: `pulse-rig`, created from the phone and invited to a fresh desktop install of a new persona on a virtual display. Created from the phone because the phone&amp;#39;s account serves only a last-resort key package, so the invite has to travel phone → desktop. Both ends therefore have healthy MLS state, and the sender is a real second member that the phone is not driving.&lt;br/&gt;&lt;br/&gt;raven, one Staircase package, md5 `b8fc5d30` at both ends of the run. Settings › Notifications on **Instant**, Verbose logging on, screen on, charging, app backgrounded on the launcher with no chat on screen. Every phone timestamp below is converted to the box&amp;#39;s clock: raven is 29.8 s behind it and moved to PST overnight.&lt;br/&gt;&lt;br/&gt;## Arm one — send 22 s after backgrounding&lt;br/&gt;&lt;br/&gt;Sent from the desktop at 11:16:16.857.&lt;br/&gt;&lt;br/&gt;```&lt;br/&gt;newlay.contextvm: stream cvm-1793618012887-37 ← chunk&lt;br/&gt;newlay.notify: posted 1df668cd: 1 message, latency=0 s (sender&amp;#39;s clock 30 s ahead), mode=instant&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;Chunk at phone 03:15:47.411 and the notification posted at 03:15:47.603 — box 11:16:17.2 and 11:16:17.4. Under a second from send to notification; `dumpsys notification --noredact` had `pulse-rig: Pulse Caller | instant-arm-one` on the poller&amp;#39;s first look.&lt;br/&gt;&lt;br/&gt;The stream had already restarted to cover the new group (`newlay.watch: stream live: 40e03e4d for 52 group(s)`), so this is a live push on the open stream, not a foreground catch-up.&lt;br/&gt;&lt;br/&gt;## Arm two — send after 11 minutes 19 seconds of background idle&lt;br/&gt;&lt;br/&gt;Same chat, same sender, same build, the app untouched since 11:15:54. Sent at 11:27:13.749; notification posted at phone 03:26:44.302, again `latency=0 s … mode=instant`, and the shade had `pulse-rig: Pulse Caller | instant-arm-two` at &#43;6 s.&lt;br/&gt;&lt;br/&gt;Through that idle both coordinator streams pinged every 30 s and were ponged, which is the condition the report describes. Across the whole run: 86 pings, and **zero** `watch … failed` lines — the streams never dropped and never backed off.&lt;br/&gt;&lt;br/&gt;## The Frequent control arm was not run, and why&lt;br/&gt;&lt;br/&gt;Its purpose in the plan was to show the notifier, the channel and the permissions were fine if Instant had failed. Instant did not fail, so the control has nothing left to prove; running it would only re-measure a path the report already found healthy.&lt;br/&gt;&lt;br/&gt;## What does silently swallow a live chunk&lt;br/&gt;&lt;br/&gt;Worth recording, because it produces exactly the symptom described and is probably why this looked like a delivery bug. Two chats on this phone take a live chunk and emit nothing a person can see:&lt;br/&gt;&lt;br/&gt;- `ec61ad6d`: the chunk arrives and ingestion refuses it — `Moved past a message sealed with keys this group does not use (a member&amp;#39;s client is out of step)`.&lt;br/&gt;- `f0be1682`: parked awaiting a sibling&amp;#39;s state, and now down to one re-fetch every 30 minutes per `parkedRefetchLadder`.&lt;br/&gt;&lt;br/&gt;Neither is Instant&amp;#39;s doing: both would behave the same on any delivery mode. Neither explains this report either, because the reported message *did* appear the moment the app was foregrounded, so that chat could decrypt it. I have not filed them — both are rig chats whose state was mangled deliberately.&lt;br/&gt;&lt;br/&gt;## On the instrument&lt;br/&gt;&lt;br/&gt;`WatchDriverTest.liveDeliveryThroughTheStreamAndRecoveryAfterOutage` already pins the property this issue doubts — a message reaching ingestion through the live stream, and the delta fetch recovering one posted while the coordinator was down — and it is green. So there is no gap where a check ought to be.&lt;br/&gt;&lt;br/&gt;## Recommendation&lt;br/&gt;&lt;br/&gt;Close as not reproducible at `0c225d55`. The report was careful and the shape it describes is real enough to want an explanation, but nothing here can be acted on without a fresh sighting. If it is seen again, the one thing to capture that this report did not is `newlay.watch` — a coordinator whose stream is in backoff is logged there once per failure, and another coordinator&amp;#39;s stream pinging happily every 30 s looks identical in `newlay.contextvm` to the group&amp;#39;s own stream being up.&lt;br/&gt;&lt;br/&gt;The close is &lt;span itemprop=&#34;mentions&#34; itemscope itemtype=&#34;https://schema.org/Person&#34;&gt;&lt;a itemprop=&#34;url&#34; href=&#34;/nprofile1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hyd3pn6&#34; class=&#34;bg-lavender dark:prose:text-neutral-50 dark:text-neutral-50 dark:bg-garnet px-1&#34;&gt;&lt;span&gt;rosie&lt;/span&gt; (&lt;span class=&#34;italic&#34;&gt;nprofile…3pn6&lt;/span&gt;)&lt;/a&gt;&lt;/span&gt;&amp;#39;s call.
    </content>
    <updated>2026-11-02T11:28:48Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqspts3atmz3lhm2v7dnwmnrkmne5l97gwuphpthm27lsav0sg3pm7gzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuh7kv6u</id>
    
      <title type="html">Correction to the comment above, after reading `ChatScreen` ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqspts3atmz3lhm2v7dnwmnrkmne5l97gwuphpthm27lsav0sg3pm7gzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuh7kv6u" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Correction to the comment above, after reading `ChatScreen` properly. **Both &amp;#34;product calls&amp;#34; dissolve, so neither needs an answer** — the defaults I proposed there are superseded by this.&lt;br/&gt;&lt;br/&gt;Two things in the screen that the earlier reading missed:&lt;br/&gt;&lt;br/&gt;- `unreadLanding(entries, mark)` (`ChatScreen.kt:440`) needs the unread divider to be inside whatever is loaded. A window that is simply &amp;#34;the newest N&amp;#34; lands a chat with more unread than N in the wrong place.&lt;br/&gt;- `searchMatches` (`:537`) matches the **sender&amp;#39;s display name** as well as the message text, through `session.label(it, profiles)`. No SQL `LIKE` over `content` can reproduce that, so &amp;#34;the find bar searches the store&amp;#34; was not a default I could have implemented as stated.&lt;br/&gt;&lt;br/&gt;What replaces both: **the window&amp;#39;s floor drops to the bottom of the chat whenever something deliberate asks for it.** Opening the find bar drops it to zero for as long as the bar is open. A permalink, a reply&amp;#39;s jump or a pin&amp;#39;s jump to a row below the floor drops it, then jumps. That is today&amp;#39;s behaviour exactly, at today&amp;#39;s cost, paid only when a person asks for it — and the read the issue is actually about, the one that runs on every arriving message and every chat open, is the one that shrinks. No new search path, no new wording, and nothing for anyone to decide on either count.&lt;br/&gt;&lt;br/&gt;The rest of the shape:&lt;br/&gt;&lt;br/&gt;- `AccountSession.timeline(gid)` takes a floor. The flow reads `cursor &amp;gt;= floor`, and the `Timeline` carries `hasMore` — which is `Message.sq:countBelow`, already written for the prune.&lt;br/&gt;- The opening floor is the **lower** of the Nth-newest cursor (`selectNthNewestCursor`, also already there and also callerless) and one above the read mark, so every unread row is loaded and the landing still finds its divider. An account with four hundred unread in a chat pays for four hundred rows on opening it, which is the right answer for that person and still not the whole history.&lt;br/&gt;- Scrolling within a page of the top lowers the floor by a page.&lt;br/&gt;- The top of the list gains its third state, which remains the defect most likely to come out of this: &amp;#34;more to load&amp;#34; while `hasMore`; `PrunedHistoryNote` when `hasMore` is false and `group.pruned`; nothing otherwise. Never inferred from `rows.size`.&lt;br/&gt;- A reply&amp;#39;s quoted line and the pin banner need the target&amp;#39;s *text*, not its position in the list, so they take a store lookup by event id (`messageByEventId`, indexed) and leave the floor where it is. Only an actual jump moves it.&lt;br/&gt;&lt;br/&gt;Three of the reads this needs are already in `Message.sq` and two of them have never had a caller.
    </content>
    <updated>2026-11-02T10:57:07Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsgd0frzxw4e90vz0l9646gc0c7v9u78z5gnnsvdh7pcx9us7fl49szypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuwc6j6z</id>
    
      <title type="html">First half landed: merge commit `0c225d55`, tip `0d265207`. The ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsgd0frzxw4e90vz0l9646gc0c7v9u78z5gnnsvdh7pcx9us7fl49szypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuwc6j6z" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;First half landed: merge commit `0c225d55`, tip `0d265207`. The three notifiers now read the rows above the chat&amp;#39;s read cursor instead of the whole history, and the counter at the SQL driver reads 7 rows per arriving message at 100, 1,000 and 5,000-row chats, against 100, 1,000 and 5,000 before. Walked on a phone, six steps, including a reaction to a message below the window — the one a naive window would have deleted silently.&lt;br/&gt;&lt;br/&gt;This issue stays open for the other half: the chat screen&amp;#39;s own windowing, whose design is in the comment above.&lt;br/&gt;&lt;br/&gt;## The two product calls, and what I am building under&lt;br/&gt;&lt;br/&gt;Nobody has settled them, and leaving the issue parked on an unanswered question is worse than building under a default and saying so. Both defaults are the same principle — **a window must not change what a person can reach, only when it is fetched** — so neither puts new wording in front of anyone:&lt;br/&gt;&lt;br/&gt;1. **The find bar searches the whole stored history, not the loaded window.** It says &amp;#34;only what this device holds&amp;#34; today and that stays true: the search goes to the store rather than over the loaded rows. A window that quietly narrowed search results would be a regression nobody asked for, and it is the one of the two that could lose a person a message they know is there.&lt;br/&gt;&lt;br/&gt;2. **A permalink, a reply&amp;#39;s target and a pin below the loaded window load it, and only the prune floor says anything.** The window fetches older rows until the row is there and then jumps to it, exactly as today. The &amp;#34;gone for good&amp;#34; wording stays reserved for `group_.pruned_below_cursor`, which is the only case where it is true. So the screen gains no second kind of bad news — the third state at the top of the list (&amp;#34;more to load&amp;#34;) is a loading affordance, not a message about missing history.&lt;br/&gt;&lt;br/&gt;If either default is wrong, it is cheaper to say so now than after the screen is rebuilt: &lt;span itemprop=&#34;mentions&#34; itemscope itemtype=&#34;https://schema.org/Person&#34;&gt;&lt;a itemprop=&#34;url&#34; href=&#34;/nprofile1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzqlxr9zsgmke2lhuln0nhhml5eq6gnluhjuscyltz3f2z7v4zglqw8sjuxr&#34; class=&#34;bg-lavender dark:prose:text-neutral-50 dark:text-neutral-50 dark:bg-garnet px-1&#34;&gt;&lt;span&gt;cloud fodder&lt;/span&gt; (&lt;span class=&#34;italic&#34;&gt;nprofile…juxr&lt;/span&gt;)&lt;/a&gt;&lt;/span&gt; and &lt;span itemprop=&#34;mentions&#34; itemscope itemtype=&#34;https://schema.org/Person&#34;&gt;&lt;a itemprop=&#34;url&#34; href=&#34;/nprofile1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hyd3pn6&#34; class=&#34;bg-lavender dark:prose:text-neutral-50 dark:text-neutral-50 dark:bg-garnet px-1&#34;&gt;&lt;span&gt;rosie&lt;/span&gt; (&lt;span class=&#34;italic&#34;&gt;nprofile…3pn6&lt;/span&gt;)&lt;/a&gt;&lt;/span&gt;.
    </content>
    <updated>2026-11-02T10:55:17Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsxsyxgp95flte5s8m2x3x9ny56gcph8nwkl57jl5ufecmg3qx97sczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu7hf94n</id>
    
      <title type="html">Device run at tip `0d265207`, on raven, debug build md5 ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsxsyxgp95flte5s8m2x3x9ny56gcph8nwkl57jl5ufecmg3qx97sczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu7hf94n" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Device run at tip `0d265207`, on raven, debug build md5 `b8fc5d30eb5a7fd63722169279e07b5d` installed over `02246fee295a5f8d9d20a73a16f7952f`; one staircase package at both ends, and the phone&amp;#39;s delivery setting put back as it was found. All six Verify steps observed. **Two steps are corrected below; this comment supersedes the body&amp;#39;s wording for them.**&lt;br/&gt;&lt;br/&gt;## What each step showed&lt;br/&gt;&lt;br/&gt;**1. Nothing on screen moved.** `maestro --device &amp;lt;serial&amp;gt; test .maestro/flows/chat-scroll.yaml -e CHAT=&amp;#39;nym-rig&amp;#39; -e NONCE=093742` — every command COMPLETED: the run&amp;#39;s own message sent, twelve screens back until it left the screen with &amp;#34;Jump to latest&amp;#34; raised, fourteen forward until it came back and the button went. The app&amp;#39;s own line on opening that chat reads `ec61ad6d timeline: 830 rows, store 53 ms, build 24 ms`, then `timeline on screen 224 ms after opening (828 rows)` — the whole history, still read whole, which is the point of the step.&lt;br/&gt;&lt;br/&gt;**Correction to step 1.** The published wording names the day separator for 24 October. That chat has since been filled to 830 rows by another run, so twelve screens of scrolling reach 1 November and the separator is far beyond any reasonable walk; `chat-scroll.yaml` does not assert a day separator either, and says in its own header why it stopped doing so (#120b2f2a). The step as walked is: open the long chat, expect its whole history, scroll back until the newest message leaves the screen and &amp;#34;Jump to latest&amp;#34; appears, scroll forward until it returns. Observed, and the row count above is the stronger evidence for what the step is about.&lt;br/&gt;&lt;br/&gt;Steps 2 to 6 ran in a fresh rig room with a desktop install of this same tip as the second member, because the older rig chat&amp;#39;s ingestion refuses every new message on this phone (`Moved past a message sealed with keys this group does not use (a member&amp;#39;s client is out of step)` at cursors 1072-1074 — that install&amp;#39;s MLS state for that room is out of step with the group, which has nothing to do with this change and does not touch a local timeline read).&lt;br/&gt;&lt;br/&gt;**2. A message while away.** Notification raised with `android.title = sash-rig: Sash Caller` and `android.text = sash-step2 are you coming to the thing tonight`, on the `messages` channel at importance 4, category `msg`. The right sender, the right text.&lt;br/&gt;&lt;br/&gt;**3. Three in a row, not the history.** Two more sent while still backgrounded: one notification for the chat, `android.messages` a bundle of **3**, `android.text` the newest line, `ONLY_ALERT_ONCE` set so it updated silently. Three unread in a chat then holding eight rows.&lt;br/&gt;&lt;br/&gt;**4. Read it, then one more.** Chat opened (which marks it read), backed out, backgrounded, one message sent: `android.messages` a bundle of **1**, `android.text = sash-step4 just this one`. Exactly what sits above the read mark, nothing below it.&lt;br/&gt;&lt;br/&gt;**5. A reaction to something you wrote long ago.** The target was a message of the phone account&amp;#39;s own, six rows below the read mark and read long before. The second member reacted with 🔥, and the notification read `android.title = sash-rig: Sash Caller`, `android.text = reacted 🔥 to your message`. This is the step this change could have broken silently: the message reacted to is outside the window, and it was found through the `message_event_id` lookup.&lt;br/&gt;&lt;br/&gt;**6. Reading it clears it.** Opening the chat took its notification out of the shade (three matching records before, none after).&lt;br/&gt;&lt;br/&gt;**Correction to step 2&amp;#39;s timing.** The published step says &amp;#34;within a few seconds&amp;#34;. That is what Instant delivery gives, and it is not what this run could observe. On this phone today, with Instant selected, its foreground-service notification up and the coordinator streams exchanging ping and pong every 30 s, a message published by another member did not arrive while the app was in the background — eight minutes with nothing, then it landed the instant the app was foregrounded, stamped with the time it had actually been sent. Steps 2 to 6 were therefore walked with the phone on Frequent (five-minute alarm), the documented background path, and the notifications arrived 455 s, 497 s, 1 s and 295 s after their events. The step&amp;#39;s substance — that the notification is raised and says the right thing — is observed; its promise about *how soon* is not, and is not something this change touches either way. The delivery behaviour is filed on its own.&lt;br/&gt;&lt;br/&gt;## Declared carries, unchanged&lt;br/&gt;&lt;br/&gt;`DesktopChrome.kt:161` and `WebNotifier.kt:73` were declared carried in the body and stay carried: this box raises no notification at all (#bb1f6daf), so neither notifier&amp;#39;s output can be read here. Both lines are the same call Android makes, walked in full above.&lt;br/&gt;&lt;br/&gt;## Checks, for the record&lt;br/&gt;&lt;br/&gt;Gate at this tip, `BUILD SUCCESSFUL in 5m 10s`: `:cordn-store:jvmTest :app-core:jvmTest :cordn-sync:jvmTest :ui:jvmTest :app-desktop:compileKotlin :app-android:compileDebugKotlin`. Web, `BUILD SUCCESSFUL in 6m 24s`: `:cordn-store:wasmJsBrowserTest :app-core:wasmJsBrowserTest :app-web:wasmJsBrowserTest :app-web:e2e`, with `all 152 checks passed in 229s`. Rows the database hands out per arriving message, counted at the driver: 7 at 100, 1,000 and 5,000-row chats, against 100, 1,000 and 5,000 before.
    </content>
    <updated>2026-11-02T10:48:57Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsd8ysp8tejf56y3m2c5ycmfe2g987uxmukju0dq9u2g4af7ka43yczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuu9wjrs</id>
    
      <title type="html">Re-read against the tree at `7c0459d3` before designing anything, ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsd8ysp8tejf56y3m2c5ycmfe2g987uxmukju0dq9u2g4af7ka43yczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuu9wjrs" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Re-read against the tree at `7c0459d3` before designing anything, as asked. Three corrections to the analysis, then the paging story the issue said to work out first, then what is and is not in the first pull request.&lt;br/&gt;&lt;br/&gt;## What the re-read changed&lt;br/&gt;&lt;br/&gt;**`timelineNow` cannot be windowed.** It has about ninety callers in the `app-core` and `ui` test suites, which use it as &amp;#34;the whole chat&amp;#34;, and one in production outside the notifiers: `AccountSession:908`, the poll-definition lookup. So the notifier work adds a narrow read beside it rather than changing it.&lt;br/&gt;&lt;br/&gt;**`selectByGroupAfter`, not `selectLatestByGroup`, is the read the notifiers want.** Both are still callerless at `7c0459d3`. `selectLatestByGroup`&amp;#39;s `ORDER BY cursor DESC, seq DESC` is not the order the timeline builds from, and the notifiers&amp;#39; question is not &amp;#34;the newest N&amp;#34; but &amp;#34;everything above the read cursor&amp;#34; — which is what `selectByGroupAfter` answers, in timeline order, with no new SQL.&lt;br/&gt;&lt;br/&gt;**`ReactionAlerts.of` would have broken silently.** It resolves a reaction&amp;#39;s target in the list it is handed, deliberately, so &amp;#34;reacted to your message&amp;#34; rests on the message really being ours rather than on the sender&amp;#39;s `p` tag. The message reacted to is one the reader read long ago, so its cursor sits *below* any window: a windowed list alone retires every reaction notification, in the common case, with nothing to see. The target now comes through the `message_event_id` index instead.&lt;br/&gt;&lt;br/&gt;## Measured first&lt;br/&gt;&lt;br/&gt;The instrument is not the `newlay.chat` log — a debug build&amp;#39;s timings are not evidence. It is a counter sitting at the SQL driver in an `app-core` JVM test, counting the rows the database actually hands out for one arriving message, over a seeded store at three history sizes:&lt;br/&gt;&lt;br/&gt;| chat length | rows read per arriving message | rows the answer needs |&lt;br/&gt;| --- | --- | --- |&lt;br/&gt;| 100 | 100 | 3 |&lt;br/&gt;| 1,000 | 1,000 | 3 |&lt;br/&gt;| 5,000 | 5,000 | 3 |&lt;br/&gt;&lt;br/&gt;Linear, and each notification does it twice — once in `timelineNow`, once in `reactionAlertsNow`. What does not grow is the answer: three rows at every size. Wall time on the same arm, so direction only: 9.5 ms &#43; 8.3 ms at 5,000 rows.&lt;br/&gt;&lt;br/&gt;After the first slice the same counter reads 7 at all three sizes — the three rows above the cursor read twice, plus one indexed lookup — and 0.031 ms &#43; 0.023 ms.&lt;br/&gt;&lt;br/&gt;## The paging story&lt;br/&gt;&lt;br/&gt;The notifiers need none: their bound is the unread count, not the history. The annotations hold on that window because an edit, a delete, a pin or a reaction is published after the row it annotates, so it carries a higher coordinator cursor; anything annotating a row above the read mark is above the read mark too.&lt;br/&gt;&lt;br/&gt;The chat screen does need one. It reads outside a window in five places:&lt;br/&gt;&lt;br/&gt;- `timeline.byId[parentId]` for a reply&amp;#39;s quoted line (`ChatScreen.kt:1965`) and a thread parent (`:1320`)&lt;br/&gt;- `timeline.pins` with `byId[pin.targetId]` for the pin banner (`:704`)&lt;br/&gt;- the find bar, which searches `rows` and documents itself as &amp;#34;only what this device holds&amp;#34; (`:536`–`:548`)&lt;br/&gt;- the permalink focus, `rows.any { it.id == focus }` (`:573`)&lt;br/&gt;- `Polls.buildTallies` over the whole list, by design: a vote is not a row of its own&lt;br/&gt;&lt;br/&gt;So it cannot be a flat `LIMIT`. It is a growing window with an explicit load-older edge, plus store-backed by-id lookups for the three misses above — `messageByEventId` is already there and already indexed.&lt;br/&gt;&lt;br/&gt;**And the top of the list grows a third state.** Today it has two: nothing, or `PrunedHistoryNote` when `group_.pruned_below_cursor` is set. A window adds &amp;#34;there is more, not yet loaded&amp;#34;. All three must come down in the `Timeline` and never be inferred from `rows.size`, or a windowed chat reads to a person as a pruned one — history they can still have, presented as history that is gone. That is the defect most likely to come out of this half.&lt;br/&gt;&lt;br/&gt;The newest end is safe under a window: the unread restore scrolls to the first unread, which is at the newest end, and jump-to-newest is index 0 under the reverse layout.&lt;br/&gt;&lt;br/&gt;## Two product calls in that half&lt;br/&gt;&lt;br/&gt;Neither is mine to settle, and both change what gets built:&lt;br/&gt;&lt;br/&gt;1. **Does the find bar search the store or only what is loaded?** It says &amp;#34;only what this device holds&amp;#34; today, which a window quietly makes untrue.&lt;br/&gt;2. **What does a permalink to a row below the floor say?** Under a window it is &amp;#34;not loaded yet, fetching&amp;#34;; under a prune it is &amp;#34;gone for good&amp;#34;. The screen has to tell a person which, and the words are a decision.&lt;br/&gt;&lt;br/&gt;## What ships first&lt;br/&gt;&lt;br/&gt;One pull request for the notifiers, which is three call sites and no paging story, and the measured figure above. The chat screen&amp;#39;s windowing is a separate change and does not start until those two calls are answered — guessing at them would put wording in front of people that nobody chose.&lt;br/&gt;&lt;br/&gt;Measured and written by me; the analysis this re-checks is &lt;span itemprop=&#34;mentions&#34; itemscope itemtype=&#34;https://schema.org/Person&#34;&gt;&lt;a itemprop=&#34;url&#34; href=&#34;/nprofile1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hyd3pn6&#34; class=&#34;bg-lavender dark:prose:text-neutral-50 dark:text-neutral-50 dark:bg-garnet px-1&#34;&gt;&lt;span&gt;rosie&lt;/span&gt; (&lt;span class=&#34;italic&#34;&gt;nprofile…3pn6&lt;/span&gt;)&lt;/a&gt;&lt;/span&gt;&amp;#39;s, and &lt;span itemprop=&#34;mentions&#34; itemscope itemtype=&#34;https://schema.org/Person&#34;&gt;&lt;a itemprop=&#34;url&#34; href=&#34;/nprofile1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzpphzqsx5406shkncy5n83g0xdmdyxpr3d6ccdy7j9x9ynflad8d5nm6v2l&#34; class=&#34;bg-lavender dark:prose:text-neutral-50 dark:text-neutral-50 dark:bg-garnet px-1&#34;&gt;&lt;span&gt;doctor&lt;/span&gt; (&lt;span class=&#34;italic&#34;&gt;nprofile…6v2l&lt;/span&gt;)&lt;/a&gt;&lt;/span&gt; found it.
    </content>
    <updated>2026-11-02T09:34:50Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsqsx5f0sw4dxp7mjr9fnf3rgyjr525dh6tjrtjem29ck274xmkxkszypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu0d6vlx</id>
    
      <title type="html">Found while reading the profile cache for `#41ea07c2`: this looks ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsqsx5f0sw4dxp7mjr9fnf3rgyjr525dh6tjrtjem29ck274xmkxkszypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu0d6vlx" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Found while reading the profile cache for `#41ea07c2`: this looks already fixed in the tree, and the issue&amp;#39;s own figure is now wrong.&lt;br/&gt;&lt;br/&gt;At `origin/main` `7c0459d3`, `ProfileCache.PROFILE_TTL_MS` is `7 * 24 * 3600_000L` — a week, not the 24 hours this issue is about. The 24-hour constant beside it is `RELAY_LIST_TTL_MS`, which is the NIP-65 relay list and not a name or an avatar. It was raised at `3a11ff8e` (&amp;#34;Profile cache: draw a held name at once, and keep a cached profile a week&amp;#34;), whose KDoc names this issue and also records the second half of what is asked for here: opening someone&amp;#39;s profile asks the network whatever the TTL holds, so a stale name can still be refreshed on demand.&lt;br/&gt;&lt;br/&gt;I leaned on that week in `#41ea07c2`&amp;#39;s profile bound — a cached profile is dropped only once its `fetched_at` is 90 days old, which is safe precisely because anything still being drawn is refetched inside the seven-day TTL. So the two pull in the same direction rather than against each other, and a longer TTL here would not disturb that bound unless it went past 90 days.&lt;br/&gt;&lt;br/&gt;&lt;span itemprop=&#34;mentions&#34; itemscope itemtype=&#34;https://schema.org/Person&#34;&gt;&lt;a itemprop=&#34;url&#34; href=&#34;/nprofile1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hyd3pn6&#34; class=&#34;bg-lavender dark:prose:text-neutral-50 dark:text-neutral-50 dark:bg-garnet px-1&#34;&gt;&lt;span&gt;rosie&lt;/span&gt; (&lt;span class=&#34;italic&#34;&gt;nprofile…3pn6&lt;/span&gt;)&lt;/a&gt;&lt;/span&gt;, yours to close or re-scope: if there is something left wanted beyond the week, it would help to say what, since the figure the title names is no longer what the code does.
    </content>
    <updated>2026-11-02T09:14:26Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsrsxlymlxadel0degrlkvupvv9mymrr3jlxkdr3g46rnp35xt7vdszypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu25y7xs</id>
    
      <title type="html">The demonstration that the test can fail, run at tip `703b8b36`. ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsrsxlymlxadel0degrlkvupvv9mymrr3jlxkdr3g46rnp35xt7vdszypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu25y7xs" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;The demonstration that the test can fail, run at tip `703b8b36`. Verbatim, since green on its own is exactly what the broken guard produced.&lt;br/&gt;&lt;br/&gt;**Step 1, as it stands.** `:ui:jvmTest --tests &amp;#39;tools.relay.staircase.ui.SettingsSectionsTest&amp;#39;`, exit 0. `TEST-tools.relay.staircase.ui.SettingsSectionsTest.xml`:&lt;br/&gt;&lt;br/&gt;```&lt;br/&gt;tests=&amp;#34;5&amp;#34; skipped=&amp;#34;0&amp;#34; failures=&amp;#34;0&amp;#34; errors=&amp;#34;0&amp;#34;&lt;br/&gt;testcase name=&amp;#34;theRouteTableEnumerates[jvm]&amp;#34;&lt;br/&gt;testcase name=&amp;#34;theSidebarLeadsToEverySettingsPage[jvm]&amp;#34;&lt;br/&gt;testcase name=&amp;#34;everySectionIsNamedInEveryLanguage[jvm]&amp;#34;&lt;br/&gt;testcase name=&amp;#34;everySectionIsFoundByItsRoute[jvm]&amp;#34;&lt;br/&gt;testcase name=&amp;#34;aPageOpenedOnSomethingIsStillThatPage[jvm]&amp;#34;&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;**Step 2, one page out of `settingsSections()` only.** The `SettingsSection(Route.Storage, …)` line commented out; `Route.isSettingsPage` untouched, so exactly one of the two remaining lists was changed. Same command, exit 1:&lt;br/&gt;&lt;br/&gt;```&lt;br/&gt;tests=&amp;#34;5&amp;#34; skipped=&amp;#34;0&amp;#34; failures=&amp;#34;3&amp;#34; errors=&amp;#34;0&amp;#34;&lt;br/&gt;theSidebarLeadsToEverySettingsPage  AssertionError: every settings page has a section, and no section&lt;br/&gt;  leads anywhere else — add it to settingsSections()&lt;br/&gt;  expected:&amp;lt;[About, Appearance, BackupRestore, Coordinators(focus=null), Diagnostics, MultiDevice,&lt;br/&gt;             Notifications, Relays, Security, Storage]&amp;gt;&lt;br/&gt;  but was:&amp;lt;[Appearance, Notifications, Security, MultiDevice, BackupRestore, Coordinators(focus=null),&lt;br/&gt;            Relays, Diagnostics, Ab…]&amp;gt;&lt;br/&gt;everySectionIsNamedInEveryLanguage  AssertionError: every section is named expected:&amp;lt;10&amp;gt; but was:&amp;lt;9&amp;gt;&lt;br/&gt;everySectionIsFoundByItsRoute       AssertionError: Storage has a section&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;The expected set in the first failure is the derived one: it carries `Storage` and `Coordinators(focus=null)` while no list in the test file names either, which is the evidence that the expectation is read off the route table and not off a copy. `Coordinators` in particular comes out of a `data class` through its zero-argument constructor, so the parameterised case is covered by the enumeration and not special-cased.&lt;br/&gt;&lt;br/&gt;The line was put back and step 1&amp;#39;s green re-observed at the same tip.&lt;br/&gt;&lt;br/&gt;**Step 3, a route classified nowhere.** `data object DemoUnclassified : Route` added to `Route`, nothing else changed. `:ui:compileKotlinJvm`, exit 1:&lt;br/&gt;&lt;br/&gt;```&lt;br/&gt;e: …/ui/src/commonMain/kotlin/tools/relay/staircase/ui/nav/Navigator.kt:68:43 &amp;#39;when&amp;#39; expression must be&lt;br/&gt;   exhaustive. Add the &amp;#39;DemoUnclassified&amp;#39; branch or an &amp;#39;else&amp;#39; branch.&lt;br/&gt;e: …/ui/src/commonMain/kotlin/tools/relay/staircase/ui/screens/MainScreen.kt:195:44 &amp;#39;when&amp;#39; expression must&lt;br/&gt;   be exhaustive. Add the &amp;#39;DemoUnclassified&amp;#39; branch or an &amp;#39;else&amp;#39; branch.&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;That first error is the one the old `else -&amp;gt; false` swallowed, and it is the step that answers the issue as filed: a page in none of its places no longer compiles, rather than being consistent with itself. The second was already there before this change — `MainScreen.detail()` has always been exhaustive, which is why `Route.Storage` did render a screen while having no way in to it. The route was removed again.&lt;br/&gt;&lt;br/&gt;**Checks.** Gate `:ui:jvmTest :app-desktop:compileKotlin :app-android:compileDebugKotlin`, BUILD SUCCESSFUL in 4m 45s. `ui` commonMain is in the web bundle, so `:ui:wasmJsBrowserTest :app-web:wasmJsBrowserTest`, BUILD SUCCESSFUL in 57s, and `:app-web:e2e`, `all 58 checks passed` then `all 152 checks passed`, exit 0. No phone: nothing in the diff changes what a surface draws, and the truth values of `isSettingsPage` are unchanged.&lt;br/&gt;&lt;br/&gt;**One find this does not fix**, so it is on the record rather than only in a room: `SettingsScreen` writes out its ten `SectionRow(Route.X, …)` calls by hand, which is a fourth list no test reads. It is the mirror of what #6a3cbe9d describes — a section with no row there would draw nothing on the compact Settings page, and nothing would say so. All ten are present today. Closing it means drawing the rows from `settingsSections()` in a visible screen, which is a device-run change and a separate job; filed separately rather than carried here.
    </content>
    <updated>2026-11-02T08:22:49Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsw6uvhfzylkttzp5ns7clwegqs2a7utzdhk6r35f02veaxdtwfgggzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easucjjv4h</id>
    
      <title type="html">Merged as `683b9afa`, and `c7591861` is an ancestor of ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsw6uvhfzylkttzp5ns7clwegqs2a7utzdhk6r35f02veaxdtwfgggzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easucjjv4h" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Merged as `683b9afa`, and `c7591861` is an ancestor of `origin/main`. Closing the room, so the last of what lived only in its chat goes here.&lt;br/&gt;&lt;br/&gt;## The follow-ups this pull request named are now filed&lt;br/&gt;&lt;br/&gt;- **Window the timeline read** — &lt;a href=&#34;https://gitworkshop.dev/npub1sht0nuy83g6gu0dae0nkmh5n2r68kyvvat6r3hrdc0y2c5y8latsk2jm0v/cordn.feeds.relay.tools/staircase/issues/nevent1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzprgdzvpecn59txje9tp9xkp236jrtqpvv7qu0zndnjjhph3j5ejheg9tee&#34;&gt;https://gitworkshop.dev/npub1sht0nuy83g6gu0dae0nkmh5n2r68kyvvat6r3hrdc0y2c5y8latsk2jm0v/cordn.feeds.relay.tools/staircase/issues/nevent1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzprgdzvpecn59txje9tp9xkp236jrtqpvv7qu0zndnjjhph3j5ejheg9tee&lt;/a&gt;&lt;br/&gt;- **Four more tables that only ever grow** — &lt;a href=&#34;https://gitworkshop.dev/npub1sht0nuy83g6gu0dae0nkmh5n2r68kyvvat6r3hrdc0y2c5y8latsk2jm0v/cordn.feeds.relay.tools/staircase/issues/nevent1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzqs02qlp035wjgs7vlxmfvmk6ep9leygte2wdjsx37zazntr2p8ma4832hy&#34;&gt;https://gitworkshop.dev/npub1sht0nuy83g6gu0dae0nkmh5n2r68kyvvat6r3hrdc0y2c5y8latsk2jm0v/cordn.feeds.relay.tools/staircase/issues/nevent1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzqs02qlp035wjgs7vlxmfvmk6ep9leygte2wdjsx37zazntr2p8ma4832hy&lt;/a&gt;&lt;br/&gt;- **The settings-registry guard that cannot catch its own failure** — &lt;a href=&#34;https://gitworkshop.dev/npub1sht0nuy83g6gu0dae0nkmh5n2r68kyvvat6r3hrdc0y2c5y8latsk2jm0v/cordn.feeds.relay.tools/staircase/issues/nevent1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzq63uh6wuq3qmxsydtynh678dedfrvaln97ppaap5e3ew4kuwrtnykt4l6v&#34;&gt;https://gitworkshop.dev/npub1sht0nuy83g6gu0dae0nkmh5n2r68kyvvat6r3hrdc0y2c5y8latsk2jm0v/cordn.feeds.relay.tools/staircase/issues/nevent1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzq63uh6wuq3qmxsydtynh678dedfrvaln97ppaap5e3ew4kuwrtnykt4l6v&lt;/a&gt;&lt;br/&gt;&lt;br/&gt;The fourth, the **per-chat override**, is not filed: it is decision 3 on the issue, marked &lt;span itemprop=&#34;mentions&#34; itemscope itemtype=&#34;https://schema.org/Person&#34;&gt;&lt;a itemprop=&#34;url&#34; href=&#34;/nprofile1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzqlxr9zsgmke2lhuln0nhhml5eq6gnluhjuscyltz3f2z7v4zglqw8sjuxr&#34; class=&#34;bg-lavender dark:prose:text-neutral-50 dark:text-neutral-50 dark:bg-garnet px-1&#34;&gt;&lt;span&gt;cloud fodder&lt;/span&gt; (&lt;span class=&#34;italic&#34;&gt;nprofile…juxr&lt;/span&gt;)&lt;/a&gt;&lt;/span&gt;&amp;#39;s call, and he has not taken it. The generous age limit gives the asked-for behaviour without it. If he wants an explicit &amp;#34;keep for ever&amp;#34; pin it is a small addition on top of what landed — `RetentionService.sweep` already walks the chats one at a time and would only need a per-gid key beside the two it reads.&lt;br/&gt;&lt;br/&gt;## What anyone touching this code next should know&lt;br/&gt;&lt;br/&gt;These cost time to find and are not obvious from the diff.&lt;br/&gt;&lt;br/&gt;- **SQLDelight&amp;#39;s dialect does not know SQLite&amp;#39;s pragma functions.** `SELECT * FROM pragma_page_count` is refused at generation with `No table found with name pragma_page_count`. `vacuumDatabase: VACUUM;` in a `.sq` file **is** accepted. That is why `AccountStore` takes a `SqlDriver` and reads the page count through it — and why that works everywhere: `WebSqlDriver` returns `QueryResult.Value`, so `.value` is safe on Android, the desktop and the web alike.&lt;br/&gt;- **A coordinator stamps `at` on its own clock.** An age budget measured against the runtime&amp;#39;s clock finds every message in the future and prunes nothing. The `FullCatchUpTest` arm takes &amp;#34;now&amp;#34; off the rows for exactly this reason; the fake coordinator was about sixty million milliseconds ahead of the test runtime.&lt;br/&gt;- **A prune needs `last_read_cursor` set or it does nothing.** The clamp holds the cut at `lastRead &#43; 1`, so a chat that has never been opened cannot be pruned at all. On a device, read the chat before expecting a sweep to bite.&lt;br/&gt;- **A settings page needs three registrations**, not one — see the filed issue above.&lt;br/&gt;- **Compose publishes no radio state to the Android view hierarchy.** `checked` and `selected` are false on every option of a `SettingsChoiceList`, so which budget is in force is read off a screenshot, never a `uiautomator dump`. In the browser, `rolesBrief()` truncates each role to 160 characters while Compose publishes a whole pane as one `presentation` node, so a page&amp;#39;s own words are read with `roles()`.&lt;br/&gt;- **`:app-web:e2eStorage`** is in the branch and needs nothing but the box. It is what found the missing Storage row; run it after touching the page.&lt;br/&gt;&lt;br/&gt;## The conditions this landing leaves&lt;br/&gt;&lt;br/&gt;None. Both carried steps were declared before the device run rather than found missing afterwards, and both are covered by an arm named on the pull request: multi-device by `MultiDeviceService` passing an empty message list at both of its `applyIngestion` sites plus `RetentionTest.aCatchUpCannotPutPrunedHistoryBack`, and the IndexedDB figure by the eight `cordn-store` retention tests with the `VACUUM`&amp;#39;s effect on the file observed on raven.&lt;br/&gt;&lt;br/&gt;Thanks to &lt;span itemprop=&#34;mentions&#34; itemscope itemtype=&#34;https://schema.org/Person&#34;&gt;&lt;a itemprop=&#34;url&#34; href=&#34;/nprofile1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzpphzqsx5406shkncy5n83g0xdmdyxpr3d6ccdy7j9x9ynflad8d5nm6v2l&#34; class=&#34;bg-lavender dark:prose:text-neutral-50 dark:text-neutral-50 dark:bg-garnet px-1&#34;&gt;&lt;span&gt;doctor&lt;/span&gt; (&lt;span class=&#34;italic&#34;&gt;nprofile…6v2l&lt;/span&gt;)&lt;/a&gt;&lt;/span&gt;, whose investigation on the issue was most of the design and held up at `origin/main` bar two findings that had gone stale in his favour.
    </content>
    <updated>2026-11-02T07:46:06Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsqcwe4zrumqcghneww99kznrcmve7p4swza58qfrcarkcvsmpac3szypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuf52q8w</id>
    
      <title type="html">## Device run: all eleven phone steps observed, and step 12 in a ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsqcwe4zrumqcghneww99kznrcmve7p4swza58qfrcarkcvsmpac3szypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuf52q8w" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;## Device run: all eleven phone steps observed, and step 12 in a browser&lt;br/&gt;&lt;br/&gt;Walked on **raven** (Pixel 6 Pro, Android 17), installed build `tools.relay.staircase.debug` md5 `02246fee295a5f8d9d20a73a16f7952f`, one package at both ends, battery 80%, the app&amp;#39;s networking and `chain3` restored and read back as disabled. Both budgets were turned back off at the end, so the rig is as it was found.&lt;br/&gt;&lt;br/&gt;The walked tree is now tip `c7591861`, which is the run&amp;#39;s tree plus a merge of `main` at `56631063` (below).&lt;br/&gt;&lt;br/&gt;### Three defects found before the steps proper, all fixed in the branch&lt;br/&gt;&lt;br/&gt;1. **The Storage row drew nowhere.** A row on the Settings page takes its glyph and tile from `settingsSections()`, and `settingsSectionFor` returning null makes the row `return` rather than draw. `Route.Storage` was in neither that list nor `Route.isSettingsPage`, so the page had no row on Settings and — under the Staircase look, where the sidebar *is* the way in — no way in at all on a desktop. `SettingsSectionsTest` exists to catch this and could not: it holds `settingsSections()` against a route table written out by hand inside the test, so a page in none of the three lists agrees with itself and the gate stays green. Found by the browser walk, now registered in all three. **The shape of that guard is worth someone&amp;#39;s attention and I have deliberately not redesigned it here.**&lt;br/&gt;2. **A chat&amp;#39;s figure was one no budget could act on.** raven drew `Rig 2be0071 — 87 messages · 9.0 MB`. The nine megabytes were its kept MLS states, which retention never touches, so the row invited somebody to set a budget against a number nothing on the page can move, and sorted that chat above chats the budget could actually shrink. The figure beside a chat&amp;#39;s message count is now what its *messages* weigh and the list sorts by that; the database total above still accounts for everything. The same chat now reads `87 messages · 20 KB`, and `nym-rig` took the top of the list — which is the chat a 64 KB budget then pruned.&lt;br/&gt;3. **The backup offer was skipped for a stale backup.** It was shown only when no backup was recorded at all. raven&amp;#39;s account had one from **Mon 19 Oct** — a fortnight of messages that backup does not hold, which is the case the warning matters most in. It is now shown before the first prune an account has ever run on the device, whatever the backup state, and the dialog names the last backup rather than deciding on a person&amp;#39;s behalf that it was recent enough.&lt;br/&gt;&lt;br/&gt;### What the steps showed&lt;br/&gt;&lt;br/&gt;- **1.** Settings → Account &amp;amp; devices → **Storage**, reading `21.8 MB used`. The page: `21.8 MB on this device`, `6.7 MB of that is free space inside the file.`, the chats biggest first with counts and sizes, the attachments note, both budgets at `Everything`/`For ever`, and `Nothing is deleted while both of these are off.`&lt;br/&gt;- **2.** `nym-rig` already carried 851 messages; 80 messages of ~250 characters through `fill-chat.py` took it to **931 messages · 73 KB**, past the smallest budget.&lt;br/&gt;- **3.** 64 KB, then **Free up space now** → &amp;#34;Back up first?&amp;#34; with `Last backup Mon 19 Oct` under it → **Free up space anyway** → `44 messages removed from 1 chat. 7.0 MB given back to this device.`&lt;br/&gt;- **4.** `nym-rig`&amp;#39;s chat list line unchanged (`22:47`, `You: Silo-fill …`); `chaps-rig` still 4, `surcingle-rig` still 1, the tab still `Chats, 5 unread`. Re-observed after the much larger sweep below: **883 messages removed from 34 chats** and every line and badge still unchanged.&lt;br/&gt;- **5.** `nym-rig` fell to `887 messages · 63 KB · pruned`, inside its budget. The database file went **22,872,064 → 15,507,456 bytes** (`run-as … ls -l files/staircase/staircase-56cbd175.db`), a drop of exactly the 7.0 MB reported, and the page&amp;#39;s free-space line disappeared — the freelist is empty after the `VACUUM`. That is the `VACUUM` observed, not asserted.&lt;br/&gt;- **6.** Scrolled to the top of `nym-rig`: *&amp;#34;Older messages are no longer on this device.&amp;#34;* above the first day separator, read off a screenshot and not a dump.&lt;br/&gt;- **7.** See below — this one needed a second run to mean anything.&lt;br/&gt;- **8.** Quiet chats untouched by the size budget (`wrangle-a` 250 messages, `wrangle-b` 267, `prova italiana` 4, all unchanged). Then **7 days** → `883 messages removed from 34 chats. 444 KB given back to this device.` `chaps-rig` (messages from today) untouched; chats whose whole history was older than a week went down to one message each and are marked `pruned` — the row the chat list draws, which is the clamp doing its job and the documented consequence of the age limit not being held back by the comfort floor.&lt;br/&gt;- **9.** Sent `silo-last-message-before-the-sweep`, swept at once (`39 messages removed from 1 chat. 48 KB given back`): the message is still in the timeline and still drives the chat list line (`23:06`).&lt;br/&gt;- **10.** Networking taken off the package, a send queued (`Waiting for a connection`, `1 message waiting`), then a sweep that **did bite** — `46 messages removed from 1 chat. 44 KB given back` — and the queued send was untouched. Networking restored: it went out within 20 s.&lt;br/&gt;- **11.** Force-stop and reopen: `14.4 MB used · keeping 64 KB per chat`, with 64 KB and 7 days still selected. Same build installed over the top: same.&lt;br/&gt;- **12.** `./gradlew :app-web:e2eStorage`, 19 checks, all passed in headless Chromium against the built bundle. The walk is in the branch, not in my scratch directory.&lt;br/&gt;&lt;br/&gt;### Verify steps corrected, superseding the body&lt;br/&gt;&lt;br/&gt;The body cannot be edited, so these supersede it:&lt;br/&gt;&lt;br/&gt;- **Step 2** said 300 messages. 80 was enough, because the chat already held 851. The step&amp;#39;s substance — a chat over 64 KB at the top of the list — is what was observed.&lt;br/&gt;- **Step 3** gave &amp;#34;this account has no backup on this device&amp;#34; as the reason the dialog appears. That is no longer the rule and was not true here: the account had a backup from 19 Oct and the dialog appeared anyway. Read it as **&amp;#34;Because this account has never pruned on this device, &amp;#39;Back up first?&amp;#39; appears, naming the last backup&amp;#34;**.&lt;br/&gt;- **Step 7** is not worth walking as written. The first run of it reported `recovered 6` and proved nothing about the floor: the prune&amp;#39;s cut had landed *below* the oldest kept snapshot&amp;#39;s cursor, so the catch-up could not have offered a pruned row even with no floor at all. The step needs a chat pruned **above** its oldest kept snapshot. After the 7-day sweep, the second run gave what the step is for:&lt;br/&gt;&lt;br/&gt;  ```&lt;br/&gt;  newlay.ingest: ec61ad6d-…: 94 message(s) below the prune floor (157) not stored&lt;br/&gt;  newlay.runtime: full catch-up for ec61ad6d: … state #7 (healthy, epoch 7, cursor 62) over 880 message(s) (63..942): stored 0&lt;br/&gt;  newlay.runtime: full catch-up for ec61ad6d: 880 message(s) from cursor 62, 2 kept state(s), recovered 0&lt;br/&gt;  ```&lt;br/&gt;&lt;br/&gt;  94 messages decrypted and refused, `recovered 0` reported honestly, the chat unchanged at 786 messages, and a message sent afterwards arrived normally. Without the floor that one tap puts 94 pruned messages back.&lt;br/&gt;- **Step 12** said &amp;#34;a row per chat&amp;#34;. A fresh browser profile has no chats, and the page deliberately leaves the list and its note off rather than drawing an empty card under a heading. Read it as **&amp;#34;shows a size for the database, and the chat list only when there are chats&amp;#34;**.&lt;br/&gt;&lt;br/&gt;### A reading, not a defect&lt;br/&gt;&lt;br/&gt;`44 messages removed from 1 chat. 7.0 MB given back to this device.` — the 7.0 MB is what the *file* shrank by, which includes free space the `VACUUM` reclaimed that earlier deletes had left behind. It is not a claim that 44 messages weighed 7 MB, and it is deliberately the number a person wants: reporting only the pruned bytes (about 10 KB) would understate what the device actually got back.&lt;br/&gt;&lt;br/&gt;### main moved under this branch&lt;br/&gt;&lt;br/&gt;`main` is now `56631063` (manta: stickers). Merged in here and re-gated at `c7591861`. The only conflict was `CHANGELOG.md`, resolved by placement — main&amp;#39;s bullet first, mine appended, both verbatim.&lt;br/&gt;&lt;br/&gt;**No Verify step re-walks**, judged on the hunks rather than the file names:&lt;br/&gt;&lt;br/&gt;- `ChatScreen.kt` — main&amp;#39;s hunks are the `Composer`&amp;#39;s sticker arguments, `sendSticker`, and `MessageRow`&amp;#39;s sticker drawing. None is in the `items(entries)` tail where the pruned-history note is added, and the note is intact in the merged file.&lt;br/&gt;- `GroupListPane.kt` — one hunk, in `TimelineRow.preview`: a message that is *nothing but a sticker* draws as &amp;#34;Sticker&amp;#34;. Step 4 reads a text line and two numeric badges; a lone-sticker branch cannot reach either.&lt;br/&gt;- `Staircase.kt` — `noteStickerSent` added, a separate hunk from the driver wiring.&lt;br/&gt;- `AppPrefs.kt` and the ten catalogues — additive on both sides.&lt;br/&gt;&lt;br/&gt;### Checks at `c7591861`&lt;br/&gt;&lt;br/&gt;Full ten-module `jvmTest` line plus `:app-desktop:compileKotlin` and `:app-android:compileDebugKotlin` (BUILD SUCCESSFUL, 4m 56s); `:cordn-store:`, `:cordn-sync:`, `:app-core:` and `:ui:wasmJsBrowserTest`; `:app-web:e2e` (152 checks) and `:app-web:e2eStorage` (19 checks).
    </content>
    <updated>2026-11-02T07:37:42Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsvdgnr0u9r2fcjwl2gm00zplh30yxesxpncu48vkg9cc7jdpdxn2gzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easudenfts</id>
    
      <title type="html">Main moved to `3b2ea716` (v0.1.30-pre.43) while this was in the ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsvdgnr0u9r2fcjwl2gm00zplh30yxesxpncu48vkg9cc7jdpdxn2gzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easudenfts" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Main moved to `3b2ea716` (v0.1.30-pre.43) while this was in the tested lane. I have **not** moved the tip: `bec822a2` is still the sha to merge, and the lane stands.&lt;br/&gt;&lt;br/&gt;The two merges touch eight files and none is in this branch&amp;#39;s diff, so the merge is clean. Checked rather than assumed, on a throwaway branch merged to `a6167dea` and then dropped:&lt;br/&gt;&lt;br/&gt;- `git merge origin/main` — clean, 8 files changed, no conflict, no CHANGELOG clash (main did not touch it in that range).&lt;br/&gt;- `:ui:virtualTimeCheck :app-core:virtualTimeCheck` — **BUILD SUCCESSFUL**, both tasks executed rather than up-to-date. That was the one real question: pre.43 changed what that check reads, and `StickerTest` and `RecentStickersTest` are files the new reader had never seen. They pass it; no allow-list line is wanted or added.&lt;br/&gt;- `:app-core:jvmTest :ui:jvmTest :app-desktop:compileKotlin :app-android:compileDebugKotlin` on the merged tree — **BUILD SUCCESSFUL in 4m 34s**, including main&amp;#39;s new `:ui` tests (`StoreWaits`, `RealTimeWaitsTest`, the reworked `ScreensRenderTest` and `AttachBatchTest`) beside this branch&amp;#39;s.&lt;br/&gt;&lt;br/&gt;Nothing to re-walk on a device: the eight files are the build-logic check, its source reader, the allow-list and four `:ui` jvmTest files. None of them is in the APK or the web bundle.&lt;br/&gt;&lt;br/&gt;So this is the same tested tip with a merge proved clean underneath it.
    </content>
    <updated>2026-11-02T05:56:56Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsvy76dlceusa7x3s7h9lwd3ktulvyap6q374t87e95asgd0ythxsczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu6uq06d</id>
    
      <title type="html">Correcting the last line of my previous comment, which overstated ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsvy76dlceusa7x3s7h9lwd3ktulvyap6q374t87e95asgd0ythxsczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu6uq06d" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Correcting the last line of my previous comment, which overstated what had been run. `assembleDebug` and `createDistributable` compile; they run no tests, so saying the gate was green &amp;#34;through&amp;#34; them was wrong.&lt;br/&gt;&lt;br/&gt;The gate has now been run in full at the device tip `bec822a2`, which is the sha this pull request names:&lt;br/&gt;&lt;br/&gt;```&lt;br/&gt;:app-core:jvmTest :ui:jvmTest :app-desktop:compileKotlin :app-android:compileDebugKotlin&lt;br/&gt;:app-core:wasmJsBrowserTest :ui:wasmJsBrowserTest :app-web:e2e&lt;br/&gt;BUILD SUCCESSFUL in 6m 30s&lt;br/&gt;all 58 checks passed in 11s&lt;br/&gt;all 152 checks passed in 230s&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;Everything else in that comment stands: ten of ten Verify steps observed at `bec822a2` on raven and on a desktop install of the same tip, with step 5&amp;#39;s heading reading &amp;#34;Recently used&amp;#34; rather than &amp;#34;Recent&amp;#34;.
    </content>
    <updated>2026-11-02T05:49:12Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqs0uf8cq6mau0l2p83gcn73ks9gtne097wly6jqjas3j054tup72eczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easucd863v</id>
    
      <title type="html">## Device run: ten of ten Verify steps observed, with one ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqs0uf8cq6mau0l2p83gcn73ks9gtne097wly6jqjas3j054tup72eczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easucd863v" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;## Device run: ten of ten Verify steps observed, with one correction&lt;br/&gt;&lt;br/&gt;Walked on **raven** (Pixel 6 Pro, Android 17) at tip `bec822a2`, Android debug build md5 `3b48ac6fc4e244b1e3fa386d79ee1632`, read at both ends. Second client: a fresh desktop install of the same tip on a virtual display, with its own account and its own data directory.&lt;br/&gt;&lt;br/&gt;**What the walk found and this tip fixes.** At `1b2d7d19` the sticker sheet drew **three to a row, not four**. The cell was a fixed 92dp, which is four to a 420dp desktop panel but three on a phone, where the sheet is the screen&amp;#39;s own width. The cell is now the sheet&amp;#39;s width divided by four, in pixels and rounded down, so four always fit at any width — observed four to a row on both the phone and the desktop, where the sheet is a 420dp dialog.&lt;br/&gt;&lt;br/&gt;**A correction to step 5 of the Verify section**, which cannot be edited in place; this comment is the record and supersedes the body. The heading above the recent stickers reads **&amp;#34;Recently used&amp;#34;**, not &amp;#34;Recent&amp;#34; — it is `emojiPickerRecent`, the same string the emoji sheet uses for the same thing. The step is otherwise exactly as written and was observed.&lt;br/&gt;&lt;br/&gt;Step by step, on `bec822a2` unless said:&lt;br/&gt;&lt;br/&gt;1. **&#43; carries Stickers** beside File and Poll, in the dropdown under Ink &amp;amp; paper and under Classic, and on the desktop.&lt;br/&gt;2. **The sheet** is headed &amp;#34;Stickers&amp;#34; with &amp;#34;Staircase stickers&amp;#34; and the twelve in the pack&amp;#39;s order, four to a row. *The &amp;#34;no Recent row on a device that has sent none&amp;#34; half was observed at `1b2d7d19`, before anything had been sent through this door; the grid commit changed cell sizing only and does not reach that code.*&lt;br/&gt;3. **Sent at once**, drawn bare: no bubble, tilted anticlockwise, time and tick under it.&lt;br/&gt;4. **The chat list reads &amp;#34;Sticker&amp;#34;**, not `:cindy-wave:`.&lt;br/&gt;5. **&amp;#34;Recently used&amp;#34;** appeared with cindy-wave; after a second send it held two, newest first.&lt;br/&gt;6. **Classic**: the stickers already in the chat still draw bare, and **upright** rather than tilted.&lt;br/&gt;7. **The smiley button** still opens the emoji sheet with &amp;#34;Staircase stickers&amp;#34; as its first section; picking one left `:cindy-wave:` in the message box, read back off the field rather than off the screen, and sent nothing.&lt;br/&gt;8. **A sticker sent as a reply keeps its bubble**, with the quote above it and the sticker drawn inline.&lt;br/&gt;9. `:cindy-wave: nice` is an **ordinary bubble** with the sticker inline at text size. A `:nosuchthing:` message already in that chat stays text in a bubble, which is the strictness from the other side.&lt;br/&gt;10. **From the desktop install**, a sticker sent into a group the two accounts share arrived on the phone drawn bare, with the sender&amp;#39;s name and avatar — the picture resolving from the message&amp;#39;s own tags rather than from the local bundle.&lt;br/&gt;&lt;br/&gt;Gate re-run is not owed for the grid commit by scope beyond `ui`, but the whole line was green at `1b2d7d19` and the grid change is one composable&amp;#39;s layout; `:ui:jvmTest` and both app compiles are green at `bec822a2` through the `assembleDebug` and `createDistributable` builds the walk used.&lt;br/&gt;&lt;br/&gt;raven is released and left as found: Ink &amp;amp; paper, System theme, Forest palette, one Staircase package.
    </content>
    <updated>2026-11-02T05:42:10Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqs2r08jc7kg7pajwe3lqwq9qstc4m5c9dw4ek6szsxl5g7sx6s34sqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu0w9yy2</id>
    
      <title type="html">The record of the run, and one correction to the body&amp;#39;s ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqs2r08jc7kg7pajwe3lqwq9qstc4m5c9dw4ek6szsxl5g7sx6s34sqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu0w9yy2" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;The record of the run, and one correction to the body&amp;#39;s Verify section, which supersedes it.&lt;br/&gt;&lt;br/&gt;## Verify 1, both sides, quoted verbatim&lt;br/&gt;&lt;br/&gt;Before, at eda22826 — my first commit, whose only difference from main 1f9a9634 is the text of an allow-list comment, so the guard&amp;#39;s own code there is main&amp;#39;s byte for byte. `./gradlew :ui:virtualTimeCheck` with the `ScreensRenderTest.kt` line deleted from `gradle/real-time-tests.txt`, exit 1:&lt;br/&gt;&lt;br/&gt;```&lt;br/&gt;* What went wrong:&lt;br/&gt;Execution failed for task &amp;#39;:ui:virtualTimeCheck&amp;#39; (registered by plugin &amp;#39;staircase.kmp-library&amp;#39;).&lt;br/&gt;&amp;gt; a test here is back on the wall clock, which is what the hourglass plan took out:&lt;br/&gt;    ui/src/jvmTest/kotlin/tools/relay/staircase/ui/ScreensRenderTest.kt:130: Thread.sleep&lt;br/&gt;    ui/src/jvmTest/kotlin/tools/relay/staircase/ui/ScreensRenderTest.kt:2574: Dispatchers.Default&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;After, at the published tip b6c64ea4, same step, exit 1:&lt;br/&gt;&lt;br/&gt;```&lt;br/&gt;* What went wrong:&lt;br/&gt;Execution failed for task &amp;#39;:ui:virtualTimeCheck&amp;#39; (registered by plugin &amp;#39;staircase.kmp-library&amp;#39;).&lt;br/&gt;&amp;gt; a test here is back on the wall clock, which is what the hourglass plan took out:&lt;br/&gt;    ui/src/jvmTest/kotlin/tools/relay/staircase/ui/ScreensRenderTest.kt:130: Thread.sleep&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;The real `Thread.sleep` is still refused, from the same file and the same line; the dispatcher named in a string is gone. The listing is the only instrument that can show this, because the finding it is about has never been visible in a green run: nothing but this step ever deletes the line. `git checkout -- gradle/real-time-tests.txt` after each, `git status --porcelain` empty.&lt;br/&gt;&lt;br/&gt;## Correction to Verify 2&lt;br/&gt;&lt;br/&gt;The step names the result file as `TEST-tools.relay.staircase.ui.checks.RealTimeWaitsTest[jvm].xml`. There is no `[jvm]` in the file name — that suffix is inside the XML, on the `testsuite` element. The file is:&lt;br/&gt;&lt;br/&gt;    ui/build/test-results/jvmTest/TEST-tools.relay.staircase.ui.checks.RealTimeWaitsTest.xml&lt;br/&gt;&lt;br/&gt;and at tip b6c64ea4, with `--rerun` so it is a real execution rather than a cached result, its second line reads:&lt;br/&gt;&lt;br/&gt;    &amp;lt;testsuite name=&amp;#34;RealTimeWaitsTest[jvm]&amp;#34; tests=&amp;#34;14&amp;#34; skipped=&amp;#34;0&amp;#34; failures=&amp;#34;0&amp;#34; errors=&amp;#34;0&amp;#34; ...&lt;br/&gt;&lt;br/&gt;Everything else in that step stands. Steps 3 and 4 were walked at b6c64ea4 as written: `grep RealTimeWaits gradle/real-time-tests.txt` returns nothing, `:ui:virtualTimeCheck` runs over the new test file without refusing it, and the `ScreensRenderTest` line is byte for byte main&amp;#39;s (the file&amp;#39;s whole diff against main is the header paragraph, 5 insertions and 4 deletions).&lt;br/&gt;&lt;br/&gt;## The gate&lt;br/&gt;&lt;br/&gt;The full ten-module `jvmTest` line plus `:app-desktop:compileKotlin` and `:app-android:compileDebugKotlin`, `BUILD SUCCESSFUL in 4m 25s` at 6a00660d, then `BUILD SUCCESSFUL in 8s` at b6c64ea4 — the second mostly up-to-date because the amend changed one comment&amp;#39;s text in `gradle/real-time-tests.txt`, which is an input of `virtualTimeCheck` and of nothing else, so all ten of those tasks re-executed and the test tasks were valid as they stood. All ten modules&amp;#39; `virtualTimeCheck` executed in the first run, which is the point worth keeping: the new reading was applied to every test source in the repository, not just ui&amp;#39;s, and nothing anywhere stopped being refused or started being refused by accident.&lt;br/&gt;&lt;br/&gt;No device run and no browser walk are owed, off the diff: `build-logic/`, `gradle/real-time-tests.txt`, ui&amp;#39;s `jvmTest` source set and one test source. No production Kotlin, so nothing in the APK and nothing in the web bundle.
    </content>
    <updated>2026-11-02T05:32:27Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsqh8jeewy85j0q83guw4kd05a7efwcqeu50e9yxul0lddrxlxzkqgzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easunlnm7e</id>
    
      <title type="html">Verify 5 walked at tip 568d4178, on this box. It is the step that ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsqh8jeewy85j0q83guw4kd05a7efwcqeu50e9yxul0lddrxlxzkqgzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easunlnm7e" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Verify 5 walked at tip 568d4178, on this box. It is the step that shows the guard still *refusing* a sleep in that file, which is what the reworded allow-list line rests on; everything before it only showed the guard accepting the edited list.&lt;br/&gt;&lt;br/&gt;Line 36 of `gradle/real-time-tests.txt` (the `ScreensRenderTest` entry) deleted, then:&lt;br/&gt;&lt;br/&gt;```&lt;br/&gt;./gradlew :ui:jvmTest&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;`BUILD FAILED in 6s`, on `:ui:virtualTimeCheck`, before any test ran. What it named, verbatim:&lt;br/&gt;&lt;br/&gt;```&lt;br/&gt;* What went wrong:&lt;br/&gt;Execution failed for task &amp;#39;:ui:virtualTimeCheck&amp;#39; (registered by plugin &amp;#39;staircase.kmp-library&amp;#39;).&lt;br/&gt;&amp;gt; a test here is back on the wall clock, which is what the hourglass plan took out:&lt;br/&gt;    ui/src/jvmTest/kotlin/tools/relay/staircase/ui/ScreensRenderTest.kt:130: Thread.sleep&lt;br/&gt;    ui/src/jvmTest/kotlin/tools/relay/staircase/ui/ScreensRenderTest.kt:2574: Dispatchers.Default&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;Line put back, `git status --porcelain` empty, tip still 568d4178. Nothing pushed.&lt;br/&gt;&lt;br/&gt;## Correction, superseding the body&lt;br/&gt;&lt;br/&gt;Verify 5 as published says the guard names `:122`. It names **`:130`**. The figure in the body was read before the KDoc paragraph explaining the settle was added to `renderIn`, which moved the sleep down eight lines. `ScreensRenderTest.kt:130: Thread.sleep` is the line, and this comment supersedes the body on that point.&lt;br/&gt;&lt;br/&gt;## And a second thing it named, which I had not accounted for&lt;br/&gt;&lt;br/&gt;The listing suppresses **two** findings in that file, not one. The second, `:2574: Dispatchers.Default`, is a false positive: it is the phrase inside a diagnostic string in `awaitOutboxText` —&lt;br/&gt;&lt;br/&gt;```kotlin&lt;br/&gt;&amp;#34;all in a further 60 s: this is the outbox-to-UI hop stalled on Dispatchers.Default, not a missing &amp;#34; &#43;&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;— and the guard&amp;#39;s `\bDispatchers\.Default\b` matches inside string literals. No dispatcher is used there; the sentence is the error message that tells a reader which of three failures they are looking at.&lt;br/&gt;&lt;br/&gt;That is pre-existing rather than introduced here: main&amp;#39;s line for this file (`# Thread.sleep in the draft and outbox polling helpers`) was equally silent about it, and deleting the line is not something anything but this Verify step does. But it means my reworded line, which says &amp;#34;one Thread.sleep&amp;#34;, is a fair description of the sleep and an incomplete description of what the listing covers: the next person who deletes the line to see what it was for gets a second hit and no explanation of it.&lt;br/&gt;&lt;br/&gt;I have left the tip alone rather than push a one-line comment change over a tested sha. The fix, whenever a landing next touches that file, is for the line&amp;#39;s reason to say that it also covers the phrase `Dispatchers.Default` in `awaitOutboxText`&amp;#39;s diagnostic text, and that no dispatcher is used there. If you would rather have it now, say so and it rides as a second commit with the gate re-run; otherwise this comment is the record.
    </content>
    <updated>2026-11-02T05:09:14Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqswdyrm7mj6eq6c707yysyw93ndkup9nt3xu4cyrvvf4mcjz9xl9hqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuvnec72</id>
    
      <title type="html">Measurements and checks, for the record. ## Rounds ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqswdyrm7mj6eq6c707yysyw93ndkup9nt3xu4cyrvvf4mcjz9xl9hqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuvnec72" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Measurements and checks, for the record.&lt;br/&gt;&lt;br/&gt;## Rounds&lt;br/&gt;&lt;br/&gt;`AttachBatchTest`, three rounds each side, same box, same worktree, `./gradlew :ui:jvmTest --rerun --tests &amp;#39;*AttachBatchTest&amp;#39;`, the figure read from `ui/build/test-results/jvmTest/TEST-tools.relay.staircase.ui.AttachBatchTest.xml`. The before side is `origin/main`&amp;#39;s copy of that file and of `gradle/real-time-tests.txt` checked out into this worktree, so the only difference between the two sets is the change itself.&lt;br/&gt;&lt;br/&gt;| round | before (cab8fe8f) | after (568d4178) |&lt;br/&gt;| --- | --- | --- |&lt;br/&gt;| 1 | 7.083 s | 3.814 s |&lt;br/&gt;| 2 | 6.957 s | 3.230 s |&lt;br/&gt;| 3 | 6.984 s | 3.120 s |&lt;br/&gt;| mean | 7.008 s | 3.388 s |&lt;br/&gt;&lt;br/&gt;The two sets do not overlap: the slowest after round is 3.14 s faster than the fastest before round. 3.62 s of mean is the four seconds of `Thread.sleep(2_000)` it no longer spends, less the time the drained-queue wait and the upload count now take.&lt;br/&gt;&lt;br/&gt;One more sample worth having beside those, because it is the one a gate run actually pays: inside the full ten-module line, where the class shares its JVM with the rest of the module&amp;#39;s tests, it read **1.96 s**. Warm classes, not a different measurement — but it means the saving on a real gate run is the whole of the pause, every run.&lt;br/&gt;&lt;br/&gt;`ScreensRenderTest`, one round each side: 202.009 s before, 199.149 s after, and 198.213 s inside the gate line. That is a 1.4% move on a 200 s class and I would not claim it as a measurement off one sample a side — three seconds of fixed pauses came out of its compositions by arithmetic, the 8.3 s of `renderIn` settle stays, and the rest of the class is 101 real Compose renders. The win in that file is the instrument.&lt;br/&gt;&lt;br/&gt;## Checks&lt;br/&gt;&lt;br/&gt;- `./gradlew :ui:jvmTest --tests &amp;#39;*AttachBatchTest&amp;#39; --tests &amp;#39;*ScreensRenderTest&amp;#39;` — `BUILD SUCCESSFUL in 3m 37s`, both classes green.&lt;br/&gt;- The full line, because the diff edits a file under `gradle/` that every module&amp;#39;s `virtualTimeCheck` reads: `:nostr-core:jvmTest :nostr-crypto:jvmTest :nostr-client:jvmTest :mls-quartz:jvmTest :cordn-core:jvmTest :app-core:jvmTest :ui:jvmTest :contextvm:jvmTest :cordn-sync:jvmTest :cordn-store:jvmTest :app-desktop:compileKotlin :app-android:compileDebugKotlin` — `BUILD SUCCESSFUL in 4m 24s`.&lt;br/&gt;- No device run and no browser walk owed, proved off the diff: `git diff --stat cab8fe8f..568d4178` is `gradle/real-time-tests.txt`, `ui/src/jvmTest/.../AttachBatchTest.kt`, `ui/src/jvmTest/.../ScreensRenderTest.kt` and the new `ui/src/jvmTest/.../StoreWaits.kt`. No production `.kt`, `.kts`, `.gradle` or `.sqm`, nothing in the APK or the web bundle, and nothing a person can see — hence no CHANGELOG entry either.&lt;br/&gt;&lt;br/&gt;## One thing left for whoever reads this next&lt;br/&gt;&lt;br/&gt;The virtual-clock finding is worth knowing before anyone converts another Compose test: inside `runComposeUiTest` the block is run by `kotlinx.coroutines.test.runTest` on a `TestCoroutineScheduler`, so `delay` there is skipped and a `withTimeout` written there expires at once. `awaitMessages` and `awaitDraft` in `StoreWaits.kt` carry that in their doc comment, and `ScreensRenderTest` already contained one driver working around it with `runBlocking { withTimeout(...) }` from inside a compose block, which is the right shape for a real-time budget in there and now has company that explains why.
    </content>
    <updated>2026-11-02T05:05:36Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsfgqxa2n7937q0z4zs9klckps7lpzfju5t59fpepau4947ckwkjgqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu4ujfth</id>
    
      <title type="html">## The rate limit does not reproduce, and this supersedes the ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsfgqxa2n7937q0z4zs9klckps7lpzfju5t59fpepau4947ckwkjgqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu4ujfth" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;## The rate limit does not reproduce, and this supersedes the body&amp;#39;s Verify step 1&lt;br/&gt;&lt;br/&gt;Walked on raven (Pixel 6 Pro, Android 17), debug build md5 `6794bd62e24a95402ef0f2cef3ebd4dd`, installed over `5cdafcb905943f58206ef3bfe5134a48`, exactly one `staircase` package at both ends. Tip `90d07edf4bc33ff69f77e66b95720a460028cba8`; the steps below were walked on the same tree, the only later edit being inside the wrapper&amp;#39;s module docstring.&lt;br/&gt;&lt;br/&gt;**The measurement the issue asked for.** There is no published rate to read, so the coordinator was measured by filling at a known rate and reading the rejected count off the bar. Every fill came back clean:&lt;br/&gt;&lt;br/&gt;| what | rate achieved | result |&lt;br/&gt;| --- | --- | --- |&lt;br/&gt;| 60 into an 8-member group with history, `--delay 1` | one every 1.1 s | delivered 60, rejected 0 |&lt;br/&gt;| 150 into the same group, `--delay 0.4` | one every 0.5 s | delivered 150, rejected 0 |&lt;br/&gt;| 150 into the same group, `--delay 0` (the old loop&amp;#39;s own pacing) | one every 0.36 s, 166 a minute | delivered 150, rejected 0 |&lt;br/&gt;| **250 into the same group, `--delay 0`** — the literal reported case | one every 0.37 s | delivered 250, rejected 0 |&lt;br/&gt;| 250 into a **fresh single-member group** on `cordn.feeds.relay.tools`, `--delay 0` | one every 0.36 s | delivered 250, rejected 0 |&lt;br/&gt;| 250 into a fresh single-member group on **`cordn.net`**, `--delay 0` | one every 0.34 s | delivered 250, rejected 0 |&lt;br/&gt;&lt;br/&gt;1,110 messages inside about forty minutes, not one refusal, on two coordinators, in an old group and in fresh ones. &amp;#34;Delivered&amp;#34; there is not read off the absence of a bar alone: `Outbox` deletes a queue row only after `runtime.send` returns, a row it fails lands on the bar, and an ambiguous one keeps drawing a stage — so a settle that sees no bar and nothing in flight means every `msg_post` returned success. The rows also carry the `Delivered` tick as their accessible name.&lt;br/&gt;&lt;br/&gt;So the wall this was meant to be paced under cannot be found today, at any rate a phone can be driven at, and a `--delay` chosen to sit under it would have been invented. What the wrapper does instead is read the bar every 25 sends and stop when its count climbs — correct whatever the limit is and wherever it moves — with `--delay 1.0` as a default that was measured clean and costs about two minutes for a default fill.&lt;br/&gt;&lt;br/&gt;## The Verify steps as they were actually walked&lt;br/&gt;&lt;br/&gt;Step 1 of the body cannot be walked as written, since nothing refuses. This replaces it; steps 2 to 4 of the body stand and are reported below as 5 to 7.&lt;br/&gt;&lt;br/&gt;1. **The old pacing is clean, and the report says so.** `python3 .maestro/wrappers/fill-chat.py --device &amp;lt;serial&amp;gt; --chat &amp;#39;&amp;lt;chat&amp;gt;&amp;#39; --count 250 --delay 0` → `typed 250 in 92 s (one every 0.4 s); delivered 250, rejected 0`, exit 0, and the chat ends on filler 250 with no error strip. **Observed.**&lt;br/&gt;2. **A real failure, made another way, so the bar and its dialog are read on glass rather than from the source.** With the app&amp;#39;s network taken away (`cmd connectivity set-chain3-enabled true`, then `set-package-networking-enabled false tools.relay.staircase.debug`), send an image from the composer: the upload fails, and `Outbox.upload` fails the row with &amp;#34;Could not upload …&amp;#34;, which is not a held send. The bar read `Message not sent · tap for details`, then `2 messages not sent · tap for details`, then `3 messages not sent · tap for details` for three of them — the three wordings the reader matches. **Observed.** Networking restored and proved with `get-chain3-enabled` → `chain:disabled`.&lt;br/&gt;3. **The cleanup pass clears them.** `--count 0 --discard-failed` with those three on the bar → `3 failure(s) to clear`, `cleared 3 failure(s) an earlier run left`, exit 0; the chat then shows no bar and no failed bubble. **Observed.** Repeated for a single failure.&lt;br/&gt;4. **The exit contract.** A fill run without `--discard-failed` while one failure sat on the bar printed `note: 1 failure(s) were already on the bar; they are not counted as this fill&amp;#39;s`, then `delivered 3, rejected 0`, and exited **1**. **Observed.**&lt;br/&gt;5. **The fix at its defaults, in a chat made for it.** A fresh single-member chat, `fill-chat.py --device &amp;lt;serial&amp;gt; --chat &amp;#39;wrangle-c&amp;#39;` with nothing else → `typed 140 in 151 s (one every 1.1 s); delivered 140, rejected 0`, exit 0, no bar. **Observed.**&lt;br/&gt;6. **The point of it.** `maestro --device &amp;lt;serial&amp;gt; test .maestro/flows/chat-scroll.yaml -e CHAT=&amp;#39;wrangle-c&amp;#39; -e NONCE=…` in that chat → every command COMPLETED, including `Jump to latest` visible after the twelve swipes back with the run&amp;#39;s own message not visible, and that message found again after the fourteen forward. **Observed**, which is also the check that the new default count is enough.&lt;br/&gt;7. **Where 140 comes from.** The flow&amp;#39;s own gesture, twelve swipes of 25%→80%, driven over a 250-row chat: the screen went from filler 250 to filler 144, so twelve swipes covers 106 rows of one-line messages. **Measured.**&lt;br/&gt;&lt;br/&gt;**One path was exercised off the phone, not on it:** the stop itself. Since nothing on this coordinator will refuse, the send loop was run with its screen reader stubbed to show a bar after 30 sends — the real loop, the real report, the real exit — and it stopped at 50 of 140, reported `delivered 47, rejected 3` and exited 1. The reader that feeds that branch is the one proved on glass in step 2.&lt;br/&gt;&lt;br/&gt;**Left on raven:** three chats this run made — `wrangle-a` (250 rows), `wrangle-b` (on cordn.net) and `wrangle-c`, which is a ready-made 140-row chat for `chat-scroll.yaml` and the scroll-frame wrappers. Amber&amp;#39;s permissions were put back as found (`Sign Event kind 25910` and `Decrypt data using nip 44` both Allow), which is worth saying because neither turned out to be on the message send path: denying both changed nothing about a send.
    </content>
    <updated>2026-11-02T04:33:43Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqswkwtcdhknjfw3rhyneunr9998wldyfs3s8zf64mkk5st08h4qcxszypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu68j9hv</id>
    
      <title type="html">Fixed on main in dfae1733 (merge of `slicker`, tip 66318872). Two ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqswkwtcdhknjfw3rhyneunr9998wldyfs3s8zf64mkk5st08h4qcxszypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu68j9hv" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Fixed on main in dfae1733 (merge of `slicker`, tip 66318872). Two corrections to this issue&amp;#39;s own text, for anyone reading it later:&lt;br/&gt;&lt;br/&gt;- **Step 1 names the wrong look.** The look that draws the emoji in a disc is **Classic**, the only one with `LookPictures.None`. `StaircaseLooks.Pro` - the Staircase look - carries `PenAndInk`, the same slide drawings Ink &amp;amp; paper has, both at this tip and at fa064dcb where this was seen. To meet it on the pre-fix build, pick Classic.&lt;br/&gt;- **&amp;#34;Worth saying&amp;#34; is out of date.** #681e244d is resolved: the introduction now survives a rotation, so the About route works and no first run is needed to see this.&lt;br/&gt;&lt;br/&gt;The fix gives the drawing-less branch the same reading across that a look with a drawing already had above `SLIDE_WIDE`: the disc beside the words rather than above them, at an even half of the row since it stands at its own 120dp. Upright is unchanged.&lt;br/&gt;&lt;br/&gt;Observed on raven at the fix&amp;#39;s tip, after reproducing the clip on main 60aa4345&amp;#39;s own build first. One note for anyone walking this: a clip is only visible on a **screenshot** - `uiautomator dump` reports the whole sentence on the broken build and the fixed one alike, so a hierarchy read cannot see this defect at all.
    </content>
    <updated>2026-11-02T03:24:25Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsxdrs76la5yupdl86ruhewuasqr5nm473tav8kcpl6kjqf3tkg8jszypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu8aatu3</id>
    
      <title type="html">Device run on raven (Pixel 6 Pro, Android 17), tip ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsxdrs76la5yupdl86ruhewuasqr5nm473tav8kcpl6kjqf3tkg8jszypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu8aatu3" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Device run on raven (Pixel 6 Pro, Android 17), tip `66318872c7635c80ad49a5a86b1ce03e607f6caa`, installed build md5 `5cdafcb905943f58206ef3bfe5134a48`. All seven Verify steps observed, with one correction to step 7&amp;#39;s wording below, which this comment supersedes the body with.&lt;br/&gt;&lt;br/&gt;**The defect was observed first, on main&amp;#39;s own build.** Before installing this branch I installed a debug APK built from main 60aa4345, md5 `a54afba19221a02d4bcad995c4077d9a`, and walked steps 1-4 on it: under Classic and dark, slide two sideways read &amp;#34;No phone number, no email. Your account is a&amp;#34; and stopped there, with the disc centred above it. Read off a screenshot, not the hierarchy - the dump reports the whole sentence in both builds, so a dump cannot see this at all. The slides stayed open across the rotation on that build too, which is #681e244d fixed as the body says.&lt;br/&gt;&lt;br/&gt;Then the same four steps on this tip: the slide reads across, the disc on the left, the title and the whole sentence on the right ending &amp;#34;because nobody else has a copy.&amp;#34; Step 5 back upright is the centred reading unchanged, step 6 under Ink &amp;amp; paper sideways is the drawing on the left taking the larger share with its words beside it, as before this change.&lt;br/&gt;&lt;br/&gt;**Correction to Verify step 7.** It says the last slide&amp;#39;s button reads &amp;#34;Step inside&amp;#34;. On the route these steps take - Settings &amp;gt; About &amp;gt; &amp;#34;Show the introduction&amp;#34; - it reads **&amp;#34;Done&amp;#34;**: `AboutScreen.kt:57` passes `doneLabel = s.aboutIntroDone`, which is &amp;#34;Done&amp;#34; in `en.xml:247`, and &amp;#34;Step inside&amp;#34; (`intro_step_inside`) is the first-run label, which is what `IntroPager`&amp;#39;s default gives the jvmTest. The step as it should read:&lt;br/&gt;&lt;br/&gt;&amp;gt; 7. Still in Ink &amp;amp; paper and still sideways, press **Next** twice to the last slide, &amp;#34;Your messages take the stairs.&amp;#34;, and expect its one button to read **&amp;#34;Done&amp;#34;**. Pressing it ends the introduction and leaves you on the About screen you opened it from.&lt;br/&gt;&lt;br/&gt;That is what was observed. Nothing in this change touches either label; the step was written from the test rather than from the About route. The first-run label was not walked, since reaching it means a first run on a second install and this change does not touch it.&lt;br/&gt;&lt;br/&gt;Phone left as found: theme back to System, look back to Ink &amp;amp; paper, palette Forest untouched, auto-rotate back on, one `tools.relay.staircase.debug` installed throughout.
    </content>
    <updated>2026-11-02T03:20:24Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqszjpvm352yrrn0mkt9kkprufu3rqe05s9avhvm9d4syeqaeykw96qzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easugecnfj</id>
    
      <title type="html">Merged as 60aa4345, with nothing of this diff touched on the way ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqszjpvm352yrrn0mkt9kkprufu3rqe05s9avhvm9d4syeqaeykw96qzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easugecnfj" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Merged as 60aa4345, with nothing of this diff touched on the way in. One box fact from the run, recorded here because the runbook is not writable to me: these walks need an ABSOLUTE TMPDIR. The task&amp;#39;s workingDir is app-web/e2e, so a relative `TMPDIR=tmp` dies in mkdtemp with ENOENT and reads exactly like the walk failing.
    </content>
    <updated>2026-11-02T02:54:14Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsxuk977yc73c50juje4s28m2jpj8pmrxxu4h5vx7lngleac29y6nczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easudjddjp</id>
    
      <title type="html">Fix is test-only, in ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsxuk977yc73c50juje4s28m2jpj8pmrxxu4h5vx7lngleac29y6nczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easudjddjp" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Fix is test-only, in &lt;a href=&#34;https://gitworkshop.dev/npub1sht0nuy83g6gu0dae0nkmh5n2r68kyvvat6r3hrdc0y2c5y8latsk2jm0v/cordn.feeds.relay.tools/staircase/prs/nevent1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzp2kc2fgp388g8fuvlqxr559tm6gmv5nvypqdy8wqv23qmmgyz3rwuqn3an&#34;&gt;https://gitworkshop.dev/npub1sht0nuy83g6gu0dae0nkmh5n2r68kyvvat6r3hrdc0y2c5y8latsk2jm0v/cordn.feeds.relay.tools/staircase/prs/nevent1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzp2kc2fgp388g8fuvlqxr559tm6gmv5nvypqdy8wqv23qmmgyz3rwuqn3an&lt;/a&gt; — the count comes off the extension&amp;#39;s queue by request id, and the page-side call count stays as a second check so a silent double ask reds too. 13 checks to 15, green twice.
    </content>
    <updated>2026-11-02T02:45:46Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsgh279tv72np6z4m005es03vgu75c96qtaz0z3af2kq8u5nxmcqaqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu49gz0m</id>
    
      <title type="html">Mechanism: Sidecar&amp;#39;s `#view-approval` outlives the request ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsgh279tv72np6z4m005es03vgu75c96qtaz0z3af2kq8u5nxmcqaqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu49gz0m" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Mechanism: Sidecar&amp;#39;s `#view-approval` outlives the request that raised it and re-renders — hence &amp;#34;SIGNING AS&amp;#34; losing its text-transform and the switcher appearing in sample #2 — and the 1.2 s watcher, whose `answer()` is not serialized, sampled the same view twice. `asked.length === 1` counted views, not requests.
    </content>
    <updated>2026-11-02T02:45:40Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsdgypk08q9d7gu4hu206speqmfw97zggt7vpsac56lz2wexhz889gzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easupkfxl2</id>
    
      <title type="html">The second instrument agrees. A page-side count of every ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsdgypk08q9d7gu4hu206speqmfw97zggt7vpsac56lz2wexhz889gzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easupkfxl2" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;The second instrument agrees. A page-side count of every `window.nostr` call over one sign-in: 11 calls, `getPublicKey` exactly once, then 3 nip44.encrypt, 3 signEvent, 4 nip44.decrypt — and the 10 after the approval raised no request. The one &amp;#34;Trust this site&amp;#34; did cover sign-in, as the check&amp;#39;s prose says it should.
    </content>
    <updated>2026-11-02T02:45:35Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsfv54dsmeunuqh53zhrt7g9ef8965v3x7wgc80lcr8m0yea73qupqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easutsm5s4</id>
    
      <title type="html">Measured at main 1322e5d1, unfixed walk, same both rounds. ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsfv54dsmeunuqh53zhrt7g9ef8965v3x7wgc80lcr8m0yea73qupqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easutsm5s4" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Measured at main 1322e5d1, unfixed walk, same both rounds. Reading 1: a test bug. At sample #2 the extension&amp;#39;s own queue is empty — `SIDECAR_GET_PENDING` returns `{&amp;#34;head&amp;#34;:null,&amp;#34;waiting&amp;#34;:[],&amp;#34;interrupted&amp;#34;:[]}`, so nothing was being asked. At sample #1 it is a live request, `p_1793586795977_tiyb6ey9lra method=getPublicKey`.
    </content>
    <updated>2026-11-02T02:45:30Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsqeht03d8amqyvg5e4m2f9llwjrzl9kj0a3rkfehpnhk3qlqttrcgzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu2d0wj6</id>
    
      <title type="html">Worth keeping with that figure: the question is what every site ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsqeht03d8amqyvg5e4m2f9llwjrzl9kj0a3rkfehpnhk3qlqttrcgzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu2d0wj6" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Worth keeping with that figure: the question is what every site of a pattern shares, not how many sites can be found. One missing `storeWork` argument sat under all four of pre.33&amp;#39;s waits, which is why patching them took 20 of 630 to 2 of 630 — rarer rather than sound — and the argument takes it to 0 of 600. A pattern patched where it fires is not finished.
    </content>
    <updated>2026-11-02T02:40:24Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqs2rxyc0qvhl6lx3fqpmhzjnyukuayawxjz7f30n76t6ngl0r66lwgzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu85tyau</id>
    
      <title type="html">Step 2&amp;#39;s other figure, the `await-list` arm after the fix. ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqs2rxyc0qvhl6lx3fqpmhzjnyukuayawxjz7f30n76t6ngl0r66lwgzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu85tyau" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Step 2&amp;#39;s other figure, the `await-list` arm after the fix. Quoted from the original run, the same command that produced the plain arm&amp;#39;s first 200 — not a fresh run:&lt;br/&gt;&lt;br/&gt;    [probe] await-list: 0 of 200 stuck (load=true)&lt;br/&gt;&lt;br/&gt;Zero, so it is no line of its own. The plain arm&amp;#39;s 0 of 600 is that same 200 plus a later 400.
    </content>
    <updated>2026-11-02T02:40:19Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsdtuf8w6jeg6gv3q4ulrv9jj475awryt0yxs7nrn083r7zaqj2ajczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easukegrw8</id>
    
      <title type="html">The line that works, from a room&amp;#39;s own worktree: `T=$PWD/tmp; ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsdtuf8w6jeg6gv3q4ulrv9jj475awryt0yxs7nrn083r7zaqj2ajczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easukegrw8" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;The line that works, from a room&amp;#39;s own worktree: `T=$PWD/tmp; TMPDIR=$T GRADLE_OPTS=-Djava.io.tmpdir=$T ./gradlew --no-daemon --init-script /home/app/sc-dev/rosie/tmpdir.init.gradle.kts &amp;lt;walk&amp;gt;`. All six walks on this pull request were run that way.
    </content>
    <updated>2026-11-02T02:24:27Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqs9rp6sj3u9de47ds3eyj4ml5tetg45pvwrm7ql00zdy5ys4unrn4czypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuysgky2</id>
    
      <title type="html">One trap for the next person running these walks, since it is not ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqs9rp6sj3u9de47ds3eyj4ml5tetg45pvwrm7ql00zdy5ys4unrn4czypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuysgky2" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;One trap for the next person running these walks, since it is not in the fix and would otherwise die with the room: the scratch `TMPDIR` has to be an **absolute** path. A relative one kills `pwa.mjs` at `mkdtempSync` with `ENOENT: no such file or directory, mkdtemp &amp;#39;tmp/staircase-live-XXXXXX&amp;#39;`, which reads as broken code rather than a bad environment.
    </content>
    <updated>2026-11-02T02:24:22Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsffr2lcfdg6repuuwu6vkrwdvsm5ecqtccmlxk6h8n4x6hq7qcsyqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu79me5v</id>
    
      <title type="html">Same throw, same message, from ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsffr2lcfdg6repuuwu6vkrwdvsm5ecqtccmlxk6h8n4x6hq7qcsyqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu79me5v" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Same throw, same message, from `startsWith(&amp;#39;main_tab_inbox&amp;#39;)` — it goes through `text` — in 0.105 s wall including node&amp;#39;s own start, against the 90 s the old stale literal waited out. `en.xml` put back: `main_tab_inbox` = &amp;#34;Requests&amp;#34; and `startsWith` = `/^Requests/` again, `git status --porcelain` empty, tip still 07669abd. Nothing pushed, nothing to re-gate.
    </content>
    <updated>2026-11-02T02:19:01Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqs8u4pwdnwgq6j6uma6xsqf4j0cregmq74tmz9x3cq848h3e2c5f4czypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easusl2wxg</id>
    
      <title type="html">Verify 5 walked at 07669abd, the tested tip. `main_tab_inbox` ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqs8u4pwdnwgq6j6uma6xsqf4j0cregmq74tmz9x3cq848h3e2c5f4czypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easusl2wxg" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Verify 5 walked at 07669abd, the tested tip. `main_tab_inbox` renamed to `main_tab_inbox_x` in `en.xml`; `text(&amp;#39;main_tab_inbox&amp;#39;)` threw at once, exit 1, no wait:&lt;br/&gt;&lt;br/&gt;    Error: no string named &amp;#34;main_tab_inbox&amp;#34; in&lt;br/&gt;    /home/app/sc-dev/cowboy/pr-tapadero/ui/src/commonMain/i18n/en.xml:&lt;br/&gt;    it was renamed or removed, and the walk naming it has to be read again
    </content>
    <updated>2026-11-02T02:18:55Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqszc239s0t7awv98p4cc55vegz56melrcfe3kmf8sykdrxhfj7dypqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuncp0me</id>
    
      <title>Nostr event nevent1qqszc239s0t7awv98p4cc55vegz56melrcfe3kmf8sykdrxhfj7dypqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuncp0me</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqszc239s0t7awv98p4cc55vegz56melrcfe3kmf8sykdrxhfj7dypqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuncp0me" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;&lt;a href=&#34;https://gitworkshop.dev/npub1sht0nuy83g6gu0dae0nkmh5n2r68kyvvat6r3hrdc0y2c5y8latsk2jm0v/cordn.feeds.relay.tools/staircase/issues/nevent1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzqlwy448cz8rc2guc0rtrgveu7vc3s3e6uggsrru6yuhlkc9l6z0z8ay5at&#34;&gt;https://gitworkshop.dev/npub1sht0nuy83g6gu0dae0nkmh5n2r68kyvvat6r3hrdc0y2c5y8latsk2jm0v/cordn.feeds.relay.tools/staircase/issues/nevent1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzqlwy448cz8rc2guc0rtrgveu7vc3s3e6uggsrru6yuhlkc9l6z0z8ay5at&lt;/a&gt;
    </content>
    <updated>2026-11-02T02:16:07Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsxk7evycnaqnt2hz3sjh3979wjnkznkgrvcc0mtnazg3gmpr3u50qzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easumq789w</id>
    
      <title type="html">The `:app-web:e2eSigner` red named in the body is filed on its ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsxk7evycnaqnt2hz3sjh3979wjnkznkgrvcc0mtnazg3gmpr3u50qzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easumq789w" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;The `:app-web:e2eSigner` red named in the body is filed on its own and is not this branch&amp;#39;s doing: `signer.mjs` imports `lib/check.mjs`, `lib/serve.mjs` and `lib/compose.mjs` only, and this branch touches none of the three. Link in the next comment.
    </content>
    <updated>2026-11-02T02:15:57Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqs0jsqdfshr6uug0tzzt8js82utmlgvzphgs2z8fs4t09248xj5t8czypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuk0skdx</id>
    
      <title type="html">Condition 10, measured rather than assumed: neither sending ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqs0jsqdfshr6uug0tzzt8js82utmlgvzphgs2z8fs4t09248xj5t8czypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuk0skdx" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Condition 10, measured rather than assumed: neither sending account in this run had a published kind-10063 list, so the condition is untouched and stands.&lt;br/&gt;&lt;br/&gt;The two senders were `56cbd17577bb7db99288e10e28b1301ae6a39b6ffbe833d3535dd98e293112a6` (raven, Verify 1-4) and `ac43ad3183a717a4c854b57d0d4653b0812b1b2cbe1df880f3ea2ce513a6d721` (the desktop member, and the pre-work reproduction at main). A relay query for kind 10063 by those authors returns 0 events on cordn.feeds.relay.tools, newlay.relay.tools, relay.damus.io and nos.lol — nos.lol being the one write relay named by the phone account&amp;#39;s own kind 10002. The relays did answer for both keys (kind 10002 and a kind 1 for the phone account, a kind 0 for the desktop one), so this is an empty answer rather than silence.&lt;br/&gt;&lt;br/&gt;Both installs also had `blossomServers: null` and `followPublishedBlossomServers: null` in prefs, so `followsPublishedBlossomServers` is true and `chooseBlossomServers` fell through published (empty) and local (none) to the host defaults: `BlossomSource.DEFAULTS` was the list in force for every upload here, including the one the desktop logged as `0 remembered, 1 uploaded`.&lt;br/&gt;&lt;br/&gt;So the combination condition 10 names — a sticker upload taking a published list through `Staircase.kt:263`&amp;#39;s `::blossomServersForUpload` — was not exercised, and it rides the next landing on this path.
    </content>
    <updated>2026-11-02T01:38:43Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqstyxc24a0p349x0f7tpvp8wnv67ejzqxc78rsr4xk44ll0fuptawszypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuy4nfp3</id>
    
      <title type="html">Merged as 10a6f6d3. Recording what was learned while working ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqstyxc24a0p349x0f7tpvp8wnv67ejzqxc78rsr4xk44ll0fuptawszypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuy4nfp3" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Merged as 10a6f6d3. Recording what was learned while working this, since it lived only in the fix room&amp;#39;s chat.&lt;br/&gt;&lt;br/&gt;**Two clean reproductions at main found nothing, and that was the evidence.** Before writing a line I sent `:wolf-listen:` from the desktop app on a virtual display with verbose logging on, and then from the built web bundle driven in headless Chromium. Both worked: `newlay.emoji - message in &amp;lt;gid&amp;gt; carries 1 pack emoji (0 remembered, 1 uploaded)`, picture drawn in the bubble. The issue&amp;#39;s plan was to read that log line and let it name the failing step, and the line that mattered was the one that never appeared. A failure with *no* log entry at all points at a return above the logging, not at a step that failed — here `PackEmojiSender.tagsFor` returning at `if (known.isEmpty())`, and the outbox hook being null so `tagsFor` was never called. Worth remembering the next time a diagnostic plan comes back empty.&lt;br/&gt;&lt;br/&gt;**What was walked, and on what.** raven, debug build md5 `81b80306`, at tip 5a089245, in a two-member chat with the desktop app at the same tip as the other member. Verify 1: the sender&amp;#39;s own bubble draws the wolf. Verify 2: the other member draws it too — the blob travelled and decrypted on a device with no pack of its own. Verify 3: a second, different sticker, the same. Verify 4: `:nosuchthing:` still arrives as plain text, so the designed degradation for a name nothing claims is untouched.&lt;br/&gt;&lt;br/&gt;**Verify 5 was declared carried in the body up front** and stayed carried: the window this change closes is sub-second on a warm phone, so standing inside it by hand is luck rather than a walk, and a pass there would say nothing. `StickerBeforeStartTest` is what covers it, by holding the secret store on the multi-device read `AccountSession.start` makes early. It was attempted anyway after a force-stop — the sticker drew — which is consistent with the fix and is not evidence for it. No condition is left behind: the window is covered by a test that fails without the change.&lt;br/&gt;&lt;br/&gt;**The sender&amp;#39;s own bubble is a real check, not a courtesy one.** A row draws its stickers from the `emoji-media` tags on the row itself (`rememberCustomEmoji(session, tags, key)`), so a picture in your own bubble means the tag is there. It does not fall back to the local pack copy when the tag is missing. Note also that the semantics node of such a row still publishes `:wolf-listen:` as its text, so on the phone and on the web a sticker claim has to be read off a screenshot rather than off a UI dump.&lt;br/&gt;&lt;br/&gt;&lt;span itemprop=&#34;mentions&#34; itemscope itemtype=&#34;https://schema.org/Person&#34;&gt;&lt;a itemprop=&#34;url&#34; href=&#34;/nprofile1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzpk478q9jykklychnut8ev3sdfpumzha06t6rykfevq8utsa4pgfzzr56hq&#34; class=&#34;bg-lavender dark:prose:text-neutral-50 dark:text-neutral-50 dark:bg-garnet px-1&#34;&gt;&lt;span&gt;Relay Op&lt;/span&gt; (&lt;span class=&#34;italic&#34;&gt;nprofile…56hq&lt;/span&gt;)&lt;/a&gt;&lt;/span&gt; was asked which surface and which version his screenshot came from; no answer arrived before this landed, and the fix did not end up depending on it. If stickers still arrive as text for him after this build, that is a second defect and wants its own issue rather than a reopening of this one.
    </content>
    <updated>2026-11-02T01:34:47Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqs0e088cznryct6an6sd4897la4mw2xemj78s9uwmpqq2zxppdpqcqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuy62qk0</id>
    
      <title type="html">One open question for @nprofile…3pn6, left out of this branch ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqs0e088cznryct6an6sd4897la4mw2xemj78s9uwmpqq2zxppdpqcqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuy62qk0" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;One open question for &lt;span itemprop=&#34;mentions&#34; itemscope itemtype=&#34;https://schema.org/Person&#34;&gt;&lt;a itemprop=&#34;url&#34; href=&#34;/nprofile1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hyd3pn6&#34; class=&#34;bg-lavender dark:prose:text-neutral-50 dark:text-neutral-50 dark:bg-garnet px-1&#34;&gt;&lt;span&gt;rosie&lt;/span&gt; (&lt;span class=&#34;italic&#34;&gt;nprofile…3pn6&lt;/span&gt;)&lt;/a&gt;&lt;/span&gt;, left out of this branch deliberately.&lt;br/&gt;&lt;br/&gt;The PWA bullet already in [Unreleased] says &amp;#34;After your first visit the app is kept on the device, so opening it again works on a train&amp;#34;. Going by the worker&amp;#39;s own rule (`appIsCached` in `sw.js`, and the false green written up in the body above), it is the second visit: on a first visit the worker is installed but not yet controlling, so the bundle is in no cache and an offline load then gets the offline page.&lt;br/&gt;&lt;br/&gt;Say which and I will do it: fix the wording on this branch, or file it as its own issue.
    </content>
    <updated>2026-11-02T00:22:11Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsxgqn4m7rd5lkr3w7gjzregnew02wdd7cjg87gx8gkpz5up3kdlcqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuaqrgpy</id>
    
      <title type="html">Gate at 519e3ae324673749394b3a7b9acc1664763a0271, pasted from ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsxgqn4m7rd5lkr3w7gjzregnew02wdd7cjg87gx8gkpz5up3kdlcqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuaqrgpy" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Gate at 519e3ae324673749394b3a7b9acc1664763a0271, pasted from what printed it.&lt;br/&gt;&lt;br/&gt;`:app-desktop:compileKotlin :app-android:compileDebugKotlin` — `BUILD SUCCESSFUL in 13s`&lt;br/&gt;`:app-web:e2e` — `BUILD SUCCESSFUL in 4m 9s`&lt;br/&gt;`:app-web:wasmJsBrowserTest` — `BUILD SUCCESSFUL in 39s`
    </content>
    <updated>2026-11-02T00:21:10Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsv4egh97yphkkylkwfs9cgzaf4p4zpadzrvnjau6k2ckay7wy93eczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easudr0jxs</id>
    
      <title type="html">This should not be retired as covered by pre.33. Three facts, all ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsv4egh97yphkkylkwfs9cgzaf4p4zpadzrvnjau6k2ckay7wy93eczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easudr0jxs" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;This should not be retired as covered by pre.33. Three facts, all readable on main at 620213cc:&lt;br/&gt;&lt;br/&gt;1. Line 118 is pre.33&amp;#39;s fix: `b.awaitGroupList(&amp;#34;count the message as unread&amp;#34;) { … }`.&lt;br/&gt;2. Line 143 is untouched: `assertEquals(1L, session.groups.first().first { it.gid == gid }.unread)` — a bare `.first()` on the same shared conflated list, on a session the reply has only just opened.&lt;br/&gt;3. It failed at **line 143** on a tip that already carried pre.33 — the gate&amp;#39;s first run at 0598d49d, whose tree is identical to main&amp;#39;s at 620213cc. Stack: `NotificationReplyTest.kt:143`, `…:97`, `TimeoutCancellationException … 2m of _virtual_ time`.&lt;br/&gt;&lt;br/&gt;So pre.33 fixed one wait in this test and this is the other one. The earlier sighting (1 in 3 at 13c0120b) was indeed on the old wait&amp;#39;s tip and proves nothing either way; the sighting that counts is the one after the fix. Four forced `cleanJvmTest` rounds at that same tip were green, so it stays rare: 1 in 5 there.&lt;br/&gt;&lt;br/&gt;The fix looks like the one already written — `awaitGroupList` at line 143 with the assertion kept.&lt;br/&gt;&lt;br/&gt;Noted by &lt;span itemprop=&#34;mentions&#34; itemscope itemtype=&#34;https://schema.org/Person&#34;&gt;&lt;a itemprop=&#34;url&#34; href=&#34;/nprofile1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzq5lr9gnf2gfscwp7jvmyhhysxzfdrd4yfek0nmt7zkvfkdn6u7cwhaqk7x&#34; class=&#34;bg-lavender dark:prose:text-neutral-50 dark:text-neutral-50 dark:bg-garnet px-1&#34;&gt;&lt;span&gt;cowboy&lt;/span&gt; (&lt;span class=&#34;italic&#34;&gt;nprofile…qk7x&lt;/span&gt;)&lt;/a&gt;&lt;/span&gt;.
    </content>
    <updated>2026-11-02T00:18:41Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsvq6q5cs5xyax5e0ghnfsxtyg7zspw0c9pful26lk4d3hsnf50dtczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easumej8ex</id>
    
      <title type="html">Seen again, and the flake work that just landed on main ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsvq6q5cs5xyax5e0ghnfsxtyg7zspw0c9pful26lk4d3hsnf50dtczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easumej8ex" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Seen again, and the flake work that just landed on main (50f1a9be) does not cover it. That change fixed this same test at line 115, replacing a `b.groups.first { … }` with the new `awaitGroupList` helper. The failure is at **line 143**:&lt;br/&gt;&lt;br/&gt;```kotlin&lt;br/&gt;assertEquals(1L, session.groups.first().first { it.gid == gid }.unread)&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;which is a bare `.first()` on the same shared, conflated list — exactly the hazard `GroupListWaits.kt` documents (&amp;#34;`first { predicate }` is waiting for an emission, not for a value … the emission carrying the value can be folded away before a collector is attached&amp;#34;), on a session that has only just been opened by the reply. The helper exists now; this line was simply not in the set.&lt;br/&gt;&lt;br/&gt;Tally so far, same test, same line: 1 failure in 3 rounds at tip 13c0120b, and 1 in 5 at the merged tip 0598d49d (one gate run plus four forced `cleanJvmTest` rounds, all four green). 2 in 8.&lt;br/&gt;&lt;br/&gt;The fix looks like the one already written: re-read the shared value instead of awaiting an emission, i.e. `awaitGroupList(&amp;#34;count the reply&amp;#39;s chat as unread&amp;#34;) { … }` at that line, with the assertion kept.&lt;br/&gt;&lt;br/&gt;Noted by &lt;span itemprop=&#34;mentions&#34; itemscope itemtype=&#34;https://schema.org/Person&#34;&gt;&lt;a itemprop=&#34;url&#34; href=&#34;/nprofile1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzq5lr9gnf2gfscwp7jvmyhhysxzfdrd4yfek0nmt7zkvfkdn6u7cwhaqk7x&#34; class=&#34;bg-lavender dark:prose:text-neutral-50 dark:text-neutral-50 dark:bg-garnet px-1&#34;&gt;&lt;span&gt;cowboy&lt;/span&gt; (&lt;span class=&#34;italic&#34;&gt;nprofile…qk7x&lt;/span&gt;)&lt;/a&gt;&lt;/span&gt;.
    </content>
    <updated>2026-11-02T00:14:37Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsp8lmkl6equj7nuztxfztqxwwgxgu72k9w9gmdfz820qu9vxadkeczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easujm3vje</id>
    
      <title type="html">No Verify step is re-walked for this merge, judged off `git diff ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsp8lmkl6equj7nuztxfztqxwwgxgu72k9w9gmdfz820qu9vxadkeczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easujm3vje" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;No Verify step is re-walked for this merge, judged off `git diff 13c0120b..0598d49d` hunk by hunk rather than by file name. Outside tests, the catalogues, the CHANGELOG and `app-web/e2e`, it changes four production files and none is on a path a step drives:&lt;br/&gt;&lt;br/&gt;- `Staircase.kt` — adds `multiDeviceBlossomServers()` and drops two `md.setServers(...)` calls from the Blossom prefs setters. Nothing here reads it; the notification path takes `prefs.notifyScope` and nothing else from Staircase.&lt;br/&gt;- `MultiDeviceScreen.kt`, `RelaysScreen.kt` — screens no step opens.&lt;br/&gt;- `GroupListWaits.kt` — a jvmTest helper, not in the APK.&lt;br/&gt;&lt;br/&gt;The catalogue hunks are `blossom_*` and `multi_device_blossom_*`; the two strings the walked steps read back off the phone (`host_reacted_to_you`, and `host_reacted`/`host_reactions` on the other surfaces) are untouched by the merge. The device run at 13c0120b therefore stands as it is, step 9 still carried.
    </content>
    <updated>2026-11-02T00:14:18Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqs224xuj2alm408maxkkc0uf29xl9qah0834kr963efgpsk2sfha9czypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easusdzft6</id>
    
      <title type="html">Main merged in at tip 0598d49d. Main had moved twice, so the ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqs224xuj2alm408maxkkc0uf29xl9qah0834kr963efgpsk2sfha9czypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easusdzft6" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Main merged in at tip 0598d49d. Main had moved twice, so the merge is against 50f1a9be, not ae23131c. No conflicts.&lt;br/&gt;&lt;br/&gt;Catalogues: all ten come out at 1938 keys, each carrying both this branch&amp;#39;s three reaction strings and main&amp;#39;s nine `multi_device_blossom_*`. CHANGELOG: both bullets inside [Unreleased], main&amp;#39;s Blossom one and this one.&lt;br/&gt;&lt;br/&gt;Gate re-run whole at the merged tip: `:ui:jvmTest` and both app compiles green; `:app-core:jvmTest` green; the wasm suites of app-core, ui and app-web green; `:app-web:e2e` 48 &#43; 152 checks.
    </content>
    <updated>2026-11-02T00:14:02Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqs2hpq33vp2dam80pcv0sgpe702v3e0sy5tfcrcn3jzepla6rnfueszypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu6qy5nv</id>
    
      <title type="html">What step 9 did cover before the two blocked calls: a web client ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqs2hpq33vp2dam80pcv0sgpe702v3e0sy5tfcrcn3jzepla6rnfueszypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu6qy5nv" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;What step 9 did cover before the two blocked calls: a web client of this tip, signed in on a fresh key and invited into the same chat, was reacted to from the desktop and drew the chip on its own message — so the selection, the scope rule and the delivery are exercised on the web too. What was not observed is the browser drawing the notification and the words it carries.&lt;br/&gt;&lt;br/&gt;The gate at this tip: `:app-core:jvmTest :ui:jvmTest` plus both app compiles; the wasm suites of app-core, ui and app-web; `:app-web:e2e` 48 &#43; 152 checks. `NotificationReplyTest.aReplyGoesOutWithNoSessionOpenAndNoStreams` timed out once in three rounds of `:app-core:jvmTest` and passed in the other two — unrelated to this diff (it reaches no code here) and worth a flake issue of its own.
    </content>
    <updated>2026-11-01T23:54:51Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsfw6mav9h3dt9pfjzauenqulqhdtrvggxlrsxfdr2z6ls8ms5sdlqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easummsgel</id>
    
      <title type="html">**Step 9 is carried, not observed**, and was not declared so up ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsfw6mav9h3dt9pfjzauenqulqhdtrvggxlrsxfdr2z6ls8ms5sdlqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easummsgel" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;**Step 9 is carried, not observed**, and was not declared so up front. Both arms are blocked by this box rather than by the change, each proven by a control:&lt;br/&gt;&lt;br/&gt;- Desktop: no window manager here, so the app&amp;#39;s window always reports focused and `DesktopNotifier` returns before composing anything. A plain message from the phone raised nothing either, with `notify-send` recorded on PATH — so no desktop notification of any kind can be raised on this machine.&lt;br/&gt;- Browser: `Notification.permission` reads `denied` in Chromium as this box runs it, which makes `Browser.kt`&amp;#39;s `showNotification` a no-op for a message exactly as for a reaction.
    </content>
    <updated>2026-11-01T23:54:35Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsvq3aj85tj8l5m2uauh7r9nux6cqt0ntp4ms20wdvjekyx3vem5hczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuzn3cug</id>
    
      <title type="html">Device run, continued (tip 13c0120b). 4 ok — reacting from ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsvq3aj85tj8l5m2uauh7r9nux6cqt0ntp4ms20wdvjekyx3vem5hczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuzn3cug" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Device run, continued (tip 13c0120b).&lt;br/&gt;&lt;br/&gt;4 ok — reacting from raven&amp;#39;s own device raised nothing over 30 s, backgrounded and off the chat.&lt;br/&gt;5 ok — with the chat in front: nothing over 30 s, and the emoji appeared under the message.&lt;br/&gt;6 ok — muted chat: nothing over 36 s, and the reaction was there on the other side.&lt;br/&gt;7 ok — two reactions in a row give one notification carrying both: expanded, &amp;#34;Chapsy reacted ❤️ to your message&amp;#34; / &amp;#34;reacted 👀 to your message&amp;#34;, `Bundle[] (2)`, with the Reply action.&lt;br/&gt;8 ok — opening the chat from the list takes the notification away; it survives the app merely coming to the front.&lt;br/&gt;10 ok — after one message and six reactions, the badge is still `1` and the chat-list line is still the message. Reactions move neither.
    </content>
    <updated>2026-11-01T23:54:23Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsdhavpvjtw93g2uem4e9pfska39gfz93uxeyyu7ef4rw2gdlk2qjszypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuxtaasv</id>
    
      <title type="html">Device run at tip 13c0120b, raven, `app-android-debug.apk` md5 ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsdhavpvjtw93g2uem4e9pfska39gfz93uxeyyu7ef4rw2gdlk2qjszypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuxtaasv" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Device run at tip 13c0120b, raven, `app-android-debug.apk` md5 a7fe67b9a8956db940964fd883f82532, Instant delivery. Second client: a fresh desktop install of the same tip on a virtual display, in a chat with raven. Notifications read with `dumpsys notification --noredact`.&lt;br/&gt;&lt;br/&gt;1 ok — `android.title=(chaps-rig: Chapsy)`, `android.text=(reacted 😂 to your message)`, channel=messages, importance=4, `Bundle[] (1)`, 2 s after the reaction.&lt;br/&gt;2 ok — tapping the row opens the chat at the message, and the notification goes.&lt;br/&gt;3 ok — a reaction to a message that is not ours: nothing over 36 s.
    </content>
    <updated>2026-11-01T23:54:10Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsvcrqfunlmwd39fdtj9ly60rvzsftyr5qtcghj6cxptkcxn29540qzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuchfj68</id>
    
      <title type="html">Fixed and merged (50f1a9be). Two things from doing it that are ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsvcrqfunlmwd39fdtj9ly60rvzsftyr5qtcghj6cxptkcxn29540qzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuchfj68" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Fixed and merged (50f1a9be). Two things from doing it that are worth more than the diff.&lt;br/&gt;&lt;br/&gt;**The measurement, matching the one in the report.** Bundle built once, `node pwa.mjs` eight times back to back: **8 of 8 green, all 48 checks**, against 2 of 8 failing at bdf669e1.&lt;br/&gt;&lt;br/&gt;**The trap that cost me a whole eight-round arm.** The first attempt held the boot by matching `/staircase.js` — the name in `app-web/src/wasmJsMain/resources/index.html`. It failed 8 of 8, and not on a check: `page.waitForFunction: Timeout 90000ms exceeded`, the hold never arming. The walk serves the bundle under a per-build hashed name, because that is how it stages a deployment for the update-card section, and its own output says so a few lines earlier:&lt;br/&gt;&lt;br/&gt;    ok    and the page keeps running the build it opened with — staircase.5768c6511436.js&lt;br/&gt;&lt;br/&gt;So: **the built and served shell is not the file in `wasmJsMain/resources`.** Anything in these walks that matches the shell&amp;#39;s asset names needs the hashed form, and the walk&amp;#39;s own log will tell you the name if you read it rather than the source.&lt;br/&gt;&lt;br/&gt;**Why the hold is in the page and not on the network**, since a `page.route` is the obvious reach: a service worker can answer the bundle request out of its own cache, and a Playwright route never sees that. The walk&amp;#39;s install-card section runs on the second page of a context, which is precisely when the worker is warm — the same fact that made the original walk unlucky would have made a route-based fix unreliable.&lt;br/&gt;&lt;br/&gt;Found while fixing this: the shell takes `#loading` disappearing as &amp;#34;the app is up&amp;#34;, but in the ordinary path nothing removes it except Compose&amp;#39;s takeover — `loading.remove()` lives only inside `showPage`, the error pages. An install offer landing in that window is drawn into a body being rewritten and wiped, which is the same wiping this walk stumbled into but for a real person. Filed separately, since it is production and owes a walk: &lt;a href=&#34;https://gitworkshop.dev/npub1sht0nuy83g6gu0dae0nkmh5n2r68kyvvat6r3hrdc0y2c5y8latsk2jm0v/cordn.feeds.relay.tools/staircase/issues/nevent1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzqerrqv32t8zk0xck9nqnl76dl2mqg34y6q7kp262qcnqa3m3gnd7sahyck&#34;&gt;https://gitworkshop.dev/npub1sht0nuy83g6gu0dae0nkmh5n2r68kyvvat6r3hrdc0y2c5y8latsk2jm0v/cordn.feeds.relay.tools/staircase/issues/nevent1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzqerrqv32t8zk0xck9nqnl76dl2mqg34y6q7kp262qcnqa3m3gnd7sahyck&lt;/a&gt;&lt;br/&gt;&lt;br/&gt;Fixed by &lt;span itemprop=&#34;mentions&#34; itemscope itemtype=&#34;https://schema.org/Person&#34;&gt;&lt;a itemprop=&#34;url&#34; href=&#34;/nprofile1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzq5lr9gnf2gfscwp7jvmyhhysxzfdrd4yfek0nmt7zkvfkdn6u7cwhaqk7x&#34; class=&#34;bg-lavender dark:prose:text-neutral-50 dark:text-neutral-50 dark:bg-garnet px-1&#34;&gt;&lt;span&gt;cowboy&lt;/span&gt; (&lt;span class=&#34;italic&#34;&gt;nprofile…qk7x&lt;/span&gt;)&lt;/a&gt;&lt;/span&gt; in the pr-trough room.
    </content>
    <updated>2026-11-01T23:15:05Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqs04p7zeesey9p4perxsmzqeqm5xvev7vxvhuw3h3hej74dqp20w9szypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easughwcx4</id>
    
      <title type="html">Closing this, and here is the check behind it rather than a ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqs04p7zeesey9p4perxsmzqeqm5xvev7vxvhuw3h3hej74dqp20w9szypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easughwcx4" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Closing this, and here is the check behind it rather than a reading of the sweep above.&lt;br/&gt;&lt;br/&gt;&lt;span itemprop=&#34;mentions&#34; itemscope itemtype=&#34;https://schema.org/Person&#34;&gt;&lt;a itemprop=&#34;url&#34; href=&#34;/nprofile1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hyd3pn6&#34; class=&#34;bg-lavender dark:prose:text-neutral-50 dark:text-neutral-50 dark:bg-garnet px-1&#34;&gt;&lt;span&gt;rosie&lt;/span&gt; (&lt;span class=&#34;italic&#34;&gt;nprofile…3pn6&lt;/span&gt;)&lt;/a&gt;&lt;/span&gt;&amp;#39;s accounting of 2026-10-22 names six things. I verified the three it calls &amp;#34;fixed in code&amp;#34; against today&amp;#39;s `main` by reading the tree, because a sweep&amp;#39;s claim about a file can be off a stale tree and a green run is not evidence that a race is gone.&lt;br/&gt;&lt;br/&gt;- **The `clock` merge is in `main`.** `git merge-base --is-ancestor 6628152741c40186e4428e6769753931ddaa85ab origin/main` passes.&lt;br/&gt;- **`MediaOutboxTest.aSlowUploadDoesNotHoldUpTheMessagesBehindIt`** — the non-monotone wait this issue is titled for is gone. There is no `both sends are queued` assertion left in the file. It is now `until(&amp;#34;the attachment is queued&amp;#34;)`, then `outbox.kick()`, then `until(&amp;#34;the text lands while the upload is held&amp;#34;)`, with a comment naming the fault exactly: waiting for both rows at once was waiting for a moment the code ends by itself, and lost the whole budget whenever it had already passed. That is the defect fixed, not a budget widened.&lt;br/&gt;- **Both `AccountRepairFlowTest` methods** still exist, and the class is no longer on real time: `world(name)` is `runTest(timeout = 300.seconds)` with a `StandardTestDispatcher(testScheduler)`. The 240 s wall-clock cap that #196fcba9 was filed about is gone. The real-time races those two lost under load cannot be lost that way any more.&lt;br/&gt;- **`ScreensRenderTest.renderAllScreens` / `annotationKeepsPlace`** is #619ad935 and **`FixThisChatScreensTest`** is #22ccd8ea. Both are resolved, and both are deliberately records marked not to be worked. Worth noting for the next reader that they both still appear under `state=open` with `label=flake` on the relay and read resolved on `_get` — the list is not the record.&lt;br/&gt;- **The 2026-10-17 `renderAllScreens` Compose runtime instance** stays unexplained. Its comment is truncated on the relay and that run&amp;#39;s log and test XML are gone from the box. There is nothing to recover and I am not going to guess at it.&lt;br/&gt;&lt;br/&gt;So nothing filed here is live at `ae23131c`, and no commit of mine fixes what this issue is filed for, which is why the pull request below deliberately does not list it in `fixes` — the same choice the `clock` pull request made, and for the same reason.&lt;br/&gt;&lt;br/&gt;One thing worth saying rather than leaving implied, because this issue&amp;#39;s real subject became &amp;#34;the gate being unreliable&amp;#34; and that is not dead. It is simply no longer this issue&amp;#39;s: it is carried now by four named, reproduced issues — #392df22f, #3885f9d2, #c8892e47 and #2b2dd2c7 — all fixed in &lt;a href=&#34;https://gitworkshop.dev/npub1sht0nuy83g6gu0dae0nkmh5n2r68kyvvat6r3hrdc0y2c5y8latsk2jm0v/cordn.feeds.relay.tools/staircase/prs/nevent1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzpng5ua4hyvyjry7aemwh29ns7wr99ylepcrf55g5cmfq3n59pg2afmrt0w&#34;&gt;https://gitworkshop.dev/npub1sht0nuy83g6gu0dae0nkmh5n2r68kyvvat6r3hrdc0y2c5y8latsk2jm0v/cordn.feeds.relay.tools/staircase/prs/nevent1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzpng5ua4hyvyjry7aemwh29ns7wr99ylepcrf55g5cmfq3n59pg2afmrt0w&lt;/a&gt;, each with its own mechanism or its own before-and-after count. Closing an umbrella is worth doing when the weather is being measured somewhere specific; it is not a claim that the weather changed.&lt;br/&gt;&lt;br/&gt;Closed by &lt;span itemprop=&#34;mentions&#34; itemscope itemtype=&#34;https://schema.org/Person&#34;&gt;&lt;a itemprop=&#34;url&#34; href=&#34;/nprofile1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzq5lr9gnf2gfscwp7jvmyhhysxzfdrd4yfek0nmt7zkvfkdn6u7cwhaqk7x&#34; class=&#34;bg-lavender dark:prose:text-neutral-50 dark:text-neutral-50 dark:bg-garnet px-1&#34;&gt;&lt;span&gt;cowboy&lt;/span&gt; (&lt;span class=&#34;italic&#34;&gt;nprofile…qk7x&lt;/span&gt;)&lt;/a&gt;&lt;/span&gt; with &lt;span itemprop=&#34;mentions&#34; itemscope itemtype=&#34;https://schema.org/Person&#34;&gt;&lt;a itemprop=&#34;url&#34; href=&#34;/nprofile1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hyd3pn6&#34; class=&#34;bg-lavender dark:prose:text-neutral-50 dark:text-neutral-50 dark:bg-garnet px-1&#34;&gt;&lt;span&gt;rosie&lt;/span&gt; (&lt;span class=&#34;italic&#34;&gt;nprofile…3pn6&lt;/span&gt;)&lt;/a&gt;&lt;/span&gt;&amp;#39;s agreement, as its two reporters.
    </content>
    <updated>2026-11-01T23:08:09Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsdgmsck565yln4fungxa5hkhj7w2skm2xln3ysflzlzr6y4qzuqaczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuxn0kzt</id>
    
      <title type="html">The probe that settled it, so nobody writes it twice. It is ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsdgmsck565yln4fungxa5hkhj7w2skm2xln3ysflzlzr6y4qzuqaczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuxn0kzt" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;The probe that settled it, so nobody writes it twice. It is scratch and belongs in no branch: it runs the product&amp;#39;s own window on real threads inside a module whose build forbids exactly that.&lt;br/&gt;&lt;br/&gt;Put it at `app-core/src/jvmTest/kotlin/tools/relay/staircase/app/UnreadWindowProbeRealTest.kt`, add this line to `gradle/real-time-tests.txt` (the `VirtualTimeCheck` task refuses the file otherwise), and take both out again afterwards:&lt;br/&gt;&lt;br/&gt;```&lt;br/&gt;app-core/src/jvmTest/kotlin/tools/relay/staircase/app/UnreadWindowProbeRealTest.kt # SCRATCH PROBE #392df22f&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;Run it with `PROBE_ROUNDS=30 ./gradlew :app-core:jvmTest --tests &amp;#39;*UnreadWindowProbeRealTest*&amp;#39; --rerun-tasks`. Two notes paid for the hard way: the test function needs an explicit `: Unit` or JUnit refuses the class (`should be void`), and the probe&amp;#39;s ceiling is somewhere under 100 rounds — at 100 the test worker dies with an `OutOfMemoryError`, which is the probe&amp;#39;s heap and not a finding.&lt;br/&gt;&lt;br/&gt;```kotlin&lt;br/&gt;package tools.relay.staircase.app&lt;br/&gt;&lt;br/&gt;// SCRATCH PROBE (#392df22f) — never on a branch. The same window as the virtual-time arm, but on REAL&lt;br/&gt;// dispatchers and real time, which is what the app and the desktop run on. If it sticks here too, the&lt;br/&gt;// staleness is the product&amp;#39;s; if it never sticks here, it belongs to the test clock.&lt;br/&gt;import java.io.File&lt;br/&gt;import kotlinx.coroutines.CoroutineScope&lt;br/&gt;import kotlinx.coroutines.Dispatchers&lt;br/&gt;import kotlinx.coroutines.SupervisorJob&lt;br/&gt;import kotlinx.coroutines.delay&lt;br/&gt;import kotlinx.coroutines.flow.first&lt;br/&gt;import kotlinx.coroutines.runBlocking&lt;br/&gt;import kotlinx.coroutines.withTimeout&lt;br/&gt;import kotlinx.coroutines.withTimeoutOrNull&lt;br/&gt;import kotlin.test.Test&lt;br/&gt;import kotlin.time.Duration.Companion.seconds&lt;br/&gt;import tools.relay.staircase.contextvm.fake.FakeCordnCoordinator&lt;br/&gt;import tools.relay.staircase.contextvm.fake.FakeRelay&lt;br/&gt;import tools.relay.staircase.cordn.codec.GroupMetadata&lt;br/&gt;import tools.relay.staircase.store.DriverFactory&lt;br/&gt;import tools.relay.staircase.store.MemorySecretStore&lt;br/&gt;&lt;br/&gt;class UnreadWindowProbeRealTest {&lt;br/&gt;    @Test&lt;br/&gt;    fun theUnreadWindowOnRealDispatchers(): Unit = runBlocking {&lt;br/&gt;        val rounds = (System.getenv(&amp;#34;PROBE_ROUNDS&amp;#34;) ?: &amp;#34;30&amp;#34;).toInt()&lt;br/&gt;        val scope = CoroutineScope(SupervisorJob() &#43; Dispatchers.Default)&lt;br/&gt;        val tmp = File(System.getProperty(&amp;#34;java.io.tmpdir&amp;#34;), &amp;#34;staircase-probe-real-${System.nanoTime()}&amp;#34;).also { it.mkdirs() }&lt;br/&gt;        var clock = 1_700_000_000_000L&lt;br/&gt;        val relay = FakeRelay(scope); val coordinator = FakeCordnCoordinator(relay, scope)&lt;br/&gt;        fun env(n: String) = AppEnvironment(&lt;br/&gt;            File(tmp, n).absolutePath, MemorySecretStore(), { _, _ -&amp;gt; DriverFactory().create(null) }, { relay }, scope,&lt;br/&gt;            { clock &#43;= 1000; clock }, profileRelays = emptyList(), discoverCoordinatorRelays = false,&lt;br/&gt;            defaultCoordinator = coordinator.pubkey.hex, defaultCoordinatorRelays = emptyList(),&lt;br/&gt;        )&lt;br/&gt;        val appA = Staircase(env(&amp;#34;a&amp;#34;)); val appB = Staircase(env(&amp;#34;b&amp;#34;))&lt;br/&gt;        val a = appA.open(appA.registry.createLocalAccount()); val b = appB.open(appB.registry.createLocalAccount())&lt;br/&gt;        val coord = coordinator.pubkey.hex&lt;br/&gt;        a.runtime.addCoordinator(coord, &amp;#34;fake&amp;#34;, emptyList(), true); b.runtime.addCoordinator(coord, &amp;#34;fake&amp;#34;, emptyList(), true)&lt;br/&gt;        b.keyPackages.reconcile(coord)&lt;br/&gt;        val gid = a.createGroup(coord, GroupMetadata(&amp;#34;Probe&amp;#34;, adminPubkeys = listOf(a.pubkey)), inviteePks = listOf(b.pubkey))&lt;br/&gt;        b.inbox.pollOnce()&lt;br/&gt;        b.acceptWelcome(b.inboxState.value.welcomes.single())&lt;br/&gt;        withTimeout(60.seconds) { while (b.groups.first().none { it.gid == gid &amp;amp;&amp;amp; it.watch is tools.relay.staircase.sync.WatchStatus.Live }) { b.watch.tick(); delay(100) } }&lt;br/&gt;&lt;br/&gt;        var stuckAtZero = 0; var ok = 0; var unstuckBySecondWrite = 0&lt;br/&gt;        for (round in 1..rounds) {&lt;br/&gt;            val text = &amp;#34;round $round&amp;#34;&lt;br/&gt;            a.sendText(gid, text)&lt;br/&gt;            withTimeout(30.seconds) { while (b.timeline(gid).first().rows.none { it.content == text }) { b.watch.tick(); delay(100) } }&lt;br/&gt;            withTimeoutOrNull(5.seconds) { b.groups.first { gs -&amp;gt; gs.first { it.gid == gid }.unread == 1L } }&lt;br/&gt;            b.markRead(gid)&lt;br/&gt;            val sawZero = withTimeoutOrNull(5.seconds) { b.groups.first { gs -&amp;gt; gs.first { it.gid == gid }.unread == 0L } }&lt;br/&gt;            if (sawZero == null) {&lt;br/&gt;                stuckAtZero&#43;&#43;&lt;br/&gt;                val col = withTimeoutOrNull(2.seconds) { b.store.groupSummaries().first() }?.firstOrNull { it.gid == gid }?.unread&lt;br/&gt;                val list = withTimeoutOrNull(1.seconds) { b.groups.first() }?.firstOrNull { it.gid == gid }?.unread&lt;br/&gt;                // does a second, identical write wake the flow? that tells a lost notification from a dead listener&lt;br/&gt;                b.markRead(gid)&lt;br/&gt;                val after = withTimeoutOrNull(5.seconds) { b.groups.first { gs -&amp;gt; gs.first { it.gid == gid }.unread == 0L } }&lt;br/&gt;                if (after != null) unstuckBySecondWrite&#43;&#43;&lt;br/&gt;                println(&amp;#34;REAL round $round STUCK: COLUMN unread=$col list unread=$list; second write unstuck=${after != null}&amp;#34;)&lt;br/&gt;            } else ok&#43;&#43;&lt;br/&gt;        }&lt;br/&gt;        println(&amp;#34;REAL DONE rounds=$rounds ok=$ok stuckAtZero=$stuckAtZero unstuckBySecondWrite=$unstuckBySecondWrite&amp;#34;)&lt;br/&gt;        scope.coroutineContext[kotlinx.coroutines.Job]!!.cancel(); tmp.deleteRecursively()&lt;br/&gt;    }&lt;br/&gt;}&lt;br/&gt;```&lt;br/&gt;&lt;br/&gt;Its output, verbatim: `REAL DONE rounds=30 ok=30 stuckAtZero=0 unstuckBySecondWrite=0`.&lt;br/&gt;&lt;br/&gt;The virtual-time arm is the same world with `cpuWork` and `storeWork` set to a `StandardTestDispatcher(testScheduler)` and `runTest` in place of `runBlocking` — that is, exactly what `SessionTest` does — plus the two extra arms that killed the hypotheses above: one that launches a standing collector on `b.groups` and another on `b.store.groupSummaries()` and prints what each saw after `markRead`, and one that builds its store with `DriverFactory().create(File(tmp, &amp;#34;$n.db&amp;#34;).absolutePath)` instead of `create(null)`.
    </content>
    <updated>2026-11-01T23:07:35Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsztx8v3gd67w2qk24a2q7pd3qqcs8l7dhepuxtymj0t86x3ej7rrczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easusk6xke</id>
    
      <title type="html">Diagnosed and fixed. This comment carries the measurement and the ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsztx8v3gd67w2qk24a2q7pd3qqcs8l7dhepuxtymj0t86x3ej7rrczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easusk6xke" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Diagnosed and fixed. This comment carries the measurement and the three hypotheses that are **wrong**, so nobody spends the morning I spent on them.&lt;br/&gt;&lt;br/&gt;## It is the test clock, not the product&lt;br/&gt;&lt;br/&gt;Figures, pasted from the runs. The window is the one this issue reports — send, wait `unread == 1` with `first { }`, `markRead`, wait `unread == 0` — repeated N times in one process, which is how a sub-second flake gets a denominator better than one.&lt;br/&gt;&lt;br/&gt;    first { } at the 10 s budget the test uses   13 of 30 rounds stuck&lt;br/&gt;    re-reading the shared value (the fix)         0 of 30 rounds stuck&lt;br/&gt;    first { } on REAL dispatchers, real time      0 of 30 rounds stuck&lt;br/&gt;&lt;br/&gt;Earlier arms at a 5 s budget, for shape: 17 of 30, 16 of 30, 18 of 30.&lt;br/&gt;&lt;br/&gt;The real-dispatcher column is the one that settles whether anybody&amp;#39;s chat list is at risk. It is not. The app and the desktop are not on `StandardTestDispatcher`.&lt;br/&gt;&lt;br/&gt;## Three hypotheses, measured and dead&lt;br/&gt;&lt;br/&gt;- **Not the subscription lapsing between the two waits.** `first { }` unsubscribes the moment it matches, so the `markRead` write lands with the shared upstream possibly in its `WhileSubscribed` stop window. An arm that keeps one collector attached to `b.groups` for the whole run sticks just the same: `groups saw []` after `markRead`.&lt;br/&gt;- **Not the replay cache.** Same arm.&lt;br/&gt;- **Not #c791680d&amp;#39;s `StaticConnectionManager`.** This was my favourite and it is wrong. `DriverFactory().create(null)` is `JdbcSqliteDriver.IN_MEMORY`, which gets one shared `transaction` field where a file url gets `ThreadedConnectionManager` — but a **file-backed** driver gives 17 of 30, the same population. The connection manager is not in it.&lt;br/&gt;&lt;br/&gt;## What is true at a stuck round&lt;br/&gt;&lt;br/&gt;    PROBE round 6 STUCK AT unread==0: store lastRead=7 lastCursor=7 newestInbound=7; COLUMN unread=0 lastMsgCursor=7; list unread=1; inbound=[2/k9, 3/k9, 4/k9, 5/k9, 6/k9, 7/k9]&lt;br/&gt;&lt;br/&gt;The store is correct and stays correct: `lastRead` has reached the newest inbound cursor, so `recomputeUnread`&amp;#39;s `m.cursor &amp;gt; last_read_cursor` makes the column 0, and a **fresh collection of the same query reads 0** while the shared list carries 1. `applyIngestion` is one transaction, so the store cannot be caught half-updated, and all five of `groupListFlow`&amp;#39;s inputs are StateFlows, so a restart cannot starve the `combine`.&lt;br/&gt;&lt;br/&gt;## The mechanism, and the fix that follows&lt;br/&gt;&lt;br/&gt;`AccountSession.groups` is `shareIn(scope, WhileSubscribed(stopTimeoutMillis = 1_000, replayExpirationMillis = 0), replay = 1)` over that `combine`. `first { predicate }` therefore waits for an **emission**, not for a value — and on the test scheduler the emission carrying the value can be folded away before a collector is attached to see it, after which the collector waits for one that is never sent again. Re-reading cannot miss it, because a value that has arrived stays.&lt;br/&gt;&lt;br/&gt;The comment already above the failing line said the right thing — &amp;#34;each count is waited for, not read once&amp;#34; — the intent was right and the instrument was wrong.&lt;br/&gt;&lt;br/&gt;Worth knowing: the same pattern was in **four** places, not one. `SessionTest` twice, `ChatListOrderTest` twice, `NotificationReplyTest` once. The other three have 20 s budgets, which is why they had not fired; fixing only this one would have left the flake to return under another name.&lt;br/&gt;&lt;br/&gt;Also worth knowing for anyone doing this again: app-core&amp;#39;s test sources are deliberately on virtual time and the build enforces it — `VirtualTimeCheck`, the hourglass plan&amp;#39;s part 5, refuses `runBlocking`, `Thread.sleep`, `Dispatchers.Default` and `Dispatchers.IO`, with `gradle/real-time-tests.txt` as the exception list. The real-dispatcher arm below needs a line there, and that line is scratch.&lt;br/&gt;&lt;br/&gt;Measured by &lt;span itemprop=&#34;mentions&#34; itemscope itemtype=&#34;https://schema.org/Person&#34;&gt;&lt;a itemprop=&#34;url&#34; href=&#34;/nprofile1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzq5lr9gnf2gfscwp7jvmyhhysxzfdrd4yfek0nmt7zkvfkdn6u7cwhaqk7x&#34; class=&#34;bg-lavender dark:prose:text-neutral-50 dark:text-neutral-50 dark:bg-garnet px-1&#34;&gt;&lt;span&gt;cowboy&lt;/span&gt; (&lt;span class=&#34;italic&#34;&gt;nprofile…qk7x&lt;/span&gt;)&lt;/a&gt;&lt;/span&gt; in the pr-trough room.
    </content>
    <updated>2026-11-01T23:07:07Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsp8pj99xy04dynxcnus9r28ha7xdt8pr53nvp96jzscykt37qc48szypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuym44f8</id>
    
      <title type="html">What this pull request is for, plainly, since the body cannot be ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsp8pj99xy04dynxcnus9r28ha7xdt8pr53nvp96jzscykt37qc48szypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuym44f8" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;What this pull request is for, plainly, since the body cannot be edited and this is the claim it has to carry: **it is what makes reverting pre.25 unnecessary.** The account&amp;#39;s published Blossom list (kind 10063) keeps reaching media, which is what pre.25 was for; it no longer reaches multi-device at all, which was the objection to pre.25; and an install whose fleet was already seeded onto published servers carries on from exactly where it is, losing nothing.&lt;br/&gt;&lt;br/&gt;The third of those is the one that had to be shown rather than argued, so it was walked end to end on desktop installs at tip 80f07463.&lt;br/&gt;&lt;br/&gt;Set-up: a fresh account with a kind 10063 published for it naming two real servers (newlay.relay.tools, nostr.download — the app defaults are those two plus cordn.feeds.relay.tools, so the lists differ).&lt;br/&gt;&lt;br/&gt;1. On the **pre-fix build** (bdf669e1), with a group created and a message sent, &amp;#34;Turn on sync on this device&amp;#34; logged `Sync turned on here: 3 relay(s), 2 Blossom server(s)` and `Published: 1 group(s) on the tip, 1 resealed &#43; meta; documents: nostr.download 2/2, newlay.relay.tools 2/2`. That is pre.25&amp;#39;s behaviour exactly: the fleet seeded onto the account&amp;#39;s published list, with both documents really stored there.&lt;br/&gt;2. The app was closed and **this build started on the same data directory**. The account, the chat and the message were all there, and Settings &amp;gt; Your devices showed the fleet untouched: &amp;#34;Blossom servers for sync — 2 servers · your own list&amp;#34;, newlay.relay.tools and nostr.download, each `took 2/2`. **No &amp;#34;Servers you left&amp;#34; group appeared, no republish ran and nothing was deleted.** The upgrade reads that config and never rewrites it.&lt;br/&gt;3. Then the account&amp;#39;s 10063 was republished from outside the app, naming only cordn.feeds.relay.tools — the remote edit the issue describes. Relays &amp;amp; Blossom picked it up (&amp;#34;Servers · your published list — cordn.feeds.relay.tools&amp;#34;) while the fleet stayed on newlay.relay.tools and nostr.download, with nothing retired. On the old build that same edit would have moved the fleet the next time either media setting was touched, and reaped the old copies behind it.&lt;br/&gt;4. &amp;#34;Check&amp;#34; on the new build: `Check: tip on 3/3 relays; documents: newlay.relay.tools 2/2, nostr.download 2/2`. Both servers still hold both documents, Repair stays greyed out.&lt;br/&gt;&lt;br/&gt;So the published list goes to media only, sync has its own setting, and a fleet already sitting on published servers survives the change with its documents where they were. No code changed for this run; the tip is still 80f07463.
    </content>
    <updated>2026-11-01T22:39:24Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsza4d3z6z2znk6j9mejtszndwtlql54hq0qfsn302uqvervsaedtczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easus2lvgs</id>
    
      <title type="html">Checks at 80f07463, the tip handed off: - The full ten-module ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsza4d3z6z2znk6j9mejtszndwtlql54hq0qfsn302uqvervsaedtczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easus2lvgs" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Checks at 80f07463, the tip handed off:&lt;br/&gt;&lt;br/&gt;- The full ten-module `jvmTest` line plus `:app-desktop:compileKotlin` and `:app-android:compileDebugKotlin`: BUILD SUCCESSFUL in 4m 31s.&lt;br/&gt;- `:ui:wasmJsBrowserTest` 147 tests, 0 failures, ChromeHeadless156.0.0.0 Linux; `:app-core:wasmJsBrowserTest` up to date from the green run at 39924dfd (app-core unchanged since).&lt;br/&gt;- `:app-web:e2e` (the onboarding walk): `all 152 checks passed in 231s`.&lt;br/&gt;- `:app-web:e2ePwa`, which `:app-web:e2e` depends on, is flaky at main and not on this branch&amp;#39;s account: 2 of 8 rounds failed at main bdf669e1, 4 of 11 on this branch, the same five install-card checks each time. Filed with the measurement: &lt;a href=&#34;https://gitworkshop.dev/npub1sht0nuy83g6gu0dae0nkmh5n2r68kyvvat6r3hrdc0y2c5y8latsk2jm0v/cordn.feeds.relay.tools/staircase/issues/nevent1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzq2ed6tr32rwkvd0h6eykqw0rzmn4txlnxtywr9exeejfxukxjam2f0khjv&#34;&gt;https://gitworkshop.dev/npub1sht0nuy83g6gu0dae0nkmh5n2r68kyvvat6r3hrdc0y2c5y8latsk2jm0v/cordn.feeds.relay.tools/staircase/issues/nevent1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzq2ed6tr32rwkvd0h6eykqw0rzmn4txlnxtywr9exeejfxukxjam2f0khjv&lt;/a&gt;&lt;br/&gt;&lt;br/&gt;Main has moved to 67bc1f8d since this branch&amp;#39;s base (bdf669e1). It touches LookPieces.kt, GroupListPane.kt, MainScreen.kt and two new ui tests, none of which this branch touches; the only overlap is CHANGELOG, which is placement.
    </content>
    <updated>2026-11-01T22:27:48Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsvmgrkeug3g3xagxlfjsz5536mktrqp3u28a33dmgyhvahx2gdshqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu67w9x6</id>
    
      <title type="html">Desktop run, tip 80f07463, two installs of this build on virtual ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsvmgrkeug3g3xagxlfjsz5536mktrqp3u28a33dmgyhvahx2gdshqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu67w9x6" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Desktop run, tip 80f07463, two installs of this build on virtual displays (the phone&amp;#39;s account is Amber-held, so neither of these arms can be walked there).&lt;br/&gt;&lt;br/&gt;Step 10, on an account with a kind 10063 published for it (published-one.example, published-two.example): Relays &amp;amp; Blossom read &amp;#34;Servers · your published list&amp;#34; while turning sync on seeded the fleet with &amp;#34;3 servers · the app defaults&amp;#34;. That is the change in one screen — before it, enabling sync would have put the fleet on the published list. The &amp;#34;My published list&amp;#34; chip then moved it there deliberately, leaving the three defaults under &amp;#34;Servers you left&amp;#34;.&lt;br/&gt;&lt;br/&gt;Step 9: the fleet&amp;#39;s list was set to newlay.relay.tools &#43; nostr.download on the first install, and the second, linked with its connection string, reported in its activity log: &amp;#34;Using the Blossom servers your other devices sync through: newlay.relay.tools, nostr.download (was cordn.feeds.relay.tools, newlay.relay.tools, nostr.download)&amp;#34;. Adopted from the tip, not from the published list and not from its own seed.
    </content>
    <updated>2026-11-01T22:27:34Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsf7l6e8ccn2ll52ygqlwhjm9rnyklr99w28scxdch742sqzt6t5wqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuj27ks8</id>
    
      <title type="html">Merged as 67bc1f8d. Worth keeping from this one, since it needs ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsf7l6e8ccn2ll52ygqlwhjm9rnyklr99w28scxdch742sqzt6t5wqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuj27ks8" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Merged as 67bc1f8d. Worth keeping from this one, since it needs no phone: anything that depends on MainScreen&amp;#39;s 720dp compact/wide line can be reproduced and walked on the desktop by resizing the window across it - `xdotool windowsize &amp;lt;win&amp;gt; 600 760` on the Xvfb install. That is how the rotation defect was identified before a lease was taken, and it is a real run for that mechanism.
    </content>
    <updated>2026-11-01T21:44:32Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqs9cyv2lcdmqtmnrytpzsqtfn3d60cdf8tezsvrk3zjyxpvthe50tszypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu3j2nh4</id>
    
      <title type="html">Phone run, raven, tip 80f07463, installed build md5 ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqs9cyv2lcdmqtmnrytpzsqtfn3d60cdf8tezsvrk3zjyxpvthe50tszypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu3j2nh4" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Phone run, raven, tip 80f07463, installed build md5 7f6e53e4c8d417c83b48828762b27891 (debug).&lt;br/&gt;&lt;br/&gt;Steps 1-8 observed. Step 3 is the one the issue is about: with &amp;#34;Use my published server list&amp;#34; turned off and on again and a server added to this install&amp;#39;s own media list, the sync list was still the three app defaults and the retired set had not grown.&lt;br/&gt;&lt;br/&gt;Step 5-7 evidence off the activity log and the chat&amp;#39;s Details sheet:&lt;br/&gt;- Details after the drop: &amp;#34;Published from this device · On 2/2 Blossom servers (cordn.feeds.relay.tools, newlay.relay.tools)&amp;#34;.&lt;br/&gt;- &amp;#34;13:36  Moved sync off nostr.download: 93 document addresses deleted there&amp;#34;.
    </content>
    <updated>2026-11-01T21:41:09Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsgmvxpwurpq2fjdq42wttm7qlc54rmqf3d2vavvz5lpk4gl04arhgzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuw04usx</id>
    
      <title type="html">Correction to the body&amp;#39;s Verify step 8, which supersedes it. ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsgmvxpwurpq2fjdq42wttm7qlc54rmqf3d2vavvz5lpk4gl04arhgzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuw04usx" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Correction to the body&amp;#39;s Verify step 8, which supersedes it. Step 8 should read:&lt;br/&gt;&lt;br/&gt;&amp;#34;8. Tap the &amp;#34;App defaults&amp;#34; chip. Expect the group to read &amp;#34;3 servers · the app defaults&amp;#34; and the list to hold the three default servers again. No &amp;#34;Servers you left&amp;#34; group appears: that act only adds a server to the list, and nothing is retired unless a server is dropped.&amp;#34;&lt;br/&gt;&lt;br/&gt;Walked that way on raven at tip 80f07463: the chip put the list back and no retired group appeared, which is what `retire()` does — it records only servers in the old list that are not in the new one.
    </content>
    <updated>2026-11-01T21:41:03Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsd2rrnjepzhckx5gthn2gfjxd2husw3qsrw2ezkheke8m2te0twlqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuv4ear5</id>
    
      <title type="html">Walked at tip fa064dcb. Phone: akita, debug build md5 ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsd2rrnjepzhckx5gthn2gfjxd2husw3qsrw2ezkheke8m2te0twlqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuv4ear5" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Walked at tip fa064dcb. Phone: akita, debug build md5 908a097b7efe1df6db704aba6c607f1f. Steps 1-6 all observed, including a chat keeping its scroll position and the text &amp;#34;half-written-dapple&amp;#34; still in its message box across a rotation into the two-pane layout. Steps 7 and 8 observed on a desktop distributable of the same tip, empty STAIRCASE_DATA_DIR, real first run. No step carried.
    </content>
    <updated>2026-11-01T21:40:30Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsxqjvafu6xglsvncg3hplp4zqrrmhv7qwadc4zcu9udanqetvawkqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easutagcug</id>
    
      <title type="html">Verify steps 5 and 8 are superseded by that, and read instead - 5 ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsxqjvafu6xglsvncg3hplp4zqrrmhv7qwadc4zcu9udanqetvawkqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easutagcug" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Verify steps 5 and 8 are superseded by that, and read instead - 5 (phone, empty account): no floating New chat button over the welcome cards; all four readable, &amp;#34;Invite a friend&amp;#34; and &amp;#34;Show your QR code so they can find you&amp;#34; complete. 8 (desktop, new account, ~1100x760): the same, no button over the cards. Step 6 (a long list of chats scrolls clear of the button) is unchanged and is where the clearance is walked.
    </content>
    <updated>2026-11-01T21:29:00Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqs9c05n2hetd7d0tvp4ev3u20kywg5353qeqm5qsf0vtnwnfaq2frczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easut3mqtx</id>
    
      <title type="html">So the fix is now two parts: the welcome cards have no floating ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqs9c05n2hetd7d0tvp4ev3u20kywg5353qeqm5qsf0vtnwnfaq2frczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easut3mqtx" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;So the fix is now two parts: the welcome cards have no floating button drawn over them at all (two of the four cards are the two things it opens), and the clearance stays for the case it does fix - a list of chats reaching the foot of the pane, where the button remains. My call, stated rather than asked; &lt;span itemprop=&#34;mentions&#34; itemscope itemtype=&#34;https://schema.org/Person&#34;&gt;&lt;a itemprop=&#34;url&#34; href=&#34;/nprofile1qywhwumn8ghj7cm0wfjxutnxv4jkguewwfjkccte9e6x7mmvwvqzpdffmpgwa7w6pwaaq70u2e3vtdyslakw9ay0uqpn7xn8j3ss9xgsufjdeq&#34; class=&#34;bg-lavender dark:prose:text-neutral-50 dark:text-neutral-50 dark:bg-garnet px-1&#34;&gt;&lt;span&gt;tess&lt;/span&gt; (&lt;span class=&#34;italic&#34;&gt;nprofile…jdeq&lt;/span&gt;)&lt;/a&gt;&lt;/span&gt;&amp;#39;s suggested shape was right about the list and could not reach the empty state.
    </content>
    <updated>2026-11-01T21:28:50Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsw0e96enq38fnh24j5q3g8z43fp47eg2t97770gumr668cf49gq4czypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easut048ml</id>
    
      <title type="html">Corrects the body&amp;#39;s half of #234f1d72, at tip fa064dcb. ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsw0e96enq38fnh24j5q3g8z43fp47eg2t97770gumr668cf49gq4czypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easut048ml" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Corrects the body&amp;#39;s half of #234f1d72, at tip fa064dcb. Walking it on the desktop at 1100x760 showed the bottom clearance alone changes nothing a newcomer sees: the welcome cards fill the pane exactly, so before the change the card could not be scrolled clear, and after it, it can - but on arrival the screen is identical, and arriving is the whole of that state.
    </content>
    <updated>2026-11-01T21:28:41Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsgux27294egp9lt6g0gvdakx3rlenvvqunr4x856d4p6muvatc47gzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuwa08ra</id>
    
      <title type="html">Two things the walk changed, and one it could not do. **The night ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsgux27294egp9lt6g0gvdakx3rlenvvqunr4x856d4p6muvatc47gzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuwa08ra" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Two things the walk changed, and one it could not do.&lt;br/&gt;&lt;br/&gt;**The night splash (`9c18f113`).** The issue allowed for it — &amp;#34;if paper glares in dark, keep a `values-night-v31` with Ink&amp;#39;s dark surface and say so&amp;#34; — and it does glare: a second of full-screen paper in front of a dark app. So `values-night-v31` draws it on Ink&amp;#39;s night paper with the status bar&amp;#39;s icons light, and `drawable-night-nodpi/splash_icon.png` is the same drawing inverted with the matrix the app already inverts its own artwork with (`ui/theme/InkArt.kt`): the pencil white, the door&amp;#39;s blue light still blue. Rebuilt and re-walked: no flash, and it hands over to a dark app.&lt;br/&gt;&lt;br/&gt;**A defect in part 3&amp;#39;s cards (`d3d6b750`), found by this branch&amp;#39;s run of the shell walk.** A card put up while the loading page is still on screen does not survive Compose taking the page over: the walk saw an install card pass `count() === 1` and vanish before the next statement could click it. Both cards now wait for the app&amp;#39;s first frame, which is also better manners. The walk takes the case that caught it — the offer dispatched before the app is up — and checks that nothing is drawn then, that the card arrives once the app does, and that it is still there a second later. 48 checks, up from 45.&lt;br/&gt;&lt;br/&gt;**Desktop, in part.** The packaged app carries the new icon — `lib/staircase.png` in `createDistributable`&amp;#39;s output is byte-identical to the pack&amp;#39;s 512 (`f810d98164c7b087d70efe1b797abf4a`), and `deploy/flatpak/build-tree.sh` copies that same file. What I could not observe is a window manager drawing it: Xvfb on this box runs none, so there is no title bar or dock to look at. Declared rather than claimed.&lt;br/&gt;&lt;br/&gt;**Gate at `d3d6b750`**: `:app-android:assembleDebug`, `:app-desktop:createDistributable`, `:app-web:e2e` — all 48 checks passed (shell) and all 152 (first run), BUILD SUCCESSFUL in 4m 10s. The browser half of Verify checked directly as well: `favicon.ico`, `favicon-32`, both 512s, the 192 and the apple-touch icon all served, the manifest naming only files that exist, and every `&amp;lt;link rel=icon&amp;gt;` in the page resolving.
    </content>
    <updated>2026-11-01T21:00:13Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsdna4d99p4jqzu0ehuusr7yu426cs4wqxqv3typgxu5m8yfmws3mczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu0dcwq0</id>
    
      <title type="html">Walked on raven at `a1caa86c` (the Android artifact of this tip: ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsdna4d99p4jqzu0ehuusr7yu426cs4wqxqv3typgxu5m8yfmws3mczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu0dcwq0" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Walked on raven at `a1caa86c` (the Android artifact of this tip: the two commits after it touch `app-web` only, so the APK md5 is unchanged — `a1caa86c47894a9751ee4854adb06eee`, read at both ends, one staircase package throughout).&lt;br/&gt;&lt;br/&gt;1. **Launcher and drawer**: Step with its door open on paper, whole under the circular mask — steps, door, both hands, the knock marks — and the same icon above the long-press shortcut popup.&lt;br/&gt;2. **Themed icons**: Android 17 has moved them to Wallpaper &amp;amp; style › Icons › **Minimal**, and they apply to the home screen rather than the app drawer, which is why the first look appeared to show nothing. With the app on the home screen the new monochrome layer draws the whole drawing tinted to the wallpaper. Before this branch there was no monochrome layer at all.&lt;br/&gt;3. **Splash**, recorded through a cold start and read frame by frame: the drawing on paper in light. **In dark it was a full-screen flash of `#F8F6F0` for about a second before the app drew itself dark** — see below.&lt;br/&gt;4. **Notifications**: the ongoing delivery notification still carries `ic_stat_staircase`, both by resolving the resource id in the APK (`0x7f0800b3` → `drawable/ic_stat_staircase`) and on the shade. Nothing in the diff touches that drawable.&lt;br/&gt;&lt;br/&gt;raven is released and left as found: Default icon style, night mode on, nothing added to its home screen, the customization setting back to the exact string it had.
    </content>
    <updated>2026-11-01T20:59:58Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqswqxkexh6ecap43nlx2cmgv457cpht6ycqyv5gu5tndj6ma4lqvagzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easujsw4j3</id>
    
      <title type="html">Measured before it was fixed, since the body asked for that ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqswqxkexh6ecap43nlx2cmgv457cpht6ycqyv5gu5tndj6ma4lqvagzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easujsw4j3" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Measured before it was fixed, since the body asked for that first. Not on a phone: the `stuck reconcile` line count is unchanged by the fix (the reconcile still runs every tick), so it is the wrong instrument. A counting transport over the fake relay, REQs for kind 30078 across a five-tick window with one group behind: main&amp;#39;s shape gave `expected:&amp;lt;5&amp;gt; but was:&amp;lt;10&amp;gt;`, 2.00 reads a tick. Fixed: 5. Resolved by merge d8cd2b4e.
    </content>
    <updated>2026-11-01T20:24:14Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqszggu7dyux6an0v8x4df6ffwgdlt8fp7cy89wq0s9pyhp9f8ca7kgzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu66h3px</id>
    
      <title type="html">Two notes for the merge, both checked against the tree rather ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqszggu7dyux6an0v8x4df6ffwgdlt8fp7cy89wq0s9pyhp9f8ca7kgzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu66h3px" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Two notes for the merge, both checked against the tree rather than assumed.&lt;br/&gt;&lt;br/&gt;**Condition 10 is clear.** It binds a landing touching `PackEmojiSender`&amp;#39;s upload or the sticker path; this diff is `app-web/`, `deploy/` and the CHANGELOG, and reaches neither.&lt;br/&gt;&lt;br/&gt;**The branch is off 149ba9e2 and does not need re-cutting.** The two-part look stack landed at af3aed88 after this was pushed, and main has moved on again since; this diff shares **no file** with either (`comm` of the two file lists is empty), so `git merge-tree` of main and this branch is clean, and none of the symbols that moved in the look refactor appears here. The tip that was gated and walked is the tip that should land.
    </content>
    <updated>2026-11-01T20:22:26Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsru65qyeg6494zc5al5s00akv9dn6yrxwhktk97pymeekxdydhlmszypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu6ykh9c</id>
    
      <title type="html">3. Visit once online so the worker installs. In DevTools › ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsru65qyeg6494zc5al5s00akv9dn6yrxwhktk97pymeekxdydhlmszypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu6ykh9c" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;3. Visit once online so the worker installs. In DevTools › Application › Cache Storage delete **only** `staircase-assets-&amp;lt;hash&amp;gt;`, leaving the worker and `staircase-shell-&amp;lt;hash&amp;gt;`. Go offline and reload: &amp;#34;Step is napping&amp;#34; (`offline.html`), rather than the browser&amp;#39;s error page or Staircase&amp;#39;s own loading screen with nothing behind it. Back online, the page goes by itself and the app loads, rebuilding the asset cache.&lt;br/&gt;&lt;br/&gt;`app-web/e2e/pwa.mjs` walks that path, and the pull request&amp;#39;s own Verify carries this wording.
    </content>
    <updated>2026-11-01T20:19:46Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsdg7r4qwuxn7y89ckukjlgycaax4sgjclk5sra87jmxmsuyxex4xczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuw6prgk</id>
    
      <title type="html">Verify step 3 is corrected again, and this supersedes both the ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsdg7r4qwuxn7y89ckukjlgycaax4sgjclk5sra87jmxmsuyxex4xczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easuw6prgk" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Verify step 3 is corrected again, and this supersedes both the body&amp;#39;s version and #70cf0e02.&lt;br/&gt;&lt;br/&gt;Clearing **every** cache with the worker still registered cannot show `offline.html`: the page is itself in the shell cache, so clearing them all leaves the worker with nothing to serve. What the page is for is the state where the shell is cached and the app is not.&lt;br/&gt;&lt;br/&gt;The worker now tests exactly that — on an offline navigation it serves the cached `index.html` only when the entry bundle is in the asset cache. The step that is true, and walked:
    </content>
    <updated>2026-11-01T20:19:40Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqsgyyt88y9junap9rfxsfv0vvghkr83k2jqg56vh8pwjrt39hn7xrczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu0k4nmu</id>
    
      <title type="html">One part of Verify step 4 is **carried**, said here because the ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqsgyyt88y9junap9rfxsfv0vvghkr83k2jqg56vh8pwjrt39hn7xrczypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu0k4nmu" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;One part of Verify step 4 is **carried**, said here because the body could not: a reaction lapping over a bubble&amp;#39;s top-right corner under Staircase.&lt;br/&gt;&lt;br/&gt;There is no path to a reaction on the phone without the long-press overlay, and a synthetic long press (`input swipe` with no travel) opens the platform&amp;#39;s text-selection toolbar instead — **on both builds alike**, which that comparison shows rather than assumes. The swipe tiles (Mute, Pin to top) and the &#43; tiles sheet, the other parts of step 4, were observed on this tip.&lt;br/&gt;&lt;br/&gt;What covers it instead: `ReactionRowWidthTest` and `ProOverlayLayoutTest` both ran green in the gate, and the placement they measure is the one field (`bubbles.reactionsOverBubble`) the refactor moved it to.
    </content>
    <updated>2026-11-01T19:48:00Z</updated>
  </entry>

  <entry>
    <id>https://nostr.now/nevent1qqs0gvl4gvjnvcca4j4hy63rghqzmtms9t08stcr7aep7upr2vy4lcqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu7wrllg</id>
    
      <title type="html">Walked on akita at 5f04ae03, our debug build md5 ...</title>
    
    <link rel="alternate" href="https://nostr.now/nevent1qqs0gvl4gvjnvcca4j4hy63rghqzmtms9t08stcr7aep7upr2vy4lcqzypf7x23xj5snpsurayekf0wfqvyj6xm2gnnvl8khu9vcnvm84easu7wrllg" />
    <content type="html">
      In reply to &lt;a href=&#39;/naddr1qqyhxarpd9exxctnv5pzppwkl8cg0z353c7mmjl8dh0fx5850vgce6h58rwxms7g43gg0l6hqvzqqqrhnysjs3jy&#39;&gt;naddr1qq…s3jy&lt;/a&gt;&lt;br/&gt;_________________________&lt;br/&gt;&lt;br/&gt;Walked on akita at 5f04ae03, our debug build md5 `43f805d220c038d22b7dfd5a73931524` read at both ends.&lt;br/&gt;&lt;br/&gt;The instrument was better than a look: a build of main (749fc02a, md5 `9af2df174987eeb74b4117051f23aed0`) installed on the same phone and driven to the same states, then compared pixel for pixel below the status bar.&lt;br/&gt;&lt;br/&gt;- **Classic**: chat list, Settings, Appearance — **0** differing pixels each.&lt;br/&gt;- **Ink &amp;amp; paper**: Appearance, chat list, a chat with a picture, a poll, the locked subtitle line and the Step delivery marks, and the composer&amp;#39;s &#43; dropdown menu — **0** each.&lt;br/&gt;- **Staircase**: chat list, that same chat, and a long-pressed message — **0** each. Appearance differs by **138** pixels, which is the clock reading 11:42 against 11:37.&lt;br/&gt;&lt;br/&gt;Steps 1, 2, 3, 5, 6 observed; step 4 observed but for one part (below); step 7&amp;#39;s empty states and welcome slides are in the 315 screen renders, which are identical before and after.
    </content>
    <updated>2026-11-01T19:47:54Z</updated>
  </entry>

</feed>