@flamingo-stack/openframe-frontend-core 0.0.420 → 0.0.421-snapshot.20260713170448

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 (113) hide show
  1. package/dist/{chunk-WBLTGO7G.cjs → chunk-2JNFQEVX.cjs} +26 -26
  2. package/dist/{chunk-WBLTGO7G.cjs.map → chunk-2JNFQEVX.cjs.map} +1 -1
  3. package/dist/{chunk-T55A3BQD.js → chunk-3BGWK5QI.js} +4 -4
  4. package/dist/{chunk-ZZFLE5LG.js → chunk-4BWQYGIY.js} +2 -2
  5. package/dist/{chunk-KEWTMPHB.js → chunk-4TMMYZTY.js} +2 -2
  6. package/dist/{chunk-U7EUN5FZ.js → chunk-6F5OKJTW.js} +4 -4
  7. package/dist/{chunk-KLMLQ35Y.cjs → chunk-A2IRUIUJ.cjs} +37 -37
  8. package/dist/{chunk-KLMLQ35Y.cjs.map → chunk-A2IRUIUJ.cjs.map} +1 -1
  9. package/dist/{chunk-KPLRIPEH.cjs → chunk-B62DI2SC.cjs} +15 -15
  10. package/dist/{chunk-KPLRIPEH.cjs.map → chunk-B62DI2SC.cjs.map} +1 -1
  11. package/dist/{chunk-PUXTMLKC.cjs → chunk-BF5BXKM7.cjs} +62 -62
  12. package/dist/{chunk-PUXTMLKC.cjs.map → chunk-BF5BXKM7.cjs.map} +1 -1
  13. package/dist/{chunk-PAF4EFRR.js → chunk-DUHM44CQ.js} +2 -2
  14. package/dist/{chunk-YYF2AYM3.js → chunk-HFK33T6A.js} +4 -4
  15. package/dist/{chunk-J7WFE7WL.js → chunk-JSL3VXGJ.js} +2 -2
  16. package/dist/{chunk-BXXXBZYY.cjs → chunk-KUDLV25M.cjs} +190 -154
  17. package/dist/chunk-KUDLV25M.cjs.map +1 -0
  18. package/dist/{chunk-OBMYKFBO.js → chunk-LHM2VUTO.js} +2 -2
  19. package/dist/{chunk-AMUTZJZX.cjs → chunk-LWC2U5OC.cjs} +9 -9
  20. package/dist/{chunk-AMUTZJZX.cjs.map → chunk-LWC2U5OC.cjs.map} +1 -1
  21. package/dist/{chunk-2AN4EHN6.cjs → chunk-O3FSSWV4.cjs} +7 -7
  22. package/dist/{chunk-2AN4EHN6.cjs.map → chunk-O3FSSWV4.cjs.map} +1 -1
  23. package/dist/{chunk-PHJ6335O.cjs → chunk-PO4PXWZN.cjs} +5 -5
  24. package/dist/{chunk-PHJ6335O.cjs.map → chunk-PO4PXWZN.cjs.map} +1 -1
  25. package/dist/{chunk-ORDDVXDS.cjs → chunk-RGKGRS2T.cjs} +59 -59
  26. package/dist/{chunk-ORDDVXDS.cjs.map → chunk-RGKGRS2T.cjs.map} +1 -1
  27. package/dist/{chunk-5DTE27A4.cjs → chunk-ULUH2X53.cjs} +11 -11
  28. package/dist/{chunk-5DTE27A4.cjs.map → chunk-ULUH2X53.cjs.map} +1 -1
  29. package/dist/{chunk-NIS5VXZJ.cjs → chunk-WAQH35VG.cjs} +26 -26
  30. package/dist/{chunk-NIS5VXZJ.cjs.map → chunk-WAQH35VG.cjs.map} +1 -1
  31. package/dist/{chunk-G4W5D7L4.js → chunk-WQEEGFDM.js} +2 -2
  32. package/dist/{chunk-GC4355MW.js → chunk-XFYQV2K2.js} +2 -2
  33. package/dist/{chunk-3AJOKFJ4.js → chunk-YJDVAT7J.js} +66 -30
  34. package/dist/chunk-YJDVAT7J.js.map +1 -0
  35. package/dist/{chunk-4OZLRBOH.js → chunk-YUMFRC3D.js} +4 -4
  36. package/dist/{chunk-NDYJ37LY.cjs → chunk-YZQNKMFQ.cjs} +31 -31
  37. package/dist/{chunk-NDYJ37LY.cjs.map → chunk-YZQNKMFQ.cjs.map} +1 -1
  38. package/dist/components/case-studies/index.cjs +8 -8
  39. package/dist/components/case-studies/index.js +2 -2
  40. package/dist/components/chat/chat-input.d.ts.map +1 -1
  41. package/dist/components/chat/embeddable-chat.d.ts +9 -0
  42. package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
  43. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
  44. package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts.map +1 -1
  45. package/dist/components/chat/index.cjs +2 -2
  46. package/dist/components/chat/index.js +1 -1
  47. package/dist/components/chat/types/api.types.d.ts +10 -0
  48. package/dist/components/chat/types/api.types.d.ts.map +1 -1
  49. package/dist/components/contact/index.cjs +3 -3
  50. package/dist/components/contact/index.js +2 -2
  51. package/dist/components/docs/index.cjs +5 -5
  52. package/dist/components/docs/index.js +4 -4
  53. package/dist/components/embeds/index.cjs +3 -3
  54. package/dist/components/embeds/index.js +2 -2
  55. package/dist/components/faq/index.cjs +3 -3
  56. package/dist/components/faq/index.js +2 -2
  57. package/dist/components/features/index.cjs +2 -2
  58. package/dist/components/features/index.js +1 -1
  59. package/dist/components/help-center-pages/index.cjs +20 -20
  60. package/dist/components/help-center-pages/index.js +11 -11
  61. package/dist/components/index.cjs +128 -128
  62. package/dist/components/index.js +9 -9
  63. package/dist/components/navigation/index.cjs +2 -2
  64. package/dist/components/navigation/index.js +1 -1
  65. package/dist/components/onboarding-guides/index.cjs +5 -5
  66. package/dist/components/onboarding-guides/index.js +4 -4
  67. package/dist/components/related-content/index.cjs +3 -3
  68. package/dist/components/related-content/index.js +2 -2
  69. package/dist/components/tabs.d.ts +10 -0
  70. package/dist/components/tabs.d.ts.map +1 -1
  71. package/dist/components/tickets/index.cjs +5 -5
  72. package/dist/components/tickets/index.js +4 -4
  73. package/dist/components/ui/dashboard-info-card.d.ts +26 -12
  74. package/dist/components/ui/dashboard-info-card.d.ts.map +1 -1
  75. package/dist/components/ui/index.cjs +2 -2
  76. package/dist/components/ui/index.js +1 -1
  77. package/dist/components/ui/tab-navigation.d.ts.map +1 -1
  78. package/dist/components/ui/tabs.d.ts +11 -0
  79. package/dist/components/ui/tabs.d.ts.map +1 -1
  80. package/dist/index.cjs +2 -2
  81. package/dist/index.js +1 -1
  82. package/package.json +1 -1
  83. package/src/components/chat/chat-input.tsx +5 -1
  84. package/src/components/chat/embeddable-chat.tsx +17 -3
  85. package/src/components/chat/hooks/use-nats-chat-adapter.ts +52 -3
  86. package/src/components/chat/hooks/use-realtime-chunk-processor.ts +25 -3
  87. package/src/components/chat/types/api.types.ts +10 -0
  88. package/src/components/tabs.tsx +10 -0
  89. package/src/components/ui/dashboard-info-card.tsx +70 -45
  90. package/src/components/ui/tab-navigation.tsx +5 -2
  91. package/src/components/ui/tabs.tsx +11 -0
  92. package/src/stories/ApprovalRequestMessageClient.stories.tsx +89 -0
  93. package/src/stories/ChatMessageEnhanced.stories.tsx +219 -0
  94. package/src/stories/ChatTypingIndicator.stories.tsx +51 -0
  95. package/src/stories/ContextCompactionDisplay.stories.tsx +44 -0
  96. package/src/stories/DashboardInfoCard.stories.tsx +94 -9
  97. package/src/stories/EmbeddableChat.stories.tsx +159 -0
  98. package/src/stories/ErrorMessageDisplay.stories.tsx +66 -0
  99. package/src/stories/ThinkingDisplay.stories.tsx +63 -0
  100. package/src/stories/TicketCard.stories.tsx +245 -0
  101. package/dist/chunk-3AJOKFJ4.js.map +0 -1
  102. package/dist/chunk-BXXXBZYY.cjs.map +0 -1
  103. /package/dist/{chunk-T55A3BQD.js.map → chunk-3BGWK5QI.js.map} +0 -0
  104. /package/dist/{chunk-ZZFLE5LG.js.map → chunk-4BWQYGIY.js.map} +0 -0
  105. /package/dist/{chunk-KEWTMPHB.js.map → chunk-4TMMYZTY.js.map} +0 -0
  106. /package/dist/{chunk-U7EUN5FZ.js.map → chunk-6F5OKJTW.js.map} +0 -0
  107. /package/dist/{chunk-PAF4EFRR.js.map → chunk-DUHM44CQ.js.map} +0 -0
  108. /package/dist/{chunk-YYF2AYM3.js.map → chunk-HFK33T6A.js.map} +0 -0
  109. /package/dist/{chunk-J7WFE7WL.js.map → chunk-JSL3VXGJ.js.map} +0 -0
  110. /package/dist/{chunk-OBMYKFBO.js.map → chunk-LHM2VUTO.js.map} +0 -0
  111. /package/dist/{chunk-G4W5D7L4.js.map → chunk-WQEEGFDM.js.map} +0 -0
  112. /package/dist/{chunk-GC4355MW.js.map → chunk-XFYQV2K2.js.map} +0 -0
  113. /package/dist/{chunk-4OZLRBOH.js.map → chunk-YUMFRC3D.js.map} +0 -0
@@ -0,0 +1,51 @@
1
+ import type { Meta, StoryObj } from "@storybook/nextjs-vite";
2
+ import type React from "react";
3
+
4
+ import { ChatTypingIndicator } from "../components/chat/chat-typing-indicator";
5
+
6
+ /**
7
+ * Three pulsing dots shown while the assistant reply streams in — the
8
+ * "Fae is typing" affordance under the message header. Also rendered
9
+ * inside `<ChatInput>` (size="sm") while a send is in flight.
10
+ */
11
+
12
+ const plainDecorator = (Story: React.ComponentType) => (
13
+ <div style={{ maxWidth: 400, background: "var(--color-bg)" }} className="p-4">
14
+ <Story />
15
+ </div>
16
+ );
17
+
18
+ const meta = {
19
+ title: "Chat/ChatTypingIndicator",
20
+ component: ChatTypingIndicator,
21
+ tags: ["autodocs"],
22
+ parameters: {
23
+ docs: {
24
+ description: {
25
+ component:
26
+ "Pulsing three-dot typing indicator. Sizes sm/md/lg; optional 'Assistant is typing' label; dot color overridable via dotClassName.",
27
+ },
28
+ },
29
+ },
30
+ decorators: [plainDecorator],
31
+ } satisfies Meta<typeof ChatTypingIndicator>;
32
+
33
+ export default meta;
34
+ type Story = StoryObj<typeof meta>;
35
+
36
+ /** Default — medium dots, no label. */
37
+ export const Default: Story = {};
38
+
39
+ /** Small — as rendered inside ChatInput while a send is in flight. */
40
+ export const Small: Story = { args: { size: "sm" } };
41
+
42
+ /** Large dots. */
43
+ export const Large: Story = { args: { size: "lg" } };
44
+
45
+ /** With the "Assistant is typing" label. */
46
+ export const WithText: Story = { args: { showText: true } };
47
+
48
+ /** Muted dots via dotClassName override. */
49
+ export const MutedDots: Story = {
50
+ args: { dotClassName: "bg-ods-text-secondary" },
51
+ };
@@ -0,0 +1,44 @@
1
+ import type { Meta, StoryObj } from "@storybook/nextjs-vite";
2
+ import type React from "react";
3
+
4
+ import { ContextCompactionDisplay } from "../components/chat/context-compaction-display";
5
+
6
+ /**
7
+ * Inline status pill for `context_compaction` message segments — shown when
8
+ * the conversation hits the context limit and earlier messages get
9
+ * summarized. `started` shows a dots loader, `completed` a green check.
10
+ */
11
+
12
+ const plainDecorator = (Story: React.ComponentType) => (
13
+ <div style={{ maxWidth: 640, background: "var(--color-bg)" }} className="p-4">
14
+ <Story />
15
+ </div>
16
+ );
17
+
18
+ const meta = {
19
+ title: "Chat/ContextCompactionDisplay",
20
+ component: ContextCompactionDisplay,
21
+ tags: ["autodocs"],
22
+ parameters: {
23
+ docs: {
24
+ description: {
25
+ component:
26
+ "Renders a `context_compaction` message segment — 'Context limit reached…' with a dots loader while running, 'Earlier context summarized.' with a check once done.",
27
+ },
28
+ },
29
+ },
30
+ decorators: [plainDecorator],
31
+ } satisfies Meta<typeof ContextCompactionDisplay>;
32
+
33
+ export default meta;
34
+ type Story = StoryObj<typeof meta>;
35
+
36
+ /** In progress — summarizing earlier messages. */
37
+ export const Started: Story = {
38
+ args: { status: "started" },
39
+ };
40
+
41
+ /** Finished — earlier context summarized. */
42
+ export const Completed: Story = {
43
+ args: { status: "completed" },
44
+ };
@@ -1,5 +1,8 @@
1
1
  import type { Meta, StoryObj } from '@storybook/nextjs-vite'
2
2
  import { DashboardInfoCard } from '../components/ui/dashboard-info-card'
3
+ import { Tag } from '../components/ui/tag'
4
+ import { MonitorIcon } from '../components/icons-v2-generated/devices/monitor-icon'
5
+ import { FaceSmile01Icon } from '../components/icons-v2-generated/users/face-smile-01-icon'
3
6
 
4
7
  const meta = {
5
8
  title: 'UI/DashboardInfoCard',
@@ -30,11 +33,13 @@ const meta = {
30
33
  },
31
34
  progressSize: {
32
35
  control: { type: 'number' },
33
- description: 'Ring diameter in px (number), or `{ base, md?, lg? }` for a responsive ring. Default 56.',
36
+ description: 'Tablet/desktop ring diameter in px (mobile always shrinks to 24px), or `{ base, md?, lg? }` for full control. Default `{ base: 24, md: 56 }` (Figma spec).',
34
37
  },
35
38
  href: { control: 'text', description: 'Navigation URL — renders as a Next.js Link with pointer cursor' },
36
39
  tooltip: { control: 'text', description: 'Tooltip content for the question-mark icon' },
37
40
  valueClassName: { control: 'text', description: 'Override className for the value text' },
41
+ titleTag: { control: false, description: 'Tag rendered in the title row after the title (Figma "status" variant)' },
42
+ icon: { control: false, description: 'Icon or image in a bordered square slot on the left (Figma "icon" variant)' },
38
43
  },
39
44
  } satisfies Meta<typeof DashboardInfoCard>
40
45
 
@@ -276,7 +281,6 @@ export const DeviceStatusesOverview: Story = {
276
281
  showProgress
277
282
  progressVariant="success"
278
283
  percentageDisplay="plain"
279
- progressSize={{ base: 24, md: 56 }}
280
284
  />
281
285
  <DashboardInfoCard
282
286
  title="Offline Devices"
@@ -285,7 +289,6 @@ export const DeviceStatusesOverview: Story = {
285
289
  showProgress
286
290
  progressVariant="error"
287
291
  percentageDisplay="plain"
288
- progressSize={{ base: 24, md: 56 }}
289
292
  />
290
293
  <DashboardInfoCard
291
294
  title="Pending Devices"
@@ -294,7 +297,6 @@ export const DeviceStatusesOverview: Story = {
294
297
  showProgress
295
298
  progressVariant="warning"
296
299
  percentageDisplay="plain"
297
- progressSize={{ base: 24, md: 56 }}
298
300
  />
299
301
  <DashboardInfoCard
300
302
  title="Archived Devices"
@@ -303,7 +305,6 @@ export const DeviceStatusesOverview: Story = {
303
305
  showProgress
304
306
  progressVariant="info"
305
307
  percentageDisplay="plain"
306
- progressSize={{ base: 24, md: 56 }}
307
308
  />
308
309
  </div>
309
310
  ),
@@ -311,16 +312,17 @@ export const DeviceStatusesOverview: Story = {
311
312
  docs: {
312
313
  description: {
313
314
  story:
314
- 'Matches the Figma "Devices Overview" — green/red/amber/grey rings with neutral percentages via `percentageDisplay="plain"`, and a responsive ring (`progressSize={{ base: 24, md: 56 }}`) that is 24px on mobile and 56px on tablet and desktop (from the `md` breakpoint up).',
315
+ 'Matches the Figma "Devices Overview" — green/red/amber/grey rings with neutral percentages via `percentageDisplay="plain"`. The responsive ring (24px on mobile, 56px from the `md` breakpoint up) is now the default — no `progressSize` needed.',
315
316
  },
316
317
  },
317
318
  },
318
319
  }
319
320
 
320
321
  /**
321
- * `progressSize` controls the ring diameter (stroke scales with it). Left: the
322
- * 24px mobile spec. Right: the 56px desktop default. In production pass
323
- * `progressSize={{ base: 24, md: 56 }}` for the responsive switch.
322
+ * `progressSize` controls the tablet/desktop ring diameter (stroke scales with
323
+ * it); on mobile the ring always shrinks to the 24px Figma spec. Left: 24px.
324
+ * Right: the 56px default. Pass `{ base: n }` to pin one size at every
325
+ * breakpoint.
324
326
  */
325
327
  export const ProgressSizeComparison: Story = {
326
328
  render: () => (
@@ -347,6 +349,89 @@ export const ProgressSizeComparison: Story = {
347
349
  ),
348
350
  }
349
351
 
352
+ /**
353
+ * Tag in the title row instead of a plain title — matches the Figma
354
+ * dashboard-card "status" variant (e.g. tickets by status).
355
+ */
356
+ export const WithTitleTag: Story = {
357
+ args: {
358
+ value: 75,
359
+ titleTag: <Tag variant="outline" label="AI-Assistance" />,
360
+ },
361
+ parameters: {
362
+ docs: {
363
+ description: {
364
+ story:
365
+ 'The Figma "status" variant: `titleTag` renders a `Tag` in the title row. Here without a `title`; combine with one for "TITLE + tag" (they sit in a 4px-gap row).',
366
+ },
367
+ },
368
+ },
369
+ }
370
+
371
+ /**
372
+ * Title and tag together in the title row.
373
+ */
374
+ export const WithTitleAndTag: Story = {
375
+ args: {
376
+ title: 'Tickets',
377
+ value: 75,
378
+ titleTag: <Tag variant="outline" label="Resolved" />,
379
+ },
380
+ }
381
+
382
+ /**
383
+ * Icon in a bordered square slot on the left — matches the Figma
384
+ * dashboard-card "icon" variant (32px box on mobile, 56px from `md` up).
385
+ */
386
+ export const WithIcon: Story = {
387
+ args: {
388
+ title: 'Endpoints',
389
+ value: 4046,
390
+ icon: <MonitorIcon />,
391
+ },
392
+ }
393
+
394
+ /**
395
+ * An `<img>` in the icon slot — the node is stretched to fill the slot's
396
+ * content area, so images work the same as icon components.
397
+ */
398
+ export const WithImage: Story = {
399
+ args: {
400
+ title: 'Organization',
401
+ value: 'Flamingo',
402
+ icon: <img src="https://github.com/flamingo-stack.png" alt="Flamingo" className="rounded-[2px] object-cover" />,
403
+ },
404
+ }
405
+
406
+ /**
407
+ * Every prop at once: icon slot, title + tag, sub-value, percentage,
408
+ * colored progress ring with wrap overflow, and navigation href.
409
+ */
410
+ export const KitchenSink: Story = {
411
+ args: {
412
+ title: 'Devices',
413
+ titleTag: <Tag variant="outline" label="Live" />,
414
+ icon: <FaceSmile01Icon />,
415
+ value: 4046,
416
+ subValue: '12 sites',
417
+ percentage: 104,
418
+ showProgress: true,
419
+ progressVariant: 'warning',
420
+ percentageDisplay: 'tag',
421
+ progressOverflow: 'wrap',
422
+ progressSize: { base: 24, md: 56 },
423
+ href: '/devices',
424
+ },
425
+ parameters: {
426
+ docs: {
427
+ description: {
428
+ story:
429
+ 'All props together: `icon`, `title` + `titleTag`, `value` + `subValue`, `percentage` as a warning `Tag` (`percentageDisplay="tag"`), a wrap-overflow warning ring (`progressOverflow="wrap"`), explicit responsive `progressSize`, and `href`. The `tooltip` prop (question-mark trigger) is shown separately in the WithTooltip story.',
430
+ },
431
+ },
432
+ },
433
+ }
434
+
350
435
  /**
351
436
  * Multiple cards in a dashboard grid layout.
352
437
  */
@@ -1,6 +1,7 @@
1
1
  import type { Meta, StoryObj } from '@storybook/nextjs-vite'
2
2
  import { within, userEvent } from 'storybook/test'
3
3
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
4
+ import * as DialogPrimitive from '@radix-ui/react-dialog'
4
5
  import React, { useMemo } from 'react'
5
6
  import {
6
7
  ChatRuntimeContext,
@@ -14,6 +15,10 @@ import type {
14
15
  } from '../components/chat/hooks/use-nats-chat-adapter'
15
16
  import type { SlashCommandSummary } from '../components/chat/hooks/use-slash-commands'
16
17
  import type { DialogItem } from '../components/chat/types/component.types'
18
+ import type {
19
+ UnifiedChatState,
20
+ UnifiedChatMessage,
21
+ } from '../components/chat/types/unified-chat-state.types'
17
22
  import type { MingoQuickAction } from '../components/chat/mingo-welcome'
18
23
  import {
19
24
  BracketCurlyIcon,
@@ -775,3 +780,157 @@ export const AgentChipsWinOverHost: Story = {
775
780
  },
776
781
  },
777
782
  }
783
+
784
+ // =============================================================================
785
+ // 10. Scripted Mingo conversation — Figma "Flamingo Website" node 4050:23837
786
+ // =============================================================================
787
+
788
+ // Simple gradient user-avatar as a self-contained data URI — no external asset
789
+ // dependency in the story. Roman's `user` bubble shows it via `message.avatar`.
790
+ const ROMAN_AVATAR =
791
+ 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPSc0MCcgaGVpZ2h0PSc0MCc+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSdnJyB4MT0nMCcgeTE9JzAnIHgyPScxJyB5Mj0nMSc+PHN0b3Agb2Zmc2V0PScwJyBzdG9wLWNvbG9yPScjNWVmYWYwJy8+PHN0b3Agb2Zmc2V0PScxJyBzdG9wLWNvbG9yPScjZmY2YjlkJy8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PGNpcmNsZSBjeD0nMjAnIGN5PScyMCcgcj0nMjAnIGZpbGw9J3VybCgjZyknLz48Y2lyY2xlIGN4PScyMCcgY3k9JzE2JyByPSc2LjUnIGZpbGw9JyMxNjE2MTYnIG9wYWNpdHk9JzAuODUnLz48cGF0aCBkPSdNOCAzNGMxLjUtNiA2LTkgMTItOXMxMC41IDMgMTIgOXonIGZpbGw9JyMxNjE2MTYnIG9wYWNpdHk9JzAuODUnLz48L3N2Zz4='
792
+
793
+ // Fixed timestamp so the bubbles render a stable "2:47 PM" (matches the Figma
794
+ // mock) instead of the current clock — 14:47 local → 2:47 PM.
795
+ const TS_247 = new Date(2024, 0, 1, 14, 47, 0)
796
+
797
+ // The exact PowerShell command from the Figma command block. Passed as
798
+ // `toolTitle` (not a `command`/`script` parameter) so `getToolCallTitle`
799
+ // renders this whole string verbatim — including the backticks — as the
800
+ // collapsed command-block preview line.
801
+ const RUN_SCRIPT_TITLE =
802
+ '`run_script` -- PowerShell: `Get-LocalUser | Where-Object { $_.PasswordRequired -eq $false -or $_.PasswordLastSet -lt (Get-Date).AddDays(-90) }`'
803
+
804
+ const SCRIPTED_MESSAGES: UnifiedChatMessage[] = [
805
+ {
806
+ id: 'm1',
807
+ role: 'assistant',
808
+ name: 'Mingo',
809
+ authorType: 'mingo', // cyan name (Figma flamingo-colors/cyan)
810
+ content: 'How can I help?',
811
+ timestamp: TS_247,
812
+ },
813
+ {
814
+ id: 'm2',
815
+ role: 'user',
816
+ name: 'Roman Smith',
817
+ authorType: 'admin', // yellow name (Figma open-colors/yellow)
818
+ avatar: ROMAN_AVATAR,
819
+ content: 'Check machines with weak password',
820
+ timestamp: TS_247,
821
+ },
822
+ {
823
+ id: 'm3',
824
+ role: 'assistant',
825
+ name: 'Mingo',
826
+ authorType: 'mingo',
827
+ content: '', // body lives entirely in `segments`
828
+ timestamp: TS_247,
829
+ segments: [
830
+ {
831
+ type: 'tool_execution',
832
+ data: {
833
+ type: 'EXECUTING_TOOL', // → DotsLoader status + collapsed command block
834
+ integratedToolType: 'OPENFRAME',
835
+ toolFunction: 'run_script',
836
+ toolTitle: RUN_SCRIPT_TITLE,
837
+ parameters: {},
838
+ },
839
+ },
840
+ ],
841
+ },
842
+ ]
843
+
844
+ /**
845
+ * Fully-stubbed `UnifiedChatState` factory. This is the "inject a static Mingo
846
+ * thread" path (`EmbeddableChat`'s `mingoState` prop): the panel renders this
847
+ * object directly and opens NO NATS subscription. Every action is a no-op so
848
+ * the scene stays read-only. `isLoading: true` + `streamingPhase: 'thinking'`
849
+ * drives the trailing "Mingo is working" dots loader under the command block.
850
+ */
851
+ function createScriptedMingoState(): UnifiedChatState {
852
+ const noop = () => {}
853
+ const asyncNoop = async () => {}
854
+ return {
855
+ messages: SCRIPTED_MESSAGES,
856
+ isLoading: true,
857
+ streamingPhase: 'thinking',
858
+ sendMessage: asyncNoop,
859
+ stopMessage: noop,
860
+ clearMessages: noop,
861
+ discussRef: noop,
862
+ displayRef: noop,
863
+ // Footer model chip — anthropic mark + "Claude Opus 4.1" (Figma footer).
864
+ currentProvider: 'anthropic',
865
+ currentModelLabel: 'Claude Opus 4.1',
866
+ currentContextWindowMaxTokens: null,
867
+ currentInputTokens: null,
868
+ currentOutputTokens: null,
869
+ currentCacheHitRatePct: null,
870
+ currentUsageBreakdown: null,
871
+ // A single active dialog titled "New Chat" so the header shows that title
872
+ // and the ⋯ menu (rename/archive) renders like the mock.
873
+ dialogs: [{ id: 'd-scripted', title: 'New Chat' }],
874
+ activeDialogId: 'd-scripted',
875
+ selectDialog: noop,
876
+ startNewDialog: async () => null,
877
+ deleteDialog: asyncNoop,
878
+ renameDialog: asyncNoop,
879
+ archiveDialog: asyncNoop,
880
+ isDialogsLoading: false,
881
+ dialogsError: false,
882
+ reloadDialogs: noop,
883
+ isMessagesLoading: false,
884
+ hasMoreDialogs: false,
885
+ loadMoreDialogs: asyncNoop,
886
+ hasMoreMessages: false,
887
+ loadMoreMessages: asyncNoop,
888
+ approveRequest: asyncNoop,
889
+ rejectRequest: asyncNoop,
890
+ dialogTokenUsage: null,
891
+ connectionState: 'connected',
892
+ }
893
+ }
894
+
895
+ /**
896
+ * Scripted Mingo conversation reproducing Figma "Flamingo Website" node
897
+ * 4050:23837 (the marketing hero chat panel): Mingo greeting → Roman Smith's
898
+ * "Check machines with weak password" → a PowerShell `run_script` command block
899
+ * (executing) → Mingo working (dots).
900
+ *
901
+ * Rendered via the `mingoState` injection path with `shell="none"`, so the chat
902
+ * body sits inline inside our own 440-wide framed box (matching the Figma
903
+ * frame) with zero backend — no NATS, no fetch, fully static + read-only.
904
+ *
905
+ * Known deviations from the mock (the shipped component is the source of
906
+ * truth): author names render with a trailing ":" ; the command-block icon is
907
+ * the OpenFrame mark (the `ToolIcon` registry has no PowerShell glyph); the
908
+ * composer placeholder reads "Ask a question…"; and the trailing loader is the
909
+ * component's dots+phrase streaming indicator.
910
+ */
911
+ export const ScriptedConversation: Story = {
912
+ parameters: { layout: 'fullscreen' },
913
+ render: () => (
914
+ // `shell="none"` expects the HOST to own the Radix Dialog context (in the
915
+ // app, AppLayoutDrawer does). Storybook is that host here, so we supply a
916
+ // headless `Dialog.Root` — it renders no DOM and does not portal, so the
917
+ // panel's visually-hidden `Dialog.Title` gets its context without altering
918
+ // the inline layout.
919
+ <DialogPrimitive.Root open>
920
+ <div className="fixed inset-0 z-[10] flex items-center justify-center bg-ods-bg p-6">
921
+ <div className="w-[440px] h-[560px] overflow-hidden rounded-[6px] border border-ods-border shadow-2xl">
922
+ <EmbeddableChat
923
+ shell="none"
924
+ defaultOpen
925
+ defaultActiveMode="mingo"
926
+ showInternalTrigger={false}
927
+ previewMode
928
+ mingoState={createScriptedMingoState()}
929
+ mingoDialogCapabilities={{ canRename: true, canArchive: true }}
930
+ />
931
+ </div>
932
+ </div>
933
+ </DialogPrimitive.Root>
934
+ ),
935
+ args: {},
936
+ }
@@ -0,0 +1,66 @@
1
+ import type { Meta, StoryObj } from "@storybook/nextjs-vite";
2
+ import type React from "react";
3
+
4
+ import { ErrorMessageDisplay } from "../components/chat/error-message-display";
5
+
6
+ /**
7
+ * Inline error card for `error` message segments — mono uppercase title
8
+ * with an alert icon; optional details expand under the chevron.
9
+ * `type` tints the icon: error (red) / warning (yellow) / info (grey).
10
+ */
11
+
12
+ const plainDecorator = (Story: React.ComponentType) => (
13
+ <div style={{ maxWidth: 640, background: "var(--color-bg)" }} className="p-4">
14
+ <Story />
15
+ </div>
16
+ );
17
+
18
+ const meta = {
19
+ title: "Chat/ErrorMessageDisplay",
20
+ component: ErrorMessageDisplay,
21
+ tags: ["autodocs"],
22
+ parameters: {
23
+ docs: {
24
+ description: {
25
+ component:
26
+ "Renders an `error` message segment — collapsed mono title row, expandable details. Icon tint follows type: error / warning / info.",
27
+ },
28
+ },
29
+ },
30
+ decorators: [plainDecorator],
31
+ } satisfies Meta<typeof ErrorMessageDisplay>;
32
+
33
+ export default meta;
34
+ type Story = StoryObj<typeof meta>;
35
+
36
+ /** Error with expandable details. */
37
+ export const WithDetails: Story = {
38
+ args: {
39
+ title: "Tool execution failed",
40
+ details:
41
+ "run_script exited with code 1: brew not found on this endpoint. Install Homebrew first or use the direct .dmg fallback.",
42
+ },
43
+ };
44
+
45
+ /** Title only — no chevron, row is not clickable. */
46
+ export const TitleOnly: Story = {
47
+ args: { title: "Stream disconnected — retrying" },
48
+ };
49
+
50
+ /** Warning tint. */
51
+ export const Warning: Story = {
52
+ args: {
53
+ type: "warning",
54
+ title: "Approval expired",
55
+ details: "The approval request timed out after 15 minutes and was cancelled automatically.",
56
+ },
57
+ };
58
+
59
+ /** Info tint. */
60
+ export const Info: Story = {
61
+ args: {
62
+ type: "info",
63
+ title: "Session resumed",
64
+ details: "Reconnected to the stream; missed chunks were replayed from history.",
65
+ },
66
+ };
@@ -0,0 +1,63 @@
1
+ import type { Meta, StoryObj } from "@storybook/nextjs-vite";
2
+ import type React from "react";
3
+
4
+ import { ThinkingDisplay } from "../components/chat/thinking-display";
5
+
6
+ /**
7
+ * Collapsible "Thought" block for `thinking` message segments — muted
8
+ * markdown inside a bordered card, one line collapsed, expandable via the
9
+ * chevron. While the segment is still streaming the label reads "Thinking"
10
+ * and the collapse transition is disabled.
11
+ */
12
+
13
+ const SHORT_THOUGHT =
14
+ "The user wants Slack installed. Homebrew is present, so `brew install --cask slack` is the cleanest path.";
15
+
16
+ const LONG_THOUGHT = `The user asked to install Slack on their Mac.
17
+
18
+ **Options considered:**
19
+
20
+ 1. Direct \`.dmg\` download — needs manual mounting and copying, hard to automate.
21
+ 2. Homebrew Cask — \`brew install --cask slack\`, idempotent and scriptable.
22
+ 3. Mac App Store via \`mas\` — requires the user to be signed in to the store.
23
+
24
+ Homebrew is already present on this endpoint (verified in the device inventory), so option 2 is the cleanest path. The install needs client approval because it modifies installed software.`;
25
+
26
+ const plainDecorator = (Story: React.ComponentType) => (
27
+ <div style={{ maxWidth: 640, background: "var(--color-bg)" }} className="p-4">
28
+ <Story />
29
+ </div>
30
+ );
31
+
32
+ const meta = {
33
+ title: "Chat/ThinkingDisplay",
34
+ component: ThinkingDisplay,
35
+ tags: ["autodocs"],
36
+ parameters: {
37
+ docs: {
38
+ description: {
39
+ component:
40
+ "Renders a `thinking` message segment — collapsed single-line 'Thought' card that expands to the full muted-markdown reasoning. Streaming state shows 'Thinking' with a dots loader.",
41
+ },
42
+ },
43
+ },
44
+ decorators: [plainDecorator],
45
+ } satisfies Meta<typeof ThinkingDisplay>;
46
+
47
+ export default meta;
48
+ type Story = StoryObj<typeof meta>;
49
+
50
+ /** Finished thought — collapsed to one line, expandable. */
51
+ export const Thought: Story = {
52
+ args: { text: SHORT_THOUGHT },
53
+ };
54
+
55
+ /** Streaming — "Thinking" label, transition disabled while tokens arrive. */
56
+ export const Streaming: Story = {
57
+ args: { text: SHORT_THOUGHT, isStreaming: true },
58
+ };
59
+
60
+ /** Long markdown thought — headings, list, inline code; expand to read. */
61
+ export const LongMarkdown: Story = {
62
+ args: { text: LONG_THOUGHT },
63
+ };