Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Generate sample data and a multi-variant
.claude/skills/rshankras-preview-data-generator/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-08 | ✗→✓ | ▲ Improved | 87% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 30% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 20% | 0% |
| case-03 | ✗→✓ | ▲ Improved | -4% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 57% | 0% |
Generates two tightly-coupled things for a SwiftUI view:
#Preview matrix — the variant blocks you'd otherwise hand-write to prototype and QA a view across light/dark, Dynamic Type, locale/RTL, device sizes, and data states.This is the design-time counterpart to testing/test-data-factory (which makes fixtures for the test suite). Where a factory already exists, this skill reuses Model.fixture() instead of inventing parallel data.
Use this skill when the user:
@Model and needs an in-memory seeded container for previewsPreviewModifierJust need fixtures for unit tests? Use testing/test-data-factory instead. Want screenshot regression tests? Pair this with testing/snapshot-test-setup — the same data + variant matrix feeds snapshot tests.
Load both before generating:
| File | Purpose | |------|---------| | preview-data-patterns.md | Sample-data design, the edge-case catalog, SwiftData in-memory seeding, PreviewModifier (iOS 18), @Previewable, reusing test-data-factory | | preview-matrix.md | The variant axes, preview traits:, .environment overrides, deployment-target fallbacks (#Preview vs PreviewProvider), data-state previews |
Generators are context-aware. Before writing code, detect:
| Check | How | Why it matters | |-------|-----|----------------| | Deployment target | Read project/.xcodeproj or Package.swift | iOS 17+ → #Preview macro; iOS 18+ → PreviewModifier + @Previewable; below 17 → PreviewProvider fallback | | Target view + its models | Read the view file; Grep its init/properties for model types | Determines which types need sample data | | Existing fixtures | Grep "static func fixture\|extension .*{ static (let\|var) preview" | Reuse Model.fixture() / existing .preview — never duplicate | | SwiftData | Grep "@Model" on the model types | Use the in-memory .modelContainer(inMemory:) seed pattern, not plain structs | | View shape | Does it take a model, a ViewModel, or fetch its own data? | Drives whether to inject data, a mock VM, or a seeded container | | Platform | iOS / macOS / multiplatform | Device variants and some traits differ |
Ask via AskUserQuestion only what you can't infer — e.g. "Which states matter for this view: empty, loading, error, loaded, or all four?"
For each model the view needs, generate a Model.preview namespace with the realistic case and the edge cases (see preview-data-patterns.md for the full catalog):
swiftextension Article { /// A typical, realistic instance for the canvas. static var preview: Article { Article(id: UUID(), title: "Designing for the Smallest Screen", author: "Mei Chen", body: String(repeating: "Lorem ipsum. ", count: 40), imageURL: URL(string: "https://picsum.photos/seed/1/600/400"), readMinutes: 6, isBookmarked: false) } /// Edge cases that expose layout bugs. static var previewLongTitle: Article { .preview.with(title: "An Extraordinarily, Almost Unreasonably Long Headline That Wraps") } static var previewNoImage: Article { .preview.with(imageURL: nil) } static var previewList: [Article] { (1...12).map { .preview.with(id: UUID(), title: "Article \($0)") } } static var previewEmpty: [Article] { [] } }
If Article.fixture() already exists (from test-data-factory), build .preview on top of it rather than re-specifying every field.
Generate the #Preview blocks for the axes that matter (see preview-matrix.md). Always include data states — that's the UI-prototyping payoff:
swift#Preview("Loaded") { ArticleListView(state: .loaded(Article.previewList)) } #Preview("Empty") { ArticleListView(state: .empty) } #Preview("Loading") { ArticleListView(state: .loading) } #Preview("Error") { ArticleListView(state: .error("No connection")) } #Preview("Dark") { ArticleListView(state: .loaded(Article.previewList)).preferredColorScheme(.dark) } #Preview("XXL Text") { ArticleListView(state: .loaded(Article.previewList)).dynamicTypeSize(.accessibility3) } #Preview("German / RTL", traits: .sizeThatFitsLayout) { ArticleDetailView(article: .previewLongTitle).environment(\.locale, .init(identifier: "de")) }
Scale the matrix to the request — don't emit 20 previews for a label. A reasonable default per view: the relevant data states + dark mode + one large Dynamic Type + (if the view has text that localizes) one long-language/RTL check.
previewContainer seeded with sample models, attached via .modelContainer(previewContainer).PreviewModifier so the seeded container/data is built once and cached across every preview, applied with #Preview(traits: .modifier(SampleData())).@State/@Bindable → use @Previewable so state lives directly in the #Preview body.Article+Preview.swift (one file per model, in the model's group).ArticleListView+Previews.swift for large matrices. Ask if unsure.#if DEBUG so it never ships in release builds.After generating, always provide:
Article+Preview.swift, PreviewSupport.swift, etc.)#Preview blocks went, how to open the canvas#Preview / PreviewModifier / PreviewProvider)snapshot-test-setup✅ Generated previews for ArticleListView (iOS 18 target)
Files created:
• Article+Preview.swift — .preview, .previewLongTitle, .previewNoImage, .previewList, .previewEmpty
• PreviewSupport.swift — SampleData: PreviewModifier (cached in-memory container)
• ArticleListView+Previews.swift — 7 #Preview blocks (4 states, dark, XXL text, German)
Integration:
• Reused Article.fixture() from test-data-factory for base fields
• Open ArticleListView+Previews.swift → canvas shows all 7 variants
API used: #Preview + PreviewModifier (.modifier) — shared container built once, cached.testing/test-data-factorytesting/snapshot-test-setup (feed it this data)#Preview {} is enoughgenerators/localization-setup already covers locale preview helpers; reuse themgenerators/persistence-setup → defines @Model types
↓
testing/test-data-factory → Model.fixture() for tests
↓
generators/preview-data-generator (THIS SKILL)
• reuses .fixture() for canvas data
• builds the #Preview state/appearance matrix
↓
testing/snapshot-test-setup → renders the same matrix for regression.fixture()/.preview reused, not duplicated#Preview matrix covering the view's data states + relevant appearance axes#Preview / PreviewModifier / PreviewProvider)#if DEBUGGenerate the data and the variants together. A preview is only as useful as the data in it — and a view is only proven when you've seen it empty, overflowing, in the dark, and at AX5.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-05 | pass→pass | 12,888 | 20,701 | +61% | 1 | 1 | 0% | 2,288 | 5,537 | +142% | 0 | 0 | — |
case-06 | pass→pass | 17,756 | 25,299 | +42% | 1 | 1 | 0% | 4,398 | 8,455 | +92% | 0 | 0 | — |
case-07 | fail→fail | 12,683 | 15,603 | +23% | 1 | 1 | 0% | 2,681 | 5,504 | +105% | 0 | 0 | — |
case-08 | fail→pass | 11,745 | 9,832 | -16% | 1 | 1 | 0% | 2,208 | 4,133 | +87% | 0 | 0 | — |
case-09 | pass→pass | 6,186 | 9,612 | +55% | 1 | 1 | 0% | 1,119 | 4,084 | +265% | 0 | 0 | — |
case-01 | fail→pass | 23,118 | 19,188 | -17% | 1 | 1 | 0% | 4,815 | 6,275 | +30% | 0 | 0 | — |
case-02 | fail→pass | 19,833 | 15,724 | -21% | 1 | 1 | 0% | 4,473 | 5,380 | +20% | 0 | 0 | — |
case-03 | fail→pass | 25,812 | 17,359 | -33% | 1 | 1 | 0% | 5,439 | 5,224 | -4% | 0 | 0 | — |
case-04 | pass→pass | 14,033 | 13,587 | -3% | 1 | 1 | 0% | 2,725 | 4,832 | +77% | 0 | 0 | — |
case-10 | fail→fail | 10,839 | 18,504 | +71% | 1 | 1 | 0% | 2,039 | 5,349 | +162% | 0 | 0 | — |
case-11 | pass→pass | 9,223 | 14,851 | +61% | 1 | 1 | 0% | 2,235 | 4,344 | +94% | 0 | 0 | — |
case-12 | pass→pass | 11,307 | 11,329 | +0% | 1 | 1 | 0% | 2,221 | 4,422 | +99% | 0 | 0 | — |
case-13 | fail→pass | 14,591 | 13,543 | -7% | 1 | 1 | 0% | 2,912 | 4,582 | +57% | 0 | 0 | — |
case-14 | pass→pass | 12,729 | 15,629 | +23% | 1 | 1 | 0% | 2,340 | 4,846 | +107% | 0 | 0 | — |
case-15 | pass→pass | 12,932 | 12,984 | +0% | 1 | 1 | 0% | 2,345 | 4,125 | +76% | 0 | 0 | — |
case-16 | fail→fail | 14,878 | 26,970 | +81% | 1 | 1 | 0% | 3,019 | 7,471 | +147% | 0 | 0 | — |
case-17 | pass→pass | 9,831 | 13,245 | +35% | 1 | 1 | 0% | 1,832 | 4,195 | +129% | 0 | 0 | — |
case-18 | pass→pass | 5,570 | 7,326 | +32% | 1 | 1 | 0% | 954 | 3,477 | +264% | 0 | 0 | — |
case-19 | pass→pass | 11,159 | 12,345 | +11% | 1 | 1 | 0% | 2,207 | 4,597 | +108% | 0 | 0 | — |
case-20 | fail→fail | 22,215 | 4,544 | -80% | 1 | 1 | 0% | 3,768 | 2,518 | -33% | 0 | 0 | — |
case-21 | fail→pass | 13,560 | 11,948 | -12% | 1 | 1 | 0% | 2,616 | 4,531 | +73% | 0 | 0 | — |
case-22 | fail→pass | 11,047 | 11,779 | +7% | 1 | 1 | 0% | 1,994 | 4,506 | +126% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 21 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +32 percentage points is the difference between those two pass rates over the 21 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.