@flamingo-stack/openframe-frontend-core 0.0.231 → 0.0.232-snapshot.20260608164341

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 (54) hide show
  1. package/dist/{chunk-BAQ6SS32.js → chunk-4ZUR4RJ7.js} +2 -2
  2. package/dist/{chunk-3C4GHSLI.js → chunk-55IS2VJD.js} +813 -771
  3. package/dist/chunk-55IS2VJD.js.map +1 -0
  4. package/dist/{chunk-J3F3LZZI.cjs → chunk-7CCNTX6H.cjs} +166 -124
  5. package/dist/chunk-7CCNTX6H.cjs.map +1 -0
  6. package/dist/{chunk-RL6VQEFT.cjs → chunk-7RR7C26P.cjs} +7 -7
  7. package/dist/{chunk-RL6VQEFT.cjs.map → chunk-7RR7C26P.cjs.map} +1 -1
  8. package/dist/{chunk-DNRO2BXS.cjs → chunk-L7AKGXCD.cjs} +10 -10
  9. package/dist/{chunk-DNRO2BXS.cjs.map → chunk-L7AKGXCD.cjs.map} +1 -1
  10. package/dist/{chunk-USACX2O4.js → chunk-N3CB52DX.js} +2 -2
  11. package/dist/{chunk-NFZLQOD7.cjs → chunk-QZFUVNYV.cjs} +25 -25
  12. package/dist/{chunk-NFZLQOD7.cjs.map → chunk-QZFUVNYV.cjs.map} +1 -1
  13. package/dist/{chunk-R46QCZLS.js → chunk-T7REIQSK.js} +2 -2
  14. package/dist/{chunk-BVLU6HLN.cjs → chunk-VENRDZ74.cjs} +9 -9
  15. package/dist/{chunk-BVLU6HLN.cjs.map → chunk-VENRDZ74.cjs.map} +1 -1
  16. package/dist/{chunk-U2OLWICZ.js → chunk-VHPEQK2V.js} +2 -2
  17. package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts.map +1 -1
  18. package/dist/components/chat/index.cjs +2 -2
  19. package/dist/components/chat/index.js +1 -1
  20. package/dist/components/chat/types/api.types.d.ts +14 -0
  21. package/dist/components/chat/types/api.types.d.ts.map +1 -1
  22. package/dist/components/contact/index.cjs +3 -3
  23. package/dist/components/contact/index.js +2 -2
  24. package/dist/components/features/entity-tag-badges.d.ts +14 -6
  25. package/dist/components/features/entity-tag-badges.d.ts.map +1 -1
  26. package/dist/components/features/index.cjs +2 -2
  27. package/dist/components/features/index.js +1 -1
  28. package/dist/components/index.cjs +77 -77
  29. package/dist/components/index.js +4 -4
  30. package/dist/components/navigation/index.cjs +2 -2
  31. package/dist/components/navigation/index.js +1 -1
  32. package/dist/components/onboarding-guides/index.cjs +26 -26
  33. package/dist/components/onboarding-guides/index.js +3 -3
  34. package/dist/components/tickets/index.cjs +64 -64
  35. package/dist/components/tickets/index.js +4 -4
  36. package/dist/components/ui/index.cjs +2 -2
  37. package/dist/components/ui/index.js +1 -1
  38. package/dist/components/ui/search-input.d.ts.map +1 -1
  39. package/dist/components/ui/tag.d.ts +1 -1
  40. package/dist/components/ui/tag.d.ts.map +1 -1
  41. package/dist/index.cjs +2 -2
  42. package/dist/index.js +1 -1
  43. package/package.json +1 -1
  44. package/src/components/chat/hooks/use-realtime-chunk-processor.ts +28 -0
  45. package/src/components/chat/types/api.types.ts +14 -0
  46. package/src/components/features/entity-tag-badges.tsx +47 -16
  47. package/src/components/ui/search-input.tsx +5 -2
  48. package/src/components/ui/tag.tsx +7 -0
  49. package/dist/chunk-3C4GHSLI.js.map +0 -1
  50. package/dist/chunk-J3F3LZZI.cjs.map +0 -1
  51. /package/dist/{chunk-BAQ6SS32.js.map → chunk-4ZUR4RJ7.js.map} +0 -0
  52. /package/dist/{chunk-USACX2O4.js.map → chunk-N3CB52DX.js.map} +0 -0
  53. /package/dist/{chunk-R46QCZLS.js.map → chunk-T7REIQSK.js.map} +0 -0
  54. /package/dist/{chunk-U2OLWICZ.js.map → chunk-VHPEQK2V.js.map} +0 -0
@@ -15,6 +15,11 @@ import { MESSAGE_TYPE } from '../types'
15
15
  import type { UseRealtimeChunkProcessorReturn, UseRealtimeChunkProcessorOptions, ChatApprovalStatus, PendingToolCallData } from '../types'
16
16
  import { getCommandText } from '../utils/tool-call-helpers'
17
17
 
18
+ // Actions allowed through once the dialog is in direct mode. Everything else is
19
+ // an AI-assistant chunk and gets dropped — an allowlist so any future assistant
20
+ // action is blocked by default.
21
+ const DIRECT_MODE_ALLOWED = new Set(['direct_message', 'message_request', 'system', 'dialog_closed'])
22
+
18
23
  /**
19
24
  * Hook for processing real-time NATS chunks into message segments
20
25
  */
@@ -31,6 +36,7 @@ export function useRealtimeChunkProcessor(
31
36
  // tickets view). Default ON so consumers that haven't wired the flag yet
32
37
  // get the new batch UI; pass `false` explicitly to fall back to legacy.
33
38
  batchApprovalsEnabled = true,
39
+ isDirectMode = false,
34
40
  } = options
35
41
 
36
42
  const accumulatorRef = useRef<MessageSegmentAccumulator>(
@@ -72,6 +78,14 @@ export function useRealtimeChunkProcessor(
72
78
  // resumed-dialog initializeWithState.
73
79
  const hasEverStreamedRef = useRef(false)
74
80
 
81
+ // Direct-mode barrier: once engaged, AI-assistant chunks are dropped. Engaged
82
+ // by the host flag (optimistic) or by an in-order DIRECT_MESSAGE chunk.
83
+ const directModeFlagRef = useRef(isDirectMode)
84
+ const sawDirectMessageRef = useRef(false)
85
+ useEffect(() => {
86
+ directModeFlagRef.current = isDirectMode
87
+ }, [isDirectMode])
88
+
75
89
  // Track pending escalated approvals (single or batch)
76
90
  const pendingEscalatedRef = useRef<
77
91
  Map<string, { command: string; explanation?: string; approvalType: string; toolCalls?: PendingToolCallData[] }>
@@ -93,6 +107,19 @@ export function useRealtimeChunkProcessor(
93
107
 
94
108
  const accumulator = accumulatorRef.current
95
109
 
110
+ if (action.action === 'direct_message') sawDirectMessageRef.current = true
111
+
112
+ if ((directModeFlagRef.current || sawDirectMessageRef.current) && !DIRECT_MODE_ALLOWED.has(action.action)) {
113
+ // Hard stop: tear down an open AI stream exactly once so the typing
114
+ // indicator and half-rendered bubble clear, then drop the chunk.
115
+ if (isInStreamRef.current) {
116
+ isInStreamRef.current = false
117
+ callbacks.onStreamEnd?.()
118
+ accumulator.resetSegments()
119
+ }
120
+ return
121
+ }
122
+
96
123
  switch (action.action) {
97
124
  case 'message_start':
98
125
  isInStreamRef.current = true
@@ -362,6 +389,7 @@ export function useRealtimeChunkProcessor(
362
389
  accumulatorRef.current.reset()
363
390
  pendingEscalatedRef.current.clear()
364
391
  hasInitializedWithData.current = false
392
+ sawDirectMessageRef.current = false
365
393
  }, [])
366
394
 
367
395
  const updateApprovalStatus = useCallback(
@@ -243,6 +243,20 @@ export interface UseRealtimeChunkProcessorOptions {
243
243
  * dropped from the UI in the unfolded mode.
244
244
  */
245
245
  batchApprovalsEnabled?: boolean
246
+ /**
247
+ * Engages the direct-mode barrier optimistically: once true, all AI-assistant
248
+ * chunks (text, thinking, tool executions, approvals, metadata, errors, etc.)
249
+ * are dropped and any open stream is torn down, leaving only human/control
250
+ * messages. A `DIRECT_MESSAGE` chunk engages the same barrier automatically
251
+ * regardless of this flag. Pass this from a host that already knows the dialog
252
+ * switched to direct mode (e.g. an optimistic mode toggle) to close the race
253
+ * window before the server emits the first `DIRECT_MESSAGE`.
254
+ *
255
+ * Do NOT set this when the host reconstructs history by replaying chunks
256
+ * through `processChunk` — a static flag would drop legitimate pre-switch AI
257
+ * history. Rely on the in-order `DIRECT_MESSAGE` detection instead.
258
+ */
259
+ isDirectMode?: boolean
246
260
  }
247
261
 
248
262
  export interface UseRealtimeChunkProcessorReturn {
@@ -1,32 +1,63 @@
1
+ import Link from 'next/link'
1
2
  import { StatusBadge } from '../ui/status-badge'
2
3
  import type { TagAssoc } from '../../types/blog'
3
4
 
4
5
  interface EntityTagBadgesProps {
5
6
  /** Flat `<entity>_tags[]` association array (from entity_tags hydration). */
6
7
  tags?: TagAssoc[] | null
8
+ /**
9
+ * When set, each badge links to `${basePath}?tags=${slug}` (clickable, SPA nav
10
+ * via next/link). Omit for a non-interactive display (badges with no slug also
11
+ * render non-interactive even when basePath is set).
12
+ */
13
+ basePath?: string
14
+ /** Cap visible badges; the remainder collapse into a "+N" badge. */
15
+ max?: number
7
16
  /** Extra classes on the wrapper row. */
8
17
  className?: string
9
18
  }
10
19
 
20
+ // The ONE tag-badge skin (OpenFrame design): rounded-rect, font-mono uppercase
21
+ // StatusBadge on ods-card + ods-border. Clickable badges add the accent hover.
22
+ const BADGE_CLASS = 'bg-ods-card border border-ods-border'
23
+
11
24
  /**
12
- * The single lib-side tag-badge renderer for detail views (release, onboarding
13
- * guide, …). Lib-package analogue of the hub's `EntityTagList` — the hub can't
14
- * be imported here, so this lives in the lib and is reused across lib detail
15
- * pages instead of each one re-implementing the StatusBadge chip row. Renders
16
- * nothing when there are no tags.
25
+ * THE single tag-badge renderer for the whole product (hub + lib). Renders the
26
+ * OpenFrame `StatusBadge` chip skin for a flat `<entity>_tags[]` array, optionally
27
+ * clickable (links each tag to its filtered list via `basePath`). The hub's
28
+ * `EntityTagList` delegates here so there is exactly one tag-display design.
29
+ * Renders nothing when there are no tags.
17
30
  */
18
- export function EntityTagBadges({ tags, className }: EntityTagBadgesProps) {
19
- if (!tags || tags.length === 0) return null
31
+ export function EntityTagBadges({ tags, basePath, max, className }: EntityTagBadgesProps) {
32
+ const items = (tags || []).filter((t): t is TagAssoc => !!t && !!t.name)
33
+ if (items.length === 0) return null
34
+
35
+ // Only null/undefined means "no cap"; max={0} shows none, negatives clamp to 0.
36
+ const visibleLimit = max == null ? items.length : Math.max(0, max)
37
+ const shown = items.slice(0, visibleLimit)
38
+ const overflow = items.length - shown.length
39
+
20
40
  return (
21
- <div className={`flex flex-wrap gap-2 w-full ${className || ''}`}>
22
- {tags.map((tag) => (
23
- <StatusBadge
24
- key={tag.tag_id ?? tag.id}
25
- text={(tag.name || '').toUpperCase()}
26
- variant="card"
27
- className="bg-ods-card border border-ods-border"
28
- />
29
- ))}
41
+ <div className={`flex flex-wrap items-center gap-2 w-full ${className || ''}`}>
42
+ {shown.map((tag) => {
43
+ const key = tag.tag_id ?? tag.id ?? tag.slug ?? tag.name
44
+ const label = (tag.name || '').toUpperCase()
45
+ if (basePath && tag.slug) {
46
+ return (
47
+ <Link key={key} href={`${basePath}?tags=${tag.slug}`} className="inline-flex">
48
+ <StatusBadge
49
+ text={label}
50
+ variant="card"
51
+ className={`${BADGE_CLASS} cursor-pointer transition-colors hover:border-ods-accent`}
52
+ />
53
+ </Link>
54
+ )
55
+ }
56
+ return <StatusBadge key={key} text={label} variant="card" className={BADGE_CLASS} />
57
+ })}
58
+ {overflow > 0 && (
59
+ <StatusBadge text={`+${overflow}`} variant="card" className={`${BADGE_CLASS} text-ods-text-secondary`} />
60
+ )}
30
61
  </div>
31
62
  )
32
63
  }
@@ -114,13 +114,16 @@ const innerInputStyles = cn(
114
114
  // Helper: chip variant → Tag variant mapping
115
115
  // ---------------------------------------------------------------------------
116
116
 
117
- function chipVariantToTagVariant(variant?: FilterChipData["variant"]): "primary" | "outline" {
117
+ function chipVariantToTagVariant(variant?: FilterChipData["variant"]): "primary" | "outline" | "badge" {
118
118
  switch (variant) {
119
119
  case "selected":
120
120
  return "primary"
121
+ // Content tags render with the unified badge skin (ods-card + ods-border,
122
+ // mono uppercase) — identical to the public EntityTagBadges display.
123
+ case "tag":
124
+ return "badge"
121
125
  case "category":
122
126
  case "subcategory":
123
- case "tag":
124
127
  default:
125
128
  return "outline"
126
129
  }
@@ -42,6 +42,13 @@ const tagVariants = cva(
42
42
  "bg-[var(--ods-system-greys-soft-grey)] text-[var(--ods-system-greys-grey)]",
43
43
  "hover:bg-[var(--ods-system-greys-soft-grey-hover)] active:bg-[var(--ods-system-greys-soft-grey-action)]",
44
44
  ],
45
+ // Matches the EntityTagBadges / StatusBadge tag skin (ods-card + ods-border,
46
+ // mono uppercase) so the tag-editor chips render identically to the public
47
+ // tag badges. Used for FilterChipData variant 'tag' (see search-input).
48
+ badge: [
49
+ "bg-ods-card text-ods-text-primary border border-ods-border font-mono uppercase tracking-wide",
50
+ "hover:border-ods-accent transition-colors",
51
+ ],
45
52
  },
46
53
  },
47
54
  defaultVariants: {