flutter-ui ยท diff
git:20260619.fbff116 to git:20260630.8299393
50 added, 266 removed. Audit A to A.
---
name: flutter-ui
description: Build Flutter UI from Figma MCP or image input. Scans src for design tokens (colors, sizes, text styles), existing components, and naming conventions before writing a single line of code. Never hard-codes values.
---
# /supergraph:flutter-ui
Build pixel-faithful Flutter UI grounded in the project's own design system.
Announce: "๐จ /supergraph:flutter-ui โ scanning project design system..."
## Input modes
- **Figma MCP** โ user provides a Figma URL or node ID
- **Image** โ user provides a screenshot or design image
- Both modes follow the same pipeline.
-
- ### Figma URL parsing
-
- Extract `fileKey` and `nodeId` from the URL before calling any Figma tool:
-
- ```
- https://www.figma.com/design/<fileKey>/Title?node-id=<raw-nodeId>
- ```
-
- - `fileKey` โ alphanumeric segment after `/design/` or `/file/`
- - `nodeId` โ value of `node-id` query param; replace `-` with `:` (e.g. `123-456` โ `123:456`)
-
- If the user provides only a `nodeId` string without a URL, use it as-is.
+ Figma URL: extract `fileKey` (after `/design/`) and `nodeId` (`node-id` param, replace `-` with `:`).
---
## Step 1 โ Scan project design system
- Before reading any Figma or image data, scan src to understand what already exists.
-
### 1a. Locate token files
- Search for files likely to hold design tokens. Common patterns:
-
- ```
- find . -type f -name "*.dart" | xargs grep -l "Color\|Colors\." | grep -iE "color|theme|token|palette|constant|style|dimen|size|spacing|radius" | head -20
- find . -type f -name "*.dart" | xargs grep -l "TextStyle\|fontSize\|FontWeight" | grep -iE "text|typo|font|style|theme" | head -20
- find . -type f -name "*.dart" | xargs grep -l "EdgeInsets\|Radius\|double" | grep -iE "dimen|size|spacing|padding|radius|constant" | head -20
+ ```bash
+ find . -type f -name "*.dart" | xargs grep -l "Color\|Colors\." | grep -iE "color|theme|token|palette|constant|style" | head -20
+ find . -type f -name "*.dart" | xargs grep -l "TextStyle\|fontSize" | grep -iE "text|typo|font|style|theme" | head -20
+ find . -type f -name "*.dart" | xargs grep -l "EdgeInsets\|Radius\|double" | grep -iE "dimen|size|spacing|radius|constant" | head -20
find . \( -name "theme" -o -name "themes" \) -type d | head -10
```
### 1b. Extract token registry
- Read the identified files and build a registry. Replace `<token_file>`, `<style_file>`, `<dimen_file>`, `<theme_file>` with the actual file paths found in Step 1a:
-
- **Colors:**
-
- ```
- grep -n "static.*Color\|Color(" <token_file> | head -40
- ```
-
- Extract: constant name โ hex/Color value. Example:
-
- ```
- AppColors.primary โ Color(0xFF6C63FF)
- AppColors.background โ Color(0xFFF5F5F5)
- ```
-
- **Text styles:**
-
- ```
- grep -n "TextStyle\|fontSize\|FontWeight\|fontFamily" <style_file> | head -40
- ```
-
- Extract: style name โ fontSize + fontWeight + color. Example:
-
- ```
- AppTextStyles.heading1 โ 24sp, FontWeight.w700, AppColors.textPrimary
- AppTextStyles.body โ 14sp, FontWeight.w400, AppColors.textSecondary
- ```
-
- **Spacing / sizing:**
-
- ```
- grep -n "static.*double\|const.*= [0-9]" <dimen_file> | head -40
- ```
-
- Extract: constant name โ value. Example:
-
- ```
- AppSizes.paddingM โ 16.0
- AppSizes.radiusS โ 8.0
- ```
-
- **Theme setup:**
-
- ```
- grep -n "ThemeData\|colorScheme\|MaterialApp" <theme_file> | head -20
- ```
-
- Determine token access pattern โ this governs all generated code:
-
- - **Direct class** (e.g. `AppColors.primary`, `AppTextStyles.body`) โ use class references in Step 5
- - **ThemeData/ColorScheme** (e.g. `Theme.of(context).colorScheme.primary`) โ use `Theme.of(context)` in Step 5; map token names to `ColorScheme` / `TextTheme` properties instead of static class fields
-
- If mixed (both patterns exist), note which is dominant and match that.
+ Read identified files and extract:
+ - **Colors**: `grep -n "static.*Color\|Color(" <token_file> | head -40` โ constant name โ hex/Color value
+ - **Text styles**: `grep -n "TextStyle\|fontSize\|FontWeight" <style_file> | head -40` โ style name โ fontSize + weight + color
+ - **Spacing/sizing**: `grep -n "static.*double\|const.*= [0-9]" <dimen_file> | head -40` โ constant name โ value
+ - **Theme**: `grep -n "ThemeData\|colorScheme\|MaterialApp" <theme_file> | head -20` โ determine access pattern (direct class vs `Theme.of(context)`)
### 1c. Find existing reusable widgets
- ```
- find . -path "*/widgets/*.dart" -o -path "*/components/*.dart" -o -path "*/common/*.dart" | head -30
- ```
-
- For each file, extract the widget class name and constructor signature using grep (avoids line-count truncation):
-
```bash
- grep -n "class \|const \|required \|this\." <widget_file> | head -20
- ```
-
- Read until the constructor closing `)` is found. List as:
-
- ```
- AppButton(label, onPressed, [variant]) โ lib/widgets/app_button.dart
- AppCard(child, [padding, elevation]) โ lib/widgets/app_card.dart
+ find . -path "*/widgets/*.dart" -o -path "*/components/*.dart" -o -path "*/common/*.dart" | head -30
```
-
- If no widget files found in any of the search paths: skip reuse โ generate all UI sections as new private classes.
+ For each: `grep -n "class \|const \|required \|this\." <widget_file> | head -20` โ list as `WidgetName(params) โ path`.
### 1d. Detect state management
```bash
- grep -rn "import.*bloc\|import.*riverpod\|import.*provider\|import.*get_it\|import.*getx\|import.*mobx\|import.*signals" lib/ pubspec.yaml | head -10
+ grep -rn "import.*bloc\|import.*riverpod\|import.*provider\|import.*getx\|import.*mobx\|import.*signals" lib/ pubspec.yaml | head -10
```
- Note the detected pattern (BLoC / Riverpod / Provider / GetX / none). Generated code must match this pattern exactly.
-
### 1e. Detect naming & import convention
- Pick the first `.dart` file found under common screen directories as the reference file:
-
```bash
- find . \( -path "*/lib/screens/*.dart" -o -path "*/lib/pages/*.dart" -o -path "*/lib/features/*.dart" -o -path "*/lib/views/*.dart" -o -path "*/lib/ui/*.dart" -o -path "*/lib/presentation/*.dart" \) | head -1
- ```
-
- If none found, widen to any dart file in lib/:
-
- ```bash
- find lib/ -name "*.dart" | head -1
- ```
-
- Then extract import style:
-
- ```bash
+ find . \( -path "*/lib/screens/*.dart" -o -path "*/lib/pages/*.dart" -o -path "*/lib/features/*.dart" \) | head -1
grep -n "import.*lib/" <reference_file> | head -10
```
- Note: barrel exports (`index.dart`), path aliases, part files. This determines how to write imports in generated code. If still no file found, default to direct path imports (`import 'package:<app>/...`').
-
---
## Step 2 โ Extract design from Figma or image
- ### If Figma MCP:
-
- ```
- mcp__Figma__get_figma_data(fileKey=<key>, nodeId=<nodeId>)
- ```
-
- From the response, extract:
-
- - **Layout**: width, height, padding, gap, axis direction (row/column), alignment
- - **Colors**: all fill/stroke hex values โ map to registry tokens (Step 1b)
- - **Text**: content, fontSize, fontWeight, fontFamily โ map to registry text styles
- - **Corners**: borderRadius values โ map to registry sizing constants
- - **Spacing**: padding, margin, gap values โ map to registry spacing constants
- - **Variants / interactive states**: if the node has variants (e.g. Normal / Hover / Disabled / Active), note each variant name. Map to a boolean or enum param on the generated widget (e.g. `isDisabled`, `isActive`, `isSelected`). Do not generate separate widgets per variant โ use a single widget with conditional styling.
-
- - **Images/icons**: for each icon/image node, classify:
- - Matches a Material icon name โ use `Icon(Icons.xxx)`, no download needed
- - Custom SVG/PNG โ note node ID for download
-
- **If a Figma color has no match in the token registry:** note it, do NOT hard-code โ STOP and ask user whether to add a new token or map to the nearest existing one. Resume only after user confirms.
-
- Download custom assets if needed:
-
- ```
- mcp__Figma__download_figma_images(fileKey=<key>, nodes=[{nodeId, fileName}], localPath=<assets/path>)
- ```
-
- After download, check `pubspec.yaml` for the `flutter.assets:` section. If the downloaded path is not declared, add it โ otherwise `Image.asset(...)` will fail at runtime.
+ **Figma:** `mcp__Figma__get_figma_data(fileKey=<key>, nodeId=<nodeId>)` โ extract layout, colors, text, corners, spacing, variants/states, icons.
- ### If image input:
+ **Image:** Analyze visually โ sections, layout direction, colors โ match to nearest token.
- Analyze the image visually:
+ Both: map every color/size/font to the token registry. **If no match โ STOP, ask user before proceeding.**
- - Break down the screen into sections (AppBar, body, cards, buttons, bottom nav, etc.)
- - Estimate layout: flex direction, alignment, rough padding/gap
- - Identify colors visually โ match to nearest token in registry (Step 1b) by visual similarity
- - Identify text hierarchy โ map to nearest text style in registry
- - Identify component shapes โ match to existing widgets (Step 1c)
- - Identify images/icons โ classify as Material icon or custom asset (same as Figma mode)
- - For any custom asset referenced: verify its directory is declared under `flutter.assets:` in `pubspec.yaml`
+ Variants/interactive states (Figma): if node has variants (Normal/Hover/Disabled/Active) โ map to boolean/enum param on the widget (`isDisabled`, `isActive`). Do NOT generate separate widgets per variant โ one widget with conditional styling.
- **If a color has no close match in registry:** note it, do NOT hard-code โ ask user whether to add a new token or use the nearest existing one.
+ Download custom assets if needed: `mcp__Figma__download_figma_images(...)`. Verify directory declared in `pubspec.yaml` under `flutter.assets:`.
---
## Step 3 โ Build token mapping table
- Before writing any code, produce a mapping table:
-
- | Design value | Token to use | Source |
- | ----------------------------- | ------------------------ | -------- |
- | `#6C63FF` (primary button bg) | `AppColors.primary` | registry |
- | `24px bold` heading | `AppTextStyles.heading1` | registry |
- | `16px` padding | `AppSizes.paddingM` | registry |
- | `8px` corner radius | `AppSizes.radiusS` | registry |
- | `#FFFFFF` background | `AppColors.background` | registry |
+ | Design value | Token to use | Source |
+ |---|---|---|
+ | `#6C63FF` primary bg | `AppColors.primary` | registry |
+ | `24px bold` heading | `AppTextStyles.heading1` | registry |
- **Unmapped token gate:** After building the full table, check for any unmapped row. If found โ STOP, present the partial table to user, ask for resolution per value, resume only after all rows are resolved. The per-value STOP in Step 2 is for early discovery during extraction; this gate is the final check before proceeding to Step 4. Do not proceed with any unmapped value.
+ **After building full table: any unmapped row โ STOP, resolve with user before Step 4.**
---
## Step 4 โ Plan widget tree
- Decompose the design into a widget tree before coding:
-
- ```
- Scaffold
- AppBar: AppBar(title: Text(..., style: AppTextStyles.heading1))
- body: SingleChildScrollView
- Column(padding: AppSizes.paddingM)
- _HeaderSection โ new private widget
- SizedBox(h: AppSizes.gapL)
- AppCard(...) โ existing widget
- _CardContent โ new private widget
- SizedBox(h: AppSizes.gapM)
- AppButton(...) โ existing widget
- ```
-
- Rules:
-
+ Decompose into widget tree before coding. Rules:
- Reuse existing widgets (Step 1c) wherever shape/behavior matches
- Extract sections > 40 lines into private `_WidgetName` classes in same file
- No widget method extraction (`Widget _buildXxx()`) โ use classes
---
## Step 5 โ Generate code
- Write the Flutter file(s) following these rules:
-
- ### Token usage (CRITICAL โ no exceptions)
-
+ **Token usage (CRITICAL):** Never hard-code colors, font sizes, spacing, or radius:
```dart
- // โ
correct
- color: AppColors.primary
- fontSize: AppTextStyles.body.fontSize
- padding: EdgeInsets.all(AppSizes.paddingM)
- borderRadius: BorderRadius.circular(AppSizes.radiusS)
-
- // โ forbidden
- color: Color(0xFF6C63FF)
- fontSize: 14
- padding: EdgeInsets.all(16)
- borderRadius: BorderRadius.circular(8)
+ // โ
color: AppColors.primary fontSize: AppTextStyles.body.fontSize padding: EdgeInsets.all(AppSizes.paddingM)
+ // โ color: Color(0xFF6C63FF) fontSize: 14 padding: EdgeInsets.all(16)
```
- ### Structure rules
-
- - One file per screen/major component
- - File name and location follow the convention detected in Step 1e โ match the naming style (`login_screen.dart`, `login_page.dart`, `login_view.dart`) and directory (`lib/screens/`, `lib/pages/`, `lib/features/xxx/`) of existing screens
- - Private widgets as classes at bottom of file, not methods
- - Import via the convention detected in Step 1e (barrel or direct path)
- - `const` constructors wherever possible
- - No `print()`, no `TODO`, no placeholder lorem ipsum unless design specifies
-
- ### Assets
-
- - Material icons โ `Icon(Icons.xxx)` โ no asset file needed
- - Custom PNG โ use `flutter_gen` generated class (e.g. `Assets.images.imgWelcome.image()`) โ check how existing screens reference images to match the generated class naming
- - Custom SVG โ use `flutter_gen` generated class (e.g. `Assets.icons.icArrowRight.svg()`) โ check how existing screens reference SVGs to match the generated class naming
- - **Asset naming convention** (applied when downloading from Figma or referencing existing files):
- - Format: `snake_case`, all lowercase, no spaces, no hyphens
- - Prefix by type: `ic_` for icons, `img_` for images, `bg_` for backgrounds
- - Include state suffix where relevant: `ic_home.svg`, `ic_home_active.svg`, `ic_home_disabled.svg`
- - No generic names: `image1.png`, `icon.svg`, `asset.png` are forbidden
- - Example: `img_onboarding_welcome.png`, `ic_arrow_right.svg`, `bg_login.png`
-
- - **`pubspec.yaml` asset declaration check:** before referencing any asset (downloaded from Figma or existing in project), verify its directory is declared under `flutter.assets:`. If not, add it before running `build_runner`.
-
- - **If `flutter_gen` is not in `pubspec.yaml`:** propose setup before writing any asset reference:
- 1. Add to `pubspec.yaml`:
- ```yaml
- dev_dependencies:
- build_runner:
- flutter_gen_runner:
- flutter_gen:
- output:
- package_parameter_enabled: false
- flutter:
- assets:
- - assets/images/
- - assets/icons/
- ```
- 2. Run `flutter pub get && dart run build_runner build`
- 3. After the generated `Assets` class exists, use it in all asset references โ never fall back to `Image.asset(string)` or `SvgPicture.asset(string)`
-
- ### Responsive
+ **Structure:** One file per screen. File name/location matches Step 1e convention. `const` constructors where possible. No `print()`, no `TODO`.
- - If design shows both mobile and tablet/desktop layouts โ use `LayoutBuilder`
- - Otherwise assume single form factor โ do not add responsive boilerplate speculatively
+ **Assets:** Material icons โ `Icon(Icons.xxx)`. Custom PNG/SVG โ `flutter_gen` generated class (check existing screens for naming). Asset name format: `snake_case`, prefix `ic_`/`img_`/`bg_`, include state suffix. Verify `pubspec.yaml` declares the asset directory.
- ### State
+ **If `flutter_gen` not in `pubspec.yaml`:** propose setup before writing any asset reference:
+ ```yaml
+ dev_dependencies:
+ build_runner:
+ flutter_gen_runner:
+ flutter: { assets: [assets/images/, assets/icons/] }
+ ```
+ Run `flutter pub get && dart run build_runner build`. Never fall back to `Image.asset(string)` or `SvgPicture.asset(string)`.
- - Use the state management pattern detected in Step 1d โ do NOT introduce a different one
- - `const` constructors only for stateless leaf widgets; omit where widget depends on runtime state or reactive data
+ **Responsive:** Only add `LayoutBuilder` if design shows multiple form factors. **State:** Use pattern from Step 1d only. `const` constructors for stateless leaf widgets only โ omit where widget depends on runtime/reactive state.
---
## Step 6 โ Self-verify before handing off
- Run these checks mentally (or with grep) before presenting code:
-
```bash
- # Hard-coded hex colors or Color.from* constructors
- grep -n "Color(0x\|Color\.from" <generated_file>
-
- # Hard-coded Colors.* named colors (review hits โ only Colors.transparent is acceptable)
- grep -n "Colors\." <generated_file>
-
- # Hard-coded spacing/size literals
- grep -nE "EdgeInsets\.(all|symmetric|only|fromLTRB)\([^A-Z]|SizedBox\((height|width):\s*[0-9]|Container\((height|width):\s*[0-9]|fontSize:\s*[0-9]" <generated_file>
-
- # No Widget method extraction
- grep -n "Widget _build" <generated_file>
-
- # Stray debug/placeholder
- grep -n "print(\|TODO" <generated_file>
+ grep -n "Color(0x\|Color\.from\|Colors\.\|Widget _build\|print(\|TODO" <generated_file>
+ grep -nE "EdgeInsets\.(all|symmetric|only)\([^A-Z]|fontSize:\s*[0-9]|SizedBox\((height|width):\s*[0-9]" <generated_file>
```
- For color hits: `Color(0x*` and `Color.from*` โ always fix. `Colors.*` โ fix unless it is `Colors.transparent`.
- For spacing hits โ replace with registry token.
- If any unfixable hit โ note in output and ask user.
+ Fix all hits (exception: `Colors.transparent` is allowed). Unfixable โ note and ask user.
---
## Output format
- Present in this order:
-
- 1. **Token mapping table** (Step 3) โ so user can verify before seeing code
- 2. **Widget tree plan** (Step 4 summary) โ one-paragraph description
- 3. **Generated code** โ full file(s), ready to paste
- 4. **Assets to download** (if any Figma images) โ list node IDs + target paths
+ 1. Token mapping table (Step 3)
+ 2. Widget tree plan (Step 4 summary)
+ 3. Generated code โ full file(s), ready to paste
+ 4. Assets to download (if any)
---
## Hard rules
- - NEVER hard-code a color, font size, spacing, or radius value
- - NEVER introduce a new design token without explicit user approval (user may approve during unmapped-color pause in Step 2/3)
- - NEVER use a state management pattern different from what src uses
+ - NEVER hard-code color, font size, spacing, or radius
+ - NEVER introduce new design token without user approval
+ - NEVER use different state management from what src uses
- NEVER extract widget sections as methods โ use private classes
- ALWAYS scan src before reading Figma/image
- - ALWAYS produce the token mapping table before code
- - If registry is empty (no tokens found): stop and ask user where design tokens are declared before proceeding
+ - If registry empty โ stop and ask where design tokens are declared