
## Overview

The widget system provides a powerful framework for building dynamic user interfaces through composable widgets. Each widget encapsulates specific functionality and can be configured, styled, and combined with other widgets to create complex applications.

```mermaid
graph TB
    subgraph "Widget Ecosystem"
        A[Widget Registry] --> B[Widget Definitions]
        B --> C[Widget Components]
        B --> D[Widget Schemas]
        B --> E[Widget Categories]
        
        F[Widget Tree] --> G[Root Widget]
        G --> H[Child Widgets]
        H --> I[Nested Widgets]
        
        J[Widget Renderer] --> K[Component Loader]
        K --> L[Dynamic Import]
        L --> M[Svelte Component]
        
        N[Widget Editor] --> O[Schema-based Forms]
        O --> P[Visual Configuration]
        P --> Q[Real-time Preview]
    end
    
    A --> F
    F --> J
    J --> N
```

## Core Concepts

### Widget Definition

A widget is defined by several key components:

```typescript
interface WidgetRegistration {
    name: string;                         // Unique identifier
    title?: InlineTranslation;            // Localized display name
    description?: InlineTranslation;      // Localized widget description (required for catalog widgets)
    category?: string;                    // Organization category
    visibleInCatalog?: boolean;           // Show in widget picker
    componentLoader: () => Promise<any>;  // Dynamic component loading
    dataSchema?: () => Promise<any>;      // Configuration schema
}
```

> **Important**: Widget descriptions use `InlineTranslation` (an object with language keys like `{ en: "...", de: "..." }`), not plain strings. Descriptions are required for widgets with `visibleInCatalog: true`. They are displayed in the widget catalog and exposed via `/docs/widgets/llm.txt` for AI agents and chatbots.

### Widget Instance

Each widget instance contains:

```typescript
interface Widget {
    id: string;                      // Unique instance ID
    name: string;                    // Widget type from registry
    data?: any;                      // Widget configuration data
    styles?: WidgetCSSStyles;        // CSS styling
    slots?: WidgetSlotMapping;       // Child widget relationships
}
```

### Widget Tree Structure

Widgets are organized in a hierarchical tree structure:

```typescript
interface WidgetTree {
    rootWidgetId: string | undefined;     // Entry point
    widgets: Record<string, Widget>;      // All widget instances
}
```

## Widget Categories

### Layout Widgets

Widgets that provide structure and organization:

- **Container**: Horizontal or vertical layout container
- **Max Width Container**: Responsive width limiting
- **App Shell**: Full application layout with header, sidebar, and content
- **Layout Child**: Flexible layout positioning

### Content Widgets

Widgets for displaying and editing content:

- **Text**: Rich text display and editing
- **Image**: Image display with responsive sizing
- **Editor.js**: Block-style content editor
- **Hero**: Large banner/hero sections
- **Check-in** (`CheckIn`): Visitor check-in widget with registration form, optional QR code scanner, and GDPR-compliant data retention settings. See [configuration details below](#check-in-widget).
- **Bunny Stream Video** (`BunnyStreamVideo`): Embeds a video from [Bunny Stream](https://bunny.net) (bunny.net) with configurable playback options. See [configuration details below](#bunny-stream-video-widget).

### Form Widgets

Interactive input and form components:

- **Form Fields**: Various input types (text, number, date, etc.)
- **Form Containers**: Form layout and validation
- **Button**: Action triggers and navigation
- **Login**: User authentication widget
- **Register**: User registration widget

### Data Widgets

Widgets for displaying and manipulating data:

- **DataView**: Sortable, filterable data display with saved queries
- **EntityEditor**: Edit entity records with validation
- **Dashboard**: Customizable dashboard with multiple widgets
- **NumberStats**: Display numeric statistics
- **NumberStatsDataTypeCount**: Count statistics by data type
- **Todo**: Task list and management
- **ProfileCard**: User profile display
- **AddressCard**: Address information display
- **StarRating**: Rating input and display
- **Maps**: Map display and location selection

### Navigation Widgets

Components for application navigation:

- **Navbar**: Top-level navigation
- **Footer**: Bottom navigation and links
- **Tabs**: Tabbed content organization
- **Breadcrumbs**: Hierarchical navigation

### Special Purpose Widgets

Specialized widgets for specific use cases:

- **Kiosk**: Full-screen kiosk mode for public displays
- **EmoticonsBar**: Emoji picker and emoticon input
- **Error**: Error display widget

### EventStream Widgets

Real-time collaborative widgets for Event Streams:

- **EventStreamMap**: GPS location sharing with OpenStreetMap integration ([docs](../widgets/event-stream-map.md))
- **EventStreamImageGallery**: Photo sharing with camera capture and gallery upload ([docs](../widgets/event-stream-image-gallery.md))
- **EventStreamChatMessages**: Real-time messaging for event streams ([docs](../widgets/event-stream-chat-messages.md))
- **EventStreamTimeline**: Chronological display of event entries ([docs](../widgets/event-stream-timeline.md))
- **EventStreamPinBoard**: Collaborative pin board for notes and ideas ([docs](../widgets/event-stream-pin-board.md))
- **EventStreamSettings**: Configuration and settings for event streams ([docs](../widgets/event-stream-settings.md))

### Website Building Block Widgets

Composable widgets for building public-facing website pages:

- **WebsiteSection**: Configurable page section wrapper with variant (`base`, `alternate`, `accent`) and padding options (`sm`, `md`, `lg`, `xl`)
- **WebsiteNavigation**: Fixed navigation bar with scroll-aware styling, responsive mobile menu, brand logo, navigation links, and CTA button
- **WebsiteFooter**: Footer with contact information, navigation links, and social media icons
- **WebsiteFrame**: Full-page wrapper combining WebsiteNavigation and WebsiteFooter around page content
- **CardGrid**: Responsive card grid layout with configurable columns (1-3) and style variants (`flat`, `elevated`, `gradient`)
- **CtaSection**: Call-to-action section with title, subtitle, button, and style variants (`subtle`, `gradient`)
- **FaqAccordion**: Expandable FAQ section with question/answer pairs
- **FeatureList**: Icon-based feature list for trust signals and feature highlights
- **PricingTable**: Responsive pricing tier comparison table with feature checklists and CTA buttons

### Utility Widgets

Specialized utility widgets:

- **QRCodeScanner**: Real-time QR code and barcode scanning with configurable actions ([docs](../widgets/qr-code-scanner.md))
- **IpForwarder**: Network utility for IP forwarding and port mapping ([docs](../widgets/ip-forwarder.md))
- **PushNotificationSubscription**: Browser notification subscription management

---

## Bunny Stream Video Widget

**Widget name constant**: `WIDGET_BUNNY_STREAM_VIDEO` (`"BunnyStreamVideo"`)  
**Category**: `content`  
**Visible in catalog**: yes

The Bunny Stream Video widget embeds a video hosted on [Bunny Stream](https://bunny.net) (bunny.net) using a responsive `<iframe>` with a standard 16:9 aspect ratio. Playback behaviour is fully configurable through widget properties that are appended as query parameters to the player URL.

### How It Works

The widget takes a Bunny Stream player URL (e.g. `https://player.mediadelivery.net/embed/...`) and appends the configured playback options as query parameters before rendering it in a responsive `<iframe>`. If no URL is provided the widget renders nothing. If the URL cannot be parsed it is used as-is.

### Configuration Properties

| Property | Type | Default | Description |
|---|---|---|---|
| `url` | `string` | `""` | Bunny Stream player URL (e.g. `https://player.mediadelivery.net/embed/...`). The widget renders nothing if this is empty. |
| `autoplay` | `boolean` | `true` | Automatically starts playback when the page loads. |
| `loop` | `boolean` | `true` | Loops the video continuously after it ends. |
| `muted` | `boolean` | `true` | Mutes the video audio. Required by most browsers for autoplay to work. |
| `preload` | `boolean` | `true` | Instructs the player to preload video data. |
| `responsive` | `boolean` | `true` | Enables responsive sizing within the player. |

### Example Configuration

```typescript
// Widget data object for the BunnyStreamVideo widget
const bunnyStreamVideoWidgetData = {
    url: "https://player.mediadelivery.net/embed/123456/abcdef-1234-5678-abcd-ef1234567890",
    autoplay: true,
    loop: true,
    muted: true,
    preload: true,
    responsive: true
};
```

### Notes

- The widget wraps the `<iframe>` in a `position: relative; padding-top: 56.25%` container to maintain a 16:9 aspect ratio at any container width.
- The `<iframe>` has `allow="accelerometer;gyroscope;autoplay;encrypted-media;picture-in-picture;"` and `allowfullscreen` set by default.
- The `<iframe>` uses `loading="lazy"` for deferred loading.
- Because `muted` defaults to `true`, the `autoplay` default will work in all modern browsers without requiring user interaction.

---

## Check-in Widget

**Widget name constant**: `WIDGET_CHECKIN` (`"CheckIn"`)  
**Category**: `content`  
**Visible in catalog**: yes

The Check-in widget provides a self-service visitor registration kiosk that can be embedded on any page. It presents a registration form driven by a configurable visitor data type, optionally allows returning visitors to identify themselves via QR code scan, and enforces GDPR-compliant data retention by letting each visitor choose how long their data is stored.

### How It Works

1. **Welcome screen** — Displays the configured title and description with a prompt to register or scan a QR code.
2. **Registration form** — Renders a schema-driven form based on the visitor data type. Internal fields (`latestEventType`, `latestEventAt`, `deleteOn`, `deleteOnPreference`) are hidden from the visitor and populated automatically.
3. **QR code scanner** (optional) — Allows returning visitors to scan their visitor pass QR code to check out or update their record.
4. **Success screen** — Shows a localized success message after check-in or check-out.
5. **Profile view** — Allows the visitor to review or edit their registered data.

On submission the widget calls the server-side Visitors API (`POST /api/projects/[projectId]/visitors`) and records a corresponding visitor event (`POST /api/projects/[projectId]/visitors/[visitorId]/events`).

### Configuration Properties

| Property | Type | Required | Description |
|---|---|---|---|
| `title` | `InlineTranslation` | No | Heading shown on the welcome screen (e.g. `{ en: "Welcome", de: "Willkommen" }`). |
| `description` | `InlineTranslation` | No | Introductory text shown below the title on the welcome screen. |
| `successSignInMessage` | `InlineTranslation` | No | Message displayed after a successful check-in. |
| `successSignOutMessage` | `InlineTranslation` | No | Message displayed after a successful check-out. |
| `smallPrint` | `InlineTranslation` | No | Privacy/legal notice shown at the bottom of the registration form (e.g. GDPR retention notice). |
| `scannerEnabled` | `boolean` | No | When `true`, a QR code scanner button is shown so returning visitors can identify themselves by scanning their visitor pass. Defaults to `false`. |
| `scannerButtonText` | `InlineTranslation` | No | Label for the QR code scanner button. Only shown when `scannerEnabled` is `true`. |
| `deletionPeriods` | `DeletionPeriod[]` | No | List of retention period options presented to the visitor. Each entry has `duration` (milliseconds), `label` (`InlineTranslation`), and `isDefault` (boolean). The entry marked `isDefault: true` is pre-selected. If omitted, a default of 28 days (2 419 200 000 ms) is used. |
| `visitorTypeId` | `string` | No | ID of the data type used to store visitor records. The widget derives the collection name and registration form schema from this type. |
| `visitorEventTypeId` | `string` | No | ID of the data type used to store visitor events (check-in / check-out log entries). |
| `contactsTypeId` | `string` | No | ID of the contacts data type, used to link a visitor to an internal contact person. |

#### `DeletionPeriod` object

```typescript
interface DeletionPeriod {
    duration: number;           // Retention duration in milliseconds
    label: InlineTranslation;   // Displayed option label
    isDefault: boolean;         // Pre-select this option
}
```

### Example Configuration

```typescript
// Widget data object for the CheckIn widget
const checkinWidgetData = {
    title: { en: "Welcome", de: "Willkommen" },
    description: {
        en: "Please register as a visitor",
        de: "Bitte registriere dich als Besucher"
    },
    successSignInMessage: {
        en: "You have been checked in successfully.",
        de: "Du wurdest erfolgreich eingecheckt."
    },
    successSignOutMessage: {
        en: "You have been checked out successfully.",
        de: "Du wurdest erfolgreich ausgecheckt."
    },
    smallPrint: {
        en: "Your data will be stored according to GDPR regulations and automatically deleted after the selected retention period.",
        de: "Deine Daten werden gemäß DSGVO gespeichert und nach dem ausgewählten Aufbewahrungszeitraum automatisch gelöscht."
    },
    scannerEnabled: true,
    scannerButtonText: { en: "Scan QR Code", de: "QR-Code scannen" },
    deletionPeriods: [
        {
            duration: 604800000,   // 7 days
            label: { en: "7 days", de: "7 Tage" },
            isDefault: false
        },
        {
            duration: 2419200000,  // 28 days
            label: { en: "28 days", de: "28 Tage" },
            isDefault: true
        },
        {
            duration: 7776000000,  // 90 days
            label: { en: "90 days", de: "90 Tage" },
            isDefault: false
        }
    ],
    visitorTypeId: "<visitor-type-id>",
    visitorEventTypeId: "<visitor-event-type-id>",
    contactsTypeId: "<contacts-type-id>"
};
```

### Template Integration

The Check-in widget is the core UI component of the **Check-in** feature template (`FeatureTemplateName.CHECKIN`). When the template is applied the `checkinAppExtension` automatically creates an application containing a pre-configured `CheckIn` widget instance wired to the generated visitor and visitor-events data types. The template now also requires the `CONTACT` feature so that visitor records can reference an internal contact person.

Related extensions created by the template:

| Extension | Purpose |
|---|---|
| `checkinAppExtension` | Creates the check-in application with a pre-configured `CheckIn` widget. |
| `checkinGdprExtension` | Creates a nightly cron action that deletes visitor records whose `deleteOn` timestamp has passed. |
| `checkinNotificationsExtension` | Creates email actions for visitor passes, contact-person notifications, and calendar invitations. |
| `visitorFeatureTypeExtension` | Creates the visitor data type (name, email, company, contact person, check-in status fields). |
| `visitorEventsFeatureTypeExtension` | Creates the visitor events data type (sign-in / sign-out log). |
| `visitorFeatureBOMExtension` | Adds a visitors management page to the backoffice navigation. |
| `visitorEventsFeatureBOMExtension` | Adds a visitor events log page to the backoffice navigation. |

### Server-Side API

The widget communicates with the following project-scoped API endpoints:

| Method | Path | Description |
|---|---|---|
| `POST` | `/api/projects/[projectId]/visitors` | Creates a new visitor record. Body: `{ collectionName, data }`. Returns `{ id }` with status `201`. |
| `GET` | `/api/projects/[projectId]/visitors/[visitorId]` | Retrieves a visitor record. Query param: `collectionName`. |
| `PATCH` | `/api/projects/[projectId]/visitors/[visitorId]` | Updates a visitor record. Body: `{ collectionName, data }`. |
| `DELETE` | `/api/projects/[projectId]/visitors/[visitorId]` | Deletes a visitor record. Query param: `collectionName`. |
| `POST` | `/api/projects/[projectId]/visitors/[visitorId]/events` | Creates a visitor event (check-in or check-out). Body: `{ collectionName, data }`. Returns `{ id }` with status `201`. |

---

## Widget Registration

Widgets are registered in the central registry:

```typescript
// Widget registration example
registerWidget({
    name: WIDGET_TEXT,
    title: { de: "Text" },
    description: { en: "A simple text display widget for showing static or dynamic text content with configurable formatting and support for Text Replacer syntax." },
    visibleInCatalog: true,
    category: "content",
    componentLoader: () => import("../../widgets/text/TextWidget.svelte"),
    dataSchema: () => import("../../widgets/text/TextWidget.schema.json")
});
```

### Registration Properties

- **name**: Unique widget identifier (constant)
- **title**: Localized display name
- **description**: Localized widget description as `InlineTranslation` object (required for catalog widgets)
- **category**: Organization category for the widget picker
- **visibleInCatalog**: Whether to show in the visual editor
- **componentLoader**: Dynamic component import function
- **dataSchema**: Configuration schema for the widget editor

### Description Requirement

**Description is required for all widgets with `visibleInCatalog: true`.** This ensures:

1. AI agents and chatbots can accurately describe widget capabilities via `/docs/widgets/llm.txt`
2. Users can discover widget features in the widget catalog with markdown rendering
3. Developers have concise reference material during integration

The description should include:
- Overview of the widget's purpose
- Key features and capabilities
- Important configuration details
- Configuration properties table
- Use cases
- Technical implementation details (where relevant)

### Accessing Widget Documentation

```typescript
import { 
    getWidgetDescription, 
    getAllWidgetDocumentation 
} from '@smallstack/client';

// Get description for a specific widget
const description = getWidgetDescription('EventStreamMap');

// Get all widget documentation (for documentation tools/chatbots)
const allDocs = await getAllWidgetDocumentation();
```

Widget documentation is also available via LLM-friendly endpoints:
- **Main platform docs**: `/llm.txt` - Links to widget catalog
- **Widget catalog**: `/docs/widgets/llm.txt` - Complete widget documentation for AI agents

## Widget Schemas

Each widget can define a JSON schema for its configuration:

```json
{
    "type": "object",
    "properties": {
        "text": {
            "type": "string",
            "title": "Text Content",
            "description": "The text to display"
        },
        "fontSize": {
            "type": "string",
            "title": "Font Size",
            "enum": ["small", "medium", "large"],
            "default": "medium"
        },
        "color": {
            "type": "string",
            "title": "Text Color",
            "x-type-schema": {
                "inputWidgetConfiguration": {
                    "string": { "type": "color" }
                }
            }
        }
    }
}
```

### Schema Features

- **Type Validation**: Ensures data integrity
- **UI Generation**: Automatically generates configuration forms
- **Localization**: Supports multiple languages
- **Custom Input Types**: Specialized input widgets
- **Validation Rules**: Client and server-side validation

## Widget Lifecycle

```mermaid
sequenceDiagram
    participant R as Registry
    participant L as Loader
    participant C as Component
    participant E as Editor
    
    R->>L: Register Widget
    L->>L: Store Definition
    E->>L: Request Widget
    L->>C: Dynamic Import
    C->>L: Component Instance
    L->>E: Rendered Widget
    E->>C: Configuration Updates
    C->>C: Re-render
```

### Lifecycle Stages

1. **Registration**: Widget is registered in the central registry
2. **Discovery**: Widget becomes available in the widget picker
3. **Selection**: User selects widget for their application
4. **Configuration**: User configures widget properties
5. **Rendering**: Widget is rendered in the application
6. **Updates**: Configuration changes trigger re-rendering
7. **Persistence**: Widget state is saved to the database

## Widget Slots

Widgets can contain other widgets through a slot system:

```typescript
interface WidgetSlotMapping {
    [slotName: string]: string[];  // Maps slot names to widget IDs
}
```

### Slot Examples

```typescript
// Container widget with multiple slots
const containerWidget: Widget = {
    id: "container-1",
    name: "Container",
    slots: {
        "children": ["text-1", "image-1", "button-1"]
    }
};

// App shell with named slots
const appShellWidget: Widget = {
    id: "app-shell-1",
    name: "AppShell",
    slots: {
        "header": ["navbar-1"],
        "sidebar": ["navigation-1"],
        "content": ["main-content-1"],
        "footer": ["footer-1"]
    }
};
```

## Widget Styling

Widgets support CSS styling through the styles property:

```typescript
interface WidgetCSSStyles {
    [key: string]: string;
}

const styledWidget: Widget = {
    id: "text-1",
    name: "Text",
    styles: {
        "color": "#333333",
        "fontSize": "18px",
        "fontWeight": "bold",
        "padding": "16px",
        "margin": "8px 0"
    }
};
```

### Styling Features

- **CSS Properties**: Direct CSS property mapping
- **Responsive Design**: Media query support
- **Theme Integration**: Automatic theme variable usage
- **Style Inheritance**: Parent-child style relationships

## Creating Custom Widgets

### 1. Create the Svelte Component

```svelte
<!-- CustomWidget.svelte -->
<script lang="ts">
    interface Props {
        title?: string;
        content?: string;
        showIcon?: boolean;
    }
    
    const { 
        title = 'Default Title',
        content = 'Default content',
        showIcon = false
    }: Props = $props();
</script>

<div class="custom-widget">
    {#if showIcon}
        <i class="fas fa-star"></i>
    {/if}
    <h3>{title}</h3>
    <p>{content}</p>
</div>
```

### 2. Define the Schema

```json
{
    "type": "object",
    "properties": {
        "title": {
            "type": "string",
            "title": "Widget Title",
            "default": "My Custom Widget"
        },
        "content": {
            "type": "string",
            "title": "Content",
            "x-type-schema": {
                "inputWidget": "textarea"
            }
        },
        "showIcon": {
            "type": "boolean",
            "title": "Show Icon",
            "default": false
        }
    }
}
```

### 3. Register the Widget

```typescript
// In widget-registry.ts
registerWidget({
    name: "CustomWidget",
    title: { en: "Custom Widget", de: "Benutzerdefiniertes Widget" },
    description: { 
        en: "A custom widget example",
        de: "Ein Beispiel für ein benutzerdefiniertes Widget"
    },
    category: "custom",
    visibleInCatalog: true,
    componentLoader: () => import("./CustomWidget.svelte"),
    dataSchema: () => import("./CustomWidget.schema.json")
});
```

## Widget Testing

### Unit Testing

```typescript
import { render } from '@testing-library/svelte';
import { describe, it, expect } from 'vitest';
import CustomWidget from './CustomWidget.svelte';

describe('CustomWidget', () => {
    it('renders with default props', () => {
        const { getByText } = render(CustomWidget);
        expect(getByText('Default Title')).toBeInTheDocument();
    });
    
    it('renders with custom data', () => {
        const data = { title: 'Test Title', content: 'Test Content' };
        const { getByText } = render(CustomWidget, { data });
        expect(getByText('Test Title')).toBeInTheDocument();
        expect(getByText('Test Content')).toBeInTheDocument();
    });
});
```

### Integration Testing

```typescript
import { test, expect } from '@playwright/test';

test('widget can be added and configured', async ({ page }) => {
    await page.goto('/editor');
    
    // Add widget to page
    await page.click('[data-testid="widget-picker"]');
    await page.click('[data-testid="custom-widget"]');
    
    // Configure widget
    await page.fill('[data-testid="title-input"]', 'Test Title');
    await page.fill('[data-testid="content-input"]', 'Test Content');
    
    // Verify widget is rendered
    await expect(page.locator('[data-testid="custom-widget"]')).toContainText('Test Title');
});
```

## Best Practices

### Widget Design

1. **Single Responsibility**: Each widget should have one clear purpose
2. **Configurability**: Expose relevant configuration options
3. **Accessibility**: Follow WCAG guidelines for all interactive elements
4. **Performance**: Optimize for fast loading and rendering
5. **Mobile-First**: Design for mobile devices first

### Schema Design

1. **Clear Naming**: Use descriptive property names
2. **Validation**: Include appropriate validation rules
3. **Defaults**: Provide sensible default values
4. **Documentation**: Include descriptions for all properties
5. **Localization**: Support multiple languages where appropriate

### Component Architecture

1. **Props Interface**: Define clear TypeScript interfaces
2. **Reactive Updates**: Use Svelte's reactivity system effectively
3. **Error Handling**: Handle invalid props gracefully
4. **Style Isolation**: Avoid global CSS conflicts
5. **Event Handling**: Emit events for parent communication

## Troubleshooting

### Common Issues

1. **Widget Not Loading**: Check dynamic import paths and component exports
2. **Schema Validation Errors**: Verify JSON schema syntax and property types
3. **Styling Conflicts**: Use CSS modules or scoped styles
4. **Performance Issues**: Optimize component re-rendering and data loading
5. **Registration Errors**: Ensure unique widget names and proper registration syntax

### Debugging Tools

1. **Widget Inspector**: Browser extension for widget tree visualization
2. **Schema Validator**: Online tool for schema validation
3. **Component Profiler**: Performance analysis for widget rendering
4. **Network Monitor**: Track widget loading and data fetching
5. **Console Logging**: Strategic logging for debugging widget behavior

---

## Next Steps

- Learn about [Widget Development](widget-development.md)
- Explore [Widget Categories](widget-categories.md)
- Understand [Widget Lifecycle](widget-lifecycle.md)
- Review [Widget Testing](widget-testing.md)
