App Store listing copy, and screenshots the store will accept
`Scripts/screenshots-ios.sh` rendered at 1125x2778... 1125x2436, the 375x812 design canvas at 3x. App Store Connect takes 1242x2688, 2688x1242, 1284x2778 or 2778x1284 and nothing else, so every shot was rejected on size. The aspect ratios are close enough that rescaling looks fine and is still not one of those numbers. `FNMSG_SHOT_SIZE=1284x2778` now asks for a pixel size. Points are derived from it at a pinned 3x, so the layout gets the room a device that size really has rather than a scaled-up picture of a smaller one, and the same request gives the same pixels on any simulator. Unset, the design canvas is unchanged, which is what it is for. ⚠️ Getting the value into the test took three attempts, recorded in the script so the next person skips them: `FOO=bar` on an xcodebuild line is a build setting the test never sees, and the TEST_RUNNER_ prefix only reaches a UI test's runner, not unit tests hosted in the app. Both arrived unset. `simctl spawn launchctl setenv` works, and the script unsets it when no size is asked for, or a previous sized run would silently apply to the next one. STORE-LISTING.md carries the rest of the App Store tab: promotional text 149/170, description 2358/4000, keywords 97/100, support and marketing URLs, copyright, and why the routing coverage file stays empty. Every URL in it returns 200. It names no competitor, and says plainly that the app has not been audited. 764 XCTest and 75 Swift Testing pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
7760ece24f
commit
8051d38009
@@ -0,0 +1,147 @@
|
||||
# App Store Connect listing
|
||||
|
||||
Copy for the App Store tab. TestFlight's own fields are in `TESTFLIGHT.md`;
|
||||
these are the ones the public listing uses.
|
||||
|
||||
Every claim here is one the project can show. Nothing says audited, nothing
|
||||
says unbreakable, and nothing compares this to another messenger by name. See
|
||||
the positioning note in `SECURITY.md`: what survives scrutiny is "a private
|
||||
messenger you host yourself, with no phone number", not a superlative.
|
||||
|
||||
## Promotional Text
|
||||
|
||||
Editable without a new build, so it is the field to use for anything that
|
||||
changes. 170 characters; this is 146.
|
||||
|
||||
```
|
||||
Now in open beta. Messages, files and a wallet in one app, encrypted on your
|
||||
device. Sign up with a username. Run the server yourself if you want to.
|
||||
```
|
||||
|
||||
## Description
|
||||
|
||||
4000 characters allowed; this is about 2,400.
|
||||
|
||||
```
|
||||
Flamenet Messenger is a private messenger you can host yourself.
|
||||
|
||||
Sign up with a username. No phone number, no email required, no contact upload.
|
||||
Your address is derived from your passphrase rather than issued by a server, so
|
||||
signing in on a different server keeps the same identity.
|
||||
|
||||
WHAT IS ENCRYPTED
|
||||
|
||||
Every one-to-one and group message is end-to-end encrypted on your device using
|
||||
the X3DH and Double Ratchet protocols. Photos, voice notes, files and stories
|
||||
get the same treatment with a per-file key. There is no plain mode to fall back to: if the app cannot encrypt, it
|
||||
refuses to send and tells you why.
|
||||
|
||||
Newer conversations add a post-quantum key agreement (ML-KEM-768) alongside the
|
||||
classical one, so a conversation recorded today is not readable later by a
|
||||
computer that does not exist yet. It holds if either half holds.
|
||||
|
||||
WHO YOU ARE TALKING TO
|
||||
|
||||
Every conversation has a safety number you can compare in person or over
|
||||
another channel. If someone's keys change, because they reinstalled, added a
|
||||
device, or something worse, the app says so and holds the message until you
|
||||
decide. Your app also checks, on its own, that the key a server hands you is
|
||||
one that server has published to a log it cannot quietly rewrite.
|
||||
|
||||
WHAT THE SERVER SEES
|
||||
|
||||
The relay carries sealed envelopes. It does not hold the keys that open them.
|
||||
It does see who a message is addressed to, when it arrived, and how large it
|
||||
is, and that is written out plainly at flamenetmessenger.com/metadata rather
|
||||
than left for you to assume. Photos have their location and camera data
|
||||
stripped before they are sent.
|
||||
|
||||
FILES AND WALLET
|
||||
|
||||
Files keeps documents encrypted on your device, with a classification you
|
||||
choose, and sends them sealed. Wallet makes and keeps keys from a standard
|
||||
twenty-four-word phrase; it is not connected to any network, and it says so
|
||||
rather than showing you a balance it cannot know.
|
||||
|
||||
HOST IT YOURSELF
|
||||
|
||||
The relay is open source and published, not described. Two containers and a
|
||||
config file. An account on your own server is a real account, not a limited
|
||||
one, and servers can exchange messages with each other if you allow it.
|
||||
|
||||
WHAT THIS IS NOT
|
||||
|
||||
This app has not been independently audited. It is built in the open by a small
|
||||
team and the source is there to read. Read flamenetmessenger.com/security
|
||||
before you rely on it for anything where being wrong would hurt.
|
||||
```
|
||||
|
||||
## Keywords
|
||||
|
||||
100 characters including commas; this is 98. No competitor names: Apple
|
||||
rejects those, and they would be the comparison the positioning note refuses.
|
||||
|
||||
```
|
||||
encrypted,messenger,private,chat,self-hosted,no phone number,secure,files,e2ee,open source,wallet
|
||||
```
|
||||
|
||||
## Support URL
|
||||
|
||||
```
|
||||
https://flamenetmessenger.com/community
|
||||
```
|
||||
|
||||
## Marketing URL
|
||||
|
||||
```
|
||||
https://flamenetmessenger.com
|
||||
```
|
||||
|
||||
## Copyright
|
||||
|
||||
The legal name has no comma; `Extant 2000, LLC` is wrong.
|
||||
|
||||
```
|
||||
2026 Extant 2000 LLC
|
||||
```
|
||||
|
||||
## Routing App Coverage File
|
||||
|
||||
Leave empty. It is only for apps that give point-to-point routing directions,
|
||||
and uploading one would claim a capability this app does not have.
|
||||
|
||||
## Availability
|
||||
|
||||
United States, Mexico, Guatemala, El Salvador, Honduras, Peru, Bolivia. The
|
||||
same list the relay's sign-up geogate enforces, so the listing and the server
|
||||
agree about where an account can be created.
|
||||
|
||||
## Screenshots
|
||||
|
||||
Up to 10, plus up to 3 app previews. Accepted sizes are 1242 x 2688 or
|
||||
2688 x 1242 (6.5 inch), and 1284 x 2778 or 2778 x 1284 (6.7 inch).
|
||||
|
||||
`Scripts/screenshots-ios.sh` renders them. Ask for the size; unset, it renders
|
||||
the 375 x 812 design canvas, which the store does not accept:
|
||||
|
||||
```bash
|
||||
FNMSG_SHOT_SIZE=1284x2778 Scripts/screenshots-ios.sh
|
||||
```
|
||||
|
||||
The size is in pixels and the points are derived from it at 3x, so the layout
|
||||
gets the room a device that size really has rather than a scaled-up picture of
|
||||
a smaller one. `1242x2688` works the same way. The script prints the pixel size
|
||||
of every file it writes, so a wrong one is visible without opening Preview.
|
||||
|
||||
Suggested order, most convincing first, one idea per shot:
|
||||
|
||||
1. A conversation, showing a sent and received message.
|
||||
2. Sign-up, showing that it asks for a username and not a phone number.
|
||||
3. The safety number screen.
|
||||
4. A key-change notice, holding a message until you decide.
|
||||
5. Files, with a classification on a document.
|
||||
6. Wallet, with the phrase screen.
|
||||
7. Stories.
|
||||
8. A call in progress.
|
||||
9. Settings, showing the privacy controls.
|
||||
10. The self-hosting screen, or a second server in the mailbox list.
|
||||
@@ -17,6 +17,17 @@ DEVICE_NAME="fnmsg-shots"
|
||||
RUNTIME="${FNMSG_RUNTIME:-com.apple.CoreSimulator.SimRuntime.iOS-26-5}"
|
||||
TYPE="${FNMSG_DEVICE:-com.apple.CoreSimulator.SimDeviceType.iPhone-17-Pro}"
|
||||
OUT="build/screenshots-ios"
|
||||
# App Store Connect takes 1242x2688, 2688x1242, 1284x2778 or 2778x1284 and
|
||||
# nothing else. Unset, the tests render the 375x812 design canvas, which is
|
||||
# right for looking at a layout and is not a size the store will accept.
|
||||
# FNMSG_SHOT_SIZE=1284x2778 Scripts/screenshots-ios.sh
|
||||
#
|
||||
# Handed over with `simctl spawn launchctl setenv`, and neither of the two
|
||||
# obvious routes works: `FOO=bar` on an xcodebuild line is a BUILD SETTING the
|
||||
# test process never sees, and the TEST_RUNNER_ prefix only reaches a UI test's
|
||||
# runner, not unit tests hosted in the app. Both were tried; both arrived
|
||||
# unset.
|
||||
SHOT_SIZE="${FNMSG_SHOT_SIZE:-}"
|
||||
|
||||
udid=$(xcrun simctl list devices -j | python3 -c "
|
||||
import json,sys
|
||||
@@ -35,7 +46,14 @@ fi
|
||||
echo "==> device $udid"
|
||||
xcrun simctl boot "$udid" >/dev/null 2>&1 || true
|
||||
|
||||
echo "==> rendering"
|
||||
if [ -n "$SHOT_SIZE" ]; then
|
||||
xcrun simctl spawn "$udid" launchctl setenv FNMSG_SHOT_SIZE "$SHOT_SIZE"
|
||||
echo "==> rendering at $SHOT_SIZE"
|
||||
else
|
||||
# Left over from a previous sized run, it would silently apply to this one.
|
||||
xcrun simctl spawn "$udid" launchctl unsetenv FNMSG_SHOT_SIZE 2>/dev/null || true
|
||||
echo "==> rendering"
|
||||
fi
|
||||
log=$(mktemp)
|
||||
xcodebuild test \
|
||||
-project FlamenetMessenger.xcodeproj \
|
||||
@@ -51,4 +69,10 @@ grep '^SHOT ' "$log" | sed 's/^SHOT //' | while read -r f; do
|
||||
[ -f "$f" ] && cp "$f" "$OUT/"
|
||||
done
|
||||
echo "==> wrote:"
|
||||
ls -1 "$OUT"/*.png 2>/dev/null | sed 's/^/ /' || echo " (nothing, see $log)"
|
||||
# The size is the whole point when these are going to the store, so print it
|
||||
# rather than leave somebody to check in Preview.
|
||||
for f in "$OUT"/*.png; do
|
||||
[ -f "$f" ] || { echo " (nothing, see $log)"; break; }
|
||||
printf ' %s %s\n' "$(sips -g pixelWidth -g pixelHeight "$f" 2>/dev/null |
|
||||
awk '/pixelWidth/{w=$2} /pixelHeight/{h=$2} END{printf "%sx%s", w, h}')" "$f"
|
||||
done
|
||||
|
||||
@@ -10,7 +10,27 @@ final class ScreenshotTests: XCTestCase {
|
||||
|
||||
/// The kit's canvas, so a PNG can be put beside the Sketch artboard
|
||||
/// directly rather than mentally rescaled from whatever device ran it.
|
||||
private let canvas = CGSize(width: 375, height: 812)
|
||||
///
|
||||
/// `FNMSG_SHOT_SIZE=<w>x<h>` asks for a pixel size instead, which is how
|
||||
/// the App Store's accepted sizes are produced: it only takes 1242x2688,
|
||||
/// 2688x1242, 1284x2778 or 2778x1284, and the design canvas is none of
|
||||
/// them. Points are the pixels divided by the scale below, so the layout
|
||||
/// gets the room a real device of that size has rather than a scaled-up
|
||||
/// picture of a smaller one.
|
||||
private var canvas: CGSize {
|
||||
guard let raw = ProcessInfo.processInfo.environment["FNMSG_SHOT_SIZE"],
|
||||
let x = raw.firstIndex(of: "x"),
|
||||
let w = Double(raw[raw.startIndex..<x]),
|
||||
let h = Double(raw[raw.index(after: x)...]),
|
||||
w > 0, h > 0
|
||||
else { return CGSize(width: 375, height: 812) }
|
||||
return CGSize(width: w / scale, height: h / scale)
|
||||
}
|
||||
|
||||
/// Fixed rather than the running simulator's, so the same request gives
|
||||
/// the same pixels on any device. Every size the App Store accepts is a
|
||||
/// 3x one.
|
||||
private let scale: CGFloat = 3
|
||||
|
||||
private lazy var outputDirectory: URL = {
|
||||
let base = FileManager.default.temporaryDirectory
|
||||
@@ -239,7 +259,9 @@ final class ScreenshotTests: XCTestCase {
|
||||
// One runloop turn.
|
||||
RunLoop.current.run(until: Date().addingTimeInterval(0.35))
|
||||
|
||||
let renderer = UIGraphicsImageRenderer(size: canvas)
|
||||
let format = UIGraphicsImageRendererFormat()
|
||||
format.scale = scale
|
||||
let renderer = UIGraphicsImageRenderer(size: canvas, format: format)
|
||||
let image = renderer.image { _ in
|
||||
host.view.drawHierarchy(in: host.view.bounds, afterScreenUpdates: true)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user