inspector/specification/v2_ux_storybook.md
Cliff Hall bcb5b643ff
docs(apps): specify the v2 Apps tab (#1265)
Adds the missing Apps tab to the v2 UX specification. Apps are the subset
of tools whose `_meta.ui.resourceUri` advertises a UI resource (per
`@modelcontextprotocol/ext-apps`). The screen mirrors the legacy v1 layout:
a sidebar of apps and a right pane that toggles between an input form
(before launch) and the embedded MCP App via `AppBridge`.

- v2_ux.md: add `[Apps]` to the navigation tab list and a new "Apps Screen"
  subsection with both states (input form / running app).
- v2_ux_features.md: full Apps Screen feature spec — ASCII diagrams,
  detection rule, presentational contract for `AppsScreen` / `AppRenderer`
  (imperative ref shape so the screen stays "dumb" and the wiring layer
  drives the bridge).
- v2_screenshots.md: link the two reference PNGs from the legacy v1 client.
- v2_ux_storybook.md: add `AppListItem` (2.3a), `AppDetailPanel` (2.5a),
  `AppRenderer` (2.5b), and `AppsScreen` (3.2a); update Build Order.

Implementation work tracked on the v2 board: #1260–#1264.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 17:22:59 -04:00

1212 lines
46 KiB
Markdown

# Inspector V2 UX - Storybook Component Plan
### [Brief](README.md) | [V1 Problems](v1_problems.md) | [V2 Scope](v2_scope.md) | [V2 Tech Stack](v2_web_client.md) | V2 UX
#### [Overview](v2_ux.md) | [Features](v2_ux_features.md) | [Handlers](v2_ux_handlers.md) | [Screenshots](v2_screenshots.md) | [Components](v2_ux_components.md) | [Interfaces](v2_ux_interfaces.md) | Storybook
---
This document defines every presentational component to be built in Storybook using Mantine. Components are organized bottom-up: elements first, then groups, then screens. Each component receives all data and callbacks via props — no store access.
## Table of Contents
* [Conventions](#conventions)
* [Tier 1 - Elements](#tier-1---elements)
* [Tier 2 - Groups](#tier-2---groups)
* [Tier 3 - Screens](#tier-3---screens)
* [Tier 4 - Page Views](#tier-4---page-views)
* [Build Order](#build-order)
* [Storybook Configuration](#storybook-configuration)
---
## Conventions
### File Structure
```
src/components/
elements/
StatusIndicator/
StatusIndicator.tsx
StatusIndicator.stories.tsx
...
groups/
ServerCard/
ServerCard.tsx
ServerCard.stories.tsx
...
screens/
ToolsScreen/
ToolsScreen.tsx
ToolsScreen.stories.tsx
...
views/
AppShell/
AppShell.tsx
AppShell.stories.tsx
...
```
### Props Pattern
Every component exports a `Props` interface. Callbacks use the `onVerb` convention. Boolean flags use `canVerb` or `isState` naming.
```typescript
// Example
export interface StatusIndicatorProps {
status: 'connected' | 'connecting' | 'disconnected' | 'failed';
latencyMs?: number;
retryCount?: number;
}
```
### Story Pattern
Each component has at minimum a `Default` story plus stories for each meaningful state variation.
```typescript
const meta: Meta<typeof StatusIndicator> = {
component: StatusIndicator,
decorators: [MantineDecorator],
};
export const Connected: Story = { args: { status: 'connected', latencyMs: 23 } };
export const Failed: Story = { args: { status: 'failed', retryCount: 3 } };
```
---
## Tier 1 - Elements
Small, single-purpose components. Most map directly to a Mantine component with specific props/styling.
### 1.1 StatusIndicator
Connection status dot with label.
| Prop | Type | Description |
|------|------|-------------|
| `status` | `'connected' \| 'connecting' \| 'disconnected' \| 'failed'` | Current state |
| `latencyMs` | `number?` | Ping latency (shown when connected) |
| `retryCount` | `number?` | Retry attempts (shown when failed) |
**Mantine:** Custom component using `Box` for the dot + `Text`. Pulsing animation via CSS for `connecting`.
**Stories:** Connected, Connecting, Disconnected, Failed, FailedWithRetries
---
### 1.2 AnnotationBadge
Tag-style badge for tool/resource annotations.
| Prop | Type | Description |
|------|------|-------------|
| `label` | `string` | Badge text (e.g., "user", "read-only", "destructive") |
| `variant` | `'audience' \| 'readOnly' \| 'destructive' \| 'longRun' \| 'priority' \| 'default'` | Visual style |
**Mantine:** `Badge` with `variant` and `color` mapped from annotation type. Destructive = red outline, long-run = yellow, read-only = dark filled, audience = gray outline, priority = orange/yellow/green by level.
**Stories:** Audience, ReadOnly, Destructive, LongRun, PriorityHigh, PriorityMedium, PriorityLow, Custom
---
### 1.3 CapabilityItem
Single capability row with check/cross icon and label.
| Prop | Type | Description |
|------|------|-------------|
| `name` | `string` | Capability name |
| `supported` | `boolean` | Whether capability is available |
| `count` | `number?` | Optional count (e.g., "Tools (4)") |
**Mantine:** `Group` with `ThemeIcon` (check or X) + `Text`.
**Stories:** Supported, SupportedWithCount, NotSupported
---
### 1.4 LogEntry
Single log line with timestamp, level badge, and message.
| Prop | Type | Description |
|------|------|-------------|
| `timestamp` | `string` | Formatted time (e.g., "14:32:01") |
| `level` | `'debug' \| 'info' \| 'notice' \| 'warning' \| 'error' \| 'critical' \| 'alert' \| 'emergency'` | RFC 5424 level |
| `message` | `string` | Log message text |
| `logger` | `string?` | Logger name |
**Mantine:** `Group` with `Text` (timestamp) + `Badge` (level, color-coded) + `Text` (message). Level colors: debug=gray, info=blue, notice=teal, warning=yellow, error=red, critical=red+filled, alert=red+bold, emergency=red+filled+bold.
**Stories:** Debug, Info, Notice, Warning, Error, Critical, Alert, Emergency, WithLogger
---
### 1.5 ProgressDisplay
Progress bar with percentage and optional step description.
| Prop | Type | Description |
|------|------|-------------|
| `progress` | `number` | 0-100 percentage |
| `description` | `string?` | Current step text |
| `elapsed` | `string?` | Elapsed time display |
**Mantine:** `Progress` bar + `Text` for description and percentage.
**Stories:** ZeroPercent, HalfComplete, NearComplete, Complete, WithDescription, WithElapsed
---
### 1.6 ContentViewer
Renders text, JSON, image, or audio content with syntax highlighting.
| Prop | Type | Description |
|------|------|-------------|
| `type` | `'text' \| 'json' \| 'image' \| 'audio'` | Content type |
| `content` | `string` | The content (text, JSON string, base64, or URL) |
| `mimeType` | `string?` | MIME type for images/audio |
| `onCopy` | `() => void` | Copy callback |
**Mantine:** `Code` block for JSON/text (with `language` prop), `Image` for images, native `<audio>` for audio. Wrapped in `Paper` with light background.
**Stories:** PlainText, JsonContent, ImagePreview, AudioPlayer, LongContent
---
### 1.7 ListChangedIndicator
Yellow dot + "List updated" text + Refresh button.
| Prop | Type | Description |
|------|------|-------------|
| `visible` | `boolean` | Whether to show the indicator |
| `onRefresh` | `() => void` | Refresh callback |
**Mantine:** `Group` with animated `Box` (yellow dot) + `Text` + `Button` with refresh icon.
**Stories:** Visible, Hidden
---
### 1.8 CopyButton
Icon button that copies text and shows brief confirmation.
| Prop | Type | Description |
|------|------|-------------|
| `value` | `string` | Text to copy |
**Mantine:** `CopyButton` (built-in) with `ActionIcon` and `Tooltip`.
**Stories:** Default, LongValue
---
### 1.9 ConnectionToggle
Switch control for connecting/disconnecting a server.
| Prop | Type | Description |
|------|------|-------------|
| `checked` | `boolean` | On/off state |
| `loading` | `boolean` | Show loading state during connection |
| `disabled` | `boolean` | Disable interaction |
| `onChange` | `(checked: boolean) => void` | Toggle callback |
**Mantine:** `Switch` with `size="lg"`.
**Stories:** Connected, Disconnected, Loading, Disabled
---
### 1.10 TransportBadge
Shows transport type (STDIO, HTTP) as a styled badge.
| Prop | Type | Description |
|------|------|-------------|
| `transport` | `'stdio' \| 'http'` | Transport type |
**Mantine:** `Badge` with `variant="outline"`.
**Stories:** Stdio, Http
---
### 1.11 InlineError
Expandable error display with retry count and doc links.
| Prop | Type | Description |
|------|------|-------------|
| `message` | `string` | Short error message |
| `details` | `string?` | Full error text (shown on expand) |
| `retryCount` | `number?` | Current retry attempt |
| `maxRetries` | `number?` | Max retry attempts |
| `onShowMore` | `() => void` | Expand callback |
| `docLink` | `string?` | URL to troubleshooting guide |
**Mantine:** `Alert` with `color="red"`, `Spoiler` for expandable details.
**Stories:** ShortError, LongErrorCollapsed, LongErrorExpanded, WithRetryCount, WithDocLink
---
### 1.12 MessageBubble
Single chat message with role label and content (text, image, or audio).
| Prop | Type | Description |
|------|------|-------------|
| `index` | `number` | Message index |
| `role` | `'user' \| 'assistant'` | Message role |
| `content` | `string` | Text content |
| `imageContent` | `{ data: string; mimeType: string }?` | Optional image |
| `audioContent` | `{ data: string; mimeType: string }?` | Optional audio |
**Mantine:** `Paper` with subtle background, `Text` for role label, content display.
**Stories:** UserText, AssistantText, UserWithImage, UserWithAudio, LongMessage
---
## Tier 2 - Groups
Composed from elements. Represent distinct functional units within a screen.
### 2.1 ServerCard
Server connection card with status, transport, command, and action buttons.
| Prop | Type | Description |
|------|------|-------------|
| `name` | `string` | Server name |
| `version` | `string?` | Server version |
| `transport` | `'stdio' \| 'http'` | Transport type |
| `command` | `string` | Command or URL |
| `status` | `'connected' \| 'connecting' \| 'disconnected' \| 'failed'` | Connection state |
| `retryCount` | `number?` | Failed retry count |
| `error` | `{ message: string; details?: string }?` | Error info |
| `onToggleConnection` | `(connect: boolean) => void` | Connect/disconnect |
| `onCopyCommand` | `() => void` | Copy command |
| `onServerInfo` | `() => void` | View server info |
| `onSettings` | `() => void` | Open settings |
| `onEdit` | `() => void` | Edit server |
| `onClone` | `() => void` | Clone server |
| `onRemove` | `() => void` | Remove server |
**Mantine:** `Card` with `Card.Section` dividers. Uses StatusIndicator, TransportBadge, ConnectionToggle, CopyButton, InlineError elements.
**Stories:** Connected, Disconnected, Connecting, Failed, FailedWithError, HttpDirect, LongCommand
---
### 2.2 AddServerMenu
Dropdown button with options: Add manually, Import config, Import server.json.
| Prop | Type | Description |
|------|------|-------------|
| `onAddManually` | `() => void` | Add manually callback |
| `onImportConfig` | `() => void` | Import config callback |
| `onImportServerJson` | `() => void` | Import server.json callback |
**Mantine:** `Menu` with `Button` trigger ("+ Add Server" with chevron).
**Stories:** Default (closed), Open
---
### 2.3 ToolListItem
Single tool entry in the sidebar list with name and annotation badges.
| Prop | Type | Description |
|------|------|-------------|
| `name` | `string` | Tool name |
| `annotations` | `AnnotationBadge[]` | Array of annotation badges |
| `selected` | `boolean` | Whether this tool is currently selected |
| `onClick` | `() => void` | Selection callback |
**Mantine:** `NavLink` or `UnstyledButton` with `Group` of AnnotationBadge children. Active state uses background highlight.
**Stories:** Default, Selected, WithAnnotations, MultipleAnnotations, LongName
---
### 2.3a AppListItem
Single MCP App entry in the sidebar list. Mirrors `ToolListItem` but renders the app's `tool.icons` (real MCP `Tool.icons`) when present and surfaces a chevron affordance.
| Prop | Type | Description |
|------|------|-------------|
| `tool` | `Tool` | App tool (real MCP `Tool` type) |
| `selected` | `boolean` | Whether this app is currently selected |
| `onClick` | `() => void` | Selection callback |
**Mantine:** `UnstyledButton` with `Group` (Icon + label/description + ChevronRight). Reuses the same active-state background highlight pattern as `ToolListItem`.
**Stories:** Default, Selected, WithIcon, NoDescription, LongName
> Alternative: fold icon support into `ToolListItem` and reuse it on the Apps screen. Decide during implementation — see issue "Tool icon support on tool list items".
---
### 2.4 ToolDetailPanel
Tool name, description, annotations display, and generated parameter form.
| Prop | Type | Description |
|------|------|-------------|
| `name` | `string` | Tool name |
| `description` | `string?` | Tool description |
| `annotations` | `object?` | Annotations object (audience, readOnly, destructive, etc.) |
| `schema` | `object` | JSON Schema for tool input |
| `formValues` | `Record<string, unknown>` | Current form values |
| `isExecuting` | `boolean` | Whether tool is currently executing |
| `progress` | `{ percent: number; description?: string }?` | Execution progress |
| `onFormChange` | `(values: Record<string, unknown>) => void` | Form value change |
| `onExecute` | `() => void` | Execute tool callback |
| `onCancel` | `() => void` | Cancel execution callback |
**Mantine:** `Stack` with `Title`, `Text`, AnnotationBadge group, SchemaForm (see 2.12), `Button` for execute, ProgressDisplay.
**Stories:** SimpleStringParam, MultipleParams, WithAnnotations, Executing, WithProgress, ComplexSchema
---
### 2.5 ResultPanel
Displays tool execution results with copy/clear actions.
| Prop | Type | Description |
|------|------|-------------|
| `content` | `Array<{ type: string; text?: string; data?: string; mimeType?: string }>` | Result content items |
| `onCopy` | `() => void` | Copy result |
| `onClear` | `() => void` | Clear result |
**Mantine:** `Paper` with ContentViewer for each content item, `Group` with action buttons.
**Stories:** TextResult, JsonResult, ImageResult, AudioResult, MixedContent, Empty
---
### 2.5a AppDetailPanel
App name, description, optional icon, and generated `App Input` form. Mirrors `ToolDetailPanel` but the action button is **Open App** (no annotations, no progress display, no cancel — execution is delegated to the embedded app).
| Prop | Type | Description |
|------|------|-------------|
| `tool` | `Tool` | App tool (real MCP `Tool` type) |
| `formValues` | `Record<string, unknown>` | Current form values |
| `isOpening` | `boolean` | Whether the app is currently being launched |
| `onFormChange` | `(values: Record<string, unknown>) => void` | Form value change |
| `onOpenApp` | `() => void` | Open App callback |
**Mantine:** `Stack` with icon + `Title` (`tool.title ?? tool.name`), description `Text`, SchemaForm (see 2.12), full-width `Button` with `IconPlayerPlay` for open. Disabled while `isOpening` or while the form has validation errors.
**Stories:** NoFields, SimpleStringParam, MultipleParams, WithIcon, Opening, ComplexSchema
---
### 2.5b AppRenderer
Embeds an MCP App in a sandboxed iframe and exposes an imperative ref so the wiring layer can drive the bridge. The component is a thin shell — it owns the iframe element and the `AppBridge` lifecycle but does **not** know about the active MCP `Client`. The wiring layer constructs the bridge (real MCP `Client` + `Implementation` + `McpUiHostCapabilities`) and passes it in via the `bridgeFactory` callback when the iframe mounts.
| Prop | Type | Description |
|------|------|-------------|
| `sandboxPath` | `string` | URL/path of the sandbox proxy iframe |
| `tool` | `Tool` | The selected app tool (for resource URI lookup) |
| `bridgeFactory` | `(iframe: HTMLIFrameElement) => AppBridge` | Wiring layer creates the bridge from the active MCP client |
| `onError` | `(err: Error) => void` | Error notification callback |
**Imperative ref (`AppRendererHandle`):**
| Method | Description |
|--------|-------------|
| `sendToolInput(args)` | Push complete tool arguments to the app |
| `sendToolResult(result)` | Push the `CallToolResult` from the server |
| `sendToolCancelled(reason)` | Notify the app that execution was cancelled |
| `teardown()` | Request graceful shutdown before unmounting |
**Mantine:** `Box` with `iframe` element. No Mantine controls — display logic only. The renderer treats the bridge handle and iframe element as runtime state, not props, but exposes the bridge actions through `useImperativeHandle`.
**Stories:** Loading, Loaded (with mock bridge), Error, Maximized
---
### 2.6 ResourceListItem
Single resource entry with name and annotation badges.
| Prop | Type | Description |
|------|------|-------------|
| `name` | `string` | Resource display name |
| `uri` | `string` | Resource URI |
| `annotations` | `object?` | Audience and priority annotations |
| `selected` | `boolean` | Selection state |
| `onClick` | `() => void` | Selection callback |
**Mantine:** Similar to ToolListItem with AnnotationBadge.
**Stories:** Default, Selected, WithAnnotations, WithHighPriority
---
### 2.7 ResourcePreviewPanel
Resource content display with URI, MIME type, annotations, and subscribe/copy actions.
| Prop | Type | Description |
|------|------|-------------|
| `uri` | `string` | Resource URI |
| `mimeType` | `string` | MIME type |
| `annotations` | `object?` | Annotations |
| `content` | `string` | Resource content |
| `lastUpdated` | `string?` | Last update timestamp |
| `isSubscribed` | `boolean` | Subscription state |
| `onCopy` | `() => void` | Copy content |
| `onSubscribe` | `() => void` | Subscribe callback |
| `onUnsubscribe` | `() => void` | Unsubscribe callback |
**Mantine:** `Stack` with metadata `Text` items, ContentViewer, action `Button` group.
**Stories:** JsonResource, TextResource, ImageResource, Subscribed, NotSubscribed, WithAnnotations
---
### 2.8 ResourceTemplateInput
Template with inline variable inputs and Go button.
| Prop | Type | Description |
|------|------|-------------|
| `template` | `string` | URI template (e.g., "user/{id}") |
| `variables` | `Record<string, string>` | Current variable values |
| `onVariableChange` | `(name: string, value: string) => void` | Variable change |
| `onSubmit` | `() => void` | Navigate/fetch callback |
**Mantine:** `Group` with `Text` for template prefix, `TextInput` for each variable, `Button` for Go.
**Stories:** SingleVariable, MultipleVariables, FilledIn
---
### 2.9 PromptArgumentsForm
Prompt selection dropdown, description, argument form, and Get Prompt button.
| Prop | Type | Description |
|------|------|-------------|
| `prompts` | `Array<{ name: string; description?: string }>` | Available prompts |
| `selectedPrompt` | `string?` | Currently selected prompt name |
| `arguments` | `Array<{ name: string; required: boolean; description?: string }>` | Prompt arguments |
| `argumentValues` | `Record<string, string>` | Current argument values |
| `onSelectPrompt` | `(name: string) => void` | Prompt selection |
| `onArgumentChange` | `(name: string, value: string) => void` | Argument value change |
| `onGetPrompt` | `() => void` | Execute prompt |
**Mantine:** `Select` for prompt dropdown, `TextInput` for each argument with required indicator, `Button` for Get Prompt. Description shown as `Text` with dimmed color.
**Stories:** NoSelection, Selected, WithRequiredArgs, AllFilled, ManyArguments
---
### 2.10 PromptMessagesDisplay
Rendered prompt result showing role-labeled messages.
| Prop | Type | Description |
|------|------|-------------|
| `messages` | `Array<{ role: string; content: string; imageContent?: object; audioContent?: object }>` | Prompt messages |
| `onCopy` | `() => void` | Copy messages |
**Mantine:** `Stack` of MessageBubble elements, `Button` for copy.
**Stories:** SingleMessage, Conversation, WithImage, WithAudio, LongConversation
---
### 2.11 LogControls
Log level selector, text filter, level checkboxes, clear/export buttons.
| Prop | Type | Description |
|------|------|-------------|
| `currentLevel` | `string` | Active log level |
| `filterText` | `string` | Text filter value |
| `visibleLevels` | `Record<string, boolean>` | Which levels are checked |
| `onSetLevel` | `(level: string) => void` | Set log level callback |
| `onFilterChange` | `(text: string) => void` | Filter text change |
| `onToggleLevel` | `(level: string, visible: boolean) => void` | Toggle level visibility |
| `onClear` | `() => void` | Clear logs |
| `onExport` | `() => void` | Export logs |
**Mantine:** `Select` for level + `Button` "Set Level", `TextInput` for filter, `Checkbox.Group` for levels (each color-coded), `Group` with action buttons.
**Stories:** AllLevelsVisible, FilteredLevels, WithFilterText, DebugLevel, ErrorLevel
---
### 2.12 SchemaForm
Dynamic form generated from JSON Schema. Core reusable component for tool params, elicitation forms, and prompt arguments.
| Prop | Type | Description |
|------|------|-------------|
| `schema` | `object` | JSON Schema definition |
| `values` | `Record<string, unknown>` | Current form values |
| `onChange` | `(values: Record<string, unknown>) => void` | Value change callback |
| `disabled` | `boolean?` | Disable all fields |
**Mantine:** Maps schema types to Mantine inputs: `TextInput` (string), `NumberInput` (number/integer), `Checkbox` (boolean), `Select` (enum/oneOf), `MultiSelect` (array+anyOf), `JsonInput` (complex fallback). Uses `Stack` layout. Required fields marked with `withAsterisk`.
**Stories:** StringFields, NumberFields, BooleanFields, EnumDropdown, TitledEnum, MultiSelectEnum, MixedTypes, NestedObject, ArrayField, ComplexFallback, AllRequired, WithDefaults, WithDescriptions
---
### 2.13 TaskCard
Single task card with status, progress, method info, and action buttons.
| Prop | Type | Description |
|------|------|-------------|
| `taskId` | `string` | Task identifier |
| `status` | `'waiting' \| 'running' \| 'completed' \| 'failed' \| 'cancelled'` | Task state |
| `method` | `string` | MCP method (e.g., "tools/call") |
| `target` | `string?` | Tool name or resource URI |
| `progress` | `number?` | 0-100 progress percentage |
| `progressDescription` | `string?` | Current step text |
| `startedAt` | `string?` | Start timestamp |
| `completedAt` | `string?` | Completion timestamp |
| `elapsed` | `string?` | Elapsed or duration string |
| `error` | `string?` | Error message (for failed tasks) |
| `onViewDetails` | `() => void` | View details callback |
| `onViewResult` | `() => void` | View result callback |
| `onCancel` | `() => void` | Cancel task callback |
| `onDismiss` | `() => void` | Dismiss from list |
**Mantine:** `Card` with `Group` for header (task ID + status Badge + progress), `Text` for metadata, `Group` for action buttons. Status badge colors: waiting=gray, running=blue, completed=green, failed=red, cancelled=yellow.
**Stories:** Running, RunningWithProgress, Waiting, Completed, Failed, FailedWithError, Cancelled
---
### 2.14 HistoryEntry
Single request history entry with expand/collapse, replay, and pin.
| Prop | Type | Description |
|------|------|-------------|
| `timestamp` | `string` | Request timestamp |
| `method` | `string` | MCP method |
| `target` | `string?` | Tool/resource/prompt name |
| `status` | `'success' \| 'error'` | Result status |
| `durationMs` | `number` | Response time |
| `parameters` | `object?` | Request parameters |
| `response` | `object?` | Response data |
| `children` | `HistoryEntry[]?` | Nested child requests (sampling/elicitation) |
| `isPinned` | `boolean` | Pin state |
| `isExpanded` | `boolean` | Expanded state |
| `onToggleExpand` | `() => void` | Expand/collapse |
| `onReplay` | `() => void` | Replay request |
| `onTogglePin` | `() => void` | Pin/unpin |
**Mantine:** `Card` with collapsible `Collapse` section. Header `Group` with timestamp, method `Badge`, target, status Badge (green/red), duration. Children rendered indented with connector lines via CSS.
**Stories:** SuccessCollapsed, SuccessExpanded, Error, WithChildren, Pinned, PinnedWithLabel, DeepNesting
---
### 2.15 SamplingRequestPanel
Displays a sampling request with messages, model preferences, parameters, and response input.
| Prop | Type | Description |
|------|------|-------------|
| `messages` | `Array<{ role: string; content: string; imageContent?: object }>` | Request messages |
| `modelHints` | `string[]?` | Model hint strings |
| `costPriority` | `number?` | 0-1 cost priority |
| `speedPriority` | `number?` | 0-1 speed priority |
| `intelligencePriority` | `number?` | 0-1 intelligence priority |
| `maxTokens` | `number?` | Max tokens |
| `stopSequences` | `string[]?` | Stop sequences |
| `temperature` | `number?` | Temperature |
| `includeContext` | `string?` | Context inclusion |
| `tools` | `Array<{ name: string; description?: string; inputSchema: object }>?` | Available tools for tool-enabled sampling |
| `toolChoice` | `string?` | Tool choice mode |
| `responseText` | `string` | Current response text |
| `modelUsed` | `string` | Model used field |
| `stopReason` | `string` | Stop reason |
| `onResponseChange` | `(text: string) => void` | Response text change |
| `onModelChange` | `(model: string) => void` | Model field change |
| `onStopReasonChange` | `(reason: string) => void` | Stop reason change |
| `onAutoRespond` | `() => void` | Auto-respond with profile |
| `onSend` | `() => void` | Send response |
| `onReject` | `() => void` | Reject request |
**Mantine:** `Stack` with MessageBubble list, `Paper` for model preferences (Badge chips + Slider displays), parameter `Text` items, `Textarea` for response, `TextInput` for model, `Select` for stop reason, action `Button` group.
**Stories:** SimpleRequest, WithModelHints, WithPriorities, WithAllParams, WithImage, WithTools, WithToolChoice, PrefilledResponse, InlineCompact
---
### 2.16 ElicitationFormPanel
Form-based elicitation with JSON Schema-generated fields and security warning.
| Prop | Type | Description |
|------|------|-------------|
| `message` | `string` | Server's message/prompt |
| `schema` | `object` | JSON Schema for form fields |
| `values` | `Record<string, unknown>` | Current form values |
| `serverName` | `string` | Requesting server name (for warning) |
| `onChange` | `(values: Record<string, unknown>) => void` | Form change |
| `onSubmit` | `() => void` | Submit form |
| `onCancel` | `() => void` | Cancel/decline |
**Mantine:** `Stack` with message `Text`, `Divider`, SchemaForm, `Alert` with warning icon and server name, action `Button` group.
**Stories:** SimpleForm, ComplexForm, WithEnums, AllRequired, WithDefaults, LongMessage
---
### 2.17 ElicitationUrlPanel
URL-based elicitation with copy, open, and waiting indicator.
| Prop | Type | Description |
|------|------|-------------|
| `message` | `string` | Server's message |
| `url` | `string` | URL to visit |
| `elicitationId` | `string` | Elicitation ID |
| `isWaiting` | `boolean` | Waiting for completion |
| `onCopyUrl` | `() => void` | Copy URL |
| `onOpenInBrowser` | `() => void` | Open URL |
| `onCancel` | `() => void` | Cancel |
**Mantine:** `Stack` with message `Text`, `Code` block for URL, `Button` group (Copy, Open in Browser), `Loader` + status text, `Alert` with domain warning.
**Stories:** Waiting, WithLongUrl, Completed
---
### 2.18 RootsTable
Table of configured roots with remove actions and add form.
| Prop | Type | Description |
|------|------|-------------|
| `roots` | `Array<{ name: string; uri: string }>` | Configured roots |
| `newRootName` | `string` | New root name field |
| `newRootPath` | `string` | New root path field |
| `onRemoveRoot` | `(uri: string) => void` | Remove root |
| `onNewRootNameChange` | `(name: string) => void` | Name field change |
| `onNewRootPathChange` | `(path: string) => void` | Path field change |
| `onAddRoot` | `() => void` | Add root |
| `onBrowse` | `() => void` | Browse for path |
**Mantine:** `Table` for roots list with `ActionIcon` delete buttons, `Divider`, `TextInput` fields for name/path, `Group` with Browse + Add buttons, `Alert` with security warning.
**Stories:** WithRoots, Empty, AddingNew, ManyRoots
---
### 2.19 ServerInfoContent
Server metadata, capabilities, and instructions display (modal body content).
| Prop | Type | Description |
|------|------|-------------|
| `name` | `string` | Server name |
| `version` | `string` | Server version |
| `protocolVersion` | `string` | Protocol version |
| `transport` | `string` | Transport type |
| `serverCapabilities` | `Array<{ name: string; supported: boolean; count?: number }>` | Server caps |
| `clientCapabilities` | `Array<{ name: string; supported: boolean; count?: number }>` | Client caps |
| `instructions` | `string?` | Server instructions text |
| `oauthDetails` | `{ authUrl?: string; scopes?: string[]; accessToken?: string }?` | OAuth info |
**Mantine:** `SimpleGrid` cols={2} for metadata pairs, two-column CapabilityItem lists, `Blockquote` for instructions, `Paper` for OAuth details.
**Stories:** FullCapabilities, MinimalCapabilities, WithInstructions, WithOAuth, NoExperimental
---
### 2.20 ServerSettingsForm
Server settings form with headers, metadata, timeouts, and OAuth.
| Prop | Type | Description |
|------|------|-------------|
| `headers` | `Array<{ key: string; value: string }>` | Custom headers |
| `metadata` | `Array<{ key: string; value: string }>` | Request metadata |
| `connectionTimeout` | `number` | Connection timeout ms |
| `requestTimeout` | `number` | Request timeout ms |
| `oauthClientId` | `string?` | OAuth client ID |
| `oauthClientSecret` | `string?` | OAuth client secret |
| `oauthScopes` | `string?` | OAuth scopes |
| `onConnectionModeChange` | `(mode: string) => void` | Mode change |
| `onAddHeader` | `() => void` | Add header |
| `onRemoveHeader` | `(index: number) => void` | Remove header |
| `onHeaderChange` | `(index: number, key: string, value: string) => void` | Edit header |
| `onAddMetadata` | `() => void` | Add metadata |
| `onRemoveMetadata` | `(index: number) => void` | Remove metadata |
| `onMetadataChange` | `(index: number, key: string, value: string) => void` | Edit metadata |
| `onTimeoutChange` | `(field: string, value: number) => void` | Timeout change |
| `onOAuthChange` | `(field: string, value: string) => void` | OAuth field change |
**Mantine:** `Stack` with `Select` for connection mode, key-value pair sections using `Group` of `TextInput` pairs with `ActionIcon` remove buttons, `NumberInput` for timeouts, `TextInput` for OAuth fields, `Divider` between sections.
**Stories:** DefaultSettings, WithHeaders, WithMetadata, WithOAuth, AllConfigured
---
### 2.21 ImportServerJsonPanel
JSON editor with validation results, package selection, env var form, and name override.
| Prop | Type | Description |
|------|------|-------------|
| `jsonContent` | `string` | Current JSON content |
| `validationResults` | `Array<{ type: 'success' \| 'warning' \| 'info' \| 'error'; message: string }>` | Validation items |
| `packages` | `Array<{ registryType: string; identifier: string; runtimeHint: string }>?` | Available packages |
| `selectedPackageIndex` | `number` | Selected package |
| `envVars` | `Array<{ name: string; description?: string; required: boolean; value: string }>` | Environment variables |
| `serverName` | `string` | Override name |
| `onJsonChange` | `(content: string) => void` | JSON content change |
| `onValidate` | `() => void` | Re-validate |
| `onSelectPackage` | `(index: number) => void` | Select package |
| `onEnvVarChange` | `(name: string, value: string) => void` | Env var change |
| `onServerNameChange` | `(name: string) => void` | Name change |
| `onAddServer` | `() => void` | Add server |
| `onCancel` | `() => void` | Cancel |
**Mantine:** `Textarea` or `JsonInput` for JSON, `List` with icons for validation results, `Radio.Group` for package selection, `TextInput` fields for env vars (required marked), `TextInput` for name, action `Button` group.
**Stories:** Empty, ValidJson, InvalidJson, MultiplePackages, WithEnvVars, FullyConfigured
---
### 2.22 ExperimentalFeaturesPanel
Server/client experimental capabilities + raw JSON-RPC tester.
| Prop | Type | Description |
|------|------|-------------|
| `serverCapabilities` | `Array<{ name: string; description?: string; methods?: string[] }>` | Server experimental caps |
| `clientCapabilities` | `Array<{ name: string; enabled: boolean }>` | Client experimental caps |
| `requestJson` | `string` | JSON-RPC request editor content |
| `responseJson` | `string?` | JSON-RPC response |
| `customHeaders` | `Array<{ key: string; value: string }>` | Custom headers for request |
| `requestHistory` | `Array<{ timestamp: string; method: string; status: string; durationMs: number }>` | Request history |
| `onToggleClientCapability` | `(name: string, enabled: boolean) => void` | Toggle cap |
| `onRequestChange` | `(json: string) => void` | Request JSON change |
| `onSendRequest` | `() => void` | Send request |
| `onAddHeader` | `() => void` | Add header |
| `onRemoveHeader` | `(index: number) => void` | Remove header |
| `onHeaderChange` | `(index: number, key: string, value: string) => void` | Edit header |
| `onCopyResponse` | `() => void` | Copy response |
**Mantine:** `Stack` with `Alert` warning, server caps as `Card` list with Test buttons, `Checkbox` group for client caps, `Divider`, `JsonInput` for request/response, header key-value pairs, action `Button`, history `Table`.
**Stories:** WithServerCaps, NoServerCaps, WithResponse, WithHistory, WithCustomHeaders
---
### 2.23 InlineSamplingRequest
Compact inline sampling request shown during tool execution.
| Prop | Type | Description |
|------|------|-------------|
| `queuePosition` | `string` | e.g., "1 of 2" |
| `modelHints` | `string[]?` | Model hints |
| `messagePreview` | `string` | Truncated first message |
| `responseText` | `string` | Response text |
| `onAutoRespond` | `() => void` | Auto-respond |
| `onEditAndSend` | `() => void` | Edit & send |
| `onReject` | `() => void` | Reject |
| `onViewDetails` | `() => void` | Expand to full view |
**Mantine:** `Paper` with `Group` header (type badge + queue position), compact content, `Textarea` for response, action `Button` group.
**Stories:** Default, WithModelHints, PrefilledResponse, InQueue
---
### 2.24 InlineElicitationRequest
Compact inline elicitation (form or URL) shown during tool execution.
| Prop | Type | Description |
|------|------|-------------|
| `mode` | `'form' \| 'url'` | Elicitation mode |
| `message` | `string` | Server message |
| `queuePosition` | `string` | e.g., "2 of 2" |
| `schema` | `object?` | JSON Schema (form mode) |
| `values` | `Record<string, unknown>?` | Form values |
| `url` | `string?` | URL (url mode) |
| `isWaiting` | `boolean?` | Waiting state (url mode) |
| `onChange` | `(values: Record<string, unknown>) => void` | Form change |
| `onSubmit` | `() => void` | Submit/confirm |
| `onCancel` | `() => void` | Cancel |
**Mantine:** `Paper` with header badge, compact SchemaForm or URL display, action buttons.
**Stories:** FormMode, UrlMode, UrlWaiting
---
### 2.25 PendingClientRequests
Container for inline sampling/elicitation requests during tool execution.
| Prop | Type | Description |
|------|------|-------------|
| `count` | `number` | Total pending requests |
| `children` | `ReactNode` | InlineSamplingRequest and/or InlineElicitationRequest |
**Mantine:** `Alert` with count header, `Stack` of children.
**Stories:** SingleSampling, SingleElicitation, MultipleMixed
---
## Tier 3 - Screens
Full screen sections composed of groups. These are the main content areas.
### 3.1 ServerListScreen
Home screen with server card grid and add button.
| Prop | Type | Description |
|------|------|-------------|
| `servers` | `ServerCard props[]` | Array of server data |
| `onAddManually` | `() => void` | Add manually |
| `onImportConfig` | `() => void` | Import config |
| `onImportServerJson` | `() => void` | Import server.json |
**Mantine:** `Container` with `Title` "MCP Inspector" header, `Group` with AddServerMenu, `SimpleGrid` cols={{ base: 1, md: 2 }} of ServerCard components.
**Stories:** MultipleServers, SingleServer, Empty, MixedStates
---
### 3.2 ToolsScreen
Three-panel layout: tool list, detail/form, and results.
| Prop | Type | Description |
|------|------|-------------|
| `tools` | `ToolListItem props[]` | Tool list data |
| `selectedTool` | `ToolDetailPanel props?` | Selected tool details |
| `result` | `ResultPanel props?` | Execution result |
| `listChanged` | `boolean` | List changed indicator |
| `searchText` | `string` | Search filter |
| `pendingRequests` | `PendingClientRequests props?` | Inline client requests |
| `onSearchChange` | `(text: string) => void` | Search change |
| `onRefreshList` | `() => void` | Refresh tool list |
| `onSelectTool` | `(name: string) => void` | Select tool |
| (tool detail & result callbacks forwarded) | | |
**Mantine:** Three-column layout using `Grid` (col spans ~3/5/4 of 12) or CSS Grid. Left panel: ListChangedIndicator + `TextInput` search + ToolListItem stack. Center: ToolDetailPanel with PendingClientRequests below. Right: ResultPanel.
**Stories:** NoSelection, ToolSelected, WithResult, Executing, WithProgress, WithListChanged, WithPendingRequests, WithSearch
---
### 3.2a AppsScreen
Two-panel layout: app list on the left, dynamic right pane that toggles between an input form (before launch) and the embedded `AppRenderer` (after launch). Receives the **already-filtered** subset of tools that are MCP Apps; filtering happens in the wiring layer via the shared `isAppTool` helper in `core/mcp/apps`.
| Prop | Type | Description |
|------|------|-------------|
| `tools` | `Tool[]` | Apps (already filtered to those with `_meta.ui.resourceUri`) |
| `listChanged` | `boolean` | List changed indicator |
| `sandboxPath` | `string` | Sandbox proxy path passed through to `AppRenderer` |
| `bridgeFactory` | `(iframe) => AppBridge` | Wiring layer creates the bridge from the active MCP client |
| `rendererRef` | `Ref<AppRendererHandle>` | Imperative ref forwarded to the embedded `AppRenderer` |
| `onRefreshList` | `() => void` | Refresh apps list |
| `onSelectApp` | `(name: string) => void` | App selection callback |
| `onOpenApp` | `(name: string, args: Record<string, unknown>) => void` | Open App callback (wiring runs `tools/call` and pushes result via the ref) |
| `onCloseApp` | `() => void` | Close currently-open app |
**Mantine:** Two-column layout. Left: `Card` with ListChangedIndicator, search `TextInput`, AppListItem stack, "Refresh Apps" `Button`. Right: `Card` whose content swaps between AppDetailPanel (form state) and AppRenderer (running state) plus a "Back to Input" / Maximize / Close header. Maximize hides the sidebar by widening the right card to full width.
**Stories:** NoSelection, AppSelected, AppRunning, AppRunningMaximized, NoFieldsApp (auto-launches on select), WithListChanged, Empty (no apps available)
---
### 3.3 ResourcesScreen
Two-panel layout: resource list (with accordion sections) and content preview.
| Prop | Type | Description |
|------|------|-------------|
| `resources` | `ResourceListItem props[]` | Resources |
| `templates` | `ResourceTemplateInput props[]` | Templates |
| `subscriptions` | `Array<{ name: string; lastUpdated?: string }>` | Active subscriptions |
| `selectedResource` | `ResourcePreviewPanel props?` | Selected resource preview |
| `listChanged` | `boolean` | List changed indicator |
| `searchText` | `string` | Search filter |
| `onSearchChange` | `(text: string) => void` | Search change |
| `onRefreshList` | `() => void` | Refresh |
| `onSelectResource` | `(uri: string) => void` | Select resource |
| (preview callbacks forwarded) | | |
**Mantine:** Two-column `Grid`. Left: ListChangedIndicator, `TextInput` search, `Accordion` with Resources/Templates/Subscriptions sections. Right: ResourcePreviewPanel.
**Stories:** WithResources, WithTemplates, WithSubscriptions, ResourceSelected, Empty, WithListChanged
---
### 3.4 PromptsScreen
Two-panel layout: prompt selection/form and message results.
| Prop | Type | Description |
|------|------|-------------|
| `promptForm` | `PromptArgumentsForm props` | Left panel props |
| `messages` | `PromptMessagesDisplay props?` | Right panel props |
| `listChanged` | `boolean` | List changed indicator |
| `onRefreshList` | `() => void` | Refresh |
**Mantine:** Two-column `Grid`. Left: ListChangedIndicator + PromptArgumentsForm. Right: PromptMessagesDisplay.
**Stories:** NoSelection, PromptSelected, WithResult, WithListChanged, ManyArguments
---
### 3.5 LoggingScreen
Two-panel layout: controls and log stream.
| Prop | Type | Description |
|------|------|-------------|
| `controls` | `LogControls props` | Left panel controls |
| `entries` | `LogEntry props[]` | Log entries |
| `autoScroll` | `boolean` | Auto-scroll state |
| `onToggleAutoScroll` | `() => void` | Toggle auto-scroll |
| `onCopyAll` | `() => void` | Copy all logs |
**Mantine:** Two-column `Grid` (~3/9 split). Left: LogControls. Right: `Paper` with header (Auto-scroll `Checkbox` + Copy All `Button`), `ScrollArea` with LogEntry stack.
**Stories:** Empty, WithEntries, MixedLevels, FilteredView, ManyEntries
---
### 3.6 TasksScreen
Grouped task cards: active and completed sections.
| Prop | Type | Description |
|------|------|-------------|
| `activeTasks` | `TaskCard props[]` | Active/waiting tasks |
| `completedTasks` | `TaskCard props[]` | Completed/failed/cancelled tasks |
| `onRefresh` | `() => void` | Refresh tasks |
| `onClearHistory` | `() => void` | Clear completed tasks |
**Mantine:** `Stack` with `Title` + Refresh `Button`, "Active Tasks" section header + TaskCard list, "Completed Tasks" section header with Clear History `Button` + TaskCard list.
**Stories:** ActiveOnly, CompletedOnly, Mixed, Empty, ManyTasks
---
### 3.7 HistoryScreen
Request history list with search, filter, pinned section.
| Prop | Type | Description |
|------|------|-------------|
| `entries` | `HistoryEntry props[]` | History entries |
| `pinnedEntries` | `HistoryEntry props[]` | Pinned entries |
| `searchText` | `string` | Search filter |
| `methodFilter` | `string?` | Method type filter |
| `totalCount` | `number` | Total entries |
| `displayedCount` | `number` | Currently shown |
| `onSearchChange` | `(text: string) => void` | Search change |
| `onMethodFilterChange` | `(method: string) => void` | Filter change |
| `onClearAll` | `() => void` | Clear history |
| `onExport` | `() => void` | Export JSON |
| (entry callbacks forwarded) | | |
**Mantine:** `Paper` with header `Group` (Title + `TextInput` search + `Select` filter + Export `Button` + Clear `Button`), `Stack` of HistoryEntry, "Pinned Requests" `Divider` + pinned entries.
**Stories:** WithEntries, WithPinned, WithNestedChildren, Filtered, Empty, ManyEntries
---
## Tier 4 - Page Views
### 4.1 HomeLayout
Layout for the disconnected state (server list page).
| Prop | Type | Description |
|------|------|-------------|
| `children` | `ReactNode` | Page content (ServerListScreen) |
| `colorScheme` | `'light' \| 'dark'` | Theme |
| `onToggleTheme` | `() => void` | Theme toggle |
**Mantine:** `AppShell` with `AppShell.Header` containing "MCP Inspector" title + theme toggle `ActionIcon`. Body renders children.
**Stories:** Light, Dark
---
### 4.2 ConnectedLayout
Layout for connected state with navigation bar.
| Prop | Type | Description |
|------|------|-------------|
| `serverName` | `string` | Server name in header |
| `status` | `'connected' \| 'connecting' \| 'failed'` | Connection status |
| `latencyMs` | `number?` | Ping latency |
| `activeTab` | `string` | Currently active nav tab |
| `availableTabs` | `string[]` | Which tabs to show (based on capabilities) |
| `colorScheme` | `'light' \| 'dark'` | Theme |
| `onTabChange` | `(tab: string) => void` | Tab navigation |
| `onDisconnect` | `() => void` | Disconnect callback |
| `onToggleTheme` | `() => void` | Theme toggle |
| `children` | `ReactNode` | Screen content |
**Mantine:** `AppShell` with `AppShell.Header` containing server name `Menu` (dropdown), StatusIndicator, navigation `Tabs` (Tools, Resources, Prompts, Logs, Tasks, History), theme toggle, Disconnect `Button` (red outline). Body renders children.
**Stories:** ToolsActive, ResourcesActive, AllTabs, LimitedTabs, LongServerName
---
## Build Order
Components should be built bottom-up. Within each tier, components can be built in parallel. Dependencies are listed to show what must exist before a component can be composed.
### Phase 1: Foundation (Elements)
All Tier 1 elements can be built in parallel. No dependencies on each other.
1. StatusIndicator
2. AnnotationBadge
3. CapabilityItem
4. LogEntry
5. ProgressDisplay
6. ContentViewer
7. ListChangedIndicator
8. CopyButton
9. ConnectionToggle
10. TransportBadge
11. InlineError
12. MessageBubble
### Phase 2: Core Groups
Depends on Phase 1 elements being complete.
1. **SchemaForm** (2.12) - Critical path, used by many other groups
2. **ServerCard** (2.1) - Uses StatusIndicator, TransportBadge, ConnectionToggle, CopyButton, InlineError
3. **AddServerMenu** (2.2)
4. **ToolListItem** (2.3) - Uses AnnotationBadge
5. **AppListItem** (2.3a) - Renders `tool.icons` (or fold into ToolListItem)
6. **ResourceListItem** (2.6) - Uses AnnotationBadge
6. **LogControls** (2.11)
7. **ResultPanel** (2.5) - Uses ContentViewer, CopyButton
8. **PromptMessagesDisplay** (2.10) - Uses MessageBubble
9. **RootsTable** (2.18)
10. **ServerInfoContent** (2.19) - Uses CapabilityItem
### Phase 3: Dependent Groups
Depends on SchemaForm and Phase 2.
1. **ToolDetailPanel** (2.4) - Uses SchemaForm, AnnotationBadge, ProgressDisplay
2. **AppDetailPanel** (2.5a) - Uses SchemaForm
3. **AppRenderer** (2.5b) - Imperative ref shell for embedded MCP App; depends on `@modelcontextprotocol/ext-apps`
4. **ResourcePreviewPanel** (2.7) - Uses ContentViewer
5. **ResourceTemplateInput** (2.8)
6. **PromptArgumentsForm** (2.9)
7. **TaskCard** (2.13) - Uses ProgressDisplay
8. **HistoryEntry** (2.14)
9. **SamplingRequestPanel** (2.15) - Uses MessageBubble
10. **ElicitationFormPanel** (2.16) - Uses SchemaForm
11. **ElicitationUrlPanel** (2.17)
12. **ServerSettingsForm** (2.20)
13. **ImportServerJsonPanel** (2.21)
14. **ExperimentalFeaturesPanel** (2.22)
### Phase 4: Inline Handlers
Depends on Phase 3 panels.
1. **InlineSamplingRequest** (2.23)
2. **InlineElicitationRequest** (2.24) - Uses SchemaForm
3. **PendingClientRequests** (2.25) - Contains inline requests
### Phase 5: Screens (Screens)
Depends on all groups.
1. **ServerListScreen** (3.1)
2. **ToolsScreen** (3.2)
3. **AppsScreen** (3.2a)
4. **ResourcesScreen** (3.3)
5. **PromptsScreen** (3.4)
6. **LoggingScreen** (3.5)
7. **TasksScreen** (3.6)
8. **HistoryScreen** (3.7)
### Phase 6: Views
Depends on screens.
1. **HomeLayout** (4.1)
2. **ConnectedLayout** (4.2)
---
## Storybook Configuration
### Global Setup
```typescript
// .storybook/preview.tsx
import { MantineProvider } from '@mantine/core';
import '@mantine/core/styles.css';
import { theme } from '../src/theme';
export const decorators = [
(Story) => (
<MantineProvider theme={theme}>
<Story />
</MantineProvider>
),
];
export const parameters = {
layout: 'centered', // elements/groups
// layout: 'fullscreen', // screens/views
};
```
### Addon Recommendations
| Addon | Purpose |
|-------|---------|
| `storybook-addon-mantine` | Theme switching, color scheme toggle |
| `@storybook/addon-actions` | Log callback invocations |
| `@storybook/addon-controls` | Interactive prop editing |
| `@storybook/addon-viewport` | Responsive testing |
| `@storybook/addon-a11y` | Accessibility checks |
### Theme Configuration
The Mantine theme should be defined once and shared between Storybook and the application:
```typescript
// src/theme.ts
import { createTheme } from '@mantine/core';
export const theme = createTheme({
primaryColor: 'dark',
// Inspector-specific overrides
});
```
### Dark Mode Stories
Every component should be verified in both light and dark mode. Use `storybook-addon-mantine` to provide a global toggle rather than duplicating stories.