diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md
deleted file mode 100644
index c082e2d..0000000
--- a/.claude/CLAUDE.md
+++ /dev/null
@@ -1,87 +0,0 @@
-# .claude/
-
-Claude Code harness config for CrocApp. Claude Code is the only agent harness this project supports — do not add config for other agents.
-
-## Layout
-
-| Path | Committed | Purpose |
-| --- | --- | --- |
-| `settings.json` | yes | Shared baseline every contributor gets |
-| `settings.local.json` | **no**, gitignored | Per-user overrides |
-| `skills/` | yes | The `sdlc-*` pipeline skills, nothing else |
-| `agents/` | yes | Project subagents (`verifier`, used by `/sdlc-build`) |
-
-## Skills
-
-`sdlc-*` follow Anthropic's AI-native SDLC playbook (https://claude.com/blog/the-ai-native-sdlc-playbook) and stay generic: project specifics live in `CLAUDE.md` and the docs it points to, which every stage reads. One skill per stage, run in order: `/sdlc-intent`, `/sdlc-spec`, `/sdlc-plan`, `/sdlc-build`, `/sdlc-review`, `/sdlc-ship`; `/sdlc-maintain` turns a failure back into a new intent. Each stage writes one artifact to `sdlc/-/` (gitignored), and the next stage reads it; a stage only starts from an artifact the user accepted. Edit these skills in place; no tool manages them.
-
-No other skill is committed here. Skills the pipeline relies on are installed per user with the `skills` CLI (https://skills.sh):
-
-```bash
-npx skills add mattpocock/skills -s grilling -g -a claude-code
-```
-
-`grilling` is required: every stage puts its questions to the user in grilling rounds; a plain yes/no confirmation, such as approving a commit, stays a plain question. A round goes out only after every subagent, lookup, and piece of research it could depend on has finished, so a printed question is never revised or withdrawn; this overrides `grilling`'s advice to ask non-dependent questions while research runs. If a later finding does reopen a settled answer, raise it as a new question in the next round. If `grilling` is not among the available skills, stop, give the user the command above, and ask them to relaunch the stage. When a change touches a domain (UI, accessibility, security, App Store review, ...) that no installed skill covers, name one for the user to install (`npx skills find `) and carry on without it.
-
-## SDLC sessions
-
-Every stage runs in its own new session, launched with the model and effort below. A fresh session reads the accepted artifact, not the conversation that produced it.
-
-| Stage | Model | Effort | Why |
-| :--- | :--- | :--- | :--- |
-| `/sdlc-intent` | `opus` | `high` | Conversation and judgment over a small context |
-| `/sdlc-spec` | `opus` | `xhigh` | Design mistakes compound through every later stage |
-| `/sdlc-plan` | `opus` | `xhigh` | Build follows the plan literally |
-| `/sdlc-build`, one slice | `sonnet` | `high` | Carries out an accepted plan; fast and cheap over a large context |
-| `/sdlc-build`, slice marked `Risk: high` | `opus` | `high` | Mistakes there are silent or costly |
-| `/sdlc-build sdlc/ fix review` | `sonnet` | `high` | Findings are already pinned to file and line |
-| `/sdlc-review` | `opus` | `high` | The gate needs the strongest fresh eyes |
-| `/sdlc-ship` | `sonnet` | `medium` | Commit, PR, and CI are mechanical |
-| `/sdlc-maintain` | `opus` | `high` | Root-cause diagnosis |
-
-Inside a stage, subagents that write code run on the stage's model or `sonnet`; only pure lookups drop to `haiku`.
-
-A build or spec session that outgrows one session (the conversation was compacted, or more work remains than fits) stops at a clean boundary, records its progress (the Build log, or a draft `spec.md`), and hands off to the same stage in a new session.
-
-When a next stage exists, the final message ends with this handoff, after the session-end report `CLAUDE.md` asks for:
-
-```text
-Next: /sdlc- in a new session (, : ). Quit this session, then run:
- claude --model --effort --name "" "/sdlc- sdlc/"
-If the stage does not start, run `claude --model --effort --name ""` and paste: /sdlc- sdlc/
-```
-
-For a review fix the stage argument is `sdlc/ fix review`. For `/sdlc-plan`, add `--permission-mode plan` to both commands. Set effort with the flag, never `/effort`: the flag lasts one session, while `/effort` saves a new default that carries into later sessions.
-
-`--name` labels the session in the `/resume` picker and the terminal title, so a later session can find the one that produced an artifact: `claude --resume ` opens the picker with that search term. The name is `/`, where `` is the directory name under `sdlc/`, and `` drops the `sdlc-` prefix and carries the build slice or the fix:
-
-| Next session | Name |
-| :--- | :--- |
-| `/sdlc-spec`, `/sdlc-plan`, `/sdlc-review`, `/sdlc-ship` | `/spec`, `/plan`, `/review`, `/ship` |
-| `/sdlc-build`, one slice | `/build-`, e.g. `/build-B1` |
-| `/sdlc-build sdlc/ fix review` | `/build-fix-review` |
-| `/sdlc-intent`, `/sdlc-maintain` (no change directory yet) | `-intent`, `-maintain` |
-
-When an earlier session of the change already used the name (a stage handing off to itself, a second review after a fix), append `-2`, `-3`, and so on.
-
-## Autonomous build phase
-
-Owner decision, 2026-10-13, for the build phase only: Claude commits, pushes, opens PRs and merges them (`/sdlc-ship` step 5) without asking, on any branch except force-push and direct pushes to `main`. Questions go unasked: take the recommended option, record it in the plan's Build log, and carry on. This overrides the "ask" clauses in the `sdlc-*` skills and the global "ask first" git rule. The owner reviews the whole project at the end and runs each new session with the commands the handoff prints. Still the owner's: device-gated proofs, secrets and signing steps (`plan.md` Owner steps), App Store submission, tags and releases. Remove this section when the build phase ends.
-
-## Settings
-
-`settings.json` is the shared floor, `settings.local.json` overrides it per person. Precedence: local > project > user.
-
-Adding an MCP server takes three edits:
-
-1. Server definition in `.mcp.json` at repo root.
-2. Server name in `enabledMcpjsonServers`.
-3. `mcp__` in `permissions.allow`, or it prompts on every call. Plugin-provided servers use `mcp__plugin__`.
-
-Plugins from non-Anthropic marketplaces need that marketplace declared in `extraKnownMarketplaces`, otherwise `enabledPlugins` resolves to nothing on a fresh clone.
-
-Never commit `settings.local.json`. To change what every contributor gets, edit `settings.json`.
-
-## Setup
-
-Install `grilling` (see Skills). Personal overrides go in `.claude/settings.local.json`; `/status` lists which settings files actually loaded.
diff --git a/.claude/agents/verifier.md b/.claude/agents/verifier.md
deleted file mode 100644
index 2b61e70..0000000
--- a/.claude/agents/verifier.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: verifier
-description: Checks a change works before the session reports done. Give it the sdlc/ directory. Runs the plan's Proof checks and exercises the changed behavior; reports only, fixes nothing.
-tools: Bash, Read, Grep, Glob
----
-Read `plan.md` in the change directory you were given, and `CLAUDE.md`. Run every check in the plan's Proof section, plus any harness `CLAUDE.md` requires for the files the change touches. Exercise the changed behavior and the two nearest neighboring flows.
-
-Report what you ran, what you saw, and any behavior that does not match `plan.md`. Quote the decisive output lines. Say "not verified" for anything you could not run, and why. Do not fix anything; report only.
diff --git a/.claude/settings.json b/.claude/settings.json
index 03ff70f..be8182d 100644
--- a/.claude/settings.json
+++ b/.claude/settings.json
@@ -7,7 +7,8 @@
"permissions": {
"allow": [
"mcp__xcode",
- "mcp__gopls"
+ "mcp__gopls",
+ "mcp__xcode__DocumentationSearch"
]
}
}
diff --git a/.claude/skills/sdlc-build/SKILL.md b/.claude/skills/sdlc-build/SKILL.md
deleted file mode 100644
index 50aed79..0000000
--- a/.claude/skills/sdlc-build/SKILL.md
+++ /dev/null
@@ -1,35 +0,0 @@
----
-name: sdlc-build
-description: Stages 3-4 (Build, Test) of the AI-native SDLC pipeline. Implements one build slice of an accepted sdlc//plan.md per session, or the Important findings of its review.md, with a feedback loop: failing test first for bugs, the project's checks after every step, plan.md kept in sync, and a fresh-context verifier before the last slice reports done. Use when a change under sdlc/ has an accepted plan.md and the user says implement, build, or go, asks to continue that change, or asks to fix its review findings.
-argument-hint: "[sdlc/ directory] [fix review]"
----
-
-# Build with a feedback loop (Stages 3-4: Build, Test)
-
-Every session checks its own work before a human sees it. A late signal makes the user the bottleneck, so what reaches them has already passed the checks, with the output attached.
-
-## Find the change
-
-Use the directory given in the arguments. Otherwise pick the change under `sdlc/` whose `plan.md` is accepted and has no `PR:` line yet; ask if none or several match. `plan.md` must say `Status: accepted`. Without an accepted plan, stop and point to `/sdlc-plan`: nothing is implemented without one.
-
-## Pick this session's work
-
-- **Fix review**: when the arguments say `fix review`, or `review.md` lists Important findings and no Build log entry records fixing that review (its `Reviewed:` line), this session fixes every Important finding in it, one step per finding ID (`I1`...).
-- **Next slice**: otherwise, the first unchecked slice under Build slices.
-- **Nothing left**: every slice is checked and no review is waiting on fixes. Say so and hand off: to `/sdlc-ship` if the latest `review.md` has no Important finding and nothing changed after it, otherwise to `/sdlc-review`.
-
-Work on the branch `plan.md` names for this slice's PR, or else a branch named after the change slug. Create it from the up-to-date default branch when it does not exist yet.
-
-## Steps
-
-1. Read `plan.md` (including its Build log), `spec.md` if there is one, and `CLAUDE.md`. The plan's Proof section is the definition of done. If a target in this session's work cannot be checked without asking the user, sharpen it with them in a `grilling` round before writing code.
-2. For a bug fix, write the failing test first. Reproduce the bug as a test, run it, and confirm it fails for the expected reason. Then make it pass without editing that test. A test that existed before the fix and was not rewritten is the proof the bug is gone.
-3. Implement this session's steps in the plan's order. After each step, run the narrowest check that covers it and fix the code until it passes, then tick the step in `plan.md`. Fix the code, not the check: never skip, delete, or loosen a failing test to get green.
-4. For UI work, close the loop visually: render or screenshot, compare against the mock or reference named in the spec, adjust. Two or three rounds is normal.
-5. Keep `plan.md` true. When the implementation departs from the plan, update `plan.md` at once and say what changed and why. Review checks the diff against the plan, so a stale plan turns correct code into findings.
-6. Before ending the session, run the checks covering everything it touched, plus any `CLAUDE.md` requires for those areas. After the last slice or a review fix, run every check in the Proof instead, then dispatch the `verifier` subagent with the change directory. It runs in a fresh context and cannot edit, so its verdict is not colored by the assumptions that produced the code.
-7. Tick the slice and append a Build log entry to `plan.md`: date, the slice, or "review fix" with the review's `Reviewed:` line and the finding IDs fixed, steps done, checks run with their result, deviations, and anything not verified. If the session must stop early, stop at a step boundary and log where the next session resumes.
-8. Commit this session's work on the change branch, following the repo's commit convention, without asking (autonomous build phase, `.claude/CLAUDE.md`). Push is `/sdlc-ship`'s job.
-9. Report in the session-end format `CLAUDE.md` asks for, covering each step, the commands run with their output, and anything not verified.
-
-Next stage: the next slice, or `/sdlc-review` after the last slice or a review fix. End with the handoff from `.claude/CLAUDE.md` (SDLC sessions), using the next slice's risk to pick its model.
diff --git a/.claude/skills/sdlc-intent/SKILL.md b/.claude/skills/sdlc-intent/SKILL.md
deleted file mode 100644
index 7ca7718..0000000
--- a/.claude/skills/sdlc-intent/SKILL.md
+++ /dev/null
@@ -1,43 +0,0 @@
----
-name: sdlc-intent
-description: Stage 1 (Plan) of the AI-native SDLC pipeline. Turns an idea, feature request, or GitHub issue into a reviewed intent.md proto-spec under sdlc/. Use when the user wants to start a change through the SDLC pipeline, asks to capture or write up an idea or issue as intent, or mentions intent.md or sdlc/.
-argument-hint: "[idea in your own words | issue number or URL]"
----
-
-# Capture intent (Stage 1: Plan)
-
-Intent is captured once, in the originator's own words, as a file the next stage can act on. It records what is wanted, why, and under which constraints. It does not record how: design is the job of `/sdlc-spec`, and a solution written in now narrows the spec before anyone has read the code.
-
-The user is the originator and the product owner. Claude writes the file; the user corrects and accepts it. Trivial changes (a typo, a one-line fix) skip the pipeline.
-
-## Steps
-
-1. Take the input. For an issue, read it with `gh issue view --comments`. For an idea, let the user describe it in their own terms: what they cannot do today, who is affected, what better looks like, what is out of scope. No formal language needed.
-2. Brainstorm until the idea is concrete. Ask the questions an analyst would ask (scope, users, constraints, what success looks like) in `grilling` rounds, each with your recommended answer. Read `CLAUDE.md` and the docs it points to only enough to ask sharp questions, not to design.
-3. Write `sdlc/-/intent.md` from the template below: today's date, a 2-5 word kebab-case slug. Keep it short; the playbook's own example is twelve lines.
-4. Show the file and apply the user's corrections. Anything Claude misunderstood is fixed here, before it costs a spec.
-5. Gate: when the user accepts it, set `Status: accepted`. If they drop it, set `Status: rejected` and add one line saying why. A draft never moves to the next stage.
-
-Next stage: `/sdlc-spec`. A small, bounded fix can go straight to `/sdlc-plan`. End with the handoff from `.claude/CLAUDE.md` (SDLC sessions).
-
-## Template
-
-```markdown
-# Intent:
-Author: (). Status: draft.
-
-## Problem
-
-
-## Proposed outcome
-
-
-## Affected users and systems
-
-
-## Constraints
-
-
-## Open questions
-
-```
diff --git a/.claude/skills/sdlc-maintain/SKILL.md b/.claude/skills/sdlc-maintain/SKILL.md
deleted file mode 100644
index 36bf517..0000000
--- a/.claude/skills/sdlc-maintain/SKILL.md
+++ /dev/null
@@ -1,47 +0,0 @@
----
-name: sdlc-maintain
-description: Stage 6 (Maintain) of the AI-native SDLC pipeline, which closes the loop. Diagnoses a failure read-only (failed CI run, crash or bug report, security finding, regression) and writes it as a new intent.md under sdlc/ so it re-enters the pipeline. Use when the user wants an incident, failing CI run, crash, bug report, or scan finding triaged or captured into the SDLC pipeline, rather than fixed on the spot.
-argument-hint: "[CI run URL | issue | log path | symptom]"
----
-
-# Close the loop (Stage 6: Maintain)
-
-What breaks re-enters the pipeline as `intent.md`, so the user triages and reviews the work instead of starting it. Diagnosis is read-only. Any action goes through the gated routes (plan, review, PR), never a direct fix on the default branch or a release.
-
-## Steps
-
-1. Gather evidence: failed run logs (`gh run view --log-failed`), the issue (`gh issue view --comments`), crash logs, repro steps. Reproduce it if that is cheap and safe.
-2. Check the known-issues list `CLAUDE.md` names, if any. If the failure is already listed, say so and stop unless the user wants it fixed now.
-3. Diagnose without editing code. State the root-cause hypothesis and label it verified, inferred, or speculative, with the evidence for each.
-4. Write `sdlc/-/intent.md` from the template below, the Stage 1 format plus an Evidence section. The proposed outcome includes the regression guard: the test or check that would have caught this, so the same class of failure cannot return silently.
-5. Triage with the user in a `grilling` round:
- - **Fix now**: set `Status: accepted`. Small and bounded goes to `/sdlc-plan`, whose bug-fix proof starts with a failing test. Anything wider goes to `/sdlc-spec`.
- - **Schedule**: add it to that known-issues list following its rules, and set `Status: deferred`. Nothing picks up a deferred intent until the user accepts it.
- - **Dismiss**: set `Status: rejected` with the reason, so the same finding is recognized next time.
-
-For a fix now, end with the handoff from `.claude/CLAUDE.md` (SDLC sessions) to `/sdlc-plan` or `/sdlc-spec`.
-
-## Template
-
-```markdown
-# Intent:
-Author: (incident: ). Status: draft.
-
-## Problem
-
-
-## Evidence
-
-
-## Proposed outcome
-
-
-## Affected users and systems
-
-
-## Constraints
-
-
-## Open questions
-
-```
diff --git a/.claude/skills/sdlc-plan/SKILL.md b/.claude/skills/sdlc-plan/SKILL.md
deleted file mode 100644
index 7d0efff..0000000
--- a/.claude/skills/sdlc-plan/SKILL.md
+++ /dev/null
@@ -1,54 +0,0 @@
----
-name: sdlc-plan
-description: Stage 3 (Build) entry of the AI-native SDLC pipeline. Works in plan mode from an accepted spec.md (or an accepted intent.md for a small fix) to an approved plan.md naming the files that change, the order of work split into one-session build slices, the risks, and the proof. Use before implementing any change tracked under sdlc/, or when the user asks to plan an accepted spec. Nothing is implemented without an accepted plan.
-argument-hint: "[sdlc/ directory]"
----
-
-# Plan mode as the starting point (Stage 3: Build)
-
-Design review happens before code exists, while changing course is still a matter of editing a document. Plan mode enforces that: no file changes until the user accepts the plan.
-
-## Find the change
-
-Use the directory given in the arguments. Otherwise pick the change under `sdlc/` that has an accepted `spec.md` and no `plan.md` yet; ask if none or several match. `spec.md` must say `Status: accepted`. A small, bounded fix may plan straight from an accepted `intent.md`; the plan then numbers its own requirements in a Requirements section, so later stages still have `R#` to cite.
-
-## Steps
-
-1. Enter plan mode if the session is not already in it.
-2. Read `intent.md`, `spec.md` if there is one, `CLAUDE.md`, and the code the change touches. For a wide change, delegate the reading to read-only subagents and keep their conclusions in this session.
-3. Interview the user in `grilling` rounds on the decisions the spec leaves open. Do not ask what the code can answer.
-4. Draft the plan from the template below. Cite spec requirements (`R1`...) in the order of work and the proof, and spec concerns in the risks, so the plan can be checked against the spec line by line. The proof names the project's real checks (from `CLAUDE.md` and the docs it points to) with a pass condition Claude can confirm alone, such as "all tests in X pass" or "the screenshot matches the mock". For a bug fix, the proof starts with a test that fails before the fix.
-5. Split the order of work into build slices. Each slice is one session of `/sdlc-build`: one coherent area, small enough to finish without the conversation being compacted, and checkable on its own before the next starts. Mark a slice `Risk: high` when a mistake there would be silent or costly (core logic, security, data, concurrency); it runs on the stronger model.
-6. Interrogate the draft before presenting it: what could this break, which step is riskiest, which options were rejected and why. Put the answers in Risks and state them when presenting, so the user can push on them.
-7. Iterate until an engineer who never saw this conversation could implement the change from the plan alone.
-8. Present the plan for approval by exiting plan mode. Once approved, write it to `plan.md` next to the spec with `Status: accepted`.
-
-Next stage: `/sdlc-build` for slice B1, in a new session. End with the handoff from `.claude/CLAUDE.md` (SDLC sessions), using B1's model.
-
-## Template
-
-```markdown
-# Plan: (from intent.md and spec.md)
-Status: draft.
-
-## Requirements
-
-
-## Files that change
- (new), ,
-
-## Order of work
-- [ ] 1. (R1, R2)
-
-## Build slices
-- [ ] B1: steps 1-3. Risk: normal | high.
-
-## Risks
- (spec concern N)
-
-## Proof
- (R#)
-
-## Build log
-
-```
diff --git a/.claude/skills/sdlc-review/SKILL.md b/.claude/skills/sdlc-review/SKILL.md
deleted file mode 100644
index 4433f0f..0000000
--- a/.claude/skills/sdlc-review/SKILL.md
+++ /dev/null
@@ -1,55 +0,0 @@
----
-name: sdlc-review
-description: Stage 5 (Deploy) review gate of the AI-native SDLC pipeline. Reviews a change's diff in a fresh context with three passes (bugs, security, compliance against spec.md, plan.md, and CLAUDE.md), ranks findings Important or Nit, and writes sdlc//review.md. Use after /sdlc-build and before opening a PR, or when the user asks to review a change tracked under sdlc/.
-argument-hint: "[sdlc/ directory]"
----
-
-# Review the change (Stage 5: Deploy)
-
-You are reviewing work you did not write. This runs in its own new session, so you see the artifacts and the diff, not the conversation that produced them. The agent that wrote the code has no way to approve it, so you report and change nothing: the author fixes, and the human decides. Your findings free the human to judge only intent and risk.
-
-## Inputs
-
-Use the directory given in the arguments. Otherwise pick the change under `sdlc/` whose `plan.md` is accepted and has no `PR:` line yet; if none or several match, stop and say so. Read `intent.md`, `spec.md` if there is one, `plan.md`, and `CLAUDE.md`. If `REVIEW.md` exists at the repo root, it is the review policy and overrides the defaults below.
-
-Review everything on the branch and in the working tree against the default branch: `git diff $(git merge-base HEAD )`, plus untracked files from `git status --porcelain`. Read the surrounding code wherever a hunk's correctness depends on it.
-
-When the diff is too large to review carefully in one context, split it by area (a directory, or a build slice from `plan.md`) and give each area to a read-only subagent with the same inputs and passes. Verify every finding they return before it goes in `review.md`.
-
-## Passes
-
-Run three passes and tag each finding with its pass:
-
-- **Bugs**: logic errors, broken edge cases, subtle regressions, error handling, concurrency.
-- **Security**: injection, missing authentication or authorization, secrets or personal data in logs, unsafe file or network handling.
-- **Compliance**: the diff meets the requirements (cite `R#` from `spec.md`, or from `plan.md` when there is no spec), matches `plan.md` (files, order, proof), and follows `CLAUDE.md`. In a bug fix, any edit to a test that existed before the fix is Important unless the plan justifies it: the fix must pass the old test, not a rewritten one. This pass exists because a reviewer reading only the diff cannot see what the plan required.
-
-## Severity
-
-- **Important**: would break behavior, leak data, or breach a policy.
-- **Nit**: everything else, including style and naming. List at most five and give the rest as a count.
-
-Skip generated files and anything CI, the formatter, or a linter already enforces. Verify each finding before reporting it: cite `file:line` and, for Important, the concrete failure. Drop what you cannot substantiate.
-
-## Feed CLAUDE.md
-
-Read the earlier `sdlc/*/review.md` files. When a finding repeats one from a past review, or the change has made `CLAUDE.md` outdated, propose the exact `CLAUDE.md` edit. Do not apply it; the user decides.
-
-## Output
-
-Write `review.md` in the change directory:
-
-```markdown
-# Review:
-Reviewed: , HEAD . Diff base: . Important: . Nit: ( more not listed).
-
-| ID | Severity | File:Line | Issue |
-| :--- | :--- | :--- | :--- |
-| I1 | Important | | : . Fix: . |
-| N1 | Nit | | : . |
-
-## Proposed CLAUDE.md changes
-
-```
-
-Reply with the counts, the path to `review.md`, and the Important findings as a short list. End with the handoff from `.claude/CLAUDE.md` (SDLC sessions): with Important findings open, `/sdlc-build sdlc/ fix review`, followed by a new `/sdlc-review`; with none left, `/sdlc-ship`.
diff --git a/.claude/skills/sdlc-ship/SKILL.md b/.claude/skills/sdlc-ship/SKILL.md
deleted file mode 100644
index 6732897..0000000
--- a/.claude/skills/sdlc-ship/SKILL.md
+++ /dev/null
@@ -1,39 +0,0 @@
----
-name: sdlc-ship
-description: Stage 5 (Deploy) of the AI-native SDLC pipeline. Commits a reviewed change, opens a PR that carries its intent, spec, plan, and review record, and babysits it until checks are green, then merges only on the user's explicit yes for that PR. Never releases.
-argument-hint: "[sdlc/ directory]"
-disable-model-invocation: true
----
-
-# Ship to the gate (Stage 5: Deploy)
-
-The agent does everything up to the production gate and nothing past it. Merging, tagging, and releasing stay human decisions.
-
-## Find the change
-
-Use the directory given in the arguments. Otherwise pick the change under `sdlc/` that has a `review.md` and no `PR:` line in `plan.md`; ask if none or several match.
-
-## Preconditions
-
-- `review.md` is newer than every file in the diff, so it reviewed what ships. If anything changed after it, run `/sdlc-review` again.
-- `review.md` has no open Important finding, unless the user explicitly accepted each remaining one.
-- The Proof checks from `plan.md` pass now. Run them again; earlier output does not count.
-
-If any fails, stop and say which.
-
-## Steps
-
-1. If anything is still uncommitted, draft its commit message following the repo's convention (read `git log` first) and stage only this change's files. If `/sdlc-build` already committed everything, skip the commit; the branch's commits are the record. `sdlc/` is gitignored, so the artifacts never land in a commit.
-2. Draft the PR. The artifacts are not committed, so the PR body is the audit record. Carry the chain:
- - **Intent**: problem and outcome, two lines.
- - **Requirements**: each `R#` and where it is met.
- - **Plan deviations**: what changed from `plan.md` and why, or "None".
- - **Review**: the counts, and any Important finding accepted rather than fixed, with the reason.
- - **Verification**: the commands run and their results, and anything not verified.
-3. Autonomous build phase (`.claude/CLAUDE.md`): no approval needed. Print the commit message, PR title, and PR body for the record, then proceed: if still on the default branch, create a branch named after the change slug; commit whatever `/sdlc-build` left uncommitted, push, and run `gh pr create`. Add `PR: ` under the `Status:` line of `plan.md`.
-4. Babysit the PR. Watch checks with `gh pr checks --watch` in the background. For each failure, read the log (`gh run view --log-failed`), say whether it looks flaky or real, fix real ones, and commit in the same convention and push. Address review comments the same way (`gh pr view --comments`; inline ones via `gh api repos/{owner}/{repo}/pulls//comments`), and say what changed with `gh pr comment`. Fix the code, not the check.
-5. When checks are green and every review comment is addressed, merge without asking (autonomous build phase), with the merge method the repo's history uses (`gh pr merge --merge --delete-branch` today), then `git switch && git pull --ff-only`, and report the merge commit. Never merge with a failing required check or an unaddressed review comment.
-
-Never approve a PR review, force-push, push to the default branch, tag, or trigger a release. Merging only through `gh pr merge` per step 5.
-
-After merge, release is a human call. If something breaks in the field, `/sdlc-maintain` brings it back into the loop. End with the handoff from `.claude/CLAUDE.md` (SDLC sessions) for whichever comes next: `/sdlc-build` for the next unchecked slice when the plan ships in several PRs, `/sdlc-maintain` for a failure, `/sdlc-intent` for the next change.
diff --git a/.claude/skills/sdlc-spec/SKILL.md b/.claude/skills/sdlc-spec/SKILL.md
deleted file mode 100644
index 5ce0e3b..0000000
--- a/.claude/skills/sdlc-spec/SKILL.md
+++ /dev/null
@@ -1,50 +0,0 @@
----
-name: sdlc-spec
-description: Stage 2 (Design) of the AI-native SDLC pipeline. Turns an accepted sdlc//intent.md into spec.md with numbered requirements, a design grounded in the existing codebase and project standards, and flagged areas of concern. Use when the user wants requirements, a design, or a spec for captured intent, or has just accepted an intent.md and asks what comes next.
-argument-hint: "[sdlc/ directory]"
----
-
-# Requirements and design (Stage 2: Design)
-
-Requirements and design collapse into one pass. Project policy is applied while the spec is written, not discovered in a review weeks later. Claude writes the spec; the user, as product owner, reviews it.
-
-## Find the change
-
-Use the directory given in the arguments. Otherwise pick the change under `sdlc/` whose `intent.md` is accepted and has no accepted `spec.md` yet; ask if none or several match. A draft `spec.md` left by an earlier session is continued, not restarted. `intent.md` must say `Status: accepted`. If it says anything else, ask the user to accept it first: an unreviewed intent is the most expensive thing to build on.
-
-## Steps
-
-1. Load the standards the spec must conform to: `CLAUDE.md`, the docs it points to that touch this change (architecture, glossary, platform constraints, known issues), and every available skill whose domain the change touches, such as UI, accessibility, security, or App Store review. These are the policies. List the ones applied in the spec header. For a domain with no installed skill, name one for the user to install.
-2. Read the code the change integrates with, so the design fits the system as it is. For a wide change, delegate the reading to read-only subagents and keep their conclusions, not the files, in this session.
-3. Run the playbook's pass:
- > Read the attached intent.md and produce a requirements and design spec for integrating it into our existing codebase. Apply the skills available to you so the plan conforms to our brand guidelines, security policies and UX standards. Document the spec fully as spec.md, ready to hand to the engineering team. Describe clearly any areas of concern, especially where you cannot satisfy contradicting policies.
-4. Write `spec.md` next to `intent.md` from the template below. Number the requirements so the plan, the tests, and the review can cite them, and make each one checkable. For UI changes, name the visual target here (a mock, a reference screenshot, or the platform pattern to follow); the build checks against it later.
-5. Review with the user in `grilling` rounds, flagged concerns first, because they are what an analyst would have escalated. Resolve each one. Then check that the spec solves the stated problem and that every open question from the intent is answered or carried forward.
-6. Gate: when the user accepts it, set `Status: accepted`. If review changed what is being asked for, update `intent.md` too, so the pair still records what was asked and what was decided.
-
-Next stage: `/sdlc-plan`. End with the handoff from `.claude/CLAUDE.md` (SDLC sessions).
-
-## Template
-
-```markdown
-# Spec:
-From: intent.md (). Status: draft.
-Standards applied:
-
-## Requirements
-- R1.
-
-## Design
-
-
-## How each constraint in the intent is met
-| Constraint | Met by |
-| :--- | :--- |
-| | |
-
-## Open questions from the intent
-- :
-
-## Areas of concern
-1. Owner: .
-```
diff --git a/.github/CLAUDE.md b/.github/AGENTS.md
similarity index 79%
rename from .github/CLAUDE.md
rename to .github/AGENTS.md
index 96c2f9a..38e8177 100644
--- a/.github/CLAUDE.md
+++ b/.github/AGENTS.md
@@ -11,7 +11,8 @@ GitHub's config for this repo: CI, releases, Pages, dependabot, issue forms, PR
| `workflows/go-weekly.yml` | Monday scan on a clock, files or updates one `security` issue on failure |
| `workflows/github-pages.yml` | builds `_site` on PR, also deploys on push to main |
| `workflows/reusable-*.yml` | `workflow_call` only. Edit the check here, never in a caller |
-| `dependabot.yml` | github-actions `/`, gomod `/crocmobile`, npm `/web/docs`, weekly |
+| `workflows/scorecard.yml` | OpenSSF Scorecard on push to main, Wednesday cron, `branch_protection_rule`, and dispatch; publishes results and uploads SARIF to code scanning |
+| `dependabot.yml` | github-actions `/`, gomod `/crocmobile`, swift `/app` (Xcode project, SwiftPM), npm `/web/docs`, weekly |
## Rules
@@ -25,13 +26,15 @@ gh api repos/OWNER/REPO/git/ref/tags/TAG --jq '.object.type + " " + .object.sha'
**Versions live in files, not in workflow text.** Xcode in `.xcode-version`, Node in `web/docs/.nvmrc`, Go in `crocmobile/go.mod`, govulncheck and gomobile in its `tool` block, the croc v10 test peer in `scripts/croc-v10.version`. Runners are pinned (`ubuntu-24.04`, `macos-26`, `xcode-27`) and are the one thing dependabot cannot see.
+**`ci pass` is an interface.** `main` requires a pull request (0 approvals) and the `ci pass` check, admins included, so renaming that job breaks merging. Merging is squash-only (title = PR title, body = PR body) and `main` requires linear history.
+
## Gotchas
- **Fork pull requests get a read-only `GITHUB_TOKEN`.** Any step that writes a comment, label, or issue needs `github.event.pull_request.head.repo.full_name == github.repository`, or a green run goes red.
- **A label named in config must already exist** in the repo. Dependabot silently drops missing ones, and a `labels:` filter on `listForRepo` silently matches nothing, which breaks issue dedup.
- **Go jobs cross-target `darwin/arm64`** because the module only ships through gomobile. Tools have to be built for the runner (`env -u GOOS -u GOARCH go build -o ...`) and then run under the job's env.
- **YAML anchors work** in workflows here (`paths: &site-paths` / `*site-paths` in `github-pages.yml`), despite the common claim that Actions rejects them.
-- **`release.yml` parses `CHANGELOG.md`.** It requires a Keep a Changelog heading, dated: `## [1.2.3] - YYYY-MM-DD`. Change the changelog's heading style and the tag stops releasing. Following practices from [Keep a Changelog](https://keepachangelog.com/).
+- **`release.yml` parses `CHANGELOG.md`.** It requires a Keep a Changelog heading, dated: `## [1.2.3] - YYYY-MM-DD`. Change the changelog's heading style and the tag stops releasing. The notes end at the next `## [` heading or at the first line starting `[label]: `, so a reference-style link definition inside a version section cuts its release notes short; use inline links there. Following practices from [Keep a Changelog](https://keepachangelog.com/).
## Expiring notes
@@ -40,7 +43,3 @@ Each subsection describes a trap that will stop existing. **Delete a subsection
### corepack
`github-pages.yml` gets pnpm with `corepack enable pnpm`, reading the version from `packageManager` in `web/docs/package.json`. Node still bundles corepack at the pinned `.nvmrc` version, but Node is removing it. When a `.nvmrc` bump makes that step fail, replace it with a direct pnpm install at the `packageManager` version, or `pnpm/action-setup`. Delete this once swapped.
-
-### `main` branch protection
-
-`main` has no branch protection, only a `v*` tag ruleset, so renaming a job breaks no required check. Protection is planned, not forgotten, and will require only `ci pass`. Delete this once it is on, and treat that job name as an interface from then on.
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 15a8603..ae40e90 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -46,3 +46,17 @@ updates:
commit-message:
prefix: "deps"
prefix-development: "deps-dev"
+
+ - package-ecosystem: "swift"
+ directory: "/app"
+ schedule:
+ interval: "weekly"
+ day: "monday"
+ groups:
+ swift-deps:
+ patterns: ["*"]
+ update-types: ["minor", "patch"]
+ labels:
+ - "dependencies"
+ commit-message:
+ prefix: "deps"
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index da1a1b0..80b2621 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,13 +1,14 @@
-
-
-
+
## What and why
Fixes # because...
+AI assistance: none / partial / substantial
+
+
## Verification
A green build is not evidence a transfer works. List the commands you actually ran and their result. Write "not verified" for anything you skipped.
@@ -15,7 +16,7 @@ A green build is not evidence a transfer works. List the commands you actually r
- [ ] `scripts/format-swift.sh --lint`
- [ ] `golangci-lint run ./...` (only if `crocmobile/` changed)
- [ ] macOS and/or iOS Simulator build
-- [ ] Matching `scripts/verify-*.sh` harness — required for changes to `crocmobile/session.go`, `CrocKit/Sources/`, or `TransferController`
+- [ ] Matching `scripts/verify-*.sh` harness, required for changes to `crocmobile/session.go`, `CrocKit/Sources/`, or `TransferController`
```
paste harness output here (if long, paste the summary lines and any failures, not the whole log)
@@ -24,7 +25,7 @@ paste harness output here (if long, paste the summary lines and any failures, no
## Docs
- [ ] Anything a user would notice → a line under `## [Unreleased]` in `CHANGELOG.md`. Write it for someone deciding whether to update, not as a summary of this diff. That section becomes the release notes verbatim
-- [ ] Durable knowledge → `docs/knowledge/` and/or `CLAUDE.md`
+- [ ] Durable knowledge → `docs/knowledge/` and/or `AGENTS.md`
- [ ] Defect fixed or accepted → `docs/known-issues.md` line deleted or added
- [ ] Nothing doc-worthy
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index a61e7ae..426cc25 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -72,7 +72,7 @@ jobs:
if [ "$declared" != "$version" ]; then
echo "error: tag says $version, Version.xcconfig says $declared."
- echo "Bump MARKETING_VERSION in a pull request first, then tag the merge commit."
+ echo "Bump MARKETING_VERSION in a pull request first, then tag the squashed commit on main."
exit 1
fi
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
new file mode 100644
index 0000000..13767c5
--- /dev/null
+++ b/.github/workflows/scorecard.yml
@@ -0,0 +1,54 @@
+name: Scorecard
+
+on:
+ push:
+ branches: [main]
+ schedule:
+ - cron: "30 5 * * 3"
+ branch_protection_rule:
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ analysis:
+ name: analysis
+
+ # Scorecard only supports the default branch.
+ if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
+
+ runs-on: ubuntu-24.04
+
+ timeout-minutes: 10
+
+ # The job-level permissions and the action list below are required for
+ # publish_results; see "Workflow Restrictions" in the scorecard-action README.
+ permissions:
+ contents: read
+ actions: read
+ security-events: write
+ id-token: write
+
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ with:
+ persist-credentials: false
+
+ - uses: ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e # v2.5.0
+ with:
+ results_file: results.sarif
+ results_format: sarif
+ publish_results: true
+
+ - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
+ if: always()
+ with:
+ name: SARIF file
+ path: results.sarif
+ retention-days: 5
+
+ - uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
+ if: always()
+ with:
+ sarif_file: results.sarif
diff --git a/.gitignore b/.gitignore
index 679831d..ec7e3c0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,3 +45,6 @@ web/docs/src/css/tokens.css
# SDLC pipeline artifacts (.claude/skills/sdlc-*)
/sdlc/
+
+# sourcekit-lsp build server config (xcode-build-server, machine-specific paths)
+/buildServer.json
diff --git a/.swift-format b/.swift-format
index ddeda36..63ea767 100644
--- a/.swift-format
+++ b/.swift-format
@@ -3,76 +3,5 @@
"indentation": {
"spaces": 4
},
- "tabWidth": 8,
- "lineLength": 100,
- "maximumBlankLines": 1,
- "indentBlankLines": false,
- "respectsExistingLineBreaks": true,
- "spacesBeforeEndOfLineComments": 2,
- "spacesAroundRangeFormationOperators": false,
- "indentConditionalCompilationBlocks": false,
- "indentSwitchCaseLabels": false,
- "lineBreakBeforeControlFlowKeywords": false,
- "lineBreakBeforeEachArgument": false,
- "lineBreakBeforeEachGenericRequirement": false,
- "lineBreakBetweenDeclarationAttributes": false,
- "lineBreakAroundMultilineExpressionChainComponents": false,
- "multiElementCollectionTrailingCommas": true,
- "prioritizeKeepingFunctionOutputTogether": true,
- "reflowMultilineStringLiterals": "never",
- "fileScopedDeclarationPrivacy": {
- "accessLevel": "private"
- },
- "orderedImports": {
- "includeConditionalImports": false,
- "shouldGroupImports": true
- },
- "noAssignmentInExpressions": {
- "allowedFunctions": ["XCTAssertNoThrow"]
- },
- "rules": {
- "AllPublicDeclarationsHaveDocumentation": false,
- "AlwaysUseLiteralForEmptyCollectionInit": true,
- "AlwaysUseLowerCamelCase": true,
- "AmbiguousTrailingClosureOverload": true,
- "AvoidRetroactiveConformances": true,
- "BeginDocumentationCommentWithOneLineSummary": false,
- "DoNotUseSemicolons": true,
- "DontRepeatTypeInStaticProperties": true,
- "FileScopedDeclarationPrivacy": true,
- "FullyIndirectEnum": true,
- "GroupNumericLiterals": true,
- "IdentifiersMustBeASCII": true,
- "NeverForceUnwrap": true,
- "NeverUseForceTry": true,
- "NeverUseImplicitlyUnwrappedOptionals": true,
- "NoAccessLevelOnExtensionDeclaration": true,
- "NoAssignmentInExpressions": true,
- "NoBlockComments": true,
- "NoCasesWithOnlyFallthrough": true,
- "NoEmptyLinesOpeningClosingBraces": true,
- "NoEmptyTrailingClosureParentheses": true,
- "NoLabelsInCasePatterns": true,
- "NoLeadingUnderscores": false,
- "NoParensAroundConditions": true,
- "NoPlaygroundLiterals": true,
- "NoVoidReturnOnFunctionSignature": true,
- "OmitExplicitReturns": false,
- "OneCasePerLine": true,
- "OneVariableDeclarationPerLine": true,
- "OnlyOneTrailingClosureArgument": true,
- "OrderedImports": true,
- "ReplaceForEachWithForLoop": true,
- "ReturnVoidInsteadOfEmptyTuple": true,
- "TypeNamesShouldBeCapitalized": true,
- "UseEarlyExits": false,
- "UseExplicitNilCheckInConditions": true,
- "UseLetInEveryBoundCaseVariable": true,
- "UseShorthandTypeNames": true,
- "UseSingleLinePropertyGetter": true,
- "UseSynthesizedInitializer": false,
- "UseTripleSlashForDocumentationComments": true,
- "UseWhereClausesInForLoops": false,
- "ValidateDocumentationComments": true
- }
+ "indentConditionalCompilationBlocks": false
}
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..e09a37f
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,3 @@
+# CrocApp
+
+Native SwiftUI GUI for the croc file-transfer CLI.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3351945..1999c14 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,26 +4,29 @@ All notable changes to CrocApp are recorded here. Each version's section is
also the body of its GitHub release, so it is written for someone deciding
whether to download, not as a summary of commits.
-The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
-the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), with
+extra sections where a release needs them, and the project follows
+[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [1.0.0] - 2026-10-15
-CrocApp 1.0 is on three channels: the iOS and iPadOS App Store, the Mac App Store, and a notarized disk image for Mac. The App Store listings are linked from crocapp.dev when they are live. The other device needs croc 11 or newer.
+CrocApp 1.0 is on three channels: the iOS and iPadOS App Store, the Mac App Store, and a notarized disk image for Mac. The App Store listings are linked from crocapp.dev. The other device needs croc 11 or newer.
**iOS 26, iPadOS 26 or macOS 26 or later. The disk image is Apple silicon only.**
+Verify a download with `gh attestation verify --repo bakirgdev/CrocApp`: the release workflow attests the disk image, the update feed and the engine archive after stapling, so the attestation covers the bytes you download. `SHA256SUMS.txt` lists the SHA-256 checksums of the disk image and the engine archive.
+
### Added
- iPhone and iPad apps through the App Store, and a Mac App Store build. 0.9.9 was macOS only.
-- The macOS direct download is now signed with a Developer ID certificate, notarized and stapled, so it opens with only the standard first-launch confirmation. It updates itself with Sparkle: choose "Check for Updates…" in the app menu. Sparkle installs an update only when its EdDSA signature matches the key built into the app, and the update check sends no system profile. Only this build has Sparkle; the App Store builds update through the App Store. The release no longer ships the separate app zip; the disk image is the download. Verify a download with `gh attestation verify --repo bakirgdev/CrocApp`: the release workflow attests the disk image, the update feed and the engine archive after stapling, so the attestation covers the bytes you download.
+- The macOS direct download is now signed with a Developer ID certificate, notarized and stapled, so it opens with only the standard first-launch confirmation.
+- The direct download updates itself with Sparkle: choose "Check for Updates…" in the app menu. Sparkle installs an update only when its EdDSA signature matches the key built into the app, and the update check sends no system profile. Only this build has Sparkle; the App Store builds update through the App Store.
- Receiving a file whose name is already taken now offers Keep Both (saves the new one as `name (1).ext`) or Replace. With auto-accept on, the setting "When a file already exists" under Confirmation chooses; it defaults to Keep Both.
-- A Connection setting, Automatic (the default) or Relay only. With Automatic, a send of 128 MB or more can also take a direct path between the devices; Relay only keeps every transfer on croc's relays. The sender's choice applies, and a receive follows it.
+- A Connection setting, Automatic (the default) or Relay only. With Automatic, a send of 128 MiB or more can also take a direct path between the devices; Relay only keeps every transfer on croc's relays. The sender's choice applies, and a receive follows it.
- Links. Opening a CrocApp link, a `crocapp.dev/r/` link or a `getcroc.com/?code=` link, or pasting one into the Receive field, opens Receive with the code filled in; nothing connects until you tap Receive. The sender's waiting screen has a Share Link button. The page at `crocapp.dev/r/` reads the code in your browser (it never reaches a server) and offers to open CrocApp, get the app, or receive in your browser through croc's own web app, which needs the default relays.
- Send as link. Turn it on in Send to upload files (not folders or text) encrypted to a transfer server and get a link and QR code instead of a code, so the receiver can download later without being online at the same time. You choose how long the link lasts (1 hour, 1 day or 7 days) and how many downloads it allows (1 to 100), up to 100 files and 1 GiB. Receive takes the link like a code and shows the file list before anything is saved. History shows when each link expires and can revoke it. The files are encrypted on your device and the key is the part of the link after the `#`, so the server cannot read them. The server is set in Settings and defaults to `https://getcroc.com`, which croc's author runs; CrocApp does not.
-
- Advanced settings (Settings > Advanced on iOS, the Advanced tab of the macOS Settings window): SOCKS5 and HTTP proxies, a built-in DNS resolver option, the encryption curve, the file hash, an upload speed limit, the base port and number of parallel connections, a multiplexing switch, a sender IP to receive from directly, and, on macOS, a multicast address. Each shows its default, checks what you type, and says which device it affects and whether the other device must match. Reset to Defaults puts them all back. The proxies also apply to Send as link and are kept in the Keychain, never in the settings file. A receive with a sender IP connects straight to the sender and skips relays. iOS has no multicast setting, because the multicast entitlement is not granted.
- Menu bar quick send on macOS. A CrocApp icon in the menu bar opens a small window: drop files or folders on it, choose them with Choose Files…, or send the clipboard text. It then shows the code, a QR code, Copy Code, progress and Cancel. It shares the one active transfer with the main window, so it also shows a transfer started there or from Shortcuts. Quick send always uses a generated code. Settings > General has Show in menu bar (on by default) and Open at login (off by default; it follows System Settings > General > Login Items and offers a shortcut there when macOS needs your approval).
- Shortcuts and Siri actions: Send Files with CrocApp, Send Text with CrocApp and Receive with CrocApp. The send actions return the code as soon as croc has one, so keep CrocApp open until the receiver finishes. Receive takes a code or any link CrocApp opens and returns the received files; it does not answer the incoming-files prompt, so turn on Auto-accept or answer it in the app. The actions also appear in Spotlight and Siri with the phrases "Send files with CrocApp", "Send text with CrocApp" and "Receive with CrocApp".
@@ -33,10 +36,14 @@ CrocApp 1.0 is on three channels: the iOS and iPadOS App Store, the Mac App Stor
- The transfer engine is croc v11.5.4. A croc 10 device can no longer complete a transfer with CrocApp, in either direction: the other device needs croc 11 or newer. Relays are picked from croc's public relay pool the way the croc 11 command line does, and a send fails with a clear message when no public relay answers.
- Failures say what went wrong: a wrong code, a refused transfer, a lost connection, a peer on an older croc, or no public relay available, instead of one generic message.
-- The relay password is kept in the Keychain instead of the settings file. History keeps only the first segment of a code phrase, never the whole code.
+- History keeps less of a code: the first word of a code shaped like a generated one, only the first two characters of any other code, and only the server's host for Send as link. It never keeps the whole code.
- Every 1.0 screen works at the largest text sizes and with VoiceOver, including the transfer percentage.
- The QR code on the sender's waiting screen now encodes a `https://crocapp.dev/r/` link instead of `croc://`, so a phone camera opens CrocApp, or a page that explains what to do when the app is missing. QR codes from 0.9.9 still scan in the app.
+### Removed
+
+- The separate app zip (`CrocApp--arm64.app.zip`). The disk image is the download.
+
### Fixed
- A receive no longer overwrites existing files by default. A zipped folder received into a folder that already holds it no longer fails. Answers left over from one transfer can no longer answer the next transfer's accept prompt.
diff --git a/CLAUDE.md b/CLAUDE.md
deleted file mode 100644
index dc3023c..0000000
--- a/CLAUDE.md
+++ /dev/null
@@ -1,70 +0,0 @@
-# CrocApp
-
-Free, open-source native SwiftUI GUI for the "croc" file-transfer CLI. Targets iOS 26 + macOS 26. See `@docs/knowledge/project-overview.md` for goals, `docs/ARCHITECTURE.md` for system structure, `docs/GLOSSARY.md` for vocabulary.
-
-## Working Rules
-
-### Verify, don't assume
-
-A green build is **not** evidence a transfer works. Any change to `crocmobile/session.go`, `CrocKit/Sources/`, or `TransferController` needs its matching harness run, output quoted. If a harness was not run, say "not verified" plainly.
-
-The checks live in `docs/BUILDING.md`: format and lint under "Format and lint", the transfer harnesses (`scripts/verify-*.sh`, what each covers and needs) under "Verification harnesses".
-
-### Delegate
-
-Sessions mostly run on a stronger model, so preserve its context and session usage. Delegate any substantial task to cheaper-model subagents with clear, accurate, self-contained instructions. Act as manager and coordinator: split the work, brief each subagent, review what comes back, and correct it before it lands.
-
-### Look up, don't guess
-
-When unsure about any API, library, tool, or platform behavior: query the context7 MCP, perform web search, deploy research subagent(s), or use tool search, before writing code. A small lookup beats a hallucination, doubly so for Swift/SwiftUI/Xcode 26 APIs (training data lags Apple releases) and the croc CLI (frequent new versions). Prefer semantic tools over grep where they exist: `gopls` MCP for `crocmobile/` (`go_search`, `go_symbol_references`, `go_file_context`, `go_package_api`, `go_diagnostics`), `xcode` MCP for build/test/diagnostics/simulator on the Swift side (needs the project open in Xcode, Settings > Intelligence > MCP enabled; not a substitute for `scripts/verify-*.sh`).
-
-### Comments
-
-Don't comment your thoughs or the obvious things. Only leave comments when really needed to explain why something is done a certain way, or to clarify non-obvious behavior. Avoid redundant comments that restate the code. If comment is needed, keep it short, clear, and relevant.
-
-### Commit and push
-
-Never commit or push unless told to. When asked to, do it with best practices and reason about applicable changes for the commit message.
-
-### Session end report
-
-Close every session with this, in order, no praise, no prompt recap, always in simple words (can omit or add other items):
-
-1. **Done**: what changed, one line per file or area.
-2. **Verified**: commands actually run and their result. Anything not run is listed as "not verified".
-3. **Docs**: knowledge files added, changed, deleted, or an explicit "nothing doc-worthy".
-4. **Open**: known gaps, deferred work, follow-ups. Anything durable goes in `docs/known-issues.md`, not only in the report.
-
-Mark speculation as speculation.
-
-
-## RTK (Rust Token Killer) - Token-Optimized Commands
-
-## Golden rule
-
-**Always prefix commands with `rtk`**. If RTK has a dedicated filter, it uses it. If not, it passes through unchanged. This means RTK is always safe to use.
-
-**Important**: Even in command chains with `&&`, use `rtk`:
-```bash
-# ❌ Wrong
-git add . && git commit -m "msg" && git push
-
-# ✅ Correct
-rtk git add . && rtk git commit -m "msg" && rtk git push
-```
-
-### Token Savings Overview
-
-| Category | Commands | Typical Savings |
-|----------|----------|-----------------|
-| Tests | vitest, playwright, cargo test | 90-99% |
-| Build | next, tsc, lint, prettier | 70-87% |
-| Git | status, log, diff, add, commit | 59-80% |
-| GitHub | gh pr, gh run, gh issue | 26-87% |
-| Package Managers | pnpm, npm, npx | 70-90% |
-| Files | ls, read, grep, find | 60-75% |
-| Infrastructure | docker, kubectl | 85% |
-| Network | curl, wget | 65-70% |
-
-Overall average: **60-90% token reduction** on common development operations.
-
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index dc46b49..146a3b4 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -43,9 +43,7 @@ We agree to restrict the following behaviors in our community. Instances, threat
Tensions can occur between community members even when they are trying their best to collaborate. Not every conflict represents a code of conduct violation, and this Code of Conduct reinforces encouraged behaviors and norms that can help avoid conflicts and minimize harm.
-When an incident does occur, it is important to report it promptly. To report a possible violation, email ****. Reports are read only by the project maintainer, Bakir Gracic. Include what happened, where, when, and links or screenshots if you have them. If your report concerns the maintainer, you may instead report the account or content to GitHub through its [abuse reporting form](https://github.com/contact/report-abuse).
-
-This project applies the Code of Conduct across its GitHub issues, pull requests, discussions, code review, and commit history, as well as any other space where the project is officially represented.
+When an incident does occur, it is important to report it promptly. To report a possible violation, email ****. Reports are read only by the project maintainer, Bakir Gracic. Include what happened, where, when, and links or screenshots if you have them. If your report concerns the maintainer, you may instead report the account or content to GitHub ([how to report](https://docs.github.com/en/communities/maintaining-your-safety-on-github/reporting-abuse-or-spam)).
Reports of violations are taken seriously and answered as promptly as one maintainer can manage. Every report is investigated, which may involve reviewing messages, logs, or the account history involved. Investigation and enforcement are kept as transparent as possible while prioritizing the safety and confidentiality of the person reporting. Enforcement actions are carried out in private with the involved parties, though communicating an outcome to the whole community may be part of a mutually agreed resolution.
@@ -74,12 +72,12 @@ This enforcement ladder is intended as a guideline. It does not limit the mainta
## Scope
-This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public or other spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
+This Code of Conduct applies in all project spaces: GitHub issues, pull requests, discussions, code review, and commit history. It also applies when someone officially represents the project in public.
## Attribution
This Code of Conduct is adapted from the Contributor Covenant, version 3.0, permanently available at [https://www.contributor-covenant.org/version/3/0/](https://www.contributor-covenant.org/version/3/0/).
-Contributor Covenant is stewarded by the Organization for Ethical Source and licensed under CC BY-SA 4.0. To view a copy of this license, visit [https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/)
+Contributor Covenant is stewarded by the Organization for Ethical Source and licensed under CC BY-SA 4.0. To view a copy of this license, visit [https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/).
-For answers to common questions about Contributor Covenant, see the FAQ at [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are provided at [https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations). Additional enforcement and community guideline resources can be found at [https://www.contributor-covenant.org/resources](https://www.contributor-covenant.org/resources). The enforcement ladder was inspired by the work of [Mozilla's code of conduct team](https://github.com/mozilla/inclusion).
+The enforcement ladder was inspired by the work of [Mozilla's code of conduct team](https://github.com/mozilla/inclusion).
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e2348a4..a1e925b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -7,18 +7,16 @@ Two things make this repo different from a typical SwiftUI app, and most review
1. **A green build is not evidence a transfer works.** Real behavior is proven by live-transfer harnesses in `scripts/`, not by the compiler.
2. **Anything visual is bound to `design/`.** Tokens only, SF Symbols only, system font only.
-Both are spelled out below.
-
| | |
|---|---|
-| [Code of Conduct](CODE_OF_CONDUCT.md) | How we behave here |
-| [Where to start](#where-to-start) | Issues, labels, feature numbers |
-| [Setup](#setup) | Toolchain, first build, croc CLI |
-| [Branches and commits](#branches-and-commits) | Conventional Branch + Conventional Commits |
-| [Code style](#code-style) | swift-format, golangci-lint, dependencies |
-| [Design rules](#design-rules) | Tokens, symbols, themes, screenshots |
+| [Code of Conduct](#code-of-conduct) | How we behave here |
+| [Where to start](#where-to-start) | Issues, discussions, upstream |
+| [Setup](#setup) | First build, repo layout |
+| [Commits](#commits) | Message conventions |
+| [Code style](#code-style) | Formatters, linters, scope |
+| [Design rules](#design-rules) | Tokens, symbols, screenshots |
| [Verification](#verification) | What to run, and how to report it |
-| [Docs](#docs) | Knowledge, known issues |
+| [Docs](#docs) | Changelog, knowledge, known issues |
| [AI-assisted contributions](#ai-assisted-contributions) | Allowed, with disclosure |
| [Opening a pull request](#opening-a-pull-request) | Flow and expectations |
| [Unlikely to be merged](#unlikely-to-be-merged) | Save yourself the work |
@@ -30,8 +28,6 @@ This project follows the [Contributor Covenant 3.0](CODE_OF_CONDUCT.md). Taking
## Where to start
-Before filing anything, check [`docs/known-issues.md`](docs/known-issues.md). Every defect in there was found on purpose and consciously not fixed, so a bug report that repeats one is a duplicate on arrival.
-
| You want to | Go to |
|---|---|
| Report a bug | [Bug report form](https://github.com/bakirgdev/CrocApp/issues/new?template=bug_report.yml) |
@@ -40,221 +36,84 @@ Before filing anything, check [`docs/known-issues.md`](docs/known-issues.md). Ev
| Report a vulnerability | [Private advisory](https://github.com/bakirgdev/CrocApp/security/advisories/new), never a public issue. See [SECURITY.md](SECURITY.md) |
| Report a transfer, relay, or crypto bug that also reproduces with the `croc` CLI | [Upstream croc](https://github.com/schollz/croc/issues) |
-Useful labels: `good first issue`, `help wanted`, `bug`, `enhancement`, `documentation`.
-
-Features carry stable numbers (`F1`, `F2`, …) used across code comments, issues, and the README. The roadmap, including what shipped and what is deferred, is [`docs/knowledge/features.md`](docs/knowledge/features.md). Reference the number when it exists.
+Issues labeled `help wanted` are open for anyone to pick up.
> [!TIP]
-> Any PR is welcome, but for a large feature or a refactor that touches the transfer engine, open an issue or a discussion first. Not for approval, just so you do not spend a weekend on something that collides with work already in flight.
+> For a large feature or a refactor that touches the transfer engine, open an issue or a discussion first. Not for approval, just so you do not spend a weekend on something that collides with work already in flight.
## Setup
-Requires **macOS 26**, **Xcode 27.0** (the pin lives in [`.xcode-version`](.xcode-version)), and **Go 1.27.2 or newer**. `gomobile` and `gobind` are rebuilt from the `go.mod` `tool` block on every run.
+Requires **macOS 26**, **Xcode 27**, and **Go 1.27.2** (or newer).
```bash
git clone https://github.com/bakirgdev/CrocApp.git
cd CrocApp
-scripts/build-xcframework.sh # Go engine -> CrocKit/Croc.xcframework
-open app/CrocApp.xcodeproj
-```
-
-> [!IMPORTANT]
-> A fresh clone builds nothing until the xcframework exists. `CrocKit`'s binary target points at a gitignored build artifact, so `scripts/build-xcframework.sh` is not optional. Rerun it after any change under `crocmobile/`.
-
-The verification harnesses also need the real `croc` CLI and outbound network access to the public relay:
-
-```bash
-brew install croc # lands at $HOMEBREW_PREFIX/bin/croc, the default
```
-Env: `CROC` overrides the CLI path (default `$HOMEBREW_PREFIX/bin/croc`), `SIM` picks the simulator (default `iPhone 17 Pro`; list them with `xcrun simctl list devices`).
-
-Deeper references: [`docs/BUILDING.md`](docs/BUILDING.md) for the exact toolchain and troubleshooting, [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for the system map, [`docs/GLOSSARY.md`](docs/GLOSSARY.md) for unfamiliar terms.
-
-### What lives where
-
-| Path | What it is | Read first |
-|---|---|---|
-| `crocmobile/` | Go wrapper around croc v11.5.4, bound into `Croc.xcframework`. `session.go` is the engine | [`docs/knowledge/crocmobile-bridge.md`](docs/knowledge/crocmobile-bridge.md) |
-| `CrocKit/` | Swift package: `CrocEngine` actor, `AsyncStream` | same file |
-| `app/CrocApp/` | SwiftUI app, iOS + macOS | [`docs/knowledge/app-ui-architecture.md`](docs/knowledge/app-ui-architecture.md) |
-| `app/CrocShare/` | Share extension | same file |
-| `app/CrocAppDirect/` | Sources only the `CrocApp Direct` (Developer ID, Sparkle) target compiles | [`docs/BUILDING.md`](docs/BUILDING.md) section 7 |
-| `design/` | Canonical design system for app, landing, docs | [`design/CLAUDE.md`](design/CLAUDE.md) |
-| `web/landing/` | The crocapp.dev landing page | `design/CLAUDE.md` |
-| `docs/` | Knowledge base, known issues | [`docs/knowledge/README.md`](docs/knowledge/README.md) |
-| `scripts/` | Build and live-transfer verification harnesses | the script headers |
-
-> [!WARNING]
-> The bridge and UI knowledge files hold hard-won invariants: event ordering, fd0 prompt-pipe semantics, per-file sender answers, relay-address blanking, room collisions on shared code prefixes. Most "obvious" fixes in those areas re-break one of them. Read the relevant file before you edit.
-
-## Branches and commits
-
-Branch off `main`, using [Conventional Branch](https://conventional-branch.github.io/) naming:
+Also needed: `croc` CLI (`brew install croc`) and outbound network access to the public relay.
-```
-feat/qr-scan-torch
-fix/relay-address-blanking
-docs/contributing-guide
-chore/bump-golangci-lint
-refactor/transfer-controller-state
-```
+## Commits
-Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/):
+Not enforced, but strongly advised: [Conventional Commits 1.0.0](https://www.conventionalcommits.org/en/v1.0.0/) and the usual git message rules.
```
():
-```
-
-| Type | Use for |
-|---|---|
-| `feat` | New user-visible capability |
-| `fix` | Bug fix |
-| `docs` | Docs, README, comments-only changes |
-| `style` | Formatting only, no behavior change |
-| `refactor` | Restructuring with no behavior change |
-| `perf` | Performance work |
-| `build` | Build scripts, gomobile, Xcode project settings, dependencies |
-| `ci` | Workflows under `.github/` |
-| `chore` | Everything else with no source impact |
-
-Scopes, one per commit, matching the repo layout: `app`, `share`, `crockit`, `crocmobile`, `design`, `landing`, `docs`, `ci`, `scripts`, `assets`. Omit the scope when a change genuinely spans everything.
-
-Rules for the subject line: imperative and present tense ("add", not "added" or "adds"), lowercase after the colon, no trailing period, roughly 72 characters or fewer. Put the why in the body when it is not obvious from the diff. Breaking changes get a `!` after the scope and a `BREAKING CHANGE:` footer.
-```
-fix(crocmobile): blank the relay address on LAN-only transfers
-
-The receiver rendered the relay host even when the connection never
-left the subnet, which reads as a privacy leak. Blank it at the event
-boundary rather than in the view, so both platforms inherit the fix.
+
Fixes #42
```
-Keep commits scoped. One logical change per commit beats one commit per file, and beats a single commit for an entire feature.
+- Types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`. The scope is optional and names the area, such as `crocmobile` or `app`.
+- Subject: imperative ("add", not "added"), lowercase after the colon, no trailing period, 72 characters or fewer.
+- Breaking change: `!` after the type or scope, plus a `BREAKING CHANGE:` footer.
+
+Pull requests are squash-merged, and the PR title becomes the commit subject on `main`. That makes the PR title the one place this convention matters most. Commits inside your branch can be as messy as you like.
## Code style
Match the surrounding file. When in doubt, the formatter decides.
```bash
-# Swift, from the repo root
+# Swift
scripts/format-swift.sh # rewrite in place, then lint
scripts/format-swift.sh --lint # lint only, exactly what CI runs
-# Go, from crocmobile/
+# Go
golangci-lint run ./...
govulncheck ./...
```
-swift-format ships with the toolchain, so there is nothing to install. Config lives in [`.swift-format`](.swift-format): 4-space indent, non-indented `#if` bodies. The source set it covers lives in one place, [`scripts/format-swift.sh`](scripts/format-swift.sh) — add a new Swift target there, not to the command in your shell history. Go config is in [`crocmobile/.golangci.yml`](crocmobile/.golangci.yml).
-
-Other expectations:
-
- **Comments explain why, never what.** Do not delete existing comments unless the code they describe is going away.
-- **Stay in scope.** No drive-by refactors, no renaming unrelated symbols, no reformatting files your change does not touch. A diff that is 90% noise gets sent back.
-- **No new third-party dependencies** without agreeing on them in an issue first. Every dependency in `crocmobile/` ends up inside a shipped binary that moves people's files.
-- Swift concurrency is used as designed: `CrocEngine` is an actor, UI state is main-actor. Do not add locks or dispatch queues to work around a warning.
+- **Stay in scope.** No drive-by refactors, no renaming unrelated symbols, no reformatting files your change does not touch.
+- **No new third-party dependencies** without agreeing on them in an issue first. New dependency may end up shipped somewhere that moves people's files!
## Design rules
-Anything visual, in the app or on the web, starts at [`design/CLAUDE.md`](design/CLAUDE.md). Its binding rules, in short:
-
-1. **Tokens, not literals.** No raw hex, px, pt, or duration in app or site code. If a value is missing, add the token to `design/` first (and mirror it into `design/tokens.css` for web).
-2. **SF Symbols only in the app.** Never bundle an icon font, never draw a custom glyph where a symbol exists.
-3. **System font only.** No bundled Apple font files, ever. They are not redistributable.
-4. **Both themes, always.** Every surface has a light and a dark value.
-5. **Accessibility floor is binding** (`design/accessibility.md`): 4.5:1 for body text, 3:1 for large text and UI boundaries, 44x44pt minimum hit target, `prefers-reduced-motion` and `prefers-reduced-transparency` respected, and never encode transfer direction in color alone.
+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.
UI pull requests need **before and after screenshots in both light and dark**, and on both platforms if both are affected.
-## Verification
-
-This is the part reviewers actually check.
-
-A build that compiles proves nothing about a transfer. CI runs only `verify-interop.sh`, against a local relay. The other harnesses and the public-pool scenario need a public relay or a device and are flaky on shared runners, so that job falls on you.
-
-| What you changed | What to run |
-|---|---|
-| Any Swift under `app/` or `CrocKit/` | `scripts/format-swift.sh --lint`, `swift test --package-path CrocKit`, `xcodebuild test -scheme CrocApp -destination 'platform=macOS'` (from `app/`), plus a macOS and/or iOS Simulator build |
-| Anything in `crocmobile/` | `go test -race ./...`, `golangci-lint run ./...`, `govulncheck ./...`, then `scripts/build-xcframework.sh` |
-| `crocmobile/session.go`, `CrocKit/Sources/`, or `TransferController` | the matching harness below, no exceptions |
-| Share extension | `scripts/verify-share-sim.sh` |
-| `web/landing/` | Load it locally: `cp design/tokens.css web/landing/ && python3 -m http.server --directory web/landing` |
-| Docs, comments only | Nothing beyond reading it back |
-
-```bash
-scripts/verify-interop.sh # 11 scenarios, engine <-> croc CLI, both directions, decline, cancel, relay, LAN, croc v10 peer, every pool relay
-scripts/verify-app-mac.sh # macOS app, 6 directions: both ways, --local, custom relay, --no-compress, --ask
-scripts/verify-app-sim.sh # iOS simulator, CLI -> app via --auto-receive
-scripts/verify-share-sim.sh # share-extension handoff (App Group staging) -> CLI receive
-```
-
-Builds, if you prefer the command line to Xcode:
-
-```bash
-cd app
-xcodebuild -scheme CrocApp -destination 'platform=macOS' -derivedDataPath /tmp/dd-mac build
-xcodebuild -scheme CrocApp -destination "platform=iOS Simulator,name=$SIM" -derivedDataPath /tmp/dd-sim build
-```
-
-A signed local macOS build of `CrocApp` needs a provisioning profile (the entitlements include Associated Domains), so it needs an Xcode account in team `HBSU359M33` and `-allowProvisioningUpdates` on `xcodebuild`; the macOS harness scripts pass it. Outside that team, build unsigned with `CODE_SIGNING_ALLOWED=NO`, as CI does. Details: [`docs/BUILDING.md`](docs/BUILDING.md), section 5.
-
-### Tests
-
-Non-UI logic gets unit tests, and CI runs them on every pull request that touches their code:
-
-- `crocmobile/`: Go's `testing`, run with `go test -race ./...` (from `crocmobile/`, with `GOOS`/`GOARCH` unset).
-- `CrocKit/`: Swift Testing in `CrocKit/Tests/CrocKitTests/`, run with `swift test --package-path CrocKit`.
-- The app: Swift Testing in `app/CrocAppTests/` (hosted in `CrocApp`, run on macOS) for models, parsing, error copy and settings. Tests that write settings or secrets use an injected store, never the real `UserDefaults` or Keychain.
-
-New or changed logic in those areas comes with tests. There is no XCUITest: gestures, camera and taps are checked by hand, and transfers by the harnesses above. Unit tests never replace a harness run.
-
-**Paste the harness output into the pull request.** Not a summary of it, the output.
-
-> [!IMPORTANT]
-> If you could not run something, write **"not verified"** and say why (no macOS 26 machine, no outbound network, no croc CLI). That is an honest, acceptable answer and the maintainer will run it before merge. A silent gap, or a claim that something passed when it was never run, is not. Unverified transfer-path changes are not merged, by anyone.
-
-## Docs
-
-Documentation is part of the change, not a follow-up. Before you open the PR:
-
-| What happened | Where it goes |
-|---|---|
-| You learned something durable about the project | Add or update a file in [`docs/knowledge/`](docs/knowledge/README.md), and `CLAUDE.md` if it changes how the repo is worked in |
-| You fixed a listed defect | Delete its line from [`docs/known-issues.md`](docs/known-issues.md) |
-| You found a defect and consciously left it | Add a line to `docs/known-issues.md` |
-| You noticed a stale or wrong doc | Fix or delete it in the same PR |
-
-Keep docs small, dense, direct. Never cite a `TODO.md` or `PLAN.md` from inside `docs/`; those are scratch files that get deleted, and a doc pointing at one rots immediately. Do not use "Phase N" as a fact anywhere. Say what the thing is, not when it was built.
-
-If nothing was doc-worthy, tick that box in the PR template and say so.
-
## AI-assisted contributions
-Allowed, and used heavily here. This repo carries a `CLAUDE.md` and a `.claude/` directory for exactly that reason.
-
-Two conditions:
+Allowed, and used heavily here. Two conditions:
-- **Disclose it.** If a model wrote a substantial part of the change, say so in the pull request. One line is enough. It tells the reviewer where to look harder, and nobody is judged for it.
-- **You own the code.** You understand every line, you can explain why each one is there, and you ran the [verification](#verification) yourself. "The model said it works" is not verification.
+- **Disclose it.** Own the responsibility to report even if we live in the day and age where it may be considered as norm. Fill in the AI assistance line in the PR template. It tells the reviewer where to look harder, and nobody is judged for it.
+- **You own the code.** You understand every line, you can explain why each one is there, and you ran all verifications and checks yourself. "The model said it works" is not verification.
-Bulk-generated pull requests that the author has not read, does not understand, or cannot verify get closed without review. That is a statement about unreviewed output, not about tooling.
+Bulk-generated pull requests that the author has not read, does not understand, or cannot verify get closed without review.
## Opening a pull request
-1. Fork, branch off `main`, commit as described above.
-2. Rebase on the current `main` so the diff is clean.
-3. Open the PR against `main` and fill in [the template](.github/pull_request_template.md): what and why, the verification commands with their output, the docs checklist, screenshots for UI.
+1. Fork the repo and branch off `main`.
+2. Rebase on the current `main` before opening, so the diff is clean.
+3. Open the PR against `main` with a Conventional Commits title, and fill in [the template](.github/pull_request_template.md).
4. Link the issue with `Fixes #123` when one exists.
5. Keep it focused. Two unrelated changes are two pull requests.
-Draft PRs are welcome for early feedback. Mark them as drafts so it is clear what you want.
+Draft PRs are welcome for early feedback.
-What to expect: CrocApp has one maintainer, so review time varies and there is no response-time promise. Comments will be direct and specific, and that is about the code, never about you. Merges are squashed, so your branch's intermediate commits do not need to be pretty, but the final PR title does (it becomes the commit subject, so give it the same Conventional Commits treatment).
-
-Things that get sent back rather than merged: unverified changes to the transfer path, raw hex or px in UI code, unrelated reformatting mixed into a fix, a new dependency nobody discussed, and PRs whose description does not say what was actually run.
+CrocApp has one maintainer, so review time varies and there is no response-time promise. Review comments are direct and about the code, never about you.
## Unlikely to be merged
@@ -262,14 +121,11 @@ Not a hard no, but open an issue first and expect a long conversation. These run
| Change | Why |
|---|---|
-| Windows, Linux, or Android support | Apple-platform only by design. The croc CLI covers everywhere else and interoperates with the app |
-| Support for iOS or macOS below 26 | Floor is `26.0`, never a minor. It is a minimum, so 26.0 and everything above it already runs |
+| Support for iOS or macOS below 26 | The floor is 26.0 |
| Telemetry, analytics, crash reporting, ads | No data leaves the device. This is a product promise, not a technical gap |
| A paid tier, license keys, or in-app purchases | MIT and free, permanently. Funding is optional sponsorship only |
-| Shelling out to the `croc` binary instead of the embedded library | The Go library binding is the whole architecture |
-| A new third-party dependency | Discuss it first. Everything in `crocmobile/` ships inside the binary |
| A UIKit or AppKit rewrite of a SwiftUI surface | Native SwiftUI is the deliberate choice |
-| Bundled fonts or icon sets | Undistributable in Apple's case, and unnecessary in every other |
+| Bundled fonts or icon sets | Apple's fonts are not redistributable, and SF Symbols and the system font make the rest unnecessary |
| Rewriting croc's protocol or crypto | Belongs [upstream](https://github.com/schollz/croc), where the whole ecosystem gets it |
## Licensing
@@ -277,7 +133,3 @@ Not a hard no, but open an issue first and expect a long conversation. These run
CrocApp is [MIT](LICENSE). By opening a pull request you agree that your contribution is licensed under the same terms. There is no CLA and no sign-off requirement.
Do not paste code you did not write and cannot license this way, including code from projects under a copyleft or proprietary license.
-
-## Contact
-
-Issues and [Discussions](https://github.com/bakirgdev/CrocApp/discussions) for anything public, for Code of Conduct matters, and a [private advisory](https://github.com/bakirgdev/CrocApp/security/advisories/new) for vulnerabilities.
diff --git a/README.md b/README.md
index 9745f9f..8cee14d 100644
--- a/README.md
+++ b/README.md
@@ -1,47 +1,37 @@
-
-
-
-
CrocApp
- Send a file to any device, anywhere, with a code phrase.
+ Send a file to any device, anywhere, with a code phrase.
+
A free, open-source native SwiftUI app for iPhone, iPad, and Mac.
+
-
## What it is
-CrocApp sends files, folders, and text between two devices anywhere in the world, encrypted end to end, using a short spoken code phrase like `cape-junky-widow`. No account. No upload to someone else's cloud. No requirement that both devices share a network.
+CrocApp sends files, folders, and text between two devices anywhere in the world, encrypted end to end, using a short code phrase like `apple-house-car`. No account, no cloud upload, no shared network needed.
-It is a graphical front end for [croc](https://github.com/schollz/croc), the command-line file-transfer tool by Zack Scholl. CrocApp embeds croc's Go library rather than shelling out to the binary, so **a code phrase generated by the croc CLI works in the app, and vice versa**.
+It is a graphical front end for [croc](https://github.com/schollz/croc), the command-line file-transfer tool by [Zack Scholl](https://github.com/schollz). CrocApp embeds croc's Go library rather than shelling out to the binary, so **a code phrase from the croc CLI works in the app, and the reverse**.
> [!NOTE]
-> CrocApp is unofficial and unaffiliated. Nothing here implies endorsement by croc's author.
-
-> [!IMPORTANT]
-> The other device needs **croc 11 or newer** (the CLI or CrocApp). croc 10 cannot complete a transfer with CrocApp.
+> CrocApp is unofficial and not affiliated with or endorsed by croc's author.
## Download
-CrocApp 1.0 comes through three channels. There is no package-manager channel.
-
-| Channel | Where |
+| Platform | Where |
|---|---|
-| iPhone and iPad (App Store) | Linked from [crocapp.dev](https://crocapp.dev) when the listing is live |
-| Mac (Mac App Store) | Linked from [crocapp.dev](https://crocapp.dev) when the listing is live |
-| Mac (notarized disk image, Apple silicon) | `CrocApp--arm64.dmg` on [GitHub Releases](https://github.com/bakirgdev/CrocApp/releases/latest), from version 1.0.0 |
+| iPhone, iPad, Mac (App Store) | Not listed yet; once live links will be posted |
+| Mac (Apple silicon only) | `CrocApp--arm64.dmg` from [GitHub Releases](https://github.com/bakirgdev/CrocApp/releases/latest) |
+| Mac (Homebrew cask) | Not available yet. Homebrew accepts a cask once the repo shows [enough public interest](https://docs.brew.sh/Package-Acceptance-Policy#notability): 225 stars, 90 watchers, or 90 forks. Starring the repo helps |
-From version 1.0.0 the disk image is signed with a Developer ID certificate, notarized and stapled, and it updates itself through Sparkle (**Check for Updates…** in the app menu). The App Store builds update through the App Store. Verify a download with `gh attestation verify --repo bakirgdev/CrocApp`; see [`SECURITY.md`](SECURITY.md).
-
-Install details: [crocapp.dev/docs/getting-started/install](https://crocapp.dev/docs/getting-started/install/). Or build it yourself: [Build from source](#build-from-source).
+The disk image is Developer ID signed, notarized, and updates itself through Sparkle (**Check for Updates…** in the app menu). Verify it with `gh attestation verify --repo bakirgdev/CrocApp` ([`SECURITY.md`](SECURITY.md)). Install guide: [crocapp.dev/docs/getting-started/install](https://crocapp.dev/docs/getting-started/install/). Or [build from source](#build-from-source).
## How it works
@@ -59,11 +49,11 @@ flowchart LR
X -. "fallback" .-> R
```
-1. The sender picks files, a folder, or a snippet of text.
-2. CrocApp generates a code phrase. Speak it, message it, or show it as a QR code.
-3. The receiver enters the phrase, sees the file list, and accepts.
+1. The sender picks files, a folder, or text.
+2. CrocApp shows a code phrase. Say it, message it, or show it as a QR code.
+3. The receiver enters the phrase, previews the file list, and accepts.
-Both sides run PAKE over the code phrase to derive a session key, so the phrase never crosses the network in the clear and the relay never learns the key. When both devices are on the same network, CrocApp connects directly and skips the relay entirely.
+Both sides run PAKE over the code phrase to derive a session key, so the phrase never crosses the network in the clear and the relay never learns the key. CrocApp tries the local network and the relay at once; on the same network the transfer goes direct.
## Screenshots
@@ -74,14 +64,14 @@ Both sides run PAKE over the code phrase to derive a session key, so the phrase
- macOS — send: code phrase and QR
+ macOS: send, with code phrase and QR
- iOS — receive: preview, then accept
+ iOS: receive, preview then accept
@@ -90,139 +80,91 @@ Both sides run PAKE over the code phrase to derive a session key, so the phrase
- macOS — settings: your own relay, local-only, compression
+ macOS: settings, with your own relay, local-only, compression
-## Security
+## Security and privacy
-Transfers are end-to-end encrypted, the code phrase authenticates both sides, and the relay only ever sees ciphertext. That is the entire claim.
+Transfers are end-to-end encrypted, the code phrase authenticates both sides, and the relay only ever sees ciphertext.
> [!WARNING]
-> There has been **no formal third-party security audit** of croc, and CrocApp does not claim one. Do not describe CrocApp as audited, certified, or zero-knowledge-verified.
+> Neither croc nor CrocApp has had a formal third-party security audit.
-- No telemetry, no analytics, no ads, no accounts. See the [privacy policy](https://crocapp.dev/privacy/) for every server the app can contact.
-- Transfer history is local to the device.
-- You can point CrocApp at your own croc relay instead of the default public one.
-- Vulnerabilities: report privately via [GitHub Security Advisories](https://github.com/bakirgdev/CrocApp/security/advisories/new), never a public issue.
+- No telemetry, analytics, ads, or accounts. The [privacy policy](https://crocapp.dev/privacy/) lists every server the app can contact.
+- Transfer history stays on the device.
+- You can use your own croc relay instead of the default public ones.
+- Report vulnerabilities privately through [GitHub Security Advisories](https://github.com/bakirgdev/CrocApp/security/advisories/new), never in a public issue. See [`SECURITY.md`](SECURITY.md).
## Features
-Everything below ships in 1.0. Feature numbers (`F1`…) are stable identifiers used across the codebase and issues, see [`docs/knowledge/features.md`](docs/knowledge/features.md) for the full list, including what is deferred.
-
Core transfer
-| | Feature | croc equivalent |
-|---|---|---|
-| F1 | Send files, multiple at once, drag-drop or picker | positional args |
-| F2 | Send folders | positional args |
-| F3 | Send text and clipboard contents | `--text` |
-| F4 | Receive by code phrase | positional args |
-| F5 | Custom code phrase | `--code` |
-| F6 | Show QR on send, scan QR on receive | `--qrcode` |
-| F7 | Choose output folder; Files-visible by default on iOS | `--out` |
-| F8 | Keep Both or Replace when a file already exists; resume with Replace | `--rename`, `--overwrite` |
-| F9 | Preview the incoming file list, then accept or decline | interactive prompt |
-| F10 | Live progress, speed, cancel; Live Activity on iOS | — |
-| F11 | LAN and relay raced automatically | croc default |
-| F12 | Local transfer history | — |
+| Feature | croc equivalent |
+|---|---|
+| Send files, multiple at once, drag-drop or picker | positional args |
+| Send folders | positional args |
+| Send text and clipboard contents | `--text` |
+| Receive by code phrase | positional args |
+| Custom code phrase | `--code` |
+| Show QR on send, scan QR on receive | `--qrcode` |
+| Choose output folder; Files-visible by default on iOS | `--out` |
+| Keep Both or Replace when a file already exists; resume with Replace | `--rename`, `--overwrite` |
+| Preview the incoming file list, then accept or decline | interactive prompt |
+| Live progress, speed, cancel; Live Activity on iOS | |
+| LAN and relay raced automatically | croc default |
+| Local transfer history | |
Power options
-| | Feature | croc equivalent |
-|---|---|---|
-| F13 | Custom relay, password, IPv6 | `--relay --relay6 --pass` |
-| F14 | Force local-only transfers | `--local` |
-| F15 | Disable compression | `--no-compress` |
-| F16 | Zip a folder before sending | `--zip` |
-| F17 | Exclude patterns, respect `.gitignore` | `--exclude --git` |
-| F18 | Auto-accept incoming, off by default | `--yes` |
-| F19 | Require confirmation on both sides | `--ask` |
-| F41 | Connection: Automatic or Relay only | `--transport auto\|relay` |
+| Feature | croc equivalent |
+|---|---|
+| Custom relay, password, IPv6 | `--relay --relay6 --pass` |
+| Force local-only transfers | `--local` |
+| Disable compression | `--no-compress` |
+| Zip a folder before sending | `--zip` |
+| Exclude patterns, respect `.gitignore` | `--exclude --git` |
+| Auto-accept incoming, off by default | `--yes` |
+| Require confirmation on both sides | `--ask` |
+| Connection: Automatic or Relay only | `--transport auto\|relay` |
Advanced settings
-| | Feature | croc equivalent |
-|---|---|---|
-| F20 | SOCKS5 proxy | `--socks5` |
-| F21 | HTTP proxy | `--connect` |
-| F22 | Upload speed limit | `--throttleUpload` |
-| F23 | Encryption curve | `--curve` |
-| F24 | File hash algorithm | `--hash` |
-| F25 | Sender IP, direct connect | `--ip` |
-| F26 | Multicast address (macOS) | `--multicast` |
-| F27 | Base port, parallel connections, multiplexing | `--port --transfers --no-multi` |
-| F28 | Built-in DNS resolver | `--internal-dns` |
-
-
-
-
-Native to the app
-
-| | Feature |
+| Feature | croc equivalent |
|---|---|
-| F30 | Share extension — send from any app (iOS) |
-| F31 | Menu bar quick send and Open at login (macOS) |
-| F32 | Receive links: `crocapp://`, `crocapp.dev/r/` and `getcroc.com/?code=` open Receive with the code filled in |
-| F34 | Shortcuts and Siri actions: Send Files, Send Text, Receive |
-| F36 | Trust UI: end-to-end badge, "how it works", active-relay indicator |
-| F42 | Share Link and a QR code that opens CrocApp or a page that explains how to receive |
-| F43 | Send as link: encrypted upload for a receiver who is not online, with expiry and revoke |
-| F44 | iPad menu bar: Show Receive Folder in Files |
+| SOCKS5 proxy | `--socks5` |
+| HTTP proxy | `--connect` |
+| Upload speed limit | `--throttleUpload` |
+| Encryption curve | `--curve` |
+| File hash algorithm | `--hash` |
+| Sender IP, direct connect | `--ip` |
+| Multicast address (macOS) | `--multicast` |
+| Base port, parallel connections, multiplexing | `--port --transfers --no-multi` |
+| Built-in DNS resolver | `--internal-dns` |
-## Build from source
-
-Requires **macOS 26**, **Xcode 27.0**, and **Go 1.27.2 or newer**. `gomobile` and `gobind` are rebuilt from the `go.mod` `tool` block on every run.
-
-```bash
-git clone https://github.com/bakirgdev/CrocApp.git
-cd CrocApp
-scripts/build-xcframework.sh # Go engine -> CrocKit/Croc.xcframework
-open app/CrocApp.xcodeproj
-```
-
-> [!IMPORTANT]
-> A fresh clone builds nothing until the xcframework exists. `CrocKit`'s binary target points at a gitignored artifact, so `scripts/build-xcframework.sh` is not optional.
-
-Full toolchain reference, build settings, and troubleshooting: [`docs/BUILDING.md`](docs/BUILDING.md).
-
-Command line builds, linting, and verification
-
-```bash
-# builds (from app/)
-xcodebuild -scheme CrocApp -destination 'platform=macOS' -derivedDataPath /tmp/dd-mac build
-xcodebuild -scheme CrocApp -destination "platform=iOS Simulator,name=$SIM" -derivedDataPath /tmp/dd-sim build
-
-# format and lint
-scripts/format-swift.sh # rewrite in place, then lint
-scripts/format-swift.sh --lint # lint only, same as CI
-golangci-lint run ./... # from crocmobile/
-govulncheck ./... # from crocmobile/
-```
-
-A green build is **not** evidence that a transfer works. Real behaviour is checked by harnesses that run live transfers against the croc CLI over the public relay:
-
-```bash
-scripts/verify-interop.sh # 15 scenarios, engine <-> croc CLI, both directions, croc v10 peer
-scripts/verify-app-mac.sh # macOS app, 12 scenarios
-scripts/verify-app-sim.sh # iOS simulator receive
-scripts/verify-share-sim.sh # share-extension handoff
-```
-
-Any change to `crocmobile/session.go`, `CrocKit/Sources/`, or `TransferController` needs the matching harness run.
+Native to the app
-Env: `CROC` is the croc CLI path (default `$HOMEBREW_PREFIX/bin/croc`), `SIM` is the simulator name (default `iPhone 17 Pro`; list them with `xcrun simctl list devices`).
+| Feature |
+|---|
+| Share extension: send from any app (iOS) |
+| Menu bar quick send and Open at login (macOS) |
+| Receive links: `crocapp://`, `crocapp.dev/r/` and `getcroc.com/?code=` open Receive with the code filled in |
+| Shortcuts and Siri actions: Send Files, Send Text, Receive |
+| Trust UI: end-to-end badge, "how it works", active-relay indicator |
+| Share Link and a QR code that opens CrocApp or a page that explains how to receive |
+| Send as link: encrypted upload for a receiver who is not online, with expiry and revoke |
+| iPad File menu: Show Receive Folder in Files |
@@ -231,75 +173,52 @@ Env: `CROC` is the croc CLI path (default `$HOMEBREW_PREFIX/bin/croc`), `SIM` is
How is this different from AirDrop?
-AirDrop needs two Apple devices in physical proximity. CrocApp works between any two devices running croc or CrocApp, on any network, at any distance — including from a Mac to a Linux server across the world.
+AirDrop needs two Apple devices close to each other. CrocApp works between any two devices running croc or CrocApp, on any network, at any distance, for example from an iPhone to a Linux server on another continent.
Does it work with the croc CLI?
-Yes, in both directions. CrocApp embeds croc v11.5.4 as a library, so it speaks the same protocol. `croc send` on a laptop, receive in the app on a phone, and the reverse. The other side needs croc 11 or newer; croc 10 cannot complete a transfer with it.
+Yes, in both directions. CrocApp embeds croc, so `croc send` on a laptop can be received in the app on a phone, and the reverse. The CLI must be croc 11 or newer.
-Why iOS 26 and macOS 26 only?
+Why iOS 26 and macOS 26 (or newer) only?
-Greenfield app with no existing users. Supporting older releases means availability checks everywhere and giving up current SwiftUI APIs and the Liquid Glass design language. The minimum is `26.0` and every later version works; the floor is pinned at `26.0` rather than at a minor so nobody on 26.0 through 26.4 is locked out.
+It lets the app use current SwiftUI APIs and Liquid Glass without availability checks everywhere. The minimum is 26.0, so every 26.x (or newer) release works.
Is it free? Will it ever have a paid tier?
-Free, MIT-licensed, and it stays that way. Monetization is limited to optional [sponsorship](https://github.com/sponsors/bakirgdev).
+Free and MIT-licensed, and it stays that way. Donations are welcome but optional, see [Support the project](#support-the-project).
Will there be a Windows, Linux, or Android version?
-No. CrocApp is Apple-platform only by design. Use the [croc CLI](https://github.com/schollz/croc) elsewhere — it interoperates with this app.
+Not plannet, yet. If you really want to se CrocApp be born on other platforms, you can show your interest via [issues](https://github.com/bakirgdev/CrocApp/issues)
-## Project layout
+## Contributing
-| Path | What lives there |
-|---|---|
-| `app/` | Xcode project: SwiftUI app (`CrocApp`), share extension (`CrocShare`) and the Developer ID build with Sparkle (`CrocApp Direct`) |
-| `CrocKit/` | Swift package wrapping the Go engine: `CrocEngine` actor, `AsyncStream` |
-| `crocmobile/` | Go wrapper around croc, bound into `Croc.xcframework` via gomobile |
-| `design/` | Design system: tokens, component specs, SF Symbols mapping |
-| `docs/` | Knowledge base, architecture decision records, known issues |
-| `scripts/` | Build and live-transfer verification harnesses |
-| `web/landing/` | The [crocapp.dev](https://crocapp.dev) landing page |
-| `web/docs/` | The [documentation site](https://crocapp.dev/docs/) (Docusaurus) |
-| `assets/` | Icon source, banner, mascot |
-
-## Docs
-
-| | |
-|---|---|
-| [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) | System map: layers, event flow, state ownership, platform split |
-| [`docs/BUILDING.md`](docs/BUILDING.md) | Exact toolchain, fresh-clone steps, verification harnesses, troubleshooting |
-| [`docs/GLOSSARY.md`](docs/GLOSSARY.md) | croc, bridge, app, and repo vocabulary in one line each |
-| [`docs/knowledge/`](docs/knowledge/README.md) | What croc is, how the bridge works, UI architecture, platform constraints |
-| [`docs/known-issues.md`](docs/known-issues.md) | Triaged defects and accepted papercuts. **Check here before filing a bug** |
-| [`design/`](design/CLAUDE.md) | Canonical design system for the app, landing page, and docs site |
-| [crocapp.dev/docs](https://crocapp.dev/docs/) | User documentation: install, transfers, settings, security, troubleshooting |
+Pull requests are welcome: bugs, features, docs, design, landing page. Start with [`CONTRIBUTING.md`](CONTRIBUTING.md) for setup, commit conventions, design rules, etc. Questions and ideas go to [Discussions](https://github.com/bakirgdev/CrocApp/discussions). Everyone follows the [Code of Conduct](CODE_OF_CONDUCT.md).
-## Contributing
+## Support the project
-Pull requests are welcome: bugs, features, docs, design, landing page. Start with [`CONTRIBUTING.md`](CONTRIBUTING.md). It covers setup, commit conventions, the design rules, and how changes to the transfer path have to be verified.
+CrocApp does not depend on donations: it has no paid tier and no features behind a paywall. If it saves you time and you want to give back, donations are welcome:
-| | |
-|---|---|
-| [`CONTRIBUTING.md`](CONTRIBUTING.md) | Setup, branches, commits, style, verification, PR flow |
-| [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) | Contributor Covenant 3.0 |
-| [`SECURITY.md`](SECURITY.md) | Scope and private reporting for vulnerabilities |
+- [GitHub Sponsors](https://github.com/sponsors/bakirgdev): one-time or monthly, also behind the **Sponsor** button at the top of this repo.
+- [Buy Me a Coffee](https://buymeacoffee.com/bakirgdev): one-time.
+
+Starring the repo, reporting bugs, and telling others about CrocApp help just as much.
-## Credits
+## Credits and license
-- [croc](https://github.com/schollz/croc) by [Zack Scholl](https://github.com/schollz) — the transfer engine that makes this possible. MIT.
-- CrocApp by [Bakir Gracic](https://github.com/bakirgdev). MIT.
+- [croc](https://github.com/schollz/croc) by [Zack Scholl](https://github.com/schollz), the transfer engine that makes this possible. MIT.
+- CrocApp by [Bakir Gracic](https://github.com/bakirgdev). [MIT](LICENSE).
diff --git a/SECURITY.md b/SECURITY.md
index 5d5a0d8..c73a85b 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -13,17 +13,15 @@ Useful in a report:
- Version or commit, plus OS version and device.
- Whether the same behavior reproduces with the upstream `croc` CLI.
-## Supported versions
-
-Security fixes go to the latest 1.0.x release. Older versions, including the 0.9.9 build, are not supported: update to 1.0.x first, then report anything that still reproduces.
-
## What to expect
-CrocApp has a single maintainer, so there is no guaranteed response time and no bug bounty. Reports are read and answered as soon as reasonably possible. You will be told whether the issue is accepted, and credited in the advisory when it is fixed, unless you ask not to be.
+For now, CrocApp has a single maintainer, so there is no guaranteed response time and no bug bounty. The aim is to acknowledge a report within 7 days, as a best effort. Reports are read and answered as soon as reasonably possible. You will be told whether the issue is accepted, and credited in the advisory when it is fixed, unless you ask not to be.
+
+Accepted issues get a GitHub Security Advisory, and a CVE is requested through it when the issue is eligible. Fixes are announced in the [advisories](https://github.com/bakirgdev/CrocApp/security/advisories) for this repository, and the release that carries the fix names it in [`CHANGELOG.md`](CHANGELOG.md) and in its release notes.
-Fixes are announced in the [GitHub Security Advisories](https://github.com/bakirgdev/CrocApp/security/advisories) for this repository, and the release that carries the fix names it in [`CHANGELOG.md`](CHANGELOG.md) and in its release notes.
+Please hold public disclosure for **90 days** from the report, or until a fix ships, whichever comes first. If the 90 days pass with no fix and no agreed extension, publish. Ask for an earlier date if a bug is being exploited and waiting is worse than telling people.
-Please hold public disclosure for **90 days** from the report, or until a fix ships, whichever comes first. If the 90 days pass with no fix and no agreed extension, publish. Ask for an earlier date if a bug is being exploited and waiting is worse than telling people. Findings on your own devices and your own relay are welcome. Do not test against other people's transfers, and do not attack the public relay infrastructure.
+Good-faith research that follows this policy is welcome, and the maintainer will not pursue legal action over it. This covers only CrocApp's own code and your own devices and relay. It does not cover the public relay or other people's transfers: do not test against those.
## Scope
@@ -31,7 +29,7 @@ Please hold public disclosure for **90 days** from the report, or until a fix sh
| Area | Path |
|---|---|
-| The apps and the share extension | `app/CrocApp/`, `app/CrocShare/` |
+| The apps, the direct-download updater, and the share extension | `app/CrocApp/`, `app/CrocAppDirect/`, `app/CrocShare/` |
| The Swift bridge | `CrocKit/` |
| The Go wrapper around croc | `crocmobile/` |
| Build and signing scripts, CI workflows | `scripts/`, `.github/workflows/` |
@@ -48,35 +46,9 @@ If you are unsure which side a bug lives on, report it here and it will be route
Also out of scope: reports generated by a scanner with no demonstrated impact, missing hardening that changes no attacker capability, and anything requiring a physically unlocked device that the attacker already controls.
-## Release artifacts
-
-Each release on the [releases page](https://github.com/bakirgdev/CrocApp/releases) carries four assets:
-
-| Asset | What it is |
-|---|---|
-| `CrocApp--arm64.dmg` | The Mac app, as a disk image |
-| `appcast.xml` | The update feed Sparkle reads |
-| `Croc.xcframework-.zip` | The prebuilt Go engine, for building from source |
-| `SHA256SUMS.txt` | Checksums for the disk image and the engine archive |
-
-No app zip is published. The disk image is the only download of the app.
-
-**Notarized and stapled.** The app inside the disk image is signed with a Developer ID certificate with the hardened runtime and a timestamp. The app and the disk image are each notarized by Apple and stapled, so macOS verifies them without a network connection. Check a download yourself:
-
-```bash
-xcrun stapler validate CrocApp--arm64.dmg
-spctl -a -vv -t open --context context:primary-signature CrocApp--arm64.dmg
-```
-
-**Build attestation.** The release workflow makes a [build provenance attestation](https://github.com/bakirgdev/CrocApp/attestations) for the disk image, the engine archive and `appcast.xml` after stapling, because stapling rewrites the disk image and the attestation has to cover the bytes that ship. Verify any of them before you run it:
-
-```bash
-gh attestation verify --repo bakirgdev/CrocApp
-```
-
-**Updates.** Only the disk image build updates itself, through Sparkle 2. Every update in `appcast.xml` is signed with an EdDSA (Ed25519) key that lives only as a secret of the repository's protected `release` environment and with the maintainer; the app carries the matching public key and installs an update only when the signature matches. The feed is served over HTTPS from this repository's releases, and update checks send no system profile. The App Store builds contain no Sparkle and no update feed.
+## Verifying downloads
-`SHA256SUMS.txt` ships alongside the assets and covers the disk image and the engine archive. An asset that fails any of these checks, or one that appears anywhere other than this repository's releases page, is worth reporting.
+Steps to check a release (notarization, build attestation, checksums, update signing) are in the [verify downloads guide](https://crocapp.dev/docs/verify-downloads/). An asset that fails any check, or one that appears anywhere other than this repository's releases page, is worth reporting.
## What CrocApp claims
@@ -85,4 +57,4 @@ Transfers are end-to-end encrypted, the code phrase authenticates both sides, an
> [!WARNING]
> There has been **no formal third-party security audit** of croc, and CrocApp does not claim one. Do not describe CrocApp as audited, certified, or zero-knowledge-verified.
-Supporting facts, not claims: no telemetry, no analytics, no accounts, transfer history stays on the device, and you can point the app at your own relay. Dependencies in `crocmobile/` are scanned by `govulncheck` on every CI run and again on a weekly schedule.
+Supporting facts (no telemetry, no accounts, local history, your own relay, dependency scanning) are on the [security and privacy page](https://crocapp.dev/docs/security-and-privacy/).
diff --git a/app/CrocApp/Models/TransferHistory.swift b/app/CrocApp/Models/TransferHistory.swift
index bc9b1dc..b73407e 100644
--- a/app/CrocApp/Models/TransferHistory.swift
+++ b/app/CrocApp/Models/TransferHistory.swift
@@ -97,7 +97,6 @@ final class HistoryStore {
// this throws, the environment itself is broken (e.g. out of
// memory) and there is no fallback left that would leave the
// app in a working state anyway.
- // swift-format-ignore: NeverUseForceTry
return try! ModelContainer(for: TransferRecord.self, configurations: config)
}
}
diff --git a/app/CrocApp/Support/AppInfo.swift b/app/CrocApp/Support/AppInfo.swift
index 7b684c4..75d8db7 100644
--- a/app/CrocApp/Support/AppInfo.swift
+++ b/app/CrocApp/Support/AppInfo.swift
@@ -4,9 +4,7 @@ import Foundation
/// shown in Settings and the About panel.
enum AppInfo {
// Literals checked by AppInfoTests, so there is no fallback to write.
- // swift-format-ignore: NeverForceUnwrap
static let privacyURL = URL(string: "https://crocapp.dev/privacy/")!
- // swift-format-ignore: NeverForceUnwrap
static let supportURL = URL(string: "https://crocapp.dev/support/")!
/// "1.0 (12)"; the build number is dropped when the bundle has none.
diff --git a/app/CrocApp/Views/HowItWorksView.swift b/app/CrocApp/Views/HowItWorksView.swift
index e2a4cc5..8e407a5 100644
--- a/app/CrocApp/Views/HowItWorksView.swift
+++ b/app/CrocApp/Views/HowItWorksView.swift
@@ -24,7 +24,7 @@ struct HowItWorksView: View {
)
item(
"point.3.connected.trianglepath.dotted", "Direct path for large files",
- "With Connection set to Automatic, a send of 128 MB or more also tries Tailcat: a peer-to-peer connection set up through tailcat.dev, with its own relays (*.ipn.dev) when the devices cannot reach each other. Whichever path is faster carries the transfer, still end-to-end encrypted. A send set to Relay only never contacts these servers; a receive follows the sender's choice."
+ "With Connection set to Automatic, a send of 128 MiB or more also tries Tailcat: a peer-to-peer connection set up through tailcat.dev, with its own relays (*.ipn.dev) when the devices cannot reach each other. Whichever path is faster carries the transfer, still end-to-end encrypted. A send set to Relay only never contacts these servers; a receive follows the sender's choice."
)
item(
"wifi", "Local network when possible",
diff --git a/app/CrocApp/Views/PowerSettingsSections.swift b/app/CrocApp/Views/PowerSettingsSections.swift
index d89f739..886e99f 100644
--- a/app/CrocApp/Views/PowerSettingsSections.swift
+++ b/app/CrocApp/Views/PowerSettingsSections.swift
@@ -75,7 +75,7 @@ struct PowerSettingsSections: View {
Text(Self.connectionLockedReason)
} else {
Text(
- "Automatic can carry files of 128 MB or more directly between devices. Only the sender's choice applies; a receive follows it."
+ "Automatic can carry files of 128 MiB or more directly between devices. Only the sender's choice applies; a receive follows it."
)
}
}
diff --git a/assets/CLAUDE.md b/assets/AGENTS.md
similarity index 89%
rename from assets/CLAUDE.md
rename to assets/AGENTS.md
index 33e7d56..567e593 100644
--- a/assets/CLAUDE.md
+++ b/assets/AGENTS.md
@@ -10,6 +10,7 @@ Design source and marketing assets: app icon, banner, mascot, DMG background, sc
| `croc-banner.webp` | 408x271. README header banner. From official croc repository |
| `mascot.png` | 512x512 source. Never served as-is, only as a derivative |
| `dmg-background.png` | 660x400 background for the macOS DMG's Finder window |
+| `social-preview.png` | 1280x640 GitHub social preview, uploaded by hand in repo Settings; not referenced by any file |
| `screenshots/` | `--{light,dark}.png` |
## Rules
diff --git a/assets/social-preview.png b/assets/social-preview.png
new file mode 100644
index 0000000..a20bcce
Binary files /dev/null and b/assets/social-preview.png differ
diff --git a/design/CLAUDE.md b/design/AGENTS.md
similarity index 100%
rename from design/CLAUDE.md
rename to design/AGENTS.md
diff --git a/design/brand.md b/design/brand.md
index f8c2599..d8bf85a 100644
--- a/design/brand.md
+++ b/design/brand.md
@@ -30,6 +30,7 @@ Accent is for: primary action fills, active/selected state, links, file-type gly
| Banner | `assets/croc-banner.webp` | README header |
| Screenshots | `assets/screenshots/--{light,dark}.png` | landing hero, README, docs pages. Regenerated only by `scripts/capture-screenshots.sh` — never hand-edited or hand-cropped |
| Social card | `web/landing/assets/img/og.jpg` | Open Graph / link previews. Hand-made on purpose, not derived from the tokens |
+| GitHub social preview | `assets/social-preview.png` | 1280×640, uploaded by hand in the repo's Settings → Social preview. Same layout and mascot as the social card, with the repo URL instead of the site |
| Mascot | `assets/mascot.png` | source, 512px. Never served |
| Mascot, web | `web/landing/assets/img/mascot-web.webp` | 128px derivative — landing page, empty states on web, docs 404 |
| Disk image background | `assets/dmg-background.png` | 660×400, the macOS DMG's Finder window. Spec in `components.md` → DiskImage |
diff --git a/design/tokens.css b/design/tokens.css
index e7dd644..d351e31 100644
--- a/design/tokens.css
+++ b/design/tokens.css
@@ -1,5 +1,5 @@
/* CrocApp design tokens — hand-written web mirror. Canonical values and the
- rules behind them live in design/*.md; start at design/CLAUDE.md.
+ rules behind them live in design/*.md; start at design/AGENTS.md.
Themed values are light-dark() pairs, so dark is not a second block: :root
declares `light dark` and follows the OS, [data-theme] pins it.
diff --git a/design/web.md b/design/web.md
index fcb6407..13d6032 100644
--- a/design/web.md
+++ b/design/web.md
@@ -6,7 +6,7 @@ How the tokens apply to the landing page and the docs site. `swiftui.md` is the
Web pages, not an app shell. Keep the Apple type scale, the colors and the 4pt grid; drop the device chrome. Both sites ship both themes and follow the OS preference by default.
-They consume `tokens.css`, which is why the marketing surface and the product look like one thing. Never edit a copy — `design/tokens.css` is the only committed one (`CLAUDE.md` → Three mirrors).
+They consume `tokens.css`, which is why the marketing surface and the product look like one thing. Never edit a copy — `design/tokens.css` is the only committed one (`AGENTS.md` → Three mirrors).
## Page fill
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index 94e643c..2510680 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -180,6 +180,6 @@ Swift 6 language mode (`SWIFT_VERSION = 6.0`), `SWIFT_DEFAULT_ACTOR_ISOLATION =
| iOS/macOS platform limits (background, multicast, sandbox, App Store review) | `docs/knowledge/apple-platform-constraints.md` |
| Feature status (shipped / planned / skipped) | `docs/knowledge/features.md` |
| Fresh-clone build steps | `docs/BUILDING.md` |
-| Design tokens, component specs, SF Symbols mapping | `design/CLAUDE.md` |
+| Design tokens, component specs, SF Symbols mapping | `design/AGENTS.md` |
| Term definitions (croc, PAKE, relay, etc.) | `docs/GLOSSARY.md` |
| Known defects, accepted papercuts | `docs/known-issues.md` |
diff --git a/docs/BUILDING.md b/docs/BUILDING.md
index 4d966d5..30a7fd3 100644
--- a/docs/BUILDING.md
+++ b/docs/BUILDING.md
@@ -36,7 +36,7 @@ open app/CrocApp.xcodeproj
The shared schemes `CrocApp` (builds the app, tests `CrocAppTests`), `CrocApp Direct` (the Developer ID build, section 7, no test action) and `CrocShare` are committed under `app/CrocApp.xcodeproj/xcshareddata/xcschemes/`, so `xcodebuild -scheme CrocApp` works on a fresh clone without opening Xcode. A new target needs its scheme committed there too; per-user schemes in `xcuserdata/` stay gitignored.
-Command-line builds, exactly as CLAUDE.md and CI run them:
+Command-line builds, exactly as CI runs them:
```bash
# from app/
@@ -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`, `scripts/verify-share-sim.sh`, and CLAUDE.md). List installed runtimes with `xcrun simctl list devices`.
+`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`.
## 3. What build-xcframework.sh actually does
diff --git a/docs/GLOSSARY.md b/docs/GLOSSARY.md
index 63f94d4..ae04ea6 100644
--- a/docs/GLOSSARY.md
+++ b/docs/GLOSSARY.md
@@ -82,8 +82,8 @@ Terms a new contributor or a fresh AI session hits in this repo and cannot infer
| Term | Means |
|---|---|
-| design token | a named value (color, spacing, type, motion) in `design/`, the only source of such values allowed in app or web code; no raw hex/px (`design/CLAUDE.md`). |
+| design token | a named value (color, spacing, type, motion) in `design/`, the only source of such values allowed in app or web code; no raw hex/px (`design/AGENTS.md`). |
| harness | one of the `scripts/verify-*.sh` scripts, or `crockit-verify`; proves a real transfer works against the croc CLI, since a green build alone is not evidence. |
-| self-heal | the end-of-session rule (root `CLAUDE.md`, "Session end report") that `docs/knowledge/` and `docs/known-issues.md` are updated to match reality before a session ends. |
+| self-heal | the end-of-session rule (root `AGENTS.md`, "Session end report") that `docs/knowledge/` and `docs/known-issues.md` are updated to match reality before a session ends. |
| verify script | any `scripts/verify-*.sh` harness; needs outbound network to the public relay plus a `croc` CLI; CI runs only `verify-interop.sh`, against a local relay (`docs/knowledge/tooling.md`, `CONTRIBUTING.md`). |
| xcframework build step | `scripts/build-xcframework.sh`, the required first build step on a fresh clone: Go compile + gomobile bind into `CrocKit/Croc.xcframework`. |
diff --git a/docs/knowledge/README.md b/docs/knowledge/README.md
index aacba90..da70e97 100644
--- a/docs/knowledge/README.md
+++ b/docs/knowledge/README.md
@@ -2,7 +2,7 @@
Evergreen project knowledge: what this project is, what croc is and how it works, and the invariants that outlive any individual feature. Written dense, for AI-session consumption first and humans second.
-What does not belong here: decisions and their rationale (commit messages and PR descriptions carry those), open defects and deferred work (`../known-issues.md`), and anything that is a snapshot of work in progress. Stale facts get fixed or deleted in the session that notices them (root `CLAUDE.md`, "Session end report").
+What does not belong here: decisions and their rationale (commit messages and PR descriptions carry those), open defects and deferred work (`../known-issues.md`), and anything that is a snapshot of work in progress. Stale facts get fixed or deleted in the session that notices them (root `AGENTS.md`, "Session end report").
## Files
@@ -22,4 +22,4 @@ What does not belong here: decisions and their rationale (commit messages and PR
| `screenshots.md` | you need to re-capture, add, or move an app screenshot |
| `app-store-listing.md` | you prepare a store submission: listing copy, review notes, export compliance |
-Anything visual — app views, landing page, docs site — starts at `design/CLAUDE.md` instead.
+Anything visual — app views, landing page, docs site — starts at `design/AGENTS.md` instead.
diff --git a/docs/knowledge/app-store-listing.md b/docs/knowledge/app-store-listing.md
index b38512c..2206280 100644
--- a/docs/knowledge/app-store-listing.md
+++ b/docs/knowledge/app-store-listing.md
@@ -70,7 +70,7 @@ WHAT YOU CAN DO
- Receive with a code phrase, a scanned QR code, or a link. Opening a link only fills in the code; nothing connects until you tap Receive.
- See the file list before you accept, and choose Keep Both or Replace when a name is already taken.
- Watch progress and speed, cancel at any time, and resume an interrupted transfer.
-- Send as a link: upload encrypted files for the receiver to download later, with a lifetime of 1 hour, 1 day or 7 days and a limit on downloads (up to 100 files and 1 GB). You can revoke the link from History.
+- Send as a link: upload encrypted files for the receiver to download later, with a lifetime of 1 hour, 1 day or 7 days and a limit on downloads (up to 100 files and 1 GiB). You can revoke the link from History.
- Use your own relay, force local-network-only transfers, or route through a proxy, from Settings.
- iPhone and iPad: send from any app with the share sheet, follow a transfer in a Live Activity, find received files in the Files app, and use Shortcuts and Siri actions.
- Mac: drag and drop, a menu bar window for quick sends, and Shortcuts actions.
@@ -111,7 +111,7 @@ New
Changed
- The transfer engine is now croc 11.5.4. A device running croc 10 can no longer transfer with CrocApp; it needs croc 11 or newer.
- Errors say what went wrong: a wrong code, a refused transfer, a lost connection, a peer on an older croc, or no public relay available.
-- The relay password is kept in the Keychain, and History keeps only the first part of a code phrase.
+- History keeps less of a code than before, and never the whole code.
- Every screen works at the largest text sizes and with VoiceOver.
- The QR code on the sender's screen now opens CrocApp, or a page that explains what to do when the app is missing.
diff --git a/docs/knowledge/tooling.md b/docs/knowledge/tooling.md
index dee51c8..be81fe2 100644
--- a/docs/knowledge/tooling.md
+++ b/docs/knowledge/tooling.md
@@ -19,6 +19,7 @@ What is wired up, what was deliberately skipped, and the traps in each.
| pnpm 12 | `packageManager` in `web/docs/package.json` (CI: `corepack enable pnpm` reads it) | docs site, local + CI |
| Docusaurus 3.10, React 19.3, TypeScript 7 | `web/docs/package.json`, `pnpm-lock.yaml`; `pnpm-workspace.yaml` holds `overrides` for transitive advisories | docs site, local + CI (`pnpm run typecheck` in `github-pages.yml`) |
| Sparkle 2.10.0 (SwiftPM, `CrocApp Direct` only; auto-update for the direct-download build) | `Package.resolved` under `app/CrocApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/`, minimum in the `XCRemoteSwiftPackageReference` in `project.pbxproj`; Dependabot does not cover Xcode-project pins, so bump by hand | `CrocApp Direct` build, local + CI (unsigned build step) |
+| MCP servers `xcode` (`xcrun mcpbridge`) and `gopls` (`gopls mcp`) for AI sessions | `.mcp.json`, enabled in `.claude/settings.json`; both wrapped in `pnpx -y caveman-shrink`, deliberately: the maintainer measured a real cut in tokens spent on tool output. Keep the wrapper | local AI sessions |
| GitHub Actions | `.github/workflows/ci.yml` | push to main, every PR; a `changes` job skips the jobs whose area is untouched; `interop (local relay)` runs `scripts/verify-interop.sh` on `macos-26` against a local `croc relay` |
| GitHub Actions | `.github/workflows/go-weekly.yml` | Mondays 06:00 UTC + manual |
| GitHub Actions | `.github/workflows/github-pages.yml` | PRs build only; push to main (`web/landing/**`, `web/docs/**`, `design/tokens.css`) also deploys |
@@ -45,14 +46,9 @@ CI gotchas:
## swift-format traps
-- **Default config does not fit this repo.** `indentation: 2` and `indentConditionalCompilationBlocks: true` are the swift-format defaults; both are wrong here. The `#if os(macOS)` bodies all over `Platform/` would gain a level of indent, producing a ~500-warning reformat that also fights Xcode's editor (Xcode indents from its own text-editing prefs, not `.swift-format`). Repo uses 4 spaces, unindented `#if` bodies.
+- **The config holds only deviations from swift-format's defaults**, and there are two: `indentation: 4` (swift-format defaults to 2, Xcode to 4, and Xcode's editor indents from its own text-editing prefs, not `.swift-format`) and `indentConditionalCompilationBlocks: false` (the `#if os(macOS)` bodies all over `Platform/` stay unindented, as Xcode leaves them). Measured against the 77 files in the source set: pure defaults give 7,940 findings, `indentation: 4` alone 1,362, both 0. Every other rule and knob is swift-format's default, so `xcrun swift-format dump-configuration` prints the full effective set. Add a key only for a deliberate, agreed deviation; restating a default hides the real decisions and freezes the default against upstream changes on an Xcode bump. The formatter binary itself is pinned by `.xcode-version`, which the CI job reads, so a changed default surfaces as a `lint --strict` failure on that bump, not silently.
- **Several enabled rules are lint-only**: they flag but never rewrite, so `format --in-place` leaves them and they must be fixed by hand. CI runs `lint --strict`, which turns every such warning into a build failure, so none may be left behind. Verified lint-only: `ReplaceForEachWithForLoop` (`.forEach { }` → for-in loop), `OnlyOneTrailingClosureArgument` (a call mixing a closure argument such as `onDismiss:` with a trailing closure → pass the trailing closure as an explicit `content:` argument), `AlwaysUseLowerCamelCase`, `TypeNamesShouldBeCapitalized`, `DontRepeatTypeInStaticProperties`, `NoBlockComments`, `AvoidRetroactiveConformances`. A clean `format` run is not evidence CI will pass; run `lint --strict` too.
-- **Every rule in the config is listed explicitly**, enabled and disabled alike, in one alphabetical block, and every non-default config knob is pinned. Checked against `xcrun swift-format dump-configuration`: all 43 rules and all knobs the Xcode 27 toolchain knows are present, no unknown keys, except `multilineTrailingCommaBehavior` (left at its default `keptAsWritten`). Xcode 27 made `orderedImports.shouldGroupImports` mandatory: a missing key fails every file with "Unable to read configuration". A rule swift-format adds in a future release still lands on its own default, but nothing already listed can shift underneath a contributor. The formatter binary itself is pinned by `.xcode-version`, which the CI job reads.
- **The source set lives in `scripts/format-swift.sh`, not in the config.** swift-format has no "which files" setting, so the path list used to be copy-pasted into CI, the PR template, `README.md`, `CONTRIBUTING.md` and here — and drifted: `CrocKit/Package.swift` was never linted by any of them. One script now owns it. `CrocKit` is enumerated file-by-file rather than passed as a directory, because `--recursive` there descends into `.build/` and `Croc.xcframework/`.
-- `UseSynthesizedInitializer` is off. It flags hand-written memberwise inits that SwiftData/`@Model` types need.
-- `NeverForceUnwrap` and `NeverUseForceTry` are **on** as of 2026-08-10. They used to be off for two sites; one is gone (`CrocEngine.crocOptions` now does `guard let ... else throw` on `CrocmobileNewOptionsDefault()`), and the other, `HistoryStore.makeContainer`, carries a `// swift-format-ignore: NeverUseForceTry` on the single line. That `try!` sits inside the *catch* branch that already fell back to a memory-only store and the return type is non-optional, so there is nothing left to fall back to. Suppressing per line makes the exception enforced instead of a prose comment nobody checks. Both rules were zero-finding on the rest of the tree when enabled.
-- `AllPublicDeclarationsHaveDocumentation` (41 violations) and `BeginDocumentationCommentWithOneLineSummary` (52) are off. `CrocKit` is a public package, so both are worth enabling once the doc comments exist.
-- `UseEarlyExits`, `OmitExplicitReturns` and `UseWhereClausesInForLoops` are off deliberately. The first two are zero-finding today and could be flipped for free, but neither is settled house style. The third has one site, `ShareStager.swift:51`, whose `guard ... else { continue }` reads better than the `where` clause the rule wants.
## golangci-lint traps
@@ -75,7 +71,6 @@ These cost real debugging time on this machine. All are environment quirks, not
| Tool | Revisit when |
|---|---|
-| SwiftLint | more than one person writes Swift here |
| Periphery | pre-1.0 dead-code sweep. Can use the committed `CrocApp` scheme; false-positives on gomobile bindings + SwiftUI reflection |
| Danger | after main is PR-only |
| Tuist / XcodeGen | first real `.pbxproj` merge conflict (Kintsugi is the lighter answer) |
diff --git a/web/docs/content/faq.md b/web/docs/content/faq.md
index d93453d..7262562 100644
--- a/web/docs/content/faq.md
+++ b/web/docs/content/faq.md
@@ -32,7 +32,7 @@ Then the transfer goes straight between the two devices and skips the relay enti
## Can large files go directly between devices?
-Yes, with the default **Automatic** connection setting. A send of 128 MB or more also tries a direct path, set up through `tailcat.dev`, and whichever path connects first carries the transfer. It is end-to-end encrypted either way. Choose **Relay only** under [power options](guide/power-options.md) to keep your sends on croc's relays and away from those servers. A receive follows the sender's choice.
+Yes, with the default **Automatic** connection setting. A send of 128 MiB or more also tries a direct path, set up through `tailcat.dev`, and whichever path connects first carries the transfer. It is end-to-end encrypted either way. Choose **Relay only** under [power options](guide/power-options.md) to keep your sends on croc's relays and away from those servers. A receive follows the sender's choice.
## Can the receiver use a browser instead of the app?
diff --git a/web/docs/content/getting-started/install.md b/web/docs/content/getting-started/install.md
index 3509309..6bc3adc 100644
--- a/web/docs/content/getting-started/install.md
+++ b/web/docs/content/getting-started/install.md
@@ -23,10 +23,11 @@ Open the disk image from GitHub Releases and drag **CrocApp** onto **Application
To check that a download came from this project's release workflow, run:
```bash
-gh attestation verify CrocApp--arm64.dmg --repo bakirgdev/CrocApp
+gh attestation verify CrocApp--arm64.dmg --repo bakirgdev/CrocApp \
+ --signer-workflow bakirgdev/CrocApp/.github/workflows/release.yml
```
-See [`SECURITY.md`](https://github.com/bakirgdev/CrocApp/blob/main/SECURITY.md) for what the attestation covers.
+See [Verify downloads](../verify-downloads.md) for what the attestation covers and the other checks.
## Updates
diff --git a/web/docs/content/guide/history.md b/web/docs/content/guide/history.md
index 097ed8c..5d22a4c 100644
--- a/web/docs/content/guide/history.md
+++ b/web/docs/content/guide/history.md
@@ -17,14 +17,14 @@ For each finished or failed transfer:
- Direction (sent or received) and whether it was a text transfer
- Up to 20 file names
- Total file count and total size
-- A **code hint**: only the first segment of the code phrase (for example, "7291-…"), never the full phrase
+- A **code hint**: the first word of a code shaped like a generated one (for example, "wiry-…"), or only the first two characters of any other code, never the full phrase. A Send as link transfer keeps the server's host instead (see [Stored uploads](#stored-uploads))
- Date and status (completed, failed, cancelled, or declined)
- For sends, a set of file bookmarks used to support **Send Again** — captured all-or-nothing, and only up to 200 items; a record with no bookmarks simply doesn't offer Send Again
## What's never recorded
- File contents
-- The full code phrase — only that first-segment hint
+- The full code phrase — only that hint
- Anything beyond the 20-name and 200-bookmark caps
## Stored uploads
diff --git a/web/docs/content/guide/power-options.md b/web/docs/content/guide/power-options.md
index 07ba5e9..3392404 100644
--- a/web/docs/content/guide/power-options.md
+++ b/web/docs/content/guide/power-options.md
@@ -29,11 +29,11 @@ The **Address** and **IPv6 address** fields read "Public relay pool" as placehol
The **Connection** picker has its own section after Relay. **Automatic** is the default. **Relay only** sends every transfer through croc's relays.
-With **Automatic**, a send of 128 MB or more also tries a direct path between the two devices. This path is called Tailcat. It is set up through `tailcat.dev`, and it uses its own relays (`*.ipn.dev`) when the devices cannot reach each other. CrocApp starts it next to croc's relay, and whichever path connects first carries the transfer. The data is still end-to-end encrypted on either path. Smaller sends never start it.
+With **Automatic**, a send of 128 MiB or more also tries a direct path between the two devices. This path is called Tailcat. It is set up through `tailcat.dev`, and it uses its own relays (`*.ipn.dev`) when the devices cannot reach each other. CrocApp starts it next to croc's relay, and whichever path connects first carries the transfer. The data is still end-to-end encrypted on either path. Smaller sends never start it.
With **Relay only**, a send never contacts `tailcat.dev` or the `*.ipn.dev` servers. A receive can still use them when the sender chose Automatic (or the croc CLI's default, `auto`).
-Only the sender's choice applies. A receive follows whatever the sender uses, so the picker has no effect when you receive. The picker is disabled while **Local network only** is on, because local-only transfers always use **Automatic**. The footer under the picker reads: "Automatic can carry files of 128 MB or more directly between devices. Only the sender's choice applies; a receive follows it."
+Only the sender's choice applies. A receive follows whatever the sender uses, so the picker has no effect when you receive. The picker is disabled while **Local network only** is on, because local-only transfers always use **Automatic**. The footer under the picker reads: "Automatic can carry files of 128 MiB or more directly between devices. Only the sender's choice applies; a receive follows it."
## Local network only (F14)
diff --git a/web/docs/content/guide/settings-and-trust.md b/web/docs/content/guide/settings-and-trust.md
index b0ed112..3fdea6b 100644
--- a/web/docs/content/guide/settings-and-trust.md
+++ b/web/docs/content/guide/settings-and-trust.md
@@ -29,7 +29,7 @@ The **Send as link** section holds the **Server** field for [stored transfers](s
## Connection
-The **Connection** picker under the [power options](power-options.md) chooses how a send carries large files. **Automatic** (the default) can also use a direct path between the devices for a send of 128 MB or more, set up through `tailcat.dev`. A send set to **Relay only** never contacts those servers; a receive follows the sender's choice. The data is end-to-end encrypted either way.
+The **Connection** picker under the [power options](power-options.md) chooses how a send carries large files. **Automatic** (the default) can also use a direct path between the devices for a send of 128 MiB or more, set up through `tailcat.dev`. A send set to **Relay only** never contacts those servers; a receive follows the sender's choice. The data is end-to-end encrypted either way.
## Advanced
diff --git a/web/docs/content/reference/croc-cli-interop.md b/web/docs/content/reference/croc-cli-interop.md
index 3c7ddeb..c07848a 100644
--- a/web/docs/content/reference/croc-cli-interop.md
+++ b/web/docs/content/reference/croc-cli-interop.md
@@ -51,7 +51,7 @@ The **Connection** setting maps to croc's `--transport` flag: **Automatic** is `
croc send --transport relay file.txt
```
-For a send under 128 MB, the two settings behave the same. See [power options](../guide/power-options.md) for what **Automatic** does above that size.
+For a send under 128 MiB, the two settings behave the same. See [power options](../guide/power-options.md) for what **Automatic** does above that size.
## Feature-to-flag mapping
diff --git a/web/docs/content/reference/glossary.md b/web/docs/content/reference/glossary.md
index 396543d..800c587 100644
--- a/web/docs/content/reference/glossary.md
+++ b/web/docs/content/reference/glossary.md
@@ -18,7 +18,7 @@ description: User-facing terms used throughout the CrocApp documentation.
| Hash algorithm | The integrity check used on transferred files. |
| LAN peer discovery | The mechanism that lets same-network transfers skip the relay entirely; not guaranteed to work on every network. |
| Connection | The setting that chooses how a send carries large files: Automatic (default) or Relay only. Only the sender's choice applies; a receive follows it. |
-| Tailcat | The direct, peer-to-peer path that Automatic can use for a send of 128 MB or more. It is set up through `tailcat.dev` and falls back to its own relays when the devices cannot reach each other. Still end-to-end encrypted. |
+| Tailcat | The direct, peer-to-peer path that Automatic can use for a send of 128 MiB or more. It is set up through `tailcat.dev` and falls back to its own relays when the devices cannot reach each other. Still end-to-end encrypted. |
| Local-only | A setting that forces a transfer to stay on the local network, refusing the relay if a direct path isn't available. |
| Compression | File data is compressed by default in transit; it can be turned off. |
| Menu bar quick send | The CrocApp icon in the Mac menu bar. Its window sends files or clipboard text and shows the code and progress. See [Menu bar](../guide/menu-bar.md). |
diff --git a/web/docs/content/security-and-privacy.md b/web/docs/content/security-and-privacy.md
index cb149ff..7ddcfa9 100644
--- a/web/docs/content/security-and-privacy.md
+++ b/web/docs/content/security-and-privacy.md
@@ -20,7 +20,7 @@ There has been **no formal third-party security audit** of croc, and CrocApp doe
- **Strong keys from short codes.** Both devices run PAKE (password-authenticated key exchange) to turn the code phrase into a strong encryption key. The code itself never crosses the network, and a wrong code fails immediately.
- **End-to-end encrypted.** Everything is encrypted on your device and decrypted only on the other one. Nothing in between can read it.
- **The relay is just a pipe.** When devices can't connect directly, an internet relay forwards the encrypted stream. The relay never has the key — it sees only ciphertext. You can also run your own relay and set it in settings.
-- **A direct path for large files.** With the default **Automatic** connection setting, a send of 128 MB or more also tries a peer-to-peer connection, set up through `tailcat.dev`, with its own relays (`*.ipn.dev`) when the devices cannot reach each other. Whichever path connects first carries the transfer, and it is end-to-end encrypted on either path. Those servers are contacted only for such a send. Choose **Relay only** under [power options](guide/power-options.md) and your sends never contact them. A receive follows the sender's choice, so receiving a large file from a sender on Automatic (or the croc CLI's default) can use them.
+- **A direct path for large files.** With the default **Automatic** connection setting, a send of 128 MiB or more also tries a peer-to-peer connection, set up through `tailcat.dev`, with its own relays (`*.ipn.dev`) when the devices cannot reach each other. Whichever path connects first carries the transfer, and it is end-to-end encrypted on either path. Those servers are contacted only for such a send. Choose **Relay only** under [power options](guide/power-options.md) and your sends never contact them. A receive follows the sender's choice, so receiving a large file from a sender on Automatic (or the croc CLI's default) can use them.
- **Local network when possible.** Devices on the same network transfer directly; the data never leaves your network. The local path and the relay race, and the faster one wins.
- **Stored uploads talk to one server.** [Send as link](guide/stored-transfers.md) encrypts the files on your device and uploads them to the server named in Settings (by default `https://getcroc.com`, run by croc's author, not by CrocApp). The key is in the part of the link after the `#`, which is never sent to the server, so the server holds ciphertext it cannot read. A stored upload contacts that server and nothing else. Anyone with the whole link can decrypt, and you can revoke an upload from [History](guide/history.md).
- **Links carry the code in the fragment.** A `https://crocapp.dev/r/#` link keeps the code after the `#`, which browsers never send to a server. The page at that address makes no network request and sets a `no-referrer` policy. Anyone who sees the link still has the code, so share it the way you would share the code.
@@ -35,8 +35,8 @@ The link page offers **Receive in your browser**, which opens `https://getcroc.c
- No telemetry, no analytics, no ads, no accounts.
- Transfer history is local to the device — see [History](guide/history.md) for exactly what it records.
- You can point CrocApp at your own croc relay instead of the default public one.
-- The disk image build checks github.com for updates through Sparkle and installs only updates whose signature matches the key built into the app; it sends no system profile. The App Store builds contain no updater.
-- Dependencies in the Go engine are scanned by `govulncheck` on every CI run and again on a weekly schedule.
+- The disk image build checks github.com for updates through Sparkle and installs only updates whose signature matches the key built into the app; it sends no system profile. See [Verify downloads](verify-downloads.md) for how to check a download. The App Store builds contain no updater.
+- Dependencies in the Go engine (`crocmobile/`) are scanned by `govulncheck` in CI when the Go code or the Go CI workflows change, and on a weekly schedule.
## Privacy policy
diff --git a/web/docs/content/verify-downloads.md b/web/docs/content/verify-downloads.md
new file mode 100644
index 0000000..1f2e26b
--- /dev/null
+++ b/web/docs/content/verify-downloads.md
@@ -0,0 +1,52 @@
+---
+sidebar_position: 5.5
+title: Verify Downloads
+description: What each CrocApp release asset is, and how to check a download is notarized, built by the release workflow, and unmodified.
+---
+
+# Verify downloads
+
+Each release on the [releases page](https://github.com/bakirgdev/CrocApp/releases) carries four assets:
+
+| Asset | What it is |
+|---|---|
+| `CrocApp--arm64.dmg` | The Mac app, as a disk image |
+| `appcast.xml` | The update feed Sparkle reads |
+| `Croc.xcframework-.zip` | The prebuilt Go engine, for building from source |
+| `SHA256SUMS.txt` | Checksums for the disk image and the engine archive |
+
+No app zip is published. The disk image is the only download of the app.
+
+## Notarized and stapled
+
+The app inside the disk image is signed with a Developer ID certificate with the hardened runtime and a timestamp. The app and the disk image are each notarized by Apple and stapled, so macOS verifies them without a network connection. Check a download yourself:
+
+```bash
+xcrun stapler validate CrocApp--arm64.dmg
+spctl -a -vv -t open --context context:primary-signature CrocApp--arm64.dmg
+```
+
+## Build attestation
+
+The release workflow makes a [build provenance attestation](https://github.com/bakirgdev/CrocApp/attestations) for the disk image, the engine archive and `appcast.xml` after stapling, because stapling rewrites the disk image and the attestation has to cover the bytes that ship. Verify any of them before you run it:
+
+```bash
+gh attestation verify --repo bakirgdev/CrocApp \
+ --signer-workflow bakirgdev/CrocApp/.github/workflows/release.yml
+```
+
+The `--signer-workflow` flag is the recommended form: it proves the file came from the release workflow, not just any workflow in the repository. Without it, `gh attestation verify --repo bakirgdev/CrocApp` checks only the repository.
+
+## Checksums
+
+`SHA256SUMS.txt` ships alongside the assets and covers the disk image and the engine archive.
+
+## Updates
+
+Only the disk image build updates itself, through Sparkle 2. Every update in `appcast.xml` is signed with an EdDSA (Ed25519) key. The private key is a secret of the repository's `release` environment, which runs only after the maintainer approves the run, plus the maintainer's own copy. CrocApp has one maintainer, so no second person reviews that approval. The app carries the matching public key and installs an update only when the signature matches.
+
+The feed is served over HTTPS from this repository's releases, and update checks send no system profile. The App Store builds contain no Sparkle and no update feed.
+
+## If a check fails
+
+An asset that fails any of these checks, or one that appears anywhere other than this repository's releases page, is worth reporting. See the [security policy](https://github.com/bakirgdev/CrocApp/blob/main/SECURITY.md) and [Security and privacy](security-and-privacy.md).
diff --git a/web/landing/index.html b/web/landing/index.html
index 595d995..84e1733 100644
--- a/web/landing/index.html
+++ b/web/landing/index.html
@@ -675,7 +675,7 @@
Contribute
After that: VoiceOver and Dynamic Type passes, translations once the copy settles, and
- anything in the issue tracker marked as a good first issue.
+ anything in the issue tracker marked help wanted.
diff --git a/web/landing/llms.txt b/web/landing/llms.txt
index 7ac232f..618925e 100644
--- a/web/landing/llms.txt
+++ b/web/landing/llms.txt
@@ -24,7 +24,7 @@ CrocApp is a graphical front end for [croc](https://github.com/schollz/croc), th
2. CrocApp generates a code phrase, for example `cape-junky-widow`. It can be spoken, messaged, or shown as a QR code.
3. The receiver enters the phrase, sees the file list, and accepts. The transfer begins.
-Both sides run PAKE over the code phrase to derive a session key, so the phrase itself never crosses the network in the clear and the relay never learns the key. Files are encrypted before they leave the device. When both devices are on the same network, CrocApp connects directly and skips the relay; otherwise a relay forwards ciphertext between them and does not store anything. Users can point CrocApp at their own croc relay. With the default Automatic connection setting, a send of about 128 MB or more also tries a direct connection set up through tailcat.dev. Send as link uploads client-side-encrypted files to a stored-transfer server (getcroc.com by default) and shares a link; the decryption key stays in the part of the link after the #.
+Both sides run PAKE over the code phrase to derive a session key, so the phrase itself never crosses the network in the clear and the relay never learns the key. Files are encrypted before they leave the device. When both devices are on the same network, CrocApp connects directly and skips the relay; otherwise a relay forwards ciphertext between them and does not store anything. Users can point CrocApp at their own croc relay. With the default Automatic connection setting, a send of 128 MiB or more also tries a direct connection set up through tailcat.dev. Send as link uploads client-side-encrypted files to a stored-transfer server (getcroc.com by default) and shares a link; the decryption key stays in the part of the link after the #.
## Security claim, stated precisely
diff --git a/web/landing/privacy/index.html b/web/landing/privacy/index.html
index 51fb9a2..e7a8ed5 100644
--- a/web/landing/privacy/index.html
+++ b/web/landing/privacy/index.html
@@ -152,7 +152,7 @@
getcroc.com, the browser client
Tailcat and DERP servers
When: the Connection setting is Automatic by default. With it, a
- send of about 128 MB or more also tries a direct device-to-device connection set up
+ send of 128 MiB or more also tries a direct device-to-device connection set up
through tailcat.dev, with relay servers under
ipn.dev when the devices cannot reach each other
directly. Whichever path connects first carries the transfer. A receive follows the
@@ -211,7 +211,8 @@
On your device
History. Transfer history stays on your device. There is no sync and
no export. It records file names (up to 20 per transfer), counts, sizes, dates, status
- and only the first segment of the code phrase, never file contents or the full phrase.
+ and a short hint of the code phrase (its first word, or only its first two characters
+ for most custom codes), never file contents or the full phrase.
For a stored upload it keeps the server's host name, not the link or the upload token.
Clear it any time from the history screen.