agentsclimarketplace

Design handoff

Skill markdavidgan/apple-dev-skills/src/skills/design-handoff

Apple platform development skills for Claude Code, Cursor, Kimi Code, Antigravity, Codex CLI, and Agy.

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

  • 2 stars2 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.

What its author says it does

Copied from the file, not written here

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.

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

Keep looking

Skills are one crate of 328,083. 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.