A fork of FluffyChat, based on Mochi.
  • Dart 95.6%
  • C++ 1.4%
  • CMake 1%
  • Shell 0.5%
  • Swift 0.4%
  • Other 1%
Find a file
2026-04-22 14:04:27 -04:00
.github CI: make dart_code_linter:metrics steps non-fatal 2026-04-22 20:56:11 +03:00
.vscode Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
android Fix cross-device outbound queue double-sends 2026-04-22 00:14:23 +03:00
assets Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
integration_test Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
ios Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
lib Image viewer: add sender header to the top bar 2026-04-22 13:59:34 -04:00
linux Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
macos Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
scripts Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
snap Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
test Format lib/ and test/ with dart format 2026-04-22 13:21:37 -04:00
web Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
windows Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
.gitignore Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
.mailmap Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
.metadata Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
.tool_versions.yaml Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
analysis_options.yaml Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
AUTHORS.md Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
CHANGELOG.md Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
config.sample.json Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
CONTRIBUTING.md Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
dart_dependency_validator.yaml Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
devtools_options.yaml Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
Dockerfile Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
l10n.yaml Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
LICENSE Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
licenses.yaml Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
PRIVACY.md Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
pubspec.lock Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
pubspec.yaml Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
README.md Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
recommended_homeservers.json Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00
SECURITY.md Mochi: fork of FluffyChat 2026-04-21 21:03:50 +03:00

Mochi

A Matrix chat client focused on local privacy, per-chat control, send reliability, and cross-device consistency. Derived from FluffyChat. Written in Flutter.


Overview

Mochi is a Matrix client forked from FluffyChat. It is protocol-compatible with every other Matrix client (Element, Nheko, NeoChat, Cinny, and others) and inherits FluffyChat's full feature surface. The fork diverges substantially from upstream on defaults, per-conversation control, message-send reliability, sticker and emoji management, and cross-device state. None of the additions break interop: every device-to-device mechanism rides on standard Matrix room or account data, which other clients ignore silently.

The canonical source of the upstream project is krille-chan/fluffychat. Upstream-compatible changes that are useful outside Mochi's opinionated scope are encouraged to be contributed back.

Differences from upstream FluffyChat

Security and local privacy

  • App Lock. PIN, passphrase, or 3×3 pattern. Variable-length PIN with a length-hidden glyph during entry. Optional scrambled numeric keypad. Configurable auto-lock timeouts. Biometric unlock with enrollment-change detection via local_auth. FLAG_SECURE screenshot protection (Android). Opt-in wipe or sign-out after a configurable number of failed attempts.
  • Database key derivation from the lock secret. When enabled (default on), the SQLCipher database key is wrapped by an Argon2id-derived KEK; the on-disk database is not decryptable without unlocking the app. Device-local, never synced.
  • Opt-in link previews. OpenGraph card previews and direct-media auto-load are separate systems, both off by default, both gated per-domain. Media URLs only render inline when the source domain is in chat.mochi.trusted_media_domains; OpenGraph cards can be suppressed per-domain via chat.mochi.dismissed_embed_domains with selectable expiry (1 day through forever, auto-expiring). A subdomain-level domain trust takes precedence over a parent-domain dismissal, so whitelisting media.tenor.com does not get silenced by dismissing tenor.com.
  • Opt-in media auto-download. Attachment auto-loading can be disabled globally or per room; a tap-to-load placeholder replaces the eager fetch.

Conversational control

  • Per-chat preferences. Read receipts, typing indicators, URL previews, OpenGraph previews, attachment auto-load, autoplay, member-change visibility, and reaction visibility can each be overridden per room via im.mochi.chat_prefs room account data. Invisible to other room members; roams across the user's own devices.
  • Per-key cross-device sync opt-out. The synced subset of application settings (stored in im.mochi.settings account data) can be included or excluded on a per-key, per-device basis. The opt-out list is itself device-local to prevent self-defeating overwrites.
  • Timed mute. Per-room mute with preset durations (15 minutes, 1 hour, 8 hours, 24 hours, 1 week, forever). The unmute timestamp is written to im.mochi.mute_until room account data, so every logged-in device decides independently whether the mute is still active from local time alone. No homeserver round-trip is needed once the timestamp is known; expired mutes auto-clear on startup and on each sync.
  • Pinned spaces. Discord-style navigation rail with pinned spaces synchronized cross-device via chat.mochi.pinned_spaces account data.
  • Chat list ordering and default tab. Default tab (pinned, favorites, direct, tag, filter) is user-configurable. Drag-to-reorder within each tab persists per-filter; room order roams across devices via im.mochi.chat_order.

Message send reliability

  • Persistent outbound queue. Outgoing text, edits, stickers, and emoji reactions pass through a per-room queue stored in im.mochi.outbound_queue room account data. The queue survives app restart, network loss, and device handoff. Failed sends do not block the tail: after a capped backoff sequence (2s, 10s, 30s, 2m, 5m) the entry is marked failed and the pump advances, so a single bad message cannot stall the queue behind it.
  • Cross-device send coordination. A short-lived lease (claim) in each queue entry prevents two devices from dispatching the same message twice. If the holding device goes offline, the lease expires after 60 seconds and another device takes over, re-dispatching with the same transaction ID so the homeserver deduplicates.
  • Interactive ghost bubbles. A pending or failed entry renders as a ghost bubble in place of the eventual message. Tap a failed ghost to retry; long-press to cancel (the cancel propagates to every device). Bubbles show "pending on <device>" when another device is holding the lease.

Content and media

  • Unified emoji picker. One scroll surface with recently-used (unicode and custom emoji mixed), the room's custom emoji organized by pack, and the standard unicode categories, with a sticky nav bar that jump-scrolls between sections. Stickers remain on a separate tab.
  • Signal-style sticker picker. Single vertical scroll with a sticky pack-icon bar. Density presets (small / medium / large columns). Long-press preview for animated stickers. Frequency-weighted recently-used surface shared with the emoji reaction bar.
  • Multi-pack sticker management. Multiple user-owned sticker packs via im.ponies.user_emotes~<slug> account data alongside the default pack. Room-pack discovery banner when entering a room advertising im.ponies.room_emotes. .mxpack transport format (ZIP archive with mimetype, pack.json manifest, README.txt, and raw stickers/ directory) with graceful degradation on clients that do not implement the format.
  • Copy-to-your-pack. Long-press any sticker in the picker, any sticker in a message, any custom emoji tile, or any custom emoji reaction to save it into one of your user packs. Shortcodes collide-safely (:smile: becomes :smile_2: if already taken). The MXC URL is reused directly, so no re-upload is needed when copying from another user on the same homeserver.
  • GIF decoder fallback. When the platform codec fails on an animated GIF (commonly: frames with unusual disposal or sub-rect layouts, such as those produced by ezgif-style video-to-GIF tools), Mochi transparently transcodes the file to APNG on first decode using the pure-Dart image package and renders the APNG instead. Animation is preserved; the transcode is cached per image.
  • Video first-frame thumbnails. Generated from a partial-byte download of the source video (256 KB) to avoid full-file retrieval on scroll. A privacy blur is applied by default and fades to zero on hover (desktop) or on tap (mobile).
  • GIF favorites. Persisted to Matrix account data with both the source URL and an MXC upload, indexed by SHA-256 so favorites survive source-URL rot. Smart send prefers a probe-alive source URL over re-uploading; a 5-minute probe cache avoids re-fetching on every send.
  • Body-only-media collapse. When a text message's body is nothing but a media URL, the text row is collapsed entirely and the embed fills the bubble. HTML entity decoding is applied before URL matching so CDN URLs with &amp;-encoded query strings are detected correctly.

Editor and composer

  • Slash-command autocomplete with argument hints. The suggestion row shows the expected signature (/invite <user>, /op <user> [power-level]) alongside the localized description.
  • Frequency-boosted emoji and emote suggestions. The :shortcode autocomplete ranks by usage frequency, tracked via a single store (PickerHistory) shared across the emoji picker, the reaction quick-bar, and the custom emoji picker, so every surface converges on the same recents.
  • Custom emoji insertion. The composition picker surfaces every custom emoji from the active room's packs and inserts :shortcode: at the cursor. Frequency feeds back into the picker's recents.
  • Escape dismisses the autocomplete overlay. Previously a silent no-op.

Reactions

  • Quick-reaction bar. The hover / long-press reaction bar is frequency-ranked (top five usages, with a fallback set used when fewer than three are tracked). A custom list is configurable via chat.mochi.quick_reactions.
  • Shared frequency pool. Unicode emoji, custom emoji (reaction or insertion), and stickers all write into the same PickerHistory store, so usage in any surface influences recents everywhere. Usage is weighted count * 2 + recency_bonus (bonus decays from 5 to 0 over 30 days).
  • Tooltips on sticker and custom-emoji surfaces. Hovering a sticker inside a message or a custom-emoji reaction chip reveals its :shortcode:. Unread-count badges surface correctly for rooms that have mentions but zero regular notifications (an upstream edge case that hid the badge entirely).

Other

  • Self Notes. Auto-pinned encrypted DM to the user, tagged im.mochi.self_notes, with a first-run onboarding tile in the chat list.
  • Advanced-settings gate. A single "Show advanced" toggle reveals platform-irrelevant and experimental options. Default-hidden options are no longer scattered behind ad-hoc heuristics.
  • Data migration from FluffyChat. On first launch, Mochi copies legacy chat.fluffy.* local preferences and im.fluffychat.* account-data events into their Mochi equivalents; settings and bundles survive the rename. Guarded against running on accounts already using Mochi.

Inherited feature surface

End-to-end encryption (E2EE by default for direct chats), cross-signing, emoji verification, encrypted key backup, multi-account, SSO / OIDC, federation, spaces, public channels with thousands of participants, all standard Matrix message types (text, image, video, audio, file, location), voice messages, Firebase Cloud Messaging and UnifiedPush push backends, Material You theming, dark mode, and onboarding via Matrix ID or QR code.

Refer to the upstream FluffyChat README for additional context on inherited functionality.

License and attribution

Mochi is distributed under the GNU Affero General Public License v3 (inherited from FluffyChat). The license file is at LICENSE.

Every contributor to FluffyChat prior to the fork is preserved in the initial commit via Co-authored-by trailers and listed in AUTHORS.md. Copyright notices embedded in platform project files (macOS PRODUCT_COPYRIGHT, native source headers) remain attributed to the original authors, as required by the license and as a matter of accuracy.

The app icon and banner assets derive from artwork originally commissioned by the FluffyChat project from Fabiyamada. The emoji verification glyph set is maintained by The Matrix Foundation and licensed under Apache 2.0. Notification sound assets are from WoodenBeaver.

Build instructions

Mochi builds using standard Flutter tooling. Vodozemac (Rust) is required for encryption support on web; cargo must be installed in that case.

git clone https://git.keimai.space/alex/Mochi.git
cd Mochi
flutter pub get

Linux

sudo apt install libjsoncpp1 libsecret-1-dev libsecret-1-0 librhash0 libwebkit2gtk-4.0-dev lld
flutter build linux --release

Android

flutter build apk --release

Firebase Cloud Messaging may be enabled before build by running ./scripts/add-firebase-messaging.sh and providing a google-services.json for the Mochi bundle ID (im.mochi.app). Without Firebase, builds fall back to UnifiedPush.

iOS and iPadOS

A Mac with Xcode and code-signing configured is required. The Mochi bundle identifier is im.mochi.app. Build via ./scripts/build-ios.sh; see the script for expected MOCHI_* environment variables.

macOS

flutter build macos --release

Windows

flutter build windows --release

Web

./scripts/prepare-web.sh
flutter build web --release

Runtime configuration may be supplied via a config.json served from the same origin as the application. See config.sample.json for the recognised keys.

Integration testing

Integration tests require Docker. A Synapse fixture is bootstrapped by ./scripts/prepare_integration_test.sh prior to each run.

./scripts/prepare_integration_test.sh
flutter test integration_test/mobile_test.dart

Contributing

Issues and merge requests are accepted at the project's repository. Mochi is an opinionated fork, and behavioural changes that diverge from upstream FluffyChat are appropriate here. Changes that are protocol-agnostic or broadly applicable are encouraged to be contributed to upstream FluffyChat as well.

See CONTRIBUTING.md for style and review guidelines.

Security

Security issues may be reported privately in accordance with SECURITY.md. Please do not file public issues for undisclosed vulnerabilities affecting either Mochi or upstream FluffyChat.