Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ Converts Stitch screens to React component systems with automated validation and
npx skills add google-labs-code/stitch-skills --skill react:components --global
```

### vue-components
Converts Stitch screens to Vue component systems with automated validation and design token consistency.

```bash
npx skills add google-labs-code/stitch-skills --skill vue:components --global
```

### remotion
Generates walkthrough videos from Stitch projects using Remotion with smooth transitions, zooming, and text overlays to showcase app screens professionally.

Expand Down
44 changes: 44 additions & 0 deletions skills/vue-components/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# vue:components

Converts Stitch designs into modular **Vue 3** Single File Components using the Composition API (`<script setup lang="ts">`).

## Prerequisites

- Node.js >= 18
- A Vite + Vue 3 project (target output directory)
- Stitch MCP server connected (or existing `.stitch/designs/` files)

## Setup

```bash
npm install
```

## Usage

```bash
# Validate a single component
npm run validate src/components/MyCard.vue

# Download a Stitch design asset
npm run fetch "<url>" ".stitch/designs/home.html"
```

## Skill conventions

| Pattern | Vue 3 implementation |
|---|---|
| Component props | `defineProps<Props>()` с типизированными дженериками в `<script setup>` |
| Props с дефолтами | `withDefaults(defineProps<Props>(), { ... })` |
| Переиспользуемая логика | Composables в `src/composables/` с префиксом `use` |
| Контент-слот | `<slot />` в шаблоне |
| Динамические классы | `:class="['base', condition ? 'a' : 'b']"` |
| Реактивные переменные | `ref()` для примитивов, `reactive()` для объектов |
| Производное состояние | `computed(() => ...)` — не дублировать пропсы в `ref` |
| Побочные эффекты | `watch()` / `watchEffect()` |
| События | `defineEmits<{ eventName: [payload: Type] }>()` |

## Validator checks

1. **Typed `defineProps`** — must use `defineProps<YourProps>()` (not runtime object syntax)
2. **No hardcoded hex colors** — use theme-mapped Tailwind classes from `resources/style-guide.json`
61 changes: 61 additions & 0 deletions skills/vue-components/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
name: vue:components
description: Converts Stitch designs into modular Vite and Vue 3 components using Composition API, script setup, and AST-based validation.
allowed-tools:
- "stitch*:*"
- "Bash"
- "Read"
- "Write"
- "web_fetch"
---

# Stitch to Vue 3 Components

You are a frontend engineer focused on transforming designs into clean Vue 3 code. You follow a modular approach with Composition API (`<script setup lang="ts">`) and use automated tools to ensure code quality.

## Retrieval and networking
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` to retrieve the design JSON.
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. 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.
- **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. 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.

## Architectural rules
* **Single File Components**: Each component lives in its own `.vue` file — `<script setup>`, `<template>`, `<style scoped>` (if needed).
* **Modular components**: Break the design into independent files. Avoid large, single-file outputs.
* **Logic isolation**: Move reactive logic, computed values, and event handlers into composables in `src/composables/`.
* **Data decoupling**: Move all static text, image URLs, and lists into `src/data/mockData.ts`.
* **Type safety**: Every component must declare props using `defineProps<Props>()`. Use `withDefaults` when defaults are needed.
* **Project specific**: Focus on the target project's needs and constraints. Leave Google license headers out of the generated Vue components.
* **Style mapping**:
* Extract the `tailwind.config` from the HTML `<head>`.
* Sync these values with `resources/style-guide.json`.
* Use theme-mapped Tailwind classes instead of arbitrary hex codes.

## Composition API conventions
* Always use `<script setup lang="ts">` — never Options API or `defineComponent`.
* Declare props with typed generics: `const props = defineProps<MyComponentProps>()`.
* Use `withDefaults(defineProps<Props>(), { ... })` when any prop has a default value.
* Use `defineEmits<{ eventName: [payload: Type] }>()` for typed emit declarations.
* Derive reactive state from props with `computed()`, not local `ref` copies.
* Prefix composable files with `use` (e.g., `useActivityFeed.ts`).

## Execution steps
1. **Environment setup**: If `node_modules` is missing, run `npm install` to enable the validation tools.
2. **Data layer**: Create `src/data/mockData.ts` based on the design content.
3. **Component drafting**: Use `resources/component-template.vue` as a base. Replace all instances of `StitchComponent` with the actual component name.
4. **Application wiring**: Update the project entry point (`App.vue`) to import and render the new components.
5. **Quality check**:
* Run `npm run validate <file_path>` for each `.vue` component.
* Verify the final output against `resources/architecture-checklist.md`.
* Start the dev server with `npm run dev` to verify the live result.

## Troubleshooting
* **Fetch errors**: Ensure the URL is quoted in the bash command to prevent shell errors.
* **Validation errors**: Review the report and fix missing `defineProps` declarations or hardcoded hex colors in the template.
* **Reactivity pitfalls**: Never destructure props directly — use `toRefs(props)` or access via `props.x` to preserve reactivity.
62 changes: 62 additions & 0 deletions skills/vue-components/examples/gold-standard-card.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<script setup lang="ts">
// Note for Agent: The '@' alias refers to the target project's src directory.
// Ensure src/data/mockData.ts is created before generating this component.
import { computed } from 'vue'
import { cardData } from '@/data/mockData'

/**
* Gold Standard: ActivityCard
* This file serves as the definitive reference for the agent.
*/
interface ActivityCardProps {
id: string
username: string
action: 'MERGED' | 'COMMIT'
timestamp: string
avatarUrl: string
repoName: string
}

const props = defineProps<ActivityCardProps>()

const isMerged = computed(() => props.action === 'MERGED')
</script>

<template>
<div class="flex items-center justify-between gap-4 rounded-lg bg-surface-dark p-4 min-h-14 shadow-sm ring-1 ring-white/10">
<div class="flex items-center gap-4 overflow-hidden">
<div
class="aspect-square h-10 w-10 flex-shrink-0 rounded-full bg-cover bg-center bg-no-repeat"
:style="{ backgroundImage: `url(${props.avatarUrl})` }"
:aria-label="`Avatar for ${props.username}`"
/>

<div class="flex flex-wrap items-center gap-x-2 gap-y-1 text-sm sm:text-base">
<a href="#" class="font-semibold text-primary hover:underline truncate">
{{ props.username }}
</a>

<span
:class="[
'inline-block px-2 py-0.5 text-xs font-semibold rounded-full',
isMerged ? 'bg-purple-500/30 text-purple-300' : 'bg-primary/30 text-primary',
]"
>
{{ props.action }}
</span>

<span class="text-white/60">in</span>

<a href="#" class="text-primary hover:underline truncate">
{{ props.repoName }}
</a>
</div>
</div>

<div class="shrink-0">
<p class="text-sm font-normal leading-normal text-white/50">
{{ props.timestamp }}
</p>
</div>
</div>
</template>
16 changes: 16 additions & 0 deletions skills/vue-components/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "vue-components",
"version": "1.0.0",
"description": "Design-to-code prompt to Vue 3 Composition API components for Stitch MCP",
"type": "module",
"scripts": {
"validate": "node scripts/validate.js",
"fetch": "bash scripts/fetch-stitch.sh"
},
"dependencies": {
"@vue/compiler-sfc": "^3.4.0"
},
"engines": {
"node": ">=18.0.0"
}
}
21 changes: 21 additions & 0 deletions skills/vue-components/resources/architecture-checklist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Architecture Quality Gate

### Structural integrity
- [ ] Logic extracted to composables in `src/composables/` (prefixed with `use`).
- [ ] No monolithic files; strictly Atomic/Composite modularity.
- [ ] All static text/URLs moved to `src/data/mockData.ts`.

### Type safety and syntax
- [ ] Props declared with `defineProps<Props>()` (typed generic, not runtime object).
- [ ] `withDefaults` used when any prop has a default value.
- [ ] Emits declared with `defineEmits<{ ... }>()` if the component emits events.
- [ ] File is syntactically valid Vue SFC (no parse errors).
- [ ] Placeholders from templates (e.g., `StitchComponent`) have been replaced with actual names.

### Reactivity
- [ ] Props accessed via `props.x`, not destructured directly.
- [ ] Derived state uses `computed()`, not local `ref` copies of props.

### Styling and theming
- [ ] Dark mode (`dark:`) applied to all color classes.
- [ ] No hardcoded hex values in template — use theme-mapped Tailwind classes.
18 changes: 18 additions & 0 deletions skills/vue-components/resources/component-template.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<template>
<div :class="['relative', props.class]">
<slot />
</div>
</template>

<script setup lang="ts">
// Use a valid identifier like 'StitchComponent' as the placeholder.
// Replace ALL occurrences of 'StitchComponent' with the actual component name.

interface StitchComponentProps {
class?: string;
}

const props = withDefaults(defineProps<StitchComponentProps>(), {
class: "",
});
</script>
27 changes: 27 additions & 0 deletions skills/vue-components/resources/style-guide.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"theme": {
"colors": {
"primary": "#19e66f",
"background": {
"light": "#f6f8f7",
"dark": "#112118",
"elevated": "#1A1A1A"
},
"accent": {
"purple": "#8A2BE2",
"lavender": "#D0A9F5"
}
},
"typography": {
"display": [
"Space Grotesk",
"sans-serif"
],
"icons": "Material Symbols Outlined"
},
"spacing": {
"header-h": "72px",
"container-max": "960px"
}
}
}
30 changes: 30 additions & 0 deletions skills/vue-components/scripts/fetch-stitch.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/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
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
69 changes: 69 additions & 0 deletions skills/vue-components/scripts/validate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* Vue 3 SFC Validator
* Checks that a .vue component follows the skill's architectural rules:
* - Has a typed defineProps<Props>() declaration
* - Contains no hardcoded hex color values in the template
*/

import { parse } from '@vue/compiler-sfc'
import fs from 'node:fs'
import path from 'node:path'

const HEX_COLOR_REGEX = /#[0-9A-Fa-f]{6}/g

async function validateComponent(filePath) {
if (!filePath) {
console.error('Usage: node scripts/validate.js <file_path>')
process.exit(1)
}

const source = fs.readFileSync(filePath, 'utf-8')
const filename = path.basename(filePath)

const { descriptor, errors } = parse(source, { filename })

if (errors.length > 0) {
console.error('❌ PARSE ERROR:', errors[0].message)
process.exit(1)
}

const scriptContent =
descriptor.scriptSetup?.content ?? descriptor.script?.content ?? ''
const templateContent = descriptor.template?.content ?? ''

// Check for typed defineProps
const hasDefineProps = /defineProps\s*</.test(scriptContent)

// Check for hardcoded hex values in template
const hexMatches = [...templateContent.matchAll(HEX_COLOR_REGEX)].map(
(m) => m[0],
)

console.log('🔍 Scanning Vue SFC...')
console.log(`--- Validation for: ${filename} ---`)

if (hasDefineProps) {
console.log('✅ defineProps<Props>() declaration found.')
} else {
console.error(
"❌ MISSING: Typed defineProps (use defineProps<YourProps>() pattern).",
)
}

if (hexMatches.length === 0) {
console.log('✅ No hardcoded hex values found in template.')
} else {
console.error(`❌ STYLE: Found ${hexMatches.length} hardcoded hex codes.`)
hexMatches.forEach((hex) => console.error(` - ${hex}`))
}

if (hasDefineProps && hexMatches.length === 0) {
console.log('\n✨ COMPONENT VALID.')
process.exit(0)
} else {
console.error('\n🚫 VALIDATION FAILED.')
process.exit(1)
}
}

validateComponent(process.argv[2])