@flamingo-stack/openframe-frontend-core 0.0.222 → 0.0.223

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 (183) hide show
  1. package/dist/{chunk-AIKPR6BE.js → chunk-4VHWSAQR.js} +6069 -5235
  2. package/dist/chunk-4VHWSAQR.js.map +1 -0
  3. package/dist/{chunk-GMNE7P7V.cjs → chunk-4WBVMIKQ.cjs} +26 -26
  4. package/dist/{chunk-GMNE7P7V.cjs.map → chunk-4WBVMIKQ.cjs.map} +1 -1
  5. package/dist/{chunk-QYRV6MKX.js → chunk-53EA4QPZ.js} +13 -7
  6. package/dist/chunk-53EA4QPZ.js.map +1 -0
  7. package/dist/{chunk-4JOWZLEL.cjs → chunk-7TKYFZ3L.cjs} +7 -7
  8. package/dist/{chunk-4JOWZLEL.cjs.map → chunk-7TKYFZ3L.cjs.map} +1 -1
  9. package/dist/{chunk-IZA5WF44.cjs → chunk-ABBU7QOM.cjs} +12 -12
  10. package/dist/{chunk-IZA5WF44.cjs.map → chunk-ABBU7QOM.cjs.map} +1 -1
  11. package/dist/{chunk-KM4KK2A6.cjs → chunk-ENDUMC7Z.cjs} +2558 -1724
  12. package/dist/chunk-ENDUMC7Z.cjs.map +1 -0
  13. package/dist/{chunk-CCSEXVI4.js → chunk-GYYGA24C.js} +2 -2
  14. package/dist/{chunk-RMOHKAHA.js → chunk-ISWWP2BH.js} +3 -3
  15. package/dist/{chunk-QNYH3WUU.cjs → chunk-JUWPBMQM.cjs} +13 -7
  16. package/dist/chunk-JUWPBMQM.cjs.map +1 -0
  17. package/dist/{chunk-A2SFHYVF.cjs → chunk-KXLTWANQ.cjs} +10 -10
  18. package/dist/{chunk-A2SFHYVF.cjs.map → chunk-KXLTWANQ.cjs.map} +1 -1
  19. package/dist/{chunk-ND24XSVT.js → chunk-TA6Z2AKD.js} +3 -3
  20. package/dist/chunk-TA6Z2AKD.js.map +1 -0
  21. package/dist/{chunk-HRIAZQ5D.js → chunk-WOKB6RJ7.js} +3 -3
  22. package/dist/components/chat/chat-archive-page.d.ts.map +1 -1
  23. package/dist/components/chat/chat-composer.d.ts.map +1 -1
  24. package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
  25. package/dist/components/chat/chat-message-skeleton.d.ts +19 -4
  26. package/dist/components/chat/chat-message-skeleton.d.ts.map +1 -1
  27. package/dist/components/chat/chat-panel-header-mobile.d.ts +24 -0
  28. package/dist/components/chat/chat-panel-header-mobile.d.ts.map +1 -0
  29. package/dist/components/chat/chat-panel-header.d.ts +6 -0
  30. package/dist/components/chat/chat-panel-header.d.ts.map +1 -1
  31. package/dist/components/chat/chat-quick-action-row.d.ts +31 -0
  32. package/dist/components/chat/chat-quick-action-row.d.ts.map +1 -0
  33. package/dist/components/chat/embeddable-chat.d.ts +27 -0
  34. package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
  35. package/dist/components/chat/entity-cards/dispatch.d.ts +14 -0
  36. package/dist/components/chat/entity-cards/dispatch.d.ts.map +1 -1
  37. package/dist/components/chat/entity-cards/github-activity-card.d.ts +10 -1
  38. package/dist/components/chat/entity-cards/github-activity-card.d.ts.map +1 -1
  39. package/dist/components/chat/guide-welcome.d.ts.map +1 -1
  40. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +9 -0
  41. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
  42. package/dist/components/chat/hooks/use-slash-commands.d.ts +26 -0
  43. package/dist/components/chat/hooks/use-slash-commands.d.ts.map +1 -1
  44. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts +12 -1
  45. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
  46. package/dist/components/chat/hooks/use-unified-chat.d.ts +12 -0
  47. package/dist/components/chat/hooks/use-unified-chat.d.ts.map +1 -1
  48. package/dist/components/chat/index.cjs +9 -3
  49. package/dist/components/chat/index.cjs.map +1 -1
  50. package/dist/components/chat/index.js +8 -2
  51. package/dist/components/chat/mingo-chat-history.d.ts +10 -0
  52. package/dist/components/chat/mingo-chat-history.d.ts.map +1 -1
  53. package/dist/components/chat/mingo-info-card.d.ts +63 -0
  54. package/dist/components/chat/mingo-info-card.d.ts.map +1 -0
  55. package/dist/components/chat/mingo-welcome.d.ts +14 -1
  56. package/dist/components/chat/mingo-welcome.d.ts.map +1 -1
  57. package/dist/components/chat/types/chat.types.d.ts +6 -0
  58. package/dist/components/chat/types/chat.types.d.ts.map +1 -1
  59. package/dist/components/chat/types/unified-chat-state.types.d.ts +30 -0
  60. package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
  61. package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
  62. package/dist/components/contact/index.cjs +4 -4
  63. package/dist/components/contact/index.js +3 -3
  64. package/dist/components/features/index.cjs +3 -3
  65. package/dist/components/features/index.js +2 -2
  66. package/dist/components/index.cjs +110 -104
  67. package/dist/components/index.cjs.map +1 -1
  68. package/dist/components/index.js +36 -30
  69. package/dist/components/index.js.map +1 -1
  70. package/dist/components/navigation/app-layout-drawer.d.ts +7 -0
  71. package/dist/components/navigation/app-layout-drawer.d.ts.map +1 -1
  72. package/dist/components/navigation/index.cjs +3 -3
  73. package/dist/components/navigation/index.js +2 -2
  74. package/dist/components/onboarding-guides/index.cjs +26 -26
  75. package/dist/components/onboarding-guides/index.js +4 -4
  76. package/dist/components/tickets/index.cjs +67 -67
  77. package/dist/components/tickets/index.js +5 -5
  78. package/dist/components/ui/actions-menu.d.ts +9 -0
  79. package/dist/components/ui/actions-menu.d.ts.map +1 -1
  80. package/dist/components/ui/dropdown-button.d.ts +2 -2
  81. package/dist/components/ui/dropdown-button.d.ts.map +1 -1
  82. package/dist/components/ui/index.cjs +9 -3
  83. package/dist/components/ui/index.cjs.map +1 -1
  84. package/dist/components/ui/index.js +8 -2
  85. package/dist/components/ui/modal-v2.d.ts.map +1 -1
  86. package/dist/components/ui/more-actions-menu.d.ts +13 -0
  87. package/dist/components/ui/more-actions-menu.d.ts.map +1 -1
  88. package/dist/components/ui/square-avatar.d.ts +4 -0
  89. package/dist/components/ui/square-avatar.d.ts.map +1 -1
  90. package/dist/components/ui/textarea.d.ts.map +1 -1
  91. package/dist/hooks/index.cjs +2 -2
  92. package/dist/hooks/index.js +1 -1
  93. package/dist/hooks/ui/use-auto-limit-tags.d.ts +7 -1
  94. package/dist/hooks/ui/use-auto-limit-tags.d.ts.map +1 -1
  95. package/dist/index.cjs +9 -3
  96. package/dist/index.cjs.map +1 -1
  97. package/dist/index.js +8 -2
  98. package/package.json +1 -1
  99. package/src/components/chat/chat-archive-page.tsx +32 -7
  100. package/src/components/chat/chat-composer.tsx +6 -2
  101. package/src/components/chat/chat-message-enhanced.tsx +24 -7
  102. package/src/components/chat/chat-message-list.tsx +1 -1
  103. package/src/components/chat/chat-message-skeleton.tsx +60 -60
  104. package/src/components/chat/chat-panel-header-mobile.tsx +94 -0
  105. package/src/components/chat/chat-panel-header.tsx +78 -50
  106. package/src/components/chat/chat-quick-action-row.tsx +146 -0
  107. package/src/components/chat/embeddable-chat.tsx +140 -45
  108. package/src/components/chat/entity-cards/dispatch.tsx +770 -232
  109. package/src/components/chat/entity-cards/github-activity-card.tsx +9 -3
  110. package/src/components/chat/guide-welcome.tsx +11 -8
  111. package/src/components/chat/hooks/use-nats-chat-adapter.ts +80 -7
  112. package/src/components/chat/hooks/use-slash-commands.ts +39 -0
  113. package/src/components/chat/hooks/use-sse-chat-adapter.ts +49 -45
  114. package/src/components/chat/hooks/use-unified-chat.ts +31 -8
  115. package/src/components/chat/mingo-chat-history.tsx +118 -36
  116. package/src/components/chat/mingo-info-card.tsx +214 -0
  117. package/src/components/chat/mingo-welcome.tsx +89 -36
  118. package/src/components/chat/types/chat.types.ts +4 -0
  119. package/src/components/chat/types/unified-chat-state.types.ts +35 -0
  120. package/src/components/chat/utils/process-historical-messages.ts +8 -0
  121. package/src/components/content-loading-container.tsx +1 -1
  122. package/src/components/empty-state.tsx +3 -3
  123. package/src/components/faq-accordion.tsx +2 -2
  124. package/src/components/features/changelog-manager.tsx +2 -2
  125. package/src/components/features/highlight-config-section.tsx +2 -2
  126. package/src/components/features/og-editor-preview.tsx +3 -3
  127. package/src/components/features/release-media-manager.tsx +2 -2
  128. package/src/components/loading/card-skeleton.tsx +3 -3
  129. package/src/components/loading/device-card-skeleton.tsx +1 -1
  130. package/src/components/loading/margin-report-skeleton.tsx +2 -2
  131. package/src/components/loading/organization-card-skeleton.tsx +1 -1
  132. package/src/components/loading/organization-icon-skeleton.tsx +1 -1
  133. package/src/components/loading/page-layout-skeleton.tsx +9 -9
  134. package/src/components/loading/users-grid-skeleton.tsx +2 -2
  135. package/src/components/logs-list.tsx +1 -1
  136. package/src/components/media-carousel.tsx +1 -1
  137. package/src/components/navigation/app-layout-drawer.tsx +70 -6
  138. package/src/components/open-source-features.tsx +2 -2
  139. package/src/components/ui/actions-menu.tsx +36 -13
  140. package/src/components/ui/allowed-domains-input.tsx +3 -3
  141. package/src/components/ui/cursor-pagination.tsx +1 -1
  142. package/src/components/ui/dropdown-button.tsx +6 -6
  143. package/src/components/ui/info-row.tsx +2 -2
  144. package/src/components/ui/modal-v2.tsx +8 -1
  145. package/src/components/ui/more-actions-menu.tsx +13 -0
  146. package/src/components/ui/square-avatar.tsx +6 -1
  147. package/src/components/ui/tags-input.tsx +1 -1
  148. package/src/components/ui/textarea.tsx +18 -4
  149. package/src/components/vendor-icon.tsx +1 -1
  150. package/src/components/vendor-tag.tsx +1 -1
  151. package/src/components/why-it-matters.tsx +1 -1
  152. package/src/hooks/ui/use-auto-limit-tags.ts +22 -8
  153. package/src/stories/ActionsMenu.stories.tsx +256 -0
  154. package/src/stories/AppLayoutSidebar.stories.tsx +6 -0
  155. package/src/stories/Board.stories.tsx +1 -1
  156. package/src/stories/ChatMingoInfoCards.stories.tsx +748 -0
  157. package/src/stories/ChatTicketList.stories.tsx +1 -1
  158. package/src/stories/DataTable.stories.tsx +17 -13
  159. package/src/stories/EmbeddableChat.stories.tsx +233 -1
  160. package/src/stories/MingoInfoCard.stories.tsx +193 -0
  161. package/src/stories/MingoOnboardingCard.stories.tsx +1 -1
  162. package/src/stories/Modal.stories.tsx +1 -0
  163. package/src/stories/NotificationDrawer.stories.tsx +14 -3
  164. package/src/stories/PageActions.stories.tsx +4 -4
  165. package/src/stories/PageContainer.stories.tsx +5 -5
  166. package/src/stories/PageLayout.stories.tsx +1 -1
  167. package/src/stories/TabSelector.stories.tsx +2 -2
  168. package/src/stories/Tag.stories.tsx +10 -5
  169. package/src/stories/TicketInfoSection.stories.tsx +23 -11
  170. package/src/stories/Toaster.stories.tsx +5 -5
  171. package/src/stories/__fixtures__/chat-card-decorator.tsx +44 -0
  172. package/dist/chunk-AIKPR6BE.js.map +0 -1
  173. package/dist/chunk-KM4KK2A6.cjs.map +0 -1
  174. package/dist/chunk-ND24XSVT.js.map +0 -1
  175. package/dist/chunk-QNYH3WUU.cjs.map +0 -1
  176. package/dist/chunk-QYRV6MKX.js.map +0 -1
  177. package/src/stories/.TicketDescriptionViewer.stories.md +0 -39
  178. package/src/stories/.TicketKnowledgeBaseList.stories.md +0 -36
  179. package/src/stories/TicketDescriptionViewer.stories.tsx +0 -70
  180. package/src/stories/TicketKnowledgeBaseList.stories.tsx +0 -51
  181. /package/dist/{chunk-CCSEXVI4.js.map → chunk-GYYGA24C.js.map} +0 -0
  182. /package/dist/{chunk-RMOHKAHA.js.map → chunk-ISWWP2BH.js.map} +0 -0
  183. /package/dist/{chunk-HRIAZQ5D.js.map → chunk-WOKB6RJ7.js.map} +0 -0
@@ -27,6 +27,12 @@ function getOwnerDisplayName(owner?: MessageOwner): string {
27
27
  return owner?.type === OWNER_TYPE.ADMIN ? 'Admin' : 'You'
28
28
  }
29
29
 
30
+ /** Per-message author avatar (e.g. an admin's profile photo) when the owner
31
+ * carries one. `imageUrl` may be relative; the host resolves it downstream. */
32
+ function getOwnerAvatar(owner?: MessageOwner): string | undefined {
33
+ return owner?.user?.image?.imageUrl ?? undefined
34
+ }
35
+
30
36
  function pushStandaloneMessages(
31
37
  processedMessages: ProcessedMessage[],
32
38
  msg: HistoricalMessage,
@@ -143,6 +149,7 @@ export function processHistoricalMessages(
143
149
  role: 'user',
144
150
  content: data.text,
145
151
  name: getOwnerDisplayName(msg.owner),
152
+ avatar: getOwnerAvatar(msg.owner),
146
153
  authorType: userAuthorType,
147
154
  timestamp: new Date(msg.createdAt),
148
155
  })
@@ -517,6 +524,7 @@ export function processHistoricalMessagesWithErrors(
517
524
  role: 'user',
518
525
  content: data.text,
519
526
  name: getOwnerDisplayName(msg.owner),
527
+ avatar: getOwnerAvatar(msg.owner),
520
528
  authorType: userAuthorType,
521
529
  timestamp: new Date(msg.createdAt),
522
530
  })
@@ -83,7 +83,7 @@ export function ContentLoadingContainer({
83
83
  {/* Loading Skeleton Overlay */}
84
84
  {isLoading && (
85
85
  <div
86
- className="absolute inset-0 z-10 bg-[#161616]"
86
+ className="absolute inset-0 z-10 bg-ods-bg"
87
87
  style={{
88
88
  opacity: loadingOpacity,
89
89
  transition: `opacity ${transitionDuration}ms ease-in-out`
@@ -177,9 +177,9 @@ export function EmptyState({
177
177
  <div className="w-full max-w-xs mb-3">
178
178
  <Button
179
179
  onClick={smartCTA.action}
180
- className={ctaVariant === 'primary'
181
- ? "w-full bg-[#FFC008] text-black hover:bg-[#FFC008]/90 transition-all duration-150 font-['DM_Sans'] font-medium"
182
- : "w-full bg-transparent border border-ods-border text-ods-text-primary hover:border-[#FFC008] hover:text-ods-accent transition-all duration-150 font-['DM_Sans'] font-medium"
180
+ className={ctaVariant === 'primary'
181
+ ? "w-full bg-ods-accent text-ods-text-on-accent hover:bg-ods-accent-hover transition-all duration-150 font-['DM_Sans'] font-medium"
182
+ : "w-full bg-transparent border border-ods-border text-ods-text-primary hover:border-ods-accent hover:text-ods-accent transition-all duration-150 font-['DM_Sans'] font-medium"
183
183
  }
184
184
  >
185
185
  {smartCTA.text}
@@ -52,7 +52,7 @@ export function FaqAccordion({ items, defaultOpenIds = [] }: FaqAccordionProps)
52
52
  }
53
53
 
54
54
  return (
55
- <div className="rounded-3xl border border-ods-border divide-y divide-[#3A3A3A] bg-ods-card overflow-hidden">
55
+ <div className="rounded-3xl border border-ods-border divide-y divide-ods-border bg-ods-card overflow-hidden">
56
56
  {items.map(item => {
57
57
  const isOpen = openSet.has(item.id)
58
58
  const { ref, maxHeight } = useMeasuredHeight(isOpen)
@@ -60,7 +60,7 @@ export function FaqAccordion({ items, defaultOpenIds = [] }: FaqAccordionProps)
60
60
  return (
61
61
  <div
62
62
  key={item.id}
63
- className={cn('group transition-colors hover:bg-[#1E1E1E]', isOpen ? 'bg-[#161616]' : 'bg-transparent')}
63
+ className={cn('group transition-colors hover:bg-[#1E1E1E]', isOpen ? 'bg-ods-bg' : 'bg-transparent')}
64
64
  >
65
65
  {/* Header */}
66
66
  <div
@@ -177,7 +177,7 @@ export function ChangelogManager({
177
177
  value={entry.title}
178
178
  onChange={(e) => updateEntry(index, 'title', e.target.value)}
179
179
  onKeyDown={(e) => e.key === 'Enter' && e.preventDefault()}
180
- className="bg-[#161616]"
180
+ className="bg-ods-bg"
181
181
  />
182
182
  </div>
183
183
 
@@ -189,7 +189,7 @@ export function ChangelogManager({
189
189
  value={entry.description || ''}
190
190
  onChange={(e) => updateEntry(index, 'description', e.target.value)}
191
191
  rows={2}
192
- className="bg-[#161616]"
192
+ className="bg-ods-bg"
193
193
  />
194
194
  </div>
195
195
  </div>
@@ -43,7 +43,7 @@ export function HighlightConfigSection({
43
43
  onValueChange={(value) => onTargetDurationChange(parseInt(value))}
44
44
  disabled={disabled}
45
45
  >
46
- <SelectTrigger className="bg-[#161616] mt-1">
46
+ <SelectTrigger className="bg-ods-bg mt-1">
47
47
  <SelectValue />
48
48
  </SelectTrigger>
49
49
  <SelectContent className="bg-ods-card">
@@ -62,7 +62,7 @@ export function HighlightConfigSection({
62
62
  checked={skipSubtitleBurning}
63
63
  onChange={(e) => onSkipSubtitleBurningChange(e.target.checked)}
64
64
  disabled={disabled}
65
- className="h-4 w-4 rounded border-ods-border bg-[#161616] text-ods-accent focus:ring-ods-accent"
65
+ className="h-4 w-4 rounded border-ods-border bg-ods-bg text-ods-accent focus:ring-ods-accent"
66
66
  />
67
67
  <Label htmlFor="skipSubtitleBurning" className="text-sm cursor-pointer">
68
68
  Skip subtitle burning
@@ -65,7 +65,7 @@ export function OGEditorPreview({
65
65
  onChange={(e) => onSeoTitleChange(e.target.value)}
66
66
  disabled={disabled}
67
67
  placeholder="Enter SEO meta title..."
68
- className="bg-[#161616] border-ods-border text-ods-text-primary"
68
+ className="bg-ods-bg border-ods-border text-ods-text-primary"
69
69
  />
70
70
  </div>
71
71
 
@@ -78,7 +78,7 @@ export function OGEditorPreview({
78
78
  onChange={(e) => onSeoKeywordsChange(e.target.value)}
79
79
  disabled={disabled}
80
80
  placeholder="Enter SEO keywords..."
81
- className="bg-[#161616] border-ods-border text-ods-text-primary"
81
+ className="bg-ods-bg border-ods-border text-ods-text-primary"
82
82
  />
83
83
  </div>
84
84
  </div>
@@ -94,7 +94,7 @@ export function OGEditorPreview({
94
94
  onChange={(e) => onSeoDescriptionChange(e.target.value)}
95
95
  disabled={disabled}
96
96
  placeholder="Enter SEO meta description..."
97
- className="bg-[#161616] border-ods-border text-ods-text-primary flex-1 resize-none"
97
+ className="bg-ods-bg border-ods-border text-ods-text-primary flex-1 resize-none"
98
98
  rows={6}
99
99
  />
100
100
  </div>
@@ -241,7 +241,7 @@ export function ReleaseMediaManager({
241
241
  onValueChange={(value: any) => updateMedia(index, 'media_type', value)}
242
242
  disabled={item._uploading}
243
243
  >
244
- <SelectTrigger className="bg-[#161616] h-8 text-xs">
244
+ <SelectTrigger className="bg-ods-bg h-8 text-xs">
245
245
  <SelectValue />
246
246
  </SelectTrigger>
247
247
  <SelectContent className="bg-ods-card">
@@ -258,7 +258,7 @@ export function ReleaseMediaManager({
258
258
  value={item.title}
259
259
  onChange={(e) => updateMedia(index, 'title', e.target.value)}
260
260
  onKeyDown={(e) => e.key === 'Enter' && e.preventDefault()}
261
- className="bg-[#161616] h-8 text-xs"
261
+ className="bg-ods-bg h-8 text-xs"
262
262
  disabled={item._uploading}
263
263
  />
264
264
  </div>
@@ -72,7 +72,7 @@ function VendorCardContent({ showActions, showMetadata }: { showActions: boolean
72
72
  {/* Header Section - Row layout matching actual VendorCard */}
73
73
  <div className="flex items-start gap-3 w-full">
74
74
  {/* Logo Frame - 60px width fixed, matching actual structure */}
75
- <div className="w-[60px] h-[60px] bg-[#161616] border border-ods-border rounded-lg p-2 flex items-center justify-center flex-shrink-0">
75
+ <div className="w-[60px] h-[60px] bg-ods-bg border border-ods-border rounded-lg p-2 flex items-center justify-center flex-shrink-0">
76
76
  <MediaSkeleton.Avatar size="sm" className="w-11 h-11" />
77
77
  </div>
78
78
 
@@ -112,7 +112,7 @@ function VendorCardContent({ showActions, showMetadata }: { showActions: boolean
112
112
 
113
113
  {/* Tag Section - Contained within card boundaries */}
114
114
  <div className="flex-shrink-0">
115
- <div className="flex items-center gap-1.5 bg-[#161616] border border-ods-border rounded px-2.5 py-1.5">
115
+ <div className="flex items-center gap-1.5 bg-ods-bg border border-ods-border rounded px-2.5 py-1.5">
116
116
  <div className="w-4 h-4 bg-ods-border rounded-sm flex items-center justify-center flex-shrink-0">
117
117
  <MediaSkeleton.Icon size="sm" className="w-2.5 h-2.5" />
118
118
  </div>
@@ -131,7 +131,7 @@ function BlogCardContent({ showActions, showMetadata }: { showActions: boolean;
131
131
  return (
132
132
  <>
133
133
  {/* Image Section — OG 1200×630 aspect (matches blog-card.tsx loaded state) */}
134
- <div className="blog-card-image-container relative w-full aspect-[1200/630] overflow-hidden bg-[#161616]">
134
+ <div className="blog-card-image-container relative w-full aspect-[1200/630] overflow-hidden bg-ods-bg">
135
135
  <MediaSkeleton.CardImage />
136
136
  </div>
137
137
 
@@ -32,7 +32,7 @@ export function DeviceCardSkeleton({ className }: DeviceCardSkeletonProps) {
32
32
  {/* Row 1: Device icon + Device name + More button */}
33
33
  <div className="flex gap-4 items-center px-4 py-3">
34
34
  {/* Device type icon (8x8 container) */}
35
- <div className="w-8 h-8 bg-[#161616] border border-ods-border rounded-[6px] flex items-center justify-center flex-shrink-0">
35
+ <div className="w-8 h-8 bg-ods-bg border border-ods-border rounded-[6px] flex items-center justify-center flex-shrink-0">
36
36
  <div className="w-4 h-4 bg-ods-border rounded animate-pulse" />
37
37
  </div>
38
38
 
@@ -11,7 +11,7 @@ interface MarginReportSkeletonProps {
11
11
 
12
12
  export function MarginReportSkeleton({ animate = true, description, overlayContent }: MarginReportSkeletonProps) {
13
13
  return (
14
- <main className={`bg-[#161616] ${animate ? 'animate-pulse' : ''} relative min-h-screen`}>
14
+ <main className={`bg-ods-bg ${animate ? 'animate-pulse' : ''} relative min-h-screen`}>
15
15
  <div className="max-w-[1920px] px-6 md:px-20 py-6 md:py-10 mx-auto space-y-10">
16
16
  {/* Header */}
17
17
  <div className="space-y-3">
@@ -69,7 +69,7 @@ export function MarginReportSkeleton({ animate = true, description, overlayConte
69
69
  {/* vendor rows */}
70
70
  <div className="flex-1 flex flex-col gap-3 p-3">
71
71
  {Array.from({ length: 5 }).map((_, j) => (
72
- <div key={j} className="bg-[#161616] border border-ods-border rounded-lg px-4 py-3 flex items-center justify-between">
72
+ <div key={j} className="bg-ods-bg border border-ods-border rounded-lg px-4 py-3 flex items-center justify-between">
73
73
  {/* left section: icon + text */}
74
74
  <div className="flex items-center gap-3 min-w-0">
75
75
  <div className="w-12 h-12 bg-[#2A2A2A] rounded-lg flex-shrink-0" />
@@ -101,7 +101,7 @@ export function OrganizationCardSkeleton({
101
101
 
102
102
  {/* Tag/Badge Section */}
103
103
  <div className="flex-shrink-0">
104
- <div className="flex items-center gap-1.5 bg-[#161616] border border-ods-border rounded px-2.5 py-1.5">
104
+ <div className="flex items-center gap-1.5 bg-ods-bg border border-ods-border rounded px-2.5 py-1.5">
105
105
  <div className="w-4 h-4 bg-ods-border rounded-sm flex items-center justify-center flex-shrink-0">
106
106
  <MediaSkeleton.Icon size="sm" className="w-2.5 h-2.5" />
107
107
  </div>
@@ -45,7 +45,7 @@ const sizeClasses = {
45
45
  * Background style classes matching VendorIcon/OrganizationIcon exactly
46
46
  */
47
47
  const backgroundClasses = {
48
- dark: 'bg-[#161616] border border-ods-border',
48
+ dark: 'bg-ods-bg border border-ods-border',
49
49
  light: 'bg-ods-card border border-ods-border',
50
50
  white: 'bg-white border border-[#E5E5E5]'
51
51
  }
@@ -308,7 +308,7 @@ export function ArticleLayoutSkeleton({ className }: PageLayoutSkeletonProps) {
308
308
  */
309
309
  export function VendorDetailLayoutSkeleton({ className }: PageLayoutSkeletonProps) {
310
310
  return (
311
- <main className={cn("bg-[#161616] min-h-screen", className)}>
311
+ <main className={cn("bg-ods-bg min-h-screen", className)}>
312
312
  <div className="max-w-[1920px] mx-auto px-6 md:px-20 py-6 md:py-10">
313
313
  {/* Breadcrumb */}
314
314
  <BreadcrumbSkeleton className="mb-6" />
@@ -457,7 +457,7 @@ export function VendorDetailLayoutSkeleton({ className }: PageLayoutSkeletonProp
457
457
  <div className="h-5 bg-[#2A2A2A] rounded animate-pulse w-16"></div>
458
458
  {Array.from({ length: 3 }).map((_, i) => (
459
459
  <div key={i} className="bg-ods-card border border-ods-border rounded-lg p-4 flex gap-3">
460
- <div className="w-12 h-12 bg-[#161616] border border-ods-border rounded flex-shrink-0 animate-pulse"></div>
460
+ <div className="w-12 h-12 bg-ods-bg border border-ods-border rounded flex-shrink-0 animate-pulse"></div>
461
461
  <div className="flex-1 space-y-2">
462
462
  <div className="h-5 bg-[#2A2A2A] rounded animate-pulse w-2/3"></div>
463
463
  <div className="h-4 bg-[#2A2A2A] rounded animate-pulse"></div>
@@ -470,7 +470,7 @@ export function VendorDetailLayoutSkeleton({ className }: PageLayoutSkeletonProp
470
470
  <div className="h-5 bg-[#2A2A2A] rounded animate-pulse w-16"></div>
471
471
  {Array.from({ length: 3 }).map((_, i) => (
472
472
  <div key={i} className="bg-ods-card border border-ods-border rounded-lg p-4 flex gap-3">
473
- <div className="w-12 h-12 bg-[#161616] border border-ods-border rounded flex-shrink-0 animate-pulse"></div>
473
+ <div className="w-12 h-12 bg-ods-bg border border-ods-border rounded flex-shrink-0 animate-pulse"></div>
474
474
  <div className="flex-1 space-y-2">
475
475
  <div className="h-5 bg-[#2A2A2A] rounded animate-pulse w-2/3"></div>
476
476
  <div className="h-4 bg-[#2A2A2A] rounded animate-pulse"></div>
@@ -489,7 +489,7 @@ export function VendorDetailLayoutSkeleton({ className }: PageLayoutSkeletonProp
489
489
  <div key={i} className="bg-ods-card border border-ods-border rounded-lg p-6 space-y-4">
490
490
  {/* Header */}
491
491
  <div className="flex items-start gap-4">
492
- <div className="w-16 h-16 bg-[#161616] border border-ods-border rounded-lg animate-pulse flex-shrink-0"></div>
492
+ <div className="w-16 h-16 bg-ods-bg border border-ods-border rounded-lg animate-pulse flex-shrink-0"></div>
493
493
  <div className="flex-1 space-y-2">
494
494
  <div className="h-6 bg-[#2A2A2A] rounded animate-pulse w-3/4"></div>
495
495
  <div className="h-4 bg-[#2A2A2A] rounded animate-pulse w-1/2"></div>
@@ -523,7 +523,7 @@ export function VendorDetailLayoutSkeleton({ className }: PageLayoutSkeletonProp
523
523
  {/* Comment Form Skeleton */}
524
524
  <div className="space-y-3">
525
525
  <div className="flex gap-3">
526
- <div className="flex-1 bg-[#161616] border border-ods-border rounded-lg overflow-hidden">
526
+ <div className="flex-1 bg-ods-bg border border-ods-border rounded-lg overflow-hidden">
527
527
  {/* Title Section */}
528
528
  <div className="border-b border-ods-border p-3">
529
529
  <div className="h-3 bg-[#2A2A2A] rounded animate-pulse w-8 mb-2"></div>
@@ -702,7 +702,7 @@ export function VendorGridSkeleton({ className }: PageLayoutSkeletonProps) {
702
702
  export function SlackCommunitySkeleton() {
703
703
  return (
704
704
  <section
705
- className="w-full bg-[#161616] px-4 md:px-20 lg:px-20 py-12 md:py-20"
705
+ className="w-full bg-ods-bg px-4 md:px-20 lg:px-20 py-12 md:py-20"
706
706
  aria-label="Slack Community Loading"
707
707
  >
708
708
  {/* Frame 651 Container */}
@@ -717,7 +717,7 @@ export function SlackCommunitySkeleton() {
717
717
  <div className="w-full flex flex-col lg:flex-row lg:justify-end lg:items-start gap-4 md:gap-6 min-w-0">
718
718
 
719
719
  {/* Channel List Skeleton */}
720
- <div className="w-full lg:w-[290px] lg:max-w-[290px] bg-[#161616] border border-ods-border rounded flex flex-col flex-shrink-0 overflow-hidden animate-pulse">
720
+ <div className="w-full lg:w-[290px] lg:max-w-[290px] bg-ods-bg border border-ods-border rounded flex flex-col flex-shrink-0 overflow-hidden animate-pulse">
721
721
  <div className="p-4 space-y-4">
722
722
  {/* Header */}
723
723
  <div className="flex flex-col gap-2">
@@ -762,7 +762,7 @@ export function SlackCommunitySkeleton() {
762
762
  <div className="flex-1 min-h-[450px] h-[450px] md:min-h-[500px] md:h-[500px] lg:min-h-[600px] lg:h-[600px] bg-ods-card border border-ods-border rounded-lg flex flex-col min-w-0 animate-pulse">
763
763
 
764
764
  {/* Header */}
765
- <div className="flex items-center justify-between p-4 md:p-6 border-b border-ods-border bg-[#161616] h-[56px] md:h-[60px] flex-shrink-0">
765
+ <div className="flex items-center justify-between p-4 md:p-6 border-b border-ods-border bg-ods-bg h-[56px] md:h-[60px] flex-shrink-0">
766
766
  <div className="flex items-center gap-2">
767
767
  <div className="h-3 md:h-4 bg-[#2A2A2A] rounded w-3 md:w-4"></div>
768
768
  <div className="h-3 md:h-4 bg-[#2A2A2A] rounded w-24 md:w-32"></div>
@@ -771,7 +771,7 @@ export function SlackCommunitySkeleton() {
771
771
  </div>
772
772
 
773
773
  {/* Messages */}
774
- <div className="flex-1 bg-[#161616] p-4 md:p-6 space-y-3 md:space-y-4 overflow-hidden min-h-[280px] md:min-h-[320px] lg:min-h-[420px]">
774
+ <div className="flex-1 bg-ods-bg p-4 md:p-6 space-y-3 md:space-y-4 overflow-hidden min-h-[280px] md:min-h-[320px] lg:min-h-[420px]">
775
775
  {Array.from({ length: 6 }).map((_, i) => (
776
776
  <div key={i} className="flex gap-2 md:gap-3">
777
777
  <div className="w-10 h-10 md:w-12 md:h-12 bg-[#2A2A2A] rounded-lg flex-shrink-0"></div>
@@ -9,7 +9,7 @@ export function UsersGridSkeleton({ rows = 10, className }: Props) {
9
9
  const skeletonRows = Array.from({ length: rows })
10
10
  return (
11
11
  <div className={cn('overflow-x-auto rounded-lg border border-ods-border bg-ods-card animate-pulse', className)}>
12
- <table className="min-w-full divide-y divide-[#3A3A3A]">
12
+ <table className="min-w-full divide-y divide-ods-border">
13
13
  <thead className="bg-[#2A2A2A]">
14
14
  <tr>
15
15
  {['Name', 'Email', 'Role', 'Created', 'Last Sign-In'].map((h) => (
@@ -17,7 +17,7 @@ export function UsersGridSkeleton({ rows = 10, className }: Props) {
17
17
  ))}
18
18
  </tr>
19
19
  </thead>
20
- <tbody className="divide-y divide-[#3A3A3A]">
20
+ <tbody className="divide-y divide-ods-border">
21
21
  {skeletonRows.map((_, idx) => (
22
22
  <tr key={idx}>
23
23
  {Array.from({ length: 5 }).map((__, cell) => (
@@ -46,7 +46,7 @@ const LogCard: React.FC<{
46
46
  {log.title}
47
47
  </p>
48
48
  <div className="flex items-center gap-2">
49
- <p className="font-['Azeret_Mono'] font-normal text-[13px] leading-4 text-[#888888] uppercase tracking-wider">
49
+ <p className="font-['Azeret_Mono'] font-normal text-[13px] leading-4 text-ods-text-secondary uppercase tracking-wider">
50
50
  {formatTimestamp(log.timestamp)}
51
51
  </p>
52
52
  {log.toolType && (
@@ -250,7 +250,7 @@ export const MediaCarousel = memo(function MediaCarousel({
250
250
  <div
251
251
  ref={carouselRef}
252
252
  className={cn(
253
- "relative bg-[#161616] border border-ods-border rounded-2xl overflow-hidden group w-full",
253
+ "relative bg-ods-bg border border-ods-border rounded-2xl overflow-hidden group w-full",
254
254
  getAspectRatioClass()
255
255
  )}
256
256
  onTouchStart={onTouchStart}
@@ -151,6 +151,24 @@ const appLayoutDrawerPanelVariants = cva(
151
151
 
152
152
  const HORIZONTAL_SIDES: ReadonlySet<DrawerSide> = new Set(["left", "right"])
153
153
 
154
+ /**
155
+ * Persist mode (`forceMount`) keeps the panel mounted across close, so the
156
+ * `slide-out-*` / `fade-out` exit animations run and then — because
157
+ * `tailwindcss-animate`'s `.animate-out` does NOT set `animation-fill-mode:
158
+ * forwards` — the panel/overlay snap back to their resting frame (on-screen,
159
+ * opacity 1). The element ends up fully visible again, which reads as "the
160
+ * drawer won't close" (and dims the whole content area via the backdrop).
161
+ *
162
+ * Pinning `fill-mode: forwards` for the closed state holds the exit animation's
163
+ * final frame (off-screen / transparent) after it ends. It can't be replaced by
164
+ * a static resting transform/opacity: that would override the animation's
165
+ * `from` value (= current computed style) and collapse the animation to a no-op.
166
+ *
167
+ * Not applied without persist — Radix unmounts `Content` after the animation,
168
+ * so there's nothing to snap back.
169
+ */
170
+ const PERSIST_CLOSED_HOLD = "data-[state=closed]:fill-mode-forwards"
171
+
154
172
  function clamp(value: number, min: number, max: number): number {
155
173
  if (max < min) return min
156
174
  return Math.min(max, Math.max(min, value))
@@ -321,12 +339,12 @@ function AppLayoutDrawerResizeHandle({
321
339
 
322
340
  const trackPosition =
323
341
  side === "right"
324
- ? "right-full top-4 bottom-4 w-3 items-center justify-end pr-1"
342
+ ? "right-full top-4 bottom-4 w-12 items-center justify-end pr-1"
325
343
  : side === "left"
326
- ? "left-full top-4 bottom-4 w-3 items-center justify-start pl-1"
344
+ ? "left-full top-4 bottom-4 w-12 items-center justify-start pl-1"
327
345
  : side === "bottom"
328
- ? "bottom-full left-4 right-4 h-3 justify-center items-end pb-1"
329
- : "top-full left-4 right-4 h-3 justify-center items-start pt-1"
346
+ ? "bottom-full left-4 right-4 h-12 justify-center items-end pb-1"
347
+ : "top-full left-4 right-4 h-12 justify-center items-start pt-1"
330
348
 
331
349
  const cursorClass = isHorizontal ? "cursor-col-resize" : "cursor-row-resize"
332
350
  const gripClass = isHorizontal ? "h-10 w-1" : "w-10 h-1"
@@ -388,6 +406,13 @@ export interface AppLayoutDrawerContentProps
388
406
  /** Override the portal container. Defaults to AppLayout's main-area container
389
407
  * (from context). Pass `null` to opt out of portaling. */
390
408
  container?: HTMLElement | null
409
+ /** Keep the panel mounted across close so its subtree state survives an
410
+ * open → close → open cycle (no remount, no refetch, no flicker). Lazy:
411
+ * nothing renders until the first open; after that the panel stays mounted
412
+ * (Radix `forceMount`) and the slide animation runs off `data-state`.
413
+ * Default `false` — preserves the standard unmount-on-close behaviour for
414
+ * every other consumer. */
415
+ keepMounted?: boolean
391
416
  /** When `true` (default), clicks on the dim overlay over the main content
392
417
  * area close the drawer. Clicks on AppLayout chrome (header, sidebar) NEVER
393
418
  * close the drawer regardless of this flag — chrome interactions shouldn't
@@ -430,6 +455,7 @@ const AppLayoutDrawerContent = React.forwardRef<
430
455
  dismissOnInteractOutside = true,
431
456
  onInteractOutside,
432
457
  debugLayoutShift = false,
458
+ keepMounted = false,
433
459
  children,
434
460
  ...props
435
461
  },
@@ -439,6 +465,20 @@ const AppLayoutDrawerContent = React.forwardRef<
439
465
  const portalContainer = container !== undefined ? container : contextContainer
440
466
  const open = React.useContext(DrawerOpenContext)
441
467
 
468
+ // Persist mode: lazily mount on the first open, then never unmount. Radix
469
+ // unmounts `Dialog.Content` on close by default, which would wipe the
470
+ // panel's subtree state (and trigger a full refetch) on every reopen.
471
+ // `forceMount` keeps it mounted; `hasOpened` defers that until the user
472
+ // opens the panel at least once (no work, no exit-animation flash before
473
+ // first open).
474
+ // Seed from `open` so an initially-open (defaultOpen / controlled open=true)
475
+ // drawer mounts on the first paint instead of flashing `null` for one frame.
476
+ const [hasOpened, setHasOpened] = React.useState<boolean>(Boolean(open))
477
+ React.useEffect(() => {
478
+ if (open) setHasOpened(true)
479
+ }, [open])
480
+ const persist = keepMounted && hasOpened
481
+
442
482
  // Diagnostic: walk ancestors and snapshot horizontal-scroll metrics
443
483
  // around the open transition. See `debugLayoutShift` prop doc.
444
484
  React.useEffect(() => {
@@ -533,8 +573,12 @@ const AppLayoutDrawerContent = React.forwardRef<
533
573
  : { height: size }
534
574
  : {}
535
575
 
576
+ // Persist mode, pre-first-open: render nothing so the panel mounts lazily.
577
+ // (All hooks above run unconditionally — this early return is hooks-safe.)
578
+ if (keepMounted && !hasOpened) return null
579
+
536
580
  return (
537
- <DialogPrimitive.Portal container={portalContainer}>
581
+ <DialogPrimitive.Portal container={portalContainer} forceMount={persist || undefined}>
538
582
  {/* Overlay rendered manually: Radix's DialogPrimitive.Overlay returns
539
583
  null in non-modal mode, so we render a plain div here. Setting
540
584
  `pointer-events-auto` ensures it catches clicks on the main area
@@ -546,17 +590,32 @@ const AppLayoutDrawerContent = React.forwardRef<
546
590
  data-state={open ? "open" : "closed"}
547
591
  className={cn(
548
592
  "absolute inset-0 z-[40] bg-[color-mix(in_srgb,var(--ods-system-greys-background)_50%,transparent)] outline-none data-[state=open]:pointer-events-auto data-[state=closed]:pointer-events-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
593
+ // Persist mode only: hold the fade-out's final (transparent) frame
594
+ // so the backdrop doesn't snap back to opacity 1 and dim the whole
595
+ // content area while the drawer is "closed" (see PERSIST_CLOSED_HOLD).
596
+ persist && PERSIST_CLOSED_HOLD,
549
597
  overlayClassName,
550
598
  )}
551
599
  />
552
600
  <DialogPrimitive.Content
553
601
  ref={ref}
602
+ forceMount={persist || undefined}
603
+ // Persist mode: the panel stays mounted while closed (slid off-screen
604
+ // by the exit animation). Make sure that off-screen content can't
605
+ // catch clicks or be reached by tab/screen-reader.
606
+ {...(persist && !open
607
+ ? { inert: true as const, "aria-hidden": true }
608
+ : {})}
554
609
  className={cn(
555
610
  appLayoutDrawerVariants({ side, flush }),
556
611
  // Mobile: span the whole container and drop the outer gap so the
557
612
  // panel renders edge-to-edge full-screen. (`p-0` overrides the
558
613
  // variant's wrapper padding via tailwind-merge.)
559
614
  isMobile && "inset-0 p-0",
615
+ "data-[state=closed]:pointer-events-none",
616
+ // Persist mode only: hold the slide-out's final (off-screen) frame
617
+ // so the panel doesn't snap back into view (see PERSIST_CLOSED_HOLD).
618
+ persist && PERSIST_CLOSED_HOLD,
560
619
  )}
561
620
  style={style}
562
621
  onInteractOutside={(event) => {
@@ -576,7 +635,12 @@ const AppLayoutDrawerContent = React.forwardRef<
576
635
  }}
577
636
  {...props}
578
637
  >
579
- {applyInlineSize ? (
638
+ {/* The resize grip sits just OUTSIDE the panel's inner edge
639
+ (`right-full` etc.), so the wrapper's `translateX(100%)` close
640
+ transform doesn't carry it off-screen — it stays as a ~12px sliver
641
+ at the container edge. In persist mode it's also non-interactive
642
+ while closed, so drop it entirely until the panel reopens. */}
643
+ {applyInlineSize && (!persist || open) ? (
580
644
  <AppLayoutDrawerResizeHandle
581
645
  side={resolvedSide}
582
646
  size={size}
@@ -13,7 +13,7 @@ const FeatureCard: React.FC<FeatureCardProps> = ({ icon, title, description }) =
13
13
  return (
14
14
  <div className="bg-ods-card border border-ods-border rounded-3xl p-6 flex flex-col gap-6 h-full hover:bg-[#252525] transition-colors duration-200">
15
15
  {/* Icon Container */}
16
- <div className="w-12 h-12 bg-[#161616] border border-ods-border rounded flex items-center justify-center">
16
+ <div className="w-12 h-12 bg-ods-bg border border-ods-border rounded flex items-center justify-center">
17
17
  <div className="w-6 h-6 text-ods-text-secondary">
18
18
  {icon}
19
19
  </div>
@@ -57,7 +57,7 @@ const OpenSourceFeatures: React.FC = () => {
57
57
  ];
58
58
 
59
59
  return (
60
- <section className="w-full bg-[#161616] py-12 md:py-16 lg:py-20">
60
+ <section className="w-full bg-ods-bg py-12 md:py-16 lg:py-20">
61
61
  <div className="w-full max-w-[1920px] mx-auto px-6 md:px-20">
62
62
  {/* Section Title */}
63
63
  <div className="flex flex-col items-center gap-10">