初始化项目版本
This commit is contained in:
36
.agents/skills/stitch-react-native/README.md
Normal file
36
.agents/skills/stitch-react-native/README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Stitch to React Native Components Skill
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npx skills add google-labs-code/stitch-skills --skill react-native --global
|
||||
```
|
||||
|
||||
## Example Prompt
|
||||
|
||||
```text
|
||||
Convert my Login screen in my Stitch Project to React Native components.
|
||||
```
|
||||
|
||||
## Skill Structure
|
||||
|
||||
This skill follows the **Agent Skills** open standard. Each skill is self-contained with its own logic, validation scripts, and design tokens.
|
||||
|
||||
```text
|
||||
skills/react-native/
|
||||
├── SKILL.md — Core instructions & workflow
|
||||
├── package.json — Validator dependencies
|
||||
├── scripts/ — Networking & AST validation
|
||||
├── resources/ — Architecture checklist & component templates
|
||||
└── examples/ — Gold-standard code samples
|
||||
```
|
||||
|
||||
## How it Works
|
||||
|
||||
When activated, the agent follows a design-to-native pipeline:
|
||||
|
||||
1. **Retrieval**: Uses a system-level `curl` script to download Stitch HTML and screenshots from Google Cloud Storage.
|
||||
2. **Mapping**: Translates HTML elements to React Native primitives (`View`, `Text`, `Pressable`, `Image`, etc.) and converts CSS/Tailwind to `StyleSheet.create()` calls.
|
||||
3. **Generation**: Scaffolds components using Atomic Design (atoms, molecules, organisms).
|
||||
4. **Validation**: Runs an automated AST check to catch missing Props interfaces or hardcoded style values.
|
||||
5. **Audit**: Performs a final self-correction check against the architecture checklist.
|
||||
172
.agents/skills/stitch-react-native/SKILL.md
Normal file
172
.agents/skills/stitch-react-native/SKILL.md
Normal file
@@ -0,0 +1,172 @@
|
||||
---
|
||||
name: stitch::react-native
|
||||
description: >-
|
||||
Convert Stitch HTML designs to React Native components, or syncs/updates existing
|
||||
native components to align with the latest Stitch designs, using StyleSheet.
|
||||
allowed-tools:
|
||||
- "stitch*:*"
|
||||
- "Bash"
|
||||
- "Read"
|
||||
- "Write"
|
||||
- "web_fetch"
|
||||
---
|
||||
|
||||
# Stitch to React Native Components
|
||||
|
||||
You are a mobile engineer focused on transforming Stitch web designs into clean, production-ready React Native code or syncing/updating existing native components to align with the latest Stitch designs. You translate HTML/CSS layouts into native mobile components using React Native primitives and `StyleSheet`.
|
||||
|
||||
> **CRITICAL: Every step in this skill is MANDATORY. Do NOT skip any step or take shortcuts. Each section contains a GATE that must be satisfied before proceeding.**
|
||||
|
||||
## Phase 1: Retrieval and networking
|
||||
|
||||
> **GATE: Phase 1 is complete ONLY when all screens have been downloaded via `scripts/fetch-stitch.sh` AND visually audited. Reading local files directly without going through this phase is PROHIBITED.**
|
||||
|
||||
1. **Namespace discovery**: Run `list_tools` to find the Stitch MCP prefix. Use this prefix (e.g., `stitch:`) for all subsequent calls.
|
||||
2. **Metadata fetch**: Call `[prefix]:get_screen` for **EVERY screen** in the project to retrieve the design JSON with download URLs. Do NOT skip any screen.
|
||||
3. **Check for existing designs**: Before downloading, check if `.stitch/designs/{page}.html` and `.stitch/designs/{page}.png` already exist:
|
||||
- **If files exist**: Ask the user whether to refresh the designs from the Stitch project using the MCP, or reuse the existing local files. **You MUST ask — do not assume.** Only re-download if the user confirms.
|
||||
- **If files do not exist**: Proceed to step 4.
|
||||
4. **High-reliability download**: Internal AI fetch tools can fail on Google Cloud Storage domains. You MUST use the provided script.
|
||||
- **HTML**: `bash scripts/fetch-stitch.sh "[htmlCode.downloadUrl]" ".stitch/designs/{page}.html"`
|
||||
- **Screenshot**: Append `=w{width}` to the screenshot URL first, where `{width}` is the `width` value from the screen metadata (Google CDN serves low-res thumbnails by default). Then run: `bash scripts/fetch-stitch.sh "[screenshot.downloadUrl]=w{width}" ".stitch/designs/{page}.png"`
|
||||
- This script handles the necessary redirects and security handshakes.
|
||||
5. **Visual audit**: Review the downloaded screenshot (`.stitch/designs/{page}.png`) to confirm design intent and layout details. **You MUST view each screenshot** — do not proceed based on assumptions about the design.
|
||||
6. **Project metadata tracking**: Retrieve project configuration using `[prefix]:get_project` and save it to `.stitch/metadata.json` (inside the app folder, and mirrored in the workspace root). Ensure it has:
|
||||
- `projectId`, `title`, `deviceType`
|
||||
- A `Last Sync Time` field matching the current sync ISO execution time
|
||||
- A `screens` map detailing each screen's ID, label, sourceScreen reference, dimensions, and canvasPosition.
|
||||
|
||||
### Anti-patterns for Phase 1
|
||||
- ❌ Reading `.stitch/designs/*.html` directly without calling MCP `get_screen` first.
|
||||
- ❌ Skipping the `fetch-stitch.sh` download script.
|
||||
- ❌ Not asking the user when existing files are found.
|
||||
- ❌ Skipping the visual audit of `.png` screenshots.
|
||||
- ❌ Failing to generate or update `.stitch/metadata.json` and its `Last Sync Time` field upon syncing.
|
||||
|
||||
## Phase 2: Theme extraction
|
||||
|
||||
> **GATE: Phase 2 is complete ONLY when `src/theme.ts` has been created or updated with tokens extracted from the current project's HTML `<head>`. Hardcoding color hex codes or using themes from a different project is NOT acceptable.**
|
||||
|
||||
1. **Extract `tailwind.config`**: Open each downloaded HTML file and locate the `tailwind.config` object in the `<head>` `<script>` block. Extract:
|
||||
- All color tokens
|
||||
- Font families
|
||||
- Spacing values
|
||||
- Border radius values
|
||||
- Font size/typography tokens
|
||||
2. **Create/Sync `src/theme.ts`**: Write the extracted tokens to `src/theme.ts` as TypeScript constants. Ensure every color, spacing, and typography value has a corresponding token.
|
||||
3. **Verify theme**: Confirm the theme colors and fonts in `src/theme.ts` match what you extracted from the HTML design.
|
||||
|
||||
### Anti-patterns for Phase 2
|
||||
- ❌ Hardcoding color hex codes or rgba strings directly inside component StyleSheet declarations.
|
||||
- ❌ Using theme tokens from a previous project without extracting them from the new design.
|
||||
- ❌ Skipping the creation/update of `src/theme.ts`.
|
||||
|
||||
## Phase 3: Architectural rules and HTML mapping
|
||||
|
||||
> **GATE: Every component MUST satisfy ALL of the following rules. Violations will cause `npm run validate` to fail.**
|
||||
|
||||
### Element mapping
|
||||
Map HTML elements to React Native components using these rules:
|
||||
|
||||
| HTML | React Native | Notes |
|
||||
|------|-------------|-------|
|
||||
| `<div>` | `View` | Default container |
|
||||
| `<span>`, `<p>`, `<h1>`-`<h6>` | `Text` | All text must be wrapped in `Text`. Nest `Text` for inline styling. |
|
||||
| `<img>` | `Image` | Use `source={{ uri }}` for remote images, `require()` for local assets. |
|
||||
| `<button>`, `<a>` | `Pressable` | Prefer `Pressable` over `TouchableOpacity`. Use `onPress` instead of `onClick`. |
|
||||
| `<input>` | `TextInput` | Map `placeholder`, `value`, `onChangeText`. |
|
||||
| `<scroll container>` | `ScrollView` | For short lists only. Use `FlatList` for long or dynamic lists. |
|
||||
| `<ul>`/`<ol>` with many items | `FlatList` | Requires `data`, `renderItem`, `keyExtractor`. |
|
||||
| `<section>` with grouped data | `SectionList` | For grouped data with headers. Use tab navigator for tab-based layouts. |
|
||||
| `<select>` | Third-party picker or custom modal | React Native has no built-in select. |
|
||||
| `<svg>` | `react-native-svg` | Convert SVG markup to `Svg`, `Path`, `Circle`, etc. |
|
||||
| Root wrapper | `SafeAreaView` | Wrap top-level screens to avoid notch/status bar overlap. |
|
||||
|
||||
### Style mapping
|
||||
CSS and Tailwind classes do not work in React Native. Convert all styles to `StyleSheet.create()`:
|
||||
|
||||
* **Layout**: Flexbox is the default layout system. `flexDirection` defaults to `'column'` (not `'row'` like web CSS).
|
||||
- `display: flex` is implicit on every `View`.
|
||||
- `justify-content` maps to `justifyContent`.
|
||||
- `align-items` maps to `alignItems`.
|
||||
- `gap` maps to `gap` (React Native 0.71+). For older versions, use `marginBottom` on children.
|
||||
* **Dimensions**: Use numbers (not strings). `width: 100` means 100 density-independent pixels.
|
||||
- Percentage strings are supported: `width: '100%'`.
|
||||
- For responsive sizing, use `useWindowDimensions()` from `react-native`.
|
||||
- There is no `vw`/`vh`. Calculate from `Dimensions.get('window')`.
|
||||
* **Typography**: All text styles must be on `Text` components, never on `View`.
|
||||
- `font-size` maps to `fontSize` (number, not string).
|
||||
- `font-weight` maps to `fontWeight` (string: `'400'`, `'700'`, `'bold'`).
|
||||
- `line-height` maps to `lineHeight` (number).
|
||||
- `letter-spacing` maps to `letterSpacing`.
|
||||
- `text-transform` maps to `textTransform`.
|
||||
- `color` applies to `Text` only.
|
||||
* **Borders and shadows**:
|
||||
- `border-radius` maps to `borderRadius`.
|
||||
- `box-shadow` does not exist. Use `elevation` (Android) and `shadowColor`/`shadowOffset`/`shadowOpacity`/`shadowRadius` (iOS). Use `Platform.select()` to apply platform-specific shadow styles.
|
||||
* **Unsupported CSS properties**: Do not use `hover`, `transition`, `animation` (use `react-native-reanimated` for animations), or `position: fixed` (use absolute positioning instead).
|
||||
|
||||
### Architectural Rules
|
||||
* **Modular components (Atomic Design)**: Break the design into independent files. Organize components as atoms (buttons, labels, icons), molecules (input groups, cards), and organisms (headers, lists, forms). Place them in `src/components/atoms/`, `src/components/molecules/`, and `src/components/organisms/`. Monolithic page/screen files are PROHIBITED.
|
||||
* **Logic isolation**: Move event handlers, API calls, and business logic into custom hooks in `src/hooks/`. Components should only handle rendering.
|
||||
* **Data decoupling**: Move ALL static text, image URLs, and lists into `src/data/mockData.ts`. No hardcoded content in components.
|
||||
* **Type safety**: EVERY component file (including screens) MUST export a TypeScript interface named `[ComponentName]Props` with `readonly` property modifiers. The validator requires the interface to be **exported** — files without an exported Props interface will FAIL validation.
|
||||
* **No hardcoded styles**: Extract colors, spacing, and font sizes into `src/theme.ts`. Reference them in `StyleSheet.create()`. Absolutely no raw color hex codes or rgba strings are allowed in component files.
|
||||
* **Navigation**: Use React Navigation for screen transitions. Define screen types with `NativeStackScreenProps` or `BottomTabScreenProps`.
|
||||
* **Accessibility**: Every interactive element must have `accessibilityLabel` and `accessibilityRole`. Images need `accessibilityLabel`. Use `accessibilityState` for toggles and checkboxes.
|
||||
* **Safe areas**: Wrap top-level screen components with `SafeAreaView` from `react-native-safe-area-context` (not the default one from `react-native`).
|
||||
* **Project specific**: Focus on the target project's needs and constraints. Leave Google license headers out of the generated components.
|
||||
|
||||
### Platform-specific code
|
||||
When the design requires different behavior on iOS and Android:
|
||||
```typescript
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
shadow: Platform.select({
|
||||
ios: {
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 4,
|
||||
},
|
||||
android: {
|
||||
elevation: 4,
|
||||
},
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
### Anti-patterns for Phase 3
|
||||
- ❌ Putting all UI in a single monolithic screen file.
|
||||
- ❌ Using HTML tags (like `div`, `span`, `p`) instead of React Native components.
|
||||
- ❌ Inline event handlers or business logic without custom hooks.
|
||||
- ❌ Hardcoding text, URLs, or colors in component files.
|
||||
- ❌ Components without an **exported** `[Name]Props` interface.
|
||||
- ❌ Using raw hex color values or rgba strings in `StyleSheet.create()`.
|
||||
|
||||
## Phase 4: Execution steps
|
||||
|
||||
> **GATE: Phase 4 verification, audits, and simulator/packager testing are optional. You MUST ask the user's permission to proceed with validation scripts, starting packagers, or simulator audits.**
|
||||
|
||||
1. **Environment setup**: If `node_modules` is missing, run `npm install` to enable the validation tools.
|
||||
2. **Theme layer**: Create `src/theme.ts` from the extracted Tailwind config.
|
||||
3. **Data layer**: Create `src/data/mockData.ts` based on the design content.
|
||||
4. **Component drafting**: Use `resources/component-template.tsx` as a base. Find and replace ALL instances of `StitchComponent` with the actual component name. Map HTML elements to React Native primitives.
|
||||
5. **Navigation wiring**: If the design has multiple screens, set up a `NavigationContainer` with a stack or tab navigator in `App.tsx`.
|
||||
6. **Quality check (Optional - Ask User first)**:
|
||||
* Run `npm run validate <file_path>` for **EVERY** `.tsx` file in components and screens to report component validity.
|
||||
* Run `tsc --noEmit` to verify TypeScript compile status.
|
||||
* Check output against `resources/architecture-checklist.md`.
|
||||
* Obtain permission before starting the packager (`npx react-native start` or `npx expo start`) or starting visual simulator audits to verify the app renders correctly on a simulator/device.
|
||||
|
||||
### Anti-patterns for Phase 4
|
||||
- ❌ Launching packagers or simulators without user consent.
|
||||
- ❌ Declaring task "done" without verifying code compiles.
|
||||
|
||||
## Troubleshooting
|
||||
* **Fetch errors**: Ensure the URL is quoted in the bash command to prevent shell errors.
|
||||
* **Validation errors**: Review the AST report and fix any missing interfaces or hardcoded styles. The most common failures are missing an **exported** `Props` interface or leaving raw hex colors in `StyleSheet.create()`.
|
||||
* **Text outside Text component**: React Native crashes if raw strings appear outside `<Text>`. Verify all text nodes are wrapped.
|
||||
* **Image sizing**: Unlike web `<img>`, React Native `Image` has no intrinsic size. Always specify `width` and `height` in styles or use `aspectRatio`.
|
||||
* **FlatList vs ScrollView**: If you see a "VirtualizedList inside ScrollView" warning, replace the outer `ScrollView` with a plain `View` or use `FlatList` `ListHeaderComponent`/`ListFooterComponent`.
|
||||
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* Copyright 2026 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { View, Text, Image, Pressable, StyleSheet, Platform } from 'react-native';
|
||||
import { colors, shadows } from '../src/theme';
|
||||
|
||||
/**
|
||||
* Gold Standard: ActivityCard
|
||||
* This file is the definitive reference for the agent.
|
||||
*/
|
||||
export interface ActivityCardProps {
|
||||
readonly id: string;
|
||||
readonly username: string;
|
||||
readonly action: 'MERGED' | 'COMMIT';
|
||||
readonly timestamp: string;
|
||||
readonly avatarUrl: string;
|
||||
readonly repoName: string;
|
||||
readonly onPress?: () => void;
|
||||
}
|
||||
|
||||
export const ActivityCard: React.FC<ActivityCardProps> = ({
|
||||
username,
|
||||
action,
|
||||
timestamp,
|
||||
avatarUrl,
|
||||
repoName,
|
||||
onPress,
|
||||
}) => {
|
||||
const isMerged = action === 'MERGED';
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
style={({ pressed }) => [styles.container, pressed && styles.pressed]}
|
||||
onPress={onPress}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={`${username} ${action.toLowerCase()} in ${repoName}`}
|
||||
>
|
||||
<View style={styles.leftContent}>
|
||||
<Image
|
||||
source={{ uri: avatarUrl }}
|
||||
style={styles.avatar}
|
||||
accessibilityLabel={`Avatar for ${username}`}
|
||||
/>
|
||||
|
||||
<View style={styles.textContent}>
|
||||
<Text style={styles.username} numberOfLines={1}>
|
||||
{username}
|
||||
</Text>
|
||||
|
||||
<View
|
||||
style={[
|
||||
styles.badge,
|
||||
isMerged ? styles.badgeMerged : styles.badgeCommit,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.badgeText,
|
||||
isMerged ? styles.badgeTextMerged : styles.badgeTextCommit,
|
||||
]}
|
||||
>
|
||||
{action}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<Text style={styles.separator}>in</Text>
|
||||
|
||||
<Text style={styles.repoName} numberOfLines={1}>
|
||||
{repoName}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text style={styles.timestamp}>{timestamp}</Text>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
gap: 16,
|
||||
borderRadius: 8,
|
||||
padding: 16,
|
||||
minHeight: 56,
|
||||
...shadows.card,
|
||||
},
|
||||
pressed: {
|
||||
opacity: 0.7,
|
||||
},
|
||||
leftContent: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 16,
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
avatar: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 20,
|
||||
},
|
||||
textContent: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
alignItems: 'center',
|
||||
columnGap: 8,
|
||||
rowGap: 4,
|
||||
flex: 1,
|
||||
},
|
||||
username: {
|
||||
fontWeight: '600',
|
||||
fontSize: 14,
|
||||
},
|
||||
badge: {
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 2,
|
||||
borderRadius: 12,
|
||||
},
|
||||
badgeMerged: {
|
||||
backgroundColor: colors.badgeMergedBg,
|
||||
},
|
||||
badgeCommit: {
|
||||
backgroundColor: colors.badgeCommitBg,
|
||||
},
|
||||
badgeText: {
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
},
|
||||
badgeTextMerged: {
|
||||
color: colors.badgeMergedText,
|
||||
},
|
||||
badgeTextCommit: {
|
||||
color: colors.badgeCommitText,
|
||||
},
|
||||
separator: {
|
||||
fontSize: 14,
|
||||
opacity: 0.6,
|
||||
},
|
||||
repoName: {
|
||||
fontSize: 14,
|
||||
},
|
||||
timestamp: {
|
||||
fontSize: 14,
|
||||
fontWeight: '400',
|
||||
opacity: 0.5,
|
||||
flexShrink: 0,
|
||||
},
|
||||
});
|
||||
|
||||
export default ActivityCard;
|
||||
283
.agents/skills/stitch-react-native/package-lock.json
generated
Normal file
283
.agents/skills/stitch-react-native/package-lock.json
generated
Normal file
@@ -0,0 +1,283 @@
|
||||
{
|
||||
"name": "react-native-components",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "react-native-components",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@swc/core": "^1.3.100"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core": {
|
||||
"version": "1.15.43",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.43.tgz",
|
||||
"integrity": "sha512-1CuKjFkPxIgGdeHVuNbkxmBxkcbdc08u0aiI43pFq6yY1tTVKmXT9hFEooyyKs/sJ3xf1GPHyEwTtk9Xl8dvQw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@swc/counter": "^0.1.3",
|
||||
"@swc/types": "^0.1.27"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/swc"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@swc/core-darwin-arm64": "1.15.43",
|
||||
"@swc/core-darwin-x64": "1.15.43",
|
||||
"@swc/core-linux-arm-gnueabihf": "1.15.43",
|
||||
"@swc/core-linux-arm64-gnu": "1.15.43",
|
||||
"@swc/core-linux-arm64-musl": "1.15.43",
|
||||
"@swc/core-linux-ppc64-gnu": "1.15.43",
|
||||
"@swc/core-linux-s390x-gnu": "1.15.43",
|
||||
"@swc/core-linux-x64-gnu": "1.15.43",
|
||||
"@swc/core-linux-x64-musl": "1.15.43",
|
||||
"@swc/core-win32-arm64-msvc": "1.15.43",
|
||||
"@swc/core-win32-ia32-msvc": "1.15.43",
|
||||
"@swc/core-win32-x64-msvc": "1.15.43"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@swc/helpers": ">=0.5.17"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@swc/helpers": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-darwin-arm64": {
|
||||
"version": "1.15.43",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.43.tgz",
|
||||
"integrity": "sha512-v1aVuvXdo/BHxJzco9V2xpHrvwWmhfS8t6gziY5wJxd+Z2h8AeJRnAwPD8itCDaGXVBwJ/CaKfxEzTkG0Va0OA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-darwin-x64": {
|
||||
"version": "1.15.43",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.43.tgz",
|
||||
"integrity": "sha512-lp3d4Lamc8dt5huYdGLSR+9hLxmfr1jb0l+4XXG2zPqZwYWRN9R0U2qYoTrggiU2RWW0oV9VbWM3kBnqIc2kdQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm-gnueabihf": {
|
||||
"version": "1.15.43",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.43.tgz",
|
||||
"integrity": "sha512-JWTQQELtsG5GgphDrr/XqqmM2pDN3cZqbMS0Mrg+iTiXL3F74sn/S2IyYE/5u4h2KLkTf9qQ7dXyxsbx7YzkeA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm64-gnu": {
|
||||
"version": "1.15.43",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.43.tgz",
|
||||
"integrity": "sha512-B4otJRdPWIsmiSBf0uG7Z/+vMWmkufjz5MmYxubwKuZazDW14Zd3symga1N62QR4RT+kEFeHEgsXfZGyn/w0hw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm64-musl": {
|
||||
"version": "1.15.43",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.43.tgz",
|
||||
"integrity": "sha512-6zB6OnpViBxYy4tgY3v2i6AZY9fwkcHZ032UOwtwUuW1d19sdT07qF0kZe6/3UR1tUaK6jjg2rmVcUIBCEYVjQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-ppc64-gnu": {
|
||||
"version": "1.15.43",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.43.tgz",
|
||||
"integrity": "sha512-coxE1ZWdB3uSDVNoEtYNrRi/1epvckZx9cTJ8ICUxTMTxGk+yvQ/Twacp3ruZSaMPGCriUjP86C37VhaT6nyRg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-s390x-gnu": {
|
||||
"version": "1.15.43",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.43.tgz",
|
||||
"integrity": "sha512-lXfLhs+LpBsD5inuYx+YDH5WsPPBQ95KPUiy8P5wq9ob9xKDZFqwNfU2QW6bGO8NqRO/H9JQomTSt5Yyh+FGfA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-x64-gnu": {
|
||||
"version": "1.15.43",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.43.tgz",
|
||||
"integrity": "sha512-07XnKwTmKy8TGOZG3D9fRnLWGynxPjwQnZLVmBFbo6F+7vHYzBIOuwXEhemrChBWb6yDNZsVCcMWCPX6FDD2xg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-x64-musl": {
|
||||
"version": "1.15.43",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.43.tgz",
|
||||
"integrity": "sha512-TJc+bsSIaBh+hZvZ5GRtW/K1bw66TJ9vsUwvVIsZdiWxU5ObLwZvfcnZ3UpgVfMnFibRes9uriJrQNBHEEogRQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-arm64-msvc": {
|
||||
"version": "1.15.43",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.43.tgz",
|
||||
"integrity": "sha512-jfd7s2/bUQYkOHLs+LWQNKZdmDa8+sufKLllhpWAhVQ2GDCwsHe3vR/j+OSiItZNtkzFuaawa3+SAKz9y5gYfw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-ia32-msvc": {
|
||||
"version": "1.15.43",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.43.tgz",
|
||||
"integrity": "sha512-rLAE8JvucqEW1ZGohxPQrQWPBQeJG4+ypKbWfdlU/qmKScvCkxf9/Jxnzki1dkUQCQ7P5Enp13RlvqOlvx/32g==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-x64-msvc": {
|
||||
"version": "1.15.43",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.43.tgz",
|
||||
"integrity": "sha512-h8MLDHZcfIukwQWj03rIJZx1I0E81AYj2X7J/nGErG4nz+QAv6G1Z+peotvinL3lqpbo32tLYSMFo32/ySzxKg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/counter": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@swc/types": {
|
||||
"version": "0.1.27",
|
||||
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.27.tgz",
|
||||
"integrity": "sha512-K6h3iUlqeM946U4sXFYeahefR1YBbXJvko+hv8WS8/0BNJ4OHiHRywMnQUJCqkR7Y9+hqQ1TvEpiKqUhz7NEFg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@swc/counter": "^0.1.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
.agents/skills/stitch-react-native/package.json
Normal file
16
.agents/skills/stitch-react-native/package.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "react-native-components",
|
||||
"version": "1.0.0",
|
||||
"description": "Design-to-code prompt to React Native components for Stitch MCP",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"validate": "node scripts/validate.js",
|
||||
"fetch": "bash scripts/fetch-stitch.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"@swc/core": "^1.3.100"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
# Architecture Quality Gate
|
||||
|
||||
### Structural integrity
|
||||
- [ ] Components organized by Atomic Design: atoms, molecules, organisms in `src/components/`.
|
||||
- [ ] Logic extracted to custom hooks in `src/hooks/`.
|
||||
- [ ] No monolithic files. Each component in its own file.
|
||||
- [ ] All static text, image URIs, and list data moved to `src/data/mockData.ts`.
|
||||
|
||||
### Type safety and syntax
|
||||
- [ ] Every component exports a `[ComponentName]Props` interface with `readonly` property modifiers.
|
||||
- [ ] File is syntactically valid TypeScript (no parse errors).
|
||||
- [ ] Placeholders from template (e.g., `StitchComponent`) have been replaced with actual names.
|
||||
- [ ] Navigation screen params are typed with `NativeStackScreenProps` or equivalent.
|
||||
|
||||
### React Native primitives
|
||||
- [ ] No HTML elements (`div`, `span`, `p`, `img`, `button`). Only React Native components.
|
||||
- [ ] All text wrapped in `Text` components. No raw strings inside `View`.
|
||||
- [ ] `Pressable` used for interactive elements (not `TouchableOpacity` or `TouchableHighlight`).
|
||||
- [ ] `FlatList` used for dynamic/long lists (not `ScrollView` with `.map()`).
|
||||
- [ ] `Image` components have explicit `width` and `height` or `aspectRatio`.
|
||||
|
||||
### Styling
|
||||
- [ ] All styles defined via `StyleSheet.create()` at the bottom of the file.
|
||||
- [ ] No inline style objects (use `StyleSheet` references).
|
||||
- [ ] No hardcoded hex values. Colors referenced from `src/theme.ts`.
|
||||
- [ ] Shadows use `Platform.select()` for iOS/Android differences.
|
||||
- [ ] `flexDirection` explicitly set where row layout is needed (default is `column`).
|
||||
|
||||
### Accessibility
|
||||
- [ ] Interactive elements have `accessibilityLabel` and `accessibilityRole`.
|
||||
- [ ] Images have descriptive `accessibilityLabel`.
|
||||
- [ ] Toggle/checkbox elements use `accessibilityState`.
|
||||
|
||||
### Platform handling
|
||||
- [ ] Top-level screens wrapped with `SafeAreaView` from `react-native-safe-area-context`.
|
||||
- [ ] Platform-specific code uses `Platform.select()` or `Platform.OS` checks.
|
||||
- [ ] Responsive dimensions use `useWindowDimensions()` (not hardcoded pixel values for screen-relative sizing).
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
|
||||
export interface StitchComponentProps {
|
||||
readonly children?: React.ReactNode;
|
||||
readonly testID?: string;
|
||||
}
|
||||
|
||||
export const StitchComponent: React.FC<StitchComponentProps> = ({
|
||||
children,
|
||||
testID,
|
||||
}) => {
|
||||
return (
|
||||
<View style={styles.container} testID={testID} accessibilityRole="none">
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {},
|
||||
});
|
||||
|
||||
export default StitchComponent;
|
||||
31
.agents/skills/stitch-react-native/scripts/fetch-stitch.sh
Executable file
31
.agents/skills/stitch-react-native/scripts/fetch-stitch.sh
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
# Copyright 2026 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
URL=$1
|
||||
OUTPUT=$2
|
||||
if [ -z "$URL" ] || [ -z "$OUTPUT" ]; then
|
||||
echo "Usage: $0 <url> <output_path>"
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p "$(dirname "$OUTPUT")"
|
||||
echo "Initiating high-reliability fetch for Stitch HTML..."
|
||||
curl -L -f -sS --connect-timeout 10 --compressed "$URL" -o "$OUTPUT"
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Successfully retrieved HTML at: $OUTPUT"
|
||||
exit 0
|
||||
else
|
||||
echo "Error: Failed to retrieve content. Check TLS/SNI or URL expiration."
|
||||
exit 1
|
||||
fi
|
||||
124
.agents/skills/stitch-react-native/scripts/validate.js
Normal file
124
.agents/skills/stitch-react-native/scripts/validate.js
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Copyright 2026 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import swc from '@swc/core';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
const HEX_COLOR_REGEX = /#[0-9A-Fa-f]{3,8}\b/;
|
||||
const RGBA_COLOR_REGEX = /^rgba?\(\s*\d/;
|
||||
const HTML_ELEMENTS = ['div', 'span', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'img', 'button', 'a', 'input', 'ul', 'ol', 'li', 'section', 'header', 'footer', 'nav', 'main'];
|
||||
|
||||
async function validateComponent(filePath) {
|
||||
if (!filePath) {
|
||||
console.error("Usage: node validate.js <path-to-component>");
|
||||
process.exit(1);
|
||||
}
|
||||
try {
|
||||
const code = fs.readFileSync(filePath, 'utf-8');
|
||||
const filename = path.basename(filePath);
|
||||
const ast = await swc.parse(code, { syntax: "typescript", tsx: true });
|
||||
let hasInterface = false;
|
||||
let hasExportedInterface = false;
|
||||
let colorIssues = [];
|
||||
let htmlElements = [];
|
||||
|
||||
console.log("Scanning AST...");
|
||||
|
||||
const walk = (node, parent) => {
|
||||
if (!node || typeof node !== 'object') return;
|
||||
if (Array.isArray(node)) {
|
||||
for (const item of node) walk(item, parent);
|
||||
return;
|
||||
}
|
||||
if (typeof node.type !== 'string') return;
|
||||
|
||||
if (node.type === 'TsInterfaceDeclaration' && node.id.value.endsWith('Props')) {
|
||||
hasInterface = true;
|
||||
if (parent?.type === 'ExportDeclaration') {
|
||||
hasExportedInterface = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for hardcoded hex values in strings
|
||||
if (node.type === 'StringLiteral' && HEX_COLOR_REGEX.test(node.value)) {
|
||||
colorIssues.push(node.value);
|
||||
}
|
||||
|
||||
// Check for rgba() color strings
|
||||
if (node.type === 'StringLiteral' && RGBA_COLOR_REGEX.test(node.value)) {
|
||||
colorIssues.push(node.value);
|
||||
}
|
||||
|
||||
// Check for HTML elements used as JSX tags
|
||||
if (node.type === 'JSXOpeningElement' && node.name?.type === 'Identifier') {
|
||||
const tagName = node.name.value;
|
||||
if (HTML_ELEMENTS.includes(tagName)) {
|
||||
htmlElements.push(tagName);
|
||||
}
|
||||
}
|
||||
|
||||
for (const key in node) {
|
||||
if (key === 'span') continue;
|
||||
if (node[key] && typeof node[key] === 'object') walk(node[key], node);
|
||||
}
|
||||
};
|
||||
walk(ast, null);
|
||||
|
||||
console.log(`--- Validation for: ${filename} ---`);
|
||||
|
||||
let valid = true;
|
||||
|
||||
if (hasExportedInterface) {
|
||||
console.log("PASS: Exported Props interface found.");
|
||||
} else if (hasInterface) {
|
||||
console.error("WARN: Props interface found but not exported. Add 'export' keyword.");
|
||||
valid = false;
|
||||
} else {
|
||||
console.error("FAIL: Missing Props interface (must end in 'Props' and be exported).");
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (colorIssues.length === 0) {
|
||||
console.log("PASS: No hardcoded color values found.");
|
||||
} else {
|
||||
console.error(`FAIL: Found ${colorIssues.length} hardcoded colors. Use theme.ts instead.`);
|
||||
colorIssues.forEach(c => console.error(` - ${c}`));
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (htmlElements.length === 0) {
|
||||
console.log("PASS: No HTML elements found. Using React Native primitives.");
|
||||
} else {
|
||||
const unique = [...new Set(htmlElements)];
|
||||
console.error(`FAIL: Found HTML elements: ${unique.join(', ')}. Replace with React Native components.`);
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
console.log("\nCOMPONENT VALID.");
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.error("\nVALIDATION FAILED.");
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("ERROR:", err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
validateComponent(process.argv[2]);
|
||||
Reference in New Issue
Block a user