Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,5 @@ Before and after screenshots, light and dark, on every affected platform.

- [ ] Tokens, SF Symbols and the system font only
- [ ] Both themes checked
- [ ] `scripts/verify-contrast.sh` and `scripts/verify-design.sh` pass
- [ ] `scripts/verify-design.sh` passes
- [ ] Screenshots recaptured if the UI changed (`scripts/capture-screenshots.sh`)
27 changes: 22 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jobs:
build: ${{ steps.filter.outputs.build }}
design: ${{ steps.filter.outputs.design }}
interop: ${{ steps.filter.outputs.interop }}
scripts: ${{ steps.filter.outputs.scripts }}

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -61,11 +62,12 @@ jobs:
}

{
out swift '^(app|CrocKit)/|^\.swift-format$|^\.xcode-version$|^scripts/format-swift\.sh$|^\.github/workflows/(ci|reusable-swift-format)\.yml$'
out swift '^(app|CrocKit)/|^scripts/screenshots/|^\.swift-format$|^\.xcode-version$|^scripts/format-swift\.sh$|^\.github/workflows/(ci|reusable-swift-format)\.yml$'
out go '^crocmobile/|^\.github/workflows/(ci|reusable-go)\.yml$'
out build '^(app|CrocKit|crocmobile)/|^scripts/build-xcframework\.sh$|^\.xcode-version$|^\.github/workflows/ci\.yml$'
out design '^design/|^app/.*\.swift$|^app/CrocApp/Assets\.xcassets/|^web/landing/.*\.(css|html)$|^web/docs/src/css/|^scripts/(lib|verify-contrast|verify-design)\.sh$|^\.github/workflows/ci\.yml$'
out design '^design/|^app/.*\.swift$|^app/CrocApp/Assets\.xcassets/|^web/landing/.*\.(css|html)$|^web/docs/src/css/|^scripts/(lib|verify-design)\.sh$|^\.github/workflows/ci\.yml$'
out interop '^(crocmobile|CrocKit)/|^scripts/(lib|verify-interop)\.sh$|^scripts/croc-v10\.version$|^\.github/workflows/ci\.yml$'
out scripts '^scripts/.*\.sh$|^\.github/workflows/ci\.yml$'
} | tee -a "$GITHUB_OUTPUT"

swift-format:
Expand Down Expand Up @@ -98,10 +100,25 @@ jobs:
with:
persist-credentials: false

- run: scripts/verify-contrast.sh

- run: scripts/verify-design.sh

# The runner image ships shellcheck, so there is nothing to install or pin.
shellcheck:
needs: changes

if: needs.changes.outputs.scripts == 'true'

runs-on: ubuntu-26.04

timeout-minutes: 5

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- run: shellcheck --version && shellcheck -x scripts/*.sh

# The interop harness against a relay on localhost, never the public relays:
# those rate-limit a shared runner address and make the run flaky. The
# public-pool scenario stays a local run (docs/BUILDING.md).
Expand Down Expand Up @@ -264,7 +281,7 @@ jobs:
pass:
name: ci pass

needs: [changes, swift-format, go, contrast, interop, build]
needs: [changes, swift-format, go, contrast, shellcheck, interop, build]

if: ${{ always() }}

Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Native SwiftUI GUI for the croc file-transfer CLI.

## Design

Anything a user sees (app views, the landing page, the docs site, release artwork, store copy, UI strings) follows `design/`. Start at `design/AGENTS.md`. Run `scripts/verify-contrast.sh` and `scripts/verify-design.sh` after touching any of it; CI runs both.
Anything a user sees (app views, the landing page, the docs site, release artwork, store copy, UI strings) follows `design/`. Start at `design/AGENTS.md`. Run `scripts/verify-design.sh` after touching any of it; CI runs it.

## Skills

Expand Down
5 changes: 2 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,10 @@ govulncheck ./...

Anything visual, in the app, the web or elsewhere, follows [`design/AGENTS.md`](design/AGENTS.md). In short, fixed design principles (brandbook), like: design tokens instead of raw hex, px, pt, or durations; SF Symbols only; system font only; a light and a dark value for every surface.

Two scripts enforce it, and CI runs both on any change to `design/`, app Swift or color sets, or web CSS. Run them yourself after touching a value:
One script enforces it, and CI runs it on any change to `design/`, app Swift or color sets, or web CSS. Run it yourself after touching a value:

```bash
scripts/verify-contrast.sh # every contrast ratio in design/colors.md, recomputed from tokens.css
scripts/verify-design.sh # tokens.css = DesignTokens.swift = color sets, and no hex, px or raw system colors in app or web code
scripts/verify-design.sh # tokens.css = DesignTokens.swift = color sets, every contrast ratio in design/colors.md, and no hex, px or raw system colors in app or web code
```

A failure means your change or `design/` is wrong. Fix one of them, never the check.
Expand Down
9 changes: 4 additions & 5 deletions design/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,13 @@ The markdown is canonical. Two hand-written mirrors carry it into code:

Change a value in a markdown file and in both mirrors in the same commit. `design/tokens.css` is the only committed CSS copy: `scripts/assemble-site.sh` copies it for the landing page and `web/docs`'s `tokens` npm script for the docs site. Never edit a copy.

Two scripts enforce this directory, and CI runs both on any change to `design/`, `app/` Swift or color sets, or web CSS:
One script enforces this directory, and CI runs it on any change to `design/`, `app/` Swift or color sets, or web CSS:

```bash
scripts/verify-contrast.sh # every ratio colors.md claims, recomputed from tokens.css, plus the seven rules
scripts/verify-design.sh # mirror parity (tokens.css = DesignTokens.swift = color sets) and the no-literals lint
scripts/verify-design.sh # mirror parity (tokens.css = DesignTokens.swift = color sets), contrast (every ratio colors.md claims plus the seven rules) and the no-literals lint
```

Run them after touching any value. A failure means the change or this directory is wrong; fix one of them, never the check.
Run it after touching any value. A failure means the change or this directory is wrong; fix one of them, never the check.

## Rules

Expand Down Expand Up @@ -76,7 +75,7 @@ Never encode a value in a name (`--green-600`, not `--green-dark`).
2. Add it to the topic file that owns it, with the role it plays.
3. Add it to `tokens.css` in the matching group.
4. If it is shared, add it to `DesignTokens.swift` or a color set and to the parity table in `scripts/verify-design.sh`. If the app deliberately does not mirror it, add it to `swiftui.md` → Not mirrored instead.
5. If it is a color, run `scripts/verify-contrast.sh` and add a row to `colors.md` → Contrast for anything that reads on top of it.
5. If it is a color, run `scripts/verify-design.sh` and add a row to `colors.md` → Contrast for anything that reads on top of it.
6. A component measurement used by one component is a component constant in `components.md`, not a token.

A token is live when the app or a site consumes it, or when it is a step in a scale. **Scales stay whole** (spacing, radius, type, weight, tracking, control height, icon size, icon stroke, glass, Apple's label/fill/background ladders) so nobody reaches for the wrong rung. Any other token with no consumer is dead: delete it from every file in the same commit.
Expand Down
2 changes: 1 addition & 1 deletion design/accessibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ The binding floor for the app, the landing page and the docs site. Nothing in `c

## Contrast

WCAG 2.2 AA: body text 4.5:1, large text and UI boundaries 3:1. **Light is the constrained theme:** check it first. Ratios and the seven rules are in `colors.md` → Contrast and are enforced by `scripts/verify-contrast.sh`. Two decide most work:
WCAG 2.2 AA: body text 4.5:1, large text and UI boundaries 3:1. **Light is the constrained theme:** check it first. Ratios and the seven rules are in `colors.md` → Contrast and are enforced by `scripts/verify-design.sh`. Two decide most work:

- **Accent is a fill, not a label.** Accent text is `--color-accent-text`, or `--color-accent-text-on-tint` on grouped and tinted surfaces. In the app that includes every bordered and plain button label.
- **A status color is a fill, never a foreground.** Status text is `--color-text-primary`.
Expand Down
2 changes: 1 addition & 1 deletion design/colors.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ Status → meaning in CrocApp: **success** = transfer complete, E2E trust badge.

## Contrast (verified, WCAG 2.2)

Every row is recomputed from `tokens.css` by `scripts/verify-contrast.sh`, which also asserts the rules below as invariants, the alias rules (4 and 6, the dark accent collapse) and the two bands of `--shadow-focus-ring` (7). Run it after touching any color. **Light is the constrained theme: check it first.**
Every row is recomputed from `tokens.css` by `scripts/verify-design.sh` (contrast phase), which also asserts the rules below as invariants, the alias rules (4 and 6, the dark accent collapse) and the two bands of `--shadow-focus-ring` (7). Run it after touching any color. **Light is the constrained theme: check it first.**

| Pair | Ratio | Verdict |
|---|---|---|
Expand Down
7 changes: 3 additions & 4 deletions docs/BUILDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ xcodebuild -scheme CrocApp -destination 'platform=macOS' -derivedDataPath /tmp/d
xcodebuild -scheme CrocApp -destination "platform=iOS Simulator,name=$SIM" -derivedDataPath /tmp/dd-sim build
```

`SIM` defaults to `iPhone 17 Pro` (used the same way in `scripts/verify-app-sim.sh` and `scripts/verify-share-sim.sh`). List installed runtimes with `xcrun simctl list devices`.
`SIM` defaults to `iPhone 17 Pro` (used the same way in `scripts/verify-app-sim.sh`). List installed runtimes with `xcrun simctl list devices`.

## 3. What build-xcframework.sh actually does

Expand Down Expand Up @@ -111,11 +111,10 @@ A green build is **not** evidence a transfer works. CI runs only `verify-interop
|---|---|---|---|
| `scripts/verify-interop.sh` | 15 scenarios, crocmobile (via its `croctest` CLI) ↔ croc CLI: file/folder/text both directions, decline, cancel both directions, forced relay, LAN-only, a croc v10 peer in both directions (R8: crocmobile reports failure code `peerVersion`), every public pool relay in both directions (LAN off, asserting which relay each side used), and the receive file policy (12: a file onto an existing one with Keep Both and Replace, answered at the prompt and preset with `-yes`, then a zipped folder received twice with each policy and accept mode), and the transport (13: a 130 MiB send in each of `auto` and `relay`, both directions, asserting byte equality and that the CLI `--debug` log shows a `tailcat-offer` under `auto` and none under `relay`), and stored transfers (14: against a loopback `croctest store-server`, a wrapper upload downloaded by the CLI, a CLI upload downloaded by the wrapper, a revoke after which the download fails, and an existing name with Keep Both, Replace and a declined preview), and the Advanced options that change the wire (15, each against the CLI with the matching flag: curve, hash, base port with transfers and the sender IP, multicast group, SOCKS5 and HTTP proxy, internal DNS; the curve and hash cases assert which side's setting the other adopts, a mismatch included). With `RELAY` set every transfer uses that relay and the pool (11), transport (13), proxy and internal DNS (15) scenarios are skipped; 14 and the rest of 15 need no public relay and still run | croc CLI, croc v10 CLI (`scripts/croc-v10.version`), outbound network (public relay) unless `RELAY` is set | `CROC`, `CROC_V10`, `RELAY` |
| `scripts/verify-app-mac.sh` | macOS app, 12 scenarios: CLI→app receive (plus SwiftData history), app→CLI send, `--local`, custom self-hosted relay, `--no-compress`, `--ask` (both sides confirm), and a CLI send onto an existing file received with `--when-exists keepBoth|replace` (prints `MAC-COLLIDE-OK`), and a 130 MiB transfer in each direction under `--transport auto` and `--transport relay` (prints `MAC-TRANSPORT-OK`); then stored transfers (9, `MAC-STORE-OK`), a send started through the menu-bar view model with a file and with clipboard text (10, `MAC-MENUBAR-OK`; it saves and restores the text on the pasteboard), Open at login following `SMAppService.status` (11, `MAC-LOGIN-OK`; it registers the login item and unregisters it again), and the three Shortcuts actions run through their `perform()` against the CLI (12, `MAC-INTENT-OK`; the receive saves into the real `~/Downloads/CrocApp` and deletes the file); in the app-as-sender scenarios the CLI receiver starts only after the app writes `verify-code-ready.txt` | outbound network (scenario 8 also reaches `tailcat.dev`), croc CLI, a macOS app build | `CROC` |
| `scripts/verify-app-sim.sh` | iOS simulator, CLI→app via the `--auto-receive` launch argument, gated on a byte-identical diff | outbound network, croc CLI, an iOS Simulator | `CROC`, `SIM` |
| `scripts/verify-share-sim.sh` | Share-extension handoff: files staged into the App Group container the way `CrocShare` does, app launched with `--auto-share-send`, received by the croc CLI (the receiver starts only after the app writes `verify-code-ready.txt`, since a receiver that joins the room first fails) | outbound network, croc CLI, an iOS Simulator | `CROC`, `SIM` |
| `scripts/verify-app-sim.sh` | iOS simulator, 2 scenarios, each gated on the app's result file and a byte-identical diff: CLI→app via the `--auto-receive` launch argument (prints `SIM-INTEROP-OK`), then the share-extension handoff: files staged into the App Group container the way `CrocShare` does, app launched with `--auto-share-send`, received by the croc CLI (prints `SHARE-SIM-OK`; the receiver starts only after the app writes `verify-code-ready.txt`, since a receiver that joins the room first fails) | outbound network, croc CLI, an iOS Simulator | `CROC`, `SIM` |
| `crockit-verify` (run via `swift run --package-path CrocKit crockit-verify send\|receive\|twice ...`) | Swift-layer send/receive, cancel fired mid-wire, and two transfers back-to-back in one process (proves fd0/stdout/cwd/mutex restoration composes) | outbound network, croc CLI, macOS only | optional `CROC_RELAY`, `CROC_PASS` to point at a private relay |

`CROC` overrides the CLI path (default `$HOMEBREW_PREFIX/bin/croc`, falling back to `/opt/homebrew/bin/croc`); `SIM` picks the simulator (default `iPhone 17 Pro`). `CROC_V10` is the croc v10 binary for `verify-interop.sh` scenario 10 (default `$HOME/go/bin/croc`; install with `go install github.com/schollz/croc/v10@$(cat scripts/croc-v10.version)`), and the script fails at start if it is not the version in `scripts/croc-v10.version`. `RELAY=host:port` sends every transfer of `verify-interop.sh` through that relay instead of the public pool. CI raises the local relay's admission limits as a margin, because every connection comes from 127.0.0.1 and croc v11's relay admits 30 joins a minute per address by default (a run on default limits passed on 2026-10-13):
`CROC` overrides the CLI path (default `$HOMEBREW_PREFIX/bin/croc`, falling back to `/opt/homebrew/bin/croc`); `SIM` picks the simulator (default `iPhone 17 Pro`). `CROC_V10` is the croc v10 binary for `verify-interop.sh` scenario 10 (default `$(go env GOPATH)/bin/croc`; install with `go install github.com/schollz/croc/v10@$(cat scripts/croc-v10.version)`), and the script fails at start if it is not the version in `scripts/croc-v10.version`. `RELAY=host:port` sends every transfer of `verify-interop.sh` through that relay instead of the public pool. CI raises the local relay's admission limits as a margin, because every connection comes from 127.0.0.1 and croc v11's relay admits 30 joins a minute per address by default (a run on default limits passed on 2026-10-13):

```bash
croc relay --host 127.0.0.1 --ports 9309,9310,9311,9312,9313 --source-join-limit 1000 --room-join-limit 1000
Expand Down
2 changes: 1 addition & 1 deletion docs/knowledge/app-ui-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ Settings overrides apply in any harness run (`isHarnessRun` is true for `--auto-

The screenshot flags (`--screen`, `--stage`, `--seed-history`, `--hold`) are documented in `docs/knowledge/screenshots.md`.

The harness writes `verify-result.txt` (`ok success=<bool>` or `error <msg>`) to Documents. It also writes `verify-code-ready.txt` (contents `ready`, written once, when the phase first reaches `.waiting`, i.e. a send has its code; `verify-share-sim.sh` and `verify-app-mac.sh` wait for it) and `verify-history.txt` (`records=N`, written when the transfer is done or failed; `verify-app-mac.sh` reads it). The result contract is shared by `scripts/verify-app-sim.sh`, `verify-app-mac.sh`, and `verify-share-sim.sh`. AutoVerify's Documents path is hardcoded, decoupled from `OutputFolderStore.defaultFolder`.
The harness writes `verify-result.txt` (`ok success=<bool>` or `error <msg>`) to Documents. It also writes `verify-code-ready.txt` (contents `ready`, written once, when the phase first reaches `.waiting`, i.e. a send has its code; `verify-app-sim.sh` and `verify-app-mac.sh` wait for it) and `verify-history.txt` (`records=N`, written when the transfer is done or failed; `verify-app-mac.sh` reads it). The result contract is shared by `scripts/verify-app-sim.sh` and `verify-app-mac.sh`. AutoVerify's Documents path is hardcoded, decoupled from `OutputFolderStore.defaultFolder`.

Launch gotchas, both macOS:

Expand Down
7 changes: 3 additions & 4 deletions docs/knowledge/croc-upgrade-playbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,9 @@ How to bump the pinned croc version (go.mod). Run this as one session whenever u
4. **Build**: `go build ./... && go vet ./...`, then `./scripts/build-xcframework.sh`, then `cd CrocKit && swift build`, then app build both platforms.
5. **Verify**, in this order — each one covers ground the previous does not:
- `crockit-verify twice` — Swift layer, and the two-transfer case proves fd0/stdout/cwd/mutex restoration still composes
- `./scripts/verify-interop.sh` — all 10 crocmobile↔CLI scenarios
- `./scripts/verify-app-mac.sh` — both directions plus local-only, custom relay, no-compress, and both-sides-confirm; the widest coverage of the option surface
- `./scripts/verify-app-sim.sh` — iOS simulator receive
- `./scripts/verify-share-sim.sh` — share-extension handoff
- `./scripts/verify-interop.sh` — all 15 crocmobile↔CLI scenarios
- `./scripts/verify-app-mac.sh` — the 12 macOS app scenarios; the widest coverage of the option surface
- `./scripts/verify-app-sim.sh` — iOS simulator receive and share-extension handoff

Any failure → root-cause it against the upstream delta before touching wrapper semantics.
6. **Docs self-heal**: update `crocmobile-bridge.md`, `what-is-croc.md` (version header + changed facts), and this file's pinned-version line.
Expand Down
Loading
Loading