setText(newText)}
+/>
+```
+
+---
+
+## Styling Native Components
+
+### Using CSS Classes
+Native components can have CSS classes for layout:
+```tsx
+
+ Click Me
+
+```
+
+```css
+.my-button-wrapper {
+ margin: 20px;
+ width: 200px;
+}
+```
+
+### Custom Colors
+Many components accept color props:
+```tsx
+
+ Delete
+
+
+
+```
+
+### Note on Styling Limitations
+Native UI components render as Flutter widgets, so:
+- ✅ Can control: position, size, margin, padding (via wrapper)
+- ❌ Cannot control: internal styling, fonts, borders (use Flutter theming)
+
+---
+
+## Resources
+
+- **Official Documentation**: https://openwebf.com/en/ui-components
+- **Cupertino Gallery**: https://openwebf.com/en/ui-components/cupertino
+- **SF Symbols Browser**: https://developer.apple.com/sf-symbols/
+- **Form Validation**: https://pub.dev/packages/form_builder_validators
+
+---
+
+## Getting Help
+
+- **Component not working?** Check that the Flutter package is installed and initialized
+- **TypeScript errors?** Ensure the npm package is installed correctly
+- **Props not working?** Check the official documentation for the exact prop names
+- **Vue components?** Generate bindings using `webf codegen` command
diff --git a/.agents/skills/webf-quickstart/SKILL.md b/.agents/skills/webf-quickstart/SKILL.md
new file mode 100644
index 0000000000..778704c15c
--- /dev/null
+++ b/.agents/skills/webf-quickstart/SKILL.md
@@ -0,0 +1,222 @@
+---
+name: webf-quickstart
+description: Get started with WebF development - setup WebF Go, create a React/Vue/Svelte project with Vite, and load your first app. Use when starting a new WebF project, onboarding new developers, or setting up development environment.
+---
+
+# WebF Quickstart
+
+> **Note**: Building WebF apps is nearly identical to building regular web apps with Vite + React/Vue/Svelte. The only difference is you replace your browser with **WebF Go** for testing during development. Everything else - project structure, build tools, testing frameworks, and deployment - works the same way.
+
+> **⚠️ IMPORTANT**: WebF Go is for **development and testing ONLY**. For production, you must build a Flutter app with WebF integration. Do NOT distribute WebF Go to end users.
+
+Get up and running with WebF in minutes. This skill guides you through setting up your development environment, creating your first project, and loading it in WebF Go.
+
+## What You Need
+
+**Only Node.js is required** - that's it!
+
+- Node.js (LTS version recommended) from [nodejs.org](https://nodejs.org/)
+- **You do NOT need**: Flutter SDK, Xcode, or Android Studio
+
+WebF lets web developers build native apps using familiar web tools.
+
+## Step-by-Step Setup
+
+### 1. Download WebF Go (For Testing Only)
+
+WebF Go is a pre-built native app containing the WebF rendering engine. It's designed for **development and testing purposes only** - not for production deployment.
+
+**For Desktop Development:**
+- Download WebF Go for your OS (macOS, Windows, Linux)
+- Get it from: **https://openwebf.com/en/go**
+
+**For Mobile Testing:**
+- iOS: Download from App Store
+- Android: Download from Google Play
+
+**Remember**: WebF Go is a testing tool. For production apps, you'll need to build a Flutter app with WebF integration.
+
+Launch WebF Go - you'll see an input field ready to load your app.
+
+### 2. Create Your Project with Vite
+
+Open your terminal and create a new project:
+
+```bash
+npm create vite@latest my-webf-app
+```
+
+Vite will prompt you to:
+1. Choose a framework: **React**, **Vue**, **Svelte**, etc.
+2. Choose a variant (JavaScript or TypeScript)
+
+### 3. Install Dependencies and Start Dev Server
+
+```bash
+# Move into your project
+cd my-webf-app
+
+# Install dependencies
+npm install
+
+# Start the dev server
+npm run dev
+```
+
+Your terminal will show URLs like:
+```
+ VITE v5.0.0 ready in 123 ms
+
+ ➜ Local: http://localhost:5173/
+ ➜ Network: http://192.168.1.100:5173/
+```
+
+### 4. Load in WebF Go
+
+**For Desktop:**
+1. Copy the `http://localhost:5173/` URL
+2. Paste into WebF Go's input field
+3. Press Enter or click "Go"
+
+**For Mobile Device:**
+⚠️ **IMPORTANT**: Mobile devices cannot access `localhost`
+
+You MUST use the Network URL instead:
+1. Make sure your computer and mobile device are on the **same WiFi network**
+2. Use `--host` flag to expose the dev server:
+ ```bash
+ npm run dev -- --host
+ ```
+3. Copy the **Network** URL (e.g., `http://192.168.1.100:5173/`)
+4. Type it into WebF Go on your mobile device
+5. Press "Go"
+
+Your app will now render in WebF! 🎉
+
+### 5. Verify Hot Reload
+
+Make a quick change to your code and save. The app should automatically update - this is Vite's Hot Module Replacement (HMR) working with WebF.
+
+### 6. (Optional) Setup Chrome DevTools
+
+To debug your app:
+1. Click the floating debug button in WebF Go
+2. Click "Copy" to get the DevTools URL (`devtools://...`)
+3. Paste into desktop Google Chrome browser
+4. You can now use Console, Elements, Network tabs
+
+**Note**: JavaScript breakpoints don't work yet - use `console.log()` instead.
+
+## Common Issues and Solutions
+
+### Issue: "Cannot connect" on mobile device
+
+**Causes & Solutions:**
+- ✗ Using `localhost` → ✓ Use Network URL (`http://192.168.x.x:5173`)
+- ✗ Different WiFi networks → ✓ Put both devices on same network
+- ✗ Missing `--host` flag → ✓ Use `npm run dev -- --host`
+- ✗ Firewall blocking port → ✓ Allow port 5173 through firewall
+
+### Issue: "Connection refused"
+
+- Dev server not running → Run `npm run dev`
+- Wrong port number → Check terminal output for correct port
+- Firewall blocking → Temporarily disable to test
+
+### Issue: App loads but doesn't update
+
+- HMR not working → Refresh the page manually
+- Dev server error → Check terminal for errors
+- Network connection lost → Reconnect WiFi
+
+## Production Deployment
+
+⚠️ **WebF Go is NOT for production use**. It's a testing tool for developers.
+
+### For Production Apps
+
+When you're ready to deploy to end users, you need to:
+
+**1. Build Your Web Bundle**
+```bash
+npm run build
+```
+
+**2. Host Your Bundle**
+- Deploy to any web hosting (Vercel, Netlify, CDN, etc.)
+- Your bundle will be accessible via URL (e.g., `https://your-app.com`)
+
+**3. Create a Flutter App with WebF Integration**
+
+You or your Flutter team needs to:
+- Set up a Flutter project
+- Add the WebF Flutter package to `pubspec.yaml`
+- Configure the app (name, icon, splash screen, permissions)
+- Load your web bundle URL in the WebF widget
+
+**Example Flutter Integration:**
+```dart
+import 'package:webf/webf.dart';
+
+class MyApp extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return WebF(
+ bundle: WebFBundle.fromUrl('https://your-app.com'),
+ );
+ }
+}
+```
+
+**4. Build and Deploy Flutter App**
+- Build for iOS and Android
+- Submit to App Store and Google Play
+
+**Resources:**
+- [WebF Integration Guide](https://openwebf.com/en/docs/developer-guide/integration)
+- [Flutter App Setup](https://openwebf.com/en/docs/developer-guide/app-setup)
+
+### Development vs Production
+
+| Aspect | Development | Production |
+|--------|------------|------------|
+| **Tool** | WebF Go | Custom Flutter app |
+| **Purpose** | Testing & iteration | End-user distribution |
+| **Setup** | Download and run | Build Flutter app |
+| **Distribution** | Don't distribute | App Store/Google Play |
+| **Requirements** | Node.js only | Flutter SDK required |
+
+## Next Steps
+
+Now that you have a working dev environment:
+
+1. **Learn the #1 difference**: WebF uses async rendering - see the `webf-async-rendering` skill
+2. **Check API compatibility**: Not all web APIs work in WebF - see `webf-api-compatibility` skill
+3. **Add navigation**: Multi-screen apps use WebF routing - see `webf-routing-setup` skill
+
+## Quick Reference
+
+```bash
+# Create new project
+npm create vite@latest my-app
+
+# Start dev server (desktop)
+npm run dev
+
+# Start dev server (mobile - with network access)
+npm run dev -- --host
+
+# Install dependencies
+npm install
+
+# Build for production
+npm run build
+```
+
+## Resources
+
+- **Getting Started Guide**: https://openwebf.com/en/docs/developer-guide/getting-started
+- **WebF Go Guide**: https://openwebf.com/en/docs/learn-webf/webf-go
+- **Development Workflow**: https://openwebf.com/en/docs/developer-guide/development-workflow
+- **Download WebF Go**: https://openwebf.com/en/go
+- **Full Documentation**: https://openwebf.com/en/docs
\ No newline at end of file
diff --git a/.agents/skills/webf-quickstart/reference.md b/.agents/skills/webf-quickstart/reference.md
new file mode 100644
index 0000000000..df0128d8d2
--- /dev/null
+++ b/.agents/skills/webf-quickstart/reference.md
@@ -0,0 +1,199 @@
+# WebF Quickstart - Quick Reference
+
+> **⚠️ IMPORTANT**: WebF Go is for **testing and development ONLY**. For production, you must build a Flutter app with WebF integration.
+
+## Setup Checklist
+
+- [ ] Node.js installed (LTS version)
+- [ ] WebF Go downloaded and installed (for testing)
+- [ ] Project created with Vite
+- [ ] Dependencies installed (`npm install`)
+- [ ] Dev server running (`npm run dev`)
+- [ ] App loaded in WebF Go
+
+## Essential Commands
+
+```bash
+# Create project
+npm create vite@latest my-app
+cd my-app
+npm install
+
+# Development
+npm run dev # Desktop (localhost)
+npm run dev -- --host # Mobile (network access)
+
+# Production (Web Bundle)
+npm run build # Create production build
+npm run preview # Preview production build
+vercel deploy # Deploy to hosting
+```
+
+## Network URLs by Platform
+
+| Platform | URL Pattern | Example |
+|----------|-------------|---------|
+| Desktop | `http://localhost:PORT` | `http://localhost:5173` |
+| Mobile | `http://NETWORK-IP:PORT` | `http://192.168.1.100:5173` |
+
+**Critical**: Mobile devices cannot access `localhost` - always use Network URL!
+
+## Troubleshooting Quick Fixes
+
+| Problem | Quick Fix |
+|---------|-----------|
+| Mobile can't connect | Use `npm run dev -- --host` and Network URL |
+| Different WiFi | Put both devices on same network |
+| Firewall blocks | Allow port 5173 (or your dev server port) |
+| HMR not working | Hard refresh or restart dev server |
+| DevTools won't connect | Must use desktop Chrome browser |
+
+## Port Configuration
+
+Default Vite port: `5173`
+
+To change port:
+```bash
+# Option 1: Flag
+npm run dev -- --port 3000
+
+# Option 2: vite.config.js
+export default {
+ server: {
+ port: 3000,
+ host: true // Expose on network
+ }
+}
+```
+
+## DevTools Setup
+
+1. Click floating button in WebF Go
+2. Click "Copy" → Gets `devtools://...` URL
+3. Paste in desktop Chrome
+4. Use Console, Elements, Network tabs
+5. ⚠️ Breakpoints don't work - use `console.log()`
+
+## Supported Frameworks
+
+All work out-of-the-box with Vite:
+
+- ✅ React (16, 17, 18, 19)
+- ✅ Vue (2, 3)
+- ✅ Svelte
+- ✅ Preact
+- ✅ Solid
+- ✅ Qwik
+- ✅ Vanilla JS
+
+## HTTPS for Mobile Testing
+
+Some APIs require HTTPS. To enable:
+
+```bash
+npm install -D @vitejs/plugin-basic-ssl
+```
+
+```js
+// vite.config.js
+import basicSsl from '@vitejs/plugin-basic-ssl'
+
+export default {
+ plugins: [basicSsl()],
+ server: {
+ https: true
+ }
+}
+```
+
+Then use `https://192.168.x.x:5173` instead of `http://`
+
+## Common Vite Flags
+
+```bash
+# Network access (mobile testing)
+npm run dev -- --host
+
+# Custom port
+npm run dev -- --port 3000
+
+# Open browser automatically
+npm run dev -- --open
+
+# Clear cache
+npm run dev -- --force
+```
+
+## File Structure
+
+```
+my-webf-app/
+├── src/
+│ ├── main.jsx # Entry point
+│ ├── App.jsx # Root component
+│ └── index.css # Global styles
+├── public/ # Static assets
+├── index.html # HTML template
+├── package.json # Dependencies
+└── vite.config.js # Vite configuration
+```
+
+## Environment Detection
+
+Check if running in WebF:
+
+```javascript
+// WebF exposes global WebF object
+if (typeof WebF !== 'undefined') {
+ console.log('Running in WebF');
+} else {
+ console.log('Running in browser');
+}
+```
+
+## Production Deployment
+
+⚠️ **WebF Go is NOT for production**. It's a testing tool only.
+
+### Production Checklist
+
+1. **Build Web Bundle**
+ ```bash
+ npm run build
+ ```
+
+2. **Deploy Bundle to Hosting**
+ ```bash
+ vercel deploy # or Netlify, AWS S3, etc.
+ ```
+
+3. **Create Flutter App with WebF**
+ - Set up Flutter project
+ - Add `webf` package to `pubspec.yaml`
+ - Configure app (icons, permissions, etc.)
+ - Load your bundle URL
+
+4. **Build & Submit Flutter App**
+ - Build for iOS and Android
+ - Submit to App Store/Google Play
+
+### Key Differences
+
+| Development | Production |
+|------------|------------|
+| WebF Go | Custom Flutter app |
+| Testing only | End-user distribution |
+| No Flutter SDK needed | Flutter SDK required |
+| Don't distribute | App Store/Google Play |
+
+**Resources:**
+- Integration: https://openwebf.com/en/docs/developer-guide/integration
+- Flutter Setup: https://openwebf.com/en/docs/developer-guide/app-setup
+
+## Next Skill After Setup
+
+After successful setup, you'll need:
+
+1. **Async Rendering** (`webf-async-rendering`) - Most important concept
+2. **API Compatibility** (`webf-api-compatibility`) - What works/doesn't work
+3. **Routing Setup** (`webf-routing-setup`) - Multi-screen navigation
\ No newline at end of file
diff --git a/.agents/skills/webf-routing-setup/SKILL.md b/.agents/skills/webf-routing-setup/SKILL.md
new file mode 100644
index 0000000000..2d27cea8bb
--- /dev/null
+++ b/.agents/skills/webf-routing-setup/SKILL.md
@@ -0,0 +1,584 @@
+---
+name: webf-routing-setup
+description: Setup hybrid routing with native screen transitions in WebF - configure navigation using WebF routing instead of SPA routing. Use when setting up navigation, implementing multi-screen apps, or when react-router-dom/vue-router doesn't work as expected.
+---
+
+# WebF Routing Setup
+
+> **Note**: WebF development is nearly identical to web development - you use the same tools (Vite, npm, Vitest), same frameworks (React, Vue, Svelte), and same deployment services (Vercel, Netlify). This skill covers **one of the 3 key differences**: routing with native screen transitions instead of SPA routing. The other two differences are async rendering and API compatibility.
+
+**WebF does NOT use traditional Single-Page Application (SPA) routing.** Instead, it uses **hybrid routing** where each route renders on a separate, native Flutter screen with platform-native transitions.
+
+## The Fundamental Difference
+
+### In Browsers (SPA Routing)
+Traditional web routing uses the History API or hash-based routing:
+
+```javascript
+// Browser SPA routing (react-router-dom, vue-router)
+// ❌ This pattern does NOT work in WebF
+import { BrowserRouter, Routes, Route } from 'react-router-dom';
+
+// Single page with client-side routing
+// All routes render in the same screen
+// Transitions are CSS-based
+```
+
+The entire app runs in one screen, and route changes are simulated with JavaScript and CSS.
+
+### In WebF (Hybrid Routing)
+Each route is a separate Flutter screen with native transitions:
+
+```javascript
+// WebF hybrid routing
+// ✅ This pattern WORKS in WebF
+import { Routes, Route, WebFRouter } from '@openwebf/react-router';
+
+// Each route renders on a separate Flutter screen
+// Transitions use native platform animations
+// Hardware back button works correctly
+```
+
+**Think of it like native mobile navigation** - each route is a new screen in a navigation stack, not a section of a single web page.
+
+## Why Hybrid Routing?
+
+WebF's approach provides true native app behavior:
+
+1. **Native Transitions** - Platform-specific animations (Cupertino for iOS, Material for Android)
+2. **Proper Lifecycle** - Each route has its own lifecycle, similar to native apps
+3. **Hardware Back Button** - Android back button works correctly
+4. **Memory Management** - Unused routes can be unloaded
+5. **Deep Linking** - Integration with platform deep linking
+6. **Synchronized Navigation** - Flutter Navigator and WebF routing stay in sync
+
+## React Setup
+
+### Installation
+
+```bash
+npm install @openwebf/react-router
+```
+
+**CRITICAL**: Do NOT use `react-router-dom` - it will not work correctly in WebF.
+
+### Basic Route Configuration
+
+```jsx
+import { Route, Routes } from '@openwebf/react-router';
+import { HomePage } from './pages/home';
+import { ProfilePage } from './pages/profile';
+import { SettingsPage } from './pages/settings';
+
+function App() {
+ return (
+
+ {/* Each Route must have a title prop */}
+ } title="Home" />
+ } title="Profile" />
+ } title="Settings" />
+
+ );
+}
+
+export default App;
+```
+
+**Important**: The `title` prop appears in the native navigation bar for that screen.
+
+### Programmatic Navigation
+
+Use the `WebFRouter` object for navigation:
+
+```jsx
+import { WebFRouter } from '@openwebf/react-router';
+
+function HomePage() {
+ // Navigate forward (push new screen)
+ const goToProfile = () => {
+ WebFRouter.pushState({ userId: 123 }, '/profile');
+ };
+
+ // Replace current screen (no back button)
+ const replaceWithSettings = () => {
+ WebFRouter.replaceState({}, '/settings');
+ };
+
+ // Navigate back
+ const goBack = () => {
+ WebFRouter.back();
+ };
+
+ // Navigate forward
+ const goForward = () => {
+ WebFRouter.forward();
+ };
+
+ return (
+
+
Home Page
+
+
+
+
+
+ );
+}
+```
+
+### Passing Data Between Routes
+
+Use the state parameter to pass data:
+
+```jsx
+import { WebFRouter, useLocation } from '@openwebf/react-router';
+
+// Sender component
+function ProductList() {
+ const viewProduct = (product) => {
+ // Pass product data to detail screen
+ WebFRouter.pushState({
+ productId: product.id,
+ productName: product.name,
+ productPrice: product.price
+ }, '/product/detail');
+ };
+
+ return (
+
+
+
+ );
+}
+
+// Receiver component
+function ProductDetail() {
+ const location = useLocation();
+ const { productId, productName, productPrice } = location.state || {};
+
+ if (!productId) {
+ return No product data
;
+ }
+
+ return (
+
+
{productName}
+
Price: ${productPrice}
+
ID: {productId}
+
+ );
+}
+```
+
+### Using Route Parameters
+
+WebF supports dynamic route parameters:
+
+```jsx
+import { Route, Routes, useParams } from '@openwebf/react-router';
+
+function App() {
+ return (
+
+ } title="Home" />
+ } title="User Profile" />
+ } title="Comment" />
+
+ );
+}
+
+function UserProfile() {
+ const { userId } = useParams();
+
+ return (
+
+
User Profile
+
User ID: {userId}
+
+ );
+}
+
+function CommentDetail() {
+ const { postId, commentId } = useParams();
+
+ return (
+
+
Comment Detail
+
Post ID: {postId}
+
Comment ID: {commentId}
+
+ );
+}
+```
+
+### Declarative Navigation with Links
+
+Use `WebFRouterLink` for clickable navigation:
+
+```jsx
+import { WebFRouterLink } from '@openwebf/react-router';
+
+function NavigationMenu() {
+ return (
+
+ );
+}
+```
+
+### Advanced Navigation Methods
+
+WebFRouter provides Flutter-style navigation for complex scenarios:
+
+```jsx
+import { WebFRouter } from '@openwebf/react-router';
+
+// Push a route (async, returns when screen is pushed)
+await WebFRouter.push('/details', { itemId: 42 });
+
+// Replace current route (no back button)
+await WebFRouter.replace('/login', { sessionExpired: true });
+
+// Pop and push (remove current, add new)
+await WebFRouter.popAndPushNamed('/success', { orderId: 'ORD-123' });
+
+// Check if can pop
+if (WebFRouter.canPop()) {
+ const didPop = WebFRouter.maybePop({ cancelled: false });
+ console.log('Did pop:', didPop);
+}
+
+// Restorable navigation (state restoration support)
+const restorationId = await WebFRouter.restorablePopAndPushNamed('/checkout', {
+ cartItems: items,
+ timestamp: Date.now()
+});
+```
+
+## Hooks API
+
+WebF routing provides React hooks for accessing route information:
+
+```jsx
+import { useLocation, useParams, useNavigate } from '@openwebf/react-router';
+
+function MyComponent() {
+ // Get current location (pathname, state, etc.)
+ const location = useLocation();
+ console.log('Current path:', location.pathname);
+ console.log('Route state:', location.state);
+
+ // Get route parameters
+ const { userId, postId } = useParams();
+
+ // Get navigation function
+ const navigate = useNavigate();
+
+ const handleClick = () => {
+ // Navigate programmatically
+ navigate('/profile', { userId: 123 });
+ };
+
+ return ;
+}
+```
+
+## Common Patterns
+
+### Pattern 1: Protected Routes
+
+Redirect to login if not authenticated:
+
+```jsx
+import { useEffect } from 'react';
+import { WebFRouter, useLocation } from '@openwebf/react-router';
+
+function ProtectedRoute({ children, isAuthenticated }) {
+ const location = useLocation();
+
+ useEffect(() => {
+ if (!isAuthenticated) {
+ // Redirect to login, save current path
+ WebFRouter.pushState({
+ redirectTo: location.pathname
+ }, '/login');
+ }
+ }, [isAuthenticated, location.pathname]);
+
+ if (!isAuthenticated) {
+ return null; // Or loading spinner
+ }
+
+ return children;
+}
+
+// Usage
+function App() {
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
+
+ return (
+
+ } title="Login" />
+
+
+
+ }
+ title="Dashboard"
+ />
+
+ );
+}
+```
+
+### Pattern 2: Redirecting After Login
+
+After successful login, navigate to saved location:
+
+```jsx
+function LoginPage() {
+ const location = useLocation();
+ const redirectTo = location.state?.redirectTo || '/';
+
+ const handleLogin = async () => {
+ // Perform login
+ await loginUser();
+
+ // Redirect to saved location or home
+ WebFRouter.replaceState({}, redirectTo);
+ };
+
+ return (
+
+ );
+}
+```
+
+### Pattern 3: Conditional Navigation
+
+Navigate based on result:
+
+```jsx
+async function handleSubmit(formData) {
+ try {
+ const result = await submitForm(formData);
+
+ if (result.success) {
+ // Navigate to success page
+ WebFRouter.pushState({
+ message: result.message,
+ orderId: result.orderId
+ }, '/success');
+ } else {
+ // Navigate to error page
+ WebFRouter.pushState({
+ error: result.error
+ }, '/error');
+ }
+ } catch (error) {
+ // Handle error
+ WebFRouter.pushState({
+ error: error.message
+ }, '/error');
+ }
+}
+```
+
+### Pattern 4: Preventing Navigation
+
+Confirm before leaving unsaved changes:
+
+```jsx
+import { useEffect } from 'react';
+import { WebFRouter } from '@openwebf/react-router';
+
+function FormPage() {
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
+
+ useEffect(() => {
+ if (!hasUnsavedChanges) return;
+
+ // Custom back button handler
+ const handleBack = () => {
+ const shouldLeave = confirm('You have unsaved changes. Leave anyway?');
+ if (shouldLeave) {
+ setHasUnsavedChanges(false);
+ WebFRouter.back();
+ }
+ };
+
+ // Note: This is a simplified example
+ // Actual implementation depends on your back button handling
+ }, [hasUnsavedChanges]);
+
+ return (
+
+ );
+}
+```
+
+## Vue Setup
+
+For Vue applications, use `@openwebf/vue-router`:
+
+```bash
+npm install @openwebf/vue-router
+```
+
+**Note**: The API is similar to Vue Router but adapted for WebF's hybrid routing. Full Vue examples are available in `examples.md`.
+
+## Cross-Platform Support
+
+For apps that run in both WebF and browsers, see `cross-platform.md` for router adapter patterns.
+
+## Key Differences from SPA Routing
+
+| Feature | SPA Routing (Browser) | Hybrid Routing (WebF) |
+|---------|----------------------|----------------------|
+| Screen transitions | CSS animations | Native platform animations |
+| Route lifecycle | JavaScript-managed | Flutter-managed |
+| Memory management | Manual | Automatic (Flutter Navigator) |
+| Back button | History API | Hardware back button |
+| Deep linking | URL-based | Platform deep linking |
+| Route stacking | Virtual | Real native screen stack |
+
+## Common Mistakes
+
+### Mistake 1: Using react-router-dom
+
+```jsx
+// ❌ WRONG - Will not work correctly in WebF
+import { BrowserRouter, Routes, Route } from 'react-router-dom';
+
+function App() {
+ return (
+
+
+ } />
+
+
+ );
+}
+```
+
+```jsx
+// ✅ CORRECT - Use @openwebf/react-router
+import { Routes, Route } from '@openwebf/react-router';
+
+function App() {
+ return (
+
+ } title="Home" />
+
+ );
+}
+```
+
+### Mistake 2: Forgetting title Prop
+
+```jsx
+// ❌ WRONG - Missing title prop
+} />
+
+// ✅ CORRECT - Include title
+} title="Home" />
+```
+
+### Mistake 3: Using window.history
+
+```javascript
+// ❌ WRONG - History API doesn't work in WebF
+window.history.pushState({}, '', '/new-path');
+
+// ✅ CORRECT - Use WebFRouter
+WebFRouter.pushState({}, '/new-path');
+```
+
+### Mistake 4: Expecting SPA Behavior
+
+```jsx
+// ❌ WRONG - Expecting all routes to share state
+// In WebF, each route is a separate screen
+const [sharedState, setSharedState] = useState({}); // Won't persist across routes
+
+// ✅ CORRECT - Use proper state management
+// Use Context, Redux, or pass data via route state
+WebFRouter.pushState({ data: myData }, '/next-route');
+```
+
+## Resources
+
+- **Routing Documentation**: https://openwebf.com/en/docs/developer-guide/routing
+- **Core Concepts - Hybrid Routing**: https://openwebf.com/en/docs/developer-guide/core-concepts#hybrid-routing
+- **Complete Examples**: See `examples.md` in this skill
+- **Cross-Platform Patterns**: See `cross-platform.md` in this skill
+- **npm Package**: https://www.npmjs.com/package/@openwebf/react-router
+
+## Quick Reference
+
+```bash
+# Install React router
+npm install @openwebf/react-router
+
+# Install Vue router
+npm install @openwebf/vue-router
+```
+
+```jsx
+// Basic setup
+import { Routes, Route, WebFRouter } from '@openwebf/react-router';
+
+// Navigate forward
+WebFRouter.pushState({ data }, '/path');
+
+// Navigate back
+WebFRouter.back();
+
+// Replace current
+WebFRouter.replaceState({ data }, '/path');
+
+// Get location
+const location = useLocation();
+
+// Get params
+const { id } = useParams();
+```
+
+## Key Takeaways
+
+✅ **DO**:
+- Use `@openwebf/react-router` or `@openwebf/vue-router`
+- Include `title` prop on all routes
+- Use `WebFRouter` for navigation
+- Pass data via route state
+- Think of routes as native screens
+
+❌ **DON'T**:
+- Use react-router-dom or vue-router directly
+- Expect SPA routing behavior
+- Use window.history API
+- Share state across routes without proper state management
+- Forget that each route is a separate Flutter screen
\ No newline at end of file
diff --git a/.agents/skills/webf-routing-setup/cross-platform.md b/.agents/skills/webf-routing-setup/cross-platform.md
new file mode 100644
index 0000000000..fea07169b8
--- /dev/null
+++ b/.agents/skills/webf-routing-setup/cross-platform.md
@@ -0,0 +1,528 @@
+# Cross-Platform Routing Patterns
+
+Build applications that work in both WebF (native) and browsers (web) using a single codebase.
+
+## The Challenge
+
+WebF uses hybrid routing with native screen transitions:
+- `@openwebf/react-router` for WebF
+- Each route is a separate Flutter screen
+
+Browsers use SPA routing with History API:
+- `react-router-dom` for browsers
+- All routes render in a single page
+- Transitions are CSS-based
+
+**Goal**: Write one app that works in both environments.
+
+## Solution: Router Adapter Pattern
+
+Create an adapter that detects the environment and uses the appropriate router.
+
+## Complete React Example
+
+### Step 1: Install Both Routers
+
+```bash
+# WebF router
+npm install @openwebf/react-router
+
+# Browser router
+npm install react-router-dom
+```
+
+### Step 2: Create Router Adapter
+
+```jsx
+// src/routing/router-adapter.jsx
+import * as WebFRouter from '@openwebf/react-router';
+import * as BrowserRouter from 'react-router-dom';
+
+// Detect if running in WebF
+const isWebF = typeof window !== 'undefined' && typeof (window as any).webf !== 'undefined';
+
+// Export appropriate router components
+export const Routes = isWebF ? WebFRouter.Routes : BrowserRouter.Routes;
+export const Route = isWebF ? WebFRouter.Route : BrowserRouter.Route;
+export const useLocation = isWebF ? WebFRouter.useLocation : BrowserRouter.useLocation;
+export const useParams = isWebF ? WebFRouter.useParams : BrowserRouter.useParams;
+export const useNavigate = isWebF ? WebFRouter.useNavigate : BrowserRouter.useNavigate;
+
+// Router provider wrapper
+export function RouterProvider({ children }) {
+ if (isWebF) {
+ // WebF doesn't need a provider wrapper
+ return <>{children}>;
+ }
+
+ // Browser needs BrowserRouter wrapper
+ return {children};
+}
+
+// Navigation helper (unified API)
+export const navigate = {
+ push: (path, state = {}) => {
+ if (isWebF) {
+ WebFRouter.WebFRouter.pushState(state, path);
+ } else {
+ // In browser, use window.history or useNavigate hook
+ window.history.pushState(state, '', path);
+ window.dispatchEvent(new PopStateEvent('popstate'));
+ }
+ },
+
+ replace: (path, state = {}) => {
+ if (isWebF) {
+ WebFRouter.WebFRouter.replaceState(state, path);
+ } else {
+ window.history.replaceState(state, '', path);
+ window.dispatchEvent(new PopStateEvent('popstate'));
+ }
+ },
+
+ back: () => {
+ if (isWebF) {
+ WebFRouter.WebFRouter.back();
+ } else {
+ window.history.back();
+ }
+ },
+
+ forward: () => {
+ if (isWebF) {
+ WebFRouter.WebFRouter.forward();
+ } else {
+ window.history.forward();
+ }
+ }
+};
+
+// Link component that works in both environments
+export function Link({ to, state, children, ...props }) {
+ const handleClick = (e) => {
+ e.preventDefault();
+ navigate.push(to, state);
+ };
+
+ return (
+
+ {children}
+
+ );
+}
+
+// Check if running in WebF
+export { isWebF };
+```
+
+### Step 3: Update App to Use Adapter
+
+```jsx
+// src/App.jsx
+import { Routes, Route, RouterProvider } from './routing/router-adapter';
+import HomePage from './pages/HomePage';
+import ProfilePage from './pages/ProfilePage';
+import SettingsPage from './pages/SettingsPage';
+
+function App() {
+ return (
+
+
+ } title="Home" />
+ } title="Profile" />
+ } title="Settings" />
+
+
+ );
+}
+
+export default App;
+```
+
+**Note**: The `title` prop is ignored by react-router-dom, so it's safe to include for both.
+
+### Step 4: Use Adapter in Components
+
+```jsx
+// src/pages/HomePage.jsx
+import { navigate, Link, isWebF } from '../routing/router-adapter';
+
+function HomePage() {
+ const goToProfile = () => {
+ navigate.push('/profile', { source: 'home' });
+ };
+
+ return (
+
+
Home Page
+
+ {isWebF && (
+
+ Running in WebF (Native App)
+
+ )}
+
+ {!isWebF && (
+
+ Running in Browser (Web)
+
+ )}
+
+
+ {/* Programmatic navigation */}
+
+
+ {/* Declarative navigation */}
+
+ Go to Settings (Link)
+
+
+
+ );
+}
+
+export default HomePage;
+```
+
+```jsx
+// src/pages/ProfilePage.jsx
+import { useLocation, navigate } from '../routing/router-adapter';
+
+function ProfilePage() {
+ const location = useLocation();
+ const source = location.state?.source;
+
+ const goBack = () => {
+ navigate.back();
+ };
+
+ return (
+
+
Profile Page
+
+ {source && (
+
Came from: {source}
+ )}
+
+
+
+ );
+}
+
+export default ProfilePage;
+```
+
+## Advanced: Custom Hook for Navigation
+
+Create a unified `useNavigation` hook:
+
+```jsx
+// src/routing/use-navigation.js
+import { useCallback } from 'react';
+import { navigate, isWebF } from './router-adapter';
+
+export function useNavigation() {
+ const push = useCallback((path, state) => {
+ navigate.push(path, state);
+ }, []);
+
+ const replace = useCallback((path, state) => {
+ navigate.replace(path, state);
+ }, []);
+
+ const back = useCallback(() => {
+ navigate.back();
+ }, []);
+
+ const forward = useCallback(() => {
+ navigate.forward();
+ }, []);
+
+ return {
+ push,
+ replace,
+ back,
+ forward,
+ isWebF
+ };
+}
+```
+
+**Usage**:
+
+```jsx
+import { useNavigation } from '../routing/use-navigation';
+
+function MyComponent() {
+ const { push, back, isWebF } = useNavigation();
+
+ const handleClick = () => {
+ push('/details', { itemId: 123 });
+ };
+
+ return (
+
+
+
+
+ {isWebF ? (
+
Native transitions enabled
+ ) : (
+
Browser mode
+ )}
+
+ );
+}
+```
+
+## Environment Detection
+
+### Method 1: Check for WebF Global
+
+```javascript
+const isWebF = typeof window !== 'undefined' && typeof window.webf !== 'undefined';
+```
+
+### Method 2: Check for WebFRouter
+
+```javascript
+import { WebFRouter } from '@openwebf/react-router';
+
+const isWebF = typeof WebFRouter !== 'undefined';
+```
+
+### Method 3: Environment Variable
+
+Set environment variable during build:
+
+```bash
+# .env.webf
+VITE_PLATFORM=webf
+
+# .env.browser
+VITE_PLATFORM=browser
+```
+
+```javascript
+const isWebF = import.meta.env.VITE_PLATFORM === 'webf';
+```
+
+## Platform-Specific Features
+
+Some features only make sense in one environment:
+
+```jsx
+import { isWebF } from './routing/router-adapter';
+import { WebFShare } from '@openwebf/webf-share';
+
+function ShareButton({ title, url }) {
+ if (!isWebF) {
+ // Browser: Use Web Share API or fallback
+ const handleShare = async () => {
+ if (navigator.share) {
+ await navigator.share({ title, url });
+ } else {
+ // Fallback: copy to clipboard
+ navigator.clipboard.writeText(url);
+ alert('Link copied to clipboard!');
+ }
+ };
+
+ return ;
+ }
+
+ // WebF: Use native share
+ if (!WebFShare.isAvailable()) {
+ return null;
+ }
+
+ const handleShare = async () => {
+ await WebFShare.shareText({ text: title, url });
+ };
+
+ return ;
+}
+```
+
+## Build Configuration
+
+### Vite Configuration
+
+```javascript
+// vite.config.js
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig(({ mode }) => {
+ const isWebF = mode === 'webf';
+
+ return {
+ plugins: [react()],
+ define: {
+ 'import.meta.env.IS_WEBF': isWebF
+ },
+ build: {
+ // Different output directories for different platforms
+ outDir: isWebF ? 'dist-webf' : 'dist-browser'
+ }
+ };
+});
+```
+
+**Build commands**:
+
+```bash
+# Build for browser
+npm run build
+
+# Build for WebF
+npm run build -- --mode webf
+```
+
+### Package.json Scripts
+
+```json
+{
+ "scripts": {
+ "dev": "vite",
+ "dev:webf": "vite --mode webf",
+ "build": "vite build",
+ "build:webf": "vite build --mode webf",
+ "preview": "vite preview"
+ }
+}
+```
+
+## TypeScript Support
+
+Add types for the router adapter:
+
+```typescript
+// src/routing/router-adapter.d.ts
+export interface NavigateState {
+ [key: string]: any;
+}
+
+export interface NavigateAPI {
+ push: (path: string, state?: NavigateState) => void;
+ replace: (path: string, state?: NavigateState) => void;
+ back: () => void;
+ forward: () => void;
+}
+
+export const navigate: NavigateAPI;
+export const isWebF: boolean;
+
+export function RouterProvider({ children }: { children: React.ReactNode }): JSX.Element;
+export function Link({ to, state, children, ...props }: {
+ to: string;
+ state?: NavigateState;
+ children: React.ReactNode;
+ [key: string]: any;
+}): JSX.Element;
+
+export { Routes, Route, useLocation, useParams, useNavigate } from '@openwebf/react-router';
+```
+
+## Testing Both Platforms
+
+### Test in Browser
+
+```bash
+npm run dev
+# Open http://localhost:5173 in browser
+```
+
+### Test in WebF
+
+```bash
+npm run dev -- --host
+# Open http://192.168.x.x:5173 in WebF Go
+```
+
+## Best Practices
+
+### 1. Use Adapter Consistently
+
+❌ **Don't mix routers**:
+```jsx
+import { WebFRouter } from '@openwebf/react-router';
+import { useNavigate } from 'react-router-dom';
+// This will break!
+```
+
+✅ **Use adapter everywhere**:
+```jsx
+import { navigate, useLocation } from './routing/router-adapter';
+// Works in both environments
+```
+
+### 2. Handle Missing State Gracefully
+
+```jsx
+function DetailPage() {
+ const location = useLocation();
+ const data = location.state?.data || getDefaultData();
+
+ // Always have fallback for missing state
+ return {data.name}
;
+}
+```
+
+### 3. Test in Both Environments
+
+- Develop in browser for fast iteration
+- Test in WebF Go regularly
+- CI/CD should test both builds
+
+### 4. Platform-Specific Code
+
+Use feature flags for platform-specific behavior:
+
+```jsx
+import { isWebF } from './routing/router-adapter';
+
+function Header() {
+ return (
+
+ My App
+ {isWebF ? (
+
+ ) : (
+
+ )}
+
+ );
+}
+```
+
+### 5. Keep Router Logic Simple
+
+Don't create overly complex abstractions. The adapter should be thin and predictable.
+
+## Summary
+
+The router adapter pattern allows you to:
+
+1. ✅ Write once, run in browser and WebF
+2. ✅ Use familiar routing APIs
+3. ✅ Handle platform differences gracefully
+4. ✅ Test consistently across platforms
+5. ✅ Maintain a single codebase
+
+**Key Files**:
+- `src/routing/router-adapter.jsx` - Router abstraction
+- `src/routing/use-navigation.js` - Navigation hook
+- `.env.webf` / `.env.browser` - Environment config
+
+**Development Flow**:
+1. Develop in browser (fast iteration)
+2. Test in WebF Go (native behavior)
+3. Build for both platforms
+4. Deploy to web and app stores
+
+This approach gives you the best of both worlds: web development speed and native app experience.
\ No newline at end of file
diff --git a/.agents/skills/webf-routing-setup/examples.md b/.agents/skills/webf-routing-setup/examples.md
new file mode 100644
index 0000000000..e514714829
--- /dev/null
+++ b/.agents/skills/webf-routing-setup/examples.md
@@ -0,0 +1,947 @@
+# WebF Routing - Complete Examples
+
+## Example 1: Basic Multi-Screen App (React)
+
+A simple app with three screens: Home, Profile, and Settings.
+
+```jsx
+// src/App.jsx
+import { Routes, Route } from '@openwebf/react-router';
+import HomePage from './pages/HomePage';
+import ProfilePage from './pages/ProfilePage';
+import SettingsPage from './pages/SettingsPage';
+
+function App() {
+ return (
+
+ } title="Home" />
+ } title="Profile" />
+ } title="Settings" />
+
+ );
+}
+
+export default App;
+```
+
+```jsx
+// src/pages/HomePage.jsx
+import { WebFRouter } from '@openwebf/react-router';
+
+function HomePage() {
+ const goToProfile = () => {
+ WebFRouter.pushState({}, '/profile');
+ };
+
+ const goToSettings = () => {
+ WebFRouter.pushState({}, '/settings');
+ };
+
+ return (
+
+
Home Page
+
Welcome to the home page!
+
+
+
+
+
+
+ );
+}
+
+export default HomePage;
+```
+
+```jsx
+// src/pages/ProfilePage.jsx
+import { WebFRouter } from '@openwebf/react-router';
+
+function ProfilePage() {
+ const goBack = () => {
+ WebFRouter.back();
+ };
+
+ return (
+
+
Profile Page
+
This is your profile.
+
+
+
+ );
+}
+
+export default ProfilePage;
+```
+
+```jsx
+// src/pages/SettingsPage.jsx
+import { WebFRouter } from '@openwebf/react-router';
+
+function SettingsPage() {
+ const goHome = () => {
+ // Replace current screen with home (no back button)
+ WebFRouter.replaceState({}, '/');
+ };
+
+ return (
+
+
Settings
+
App settings go here.
+
+
+
+ );
+}
+
+export default SettingsPage;
+```
+
+## Example 2: Passing Data Between Screens
+
+Shopping app with product list and detail screens.
+
+```jsx
+// src/App.jsx
+import { Routes, Route } from '@openwebf/react-router';
+import ProductListPage from './pages/ProductListPage';
+import ProductDetailPage from './pages/ProductDetailPage';
+import CartPage from './pages/CartPage';
+
+function App() {
+ return (
+
+ } title="Products" />
+ } title="Product Detail" />
+ } title="Shopping Cart" />
+
+ );
+}
+
+export default App;
+```
+
+```jsx
+// src/pages/ProductListPage.jsx
+import { WebFRouter } from '@openwebf/react-router';
+
+const PRODUCTS = [
+ { id: 1, name: 'Laptop', price: 999.99, description: 'Powerful laptop for work' },
+ { id: 2, name: 'Mouse', price: 29.99, description: 'Wireless mouse' },
+ { id: 3, name: 'Keyboard', price: 79.99, description: 'Mechanical keyboard' }
+];
+
+function ProductListPage() {
+ const viewProduct = (product) => {
+ // Pass entire product object to detail screen
+ WebFRouter.pushState({
+ product: product,
+ source: 'list'
+ }, '/product/detail');
+ };
+
+ const goToCart = () => {
+ WebFRouter.pushState({}, '/cart');
+ };
+
+ return (
+
+
Products
+
+
+
+
+
+
+ {PRODUCTS.map(product => (
+
+
{product.name}
+
${product.price}
+
+
+ ))}
+
+
+ );
+}
+
+export default ProductListPage;
+```
+
+```jsx
+// src/pages/ProductDetailPage.jsx
+import { useState } from 'react';
+import { WebFRouter, useLocation } from '@openwebf/react-router';
+
+function ProductDetailPage() {
+ const location = useLocation();
+ const { product, source } = location.state || {};
+ const [quantity, setQuantity] = useState(1);
+
+ if (!product) {
+ return (
+
+
No product data found.
+
+
+ );
+ }
+
+ const addToCart = () => {
+ // In a real app, you'd add to cart state/context
+ console.log(`Added ${quantity} x ${product.name} to cart`);
+
+ // Navigate to cart with confirmation
+ WebFRouter.pushState({
+ addedProduct: product,
+ addedQuantity: quantity
+ }, '/cart');
+ };
+
+ const goBack = () => {
+ WebFRouter.back();
+ };
+
+ return (
+
+
{product.name}
+
+ ${product.price}
+
+
{product.description}
+
+
+
+
+
+
+
+
+
+
+ {source && (
+
+ Came from: {source}
+
+ )}
+
+ );
+}
+
+export default ProductDetailPage;
+```
+
+```jsx
+// src/pages/CartPage.jsx
+import { useLocation, WebFRouter } from '@openwebf/react-router';
+
+function CartPage() {
+ const location = useLocation();
+ const { addedProduct, addedQuantity } = location.state || {};
+
+ const goBack = () => {
+ WebFRouter.back();
+ };
+
+ const continueShopping = () => {
+ // Go back to product list (could use replaceState to prevent back navigation)
+ WebFRouter.pushState({}, '/');
+ };
+
+ return (
+
+
Shopping Cart
+
+ {addedProduct && (
+
+ ✓ Added to cart: {addedQuantity} x {addedProduct.name}
+
+ )}
+
+
Your cart items would appear here.
+
+
+
+
+
+
+ );
+}
+
+export default CartPage;
+```
+
+## Example 3: Dynamic Routes with Parameters
+
+Blog app with dynamic post IDs.
+
+```jsx
+// src/App.jsx
+import { Routes, Route } from '@openwebf/react-router';
+import BlogListPage from './pages/BlogListPage';
+import BlogPostPage from './pages/BlogPostPage';
+import AuthorPage from './pages/AuthorPage';
+
+function App() {
+ return (
+
+ } title="Blog" />
+ } title="Post" />
+ } title="Author" />
+
+ );
+}
+
+export default App;
+```
+
+```jsx
+// src/pages/BlogListPage.jsx
+import { WebFRouter } from '@openwebf/react-router';
+
+const POSTS = [
+ { id: '1', title: 'Getting Started with WebF', authorId: 'alice' },
+ { id: '2', title: 'Understanding Async Rendering', authorId: 'bob' },
+ { id: '3', title: 'Hybrid Routing Explained', authorId: 'alice' }
+];
+
+function BlogListPage() {
+ const viewPost = (postId) => {
+ // Navigate using route parameter
+ WebFRouter.pushState({}, `/post/${postId}`);
+ };
+
+ return (
+
+
Blog Posts
+
+
+ {POSTS.map(post => (
+
+
{post.title}
+
By: {post.authorId}
+
+
+ ))}
+
+
+ );
+}
+
+export default BlogListPage;
+```
+
+```jsx
+// src/pages/BlogPostPage.jsx
+import { useParams, WebFRouter } from '@openwebf/react-router';
+
+const POSTS = {
+ '1': {
+ id: '1',
+ title: 'Getting Started with WebF',
+ content: 'WebF is a W3C/WHATWG-compliant web runtime for Flutter...',
+ authorId: 'alice',
+ authorName: 'Alice Smith'
+ },
+ '2': {
+ id: '2',
+ title: 'Understanding Async Rendering',
+ content: 'WebF uses async rendering which is different from browsers...',
+ authorId: 'bob',
+ authorName: 'Bob Johnson'
+ },
+ '3': {
+ id: '3',
+ title: 'Hybrid Routing Explained',
+ content: 'Each route in WebF is a separate Flutter screen...',
+ authorId: 'alice',
+ authorName: 'Alice Smith'
+ }
+};
+
+function BlogPostPage() {
+ // Extract postId from URL parameter
+ const { postId } = useParams();
+ const post = POSTS[postId];
+
+ if (!post) {
+ return (
+
+
Post Not Found
+
+
+ );
+ }
+
+ const viewAuthor = () => {
+ WebFRouter.pushState(
+ { authorName: post.authorName },
+ `/author/${post.authorId}`
+ );
+ };
+
+ const goBack = () => {
+ WebFRouter.back();
+ };
+
+ return (
+
+
{post.title}
+
+
+ By
+
+
+
+ {post.content}
+
+
+
+
+ );
+}
+
+export default BlogPostPage;
+```
+
+```jsx
+// src/pages/AuthorPage.jsx
+import { useParams, useLocation, WebFRouter } from '@openwebf/react-router';
+
+function AuthorPage() {
+ const { authorId } = useParams();
+ const location = useLocation();
+ const { authorName } = location.state || {};
+
+ const goBack = () => {
+ WebFRouter.back();
+ };
+
+ return (
+
+
Author: {authorName || authorId}
+
Author ID: {authorId}
+
This would show the author's profile and posts.
+
+
+
+ );
+}
+
+export default AuthorPage;
+```
+
+## Example 4: Authentication Flow
+
+Login flow with protected routes and redirect after authentication.
+
+```jsx
+// src/App.jsx
+import { Routes, Route } from '@openwebf/react-router';
+import { useState } from 'react';
+import LoginPage from './pages/LoginPage';
+import DashboardPage from './pages/DashboardPage';
+import ProfilePage from './pages/ProfilePage';
+import ProtectedRoute from './components/ProtectedRoute';
+
+function App() {
+ const [user, setUser] = useState(null);
+
+ return (
+
+ }
+ title="Login"
+ />
+
+
+
+ }
+ title="Dashboard"
+ />
+
+
+
+ }
+ title="Profile"
+ />
+
+ );
+}
+
+export default App;
+```
+
+```jsx
+// src/components/ProtectedRoute.jsx
+import { useEffect } from 'react';
+import { WebFRouter, useLocation } from '@openwebf/react-router';
+
+function ProtectedRoute({ children, user }) {
+ const location = useLocation();
+
+ useEffect(() => {
+ if (!user) {
+ // Save current path for redirect after login
+ WebFRouter.replaceState({
+ redirectTo: location.pathname
+ }, '/login');
+ }
+ }, [user, location.pathname]);
+
+ if (!user) {
+ return null; // Or a loading spinner
+ }
+
+ return children;
+}
+
+export default ProtectedRoute;
+```
+
+```jsx
+// src/pages/LoginPage.jsx
+import { useState } from 'react';
+import { WebFRouter, useLocation } from '@openwebf/react-router';
+
+function LoginPage({ setUser }) {
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [error, setError] = useState('');
+ const location = useLocation();
+ const redirectTo = location.state?.redirectTo || '/';
+
+ const handleLogin = async (e) => {
+ e.preventDefault();
+ setError('');
+
+ // Simulate login
+ if (email && password) {
+ // In real app, call authentication API
+ const user = {
+ id: 1,
+ email: email,
+ name: 'John Doe'
+ };
+
+ setUser(user);
+
+ // Redirect to saved location or home
+ WebFRouter.replaceState({}, redirectTo);
+ } else {
+ setError('Please enter email and password');
+ }
+ };
+
+ return (
+
+
Login
+
+ {redirectTo !== '/' && (
+
+ Please log in to continue to {redirectTo}
+
+ )}
+
+
+
+ );
+}
+
+export default LoginPage;
+```
+
+```jsx
+// src/pages/DashboardPage.jsx
+import { WebFRouter } from '@openwebf/react-router';
+
+function DashboardPage({ user }) {
+ const goToProfile = () => {
+ WebFRouter.pushState({}, '/profile');
+ };
+
+ return (
+
+
Dashboard
+
Welcome, {user.name}!
+
+
+
+
+
+ );
+}
+
+export default DashboardPage;
+```
+
+```jsx
+// src/pages/ProfilePage.jsx
+import { WebFRouter } from '@openwebf/react-router';
+
+function ProfilePage({ user, setUser }) {
+ const handleLogout = () => {
+ setUser(null);
+ WebFRouter.replaceState({}, '/login');
+ };
+
+ const goBack = () => {
+ WebFRouter.back();
+ };
+
+ return (
+
+
Profile
+
Name: {user.name}
+
Email: {user.email}
+
ID: {user.id}
+
+
+
+
+
+
+ );
+}
+
+export default ProfilePage;
+```
+
+## Example 5: Tabs with Navigation
+
+App with tab navigation and nested routes.
+
+```jsx
+// src/App.jsx
+import { Routes, Route } from '@openwebf/react-router';
+import TabsLayout from './layouts/TabsLayout';
+import HomePage from './pages/tabs/HomePage';
+import ExplorePage from './pages/tabs/ExplorePage';
+import NotificationsPage from './pages/tabs/NotificationsPage';
+import ProfilePage from './pages/tabs/ProfilePage';
+import SettingsPage from './pages/SettingsPage';
+
+function App() {
+ return (
+
+ {/* Tab routes wrapped in TabsLayout */}
+ } title="Home" />
+ } title="Explore" />
+ } title="Notifications" />
+ } title="Profile" />
+
+ {/* Full screen routes (no tabs) */}
+ } title="Settings" />
+
+ );
+}
+
+export default App;
+```
+
+```jsx
+// src/layouts/TabsLayout.jsx
+import { WebFRouter, useLocation } from '@openwebf/react-router';
+
+function TabsLayout({ children }) {
+ const location = useLocation();
+ const currentPath = location.pathname;
+
+ const tabs = [
+ { path: '/', label: 'Home' },
+ { path: '/explore', label: 'Explore' },
+ { path: '/notifications', label: 'Notifications' },
+ { path: '/profile', label: 'Profile' }
+ ];
+
+ const navigateToTab = (path) => {
+ if (path !== currentPath) {
+ WebFRouter.pushState({}, path);
+ }
+ };
+
+ return (
+
+ {/* Content area */}
+
+ {children}
+
+
+ {/* Tab bar */}
+
+ {tabs.map(tab => (
+
+ ))}
+
+
+ );
+}
+
+export default TabsLayout;
+```
+
+```jsx
+// src/pages/tabs/HomePage.jsx
+import { WebFRouter } from '@openwebf/react-router';
+
+function HomePage() {
+ const goToSettings = () => {
+ // Navigate to full-screen settings (no tabs)
+ WebFRouter.pushState({}, '/settings');
+ };
+
+ return (
+
+
Home
+
Welcome to the home tab!
+
+
+
+ );
+}
+
+export default HomePage;
+```
+
+```jsx
+// src/pages/tabs/ProfilePage.jsx
+function ProfilePage() {
+ return (
+
+
Profile
+
Your profile information.
+
+ );
+}
+
+export default ProfilePage;
+```
+
+```jsx
+// src/pages/SettingsPage.jsx
+import { WebFRouter } from '@openwebf/react-router';
+
+function SettingsPage() {
+ const goBack = () => {
+ WebFRouter.back();
+ };
+
+ return (
+
+
Settings
+
App settings (full screen, no tabs).
+
+
+
+ );
+}
+
+export default SettingsPage;
+```
+
+## Key Patterns Summary
+
+### Pattern 1: Basic Navigation
+```jsx
+WebFRouter.pushState({}, '/path');
+```
+
+### Pattern 2: Pass Data
+```jsx
+WebFRouter.pushState({ data: value }, '/path');
+```
+
+### Pattern 3: Access Data
+```jsx
+const location = useLocation();
+const data = location.state?.data;
+```
+
+### Pattern 4: Route Parameters
+```jsx
+// Define route: /user/:userId
+const { userId } = useParams();
+```
+
+### Pattern 5: Replace (No Back Button)
+```jsx
+WebFRouter.replaceState({}, '/path');
+```
+
+### Pattern 6: Go Back
+```jsx
+WebFRouter.back();
+```
+
+### Pattern 7: Protected Routes
+```jsx
+
+
+
+```
+
+## Testing Navigation
+
+To test navigation in development:
+
+1. **Check route transitions**: Verify native transitions happen
+2. **Test back button**: Hardware back button should work on Android
+3. **Verify state passing**: Data should persist between screens
+4. **Test deep linking**: Direct URLs should work
+5. **Check lifecycle**: Components should mount/unmount correctly
+
+## Common Issues and Solutions
+
+### Issue: "Cannot read property 'state' of undefined"
+**Cause**: Accessing `location.state` when no state was passed
+**Solution**: Use optional chaining or default values
+```jsx
+const data = location.state?.data || defaultValue;
+```
+
+### Issue: Navigation doesn't work
+**Cause**: Using react-router-dom instead of @openwebf/react-router
+**Solution**: Install and import correct package
+```bash
+npm install @openwebf/react-router
+```
+
+### Issue: Back button goes to wrong screen
+**Cause**: Using `pushState` when should use `replaceState`
+**Solution**: Use `replaceState` for redirects
+```jsx
+// After login, replace login screen
+WebFRouter.replaceState({}, '/dashboard');
+```
diff --git a/integration_tests/.metadata b/integration_tests/.metadata
index 0b0bf63d1b..22fd632353 100644
--- a/integration_tests/.metadata
+++ b/integration_tests/.metadata
@@ -4,7 +4,27 @@
# This file should be version controlled and should not be manually edited.
version:
- revision: 09126abb222d0f25b03318a1ab4a99d27d9aaa8d
- channel: unknown
+ revision: "582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536"
+ channel: "[user-branch]"
project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536
+ base_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536
+ - platform: android
+ create_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536
+ base_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536
+
+ # User provided section
+
+ # List of Local paths (relative to this file) that should be
+ # ignored by the migrate tool.
+ #
+ # Files that are not part of the templates will be ignored by default.
+ unmanaged_files:
+ - 'lib/main.dart'
+ - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/integration_tests/android/.gitignore b/integration_tests/android/.gitignore
new file mode 100644
index 0000000000..be3943c96d
--- /dev/null
+++ b/integration_tests/android/.gitignore
@@ -0,0 +1,14 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+.cxx/
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/to/reference-keystore
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/integration_tests/android/app/build.gradle.kts b/integration_tests/android/app/build.gradle.kts
new file mode 100644
index 0000000000..914f3d511e
--- /dev/null
+++ b/integration_tests/android/app/build.gradle.kts
@@ -0,0 +1,57 @@
+plugins {
+ id("com.android.application")
+ id("kotlin-android")
+ // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
+ id("dev.flutter.flutter-gradle-plugin")
+}
+
+val webfTargetAbis: List =
+ providers
+ .gradleProperty("webfTargetAbis")
+ .orElse("arm64-v8a")
+ .get()
+ .split(',')
+ .map(String::trim)
+ .filter(String::isNotEmpty)
+
+android {
+ namespace = "com.example.webf_integration_tests"
+ compileSdk = flutter.compileSdkVersion
+ ndkVersion = flutter.ndkVersion
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_17.toString()
+ }
+
+ defaultConfig {
+ // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+ applicationId = "com.example.webf_integration_tests"
+ // You can update the following values to match your application needs.
+ // For more information, see: https://flutter.dev/to/review-gradle-config.
+ minSdk = flutter.minSdkVersion
+ targetSdk = flutter.targetSdkVersion
+ versionCode = flutter.versionCode
+ versionName = flutter.versionName
+
+ ndk {
+ abiFilters += webfTargetAbis
+ }
+ }
+
+ buildTypes {
+ release {
+ // TODO: Add your own signing config for the release build.
+ // Signing with the debug keys for now, so `flutter run --release` works.
+ signingConfig = signingConfigs.getByName("debug")
+ }
+ }
+}
+
+flutter {
+ source = "../.."
+}
diff --git a/integration_tests/android/app/src/debug/AndroidManifest.xml b/integration_tests/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000000..399f6981d5
--- /dev/null
+++ b/integration_tests/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/integration_tests/android/app/src/main/AndroidManifest.xml b/integration_tests/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..22154ef7b9
--- /dev/null
+++ b/integration_tests/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/integration_tests/android/app/src/main/kotlin/com/example/webf_integration_tests/MainActivity.kt b/integration_tests/android/app/src/main/kotlin/com/example/webf_integration_tests/MainActivity.kt
new file mode 100644
index 0000000000..fbe9649fc4
--- /dev/null
+++ b/integration_tests/android/app/src/main/kotlin/com/example/webf_integration_tests/MainActivity.kt
@@ -0,0 +1,5 @@
+package com.example.webf_integration_tests
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity : FlutterActivity()
diff --git a/integration_tests/android/app/src/main/res/drawable-v21/launch_background.xml b/integration_tests/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000000..f74085f3f6
--- /dev/null
+++ b/integration_tests/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/integration_tests/android/app/src/main/res/drawable/launch_background.xml b/integration_tests/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000000..304732f884
--- /dev/null
+++ b/integration_tests/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/integration_tests/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/integration_tests/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000000..db77bb4b7b
Binary files /dev/null and b/integration_tests/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/integration_tests/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/integration_tests/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000000..17987b79bb
Binary files /dev/null and b/integration_tests/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/integration_tests/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/integration_tests/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000000..09d4391482
Binary files /dev/null and b/integration_tests/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/integration_tests/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/integration_tests/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..d5f1c8d34e
Binary files /dev/null and b/integration_tests/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/integration_tests/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/integration_tests/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..4d6372eebd
Binary files /dev/null and b/integration_tests/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/integration_tests/android/app/src/main/res/values-night/styles.xml b/integration_tests/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000000..06952be745
--- /dev/null
+++ b/integration_tests/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/integration_tests/android/app/src/main/res/values/styles.xml b/integration_tests/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000000..cb1ef88056
--- /dev/null
+++ b/integration_tests/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/integration_tests/android/app/src/profile/AndroidManifest.xml b/integration_tests/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000000..399f6981d5
--- /dev/null
+++ b/integration_tests/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/integration_tests/android/build.gradle.kts b/integration_tests/android/build.gradle.kts
new file mode 100644
index 0000000000..dbee657bb5
--- /dev/null
+++ b/integration_tests/android/build.gradle.kts
@@ -0,0 +1,24 @@
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+val newBuildDir: Directory =
+ rootProject.layout.buildDirectory
+ .dir("../../build")
+ .get()
+rootProject.layout.buildDirectory.value(newBuildDir)
+
+subprojects {
+ val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
+ project.layout.buildDirectory.value(newSubprojectBuildDir)
+}
+subprojects {
+ project.evaluationDependsOn(":app")
+}
+
+tasks.register("clean") {
+ delete(rootProject.layout.buildDirectory)
+}
diff --git a/integration_tests/android/gradle.properties b/integration_tests/android/gradle.properties
new file mode 100644
index 0000000000..fbee1d8cda
--- /dev/null
+++ b/integration_tests/android/gradle.properties
@@ -0,0 +1,2 @@
+org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
+android.useAndroidX=true
diff --git a/integration_tests/android/gradle/wrapper/gradle-wrapper.properties b/integration_tests/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000..e4ef43fb98
--- /dev/null
+++ b/integration_tests/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip
diff --git a/integration_tests/android/settings.gradle.kts b/integration_tests/android/settings.gradle.kts
new file mode 100644
index 0000000000..ca7fe065c1
--- /dev/null
+++ b/integration_tests/android/settings.gradle.kts
@@ -0,0 +1,26 @@
+pluginManagement {
+ val flutterSdkPath =
+ run {
+ val properties = java.util.Properties()
+ file("local.properties").inputStream().use { properties.load(it) }
+ val flutterSdkPath = properties.getProperty("flutter.sdk")
+ require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
+ flutterSdkPath
+ }
+
+ includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
+
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+plugins {
+ id("dev.flutter.flutter-plugin-loader") version "1.0.0"
+ id("com.android.application") version "8.11.1" apply false
+ id("org.jetbrains.kotlin.android") version "2.2.20" apply false
+}
+
+include(":app")
diff --git a/integration_tests/integration_test/profile_hotspot_cases_test.dart b/integration_tests/integration_test/profile_hotspot_cases_test.dart
index 66a7b3ae3f..cc7ec25b4a 100644
--- a/integration_tests/integration_test/profile_hotspot_cases_test.dart
+++ b/integration_tests/integration_test/profile_hotspot_cases_test.dart
@@ -18,6 +18,36 @@ final developer.UserTag _paragraphRebuildProfileTag =
developer.UserTag('profile_hotspots.paragraph_rebuild');
final developer.UserTag _flexInlineLayoutProfileTag =
developer.UserTag('profile_hotspots.flex_inline_layout');
+final developer.UserTag _fiatFilterPopupProfileTag =
+ developer.UserTag('profile_hotspots.fiat_filter_popup');
+final developer.UserTag _paymentMethodSheetProfileTag =
+ developer.UserTag('profile_hotspots.payment_method_sheet');
+final developer.UserTag _paymentMethodBottomSheetProfileTag =
+ developer.UserTag('profile_hotspots.payment_method_bottom_sheet');
+final developer.UserTag _paymentMethodBottomSheetTightProfileTag =
+ developer.UserTag('profile_hotspots.payment_method_bottom_sheet_tight');
+final developer.UserTag _paymentMethodFastPathSheetProfileTag =
+ developer.UserTag('profile_hotspots.payment_method_fastpath_sheet');
+final developer.UserTag _paymentMethodPickerModalProfileTag =
+ developer.UserTag('profile_hotspots.payment_method_picker_modal');
+final developer.UserTag _paymentMethodOtcSourceSheetProfileTag =
+ developer.UserTag('profile_hotspots.payment_method_otc_source_sheet');
+final developer.UserTag _flexAdjustFastPathProfileTag =
+ developer.UserTag('profile_hotspots.flex_adjust_fastpath');
+final developer.UserTag _flexNestedGroupFastPathProfileTag =
+ developer.UserTag('profile_hotspots.flex_nested_group_fastpath');
+final developer.UserTag _flexRunMetricsDenseProfileTag =
+ developer.UserTag('profile_hotspots.flex_runmetrics_dense');
+final developer.UserTag _flexTightFastPathDenseProfileTag =
+ developer.UserTag('profile_hotspots.flex_tight_fastpath_dense');
+final developer.UserTag _flexHybridFastPathDenseProfileTag =
+ developer.UserTag('profile_hotspots.flex_hybrid_fastpath_dense');
+final developer.UserTag _flexAdjustWidgetDenseProfileTag =
+ developer.UserTag('profile_hotspots.flex_adjust_widget_dense');
+const String _profileCaseFilter = String.fromEnvironment(
+ 'WEBF_PROFILE_CASE_FILTER',
+ defaultValue: '',
+);
void main() {
final IntegrationTestWidgetsFlutterBinding binding =
@@ -27,6 +57,10 @@ void main() {
await _configureProfileTestEnvironment();
});
+ tearDownAll(() async {
+ await _persistProfileArtifacts(binding.reportData);
+ });
+
setUp(() {
WebFControllerManager.instance.initialize(
WebFControllerManagerConfig(
@@ -42,8 +76,8 @@ void main() {
await Future.delayed(const Duration(milliseconds: 100));
});
- group('profile hotspot cases', () {
- testWidgets('profiles deep direction inheritance hotspot',
+ group('profile_hotspot_cases', () {
+ testWidgets('direction_inheritance',
(WidgetTester tester) async {
final _PreparedProfileCase prepared = await _prepareProfileCase(
tester,
@@ -75,9 +109,9 @@ void main() {
);
expect(host.renderStyle.direction, TextDirection.rtl);
- });
+ }, skip: !_shouldRunProfileCase('direction_inheritance'));
- testWidgets('profiles deep textAlign inheritance hotspot',
+ testWidgets('text_align_inheritance',
(WidgetTester tester) async {
final _PreparedProfileCase prepared = await _prepareProfileCase(
tester,
@@ -109,9 +143,9 @@ void main() {
);
expect(host.renderStyle.textAlign, TextAlign.center);
- });
+ }, skip: !_shouldRunProfileCase('text_align_inheritance'));
- testWidgets('profiles inline paragraph rebuild hotspot',
+ testWidgets('paragraph_rebuild',
(WidgetTester tester) async {
final _PreparedProfileCase prepared = await _prepareProfileCase(
tester,
@@ -160,9 +194,9 @@ void main() {
expect(host.getBoundingClientRect().width, greaterThan(0));
expect(paragraph.getBoundingClientRect().height, greaterThan(0));
- });
+ }, skip: !_shouldRunProfileCase('paragraph_rebuild'));
- testWidgets('profiles opacity transition hotspot',
+ testWidgets('opacity_transition',
(WidgetTester tester) async {
final _PreparedProfileCase prepared = await _prepareProfileCase(
tester,
@@ -192,291 +226,6532 @@ void main() {
);
expect(stage.className, isEmpty);
- });
+ }, skip: !_shouldRunProfileCase('opacity_transition'));
- testWidgets('profiles flex inline layout hotspot',
+ testWidgets('fiat_filter_popup',
(WidgetTester tester) async {
final _PreparedProfileCase prepared = await _prepareProfileCase(
tester,
controllerName:
- 'profile-flex-inline-${DateTime.now().millisecondsSinceEpoch}',
- html: _buildFlexInlineLayoutHtml(cardCount: 72),
+ 'profile-fiat-popup-${DateTime.now().millisecondsSinceEpoch}',
+ html: _buildFiatFilterPopupHtml(optionCount: 64),
);
final dom.Element host = prepared.getElementById('host');
- final dom.Element board = prepared.getElementById('board');
+ final dom.Element popup = prepared.getElementById('popup');
binding.reportData ??= {};
- binding.reportData!['flex_inline_layout_meta'] = {
- 'cardCount': 72,
- 'mutationIterations': 32,
- 'styleMutationPhases': 4,
- 'layoutMode': 'no-flex-no-stretch-no-baseline-nowrap',
+ binding.reportData!['fiat_filter_popup_meta'] = {
+ 'optionCount': 64,
+ 'mutationIterations': 40,
+ 'layoutMode': 'fixed-height-popup-option-list',
};
- await _runFlexInlineLayoutLoop(
+ await _runFiatFilterPopupLoop(
prepared,
mutationIterations: 10,
- widths: const ['360px', '324px', '296px', '344px'],
+ widths: const ['364px', '328px', '388px', '340px'],
);
- binding.reportData!['flex_inline_layout_cpu_samples'] =
+ binding.reportData!['fiat_filter_popup_cpu_samples'] =
await _captureCpuSamples(
- userTag: _flexInlineLayoutProfileTag,
+ userTag: _fiatFilterPopupProfileTag,
action: () async {
await binding.traceAction(
() async {
- await _runFlexInlineLayoutLoop(
+ await _runFiatFilterPopupLoop(
prepared,
- mutationIterations: 32,
- widths: const ['360px', '324px', '296px', '344px'],
+ mutationIterations: 40,
+ widths: const ['364px', '328px', '388px', '340px'],
);
},
- reportKey: 'flex_inline_layout_timeline',
+ reportKey: 'fiat_filter_popup_timeline',
);
},
);
expect(host.getBoundingClientRect().width, greaterThan(0));
- expect(board.getBoundingClientRect().height, greaterThan(0));
- });
- });
-}
+ expect(popup.getBoundingClientRect().height, greaterThan(0));
+ }, skip: !_shouldRunProfileCase('fiat_filter_popup'));
-Future