Files
Paul HittandClaude Opus 5 15b9d5b44a Say what is true, without dating it
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>
2026-09-09 09:27:20 -04:00

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 to messenger.flamenet.io and 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
  • ComingSoonSheet and the stubbedFeature plumbing 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.


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

  1. 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.
  2. Apple Developer membership + release Xcode: calendar time, not effort.
  3. Decide the export-compliance answer and set ITSAppUsesNonExemptEncryption.
  4. Remove the ATS exception for release; reconcile the version numbers.
  5. 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.
  6. 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.