Staircase
tools.relay.staircase
cloud fodder cloudfodder@relay.tools Private, end-to-end encrypted group chat over Nostr
Staircase is a private group messenger built on [Cordn](https://cordn.net): end-to-end encrypted MLS groups (RFC 9420) whose messages are ordered by a coordinator you choose and carried over Nostr relays. No phone number, no account on anyone's server, and nobody in the middle can read along. - **Group chats** with replies, reactions, @mentions, pins and encrypted photos and files - **You decide who's in**: admins, invite links and QR codes; members join with their own Nostr key - **Your keys**: a local key or Amber (NIP-55); nothing leaves the device unencrypted - **Multi-device**: link your phone and desktop to the same groups - **Open network**: pick your coordinators, relays and Blossom servers - Works with other Cordn clients such as cordn-web; also available for Linux desktop (deb, rpm, Arch)
First release: Sep 25, 2026, 11 total releases.
Most recent release: Oct 21, 2026.
Appears in 3 app stacks.
0 sats / 0 zaps received in the past year.
Sats Received
Underlying data available via MCP: app_zaps, app_releases.
Zap Count
Underlying data available via MCP: app_zaps, app_releases.
Releases
- Oct 21, 2026 0.1.25### Added - Verbose logging now says how late each notification was. With Settings › Diagnostics › Verbose logging on, every notification Staircase posts for a chat is followed by one `newlay.notify` line naming the chat, how many messages it carries, the seconds between the newest message being sent and the notification appearing, and the delivery mode in force — the number to read when comparing Periodic, Frequent and Instant on your own phone. - A fourth way of being told about messages on Android: "Frequent", in Settings › Notifications and on the first-run notifications step, beside Off, Periodic and Instant. It wakes the phone with an alarm about every 5 minutes (or 10, your choice) to check for messages, then disconnects: no ongoing notification and very little battery. It works while the phone is awake or charging; in deep sleep Android holds the check until its own window, so a message can then be as late as on Periodic. The Settings list's Notifications row reads "Frequent · about every 5 minutes" while
More…
it is on. - On Android, a background check in Periodic or Frequent that finds no network, or the app being left while offline, is now followed by one check the moment a network is back, instead of waiting for the next scheduled wake (after airplane mode, a tunnel or a dead spot). At most one such check every two minutes, so a flickering connection does not run them back to back; Instant is unchanged. ### Fixed - Starting the app no longer raises a notification about your account that has nothing behind it. On an account with multi-device sync on, every start of the app read its own messages — the ones you had sent from your other devices — as another app using your account: a "Your account" notification appeared, this device's own sends were held for a moment, and the notice cleared itself a second or two later, so anyone who tapped it arrived at a checkup with nothing in it. The app now knows whether this device syncs with your others before it judges any of that, and a real finding still appears and still opens the checkup. A notice that stops standing now also takes its notification with it, instead of leaving it in the shade until it is tapped (#72ed6bbe). - Zooming into a picture on Android now shows the detail that is in it. Every image was decoded down to 2048 px on its longest side before being shown, so pinching into a large or a tall one — a page of small text, a long screenshot — only magnified what had survived, and the text stayed blocky however far in you went. The image viewer now decodes for the screen it is drawn on: as much detail as its zoom can use, bounded by what the phone can draw in one go and by what it can hold while a chat full of pictures is swiped through. Chat bubbles, thumbnails and group pictures keep the smaller decode, which was never visible there. The viewer's decode also moved off the drawing thread, so opening a large picture no longer holds the screen up while it is read. - Asking to join a group no longer leaves you waiting for ever. The row in your chat list sat on "Waiting for an admin" with a spinner indefinitely, so an admin who had turned the request down and an admin who had not looked at it yet were the same thing for ever — nothing is sent to you when a request is declined. The row now stops claiming to be waiting once nobody is holding the request for an admin to answer any more, says only what is known (it was dealt with, or it expired; Staircase cannot tell which, and you were not added), and offers to ask again. On the admin's side, declining a join request has left its place directly under Approve, where it was a mis-click away, for the request's own menu, and now asks first: it cannot be undone, and the person who asked is not told (#e5d1ab0d). - Scrolling a chat whose senders this device has not seen before no longer pauses as their names arrive. Each name Staircase looked up redrew every message on screen, including the ones whose own sender was already known and whose text could not have changed, so scrolling into a run of unfamiliar senders cost a redraw of the whole screen for every batch of names that landed. A message is now drawn again only when a name it actually shows changes — its sender, somebody it mentions, the message it replies to, or whoever reacted to it. Looking up a name no longer reads this device's stored profiles while the screen is being drawn either (#6393ec4b). - Relays you type while adding a coordinator are now kept. In Settings › Coordinators › Add a coordinator, a relay address typed into Relays was stored as though the coordinator had published it, so the next time the Coordinators screen opened it was replaced by the coordinator's own published list — silently, and without the screen ever having said the list was yours. A list you type is now yours: the card reads "Set by you · kept until you choose the coordinator's published list", and it stays until you do. Relays that came from an `nprofile1…` you pasted are the operator's rather than yours and still give way to a published list, as before (#74a96a4b). - Starting the app no longer reads every account's whole message database into memory. To decide which accounts had a database on this device, Staircase read each file from end to end as if it were text — tens of megabytes of it on a phone that has been in use for a while — and then threw the text away. On a database of about 40 MB that meant asking Android for a single 128 MiB block at every start: granted, it made start-up several seconds longer; refused, the failure was silent and also skipped the pass that brings a signed-out account's database up to date with how databases are stored now. Staircase now asks whether the file is there. Start-up on such a phone is several seconds shorter and some two hundred megabytes lighter (#ac10a449). - The account checkup no longer tells you your held messages have gone out when they have not. Where the checkup could not settle what it found — a coordinator that has stopped answering, or a finding no clean-up touches — it still ended on "Done" and "1 message sent · What was held while this was unsettled has gone out", and the message was back to "Held · another app uses this account" a moment later. The last screen now says your account is still held, how many messages are still waiting, and what is left, and it offers "Continue anyway, and let my messages out" and "Check again" right there instead of behind the question a step back. Nothing is counted as sent unless the hold has actually let it out. "What we found" also no longer reads "No keys but your own" while the card on your Inbox says a key belongs to none of your devices: where a key is served that nothing here can account for, it says so and names the coordinator that did not answer. The clean-up step, reached with nothing it can clean up, offers Check again rather than a button that cannot be pressed (#fb3cf2cc). - Adding a coordinator you already have no longer replaces it. In Settings › Coordinators › Add a coordinator, a key that was already one of your coordinators was stored over that coordinator's record: its name, its relays and your choice about its relay list were replaced by whatever was in the sheet, no second card appeared, and nothing said so — a coordinator holding your chats could be left pointing at a relay that does not answer. The sheet now recognises the key, names the coordinator it belongs to, and offers to open it for editing instead (#78b9d8be). - Renaming a coordinator no longer stops it following its published relay list. In Settings › Coordinators › a card › Edit, saving a new name pinned the relays the dialog had filled in for you, so the card went from "The coordinator's published list · follows its changes" to "Set by you" and the coordinator's own later changes were ignored. A save that leaves the Relays field as it opened now changes nothing about the relays: one that was following its coordinator still follows it, and one whose list you had set is still yours (#d08f27b6). - A poll that names somebody now reads their name in the chat list and in a notification, not their npub. A poll asking `Did @Avery see this one` read that way in the chat, but in the chat list as `📊 Poll: Did nostr:npub1yhc0d7g…` — sixty-odd characters of npub taking the whole of that chat's line — and the notification in the shade did the same, pushing the question out of sight. Both now read a question the way the chat does: a mention as `@their name`, or as a short npub where no name for them is known on this device, and a quoted message as a short link. The question in the poll's own bubble reads that way from the moment it is sent too, instead of showing the npub for the second the poll is in flight and then changing (#673c006c). - A finished account clean-up no longer brings the same notice straight back. After the checkup had removed a key that belonged to none of your devices and told you it was done, the "Your account" card on your Inbox could reappear within seconds naming the key that had just been removed, inviting you to walk the same checkup again over a key no coordinator serves any more; a second walk cleared it. A check of your coordinators that was still running when the clean-up finished reported what it had read before the removal, and that reading was taken as a fresh finding. A reading taken before a key was removed is no longer counted, and a key that is published again after that is still found (#77b8c55e). - Oct 20, 2026 0.1.24### Added - Staircase now runs in a web browser. Every screen, drawn the same way it is drawn on a phone, is served as a web app: it opens on the Staircase welcome and, once you have been through the short introduction, on a sign-in screen. The only way in is a Nostr browser extension, which keeps your key and signs for the page; a browser without one is told so and told what to install, and creating a key or pasting one in is not offered on the web. Click "Sign in with a browser extension" and the extension asks you to allow the page once; from then on it signs everything Staircase sends and reads everything Staircase is sent, and the rest of the app works as it does on a phone — join a chat from its link, send and receive messages, and find them still there after a reload. An extension that cannot do NIP-44 encryption is refused at sign-in and told why, rather than failing later on the first reply a coordinator sends. Signing and verifying events in a browser uses a JavaScript elliptic-curve library in place of the native one
More…
the phone uses, and encrypting message contents uses an AES-GCM implementation written for the browser; both are held to the same official BIP-340, NIP-44 and AES-GCM test vectors as the phone's, so a signature made in a browser is the signature the phone would have made, and the encrypted group messaging itself is Staircase's own code passing the same interoperability fixtures there that it passes on a phone — a group created in a browser is the same group on the wire. A browser keeps its own copy of an account's chats, encrypted with the same key the phone's database file uses: what sits in browser storage is ciphertext, nothing readable is written, it refuses to open with the wrong key instead of quietly starting empty, and the browser never stores a private key at all. The two secrets a browser cannot keep in its database — the database key and the multi-device link — are encrypted by the extension to your own key, so only wrapped blobs are stored and nothing can read them without it; it asks once a session rather than once a message. A browser build opens its relay connections through the browser itself rather than through the socket library the phone uses, so a connection carries the page's own address and a file server that has not allowed the web app may refuse to hand over an attachment; relays and coordinators are unaffected. A browser too old to run the app gets a plain page saying which browsers do, and never downloads it. Settings says "Signing with your browser extension" where it would have shown an internal name, the option to reveal a secret key is absent on the web, where there is none to reveal, and removing the account takes the stored chats and the wrapped secrets with it. Images, dates, times and the text menus all come from the browser rather than from the phone's or the desktop's own; nothing changes in the phone or desktop app (#1bf4adac) (#11cd1da7) (#b880a47a) (#c33299f4) (#39b4e753) (#c59109dc). - A link to a repository on gitworkshop.dev now shows the same kind of card in a chat that a link to an issue or a pull request already did: the repository's name, its description, who owns it and where it clones from, with the long URL itself taken out of the message. A repository nobody has announced, or one whose owner the link does not name, still gets a card naming the repository from the link rather than an unreadable URL, and a tap on any of them opens the page. A `nostr:naddr…` mention of a repository gets the card too, where before it read as a bare "repository" label. A message carrying both a repository link and one of its issue links now has both cards and neither URL left behind in its text. A card whose event cannot be read now says so where it used to go on reading for ever, which it did for an issue or pull request too, and is tried again the next time the message is drawn instead of staying that way until the app is restarted (#f81be1d1). - Staircase can now be deployed as a web app. Every tagged build publishes the browser app as an archive of static files, beside the Android and Linux packages, for anyone who wants to put it on a web server; every file in it but the page itself is named after a hash of its contents, so a browser can keep the big ones for a year and still pick up a new release the moment it lands. The version the build was cut from is shown under Settings › About, as it is on the other two platforms. The site can be installed to a home screen or a dock and opens in its own window. Opening the site with a Staircase chat link in its address joins that group, the way tapping a cordn.net link does on a phone — including when the link arrives before anyone has signed in, in which case the group opens as soon as they do. Opening a second tab now says Staircase is already open in another tab rather than letting two tabs write over each other's chats, and it takes over by itself when the first tab closes. The README has a Web section covering the browser versions it needs, what a browser extension is needed for, what is stored and how it is encrypted, and what the web cannot do. The web app has an automated walk of its own, the way the phone app has its flows, and the build job that assembles the bundle now runs in the build image, where it had failed on every pipeline since it was added (#98e6ff5c) (#37ddbbd6) (#f892c589). - Staircase now notices when another app or install is using the same account, and keeps a record of everything that says so: a key package a coordinator serves for the account that this device never published, an invitation sealed to a key package only the other app can open, a chat the other app changed, and a message sent from the account at a point this device had already used. The record survives closing the app and is cleared only when every piece of it is explained — by linking the two in Settings › Multi-device, which shares the key between them — or by taking the account over on this device. Everything that was found is listed under Settings › Diagnostics (#6f670e54). - Messages the account sent from another app or install are now recognised for what they are. Such a message is the account's own, so the chat still shows it as sent by you, but Staircase now records that this device was not the one that sent it, notes it in the chat's connection details with the point in the chat it arrived at, and counts it under Settings › Diagnostics as another sign that a second app is using the account (#113bdc07). - While another app or install is using the same account, Staircase now holds everything that would change a chat or put a message on the wire, instead of letting the two apps overwrite each other. Renaming a chat, adding or removing people, making a new chat and asking to join one are refused with a line saying another app is using the account and what the ways out are; a message you send waits in the chat marked "Held · another app uses this account" and is never half-sent. The hold covers the whole account rather than one chat, and applies whether or not multi-device is on, because an app that was never linked is exactly the case it is for. Settings › Multi-device › Advanced now has "Continue anyway" for when you know the other app is yours and want to carry on regardless: turning it on sends everything that was waiting and lets changes through — at the risk of splitting a chat for the other app — and turning it off holds again. The hold also lifts by itself once the other app is explained, by linking the two or by taking the account over (#6401ebe3). - Staircase now says on screen when another app or install is using your account, instead of only refusing what it cannot safely do. A banner above the chat list says what was seen, when, and on which coordinator, and offers Multi-device — where the whole story is — or "Continue anyway", which sends what was waiting and lets changes through. Settings › Multi-device has a new card, "Other apps on this account", listing every key package served under your key, coordinator by coordinator, each one marked held on this device or not held here, with the date it was published and how old it is; under them the invitations that went to the other app, the chats it changed (tap one to open its group info), and the messages it sent. The ways out are on the card as well: link this device, or continue anyway. Once the other app is explained the card stays for a day, folded, saying what resolved — so linking a device visibly worked. A choice to continue anyway covers the app it was made about: if a different one turns up later, the banner asks again. Settings › Coordinators no longer offers to republish your key package on one coordinator and points at the new card instead, where every package found is listed together (#035626ee). - You can now take an account over on the device you are holding. Settings › Multi-device › Other apps on this account offers "Take over this account": the device you are on keeps the account, and every other app or install using it loses the chats this device is in. A confirmation lists what that costs before anything happens — the other apps lose those chats for good and only a fresh invitation brings one of them back, chats that exist only on the other app stay there, invitations already sent to its key package will fail, the other app is not signed out and may keep trying, and with sync on your connection string is replaced so every device you linked has to be linked again. It takes a second tap on the red button, not a typed phrase. Chats this device can no longer follow are not taken over: they are listed in the confirmation by name, each with "Fix this chat" as its way, and are left exactly as they are. While it runs the card says where it is, and afterwards what it did; each chat that was taken over says "You took this account over on this device" in its history, and your messages and group changes go out normally again. A chat a coordinator refuses at that moment is tried again, and if one still cannot be taken over the card stays, naming it, so you can finish the job (#1ba4209b). - A message you sent from another app or install now says so where you read it. In the chat it is still your own message, on the right as before, with "From another device" in small print under its time; in the chat list the chat's preview line reads "From another device: …" where one you sent here reads "You: …". A screen reader reads the caption along with the message. Nothing else about such a message changes, and a message you send on this device carries neither (#0a40ce01). - Your account on other devices: one place that deals with every way your account can be in more than one app or on more than one device. Your Inbox now has a "Your account" section when there is something to tell you about — keys that belong to none of your devices, an invitation this device could not open, a chat changed from somewhere else — with a count on the Inbox tab and one notification, on the channel now called "Invitations and account notices". Its button opens a walk: it checks each of your coordinators and says which answered, counts what it found in plain words, and asks the one thing it cannot work out for you — is this another device of yours, and do you still use it? Answer "yes" and it links this device to the other one and tells you how many chats have arrived; answer "a previous install" and it removes the keys nothing uses any more, so nobody is invited into a chat none of your devices can open; answer "I don't know" and it offers the take-over, which was already there, with the line "if you still use the other app, do not do this; link instead" above what it costs. Leaving the screen comes back to the same step. Every wait says what it is waiting for and roughly how long, nothing is said by colour alone, and a coordinator that will not answer is named rather than guessed about (#f2f42f85). - Linked devices stop counting as another app using your account. A message sent from a device you have linked, a send that collided with it, and an invitation it accepted no longer hold this device's messages back, as long as that device is publishing the documents that explain them. An invitation no device of yours could open stays listed, with Clear, and says to ask to be invited again. With multi-device off nothing changes (#26e90ff4). - A repair request ("Fix this chat") that a clean-up on another of your devices removed now puts itself back, so the admins can still answer it. - Settings › Backup & restore: save your account to a file, and put one back. A backup carries every chat with the keys it needs to carry on, the coordinators the account uses, the account itself, the link to your other devices, and your messages if you ask for them, in the same file format the web client at cordn.net reads and writes — so a file can go from one to the other and back. The file is locked with your own key by default — Amber's, your browser extension's, or the key Staircase holds — so it opens on any device signed in as you, here or at cordn.net, with nothing to remember; a passphrase is offered instead, exactly as the web client locks one today, and saving one unlocked asks first and names what the file would then carry in the clear. A backup from an Amber account or a browser extension carries no key material at all; one from an account whose key Staircase holds carries that key. The page says when you last saved a backup and how many chats you have joined or changed since. Restoring starts from the file: it says whose backup it is and what is in it before anything is applied, asks again if it belongs to another account, and then reports every chat on its own — restored, already here, brought up to date from your other device, or stuck, with a button to ask to be invited back. The format is written out in full in docs/backup-format.md, which also records what the coordinators keep, and so how old a backup may be (#fe042706) (#004964e7) (#f832b934) (#1f1d519f). - Restoring a backup the web client at cordn.net wrote no longer quietly loses the parts of it only that client uses. Those details have nowhere to live in Staircase, so they are kept aside and written back into the next backup you save here — which means a file can go from cordn.net to Staircase and back without arriving home missing anything (#dc82f2d7). - In a web browser, a file can now be dragged into a chat and dropped on it, and an image can be pasted into one with Ctrl+V, instead of the paper-clip being the only way in. Dragging something over an open chat says it will be taken, and dropping it opens the same "Send this image?" question the paper-clip does, with its preview and its caption box. Anything can be dropped, not only pictures; several files at once are offered one after another so each is seen before it goes, and each is sent with its own progress. Pasting text still goes into the message box as it always did. A file dropped anywhere else in the app does nothing, and — this is the part that used to go wrong on any web page — the browser no longer opens the dropped file itself and loses your place in the chat. Nothing changes in the phone or desktop app (#8684b6e2). - Setting up a new phone or computer starts with your backup: "Restore a backup — From Staircase or cordn-web" now stands beside "Create a new identity" and the other ways in, on a device with no account at all. Choose your file and it says whose account it is and what it holds before anything is written. A file locked with a passphrase asks for it; one locked with your key asks you to sign in as that account first — with Amber, with your browser extension, or by pasting the key — and a key or a signer belonging to somebody else is refused by name, without being put on the device. A file from a Staircase account that keeps its own key needs nothing at all. Then the same list you would see in Settings tells you how each chat came back, and onboarding carries on where it would have: no "keep a copy of your key" step, because your backup is that copy (#4ba842a3). ### Changed - The chat list's warning about another app using your account is now a slim neutral strip reading "Your account needs attention · Open", and it is shown only while messages are actually being held. Everything it used to say is in your Inbox and in the walk the strip opens. Settings › Multi-device shows a summary and a Start button instead of the whole list, and Settings › Coordinators points there as well. - The button that takes an account over now carries the warning colour as its background, with its own readable label, instead of warning-coloured text on an ordinary button: the label and the button it sat on were the same lightness, so the words disappeared for anyone reading by lightness rather than by colour, and in a greyscale screenshot. The count on the Inbox tab is now read out by a screen reader as well as drawn — "Inbox, 1 waiting" — where before a screen reader was told only that the tab was the Inbox. - A brand-new install no longer creates its database key before it has a database. The key was minted on the first launch whether or not an account existed; it is now read the first time a database is actually opened. Nothing changes for an install that already has one (#1bf4adac). - Under Settings › Notifications › "Only mentions", a chat with exactly one other person now alerts on every message, the way it would under "All messages" — with one other person every message is addressed to you, so a two-person chat is a direct message however it was made. The unread badge agrees: a two-person chat's unread counts towards the Chats badge under "Only mentions" instead of being left out of it. Nothing changes for a chat with three or more people, nor under "All messages", and nothing changes about what a mention is: a chat you muted yourself stays quiet unless a message really names you, in a two-person chat as in a group. It is the number of members that decides, not who sent the message, so a two-person chat whose other member leaves goes quiet again and one that gains a third person behaves like any other group from that moment (#e6f63dae). - Staircase now keeps exactly one key package per account — the one anyone needs to invite you — and publishes it on every coordinator you have added, instead of also keeping a small stock of single-use ones on each. Being invited works as before, and with one difference for the better: an invitation sent to this account can now be opened by this device whichever coordinator it came through, where before an invitation built on another app's single-use package arrived with nothing this device could read. Settings › Coordinators says "reachable here" or "not published here" for each coordinator instead of counting packages, and a new "Keep me reachable automatically" switch (on) turns the publishing off for anyone who would rather Staircase only looked and reported. If another app is already using this account, Staircase leaves its key package alone rather than replacing it — replacing it would cut that app off — and says so on the coordinator's card, where "Take over" is still yours to tap (#fdc7a4f3). - Checking your account is one checkup now, however many things it finds. The Inbox used to show a card for each thing — keys left behind by an old install, invitations this device could not open — with its own button, and each button started the same walk over again, so somebody with two findings answered the same questions twice and was met by the second card when they got back from the first. There is one card and one "Check my account" button; it checks your coordinators once, lists everything it found on one screen, asks the one question only you can answer, and leaves nothing behind afterwards — the invitations nothing can open are read and cleared as part of the pass rather than waiting for you to come back and dismiss them, and choosing to carry on as you are ends the checkup instead of raising the same cards again. A finding that turns up later still tells you about it. The account's notification says everything that is waiting rather than only the newest thing, and Settings › Multi-device opens the same one checkup (#cdb13a5d). ### Fixed - Signing out of the web app leaves you on the sign-in screen, and keeps you signed out. Confirming "Sign out" in a browser left the dialog and its grey backdrop frozen over the sign-in screen, taking no clicks until the page was reloaded: closing the account cut short work that was still out with the extension, and that was reported as an error nobody was left to catch, which stopped the page redrawing. Signing out was also being cut off half way — the session closed, so the sign-in screen appeared and looked right, while the account stayed marked as the one to open, and reloading the page signed you straight back in. Both come of the same thing: signing out takes away the screen that asked for it. Closing an account, and taking one off the device, now run to the end whether or not the screen that asked is still there, on every platform (#11cd1da7). - Settings says which signer holds the key for the account you are signed in to, on a line of its own under the npub. For an account signed in with Amber that name used to be tacked onto the end of the npub, which on a phone ran out of room and was cut off mid-text, so the card read as an npub followed by a dangling "·" with nothing after it. The name, the nip-05 address and the npub on that card now end in an ellipsis when they are too long to fit, rather than being cut through a character (#73b05aed). - A profile that changes is picked up again. Someone who updates their name, their picture or anything else in their profile, and publishes it to the relay a chat lives on, was shown with their old profile for up to a day: the app asked that relay for a profile only when no other relay had one at all, so an older copy anywhere else hid the new one. Every relay the app already talks to is now asked together and the newest copy of a profile wins, and a profile arriving from a slower relay a moment later is no longer dropped in favour of the first answer. The clearest case is the agent accounts in this team's own chats, which now show their "BOT" mark (#8ba2a690). - A coordinator that is down no longer holds up the Inbox. Invitations and join requests waiting on a coordinator that is working now appear as soon as it answers — every coordinator is asked at the same time instead of one after another, so one that never replies no longer spends a minute of its retry budget in front of the others. While that wait is on, the Inbox says which coordinator is not responding rather than showing an empty "Nothing waiting" behind a spinning Refresh, and a coordinator already known to be down is given one attempt per round instead of three, so a new invitation is not pushed further and further out. Invitations already on the device are listed the moment the Inbox opens, before any network work (#e19d4e1f). - Sending a photo or a file now shows in the chat while it goes. A placeholder bubble appears the moment you tap Send — the picture itself where it can be shown, otherwise the file's name and size, with the caption under it — carrying a progress bar and what the send is doing: encrypting, uploading (with a percentage where the connection reports one), checking the upload, sending. The placeholder turns into the real message when it lands. Attachments now wait in the same queue as typed messages, which brings the rest of what a typed message has: an upload that fails says so in the chat with Retry and Discard beside it rather than silently doing nothing, the file is kept on the device so Retry sends the same photo without picking it again, and it survives closing the app. The composer stays usable while a file is on its way, and a large upload no longer holds up the messages queued behind it (#73a4bedc). - Music and podcasts come back after a video. A video played in a chat quietens whatever else is playing, as it should, but it used to keep the phone's audio output after it had finished or been paused, so the other app stayed silent until the video's message scrolled off the screen or you left the chat. The output now goes back the moment the video stops making sound, however it stopped, and it is asked for the way a short clip should ask for it, so the app that was interrupted starts itself again rather than waiting to be told. A call or an alarm over a playing video still pauses the video, which carries on where it was once the interruption is over (#6e9acc34). - A picture opened full size can be seen in full. The caption and the title bar are painted over the top and bottom of the picture, and on a tall screenshot with a caption of a few lines — more still with a large system font — they covered a third of it with no way to move them: a single tap on the picture did nothing. A tap now takes both away so the whole picture shows, and the next tap brings them back; double-tap to zoom, pinch, the swipe between pictures and the ways out of the viewer are unchanged, and Back still closes it while they are hidden. A mention in a caption reads as the person's name, as it does in the chat, instead of as `nostr:npub1…`. And a caption longer than the four lines there is room for is no longer cut off for good: "More" opens it in full, scrolling if it is very long, and "Less" puts it back (#4982af5a). - A database that recorded a newer layout than it actually had is now repaired at startup instead of crashing the app forever. A phone could be left in that state by a development build, and the symptom was brutal: the app died at the chat list within a moment of opening, every time, and the only way out was removing the app and losing the account with it. Staircase now compares what the database records against what its tables actually contain on every open and, when they disagree, trusts the tables and applies the upgrades that were missed, keeping every chat and message. The same check already covered a database whose recorded layout had been lost entirely; it now covers one that is ahead as well, on the phone, on the desktop and in the browser (#1f00b38f). - A caption on a photo or a file can mention someone again. Typing `@` in the caption box now offers the chat's members the way the message box does, and tapping a name puts a real mention in the caption, so the person is alerted and the chat counts it as a mention of them. Captions did not do either before: there was no list of names to pick from, and — the part that bit even if you knew the trick — a caption that named someone outright, by pasting or typing their npub, was sent with nothing to say who had been named, so they were never told. Both caption boxes have it: the one on "Send this image?" in a chat, and the one on the screen that appears when something is shared into Staircase from another app or forwarded from another chat. The share screen needs the group picked first, because the names come from that chat's members. A `@word` that matches nobody still sends as plain text (#c6e6f710). - Taking this account over no longer reports a chat it was never going to touch as one that "could not be taken over". A chat that has stopped before a message this device cannot open is left exactly as it is, like the chats this device is not on the line of: it is named in the confirmation before you start and in the report afterwards, under the chats that are skipped, and it is not counted among the ones that will be taken over. Each skipped chat now offers the way out it actually has and goes there when tapped — "Fix this chat" on the group's info screen for a chat the group has to let this device back into, the chat itself for one stopped before a message it cannot open, where the chat's own banner offers to skip that message (#260904f0). - "Your account on other devices" no longer takes a minute and a half to show anything when a coordinator is down. Every coordinator is asked at once rather than one after another, and the check stops waiting after ten seconds and shows what it has. A coordinator still answering is not written off: its row says it is still being waited on, and what it has to say is added to the page when it arrives. Only one that gives nothing at all is reported as not reachable (#4847cfb0). - A device no longer counts keys that are not there any more, and no longer counts the keys its own other devices use. Cleaning up on one of your devices used to leave every other one saying "2 keys left to clean" for good, because a key count there only ever went up; now any check that reaches a coordinator forgets what the coordinator has stopped serving. And a one-time key one of your linked devices published — the kind "Fix this chat" asks with — is no longer counted as a stray or offered for removal on its siblings: taking it away would cancel a request that device is waiting on. It is listed, named as another of your devices', and left alone (#4847cfb0). - Invitations this device could not open can be cleared where they are shown. The notice in the Inbox, and the same one in Settings › Multi-device, now has Clear beside it; before, the only way to be rid of one was a button several taps inside the repair walk, and where the key the invitation was sealed to belongs to no device that is still running, nothing could ever accept it, so the notice stood for good. Clearing takes the invitations listed at the time; one that arrives afterwards is shown as usual (#4847cfb0). - The web app asks about notifications in a browser's terms. Signing in to the web app for the first time put the phone's question on screen — "your phone asks the coordinators itself", a choice between keeping a connection open with an ongoing notification, checking every 15 minutes, or nothing at all — none of which a browser tab has or does, and the choice made no difference there. The web now asks the one thing that does: whether to show notifications, saying plainly that one can arrive while the tab is open and nothing arrives once it is closed, and that a refusal is the tab's own setting to change. Settings › Notifications in a browser says the same and offers the permission instead of delivery modes and battery advice, and the Settings list reads "Only while this tab is open". Which messages alert — all of them, or only mentions — is unchanged and still asked everywhere; the phone and desktop app are untouched (#e4baa1ac). - Adding a second account to a device that already has one works again, and says what it does. In Settings › account menu › Switch account, the button that takes you to the sign-in screen read "Add another account" while signing the current account out; it now reads "Sign out and add another", and the dialog says that one account is signed in at a time, that adding another signs this one out first, and where it takes you. The account you signed out of stays on the device and signs back in on a tap, as before (#38e74cc4). - The sign-in screen scrolls. With a lot of accounts on the device the list of them filled the page and "Create a new identity", "Import an existing key (nsec)" and the sign-in-with-a-signer button sat below the bottom edge with no way to scroll down to them, so there was no route to a new or imported account at all short of removing accounts until the buttons fitted. The whole page now scrolls, and still sits centred when it fits (#ffda4733). - The note Settings › Multi-device shows after an account checkup now says what the checkup actually did, in ordinary words. It used to read "1 invitation(s) another app has to accept" — a count written as a placeholder, a description of the one thing no app of this account can ever do with that invitation, and no mention at all of the keys the checkup had just removed, although the screen before it had counted them. It now names everything that had been found, counted properly, and says what became of each part of it: keys removed from your coordinators or found to be a device of your own, an invitation this device could not open read and cleared, an account taken over (#5ebb2a1a). - A chat holding messages the coordinator refused now opens on its newest message again. Those messages were drawn after the whole conversation, so every one of them sat below the chat's newest message: after a burst of sends the coordinator rate-limited, a message sent afterwards went out, the chat list showed it as the latest, and the chat itself opened on the refused ones with the new message out of sight above them — which reads as a message lost. A refused message now stays where it was written, among the messages around it, and what you send is the bottom of the chat. A message still on its way is unchanged: it sits at the bottom, where you have just typed it (#0de2a3c9). - The notification about your account arrives for something that was already waiting when you opened the app. Until now it only reached you if the finding turned up after you had put the app away: anything found as the app started — which, on a new phone, is everything there is to find, and what the account checkup exists for — was recorded as told about while the app was still in front of you, where no notification is posted, and so was never mentioned again for the rest of that session. Nothing is marked as said until it has actually been said, and whatever is still standing is notified the moment you leave the app. The notification itself, its wording, what it opens and how it clears are unchanged (#5a9d7127). - In a web browser, the progress of a photo or video being sent is no longer a figure that is wrong. It used to read "Uploading… 100%" with a full bar within a few milliseconds and sit there for the whole upload, which looked like a send that had stalled — it had not, and the photo did arrive. A browser cannot measure how much of an upload has actually gone out, so the browser build now shows a running bar and the stage it has reached ("Encrypting…", "Uploading…", "Checking the upload…", "Sending…") and no percentage, rather than a percentage that means nothing. The phone and desktop apps, where the figure is real, still show it (#87075dd5). - A key package shared with this device by linking another one is no longer shown a day older than it is. Settings › Multi-device lists the keys served under your account with when each was published, and the moment a link made this device a holder of one, its time jumped back exactly 24 hours — a day earlier than the device that published it said, and a day earlier than the same screen had read a few minutes before. The shared key was being dated from the start of its validity window, which is set a day early on purpose so that a key stays usable by somebody whose clock is behind yours. It now carries the time it was really published. Settings › Coordinators › "Your key packages" also shows that time now, so the age of each key your account is reachable through can be read in one place (#c3874da1). - Restoring a backup with a lot of chats no longer accuses some of them of being unreadable. A restore asks the coordinator for everything it has just put back in one go, the way the app catches up normally, instead of once for every chat and then once more — which asked a server thirty-two chats' worth of questions in a few seconds and got some of them turned away. A chat the server did not answer for says it is still catching up and will finish by itself, which is what happens; only a chat that genuinely cannot be brought up to date still says so and offers to ask an admin to be let back in (#af3a485f). - An `nprofile1…` pasted into Settings › Coordinators › Add a coordinator brings the coordinator's relays with it. An nprofile names the relays its coordinator is reached through; the field took one but kept only the key, which left a coordinator whose operator publishes no relay list of its own with no address at all. It was then called on the app's default relays, where it is not listening, and its card read "not responding". Relays you type yourself still win, and a coordinator's published list, where there is one, still replaces what the nprofile carried. The field says an nprofile is accepted (#fe9884b0). - A request to join one of your groups, approved on one of your devices, no longer stands waiting to be approved again on every other device of the same account. The other device settles it on its next check — from the group itself, as soon as it has read the change that let the person in, without needing to reach the coordinator — and it now does so while the Inbox is open, instead of going on offering Approve and Reject until the app was started again. A notification about an Inbox that has emptied is taken out of the notification shade too (#2481c4ef). - The "Send this image?" panel no longer jumps about while a caption is typed. On a phone it is now a panel at the bottom of the screen, like the poll composer, rather than a card floating in the middle: a card is re-centred in whatever space the keyboard leaves, so it moved every time the keyboard's own height changed — a suggestion strip coming and going on a tall keyboard does that on practically every keystroke — and again each time the caption wrapped to a new line. The title, the picture and the file name now stay where they are as the caption grows, and a panel taller than the space above the keyboard scrolls rather than moving itself about. On a desktop or a tablet it is still a centred card, and it still sends with Return, starts a line with Shift+Return and drops the file with Escape (#3f6ee9d1). - Oct 16, 2026 0.1.23### Added - Polls in a chat. The composer's + button now offers "Poll" beside "File": ask a question, give it two to ten options, and choose whether people may pick more than one and when it closes (never, or in an hour, a day, three days or a week). The poll arrives as its own bubble — the question, the options with their vote counts, a bar showing each option's share, and your own choice ticked — and a tap on an option votes. Changing your mind moves your vote instead of adding a second one, and a poll past its closing time says "Closed" and takes no more votes. The counts wait for you: how each option is doing is hidden until you have voted, so the votes already in cannot steer yours, and a poll that has closed shows its result to everyone. A link in the question works like a link in a message — it opens, and a link to an image or a GIF shows the picture itself inside the poll. A poll counts as a message: it is what the chat list shows ("📊 Poll: …"), what the unread badge counts and what the notification
More…
says. Votes themselves are not messages and do not appear in the chat or in any badge. Polls work both ways with cordn-web, which uses the same format (NIP-88 kinds 1068 and 1018), so a poll made in one client is read, voted on and tallied in the other; a poll a client cannot read says so rather than showing an empty bubble (#610c098f). - A chat this device can no longer follow can be repaired without finding an admin to ask. Where the chat and its Group info screen say the chat is stuck, there is now a "Fix this chat" action: it asks the group to let this device back in, and the chat repairs itself the moment someone approves — with the messages already on the device still there. Nothing is removed from the device to ask, and the chat carries on exactly as it was until the request is answered, so there is nothing to lose by trying; the wait is shown where the request was made and can be given up on. The admins see it in their inbox as what it is, "is in this group already and asking to be let back in", rather than as a stranger asking to join a group they are already in, and approving replaces that member's place in the group in one change, so nobody else is disturbed. Where another device or app using the same account is what moved the group on, turning on multi-device is offered first and is one tap away, with the fix still underneath it for anyone whose other device is gone for good. - A chat can be marked read from the chat list, without opening it: "Mark as read" at the top of a row's menu (long-press on Android, right-click on desktop) clears that chat's unread and mention badges, and with them the folder's roll-up and the desktop window and tray counts. Nothing is sent to anybody — Staircase has no read receipts. The item is there but greyed out when a chat has nothing unread, and a chat marked read this way stays where it is under "Unread first" sorting, as one read by opening it does. - Reply to a chat straight from its notification on Android, without opening the app: the notification carries a Reply box, and what you type appears in its own thread as soon as it is on its way. The reply goes through the same send queue as one typed in the app, so one that cannot go out yet is kept and retried rather than lost, and the notification says which it is ("Sending…", nothing once it has landed, "Waiting for a connection — it will go out by itself"). It works when Android has stopped the app: the account is opened without starting the sync streams. With the app lock on, Android 12 and newer ask for your lock before the reply is sent, and older releases do not offer the Reply box at all. Replying does not mark the chat read (#cb577d8c). - A test build of Staircase now sits on the phone beside an installed release build instead of replacing it. It is a separate app — called "Staircase (dev)" in the launcher, with its own accounts, groups and messages — so it starts empty the first time it is opened, and installing it no longer asks anyone to uninstall the Staircase they use and lose its accounts and group history. The two are updated independently. Share links and `cordn:` links are claimed by both when both are installed, so Android asks which one to open; only the release build opens them without asking. Settings › About says which of the two you are looking at: a test build's version is marked "built from source, not a release", where a release build's is not. - An agent is marked wherever the app names a key. A profile that says it belongs to a bot (the NIP-24 `bot` flag) now carries a small "BOT" mark beside its name in a group's member list, above the first message of a run in a chat, in the people search and on the list of people about to be invited — not only on the profile screen, which was the only place that showed it and meant opening a profile to tell an agent from a person. The mark is the one the profile screen already used, it reads "Bot account" to a screen reader, and it is drawn only for a profile that published the flag. - A chat, or a whole folder of chats, can be muted for 3 hours, 24 hours or until you unmute it: "Mute…" in a chat row's menu or a folder header's (long-press on Android, right-click on desktop). A muted chat makes no sound and shows no unread badge at all — the list goes quiet to look at, not just to listen to — and its name is no longer bold. The exception is a message that mentions you, which still badges (as the "@" badge, with how many name you) and still alerts, so a mute cannot cost you something addressed to you. A folder's mute covers every chat in it, including one moved in afterwards; a chat inside a muted folder says so and is unmuted by unmuting the folder, or muted for longer on its own. A timed mute ends by itself, and the badges come back within a minute without reopening the app. Muting travels with the account rather than the handset: once a second device is linked, a chat muted on one is muted on both. - An admin can put a member back whose own copy of a group has stopped working — they reinstalled, lost their keys, or their app says the chat is stale or stranded and tells them to ask an admin to re-invite them. "Re-add to group…" in the member's menu in Group info replaces their place in the group: it says what that means before it does anything (they keep the history their device still has, they cannot read anything sent before the re-add on a device that lost its keys, and an admin stays an admin), and it checks first that their app has left a key for them on the coordinator, so nobody is ever taken out of a group with nothing to invite them back with. Add people offers the same thing on the row that used to be a dead end: a person it shows as "Already in this group" now carries "Re-add" for an admin. The group sees one line — "… was re-added" — rather than a departure followed by a join, and the person being re-added gets the group back by accepting the invitation that arrives in their inbox. - An account can be taken off this device, not only signed out of. "Remove from this device" is its own item in the account menu in Settings, on every other account under "Accounts on this device", and on every row of the sign-in screen's list — which is where it is wanted, since signing out leaves you standing there. It deletes that account's secret key, its groups, messages and drafts, and its link to your other devices, after a confirmation that says plainly that it cannot be undone: nothing but your own copy of the secret key brings the account back, and not even that brings the groups back, because the keys that read them go with it and someone in each group has to add you again. Other people go on seeing you as a member until they remove you. On an account whose key is held by a signer app such as Amber there is no key here to delete, and the confirmation says where the key is instead of claiming to delete it. - Marking chats read in bulk, without opening any of them. A folder's menu — long-press its row, right-click it, or the ⋮ on its header — now has "Mark folder read", which clears the unread and mention badges of every chat in that folder at once and leaves the rest of the list alone. For the whole account there is "Mark all read" in the chat list's top-bar menu, beside the sort choices: it clears every chat, in every folder and outside them, muted ones included. Neither asks for a confirmation: nothing is deleted, nothing is sent to anybody, and a message that arrives afterwards is unread as usual. Both are shown but unavailable when there is nothing unread to clear, so the action can be found before it is needed. Read state belongs to the device it is marked on, as it already does when a chat is read by opening it, so marking everything read here leaves your other devices' badges alone. - A folder's header now shows its ⋮ options button next to the unread badge instead of behind it, and on a folded folder as well as an open one. The badge used to take the button's place exactly when a folder had something unread in it, which is when its menu is most wanted. ### Changed - Verbose logging now times how long a chat takes to appear. With Settings › Diagnostics › Verbose logging on, each chat open records how long the store took to return its messages, how long building the timeline from them took, and how long the whole wait was before the messages reached the screen. The three numbers say which of those three steps a slow-opening chat is spending its time in, on the device where it is actually slow, instead of needing the chat reproduced somewhere else first (#69fcda8f). - A pinned chat now sits at the very top of the chat list, above the first folder, instead of at the top of whichever folder it is filed in. The pinned chats are a block of their own under a "Pinned" label, in the same order the rest of the list is in, and each one is drawn there and nowhere else: a chat pinned out of a folder leaves that folder's rows and its unread roll-up, so a folded folder no longer counts a chat you can already see above it. Unpinning puts the chat back where it was filed. With no folders nothing changes — that list is already pinned-first, and has no label. - A message that links to an image or a GIF now shows the picture without the link. The URL leaves the text, and so does the punctuation left where it stood, so a message that is nothing but a link is nothing but a picture. What the URL was good for moved onto the picture itself: long-press it on Android, or right-click it on the desktop, for Save image, Copy link and Show link. A saved file is named after the URL's own last path segment. "Copy message" and "Copy text" still give the message as it was written, with the URL in it. With auto-load media off, the strip that waits for a tap has the same menu without Save image, since nothing has been fetched yet, and a link that cannot be loaded keeps a one-line strip of its own so that the URL is never out of reach. - Searching for people by name says what the search relays match, and keeps more of what they find. The line under the Search button now says that a name is matched from its start — "Luc" finds "Lucas", "cas" finds nobody — that a partial name needs three letters, and that capitals, accents and small typos make no difference, so the search that cannot work is no longer worth retrying. A search for a single name also asks for far more people than before and lists them a page at a time, with a "Show N more" button under the page: a common first name used to be cut off at twenty, which dropped the person being looked for when the relay ranked them twenty-first — and a relay ranks every match on a part of a name below every exact one, so those are the hits a short first page loses. - What is on screen is no longer offered to Android's content-capture service, which is how the system reads the contents of an app for features like "what's on my screen". Messages here are end-to-end encrypted and handing each one you scroll past to a service outside the app worked against that. Turning it off also makes scrolling cheaper: serving it was the largest single piece of work the app did on its main thread while a list moved, arriving as a burst longer than a whole frame several times a second. Accessibility is a separate service and is unaffected, so TalkBack and screen readers behave exactly as before. ### Fixed - Opening a group's info no longer does its cryptographic work on the drawing thread. The member list is read from the group's MLS state, which has to be restored first; that was happening on the thread that draws, so a group whose members had not been read yet could hold the screen still while it ran. It now runs off that thread and the screen fills in when it is ready. - The account list in Settings scrolls, so every account on the device can be reached. With six accounts the last row was already cut off at the bottom of the dialog and the list would not move; a seventh could neither be switched to nor removed, and there is no other route to either (#2914e41a). - "Sync now" in Multi-device no longer says your own groups just arrived from somewhere else. Re-reading this device's own tip recorded "Received from another device · 27 groups added" in Recent activity, on an account with no other device, beside a reconcile that had changed nothing. A tip that brings nothing new now adds no line at all, and Sync status goes on showing where the last tip really came from (#e510d76d). - Linking a connection string that belongs to a different account now says so instead of reporting success. The tip is readable only by the identity that wrote it, so such a link could never work, but it reported "Sync is on" and then retried a publish it could never make ("failed: no DEK") for as long as the app was installed. The link is refused with a reason and nothing is left switched on (#f5f0a730). - The chat list scrolls, so every chat in it can be opened. Join requests still waiting on an admin were drawn above the list rather than as rows in it, and enough of them left the list no room to scroll in at all: the chats past the fold could not be reached, the last one was drawn behind the bottom navigation bar, and dragging the list did nothing. Everything on the screen is one list now, and scrolls as one (#e72ab45a). - "Unread first" puts a chat with unread messages above the join requests waiting on an admin. A request has no messages in it, but it sat above every chat whatever the sort was, so the chat the sort was picked for could stay out of reach. Under "Unread first" the requests now follow the chats; "Most recent first" keeps them at the top, where something sent minutes ago belongs (#a0c2931b). - A request to a coordinator that travels inside an encrypted gift wrap now carries the time it was actually sent, rather than the time read before the message was signed. Relays refuse an ephemeral request more than a minute old, so a slow signing step could get one rejected as expired. - A key is read whatever its case. A QR code carries bech32 in upper case — its alphanumeric mode has no lower-case letters — so scanning someone's npub into Add people answered "That QR code isn't a person's key", an `NPUB1…` pasted there was looked up as if it were a name, and `NSEC1…` on the import screen was "not a valid nsec". The decoders underneath were always case-insensitive, as bech32 requires; the three places above them that looked at the prefix were not. A pasted key and a key in a cordn link now go through one reader (#1e6380b8). - Add people says what it is waiting for: one letter typed into the field left it silent with nothing underneath, which reads as a lookup that does not work. It now says that two letters or more look someone up by name (#1e6380b8). - Add people finds the people who are on your coordinator by name, not only by key. The app already asked the coordinator who has a key package there every time the sheet opened, and used the answer only to mark whether someone you had already found could be invited; nobody asked who those people were. Their names are now looked up (on the profile relays and on the coordinator's own), so typing a name finds someone on your coordinator even when you share no chat with them and no search relay ranks them for you (#934c75a1). - A message you just sent no longer comes back as a draft. A draft was written from what the composer held when the write was scheduled rather than when it landed, so a save that arrived just after a send put the sent message back in the box, leaving a chat within a moment of typing stored the text the chat was opened with instead of what you typed, and cancelling an edit left the edited message behind as a draft. Every draft write now reads the composer as it is when it happens, and a draft that comes back takes the cursor after it, so what you type lands at the end. Cancelling a reply is deliberately not the same as cancelling an edit and has not changed: the quote goes and what you typed stays, because those words are yours, where an edit's text belongs to the message it was opened from. What a cancelled reply no longer does is stay attached to the saved draft (#4defc582). - Asking to join a group no longer fails with "ephemeral event expired" when signing takes a moment. A join request is signed with your own key, and with an external signer such as Amber that means a prompt and a tap — up to a couple of minutes. The request went to the relays carrying the time read before that prompt appeared, and relays refuse a request of this kind more than a minute old, so the join was lost and the screen showed the raw relay error. The request now travels inside an encrypted wrap stamped when it is actually sent, so the wait for the tap no longer counts against it. A request the relays still reject as expired is sent once more with the current time. If every relay rejects it even then, the clock on this device is behind, and Staircase says so and points at the date and time settings instead of repeating the same relay error once per relay. - A join request now survives the wait for an external signer, however long the taps take. Approving a prompt in Amber puts Staircase in the background, and asking to join needs more than one: one to sign the request, and one to open the coordinator's reply, which comes back encrypted to you. Everything that could go wrong around those prompts did. The time a request had to be answered was counted from before the first prompt appeared, so every attempt was spent before the tap landed and the join failed with "no response for kp_publish". Staircase paused its connections while the signer was in front, throwing away the coordinator's answer as it arrived. A second prompt for the same request could cancel the first one's answer. And the time the reply's own prompt spent waiting for you was counted too, so Staircase gave up on an answer it had already been handed and asked to join again — which raised two more prompts, and could put a dozen of them in front of one person for a single join. Staircase now counts only time it is actually waiting on the coordinator: a prompt waiting on you costs a request nothing, at either end. Connections stay up while a prompt is out, prompts wait their turn instead of cancelling each other, and one prompt nobody has answered yet no longer holds up everything else arriving behind it. An answer that arrives but cannot be decrypted says so, rather than reporting the coordinator as not responding. - Staircase no longer asks an external signer for something while it has no window on screen. Android will not let an app that is not in front open another app's screen, so those requests reached Amber without ever appearing: Amber parks them in its own "Incoming Request" list and shows nothing, and the person is asked nothing. Each one then spent the full two-minute wait on a question nobody saw, and because a signer answers one request at a time, everything queued behind it waited too — a background check for new messages could hold up a join request for minutes. Staircase now leaves those until you next open it, which is the only time the prompt can appear, and says so rather than reporting the signer as unresponsive. When a request really does go unanswered, the message says to open the signer and confirm what is waiting there. And an answer that arrives after the request that asked for it has given up is now discarded instead of being handed to whatever is waiting: with Amber, confirming a parked request minutes later could otherwise deliver a signature for one event to a completely different one. - A message you were part way through writing as a reply still goes out as a reply. Leaving a chat kept what you had typed but forgot which message you were answering, and sending it afterwards put a plain message in the group that pointed at nothing — nothing on screen gave it away, because the composer looked like the one you left. The quote is now back above the composer when you come back, naming the same message as before. If that message is gone in the meantime — deleted, or not among the ones this device has — your text is kept and the composer says the message you were answering is gone, rather than sending it as a reply to nothing. - Leaving a chat part way through editing a message no longer turns that edit into a draft. The half-edited text came back in the box as if you had typed it, and sending it put a copy of your own old message in the group as a new one. Leaving now abandons the edit, which is what the ✕ beside it has always done, and a message you were writing before you started the edit is given back to you instead: starting an edit used to throw that away. - A gitworkshop.dev issue or pull-request link shows as a card again. The links the board prints now carry the git host between the owner and the repository (`.../<npub>/cordn.feeds.relay.tools/staircase/issues/...`), a shape Staircase did not recognise, so eighty characters of link sat in the bubble instead of the issue's subject and status. Both the old and the new form are read now, and so is any further routing the board adds between the two. - A link is read whatever its case, so a URL typed as the first thing in a message is still a link. A keyboard capitalises the first letter of a message, which turned `https://` into `Https://`, and that was not read as a URL at all: no underline, nothing to tap, and no picture, GIF, invite card or gitworkshop card under it. The same URL typed after a word worked, so there was nothing on screen to tell the sender that what they sent went out as plain words. A URL's scheme and host are now read in any case and shown lower-cased; the part after the host keeps the case it was typed in, since that half of a URL is case-sensitive and a picture at `.../Cat03.jpg` has to keep its capitals to load. A `cordn:` invite link and a bare group code typed first are read the same way (#4db65400). - A secret key pasted into a field that looks people up is refused instead of searched for. An `nsec1…` typed or pasted into Group info › Add people, Find people or Settings › Relays & Blossom's "Rank results through" was read as a name, and one tap of the keyboard's Search key sent it to the search relay as the thing to look for, in a query signed with the account it belonged to. Those fields now take no text that holds a secret key at all: it does not reach the field, nothing is looked up, and the field says what happened. A password-encrypted `ncryptsec1…` and a truncated or mistyped key are refused the same way. Importing a key where a key belongs, under "Import an existing key", is unchanged (#3fb04a26). - Typing in a chat with a long history no longer stutters on Android. Each character used to redraw the whole chat screen: every message on screen was rebuilt and the chat's history scanned once for each of them, so the longer the chat, the further the text lagged behind the keyboard. The message list is now left alone while you type, and the two scans are gone. Nothing about the composer itself changes — a draft still comes back with the cursor after it, with its quote, and is still saved once you stop typing. - The sign-out dialog no longer promises something that was never true. It said "you will need your secret key to sign back in" while the key stayed on the device, so the account went on being listed and signed straight back in on a tap, with nothing asked for — and that promise is the part a person handing their phone over acts on. Keeping the key is the intended behaviour, so the dialog now says what signing out really does and what it leaves behind, the lists of accounts say why one you signed out of is still on them, and "Remove from this device" is there for when taking it off is what you meant. Signing out also stops the account being opened again behind your back: it stayed the device's active account, so background message checks went on syncing it and raising its notifications, and the next time Staircase was started it signed back into it on its own. - Live messages arrive in the order they were sent, and a message is never passed over. A chat's live feed arrives as a stream of separate encrypted frames, each opened on its own, so under load they could be handed to the app in the order the decryptions happened to finish rather than the order the server wrote them — which moved the chat's position marker past a message and left it out of the chat for good. A frame that arrives early now waits for the one before it, and one that never arrives stops the feed and re-reads the chat from its last known position instead of skipping ahead. A feed whose reader has fallen too far behind is also restarted properly rather than stopping for the rest of the session (#7409923d). - A chat says so when this device can no longer follow it, instead of going quiet. When an admin puts a member back into a group, the change that does it cannot be read by the copy of the group that member's device already has, so that copy stays where it was while the group moves on. It used to keep calling itself up to date: every message that followed was put down to the sender's app being out of step and passed over without a word, so the chat simply stopped receiving while looking healthy, and messages written in it went out sealed for a group nobody was in any more. Such a chat is now marked as behind from the moment the change arrives — the warning appears in the chat and in the chat list, changes to the group are refused, and messages are held and sent once the device is back on the group's line. Accepting the invitation waiting in the Inbox clears it, and so does any later message the device can read, so a chat marked this way by mistake rights itself (#a990fee0). - Approving a join request from someone who was already a member of the group did nothing at all: the inbox reported it as approved and no change was made, so whoever asked was answered by silence. Such a request is now a repair, and approving it puts them back. Those requests also used to disappear from the admins' inbox on the next check, within a minute of arriving, before anyone could see them. - An invitation that would mend a broken chat is no longer the quiet option on the screen. When an admin has already put you back into a group whose copy on this device had stopped working, the inbox said only "You're already in this group", offered "Open chat" as the main button, and left the thing that actually mends the chat behind a secondary "Accept anyway" — so the repair was played down for exactly the people it was for. The card now says the chat has stopped working on this device and offers the repair as its main action, and the broken chat itself offers that waiting invitation rather than asking the group for a repair that has already been granted. - Amber's prompt says what Staircase is asking it to sign. Two of the things Staircase signs are event kinds Amber has no name for, so its prompt read "wants you to sign a Event kind 25910" and offered a "Report missing event kind translation" button, which looked like Amber reporting a fault in Staircase. Both now carry a short description that Amber shows instead — "a request to the Cordn coordinator that carries your Staircase chats" when the app calls a coordinator, and "a private index that lets your other Staircase devices find the same chats" when it publishes the multi-device pointer — and the report button is gone. Nothing changes for anyone who has already told Amber to approve these without asking (#d24a9fc3). - Opening a chat on a well-used account took a third of a second, however small the chat (and the chat's "Group info" connection dialog paused the same way): the screen read its saved draft, its read mark and its connection state straight from the database on the drawing thread, and the database is a single connection that was busy loading the chat, so the screen waited for its turn — twice. Those reads now happen on a worker and the chat appears as soon as its messages are loaded (#c3680019). - Opening a chat no longer queues its reads one behind another. On Android the database allowed one reader at a time, so the messages, the chat list, the outbox and the group's own row — all asked for at once when a chat opens — were read in turn, each waiting for the one before it; the database now keeps several connections open (write-ahead logging), so those reads run side by side (#3ffd8343). And the chat list itself is read once for the whole app rather than once per screen looking at it: the open chat's header, Group info, the notifications and the desktop badge used to each keep a list of their own and re-read it on every message, so opening a chat read the whole list twice more just to find that chat's name (#28c2af9c). - Oct 9, 2026 0.1.22### Added - Five colour palettes beside the one Staircase has always had — Violet, Teal, Forest, Ember and Rose — each with a light and a dark scheme, under Settings › Appearance › Theme. They are built the way a phone builds its wallpaper theme (one seed colour expanded into Material 3's tonal palettes), only from a fixed seed, so a palette looks the same on every phone and on the desktop app. Default still follows your device's own colours where it has them (Android 12 and newer); choosing any other palette uses that palette instead (#ebbcc747). - A gitworkshop.dev link to an issue or PR shows as a card: its kind, repo, subject, author, labels and status (open, resolved/applied, closed, draft), read from the NIP-34 event on the relays the link names. The link leaves the bubble's text (Copy message keeps it; the card has Copy link and Show link) and a tap opens it in the browser. A link without relay hints stays a plain link.
More…
- Find people can search a relay's whole index when no web of trust can rank a search for this account: "Unranked results" in Settings › Relays & Blossom, off by default. On, it asks once more with the relay's `include:spam` waiver after the ranked and observer asks found nobody, heads those names "unranked" with a warning about impostors and spam, and links to brainstorm.world to set up scores of your own. - While reading one chat, the back arrow carries a badge with what the other chats hold unread, in the colours the chat list uses (an "@" when any of it mentions you), and the Chats tab has the same badge next to the Inbox one. Both follow the notification settings: muted chats do not count, and with alerts set to mentions only mentions do (#daa8a1bb). - Find people works for an account with no web of trust of its own. The default search relay ranks people through the reader's trust graph, so a fresh account found no one and was sent off to set up a NIP-85 list first. A ranked relay's empty answer is now asked again through somebody else's eyes (the relay takes an `observer:` token unsigned; by default search.brainstorm.world's own key), the results say whose ranking they are, and Settings › Search relays has "Rank results through" to change or clear it (#360b00f4). ### Changed - Settings has fewer pages. The goodbye message and media auto-load moved onto the Appearance page, and Search relays and Blossom servers became one "Relays & Blossom" page. App links to the old pages (`staircase:settings:chats`, `:media`, `:search-relays`, `:blossom`) still land on the right page. ### Fixed - With the app set to Light on a phone in dark mode (or Dark on a light phone), the status bar's clock and icons now follow the app's choice instead of the phone's, so they stay readable (#25eeca02). - The light Teal and Forest palettes had bright aqua and mint bubbles for your own messages; they are now as pastel as Violet, Ember and Rose (#5538128c). - Inviting someone could leave them a member who was never told. The invite ran in the Add people sheet's own coroutine, so closing the sheet, going Back or switching accounts mid-invite cancelled it half-way: the person's key package taken and nothing posted, or the commit posted and their Welcome never sent. Invites, removals, group edits, accepting an invitation and approving a join request now run on the session and finish whatever the screen does. A Welcome still owed (the coordinator was unreachable when it was due) is retried on every inbox poll, not only at the next app start. And while an invite is in flight, the sheet no longer says the person being invited is "Already in this group": it checks against the members as they were when Invite was tapped. - A coordinator's relay list you typed stays. Opening the Coordinators screen (and the six-hourly refresh) replaced it with the coordinator's published list and announced "Updated relay list", so any list that departed from the published one was undone by the next visit. A hand-set list is now kept until you choose "Use the coordinator's published list" in the editor (or save an empty list); a list the app discovered keeps following the operator's changes as before. The card says which kind it has (#0c6a60c0). - The public Blossom fallback can take an upload again. The default list's third host, blossom.primal.net, refuses `application/octet-stream`, and encrypted media is always that, so it could never catch an outage of the first two. It is replaced by nostr.download, which takes an octet-stream from an unknown key, serves the same bytes back and honours a delete. When every server fails, the error now names each one and its reason instead of the last one only. Existing accounts keep their own list (#2f233cd7). - Correcting a coordinator's relays reconnects right away. Saving a new relay list closed the connection but left the live message streams to be re-opened by a background check up to a minute later — and if the coordinator was already failing (the usual reason to edit its relays) its retry ladder had to run out first, up to five minutes, so the chat stayed offline and the only quick way out was restarting the app. The connection indicator also went on showing what the dead stream last said; it now says "connecting" the moment the relays change. Saving a list that changes nothing no longer costs a reconnect, and in the relay box a line that is not a `wss://` address is reported instead of being dropped in silence. - An attachment no longer carries the name it was picked under. `VID_20261006_143012.mp4` says when it was shot and by which app, a screenshot's name says which app it was taken in, and a document's name is often its content; the name went to every member of the group inside the encrypted message. Files now go out as `image-1a2b3c4d.jpg`, `video-…`, `audio-…` or `file-…`, keeping only the extension so the receiving side can open and save them. This applies to documents too, like the rest of the metadata strip (#dfda283b). - A GIF in the chat kept its place while scrolling. A row that scrolled out of view was rebuilt from nothing when it came back: a linked GIF collapsed to zero height while it was decoded again (so the feed jumped), and an attached GIF was fetched from the blob server and decrypted again every time. Decoded frames are now kept across the rebuild, a linked image's shape is remembered so its slot is right from the first frame, and an attachment's decrypted bytes stay with the chat (#bf693427). - Oct 7, 2026 0.1.21### Added - Blossom servers: dropping a server from the list used to move new writes only, and the sealed sync documents stayed on the old host with nothing able to reach them again. The multi-device config now remembers the servers you left, the Blossom page lists them under "Servers you left" with "Move sync documents off them", which republishes everything to the current list first and then deletes your documents from the old hosts. The page says plainly that attachments already sent stay where they were uploaded (#50088efb). - Multi-device: the tip relays (where your devices find each other's state) can be edited. A "Tip relays" group on the Sync page lists them with add, remove, reorder, the app defaults and your own NIP-65 relays as one-tap presets; a change republishes the tip. A relay that refuses kind 30078 is named as such in Sync status instead of showing its raw error. The list still travels only in the connection string, so change it on each device or link again
More…
(#7ee23957; carrying it on the tip is #e31f9de6). - Coordinators can be renamed and removed. The card's Edit dialog has a Name field (empty goes back to the operator's profile name, or the short npub when they have none), so a coordinator that arrived with a group or has no profile no longer shows as an npub for good (#ccfe2996). Remove forgets the coordinator and its relays, retracts the key packages published there and promotes another default; it refuses while a chat still lives there, naming how many (#7aadb46d). ### Fixed - Multi-device siblings were never invitable on a second coordinator. Each coordinator got its own last-resort key package, but the meta document carries exactly one, so the other devices held the key for one coordinator's package and saw "another device holds the last-resort package" on the rest. An identity now has one last-resort package published on every coordinator; a coordinator still serving an older one of ours gets the shared one on the next Check or session start. - Seen on a three-device fleet: the Blossom page kept "Servers you left" after the move, the tip-relay editor kept the old list after a change, and the coordinator card kept "another device holds the last-resort package" after the key had arrived through sync. The multi-device config is one object changed in place, so those screens now follow a save counter, and the Coordinators screen re-checks a coordinator with that state when it opens. - "Another device of yours holds the last-resort package" was a dead end after a reinstall: the coordinator kept serving a package whose key was gone, and nothing in the app could replace it. The coordinator card now carries that state (not a snackbar) with a Take over button that removes the orphan and publishes this device's own package, and the message says which case you are in: another live device (link them; the key then arrives with the next sync) or a previous install (take it over). The reconcile summary is two sentences instead of a sentence glued to a count (#c182adb3). - Leaving a screen while a coordinator request was still in flight marked that coordinator "not responding" (and the chat's connection sheet showed Compose's own "left the composition" text as the last error). A cancelled call now leaves the coordinator's health alone (#ffa407ca). ### Changed - Defaults moved onto the project's own servers. A new account starts with three coordinators: cordn.feeds.relay.tools (the default for new groups; it hosts the Staircase chat), cordn.net and the Newlay Coordinator, so people on any of them can invite a newcomer without a coordinator-invite step. The default Blossom list is cordn.feeds.relay.tools, newlay.relay.tools and blossom.primal.net (the public hosts it replaces answered 502/503 through a day of testing); a user who never customised the list follows it. New multi-device fleets keep their tip on newlay.relay.tools, relay.nostr.net and relay.primal.net; relay.ditto.pub is gone from that list because it refuses unauthenticated subscriptions, so the tip could never be read there. Existing accounts and fleets keep what they have. - Settings is a hub again: the account card, then one row per area with what it is set to now, in three groups — Preferences (Appearance, Notifications, Chats, Media), Account & devices (Privacy & security, Coordinators, Multi-device, Search relays, Blossom servers) and Help (About, Diagnostics). Theme, clock, the desktop rendering choice, the goodbye message, auto-load media, the passphrase and app lock, trust scores and verbose logging each moved to their page; About now carries the version (tap to copy) and the cordn.net link. A cordn.net page link to appearance, chat, media or about settings opens that page, and the Android notification shortcut lands on Notifications. - Oct 7, 2026 0.1.20### Added - Onboarding. Before the first identity, three short pages explain how Staircase works (private groups, your key is you, coordinators carry the messages); Skip ends them, and Settings → About shows them again. A new or imported identity then gets a few steps, each skippable: a name (said plainly to be public; skipped when an imported key already has one), a copy of the key (local keys only), and on Android how to be notified, with nothing preselected. The notification permission is asked there, once you pick Instant or Every 15 minutes, no longer at launch. An existing account never sees these steps. The empty chat list now offers what you can do on your own: set your name, start a group, join with a link or code, invite someone. A join request you sent shows in the chat list as "Waiting for an admin" until the group arrives; after ten minutes it says the wait is normal, and tapping it can cancel it. One-time hints explain a chat's privacy, inviting into a new group and saving your key. The last step, and a card on
More…
the empty chat list, offer the project's chat, cordn-feeds: one tap asks to join, and an admin lets you in. - Search in a chat: the magnifier in the chat's top bar (or Ctrl+F on the desktop) opens a find bar. It counts the matches ("3 of 17"), and the arrows (or Return and Shift+Return) step through them newest first, each scrolled to and highlighted. It matches the text as you see it (mentions by name), the sender's name and attachment filenames, never a deleted message, and only what this device holds: with no match it says how far back this device's history goes. Back or Escape closes it and leaves the chat where the search took you. - Clock: Settings → Appearance now chooses between 24-hour and 12-hour time, and every time in the app follows — the time on a message, the chat list, the inbox, the image viewer. 24-hour stays the default, and 12-hour reads `3:45 pm`. The setting is this device's; dates are unchanged. - Forward a message into another chat: "Forward…" in the message menu opens "Forward to…". A text message comes in ready to edit. A photo or file goes with your own comment, if you write one, and its original caption only if you switch on "Include the original caption", which quotes it under your comment; otherwise just the file goes. An attachment is downloaded and decrypted there, then sent re-encrypted for the chat you pick. A forward is a plain new message from you: it says nothing about who wrote the original or which chat it came from, and doesn't carry what it replied to. - Lines starting with "> " are drawn as a quote, with a bar beside them. - Chat folders. Group your chats into folders of your own and fold them shut; a folded folder looks like a chat, with a tile of its chats' avatars, the newest message inside and how much is unread. Make the first one from a chat's own menu ("Move to folder…" → "New folder…"); rename, reorder or delete a folder from its own menu, which never deletes a chat. Chats in no folder sit under "Other chats". Folders stay on the device; with multi-device on, your linked devices share them, encrypted, as an event shaped like the multi-device tip on the tip's relays (nothing in it is signed with your key, so no signer prompt). Two devices that reorganise while apart keep both sets of changes. Changes arrive when you come back to the app, every 15 minutes, or from "Sync folders now" in the chat list's menu. Which folders are folded shut is per device. ### Changed - New accounts start on the coordinator cordn.feeds.relay.tools instead of cordn.net. Existing accounts keep the coordinators they have. ### Fixed - A new identity made on the welcome screen published no key packages until Staircase was restarted, so nobody could invite it. Leaving the welcome screen cut the account's start short; it now finishes regardless. - On desktop Linux a group whose picture is an emoji icon (❤, ☀, ✔ …) drew it as black line art; it now uses the colour emoji font like every other emoji in the app, in the chat list, the chat header and the folder tiles. - The quoted message in a reply, and a file card, no longer sit on a bright near-white card inside the bubble: they are a soft tint of the bubble itself, a little darker on the light theme and a little lighter on the dark one. - With the chat list sorted "Unread first", opening a chat no longer re-sorts the list under your hand: the chat you read keeps its place (its badge still clears), a chat that gets new messages still moves up, and picking the sort again puts everything in fresh order. - An invite card inside a message no longer paints a second grey panel over the bubble, and its shade no longer moves with the pointer: it takes the bubble's own colour with a thin outline. - The "Send this image?" dialog opens with the caption field focused, and the keyboard drives it like the composer: Return (or Ctrl+Return) sends, Shift+Return starts a new line, Escape drops the file. - A message you just sent no longer changes shape while it goes out. The sending status ("Encrypting…", "Accepted by 2 relays · waiting for the coordinator…") used to be measured as part of the bubble, so a short message's bubble swelled, its text reflowed and it shrank again as the stages went by, and once more when the sent message replaced it. The status is small print under the bubble now, on one line of reserved height, and so are Discard and Retry: the bubble is exactly the bubble the message keeps when it lands, and only the small print moves. - The chat list stays at the top when something new lands above the first row (a chat with a new message, or a folder you just made); before, the new row could be laid out just above the screen, out of sight. - Oct 4, 2026 0.1.19### Added - Start a new group from an existing chat: Group info → "New group with these people". It opens New group with the chat's name, icon, picture and coordinator, and everyone else in the chat ticked; untick anyone to leave them out, or add someone from outside the chat. The chat's admins who come along stay admins. Each coordinator says how many of the ticked people it can invite ("3 of 4 can be invited"), so you can pick the one that reaches everyone. - Save a received file: "Save file" in the message menu and Save in the image viewer write the decrypted file where you choose (the system save picker on Android, a save dialog starting in Downloads on the desktop). Tapping a file no app can open now offers to save it, instead of doing nothing. - Auto-load media has a third choice on Android: Always, Only on Wi-Fi, Never. "Only on Wi-Fi" waits for a tap on mobile data or a Wi-Fi marked as metered.
More…
### Changed - Auto-load media now covers attached photos and GIFs, not just images that messages link to. When it's off, a photo shows its blurred preview with "Tap to load" and nothing downloads until you tap. Videos always waited for a tap and still do. - Creating a group shows how far it has got ("Checking key packages · 12 of 40", then "Sending invitations…"), and leaving the screen no longer cuts it off halfway: it finishes, and Staircase says how it went with an "Open group" button. Back asks first while it runs. Add people shows the same count. ### Fixed - Inviting many people at once no longer fails for everyone because one of them has an expired key package (cordn-web key packages last about two weeks). They're left out, named in the result ("their key package has expired; it renews when they next open their app"), and everyone else is invited. - Oct 4, 2026 0.1.18### Added - Scrolled down the chat list when a chat gets a new message and moves up out of sight? A small notice at the top says so ("New in Design review", or "3 chats with new messages"); tap it to go up. Nothing scrolls unless you tap it, and muted chats don't raise it. - Notifications can alert only when you're mentioned (Settings → Notifications → Alert me for: Only mentions). Other messages still count as unread; muted chats stay silent either way. On Android, once a mention is up, later messages in that chat update it quietly and only another mention alerts again. Desktop has the same setting, and now alerts only for messages it hasn't shown yet. ### Changed
More…
- The group screen is easier to reach around. Delete moved to the top bar's menu (and the chat list's long-press menu), away from Save; editing the name, description and picture has its own sheet (the pencil in the top bar); Add people sits next to the Members heading instead of below the whole list; long member lists show the first 8 with "Show all". Results appear as a message at the bottom of the screen, where they are seen and read out. Screen readers get a "Members" heading and each member's name on their menu button. The coordinator shows by name, not as a key. ### Fixed - With multi-device, an invitation you accepted on one device no longer sits in the other device's Inbox forever offering Accept and Dismiss. It goes away by itself once the other device sees the group or the coordinator stops offering it. An invitation to a group you're already in now offers "Open chat" instead of Accept. - When a coordinator is down, the app says so. Settings → Coordinators shows "not responding since 11:16" for it; a chat on it says "cordn.net isn't responding" once that has lasted, instead of "Reconnecting · a moment" every time you open it; errors name the coordinator rather than an internal request ("isn't responding. Try again later, or use another coordinator"). Creating a group with invitees on a coordinator that doesn't answer stops before anything is made, so no empty group is left behind, and the new-group screen marks such a coordinator beforehand. In the Inbox, a refresh no longer greys out Accept, and a line says which coordinator couldn't be reached. - Inviting people says, as you type, whether each one can be invited on the group's coordinator ("Can be invited", "Not on Newlay yet", "Already in this group"), with their name, and offers the coordinator invite for anyone who isn't there yet. One person without a key package no longer cancels the invite for everyone: the others are invited and you're told who wasn't and why. Creating a group with invitees works the same way, and if inviting fails after the group exists, you're told and the group opens, instead of an error over a group you didn't know was made. Pasting an nprofile works too. - A chat with a new message moves to the top of the chat list and is actually shown there. The list used to hold its place so precisely that the chat which had just moved up ended up hidden a row above the top of the list, and nothing seemed to have happened. A list you have scrolled yourself is still left exactly where you put it. - A mention pasted as `nostr:nprofile1…` now tags the person it names, like a name picked from the list, so it counts as mentioning them. - An invitation in the Inbox says which group it is: its name and picture, how many members it has, and the coordinator by the name you gave it, where it showed "You're invited to a group" and an npub. The Welcome always carried all of it; it is now read on arrival, without joining. It also says when you are already in that group. - On Android, the keyboard capitalizes the first letter of a sentence -- or rather, it does so for the first time: the message field never asked it to, so a message started lowercase unless you reached for shift yourself. The same now goes for the other places you type words of your own: a photo caption, a group's name and description, a coordinator's name, and the display name and About on your profile. Fields holding something typed exactly -- a key, a share link, a relay address, your username -- are deliberately left alone. - Obtainium reads Staircase's version properly. The "Get it on Obtainium" button handed it nothing but the APK index, and with no version to read it fell back to showing (and comparing) a hash of part of the download, which is why an update could go unnoticed until you pushed it. The button now carries the settings that pull the version out of the filename. Anyone who added the source before this keeps their old settings; the README says which three to change. - Oct 2, 2026 0.1.17### Added - Swipe a message sideways to reply (Android). The reply opens with the cursor in the message box, which also happens now when you pick Reply or Edit from a menu. - Long-press a reaction (right-click on desktop) to see who sent it, grouped by emoji; a name opens their profile. - Images and GIFs that a message links to (.gif, .png, .jpg, .webp) show under the text, and GIFs play — attached ones too. A new setting, Settings › Media › Auto-load media (on by default), makes each linked image wait for a tap instead, since loading one lets its server see that you read the message. - Video attachments play right in the chat on Android. On desktop they open in your usual media player. - Photos open full size inside the app: swipe between a chat's photos, pinch, scroll or double-tap to zoom, back to close. "Open with…" is still there when you want another app.
More…
- A picked or pasted file now waits for Send, showing the picture (or the file's name and type), its size, whether its metadata was removed, and a caption you can edit, so the wrong file can't go out by mistake. - Long-press a chat in the chat list (right-click on desktop) to pin it to the top, copy its share link or show its QR code. A sort button at the top of the list switches between Most recent first and Unread first. - A note, event or article reference (nevent, note1, naddr) in a message shows as a short link such as "📝 note by @alice", opening the note on njump.me, instead of 80 characters of code. Copying the message keeps the full reference. - Desktop: the @mention list works from the keyboard — Tab or Down and Shift+Tab or Up to move, Return to take a name, Esc to close it. - "Full catch-up" in a chat's connection details: fetches the chat's history again and fills in any messages this device is missing but can still open, without changing anything else. ### Changed - A chat with unread messages opens at the "New messages" line, so you read on from where you stopped. The button that takes you down counts the new messages properly (it nearly always said "1 new"). - Reacting to, editing, deleting or pinning an older message keeps you where you were reading instead of jumping to the newest message. A send that fails gets a line above the message box, wherever you are in the chat. - Coming back to the app reconnects only the relays that went quiet and don't answer a quick check, Instant mode included, instead of every relay after a fixed time. A change of network still reconnects everything. - On Android, Staircase stays running while a photo or file uploads, with a quiet "Sending a file…" notification, so leaving the app no longer freezes the upload. - Decrypted files left over from opening attachments are cleared when the app starts. - Desktop: the menu bar is gone (it was a white strip above the app). Ctrl+, Ctrl+N and Ctrl+Q still work. ### Fixed - On Android, a short trip out of the app no longer costs the connection. Coming back after a few seconds reconnected every relay even though nothing had been disconnected, so a glance at another app showed "Connecting" on return; now only an absence long enough for Android to have cut the network, or a change of network while you were away, reconnects. - On Android, leaving the app while a photo or file is uploading no longer closes the connections under the send — which is the common case, since picking the file is what put the app in the background. The background pause now waits for a send that is actually on the wire, up to half a minute, before it drops the streams. A send that is queued, held or failed does not hold it: those are waiting for the network anyway. - Sending a photo or a file no longer does its last step on the main thread: encrypting and uploading were already off it, but sealing the message and posting it to the coordinator were not, so a send could stutter the app just as it finished (and, in a chat that had fallen behind, catch the whole chat up from the main thread first). - Queueing a message, reaction or edit and saving a draft no longer write to the database on the main thread. - Desktop: clicking a link in a message opens it in the browser, and opening a received file works again (both did nothing on most Linux desktops). - Desktop: the file pickers open in front of the Staircase window instead of behind it. - Desktop: emoji show in color instead of thin outlines (❤, 😂 and 👍 among them). - Desktop: switching between chats no longer lands you at the top of a chat you had already read. - After catching up with another of your devices, a missing older copy of its state no longer stops the catch-up altogether, so the messages in between are filled in as far as this device can open them. - The chat's connection details scroll, so everything at the bottom can be reached on a phone. - Sep 30, 2026 0.1.15### Added - Cordn invites in a chat show as a card: a group, a coordinator or a person, with Join (or Open chat, when you're already in), Copy link and Show link. The long link leaves the message text; Copy message still includes it. Tapping a cordn link in a message opens it in the app instead of the browser. ### Changed - The MLS engine is now upstream Quartz (amethyst `950ae96ea1`) with no local changes: the fixes and features Staircase carried as patches were all merged upstream. Nothing changes on the wire; saved state for past epochs now matches what ts-mls writes more closely.
More…
### Fixed - Coming back to the app after a while in the background (seen on a Pixel 8a) could leave the chats "Connecting" for up to a minute: Android had cut the app's network a few seconds after it left the screen, and every relay was waiting out a long retry delay when you returned. Back after more than a few seconds (a minute in Instant mode), the app now reconnects every relay at once, and a change of network (Wi-Fi to mobile data) does the same. - Returning to the app right as its background pause came due could close the connections it had just reopened. - Removing the last other member of a group sent a commit without the update path RFC 9420 requires, so other MLS clients could refuse it (upstream fix `ce640e7742`). - Sep 25, 2026 0.1.7### Fixed - A relay that stopped answering is reconnected automatically. Before, it could stay disconnected until the app was restarted, and a coordinator reachable only through it showed "not responding". - Subscriptions a relay closed are re-opened, so one relay dropping out no longer stops messages arriving. - Sending no longer waits for slow or dead relays once one relay has accepted the message. - At startup, two requests to the same coordinator could get each other's replies; replies are now matched exactly. ### Changed - Release builds log much less: warnings and connection changes only, with no message content or full identifiers. Settings › Diagnostics › Verbose logging turns on detailed logs for a bug report.