Paul HittandClaude Opus 5 0eb44bb3a6 Design pass with the screens actually in front of me
The JVM harness renders whole screens now, not just parts. The screens
take their state as `phase` and only touch the view model when
something is tapped, so an unstarted model is enough — it opens the
Keystore vault in `init`, which fails inside a runCatching on a
background dispatcher under Robolectric. That is why this works rather
than by design; if it starts hanging, look there first.

With the screens visible at the kit's own 375x812 canvas, four things
were obvious that were not from inside the code:

🔴 The large title said "fnreviewalpha" over a relay and a device id.
The kit's title names the SCREEN — "Messages". Three lines about who
you are, at the top of a list about other people, and it dominated
because a handle is longer than a title. Identity belongs on Account,
where it already was.

Contact rows read "fnreviewbeta / fnreviewbeta · online" — the name
twice and a status — because the handle is usually the same string as
the display name. It is dropped when it matches.

"No groups yet. Start one with your buddies." was a line of prose in
the middle of a list, with a section label above it, for the ordinary
state of a new account: two lines of furniture for zero rows. Both go
when the section is empty AND loaded. The failure case still speaks,
because the reason that message existed is real — an empty list
rendering nothing gave no way to tell "you have none" from "it did not
load".

Alerts offered "Mark all read" on an empty screen, where its only
possible effect was nothing, and it was the most prominent thing there.
"Nothing new." sat against the heading like a subtitle of it. Proper
empty state now, and the button appears only when something is unread.
Alert rows drew initials of the KIND, so "contact request" became "CR"
— two letters that look like a person's initials and name nobody.

Address card: the QR was the largest thing on the Account screen and
pushed everything below the fold, and the address wrapped mid-hostname
as "…@rela / y.flamenet.io". A relay name split across a line break is
the kind of thing somebody retypes wrong, so the host is its own line
now. Two fields whose placeholder repeated the label above them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 14:32:34 -04:00
2026-08-27 14:15:11 -04:00

Flamenet Messenger

An end-to-end encrypted messenger: the apps, the server they talk to, and a specification complete enough that someone else could build a compatible client from it.

This repository holds all of it, and is the source of truth for every part. A protocol and the thing that speaks it drifting apart in separate repositories is how "compatible" quietly stops being true — so the interop harness here compiles the iOS engine and runs it against the JavaScript one on every change, which only works while they sit in the same checkout.

engine/     the JavaScript engine, the specification, and the published vectors
server/     the Swift relay — protocol, mobile routes, and this project's website
Sources/    the iOS app (SwiftUI), including its own Swift engine
android/    the Kotlin engine. No app yet; see android/README.md
docs/       historical design notes

Two of those are also published on their own, for people who want one piece rather than the whole project:

Repo Mirrors For
flamenet-e2e engine/ The protocol, as a package
flamenet-relay server/ Running your own relay

Those are extracts, not forks. Changes are made here and mirrored out; a patch sent to one of them has to land here or it will be overwritten. They have already drifted once, badly, which is why it is written down.

The three engines

The protocol is implemented three times, and that is the point rather than an accident. Two implementations that agree may agree because one was written while reading the other; a third, written against the specification and the published vectors, is what finds the places where the spec says less than the first two quietly assumed.

They are checked against each other rather than trusted:

Engine Where State
JavaScript engine/src/ Complete — the reference implementation
Swift Sources/E2E/ Complete, and compiled directly into the interop harness rather than reimplemented for it
Kotlin android/engine/ Safety numbers and key transparency only — see android/README.md

Running the tests

cd server && swift build          # the relay binary the suite drives
cd ../engine && npm test          # 478 assertions across five suites
cd ../android && ./gradlew :engine:test

npm test runs conformance, ratchet, client, interop and relay. The interop suite compiles the iOS app's own Sources/E2E/*.swift and runs it against the JavaScript engine, so it needs a Mac; the relay suite drives the compiled Swift binary over real HTTP, so swift build has to have run first — a stale binary in server/.build/ will happily pass tests for code you just changed.

Running the relay

cd server
cp .env.example .env    # then fill in the secrets it asks for
docker compose up -d

Open http://localhost:8080. The relay serves this project's website itself, so a working install explains what it is rather than answering with a 404.

For production, see server/DEPLOY.md.

Building the iOS app

xcodegen generate
open FlamenetMessenger.xcodeproj

Requires Xcode and XcodeGen (brew install xcodegen). The app is pinned to relay.flamenet.io; the sign-in screen accepts another address for a relay you run yourself.

The app has never been released. SCOPING-v1.md tracks what stands between it and a first TestFlight build.

What is published, and what is not

engine/ and server/ are mirrored to a public repository under the MIT licence. The iOS app is not: the mirror is built from an allowlist rather than an ignore list, so a new directory here is private by default instead of published by accident. The mirror job is manual — a push to a public repository is the one step in this pipeline that cannot be undone.

  • The protocol: engine/docs/SPEC.md, also served at /spec
  • What an auditor would need: engine/docs/AUDIT-SCOPE.md, served at /audit
  • What the relay stores: served at /metadata
  • What is not done: served at /roadmap
S
Description
Flamenet Messenger for iOS — X3DH + Double Ratchet with hybrid post-quantum key agreement, on CryptoKit. MIT.
Readme MIT
53 MiB
Languages
Swift 98.1%
Shell 1.3%
Python 0.4%
JavaScript 0.2%