sunpeak 0.8.5 → 0.8.8

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 (34) hide show
  1. package/bin/commands/build.mjs +3 -3
  2. package/bin/commands/pull.mjs +2 -2
  3. package/bin/sunpeak.js +6 -8
  4. package/dist/mcp/entry.cjs.map +1 -1
  5. package/dist/mcp/entry.js.map +1 -1
  6. package/dist/style.css +0 -37
  7. package/package.json +1 -1
  8. package/template/.sunpeak/dev.tsx +2 -2
  9. package/template/README.md +5 -5
  10. package/template/dist/albums.js +1 -1
  11. package/template/dist/albums.json +1 -1
  12. package/template/dist/carousel.js +1 -1
  13. package/template/dist/carousel.json +1 -1
  14. package/template/dist/map.js +1 -1
  15. package/template/dist/map.json +1 -1
  16. package/template/dist/review.js +49 -0
  17. package/template/dist/{confirmation.json → review.json} +4 -4
  18. package/template/node_modules/.vite/deps/_metadata.json +19 -19
  19. package/template/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  20. package/template/src/resources/index.ts +4 -4
  21. package/template/src/resources/map-resource.test.tsx +95 -0
  22. package/template/src/resources/{confirmation-resource.json → review-resource.json} +3 -3
  23. package/template/src/resources/review-resource.test.tsx +538 -0
  24. package/template/src/resources/{confirmation-resource.tsx → review-resource.tsx} +20 -20
  25. package/template/src/simulations/{confirmation-diff-simulation.json → review-diff-simulation.json} +4 -4
  26. package/template/src/simulations/{confirmation-post-simulation.json → review-post-simulation.json} +4 -4
  27. package/template/src/simulations/{confirmation-purchase-simulation.json → review-purchase-simulation.json} +4 -4
  28. package/template/dist/confirmation.js +0 -49
  29. package/template/dist/counter.js +0 -49
  30. package/template/dist/counter.json +0 -15
  31. package/template/src/resources/counter-resource.json +0 -12
  32. package/template/src/resources/counter-resource.test.tsx +0 -116
  33. package/template/src/resources/counter-resource.tsx +0 -101
  34. package/template/src/simulations/counter-show-simulation.json +0 -20
@@ -0,0 +1,538 @@
1
+ import { render, screen, fireEvent } from '@testing-library/react';
2
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
3
+ import { ReviewResource } from './review-resource';
4
+
5
+ // Mock sunpeak hooks
6
+ const mockSetWidgetState = vi.fn();
7
+ const mockRequestDisplayMode = vi.fn();
8
+
9
+ let mockWidgetData: Record<string, unknown> = { title: 'Test Review' };
10
+ let mockWidgetState: Record<string, unknown> = { decision: null, decidedAt: null };
11
+ let mockSafeArea: { insets: { top: number; bottom: number; left: number; right: number } } | null =
12
+ {
13
+ insets: { top: 0, bottom: 0, left: 0, right: 0 },
14
+ };
15
+ let mockMaxHeight: number | null = 600;
16
+ let mockUserAgent: {
17
+ device: { type: 'desktop' | 'mobile' | 'tablet' | 'unknown' };
18
+ capabilities: { hover: boolean; touch: boolean };
19
+ } | null = {
20
+ device: { type: 'desktop' },
21
+ capabilities: { hover: true, touch: false },
22
+ };
23
+ let mockDisplayMode: 'inline' | 'fullscreen' = 'inline';
24
+
25
+ vi.mock('sunpeak', () => ({
26
+ useWidgetProps: (defaultFn: () => Record<string, unknown>) => {
27
+ const defaults = defaultFn();
28
+ return { ...defaults, ...mockWidgetData };
29
+ },
30
+ useWidgetState: () => [mockWidgetState, mockSetWidgetState],
31
+ useSafeArea: () => mockSafeArea,
32
+ useMaxHeight: () => mockMaxHeight,
33
+ useUserAgent: () => mockUserAgent,
34
+ useDisplayMode: () => mockDisplayMode,
35
+ useWidgetAPI: () => ({ requestDisplayMode: mockRequestDisplayMode }),
36
+ }));
37
+
38
+ // Mock Button component
39
+ vi.mock('@openai/apps-sdk-ui/components/Button', () => ({
40
+ Button: ({
41
+ children,
42
+ onClick,
43
+ variant,
44
+ color,
45
+ size,
46
+ className,
47
+ 'aria-label': ariaLabel,
48
+ }: {
49
+ children: React.ReactNode;
50
+ onClick?: () => void;
51
+ variant?: string;
52
+ color?: string;
53
+ size?: string;
54
+ className?: string;
55
+ 'aria-label'?: string;
56
+ }) => (
57
+ <button
58
+ onClick={onClick}
59
+ data-variant={variant}
60
+ data-color={color}
61
+ data-size={size}
62
+ className={className}
63
+ aria-label={ariaLabel}
64
+ >
65
+ {children}
66
+ </button>
67
+ ),
68
+ }));
69
+
70
+ // Mock Icon component
71
+ vi.mock('@openai/apps-sdk-ui/components/Icon', () => ({
72
+ ExpandLg: ({ className }: { className?: string }) => (
73
+ <span data-testid="expand-icon" className={className}>
74
+ Expand
75
+ </span>
76
+ ),
77
+ }));
78
+
79
+ describe('ReviewResource', () => {
80
+ beforeEach(() => {
81
+ vi.clearAllMocks();
82
+ mockWidgetData = { title: 'Test Review' };
83
+ mockWidgetState = { decision: null, decidedAt: null };
84
+ mockSafeArea = { insets: { top: 0, bottom: 0, left: 0, right: 0 } };
85
+ mockMaxHeight = 600;
86
+ mockUserAgent = { device: { type: 'desktop' }, capabilities: { hover: true, touch: false } };
87
+ mockDisplayMode = 'inline';
88
+ });
89
+
90
+ describe('Basic Rendering', () => {
91
+ it('renders with title', () => {
92
+ mockWidgetData = { title: 'Confirm Purchase' };
93
+
94
+ render(<ReviewResource />);
95
+
96
+ expect(screen.getByText('Confirm Purchase')).toBeInTheDocument();
97
+ });
98
+
99
+ it('renders with description', () => {
100
+ mockWidgetData = {
101
+ title: 'Test',
102
+ description: 'Please review the following items',
103
+ };
104
+
105
+ render(<ReviewResource />);
106
+
107
+ expect(screen.getByText('Please review the following items')).toBeInTheDocument();
108
+ });
109
+
110
+ it('renders empty state when no sections', () => {
111
+ mockWidgetData = { title: 'Test', sections: [] };
112
+
113
+ render(<ReviewResource />);
114
+
115
+ expect(screen.getByText('Nothing to confirm')).toBeInTheDocument();
116
+ });
117
+
118
+ it('has the correct displayName', () => {
119
+ expect(ReviewResource.displayName).toBe('ReviewResource');
120
+ });
121
+ });
122
+
123
+ describe('Action Buttons', () => {
124
+ it('renders default button labels', () => {
125
+ render(<ReviewResource />);
126
+
127
+ expect(screen.getByText('Confirm')).toBeInTheDocument();
128
+ expect(screen.getByText('Cancel')).toBeInTheDocument();
129
+ });
130
+
131
+ it('renders custom button labels', () => {
132
+ mockWidgetData = {
133
+ title: 'Test',
134
+ acceptLabel: 'Approve',
135
+ rejectLabel: 'Decline',
136
+ };
137
+
138
+ render(<ReviewResource />);
139
+
140
+ expect(screen.getByText('Approve')).toBeInTheDocument();
141
+ expect(screen.getByText('Decline')).toBeInTheDocument();
142
+ });
143
+
144
+ it('calls setWidgetState with accepted decision when accept clicked', () => {
145
+ render(<ReviewResource />);
146
+
147
+ const acceptButton = screen.getByText('Confirm');
148
+ fireEvent.click(acceptButton);
149
+
150
+ expect(mockSetWidgetState).toHaveBeenCalledWith(
151
+ expect.objectContaining({
152
+ decision: 'accepted',
153
+ decidedAt: expect.any(String),
154
+ })
155
+ );
156
+ });
157
+
158
+ it('calls setWidgetState with rejected decision when reject clicked', () => {
159
+ render(<ReviewResource />);
160
+
161
+ const rejectButton = screen.getByText('Cancel');
162
+ fireEvent.click(rejectButton);
163
+
164
+ expect(mockSetWidgetState).toHaveBeenCalledWith(
165
+ expect.objectContaining({
166
+ decision: 'rejected',
167
+ decidedAt: expect.any(String),
168
+ })
169
+ );
170
+ });
171
+
172
+ it('renders danger styling for accept button when acceptDanger is true', () => {
173
+ mockWidgetData = { title: 'Test', acceptDanger: true };
174
+
175
+ render(<ReviewResource />);
176
+
177
+ const acceptButton = screen.getByText('Confirm');
178
+ expect(acceptButton).toHaveAttribute('data-color', 'danger');
179
+ });
180
+
181
+ it('renders primary styling for accept button by default', () => {
182
+ render(<ReviewResource />);
183
+
184
+ const acceptButton = screen.getByText('Confirm');
185
+ expect(acceptButton).toHaveAttribute('data-color', 'primary');
186
+ });
187
+ });
188
+
189
+ describe('Decision State', () => {
190
+ it('shows accepted message after accepting', () => {
191
+ mockWidgetState = { decision: 'accepted', decidedAt: '2024-01-01T00:00:00.000Z' };
192
+
193
+ render(<ReviewResource />);
194
+
195
+ expect(screen.getByText('Confirmed')).toBeInTheDocument();
196
+ expect(screen.queryByText('Confirm')).not.toBeInTheDocument();
197
+ });
198
+
199
+ it('shows rejected message after rejecting', () => {
200
+ mockWidgetState = { decision: 'rejected', decidedAt: '2024-01-01T00:00:00.000Z' };
201
+
202
+ render(<ReviewResource />);
203
+
204
+ expect(screen.getByText('Cancelled')).toBeInTheDocument();
205
+ expect(screen.queryByText('Cancel')).not.toBeInTheDocument();
206
+ });
207
+
208
+ it('shows custom accepted message', () => {
209
+ mockWidgetData = { title: 'Test', acceptedMessage: 'Order Placed!' };
210
+ mockWidgetState = { decision: 'accepted', decidedAt: '2024-01-01T00:00:00.000Z' };
211
+
212
+ render(<ReviewResource />);
213
+
214
+ expect(screen.getByText('Order Placed!')).toBeInTheDocument();
215
+ });
216
+
217
+ it('shows custom rejected message', () => {
218
+ mockWidgetData = { title: 'Test', rejectedMessage: 'Order Cancelled' };
219
+ mockWidgetState = { decision: 'rejected', decidedAt: '2024-01-01T00:00:00.000Z' };
220
+
221
+ render(<ReviewResource />);
222
+
223
+ expect(screen.getByText('Order Cancelled')).toBeInTheDocument();
224
+ });
225
+
226
+ it('shows decidedAt timestamp', () => {
227
+ mockWidgetState = { decision: 'accepted', decidedAt: '2024-01-15T10:30:00.000Z' };
228
+
229
+ render(<ReviewResource />);
230
+
231
+ // The timestamp should be displayed
232
+ const timestampElement = screen.getByText(/2024/);
233
+ expect(timestampElement).toBeInTheDocument();
234
+ });
235
+ });
236
+
237
+ describe('Sections', () => {
238
+ it('renders details section', () => {
239
+ mockWidgetData = {
240
+ title: 'Test',
241
+ sections: [
242
+ {
243
+ title: 'Order Details',
244
+ type: 'details',
245
+ content: [
246
+ { label: 'Item', value: 'Widget' },
247
+ { label: 'Price', value: '$10.00' },
248
+ ],
249
+ },
250
+ ],
251
+ };
252
+
253
+ render(<ReviewResource />);
254
+
255
+ expect(screen.getByText('Order Details')).toBeInTheDocument();
256
+ expect(screen.getByText('Item')).toBeInTheDocument();
257
+ expect(screen.getByText('Widget')).toBeInTheDocument();
258
+ expect(screen.getByText('Price')).toBeInTheDocument();
259
+ expect(screen.getByText('$10.00')).toBeInTheDocument();
260
+ });
261
+
262
+ it('renders items section', () => {
263
+ mockWidgetData = {
264
+ title: 'Test',
265
+ sections: [
266
+ {
267
+ title: 'Cart Items',
268
+ type: 'items',
269
+ content: [
270
+ { id: '1', title: 'Product A', subtitle: 'Small', value: '$5.00' },
271
+ { id: '2', title: 'Product B', badge: 'Sale', value: '$15.00' },
272
+ ],
273
+ },
274
+ ],
275
+ };
276
+
277
+ render(<ReviewResource />);
278
+
279
+ expect(screen.getByText('Cart Items')).toBeInTheDocument();
280
+ expect(screen.getByText('Product A')).toBeInTheDocument();
281
+ expect(screen.getByText('Small')).toBeInTheDocument();
282
+ expect(screen.getByText('Product B')).toBeInTheDocument();
283
+ expect(screen.getByText('Sale')).toBeInTheDocument();
284
+ });
285
+
286
+ it('renders changes section', () => {
287
+ mockWidgetData = {
288
+ title: 'Test',
289
+ sections: [
290
+ {
291
+ title: 'File Changes',
292
+ type: 'changes',
293
+ content: [
294
+ { id: '1', type: 'create', path: 'src/new.ts', description: 'New file' },
295
+ { id: '2', type: 'modify', path: 'src/old.ts', description: 'Updated imports' },
296
+ { id: '3', type: 'delete', path: 'src/deprecated.ts', description: 'Removed' },
297
+ ],
298
+ },
299
+ ],
300
+ };
301
+
302
+ render(<ReviewResource />);
303
+
304
+ expect(screen.getByText('File Changes')).toBeInTheDocument();
305
+ expect(screen.getByText('src/new.ts')).toBeInTheDocument();
306
+ expect(screen.getByText('New file')).toBeInTheDocument();
307
+ expect(screen.getByText('src/old.ts')).toBeInTheDocument();
308
+ expect(screen.getByText('Updated imports')).toBeInTheDocument();
309
+ });
310
+
311
+ it('renders preview section', () => {
312
+ mockWidgetData = {
313
+ title: 'Test',
314
+ sections: [
315
+ {
316
+ title: 'Preview',
317
+ type: 'preview',
318
+ content: 'This is the preview content that will be displayed.',
319
+ },
320
+ ],
321
+ };
322
+
323
+ render(<ReviewResource />);
324
+
325
+ expect(screen.getByText('Preview')).toBeInTheDocument();
326
+ expect(
327
+ screen.getByText('This is the preview content that will be displayed.')
328
+ ).toBeInTheDocument();
329
+ });
330
+
331
+ it('renders summary section', () => {
332
+ mockWidgetData = {
333
+ title: 'Test',
334
+ sections: [
335
+ {
336
+ type: 'summary',
337
+ content: [
338
+ { label: 'Subtotal', value: '$20.00' },
339
+ { label: 'Total', value: '$25.00', emphasis: true },
340
+ ],
341
+ },
342
+ ],
343
+ };
344
+
345
+ render(<ReviewResource />);
346
+
347
+ expect(screen.getByText('Subtotal')).toBeInTheDocument();
348
+ expect(screen.getByText('$20.00')).toBeInTheDocument();
349
+ expect(screen.getByText('Total')).toBeInTheDocument();
350
+ expect(screen.getByText('$25.00')).toBeInTheDocument();
351
+ });
352
+ });
353
+
354
+ describe('Alerts', () => {
355
+ it('renders info alert', () => {
356
+ mockWidgetData = {
357
+ title: 'Test',
358
+ alerts: [{ type: 'info', message: 'This is informational' }],
359
+ };
360
+
361
+ render(<ReviewResource />);
362
+
363
+ expect(screen.getByText('This is informational')).toBeInTheDocument();
364
+ });
365
+
366
+ it('renders warning alert', () => {
367
+ mockWidgetData = {
368
+ title: 'Test',
369
+ alerts: [{ type: 'warning', message: 'Please review carefully' }],
370
+ };
371
+
372
+ render(<ReviewResource />);
373
+
374
+ expect(screen.getByText('Please review carefully')).toBeInTheDocument();
375
+ });
376
+
377
+ it('renders error alert', () => {
378
+ mockWidgetData = {
379
+ title: 'Test',
380
+ alerts: [{ type: 'error', message: 'Something went wrong' }],
381
+ };
382
+
383
+ render(<ReviewResource />);
384
+
385
+ expect(screen.getByText('Something went wrong')).toBeInTheDocument();
386
+ });
387
+
388
+ it('renders success alert', () => {
389
+ mockWidgetData = {
390
+ title: 'Test',
391
+ alerts: [{ type: 'success', message: 'All checks passed' }],
392
+ };
393
+
394
+ render(<ReviewResource />);
395
+
396
+ expect(screen.getByText('All checks passed')).toBeInTheDocument();
397
+ });
398
+
399
+ it('renders multiple alerts', () => {
400
+ mockWidgetData = {
401
+ title: 'Test',
402
+ alerts: [
403
+ { type: 'warning', message: 'Warning message' },
404
+ { type: 'info', message: 'Info message' },
405
+ ],
406
+ };
407
+
408
+ render(<ReviewResource />);
409
+
410
+ expect(screen.getByText('Warning message')).toBeInTheDocument();
411
+ expect(screen.getByText('Info message')).toBeInTheDocument();
412
+ });
413
+ });
414
+
415
+ describe('Safe Area and Layout', () => {
416
+ it('respects safe area insets', () => {
417
+ mockSafeArea = { insets: { top: 20, bottom: 30, left: 10, right: 15 } };
418
+
419
+ const { container } = render(<ReviewResource />);
420
+ const mainDiv = container.firstChild as HTMLElement;
421
+
422
+ expect(mainDiv).toHaveStyle({
423
+ paddingTop: '20px',
424
+ paddingBottom: '30px',
425
+ paddingLeft: '10px',
426
+ paddingRight: '15px',
427
+ });
428
+ });
429
+
430
+ it('respects maxHeight constraint', () => {
431
+ mockMaxHeight = 400;
432
+
433
+ const { container } = render(<ReviewResource />);
434
+ const mainDiv = container.firstChild as HTMLElement;
435
+
436
+ expect(mainDiv).toHaveStyle({
437
+ maxHeight: '400px',
438
+ });
439
+ });
440
+
441
+ it('handles null safe area', () => {
442
+ mockSafeArea = null;
443
+
444
+ const { container } = render(<ReviewResource />);
445
+ const mainDiv = container.firstChild as HTMLElement;
446
+
447
+ expect(mainDiv).toHaveStyle({
448
+ paddingTop: '0px',
449
+ paddingBottom: '0px',
450
+ paddingLeft: '0px',
451
+ paddingRight: '0px',
452
+ });
453
+ });
454
+
455
+ it('handles null maxHeight', () => {
456
+ mockMaxHeight = null;
457
+
458
+ const { container } = render(<ReviewResource />);
459
+ const mainDiv = container.firstChild as HTMLElement;
460
+
461
+ expect(mainDiv.style.maxHeight).toBe('');
462
+ });
463
+ });
464
+
465
+ describe('Touch Device Support', () => {
466
+ it('renders larger buttons for touch devices', () => {
467
+ mockUserAgent = { device: { type: 'mobile' }, capabilities: { hover: false, touch: true } };
468
+
469
+ render(<ReviewResource />);
470
+
471
+ const acceptButton = screen.getByText('Confirm');
472
+ const rejectButton = screen.getByText('Cancel');
473
+
474
+ expect(acceptButton).toHaveAttribute('data-size', 'lg');
475
+ expect(rejectButton).toHaveAttribute('data-size', 'lg');
476
+ });
477
+
478
+ it('renders standard buttons for non-touch devices', () => {
479
+ mockUserAgent = { device: { type: 'desktop' }, capabilities: { hover: true, touch: false } };
480
+
481
+ render(<ReviewResource />);
482
+
483
+ const acceptButton = screen.getByText('Confirm');
484
+ const rejectButton = screen.getByText('Cancel');
485
+
486
+ expect(acceptButton).toHaveAttribute('data-size', 'md');
487
+ expect(rejectButton).toHaveAttribute('data-size', 'md');
488
+ });
489
+
490
+ it('handles null userAgent gracefully', () => {
491
+ mockUserAgent = null;
492
+
493
+ render(<ReviewResource />);
494
+
495
+ const acceptButton = screen.getByText('Confirm');
496
+ expect(acceptButton).toHaveAttribute('data-size', 'md');
497
+ });
498
+ });
499
+
500
+ describe('Fullscreen Mode', () => {
501
+ it('shows expand button when not in fullscreen mode', () => {
502
+ mockDisplayMode = 'inline';
503
+
504
+ render(<ReviewResource />);
505
+
506
+ expect(screen.getByTestId('expand-icon')).toBeInTheDocument();
507
+ });
508
+
509
+ it('hides expand button when in fullscreen mode', () => {
510
+ mockDisplayMode = 'fullscreen';
511
+
512
+ render(<ReviewResource />);
513
+
514
+ expect(screen.queryByTestId('expand-icon')).not.toBeInTheDocument();
515
+ });
516
+
517
+ it('calls requestDisplayMode when expand button clicked', () => {
518
+ mockDisplayMode = 'inline';
519
+
520
+ render(<ReviewResource />);
521
+
522
+ const expandButton = screen.getByLabelText('Enter fullscreen');
523
+ fireEvent.click(expandButton);
524
+
525
+ expect(mockRequestDisplayMode).toHaveBeenCalledWith({ mode: 'fullscreen' });
526
+ });
527
+ });
528
+
529
+ describe('Ref Forwarding', () => {
530
+ it('forwards ref to the container div', () => {
531
+ const ref = vi.fn();
532
+ render(<ReviewResource ref={ref} />);
533
+
534
+ expect(ref).toHaveBeenCalled();
535
+ expect(ref.mock.calls[0][0]).toBeInstanceOf(HTMLDivElement);
536
+ });
537
+ });
538
+ });
@@ -12,14 +12,14 @@ import { Button } from '@openai/apps-sdk-ui/components/Button';
12
12
  import { ExpandLg } from '@openai/apps-sdk-ui/components/Icon';
13
13
 
14
14
  /**
15
- * Production-ready Confirmation Resource
15
+ * Production-ready Review Resource
16
16
  *
17
- * A flexible confirmation dialog that adapts to various use cases:
18
- * - Purchase confirmations (items, totals, payment)
19
- * - Code change confirmations (file changes with diffs)
20
- * - Social media post confirmations (content preview)
21
- * - Booking confirmations (details, dates, prices)
22
- * - Generic action confirmations (simple approve/reject)
17
+ * A flexible review dialog that adapts to various use cases:
18
+ * - Purchase reviews (items, totals, payment)
19
+ * - Code change reviews (file changes with diffs)
20
+ * - Social media post reviews (content preview)
21
+ * - Booking reviews (details, dates, prices)
22
+ * - Generic action reviews (simple approve/reject)
23
23
  */
24
24
 
25
25
  // ============================================================================
@@ -75,15 +75,15 @@ interface Section {
75
75
  content: Detail[] | Item[] | Change[] | string;
76
76
  }
77
77
 
78
- /** Tool call configuration for domain-specific confirmation actions */
79
- interface ConfirmationTool {
78
+ /** Tool call configuration for domain-specific review actions */
79
+ interface ReviewTool {
80
80
  /** Tool name to call (e.g., "complete_purchase", "publish_post") */
81
81
  name: string;
82
82
  /** Additional arguments to pass to the tool */
83
83
  arguments?: Record<string, unknown>;
84
84
  }
85
85
 
86
- interface ConfirmationData extends Record<string, unknown> {
86
+ interface ReviewData extends Record<string, unknown> {
87
87
  /** Main title */
88
88
  title: string;
89
89
  /** Optional description below title */
@@ -102,11 +102,11 @@ interface ConfirmationData extends Record<string, unknown> {
102
102
  acceptedMessage?: string;
103
103
  /** Message shown after rejecting */
104
104
  rejectedMessage?: string;
105
- /** Domain-specific tool to call on confirmation */
106
- confirmationTool?: ConfirmationTool;
105
+ /** Domain-specific tool to call on review */
106
+ reviewTool?: ReviewTool;
107
107
  }
108
108
 
109
- interface ConfirmationState extends Record<string, unknown> {
109
+ interface ReviewState extends Record<string, unknown> {
110
110
  decision?: 'accepted' | 'rejected' | null;
111
111
  decidedAt?: string | null;
112
112
  }
@@ -306,13 +306,13 @@ function AlertBanner({ alert }: { alert: Alert }) {
306
306
  // Main Component
307
307
  // ============================================================================
308
308
 
309
- export const ConfirmationResource = React.forwardRef<HTMLDivElement>((_props, ref) => {
310
- const data = useWidgetProps<ConfirmationData>(() => ({
311
- title: 'Confirm',
309
+ export const ReviewResource = React.forwardRef<HTMLDivElement>((_props, ref) => {
310
+ const data = useWidgetProps<ReviewData>(() => ({
311
+ title: 'Review',
312
312
  sections: [],
313
313
  }));
314
314
 
315
- const [widgetState, setWidgetState] = useWidgetState<ConfirmationState>(() => ({
315
+ const [widgetState, setWidgetState] = useWidgetState<ReviewState>(() => ({
316
316
  decision: null,
317
317
  decidedAt: null,
318
318
  }));
@@ -338,7 +338,7 @@ export const ConfirmationResource = React.forwardRef<HTMLDivElement>((_props, re
338
338
  decidedAt,
339
339
  });
340
340
 
341
- const tool = data.confirmationTool;
341
+ const tool = data.reviewTool;
342
342
  if (tool) {
343
343
  console.log('callTool', {
344
344
  name: tool.name,
@@ -358,7 +358,7 @@ export const ConfirmationResource = React.forwardRef<HTMLDivElement>((_props, re
358
358
  decidedAt,
359
359
  });
360
360
 
361
- const tool = data.confirmationTool;
361
+ const tool = data.reviewTool;
362
362
  if (tool) {
363
363
  console.log('callTool', {
364
364
  name: tool.name,
@@ -476,4 +476,4 @@ export const ConfirmationResource = React.forwardRef<HTMLDivElement>((_props, re
476
476
  </div>
477
477
  );
478
478
  });
479
- ConfirmationResource.displayName = 'ConfirmationResource';
479
+ ReviewResource.displayName = 'ReviewResource';
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "userMessage": "I'd like to refactor the authentication module",
3
3
  "tool": {
4
- "name": "diff-confirmation",
5
- "description": "Show a confirmation dialog for a proposed code diff",
4
+ "name": "diff-review",
5
+ "description": "Show a review dialog for a proposed code diff",
6
6
  "inputSchema": { "type": "object", "properties": {}, "additionalProperties": false },
7
- "title": "Diff Confirmation",
7
+ "title": "Diff Review",
8
8
  "annotations": { "readOnlyHint": false },
9
9
  "_meta": {
10
10
  "openai/toolInvocation/invoking": "Preparing changes",
@@ -62,7 +62,7 @@
62
62
  "rejectLabel": "Cancel",
63
63
  "acceptedMessage": "Changes applied",
64
64
  "rejectedMessage": "Changes cancelled",
65
- "confirmationTool": {
65
+ "reviewTool": {
66
66
  "name": "apply_changes",
67
67
  "arguments": {
68
68
  "changesetId": "cs_789",
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "userMessage": "Post this to my social media",
3
3
  "tool": {
4
- "name": "confirm-post",
5
- "description": "Confirm a social media post before publishing",
4
+ "name": "review-post",
5
+ "description": "Review a social media post before publishing",
6
6
  "inputSchema": { "type": "object", "properties": {}, "additionalProperties": false },
7
- "title": "Confirm Post",
7
+ "title": "Review Post",
8
8
  "annotations": { "readOnlyHint": false },
9
9
  "_meta": {
10
10
  "openai/toolInvocation/invoking": "Preparing post",
@@ -43,7 +43,7 @@
43
43
  "rejectLabel": "Cancel",
44
44
  "acceptedMessage": "Post published!",
45
45
  "rejectedMessage": "Post cancelled",
46
- "confirmationTool": {
46
+ "reviewTool": {
47
47
  "name": "publish_post",
48
48
  "arguments": {
49
49
  "postId": "draft_456",