Files
Paul HittandClaude Opus 5 b3c47c9926 Spanish for the 79 strings the 0.32 features added
`Scripts/check-spanish-catalog.py` reported 1466 keys in code against 1391 in
the catalog. The 79 without Spanish were all from the release branch: greeting
cards, events, games, the Today tab, the small-web directory, story
take-downs, and the two scanner refusal lines added by this merge. It now
reports 0 missing.

Terms follow docs/i18n/glossary-es.md and, where Android already shipped the
same label, Android's wording: Today is "Hoy", Card "Tarjeta", Event "Evento",
Game "Juego", story "historia", take down "retirar", directory "directorio",
bridge line "línea del puente". Every entry is `needs_review`, like the rest
of the es column: nothing ships until the owner or a Localization Lab
volunteer has read it.

The old parental-controls footer is deleted. The merge replaced it with the
longer text that also covers Today and the directory, so its key was dead.
"Attach photo" and "Search contacts" are still reported unused, as on main:
both reach the catalog through a `String` parameter the compiler does not
record, and "Search contacts" has three live call sites.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-14 11:02:23 -04:00

372 lines
18 KiB
YAML

name: FlamenetMessenger
options:
bundleIdPrefix: net.flamenet
deploymentTarget:
# 26.0, and it belongs HERE. This was raised in the generated
# `project.pbxproj`, which is gitignored, so post-quantum key agreement
# compiled on the machine that made the change and reverted to an iOS 16
# floor for anybody who ran `xcodegen generate`, which is the documented
# way to add a file. `MLKEM768` then fails to build with an availability
# error that points at the crypto rather than at the project file.
iOS: "26.0"
createIntermediateGroups: true
# Spanish (es) is a DRAFT, needs native review: nothing in
# Resources/Localizable.xcstrings or Resources/InfoPlist.xcstrings ships
# until the owner or a Localization Lab volunteer has read it. English stays
# the development language; the catalogs carry the es column. See
# docs/i18n/glossary-es.md.
developmentLanguage: en
settings:
base:
# 1.0 for the first App Store submission. App Store Connect
# refuses a version string that disagrees with the uploaded binary, so this
# is what the listing has to say too.
#
# Kept in step with Android on purpose. Android ships versionName
# "0.11" (versionCode 13); this is the same release, so it is 0.11.0 and
# not 1.0. Two apps that are the same build answering differently when
# somebody is asked "what version are you on?" costs more than a tidy
# version string is worth.
#
# This file is the only place the version can be set. The
# `.xcodeproj` is gitignored and regenerated by `Scripts/generate.sh`, so a
# version typed into Xcode's UI survives exactly until the next generate,
# which is how the repo came to say 1.0 while TestFlight had 0.11.0.
#
# CURRENT_PROJECT_VERSION must be unique per MARKETING_VERSION and only
# ever go up. If an upload is rejected as a duplicate, bump this, not the
# version.
#
# Numbered to line up with the version rather than counting uploads: the
# build number carries the release it belongs to, so 0.13.0 was build 13.
# Bumped when the previous one goes up rather than when the next one is
# archived: an upload rejected as a duplicate is found out slowly, at the
# end of an archive, and costs the whole round trip again.
#
# 0.14.0 build 14 is on TestFlight. This is the release that follows it:
# the Face ID passphrase store removed, an unanswered ring that releases
# itself, and the first build that runs on iPad.
#
# Build 15 went up and is not for testers. With one account on two
# devices, the second device's expiring ring hung up the call answered on
# the first, and on iPad the Files, Wallet and Account tabs were squeezed
# into the list column. 16 carries the fixes for both; the version stays
# 0.15.0 because it is the same release, corrected.
#
# 0.19.0 is 0.18.0 with calls that work. 18 went to TestFlight with them
# broken, and 19 was cut before the last of the call fixes landed, so 20 is
# the first build that carries all of them. An
# answer never left the phone, because CallKit answering its own call
# fetched the waiting offer and stopped there, and every hangup was marked
# urgent so it rang the other phone instead of waking it. Proved three
# times against a real handset: the relay saw the offer arrive and nothing
# come back.
#
# 0.20.0 is the first build a locked phone can be rung on at all. Every
# call proved before this one was proved with the app open, because a
# build installed from Xcode holds a SANDBOX push token and the relay
# sends to production: it refused every push, forgot the token, and the
# app registered another sandbox one on the next launch, round and round.
# Nothing in the app was wrong and nothing could have been read from
# testing it that way. It also carries the repair ladder -- a refusal now
# fetches a fresh credential, re-registers and re-uploads its push tokens
# before anybody is asked to do anything -- and the call buttons that went
# grey after one call and stayed grey.
#
# Build 20 rather than 21, keeping the number lined up with the release the
# way every one before it is. 20 was set under 0.19.0 and never went up
# under it; the pair is what has to be unique, so (0.20.0, 20) is free
# whatever happened to (0.19.0, 20).
#
# 0.21.0 is the build that stops envelopes arriving twice. Both clients
# already refused an envelope id they had seen before and neither ever sent
# one on the ordinary route, so every envelope on the relay carried a null
# id and the check could never fire. Measured on a real pair of phones: one
# call offer written to the relay, two deliveries 2.4 seconds apart, and
# the second answered "busy" by the phone that was ringing for the first.
# Messages and call log entries doubled the same way and more quietly.
# Also here: history that a locked phone can actually save, and closing the
# app during a call ending it on the other phone rather than leaving
# somebody saying hello into it.
#
# 0.22.0 is about a person being able to act on what the app tells them.
# Three screens showed a key change and one place could accept it, so the
# warning was reachable and the cure was not. And signing a phone out read
# as an identity change, which meant using the Devices screen made every
# contact refuse to message you until they accepted keys that were not new.
#
# 0.32.0 follows 0.31.0, which is in App Review. It carries the privacy
# batch: every message padded to a fixed size (spec section 23), and
# this account's own other devices sent nothing until they are linked
# (section 17.3). It also carries the 0.32 features: greeting cards
# (6.31), events (6.32), games in a chat (6.33), hello links (6.34),
# story take-downs (6.35), the Today tab (8.12) and the small-web
# directory behind it.
MARKETING_VERSION: "0.32.0"
CURRENT_PROJECT_VERSION: "32"
SWIFT_VERSION: "5.0"
# The compiler writes every LocalizedStringKey literal and every
# String(localized:) into the build, which is how Localizable.xcstrings
# learns its keys (xcodebuild -exportLocalizations reads the same data).
# A user-facing String that is neither of those is invisible to it and
# stays English in every language.
SWIFT_EMIT_LOC_STRINGS: "YES"
LOCALIZATION_PREFERS_STRING_CATALOGS: "YES"
# The PAID team, not the personal one. This said "2W7DA3A8W2", the
# free personal team attached to the iCloud address on this Mac, while the
# App Store provisioning profile that already exists for
# `net.flamenet.messenger` (issued, expires 2027-06-04) belongs to
# M5923Y84P5, the team that ships hittfitness and shepherdgiving. A personal
# team cannot produce a distribution build at all, so every attempt to
# archive for TestFlight would have failed at signing, naming the
# certificate rather than the team ID that made it unobtainable.
DEVELOPMENT_TEAM: "M5923Y84P5"
CODE_SIGN_STYLE: Automatic
TARGETED_DEVICE_FAMILY: "1"
packages:
# Google's WebRTC, packaged for SPM (binary xcframework). Media for voice
# calls; DTLS-SRTP does the media encryption, and the DTLS fingerprints are
# authenticated by riding our Double Ratchet signalling (spec §6.8).
# Wrapped locally, see Packages/WebRTCBinary/Package.swift for why a
# version pin stopped being a pin.
WebRTCBinary:
path: Packages/WebRTCBinary
# The Files crypto and wire layer, vendored under Packages/ as the Files app
# is retired into this one. Local rather than a git dependency: the repo it
# came from is going away, and a build that reaches for it would go with it.
FlamenetFiles:
path: Packages/FlamenetFiles
# The obfs4 transport, as a SUBMODULE rather than vendored source.
#
# That repo is the implementation's only home: it carries the vector
# suite and the live interoperability tests against a real lyrebird bridge,
# in both directions. A vendored copy would let the code this app ships
# drift away from the code that is actually proven to interoperate, and
# nothing would report it.
#
# Only the pure-Swift modules are linked. `Obfs4NIO` exists there too and
# would drag SwiftNIO into the app; the SOCKS shim here is built on the
# sans-I/O core and Foundation instead.
Obfs4:
path: Packages/Obfs4
# The rules engines for the games played in a chat (§6.33). Local and with
# no dependencies; the Android `:games` module runs the same vectors.
FlamenetGames:
path: Packages/FlamenetGames
targets:
FlamenetMessenger:
type: application
platform: iOS
sources:
- path: Sources
- path: Resources
excludes:
- "stickers"
- "cards"
- "directory"
# Folders, not groups, so the bundle keeps the paths both apps name:
# `stickers/<pack>.json`, `stickers/<pack>/<image>`,
# `cards/<design>.png` and `directory/directory-v1.json`. A group copies
# every file to the bundle root, where `love.png` the card and a sticker
# of the same name would collide.
#
# `group:` names a group OTHER than Resources on purpose. Put in a group
# whose own path is `Resources`, xcodegen 2.45.2 writes the reference as
# `path = stickers; sourceTree = SOURCE_ROOT`, which resolves against the
# project root and not the group, so the build fails with "The file
# 'stickers' couldn't be opened". Outside that group it writes
# `path = Resources/stickers`, which is correct.
- path: Resources/stickers
type: folder
group: Bundle folders
- path: Resources/cards
type: folder
group: Bundle folders
- path: Resources/directory
type: folder
group: Bundle folders
dependencies:
- package: WebRTCBinary
product: WebRTC
- package: FlamenetFiles
product: FilesEngine
- package: FlamenetFiles
product: FilesClient
# The sans-I/O core only. `Obfs4NIO` from the same package would pull
# SwiftNIO into the app; the SOCKS shim is built on Foundation instead.
- package: Obfs4
product: Obfs4
# Explicitly, because Swift does not re-export a module's own
# dependencies: `Obfs4` uses `Elligator2` but does not hand it on.
- package: Obfs4
product: Elligator2
- package: Obfs4
product: Obfs4Framing
- package: FlamenetGames
product: FlamenetGames
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: net.flamenet.messenger
# The Push Notifications capability. Without the entitlement,
# registerForRemoteNotifications fails at runtime with "no valid aps
# environment" and no token ever arrives, which reads as a server
# problem. It also needs the capability enabled on the App ID in the
# developer portal; the entitlement alone is not enough.
CODE_SIGN_ENTITLEMENTS: Resources/FlamenetMessenger.entitlements
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME: AccentColor
# Must live at TARGET level: xcodegen emits a target default of "1,2"
# that silently overrides the project-level value. Setting it at
# project level does nothing, which is how an archive once shipped as
# iPhone+iPad by accident.
#
# "1,2" is iPhone and iPad, on purpose this time. What made the
# accidental version fail review was shipping iPad without the iPad
# icon or the orientations App Store Connect requires with it; both are
# handled below rather than left to be discovered at upload.
TARGETED_DEVICE_FAMILY: "1,2"
info:
path: Resources/Info.plist
properties:
CFBundleDisplayName: Flamenet
# One source of truth for versioning: the build settings above.
# Wakes a suspended app so a message can arrive before it is opened.
# This is the ONE place Apple sits on the delivery path; the push is
# content-free and the device decrypts locally. See PushRegistrar.
# `voip` is what lets a locked, suspended app be woken to RING.
# `remote-notification` alone wakes it briefly to fetch, which is right
# for a message and useless for a call, by the time a background fetch
# has polled, decrypted and decided, the caller has given up.
#
# A VoIP push is a promise: iOS requires the app to report an
# incoming call to CallKit as soon as one arrives, and terminates it if
# it does not. See VoIPCallReceiver.
#
# `audio` is for the call itself once it is up. CallKit activates the
# audio session, but the capture and playback are WebRTC's, and without
# this mode iOS suspends them when the screen locks or the person
# switches apps mid-call: the far end hears silence while both
# screens say the call is connected. Apple's own CallKit sample
# declares both.
UIBackgroundModes: [remote-notification, voip, audio]
CFBundleShortVersionString: $(MARKETING_VERSION)
CFBundleVersion: $(CURRENT_PROJECT_VERSION)
# canOpenURL answers false for any scheme not listed here,
# whatever is installed, so without these the jailbreak check
# would report a clean phone it never actually examined, which
# reads as a pass. See Sources/Security/DeviceIntegrity.swift.
LSApplicationQueriesSchemes: [cydia, sileo, zbra]
# Two schemes, two entries, and both are load-bearing.
#
# Section 8.14: contact links in their app form,
# flamenet:<fnid>@<relay>?u=..&t=.. (or l=..). The web form keeps its
# payload in the fragment and the relay's /add page hands it here.
#
# Hello links (SPEC §6.34). The relay's /hi page reads the address
# from its own fragment and hands it over as
# flamenetmessenger://hi#<address>; the app only fills in Home's
# add-by-address screen, and nothing is sent until the person chooses
# to. Universal links would need a change in the Apple developer
# portal, so they wait.
CFBundleURLTypes:
- CFBundleURLName: io.flamenet.messenger.contact-link
CFBundleURLSchemes: [flamenet]
- CFBundleURLName: net.flamenet.messenger.hello
CFBundleTypeRole: Viewer
CFBundleURLSchemes: [flamenetmessenger]
UILaunchScreen: {}
# The iPhone stays portrait. Nothing about holding a phone sideways
# improves a chat, and every screen here was drawn for one column.
UISupportedInterfaceOrientations:
- UIInterfaceOrientationPortrait
# The iPad does not get that choice. App Store Connect requires an iPad
# build to support all four orientations unless it declares
# `UIRequiresFullScreen`, and declaring that opts the app out of Split
# View and Slide Over entirely, which is most of what running on an
# iPad is for. So: all four, and every screen has to survive being
# wide. That is the actual work in this change; the device family above
# is one line.
UISupportedInterfaceOrientations~ipad:
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
UIApplicationSceneManifest:
UIApplicationSupportsMultipleScenes: false
NSFaceIDUsageDescription: >-
Flamenet can require Face ID before opening the app, so somebody
holding your unlocked phone cannot read your conversations.
NSMicrophoneUsageDescription: >-
Flamenet uses the microphone for voice notes and voice calls,
which are end-to-end encrypted before they leave your device.
NSCameraUsageDescription: >-
Flamenet uses the camera to scan a contact's address code. Nothing
is recorded and no image leaves your device.
# Add-only, and the string says so. Without this key the app does
# not warn, it crashes the first time somebody saves a story.
NSPhotoLibraryAddUsageDescription: >-
Flamenet can save a story you posted yourself to your photos, so you
keep it after it disappears for everyone else. It only ever adds;
it cannot see the photos you already have.
# Export compliance: non-exempt, mass-market
# self-classified 5D992.c, see EXPORT-COMPLIANCE.md. Answered
# interactively in App Store Connect for now: embedding
# ITSAppUsesNonExemptEncryption without a matching
# ITSEncryptionExportComplianceCode from ASC's issued documentation
# rejects the upload ("Invalid Export Compliance Code"). Once ASC
# issues the code, add ITSEncryptionExportComplianceCode here.
# ATS fully on. There is deliberately no NSAllowsLocalNetworking
# exception: a previous local run added one "TEMPORARY-LOCAL-ONLY:
# reverted immediately after the run" and it was not reverted, which
# is exactly how a dev exception reaches an App Store build. It turns
# out not to be needed. ATS permits cleartext to localhost /
# 127.0.0.1 without it, verified against the local relay in the
# simulator, so the honest fix is that it never goes back in.
FlamenetMessengerTests:
type: bundle.unit-test
platform: iOS
sources:
- path: Tests
dependencies:
- target: FlamenetMessenger
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: net.flamenet.messenger.tests
GENERATE_INFOPLIST_FILE: "YES"
# Host-application tests: the app's types are `internal`, and testing
# them from a bundle that does not load into the app would mean
# widening half the codebase to `public` to satisfy the test runner.
TEST_HOST: "$(BUILT_PRODUCTS_DIR)/FlamenetMessenger.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/FlamenetMessenger"
BUNDLE_LOADER: "$(TEST_HOST)"
FlamenetMessengerUITests:
type: bundle.ui-testing
platform: iOS
sources:
- path: UITests
dependencies:
- target: FlamenetMessenger
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: net.flamenet.messenger.uitests
GENERATE_INFOPLIST_FILE: "YES"
TEST_TARGET_NAME: FlamenetMessenger
schemes:
FlamenetMessenger:
build:
targets:
FlamenetMessenger: all
FlamenetMessengerTests: [test]
FlamenetMessengerUITests: [test]
test:
targets:
- FlamenetMessengerTests
- FlamenetMessengerUITests
run:
config: Debug