CHANGELOG.md
Rama main · réplica de lectura del repositorio original.
# Changelog
All notable changes to Ohiyo are documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
Ohiyo follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html) — though while
we're pre-1.0, minor releases can still carry breaking changes.
Making a user-facing or security-relevant change? Add a line under **[Unreleased]** in
the matching category — see [CONTRIBUTING](CONTRIBUTING.md#changelog).
## [Unreleased]
### Added
- **A paid plan for Instant Servers,** about $8 a month per community. Upgrade a server from
Instant Servers and pay with Stripe Checkout; the plan turns on when Stripe confirms the
payment. Ohiyo never puts a paid server to sleep, and paid servers don't count toward the
3 free ones. Cancel in the app (it stays paid until the period ends) or manage the card and
invoices in Stripe's billing portal. A failed renewal keeps the plan while Stripe retries,
then the server moves to the free plan and keeps running. Deleting a paid server or the
account cancels its plan first. Where payments or real hosting aren't set up, the app says
"Paid plan unavailable" and why, and a development server says its servers are simulated.
- **Events, properly.** A month calendar (today marked, arrow keys move between days) and an
agenda, which phones open on. An event can have an end time, a place (one of the space's
voice or text channels, or an address or link of your own) and a cover picture. Answer
Interested, Going or Can't go, and see who's going. The host, or anyone who can manage the
space, can edit an event or cancel it, and people who answered are told. People who said
Interested or Going get a reminder 15 minutes before and at the start, with Join voice for
an event in a voice channel, unless they silenced the space. Add to calendar saves an .ics
file. Times show in your own time zone.
- **Help & feedback,** at the foot of Settings and in the command palette: this home's live
status ("All systems normal", or which parts aren't working), what's new, a bug report
prefilled with your app version and system (and nothing private), questions and ideas,
private security reports, the Terms and Privacy pages, and an honest note on getting back
into an account. The sign-up form links the Terms and the Privacy Policy.
- **Notification sounds you can choose,** in Settings → Notifications: on or off, five short
sounds, a volume, and whether only DMs and @mentions make one (the default) or everything
you'd be notified about.
- **Two-factor sign-in.** Settings → Account → Two-factor sign-in: enter your password, scan
the QR code with an authenticator app (Google Authenticator, 1Password, Authy, Aegis…) or
type its setup key, enter the code it shows, and save the 10 recovery codes. Signing in then
asks for a code after the password, and a recovery code works instead, once each. Changing
your password or deleting your account also asks for a code, and so do turning it off and
making new recovery codes. Your other devices are told when it's turned on or off. The
secret is stored encrypted and recovery codes only as hashes. Signing in to an account with
it on needs this version of the app: the desktop app gets it in its next release.
- **Incoming webhooks, like Discord's.** People who manage a space's channels can make a
webhook for a text channel (Space menu → Webhooks): a secret URL that another service posts
`{"content": "…"}` to, and the message appears in the channel under the webhook's name and
picture, marked APP. The URL is shown once, can be renewed, and only a hash of its token is
kept. A post can't pose as a person or ping @everyone, and posting is rate-limited. Not in
DMs or group chats, which are end-to-end encrypted.
- **Threads: rename, delete, follow, unread and archive.** Whoever started a thread, or anyone
who manages the channel's messages or channels, can rename or delete it (the thread's ⋯
menu). You follow a thread when you start it or reply in it, and can follow or unfollow it
from its header: a followed thread counts as unread ("3 new" under its message and in the
Threads list) and notifies like its channel, a push included, under the channel's
notification settings; any other thread only notifies when it @mentions you. A thread nobody
has written in for a week shows as archived, under "Older" in the Threads list, until
someone replies.
### Fixed
- **The GIF picker works again.** It searched Tenor from the browser, and Google shut Tenor's
API down on 30 June 2026. GIF search now goes through your home's server to KLIPY
(`KLIPY_API_KEY`), so your searches don't carry your IP address to it; a home without a key
shows no GIFs tab, and encrypted chats show none either.
- **Sounds stop ticking on every message.** The old "Message sound" plugin played for your own
messages, muted chats and busy spaces; sounds now follow the notification rules.
- **@mentions work for every username,** including ones with dots and dashes (`@alice.b`,
`@bob-c`): they're highlighted, offered in the mention picker, and count as mentioning you.
A full stop after a name ("thanks @alice.") isn't part of it, an email address is no longer
highlighted as a mention, and `@john.doe` no longer notifies `john`. The app and the push
relay read mentions by the same rule.
- **On a phone, Enter starts a new line** and the Send button sends, like Discord's phone app.
With a keyboard nothing changes: Enter sends, Shift+Enter starts a new line.
- **Notification settings go with what they were for:** deleting a space, a channel or a
thread deletes everyone's settings for it.
## [0.3.1] — 2026-10-12
### Added
- **ohiyo.gg has a Download section** for the desktop app, with the steps to open the Mac beta.
- **Notification settings, like Discord's.** A space can notify you about every message, only
@mentions, or nothing (Space menu → Notification settings). Mute a space, a channel or a DM
for 15 minutes, an hour, 8 hours, a day, or until you turn it back on (the bell in its
header). A muted chat sends no notification or push and adds nothing to your unread
number; an @mention still gets through unless the space is set to Nothing. Settings follow
your account to every device, and the push relay obeys them.
- **Messages format like Discord's.** Headings (`#`, `##`, `###`), `-#` subtext, `>` and `>>>`
quotes, `-` and `1.` lists, `__underline__`, `~~strike~~`, `_italic_`, ```` ```language ````
code blocks, `[masked](https://…)` links (the real address shows on hover, and beside the
link when its words name a different site), `\` escapes and `<t:…>` timestamps shown in
your own time and language. Shift+Enter starts a new line, and line breaks show.
- **Threads.** Start one from any message in a space's text channel and the replies stay out of
the channel. Under the message: the thread's name and "3 replies · last reply 5m ago",
updating live. It opens beside the channel on a computer and full screen on a phone, and the
channel header lists its threads. A thread follows its channel's permissions and is deleted
with it. Not in DMs or group chats, which are end-to-end encrypted.
### Changed
- **The space menu opens from the space name,** like Discord: invite people, change the logo,
create a channel, events, the moderation queue, notification settings, and leave or delete,
all labelled in one menu instead of a row of unlabelled icons.
- **A two-line user bar:** your name, then your status. Activity and sign-out are in the menu
that opens from it.
- **Friendlier times:** "Today at 9:23 AM", "Yesterday at 7:12 PM", then the date in your own
language; the exact time shows on hover.
- **Less in the sidebar:** the getting-started checklist goes away once it's finished, and the
permanent "Ready when you are" card is gone.
- **Settings speaks in sentence case** like the rest of the app, and the Account card no longer
prints your user ID; "Copy user ID" puts it on the clipboard when support or a self-hosted
setup asks for it.
- **The command palette only offers Instant Servers where they can be made.** The hosted
service can't make them, so its palette no longer lists them. The server answers
`GET /api/v1/instances/available`.
- **The app's typefaces ship with it.** Inter, Sora and Geist Mono used to load from Google
Fonts, which held back the first paint and showed Google the IP address of everyone who
opened Ohiyo. The web and desktop content policies no longer allow Google's font hosts.
### Fixed
- **Desktop: signing out clears the dock badge, the tray tooltip and "Leave call",** which kept
their last values before.
- **Desktop: "Check for updates" in the tray answers before you sign in** instead of doing
nothing.
## [0.3.0] — 2026-10-12
### Added
- **Join a space with an invite from the + button.** "Add a space" now offers both: create
one, or paste an invite code or link to join one. Before, the only place to paste an
invite was the welcome screen, so someone who already had a space couldn't join another
from the desktop app.
- **Desktop app for Mac and Linux:** it can stay in the tray when you close the window, so
notifications keep arriving (on by default on Mac, a setting on Linux), with the unread
count on the Mac dock icon and a tray menu (open, leave call, check for updates, open at
login, quit). The official builds offer their own updates and install one when you say
so; a copy you build yourself never looks. On Mac it asks for the microphone and camera properly,
so voice can work. It talks to the current server; 0.2.0 still pointed at the old one
and has to be replaced by hand once. Mac builds are a beta that Apple has not verified.
Windows is not included yet.
- **Delete your account**, under Settings → Account. It needs your password and your
username typed out, and it happens at once. Every message you sent is deleted, for
everyone. A space you own goes to its longest-standing member, or is deleted if nobody
else is in it; a group you own goes to the next member. Instant Servers you run are shut
down. Your encryption keys and recovery backup are deleted on the server and on that
device. Emoji you added to someone else's space, and pictures others still use, stay.
- **You're told when a new device signs in to your account.** Every device has its own
encryption keys, and people encrypt a copy of each message for each one. So someone
with a stolen session could add a device and quietly read everything sent to you after
that. Your other devices now say so the moment it happens, or the next time you open
them. "It was me" keeps it; "Not me" takes it off your account and signs out every
session, that one included.
- **Sign out everywhere**, under Privacy & security → Linked devices. Ends every session
of your account, on this device too. The server could already do this; nothing in the
app asked it to.
- **A contact's new device is announced in your chat.** Each of their devices can read
what you send, and a new one used to be trusted without a word. Now the chat says
"They added a new device." If you had verified them, it's a loud warning instead, and
the verified mark comes off until you compare safety numbers again: verified now means
the devices you actually compared, not whatever they add later.
- **Change your password**, under Settings → Account. It needs your current password, and
it signs you out everywhere else, so someone who knew the old one, or copied a session,
is out. The device you change it on stays signed in. Your encryption keys and recovery
backup don't use the password, so nothing else needs redoing.
- **Leave a space, or delete it if it's yours.** The ⋯ button beside a space's name has
"Leave space" for members and "Delete space" for the owner, which asks for your password.
The app had neither before.
- **YouTube videos and posts on X play in the chat:** a YouTube link shows the video's picture
with a play button; press it and the video plays right there, from the moment the link points
at. A link to a post on X opens the post in the chat, sized to fit. No player is loaded
from YouTube or X until you press the button (outside encrypted chats the video's picture
is, to draw the card), each card has a Close button, and leaving the chat closes them. It
also works in end-to-end encrypted chats: there the card is a plain button with no picture
or title, and nothing at all is fetched before the press. The app's security policy now
allows frames from exactly two places, YouTube's player and X's post frame.
- **Dream room lighting and gestures:** an outside-only live compositor halo where supported, lighter mode controls, local volume by sliding the right surround, and host double-click/tap playback. Provider controls stay uncovered; unsupported mobile volume uses device controls.
- **Watch-party viewing modes:** Dream feathers the video edges into a softened room;
Cinema opens a black fullscreen view with thin top/bottom insets. Both keep the
existing player and playback state, with accessible mode controls.
- **Voice pre-join roster:** voice channels now show who is already in the call before
you join, including a live count and compact participant preview.
- **Call entry preview:** joining a non-empty voice room now opens a “Ready to join?”
panel with occupants, Join, Join muted, and Cancel.
- **Privacy polish:** the sidebar now shows a Privacy Mode badge when metadata privacy
is enabled; encrypted attachment previews/messages show trust labels; empty channels,
empty DMs, onboarding link entry, and the command palette now guide users toward the
next private action.
- **Owner activation checklist:** new community owners now get a local-only launch
checklist for account → first space → first message → invite → voice call, with
clearer first-space onboarding that explains the seeded #general and voice room.
- **Instant Servers UX:** added a rail-accessible manager for create/list/use, sleep/wake,
ownership-pack export, self-host graduation instructions, delete, and paid-tier billing
handoff; backend lifecycle endpoints now support sleep, wake, export, graduate, and tier
state with owner-scoped tests.
- **Push/mobile foundation:** added PWA manifest/service worker, Settings → Notifications,
content-free push-device registration and relay queue APIs, offline-recipient enqueue on
message send, notification privacy copy, and an APNs/FCM runbook for native mobile.
- **Reliability foundation:** added a public status page/API, hosted-community cost model,
backup-restore drill script, status/alert check script, gateway/message load-smoke script,
and reliability runbooks for backups, observability, alerting, and load testing.
- **One-command Discord template migration:** added an owner-authenticated template import
endpoint and CLI script that reconstructs category/channel hierarchy, roles, best-effort
permissions, overwrite snapshots, server icons, and custom emoji assets.
- **In-app Discord template move-in:** the Discord import wizard now accepts a template
link directly and shows a permission-review gate with mapped roles, overwrite counts,
imported emoji/assets, and a clear “review before inviting” warning.
- **Permission matrix audit:** imported Discord overwrite rows now have an owner/mod-only
review API and in-app 1-2-3 audit view with exact allow/deny bitfields, decoded flags,
manual-review reasons, asset provenance, and a safe invite checklist.
- **Grandma-readable migration review:** permission rows now include plain-English verdicts
such as likely-private, read-only, can-see-and-chat, voice-room access, or powerful
channel control, with a simple "do this before inviting" instruction.
- **Discord migration landing wedge:** the public landing page now advertises
"Move your Discord community in one link" and routes admins to the app plus the
migration guide.
- **Recovery backup v2:** personal recovery now writes a future-continuous, keys-only
v2 backup envelope with per-entry device provenance, recovery-secret-derived blinded
coverage handles, manifest-consuming restore preview, legacy v1 restore support, a v1
refresh nudge, durable design notes, calmer protection-first copy, and first-class
undecryptable-message states instead of a fake retry loop.
### Security
- **Encryption trusts only the devices you can see.** A message is encrypted only to the
devices in the other person's device list, under the key listed there, which is the same
list their safety number and "They added a new device" come from. So the server can't
slip in an extra reader, and if the list can't be fetched the message isn't sent. A
message from a device that isn't on the list raises the new-device notice instead of
showing as normal, and the first encrypted-DM format, which the server could forge, is no
longer opened. Removing a device gives your group chats new keys, so it can't read what
is sent after.
- **Signing out everywhere ends everything a stolen session left behind.** "Sign out
everywhere", "Not me" and a password change now also cancel any device-link code still
waiting, and stop push notifications to devices whose session has ended.
- **Lasting changes ask for your password:** deleting a space, replacing or deleting your
recovery backup, and changing the dead man's switch. Wrong guesses are limited to 5 a
minute.
- **The new-device alert is harder to miss.** It is checked again whenever the app
reconnects, so a device added while your laptop slept is still flagged; a second tab can
no longer erase it; it shows when the device was added, not when you noticed; and "Not me"
removes the device and signs everyone out before it forgets the device, so a failure can
be retried.
- **The dead man's switch ignores our outages.** Time the server was down no longer counts
against you, and a switch that has fired doesn't fire again.
- **A block also stops group chats:** someone you blocked can't add you to a group chat,
and you can't add them.
- **Deleting a channel or a space deletes its files** (attachments, custom emoji and the
space's icon), and links to them stop working.
- **Saved shows only messages you can still see.** Leave a space or lose access to a
channel, and its messages leave your Saved list.
- **One account can't swamp everyone else:** presence, activity, voice state, watch-party
controls and group encryption keys are rate-limited; an account keeps its 10 most recent
push devices; a device holds at most 500 one-time keys, published at most 30 times a
minute; and an account gets at most 60 connection tickets a minute.
- **Permissions:** role hierarchy is now enforced when unassigning and deleting roles;
the gateway's initial channel list and unread counts respect View Channel; kick, ban
and leave clear the member's roles, member-level overwrites and voice seat.
- **Operator-only actions:** Discord imports that read local files or use the managed
bot, and Instant Server tier changes, are limited to the accounts listed in
`OHIYO_OPERATOR_USER_IDS` (unset means nobody). The local import reads media only
from `OHIYO_DISCRAWL_MEDIA_ROOT`, and import asset URLs are fetched only from public
addresses.
- **Rate limits:** limits are keyed on the real client address (`Fly-Client-IP` on Fly,
`TRUSTED_PROXY_HOPS` behind other proxies) and IPv6 clients are counted by /64 and
/48. Login is limited per username, registration to 10 new accounts an hour per
address (`OHIYO_REGISTER_LIMIT_PER_HOUR`), and invite previews, prekey fetches and
template imports have limits of their own. An account can hold 10 devices.
- **Abuse limits:** length caps on names, topics, bios, statuses, profile and poll
fields; a message carries at most 10 attachments; the message list page size is
clamped to 100; gateway frame size and sockets per user are
capped and silent sockets are closed; password hashing runs off the async runtime
with bounded concurrency; failed uploads no longer leave temp files behind.
- **Sessions:** "log out everywhere" now closes open gateway sockets and voids gateway
tickets issued before it.
- **Outbound requests:** the link-preview and import fetchers refuse IPv4-mapped, NAT64,
6to4, Teredo and other non-public addresses. Push dispatch accepts only known web
push services and well-formed APNs tokens, with a timeout and no redirects.
- **Privacy Mode** now also covers read cursors and last-active times reported over
REST, and the dead-man's switch no longer fires for users who are online.
- **Encryption (app):** a group message is never sent as plaintext when encryption
fails; group message signatures are verified before the key ratchet advances;
forwarding cannot carry decrypted content into the clear or plaintext into an
encrypted chat; only DMs and group chats can enter encrypted mode; decrypted messages
and encrypted chats show no link previews or embeds; encrypted attachment links must
point at this home's file store; polls and watch parties are hidden in encrypted
chats because they are not encrypted; a decrypted message cannot be edited while the
lock is off.
- **Local data (app):** signing out of the last account on a device asks first, then
clears the decrypted-message cache, the unsent outbox and drafts (keys are kept); the
same data is cleared when a different account signs in there, and kept when a session
simply expires. Drafts in encrypted chats stay in memory only; cached decrypted
messages are dropped once they expire; a full browser storage evicts the oldest
cached messages instead of crashing the app.
- **Attachments:** files attached before a chat became encrypted are removed from the
composer, and an encrypted chat refuses to send a file that was not encrypted.
- **Desktop vault:** a keychain or sealed-file failure now leaves the vault locked
instead of starting an empty one that would overwrite the saved keys. The locked
screen offers Try again and, when the saved keys cannot be opened, a confirmed reset.
The sealed file is written atomically, and burning the vault restarts the app.
- **Calls:** call signalling is ignored unless you are in that call and the sender is
one of its participants.
- **Plugins:** more network-capable APIs are removed from plugin workers, and plugin
CSS containing escapes or anything that can make a request is rejected.
- **Web app headers:** Cloudflare Pages now sends `X-Frame-Options: DENY`,
`frame-ancestors 'none'`, HSTS, `nosniff`, a referrer policy and a permissions policy.
- Message notifications respect the current blocked list.
- **Dependencies:** `jsonwebtoken` 10, `rustls`, `quinn-proto` and `event-listener`
updated past their advisories; CI now runs `npm audit` and `cargo audit`.
### Changed
- **A flatter message bar.** One bar holds everything: + on the left, emoji and send on the
right, with no box inside a box. On a computer, send shows once there's something to send;
on a phone it's always there.
- **A new sign-in screen.** A painting fills the screen and follows your local time:
sunrise, morning, the meadow in the afternoon, sunset, and a starry night with fireflies.
It greets you to match ("Good morning.", "Good evening.", "Still up?"). The fields sit
straight on the scene with no card around them, the long note about forgotten passwords is
tucked behind one line, and the whole form fits the desktop app's window without scrolling.
- **The client installs with Bun.** `client/bun.lock` replaces `package-lock.json`, and CI,
the release build, the web app's Docker image and the Tauri dev/build hooks use Bun to
install packages and run scripts. Node still runs `tsc`, ESLint, Vite and the unit tests.
Contributors need [Bun](https://bun.sh) 1.4+ alongside Node 22.
- **Dependency updates.** The server moves to tokio 1.53, serde_json 1.0.151, futures
0.3.34, flate2 1.1.10 and uuid 1.27. The release build uses tauri-action v1 (same
inputs, now on Node 24; it still uploads the updater's `latest.json` and leaves the
release a draft), and the watch-modes check uses setup-node v7 and upload-artifact v7.
- **Restoring keys warns you first.** A restore makes this device take the place of the
device that made the backup. If both keep being used, encrypted messages can stop
decrypting on both. The restore box now says so, and points to Link a device when the
old device still works.
- **The desktop app is on Tauri 2.12.** The Rust crates and the `@tauri-apps` npm packages
moved together (autostart 2.7, updater 2.13, deep-link 2.6, notification 2.6, opener
2.7), because the Tauri CLI refuses a build where a plugin's crate and npm package are on
different minors. Nothing the app does changes.
- **Usernames are the same in any case, and use a fixed set of characters.** For new
sign-ups, "Alice" and "alice" are the same name, and a name can use letters, numbers,
dots, dashes and underscores. The sign-up form says so before you submit. You can sign in
with your name in any case. Existing names keep working as they are.
- **Uploads: 1 GB per account,** down from 10 GB (`MAX_UPLOAD_BYTES_PER_USER` changes it).
Uploads also stop while the server's disk has less than 5 GB free
(`MIN_FREE_DISK_BYTES`; 0 turns it off), so the database always has room to write. A
refused upload, including a picture or emoji, now says why. Running out says how much
space you have and that deleting messages with files you no longer need makes room.
- **Plain words for signing out and deleting.** The app no longer says "session". The list
of what deleting your account does now covers group chats, one-to-one chats (they show
"Deleted account") and your username (free for anyone again). After a deletion the app
says calmly what it knows.
- **Red buttons and red text are readable in every theme** (custom themes get a "Danger
button" colour in the theme editor), and the new-device alert's answers are big enough for
a finger. Delete account puts the cursor in the password field, and "Not me" and "Sign out
everywhere…" move keyboard focus to their question and back on Cancel.
- **DMs show who they're with.** After a reload a 1:1 chat used to be called "Direct
Message"; it now shows the person's name, picture and online dot. A new DM appears in
the other person's list at once. Deleting your account fits a phone screen.
- **Outage alerts can go to a phone.** The reliability check posts plain text with a title
to an ntfy topic (`ALERT_WEBHOOK_URL=https://ntfy.sh/<topic>`), and Slack or Discord
webhooks now get real line breaks.
- **ohiyo.gg has a new first screen:** one painting, edge to edge. A sunlit meadow with the
Ohiyo mark as a stone sculpture and Kikka sitting beside it, with the headline, one line of
description and the two buttons on the sky at the left. Leaves drift across, butterflies and
far-off birds move, and the sun glows. On tablets and phones the words come first and the
sculpture and Kikka sit below them. The app screenshots moved to just under the first
screen, and the link preview picture (the card shown when ohiyo.gg is shared) uses the new
painting too.
- **Plugins page you can read:** a plain introduction, the list first, real on/off switches that
say which plugin they changed, and adding a plugin from a link folded away at the bottom with
what such a plugin can and cannot do. Four switches that changed nothing are gone (Custom CSS,
Font Picker, Link Preview, Code Highlight) and the rest have plain names.
- **Spoilers for everyone:** `||text||` is hidden until clicked for every reader. It used to be
a plugin, so only readers who had switched it on got it.
- **Plainer wording across the app,** and several lines that were not true are fixed: a DM opened
from a one-time link is not encrypted until the lock is on, a recovery code restores keys but
does not sign you in, uploads have a size limit, and the dead man's switch now says what it
deletes. Notifications no longer shows server setup instructions to everyone.
- **ohiyo.gg comes alive:** the birds now flap their wings and fly in two flocks, near and far.
Butterflies and drifting seeds move over the meadow, a rabbit peeks over the hill now and
then, a shooting star crosses the night sky, sun rays turn slowly behind the sunrise, and
Kikka breathes on her hill. Scenes hold still while they are off screen, and everything stops
for people who ask for reduced motion.
- **ohiyo.gg scenery:** the landing page now looks like a day in a valley, painted in
gouache. A dawn valley behind the top of the page, a starry night behind the privacy
section, a sunrise behind the mission, and Kikka on a hill in the morning at the end. A few
small things move on top of the paintings (a glow on the sun, mist, birds, twinkling stars,
fireflies) and stop for people who ask for reduced motion. Hard outlines and block shadows
are replaced by soft shapes and soft shadows.
- **ohiyo.gg redesign:** a new landing page in plain language, built from real app
screenshots and the Kikka mark. It explains how Ohiyo works in three steps, shows what the
lock does with a small demo, and states the mission. Fonts are self-hosted, so the site
now loads nothing from other websites. The privacy, terms, threat model and status pages
share the new look.
- **ohiyo.gg claims brought up to date:** the page no longer advertises Instant Servers (off
on the hosted service) or the desktop downloads (the released builds still point at the
old server), and it says plainly that channels in a space are not end-to-end encrypted.
The privacy and terms pages name the current server address.
- **Dream mode glow:** the whole room now takes the video's colours (a wider, much more
saturated blur, with a lighter dimmer over the page), and the video no longer ends in a
rectangle: the glow fades in over its edges, so the picture dissolves into a blur of itself.
The fade is a little narrower at the top and bottom so the player's title and controls stay
readable. The middle of the video is never touched.
- **Dream mode is desktop only:** the button no longer appears on phones or touch-first
tablets (including a phone held sideways), or in a window narrower than 701px. If a
desktop window is shrunk to that size while Dream is on, Dream turns off and stays off
when the window grows again. Cinema is unchanged.
- **Default avatar:** a member who has not set a profile picture now shows the Ohiyo logo
instead of their first initial, in chat, the member lists, profile cards, calls and settings.
Group chats keep their letter. The two sidebar circles that painted no background (so the
letter was dark on dark) now draw their gradient.
- **Home rail:** use the Ohiyo logo in place of its initials and remove the lightning
shortcut. Instant Servers remains available from the command palette (Ctrl/⌘K).
- **The hosted backend moved from Fly.io to Railway and started with an empty
database.** Existing hosted accounts, messages and files were not carried over, and
the web app drops a stored `ohiyo.fly.dev` home and asks you to sign in again. The
desktop app connects to the new server: release builds now take the server address from
`client/.env.production` instead of a repository secret that still named the old one.
Instant Servers are unavailable on the hosted service until they are rebuilt for
another provider. The hosted web app is served from a container (`client/Dockerfile`)
on Railway as well, and `scripts/deploy-web.sh` deploys it.
- Group encryption is now labelled **Experimental** in the app, with a note that it can
miss messages sent while a member was offline.
- Deleting a message now also deletes its attachments when nothing else refers to them.
- Message history pages no longer skip or repeat messages created in the same second.
- The gateway now answers heartbeats, so an idle connection is no longer treated as
dead and reconnected every 40 seconds.
- A call is no longer dropped when another of your devices disconnects.
- A member with Manage Roles now creates roles just below their own highest role, so
they can manage what they create.
- "Activate paid" for Instant Servers is replaced by a disabled "Paid plan: coming soon"
control until billing exists.
- The local Discord import no longer takes a media folder from the request, and import
options your account cannot use are hidden.
- Profile and channel fields enforce the server's length limits, and rate-limit and
device-limit errors show clear messages.
- Voice state join/leave/mute/video metadata now reaches everyone who can access the
channel so the sidebar updates live; WebRTC signaling and voice encryption keys remain
restricted to actual call participants.
- The public download page now includes a Mac beta FAQ explaining the current
non-notarized Gatekeeper warning and recommending the browser app for the smoothest
first run today.
### Fixed
- **Pasting an invite into "Add an Ohiyo home" no longer breaks the app.** It used to save
the code as a server address and switch to it, so nothing could connect. An invite or
private DM link pasted there now opens on your current home, and a home is only added
once it answers as an Ohiyo server.
- **No more missed messages after a reconnect.** When a phone wakes, Wi-Fi changes or the
server restarts, the open chat now catches up on what was sent, edited or deleted while
you were away. A connection that falls too far behind starts over instead of silently
losing events.
- **A deleted account leaves everyone's screen at once.** Its messages disappear live, and
1:1 chats with it show "Deleted account" with no message box.
- **A space you join shows up on your other devices** without a reload.
- **Missing data no longer looks like loss.** If your spaces or chats can't be fully read
when you connect, the app reconnects instead of showing them empty.
- **Two things at once no longer give a server error:** opening the same DM twice (you get
one DM), signing up with the same name twice (the second is told it's taken), quick
reaction taps, two people sending each other friend requests (they become friends), and
adding someone to a group twice (they're added once).
- **Changing your password with two tabs open** keeps the other tab signed in after a
reload.
- **A signed-out device keeps checking** when the server can't be reached, instead of
getting stuck.
- **Linked devices says when its list didn't load,** with Try again, instead of showing no
devices. Each Remove button names its device.
- **A light custom theme gets readable warning colours.**
- **Sending a file that's no longer stored** is refused with "A file in this message is no
longer stored. Attach it again." instead of sending the message without it.
- **Rows with reactions, and links with no preview,** no longer overlap the next message or
leave a gap.
- **A new device's first key upload no longer fails sometimes.** It could arrive while
the same device was signing in to the live connection, and the server answered with an
error instead of waiting a moment. Opening a private DM link, and the dead-man's switch
deleting messages, had the same problem and are fixed the same way.
- **Warning notes show in a warning colour.** The note asking you to back up again (for an
older recovery backup) and a sleeping or waking Instant Server asked for a warning colour
no theme defined, so they showed as plain text. Every theme has one now, readable on its
own backgrounds; custom themes can set it in the theme editor.
- **A device whose session ended now goes to sign-in.** When every session was ended from
another device (or by a password change), the others sat on "Reconnecting you now…"
forever. They now notice, go to the sign-in screen, and say why.
- **Restoring your keys on a new device no longer leaves a dead device behind.** A restore
makes the new device take over the backed-up device. Before, the new device's own
sign-in stayed listed on your account with keys nothing could use. People kept sending
it a copy of every encrypted message that no one could read, and it counted toward your
10-device limit. It is now removed during the restore, or at your next sign-in if that
fails.
- **Encrypted chats keep working after a restore.** If you sent an encrypted message from
the new device before restoring, the people you sent it to could not read anything you
sent after the restore, on the devices that got that message. The restore now replaces
the new device's own encryption state instead of mixing it with the backup's.
- **Restored disappearing messages now disappear on time.** Restoring a recovery backup made
before backups became keys-only also restores the readable copies of old encrypted
messages it held. Those copies were stored where the expiry cleanup never looked, so a
disappearing message among them that you never opened stayed on the device after its
time ran out. The cleanup now finds them when you sign in: copies already past their
time are deleted, and the rest disappear on schedule. Expired copies also no longer wait
for you to open an encrypted chat before they are cleaned up.
- **Locked messages no longer overlap.** On a new device, a message it can't decrypt yet
shows a card that explains why, with an "Open recovery" button. The chat list made room
for one line of text, so the next message covered the card and its button. The list now
makes room for the whole card, on phones too.
- **Your own encrypted messages stay readable.** Right after you sent a message in an
encrypted chat, it could turn into "This message needs keys this device doesn't have" on
your own screen until you reloaded. The message was never lost and the other person could
always read it; your copy now stays readable too.
- **Desktop: no more frozen white window at launch.** When your computer asks for a password
before it lets Ohiyo read its saved keys (a Mac does after every update of the unsigned
beta), the app used to sit frozen behind that prompt with an empty window. It now shows
"Unlocking your keys" and says what the prompt is for.
- **A calmer first screen.** The owner's launch checklist no longer fills the sidebar: it is
one "Getting started" row under the channels that opens when you want it. The message box
has a single "+" with "Upload a file" and "Create a poll" inside. The home switcher and its
own "+" moved to the bottom of the far-left rail, so the "+" beside your spaces is the only
one in reach. People who run a space are no longer offered a button to report it.
- **The app on phones:** message text no longer runs underneath each message's ⋯ button,
and touching a message no longer pops the desktop toolbar over its neighbours. The ⋯ menu
can add a reaction. The app is sized to the part of the screen the browser leaves
visible, so the message box is not hidden behind the browser's bar. The channel drawer is
wider, lists the channels before the launch checklist, and its small buttons are big
enough to hit. Dialogs and the call screen fit the screen, the sign-in and first-space
screens scroll on small phones instead of cutting their tops off, notices appear at the
top instead of over the newest messages, and fields no longer make an iPhone zoom in.
- **Settings said no notification ever shows message text.** That is true of push
notifications only. Notifications while Ohiyo is running show who wrote and the start
of the message, except in encrypted chats. The page now says so.
- **"Invite someone" gives a real invite:** the step in the first-run checklist copied a note
about Ohiyo with a link to the project, so a friend who followed it did not land in your
space. It now opens the space's own invite link, the same one the sidebar's Invite button
makes.
- **ohiyo.gg could be drawn with last release's styles:** browsers keep the stylesheet for ten
minutes, and pages linked it by a fixed name. Right after a release, a returning visitor
could get the new page with the old stylesheet: the phone screenshot broke out of its frame,
the headline ran across the sculpture and the menu sat on the stone. Pages now link the
stylesheet and script by a hash of their contents, so a new page always loads the new files.
- **The menu and headline on ohiyo.gg stand out more:** a veil of light sits behind the menu
and a soft haze behind the words, and the menu's letters are heavier. On very wide screens
the first screen now follows the painting's full height, so the sculpture's top is not cut
off and the menu never lands on it.
- **Settings on a phone:** under 560px wide the settings tabs shrank to eight identical dots
with no names, and the only "Back to Ohiyo" button was hidden with them, so there was no
way to tell the tabs apart or to leave Settings. The tabs are now a row of named pills that
scrolls sideways, with "Back to Ohiyo" always at the top.
- **Videos in chat no longer start over:** every row of the chat was thrown away and rebuilt
whenever the chat re-rendered, which happens on each letter you type and each new message.
Anything living in a row was reset: a playing video restarted, a revealed spoiler hid again.
Rows now keep their identity (and keep it when older history loads in above them).
- **Link cards no longer run into the next message:** the space kept for a YouTube card was
too small at full width, and a link written twice got two cards with room for one. Cards
now have fixed sizes that the list knows, each link gets one card (none for a link inside
code or a spoiler), and long links are counted the way a browser wraps them.
- **Punctuation after a link is no longer swallowed:** "(https://example.com)." lost its
closing bracket and full stop.
- **The chat follows the window:** narrowing a desktop window no longer leaves the chat wide and
the message box off screen, and on a phone a message that wraps onto a second line no longer
runs into the one below it. Rows are now sized for the real width of the chat, for the phone
font size, and for wide characters such as emoji and CJK.
- **`/me` sends italics:** it sent `_underscores_`, which the chat does not render.
- **Focus mode on a phone:** it hid the drawer, which is the only way to another chat or back to
Settings. It now only hides the sidebars on wide windows.
- **Dream mode's glow and the blur behind dialogs were missing in the production app:** the
production build kept only `-webkit-backdrop-filter` for two rules, which Chrome, Edge and
Firefox ignore, so the blur never applied there (it looked fine in development, which does
not minify CSS). Both rules now keep the standard property, and a test runs the real
production build to catch this.
- **Profile editor:** Grouped, responsive fields with visible focus and character counts; fixed the avatar being hidden behind the banner.
- **Top songs:** Compact add/edit/remove favorites, optional listening links with validation, and a simpler public music list. Drafts remain local until Save profile.
- **Watch parties work on the web app.** A YouTube party showed an empty box, because
the site's security policy blocks YouTube's player script; the player is now an embed
driven by messages, so nothing from YouTube runs inside the app. Guests are kept in
step when they pause or scrub (only the host controls playback) and no longer get an
End button that did nothing. Sync no longer depends on each device's clock being
right, and a browser that blocks autoplay shows a "Click to join the party" button.
- The server image builds again: the Discord import tool it bundles is pinned to a
release instead of cloning its latest commit, which had started to need a newer Go.
- Turning on encryption in a DM right after a reload no longer fails with "your friend
needs to open Ohiyo".
- One undecryptable message no longer makes a group chat load empty.
- Production call smoke tests now skip the dev-only low-level peer-connection inspector
while still verifying the live roster/UI behavior.
## [0.2.0] — 2026-06-22
Two headline themes: **import your community from Discord**, and a top-to-bottom
**security & quality hardening** pass (three review-and-fix rounds, ~75 findings, all CI
gates green). Upgrading needs no data migration; the only opt-in change is signed file
URLs (off by default — see **Added**).
### Added
- **Import from Discord.** A guided wizard imports a Discord server from an exported
archive (Discrawl) — drag-and-drop with an automatic preview and explicit safety
labels — or a managed "clone" flow that installs a bot and lets you pick the server.
- **Multiple homes.** Switch between Ohiyo servers (your own self-host, an Instant
Server, a friend's box) at runtime, each with its own session.
- **Signed file URLs** behind the `OHIYO_REQUIRE_SIGNED_FILES` flag — HMAC capability
URLs for `/files/…`. Default **off**; see [DEPLOY.md](DEPLOY.md) before enabling it on
an existing deployment.
- **"Log out everywhere"** (`POST /auth/logout-everywhere`) — instantly revokes every
session for your account.
- **Listen-only voice** — join a call to listen without a microphone.
- A tabbed DM messenger strip.
- This `CHANGELOG.md`.
### Security
- **End-to-end group messaging:** sender-key messages now use a random per-message IV
(carried in the envelope) instead of a deterministic one — closing an AES-GCM
nonce-reuse window on concurrent / cross-tab sends. Existing messages still decrypt.
- **Encrypted message edits**, and a fix for a role-assignment privilege escalation.
- **Authorization:** closed cross-server / cross-channel access bugs (event RSVPs, poll
votes) and access-checked the typing indicator, voice join/meta, and watch-party
controls.
- **Sessions & passwords:** per-request token-version revocation (powers "log out
everywhere"); new password hashes use Argon2id.
- **Secrets at rest (desktop):** the session token, the decrypted-message cache, and the
unsent-message outbox are sealed in the encrypted vault instead of plain local
storage; recovery backups no longer include the session token.
- **SSRF / traversal / XSS:** the link-preview fetcher pins resolved IPs (DNS-rebind
safe); Discord-import file paths are confined; profile, social, and in-chat links are
validated through a single safe-URL guard; the plugin sandbox neutralizes more globals
and blocks CSS injection.
- **Supply chain & infrastructure:** dropped the unused MySQL backend from sqlx, removing
the `rsa` crate (RUSTSEC-2023-0071) from the build; the server container runs as a
non-root user; added a `Permissions-Policy` header and an inbound WebSocket frame-size
cap; the gateway recovers from poisoned locks instead of aborting the process.
### Changed
- Screen-share is now the primary in-call stage, with a polished in-call layout.
- Settings, the custom-appearance editor, and the voice sidebar got a polish pass.
- Internal Fly machine/volume IDs are no longer returned in instance API responses.
- Database: added indexes on `dm_participants(user_id)` and `files(uploader_id)` for the
DM-list and upload-quota hot paths.
- macOS desktop downloads now require a notarized build (unnotarized downloads paused).
### Fixed
- Voice: mic playback and speaking indicators, the listen-only badge, screen-share
teardown re-entrancy, and double-join connection leaks.
- Reliability: duplicate-message de-duplication, single-flight outbox retries, and
WebSocket dead-connection detection + reconnect.
- Accessibility: the settings dialog gained proper dialog semantics + a focus trap, and
the speaking indicator honors reduced-motion.
- Error boundaries around the call, plugin, and watch-party surfaces — plus many smaller
correctness fixes across the gateway, imports, and React effects.
## [0.1.1] — 2026-06-16
Release and CI plumbing to unblock multi-platform desktop builds.
## [0.1.0] — 2026-06-14
Initial public, self-hostable release: servers, channels, DMs, threads, reactions, and
read receipts; Signal-protocol end-to-end-encrypted DMs and group chats (multi-device,
disappearing messages, safety numbers); WebRTC voice / video / screen-share; a sandboxed
plugin system; the Daybreak and Dusk themes; and a Tauri desktop app.
[Unreleased]: https://github.com/New1Direction/ohiyo/compare/v0.3.1...HEAD
[0.3.1]: https://github.com/New1Direction/ohiyo/compare/v0.3.0...v0.3.1
[0.3.0]: https://github.com/New1Direction/ohiyo/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/New1Direction/ohiyo/compare/v0.1.1...v0.2.0
[0.1.1]: https://github.com/New1Direction/ohiyo/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/New1Direction/ohiyo/releases/tag/v0.1.0