Added Claude Skills

This commit is contained in:
Marc Rejohn Castillano 2026-03-20 15:14:21 +08:00
parent a39e33bc6b
commit 27ebb89052
30 changed files with 7222 additions and 2 deletions

View File

@ -1,6 +1,12 @@
{ {
"permissions": {
"allow": [
"Bash(flutter analyze:*)",
"Bash(grep -n \"StreamProvider\\\\|FutureProvider\" /d/tasq/tasq/lib/providers/*.dart)"
]
},
"enableAllProjectMcpServers": true,
"enabledMcpjsonServers": [ "enabledMcpjsonServers": [
"supabase" "supabase"
], ]
"enableAllProjectMcpServers": true
} }

View File

@ -0,0 +1,516 @@
---
name: custom-plugin-flutter-skill-animations
description: Production-grade Flutter animations mastery - Implicit and explicit animations, AnimationController, Hero transitions, physics-based motion, Lottie/Rive integration, 60fps optimization with comprehensive code examples
sasmp_version: "1.3.0"
bonded_agent: 01-flutter-ui-development
bond_type: PRIMARY_BOND
---
# custom-plugin-flutter: Animations Skill
## Quick Start - Production Animation Pattern
```dart
class AnimatedProductCard extends StatefulWidget {
final Product product;
final bool isSelected;
const AnimatedProductCard({
required this.product,
required this.isSelected,
});
@override
State<AnimatedProductCard> createState() => _AnimatedProductCardState();
}
class _AnimatedProductCardState extends State<AnimatedProductCard>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late final Animation<double> _scaleAnimation;
late final Animation<double> _opacityAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 200),
vsync: this,
);
_scaleAnimation = Tween<double>(begin: 1.0, end: 0.95).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
_opacityAnimation = Tween<double>(begin: 1.0, end: 0.8).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _onTapDown(TapDownDetails details) => _controller.forward();
void _onTapUp(TapUpDetails details) => _controller.reverse();
void _onTapCancel() => _controller.reverse();
@override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: _onTapDown,
onTapUp: _onTapUp,
onTapCancel: _onTapCancel,
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.scale(
scale: _scaleAnimation.value,
child: Opacity(
opacity: _opacityAnimation.value,
child: child,
),
);
},
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
decoration: BoxDecoration(
border: Border.all(
color: widget.isSelected ? Colors.blue : Colors.grey,
width: widget.isSelected ? 2 : 1,
),
borderRadius: BorderRadius.circular(12),
),
child: ProductContent(product: widget.product),
),
),
);
}
}
```
## 1. Implicit Animations
### Built-in Animated Widgets
```dart
// AnimatedContainer - Animate multiple properties
AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
width: isExpanded ? 300 : 100,
height: isExpanded ? 200 : 100,
decoration: BoxDecoration(
color: isActive ? Colors.blue : Colors.grey,
borderRadius: BorderRadius.circular(isExpanded ? 16 : 8),
boxShadow: isActive ? [BoxShadow(blurRadius: 10)] : [],
),
child: content,
)
// AnimatedOpacity
AnimatedOpacity(
opacity: isVisible ? 1.0 : 0.0,
duration: Duration(milliseconds: 200),
child: content,
)
// AnimatedPositioned (inside Stack)
AnimatedPositioned(
duration: Duration(milliseconds: 300),
left: isLeft ? 0 : 100,
top: isTop ? 0 : 100,
child: widget,
)
// AnimatedSwitcher - Cross-fade between widgets
AnimatedSwitcher(
duration: Duration(milliseconds: 300),
transitionBuilder: (child, animation) {
return FadeTransition(opacity: animation, child: child);
},
child: Text(
currentText,
key: ValueKey(currentText), // Important!
),
)
// TweenAnimationBuilder - Custom implicit animation
TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: progress),
duration: Duration(milliseconds: 500),
builder: (context, value, child) {
return CircularProgressIndicator(value: value);
},
)
```
## 2. Explicit Animations
### AnimationController Pattern
```dart
class ExplicitAnimationWidget extends StatefulWidget {
@override
State<ExplicitAnimationWidget> createState() => _ExplicitAnimationWidgetState();
}
class _ExplicitAnimationWidgetState extends State<ExplicitAnimationWidget>
with TickerProviderStateMixin {
late final AnimationController _controller;
late final Animation<Offset> _slideAnimation;
late final Animation<double> _fadeAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: 500),
vsync: this,
);
_slideAnimation = Tween<Offset>(
begin: Offset(0, 1),
end: Offset.zero,
).animate(CurvedAnimation(
parent: _controller,
curve: Interval(0.0, 0.6, curve: Curves.easeOut),
));
_fadeAnimation = Tween<double>(
begin: 0,
end: 1,
).animate(CurvedAnimation(
parent: _controller,
curve: Interval(0.3, 1.0, curve: Curves.easeIn),
));
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SlideTransition(
position: _slideAnimation,
child: FadeTransition(
opacity: _fadeAnimation,
child: Content(),
),
);
}
}
```
### Animation Transitions
```dart
// Built-in transitions
FadeTransition(opacity: animation, child: widget)
SlideTransition(position: offsetAnimation, child: widget)
ScaleTransition(scale: animation, child: widget)
RotationTransition(turns: animation, child: widget)
SizeTransition(sizeFactor: animation, child: widget)
// AnimatedBuilder - Custom rendering
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY(_controller.value * pi),
alignment: Alignment.center,
child: child,
);
},
child: Card(),
)
```
## 3. Hero Animations
```dart
// Source screen
Hero(
tag: 'product-${product.id}',
child: Image.network(product.imageUrl),
)
// Destination screen
Hero(
tag: 'product-${product.id}',
child: Image.network(product.imageUrl),
)
// Custom Hero flight
Hero(
tag: 'avatar',
flightShuttleBuilder: (
flightContext,
animation,
flightDirection,
fromHeroContext,
toHeroContext,
) {
return AnimatedBuilder(
animation: animation,
builder: (context, _) {
return CircleAvatar(
radius: lerpDouble(40, 60, animation.value),
);
},
);
},
child: CircleAvatar(),
)
```
## 4. Staggered Animations
```dart
class StaggeredList extends StatefulWidget {
@override
State<StaggeredList> createState() => _StaggeredListState();
}
class _StaggeredListState extends State<StaggeredList>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
final List<Animation<Offset>> _animations = [];
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: 1000),
vsync: this,
);
// Create staggered animations
for (int i = 0; i < 5; i++) {
final start = i * 0.1;
final end = start + 0.4;
_animations.add(
Tween<Offset>(begin: Offset(1, 0), end: Offset.zero).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(start, end, curve: Curves.easeOut),
),
),
);
}
_controller.forward();
}
@override
Widget build(BuildContext context) {
return Column(
children: List.generate(5, (index) {
return SlideTransition(
position: _animations[index],
child: ListTile(title: Text('Item $index')),
);
}),
);
}
}
```
## 5. Physics-Based Animations
```dart
// Spring simulation
class SpringAnimation extends StatefulWidget {
@override
State<SpringAnimation> createState() => _SpringAnimationState();
}
class _SpringAnimationState extends State<SpringAnimation>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late Animation<double> _animation;
final SpringDescription spring = SpringDescription(
mass: 1,
stiffness: 100,
damping: 10,
);
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
final simulation = SpringSimulation(spring, 0, 1, 0);
_controller.animateWith(simulation);
_animation = _controller.drive(Tween(begin: 0.0, end: 100.0));
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Transform.translate(
offset: Offset(0, _animation.value),
child: child,
);
},
child: Ball(),
);
}
}
```
## 6. Lottie & Rive Integration
```dart
// Lottie animation
import 'package:lottie/lottie.dart';
Lottie.asset(
'assets/loading.json',
width: 200,
height: 200,
fit: BoxFit.contain,
repeat: true,
animate: true,
onLoaded: (composition) {
_controller.duration = composition.duration;
},
)
// Rive animation
import 'package:rive/rive.dart';
RiveAnimation.asset(
'assets/animation.riv',
fit: BoxFit.cover,
stateMachines: ['StateMachine1'],
onInit: (artboard) {
final controller = StateMachineController.fromArtboard(
artboard,
'StateMachine1',
);
artboard.addController(controller!);
_trigger = controller.findInput<bool>('trigger') as SMITrigger;
},
)
```
## 7. Page Transitions
```dart
// Custom page route
class FadePageRoute<T> extends PageRouteBuilder<T> {
final Widget page;
FadePageRoute({required this.page})
: super(
pageBuilder: (context, animation, secondaryAnimation) => page,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(opacity: animation, child: child);
},
transitionDuration: Duration(milliseconds: 300),
);
}
// Slide + fade combined
transitionsBuilder: (context, animation, secondaryAnimation, child) {
final offsetAnimation = Tween<Offset>(
begin: Offset(1.0, 0.0),
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeInOut,
));
return SlideTransition(
position: offsetAnimation,
child: FadeTransition(
opacity: animation,
child: child,
),
);
}
```
## 8. Performance Optimization
```dart
// Use const where possible
const AnimatedContainer(...)
// RepaintBoundary for expensive animations
RepaintBoundary(
child: AnimatedWidget(),
)
// AnimatedBuilder isolates rebuilds
AnimatedBuilder(
animation: _controller,
child: ExpensiveWidget(), // Not rebuilt
builder: (context, child) {
return Transform.scale(
scale: _controller.value,
child: child, // Reused
);
},
)
// Avoid layout-triggering animations
// Good: Transform, Opacity
// Avoid: width/height in tight loops
```
## Troubleshooting Guide
**Issue: Animation jank (dropped frames)**
```
1. Use RepaintBoundary to isolate
2. Profile with DevTools Performance
3. Avoid layout changes during animation
4. Use AnimatedBuilder, not setState
5. Check for heavy build methods
```
**Issue: Animation doesn't start**
```
1. Verify AnimationController.forward() called
2. Check vsync: this (TickerProviderStateMixin)
3. Verify widget is mounted before animating
4. Check duration is set
```
**Issue: Animation leaks memory**
```
1. Dispose AnimationController in dispose()
2. Cancel any listeners
3. Use mounted check before setState
```
## Animation Selection Guide
| Need | Solution |
|------|----------|
| Simple property change | AnimatedContainer |
| Cross-fade widgets | AnimatedSwitcher |
| Custom timing control | AnimationController |
| Shared element | Hero |
| List items appearing | Staggered animations |
| Natural motion | Physics simulation |
| Complex vector | Lottie/Rive |
---
**Create fluid, 60fps animations in Flutter.**

View File

@ -0,0 +1,41 @@
# animations Configuration
# Category: general
# Generated: 2025-12-30
skill:
name: animations
version: "1.0.0"
category: general
settings:
# Default settings for animations
enabled: true
log_level: info
# Category-specific defaults
validation:
strict_mode: false
auto_fix: false
output:
format: markdown
include_examples: true
# Environment-specific overrides
environments:
development:
log_level: debug
validation:
strict_mode: false
production:
log_level: warn
validation:
strict_mode: true
# Integration settings
integrations:
# Enable/disable integrations
git: true
linter: true
formatter: true

View File

@ -0,0 +1,60 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "animations Configuration Schema",
"type": "object",
"properties": {
"skill": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string",
"pattern": "^\\d+\\.\\d+\\.\\d+$"
},
"category": {
"type": "string",
"enum": [
"api",
"testing",
"devops",
"security",
"database",
"frontend",
"algorithms",
"machine-learning",
"cloud",
"containers",
"general"
]
}
},
"required": [
"name",
"version"
]
},
"settings": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean",
"default": true
},
"log_level": {
"type": "string",
"enum": [
"debug",
"info",
"warn",
"error"
]
}
}
}
},
"required": [
"skill"
]
}

View File

@ -0,0 +1,95 @@
# Animations Guide
## Overview
This guide provides comprehensive documentation for the **animations** skill in the custom-plugin-flutter plugin.
## Category: General
## Quick Start
### Prerequisites
- Familiarity with general concepts
- Development environment set up
- Plugin installed and configured
### Basic Usage
```bash
# Invoke the skill
claude "animations - [your task description]"
# Example
claude "animations - analyze the current implementation"
```
## Core Concepts
### Key Principles
1. **Consistency** - Follow established patterns
2. **Clarity** - Write readable, maintainable code
3. **Quality** - Validate before deployment
### Best Practices
- Always validate input data
- Handle edge cases explicitly
- Document your decisions
- Write tests for critical paths
## Common Tasks
### Task 1: Basic Implementation
```python
# Example implementation pattern
def implement_animations(input_data):
"""
Implement animations functionality.
Args:
input_data: Input to process
Returns:
Processed result
"""
# Validate input
if not input_data:
raise ValueError("Input required")
# Process
result = process(input_data)
# Return
return result
```
### Task 2: Advanced Usage
For advanced scenarios, consider:
- Configuration customization via `assets/config.yaml`
- Validation using `scripts/validate.py`
- Integration with other skills
## Troubleshooting
### Common Issues
| Issue | Cause | Solution |
|-------|-------|----------|
| Skill not found | Not installed | Run plugin sync |
| Validation fails | Invalid config | Check config.yaml |
| Unexpected output | Missing context | Provide more details |
## Related Resources
- SKILL.md - Skill specification
- config.yaml - Configuration options
- validate.py - Validation script
---
*Last updated: 2025-12-30*

View File

@ -0,0 +1,87 @@
# Animations Patterns
## Design Patterns
### Pattern 1: Input Validation
Always validate input before processing:
```python
def validate_input(data):
if data is None:
raise ValueError("Data cannot be None")
if not isinstance(data, dict):
raise TypeError("Data must be a dictionary")
return True
```
### Pattern 2: Error Handling
Use consistent error handling:
```python
try:
result = risky_operation()
except SpecificError as e:
logger.error(f"Operation failed: {e}")
handle_error(e)
except Exception as e:
logger.exception("Unexpected error")
raise
```
### Pattern 3: Configuration Loading
Load and validate configuration:
```python
import yaml
def load_config(config_path):
with open(config_path) as f:
config = yaml.safe_load(f)
validate_config(config)
return config
```
## Anti-Patterns to Avoid
### ❌ Don't: Swallow Exceptions
```python
# BAD
try:
do_something()
except:
pass
```
### ✅ Do: Handle Explicitly
```python
# GOOD
try:
do_something()
except SpecificError as e:
logger.warning(f"Expected error: {e}")
return default_value
```
## Category-Specific Patterns: General
### Recommended Approach
1. Start with the simplest implementation
2. Add complexity only when needed
3. Test each addition
4. Document decisions
### Common Integration Points
- Configuration: `assets/config.yaml`
- Validation: `scripts/validate.py`
- Documentation: `references/GUIDE.md`
---
*Pattern library for animations skill*

View File

@ -0,0 +1,131 @@
#!/usr/bin/env python3
"""
Validation script for animations skill.
Category: general
"""
import os
import sys
import yaml
import json
from pathlib import Path
def validate_config(config_path: str) -> dict:
"""
Validate skill configuration file.
Args:
config_path: Path to config.yaml
Returns:
dict: Validation result with 'valid' and 'errors' keys
"""
errors = []
if not os.path.exists(config_path):
return {"valid": False, "errors": ["Config file not found"]}
try:
with open(config_path, 'r') as f:
config = yaml.safe_load(f)
except yaml.YAMLError as e:
return {"valid": False, "errors": [f"YAML parse error: {e}"]}
# Validate required fields
if 'skill' not in config:
errors.append("Missing 'skill' section")
else:
if 'name' not in config['skill']:
errors.append("Missing skill.name")
if 'version' not in config['skill']:
errors.append("Missing skill.version")
# Validate settings
if 'settings' in config:
settings = config['settings']
if 'log_level' in settings:
valid_levels = ['debug', 'info', 'warn', 'error']
if settings['log_level'] not in valid_levels:
errors.append(f"Invalid log_level: {settings['log_level']}")
return {
"valid": len(errors) == 0,
"errors": errors,
"config": config if not errors else None
}
def validate_skill_structure(skill_path: str) -> dict:
"""
Validate skill directory structure.
Args:
skill_path: Path to skill directory
Returns:
dict: Structure validation result
"""
required_dirs = ['assets', 'scripts', 'references']
required_files = ['SKILL.md']
errors = []
# Check required files
for file in required_files:
if not os.path.exists(os.path.join(skill_path, file)):
errors.append(f"Missing required file: {file}")
# Check required directories
for dir in required_dirs:
dir_path = os.path.join(skill_path, dir)
if not os.path.isdir(dir_path):
errors.append(f"Missing required directory: {dir}/")
else:
# Check for real content (not just .gitkeep)
files = [f for f in os.listdir(dir_path) if f != '.gitkeep']
if not files:
errors.append(f"Directory {dir}/ has no real content")
return {
"valid": len(errors) == 0,
"errors": errors,
"skill_name": os.path.basename(skill_path)
}
def main():
"""Main validation entry point."""
skill_path = Path(__file__).parent.parent
print(f"Validating animations skill...")
print(f"Path: {skill_path}")
# Validate structure
structure_result = validate_skill_structure(str(skill_path))
print(f"\nStructure validation: {'PASS' if structure_result['valid'] else 'FAIL'}")
if structure_result['errors']:
for error in structure_result['errors']:
print(f" - {error}")
# Validate config
config_path = skill_path / 'assets' / 'config.yaml'
if config_path.exists():
config_result = validate_config(str(config_path))
print(f"\nConfig validation: {'PASS' if config_result['valid'] else 'FAIL'}")
if config_result['errors']:
for error in config_result['errors']:
print(f" - {error}")
else:
print("\nConfig validation: SKIPPED (no config.yaml)")
# Summary
all_valid = structure_result['valid']
print(f"\n==================================================")
print(f"Overall: {'VALID' if all_valid else 'INVALID'}")
return 0 if all_valid else 1
if __name__ == "__main__":
sys.exit(main())

View File

@ -0,0 +1,39 @@
---
name: deepsearch
description: Thorough codebase search
user-invocable: true
---
# Deep Search Mode
[DEEPSEARCH MODE ACTIVATED]
## Objective
Perform thorough search of the codebase for the specified query, pattern, or concept.
## Search Strategy
1. **Broad Search**
- Search for exact matches
- Search for related terms and variations
- Check common locations (components, utils, services, hooks)
2. **Deep Dive**
- Read files with matches
- Check imports/exports to find connections
- Follow the trail (what imports this? what does this import?)
3. **Synthesize**
- Map out where the concept is used
- Identify the main implementation
- Note related functionality
## Output Format
- **Primary Locations** (main implementations)
- **Related Files** (dependencies, consumers)
- **Usage Patterns** (how it's used across the codebase)
- **Key Insights** (patterns, conventions, gotchas)
Focus on being comprehensive but concise. Cite file paths and line numbers.

View File

@ -0,0 +1,316 @@
---
name: flutter-development
description: Build beautiful cross-platform mobile apps with Flutter and Dart. Covers widgets, state management with Provider/BLoC, navigation, API integration, and material design.
---
# Flutter Development
## Overview
Create high-performance, visually stunning mobile applications using Flutter with Dart language. Master widget composition, state management patterns, navigation, and API integration.
## When to Use
- Building iOS and Android apps with native performance
- Designing custom UIs with Flutter's widget system
- Implementing complex animations and visual effects
- Rapid app development with hot reload
- Creating consistent UX across platforms
## Instructions
### 1. **Project Structure & Navigation**
```dart
// pubspec.yaml
name: my_flutter_app
version: 1.0.0
dependencies:
flutter:
sdk: flutter
provider: ^6.0.0
http: ^1.1.0
go_router: ^12.0.0
// main.dart with GoRouter navigation
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Flutter App',
theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
routerConfig: _router,
);
}
}
final GoRouter _router = GoRouter(
routes: <RouteBase>[
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'details/:id',
builder: (context, state) => DetailsScreen(
itemId: state.pathParameters['id']!
),
),
],
),
GoRoute(
path: '/profile',
builder: (context, state) => const ProfileScreen(),
),
],
);
```
### 2. **State Management with Provider**
```dart
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class User {
final String id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'],
name: json['name'],
email: json['email'],
);
}
}
class UserProvider extends ChangeNotifier {
User? _user;
bool _isLoading = false;
String? _error;
User? get user => _user;
bool get isLoading => _isLoading;
String? get error => _error;
Future<void> fetchUser(String userId) async {
_isLoading = true;
_error = null;
notifyListeners();
try {
final response = await http.get(
Uri.parse('https://api.example.com/users/$userId'),
headers: {'Content-Type': 'application/json'},
);
if (response.statusCode == 200) {
_user = User.fromJson(jsonDecode(response.body));
} else {
_error = 'Failed to fetch user';
}
} catch (e) {
_error = 'Error: ${e.toString()}';
} finally {
_isLoading = false;
notifyListeners();
}
}
void logout() {
_user = null;
notifyListeners();
}
}
class ItemsProvider extends ChangeNotifier {
List<Map<String, dynamic>> _items = [];
List<Map<String, dynamic>> get items => _items;
Future<void> fetchItems() async {
try {
final response = await http.get(
Uri.parse('https://api.example.com/items'),
);
if (response.statusCode == 200) {
_items = List<Map<String, dynamic>>.from(
jsonDecode(response.body) as List
);
notifyListeners();
}
} catch (e) {
print('Error fetching items: $e');
}
}
}
```
### 3. **Screens with Provider Integration**
```dart
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
@override
void initState() {
super.initState();
Future.microtask(() {
Provider.of<ItemsProvider>(context, listen: false).fetchItems();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home Feed')),
body: Consumer<ItemsProvider>(
builder: (context, itemsProvider, child) {
if (itemsProvider.items.isEmpty) {
return const Center(child: Text('No items found'));
}
return ListView.builder(
itemCount: itemsProvider.items.length,
itemBuilder: (context, index) {
final item = itemsProvider.items[index];
return ItemCard(item: item);
},
);
},
),
);
}
}
class ItemCard extends StatelessWidget {
final Map<String, dynamic> item;
const ItemCard({required this.item, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.all(8),
child: ListTile(
title: Text(item['title'] ?? 'Untitled'),
subtitle: Text(item['description'] ?? ''),
trailing: const Icon(Icons.arrow_forward),
onTap: () => context.go('/details/${item['id']}'),
),
);
}
}
class DetailsScreen extends StatelessWidget {
final String itemId;
const DetailsScreen({required this.itemId, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Details')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Item ID: $itemId', style: const TextStyle(fontSize: 18)),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => context.pop(),
child: const Text('Go Back'),
),
],
),
),
);
}
}
class ProfileScreen extends StatelessWidget {
const ProfileScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Profile')),
body: Consumer<UserProvider>(
builder: (context, userProvider, child) {
if (userProvider.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (userProvider.error != null) {
return Center(child: Text('Error: ${userProvider.error}'));
}
final user = userProvider.user;
if (user == null) {
return const Center(child: Text('No user data'));
}
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Name: ${user.name}', style: const TextStyle(fontSize: 18)),
Text('Email: ${user.email}', style: const TextStyle(fontSize: 16)),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => userProvider.logout(),
child: const Text('Logout'),
),
],
),
);
},
),
);
}
}
```
## Best Practices
### ✅ DO
- Use widgets for every UI element
- Implement proper state management
- Use const constructors where possible
- Dispose resources in state lifecycle
- Test on multiple device sizes
- Use meaningful widget names
- Implement error handling
- Use responsive design patterns
- Test on both iOS and Android
- Document custom widgets
### ❌ DON'T
- Build entire screens in build() method
- Use setState for complex state logic
- Make network calls in build()
- Ignore platform differences
- Create overly nested widget trees
- Hardcode strings
- Ignore performance warnings
- Skip testing
- Forget to handle edge cases
- Deploy without thorough testing

View File

@ -0,0 +1,14 @@
{
"id": "aj-geddes-useful-ai-prompts-skills-flutter-development-skill-md",
"name": "flutter-development",
"author": "aj-geddes",
"authorAvatar": "https://avatars.githubusercontent.com/u/211219442?v=4",
"description": "Build beautiful cross-platform mobile apps with Flutter and Dart. Covers widgets, state management with Provider/BLoC, navigation, API integration, and material design.",
"githubUrl": "https://github.com/aj-geddes/useful-ai-prompts/tree/main/skills/flutter-development",
"stars": 12,
"forks": 0,
"updatedAt": 1764499235,
"hasMarketplace": false,
"path": "SKILL.md",
"branch": "main"
}

View File

@ -0,0 +1,614 @@
---
name: custom-plugin-flutter-skill-ui
description: 1700+ lines of Flutter UI mastery - widgets, layouts, Material Design, animations, responsive design with production-ready code examples and enterprise patterns.
sasmp_version: "1.3.0"
bonded_agent: 01-flutter-ui-development
bond_type: PRIMARY_BOND
---
# custom-plugin-flutter: UI Development Skill
## Quick Start - Production UI Pattern
```dart
class ResponsiveProductScreen extends ConsumerWidget {
const ResponsiveProductScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final isMobile = MediaQuery.of(context).size.width < 600;
final productState = ref.watch(productProvider);
return Scaffold(
appBar: AppBar(
title: const Text('Products'),
elevation: 0,
),
body: productState.when(
loading: () => const LoadingWidget(),
data: (products) => isMobile
? _MobileProductList(products: products)
: _DesktopProductGrid(products: products),
error: (error, st) => ErrorWidget(error: error.toString()),
),
);
}
}
class _MobileProductList extends StatelessWidget {
final List<Product> products;
const _MobileProductList({required this.products});
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) => ProductCard(product: products[index]),
);
}
}
class _DesktopProductGrid extends StatelessWidget {
final List<Product> products;
const _DesktopProductGrid({required this.products});
@override
Widget build(BuildContext context) {
return GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 0.8,
),
itemCount: products.length,
itemBuilder: (context, index) => ProductCard(product: products[index]),
);
}
}
```
## 1. Widget System Mastery
### Understanding the Widget Tree
**Stateless Widgets** - Pure, immutable widgets:
```dart
class PureWidget extends StatelessWidget {
final String title;
const PureWidget({required this.title});
@override
Widget build(BuildContext context) => Text(title);
}
```
**Stateful Widgets** - Managing internal state:
```dart
class CounterWidget extends StatefulWidget {
const CounterWidget({Key? key}) : super(key: key);
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _count = 0;
void _increment() {
setState(() => _count++);
}
@override
void initState() {
super.initState();
// Initialize resources
}
@override
void dispose() {
// Clean up resources
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: $_count'),
ElevatedButton(
onPressed: _increment,
child: const Text('Increment'),
),
],
);
}
}
```
**Const Constructors** - Preventing unnecessary rebuilds:
```dart
// ✅ Good - Const constructor
const SizedBox(height: 16, child: Text('Hello'))
// ❌ Bad - Non-const, rebuilds every time
SizedBox(height: 16, child: Text('Hello'))
// ✅ Make all widgets const when possible
class MyWidget extends StatelessWidget {
const MyWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) => const Placeholder();
}
```
**Inherited Widgets** - Efficient state propagation:
```dart
class ThemeProvider extends InheritedWidget {
final ThemeData theme;
const ThemeProvider({
required this.theme,
required super.child,
super.key,
});
static ThemeProvider of(BuildContext context) {
final result = context.dependOnInheritedWidgetOfExactType<ThemeProvider>();
assert(result != null, 'No ThemeProvider found in context');
return result!;
}
@override
bool updateShouldNotify(ThemeProvider oldWidget) => theme != oldWidget.theme;
}
// Usage
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ThemeProvider(
theme: ThemeData.light(),
child: MaterialApp(home: MyScreen()),
);
}
}
class MyScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = ThemeProvider.of(context).theme;
return Scaffold(
backgroundColor: theme.scaffoldBackgroundColor,
);
}
}
```
## 2. Constraint-Based Layout System
### Understanding Constraints
**Basic Constraint Flow**:
```dart
// Parent imposes constraints on children
// Child sizes itself based on constraints
// Parent positions child based on alignment
Center( // Imposes tight constraint
child: Container(
width: 200,
height: 100,
color: Colors.blue,
),
)
```
**Layout Widgets**:
```dart
// Row - horizontal layout
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('Left'),
Text('Middle'),
Text('Right'),
],
)
// Column - vertical layout
Column(
mainAxisSize: MainAxisSize.max,
children: [
Container(height: 100, color: Colors.red),
Container(height: 100, color: Colors.blue),
Container(height: 100, color: Colors.green),
],
)
// Flex - flexible layout
Flex(
direction: Axis.horizontal,
children: [
Flexible(flex: 2, child: Container(color: Colors.red)),
Flexible(flex: 1, child: Container(color: Colors.blue)),
],
)
```
**Advanced Layouts**:
```dart
// GridView - grid layout
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
itemCount: 12,
itemBuilder: (context, index) => Container(
color: Colors.blue[100 * ((index % 9) + 1)],
),
)
// Stack - overlaying widgets
Stack(
alignment: Alignment.bottomRight,
children: [
Image.asset('background.png'),
Positioned(
right: 16,
bottom: 16,
child: FloatingActionButton(onPressed: () {}),
),
],
)
// CustomMultiChildLayout - manual layout
CustomMultiChildLayout(
delegate: MyLayoutDelegate(),
children: [
LayoutId(id: 'title', child: Text('Title')),
LayoutId(id: 'body', child: Text('Body')),
],
)
```
## 3. Material Design 3 Implementation
### Theme Configuration
```dart
class AppTheme {
static ThemeData lightTheme = ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.light,
),
typography: Typography.material2021(
platform: defaultTargetPlatform,
),
appBarTheme: AppBarTheme(
elevation: 0,
backgroundColor: Colors.transparent,
),
cardTheme: CardTheme(
elevation: 2,
margin: EdgeInsets.all(16),
),
);
static ThemeData darkTheme = ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.dark,
),
);
}
// Usage
MaterialApp(
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: ThemeMode.system,
)
```
### Material Components
```dart
// Modern AppBar
AppBar(
title: Text('Title'),
elevation: 0,
backgroundColor: Theme.of(context).colorScheme.surface,
foregroundColor: Theme.of(context).colorScheme.onSurface,
)
// Enhanced Button
ElevatedButton.icon(
onPressed: () {},
icon: Icon(Icons.send),
label: Text('Send'),
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
)
// Material TextField
TextField(
decoration: InputDecoration(
labelText: 'Enter name',
hintText: 'John Doe',
prefixIcon: Icon(Icons.person),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
filled: true,
fillColor: Colors.grey[100],
),
)
// Material Form
Form(
key: _formKey,
child: Column(
children: [
TextFormField(
validator: (value) {
if (value?.isEmpty ?? true) return 'Required';
return null;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// Submit form
}
},
child: Text('Submit'),
),
],
),
)
```
## 4. Animation Framework
### AnimationController Pattern
```dart
class AnimatedCounterWidget extends StatefulWidget {
const AnimatedCounterWidget();
@override
State<AnimatedCounterWidget> createState() => _AnimatedCounterWidgetState();
}
class _AnimatedCounterWidgetState extends State<AnimatedCounterWidget>
with TickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
int _count = 0;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: 300),
vsync: this,
);
_animation = Tween<double>(begin: 1, end: 0.8).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
}
void _increment() {
setState(() => _count++);
_controller.forward().then((_) => _controller.reverse());
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ScaleTransition(
scale: _animation,
child: GestureDetector(
onTap: _increment,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.blue,
),
child: Center(
child: Text(
'$_count',
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
color: Colors.white,
),
),
),
),
),
);
}
}
```
### Implicit Animations
```dart
// AnimatedContainer - animate properties smoothly
class AnimatedBoxWidget extends StatefulWidget {
@override
State<AnimatedBoxWidget> createState() => _AnimatedBoxWidgetState();
}
class _AnimatedBoxWidgetState extends State<AnimatedBoxWidget> {
bool _expanded = false;
@override
Widget build(BuildContext context) {
return Center(
child: GestureDetector(
onTap: () => setState(() => _expanded = !_expanded),
child: AnimatedContainer(
duration: Duration(milliseconds: 500),
width: _expanded ? 300 : 100,
height: _expanded ? 300 : 100,
decoration: BoxDecoration(
color: _expanded ? Colors.blue : Colors.red,
borderRadius: BorderRadius.circular(_expanded ? 16 : 8),
),
child: Center(
child: AnimatedOpacity(
opacity: _expanded ? 1 : 0,
duration: Duration(milliseconds: 500),
child: Text('Expanded'),
),
),
),
),
);
}
}
```
## 5. Responsive Design Pattern
```dart
class ResponsiveWidget extends StatelessWidget {
final Widget Function(BuildContext) mobileBuilder;
final Widget Function(BuildContext) tabletBuilder;
final Widget Function(BuildContext) desktopBuilder;
const ResponsiveWidget({
required this.mobileBuilder,
required this.tabletBuilder,
required this.desktopBuilder,
});
@override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
if (width < 600) {
return mobileBuilder(context);
} else if (width < 1200) {
return tabletBuilder(context);
} else {
return desktopBuilder(context);
}
}
}
// Usage
ResponsiveWidget(
mobileBuilder: (context) => MobileLayout(),
tabletBuilder: (context) => TabletLayout(),
desktopBuilder: (context) => DesktopLayout(),
)
```
## 6. Accessibility Best Practices
```dart
class AccessibleWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Semantics(
button: true,
label: 'Submit form',
enabled: true,
onTap: () {},
child: Container(
constraints: BoxConstraints(minHeight: 48, minWidth: 48),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: Text(
'Submit',
style: TextStyle(fontSize: 16), // Respect system font scaling
),
),
),
);
}
}
```
## 7. Performance Optimization Tips
- ✅ Use `const` constructors everywhere possible
- ✅ Extract widgets to separate classes to limit rebuilds
- ✅ Use `RepaintBoundary` for expensive rendering
- ✅ Use `ListView.builder` instead of `ListView`
- ✅ Cache images with `CachedNetworkImage`
- ✅ Profile with DevTools regularly
- ✅ Use `LayoutBuilder` for responsive design
- ✅ Prefer `SingleChildScrollView` over custom scrolling
## 8. Custom Widget Template
```dart
class CustomButton extends StatelessWidget {
final VoidCallback onPressed;
final String label;
final ButtonSize size;
const CustomButton({
required this.onPressed,
required this.label,
this.size = ButtonSize.medium,
});
@override
Widget build(BuildContext context) {
return Material(
child: InkWell(
onTap: onPressed,
child: Container(
padding: _paddingForSize(size),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(8),
),
child: Text(
label,
style: Theme.of(context).textTheme.labelLarge,
),
),
),
);
}
EdgeInsets _paddingForSize(ButtonSize size) {
switch (size) {
case ButtonSize.small:
return EdgeInsets.symmetric(horizontal: 12, vertical: 8);
case ButtonSize.medium:
return EdgeInsets.symmetric(horizontal: 16, vertical: 12);
case ButtonSize.large:
return EdgeInsets.symmetric(horizontal: 24, vertical: 16);
}
}
}
enum ButtonSize { small, medium, large }
```
---
**Master Flutter UI development with this comprehensive skill reference.**

View File

@ -0,0 +1,20 @@
widgets:
stateless:
- Container
- Text
- Icon
- Image
stateful:
- TextField
- ListView
- AnimatedWidget
layouts:
- Column
- Row
- Stack
- GridView
- CustomScrollView
theming:
- material_3
- cupertino
- custom_theme

View File

@ -0,0 +1,12 @@
# Flutter UI Guide
## Widget System
- Everything is a widget
- Composition over inheritance
- StatelessWidget for static UI
- StatefulWidget for dynamic UI
## Layout Widgets
- Column/Row: Linear layout
- Stack: Overlapping
- ListView: Scrollable lists
- GridView: Grid layouts

View File

@ -0,0 +1,5 @@
#!/usr/bin/env python3
"""Flutter widget analyzer."""
import json
def analyze(): return {"categories": ["stateless", "stateful", "inherited", "render"], "layouts": ["flex", "stack", "custom"]}
if __name__ == "__main__": print(json.dumps(analyze(), indent=2))

View File

@ -0,0 +1,42 @@
---
name: frontend-design
description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.
license: Complete terms in LICENSE.txt
---
This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.
## Design Thinking
Before coding, understand the context and commit to a BOLD aesthetic direction:
- **Purpose**: What problem does this interface solve? Who uses it?
- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.
- **Constraints**: Technical requirements (framework, performance, accessibility).
- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?
**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.
Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is:
- Production-grade and functional
- Visually striking and memorable
- Cohesive with a clear aesthetic point-of-view
- Meticulously refined in every detail
## Frontend Aesthetics Guidelines
Focus on:
- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.
- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.
- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.
- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.
- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.
NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.
Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.
**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.
Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.

View File

@ -0,0 +1,327 @@
---
name: material-thinking
description: Comprehensive Material Design 3 (M3) and M3 Expressive guidance for building modern, accessible, and engaging user interfaces. Use when designing or implementing Material Design interfaces, reviewing component designs for M3 compliance, generating design tokens (color schemes, typography, shapes), applying M3 Expressive motion and interactions, or migrating existing UIs to Material 3. Covers all 38 M3 components, foundations (accessibility, layout, interaction), styles (color, typography, elevation, shape, icons, motion), and M3 Expressive tactics for more engaging experiences.
---
# Material Thinking
Apply Material Design 3 and M3 Expressive principles to create accessible, consistent, and engaging user interfaces.
## Overview
This skill provides comprehensive guidance for implementing Material Design 3 (M3) and M3 Expressive across all platforms. Material Design 3 is Google's open-source design system that provides UX guidance, reusable components, and design tools for creating beautiful, accessible interfaces.
**Key capabilities:**
- Design new products with M3 principles
- Review existing designs for M3 compliance
- Generate design tokens (colors, typography, shapes)
- Apply M3 Expressive for engaging, emotionally resonant UIs
- Select appropriate components for specific use cases
- Ensure accessibility and responsive design
## Core Resources
This skill includes four comprehensive reference documents. Read these as needed during your work:
### 1. Foundations (`references/foundations.md`)
Read when working with:
- Accessibility requirements
- Layout and responsive design (window size classes, canonical layouts)
- Interaction patterns (states, gestures, selection)
- Content design and UX writing
- Design tokens and adaptive design
### 2. Styles (`references/styles.md`)
Read when working with:
- Color systems (dynamic color, color roles, tonal palettes)
- Typography (type scale, fonts)
- Elevation and depth
- Shapes and corner radius
- Icons (Material Symbols)
- Motion and transitions
### 3. Components (`references/components.md`)
Read when selecting or implementing:
- Action components (buttons, FAB, segmented buttons)
- Selection and input (checkbox, radio, switch, text fields, chips)
- Navigation (nav bar, drawer, rail, app bars, tabs)
- Containment and layout (cards, lists, carousel, sheets)
- Communication (dialogs, snackbar, badges, progress, tooltips, menus)
### 4. M3 Expressive (`references/m3-expressive.md`)
Read when creating more engaging experiences:
- Expressive motion tactics
- Shape morphing
- Dynamic animations
- Brand expression
- Balancing expressiveness with usability
## Workflows
### Workflow 1: Designing a New Interface
When designing a new product or feature with Material 3:
1. **Define layout structure**
- Read `references/foundations.md` → Layout section
- Determine window size classes (compact/medium/expanded)
- Choose canonical layout if applicable (list-detail, feed, supporting pane)
2. **Select components**
- Read `references/components.md`
- Use Component Selection Guide to choose appropriate components
- Review specific component guidelines for usage and specs
3. **Establish visual style**
- Read `references/styles.md`
- Define color scheme (dynamic or static)
- Set typography scale
- Choose shape scale (corner radius values)
- Plan motion and transitions
4. **Apply M3 Expressive (optional)**
- Read `references/m3-expressive.md`
- Identify key moments for expressive design
- Apply emphasized easing and extended durations
- Consider shape morphing for transitions
- Follow 80/20 rule (80% standard, 20% expressive)
5. **Validate accessibility**
- Read `references/foundations.md` → Accessibility section
- Check color contrast (WCAG compliance)
- Verify touch targets (minimum 48×48dp)
- Ensure keyboard navigation and screen reader support
### Workflow 2: Reviewing Existing Designs
When reviewing designs for Material 3 compliance:
1. **Component compliance**
- Read `references/components.md` for relevant components
- Check if components follow M3 specifications
- Verify proper component usage (e.g., not using filled buttons excessively)
- Validate component states (enabled, hover, focus, pressed, disabled)
2. **Visual consistency**
- Read `references/styles.md`
- Verify color roles are used correctly
- Check typography matches type scale
- Validate elevation levels
- Review shape consistency (corner radius)
3. **Accessibility audit**
- Read `references/foundations.md` → Accessibility section
- Test color contrast ratios
- Check touch target sizes
- Verify text resizing support
- Review focus indicators
4. **Interaction patterns**
- Read `references/foundations.md` → Interaction section
- Verify state layers are present
- Check gesture support for mobile
- Validate selection patterns
### Workflow 3: Generating Design Tokens
When creating design tokens for a new theme:
1. **Color tokens**
- Read `references/styles.md` → Color section
- Choose color scheme type (dynamic or static)
- Define source color(s)
- Generate tonal palette (13 tones per key color)
- Map color roles (primary, secondary, tertiary, surface, error)
- Create light and dark theme variants
2. **Typography tokens**
- Read `references/styles.md` → Typography section
- Select font family
- Define type scale (display, headline, title, body, label × small/medium/large)
- Set letter spacing and line height
3. **Shape tokens**
- Read `references/styles.md` → Shape section
- Define corner radius scale (none, extra-small, small, medium, large, extra-large)
- Map shapes to component categories
4. **Motion tokens**
- Read `references/styles.md` → Motion section
- Define duration values (short, medium, long)
- Set easing curves (emphasized, standard)
- For M3 Expressive, read `references/m3-expressive.md` → Expressive Motion
### Workflow 4: Implementing M3 Expressive
When adding expressive elements to enhance engagement:
1. **Identify key moments**
- Onboarding flows
- Primary user actions (FAB, main CTAs)
- Screen transitions
- Success/completion states
2. **Apply expressive tactics**
- Read `references/m3-expressive.md` → Design Tactics
- Use emphasized easing for important transitions
- Extend animation durations (400-700ms)
- Add exaggerated scale changes
- Implement layered/staggered animations
- Consider shape morphing
3. **Balance with usability**
- Follow 80/20 rule (most interactions remain standard)
- Respect `prefers-reduced-motion`
- Avoid excessive motion in productivity contexts
- Test on lower-end devices
## Quick Reference
### When to Read Each Reference
| Your Question | Read This |
| ---------------------------------------------- | ---------------------------------------------------------- |
| "What components should I use for navigation?" | `references/components.md` → Navigation Components |
| "How do I create a color scheme?" | `references/styles.md` → Color |
| "What are the responsive breakpoints?" | `references/foundations.md` → Layout → Window Size Classes |
| "How do I make my design more engaging?" | `references/m3-expressive.md` |
| "What's the correct button hierarchy?" | `references/components.md` → Action Components → Buttons |
| "How do I ensure accessibility?" | `references/foundations.md` → Accessibility |
| "What motion timing should I use?" | `references/styles.md` → Motion |
| "How do I implement shape morphing?" | `references/m3-expressive.md` → Shape and Form |
### Component Quick Selector
**Actions:**
- Primary screen action → FAB or Filled Button
- Secondary action → Tonal/Outlined Button
- Tertiary action → Text Button
- Compact action → Icon Button
- Toggle options (2-5) → Segmented Button
**Input:**
- Single choice → Radio Button
- Multiple choices → Checkbox
- On/Off toggle → Switch
- Text input → Text Field
- Date/time → Date/Time Picker
- Range value → Slider
- Tags → Input Chips
**Navigation:**
- Compact screens (<600dp) Navigation Bar
- Medium screens (600-840dp) → Navigation Rail
- Large screens (>840dp) → Navigation Drawer
- Secondary nav → Tabs or Top App Bar
**Communication:**
- Important decision → Dialog
- Quick feedback → Snackbar
- Notification count → Badge
- Loading status → Progress Indicator
- Contextual help → Tooltip
- Action list → Menu
### Design Token Defaults
**Color (Light Theme):**
- Primary: tone 40
- On-primary: tone 100 (white)
- Primary container: tone 90
- Surface: tone 98
**Typography:**
- Display Large: 57sp
- Headline Large: 32sp
- Title Large: 22sp
- Body Large: 16sp
- Label Large: 14sp
**Shape:**
- Extra Small: 4dp (chips, checkboxes)
- Small: 8dp (small buttons)
- Medium: 12dp (cards, standard buttons)
- Large: 16dp (FAB)
- Extra Large: 28dp (dialogs, sheets)
**Elevation:**
- Level 0: 0dp (standard surface)
- Level 1: 1dp (cards)
- Level 2: 3dp (search bars)
- Level 3: 6dp (FAB)
- Level 5: 12dp (modals, dialogs)
## Best Practices
### General Principles
1. **Consistency**: Use design tokens consistently across the product
2. **Hierarchy**: Establish clear visual hierarchy through size, color, and spacing
3. **Accessibility**: Always meet WCAG 2.1 Level AA standards (minimum)
4. **Responsiveness**: Design for all window size classes
5. **Platform conventions**: Respect platform-specific patterns when appropriate
### Common Mistakes to Avoid
- **Too many filled buttons**: Use only one filled button per screen for primary action
- **Ignoring window size classes**: Design must adapt to different screen sizes
- **Poor color contrast**: Always validate contrast ratios
- **Inconsistent spacing**: Use 4dp grid system throughout
- **Overusing M3 Expressive**: Keep 80% standard, 20% expressive
- **Small touch targets**: Minimum 48×48dp for all interactive elements
- **Unclear component states**: All components must show hover, focus, pressed states
### Platform-Specific Notes
**Flutter:**
- Use Material 3 theme in `ThemeData(useMaterial3: true)`
- Access design tokens via `Theme.of(context)`
- Official documentation: https://m3.material.io/develop/flutter
**Android (Jetpack Compose):**
- Use Material3 package
- MaterialTheme provides M3 components and tokens
- Official documentation: https://m3.material.io/develop/android/jetpack-compose
**Web:**
- Use Material Web Components library
- CSS custom properties for design tokens
- Official documentation: https://m3.material.io/develop/web
**Platform-agnostic:**
- Export design tokens from Material Theme Builder
- Apply M3 principles manually to any framework
## Tools and Resources
### Material Theme Builder
Web-based tool for creating M3 color schemes and design tokens:
- Generate color schemes from source colors
- Create light and dark themes
- Export tokens for various platforms
- URL: https://material-foundation.github.io/material-theme-builder/
### Material Symbols
Variable icon font with 2,500+ icons:
- Styles: Outlined, Filled, Rounded, Sharp
- Variable features: weight, grade, optical size, fill
- URL: https://fonts.google.com/icons
### Official Documentation
- Material Design 3: https://m3.material.io/
- Get Started: https://m3.material.io/get-started
- Blog (updates and announcements): https://m3.material.io/blog
## Summary
This skill enables comprehensive Material Design 3 implementation:
1. **Read references as needed**: Don't try to memorize everything—reference files exist to be consulted during work
2. **Follow workflows**: Use structured workflows for common tasks (designing, reviewing, generating tokens)
3. **Start with foundations**: Layout, accessibility, and interaction patterns form the base
4. **Build with components**: Use the 38 documented M3 components appropriately
5. **Apply styles consistently**: Color, typography, shape, elevation, icons, motion
6. **Enhance with M3 Expressive**: Add engaging, emotionally resonant elements where appropriate
7. **Validate accessibility**: Always check contrast, touch targets, and keyboard navigation
Material Design 3 is a complete design system—this skill helps you apply it effectively across all contexts.

View File

@ -0,0 +1,515 @@
# Material 3 Components
Material 3は38のドキュメント化されたコンポーネントを提供します。各コンポーネントには、概要、ガイドライン、仕様、アクセシビリティのサブページがあります。
## Table of Contents
1. [Action Components](#action-components)
2. [Selection and Input Components](#selection-and-input-components)
3. [Navigation Components](#navigation-components)
4. [Containment and Layout Components](#containment-and-layout-components)
5. [Communication Components](#communication-components)
---
## Action Components
ユーザーがアクションを実行するためのコンポーネント。
### Buttons
#### Common Buttons
主要なアクションのための標準的なボタン。
**Variants:**
- **Filled**: 最も高い強調度、プライマリアクション
- **Filled Tonal**: 中程度の強調度、セカンダリアクション
- **Outlined**: 線のみ、中程度の強調度
- **Elevated**: 影付き、強調が必要だがFilledほどではない
- **Text**: 最も低い強調度、補助的なアクション
**Usage Guidelines:**
- 1つの画面にFilledボタンは1つまで推奨
- ボタンの階層を明確にFilled > Tonal > Outlined > Text
- 最小タッチターゲット: 48×48dp
- ラベルは動詞で開始(例: "保存", "送信", "削除"
URL: https://m3.material.io/components/buttons/overview
#### Icon Buttons
コンパクトな補助的アクションボタン。
**Variants:**
- Standard
- Filled
- Filled Tonal
- Outlined
**Usage:**
- 繰り返し使用されるアクション(お気に入り、共有、削除)
- 限られたスペース
- アイコンのみで意味が明確な場合
URL: https://m3.material.io/components/icon-buttons/overview
#### Floating Action Button (FAB)
画面の主要アクションのための浮遊ボタン。
**Types:**
- **FAB**: 標準的なFAB
- **Small FAB**: 小さいFAB
- **Large FAB**: 大きいFAB
- **Extended FAB**: テキストラベル付きFAB
**Guidelines:**
- 1画面に1つのFAB推奨
- 最も重要なアクションのみ
- 配置: 通常は右下
- スクロール時の動作を考慮(隠す/縮小)
URL: https://m3.material.io/components/floating-action-button/overview
#### Segmented Buttons
関連するオプションの単一選択または複数選択グループ。
**Usage:**
- ビューの切り替え(リスト/グリッド)
- フィルタリング(カテゴリ選択)
- 設定オプション
**Guidelines:**
- 2-5個のオプション推奨
- 各オプションは簡潔に1-2語
- アイコン+テキストまたはテキストのみ
URL: https://m3.material.io/components/segmented-buttons/overview
---
## Selection and Input Components
ユーザーが選択や入力を行うためのコンポーネント。
### Checkbox
リストから複数のアイテムを選択。
**States:**
- Unchecked
- Checked
- Indeterminate部分選択
**Usage:**
- 複数選択
- オン/オフ設定ただしSwitchの方が適切な場合も
- リスト項目の選択
URL: https://m3.material.io/components/checkbox/guidelines
### Radio Button
セットから1つのオプションを選択。
**Usage:**
- 相互排他的なオプション1つのみ選択可能
- すべてのオプションを表示する必要がある場合
- 2-7個のオプション推奨
**Guidelines:**
- デフォルト選択肢を提供
- オプションは垂直に配置推奨
- ラベルはクリック可能に
URL: https://m3.material.io/components/radio-button/overview
### Switch
バイナリのオン/オフ切り替え。
**Usage:**
- 即座に効果が反映される設定
- 単一アイテムの有効/無効化
- リスト内の個別項目の切り替え
**vs Checkbox:**
- Switch: 即座に効果、状態の切り替え
- Checkbox: 保存が必要、複数選択
URL: https://m3.material.io/components/switch/guidelines
### Text Fields
テキスト入力用のフォームフィールド。
**Types:**
- **Filled**: デフォルト、背景塗りつぶし
- **Outlined**: 線のみ、フォーム内で推奨
**Elements:**
- Label: 入力内容の説明
- Input text: ユーザー入力
- Helper text: 補助的な説明
- Error text: エラーメッセージ
- Leading/Trailing icons: アイコン
**Guidelines:**
- ラベルは簡潔に
- プレースホルダーは補助的な例として使用
- エラーは具体的に("無効な入力" ではなく "有効なメールアドレスを入力してください"
URL: https://m3.material.io/components/text-fields/overview
### Chips
コンパクトな情報要素。
**Types:**
- **Assist**: アクションやヘルプのサジェスト
- **Filter**: コンテンツのフィルタリング
- **Input**: ユーザー入力(タグ、連絡先)
- **Suggestion**: 動的な提案
**Usage:**
- タグや属性の表示
- フィルタリングオプション
- 選択されたアイテムの表示
URL: https://m3.material.io/components/chips/guidelines
### Sliders
範囲内の値を選択。
**Types:**
- Continuous: 連続的な値
- Discrete: 離散的な値(ステップ付き)
**Usage:**
- 音量、明るさ調整
- 価格範囲選択
- 数値設定
URL: https://m3.material.io/components/sliders/specs
### Date Pickers / Time Pickers
日付と時刻の選択。
**Date Picker Modes:**
- Modal: ダイアログ形式
- Docked: インライン表示
**Time Picker Types:**
- Dial: ダイヤル形式
- Input: テキスト入力形式
URL: https://m3.material.io/components/date-pickers
---
## Navigation Components
アプリ内のナビゲーションを提供するコンポーネント。
### Navigation Bar
モバイル向けボトムナビゲーション。
**Guidelines:**
- 3-5個の主要な目的地
- アイコン+ラベル(アイコンのみは避ける)
- 常に表示(スクロールしても固定)
- Compact window size class向け
URL: https://m3.material.io/components/navigation-bar/overview
### Navigation Drawer
サイドナビゲーション。
**Types:**
- **Standard**: 画面端から開閉
- **Modal**: オーバーレイ形式
**Usage:**
- 5個以上の目的地
- Medium/Expanded window size class
- アプリの主要セクション
URL: https://m3.material.io/components/navigation-drawer/overview
### Navigation Rail
垂直方向のナビゲーション(中型画面)。
**Usage:**
- Medium window size classタブレット縦向き
- 3-7個の目的地
- 画面左端に固定
URL: https://m3.material.io/components/navigation-rail/overview
### Top App Bar
画面上部のタイトルとアクション。
**Types:**
- **Small**: 標準的なアプリバー
- **Medium**: 中サイズ(スクロールで縮小)
- **Large**: 大サイズ(スクロールで縮小)
**Elements:**
- Navigation icon: 戻る、メニュー
- Title: 画面タイトル
- Action icons: 主要なアクション最大3つ推奨
URL: https://m3.material.io/components/app-bars/overview
### Tabs
コンテンツを複数のビューに整理。
**Types:**
- Primary tabs: メインコンテンツの切り替え
- Secondary tabs: サブセクションの切り替え
**Guidelines:**
- 2-6個のタブ推奨
- ラベルは簡潔に1-2語
- スワイプジェスチャーでの切り替えをサポート
URL: https://m3.material.io/components/tabs/guidelines
---
## Containment and Layout Components
コンテンツを整理・表示するためのコンポーネント。
### Cards
関連情報をまとめたコンテナ。
**Types:**
- **Elevated**: 影付き
- **Filled**: 背景塗りつぶし
- **Outlined**: 線のみ
**Usage:**
- 異なるコンテンツのコレクション
- アクション可能なコンテンツ
- エントリーポイント
**Guidelines:**
- 過度に使用しない(リストで十分な場合も)
- 明確なアクションを提供
- 情報の階層を維持
URL: https://m3.material.io/components/cards/guidelines
### Lists
垂直方向のテキストと画像のインデックス。
**Types:**
- Single-line
- Two-line
- Three-line
**Elements:**
- Leading element: アイコン、画像、チェックボックス
- Primary text: メインテキスト
- Secondary text: サブテキスト
- Trailing element: メタ情報、アクション
**Usage:**
- 同質なコンテンツのコレクション
- スキャン可能な情報
- 詳細へのエントリーポイント
URL: https://m3.material.io/components/lists/overview
### Carousel
スクロール可能なビジュアルアイテムのコレクション。
**Types:**
- Hero: 大きい、フォーカスされたアイテム
- Multi-browse: 複数アイテム表示
- Uncontained: フルブリード
**Usage:**
- 画像ギャラリー
- プロダクトショーケース
- オンボーディング
URL: https://m3.material.io/components/carousel/overview
### Bottom Sheets / Side Sheets
追加コンテンツを表示するサーフェス。
**Types:**
- **Standard**: 永続的、画面の一部
- **Modal**: 一時的、フォーカスが必要
**Bottom Sheet Usage:**
- コンテキストアクション
- 追加オプション
- Mobile向け
**Side Sheet Usage:**
- 詳細情報、フィルタ
- Tablet/Desktop向け
URL: https://m3.material.io/components/bottom-sheets/overview
---
## Communication Components
ユーザーにフィードバックや情報を伝えるコンポーネント。
### Dialogs
ユーザーアクションが必要な重要なプロンプト。
**Types:**
- **Basic**: タイトル、本文、アクション
- **Full-screen**: フルスクリーンダイアログ(モバイル)
**Usage:**
- 重要な決定(削除確認など)
- 必須の情報入力
- エラーや警告
**Guidelines:**
- タイトルは質問形式推奨
- アクションは明確に("削除"、"キャンセル"
- 破壊的なアクションは右側に配置しない
URL: https://m3.material.io/components/dialogs/guidelines
### Snackbar
プロセスの簡潔な更新を画面下部に表示。
**Usage:**
- 操作完了の確認("メッセージを送信しました"
- 軽微なエラー通知
- オプショナルなアクション提供
**Guidelines:**
- 表示時間: 4-10秒
- 1行のメッセージ推奨
- 最大1つのアクション
- 重要な情報には使用しないDialogを使用
URL: https://m3.material.io/components/snackbar/overview
### Badges
ナビゲーション項目上の通知とカウント。
**Types:**
- Numeric: 数値表示1-999
- Dot: ドット表示(新着あり)
**Usage:**
- 未読通知の数
- 新着コンテンツのインジケーター
URL: https://m3.material.io/components/badges/overview
### Progress Indicators
進行中のプロセスのステータス表示。
**Types:**
- **Circular**: 円形、不定期または確定的
- **Linear**: 線形、確定的な進捗
**Usage:**
- Circular: ローディング、処理中
- Linear: ファイルアップロード、ダウンロード
**Guidelines:**
- 2秒以上かかる処理で表示
- 可能な限り確定的な進捗を使用
- 進捗率がわからない場合は不定期
URL: https://m3.material.io/components/progress-indicators/overview
### Tooltips
コンテキストラベルとメッセージ。
**Types:**
- Plain: テキストのみ
- Rich: テキスト+アイコン/画像
**Usage:**
- アイコンボタンの説明
- 切り詰められたテキストの完全版
- 補助的な情報
**Guidelines:**
- 簡潔に1行推奨
- 重要な情報には使用しない
- タッチデバイスではlong press
URL: https://m3.material.io/components/tooltips/guidelines
### Menus
一時的なサーフェース上の選択肢リスト。
**Types:**
- Standard menu
- Dropdown menu
- Exposed dropdown menu選択状態を表示
**Usage:**
- コンテキストメニュー
- 選択オプション
- アクションのリスト
**Guidelines:**
- 2-7個のアイテム推奨
- アイコンはオプション
- 破壊的なアクションは分離
URL: https://m3.material.io/components/menus/overview
### Search
検索バーとサジェスト。
**Elements:**
- Search bar: 検索入力フィールド
- Search view: 全画面検索インターフェース
**Usage:**
- アプリ内検索
- フィルタリング
- サジェスト表示
URL: https://m3.material.io/components/search/overview
---
## Component Selection Guide
### Action Selection
| Need | Component |
|------|-----------|
| Primary screen action | FAB or Filled Button |
| Secondary action | Tonal/Outlined Button |
| Tertiary action | Text Button |
| Compact action | Icon Button |
| Toggle between 2-5 options | Segmented Button |
### Input Selection
| Need | Component |
|------|-----------|
| Single choice from list | Radio Button |
| Multiple choices | Checkbox |
| On/Off toggle | Switch |
| Text input | Text Field |
| Date selection | Date Picker |
| Value from range | Slider |
| Tags or attributes | Input Chips |
### Navigation Selection
| Window Size | Primary Nav | Secondary Nav |
|-------------|-------------|---------------|
| Compact (<600dp) | Navigation Bar | Tabs |
| Medium (600-840dp) | Navigation Rail | Tabs |
| Expanded (>840dp) | Navigation Drawer | Tabs, Top App Bar |
---
## References
- Material Design 3 Components: https://m3.material.io/components/
- All Components List: https://m3.material.io/components/all-buttons

View File

@ -0,0 +1,207 @@
# Material 3 Foundations
Material 3 Foundationsは、すべてのMaterialインターフェースの基盤となる設計原則とパターンを定義します。
## Table of Contents
1. [Accessibility](#accessibility)
2. [Layout](#layout)
3. [Interaction](#interaction)
4. [Content Design](#content-design)
5. [Design Tokens](#design-tokens)
6. [Adaptive Design](#adaptive-design)
---
## Accessibility
### Core Principles
- 多様な能力を持つユーザーのための設計
- スクリーンリーダーなどの支援技術との統合
- WCAG準拠のコントラスト比
### Key Areas
#### Structure and Elements
- 直感的なレイアウト階層
- アクセシブルなUI要素の設計
- フォーカス管理とナビゲーション
URL: https://m3.material.io/foundations/designing/structure
#### Color Contrast
- WCAG準拠のカラーコントラスト
- テキストとUIコントロールの視認性
- 4.5:1通常テキスト、3:1大きいテキスト、UIコンポーネント
URL: https://m3.material.io/foundations/designing/color-contrast
#### Text Accessibility
- テキストリサイズのサポート200%まで)
- アクセシブルなテキスト切り詰め
- 明確で適応可能な文章
URL: https://m3.material.io/foundations/writing/text-resizing
---
## Layout
### Understanding Layout
#### Core Components
- **Regions**: 画面の主要エリア(ヘッダー、本文、ナビゲーション)
- **Columns**: グリッドシステムの基本単位
- **Gutters**: カラム間のスペース
- **Spacing**: 4dpベースの一貫したスペーシングシステム
URL: https://m3.material.io/foundations/layout/understanding-layout/overview
### Window Size Classes
画面サイズに応じたレスポンシブデザイン:
| Size Class | Width | Typical Device | Key Patterns |
|-----------|-------|---------------|--------------|
| Compact | <600dp | Phone | Single pane, bottom nav |
| Medium | 600-840dp | Tablet (portrait) | Dual pane optional, nav rail |
| Expanded | >840dp | Tablet (landscape), Desktop | Dual/multi pane, nav drawer |
| Large/XL | >1240dp | Large screens, TV | Multi-pane, extensive nav |
URL: https://m3.material.io/foundations/layout/applying-layout/window-size-classes
### Canonical Layouts
よく使われるレイアウトパターン:
1. **List-detail**: マスター・詳細ナビゲーション
2. **Feed**: コンテンツフィード
3. **Supporting pane**: 補助コンテンツパネル
URL: https://m3.material.io/foundations/layout/canonical-layouts/overview
---
## Interaction
### States
#### Visual States
- **Enabled**: デフォルト状態
- **Hover**: ポインタがホバーしている状態(デスクトップ)
- **Focused**: キーボードフォーカス
- **Pressed**: アクティブに押されている状態
- **Dragged**: ドラッグ中
- **Disabled**: 無効化状態
#### State Layers
半透明なオーバーレイで状態を視覚的に示す:
- Hover: 8% opacity
- Focus: 12% opacity
- Press: 12% opacity
URL: https://m3.material.io/foundations/interaction/states/state-layers
### Gestures
モバイルインターフェース向けタッチジェスチャー:
- Tap: 基本的な選択
- Long press: コンテキストメニュー
- Drag: 移動、並べ替え
- Swipe: ナビゲーション、削除
- Pinch: ズーム
URL: https://m3.material.io/foundations/interaction/gestures
### Selection
選択インタラクションパターン:
- **Single selection**: ラジオボタン、リスト項目
- **Multi selection**: チェックボックス、選択可能なリスト
URL: https://m3.material.io/foundations/interaction/selection
---
## Content Design
### UX Writing Principles
1. **Clear**: 明確で理解しやすい
2. **Concise**: 簡潔で要点を押さえた
3. **Useful**: ユーザーのニーズに応える
4. **Consistent**: 用語とトーンの一貫性
### Notifications
効果的な通知コンテンツ:
- アクション可能な情報
- 明確な次のステップ
- ユーザーコンテキストの理解
URL: https://m3.material.io/foundations/content-design/notifications
### Alt Text
アクセシブルな画像説明:
- 装飾的画像: 空のalt属性
- 機能的画像: アクションを説明
- 情報的画像: 内容を簡潔に説明
URL: https://m3.material.io/foundations/content-design/alt-text
### Global Writing
国際的なオーディエンス向けの文章:
- ローカライゼーションを考慮した単語選択
- 文化的に中立な表現
- 翻訳しやすい文法構造
URL: https://m3.material.io/foundations/content-design/global-writing/overview
---
## Design Tokens
### What are Design Tokens?
デザイントークンは、デザイン、ツール、コード全体で使用される設計上の決定の最小単位:
- **Color tokens**: primary, secondary, surface, error など
- **Typography tokens**: displayLarge, bodyMedium など
- **Shape tokens**: cornerRadius, roundedCorner など
- **Motion tokens**: duration, easing curves
### Benefits
- デザインとコード間の一貫性
- テーマのカスタマイズが容易
- プラットフォーム間での統一
URL: https://m3.material.io/foundations/design-tokens/overview
---
## Adaptive Design
### Principles
- **Responsive**: ウィンドウサイズに応じた調整
- **Adaptive**: デバイス特性に応じた最適化
- **Contextual**: 使用コンテキストを考慮
### Key Strategies
1. Window size classesに基づくレイアウト調整
2. 入力方式(タッチ、マウス、キーボード)への対応
3. デバイス機能(カメラ、位置情報等)の活用
4. オフラインとオンラインシナリオの対応
URL: https://m3.material.io/foundations/adaptive-design
---
## References
- Material Design 3 Foundations: https://m3.material.io/foundations/
- Glossary: https://m3.material.io/foundations/glossary

View File

@ -0,0 +1,470 @@
# Material 3 Expressive
M3 Expressiveは、Googleが2024-2025年に導入したMaterial 3の進化版で、より魅力的で感情的に共鳴するインターフェースを実現します。
## Table of Contents
1. [Overview](#overview)
2. [Usability Principles](#usability-principles)
3. [Design Tactics](#design-tactics)
4. [Expressive Motion](#expressive-motion)
5. [Shape and Form](#shape-and-form)
6. [Implementation Guidelines](#implementation-guidelines)
---
## Overview
### What is M3 Expressive?
M3 Expressiveは、標準のMaterial 3を拡張し、以下を実現します:
- **Engaging**: ユーザーの注意を引き、関心を維持
- **Emotionally resonant**: 感情的なつながりを生む
- **User-friendly**: 使いやすさを犠牲にしない
- **Brand expression**: ブランドの個性を表現
### Key Differences from Standard M3
| Aspect | Standard M3 | M3 Expressive |
|--------|-------------|---------------|
| Motion | 控えめ、機能的 | 大胆、表現豊か |
| Shapes | 一貫した角丸 | 動的な形状変形 |
| Emphasis | 明確、シンプル | ドラマチック、インパクト |
| Timing | 速い200-300ms | やや長め400-700ms |
URL: https://m3.material.io/blog/building-with-m3-expressive
---
## Usability Principles
### Creating Engaging Products
M3 Expressiveは、以下のusability原則に基づきます:
#### 1. Guide Users
ユーザーを適切に誘導する:
- **Motion paths**: アニメーションでフローを示す
- **Visual hierarchy**: 動きで注意を引く
- **Staged reveal**: 段階的に情報を開示
#### 2. Emphasize Actions
重要なアクションを強調:
- **Scale changes**: サイズ変化で重要性を示す
- **Color dynamics**: 色の変化で状態を表現
- **Focused attention**: 1つの要素に注意を集中
#### 3. Provide Feedback
ユーザーのアクションに対する明確なフィードバック:
- **Immediate response**: 即座の視覚的反応
- **State transitions**: 状態変化を明確に表現
- **Completion signals**: アクション完了を示す
URL: https://m3.material.io/foundations/usability/overview
---
## Design Tactics
M3 Expressiveを実装するための具体的なデザイン戦術。
URL: https://m3.material.io/foundations/usability/applying-m-3-expressive
### 1. Emphasized Easing
**Standard easing**よりも劇的な**Emphasized easing**を使用:
```
Emphasized Decelerate: cubic-bezier(0.05, 0.7, 0.1, 1.0)
Emphasized Accelerate: cubic-bezier(0.3, 0.0, 0.8, 0.15)
```
**When to use:**
- 重要なトランジション
- ユーザーの注意を引く必要がある場合
- ブランド表現を強化したい場合
**Example:**
```css
.expressive-enter {
animation: enter 500ms cubic-bezier(0.05, 0.7, 0.1, 1.0);
}
```
### 2. Extended Duration
標準より長いアニメーション時間:
| Element | Standard | Expressive |
|---------|----------|------------|
| Small changes | 100ms | 150-200ms |
| Medium changes | 250ms | 400-500ms |
| Large transitions | 300ms | 500-700ms |
**Caution:** 1000msを超えないこと
### 3. Exaggerated Scale
スケール変化を誇張:
**Standard:**
- Scale: 1.0 → 1.05+5%
**Expressive:**
- Scale: 1.0 → 1.15+15%
- Scale: 1.0 → 0.9 → 1.1bounce effect
**Example use case:**
- FABのタップアニメーション
- カードの選択状態
- アイコンのアクティブ状態
### 4. Dynamic Color Transitions
色の動的な変化:
**Techniques:**
- Gradient animations: グラデーションの動的変化
- Color pulse: 色のパルス効果
- Hue rotation: 色相の変化
**Example:**
```css
.expressive-button:active {
background: linear-gradient(45deg, primary, tertiary);
transition: background 400ms cubic-bezier(0.05, 0.7, 0.1, 1.0);
}
```
### 5. Layered Motion
複数の要素が異なるタイミングで動く:
**Stagger animations:**
- 遅延: 50-100ms per item
- リストアイテムの順次表示
- カードグリッドの表示
**Example timing:**
```
Item 1: 0ms
Item 2: 80ms
Item 3: 160ms
Item 4: 240ms
```
### 6. Shape Morphing
形状の動的な変形(後述)
---
## Expressive Motion
M3 Expressiveの中核となるモーションシステム。
URL: https://m3.material.io/blog/m3-expressive-motion-theming
### Motion Theming System
カスタマイズ可能な新しいモーションテーマシステム:
#### Motion Tokens
**Duration tokens:**
```
motion.duration.short: 150ms
motion.duration.medium: 400ms
motion.duration.long: 600ms
motion.duration.extra-long: 1000ms
```
**Easing tokens:**
```
motion.easing.emphasized: cubic-bezier(0.05, 0.7, 0.1, 1.0)
motion.easing.emphasizedDecelerate: cubic-bezier(0.05, 0.7, 0.1, 1.0)
motion.easing.emphasizedAccelerate: cubic-bezier(0.3, 0.0, 0.8, 0.15)
motion.easing.standard: cubic-bezier(0.2, 0.0, 0, 1.0)
```
### Expressive Transition Patterns
#### 1. Container Transform (Enhanced)
**Standard container transform:**
- Duration: 300ms
- Easing: standard
**Expressive container transform:**
- Duration: 500ms
- Easing: emphasized
- 追加効果: 軽いスケール変化、色の変化
#### 2. Shared Axis (Enhanced)
**Expressive enhancements:**
- より大きいスライド距離(+20%
- フェード+スケール効果の組み合わせ
- ステージングされた要素の動き
#### 3. Morph Transition
新しいトランジションタイプ:
- 形状の滑らかな変形
- 複数プロパティの同時変化(サイズ、色、形状)
- 有機的な動き
**Example:**
```
Circle → Rounded Rectangle → Full Screen
(300ms) → (200ms)
```
### Micro-interactions
小さいが印象的なインタラクション:
#### Button Press
```
1. Scale down: 0.95 (50ms)
2. Scale up: 1.0 (150ms, emphasized easing)
3. Ripple effect: expanded, slower
```
#### Icon State Change
```
1. Scale out: 0.8 + rotate 15deg (100ms)
2. Icon swap
3. Scale in: 1.0 + rotate 0deg (200ms, emphasized)
```
#### Loading States
```
- Pulse animation: 1.0 → 1.1 → 1.0 (800ms, loop)
- Color shift: primary → tertiary → primary
```
---
## Shape and Form
### Shape Morph
動的な形状変形でブランド表現を強化。
URL: https://m3.material.io/styles/shape/shape-morph
#### Basic Shape Morph
形状の滑らかな変化:
**Example scenarios:**
1. **FAB → Dialog**
- Circle (56dp) → Rounded rectangle (280×400dp)
- Duration: 500ms
- Easing: emphasized decelerate
2. **Chip → Card**
- Small rounded (32dp) → Medium rounded (card size)
- Duration: 400ms
3. **Button → Full Width**
- Fixed width → Full screen width
- Corner radius維持
#### Advanced Techniques
**Path morphing:**
- SVGパスの変形
- ベジェ曲線の補間
- 複雑な形状間の遷移
**Example SVG morph:**
```svg
<path d="M10,10 L90,10 L90,90 L10,90 Z">
<animate attributeName="d"
to="M50,10 L90,50 L50,90 L10,50 Z"
dur="500ms"
fill="freeze"/>
</path>
```
### Organic Shapes
より自然で有機的な形状:
**Characteristics:**
- 非対称な角丸
- 流動的なライン
- 自然界からのインスピレーション
**Use cases:**
- ブランド要素
- ヒーローセクション
- イラストレーション
---
## Implementation Guidelines
### When to Use M3 Expressive
#### Good Use Cases ✓
- **Consumer apps**: エンターテイメント、ソーシャル、ゲーム
- **Brand-forward products**: ブランド表現が重要
- **Engagement-critical flows**: オンボーディング、チュートリアル
- **Hero moments**: 重要なマイルストーン、達成
#### Use with Caution ⚠
- **Productivity apps**: 過度なアニメーションは避ける
- **Frequent actions**: 繰り返し使用される操作
- **Data-heavy interfaces**: 情報が優先される場合
#### Avoid ✗
- **Accessibility concerns**: 動きに敏感なユーザー
- **Performance-constrained**: 低スペックデバイス
- **Critical tasks**: エラーや警告の表示
### Balancing Expressiveness and Usability
#### The 80/20 Rule
- **80%**: 標準のM3速く、機能的
- **20%**: M3 Expressive印象的、ブランド表現
**Example distribution:**
- Standard M3: リスト項目タップ、フォーム入力、設定変更
- M3 Expressive: 画面遷移、主要アクションFAB、初回体験
### Respect User Preferences
#### Reduced Motion
`prefers-reduced-motion`メディアクエリを尊重:
```css
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
```
#### Accessibility
- **Vestibular disorders**: 大きい動きを避ける
- **Cognitive load**: 同時に動く要素を制限
- **Focus management**: アニメーション中もフォーカス可能
---
## Practical Examples
### Example 1: Expressive FAB Tap
```css
.fab {
transition: transform 150ms cubic-bezier(0.05, 0.7, 0.1, 1.0),
box-shadow 150ms cubic-bezier(0.05, 0.7, 0.1, 1.0);
}
.fab:active {
transform: scale(0.92);
}
.fab:not(:active) {
transform: scale(1.0);
}
/* Ripple with longer duration */
.fab::after {
animation: ripple 600ms cubic-bezier(0.05, 0.7, 0.1, 1.0);
}
```
### Example 2: Card to Detail Transition
```javascript
// Container transform with expressive timing
const expandCard = (card) => {
card.animate([
{
transform: 'scale(1)',
borderRadius: '12px'
},
{
transform: 'scale(1.02)',
borderRadius: '28px',
offset: 0.3
},
{
transform: 'scale(1)',
borderRadius: '0px'
}
], {
duration: 500,
easing: 'cubic-bezier(0.05, 0.7, 0.1, 1.0)',
fill: 'forwards'
});
};
```
### Example 3: Staggered List Animation
```css
.list-item {
opacity: 0;
transform: translateY(20px);
animation: fadeInUp 400ms cubic-bezier(0.05, 0.7, 0.1, 1.0) forwards;
}
.list-item:nth-child(1) { animation-delay: 0ms; }
.list-item:nth-child(2) { animation-delay: 80ms; }
.list-item:nth-child(3) { animation-delay: 160ms; }
.list-item:nth-child(4) { animation-delay: 240ms; }
@keyframes fadeInUp {
to {
opacity: 1;
transform: translateY(0);
}
}
```
---
## Resources and Tools
### Design Tools
- **Material Theme Builder**: M3 Expressiveモーションプリセット
- **Figma Plugins**: Motion timing visualization
- **After Effects**: プロトタイプアニメーション
### Code Libraries
- **Web**: Material Web Components (M3 support)
- **Flutter**: Material 3 with custom motion
- **Android**: Jetpack Compose Material3
### References
- M3 Expressive announcement: https://m3.material.io/blog/building-with-m3-expressive
- Motion theming: https://m3.material.io/blog/m3-expressive-motion-theming
- Usability tactics: https://m3.material.io/foundations/usability/applying-m-3-expressive
---
## Summary Checklist
When implementing M3 Expressive, ensure:
- [ ] Emphasized easing for key transitions
- [ ] Extended durations (but <1000ms)
- [ ] Exaggerated scale changes where appropriate
- [ ] Layered/staggered animations for lists
- [ ] Shape morphing for container transforms
- [ ] Color dynamics for feedback
- [ ] Respect `prefers-reduced-motion`
- [ ] 80/20 balance (Standard M3 vs Expressive)
- [ ] Test on lower-end devices
- [ ] Validate accessibility

View File

@ -0,0 +1,318 @@
# Material 3 Styles
Material 3 Stylesは、カラー、タイポグラフィ、形状、エレベーション、アイコン、モーションを通じて視覚言語を定義します。
## Table of Contents
1. [Color](#color)
2. [Typography](#typography)
3. [Elevation](#elevation)
4. [Shape](#shape)
5. [Icons](#icons)
6. [Motion](#motion)
---
## Color
### Color System Overview
Material 3のカラーシステムは、アクセシブルでパーソナライズ可能なカラースキームを作成します。
URL: https://m3.material.io/styles/color/system/overview
### Color Roles
UIエレメントを特定の色に結びつける役割:
#### Primary Colors
- **primary**: アプリの主要色(メインボタン、アクティブ状態)
- **onPrimary**: プライマリ色上のテキスト/アイコン
- **primaryContainer**: プライマリ要素のコンテナ
- **onPrimaryContainer**: コンテナ上のテキスト
#### Secondary & Tertiary
- **secondary**: アクセントカラー
- **tertiary**: 強調やバランス調整
#### Surface Colors
- **surface**: カード、シート、メニューの背景
- **surfaceVariant**: わずかに異なる背景
- **surfaceTint**: エレベーション表現用
#### Semantic Colors
- **error**: エラー状態
- **warning**: 警告(一部実装で利用可能)
- **success**: 成功状態(一部実装で利用可能)
URL: https://m3.material.io/styles/color/roles
### Color Schemes
#### Dynamic Color
ユーザーの壁紙や選択から色を抽出:
- **User-generated**: ユーザーの選択から
- **Content-based**: 画像/コンテンツから抽出
URL: https://m3.material.io/styles/color/dynamic-color/overview
#### Static Color
固定されたカラースキーム:
- **Baseline**: デフォルトのMaterialベースライン
- **Custom brand**: カスタムブランドカラー
URL: https://m3.material.io/styles/color/static/baseline
### Key Colors and Tones
- **Source color**: スキーム生成の起点となる色
- **Tonal palette**: 各キーカラーから生成される13段階のトーン0, 10, 20, ..., 100
- Light theme: 通常トーン40をプライマリに使用
- Dark theme: 通常トーン80をプライマリに使用
URL: https://m3.material.io/styles/color/the-color-system/key-colors-tones
### Tools
**Material Theme Builder**: カラースキーム生成、カスタマイズ、エクスポートツール
URL: https://m3.material.io/blog/material-theme-builder-2-color-match
---
## Typography
### Type Scale
Material 3は5つのロール×3つのサイズ = 15のタイプスタイルを定義:
#### Roles
1. **Display**: 大きく短いテキスト(ヒーロー、見出し)
2. **Headline**: 中規模の見出し
3. **Title**: 小さい見出し(アプリバー、リスト項目)
4. **Body**: 本文テキスト
5. **Label**: ボタン、タブ、小さいテキスト
#### Sizes
- **Large**: 最大サイズ
- **Medium**: 標準サイズ
- **Small**: 最小サイズ
#### Example Styles
```
displayLarge: 57sp, -0.25 letter spacing
headlineMedium: 28sp, 0 letter spacing
bodyLarge: 16sp, 0.5 letter spacing
labelSmall: 11sp, 0.5 letter spacing
```
URL: https://m3.material.io/styles/typography/overview
### Fonts
- デフォルト: **Roboto** (Android), **San Francisco** (iOS), **Roboto** (Web)
- カスタムフォントのサポート
- 変数フォントの活用
URL: https://m3.material.io/styles/typography/fonts
### Applying Typography
- セマンティックな使用見出しにはheadline、本文にはbody
- 一貫した階層
- 行の高さと余白の適切な設定
URL: https://m3.material.io/styles/typography/applying-type
---
## Elevation
### Overview
エレベーションはZ軸上のサーフェス間の距離を表現します。
URL: https://m3.material.io/styles/elevation/overview
### Elevation Levels
Material 3は6つのエレベーションレベルを定義:
| Level | DP | Use Case |
|-------|-----|----------|
| 0 | 0dp | 通常のサーフェス |
| 1 | 1dp | カード、わずかに浮いた要素 |
| 2 | 3dp | 検索バー |
| 3 | 6dp | FAB休止状態 |
| 4 | 8dp | ナビゲーションドロワー |
| 5 | 12dp | モーダルボトムシート、ダイアログ |
### Elevation Representation
Material 3では2つの方法でエレベーションを表現:
1. **Shadow**: 影によるエレベーションLight theme主体
2. **Surface tint**: サーフェスに色のティントを重ねるDark theme主体
URL: https://m3.material.io/styles/elevation/applying-elevation
---
## Shape
### Overview
形状は、注意の誘導、状態表現、ブランド表現に使用されます。
URL: https://m3.material.io/styles/shape/overview-principles
### Corner Radius Scale
Material 3は5つの形状トークンを定義:
| Token | Default Value | Use Case |
|-------|---------------|----------|
| None | 0dp | フルスクリーン、厳格なレイアウト |
| Extra Small | 4dp | チェックボックス、小さい要素 |
| Small | 8dp | チップ、小さいボタン |
| Medium | 12dp | カード、標準ボタン |
| Large | 16dp | FAB、大きいカード |
| Extra Large | 28dp | ダイアログ、ボトムシート |
| Full | 9999dp | 完全な円形 |
### Shape Morph
**M3 Expressiveの重要機能**: 形状が滑らかに変形するアニメーション
- トランジション時の視覚的な流れ
- ブランド表現の強化
- ユーザーの注意を引く
URL: https://m3.material.io/styles/shape/shape-morph
---
## Icons
### Material Symbols
Material Symbolsは可変アイコンフォント:
#### Styles
- **Outlined**: 線のみのスタイル(デフォルト)
- **Filled**: 塗りつぶしスタイル
- **Rounded**: 丸みを帯びたスタイル
- **Sharp**: シャープなスタイル
#### Variable Features
- **Weight**: 線の太さ100-700
- **Grade**: 視覚的な重み(-25 to 200
- **Optical size**: 表示サイズ最適化20, 24, 40, 48dp
- **Fill**: 塗りつぶし状態0-1
#### Sizes
- 20dp: 密なレイアウト
- 24dp: 標準サイズ
- 40dp: タッチターゲット拡大
- 48dp: 大きいタッチターゲット
URL: https://m3.material.io/styles/icons/overview
### Custom Icons
カスタムアイコンのデザインガイドライン:
- 24×24dpグリッド
- 2dpストローク幅
- 2dpの角丸
- 一貫したメタファー
URL: https://m3.material.io/styles/icons/designing-icons
---
## Motion
**M3 Expressiveの中核要素**: モーションは、UIを表現豊かで使いやすくします。
URL: https://m3.material.io/styles/motion/overview
### Motion Principles
1. **Informative**: ユーザーに情報を伝える
2. **Focused**: 注意を適切に誘導
3. **Expressive**: 感情的なエンゲージメントを高める
URL: https://m3.material.io/styles/motion/overview/how-it-works
### Easing and Duration
#### Easing Types
Material 3は4つのイージングカーブを定義:
1. **Emphasized**: 劇的で表現豊かな動き
- Decelerate: cubic-bezier(0.05, 0.7, 0.1, 1.0)
- Accelerate: cubic-bezier(0.3, 0.0, 0.8, 0.15)
- Standard: cubic-bezier(0.2, 0.0, 0, 1.0)
2. **Standard**: バランスの取れた標準的な動き
- cubic-bezier(0.2, 0.0, 0, 1.0)
3. **Emphasized Decelerate**: 要素が画面に入る
- cubic-bezier(0.05, 0.7, 0.1, 1.0)
4. **Emphasized Accelerate**: 要素が画面から出る
- cubic-bezier(0.3, 0.0, 0.8, 0.15)
#### Duration Guidelines
| Element Change | Duration |
|----------------|----------|
| Small (icon state) | 50-100ms |
| Medium (component state) | 250-300ms |
| Large (layout change) | 400-500ms |
| Complex transition | 500-700ms |
**重要**: 長すぎるアニメーション(>1000msは避ける
URL: https://m3.material.io/styles/motion/easing-and-duration
### Transitions
ナビゲーション時のトランジションパターン:
#### Transition Types
1. **Container transform**: コンテナが変形して次の画面へ
2. **Shared axis**: 共通軸に沿った移動X, Y, Z軸
3. **Fade through**: フェードアウト→フェードイン
4. **Fade**: シンプルなフェード
#### When to Use Each
- **Container transform**: リスト項目→詳細画面
- **Shared axis X**: タブ切り替え、水平ナビゲーション
- **Shared axis Y**: ステッパー、垂直ナビゲーション
- **Shared axis Z**: 前後のナビゲーション(戻る/進む)
- **Fade through**: コンテンツ更新(関連性が低い)
- **Fade**: オーバーレイ、補助的な変更
URL: https://m3.material.io/styles/motion/transitions/transition-patterns
### M3 Expressive Motion
**新しい表現豊かなモーションシステム**:
- より大胆なアニメーション
- カスタマイズ可能なモーションテーマ
- ブランド表現の強化
URL: https://m3.material.io/blog/m3-expressive-motion-theming
---
## References
- Material Design 3 Styles: https://m3.material.io/styles/
- Material Theme Builder: https://material-foundation.github.io/material-theme-builder/
- Material Symbols: https://fonts.google.com/icons

View File

@ -0,0 +1,201 @@
---
name: "moai-platform-supabase"
description: "Supabase specialist covering PostgreSQL 16, pgvector, RLS, real-time subscriptions, and Edge Functions. Use when building full-stack apps with Supabase backend."
version: 2.0.0
category: "platform"
modularized: true
tags: ['supabase', 'postgresql', 'pgvector', 'realtime', 'rls', 'edge-functions']
context7-libraries: ['/supabase/supabase']
related-skills: ['moai-platform-neon', 'moai-lang-typescript']
updated: 2026-01-06
status: "active"
allowed-tools: "Read, Grep, Glob, mcp__context7__resolve-library-id, mcp__context7__get-library-docs"
---
# moai-platform-supabase: Supabase Platform Specialist
## Quick Reference (30 seconds)
Supabase Full-Stack Platform: PostgreSQL 16 with pgvector for AI/vector search, Row-Level Security for multi-tenant apps, real-time subscriptions, Edge Functions with Deno runtime, and integrated Storage with transformations.
### Core Capabilities
PostgreSQL 16: Latest PostgreSQL with full SQL support, JSONB, and advanced features
pgvector Extension: AI embeddings storage with HNSW/IVFFlat indexes for similarity search
Row-Level Security: Automatic multi-tenant data isolation at database level
Real-time Subscriptions: Live data sync via Postgres Changes and Presence
Edge Functions: Serverless Deno functions at the edge
Storage: File storage with automatic image transformations
Auth: Built-in authentication with JWT integration
### When to Use Supabase
- Multi-tenant SaaS applications requiring data isolation
- AI/ML applications needing vector embeddings and similarity search
- Real-time collaborative features (presence, live updates)
- Full-stack applications needing auth, database, and storage
- Projects requiring PostgreSQL-specific features
### Context7 Documentation Access
For latest Supabase API documentation, use the Context7 MCP tools:
Step 1 - Resolve library ID:
Use mcp__context7__resolve-library-id with query "supabase" to get the Context7-compatible library ID
Step 2 - Fetch documentation:
Use mcp__context7__get-library-docs with the resolved library ID, specifying topic and token allocation
Example topics: "postgresql pgvector", "row-level-security policies", "realtime subscriptions presence", "edge-functions deno", "storage transformations", "auth jwt"
---
## Module Index
This skill uses progressive disclosure with specialized modules for detailed implementation patterns.
### Core Modules
**postgresql-pgvector** - PostgreSQL 16 with pgvector extension for AI embeddings and semantic search
- Vector storage with 1536-dimension OpenAI embeddings
- HNSW and IVFFlat index strategies
- Semantic search functions
- Hybrid search combining vector and full-text
**row-level-security** - RLS policies for multi-tenant data isolation
- Basic tenant isolation patterns
- Hierarchical organization access
- Role-based modification policies
- Service role bypass for server operations
**realtime-presence** - Real-time subscriptions and presence tracking
- Postgres Changes subscription patterns
- Filtered change listeners
- Presence state management
- Collaborative cursor and typing indicators
**edge-functions** - Serverless Deno functions at the edge
- Basic Edge Function with authentication
- CORS header configuration
- JWT token verification
- Rate limiting implementation
**storage-cdn** - File storage with image transformations
- File upload patterns
- Image transformation URLs
- Thumbnail generation
- Cache control configuration
**auth-integration** - Authentication patterns and JWT handling
- Server-side client creation
- Cookie-based session management
- Auth state synchronization
- Protected route patterns
**typescript-patterns** - TypeScript client patterns and service layers
- Server-side client for Next.js App Router
- Service layer abstraction pattern
- Subscription management
- Type-safe database operations
---
## Quick Start Patterns
### Database Setup
Enable pgvector extension and create embeddings table:
```sql
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE documents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
content TEXT NOT NULL,
embedding vector(1536),
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_documents_embedding ON documents
USING hnsw (embedding vector_cosine_ops);
```
### Basic RLS Policy
```sql
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
CREATE POLICY "tenant_isolation" ON projects FOR ALL
USING (tenant_id = (auth.jwt() ->> 'tenant_id')::UUID);
```
### Real-time Subscription
```typescript
const channel = supabase.channel('db-changes')
.on('postgres_changes',
{ event: '*', schema: 'public', table: 'messages' },
(payload) => console.log('Change:', payload)
)
.subscribe()
```
### Edge Function Template
```typescript
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
serve(async (req) => {
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)
// Process request
return new Response(JSON.stringify({ success: true }))
})
```
---
## Best Practices
Performance: Use HNSW indexes for vectors, Supavisor for connection pooling in serverless
Security: Always enable RLS, verify JWT tokens, use service_role only in Edge Functions
Migration: Use Supabase CLI (supabase migration new, supabase db push)
---
## Works Well With
- moai-platform-neon - Alternative PostgreSQL for specific use cases
- moai-lang-typescript - TypeScript patterns for Supabase client
- moai-domain-backend - Backend architecture integration
- moai-foundation-quality - Security and RLS best practices
- moai-workflow-testing - Test-driven development with Supabase
---
## Module References
For detailed implementation patterns, see the modules directory:
- modules/postgresql-pgvector.md - Complete vector search implementation
- modules/row-level-security.md - Multi-tenant RLS patterns
- modules/realtime-presence.md - Real-time collaboration features
- modules/edge-functions.md - Serverless function patterns
- modules/storage-cdn.md - File storage and transformations
- modules/auth-integration.md - Authentication patterns
- modules/typescript-patterns.md - TypeScript client architecture
For API reference summary, see reference.md
For full-stack templates, see examples.md
---
Status: Production Ready
Generated with: MoAI-ADK Skill Factory v2.0
Last Updated: 2026-01-06
Version: 2.0.0 (Modularized)
Coverage: PostgreSQL 16, pgvector, RLS, Real-time, Edge Functions, Storage

View File

@ -0,0 +1,502 @@
---
name: supabase-examples
description: Full-stack templates and working examples for Supabase applications
parent-skill: moai-platform-supabase
version: 1.0.0
updated: 2026-01-06
---
# Supabase Full-Stack Examples
## Multi-Tenant SaaS Application
### Database Schema
```sql
-- Organizations (tenants)
CREATE TABLE organizations (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name TEXT NOT NULL,
slug TEXT UNIQUE NOT NULL,
plan TEXT DEFAULT 'free' CHECK (plan IN ('free', 'pro', 'enterprise')),
settings JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Organization members with roles
CREATE TABLE organization_members (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
user_id UUID NOT NULL,
role TEXT NOT NULL CHECK (role IN ('owner', 'admin', 'member', 'viewer')),
joined_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(organization_id, user_id)
);
-- Projects within organizations
CREATE TABLE projects (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
name TEXT NOT NULL,
description TEXT,
owner_id UUID NOT NULL,
status TEXT DEFAULT 'active',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Enable RLS
ALTER TABLE organizations ENABLE ROW LEVEL SECURITY;
ALTER TABLE organization_members ENABLE ROW LEVEL SECURITY;
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
-- Policies
CREATE POLICY "org_member_select" ON organizations FOR SELECT
USING (id IN (SELECT organization_id FROM organization_members WHERE user_id = auth.uid()));
CREATE POLICY "org_admin_update" ON organizations FOR UPDATE
USING (id IN (SELECT organization_id FROM organization_members
WHERE user_id = auth.uid() AND role IN ('owner', 'admin')));
CREATE POLICY "member_view" ON organization_members FOR SELECT
USING (organization_id IN (SELECT organization_id FROM organization_members WHERE user_id = auth.uid()));
CREATE POLICY "project_access" ON projects FOR ALL
USING (organization_id IN (SELECT organization_id FROM organization_members WHERE user_id = auth.uid()));
-- Indexes
CREATE INDEX idx_org_members_user ON organization_members(user_id);
CREATE INDEX idx_org_members_org ON organization_members(organization_id);
CREATE INDEX idx_projects_org ON projects(organization_id);
```
### TypeScript Service Layer
```typescript
// services/organization-service.ts
import { supabase } from '@/lib/supabase/client'
import type { Database } from '@/types/database'
type Organization = Database['public']['Tables']['organizations']['Row']
type OrganizationMember = Database['public']['Tables']['organization_members']['Row']
export class OrganizationService {
async create(name: string, slug: string): Promise<Organization> {
const { data: { user } } = await supabase.auth.getUser()
if (!user) throw new Error('Not authenticated')
// Create organization
const { data: org, error: orgError } = await supabase
.from('organizations')
.insert({ name, slug })
.select()
.single()
if (orgError) throw orgError
// Add creator as owner
const { error: memberError } = await supabase
.from('organization_members')
.insert({
organization_id: org.id,
user_id: user.id,
role: 'owner'
})
if (memberError) {
// Rollback org creation
await supabase.from('organizations').delete().eq('id', org.id)
throw memberError
}
return org
}
async getMyOrganizations(): Promise<Organization[]> {
const { data, error } = await supabase
.from('organizations')
.select('*, organization_members!inner(role)')
.order('name')
if (error) throw error
return data
}
async getMembers(orgId: string): Promise<OrganizationMember[]> {
const { data, error } = await supabase
.from('organization_members')
.select('*, user:profiles(*)')
.eq('organization_id', orgId)
.order('joined_at')
if (error) throw error
return data
}
async inviteMember(orgId: string, email: string, role: string): Promise<void> {
const { error } = await supabase.functions.invoke('invite-member', {
body: { organizationId: orgId, email, role }
})
if (error) throw error
}
}
export const organizationService = new OrganizationService()
```
## AI Document Search Application
### Database Schema
```sql
-- Enable extensions
CREATE EXTENSION IF NOT EXISTS vector;
-- Documents with embeddings
CREATE TABLE documents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
title TEXT NOT NULL,
content TEXT NOT NULL,
embedding vector(1536),
metadata JSONB DEFAULT '{}',
created_by UUID NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- HNSW index for fast similarity search
CREATE INDEX idx_documents_embedding ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- Full-text search index
CREATE INDEX idx_documents_content_fts ON documents
USING gin(to_tsvector('english', content));
-- Enable RLS
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
CREATE POLICY "document_access" ON documents FOR ALL
USING (project_id IN (
SELECT p.id FROM projects p
JOIN organization_members om ON p.organization_id = om.organization_id
WHERE om.user_id = auth.uid()
));
-- Semantic search function
CREATE OR REPLACE FUNCTION search_documents(
p_project_id UUID,
p_query_embedding vector(1536),
p_match_threshold FLOAT DEFAULT 0.7,
p_match_count INT DEFAULT 10
) RETURNS TABLE (
id UUID,
title TEXT,
content TEXT,
similarity FLOAT
) LANGUAGE plpgsql AS $$
BEGIN
RETURN QUERY
SELECT d.id, d.title, d.content,
1 - (d.embedding <=> p_query_embedding) AS similarity
FROM documents d
WHERE d.project_id = p_project_id
AND 1 - (d.embedding <=> p_query_embedding) > p_match_threshold
ORDER BY d.embedding <=> p_query_embedding
LIMIT p_match_count;
END; $$;
```
### Edge Function for Embeddings
```typescript
// supabase/functions/generate-embedding/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type'
}
serve(async (req) => {
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
try {
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)
const { documentId, content } = await req.json()
// Generate embedding using OpenAI
const embeddingResponse = await fetch('https://api.openai.com/v1/embeddings', {
method: 'POST',
headers: {
'Authorization': `Bearer ${Deno.env.get('OPENAI_API_KEY')}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'text-embedding-ada-002',
input: content.slice(0, 8000)
})
})
const embeddingData = await embeddingResponse.json()
const embedding = embeddingData.data[0].embedding
// Update document with embedding
const { error } = await supabase
.from('documents')
.update({ embedding })
.eq('id', documentId)
if (error) throw error
return new Response(
JSON.stringify({ success: true }),
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
)
} catch (error) {
return new Response(
JSON.stringify({ error: error.message }),
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
)
}
})
```
### React Search Component
```typescript
// components/DocumentSearch.tsx
'use client'
import { useState, useEffect } from 'react'
import { useDebounce } from '@/hooks/useDebounce'
import { documentService } from '@/services/document-service'
interface SearchResult {
id: string
title: string
content: string
similarity: number
}
export function DocumentSearch({ projectId }: { projectId: string }) {
const [query, setQuery] = useState('')
const [results, setResults] = useState<SearchResult[]>([])
const [loading, setLoading] = useState(false)
const debouncedQuery = useDebounce(query, 300)
useEffect(() => {
if (debouncedQuery.length < 3) {
setResults([])
return
}
async function search() {
setLoading(true)
try {
const data = await documentService.semanticSearch(projectId, debouncedQuery)
setResults(data)
} catch (error) {
console.error('Search failed:', error)
} finally {
setLoading(false)
}
}
search()
}, [debouncedQuery, projectId])
return (
<div className="space-y-4">
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search documents..."
className="w-full px-4 py-2 border rounded-lg"
/>
{loading && <div>Searching...</div>}
<div className="space-y-2">
{results.map((result) => (
<div key={result.id} className="p-4 border rounded-lg">
<div className="flex justify-between">
<h3 className="font-medium">{result.title}</h3>
<span className="text-sm text-gray-500">
{(result.similarity * 100).toFixed(1)}% match
</span>
</div>
<p className="mt-2 text-gray-600 line-clamp-2">{result.content}</p>
</div>
))}
</div>
</div>
)
}
```
## Real-Time Collaboration
### Collaborative Editor with Presence
```typescript
// components/CollaborativeEditor.tsx
'use client'
import { useEffect, useState, useCallback } from 'react'
import { supabase } from '@/lib/supabase/client'
interface User {
id: string
name: string
color: string
}
interface PresenceState {
user: User
cursor: { x: number; y: number } | null
selection: { start: number; end: number } | null
}
export function CollaborativeEditor({
documentId,
currentUser
}: {
documentId: string
currentUser: User
}) {
const [content, setContent] = useState('')
const [otherUsers, setOtherUsers] = useState<PresenceState[]>([])
const [channel, setChannel] = useState<ReturnType<typeof supabase.channel> | null>(null)
useEffect(() => {
const ch = supabase.channel(`doc:${documentId}`, {
config: { presence: { key: currentUser.id } }
})
ch.on('presence', { event: 'sync' }, () => {
const state = ch.presenceState<PresenceState>()
const users = Object.values(state)
.flat()
.filter((p) => p.user.id !== currentUser.id)
setOtherUsers(users)
})
ch.on('broadcast', { event: 'content-update' }, ({ payload }) => {
if (payload.userId !== currentUser.id) {
setContent(payload.content)
}
})
ch.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
await ch.track({
user: currentUser,
cursor: null,
selection: null
})
}
})
setChannel(ch)
return () => {
supabase.removeChannel(ch)
}
}, [documentId, currentUser])
const handleContentChange = useCallback(
async (newContent: string) => {
setContent(newContent)
if (channel) {
await channel.send({
type: 'broadcast',
event: 'content-update',
payload: { userId: currentUser.id, content: newContent }
})
}
},
[channel, currentUser.id]
)
return (
<div className="relative">
<textarea
value={content}
onChange={(e) => handleContentChange(e.target.value)}
className="w-full h-96 p-4 border rounded-lg"
/>
<div className="mt-4 flex gap-2">
<span className="text-sm text-gray-500">Active:</span>
{otherUsers.map((presence) => (
<span
key={presence.user.id}
className="px-2 py-1 text-xs rounded-full"
style={{ backgroundColor: presence.user.color + '20', color: presence.user.color }}
>
{presence.user.name}
</span>
))}
</div>
</div>
)
}
```
## Project Structure Template
```
my-supabase-app/
├── supabase/
│ ├── functions/
│ │ ├── generate-embedding/
│ │ │ └── index.ts
│ │ └── invite-member/
│ │ └── index.ts
│ ├── migrations/
│ │ ├── 20240101000000_initial_schema.sql
│ │ └── 20240101000001_add_embeddings.sql
│ └── config.toml
├── src/
│ ├── lib/
│ │ └── supabase/
│ │ ├── client.ts
│ │ └── server.ts
│ ├── services/
│ │ ├── organization-service.ts
│ │ ├── project-service.ts
│ │ └── document-service.ts
│ ├── types/
│ │ └── database.ts
│ └── components/
│ ├── DocumentSearch.tsx
│ ├── CollaborativeEditor.tsx
│ └── FileUploader.tsx
├── .env.local
└── package.json
```
## Environment Configuration
```bash
# .env.local
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# Server-side only
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# Edge Functions secrets (set via CLI)
# supabase secrets set OPENAI_API_KEY=sk-...
```

View File

@ -0,0 +1,384 @@
---
name: auth-integration
description: Authentication patterns and JWT handling for Supabase applications
parent-skill: moai-platform-supabase
version: 1.0.0
updated: 2026-01-06
---
# Auth Integration Module
## Overview
Supabase Auth provides authentication with multiple providers, JWT-based sessions, and seamless integration with Row-Level Security policies.
## Client Setup
### Browser Client
```typescript
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
```
### Server-Side Client (Next.js App Router)
```typescript
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
import { Database } from './database.types'
export function createServerSupabase() {
const cookieStore = cookies()
return createServerClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return cookieStore.get(name)?.value
},
set(name, value, options) {
cookieStore.set({ name, value, ...options })
},
remove(name, options) {
cookieStore.set({ name, value: '', ...options })
}
}
}
)
}
```
### Middleware Client (Next.js)
```typescript
// middleware.ts
import { createServerClient, type CookieOptions } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'
export async function middleware(request: NextRequest) {
let response = NextResponse.next({
request: { headers: request.headers }
})
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return request.cookies.get(name)?.value
},
set(name: string, value: string, options: CookieOptions) {
request.cookies.set({ name, value, ...options })
response.cookies.set({ name, value, ...options })
},
remove(name: string, options: CookieOptions) {
request.cookies.set({ name, value: '', ...options })
response.cookies.set({ name, value: '', ...options })
}
}
}
)
await supabase.auth.getUser()
return response
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)']
}
```
## Authentication Methods
### Email/Password Sign Up
```typescript
async function signUp(email: string, password: string) {
const { data, error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${window.location.origin}/auth/callback`
}
})
if (error) throw error
return data
}
```
### Email/Password Sign In
```typescript
async function signIn(email: string, password: string) {
const { data, error } = await supabase.auth.signInWithPassword({
email,
password
})
if (error) throw error
return data
}
```
### OAuth Provider
```typescript
async function signInWithGoogle() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: `${window.location.origin}/auth/callback`,
queryParams: {
access_type: 'offline',
prompt: 'consent'
}
}
})
if (error) throw error
return data
}
```
### Magic Link
```typescript
async function signInWithMagicLink(email: string) {
const { data, error } = await supabase.auth.signInWithOtp({
email,
options: {
emailRedirectTo: `${window.location.origin}/auth/callback`
}
})
if (error) throw error
return data
}
```
## Session Management
### Get Current User
```typescript
async function getCurrentUser() {
const { data: { user }, error } = await supabase.auth.getUser()
if (error) throw error
return user
}
```
### Get Session
```typescript
async function getSession() {
const { data: { session }, error } = await supabase.auth.getSession()
if (error) throw error
return session
}
```
### Sign Out
```typescript
async function signOut() {
const { error } = await supabase.auth.signOut()
if (error) throw error
}
```
### Listen to Auth Changes
```typescript
useEffect(() => {
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(event, session) => {
if (event === 'SIGNED_IN') {
console.log('User signed in:', session?.user)
}
if (event === 'SIGNED_OUT') {
console.log('User signed out')
}
if (event === 'TOKEN_REFRESHED') {
console.log('Token refreshed')
}
}
)
return () => subscription.unsubscribe()
}, [])
```
## Auth Callback Handler
### Next.js App Router
```typescript
// app/auth/callback/route.ts
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
import { NextResponse, type NextRequest } from 'next/server'
export async function GET(request: NextRequest) {
const { searchParams, origin } = new URL(request.url)
const code = searchParams.get('code')
const next = searchParams.get('next') ?? '/'
if (code) {
const cookieStore = cookies()
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return cookieStore.get(name)?.value
},
set(name, value, options) {
cookieStore.set({ name, value, ...options })
},
remove(name, options) {
cookieStore.set({ name, value: '', ...options })
}
}
}
)
const { error } = await supabase.auth.exchangeCodeForSession(code)
if (!error) {
return NextResponse.redirect(`${origin}${next}`)
}
}
return NextResponse.redirect(`${origin}/auth/error`)
}
```
## Protected Routes
### Server Component Protection
```typescript
// app/dashboard/page.tsx
import { redirect } from 'next/navigation'
import { createServerSupabase } from '@/lib/supabase/server'
export default async function DashboardPage() {
const supabase = createServerSupabase()
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
redirect('/login')
}
return <Dashboard user={user} />
}
```
### Client Component Protection
```typescript
'use client'
import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'
import { supabase } from '@/lib/supabase/client'
export function useRequireAuth() {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
const router = useRouter()
useEffect(() => {
supabase.auth.getUser().then(({ data: { user } }) => {
if (!user) {
router.push('/login')
} else {
setUser(user)
}
setLoading(false)
})
}, [router])
return { user, loading }
}
```
## Custom Claims
### Setting Custom Claims (Edge Function)
```typescript
// supabase/functions/set-claims/index.ts
serve(async (req) => {
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)
const { userId, claims } = await req.json()
// Update user metadata (available in JWT)
const { error } = await supabase.auth.admin.updateUserById(userId, {
app_metadata: claims
})
if (error) throw error
return new Response(JSON.stringify({ success: true }))
})
```
### Reading Claims in RLS
```sql
-- Access claims in RLS policies
CREATE POLICY "admin_only" ON admin_data FOR ALL
USING ((auth.jwt() ->> 'role')::text = 'admin');
```
## Password Reset
```typescript
async function resetPassword(email: string) {
const { data, error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${window.location.origin}/auth/reset-password`
})
if (error) throw error
return data
}
async function updatePassword(newPassword: string) {
const { data, error } = await supabase.auth.updateUser({
password: newPassword
})
if (error) throw error
return data
}
```
## Context7 Query Examples
For latest Auth documentation:
Topic: "supabase auth signIn signUp"
Topic: "supabase ssr server client"
Topic: "auth jwt claims custom"
---
Related Modules:
- row-level-security.md - Auth integration with RLS
- typescript-patterns.md - Type-safe auth patterns
- edge-functions.md - Server-side auth verification

View File

@ -0,0 +1,371 @@
---
name: edge-functions
description: Serverless Deno functions at the edge with authentication and rate limiting
parent-skill: moai-platform-supabase
version: 1.0.0
updated: 2026-01-06
---
# Edge Functions Module
## Overview
Supabase Edge Functions are serverless functions running on the Deno runtime at the edge, providing low-latency responses globally.
## Basic Edge Function
### Function Structure
```typescript
// supabase/functions/api/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type'
}
serve(async (req) => {
// Handle CORS preflight
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
try {
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)
// Process request
const body = await req.json()
return new Response(
JSON.stringify({ success: true, data: body }),
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
)
} catch (error) {
return new Response(
JSON.stringify({ error: error.message }),
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
)
}
})
```
## Authentication
### JWT Token Verification
```typescript
serve(async (req) => {
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)
// Verify JWT token
const authHeader = req.headers.get('authorization')
if (!authHeader) {
return new Response(
JSON.stringify({ error: 'Unauthorized' }),
{ status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
)
}
const { data: { user }, error } = await supabase.auth.getUser(
authHeader.replace('Bearer ', '')
)
if (error || !user) {
return new Response(
JSON.stringify({ error: 'Invalid token' }),
{ status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
)
}
// User is authenticated, proceed with request
return new Response(
JSON.stringify({ success: true, user_id: user.id }),
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
)
})
```
### Using User Context Client
Create a client that inherits user permissions:
```typescript
serve(async (req) => {
const authHeader = req.headers.get('authorization')!
// Client with user's permissions (respects RLS)
const supabaseUser = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_ANON_KEY')!,
{ global: { headers: { Authorization: authHeader } } }
)
// This query respects RLS policies
const { data, error } = await supabaseUser
.from('projects')
.select('*')
return new Response(JSON.stringify({ data }))
})
```
## Rate Limiting
### Database-Based Rate Limiting
```sql
-- Rate limits table
CREATE TABLE rate_limits (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
identifier TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_rate_limits_lookup ON rate_limits(identifier, created_at);
```
### Rate Limit Function
```typescript
async function checkRateLimit(
supabase: SupabaseClient,
identifier: string,
limit: number,
windowSeconds: number
): Promise<boolean> {
const windowStart = new Date(Date.now() - windowSeconds * 1000).toISOString()
const { count } = await supabase
.from('rate_limits')
.select('*', { count: 'exact', head: true })
.eq('identifier', identifier)
.gte('created_at', windowStart)
if (count && count >= limit) {
return false
}
await supabase.from('rate_limits').insert({ identifier })
return true
}
```
### Usage in Edge Function
```typescript
serve(async (req) => {
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)
// Get client identifier (IP or user ID)
const identifier = req.headers.get('x-forwarded-for') || 'anonymous'
// 100 requests per minute
const allowed = await checkRateLimit(supabase, identifier, 100, 60)
if (!allowed) {
return new Response(
JSON.stringify({ error: 'Rate limit exceeded' }),
{ status: 429, headers: corsHeaders }
)
}
// Process request...
})
```
## External API Integration
### Webhook Handler
```typescript
serve(async (req) => {
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)
// Verify webhook signature
const signature = req.headers.get('x-webhook-signature')
const body = await req.text()
const expectedSignature = await crypto.subtle.digest(
'SHA-256',
new TextEncoder().encode(body + Deno.env.get('WEBHOOK_SECRET'))
)
if (!verifySignature(signature, expectedSignature)) {
return new Response('Invalid signature', { status: 401 })
}
const payload = JSON.parse(body)
// Process webhook
await supabase.from('webhook_events').insert({
type: payload.type,
data: payload.data,
processed: false
})
return new Response('OK', { status: 200 })
})
```
### External API Call
```typescript
serve(async (req) => {
const { query } = await req.json()
// Call external API
const response = await fetch('https://api.openai.com/v1/embeddings', {
method: 'POST',
headers: {
'Authorization': `Bearer ${Deno.env.get('OPENAI_API_KEY')}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'text-embedding-ada-002',
input: query
})
})
const data = await response.json()
return new Response(
JSON.stringify({ embedding: data.data[0].embedding }),
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
)
})
```
## Error Handling
### Structured Error Response
```typescript
interface ErrorResponse {
error: string
code: string
details?: unknown
}
function errorResponse(
message: string,
code: string,
status: number,
details?: unknown
): Response {
const body: ErrorResponse = { error: message, code, details }
return new Response(
JSON.stringify(body),
{ status, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
)
}
serve(async (req) => {
try {
// ... processing
} catch (error) {
if (error instanceof AuthError) {
return errorResponse('Authentication failed', 'AUTH_ERROR', 401)
}
if (error instanceof ValidationError) {
return errorResponse('Invalid input', 'VALIDATION_ERROR', 400, error.details)
}
console.error('Unexpected error:', error)
return errorResponse('Internal server error', 'INTERNAL_ERROR', 500)
}
})
```
## Deployment
### Local Development
```bash
supabase functions serve api --env-file .env.local
```
### Deploy Function
```bash
supabase functions deploy api
```
### Set Secrets
```bash
supabase secrets set OPENAI_API_KEY=sk-xxx
supabase secrets set WEBHOOK_SECRET=whsec-xxx
```
### List Functions
```bash
supabase functions list
```
## Best Practices
### Cold Start Optimization
Keep imports minimal at the top level:
```typescript
// Good: Import only what's needed
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
// Bad: Heavy imports at top level increase cold start
// import { everything } from 'large-library'
```
### Response Streaming
Stream large responses:
```typescript
serve(async (req) => {
const stream = new ReadableStream({
async start(controller) {
for (let i = 0; i < 10; i++) {
await new Promise(r => setTimeout(r, 100))
controller.enqueue(new TextEncoder().encode(`data: ${i}\n\n`))
}
controller.close()
}
})
return new Response(stream, {
headers: { 'Content-Type': 'text/event-stream' }
})
})
```
## Context7 Query Examples
For latest Edge Functions documentation:
Topic: "edge functions deno runtime"
Topic: "supabase functions deploy secrets"
Topic: "edge functions cors authentication"
---
Related Modules:
- auth-integration.md - Authentication patterns
- typescript-patterns.md - Client invocation

View File

@ -0,0 +1,231 @@
---
name: postgresql-pgvector
description: PostgreSQL 16 with pgvector extension for AI embeddings and semantic search
parent-skill: moai-platform-supabase
version: 1.0.0
updated: 2026-01-06
---
# PostgreSQL 16 + pgvector Module
## Overview
PostgreSQL 16 with pgvector extension enables AI-powered semantic search through vector embeddings storage and similarity search operations.
## Extension Setup
Enable required extensions for vector operations:
```sql
-- Enable required extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS vector;
```
## Embeddings Table Schema
Create a table optimized for storing AI embeddings:
```sql
CREATE TABLE documents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
content TEXT NOT NULL,
embedding vector(1536), -- OpenAI ada-002 dimensions
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW()
);
```
### Common Embedding Dimensions
- OpenAI ada-002: 1536 dimensions
- OpenAI text-embedding-3-small: 1536 dimensions
- OpenAI text-embedding-3-large: 3072 dimensions
- Cohere embed-english-v3.0: 1024 dimensions
- Google PaLM: 768 dimensions
## Index Strategies
### HNSW Index (Recommended)
HNSW (Hierarchical Navigable Small World) provides fast approximate nearest neighbor search:
```sql
CREATE INDEX idx_documents_embedding ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
```
Parameters:
- m: Maximum number of connections per layer (default 16, higher = more accurate but slower)
- ef_construction: Size of dynamic candidate list during construction (default 64)
### IVFFlat Index (Large Datasets)
IVFFlat is better for datasets with millions of rows:
```sql
CREATE INDEX idx_documents_ivf ON documents
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
```
Guidelines for lists parameter:
- Less than 1M rows: lists = rows / 1000
- More than 1M rows: lists = sqrt(rows)
## Distance Operations
Available distance operators:
- `<->` - Euclidean distance (L2)
- `<#>` - Negative inner product
- `<=>` - Cosine distance
For normalized embeddings, cosine distance is recommended.
## Semantic Search Function
Basic semantic search with threshold and limit:
```sql
CREATE OR REPLACE FUNCTION search_documents(
query_embedding vector(1536),
match_threshold FLOAT DEFAULT 0.8,
match_count INT DEFAULT 10
) RETURNS TABLE (id UUID, content TEXT, similarity FLOAT)
LANGUAGE plpgsql AS $$
BEGIN
RETURN QUERY SELECT d.id, d.content,
1 - (d.embedding <=> query_embedding) AS similarity
FROM documents d
WHERE 1 - (d.embedding <=> query_embedding) > match_threshold
ORDER BY d.embedding <=> query_embedding
LIMIT match_count;
END; $$;
```
### Usage Example
```sql
SELECT * FROM search_documents(
'[0.1, 0.2, ...]'::vector(1536),
0.75,
20
);
```
## Hybrid Search
Combine vector similarity with full-text search for better results:
```sql
CREATE OR REPLACE FUNCTION hybrid_search(
query_text TEXT,
query_embedding vector(1536),
match_count INT DEFAULT 10,
full_text_weight FLOAT DEFAULT 0.3,
semantic_weight FLOAT DEFAULT 0.7
) RETURNS TABLE (id UUID, content TEXT, score FLOAT) AS $$
BEGIN
RETURN QUERY
WITH semantic AS (
SELECT e.id, e.content, 1 - (e.embedding <=> query_embedding) AS similarity
FROM documents e ORDER BY e.embedding <=> query_embedding LIMIT match_count * 2
),
full_text AS (
SELECT e.id, e.content,
ts_rank(to_tsvector('english', e.content), plainto_tsquery('english', query_text)) AS rank
FROM documents e
WHERE to_tsvector('english', e.content) @@ plainto_tsquery('english', query_text)
LIMIT match_count * 2
)
SELECT COALESCE(s.id, f.id), COALESCE(s.content, f.content),
(COALESCE(s.similarity, 0) * semantic_weight + COALESCE(f.rank, 0) * full_text_weight)
FROM semantic s FULL OUTER JOIN full_text f ON s.id = f.id
ORDER BY 3 DESC LIMIT match_count;
END; $$ LANGUAGE plpgsql;
```
## Full-Text Search Index
Add GIN index for efficient full-text search:
```sql
CREATE INDEX idx_documents_content_fts ON documents
USING gin(to_tsvector('english', content));
```
## Performance Optimization
### Query Performance
Set appropriate ef_search for HNSW queries:
```sql
SET hnsw.ef_search = 100; -- Higher = more accurate, slower
```
### Batch Insertions
Use COPY or multi-row INSERT for bulk embeddings:
```sql
INSERT INTO documents (content, embedding, metadata)
VALUES
('Content 1', '[...]'::vector(1536), '{"source": "doc1"}'),
('Content 2', '[...]'::vector(1536), '{"source": "doc2"}'),
('Content 3', '[...]'::vector(1536), '{"source": "doc3"}');
```
### Index Maintenance
Reindex after large bulk insertions:
```sql
REINDEX INDEX CONCURRENTLY idx_documents_embedding;
```
## Metadata Filtering
Combine vector search with JSONB metadata filters:
```sql
CREATE OR REPLACE FUNCTION search_with_filters(
query_embedding vector(1536),
filter_metadata JSONB,
match_count INT DEFAULT 10
) RETURNS TABLE (id UUID, content TEXT, similarity FLOAT) AS $$
BEGIN
RETURN QUERY SELECT d.id, d.content,
1 - (d.embedding <=> query_embedding) AS similarity
FROM documents d
WHERE d.metadata @> filter_metadata
ORDER BY d.embedding <=> query_embedding
LIMIT match_count;
END; $$;
```
### Usage with Filters
```sql
SELECT * FROM search_with_filters(
'[0.1, 0.2, ...]'::vector(1536),
'{"category": "technical", "language": "en"}'::jsonb,
10
);
```
## Context7 Query Examples
For latest pgvector documentation:
Topic: "pgvector extension indexes hnsw ivfflat"
Topic: "vector similarity search operators"
Topic: "postgresql full-text search tsvector"
---
Related Modules:
- row-level-security.md - Secure vector data access
- typescript-patterns.md - Client-side search implementation

View File

@ -0,0 +1,354 @@
---
name: realtime-presence
description: Real-time subscriptions and presence tracking for collaborative features
parent-skill: moai-platform-supabase
version: 1.0.0
updated: 2026-01-06
---
# Real-time and Presence Module
## Overview
Supabase provides real-time capabilities through Postgres Changes (database change notifications) and Presence (user state tracking) for building collaborative applications.
## Postgres Changes Subscription
### Basic Setup
Subscribe to all changes on a table:
```typescript
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
const channel = supabase.channel('db-changes')
.on('postgres_changes',
{ event: '*', schema: 'public', table: 'messages' },
(payload) => console.log('Change:', payload)
)
.subscribe()
```
### Event Types
Available events:
- `INSERT` - New row added
- `UPDATE` - Row modified
- `DELETE` - Row removed
- `*` - All events
### Filtered Subscriptions
Filter changes by specific conditions:
```typescript
supabase.channel('project-updates')
.on('postgres_changes',
{
event: 'UPDATE',
schema: 'public',
table: 'projects',
filter: `id=eq.${projectId}`
},
(payload) => handleProjectUpdate(payload.new)
)
.subscribe()
```
### Multiple Tables
Subscribe to multiple tables on one channel:
```typescript
const channel = supabase.channel('app-changes')
.on('postgres_changes',
{ event: '*', schema: 'public', table: 'tasks' },
handleTaskChange
)
.on('postgres_changes',
{ event: '*', schema: 'public', table: 'comments' },
handleCommentChange
)
.subscribe()
```
## Presence Tracking
### Presence State Interface
```typescript
interface PresenceState {
user_id: string
online_at: string
typing?: boolean
cursor?: { x: number; y: number }
}
```
### Channel Setup with Presence
```typescript
const channel = supabase.channel('room:collaborative-doc', {
config: { presence: { key: userId } }
})
channel
.on('presence', { event: 'sync' }, () => {
const state = channel.presenceState<PresenceState>()
console.log('Online users:', Object.keys(state))
})
.on('presence', { event: 'join' }, ({ key, newPresences }) => {
console.log('User joined:', key, newPresences)
})
.on('presence', { event: 'leave' }, ({ key, leftPresences }) => {
console.log('User left:', key, leftPresences)
})
.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
await channel.track({
user_id: userId,
online_at: new Date().toISOString()
})
}
})
```
### Update Presence State
Update user presence in real-time:
```typescript
// Track typing status
await channel.track({ typing: true })
// Track cursor position
await channel.track({ cursor: { x: 100, y: 200 } })
// Clear typing after timeout
setTimeout(async () => {
await channel.track({ typing: false })
}, 1000)
```
## Collaborative Features
### Collaborative Cursors
```typescript
interface CursorState {
user_id: string
user_name: string
cursor: { x: number; y: number }
color: string
}
function setupCollaborativeCursors(documentId: string, userId: string, userName: string) {
const channel = supabase.channel(`cursors:${documentId}`, {
config: { presence: { key: userId } }
})
const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7']
const userColor = colors[Math.abs(userId.hashCode()) % colors.length]
channel
.on('presence', { event: 'sync' }, () => {
const state = channel.presenceState<CursorState>()
renderCursors(Object.values(state).flat())
})
.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
await channel.track({
user_id: userId,
user_name: userName,
cursor: { x: 0, y: 0 },
color: userColor
})
}
})
// Track mouse movement
document.addEventListener('mousemove', async (e) => {
await channel.track({
user_id: userId,
user_name: userName,
cursor: { x: e.clientX, y: e.clientY },
color: userColor
})
})
return channel
}
```
### Live Editing Indicators
```typescript
interface EditingState {
user_id: string
user_name: string
editing_field: string | null
}
function setupFieldLocking(formId: string) {
const channel = supabase.channel(`form:${formId}`, {
config: { presence: { key: currentUserId } }
})
channel
.on('presence', { event: 'sync' }, () => {
const state = channel.presenceState<EditingState>()
updateFieldLocks(Object.values(state).flat())
})
.subscribe()
return {
startEditing: async (fieldName: string) => {
await channel.track({
user_id: currentUserId,
user_name: currentUserName,
editing_field: fieldName
})
},
stopEditing: async () => {
await channel.track({
user_id: currentUserId,
user_name: currentUserName,
editing_field: null
})
}
}
}
```
## Broadcast Messages
Send arbitrary messages to channel subscribers:
```typescript
const channel = supabase.channel('room:chat')
// Subscribe to broadcasts
channel
.on('broadcast', { event: 'message' }, ({ payload }) => {
console.log('Received:', payload)
})
.subscribe()
// Send broadcast
await channel.send({
type: 'broadcast',
event: 'message',
payload: { text: 'Hello everyone!', sender: userId }
})
```
## Subscription Management
### Unsubscribe
```typescript
// Unsubscribe from specific channel
await supabase.removeChannel(channel)
// Unsubscribe from all channels
await supabase.removeAllChannels()
```
### Subscription Status
```typescript
channel.subscribe((status) => {
switch (status) {
case 'SUBSCRIBED':
console.log('Connected to channel')
break
case 'CLOSED':
console.log('Channel closed')
break
case 'CHANNEL_ERROR':
console.log('Channel error')
break
case 'TIMED_OUT':
console.log('Connection timed out')
break
}
})
```
## React Integration
### Custom Hook for Presence
```typescript
import { useEffect, useState } from 'react'
import { supabase } from './supabase/client'
export function usePresence<T>(channelName: string, userId: string, initialState: T) {
const [presences, setPresences] = useState<Record<string, T[]>>({})
useEffect(() => {
const channel = supabase.channel(channelName, {
config: { presence: { key: userId } }
})
channel
.on('presence', { event: 'sync' }, () => {
setPresences(channel.presenceState<T>())
})
.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
await channel.track(initialState)
}
})
return () => {
supabase.removeChannel(channel)
}
}, [channelName, userId])
const updatePresence = async (state: Partial<T>) => {
const channel = supabase.getChannels().find(c => c.topic === channelName)
if (channel) {
await channel.track({ ...initialState, ...state } as T)
}
}
return { presences, updatePresence }
}
```
### Usage
```typescript
function CollaborativeEditor({ documentId, userId }) {
const { presences, updatePresence } = usePresence(
`doc:${documentId}`,
userId,
{ user_id: userId, typing: false, cursor: null }
)
return (
<div>
{Object.values(presences).flat().map(p => (
<Cursor key={p.user_id} position={p.cursor} />
))}
</div>
)
}
```
## Context7 Query Examples
For latest real-time documentation:
Topic: "realtime postgres_changes subscription"
Topic: "presence tracking channel"
Topic: "broadcast messages supabase"
---
Related Modules:
- typescript-patterns.md - Client architecture
- auth-integration.md - Authenticated subscriptions

View File

@ -0,0 +1,286 @@
---
name: row-level-security
description: RLS policies for multi-tenant data isolation and access control
parent-skill: moai-platform-supabase
version: 1.0.0
updated: 2026-01-06
---
# Row-Level Security (RLS) Module
## Overview
Row-Level Security provides automatic data isolation at the database level, ensuring users can only access data they are authorized to see.
## Basic Setup
Enable RLS on a table:
```sql
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
```
## Policy Types
RLS policies can be created for specific operations:
- SELECT: Controls read access
- INSERT: Controls creation
- UPDATE: Controls modification
- DELETE: Controls removal
- ALL: Applies to all operations
## Basic Tenant Isolation
### JWT-Based Tenant Isolation
Extract tenant ID from JWT claims:
```sql
CREATE POLICY "tenant_isolation" ON projects FOR ALL
USING (tenant_id = (auth.jwt() ->> 'tenant_id')::UUID);
```
### Owner-Based Access
Restrict access to resource owners:
```sql
CREATE POLICY "owner_access" ON projects FOR ALL
USING (owner_id = auth.uid());
```
## Hierarchical Access Patterns
### Organization Membership
Allow access based on organization membership:
```sql
CREATE POLICY "org_member_select" ON organizations FOR SELECT
USING (id IN (SELECT org_id FROM org_members WHERE user_id = auth.uid()));
```
### Role-Based Modification
Restrict modifications to specific roles:
```sql
CREATE POLICY "org_admin_modify" ON organizations FOR UPDATE
USING (id IN (
SELECT org_id FROM org_members
WHERE user_id = auth.uid() AND role IN ('owner', 'admin')
));
```
### Cascading Project Access
Grant project access through organization membership:
```sql
CREATE POLICY "project_access" ON projects FOR ALL
USING (org_id IN (SELECT org_id FROM org_members WHERE user_id = auth.uid()));
```
## Service Role Bypass
Allow service role to bypass RLS for server-side operations:
```sql
CREATE POLICY "service_bypass" ON organizations FOR ALL TO service_role USING (true);
```
## Multi-Tenant SaaS Schema
### Complete Schema Setup
```sql
-- Organizations (tenants)
CREATE TABLE organizations (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name TEXT NOT NULL,
slug TEXT UNIQUE NOT NULL,
plan TEXT DEFAULT 'free' CHECK (plan IN ('free', 'pro', 'enterprise')),
settings JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Organization members with roles
CREATE TABLE organization_members (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
user_id UUID NOT NULL,
role TEXT NOT NULL CHECK (role IN ('owner', 'admin', 'member', 'viewer')),
joined_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(organization_id, user_id)
);
-- Projects within organizations
CREATE TABLE projects (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
name TEXT NOT NULL,
owner_id UUID NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
```
### Enable RLS on All Tables
```sql
ALTER TABLE organizations ENABLE ROW LEVEL SECURITY;
ALTER TABLE organization_members ENABLE ROW LEVEL SECURITY;
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
```
### Comprehensive RLS Policies
```sql
-- Organization read access
CREATE POLICY "org_member_select" ON organizations FOR SELECT
USING (id IN (SELECT organization_id FROM organization_members WHERE user_id = auth.uid()));
-- Organization admin update
CREATE POLICY "org_admin_update" ON organizations FOR UPDATE
USING (id IN (SELECT organization_id FROM organization_members
WHERE user_id = auth.uid() AND role IN ('owner', 'admin')));
-- Project member access
CREATE POLICY "project_member_access" ON projects FOR ALL
USING (organization_id IN (SELECT organization_id FROM organization_members WHERE user_id = auth.uid()));
-- Member management (admin only)
CREATE POLICY "member_admin_manage" ON organization_members FOR ALL
USING (organization_id IN (SELECT organization_id FROM organization_members
WHERE user_id = auth.uid() AND role IN ('owner', 'admin')));
```
## Helper Functions
### Check Organization Membership
```sql
CREATE OR REPLACE FUNCTION is_org_member(org_id UUID)
RETURNS BOOLEAN AS $$
BEGIN
RETURN EXISTS (
SELECT 1 FROM organization_members
WHERE organization_id = org_id AND user_id = auth.uid()
);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
```
### Check Organization Role
```sql
CREATE OR REPLACE FUNCTION has_org_role(org_id UUID, required_roles TEXT[])
RETURNS BOOLEAN AS $$
BEGIN
RETURN EXISTS (
SELECT 1 FROM organization_members
WHERE organization_id = org_id
AND user_id = auth.uid()
AND role = ANY(required_roles)
);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
```
### Usage in Policies
```sql
CREATE POLICY "project_admin_delete" ON projects FOR DELETE
USING (has_org_role(organization_id, ARRAY['owner', 'admin']));
```
## Performance Optimization
### Index for RLS Queries
Create indexes on foreign keys used in RLS policies:
```sql
CREATE INDEX idx_org_members_user ON organization_members(user_id);
CREATE INDEX idx_org_members_org ON organization_members(organization_id);
CREATE INDEX idx_projects_org ON projects(organization_id);
```
### Materialized View for Complex Policies
For complex permission checks, use materialized views:
```sql
CREATE MATERIALIZED VIEW user_accessible_projects AS
SELECT p.id as project_id, om.user_id, om.role
FROM projects p
JOIN organization_members om ON p.organization_id = om.organization_id;
CREATE INDEX idx_uap_user ON user_accessible_projects(user_id);
REFRESH MATERIALIZED VIEW CONCURRENTLY user_accessible_projects;
```
## Testing RLS Policies
### Test as Authenticated User
```sql
SET request.jwt.claim.sub = 'user-uuid-here';
SET request.jwt.claims = '{"role": "authenticated"}';
SELECT * FROM projects; -- Returns only accessible projects
```
### Verify Policy Restrictions
```sql
-- Should fail if not a member
INSERT INTO projects (organization_id, name, owner_id)
VALUES ('non-member-org-id', 'Test', auth.uid());
```
## Common Patterns
### Public Read, Owner Write
```sql
CREATE POLICY "public_read" ON posts FOR SELECT USING (true);
CREATE POLICY "owner_write" ON posts FOR INSERT WITH CHECK (author_id = auth.uid());
CREATE POLICY "owner_update" ON posts FOR UPDATE USING (author_id = auth.uid());
CREATE POLICY "owner_delete" ON posts FOR DELETE USING (author_id = auth.uid());
```
### Draft vs Published
```sql
CREATE POLICY "published_read" ON articles FOR SELECT
USING (status = 'published' OR author_id = auth.uid());
```
### Time-Based Access
```sql
CREATE POLICY "active_subscription" ON premium_content FOR SELECT
USING (
EXISTS (
SELECT 1 FROM subscriptions
WHERE user_id = auth.uid()
AND expires_at > NOW()
)
);
```
## Context7 Query Examples
For latest RLS documentation:
Topic: "row level security policies supabase"
Topic: "auth.uid auth.jwt functions"
Topic: "rls performance optimization"
---
Related Modules:
- auth-integration.md - Authentication patterns
- typescript-patterns.md - Client-side access patterns

View File

@ -0,0 +1,319 @@
---
name: storage-cdn
description: File storage with image transformations and CDN delivery
parent-skill: moai-platform-supabase
version: 1.0.0
updated: 2026-01-06
---
# Storage and CDN Module
## Overview
Supabase Storage provides file storage with automatic image transformations, CDN delivery, and fine-grained access control through storage policies.
## Basic Upload
### Upload File
```typescript
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
async function uploadFile(file: File, bucket: string, path: string) {
const { data, error } = await supabase.storage
.from(bucket)
.upload(path, file, {
cacheControl: '3600',
upsert: false
})
if (error) throw error
return data.path
}
```
### Upload with User Context
```typescript
async function uploadUserFile(file: File, userId: string) {
const fileName = `${userId}/${Date.now()}-${file.name}`
const { data, error } = await supabase.storage
.from('user-files')
.upload(fileName, file, {
cacheControl: '3600',
upsert: false
})
if (error) throw error
return data
}
```
## Image Transformations
### Get Transformed URL
```typescript
async function uploadImage(file: File, userId: string) {
const fileName = `${userId}/${Date.now()}-${file.name}`
const { data, error } = await supabase.storage
.from('images')
.upload(fileName, file, { cacheControl: '3600', upsert: false })
if (error) throw error
// Get original URL
const { data: { publicUrl } } = supabase.storage
.from('images')
.getPublicUrl(fileName)
// Get resized URL
const { data: { publicUrl: resizedUrl } } = supabase.storage
.from('images')
.getPublicUrl(fileName, {
transform: { width: 800, height: 600, resize: 'contain' }
})
// Get thumbnail URL
const { data: { publicUrl: thumbnailUrl } } = supabase.storage
.from('images')
.getPublicUrl(fileName, {
transform: { width: 200, height: 200, resize: 'cover' }
})
return { originalPath: data.path, publicUrl, resizedUrl, thumbnailUrl }
}
```
### Transform Options
Available transformation parameters:
- width: Target width in pixels
- height: Target height in pixels
- resize: 'cover' | 'contain' | 'fill'
- format: 'origin' | 'avif' | 'webp'
- quality: 1-100
### Example Transforms
```typescript
// Square thumbnail with crop
const thumbnail = supabase.storage
.from('images')
.getPublicUrl(path, {
transform: { width: 150, height: 150, resize: 'cover' }
})
// WebP format for smaller size
const webp = supabase.storage
.from('images')
.getPublicUrl(path, {
transform: { width: 800, format: 'webp', quality: 80 }
})
// Responsive image
const responsive = supabase.storage
.from('images')
.getPublicUrl(path, {
transform: { width: 400, resize: 'contain' }
})
```
## Bucket Management
### Create Bucket
```sql
-- Via SQL
INSERT INTO storage.buckets (id, name, public)
VALUES ('images', 'images', true);
```
### Bucket Policies
```sql
-- Allow authenticated users to upload to their folder
CREATE POLICY "User upload" ON storage.objects
FOR INSERT
TO authenticated
WITH CHECK (bucket_id = 'user-files' AND (storage.foldername(name))[1] = auth.uid()::text);
-- Allow public read on images bucket
CREATE POLICY "Public read" ON storage.objects
FOR SELECT
TO public
USING (bucket_id = 'images');
-- Allow users to delete their own files
CREATE POLICY "User delete" ON storage.objects
FOR DELETE
TO authenticated
USING (bucket_id = 'user-files' AND (storage.foldername(name))[1] = auth.uid()::text);
```
## Download Files
### Get Signed URL
For private buckets:
```typescript
async function getSignedUrl(bucket: string, path: string, expiresIn: number = 3600) {
const { data, error } = await supabase.storage
.from(bucket)
.createSignedUrl(path, expiresIn)
if (error) throw error
return data.signedUrl
}
```
### Download File
```typescript
async function downloadFile(bucket: string, path: string) {
const { data, error } = await supabase.storage
.from(bucket)
.download(path)
if (error) throw error
return data // Blob
}
```
## File Management
### List Files
```typescript
async function listFiles(bucket: string, folder: string) {
const { data, error } = await supabase.storage
.from(bucket)
.list(folder, {
limit: 100,
offset: 0,
sortBy: { column: 'created_at', order: 'desc' }
})
if (error) throw error
return data
}
```
### Delete File
```typescript
async function deleteFile(bucket: string, paths: string[]) {
const { data, error } = await supabase.storage
.from(bucket)
.remove(paths)
if (error) throw error
return data
}
```
### Move/Rename File
```typescript
async function moveFile(bucket: string, fromPath: string, toPath: string) {
const { data, error } = await supabase.storage
.from(bucket)
.move(fromPath, toPath)
if (error) throw error
return data
}
```
## React Integration
### Upload Component
```typescript
function FileUploader({ bucket, onUpload }: Props) {
const [uploading, setUploading] = useState(false)
async function handleUpload(event: React.ChangeEvent<HTMLInputElement>) {
const file = event.target.files?.[0]
if (!file) return
setUploading(true)
try {
const path = `${Date.now()}-${file.name}`
const { error } = await supabase.storage
.from(bucket)
.upload(path, file)
if (error) throw error
onUpload(path)
} finally {
setUploading(false)
}
}
return (
<input
type="file"
onChange={handleUpload}
disabled={uploading}
/>
)
}
```
### Image with Fallback
```typescript
function StorageImage({ path, bucket, width, height, fallback }: Props) {
const { data: { publicUrl } } = supabase.storage
.from(bucket)
.getPublicUrl(path, {
transform: { width, height, resize: 'cover' }
})
return (
<img
src={publicUrl}
alt=""
onError={(e) => { e.currentTarget.src = fallback }}
width={width}
height={height}
/>
)
}
```
## Best Practices
File Organization:
- Use user ID as folder prefix for user content
- Include timestamp in filenames to prevent collisions
- Use consistent naming conventions
Performance:
- Set appropriate cache-control headers
- Use image transformations instead of storing multiple sizes
- Leverage CDN for global delivery
Security:
- Always use RLS-style policies for storage
- Use signed URLs for private content
- Validate file types before upload
## Context7 Query Examples
For latest Storage documentation:
Topic: "supabase storage upload download"
Topic: "storage image transformations"
Topic: "storage bucket policies"
---
Related Modules:
- row-level-security.md - Storage access policies
- typescript-patterns.md - Client patterns

View File

@ -0,0 +1,453 @@
---
name: typescript-patterns
description: TypeScript client patterns and service layer architecture for Supabase
parent-skill: moai-platform-supabase
version: 1.0.0
updated: 2026-01-06
---
# TypeScript Patterns Module
## Overview
Type-safe Supabase client patterns for building maintainable full-stack applications with TypeScript.
## Type Generation
### Generate Types from Database
```bash
supabase gen types typescript --project-id your-project-id > database.types.ts
```
### Database Types Structure
```typescript
// database.types.ts
export type Database = {
public: {
Tables: {
projects: {
Row: {
id: string
name: string
organization_id: string
owner_id: string
created_at: string
}
Insert: {
id?: string
name: string
organization_id: string
owner_id: string
created_at?: string
}
Update: {
id?: string
name?: string
organization_id?: string
owner_id?: string
created_at?: string
}
}
// ... other tables
}
Functions: {
search_documents: {
Args: {
query_embedding: number[]
match_threshold: number
match_count: number
}
Returns: { id: string; content: string; similarity: number }[]
}
}
}
}
```
## Client Configuration
### Browser Client with Types
```typescript
import { createClient } from '@supabase/supabase-js'
import { Database } from './database.types'
export const supabase = createClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
```
### Server Client (Next.js App Router)
```typescript
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
import { Database } from './database.types'
export function createServerSupabase() {
const cookieStore = cookies()
return createServerClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return cookieStore.get(name)?.value
},
set(name, value, options) {
cookieStore.set({ name, value, ...options })
},
remove(name, options) {
cookieStore.set({ name, value: '', ...options })
}
}
}
)
}
```
## Service Layer Pattern
### Base Service
```typescript
import { supabase } from './supabase/client'
import { Database } from './database.types'
type Tables = Database['public']['Tables']
export abstract class BaseService<T extends keyof Tables> {
constructor(protected tableName: T) {}
async findAll() {
const { data, error } = await supabase
.from(this.tableName)
.select('*')
if (error) throw error
return data as Tables[T]['Row'][]
}
async findById(id: string) {
const { data, error } = await supabase
.from(this.tableName)
.select('*')
.eq('id', id)
.single()
if (error) throw error
return data as Tables[T]['Row']
}
async create(item: Tables[T]['Insert']) {
const { data, error } = await supabase
.from(this.tableName)
.insert(item)
.select()
.single()
if (error) throw error
return data as Tables[T]['Row']
}
async update(id: string, item: Tables[T]['Update']) {
const { data, error } = await supabase
.from(this.tableName)
.update(item)
.eq('id', id)
.select()
.single()
if (error) throw error
return data as Tables[T]['Row']
}
async delete(id: string) {
const { error } = await supabase
.from(this.tableName)
.delete()
.eq('id', id)
if (error) throw error
}
}
```
### Document Service with Embeddings
```typescript
import { supabase } from './supabase/client'
export class DocumentService {
async create(projectId: string, title: string, content: string) {
const { data: { user } } = await supabase.auth.getUser()
const { data, error } = await supabase
.from('documents')
.insert({
project_id: projectId,
title,
content,
created_by: user!.id
})
.select()
.single()
if (error) throw error
// Generate embedding asynchronously
await supabase.functions.invoke('generate-embedding', {
body: { documentId: data.id, content }
})
return data
}
async semanticSearch(projectId: string, query: string) {
// Get embedding for query
const { data: embeddingData } = await supabase.functions.invoke(
'get-embedding',
{ body: { text: query } }
)
// Search using RPC
const { data, error } = await supabase.rpc('search_documents', {
p_project_id: projectId,
p_query_embedding: embeddingData.embedding,
p_match_threshold: 0.7,
p_match_count: 10
})
if (error) throw error
return data
}
async findByProject(projectId: string) {
const { data, error } = await supabase
.from('documents')
.select('*, created_by_user:profiles!created_by(*)')
.eq('project_id', projectId)
.order('created_at', { ascending: false })
if (error) throw error
return data
}
subscribeToChanges(projectId: string, callback: (payload: any) => void) {
return supabase.channel(`documents:${projectId}`)
.on('postgres_changes',
{
event: '*',
schema: 'public',
table: 'documents',
filter: `project_id=eq.${projectId}`
},
callback
)
.subscribe()
}
}
export const documentService = new DocumentService()
```
## React Query Integration
### Query Keys
```typescript
export const queryKeys = {
projects: {
all: ['projects'] as const,
list: (filters?: ProjectFilters) => [...queryKeys.projects.all, 'list', filters] as const,
detail: (id: string) => [...queryKeys.projects.all, 'detail', id] as const
},
documents: {
all: ['documents'] as const,
list: (projectId: string) => [...queryKeys.documents.all, 'list', projectId] as const,
search: (projectId: string, query: string) =>
[...queryKeys.documents.all, 'search', projectId, query] as const
}
}
```
### Custom Hooks
```typescript
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { documentService } from '@/services/document-service'
export function useDocuments(projectId: string) {
return useQuery({
queryKey: queryKeys.documents.list(projectId),
queryFn: () => documentService.findByProject(projectId)
})
}
export function useSemanticSearch(projectId: string, query: string) {
return useQuery({
queryKey: queryKeys.documents.search(projectId, query),
queryFn: () => documentService.semanticSearch(projectId, query),
enabled: query.length > 2
})
}
export function useCreateDocument(projectId: string) {
const queryClient = useQueryClient()
return useMutation({
mutationFn: ({ title, content }: { title: string; content: string }) =>
documentService.create(projectId, title, content),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: queryKeys.documents.list(projectId)
})
}
})
}
```
## Real-time with React
### Subscription Hook
```typescript
import { useEffect } from 'react'
import { useQueryClient } from '@tanstack/react-query'
import { supabase } from '@/lib/supabase/client'
export function useRealtimeDocuments(projectId: string) {
const queryClient = useQueryClient()
useEffect(() => {
const channel = supabase
.channel(`documents:${projectId}`)
.on('postgres_changes',
{
event: '*',
schema: 'public',
table: 'documents',
filter: `project_id=eq.${projectId}`
},
() => {
queryClient.invalidateQueries({
queryKey: queryKeys.documents.list(projectId)
})
}
)
.subscribe()
return () => {
supabase.removeChannel(channel)
}
}, [projectId, queryClient])
}
```
### Optimistic Updates
```typescript
export function useUpdateDocument() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async ({ id, updates }: { id: string; updates: Partial<Document> }) => {
const { data, error } = await supabase
.from('documents')
.update(updates)
.eq('id', id)
.select()
.single()
if (error) throw error
return data
},
onMutate: async ({ id, updates }) => {
await queryClient.cancelQueries({ queryKey: ['documents'] })
const previousDocuments = queryClient.getQueryData(['documents'])
queryClient.setQueryData(['documents'], (old: Document[]) =>
old.map(doc => doc.id === id ? { ...doc, ...updates } : doc)
)
return { previousDocuments }
},
onError: (err, variables, context) => {
if (context?.previousDocuments) {
queryClient.setQueryData(['documents'], context.previousDocuments)
}
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['documents'] })
}
})
}
```
## Error Handling
### Custom Error Types
```typescript
export class SupabaseError extends Error {
constructor(
message: string,
public code: string,
public details?: unknown
) {
super(message)
this.name = 'SupabaseError'
}
}
export function handleSupabaseError(error: PostgrestError): never {
switch (error.code) {
case '23505':
throw new SupabaseError('Resource already exists', 'DUPLICATE', error)
case '23503':
throw new SupabaseError('Referenced resource not found', 'NOT_FOUND', error)
case 'PGRST116':
throw new SupabaseError('Resource not found', 'NOT_FOUND', error)
default:
throw new SupabaseError(error.message, error.code, error)
}
}
```
### Service with Error Handling
```typescript
async findById(id: string) {
const { data, error } = await supabase
.from(this.tableName)
.select('*')
.eq('id', id)
.single()
if (error) {
handleSupabaseError(error)
}
return data
}
```
## Context7 Query Examples
For latest client documentation:
Topic: "supabase-js typescript client"
Topic: "supabase ssr next.js app router"
Topic: "supabase realtime subscription"
---
Related Modules:
- auth-integration.md - Auth patterns
- realtime-presence.md - Real-time subscriptions
- postgresql-pgvector.md - Database operations

View File

@ -0,0 +1,284 @@
---
name: supabase-reference
description: API reference summary for Supabase platform
parent-skill: moai-platform-supabase
version: 1.0.0
updated: 2026-01-06
---
# Supabase API Reference
## Client Methods
### Database Operations
```typescript
// Select
const { data, error } = await supabase.from('table').select('*')
const { data } = await supabase.from('table').select('id, name, relation(*)').eq('id', 1)
// Insert
const { data, error } = await supabase.from('table').insert({ column: 'value' }).select()
const { data } = await supabase.from('table').insert([...items]).select()
// Update
const { data, error } = await supabase.from('table').update({ column: 'value' }).eq('id', 1).select()
// Upsert
const { data, error } = await supabase.from('table').upsert({ id: 1, column: 'value' }).select()
// Delete
const { error } = await supabase.from('table').delete().eq('id', 1)
```
### Query Filters
```typescript
.eq('column', 'value') // Equal
.neq('column', 'value') // Not equal
.gt('column', 0) // Greater than
.gte('column', 0) // Greater than or equal
.lt('column', 100) // Less than
.lte('column', 100) // Less than or equal
.like('column', '%pattern%') // LIKE
.ilike('column', '%pattern%') // ILIKE (case insensitive)
.is('column', null) // IS NULL
.in('column', ['a', 'b']) // IN
.contains('array_col', ['a']) // Array contains
.containedBy('col', ['a','b']) // Array contained by
.range('col', '[1,10)') // Range
.textSearch('col', 'query') // Full-text search
.filter('col', 'op', 'val') // Generic filter
```
### Query Modifiers
```typescript
.order('column', { ascending: false })
.limit(10)
.range(0, 9) // Pagination
.single() // Expect exactly one row
.maybeSingle() // Expect zero or one row
.count('exact', { head: true }) // Count only
```
### RPC (Remote Procedure Call)
```typescript
const { data, error } = await supabase.rpc('function_name', {
arg1: 'value1',
arg2: 'value2'
})
```
## Auth Methods
```typescript
// Sign up
await supabase.auth.signUp({ email, password })
// Sign in
await supabase.auth.signInWithPassword({ email, password })
await supabase.auth.signInWithOAuth({ provider: 'google' })
await supabase.auth.signInWithOtp({ email })
// Session
await supabase.auth.getUser()
await supabase.auth.getSession()
await supabase.auth.refreshSession()
// Sign out
await supabase.auth.signOut()
// Password
await supabase.auth.resetPasswordForEmail(email)
await supabase.auth.updateUser({ password: newPassword })
// Listener
supabase.auth.onAuthStateChange((event, session) => {})
```
## Real-time Methods
```typescript
// Subscribe to changes
const channel = supabase.channel('channel-name')
.on('postgres_changes',
{ event: '*', schema: 'public', table: 'table_name' },
(payload) => {}
)
.subscribe()
// Presence
channel.on('presence', { event: 'sync' }, () => {
const state = channel.presenceState()
})
await channel.track({ user_id: 'id', online_at: new Date() })
// Broadcast
await channel.send({ type: 'broadcast', event: 'name', payload: {} })
channel.on('broadcast', { event: 'name' }, ({ payload }) => {})
// Unsubscribe
await supabase.removeChannel(channel)
await supabase.removeAllChannels()
```
## Storage Methods
```typescript
// Upload
await supabase.storage.from('bucket').upload('path/file.ext', file, { cacheControl: '3600' })
// Download
await supabase.storage.from('bucket').download('path/file.ext')
// Get URL
supabase.storage.from('bucket').getPublicUrl('path/file.ext', {
transform: { width: 800, height: 600, resize: 'cover' }
})
// Signed URL
await supabase.storage.from('bucket').createSignedUrl('path/file.ext', 3600)
// List
await supabase.storage.from('bucket').list('folder', { limit: 100 })
// Delete
await supabase.storage.from('bucket').remove(['path/file.ext'])
// Move
await supabase.storage.from('bucket').move('old/path', 'new/path')
```
## Edge Functions
```typescript
// Invoke
const { data, error } = await supabase.functions.invoke('function-name', {
body: { key: 'value' },
headers: { 'Custom-Header': 'value' }
})
```
## SQL Quick Reference
### pgvector
```sql
CREATE EXTENSION vector;
-- Create table with vector column
CREATE TABLE items (
id UUID PRIMARY KEY,
embedding vector(1536)
);
-- HNSW index (recommended)
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops);
-- IVFFlat index (large datasets)
CREATE INDEX ON items USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
-- Distance operators
<-> -- Euclidean distance
<=> -- Cosine distance
<#> -- Negative inner product
```
### Row-Level Security
```sql
-- Enable RLS
ALTER TABLE table_name ENABLE ROW LEVEL SECURITY;
-- Create policy
CREATE POLICY "policy_name" ON table_name
FOR SELECT | INSERT | UPDATE | DELETE | ALL
TO role_name
USING (expression)
WITH CHECK (expression);
-- Auth functions
auth.uid() -- Current user ID
auth.jwt() ->> 'claim' -- JWT claim value
auth.role() -- Current role
```
### Useful Functions
```sql
gen_random_uuid() -- Generate UUID
uuid_generate_v4() -- Generate UUID (requires uuid-ossp)
NOW() -- Current timestamp
CURRENT_TIMESTAMP -- Current timestamp
to_tsvector('english', text) -- Full-text search vector
plainto_tsquery('query') -- Full-text search query
ts_rank(vector, query) -- Full-text search rank
```
## Environment Variables
```bash
# Public (safe for client)
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# Private (server-only)
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
SUPABASE_JWT_SECRET=your-jwt-secret
```
## CLI Commands
```bash
# Project
supabase init
supabase start
supabase stop
supabase status
# Database
supabase db diff
supabase db push
supabase db reset
supabase migration new migration_name
supabase migration list
# Types
supabase gen types typescript --project-id xxx > database.types.ts
# Functions
supabase functions new function-name
supabase functions serve function-name
supabase functions deploy function-name
supabase functions list
# Secrets
supabase secrets set KEY=value
supabase secrets list
```
## Context7 Documentation Access
For detailed API documentation, use Context7 MCP tools:
```
Step 1: Resolve library ID
mcp__context7__resolve-library-id with query "supabase"
Step 2: Fetch documentation
mcp__context7__get-library-docs with:
- context7CompatibleLibraryID: resolved ID
- topic: "specific topic"
- tokens: 5000-10000
```
Common topics:
- "javascript client select insert update"
- "auth signIn signUp oauth"
- "realtime postgres_changes presence"
- "storage upload download transform"
- "edge-functions deploy invoke"
- "row-level-security policies"
- "pgvector similarity search"