agentsclimarketplace

Design handoff

Skill markdavidgan/apple-dev-skills/platforms/cursor/skills/design-handoff

Produce a current, labeled screenshot package of a whole app experience for an external design reviewer (Claude Design, a human designer, or any image-consuming tool), reusing the project's existing screenshot UITest target and fastlane snapshot lane. Use when you need an up-to-date visual handoff of an iOS app's UI states, refresh a stale handoff after UI changes, or bundle screenshots plus design context for upload. Has an authoritative simulator-capability guard so it never tries to capture on a machine that may not run the simulator.From its SKILL.md

Install
npx -y skills add markdavidgan/apple-dev-skills --skill design-handoff

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

10.0 KB, ~2.3k tokens by cl100k_base, as published. Nobody here has run it

Design Handoff

Purpose: Turn an app's real UI states into a current, captioned screenshot package that an external reviewer can consume — without inventing a parallel screenshot mechanism, and without ever capturing on a machine that may not run the simulator. Trigger: You need to hand an app's UI to Claude Design / a designer / an image-consuming tool, and the committed screenshots are missing or stale.

This skill is the generic engine. A project binds it through a thin overlay skill (generated by overlay-sync) that supplies the app list, schemes, paths, and a design-context brief. The engine never hard-codes a project.

Core principle: reuse, never duplicate

Most iOS apps already have a screenshot path — a UITest target driven by fastlane snapshot (capture_ios_screenshots), a seeding mechanism for deterministic states, and stable accessibility identifiers. This skill drives that existing path. It does not create a second harness, a second seeding system, or a -STATE enum. If the app's deterministic states live in a seeder enum, the manifest references those enum cases by name. If a needed state is missing, you extend the existing seeder/UITest target — you do not start a parallel one. (See the "never keep multiple versions of a feature" rule that most Apple projects in this org enforce.)

Phases

/design-handoff <app> [phase] [--dest <dir>]
PhaseWhere it may runDoes
prepareany host (incl. capture-forbidden)Reconcile the manifest against the app's screenshot UITest target — report/author the test methods that produce each manifest shot. No simulator.
capturecapture-capable host onlyRun the host guard, then the project's existing fastlane screenshot lane → raw PNGs.
packageany hostCollect the raw PNGs into latest/, archive the previous set, write captions + design context + the reviewer prompt, stamp provenance.
bundleany hostZip latest/ and copy it to --dest.
allcapture-capable hostcapture then package (then bundle if --dest is given).

Default phase is package if raw screenshots already exist, otherwise the skill tells you to run capture on a capable host first.

Step 1 — Simulator-capability guard (REQUIRED before capture/all)

You cannot reliably probe whether the simulator will destabilize this machine. An explicit opt-out therefore always wins over any hardware probe. Run the shipped guard, which echoes one of forbidden | unavailable | insufficient | capable:

bash "<skill-dir>/scripts/host-guard.sh"
ResultAction
forbiddenA marker (.claude/NO_SIMULATOR.md, $AETHER_NO_SIMULATOR, ~/.config/no-simulator) opts this machine out. Stop. Never override. Tell the user to run capture on a capture-capable Mac, then package anywhere.
unavailableNot macOS, no Xcode, or no installed runtime. Cannot capture here.
insufficientBelow the RAM/disk floor. Warn; prefer a capable host.
capableProceed to capture.

The marker is authoritative. If unsure, treat as forbidden. This is the same ladder preview-capture uses — one convention across the toolkit.

Step 2 — The manifest (<app screenshots dir>/shots.yaml)

Single source of truth for "which states matter for review." One entry per shot. The manifest maps each shot to the app's existing deterministic state mechanism — do not invent launch flags the app does not already read.

# Captions double as the reviewer's per-image context.
scheme: AppName-Screenshots          # the existing fastlane snapshot scheme
seedMechanism: launchArgScenario      # how this app stages state (documentation only)
shots:
  - id: "01_TimerIdle"               # MUST equal the snapshot("<id>") name in the UITest
    title: "Timer — idle"
    caption: "Resting dial at 25:00; first-run hint visible."
    scenario: "freshInstall"          # an existing seeder case, not a new one
    nav: []                           # accessibility-id taps after launch, in order
  - id: "03_QuickCapture"
    title: "Capture — typing"
    caption: "Thought-parking sheet open with sample text."
    scenario: "multipleCaptures"
    nav: ["captureButton", "type:captureTextField:Email the team"]
devices: ["iPhone 17 Pro Max"]        # informational; the Snapfile owns the real device list
# States worth adding later but not yet in the UITest target — never silently dropped:
wishlist:
  - "Completion moment"
  - "Session detail / ThoughtVerse"

A template lives at <skill-dir>/templates/shots.example.yaml.

Step 3 — prepare: reconcile manifest ↔ UITest target

  1. Read shots.yaml and the app's existing screenshot UITest file (the one the scheme builds).
  2. For every shot whose id has no matching snapshot("<id>") call, author one test method following the file's established pattern (seed the scenario, launch, apply nav, wait on the relevant accessibility id, then snapshot("<id>")). Reuse the existing seeding helper (e.g. TestDataSeeder/setupSnapshot) — do not introduce a new one.
  3. Report a diff: shots added, updated, already present, and any manifest id you could not map (with why). prepare is idempotent — an unchanged manifest produces no edits.
  4. Commit the UITest changes. Never run the UITests here (capture happens in its own phase, on a capable host).

If the app has no screenshot UITest target yet, say so and point the user at the project's UITest conventions; scaffold one only with explicit approval.

Step 4 — capture (capable host only)

After the guard passes, run the project's existing lane (the overlay supplies the exact command), e.g.:

cd <app dir> && bundle exec fastlane ios_screenshots

Raw PNGs land wherever the project's Snapfile/lane writes them (commonly fastlane/screenshots/<locale>/). Do not reimplement capture; if the project also has a Watch lane and the manifest has Watch shots, run that too. Never run plain UI tests as part of this — capture is a render pass, not a test pass.

Step 5 — package

Run the shipped packager, then write the prose:

node "<skill-dir>/scripts/package-handoff.mjs" \
  --raw "<dir of captured PNGs>" \
  --out "<app dir>/design-handoff" \
  --scheme "<scheme>" \
  --shots "<app screenshots dir>/shots.yaml"

The packager (deterministic file plumbing only):

  • Archives the current design-handoff/latest/ into design-handoff/archive/<date>-<sha>/ (gitignored history).
  • Copies the captured PNGs into design-handoff/latest/.
  • Writes latest/STAMP.txt — git SHA, marketing + build version (best-effort), capture date, scheme, image count — so a stale set can never again pass as current.
  • Emits latest/manifest.json (id → caption) for downstream tools.

Then you write the prose into latest/ (the packager does not fabricate these):

  • manifest.md — each image mapped to its caption.
  • <app>-design-context.md — a code-grounded brief (design tokens, per-screen intent). The overlay supplies the app's design language; ground every claim in the actual code, never invent palette/spacing.
  • CLAUDE-DESIGN-PROMPT.md — the paste-in reviewer prompt + the explicit upload list + "point the reviewer at the scoped <app dir>/ subdirectory, not the whole monorepo." Start from <skill-dir>/templates/CLAUDE-DESIGN-PROMPT.template.md.

Honesty rule: if some manifest shots were not captured (forbidden host, wishlist states), package lists them as missing rather than shipping a handoff that looks complete. Never claim visual parity you cannot see.

Step 6 — bundle (optional)

node "<skill-dir>/scripts/package-handoff.mjs" --zip "<app dir>/design-handoff/latest" --dest "<dir>"

Zips latest/ to <app>-handoff-<version>-<sha>.zip and copies it to --dest (default ~/Desktop/<project>-handoffs/). The zip is transient — gitignore it.

Output layout

<app dir>/design-handoff/
  latest/                      # committed, canonical upload target
    01_TimerIdle.png ...
    manifest.json              # generated (id -> caption)
    manifest.md                # authored
    <app>-design-context.md    # authored, code-grounded
    CLAUDE-DESIGN-PROMPT.md     # authored
    STAMP.txt                  # generated provenance
  archive/<date>-<sha>/        # gitignored history

Recommended .gitignore: **/design-handoff/archive/ and *-handoff-*.zip.

Error handling

SymptomCauseFix
Guard returns capable on a known-bad machineMarker missingCreate .claude/NO_SIMULATOR.md; the marker is authoritative
capture invoked on forbidden hostWrong machineRefuse; run capture on a capable Mac, package anywhere
Manifest id not found in UITestTest method missingRun prepare; author the method against the existing pattern
Missing PNG for a manifest idCapture skipped/failedpackage lists it as missing — do not hand-fill
STAMP older than HEADlatest/ is staleRe-run capture+package on a capable host

Cross-references

  • Generated per-project overlay + descriptor sync: overlay-sync
  • Simulator-capability convention shared here: preview-capture
  • Device selection / simctl: ios-simulate
  • Why UI-test execution is gated: ios-test

What ships with it: 5 files

14.0 KB alongside SKILL.md, 2 of them executable

scripts/

Keep looking

Skills are one crate of 625,605. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.