Your NameandClaude Opus 5 a368292101 feat(join): say what actually went wrong instead of nothing
A failed join looked exactly like a clean one (TODO #145), and the reason turned
out to be structural rather than a matter of wording.

`joinWithNickname` called `setPendingJoin(null)` unconditionally, which unmounted
the join sheet the moment the call returned - before the sheet could render
anything. So the inline error path was DEAD: whatever message the UI set was set
on an unmounted component and never appeared. That is why a dead end was
indistinguishable from a clean join, and it means the old "Could not join group.
Check the invite link and try again" was never actually shown either.

The sheet is now dismissed only when there is nothing left to say - success, a
blocked link (which raises its own toast), or a benign outcome.

On top of that, the outcomes are finally reported:

- `reason` is threaded through the repair_failed return in src/invite.js. It was
  already computed by repairKeylessGroupFromInvite, whose own comment says it
  exists "so the UI can say something honest either way", and simply dropped
- src/lib/joinOutcome.js maps (error, reason) to a plain-language message and a
  tone, pure and testable. Each repair reason gets its own advice: a key-conflict
  cannot be fixed by retrying, so it says to get re-added, while reconcile-failed
  says to try again
- outcomes that are not failures (already_member, already-keyed) are toned `info`
  rather than red, because colouring a non-problem red teaches people to ignore
  red
- a throw inside handleInviteLink is caught and reported. It used to leave the
  sheet spinning forever, since the caller only ever reacted to a returned result
- an unknown code names itself rather than inventing a cause

Also fixes the debugging trap #145 records: `Joined group swarm: … topic:` was
printing the groupKey, not the topic. For a keyless group they are identical, for
a keyed one the topic is hash('pearcal-enc-topic-v1:' + groupKey), so the line
looked like proof a device had joined the unencrypted topic while proving
nothing. It now prints both under their own names plus (encrypted)/(unencrypted).

330 unit tests, 18 new, including that no two repair reasons produce the same
advice, that no message leaks an error code as jargon, and that a missing group
name never renders "undefined".

Verified on the TCL, both tones on screen:
  - malformed link -> "That invite link does not look right. Ask for a new one
    and paste the whole thing." inline in red, sheet stays open
  - already a member -> "You are already in "KeyGuard39"." as a neutral toast,
    sheet closes
And the corrected swarm log distinguishes the two damaged groups (groupKey ==
topic, unencrypted) from the healthy one (distinct derived topic, encrypted).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HG8ayyquJuDMPSVLKDQKVh
2026-07-27 09:19:29 -05:00
2026-07-22 14:01:30 -05:00
2026-07-22 14:01:30 -05:00
2026-02-21 16:21:31 -06:00
2026-03-16 12:39:46 -05:00
2026-02-23 13:16:54 -06:00
2026-02-21 16:21:31 -06:00
2026-02-21 16:21:31 -06:00

🍐 PearCal

A decentralized calendar sharing app for Android, iOS and desktop.

PearCal syncs your calendar directly between devices - no accounts, no servers, no subscriptions. Your data lives only on the devices in your groups.

Part of the PeerLoom suite of account-free, peer-to-peer apps.


Features

  • Shared group calendars - create a group, invite family or friends via a link, and your events sync automatically. Sharing is free and needs no plan, no server and no account for anyone in the group
  • Import and export .ics - bring events in from another calendar and take yours with you, so nothing is locked in
  • Fully offline-first - works without an internet connection; syncs whenever devices are on the same network or can reach each other over the internet
  • Event reminders - per-event reminder notifications with customizable lead times
  • Recurring events - daily, weekly, bi-weekly, monthly and yearly repeat with a configurable end date
  • Event locations - add an address or place to any event; tap 🧭 to open it in your maps app
  • Custom group colors - color-code your groups for quick visual identification
  • Profile photos - set a photo or avatar that appears across all your groups
  • Dark mode - automatic dark theme throughout
  • No accounts - your identity is a cryptographic key pair generated on your device; nothing is tied to an email or phone number
  • No data collection - no company, including us, ever sees your calendar data

How It Works

PearCal uses peer-to-peer technology powered by Hypercore Protocol to sync your calendar directly between devices.

No servers

Most calendar apps (Google Calendar, Apple Calendar, etc.) store your data on a central server. The app company can read your events, sell your data, get hacked, go down or shut down. PearCal has no central server. Your calendar data never leaves your devices.

How sync works

When two devices in the same group are online at the same time - whether on the same Wi-Fi network or anywhere on the internet - they find each other using a distributed hash table (DHT), a technology similar to how BitTorrent works. Once connected, they sync directly, device to device, with no middleman.

Encrypted by default

All data is encrypted in transit using the same cryptographic primitives that secure modern messaging apps. Nobody on the network can read your calendar data except the devices in your groups.

What about offline changes?

PearCal is designed to handle this gracefully. If you add an event while offline, it will sync to your group members the next time your devices connect. Conflicts are resolved automatically using a last-write-wins strategy.

Invites

Joining a group works via an invite link or QR code. The link encodes the cryptographic address of the group - there's no server involved. Share it however you like: copy it to a message, share it via the system share sheet or let someone scan your QR code directly. If you remove a member, their link expires and they can no longer sync.

Optional blind seeder

Because there is no server, a group syncs only while two of its devices are online together. If you want a group to stay reachable when every phone is asleep, you can run the blind seeder on hardware you own.

It is blind by construction. It replicates the group's encrypted blocks and can report only counts - bytes, blocks, writers and peers - never contents. It holds no key that could decrypt an event. The group admits a seeder explicitly and can revoke it group-wide.

Packaging lives in seeder-launcher/, including an Umbrel app manifest and a script that builds the Start9 .s9pk. Running one is entirely optional; PearCal works without it.

When phones can't reach each other directly

Most of the time your devices connect straight to one another. Some mobile networks make that impossible, and on those networks a purely direct app simply never connects. So when - and only when - a direct connection has already failed, PearCal falls back to a relay run by PeerLoom: one public machine both devices can reach, which passes the scrambled data between them.

Being honest about what that means:

  • The relay cannot read anything. Your devices encrypt end to end and the relay never holds a key. It forwards scrambled bytes and forgets them; it stores nothing.
  • It does see that two devices are talking and roughly how much - the usual disclosure for any relay. That is not nothing, so we say so plainly rather than calling it zero-knowledge.
  • It is a last resort, never a first choice. A direct connection is always tried first, and if one becomes possible later the relay is dropped.
  • You can turn it off on Android and iOS: Profile → Advanced → Connection → "Use a relay when direct fails". Off means strictly device to device, at the cost of not connecting at all on a network that blocks it. The desktop apps use the relay on the same direct-first terms but do not expose the switch yet - it is on there and cannot currently be turned off.

Screenshots

Month view (light) Event details (dark) Group sharing (light) Invite (dark) Profile (light)


Privacy

  • No accounts or sign-up required
  • No analytics, tracking or telemetry
  • No third-party SDKs
  • Location search is not used - locations are plain text to avoid sending queries to external services
  • All sync traffic is encrypted end-to-end

Desktop (beta)

PearCal also runs on macOS, Windows and Linux as a native desktop app built on Electron, sharing the same peer-to-peer backend as mobile. Desktop and mobile pair into a single identity - events, groups and reminders sync between every device on the same identity.

Installers are produced by the local build scripts in electron/scripts/ and land in electron/dist/:

  • macOS: PearCal-X.Y.Z-arm64.dmg and PearCal-X.Y.Z.dmg - signed with a Developer ID Application certificate. Hardened runtime and notarization are currently off, because macOS Sequoia's local-network handling does not recognize Hyperswarm's raw sockets under the hardened runtime. Restoring them is tracked as an open item, so expect an "unidentified developer" prompt on first launch
  • Windows: PearCal Setup X.Y.Z.exe - NSIS installer, currently unsigned (Authenticode signing not yet wired), so SmartScreen warns on first download
  • Linux: PearCal-X.Y.Z.AppImage and pearcal-electron_X.Y.Z_amd64.deb

Pairing on desktop uses the same pearcal://pair?topic=… URL the mobile app generates - paste it into the onboarding sheet, click it from a browser or scan a QR code from a phone. Camera capture and QR scanning are not wired on desktop, by design.

The full architecture pivot from the original Pear runtime to Electron is documented at docs/superpowers/plans/2026-04-27-pear-desktop-electron-pivot.md - that's the canonical reference for build pipeline, signing posture, native module replacements and what survives vs. what was replaced.

Known coverage gaps:

  • Linux distro coverage: only Debian (.deb via apt) has been verified end-to-end. The AppImage and other distros (Fedora, Arch) are likely fine but not yet smoke-tested.
  • UI density: the calendar UI still renders as a stretched mobile layout on a wide window. A desktop-tailored multi-pane redesign is tracked separately and not part of this beta.

Known Limitations

  • Two devices must be online at the same time to sync - there is no push delivery when every device in a group is offline at once. Running the optional blind seeder removes this limitation, because the seeder is always online
  • No CalDAV - PearCal will not subscribe to or two-way-sync an existing Google, iCloud or work calendar. It is a shared calendar for a group, not a replacement client for a hosted one. Use .ics import and export to move events between them
  • It does not hide anything from your group members - the design keeps your calendar away from any server operator, including us. Everyone you invite to a group sees that group's events

License

MIT © 2026 PeerLoom LLC


Feedback & Bug Reports

Please open an issue on GitHub. Include your platform (Android, iOS or desktop), OS version and a description of what happened.

S
Description
Mirror of PearCal
Readme MIT
19 MiB
Languages
JavaScript 90.8%
Kotlin 3.3%
TypeScript 2.1%
Swift 2%
Shell 0.7%
Other 1.1%