~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
126 lines · 5.8 KB Raw History

Knotless for iOS

A native app for reading knotless: inbox, pulls and their diffs, repositories and profiles. SwiftUI, iOS 18 and up, no dependencies. It reads knotless.dev's XRPC queries and writes through its procedures.

The project is generated, so edit project.yml, not an .xcodeproj:

brew install xcodegen
ios/run.sh                        # builds and runs in the simulator
KNOTLESS_ACTOR=<handle> ios/run.sh inbox

Signing in

The app signs in through a browser, ending with the session a page's cookie would hold, kept in the keychain and sent as that cookie (docs/design/serve.md, Logging in).

A debug build can also ask as an account without signing in: with goat logged in and KNOTLESS_ACTOR set, run.sh mints an hour's service token for each method into build/.tokens.json and hands the app its path. That code isn't in a release build.

TestFlight

Raise CURRENT_PROJECT_VERSION in project.yml for each upload, then:

cd ios && xcodegen generate
xcodebuild archive -project Knotless.xcodeproj -scheme Knotless -configuration Release \
  -destination 'generic/platform=iOS' -archivePath build/Knotless.xcarchive -allowProvisioningUpdates
xcodebuild -exportArchive -archivePath build/Knotless.xcarchive -exportPath build/export \
  -exportOptionsPlist ExportOptions.plist -allowProvisioningUpdates

build/export/Knotless.ipa goes up with Xcode's Organizer or Transporter. Signing in needs webcredentials on Apple's CDN (https://app-site-association.cdn-apple.com/a/v1/knotless.dev), as a release build can't use developer mode: it can take a day or two after the file changes.

Making everyone update

An appview's dev.knotless.describeServer can name the oldest build it lets run. A build older shows only a screen with a way to update, in place of the whole app, with the reason if there is one:

# in .knotless/workflows/deploy.yml, landed as any pull
KNOTLESS_IOS_MINIMUM_BUILD: "5"
KNOTLESS_IOS_MINIMUM_REASON: "This version can lose what you write."

The number is CURRENT_PROJECT_VERSION. Raise it only once that build, or a newer one, is live for the people it stops, on the App Store and on TestFlight: the screen is all they can see, and there is nothing else to send them to. Taking the setting out lets them back in, the next time the app comes to the front.

The app asks when it comes to the front and keeps the answer, so a build stopped stays stopped offline, and one that has never been told runs. A server that doesn't answer changes nothing. Its button opens TestFlight in a TestFlight build and the App Store in any other. Only builds from 5 up do this: older ones ignore the setting.

Sizes and colours

Every colour, space, corner radius, icon and avatar size is a token in Knotless/Support/Theme.swift or Knotless/Support/Palette.swift: take one by name, and add one there when none fits. The spaces follow the scale in web/src/tokens.css. Never take an iOS colour, as Color(.systemBackground) or Color.secondary, directly: the tokens stand for those where there's no theme, and for a theme's colours where there is.

Themes

The app is drawn in the themes the pages are. Settings, Appearance chooses: Same as knotless.dev (the default) follows the account's appearancePref, day and night pair included; Automatic, Light and Dark are iOS's own colours; then the themes the pages offer by name, and the account's own, those it published and those it added. A palette leaves out what the pages fill in, and Palette(record:) fills it in as tokens.css does.

A theme's colours come in as each is drawn: a token is a dynamic UIColor that reads the theme in use (Themes), and a trait carried from the root (ThemeTrait) draws them again when it changes, without rebuilding a screen. As on the pages, only the main button takes a colour, the theme's primary under its onPrimary; a state's pill is its colour under onSolid; and a button beside the main one is quietButton, on the theme's surface.

Looking at a screen

just ios-look <screen> draws a screen in the simulator, light and dark, from all the way down, as pictures in target/look, with no server and no account. Look at what you changed before you push it:

just ios-look pull-open
just ios-look pull-open --set getMergeStatus.checks=failed --top --light
just ios-look pull-open --tap "Add to merge queue" --light
just ios-look pull-open --theme dracula --dark
just ios-look appearance --tap Ember

Its answers are what harness/shots/fixtures.json recorded for the web pages, with what that lacks in look-answers.json. A write does nothing, so a --tap that writes shows what's under way, not what comes after. ios/look.mjs lists the screens and options. Each run starts with Appearance as a new install has it, whatever the simulator was left in. It needs Xcode, so no check runs it.

Tests

KnotlessUITests taps through the app against live knotless.dev. It needs the network and those tokens, and CI doesn't build iOS, so it is run by hand:

TEST_RUNNER_KNOTLESS_TOKENS=$PWD/ios/build/.tokens.json \
xcodebuild test -project ios/Knotless.xcodeproj -scheme Knotless \
  -destination 'platform=iOS Simulator,name=iPhone 17 Pro' \
  -derivedDataPath ios/build -allowProvisioningUpdates

CI can't build the app, as Xcode needs macOS. On a pull, its ios check compiles Knotless/API/Models.swift on Linux and reads the recorded XRPC answers with it: ios/check-models.sh runs the same here.

Diagrams

A Mermaid block in Markdown is drawn by Pele, as the pages draw it (Knotless/Support/Diagram.swift). Knotless/Vendor/pele.js is the version web/package-lock.json pins: after raising it there, just ios-pele copies it here, and the ios check fails while the two differ.

Icons

Assets.xcassets holds Octicons (MIT), the same set the web pages draw.