Author SHA1 Message Date
Paul HittandClaude Opus 5.5 11fb7aed76 Store shots: default to the iPhone 16 Pro Max for the 6.9 inch slot
ci / unit-tests (push) Failing after 1h1m39s
ci / ui-tests (push) Failing after 41m37s
ci / parity-scrub (push) Successful in 17s
ci / vectors-head (push) Successful in 22s
ci / secret-sweep (push) Successful in 1m34s
App Store Connect refused the 1284x2778 set on 2026-10-20 and took
1320x2868 in the 6.9 inch slot. The header now names the iPad device
type for 2048x2732 too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 13:57:50 -04:00
Paul HittandClaude Opus 5.5 67408e02ae String catalogs as Xcode writes them after a build
ci / ui-tests (push) Failing after 41m36s
ci / vectors-head (push) Successful in 9s
ci / parity-scrub (push) Successful in 1m5s
ci / secret-sweep (push) Successful in 16s
ci / unit-tests (push) Failing after 1h1m36s
Xcode reformats both catalogs on every build. Beyond the formatting:
"Send" gains a second translator comment, "Search contacts" and
"Sam Rivera" are marked stale, and InfoPlist gains one extracted key.
No translation changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 13:38:37 -04:00
Paul HittandClaude Opus 5.5 dda92223d6 Store shots: pass the relay to the app when signing in to an account
ci / vectors-head (push) Successful in 9s
ci / secret-sweep (push) Successful in 24s
ci / parity-scrub (push) Successful in 31s
ci / unit-tests (push) Failing after 1h1m40s
ci / ui-tests (push) Failing after 41m52s
Test launches default to http://127.0.0.1:9 so no suite reaches
production by accident. The shot test set -FNMSGServer only for a
throwaway sign-up, so signing in to a named account hit the dead
loopback and failed with "Could not connect to the server".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 13:34:55 -04:00
Paul HittandClaude Opus 5.5 abd2a557e9 Clear four compiler warnings in E2EInbox and TodayStore
ci / vectors-head (push) Successful in 10s
ci / secret-sweep (push) Successful in 28s
ci / parity-scrub (push) Successful in 34s
ci / unit-tests (push) Failing after 1h1m38s
ci / ui-tests (push) Failing after 41m35s
Three @Published properties carried internal(set) on an internal
property, which changes nothing. forgetOnThisPhone defaulted its store
to .shared, evaluated outside the main actor; it now takes an optional
and resolves .shared inside the body.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 13:21:38 -04:00
Paul HittandClaude Opus 5.5 a50d12722f Hide the wallet on iOS for App Store guideline 3.1.5
ci / vectors-head (push) Successful in 10s
ci / secret-sweep (push) Successful in 30s
ci / parity-scrub (push) Successful in 33s
ci / unit-tests (push) Failing after 1h2m10s
ci / ui-tests (push) Failing after 42m22s
App Review rejected 0.32.0 under 3.1.5: crypto wallets are allowed only
from developers enrolled as an organization, and this account is an
individual one. WalletAvailability.hidden removes the Wallet tab, the
Home header button and the Privacy setting. The store shot test no
longer captures a wallet screen. Android and F-Droid keep the wallet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 08:50:03 -04:00
10 changed files with 12886 additions and 12852 deletions
+77 -65
View File
@@ -1,96 +1,108 @@
{
"sourceLanguage": "en",
"strings": {
"CFBundleDisplayName": {
"comment": "App name under the icon. Brand, not translated.",
"extractionState": "extracted_with_value",
"localizations": {
"en": {
"stringUnit": {
"state": "new",
"value": "Flamenet"
"sourceLanguage" : "en",
"strings" : {
"CFBundleDisplayName" : {
"comment" : "App name under the icon. Brand, not translated.",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Flamenet"
}
},
"es": {
"stringUnit": {
"state": "needs_review",
"value": "Flamenet"
"es" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Flamenet"
}
}
}
},
"NSCameraUsageDescription": {
"comment": "Permission prompt. DRAFT, needs native review.",
"extractionState": "extracted_with_value",
"localizations": {
"en": {
"stringUnit": {
"state": "new",
"value": "Flamenet uses the camera to scan a contact's address code. Nothing is recorded and no image leaves your device."
}
},
"es": {
"stringUnit": {
"state": "needs_review",
"value": "Flamenet usa la cámara para escanear el código de dirección de un contacto. No se graba nada y ninguna imagen sale de tu dispositivo."
"CFBundleName" : {
"comment" : "Bundle name",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "FlamenetMessenger"
}
}
}
},
"NSFaceIDUsageDescription": {
"comment": "Permission prompt. DRAFT, needs native review.",
"extractionState": "extracted_with_value",
"localizations": {
"en": {
"stringUnit": {
"state": "new",
"value": "Flamenet can require Face ID before opening the app, so somebody holding your unlocked phone cannot read your conversations."
"NSCameraUsageDescription" : {
"comment" : "Permission prompt. DRAFT, needs native review.",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Flamenet uses the camera to scan a contact's address code. Nothing is recorded and no image leaves your device."
}
},
"es": {
"stringUnit": {
"state": "needs_review",
"value": "Flamenet puede pedir Face ID antes de abrir la aplicación, para que nadie que tenga tu teléfono desbloqueado en la mano pueda leer tus conversaciones."
"es" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Flamenet usa la cámara para escanear el código de dirección de un contacto. No se graba nada y ninguna imagen sale de tu dispositivo."
}
}
}
},
"NSMicrophoneUsageDescription": {
"comment": "Permission prompt. DRAFT, needs native review.",
"extractionState": "extracted_with_value",
"localizations": {
"en": {
"stringUnit": {
"state": "new",
"value": "Flamenet uses the microphone for voice notes and voice calls, which are end-to-end encrypted before they leave your device."
"NSFaceIDUsageDescription" : {
"comment" : "Permission prompt. DRAFT, needs native review.",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Flamenet can require Face ID before opening the app, so somebody holding your unlocked phone cannot read your conversations."
}
},
"es": {
"stringUnit": {
"state": "needs_review",
"value": "Flamenet usa el micrófono para las notas de voz y las llamadas de voz, que se cifran de extremo a extremo antes de salir de tu dispositivo."
"es" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Flamenet puede pedir Face ID antes de abrir la aplicación, para que nadie que tenga tu teléfono desbloqueado en la mano pueda leer tus conversaciones."
}
}
}
},
"NSPhotoLibraryAddUsageDescription": {
"comment": "Permission prompt. DRAFT, needs native review.",
"extractionState": "extracted_with_value",
"localizations": {
"en": {
"stringUnit": {
"state": "new",
"value": "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."
"NSMicrophoneUsageDescription" : {
"comment" : "Permission prompt. DRAFT, needs native review.",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Flamenet uses the microphone for voice notes and voice calls, which are end-to-end encrypted before they leave your device."
}
},
"es": {
"stringUnit": {
"state": "needs_review",
"value": "Flamenet puede guardar en tus fotos una historia que publicaste tú, para que la conserves cuando desaparezca para todos los demás. Solo puede agregar; no puede ver las fotos que ya tienes."
"es" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Flamenet usa el micrófono para las notas de voz y las llamadas de voz, que se cifran de extremo a extremo antes de salir de tu dispositivo."
}
}
}
},
"NSPhotoLibraryAddUsageDescription" : {
"comment" : "Permission prompt. DRAFT, needs native review.",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "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."
}
},
"es" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Flamenet puede guardar en tus fotos una historia que publicaste tú, para que la conserves cuando desaparezca para todos los demás. Solo puede agregar; no puede ver las fotos que ya tienes."
}
}
}
}
},
"version": "1.0"
"version" : "1.0"
}
+12775 -12771
View File
File diff suppressed because it is too large Load Diff
+5 -3
View File
@@ -4,8 +4,10 @@
# FN_SHOT_HANDLE=… FN_SHOT_PASSPHRASE=… Scripts/store-shots.sh [device type]
#
# One simulator, one sign-in, one shot per tab. The device decides the pixel
# size, so the default is an iPhone 13 Pro Max: 1284 x 2778, which is a size
# App Store Connect accepts. Results land in build/store-shots/ and the script
# size, so the default is an iPhone 16 Pro Max: 1320 x 2868, the 6.9 inch
# slot. Since 2026-10-20 App Store Connect refuses 1284 x 2778 there. For
# iPad pass com.apple.CoreSimulator.SimDeviceType.iPad-Pro-12-9-inch-6th-generation-8GB
# (2048 x 2732). Results land in build/store-shots/ and the script
# prints the size of each, so a wrong device is visible without opening one.
#
# The conversation shot is not here. It needs a second phone to talk to, so it
@@ -13,7 +15,7 @@
set -uo pipefail
cd "$(dirname "$0")/.."
DEVICE="${1:-com.apple.CoreSimulator.SimDeviceType.iPhone-13-Pro-Max}"
DEVICE="${1:-com.apple.CoreSimulator.SimDeviceType.iPhone-16-Pro-Max}"
RELAY="${FN_SHOT_RELAY:-https://relay1.flamenet.io}"
HANDLE="${FN_SHOT_HANDLE:-}"
PASS="${FN_SHOT_PASSPHRASE:-}"
+3 -3
View File
@@ -542,13 +542,13 @@ final class E2EInbox: ObservableObject {
/// Section 17.6 and 8.9d, see `E2EInbox+AccountState.swift`. Written only
/// through `writeSync` and the merge of what linked devices send.
@Published internal(set) var syncState = SyncState()
@Published var syncState = SyncState()
/// Section 17.3: the code this device is showing so a linked device can
/// link it, and when it stops being good. Memory only: ten minutes, one
/// use.
@Published internal(set) var pendingLinkCode: (code: DeviceLinkCode, expires: Date)?
@Published var pendingLinkCode: (code: DeviceLinkCode, expires: Date)?
/// A numeric-fallback offer held until the person confirms the number here.
@Published internal(set) var pendingNumericOffer: PendingLinkOffer?
@Published var pendingNumericOffer: PendingLinkOffer?
/// What the person is told about changes to the linked set.
@Published var linkNotices: [LinkNotice] = []
/// Section 17.6: ops written here, waiting to go to the linked devices.
+4 -2
View File
@@ -393,8 +393,10 @@ final class TodayStore: ObservableObject {
/// other traces. The file is public; the picks say where somebody cares
/// about, and the next person to use this phone has no business with
/// either.
static func forgetOnThisPhone(_ store: TodayStore = .shared, defaults: UserDefaults = .standard) {
store.forgetEverything()
/// `store` is optional because a default of `.shared` is evaluated
/// outside the main actor, which Swift 6 refuses.
static func forgetOnThisPhone(_ store: TodayStore? = nil, defaults: UserDefaults = .standard) {
(store ?? .shared).forgetEverything()
defaults.removeObject(forKey: TodayPlaces.key)
}
}
+1 -1
View File
@@ -62,7 +62,7 @@ struct BuddyListView: View {
// Small, and first: it is the least-used of the three, so it
// sits furthest from the thumb rather than displacing the add
// button people reach for.
MiniWallet()
if !WalletAvailability.hidden { MiniWallet() }
// The explicit way in for people who don't think to type into
// a SEARCH box to add somebody.
Button {
+8 -4
View File
@@ -158,9 +158,13 @@ struct MainTabView: View {
.init(
id: 5, title: String(localized: "Files"), symbol: "folder.fill",
identifier: "files"),
.init(
id: 6, title: String(localized: "Wallet"), symbol: "wallet.bifold.fill",
identifier: "wallet"),
// App Store 3.1.5 allows wallets only on organization accounts.
// This account is an individual one, so the iOS build has none.
WalletAvailability.hidden
? nil
: .init(
id: 6, title: String(localized: "Wallet"), symbol: "wallet.bifold.fill",
identifier: "wallet"),
.init(
id: 4, title: String(localized: "Account"),
symbol: "person.crop.circle.fill", badge: unreadAlerts,
@@ -209,7 +213,7 @@ struct MainTabView: View {
.padding(Theme.Kit.margin)
}
case 5: FilesView()
case 6: WalletScreen()
case 6 where !WalletAvailability.hidden: WalletScreen()
// Checked here as well as in the bar, so a tab chosen before
// supervision was switched on cannot still show it.
case 7 where !ParentalControls.todayHidden:
+6
View File
@@ -1,5 +1,11 @@
import SwiftUI
/// Off in the App Store build. Guideline 3.1.5 allows crypto wallets only
/// from developers enrolled as an organization, and this account is not.
enum WalletAvailability {
static let hidden = true
}
/// The wallet, from anywhere.
struct MiniWallet: View {
@EnvironmentObject private var environment: AppEnvironment
@@ -69,7 +69,9 @@ struct PrivacySettingsView: View {
Section {
Toggle("Messages", isOn: $passwordForMessages)
Toggle("Files", isOn: $passwordForFiles)
Toggle("Wallet", isOn: $passwordForWallet)
if !WalletAvailability.hidden {
Toggle("Wallet", isOn: $passwordForWallet)
}
} header: {
Text("Also ask for your password")
} footer: {
+4 -2
View File
@@ -52,7 +52,9 @@ final class AppStoreShotsUITests: XCTestCase {
let app = XCUIApplication.underTest()
app.launchArguments = ["-FNMSGResetState"]
if !haveAccount {
// A test launch defaults to a dead loopback address so no suite
// reaches production by accident. The store set names its relay.
if !signUpRelay.isEmpty {
app.launchArguments += ["-FNMSGServer", signUpRelay]
}
app.launch()
@@ -76,7 +78,7 @@ final class AppStoreShotsUITests: XCTestCase {
for (tab, name) in [
("home", "02-home"), ("messages", "03-messages"),
("calls", "04-calls"), ("files", "05-files"),
("wallet", "06-wallet"), ("account", "07-account"),
("account", "07-account"),
] {
if openTab(app, tab) { shot(app, name) }
}