@slipstream-ui/ai 0.0.0-stage → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/LICENSE +25 -0
  2. package/dist/index.d.mts +579 -0
  3. package/dist/index.d.ts +579 -0
  4. package/dist/index.js +1778 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/index.mjs +1706 -0
  7. package/dist/index.mjs.map +1 -0
  8. package/dist/manifests/attachment.manifest.json +33 -0
  9. package/dist/manifests/branch.manifest.json +65 -0
  10. package/dist/manifests/chain-thought.manifest.json +39 -0
  11. package/dist/manifests/code-block.manifest.json +50 -0
  12. package/dist/manifests/command-palette.manifest.json +80 -0
  13. package/dist/manifests/confirmation.manifest.json +143 -0
  14. package/dist/manifests/conversation.manifest.json +290 -0
  15. package/dist/manifests/inline-citation.manifest.json +46 -0
  16. package/dist/manifests/message-actions.manifest.json +35 -0
  17. package/dist/manifests/message.manifest.json +235 -0
  18. package/dist/manifests/prompt-input.manifest.json +334 -0
  19. package/dist/manifests/reasoning.manifest.json +177 -0
  20. package/dist/manifests/response.manifest.json +51 -0
  21. package/dist/manifests/source.manifest.json +78 -0
  22. package/dist/manifests/streaming-indicator.manifest.json +39 -0
  23. package/dist/manifests/suggestions.manifest.json +45 -0
  24. package/dist/manifests/tool-call.manifest.json +158 -0
  25. package/package.json +61 -3
  26. package/src/adapter/adapter.test.tsx +83 -0
  27. package/src/adapter/hooks.ts +82 -0
  28. package/src/adapter/index.ts +3 -0
  29. package/src/adapter/messages.ts +49 -0
  30. package/src/adapter/types.ts +24 -0
  31. package/src/components/attachment/Attachment.test.tsx +72 -0
  32. package/src/components/attachment/Attachment.tsx +84 -0
  33. package/src/components/attachment/AttachmentList.tsx +30 -0
  34. package/src/components/attachment/index.ts +5 -0
  35. package/src/components/branch/Branch.test.tsx +52 -0
  36. package/src/components/branch/Branch.tsx +77 -0
  37. package/src/components/branch/index.ts +2 -0
  38. package/src/components/chainthought/ChainThought.test.tsx +34 -0
  39. package/src/components/chainthought/ChainThought.tsx +45 -0
  40. package/src/components/chainthought/index.ts +2 -0
  41. package/src/components/code-block/CodeBlock.test.tsx +46 -0
  42. package/src/components/code-block/CodeBlock.tsx +95 -0
  43. package/src/components/code-block/index.ts +2 -0
  44. package/src/components/command-palette/CommandPalette.test.tsx +74 -0
  45. package/src/components/command-palette/CommandPalette.tsx +156 -0
  46. package/src/components/command-palette/index.ts +2 -0
  47. package/src/components/confirmation/Confirmation.test.tsx +97 -0
  48. package/src/components/confirmation/Confirmation.tsx +146 -0
  49. package/src/components/confirmation/index.ts +2 -0
  50. package/src/components/conversation/Conversation.test.tsx +49 -0
  51. package/src/components/conversation/Conversation.tsx +301 -0
  52. package/src/components/conversation/Conversation.welcome.test.tsx +73 -0
  53. package/src/components/conversation/index.ts +2 -0
  54. package/src/components/inline-citation/InlineCitation.test.tsx +42 -0
  55. package/src/components/inline-citation/InlineCitation.tsx +56 -0
  56. package/src/components/inline-citation/index.ts +2 -0
  57. package/src/components/message/Message.error-edit.test.tsx +60 -0
  58. package/src/components/message/Message.test.tsx +80 -0
  59. package/src/components/message/Message.tsx +205 -0
  60. package/src/components/message/index.ts +2 -0
  61. package/src/components/message-actions/MessageActions.test.tsx +77 -0
  62. package/src/components/message-actions/MessageActions.tsx +134 -0
  63. package/src/components/message-actions/index.ts +2 -0
  64. package/src/components/prompt-input/PromptInput.compound.test.tsx +150 -0
  65. package/src/components/prompt-input/PromptInput.test.tsx +74 -0
  66. package/src/components/prompt-input/PromptInput.tsx +584 -0
  67. package/src/components/prompt-input/index.ts +21 -0
  68. package/src/components/reasoning/Reasoning.duration.test.tsx +35 -0
  69. package/src/components/reasoning/Reasoning.test.tsx +58 -0
  70. package/src/components/reasoning/Reasoning.tsx +85 -0
  71. package/src/components/reasoning/index.ts +2 -0
  72. package/src/components/response/Response.test.tsx +69 -0
  73. package/src/components/response/Response.tsx +75 -0
  74. package/src/components/response/close-incomplete-markdown.ts +47 -0
  75. package/src/components/response/index.ts +3 -0
  76. package/src/components/source/Source.tsx +51 -0
  77. package/src/components/source/Sources.tsx +42 -0
  78. package/src/components/source/index.ts +5 -0
  79. package/src/components/source/source.test.tsx +72 -0
  80. package/src/components/streaming/StreamingIndicator.test.tsx +26 -0
  81. package/src/components/streaming/StreamingIndicator.tsx +46 -0
  82. package/src/components/streaming/index.ts +2 -0
  83. package/src/components/suggestion/Suggestion.test.tsx +43 -0
  84. package/src/components/suggestion/Suggestion.tsx +47 -0
  85. package/src/components/suggestion/index.ts +2 -0
  86. package/src/components/tool-call/ToolCall.test.tsx +45 -0
  87. package/src/components/tool-call/ToolCall.tsx +101 -0
  88. package/src/components/tool-call/index.ts +2 -0
  89. package/src/index.ts +29 -0
  90. package/src/manifests/attachment.manifest.json +33 -0
  91. package/src/manifests/branch.manifest.json +65 -0
  92. package/src/manifests/chain-thought.manifest.json +39 -0
  93. package/src/manifests/code-block.manifest.json +50 -0
  94. package/src/manifests/command-palette.manifest.json +80 -0
  95. package/src/manifests/confirmation.manifest.json +143 -0
  96. package/src/manifests/conversation.manifest.json +290 -0
  97. package/src/manifests/inline-citation.manifest.json +46 -0
  98. package/src/manifests/message-actions.manifest.json +35 -0
  99. package/src/manifests/message.manifest.json +235 -0
  100. package/src/manifests/prompt-input.manifest.json +334 -0
  101. package/src/manifests/reasoning.manifest.json +177 -0
  102. package/src/manifests/response.manifest.json +51 -0
  103. package/src/manifests/source.manifest.json +78 -0
  104. package/src/manifests/streaming-indicator.manifest.json +39 -0
  105. package/src/manifests/suggestions.manifest.json +45 -0
  106. package/src/manifests/tool-call.manifest.json +158 -0
  107. package/src/test/jest-axe.d.ts +16 -0
  108. package/src/test/setup.ts +18 -0
  109. package/src/test/vitest-matchers.d.ts +10 -0
  110. package/src/types/index.ts +40 -0
  111. package/README.md +0 -3
@@ -0,0 +1,45 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { axe } from 'jest-axe';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { ToolCall } from './ToolCall';
5
+
6
+ const toolCall = {
7
+ id: 'call_1',
8
+ name: 'get_weather',
9
+ arguments: { city: 'Paris' },
10
+ result: { temp: 21 },
11
+ status: 'completed' as const,
12
+ };
13
+
14
+ describe('ToolCall', () => {
15
+ it('renders the tool name, id, and status', () => {
16
+ render(<ToolCall toolCall={toolCall} />);
17
+ expect(screen.getByText('get_weather')).toBeInTheDocument();
18
+ expect(screen.getByText('call_1')).toBeInTheDocument();
19
+ expect(screen.getByText('Completed')).toBeInTheDocument();
20
+ });
21
+
22
+ it.each(['pending', 'running', 'failed'] as const)('labels the %s status', (status) => {
23
+ const labels = { pending: 'Pending', running: 'Running', failed: 'Failed' };
24
+ render(<ToolCall toolCall={{ ...toolCall, result: undefined, status }} />);
25
+ expect(screen.getByText(labels[status])).toBeInTheDocument();
26
+ });
27
+
28
+ it('serializes arguments as JSON', () => {
29
+ render(<ToolCall toolCall={toolCall} />);
30
+ expect(screen.getByText(/"city":\s*"Paris"/)).toBeInTheDocument();
31
+ });
32
+
33
+ it('shows the result when present and hides it with showResult=false', () => {
34
+ const { rerender } = render(<ToolCall toolCall={{ ...toolCall, result: 'Done' }} />);
35
+ expect(screen.getByText('Done')).toBeInTheDocument();
36
+
37
+ rerender(<ToolCall toolCall={toolCall} showResult={false} />);
38
+ expect(screen.queryByText(/"temp"/)).not.toBeInTheDocument();
39
+ });
40
+
41
+ it('has no accessibility violations', async () => {
42
+ const { container } = render(<ToolCall toolCall={toolCall} />);
43
+ expect(await axe(container)).toHaveNoViolations();
44
+ });
45
+ });
@@ -0,0 +1,101 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { cn } from '@slipstream-ui/core';
5
+ import type { ToolCall as ToolCallType } from '../../types';
6
+
7
+ export type ToolCallProps = {
8
+ toolCall: ToolCallType;
9
+ className?: string;
10
+ showResult?: boolean;
11
+ };
12
+
13
+ const statusChipClasses: Record<ToolCallType['status'], string> = {
14
+ pending: 'bg-warning/10 text-warning border-warning/20',
15
+ running: 'bg-info/10 text-info border-info/20',
16
+ completed: 'bg-success/10 text-success border-success/20',
17
+ failed: 'bg-destructive/10 text-destructive border-destructive/20',
18
+ };
19
+
20
+ const statusLabels: Record<ToolCallType['status'], string> = {
21
+ pending: 'Pending',
22
+ running: 'Running',
23
+ completed: 'Completed',
24
+ failed: 'Failed',
25
+ };
26
+
27
+ /**
28
+ * Displays a single tool invocation: name, status chip, serialized
29
+ * arguments, optional result, and an error block on failure.
30
+ */
31
+ const ToolCall = React.forwardRef<HTMLDivElement, ToolCallProps>(
32
+ ({ toolCall, className, showResult = true, ...props }, ref) => {
33
+ // Serialize once per tool-call change instead of on every render.
34
+ const serializedArguments = React.useMemo(
35
+ () => JSON.stringify(toolCall.arguments, null, 2),
36
+ [toolCall.arguments]
37
+ );
38
+ const serializedResult = React.useMemo(() => {
39
+ if (toolCall.result === undefined) return undefined;
40
+ return typeof toolCall.result === 'string'
41
+ ? toolCall.result
42
+ : JSON.stringify(toolCall.result, null, 2);
43
+ }, [toolCall.result]);
44
+
45
+ return (
46
+ <div
47
+ ref={ref}
48
+ className={cn(
49
+ 'border rounded-lg p-4 bg-muted/30',
50
+ className
51
+ )}
52
+ {...props}
53
+ >
54
+ <div className="flex items-center justify-between mb-3">
55
+ <div className="flex items-center gap-2">
56
+ <span className="font-medium text-sm">{toolCall.name}</span>
57
+ <span
58
+ className={cn(
59
+ 'text-xs px-2 py-0.5 rounded-full border',
60
+ statusChipClasses[toolCall.status]
61
+ )}
62
+ >
63
+ {statusLabels[toolCall.status]}
64
+ </span>
65
+ </div>
66
+ <span className="text-xs text-muted-foreground">{toolCall.id}</span>
67
+ </div>
68
+
69
+ <div className="space-y-2">
70
+ <div>
71
+ <div className="text-xs font-medium text-muted-foreground mb-1">Arguments</div>
72
+ <pre className="text-xs bg-muted p-2 rounded overflow-x-auto">
73
+ <code>{serializedArguments}</code>
74
+ </pre>
75
+ </div>
76
+
77
+ {showResult && serializedResult !== undefined && (
78
+ <div>
79
+ <div className="text-xs font-medium text-muted-foreground mb-1">Result</div>
80
+ <pre className="text-xs bg-muted p-2 rounded overflow-x-auto">
81
+ <code>{serializedResult}</code>
82
+ </pre>
83
+ </div>
84
+ )}
85
+
86
+ {toolCall.status === 'failed' && toolCall.error && (
87
+ <div role="alert">
88
+ <div className="text-xs font-medium text-destructive mb-1">Error</div>
89
+ <pre className="text-xs bg-destructive/10 text-destructive p-2 rounded overflow-x-auto whitespace-pre-wrap">
90
+ <code>{toolCall.error}</code>
91
+ </pre>
92
+ </div>
93
+ )}
94
+ </div>
95
+ </div>
96
+ );
97
+ }
98
+ );
99
+ ToolCall.displayName = 'ToolCall';
100
+
101
+ export { ToolCall };
@@ -0,0 +1,2 @@
1
+ export { ToolCall } from './ToolCall';
2
+ export type { ToolCallProps } from './ToolCall';
package/src/index.ts ADDED
@@ -0,0 +1,29 @@
1
+ export type {
2
+ MessageRole,
3
+ ReasoningStep,
4
+ SourceItem,
5
+ } from './types';
6
+ // Data-model aliases: exporting these under their bare names would collide
7
+ // with same-named components in the bundled .d.ts output.
8
+ export type {
9
+ Message as MessageData,
10
+ ToolCall as ToolCallData,
11
+ } from './types';
12
+ export * from './components/message';
13
+ export * from './components/message-actions';
14
+ export * from './components/attachment';
15
+ export * from './components/response';
16
+ export * from './components/code-block';
17
+ export * from './components/confirmation';
18
+ export * from './components/branch';
19
+ export * from './components/inline-citation';
20
+ export * from './components/conversation';
21
+ export * from './components/prompt-input';
22
+ export * from './components/reasoning';
23
+ export * from './components/tool-call';
24
+ export * from './components/source';
25
+ export * from './components/suggestion';
26
+ export * from './components/chainthought';
27
+ export * from './components/streaming';
28
+ export * from './components/command-palette';
29
+ export * from './adapter';
@@ -0,0 +1,33 @@
1
+ {
2
+ "id": "attachment",
3
+ "name": "Attachment / AttachmentList",
4
+ "tier": "molecule",
5
+ "description": "File chips for prompts and messages. `Attachment` renders one file; `AttachmentList` renders a wrapping row. Display-only: upload and file-system logic stay in the consumer.",
6
+ "designIntent": "Show what files are part of a conversation turn (name, size, type, thumbnail, upload status) without coupling the library to any upload implementation.",
7
+ "props": [
8
+ { "name": "attachment", "type": "AttachmentItem", "required": true, "description": "File descriptor: id, name, size, type, previewUrl, status." },
9
+ { "name": "attachments", "type": "AttachmentItem[]", "required": true, "description": "Files to display (AttachmentList)." },
10
+ { "name": "onRemove", "type": "(attachment) => void", "required": false, "description": "Remove callback; omit to hide remove controls." }
11
+ ],
12
+ "examples": [
13
+ {
14
+ "title": "Prompt attachments",
15
+ "code": "<AttachmentList attachments={[{ id: '1', name: 'spec.pdf', size: '2.4 MB', status: 'done' }]} onRemove={remove} />"
16
+ },
17
+ {
18
+ "title": "Uploading image",
19
+ "code": "<Attachment attachment={{ id: '2', name: 'chart.png', previewUrl: '/chart.png', status: 'uploading' }} />"
20
+ }
21
+ ],
22
+ "antiPatterns": [
23
+ { "title": "Do not use Attachment for drag-and-drop targets", "explanation": "The chip displays an existing item; build dropzones in the consumer and render AttachmentList once items exist." }
24
+ ],
25
+ "accessibility": [
26
+ { "feature": "Remove control", "implementation": "Icon-only remove button has an aria-label including the file name." },
27
+ { "feature": "Upload status", "implementation": "Uploading state is announced via role=status." },
28
+ { "feature": "Decorative thumbnails", "implementation": "Preview images are aria-hidden; the file name carries the meaning." }
29
+ ],
30
+ "package": "@slipstream-ui/ai",
31
+ "importPath": "@slipstream-ui/ai",
32
+ "version": "0.0.1"
33
+ }
@@ -0,0 +1,65 @@
1
+ {
2
+ "id": "branch",
3
+ "name": "Branch",
4
+ "tier": "atom",
5
+ "description": "Variant navigation for branched messages: previous/next arrows with an N/M position indicator.",
6
+ "designIntent": "Let users compare alternative AI responses without leaving the transcript flow.",
7
+ "props": [
8
+ {
9
+ "name": "count",
10
+ "type": "number",
11
+ "required": true,
12
+ "description": "Total variants; renders nothing below 2."
13
+ },
14
+ {
15
+ "name": "selected",
16
+ "type": "number",
17
+ "required": false,
18
+ "description": "Controlled zero-based index."
19
+ },
20
+ {
21
+ "name": "defaultSelected",
22
+ "type": "number",
23
+ "defaultValue": 0,
24
+ "required": false,
25
+ "description": "Uncontrolled initial index."
26
+ },
27
+ {
28
+ "name": "onSelect",
29
+ "type": "(index: number) => void",
30
+ "required": false,
31
+ "description": "Called with the chosen variant index."
32
+ },
33
+ {
34
+ "name": "labels",
35
+ "type": "object",
36
+ "required": false,
37
+ "description": "Overrides for previous/next aria-labels."
38
+ }
39
+ ],
40
+ "examples": [
41
+ {
42
+ "title": "Two variants",
43
+ "code": "<Branch count={variants.length} selected={i} onSelect={setI} />"
44
+ }
45
+ ],
46
+ "antiPatterns": [
47
+ {
48
+ "title": "Do not swap message content without syncing selected",
49
+ "explanation": "In controlled mode the displayed variant must follow onSelect."
50
+ }
51
+ ],
52
+ "accessibility": [
53
+ {
54
+ "feature": "Boundary disabling",
55
+ "implementation": "Arrows disable at ends instead of wrapping silently."
56
+ },
57
+ {
58
+ "feature": "Position announcement",
59
+ "implementation": "N/M indicator uses aria-live=polite."
60
+ }
61
+ ],
62
+ "package": "@slipstream-ui/ai",
63
+ "importPath": "@slipstream-ui/ai",
64
+ "version": "0.0.1"
65
+ }
@@ -0,0 +1,39 @@
1
+ {
2
+ "id": "chain-thought",
3
+ "name": "ChainThought",
4
+ "tier": "molecule",
5
+ "description": "Displays ordered reasoning steps with status indicators.",
6
+ "designIntent": "Provide a compact progress view for multi-step agent work.",
7
+ "props": [
8
+ {
9
+ "name": "steps",
10
+ "type": "ChainThoughtStep[]",
11
+ "required": true,
12
+ "description": "Ordered reasoning steps."
13
+ },
14
+ {
15
+ "name": "compact",
16
+ "type": "boolean",
17
+ "defaultValue": false,
18
+ "required": false,
19
+ "description": "Use compact typography."
20
+ }
21
+ ],
22
+ "examples": [
23
+ {
24
+ "title": "Agent steps",
25
+ "code": "<ChainThought steps={[{ id: '1', content: 'Analyzing…', status: 'pending' }]} />"
26
+ }
27
+ ],
28
+ "accessibility": [
29
+ {
30
+ "feature": "Status visibility",
31
+ "implementation": "Each step exposes a text label and visual status."
32
+ }
33
+ ],
34
+ "package": "@slipstream-ui/ai",
35
+ "importPath": "@slipstream-ui/ai",
36
+ "version": "0.0.1",
37
+ "sourceDir": "chainthought",
38
+ "exportName": "ChainThought"
39
+ }
@@ -0,0 +1,50 @@
1
+ {
2
+ "id": "code-block",
3
+ "name": "CodeBlock",
4
+ "tier": "atom",
5
+ "description": "Syntax-highlighted code block with language label and copy-to-clipboard button.",
6
+ "designIntent": "Display AI-generated or referenced code readably with one-click copy.",
7
+ "props": [
8
+ {
9
+ "name": "code",
10
+ "type": "string",
11
+ "required": true,
12
+ "description": "Raw code to display."
13
+ },
14
+ {
15
+ "name": "language",
16
+ "type": "string",
17
+ "defaultValue": "tsx",
18
+ "required": false,
19
+ "description": "Prism language identifier."
20
+ },
21
+ {
22
+ "name": "showCopyButton",
23
+ "type": "boolean",
24
+ "defaultValue": true,
25
+ "required": false,
26
+ "description": "Show/hide the copy control."
27
+ }
28
+ ],
29
+ "examples": [
30
+ {
31
+ "title": "Basic",
32
+ "code": "<CodeBlock code={sourceCode} language=\"ts\" />"
33
+ }
34
+ ],
35
+ "antiPatterns": [
36
+ {
37
+ "title": "Do not nest CodeBlock in Response code fences manually",
38
+ "explanation": "Response already routes fenced code to CodeBlock."
39
+ }
40
+ ],
41
+ "accessibility": [
42
+ {
43
+ "feature": "Copy button",
44
+ "implementation": "Icon-labeled button with aria-label that flips to Copied."
45
+ }
46
+ ],
47
+ "package": "@slipstream-ui/ai",
48
+ "importPath": "@slipstream-ui/ai",
49
+ "version": "0.0.1"
50
+ }
@@ -0,0 +1,80 @@
1
+ {
2
+ "id": "command-palette",
3
+ "name": "CommandPalette",
4
+ "tier": "organism",
5
+ "description": "Cmd+K style command palette with lexical search, sectioned results and full keyboard navigation",
6
+ "designIntent": "AI-assisted and power-user apps expose many actions; a command palette keeps them all one keystroke away. The palette is a combobox+listbox with aria-activedescendant navigation so focus never leaves the input, filters lexically over labels/sections/keywords, and reports selection through a single callback.",
7
+ "props": [
8
+ {
9
+ "name": "open",
10
+ "type": "boolean",
11
+ "required": true,
12
+ "description": "Whether the palette is visible (controlled)"
13
+ },
14
+ {
15
+ "name": "onOpenChange",
16
+ "type": "(open: boolean) => void",
17
+ "required": true,
18
+ "description": "Called when the user opens or dismisses the palette"
19
+ },
20
+ {
21
+ "name": "commands",
22
+ "type": "Command[]",
23
+ "required": true,
24
+ "description": "Available commands: { id, label, section?, keywords?, shortcut? }",
25
+ "examples": [{ "id": "go-docs", "label": "Open documentation", "section": "Navigation" }]
26
+ },
27
+ {
28
+ "name": "onSelect",
29
+ "type": "(command: Command) => void",
30
+ "required": true,
31
+ "description": "Invoked with the chosen command before the palette closes"
32
+ },
33
+ {
34
+ "name": "placeholder",
35
+ "type": "string",
36
+ "defaultValue": "Type a command…",
37
+ "required": false,
38
+ "description": "Input placeholder text"
39
+ }
40
+ ],
41
+ "examples": [
42
+ {
43
+ "title": "Wired to Cmd+K",
44
+ "code": "const [open, setOpen] = useState(false);\n\nuseEffect(() => {\n const onKeyDown = (event: KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'k') {\n event.preventDefault();\n setOpen((current) => !current);\n }\n };\n window.addEventListener('keydown', onKeyDown);\n return () => window.removeEventListener('keydown', onKeyDown);\n}, []);\n\n<CommandPalette\n open={open}\n onOpenChange={setOpen}\n commands={commands}\n onSelect={(command) => run(command.id)}\n/>",
45
+ "description": "Toggle the palette with Cmd/Ctrl+K and run the selected command."
46
+ }
47
+ ],
48
+ "antiPatterns": [
49
+ {
50
+ "title": "Do not use the palette as a text editor command",
51
+ "explanation": "The palette searches discrete commands. For AI chat input use PromptInput instead."
52
+ }
53
+ ],
54
+ "accessibility": [
55
+ {
56
+ "feature": "Combobox pattern",
57
+ "implementation": "The input uses role=combobox with aria-expanded, aria-controls, aria-activedescendant and aria-autocomplete=list."
58
+ },
59
+ {
60
+ "feature": "Keyboard navigation",
61
+ "implementation": "Arrow keys cycle options, Enter selects, Escape closes; focus never leaves the input."
62
+ },
63
+ {
64
+ "feature": "Option semantics",
65
+ "implementation": "Each result is role=option with aria-selected marking the active item."
66
+ }
67
+ ],
68
+ "compositionRules": [
69
+ {
70
+ "component": "PromptInput",
71
+ "relationship": "CommandPalette switches modes/actions; PromptInput captures free-form chat text."
72
+ }
73
+ ],
74
+ "relatedComponents": ["PromptInput", "Dialog"],
75
+ "package": "@slipstream-ui/ai",
76
+ "importPath": "@slipstream-ui/ai",
77
+ "version": "0.0.1",
78
+ "sourceDir": "command-palette",
79
+ "exportName": "CommandPalette"
80
+ }
@@ -0,0 +1,143 @@
1
+ {
2
+ "id": "confirmation",
3
+ "name": "Confirmation",
4
+ "tier": "molecule",
5
+ "description": "Human-in-the-loop approval gate for agent actions. Pauses execution until the user approves or rejects.",
6
+ "designIntent": "Make destructive or consequential tool calls require explicit user consent.",
7
+ "props": [
8
+ {
9
+ "name": "title",
10
+ "type": "string",
11
+ "required": true,
12
+ "description": "Summary of the action requiring approval."
13
+ },
14
+ {
15
+ "name": "description",
16
+ "type": "string",
17
+ "required": false,
18
+ "description": "Detail: affected resource or risk note."
19
+ },
20
+ {
21
+ "name": "toolName",
22
+ "type": "string",
23
+ "required": false,
24
+ "description": "Tool/action name shown as mono text."
25
+ },
26
+ {
27
+ "name": "status",
28
+ "type": "'pending' | 'approved' | 'rejected'",
29
+ "defaultValue": "pending",
30
+ "required": false,
31
+ "description": "Controlled outcome; omit for uncontrolled."
32
+ },
33
+ {
34
+ "name": "variant",
35
+ "type": "'default' | 'warning' | 'destructive' | 'info' | 'success'",
36
+ "defaultValue": "default",
37
+ "required": false,
38
+ "description": "Accent color of the pending card; outcome chips always follow the result.",
39
+ "enum": [
40
+ "default",
41
+ "warning",
42
+ "destructive",
43
+ "info",
44
+ "success"
45
+ ],
46
+ "examples": [
47
+ "destructive",
48
+ "info"
49
+ ]
50
+ },
51
+ {
52
+ "name": "onApprove",
53
+ "type": "() => void",
54
+ "required": false,
55
+ "description": "Called on approval."
56
+ },
57
+ {
58
+ "name": "onReject",
59
+ "type": "() => void",
60
+ "required": false,
61
+ "description": "Called on rejection."
62
+ },
63
+ {
64
+ "name": "labels",
65
+ "type": "object",
66
+ "required": false,
67
+ "description": "Overrides for approve/reject/approved/rejected labels."
68
+ },
69
+ {
70
+ "name": "approveVariant",
71
+ "type": "'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'",
72
+ "defaultValue": "default",
73
+ "required": false,
74
+ "description": "Button variant for the approve action.",
75
+ "enum": [
76
+ "default",
77
+ "destructive",
78
+ "outline",
79
+ "secondary",
80
+ "ghost",
81
+ "link"
82
+ ],
83
+ "examples": [
84
+ "destructive",
85
+ "default"
86
+ ]
87
+ },
88
+ {
89
+ "name": "rejectVariant",
90
+ "type": "'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link'",
91
+ "defaultValue": "outline",
92
+ "required": false,
93
+ "description": "Button variant for the reject action.",
94
+ "enum": [
95
+ "default",
96
+ "destructive",
97
+ "outline",
98
+ "secondary",
99
+ "ghost",
100
+ "link"
101
+ ],
102
+ "examples": [
103
+ "ghost"
104
+ ]
105
+ },
106
+ {
107
+ "name": "showActions",
108
+ "type": "boolean",
109
+ "defaultValue": "true",
110
+ "required": false,
111
+ "description": "Hide the action buttons (controlled-only gate; status drives the chip).",
112
+ "examples": [
113
+ "true",
114
+ "false"
115
+ ]
116
+ }
117
+ ],
118
+ "examples": [
119
+ {
120
+ "title": "Gate a destructive tool",
121
+ "code": "<Confirmation title=\"Delete all files?\" toolName=\"delete_all\" onApprove={run} onReject={cancel} />"
122
+ }
123
+ ],
124
+ "antiPatterns": [
125
+ {
126
+ "title": "Do not auto-approve after timeout",
127
+ "explanation": "Silent approval defeats the safety purpose; keep waiting or fail explicitly."
128
+ }
129
+ ],
130
+ "accessibility": [
131
+ {
132
+ "feature": "Dialog semantics",
133
+ "implementation": "Pending state renders role=alertdialog labeled by the title."
134
+ },
135
+ {
136
+ "feature": "Outcome announcement",
137
+ "implementation": "Status chip text announces Approved/Rejected."
138
+ }
139
+ ],
140
+ "package": "@slipstream-ui/ai",
141
+ "importPath": "@slipstream-ui/ai",
142
+ "version": "0.0.1"
143
+ }