Ctrl K
ComunidadGuías…

CHANGELOG.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# 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