Comments and docs kept dating themselves: when a constraint was set, when a pair was verified, what a limit was before some day, which day a subtree was split out. A reader wants the current state, and the date goes stale faster than the sentence around it. Kept, because they are data rather than narration: ISO timestamps in test fixtures, the certificate expiry in project.yml, which an operator acts on, and docs/BUGS-2026-09-01.md, whose name is the record. ⚠️ The first pass collapsed indentation. A whitespace rule meant for prose matched YAML list items and markdown continuation lines, and project.yml stopped parsing: "did not find expected key: - path: Sources". The tests still passed, because they ran against the project generated before the break, which is exactly how this would reach somebody else. Reverted and redone without touching leading whitespace, then xcodegen was run to prove the file parses. 764 XCTest and 75 Swift Testing pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
7.6 KiB
Flamenet Messenger, scoping the first TestFlight
Status. Step 1 is done, all seven stub surfaces were cut in MR !4 (
67cebad), 356 deletions, three tabs, verified in the simulator. Most of the distribution prerequisites below are done too. What now gates a TestFlight build is deploying the relay, because the app has been pinned tomessenger.flamenet.ioand that name does not resolve yet.Also fixed since: the Community tab called a WordPress route the relay does not serve and answered 404 on one of the app's three tabs. It is an exact-username search now.
Written against main @ de23972. Build verified: xcodegen +
simulator build succeeds: 63 Swift files, 12,574 lines.
The app has never shipped, and the landing page still carries a TODO waiting
on the TestFlight invite link. So nothing here is a regression to fix, it is a
first release to define.
The question is not "is it done." It is which app ships. Right now a tester opens it and finds one of four tabs entirely empty, four buttons that do nothing, and a settings screen configuring a feature that does not exist. That reads as abandoned rather than early, and it is fixable by deletion rather than by work.
What is real and ships as-is
| Area | Size | State |
|---|---|---|
| E2E crypto | 9 files, 1,814 lines | X3DH, Double Ratchet, Keychain persistence, safety numbers. Includes the 12 Aug prekey-block re-attach and OPK-burn fixes |
| Messaging | fnmsg_send / fnmsg_poll, high-water polling |
Real round trip against the plugin |
| Auth | 298 lines | Sign up, sign in, change password, delete account |
| Buddy list + profiles | — | Real, backed by the plugin |
| Community directory | — | Member list from /wp/v2/users, searchable, taps into buddy profile |
| Commands | 264 lines | 12 commands via the server's /search dispatcher |
| Settings | 6 screens | Account, chat, notifications, privacy, data, about |
That is a coherent product: an encrypted one-to-one messenger with a member directory and slash commands. It is worth putting in front of testers.
What was stubbed, and what was done, complete
| Surface | Where | Call |
|---|---|---|
| Calls tab: whole tab, "Voice & video coming soon" | CallsView.swift |
Cut the tab |
| Video call / Voice call buttons | ChatView.swift:237,243 |
Cut |
| Attach photo | ChatView.swift:432 |
Cut |
| Voice note | ChatView.swift:439 |
Cut |
| Add Story + the Stories strip | BuddyListView.swift:214,191 |
Cut |
| Groups section — "Groups coming soon" | BuddyListView.swift:268 |
Cut |
| Auto-download Photos / Videos toggles | DataStorageView.swift:14,15 |
Cut |
Cut rather than build, because none of these is a UI task:
- Voice/video needs signalling, TURN, and a WebRTC stack. Under E2E it also needs key agreement per call. This is not a sprint.
- Attachments and voice notes need encrypted blob storage server-side, upload, retention, and per-recipient key wrapping. The plugin has none of it.
- Groups under E2E is the largest of all: pairwise Double Ratchet does not extend to groups without sender keys or per-member fan-out, plus membership changes forcing rekeys. Shipping groups means designing group crypto.
- Stories needs media storage plus an expiry model.
The Stories strip is worth singling out: it currently renders your online buddies dressed as stories. That is not an empty state, it is misleading chrome, a tester will tap a buddy expecting a story.
The auto-download toggles are the sharpest tell. They persist to AppStorage and
configure the downloading of photos and videos that the app cannot send or receive. Same
screen shows Cached messages, with a literal dash where a number belongs.
What cutting costs
- Tab bar: 4 tabs → 3 (Home, Community, Account)
- Chat: loses 2 toolbar buttons and 2 composer buttons
- Home: loses the Stories strip and the Groups section
- Data settings: loses 2 toggles; the
,needs a real number or the row goes ComingSoonSheetand thestubbedFeatureplumbing in three views become dead code
Roughly a day's work, almost all deletion. Nothing in the messaging or E2E path is touched.
Distribution prerequisites, independent of features
These block a build reaching anyone, no matter what the app contains.
Status. Five of the seven are resolved. What is left is one thing only I can do and two that need you.
| # | Prerequisite | State |
|---|---|---|
| 1 | Signing identity. DEVELOPMENT_TEAM was empty; no identity means no archive and no TestFlight. |
Set in project.yml |
| 2 | Export compliance. The app implements its own X3DH and Double Ratchet, so the answer is not the usual trivial one. | Decided, non-exempt, mass-market 5D992.c, see EXPORT-COMPLIANCE.md. The plist key stays out until App Store Connect issues the matching code; setting it alone rejects the upload |
| 3 | NSAllowsLocalNetworking in the Info.plist, kept for local development. |
Removed. It turned out to be unnecessary. ATS permits cleartext to 127.0.0.1 regardless, verified in the simulator |
| 4 | Release Xcode. App Store Connect rejects builds from a beta, and an iCloud-resident toolchain can be evicted mid-build. | Xcode 26.6 is installed at /Applications/Xcode.app, but xcode-select still points at the iCloud beta. One command, and it needs your password |
| 5 | Versioning. project.yml and the Info.plist disagreed on the version string. |
One source of truth: the Info.plist takes $(MARKETING_VERSION) |
| 6 | No export pipeline. No fastlane, no exportOptions plist. | Fine for a first release, the Xcode Organizer does it. Automate later |
| 7 | Landing page. A TODO waiting on the TestFlight link. |
Still waiting on the link |
A prerequisite that did not exist when this was written: the app had no
backend. It defaulted to https://flamenet.io, whose plugin was removed on
It is pinned to messenger.flamenet.io now, which means the relay
has to be deployed before a tester can do anything at all. DNS for that name
does not resolve yet. server/DEPLOY.md has the sequence.
So the ordering has changed. Step 2 below is no longer the gate; deploying the relay is, because a TestFlight build that cannot reach a server is worse than no build.
Recommended v1
Three tabs. Encrypted 1:1 messaging, a member directory, and slash commands. Described honestly as a private beta of an encrypted messenger, not as a Signal alternative, and without repeating the "we can't read your messages" claim that is false for the web client.
Nothing shipped is a placeholder. Everything absent is absent, not advertised as coming.
Sequence
Cut the seven stubs, done, MR !4. Went slightly wider than planned: the Message/Group filter pill was removed too, since it became a two-option toggle with one option once Groups was gone. The buddy list is now unfiltered under a plain "Chat" header.- Apple Developer membership + release Xcode: calendar time, not effort.
- Decide the export-compliance answer and set
ITSAppUsesNonExemptEncryption. - Remove the ATS exception for release; reconcile the version numbers.
- Archive → TestFlight internal. Test E2E across an app restart, not just a round trip, that is precisely the class of bug the 12 Aug fixes addressed on web.
- External TestFlight, then wire the landing-page link.
Step 1 is done. Step 2. Apple Developer membership and a release Xcode, now gates everything else, and it is calendar time rather than effort, so it is worth starting before the remaining work is scheduled.