Scatto — Pixelfed iOS client, forked from Impressia
  • Swift 99.9%
  • Python 0.1%
Find a file
vinz 436a272434 Meet Pixelfed 0.14.2 where it differs
scatto.social is on 0.14.2, which rewrote direct messages. Every endpoint
the app calls still works, but three things behave differently.

A message request now allows exactly one message until it is accepted.
The second send answers 403 with its own sentence; the app showed
"Sending the message failed.", which invites a retry that cannot work.
It now arrives as DirectMessageSendError.requestNotYetAccepted, on both
the text and the photo route, carrying the server's wording.

That change turned up an older fault. Pixelfed says "Account size limit
reached" when an upload runs out of room, and indicatesStorageQuota
matched only "storage" or "quota" — so the two obvious words missed the
only message the function exists to catch, and every real out-of-room
refusal had been arriving as a plain 403. Harmless while nothing else
claimed 403; not harmless the moment this commit gave it a meaning. Now
also matches "size limit", with the real wording as a test.

Conversation paging takes its cursor from the Link header alone. There
is no id in the body that works on both server generations: before
0.14.2 the cursor was the conversation row id, since 0.14.2 it is the
last message id, and a conversation's own id is now a snowflake larger
than any message id. The old fallback therefore asked for everything
older than a huge number, got page one back, and did it again on every
scroll — the reducer dropped the duplicates, so the list never ended and
never grew. Measured on scatto.social: rel="next" carries the last
message id, and a final page sends no next link at all.

And two message kinds the rewrite can produce, videos and media, were
decoding as unknown while the thread gates attachments on isMedia — so
such a message drew an empty bubble and dropped the pictures. Both are
named now, and the bubble also renders attachments for an unknown kind,
so the next word a server invents degrades rather than disappears.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 19:52:11 +02:00
.github Add sponsor information 2023-03-28 10:46:49 +02:00
Assets Replace app icon with Scatto cherry-glow design; strip 16 alternate icons 2026-07-28 21:48:43 +02:00
ClientKit Meet Pixelfed 0.14.2 where it differs 2026-09-21 19:52:11 +02:00
CoreData Rebuild the notifications screen around the post 2026-09-21 10:38:35 +02:00
EnvironmentKit Rebuild the notifications screen around the post 2026-09-21 10:38:35 +02:00
Localization Meet Pixelfed 0.14.2 where it differs 2026-09-21 19:52:11 +02:00
PixelfedKit Meet Pixelfed 0.14.2 where it differs 2026-09-21 19:52:11 +02:00
Resources Add Spanish translation 2023-10-18 17:52:49 +02:00
Scatto Meet Pixelfed 0.14.2 where it differs 2026-09-21 19:52:11 +02:00
Scatto.xcodeproj Bump to build 80 2026-09-21 13:59:33 +02:00
ScattoCore Meet Pixelfed 0.14.2 where it differs 2026-09-21 19:52:11 +02:00
ScattoShare Ask for approximate location, which is all the feature uses 2026-09-10 21:40:56 +02:00
ScattoWidget Downscale the widget placeholder to 1000px; bump to build 46 2026-08-29 21:44:00 +02:00
Scripts Suggest nearby places when composing, from an offline copy of the catalog 2026-08-27 21:22:27 +02:00
ServicesKit Decide the audio session from what is playing, not the category 2026-09-18 15:48:46 +02:00
SharedAssets.xcassets Add a dark green accent 2026-08-05 22:20:29 +02:00
UploadKit Collections: choose them while composing 2026-09-03 14:37:41 +02:00
WidgetsKit Rebuild the notifications screen around the post 2026-09-21 10:38:35 +02:00
.gitignore Stop tracking the App Store metadata doc 2026-09-02 22:18:16 +02:00
.swiftlint.yml Change version to 1.4.0 (113) 2023-04-15 08:24:13 +02:00
CLAUDE.md Settle the four string-catalog keys with no source behind them 2026-08-08 19:08:57 +02:00
LICENSE Change license to Apache 2.0 2023-03-28 10:35:38 +02:00
NOTICE Complete Impressia → Scatto rename; add NOTICE, rewrite README 2026-07-28 21:37:22 +02:00
PRIVACY-NOTICE.md Suggest nearby places when composing, from an offline copy of the catalog 2026-08-27 21:22:27 +02:00
README.md Update README for push notifications and interaction sounds 2026-07-31 15:31:54 +02:00
RELEASE_NOTES.md Fix six defects found reviewing the notifications and messages work 2026-09-21 14:15:18 +02:00
sounds_license.md Credit BigSoundBank for the interaction sounds 2026-08-05 18:32:22 +02:00
Tips.storekit Match the local tip products to what's on the App Store 2026-08-07 09:10:35 +02:00

Scatto

Scatto is a personal fork of Impressia, the SwiftUI iOS client for Pixelfed. It keeps Impressia's photo-first experience and adds three things Impressia doesn't have: first-class video support, real push notifications, and interaction sound effects.

Scatto runs against the maintainer's own Pixelfed instances (pix.hever.de, scatto.social) and is being prepared for an App Store release. There is no plan to open a public issue tracker on this repo.

What Scatto adds over Impressia

Push notifications

Pixelfed speaks Web Push, which iOS apps can't receive directly — so Scatto pairs with a companion relay, scatto-push-relay, that bridges Pixelfed's Web Push to Apple Push Notification service (APNs).

  • Real push for likes, comments, follows, and boosts, delivered even when the app is closed.
  • Multi-account and multi-instance: each signed-in account gets its own subscription on its own instance, and enabling push registers them all. Signing into a second account no longer silences the first.
  • Deep-linking: tapping a notification opens the post or profile it refers to.
  • Device-wide badge: the app-icon badge shows the total unread across all accounts, not just whichever was pushed to last.
  • Per-subscription control of the icon badge and interaction sounds, so the in-app toggles are honoured even for pushes that land while the app is closed (iOS applies aps.badge/aps.sound from the payload before any app code runs, so the relay has to be told).

Interaction sounds

Scatto plays a short sound for social interactions — a heartbeat when you like (or are liked), a whoosh when you boost (or are boosted), and a typewriter for comments (yours and received). A single Interaction sounds toggle in Settings gates them (default on). Foreground pushes play through the app; backgrounded pushes carry the sound name in the APNs payload.

Video

First-class video support in an app that upstream keeps deliberately photos-only — the original reason for the fork.

Credits

Scatto exists because Impressia exists. All the hard work is upstream:

Impressia is licensed under the Apache License 2.0. Scatto keeps the same license (see LICENSE) and preserves upstream attribution in NOTICE and in the per-file copyright headers.

Why fork instead of contribute?

Impressia is intentionally photos-only, and its push-free design fits a pure Pixelfed client. Adding video changes the app's positioning, and push notifications pull in a whole companion service — neither is a good fit for upstreaming. Forking lets us keep pulling in upstream fixes (git fetch upstream && git merge upstream/develop) while diverging on our own feature set (video, push, interaction sounds).

Relationship to upstream

  • upstream remote: https://github.com/Impressia/Impressia
  • origin remote: this repository
  • Bundle IDs, target names, and directories have been renamed from ImpressiaScatto — merges from upstream on those files will conflict and need manual resolution.

Build

Requirements: Xcode 16+, iOS 17 SDK, and either a free Apple ID (7-day sideload) or the Apple Developer Program ($99/yr) for stable installs.

git clone git@git.hever.de:vinz/scatto.git
cd scatto
open Scatto.xcodeproj

Set the signing team to your Apple ID for the three targets: Scatto, ScattoShareExtension, ScattoWidgetExtension. Bundle IDs are namespaced under de.hever.scatto.

Push notifications are optional. They require the companion scatto-push-relay running on your own host (with an APNs auth key) and its base URL + subscribe token wired into AppSecrets. Without it, everything else — timelines, uploads, video, interaction sounds when the app is open — works normally.

License

Apache License 2.0 — see LICENSE and NOTICE.