stitch-kit 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/AGENTS.md +139 -0
  2. package/CHANGELOG.md +86 -0
  3. package/README.md +167 -0
  4. package/agents/stitch-kit.md +93 -0
  5. package/bin/stitch-kit.mjs +430 -0
  6. package/docs/architecture.md +118 -0
  7. package/docs/color-prompt-guide.md +119 -0
  8. package/docs/mcp-naming-convention.md +64 -0
  9. package/docs/mcp-schemas/README.md +130 -0
  10. package/docs/mcp-schemas/apply_design_system.json +36 -0
  11. package/docs/mcp-schemas/create_design_system.json +78 -0
  12. package/docs/mcp-schemas/create_project.json +290 -0
  13. package/docs/mcp-schemas/delete_project.json +20 -0
  14. package/docs/mcp-schemas/edit_screens.json +46 -0
  15. package/docs/mcp-schemas/generate_screen_from_text.json +242 -0
  16. package/docs/mcp-schemas/generate_variants.json +77 -0
  17. package/docs/mcp-schemas/get_project.json +137 -0
  18. package/docs/mcp-schemas/get_screen.json +92 -0
  19. package/docs/mcp-schemas/list_design_systems.json +32 -0
  20. package/docs/mcp-schemas/list_projects.json +136 -0
  21. package/docs/mcp-schemas/list_screens.json +56 -0
  22. package/docs/mcp-schemas/update_design_system.json +32 -0
  23. package/docs/mcp-schemas/upload_screens_from_images.json +52 -0
  24. package/docs/prd-to-stitch-workflow.md +137 -0
  25. package/docs/skills-index.md +108 -0
  26. package/docs/tailwind-reference.md +207 -0
  27. package/package.json +41 -0
  28. package/skills/stitch-a11y/SKILL.md +428 -0
  29. package/skills/stitch-a11y/resources/audit-checklist.md +102 -0
  30. package/skills/stitch-animate/SKILL.md +371 -0
  31. package/skills/stitch-animate/resources/animation-patterns.md +248 -0
  32. package/skills/stitch-design-md/SKILL.md +215 -0
  33. package/skills/stitch-design-md/examples/DESIGN.md +54 -0
  34. package/skills/stitch-design-md/examples/usage.md +67 -0
  35. package/skills/stitch-design-md/scripts/fetch-stitch.sh +35 -0
  36. package/skills/stitch-design-system/SKILL.md +314 -0
  37. package/skills/stitch-design-system/resources/tokens-template.css +237 -0
  38. package/skills/stitch-html-components/SKILL.md +344 -0
  39. package/skills/stitch-html-components/resources/architecture-checklist.md +74 -0
  40. package/skills/stitch-html-components/scripts/fetch-stitch.sh +45 -0
  41. package/skills/stitch-loop/SKILL.md +183 -0
  42. package/skills/stitch-loop/examples/SITE.md +39 -0
  43. package/skills/stitch-loop/examples/next-prompt.md +24 -0
  44. package/skills/stitch-loop/examples/usage.md +77 -0
  45. package/skills/stitch-mcp-apply-design-system/SKILL.md +82 -0
  46. package/skills/stitch-mcp-create-design-system/SKILL.md +117 -0
  47. package/skills/stitch-mcp-create-project/SKILL.md +77 -0
  48. package/skills/stitch-mcp-delete-project/SKILL.md +62 -0
  49. package/skills/stitch-mcp-edit-screens/SKILL.md +121 -0
  50. package/skills/stitch-mcp-generate-screen-from-text/SKILL.md +102 -0
  51. package/skills/stitch-mcp-generate-screen-from-text/examples/desktop.md +53 -0
  52. package/skills/stitch-mcp-generate-screen-from-text/examples/mobile.md +71 -0
  53. package/skills/stitch-mcp-generate-variants/SKILL.md +124 -0
  54. package/skills/stitch-mcp-get-project/SKILL.md +67 -0
  55. package/skills/stitch-mcp-get-screen/SKILL.md +117 -0
  56. package/skills/stitch-mcp-get-screen/scripts/fetch-stitch.sh +35 -0
  57. package/skills/stitch-mcp-list-design-systems/SKILL.md +84 -0
  58. package/skills/stitch-mcp-list-projects/SKILL.md +77 -0
  59. package/skills/stitch-mcp-list-screens/SKILL.md +69 -0
  60. package/skills/stitch-mcp-update-design-system/SKILL.md +82 -0
  61. package/skills/stitch-mcp-upload-screens-from-images/SKILL.md +95 -0
  62. package/skills/stitch-mcp-upload-screens-from-images/scripts/encode-image.sh +43 -0
  63. package/skills/stitch-nextjs-components/SKILL.md +181 -0
  64. package/skills/stitch-nextjs-components/resources/architecture-checklist.md +65 -0
  65. package/skills/stitch-nextjs-components/resources/component-template.tsx +101 -0
  66. package/skills/stitch-nextjs-components/scripts/fetch-stitch.sh +45 -0
  67. package/skills/stitch-orchestrator/SKILL.md +337 -0
  68. package/skills/stitch-orchestrator/examples/workflow.md +173 -0
  69. package/skills/stitch-react-components/SKILL.md +236 -0
  70. package/skills/stitch-react-components/references/tailwind-to-react.md +117 -0
  71. package/skills/stitch-react-components/resources/architecture-checklist.md +34 -0
  72. package/skills/stitch-react-components/resources/component-template.tsx +35 -0
  73. package/skills/stitch-react-components/scripts/fetch-stitch.sh +35 -0
  74. package/skills/stitch-react-native-components/SKILL.md +333 -0
  75. package/skills/stitch-react-native-components/resources/architecture-checklist.md +74 -0
  76. package/skills/stitch-react-native-components/resources/component-template.tsx +104 -0
  77. package/skills/stitch-react-native-components/scripts/fetch-stitch.sh +45 -0
  78. package/skills/stitch-remotion/SKILL.md +280 -0
  79. package/skills/stitch-setup/SKILL.md +183 -0
  80. package/skills/stitch-shadcn-ui/SKILL.md +255 -0
  81. package/skills/stitch-skill-creator/SKILL.md +257 -0
  82. package/skills/stitch-skill-creator/references/output-patterns.md +71 -0
  83. package/skills/stitch-skill-creator/scripts/init_stitch_skill.py +229 -0
  84. package/skills/stitch-svelte-components/SKILL.md +282 -0
  85. package/skills/stitch-svelte-components/resources/architecture-checklist.md +62 -0
  86. package/skills/stitch-svelte-components/resources/component-template.svelte +193 -0
  87. package/skills/stitch-svelte-components/scripts/fetch-stitch.sh +36 -0
  88. package/skills/stitch-swiftui-components/SKILL.md +424 -0
  89. package/skills/stitch-swiftui-components/resources/architecture-checklist.md +83 -0
  90. package/skills/stitch-swiftui-components/resources/component-template.swift +131 -0
  91. package/skills/stitch-swiftui-components/resources/layout-mapping.md +155 -0
  92. package/skills/stitch-swiftui-components/scripts/fetch-stitch.sh +45 -0
  93. package/skills/stitch-ued-guide/SKILL.md +124 -0
  94. package/skills/stitch-ui-design-spec-generator/SKILL.md +115 -0
  95. package/skills/stitch-ui-design-spec-generator/examples/usage.md +79 -0
  96. package/skills/stitch-ui-design-variants/SKILL.md +127 -0
  97. package/skills/stitch-ui-prompt-architect/SKILL.md +196 -0
  98. package/skills/stitch-ui-prompt-architect/references/KEYWORDS.md +170 -0
@@ -0,0 +1,193 @@
1
+ <!--
2
+ StitchComponent.svelte
3
+
4
+ Generated from Stitch design via stitch-svelte-components skill.
5
+ Replace "StitchComponent" with the actual component name.
6
+ Import path: $lib/components/StitchComponent.svelte
7
+
8
+ Svelte 5 runes API. No legacy Options API patterns.
9
+ -->
10
+
11
+ <script lang="ts">
12
+ import { fade } from 'svelte/transition'
13
+
14
+ // ----------------------------------------------------------
15
+ // Types & Props
16
+ // ----------------------------------------------------------
17
+
18
+ /**
19
+ * Props for this component.
20
+ * All data comes through $props() — never imported directly from a store or module.
21
+ */
22
+ interface Props {
23
+ /** Primary heading text */
24
+ title: string
25
+ /** Supporting description — optional */
26
+ description?: string
27
+ /** Callback for primary action */
28
+ onAction?: () => void
29
+ /** Render slot content */
30
+ children?: import('svelte').Snippet
31
+ }
32
+
33
+ /**
34
+ * $props() is the Svelte 5 replacement for `export let`.
35
+ * Destructure with defaults for optional props.
36
+ */
37
+ const { title, description = '', onAction, children }: Props = $props()
38
+
39
+ // ----------------------------------------------------------
40
+ // State
41
+ // ----------------------------------------------------------
42
+
43
+ /** $state() is the Svelte 5 replacement for reactive `let`. */
44
+ let isExpanded = $state(false)
45
+
46
+ // ----------------------------------------------------------
47
+ // Derived values
48
+ // ----------------------------------------------------------
49
+
50
+ /** $derived() is the Svelte 5 replacement for $: reactive statements. */
51
+ const buttonLabel = $derived(isExpanded ? 'Collapse' : 'Expand')
52
+
53
+ // ----------------------------------------------------------
54
+ // Effects (use sparingly — prefer derived state when possible)
55
+ // ----------------------------------------------------------
56
+
57
+ // $effect(() => {
58
+ // // Runs when dependencies change. Cleanup by returning a function.
59
+ // console.log('title changed:', title)
60
+ // return () => { /* cleanup */ }
61
+ // })
62
+ </script>
63
+
64
+ <!--
65
+ Semantic landmark element.
66
+ aria-labelledby connects the section to its visible heading for screen readers.
67
+ -->
68
+ <section
69
+ aria-labelledby="stitch-component-title"
70
+ in:fade={{ duration: 200 }}
71
+ >
72
+ <!-- Heading — maintain hierarchy (h1 → h2 → h3, never skip) -->
73
+ <h2 id="stitch-component-title">{title}</h2>
74
+
75
+ <!-- Description — only render when provided -->
76
+ {#if description}
77
+ <p class="description">{description}</p>
78
+ {/if}
79
+
80
+ <!-- Named slot for nested content -->
81
+ {#if children}
82
+ <div class="content">
83
+ {@render children()}
84
+ </div>
85
+ {/if}
86
+
87
+ <!-- Example interactive element -->
88
+ {#if onAction}
89
+ <button
90
+ type="button"
91
+ onclick={onAction}
92
+ class="btn-primary"
93
+ >
94
+ {buttonLabel}
95
+ </button>
96
+ {/if}
97
+ </section>
98
+
99
+ <!--
100
+ Styles are SCOPED to this component by default.
101
+ Use CSS custom properties (var(--token)) for theming — never hardcode hex values.
102
+ The :global() selector escapes scoping when needed for child components.
103
+ -->
104
+ <style>
105
+ section {
106
+ display: flex;
107
+ flex-direction: column;
108
+ gap: var(--space-4);
109
+ padding: var(--space-6);
110
+ background-color: var(--color-surface);
111
+ color: var(--color-text);
112
+ border: 1px solid var(--color-border);
113
+ border-radius: var(--radius-lg);
114
+ box-shadow: var(--shadow-sm);
115
+ width: 100%;
116
+ max-width: 42rem;
117
+
118
+ /* Responsive padding */
119
+ @media (min-width: 768px) {
120
+ padding: var(--space-8);
121
+ }
122
+ }
123
+
124
+ h2 {
125
+ font-size: var(--text-2xl);
126
+ font-weight: 700;
127
+ font-family: var(--font-sans);
128
+ color: var(--color-text);
129
+ line-height: 1.25;
130
+ margin: 0;
131
+ }
132
+
133
+ .description {
134
+ font-size: var(--text-base);
135
+ color: var(--color-text-muted);
136
+ line-height: 1.6;
137
+ margin: 0;
138
+ }
139
+
140
+ .content {
141
+ display: flex;
142
+ flex-direction: column;
143
+ gap: var(--space-3);
144
+ }
145
+
146
+ /* Button — micro-interaction with CSS transitions */
147
+ .btn-primary {
148
+ display: inline-flex;
149
+ align-items: center;
150
+ justify-content: center;
151
+ gap: var(--space-2);
152
+ padding: var(--space-2) var(--space-4);
153
+ background-color: var(--color-primary);
154
+ color: var(--color-primary-fg);
155
+ border: none;
156
+ border-radius: var(--radius-md);
157
+ font-size: var(--text-sm);
158
+ font-weight: 600;
159
+ cursor: pointer;
160
+ align-self: flex-start;
161
+
162
+ /* Smooth micro-transition */
163
+ transition:
164
+ background-color var(--motion-duration-fast) var(--motion-ease-default),
165
+ transform var(--motion-duration-fast) var(--motion-ease-default),
166
+ box-shadow var(--motion-duration-fast) var(--motion-ease-default);
167
+ }
168
+
169
+ .btn-primary:hover {
170
+ background-color: var(--color-primary-hover);
171
+ transform: translateY(-1px);
172
+ box-shadow: var(--shadow-sm);
173
+ }
174
+
175
+ .btn-primary:active {
176
+ transform: translateY(0);
177
+ box-shadow: none;
178
+ }
179
+
180
+ /* Focus ring — never outline: none without a replacement */
181
+ .btn-primary:focus-visible {
182
+ outline: 2px solid var(--color-border-focus);
183
+ outline-offset: 2px;
184
+ }
185
+
186
+ /* Reduced motion — disable all transitions in this component */
187
+ @media (prefers-reduced-motion: reduce) {
188
+ section,
189
+ .btn-primary {
190
+ transition: none;
191
+ }
192
+ }
193
+ </style>
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env bash
2
+ # fetch-stitch.sh
3
+ # Reliably downloads Stitch HTML from Google Cloud Storage URLs.
4
+ # GCS URLs require redirect handling and specific security handshakes that
5
+ # AI fetch tools often fail on. This script handles both.
6
+ #
7
+ # Usage:
8
+ # bash scripts/fetch-stitch.sh "<url>" "<output-path>"
9
+ #
10
+ # Example:
11
+ # bash scripts/fetch-stitch.sh "$htmlCode_downloadUrl" "temp/source.html"
12
+
13
+ set -euo pipefail
14
+
15
+ URL="${1:?Usage: fetch-stitch.sh <url> <output-path>}"
16
+ OUTPUT="${2:?Usage: fetch-stitch.sh <url> <output-path>}"
17
+
18
+ mkdir -p "$(dirname "$OUTPUT")"
19
+
20
+ curl -L \
21
+ -A "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36" \
22
+ --compressed \
23
+ --retry 3 \
24
+ --retry-delay 1 \
25
+ --max-time 30 \
26
+ --silent \
27
+ --show-error \
28
+ --output "$OUTPUT" \
29
+ "$URL"
30
+
31
+ if [ ! -s "$OUTPUT" ]; then
32
+ echo "Error: Downloaded file is empty. URL may be expired or invalid." >&2
33
+ exit 1
34
+ fi
35
+
36
+ echo "Downloaded to: $OUTPUT ($(wc -c < "$OUTPUT") bytes)"
@@ -0,0 +1,424 @@
1
+ ---
2
+ name: stitch-swiftui-components
3
+ description: Converts Stitch mobile designs into SwiftUI views for native iOS apps — VStack/HStack/ZStack layout mapping, Color asset tokens with dark mode, NavigationStack/TabView routing, and Xcode project structure.
4
+ allowed-tools:
5
+ - "stitch*:*"
6
+ - "Bash"
7
+ - "Read"
8
+ - "Write"
9
+ ---
10
+
11
+ # Stitch → SwiftUI (Native iOS)
12
+
13
+ You are a Swift/SwiftUI engineer. You convert Stitch mobile designs (deviceType: MOBILE) into native iOS SwiftUI views — `.swift` files that build and run in Xcode. You follow Apple's Human Interface Guidelines and produce code that feels like it belongs on iOS.
14
+
15
+ ## When to use this skill
16
+
17
+ Use this skill when:
18
+ - The user wants **native iOS** output from a Stitch design
19
+ - The user mentions "SwiftUI", "Xcode", "iOS", "native iOS app"
20
+ - The design was generated with `deviceType: MOBILE`
21
+
22
+ **Note:** This skill targets iOS 16+ with SwiftUI. For cross-platform (iOS + Android), use `stitch-react-native-components` instead.
23
+
24
+ ## Prerequisites
25
+
26
+ - Stitch design with `deviceType: MOBILE`
27
+ - Xcode 15+ on macOS
28
+ - Swift 5.9+
29
+
30
+ ## Step 1: Retrieve the design
31
+
32
+ 1. `list_tools` → find Stitch MCP prefix
33
+ 2. `[prefix]:get_screen` → fetch metadata
34
+ 3. Download HTML: `bash scripts/fetch-stitch.sh "[htmlCode.downloadUrl]" "temp/source.html"`
35
+ 4. Check `screenshot.downloadUrl` — confirm mobile layout before converting
36
+
37
+ Only convert **MOBILE** designs. Desktop Stitch designs don't map well to SwiftUI without significant layout rethinking.
38
+
39
+ ## Step 2: Xcode project structure
40
+
41
+ ```
42
+ MyApp/
43
+ ├── MyApp.swift ← @main entry point
44
+ ├── ContentView.swift ← Root view (TabView or NavigationStack)
45
+ ├── Theme/
46
+ │ ├── ThemeTokens.swift ← Design token constants
47
+ │ └── Color+App.swift ← Color extension with semantic names
48
+ ├── Views/
49
+ │ ├── [ScreenName]View.swift ← One file per Stitch screen
50
+ │ └── Components/
51
+ │ └── [Name]View.swift ← Reusable component views
52
+ ├── Models/
53
+ │ └── MockData.swift ← Static preview data
54
+ └── Assets.xcassets/
55
+ └── Colors/ ← Color assets for light/dark mode
56
+ ```
57
+
58
+ ## Step 3: The HTML/CSS → SwiftUI layout mapping
59
+
60
+ This is the core translation. Apply these rules to every element in the Stitch HTML:
61
+
62
+ ### Layout containers
63
+
64
+ | HTML/CSS pattern | → SwiftUI |
65
+ |---|---|
66
+ | `display:flex; flex-direction:column` | `VStack(alignment: .leading, spacing: 16)` |
67
+ | `display:flex; flex-direction:row` | `HStack(alignment: .center, spacing: 12)` |
68
+ | `display:flex; justify-content:space-between` | `HStack { Spacer() }` pattern |
69
+ | `position:absolute` overlay | `ZStack` with layered views |
70
+ | `display:grid` (2-column) | `LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 16)` |
71
+ | `overflow-y: scroll` | `ScrollView(.vertical, showsIndicators: false)` |
72
+ | Repeated list of items | `List` or `ForEach` inside `ScrollView + LazyVStack` |
73
+ | `position:fixed` bottom nav | `TabView` (preferred) or explicit `VStack` with `Spacer()` |
74
+
75
+ ### Spacing mapping
76
+
77
+ SwiftUI uses points (1pt ≈ 1dp on non-retina, 2px on Retina @2x):
78
+
79
+ ```swift
80
+ // Spacing from Tailwind → SwiftUI points
81
+ // p-1(4px)→4 p-2(8px)→8 p-3(12px)→12 p-4(16px)→16
82
+ // p-6(24px)→24 p-8(32px)→32 p-12(48px)→48 p-16(64px)→64
83
+ ```
84
+
85
+ ### Geometry mapping
86
+
87
+ ```swift
88
+ // Tailwind rounded- → SwiftUI cornerRadius
89
+ // rounded-sm → .cornerRadius(4)
90
+ // rounded-md → .cornerRadius(8)
91
+ // rounded-lg → .cornerRadius(12)
92
+ // rounded-xl → .cornerRadius(16)
93
+ // rounded-full → .clipShape(Capsule()) or .cornerRadius(9999)
94
+ ```
95
+
96
+ ### Content elements
97
+
98
+ | HTML | → SwiftUI |
99
+ |---|---|
100
+ | `<p>`, `<span>`, text | `Text("content")` |
101
+ | `<h1>` | `Text("title").font(.largeTitle).fontWeight(.bold)` |
102
+ | `<h2>` | `Text("title").font(.title2).fontWeight(.semibold)` |
103
+ | `<h3>` | `Text("title").font(.headline)` |
104
+ | `<p>` body | `Text("body").font(.body)` |
105
+ | `<small>` / caption | `Text("caption").font(.caption).foregroundStyle(.secondary)` |
106
+ | `<img>` | `AsyncImage(url: URL(string: "..."))` for remote, `Image("name")` for asset |
107
+ | `<button>` primary | `Button("Label") { action() }.buttonStyle(.borderedProminent)` |
108
+ | `<button>` secondary | `Button("Label") { action() }.buttonStyle(.bordered)` |
109
+ | `<button>` ghost/text | `Button("Label") { action() }.buttonStyle(.plain)` |
110
+ | `<input type="text">` | `TextField("Placeholder", text: $binding)` |
111
+ | `<input type="password">` | `SecureField("Password", text: $binding)` |
112
+ | `<select>` | `Picker("Label", selection: $binding) { ForEach(...) }` |
113
+ | `<toggle>` / checkbox | `Toggle("Label", isOn: $binding)` |
114
+ | Icon-only button | `Button { action() } label: { Image(systemName: "xmark") }` |
115
+
116
+ ### Navigation patterns
117
+
118
+ | Pattern | SwiftUI implementation |
119
+ |---------|----------------------|
120
+ | Bottom tab bar | `TabView` with `tabItem { Label("Home", systemImage: "house") }` |
121
+ | Stack navigation | `NavigationStack { ... NavigationLink(destination: ...) }` |
122
+ | Modal / sheet | `.sheet(isPresented: $showModal) { ModalView() }` |
123
+ | Full screen modal | `.fullScreenCover(isPresented: $show) { FullView() }` |
124
+ | Back navigation | Automatic with `NavigationStack` |
125
+ | Action sheet | `.confirmationDialog("Title", isPresented: $show) { ... }` |
126
+
127
+ ## Step 4: Design tokens in SwiftUI
128
+
129
+ ### Color extension (semantic tokens)
130
+
131
+ ```swift
132
+ // Theme/Color+App.swift
133
+
134
+ import SwiftUI
135
+
136
+ extension Color {
137
+ // Extract these hex values from the Stitch HTML's tailwind.config
138
+
139
+ // Backgrounds
140
+ static let appBackground = Color("AppBackground") // Asset catalog
141
+ static let appSurface = Color("AppSurface")
142
+
143
+ // Brand
144
+ static let appPrimary = Color("AppPrimary")
145
+ static let appPrimaryFg = Color("AppPrimaryForeground")
146
+
147
+ // Text
148
+ static let appText = Color("AppText")
149
+ static let appTextMuted = Color("AppTextMuted")
150
+
151
+ // Borders
152
+ static let appBorder = Color("AppBorder")
153
+ }
154
+ ```
155
+
156
+ ### Color asset catalog (light + dark)
157
+
158
+ Create named Color Sets in `Assets.xcassets/Colors/`:
159
+
160
+ For each color (e.g., `AppPrimary`):
161
+ - **Any Appearance:** `#6366F1` (the light mode value)
162
+ - **Dark Appearance:** `#818CF8` (lighter shade for dark bg)
163
+
164
+ Alternatively, define programmatically (no asset catalog needed):
165
+
166
+ ```swift
167
+ // Theme/ThemeTokens.swift
168
+
169
+ import SwiftUI
170
+
171
+ struct ThemeTokens {
172
+ let background: Color
173
+ let surface: Color
174
+ let primary: Color
175
+ let primaryFg: Color
176
+ let text: Color
177
+ let textMuted: Color
178
+ let border: Color
179
+
180
+ static let light = ThemeTokens(
181
+ background: Color(hex: "#FFFFFF"),
182
+ surface: Color(hex: "#F4F4F5"),
183
+ primary: Color(hex: "#6366F1"),
184
+ primaryFg: Color(hex: "#FFFFFF"),
185
+ text: Color(hex: "#09090B"),
186
+ textMuted: Color(hex: "#71717A"),
187
+ border: Color(hex: "#E4E4E7")
188
+ )
189
+
190
+ static let dark = ThemeTokens(
191
+ background: Color(hex: "#09090B"),
192
+ surface: Color(hex: "#18181B"),
193
+ primary: Color(hex: "#818CF8"), // Lightened for dark bg
194
+ primaryFg: Color(hex: "#09090B"),
195
+ text: Color(hex: "#FAFAFA"),
196
+ textMuted: Color(hex: "#A1A1AA"),
197
+ border: Color(hex: "#27272A")
198
+ )
199
+ }
200
+
201
+ // Convenience: Color from hex string
202
+ extension Color {
203
+ init(hex: String) {
204
+ let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
205
+ var int: UInt64 = 0
206
+ Scanner(string: hex).scanHexInt64(&int)
207
+ let r = Double((int & 0xFF0000) >> 16) / 255
208
+ let g = Double((int & 0x00FF00) >> 8) / 255
209
+ let b = Double(int & 0x0000FF) / 255
210
+ self.init(red: r, green: g, blue: b)
211
+ }
212
+ }
213
+ ```
214
+
215
+ ### Environment-based theme access
216
+
217
+ ```swift
218
+ // Anywhere in a view — automatic dark mode
219
+ @Environment(\.colorScheme) var colorScheme
220
+
221
+ var theme: ThemeTokens {
222
+ colorScheme == .dark ? .dark : .light
223
+ }
224
+
225
+ // Usage
226
+ Text("Hello")
227
+ .foregroundStyle(theme.text)
228
+ .background(theme.surface)
229
+ ```
230
+
231
+ ## Step 5: Component template
232
+
233
+ ```swift
234
+ // Views/Components/StitchComponentView.swift
235
+
236
+ import SwiftUI
237
+
238
+ /// StitchComponent — [describe purpose in one sentence]
239
+ struct StitchComponentView: View {
240
+ // MARK: - Properties (equivalent to props)
241
+ let title: String
242
+ var description: String = ""
243
+ var onAction: (() -> Void)? = nil
244
+
245
+ // MARK: - Environment
246
+ @Environment(\.colorScheme) private var colorScheme
247
+
248
+ private var theme: ThemeTokens {
249
+ colorScheme == .dark ? .dark : .light
250
+ }
251
+
252
+ // MARK: - State
253
+ @State private var isPressed = false
254
+
255
+ // MARK: - Body
256
+ var body: some View {
257
+ VStack(alignment: .leading, spacing: 8) {
258
+ Text(title)
259
+ .font(.headline)
260
+ .foregroundStyle(theme.text)
261
+
262
+ if !description.isEmpty {
263
+ Text(description)
264
+ .font(.subheadline)
265
+ .foregroundStyle(theme.textMuted)
266
+ }
267
+
268
+ if let action = onAction {
269
+ Button("Action", action: action)
270
+ .buttonStyle(.borderedProminent)
271
+ .tint(theme.primary)
272
+ }
273
+ }
274
+ .padding(16)
275
+ .frame(maxWidth: .infinity, alignment: .leading)
276
+ .background(theme.surface)
277
+ .clipShape(RoundedRectangle(cornerRadius: 12))
278
+ .overlay(
279
+ RoundedRectangle(cornerRadius: 12)
280
+ .stroke(theme.border, lineWidth: 1)
281
+ )
282
+ // Minimum touch target — 44pt Apple HIG requirement
283
+ .frame(minHeight: 44)
284
+ }
285
+ }
286
+
287
+ // MARK: - Preview
288
+ #Preview {
289
+ VStack(spacing: 16) {
290
+ StitchComponentView(title: "Card Title", description: "Supporting text")
291
+ StitchComponentView(title: "With Action", description: "Tap the button", onAction: {})
292
+ }
293
+ .padding()
294
+ }
295
+ ```
296
+
297
+ ## Step 6: Main app entry point
298
+
299
+ ```swift
300
+ // MyApp.swift
301
+ import SwiftUI
302
+
303
+ @main
304
+ struct MyApp: App {
305
+ var body: some Scene {
306
+ WindowGroup {
307
+ ContentView()
308
+ }
309
+ }
310
+ }
311
+
312
+ // ContentView.swift — root with TabView
313
+ struct ContentView: View {
314
+ var body: some View {
315
+ TabView {
316
+ HomeView()
317
+ .tabItem {
318
+ Label("Home", systemImage: "house")
319
+ }
320
+ ProfileView()
321
+ .tabItem {
322
+ Label("Profile", systemImage: "person")
323
+ }
324
+ }
325
+ }
326
+ }
327
+ ```
328
+
329
+ ## Step 7: Accessibility in SwiftUI
330
+
331
+ SwiftUI handles much of this automatically, but always verify:
332
+
333
+ ```swift
334
+ // Image accessibility
335
+ Image("hero-photo")
336
+ .accessibilityLabel("Team collaborating in a modern office")
337
+
338
+ // Decorative images (screen reader skips)
339
+ Image(decorative: "background-pattern")
340
+
341
+ // Buttons — label is automatic if using Text inside
342
+ Button("Sign In") { ... } // VoiceOver reads "Sign In, button"
343
+
344
+ // Custom accessibility label when button label is ambiguous
345
+ Button { deleteItem() } label: {
346
+ Image(systemName: "trash")
347
+ }
348
+ .accessibilityLabel("Delete item")
349
+
350
+ // Group elements (treats as single unit)
351
+ VStack {
352
+ Text("Sarah Johnson")
353
+ Text("Product Designer")
354
+ }
355
+ .accessibilityElement(children: .combine)
356
+
357
+ // Dynamic type support — always use semantic fonts
358
+ Text("Headline")
359
+ .font(.headline) // ✅ Scales with user's text size
360
+ // NOT .font(.system(size: 17, weight: .semibold)) // ❌ Fixed size
361
+ ```
362
+
363
+ ## Step 8: SwiftUI animations
364
+
365
+ SwiftUI has excellent built-in animations — use them for the micro-interactions:
366
+
367
+ ```swift
368
+ // Button press spring
369
+ Button(action: primaryAction) {
370
+ Text("Get Started")
371
+ .padding(.horizontal, 24)
372
+ .padding(.vertical, 14)
373
+ .background(theme.primary)
374
+ .foregroundStyle(theme.primaryFg)
375
+ .clipShape(Capsule())
376
+ .scaleEffect(isPressed ? 0.96 : 1.0)
377
+ .animation(.spring(response: 0.2, dampingFraction: 0.6), value: isPressed)
378
+ }
379
+ .simultaneousGesture(
380
+ DragGesture(minimumDistance: 0)
381
+ .onChanged { _ in isPressed = true }
382
+ .onEnded { _ in isPressed = false }
383
+ )
384
+
385
+ // Card appear transition
386
+ VStack { /* card content */ }
387
+ .transition(.move(edge: .bottom).combined(with: .opacity))
388
+
389
+ // Respect reduced motion
390
+ @Environment(\.accessibilityReduceMotion) var reduceMotion
391
+
392
+ var animation: Animation {
393
+ reduceMotion ? .none : .spring(response: 0.3, dampingFraction: 0.7)
394
+ }
395
+ ```
396
+
397
+ ## Execution steps
398
+
399
+ 1. **Verify** the Stitch design uses `deviceType: MOBILE`
400
+ 2. **Create Xcode project** — File → New → App, SwiftUI interface, Swift language
401
+ 3. **Data layer** — create `Models/MockData.swift` from static content in the design
402
+ 4. **Theme** — create `Theme/ThemeTokens.swift` with extracted hex values, and `Color+App.swift`
403
+ 5. **Components** — convert the Stitch HTML sections to SwiftUI views, file by file
404
+ 6. **Navigation** — wire up `TabView` (tab bar) or `NavigationStack` (stack)
405
+ 7. **Build and run** — in Xcode, Cmd+R. Test on both light and dark mode (⌃⌘A toggles appearance in Simulator)
406
+
407
+ ## Troubleshooting
408
+
409
+ | Issue | Fix |
410
+ |-------|-----|
411
+ | View overflows screen | Add `.frame(maxWidth: .infinity)` + parent `ScrollView` |
412
+ | Text truncates unexpectedly | Add `.lineLimit(nil)` or `.fixedSize(horizontal: false, vertical: true)` |
413
+ | Color looks wrong in dark mode | Ensure the Color Set in Assets.xcassets has a Dark appearance set |
414
+ | Image not loading | For `AsyncImage`, check URL is valid. For local images, file must be in Assets.xcassets |
415
+ | TabView items don't show label | Content must be directly inside `.tabItem { }` — no wrapping views |
416
+ | Sheet not dismissible | Add `@Environment(\.dismiss) var dismiss` and call `dismiss()` in the sheet |
417
+ | Preview crashes | Check `#Preview` has valid mock data — never optional-unwrap without fallback |
418
+
419
+ ## References
420
+
421
+ - `resources/component-template.swift` — Boilerplate SwiftUI view
422
+ - `resources/layout-mapping.md` — Full HTML/CSS → SwiftUI reference
423
+ - `resources/architecture-checklist.md` — Pre-ship checklist
424
+ - `scripts/fetch-stitch.sh` — Reliable GCS HTML downloader