/sdk
/typescript
/ui
/widget manifest

Widget Manifest & UI Testing Guide

Learn how to create and use widget manifests for independent frontend development and comprehensive UI testing in NitroStack Studio.

Overview

The Widget Manifest system revolutionizes frontend development by enabling you to:

  • Preview widgets with example data without running the backend
  • Work independently from backend API development
  • Test multiple UI states using different examples
  • Document widget capabilities automatically

What is a Widget Manifest?

A Widget Manifest (widget-manifest.json) is a structured JSON file that lives in your project's src/widgets/ directory. It contains:

  • Widget metadata (names, descriptions, URIs)
  • Example data for each widget
  • Multiple examples to test different UI states
  • Tags for organization and discovery

Quick Start

1. Create Your Widget Manifest

Create src/widgets/widget-manifest.json:

{
  "version": "1.0.0",
  "widgets": [
    {
      "uri": "/user-profile",
      "name": "User Profile",
      "description": "Displays user information and stats",
      "examples": [
        {
          "name": "Active User",
          "description": "User with orders and activity",
          "data": {
            "user": {
              "name": "Emily Johnson",
              "email": "emily@example.com",
              "avatar": "https://api.dicebear.com/7.x/avataaars/svg?seed=Emily"
            },
            "stats": {
              "orders": 12,
              "spent": 1234.56
            }
          }
        }
      ],
      "tags": ["users", "profile"]
    }
  ]
}

2. View in Studio

npm run dev

Navigate to Studio → Resources Tab → See your widget in the UI Widgets section!

Complete Manifest Structure

{
  "version": "1.0.0",
  "widgets": [
    {
      "uri": "/widget-route",
      "name": "Widget Display Name",
      "description": "What this widget displays",
      "examples": [
        {
          "name": "Example Scenario Name",
          "description": "Description of this scenario",
          "data": {
            "field1": "value1",
            "field2": "value2",
            "nested": {
              "field3": "value3"
            }
          }
        }
      ],
      "tags": ["category", "type", "feature"]
    }
  ],
  "generatedAt": "2025-01-24T00:00:00.000Z"
}

Field Reference

Widget Fields

FieldTypeRequiredDescription
uristringāœ…Widget route (must match folder name, e.g., /product-card)
namestringāœ…Display name shown in Studio
descriptionstringāœ…Clear description of widget's purpose
examplesarrayāœ…Array of example data scenarios
tagsarray⬜Tags for categorization and filtering

Example Fields

FieldTypeRequiredDescription
namestringāœ…Example name (shown in dropdown selector)
descriptionstring⬜Additional context for this example
dataobjectāœ…The actual data props passed to the widget

Creating Widget Manifests

Method 1: From Tool Examples

If your tools already have examples, use the response data:

// In your tool definition
@Tool({
  name: 'get_product',
  examples: {
    request: { product_id: 'prod-1' },
    response: {  // ← Use this for widget manifest!
      product: {
        id: 'prod-1',
        name: 'Wireless Headphones',
        price: 79.99,
        image_url: 'https://example.com/headphones.jpg'
      },
      availability: 'In Stock'
    }
  }
})
@Widget('product-card')
async getProduct(input: any) { ... }

Add to manifest:

{
  "uri": "/product-card",
  "name": "Product Card",
  "description": "Displays detailed product information",
  "examples": [
    {
      "name": "In Stock Product",
      "data": {
        "product": {
          "id": "prod-1",
          "name": "Wireless Headphones",
          "price": 79.99,
          "image_url": "https://example.com/headphones.jpg"
        },
        "availability": "In Stock"
      }
    }
  ],
  "tags": ["products", "details"]
}

Method 2: Widget-First Development

Create examples before the backend is ready:

{
  "uri": "/shopping-cart",
  "name": "Shopping Cart",
  "description": "Displays cart items with totals",
  "examples": [
    {
      "name": "Empty Cart",
      "description": "No items in cart",
      "data": {
        "items": [],
        "total": 0,
        "itemCount": 0
      }
    },
    {
      "name": "Cart with Items",
      "description": "2 products in cart",
      "data": {
        "items": [
          {
            "id": "cart-1",
            "product_id": "prod-1",
            "name": "Wireless Headphones",
            "price": 79.99,
            "quantity": 1,
            "image_url": "https://example.com/headphones.jpg"
          },
          {
            "id": "cart-2",
            "product_id": "prod-2",
            "name": "Phone Case",
            "price": 19.99,
            "quantity": 2,
            "image_url": "https://example.com/case.jpg"
          }
        ],
        "total": 119.97,
        "itemCount": 2
      }
    }
  ],
  "tags": ["cart", "ecommerce"]
}

Testing UI States

Create multiple examples to test different scenarios:

{
  "uri": "/order-status",
  "name": "Order Status",
  "description": "Shows current order status",
  "examples": [
    {
      "name": "Pending Order",
      "description": "Order just placed",
      "data": {
        "orderId": "order-123",
        "status": "pending",
        "message": "Your order is being processed",
        "estimatedDelivery": "3-5 business days"
      }
    },
    {
      "name": "Shipped Order",
      "description": "Order in transit",
      "data": {
        "orderId": "order-123",
        "status": "shipped",
        "message": "Your order is on its way!",
        "trackingNumber": "TRK123456789",
        "estimatedDelivery": "Tomorrow"
      }
    },
    {
      "name": "Delivered Order",
      "description": "Order completed",
      "data": {
        "orderId": "order-123",
        "status": "delivered",
        "message": "Your order has been delivered",
        "deliveredAt": "2025-01-24T10:30:00Z"
      }
    },
    {
      "name": "Cancelled Order",
      "description": "Order was cancelled",
      "data": {
        "orderId": "order-123",
        "status": "cancelled",
        "message": "This order was cancelled",
        "reason": "Customer requested cancellation"
      }
    }
  ],
  "tags": ["orders", "status"]
}

Viewing Widgets in Studio

Resources Tab → UI Widgets

When you navigate to the Resources tab in Studio, you'll see:

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│  šŸŽØ UI Widgets (16)                      │
│                                          │
│  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”  │
│  │ šŸ›ļø Shopping Cart                   │  │
│  │ 2 examples                         │  │
│  ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤  │
│  │ Displays cart items with totals    │  │
│  │ /shopping-cart                     │  │
│  │ #cart #ecommerce                   │  │
│  ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤  │
│  │                                    │  │
│  │  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”  │  │
│  │  │ ✨ Cart with Items            │  │  │ 
│  │  │                              │  │  │
│  │  │  [Live Widget Preview]       │  │  │
│  │  │  • Headphones - $79.99       │  │  │
│  │  │  • Phone Case x2 - $39.98    │  │  │
│  │  │  Total: $119.97              │  │  │
│  │  │                              │  │  │
│  │  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜  │  │
│  │                                    │  │
│  │  [Select Example ā–¼]  [ā›¶ Enlarge]  │  │
│  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜  │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Interactive Features

  • Example Dropdown: Select different examples to see different UI states
  • Live Preview: Widget renders in real-time with selected data
  • Enlarge Button: Open full-screen modal for better viewing
  • Search/Filter: Find widgets by name, description, or tags

Best Practices

āœ… DO: Use Realistic Data

{
  "data": {
    "user": {
      "name": "Emily Johnson",
      "email": "emily.johnson@company.com",
      "avatar": "https://api.dicebear.com/7.x/avataaars/svg?seed=Emily",
      "joinDate": "2024-01-15T00:00:00Z"
    }
  }
}

āŒ DON'T: Use Placeholder Data

{
  "data": {
    "user": {
      "name": "User Name",
      "email": "user@email.com",
      "avatar": "image.jpg",
      "joinDate": "2024-01-01"
    }
  }
}

āœ… DO: Match Tool Output Exactly

// Tool returns:
return {
  items: [...],
  total: 123.45,
  itemCount: 3
};

// Manifest should match:
{
  "data": {
    "items": [...],
    "total": 123.45,
    "itemCount": 3
  }
}

āœ… DO: Provide Multiple Examples

Test edge cases:

  • Empty states
  • Single item
  • Multiple items
  • Maximum items
  • Long text
  • Special characters

āœ… DO: Use Descriptive Example Names

Good:

  • "In Stock Product"
  • "Low Stock Warning"
  • "Out of Stock"

Bad:

  • "Example 1"
  • "Test"
  • "Data"

Example Workflow

Scenario: Building a Product Listing

Step 1: Create the widget

// src/widgets/app/product-card/page.tsx
'use client';

export default function ProductCard({ data }: { data: any }) {
  const { product, availability } = data;
  
  return (
    <div className="border rounded-lg p-4">
      <img src={product.image_url} alt={product.name} />
      <h3>{product.name}</h3>
      <p className="text-2xl font-bold">${product.price}</p>
      <span className={
        availability === 'In Stock' 
          ? 'text-green-600' 
          : 'text-red-600'
      }>
        {availability}
      </span>
    </div>
  );
}

Step 2: Add to manifest

{
  "uri": "/product-card",
  "name": "Product Card",
  "description": "Displays product with price and availability",
  "examples": [
    {
      "name": "Available Product",
      "data": {
        "product": {
          "name": "Wireless Headphones",
          "price": 79.99,
          "image_url": "https://images.unsplash.com/photo-1505740420928-5e560c06d30e"
        },
        "availability": "In Stock"
      }
    },
    {
      "name": "Out of Stock",
      "data": {
        "product": {
          "name": "Smart Watch",
          "price": 299.99,
          "image_url": "https://images.unsplash.com/photo-1523275335684-37898b6baf30"
        },
        "availability": "Out of Stock"
      }
    }
  ],
  "tags": ["products", "card"]
}

Step 3: Preview in Studio

npm run dev
  • Open Studio → Resources → UI Widgets
  • Find "Product Card"
  • Toggle between "Available Product" and "Out of Stock"
  • Click Enlarge to view full size
  • Iterate on styles without backend!

Troubleshooting

Widgets Not Showing

Problem: UI Widgets section is empty

Solutions:

  1. Verify widget-manifest.json exists in src/widgets/
  2. Check JSON syntax is valid
  3. Rebuild: npm run build
  4. Restart: npm run dev

Preview Not Loading

Problem: Widget shows as blank

Solutions:

  1. Check uri matches folder name exactly
  2. Ensure widget component exists at src/widgets/app{uri}/page.tsx
  3. Verify uri starts with /
  4. Check browser console for errors

Data Not Matching

Problem: Widget shows wrong structure

Solutions:

  1. Compare manifest data with tool's examples.response
  2. Check for typos in field names
  3. Verify nested structure matches
  4. Ensure data types are correct

Advanced: Type-Safe Manifests

Use TypeScript for type safety:

// scripts/generate-manifest.ts
import { defineWidgetMetadata, type WidgetManifest } from 'nitrostack/widgets';
import { writeFileSync } from 'fs';

const manifest: WidgetManifest = {
  version: '1.0.0',
  widgets: [
    defineWidgetMetadata({
      uri: '/product-card',
      name: 'Product Card',
      description: 'Product details display',
      examples: [{
        name: 'Example',
        data: {
          product: { name: 'Item', price: 9.99 },
          availability: 'In Stock'
        }
      }],
      tags: ['products']
    })
  ]
};

writeFileSync(
  'src/widgets/widget-manifest.json',
  JSON.stringify(manifest, null, 2)
);

Run: ts-node scripts/generate-manifest.ts

Next Steps


Questions? Check the main documentation or open an issue on GitHub.