iOS app for Paperless-ngx
Swift Paperless native iOS app for the Paperless-ngx software. Paperless-ngx is a self-hosted document management system that helps you organize your documents digitally.
This application requires a self-hosted instance to function!
To get the latest development version of the app, grab it on TestFlight!
The Xcode project is generated with XcodeGen
from project.yml; build settings live in Config/ as
.xcconfig files. swift-paperless.xcodeproj is not committed — generate it before
opening or building:
brew install xcodegen # once
just generate # or: xcodegen generate
open swift-paperless.xcodeprojjust build / just test-xcode regenerate the project automatically. Edit
project.yml (targets, sources, dependencies) and Config/*.xcconfig (build
settings) rather than the generated project, then re-run just generate.
If you have any questions or need support create an issue on GitHub or send me a message.
Automation lives in the scripts/ package (swpngx) and
fastlane/. Run swpngx via uv run --project scripts swpngx …
from the repo root (or install the package so swpngx is on your PATH).
Device ids, simulator names, bezel PNGs, and framing geometry are defined once in
screenshot_devices.toml. screenshots.toml
references those ids for capture; frames.toml references the same file
for framing. iPhone screenshots use Pro Max only (App Store Connect scales for smaller
phones). Check alignment with:
uv run --project scripts swpngx devices checkA beta is cut entirely on CI — no commit, no tag, no local build:
just beta # from origin/main
just beta --ref develop/v1.12 # from another branch
just beta --dry-run # CI builds and exports, uploads nothingThis previews the notes the build will publish, then dispatches
.github/workflows/beta.yml (which you can also
start straight from the Actions tab). CI asks App Store Connect for the build
number, writes it into Config/Shared/Version.xcconfig for that build only,
archives, signs, uploads, and — after the upload succeeds — creates the
builds/<version>/<build> tag and its GitHub prerelease at the built commit. The
tag is the record of what shipped, not the trigger, so it can never name a build
that failed to upload.
Notes come from current_changelog.txt, which
accumulates the user-facing bullets for the current marketing version. Nothing
clears it at build time:
- a build's notes are the bullets added since the previous build tag; they
become the prerelease body, which is what the in-app What's New screen shows
(
just beta-notespreviews them) - TestFlight "What to Test" is one
1.11.0 (209)section per build of the current version, newest first — the same shape aschangelog.txt, scoped to the version in flight. App Store Connect caps that field at 4000 characters and a version's notes routinely run past it, so whole builds are dropped off the oldest end until it fits and a link to the releases takes their place. A build is either there in full or not at all; only a single build whose own notes exceed the budget falls back to trimming by line. No more clearing the changelog by hand to fit - when the version ships, empty the file in the same commit that bumps
MARKETING_VERSIONwithjust set-version
To fix notes after the fact, edit the release body on GitHub (the copy users
read) and, for TestFlight, run just set-test-notes <build>.
changelog.txt is an offline copy of the per-build notes,
regenerated from the releases with just changelog-archive.
1. Capture raw simulator PNGs (config: screenshots.toml):
uv run --project scripts swpngx capture setup
# Or pin the screenshot backend:
uv run --project scripts swpngx capture setup --pngx-tag 2.19.4
# Or add random tags for UI stress testing:
uv run --project scripts swpngx capture setup --random-tags 1000
# Or add multiple random metadata types:
uv run --project scripts swpngx capture setup --random-tags 1000 --random-correspondents 500 --random-document-types 250
uv run --project scripts swpngx capture captureWrites files like fastlane/screenshots/en-US/iPhone_17_Pro_Max-01_documents.png
(the prefix is the device id from screenshot_devices.toml). Tear down the backend
with uv run --project scripts swpngx capture teardown.
2. Install device bezels from Apple Design Resources
(Product Bezels). Each [[device]] in screenshot_devices.toml
names a bezel_pack from bezel_packs.toml and the PNG filename
to install under fastlane/screenshots/frames/:
# Use DMGs you already downloaded (e.g. from ~/Downloads):
uv run --project scripts swpngx frames download --dmg-dir ~/Downloads
# Or let swpngx download from Apple into ~/Library/Caches/swpngx/bezels:
uv run --project scripts swpngx frames download
# Only the iPhone 17 pack, with one DMG path:
uv run --project scripts swpngx frames download --pack iphone_17 \
--dmg-path ~/Downloads/Bezel-iPhone-17.dmgRequires macOS (hdiutil). Apple’s DMG license prompt is accepted automatically
(Y); you must comply with the Apple Design Resources license.
3. Frame screenshots for the App Store (config: frames.toml):
uv run --project scripts swpngx frameReads fastlane/screenshots/<locale>/, composites device bezels and localized
titles (from fastlane/screenshots/Screenshots.xcstrings),
and writes fastlane/screenshots/framed/<locale>/*-framed.png. Device names in
filenames must match device id in screenshot_devices.toml.
The framing font (Open Sans) is not in git (fastlane/.gitignore ignores *.ttf).
swpngx frame downloads it automatically from Google Fonts
via fonts.toml, or you can prefetch:
uv run --project scripts swpngx fonts downloadPreview locally:
uv run --project scripts swpngx preview4. Upload metadata and framed screenshots with deliver
(config: fastlane/Deliverfile):
just deliver-preview # dry run
just deliver # metadata + screenshots
just deliver-metadata # metadata only (What's New, description, …)just deliver uploads both metadata and screenshots in one fastlane deliver
run (not a separate command). It uses MARKETING_VERSION from
Config/Shared/Version.xcconfig, replaces all screenshots, and reads metadata
from fastlane/metadata/.
What's New is edited only in fastlane/metadata/default/release_notes.txt.
The Deliverfile reads that once and applies it to every metadata locale (including
en-US, which App Store Connect does not fill from default/ on its own).
Before uploading:
- Edit
fastlane/metadata/default/release_notes.txtfor the new version.skip_binary_uploadis enabled in the Deliverfile, so this uploads metadata and screenshots only (no IPA). TestFlight builds go throughjust betaseparately.
Authentication uses the same App Store Connect API key as TestFlight
(APP_STORE_CONNECT_API_KEY_ID, APP_STORE_CONNECT_ISSUER_ID, and
APP_STORE_CONNECT_KEY_FILEPATH or APP_STORE_CONNECT_KEY_CONTENT), plus
APPLE_ID, ITC_TEAM_ID, and TEAM_ID from fastlane/Appfile.
PNG screenshots under fastlane/screenshots/ are gitignored; generate them locally
before upload.
There is also bundle exec fastlane screenshots (snapshot + fastlane/Snapfile),
a UI-test-based path that is separate from the swpngx workflow above.
Concatenate framed screenshots (adjust the glob for your device name):
montage fastlane/screenshots/framed/en-US/iPhone_17_Pro_Max-0*-framed.png \
-tile 4x1 -geometry +20+0 panorama.pngIf montage fails with unable to read font `', the ImageMagick install has no
configured fonts; pass one explicitly, e.g.
-font /System/Library/Fonts/Supplemental/Arial.ttf.
docs/panorama.png is a symlink to the top-level panorama.png, so regenerating
it updates both the README and the docs site.
