foldsmith-duo
BSecurityAdapt a whole existing iOS app to iPhone Duo (Apple's foldable, iOS 27.1), hands-free, from one prompt to every screen verified in every pose. Classifies each screen against a measured decision table, applies a named layout pattern, proves every pose with screenshots, and keeps .duo/migration.json current, so the user can follow it (in Claude Code the Foldsmith plugin's pane draws it) and a later session resumes. Use when the user mentions iPhone Duo, foldable iPhone, the fold or crease or hi...
- 1 star
- 0 votes
- 0 copies
- 4 views
- Added October 31, 2026
Works with
Security analysis
85/100- Performs destructive filesystem operations
Pro scans all 19 files and shows the line behind each finding
npx -y skills add 3nchiridion/foldsmith --skill foldsmith-duo --agent claude-codeAre you the author of foldsmith-duo?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/3nchiridion-foldsmith-duo)---
name: foldsmith-duo
description: Adapt a whole existing iOS app to iPhone Duo (Apple's foldable, iOS 27.1), hands-free, from one prompt to every screen verified in every pose. Classifies each screen against a measured decision table, applies a named layout pattern, proves every pose with screenshots, and keeps .duo/migration.json current, so the user can follow it (in Claude Code the Foldsmith plugin's pane draws it) and a later session resumes. Use when the user mentions iPhone Duo, foldable iPhone, the fold or crease or hinge, book or laptop pose, vertical toolbars or tab bars, ArrangementView, ReservedRegion, "squeezed when half open", or asks to make an app work on the Duo's inner and outer displays.
license: MIT
---
# Foldsmith: layout patterns for iPhone Duo
iPhone and iPhone Duo are trademarks of Apple Inc. Foldsmith is independent and is not affiliated with, sponsored by, or
endorsed by Apple Inc.
This skill routes; the knowledge lives in `references/` (the pattern pages in `references/patterns/`). Do not improvise a layout rule that is not in the decision table.
Paths here (`references/`, `tools/`) are relative to this skill's folder, wherever it is installed (`~/.claude/skills/`, `.agents/skills/`, `.cursor/skills/`, a plugin); run the tools from the app's repository by that folder's path, with their interpreters (`bash …/tools/capture-app.sh`, `python3 …/tools/progress.py`), since an installer may not keep the executable bit. Nothing here needs Claude Code: the tools are shell, Python and Swift, and the progress file is plain JSON.
## Hands-free
One prompt takes the whole app from audit to verified. The user follows `.duo/migration.json` (in Claude Code, the Foldsmith plugin's pane draws it) and can interrupt at any time; you do not wait for them.
- Never ask a question or stop for approval. Where a choice is needed, take the one the decision table and the pattern page give, and write why in the log.
- Never stop at a problem. Record it in `todo` with the reason, and go on to the next screen. The run ends only when every screen is verified or listed in `todo`.
- Commit in the app's repository after each change, on a branch `duo-migration`, so each can be reviewed or reverted on its own: `Duo: <screen> → <pattern>`, naming every screen the change fixes when it fixes several (`Duo: timeline, article → Fold-aligned split`: one split view serves both). Commit `.duo/migration.json`, `.duo/flows/` and the reports with it; keep `.duo/captures/` and `.duo/.migration.lock` out of git (add them to `.git/info/exclude`). Never push.
- Never create, clone or erase simulators.
## Start or resume
- If `.duo/migration.json` exists, resume from it: skip every cell already `verified` or `unchanged`, keep `done`, and take `todo` from the top. Never redo finished work.
- Otherwise start at step 1. Find the Duo simulator once (`references/simulator.md`), then `tools/progress.py init <app> --simulator <udid>`.
- If `duo.py` is not on this machine, fetch the community driver at the commit these tools are tested with, into a cache rather than a skills folder: `D=~/Library/Caches/duo-driver; SHA=60cdda2f487434c9c3cd4dad9431e12a48ec1d04; [ "$(git -C "$D" rev-parse HEAD 2>/dev/null)" = "$SHA" ] || { rm -rf "${D:?}"; git init -q "$D" && git -C "$D" fetch -q --depth 1 https://github.com/WhitenWhiten/iphone-duo-simulator-skill "$SHA" && git -C "$D" checkout -q FETCH_HEAD; }`; then pass `DUO_PY=~/Library/Caches/duo-driver/scripts/duo.py` to the tools (`references/simulator.md`).
## Workflow
1. **Find the screens.** Run the static scans (`references/detectors-static.md`) first. An app with no scene lifecycle (T13) cannot launch on the 27.1 SDK: adopt it with Apple's `app-resizability` skill if it is installed and commit that (`Duo: scene lifecycle`); otherwise put T13 in `todo` and end the run, since nothing can be captured. Read the app's navigation (root view, tabs, navigation destinations, sheets, settings) and list the screens a user spends time in: each top-level destination, each list and its detail, and the sheets a user opens often. Write `.duo/flows/<app>.flow`: `BUNDLE=<bundle id>`, optional `LAUNCH_ARGS=…`, and one `"<screen>=<tap>;<tap>"` line per screen in `SCREENS=( … )` (tap syntax: the header of `tools/capture-corpus-flow.sh`). Check each line reaches its screen with one `closed` capture (`ONLY=closed tools/capture-app.sh`); it exits 3 and names each screen it could not reach. A screen no taps reach (behind a sign-in, say) goes to `todo` with the reason; do not guess at it. A screen that shows its layout only with content (saved items, open tabs, a list the person fills) gets some before the audit, made through the app itself with the tap runner (save, open, add; never by writing its files, never by signing in), and logged. The progress file uses the flow's screen names: the capture records its shots under them.
2. **Audit before touching code.** Build the app unmodified against the iOS 27.1 SDK and capture every screen in every pose: `tools/capture-app.sh .duo/flows/<app>.flow .duo/captures/<app>/before` (`references/simulator.md`). It runs the detectors (`tools/detect`) and writes each shot's path to its cell as `before`. Each cell gets `finding` (label = the taxonomy ids, all of them: `T14 · T7`) or `unchanged`. Write `.duo/audit.md`: one row per screen × pose with the taxonomy class ids found (`references/taxonomy.md`). Look at every shot: a defect the detectors miss is still a `finding`, and a detector line the shot does not bear out is not one (log why): a map's pins and place names pan, so T1 on them is not a finding; T7 where the same screen is there in another presentation (an overlay with Close on the cover, inline on the inner display) is not one either. A capture line saying the shut phone lost the screen after a pose is evidence for T7 (state lost on the fold) in that pose; an app that crashed on the way to a pose is T7 there too (the detectors report it; find the crash report). If the capture exits 3, some cells were skipped: capture them again before you write the audit, never count them clean.
3. **Classify each screen.** Decide content relationship (hierarchical / peer / supporting / continuous / object) and interaction mode (viewing / editing / controlling) from the model and the screen's purpose, not from its current layout. A feed is classed by what its items are (the decision table's I1): uniform rows that each open a page are hierarchical, cards complete where they stand are peer. A map is continuous and no pattern reflows it: it stays whole, and only what sits on it (a search field, a card) moves off the fold, by the decision table's map rule; that is a fix to make (cells `proposed`, label "map rule"), not "no pattern". Record the reasoning in `.duo/audit.md`.
4. **Look up the pattern.** `references/decision-table.md` maps (relationship, mode, pose) → pattern. A cell marked `UNJUSTIFIED` counts as no cell; a pose with no finding there stays `unchanged`. Pages exist for **Fold-aligned split**, **Even grid**, **Reflowing columns** and **Arrangement** (`references/patterns/`), each with its reference implementation beside it (`FoldAlignedSplit.swift`, `EvenGrid.swift`, `ReflowingColumns.swift`, `Arrangement.swift`). Mark each cell the pattern changes `proposed` (label = pattern). A screen with no cell, or one whose pattern has no page yet (Whole object), goes to `todo` as "no pattern yet" with its findings, and is left as it is.
5. **Migrate, one screen at a time.** Apply the reference implementation beside the pattern page. For P1 in SwiftUI use `FoldAlignedTiledSplit` (with `FoldAlignedSplit.swift`), which keeps both panes upright; keep the selection and any sidebar path in the caller. An app whose split is a `TabView` sidebar keeps it (the page's Solution): add `readsBookFold()` on the window's root and `foldAwareSheetPlacement()` on each sheet, and fold with every tab selected. A UIKit app follows the page's UIKit section where it has one (with or without a split view controller); otherwise translate the SwiftUI reference and log how. Bar items that must go vertical are symbols with a title, or custom views set to `axisBehavior = .verticalPreferred`; where the vertical bar is short, choose what stays with `visibilityPriority` and put the app's own menu items in the bar's overflow (`additionalOverflowItems`) rather than nesting a More in it. A custom view turned into a titled item reaches every device: keep its accessibility label (VoiceOver reads the title otherwise), the state it drew (Wikipedia's open-contents button lost its fill everywhere), and its enabled state in the overflow (a deferred element too); where the custom view can keep its look, `.verticalPreferred` changes less. An article in a web view follows Reflowing columns' "When the text is a web page". For P4 use `Arrangement` (`.split` for two views neither may cover, `.overlay` for controls over scrolling content) in place of the app's `HStack`, `VStack` or `ZStack` of the two views, inside the screen's navigation container; a UIKit app uses `UIArrangementViewController` (the page's Reference implementation). `ArrangementView` lays the views out by the window's shape on every iOS 27.1 device, so gate it like any Duo change: use `Arrangement` where the window has a vertical bar or a reserved region (every Duo pose has one or the other) and keep the app's own stack elsewhere; if the app takes the system's layout everywhere instead, log it and capture the screen on an iOS 27.1 simulator without a fold. A hierarchy with three levels (sidebar, list, detail) gets P1 with two panes, the list pane and the detail: unfolded from the sidebar, the sidebar sits beside the detail, and a picked item pushes the list onto the list pane (the page's "Three levels"; a UIKit triple-column split follows its UIKit section). Probe the poses a change touches while you work (`references/simulator.md`: about 25 s a shot, or `SCREEN=<screen> ONLY=<poses>` for a screen behind taps); the full capture is for step 6. Prefer system containers (`NavigationSplitView`, `TabView`, `ArrangementView`) over custom geometry. Keep navigation containers outside `ArrangementView`. Never put `ArrangementView` inside `List`/`ScrollView`. Gate iOS 27.1 APIs behind `#available` with the pre-Duo layout as fallback, and gate what you change for the Duo the same way even where its API is older (`visibilityPriority` is iOS 27.0, `additionalOverflowItems` 16.0), on the condition that calls for it (a vertical bar, a reserved region); a new container that wraps a screen on every iOS 27.1 device changes the hierarchy there too, so without a reserved region the app must still behave as before, and behind `#if os(iOS) && !targetEnvironment(macCatalyst)` in code that a Mac Catalyst, visionOS or macOS target also compiles (their 27.1-era SDKs have no reserved regions; the reference files do this already); build those targets too. Never branch on device identity, idiom or orientation. Build after each change; cells become `migrated`; commit.
6. **Prove it.** Rebuild, recapture every pose with the same flows into `.duo/captures/<app>/after/`, re-run `tools/detect`, and look at each shot. `capture-app.sh` writes each shot's path to its cell as `after`. A cell with no finding left becomes `verified`. A finding that remains gets one more fix; if it is still there, the cell stays `finding` and the screen goes to `todo` with what is wrong. A screen must never end worse than it started: a finding in a pose that was clean before, or a shot that reads worse (text cut off at a pane's edge or broken mid-word, a control gone, a pane's content stopping short of its edge), is a regression. Fix it, or revert that change for the screen, before anything goes to `todo`; `todo` holds what is left, never what the run broke. `tools/progress.py` holds you to this where the detectors can see it: `done <screen>: …` and `phase done` refuse while a pose clean in the before capture has a finding in the after capture, or no detector ran there (not captured, or `no-rotate`) (`progress.py check` lists them); a line the shot does not bear out gets `progress.py waive <screen> <pose> <id> "<why>"`, and the report says why. Most of what Wikipedia's reviews found, no detector reads, so look for it on purpose: in every changed pose measure each pane's controls and rows against the pane's edges in the snapshot (a contact sheet hides 84 pt), check that nothing is clipped, and check that every control the change moved (into an overflow, out of the vertical bar, into another pane) still works and is still reachable, opening the overflow to see it; say where each one went, and count each action that now takes more taps as a cost in the report. Then read your diff for what no shot shows: every walk of the hierarchy you changed (`as? UINavigationController`, `selectedViewController`, `presentedViewController`, `viewControllers`: state restoration, theming, routing, coordinators handed a navigation controller), every other route into a changed screen (user activities, deep links, notifications, not only the flow's), what a view you added covers (hide it from accessibility), and every state that follows the screen it belongs to (a pane showing one article's contents after another opens). If you can start a subagent, give a fresh one the diff and the before and after captures, not your conclusions, ask it for regressions, and fix what it confirms: on Wikipedia each such review found regressions the run had passed, among them state restoration and theming broken on every iOS 27.1 device. A screen whose cells are all `verified` or `unchanged` goes to `done`.
7. **Report.** Set `phase` to `done` and write `.duo/REPORT.md`: per screen the pattern, the before/after shots per pose, what is left and why. If a corpus rule or an API name in `references/` does not match the SDK you compiled against, say so and prefer the compiler.
## Poses to capture, always, in this order
closed · closed-sideways · book · laptop · tent · flat-sideways · flat-upright. These are Apple's six HIG poses (closed, book, laptop, tent — standing on its edges —, flat upright, flat sideways), plus the outer display sideways. A screen is done only when every one is captured and verified. Pose control: the `duo.py` driver (see `references/simulator.md`); tent is laptop's fold turned hinge-up (`duo.py fold laptop`, `orient landscape-right`). Split View's half window cannot be driven headless: lay out from size classes and it follows.
## Progress file
After every step, and after each screen within a step, update `.duo/migration.json` in the app's repo, so the user (or the Foldsmith pane, in Claude Code) and the next session see where things stand. Change it only through `tools/progress.py` (`phase`, `screen`, `cell`, `todo`, `done`, `waive`, `log`, and `check` to read the regressions; `todo --remove` and `done --remove` take a wrong line back; `references/simulator.md`), which locks it and writes it whole, so a capture running beside you loses nothing and the pane never reads half a file.
```json
{
"app": "Mail",
"phase": "screens | audit | classify | migrate | verify | done",
"simulator": "<Duo simulator UDID>",
"poses": ["closed", "closed-sideways", "book", "laptop", "tent", "flat-sideways", "flat-upright"],
"screens": [
{ "name": "Inbox", "pattern": "Fold-aligned split",
"cells": { "book": { "state": "verified", "label": "split",
"before": ".duo/captures/Mail/before/inbox-book-inner.png",
"after": ".duo/captures/Mail/after/inbox-book-inner.png" } } }
],
"done": ["Inbox: Fold-aligned split, verified in every pose"],
"todo": ["Viewer: no pattern yet (Whole object); T1 in laptop", "Message: verify tent"],
"log": ["newest last, one line per action"]
}
```
Cell `state`: `pending` (not captured), `unchanged` (fine as is), `finding` (label = the taxonomy ids), `proposed` (label = pattern), `migrated`, `verified`. A pose with no cell counts as `pending`. `before` and `after` are paths relative to the app's repo; leave them out until the shot exists. `todo` is what is left, in the order you will do it; `done` is what is finished.
## Hard rules (from Apple, cited in references/apple.md)
- Layout from size classes and reserved regions; hinge angle only for effects.
- Continuously scrolling content does not displace across the fold.
- Bars: use navigation-container toolbars; every item needs title + symbol; don't override default placement; overflow priorities on frequent and status-bearing items.
- Laptop pose: viewed content on the upper half, controls on the lower half. Book pose: alerts move to the trailing half, and sheets slide off the fold (the patterns put them trailing; T15).
- Request inactive division regions when a column count or split depends on the fold.
Files in this skill
- LICENSE
- SKILL.md
- references/README.md
- references/apple.md
- references/decision-table.md
- references/detectors-static.md
- references/metrics.md
- references/patterns/Arrangement.swift
- references/patterns/EvenGrid.swift
- references/patterns/FoldAlignedSplit.swift
- references/patterns/FoldAlignedTiledSplit.swift
- references/patterns/ReflowingColumns.swift
- references/patterns/arrangement.md
- references/patterns/even-grid.md
- references/patterns/fold-aligned-split.md
- references/patterns/matrices/arrangement.png
- references/patterns/matrices/columns.png
- references/patterns/matrices/grid.png
- references/patterns/reflowing-columns.md
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.