@cohesivesystems/presentation-react-shadcn 0.1.0-alpha.1

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 (262) hide show
  1. package/LICENSE +55 -0
  2. package/dist/document-workspace-surface-slot-renderers.d.ts +58 -0
  3. package/dist/document-workspace-surface-slot-renderers.d.ts.map +1 -0
  4. package/dist/document-workspace-surface-slot-renderers.js +206 -0
  5. package/dist/document-workspace-surface-slot-renderers.js.map +1 -0
  6. package/dist/index.d.ts +53 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +53 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/navigation-shell-frame-renderer-registry.d.ts +8 -0
  11. package/dist/navigation-shell-frame-renderer-registry.d.ts.map +1 -0
  12. package/dist/navigation-shell-frame-renderer-registry.js +2 -0
  13. package/dist/navigation-shell-frame-renderer-registry.js.map +1 -0
  14. package/dist/navigation-shell-slot-renderer-registry.d.ts +9 -0
  15. package/dist/navigation-shell-slot-renderer-registry.d.ts.map +1 -0
  16. package/dist/navigation-shell-slot-renderer-registry.js +2 -0
  17. package/dist/navigation-shell-slot-renderer-registry.js.map +1 -0
  18. package/dist/page-host-unavailable-renderer.d.ts +10 -0
  19. package/dist/page-host-unavailable-renderer.d.ts.map +1 -0
  20. package/dist/page-host-unavailable-renderer.js +27 -0
  21. package/dist/page-host-unavailable-renderer.js.map +1 -0
  22. package/dist/presentation-action-group.d.ts +99 -0
  23. package/dist/presentation-action-group.d.ts.map +1 -0
  24. package/dist/presentation-action-group.js +214 -0
  25. package/dist/presentation-action-group.js.map +1 -0
  26. package/dist/presentation-badge-target-interpreter.d.ts +26 -0
  27. package/dist/presentation-badge-target-interpreter.d.ts.map +1 -0
  28. package/dist/presentation-badge-target-interpreter.js +17 -0
  29. package/dist/presentation-badge-target-interpreter.js.map +1 -0
  30. package/dist/presentation-component-groups.d.ts +968 -0
  31. package/dist/presentation-component-groups.d.ts.map +1 -0
  32. package/dist/presentation-component-groups.js +505 -0
  33. package/dist/presentation-component-groups.js.map +1 -0
  34. package/dist/presentation-component-system-diagnostics.d.ts +27 -0
  35. package/dist/presentation-component-system-diagnostics.d.ts.map +1 -0
  36. package/dist/presentation-component-system-diagnostics.js +399 -0
  37. package/dist/presentation-component-system-diagnostics.js.map +1 -0
  38. package/dist/presentation-component-system.d.ts +31 -0
  39. package/dist/presentation-component-system.d.ts.map +1 -0
  40. package/dist/presentation-component-system.js +61 -0
  41. package/dist/presentation-component-system.js.map +1 -0
  42. package/dist/presentation-icon-diagnostics.d.ts +12 -0
  43. package/dist/presentation-icon-diagnostics.d.ts.map +1 -0
  44. package/dist/presentation-icon-diagnostics.js +17 -0
  45. package/dist/presentation-icon-diagnostics.js.map +1 -0
  46. package/dist/presentation-icon-registry.d.ts +28 -0
  47. package/dist/presentation-icon-registry.d.ts.map +1 -0
  48. package/dist/presentation-icon-registry.js +80 -0
  49. package/dist/presentation-icon-registry.js.map +1 -0
  50. package/dist/presentation-projection-trace-panel.d.ts +10 -0
  51. package/dist/presentation-projection-trace-panel.d.ts.map +1 -0
  52. package/dist/presentation-projection-trace-panel.js +48 -0
  53. package/dist/presentation-projection-trace-panel.js.map +1 -0
  54. package/dist/presentation-routed-surface-host.d.ts +19 -0
  55. package/dist/presentation-routed-surface-host.d.ts.map +1 -0
  56. package/dist/presentation-routed-surface-host.js +12 -0
  57. package/dist/presentation-routed-surface-host.js.map +1 -0
  58. package/dist/presentation-shadcn-component-system.d.ts +22 -0
  59. package/dist/presentation-shadcn-component-system.d.ts.map +1 -0
  60. package/dist/presentation-shadcn-component-system.js +2 -0
  61. package/dist/presentation-shadcn-component-system.js.map +1 -0
  62. package/dist/presentation-standard-renderers.d.ts +168 -0
  63. package/dist/presentation-standard-renderers.d.ts.map +1 -0
  64. package/dist/presentation-standard-renderers.js +440 -0
  65. package/dist/presentation-standard-renderers.js.map +1 -0
  66. package/dist/presentation-surface-renderer.d.ts +23 -0
  67. package/dist/presentation-surface-renderer.d.ts.map +1 -0
  68. package/dist/presentation-surface-renderer.js +119 -0
  69. package/dist/presentation-surface-renderer.js.map +1 -0
  70. package/dist/presentation-value-renderers.d.ts +10 -0
  71. package/dist/presentation-value-renderers.d.ts.map +1 -0
  72. package/dist/presentation-value-renderers.js +14 -0
  73. package/dist/presentation-value-renderers.js.map +1 -0
  74. package/dist/process-task-icons.d.ts +49 -0
  75. package/dist/process-task-icons.d.ts.map +1 -0
  76. package/dist/process-task-icons.js +154 -0
  77. package/dist/process-task-icons.js.map +1 -0
  78. package/dist/projected-activity-state.d.ts +17 -0
  79. package/dist/projected-activity-state.d.ts.map +1 -0
  80. package/dist/projected-activity-state.js +22 -0
  81. package/dist/projected-activity-state.js.map +1 -0
  82. package/dist/projected-collection-action-runtime-diagnostics.d.ts +4 -0
  83. package/dist/projected-collection-action-runtime-diagnostics.d.ts.map +1 -0
  84. package/dist/projected-collection-action-runtime-diagnostics.js +9 -0
  85. package/dist/projected-collection-action-runtime-diagnostics.js.map +1 -0
  86. package/dist/projected-collection-view.d.ts +90 -0
  87. package/dist/projected-collection-view.d.ts.map +1 -0
  88. package/dist/projected-collection-view.js +532 -0
  89. package/dist/projected-collection-view.js.map +1 -0
  90. package/dist/projected-document-action-status-notices.d.ts +15 -0
  91. package/dist/projected-document-action-status-notices.d.ts.map +1 -0
  92. package/dist/projected-document-action-status-notices.js +33 -0
  93. package/dist/projected-document-action-status-notices.js.map +1 -0
  94. package/dist/projected-document-badges.d.ts +24 -0
  95. package/dist/projected-document-badges.d.ts.map +1 -0
  96. package/dist/projected-document-badges.js +53 -0
  97. package/dist/projected-document-badges.js.map +1 -0
  98. package/dist/projected-document-editor-surface.d.ts +46 -0
  99. package/dist/projected-document-editor-surface.d.ts.map +1 -0
  100. package/dist/projected-document-editor-surface.js +399 -0
  101. package/dist/projected-document-editor-surface.js.map +1 -0
  102. package/dist/projected-document-workspace-notices.d.ts +24 -0
  103. package/dist/projected-document-workspace-notices.d.ts.map +1 -0
  104. package/dist/projected-document-workspace-notices.js +14 -0
  105. package/dist/projected-document-workspace-notices.js.map +1 -0
  106. package/dist/projected-document-workspace-prompt-child-view-registry.d.ts +25 -0
  107. package/dist/projected-document-workspace-prompt-child-view-registry.d.ts.map +1 -0
  108. package/dist/projected-document-workspace-prompt-child-view-registry.js +41 -0
  109. package/dist/projected-document-workspace-prompt-child-view-registry.js.map +1 -0
  110. package/dist/projected-document-workspace-prompt-child-view-renderers.d.ts +32 -0
  111. package/dist/projected-document-workspace-prompt-child-view-renderers.d.ts.map +1 -0
  112. package/dist/projected-document-workspace-prompt-child-view-renderers.js +77 -0
  113. package/dist/projected-document-workspace-prompt-child-view-renderers.js.map +1 -0
  114. package/dist/projected-document-workspace-shell.d.ts +100 -0
  115. package/dist/projected-document-workspace-shell.d.ts.map +1 -0
  116. package/dist/projected-document-workspace-shell.js +153 -0
  117. package/dist/projected-document-workspace-shell.js.map +1 -0
  118. package/dist/projected-document-workspace.d.ts +39 -0
  119. package/dist/projected-document-workspace.d.ts.map +1 -0
  120. package/dist/projected-document-workspace.js +63 -0
  121. package/dist/projected-document-workspace.js.map +1 -0
  122. package/dist/projected-field-value-rendering.d.ts +94 -0
  123. package/dist/projected-field-value-rendering.d.ts.map +1 -0
  124. package/dist/projected-field-value-rendering.js +377 -0
  125. package/dist/projected-field-value-rendering.js.map +1 -0
  126. package/dist/projected-inline-list.d.ts +20 -0
  127. package/dist/projected-inline-list.d.ts.map +1 -0
  128. package/dist/projected-inline-list.js +149 -0
  129. package/dist/projected-inline-list.js.map +1 -0
  130. package/dist/projected-input-form.d.ts +143 -0
  131. package/dist/projected-input-form.d.ts.map +1 -0
  132. package/dist/projected-input-form.js +612 -0
  133. package/dist/projected-input-form.js.map +1 -0
  134. package/dist/projected-metric-dashboard.d.ts +26 -0
  135. package/dist/projected-metric-dashboard.d.ts.map +1 -0
  136. package/dist/projected-metric-dashboard.js +20 -0
  137. package/dist/projected-metric-dashboard.js.map +1 -0
  138. package/dist/projected-metric-strip.d.ts +20 -0
  139. package/dist/projected-metric-strip.d.ts.map +1 -0
  140. package/dist/projected-metric-strip.js +131 -0
  141. package/dist/projected-metric-strip.js.map +1 -0
  142. package/dist/projected-navigation-shell-view-host.d.ts +34 -0
  143. package/dist/projected-navigation-shell-view-host.d.ts.map +1 -0
  144. package/dist/projected-navigation-shell-view-host.js +75 -0
  145. package/dist/projected-navigation-shell-view-host.js.map +1 -0
  146. package/dist/projected-navigation-shell.d.ts +33 -0
  147. package/dist/projected-navigation-shell.d.ts.map +1 -0
  148. package/dist/projected-navigation-shell.js +112 -0
  149. package/dist/projected-navigation-shell.js.map +1 -0
  150. package/dist/projected-presentation-badges.d.ts +34 -0
  151. package/dist/projected-presentation-badges.d.ts.map +1 -0
  152. package/dist/projected-presentation-badges.js +22 -0
  153. package/dist/projected-presentation-badges.js.map +1 -0
  154. package/dist/projected-presentation-diagnostics.d.ts +10 -0
  155. package/dist/projected-presentation-diagnostics.d.ts.map +1 -0
  156. package/dist/projected-presentation-diagnostics.js +40 -0
  157. package/dist/projected-presentation-diagnostics.js.map +1 -0
  158. package/dist/projected-presentation-flow-layer.d.ts +33 -0
  159. package/dist/projected-presentation-flow-layer.d.ts.map +1 -0
  160. package/dist/projected-presentation-flow-layer.js +71 -0
  161. package/dist/projected-presentation-flow-layer.js.map +1 -0
  162. package/dist/projected-process-task-notices.d.ts +37 -0
  163. package/dist/projected-process-task-notices.d.ts.map +1 -0
  164. package/dist/projected-process-task-notices.js +225 -0
  165. package/dist/projected-process-task-notices.js.map +1 -0
  166. package/dist/projected-prompt-child-view-renderer.d.ts +101 -0
  167. package/dist/projected-prompt-child-view-renderer.d.ts.map +1 -0
  168. package/dist/projected-prompt-child-view-renderer.js +442 -0
  169. package/dist/projected-prompt-child-view-renderer.js.map +1 -0
  170. package/dist/projected-prompt-document-preview.d.ts +39 -0
  171. package/dist/projected-prompt-document-preview.d.ts.map +1 -0
  172. package/dist/projected-prompt-document-preview.js +50 -0
  173. package/dist/projected-prompt-document-preview.js.map +1 -0
  174. package/dist/projected-prompt-view.d.ts +35 -0
  175. package/dist/projected-prompt-view.d.ts.map +1 -0
  176. package/dist/projected-prompt-view.js +292 -0
  177. package/dist/projected-prompt-view.js.map +1 -0
  178. package/dist/projected-query-form.d.ts +56 -0
  179. package/dist/projected-query-form.d.ts.map +1 -0
  180. package/dist/projected-query-form.js +100 -0
  181. package/dist/projected-query-form.js.map +1 -0
  182. package/dist/projected-record-details.d.ts +20 -0
  183. package/dist/projected-record-details.d.ts.map +1 -0
  184. package/dist/projected-record-details.js +60 -0
  185. package/dist/projected-record-details.js.map +1 -0
  186. package/dist/projected-routed-surface-runtime.d.ts +86 -0
  187. package/dist/projected-routed-surface-runtime.d.ts.map +1 -0
  188. package/dist/projected-routed-surface-runtime.js +332 -0
  189. package/dist/projected-routed-surface-runtime.js.map +1 -0
  190. package/dist/projected-tabs-view.d.ts +21 -0
  191. package/dist/projected-tabs-view.d.ts.map +1 -0
  192. package/dist/projected-tabs-view.js +140 -0
  193. package/dist/projected-tabs-view.js.map +1 -0
  194. package/dist/projected-view-chrome.d.ts +15 -0
  195. package/dist/projected-view-chrome.d.ts.map +1 -0
  196. package/dist/projected-view-chrome.js +67 -0
  197. package/dist/projected-view-chrome.js.map +1 -0
  198. package/dist/projected-view-surface.d.ts +34 -0
  199. package/dist/projected-view-surface.d.ts.map +1 -0
  200. package/dist/projected-view-surface.js +40 -0
  201. package/dist/projected-view-surface.js.map +1 -0
  202. package/dist/standard-collection-chrome-slot-renderers.d.ts +26 -0
  203. package/dist/standard-collection-chrome-slot-renderers.d.ts.map +1 -0
  204. package/dist/standard-collection-chrome-slot-renderers.js +250 -0
  205. package/dist/standard-collection-chrome-slot-renderers.js.map +1 -0
  206. package/dist/standard-view-chrome-slot-renderers.d.ts +57 -0
  207. package/dist/standard-view-chrome-slot-renderers.d.ts.map +1 -0
  208. package/dist/standard-view-chrome-slot-renderers.js +614 -0
  209. package/dist/standard-view-chrome-slot-renderers.js.map +1 -0
  210. package/package.json +68 -0
  211. package/src/document-workspace-surface-slot-renderers.tsx +395 -0
  212. package/src/index.ts +53 -0
  213. package/src/navigation-shell-frame-renderer-registry.ts +29 -0
  214. package/src/navigation-shell-slot-renderer-registry.ts +46 -0
  215. package/src/page-host-unavailable-renderer.tsx +79 -0
  216. package/src/presentation-action-group.tsx +475 -0
  217. package/src/presentation-badge-target-interpreter.ts +54 -0
  218. package/src/presentation-component-groups.ts +2505 -0
  219. package/src/presentation-component-system-diagnostics.ts +848 -0
  220. package/src/presentation-component-system.ts +365 -0
  221. package/src/presentation-icon-diagnostics.ts +53 -0
  222. package/src/presentation-icon-registry.tsx +181 -0
  223. package/src/presentation-projection-trace-panel.tsx +140 -0
  224. package/src/presentation-routed-surface-host.tsx +48 -0
  225. package/src/presentation-shadcn-component-system.ts +102 -0
  226. package/src/presentation-standard-renderers.tsx +1290 -0
  227. package/src/presentation-surface-renderer.tsx +263 -0
  228. package/src/presentation-value-renderers.tsx +40 -0
  229. package/src/process-task-icons.tsx +258 -0
  230. package/src/projected-activity-state.tsx +63 -0
  231. package/src/projected-collection-action-runtime-diagnostics.ts +22 -0
  232. package/src/projected-collection-view.tsx +1273 -0
  233. package/src/projected-document-action-status-notices.tsx +66 -0
  234. package/src/projected-document-badges.tsx +134 -0
  235. package/src/projected-document-editor-surface.tsx +808 -0
  236. package/src/projected-document-workspace-notices.tsx +77 -0
  237. package/src/projected-document-workspace-prompt-child-view-registry.ts +99 -0
  238. package/src/projected-document-workspace-prompt-child-view-renderers.tsx +205 -0
  239. package/src/projected-document-workspace-shell.tsx +427 -0
  240. package/src/projected-document-workspace.tsx +216 -0
  241. package/src/projected-field-value-rendering.tsx +704 -0
  242. package/src/projected-inline-list.tsx +434 -0
  243. package/src/projected-input-form.tsx +1218 -0
  244. package/src/projected-metric-dashboard.tsx +106 -0
  245. package/src/projected-metric-strip.tsx +244 -0
  246. package/src/projected-navigation-shell-view-host.tsx +177 -0
  247. package/src/projected-navigation-shell.tsx +219 -0
  248. package/src/projected-presentation-badges.tsx +95 -0
  249. package/src/projected-presentation-diagnostics.tsx +120 -0
  250. package/src/projected-presentation-flow-layer.tsx +186 -0
  251. package/src/projected-process-task-notices.tsx +501 -0
  252. package/src/projected-prompt-child-view-renderer.tsx +698 -0
  253. package/src/projected-prompt-document-preview.tsx +197 -0
  254. package/src/projected-prompt-view.tsx +592 -0
  255. package/src/projected-query-form.tsx +286 -0
  256. package/src/projected-record-details.tsx +133 -0
  257. package/src/projected-routed-surface-runtime.tsx +713 -0
  258. package/src/projected-tabs-view.tsx +308 -0
  259. package/src/projected-view-chrome.tsx +148 -0
  260. package/src/projected-view-surface.tsx +131 -0
  261. package/src/standard-collection-chrome-slot-renderers.tsx +391 -0
  262. package/src/standard-view-chrome-slot-renderers.tsx +1064 -0
@@ -0,0 +1,1064 @@
1
+ import { useMemo, type ReactNode } from 'react'
2
+
3
+ import {
4
+ createPresentationProjectionDiagnostic,
5
+ createViewChromeSlotRendererRegistry,
6
+ findDocumentFieldNavigationRouteBinding,
7
+ findPresentationField,
8
+ findPresentationView,
9
+ formatPresentationValue,
10
+ getViewChromeSlotRendererRegistryKeys,
11
+ isProjectedDocumentEntityReferenceField,
12
+ type NavigationRouteParameters,
13
+ projectDocumentFieldNavigation,
14
+ readPresentationFieldValue,
15
+ resolvePresentationBadges,
16
+ resolvePresentationValue,
17
+ resolvePromptStatusMessages,
18
+ resolveViewChromeSlotRenderer,
19
+ viewChromeIconIds,
20
+ type DocumentEditorLayout,
21
+ type PresentationBadgeDefinition,
22
+ type PresentationDataSourceResolver,
23
+ type PresentationModuleDefinition,
24
+ type PresentationProjectionDiagnostic,
25
+ type ProjectedDocumentActionStatusMap,
26
+ type ProjectedDocumentFieldDefinitionLike,
27
+ type ViewChromeSlotDefinition,
28
+ type ViewDefinition,
29
+ } from '@cohesivesystems/presentation-core'
30
+ import {
31
+ PresentationActionGroup,
32
+ type PresentationActionGroupOptions,
33
+ } from './presentation-action-group'
34
+ import {
35
+ renderPresentationIcon,
36
+ } from './presentation-icon-registry'
37
+ import {
38
+ usePresentationNavigationRuntime,
39
+ useRegisterPresentationProjectionDiagnostics,
40
+ } from '@cohesivesystems/presentation-react'
41
+ import type {
42
+ PresentationShadcnComponentSystem as PresentationComponentSystem,
43
+ } from './presentation-shadcn-component-system'
44
+ import type { PresentationDesignSystem } from '@cohesivesystems/presentation-tailwind'
45
+ import {
46
+ interpretPresentationBadgeTarget,
47
+ type PresentationBadgeTargetInterpreterRegistry,
48
+ } from './presentation-badge-target-interpreter'
49
+ import {
50
+ ProjectedPresentationBadge,
51
+ } from './projected-presentation-badges'
52
+ import {
53
+ renderProjectedEntityReferenceValue,
54
+ } from './projected-field-value-rendering'
55
+ import { ProjectedStatusBlock } from './projected-activity-state'
56
+ import {
57
+ ProjectedMetricStrip,
58
+ type ProjectedMetricValue,
59
+ } from './projected-metric-strip'
60
+ import {
61
+ viewChromeSlotKinds,
62
+ } from '@cohesivesystems/presentation-contracts'
63
+
64
+ export type ProjectedViewChromeMetadataRole = string
65
+
66
+ export type ProjectedViewChromeMetadataBadgeRoleReader = (
67
+ badge: Pick<PresentationBadgeDefinition, 'Annotations'>,
68
+ field: Pick<ProjectedDocumentFieldDefinitionLike, 'Annotations'> | null | undefined,
69
+ ) => ProjectedViewChromeMetadataRole | null
70
+
71
+ export type ProjectedViewChromeMetadataFieldRoleReader = (
72
+ field: Pick<ProjectedDocumentFieldDefinitionLike, 'Annotations'> | null | undefined,
73
+ ) => ProjectedViewChromeMetadataRole | null
74
+
75
+ export interface ProjectedViewChromeSlotRenderContext<TActionContext> {
76
+ readonly actionContext: TActionContext
77
+ readonly actionGroupOptions?: PresentationActionGroupOptions<TActionContext>
78
+ readonly actionStatuses?: ProjectedDocumentActionStatusMap
79
+ readonly activeViewId?: string | null
80
+ readonly componentSystem: PresentationComponentSystem
81
+ readonly createHref?: (
82
+ routeId: string,
83
+ parameters?: NavigationRouteParameters,
84
+ ) => string | null
85
+ readonly dataSourceResolver: PresentationDataSourceResolver
86
+ readonly designSystem: PresentationDesignSystem
87
+ readonly documentViewIds?: readonly string[]
88
+ readonly badgeInterpreters?: PresentationBadgeTargetInterpreterRegistry
89
+ readonly layout?: DocumentEditorLayout | null
90
+ readonly metadataEntityReferenceRole?: ProjectedViewChromeMetadataRole | null
91
+ readonly metricMessage?: ReactNode
92
+ readonly metricValues?: Readonly<Record<string, ProjectedMetricValue>>
93
+ readonly module: PresentationModuleDefinition | null
94
+ readonly navigateHref?: (href: string) => void
95
+ readonly onActiveViewIdChange?: (viewId: string) => void
96
+ readonly onLayoutChange?: (layout: DocumentEditorLayout) => void
97
+ readonly readMetadataBadgeRole?: ProjectedViewChromeMetadataBadgeRoleReader
98
+ readonly readMetadataFieldRole?: ProjectedViewChromeMetadataFieldRoleReader
99
+ readonly resource?: unknown
100
+ readonly slot: ViewChromeSlotDefinition
101
+ readonly view: ViewDefinition | null
102
+ readonly workspaceView?: ViewDefinition | null
103
+ }
104
+
105
+ export const standardViewChromeSlotRenderers =
106
+ createViewChromeSlotRendererRegistry<
107
+ ProjectedViewChromeSlotRenderContext<object>,
108
+ ReactNode
109
+ >([
110
+ {
111
+ kind: viewChromeSlotKinds.actions,
112
+ render: renderStandardViewActions,
113
+ },
114
+ {
115
+ kind: viewChromeSlotKinds.badgeStrip,
116
+ render: renderStandardViewBadgeStrip,
117
+ },
118
+ {
119
+ kind: viewChromeSlotKinds.metricStrip,
120
+ render: renderStandardViewMetricStrip,
121
+ },
122
+ {
123
+ kind: viewChromeSlotKinds.viewSwitch,
124
+ render: renderStandardViewSwitch,
125
+ },
126
+ {
127
+ kind: viewChromeSlotKinds.layoutSwitch,
128
+ render: renderStandardLayoutSwitch,
129
+ },
130
+ {
131
+ kind: viewChromeSlotKinds.headingTrailing,
132
+ render: renderStandardViewBadgeStrip,
133
+ },
134
+ {
135
+ kind: viewChromeSlotKinds.status,
136
+ render: renderStandardViewStatus,
137
+ },
138
+ ])
139
+
140
+ export const standardViewChromeSlotRendererKeys =
141
+ getViewChromeSlotRendererRegistryKeys(standardViewChromeSlotRenderers)
142
+
143
+ const emptyMetricValues: Readonly<Record<string, ProjectedMetricValue>> = {}
144
+
145
+ type ProjectedViewBadgeRenderingMode =
146
+ | 'role-interpreter'
147
+ | 'standard-badge'
148
+ | 'unrendered'
149
+
150
+ interface ProjectedViewBadgeRenderingEntry {
151
+ readonly badgeFieldId: string | null
152
+ readonly badgeId: string
153
+ readonly badgeName: string
154
+ readonly field: ProjectedDocumentFieldDefinitionLike | null
155
+ readonly fieldId: string
156
+ readonly mode: ProjectedViewBadgeRenderingMode
157
+ readonly node: ReactNode
158
+ readonly rendered: boolean
159
+ readonly role: ProjectedViewChromeMetadataRole | null
160
+ readonly value: unknown
161
+ }
162
+
163
+ export function renderStandardViewChromeSlot<TActionContext>(
164
+ context: ProjectedViewChromeSlotRenderContext<TActionContext>,
165
+ ) {
166
+ const renderer = resolveViewChromeSlotRenderer(
167
+ standardViewChromeSlotRenderers,
168
+ context.slot,
169
+ )
170
+ return renderer?.(
171
+ context as unknown as ProjectedViewChromeSlotRenderContext<object>,
172
+ ) ?? null
173
+ }
174
+
175
+ function renderStandardViewActions<TActionContext>({
176
+ actionContext,
177
+ actionGroupOptions,
178
+ componentSystem,
179
+ dataSourceResolver,
180
+ designSystem,
181
+ module,
182
+ slot,
183
+ view,
184
+ }: ProjectedViewChromeSlotRenderContext<TActionContext>) {
185
+ if (!actionGroupOptions || !module || !view) {
186
+ return null
187
+ }
188
+
189
+ return componentSystem.viewChrome.ActionSlot({
190
+ children: (
191
+ <PresentationActionGroup
192
+ context={actionContext}
193
+ dataSourceResolver={dataSourceResolver}
194
+ module={module}
195
+ options={{
196
+ ...actionGroupOptions,
197
+ componentSystem,
198
+ designSystem,
199
+ }}
200
+ view={view}
201
+ />
202
+ ),
203
+ slotId: slot.Id,
204
+ viewId: view.Id,
205
+ })
206
+ }
207
+
208
+ function renderStandardViewBadgeStrip({
209
+ componentSystem,
210
+ createHref,
211
+ designSystem,
212
+ badgeInterpreters,
213
+ metadataEntityReferenceRole,
214
+ module,
215
+ navigateHref,
216
+ readMetadataBadgeRole,
217
+ readMetadataFieldRole,
218
+ resource,
219
+ slot,
220
+ view,
221
+ }: ProjectedViewChromeSlotRenderContext<object>) {
222
+ return (
223
+ <ProjectedViewBadgeStrip
224
+ badges={slot.Badges ?? []}
225
+ componentSystem={componentSystem}
226
+ designSystem={designSystem}
227
+ badgeInterpreters={badgeInterpreters}
228
+ createHref={createHref}
229
+ fieldIds={slot.FieldIds ?? []}
230
+ metadataEntityReferenceRole={metadataEntityReferenceRole}
231
+ module={module}
232
+ navigateHref={navigateHref}
233
+ readMetadataBadgeRole={readMetadataBadgeRole}
234
+ readMetadataFieldRole={readMetadataFieldRole}
235
+ resource={resource}
236
+ slotId={slot.Id}
237
+ viewId={view?.Id}
238
+ viewName={view?.Name}
239
+ />
240
+ )
241
+ }
242
+
243
+ function renderStandardViewMetricStrip({
244
+ componentSystem,
245
+ dataSourceResolver,
246
+ metricMessage,
247
+ metricValues = emptyMetricValues,
248
+ slot,
249
+ view,
250
+ }: ProjectedViewChromeSlotRenderContext<object>) {
251
+ if (!slot.FieldIds || slot.FieldIds.length === 0) {
252
+ return null
253
+ }
254
+
255
+ return componentSystem.viewChrome.MetricStripSlot({
256
+ children: (
257
+ <ProjectedMetricStrip
258
+ className="sm:grid-cols-2 lg:grid-cols-4"
259
+ componentSystem={componentSystem}
260
+ dataSourceResolver={dataSourceResolver}
261
+ fieldIds={slot.FieldIds}
262
+ values={metricValues}
263
+ viewId={view?.Id}
264
+ />
265
+ ),
266
+ message: metricMessage,
267
+ slotId: slot.Id,
268
+ viewId: view?.Id,
269
+ })
270
+ }
271
+
272
+ function renderStandardViewStatus({
273
+ actionStatuses = {},
274
+ componentSystem,
275
+ dataSourceResolver,
276
+ resource,
277
+ slot,
278
+ view,
279
+ }: ProjectedViewChromeSlotRenderContext<object>) {
280
+ const promptMessages = view?.PromptStatusMessages
281
+ ? resolvePromptStatusMessages({
282
+ actionStatuses,
283
+ dataSourceResolver,
284
+ region: slot.StateId ?? slot.Id,
285
+ view,
286
+ })
287
+ : []
288
+ const literalValue = formatPresentationValue(resolvePresentationValue(slot.Value, resource))
289
+ const messages = promptMessages.length > 0
290
+ ? promptMessages
291
+ : literalValue
292
+ ? [{ label: literalValue, tone: 'default' as const }]
293
+ : []
294
+
295
+ if (messages.length === 0) {
296
+ return null
297
+ }
298
+
299
+ return (
300
+ <div className="grid gap-2">
301
+ {messages.map((message, index) => (
302
+ <ProjectedStatusBlock
303
+ componentSystem={componentSystem}
304
+ key={promptMessages[index]?.definition.Id ?? `${slot.Id}:${index}`}
305
+ label={message.label}
306
+ tone={message.tone}
307
+ />
308
+ ))}
309
+ </div>
310
+ )
311
+ }
312
+
313
+ function renderStandardViewSwitch({
314
+ activeViewId,
315
+ componentSystem,
316
+ documentViewIds,
317
+ module,
318
+ onActiveViewIdChange,
319
+ slot,
320
+ workspaceView,
321
+ }: ProjectedViewChromeSlotRenderContext<object>) {
322
+ const viewIds = documentViewIds && documentViewIds.length > 0
323
+ ? documentViewIds
324
+ : slot.ViewIds
325
+ if (viewIds.length <= 1 || !activeViewId || !onActiveViewIdChange) {
326
+ return null
327
+ }
328
+
329
+ return componentSystem.viewChrome.ViewSwitch({
330
+ ariaLabel: 'Document view',
331
+ items: viewIds.map((viewId) => {
332
+ const label = getDocumentViewLabel(module, workspaceView ?? null, viewId)
333
+ const icon = resolveDocumentViewIcon(label, viewId)
334
+
335
+ return {
336
+ icon: renderViewChromeIcon({
337
+ fallbackIcon: icon.fallbackIcon,
338
+ icon: icon.id,
339
+ module,
340
+ }),
341
+ id: viewId,
342
+ isActive: activeViewId === viewId,
343
+ label,
344
+ onSelect: () => onActiveViewIdChange(viewId),
345
+ }
346
+ }),
347
+ slotId: slot.Id,
348
+ viewId: workspaceView?.Id,
349
+ })
350
+ }
351
+
352
+ function renderStandardLayoutSwitch({
353
+ componentSystem,
354
+ layout,
355
+ module,
356
+ onLayoutChange,
357
+ slot,
358
+ view,
359
+ }: ProjectedViewChromeSlotRenderContext<object>) {
360
+ if (!layout || !onLayoutChange) {
361
+ return null
362
+ }
363
+
364
+ return componentSystem.viewChrome.LayoutSwitch({
365
+ ariaLabel: 'Document layout',
366
+ items: [
367
+ {
368
+ icon: renderViewChromeIcon({
369
+ fallbackIcon: 'square',
370
+ icon: viewChromeIconIds.layoutSingle,
371
+ module,
372
+ }),
373
+ id: 'single',
374
+ isActive: layout === 'single',
375
+ label: 'Single',
376
+ onSelect: () => onLayoutChange('single'),
377
+ },
378
+ {
379
+ icon: renderViewChromeIcon({
380
+ fallbackIcon: 'columns-2',
381
+ icon: viewChromeIconIds.layoutSplit,
382
+ module,
383
+ }),
384
+ id: 'split',
385
+ isActive: layout === 'split',
386
+ label: 'Split',
387
+ onSelect: () => onLayoutChange('split'),
388
+ },
389
+ ],
390
+ slotId: slot.Id,
391
+ viewId: view?.Id,
392
+ })
393
+ }
394
+
395
+ export function ProjectedViewBadgeStrip({
396
+ badges,
397
+ componentSystem,
398
+ createHref,
399
+ designSystem,
400
+ badgeInterpreters,
401
+ fieldIds,
402
+ metadataEntityReferenceRole,
403
+ module,
404
+ navigateHref,
405
+ readMetadataBadgeRole,
406
+ readMetadataFieldRole,
407
+ resource,
408
+ slotId = 'adhoc',
409
+ viewId,
410
+ viewName,
411
+ }: {
412
+ readonly badges?: readonly PresentationBadgeDefinition[]
413
+ readonly componentSystem: PresentationComponentSystem
414
+ readonly createHref?: (
415
+ routeId: string,
416
+ parameters?: NavigationRouteParameters,
417
+ ) => string | null
418
+ readonly designSystem: PresentationDesignSystem
419
+ readonly badgeInterpreters?: PresentationBadgeTargetInterpreterRegistry
420
+ readonly fieldIds: readonly string[]
421
+ readonly metadataEntityReferenceRole?: ProjectedViewChromeMetadataRole | null
422
+ readonly module: PresentationModuleDefinition | null
423
+ readonly navigateHref?: (href: string) => void
424
+ readonly readMetadataBadgeRole?: ProjectedViewChromeMetadataBadgeRoleReader
425
+ readonly readMetadataFieldRole?: ProjectedViewChromeMetadataFieldRoleReader
426
+ readonly resource: unknown
427
+ readonly slotId?: string
428
+ readonly viewId?: string | null
429
+ readonly viewName?: string | null
430
+ }) {
431
+ const navigation = usePresentationNavigationRuntime()
432
+ const resolvedCreateHref = createHref ?? navigation.createHref
433
+ const resolvedNavigateHref = navigateHref ?? navigation.navigateHref
434
+ const badgeDefinitions = useMemo(
435
+ () => badges && badges.length > 0
436
+ ? badges
437
+ : fieldIds.map(createFieldBackedBadgeDefinition),
438
+ [badges, fieldIds],
439
+ )
440
+ const renderedBadgeEntries = useMemo(
441
+ (): readonly ProjectedViewBadgeRenderingEntry[] => badgeDefinitions
442
+ .map((badge) => {
443
+ const fieldId = badge.FieldId ?? badge.Id
444
+ const field = findPresentationField<ProjectedDocumentFieldDefinitionLike>(module, fieldId)
445
+ const value = readPresentationFieldValue(resource, field?.Field)
446
+ const resolvedBadge = resolvePresentationBadges([badge], resource, module)[0] ?? null
447
+ const role = readMetadataBadgeRole?.(badge, field) ?? null
448
+ const interpretedBadge = interpretPresentationBadgeTarget(badgeInterpreters, {
449
+ badge,
450
+ componentSystem,
451
+ designSystem,
452
+ field,
453
+ fieldId,
454
+ module,
455
+ resolvedBadge,
456
+ resource,
457
+ value,
458
+ })
459
+
460
+ if (interpretedBadge !== undefined) {
461
+ const node = interpretedBadge === null ? null : (
462
+ <span className="contents" key={fieldId}>
463
+ {interpretedBadge}
464
+ </span>
465
+ )
466
+ return {
467
+ badgeFieldId: badge.FieldId ?? null,
468
+ badgeId: badge.Id,
469
+ badgeName: badge.Name,
470
+ field,
471
+ fieldId,
472
+ mode: 'role-interpreter',
473
+ node,
474
+ rendered: Boolean(node),
475
+ role,
476
+ value,
477
+ }
478
+ }
479
+
480
+ const node = renderStandardBadgeNode({
481
+ badge,
482
+ componentSystem,
483
+ createHref: resolvedCreateHref,
484
+ designSystem,
485
+ field,
486
+ module,
487
+ navigateHref: resolvedNavigateHref,
488
+ resolvedBadge,
489
+ resource,
490
+ value,
491
+ })
492
+ return {
493
+ badgeFieldId: badge.FieldId ?? null,
494
+ badgeId: badge.Id,
495
+ badgeName: badge.Name,
496
+ field,
497
+ fieldId,
498
+ mode: node ? 'standard-badge' : 'unrendered',
499
+ node,
500
+ rendered: Boolean(node),
501
+ role,
502
+ value,
503
+ }
504
+ }),
505
+ [
506
+ badgeDefinitions,
507
+ componentSystem,
508
+ createHref,
509
+ designSystem,
510
+ badgeInterpreters,
511
+ module,
512
+ navigateHref,
513
+ resolvedCreateHref,
514
+ resolvedNavigateHref,
515
+ readMetadataBadgeRole,
516
+ resource,
517
+ ],
518
+ )
519
+ const renderedBadges = renderedBadgeEntries.flatMap((entry) =>
520
+ entry.node ? [entry.node] : [])
521
+ const roleDiagnosticSource =
522
+ `projected-view-badge-strip:${viewId ?? 'unknown'}:${slotId}:badge-roles`
523
+ const roleDiagnostics = useMemo(
524
+ () => projectViewBadgeStripRoleDiagnostics({
525
+ entries: renderedBadgeEntries,
526
+ fieldIds,
527
+ metadataEntityReferenceRole,
528
+ module,
529
+ readMetadataFieldRole,
530
+ resource,
531
+ sourceId: roleDiagnosticSource,
532
+ slotId,
533
+ viewId,
534
+ viewName,
535
+ }),
536
+ [
537
+ fieldIds,
538
+ metadataEntityReferenceRole,
539
+ module,
540
+ readMetadataFieldRole,
541
+ renderedBadgeEntries,
542
+ resource,
543
+ roleDiagnosticSource,
544
+ slotId,
545
+ viewId,
546
+ viewName,
547
+ ],
548
+ )
549
+ useRegisterPresentationProjectionDiagnostics(
550
+ roleDiagnosticSource,
551
+ roleDiagnostics,
552
+ )
553
+
554
+ if (renderedBadges.length === 0) {
555
+ return null
556
+ }
557
+
558
+ return componentSystem.viewChrome.BadgeStrip({
559
+ badges: renderedBadges,
560
+ slotId,
561
+ viewId,
562
+ })
563
+ }
564
+
565
+ function renderStandardBadgeNode({
566
+ badge,
567
+ componentSystem,
568
+ createHref,
569
+ designSystem,
570
+ field,
571
+ module,
572
+ navigateHref,
573
+ resolvedBadge,
574
+ resource,
575
+ value,
576
+ }: {
577
+ readonly badge: PresentationBadgeDefinition
578
+ readonly componentSystem: PresentationComponentSystem
579
+ readonly createHref?: (
580
+ routeId: string,
581
+ parameters?: NavigationRouteParameters,
582
+ ) => string | null
583
+ readonly designSystem: PresentationDesignSystem
584
+ readonly field: ProjectedDocumentFieldDefinitionLike | null
585
+ readonly module: PresentationModuleDefinition | null
586
+ readonly navigateHref?: (href: string) => void
587
+ readonly resolvedBadge: ReturnType<typeof resolvePresentationBadges>[number] | null
588
+ readonly resource: unknown
589
+ readonly value: unknown
590
+ }) {
591
+ if (
592
+ field &&
593
+ navigateHref &&
594
+ isProjectedDocumentEntityReferenceField(field) &&
595
+ findDocumentFieldNavigationRouteBinding(module, field)
596
+ ) {
597
+ const entityReferenceBadge = renderProjectedEntityReferenceValue({
598
+ componentSystem,
599
+ createHref,
600
+ field,
601
+ module,
602
+ navigateHref,
603
+ resource,
604
+ unboundEntityReferenceStyle: 'badge',
605
+ value,
606
+ })
607
+
608
+ return entityReferenceBadge ? (
609
+ <span className="contents" key={badge.Id}>
610
+ {entityReferenceBadge}
611
+ </span>
612
+ ) : null
613
+ }
614
+
615
+ return resolvedBadge ? (
616
+ <ProjectedPresentationBadge
617
+ badge={resolvedBadge}
618
+ componentSystem={componentSystem}
619
+ designSystem={designSystem}
620
+ key={badge.Id}
621
+ variant="outline"
622
+ />
623
+ ) : null
624
+ }
625
+
626
+ function projectViewBadgeStripRoleDiagnostics({
627
+ entries,
628
+ fieldIds,
629
+ metadataEntityReferenceRole,
630
+ module,
631
+ readMetadataFieldRole,
632
+ resource,
633
+ sourceId,
634
+ slotId,
635
+ viewId,
636
+ viewName,
637
+ }: {
638
+ readonly entries: readonly ProjectedViewBadgeRenderingEntry[]
639
+ readonly fieldIds: readonly string[]
640
+ readonly metadataEntityReferenceRole?: ProjectedViewChromeMetadataRole | null
641
+ readonly module: PresentationModuleDefinition | null
642
+ readonly readMetadataFieldRole?: ProjectedViewChromeMetadataFieldRoleReader
643
+ readonly resource: unknown
644
+ readonly sourceId: string
645
+ readonly slotId: string
646
+ readonly viewId?: string | null
647
+ readonly viewName?: string | null
648
+ }): readonly PresentationProjectionDiagnostic[] {
649
+ const diagnostics: PresentationProjectionDiagnostic[] = []
650
+ const roleEntries = entries.filter((entry) => entry.role)
651
+ const unhandledRoleEntries = roleEntries.filter((entry) =>
652
+ entry.mode !== 'role-interpreter')
653
+ const referencedFieldIds = new Set(entries
654
+ .map((entry) => entry.badgeFieldId)
655
+ .filter((fieldId): fieldId is string => Boolean(fieldId)))
656
+ const unreferencedRoleFields = fieldIds
657
+ .map((fieldId) => findPresentationField<ProjectedDocumentFieldDefinitionLike>(module, fieldId))
658
+ .filter((field): field is ProjectedDocumentFieldDefinitionLike => Boolean(field))
659
+ .map((field) => ({
660
+ field,
661
+ role: readMetadataFieldRole?.(field) ?? null,
662
+ }))
663
+ .filter((entry) => entry.role && !referencedFieldIds.has(entry.field.Id))
664
+
665
+ if (roleEntries.length > 0 || unreferencedRoleFields.length > 0) {
666
+ const counts = countBadgeRenderingModes(roleEntries)
667
+ diagnostics.push(
668
+ createPresentationProjectionDiagnostic({
669
+ category: 'local-interpretation',
670
+ details: {
671
+ badges: roleEntries.map((entry) => ({
672
+ badgeId: entry.badgeId,
673
+ fieldId: entry.badgeFieldId,
674
+ mode: entry.mode,
675
+ rendered: entry.rendered,
676
+ role: entry.role,
677
+ })),
678
+ counts,
679
+ slotId,
680
+ viewId: viewId ?? null,
681
+ },
682
+ id: `view.${viewId ?? 'unknown'}.badge-strip.${slotId}.metadata-badge-coverage`,
683
+ interpretation: {
684
+ status: unhandledRoleEntries.length === 0 ? 'locally-interpreted' : 'unbound',
685
+ target: 'presentation-badge-target-interpreter',
686
+ },
687
+ message:
688
+ `Badge strip slot '${slotId}'${viewName ? ` on '${viewName}'` : ''} ` +
689
+ `renders ${roleEntries.length} metadata badge role(s): ` +
690
+ `${counts['role-interpreter']} via role interpreters, ` +
691
+ `${counts['standard-badge']} via standard badge semantics, ` +
692
+ `${counts.unrendered} unrendered.`,
693
+ severity: 'info',
694
+ source: sourceId,
695
+ subject: {
696
+ id: slotId,
697
+ kind: 'view-chrome-slot',
698
+ name: viewName,
699
+ },
700
+ }),
701
+ )
702
+ }
703
+
704
+ diagnostics.push(
705
+ ...projectViewBadgeStripEntityReferenceDiagnostics({
706
+ entries: roleEntries,
707
+ metadataEntityReferenceRole,
708
+ module,
709
+ resource,
710
+ sourceId,
711
+ slotId,
712
+ viewId,
713
+ viewName,
714
+ }),
715
+ )
716
+
717
+ for (const entry of unhandledRoleEntries) {
718
+ diagnostics.push(
719
+ createPresentationProjectionDiagnostic({
720
+ category: 'missing-binding',
721
+ details: {
722
+ badgeId: entry.badgeId,
723
+ fieldId: entry.badgeFieldId,
724
+ mode: entry.mode,
725
+ role: entry.role,
726
+ slotId,
727
+ viewId: viewId ?? null,
728
+ },
729
+ id: `view.${viewId ?? 'unknown'}.badge-strip.${slotId}.badge.${entry.badgeId}.role-unbound`,
730
+ interpretation: {
731
+ status: 'unbound',
732
+ target: 'presentation-badge-target-interpreter',
733
+ },
734
+ message:
735
+ `Metadata badge '${entry.badgeName}' declares role '${entry.role}', ` +
736
+ `but no badge target interpreter handled it.`,
737
+ severity: 'warning',
738
+ source: sourceId,
739
+ subject: {
740
+ id: entry.badgeId,
741
+ kind: 'presentation-badge',
742
+ name: entry.badgeName,
743
+ },
744
+ suggestedNextStep:
745
+ 'Bind this metadata badge role in the frontend badge target interpreter registry.',
746
+ }),
747
+ )
748
+ }
749
+
750
+ for (const { field, role } of unreferencedRoleFields) {
751
+ diagnostics.push(
752
+ createPresentationProjectionDiagnostic({
753
+ category: 'incomplete-projection',
754
+ details: {
755
+ fieldId: field.Id,
756
+ fieldPath: field.Field,
757
+ role,
758
+ slotId,
759
+ viewId: viewId ?? null,
760
+ },
761
+ id: `view.${viewId ?? 'unknown'}.badge-strip.${slotId}.field.${field.Id}.role-unreferenced`,
762
+ interpretation: {
763
+ status: 'unbound',
764
+ target: 'presentation-badge-definition',
765
+ },
766
+ message:
767
+ `Metadata field '${field.Label}' declares role '${role}', but no ` +
768
+ `badge in slot '${slotId}' references it.`,
769
+ severity: 'warning',
770
+ source: sourceId,
771
+ subject: {
772
+ id: field.Id,
773
+ kind: 'field',
774
+ name: field.Label,
775
+ },
776
+ suggestedNextStep:
777
+ 'Add a role-annotated PresentationBadgeDefinition for this metadata field.',
778
+ }),
779
+ )
780
+ }
781
+
782
+ return diagnostics
783
+ }
784
+
785
+ function projectViewBadgeStripEntityReferenceDiagnostics({
786
+ entries,
787
+ metadataEntityReferenceRole,
788
+ module,
789
+ resource,
790
+ sourceId,
791
+ slotId,
792
+ viewId,
793
+ viewName,
794
+ }: {
795
+ readonly entries: readonly ProjectedViewBadgeRenderingEntry[]
796
+ readonly metadataEntityReferenceRole?: ProjectedViewChromeMetadataRole | null
797
+ readonly module: PresentationModuleDefinition | null
798
+ readonly resource: unknown
799
+ readonly sourceId: string
800
+ readonly slotId: string
801
+ readonly viewId?: string | null
802
+ readonly viewName?: string | null
803
+ }): readonly PresentationProjectionDiagnostic[] {
804
+ if (!metadataEntityReferenceRole) {
805
+ return []
806
+ }
807
+
808
+ const entityReferenceEntries = entries.filter((entry) =>
809
+ entry.role === metadataEntityReferenceRole)
810
+ if (entityReferenceEntries.length === 0) {
811
+ return []
812
+ }
813
+
814
+ const routeStates = entityReferenceEntries.map((entry) => {
815
+ const binding = entry.field
816
+ ? findDocumentFieldNavigationRouteBinding(module, entry.field)
817
+ : null
818
+ const projection =
819
+ entry.field && binding?.RouteId
820
+ ? projectDocumentFieldNavigation({
821
+ field: entry.field,
822
+ module,
823
+ resource,
824
+ value: entry.value,
825
+ })
826
+ : null
827
+
828
+ return {
829
+ binding,
830
+ entry,
831
+ projection,
832
+ }
833
+ })
834
+ const routeMissing = routeStates.filter((state) => !state.binding?.RouteId)
835
+ const unresolvedParameters = routeStates.filter((state) =>
836
+ (state.projection?.missingParameterNames.length ?? 0) > 0)
837
+ const routeReady = routeStates.filter((state) =>
838
+ state.binding?.RouteId &&
839
+ state.projection &&
840
+ state.projection.missingParameterNames.length === 0)
841
+ const fallback = routeStates.filter((state) =>
842
+ !state.binding?.RouteId || !state.projection)
843
+ const diagnostics: PresentationProjectionDiagnostic[] = [
844
+ createPresentationProjectionDiagnostic({
845
+ category: 'local-interpretation',
846
+ details: {
847
+ fallback: fallback.map(({ entry }) => entry.badgeId),
848
+ routeMissing: routeMissing.map(({ entry }) => entry.badgeId),
849
+ routeReady: routeReady.map(({ entry, projection }) => ({
850
+ badgeId: entry.badgeId,
851
+ routeId: projection?.routeId,
852
+ })),
853
+ slotId,
854
+ unresolvedParameters: unresolvedParameters.map(({ entry, projection }) => ({
855
+ badgeId: entry.badgeId,
856
+ missingParameterNames: projection?.missingParameterNames ?? [],
857
+ routeId: projection?.routeId,
858
+ })),
859
+ viewId: viewId ?? null,
860
+ },
861
+ id: `view.${viewId ?? 'unknown'}.badge-strip.${slotId}.entity-reference-badge-coverage`,
862
+ interpretation: {
863
+ status: routeMissing.length === 0 && unresolvedParameters.length === 0
864
+ ? 'locally-interpreted'
865
+ : 'unbound',
866
+ target: 'entity-reference-badge-route-binding',
867
+ },
868
+ message:
869
+ `Badge strip slot '${slotId}'${viewName ? ` on '${viewName}'` : ''} ` +
870
+ `renders ${entityReferenceEntries.length} entity-reference badge(s): ` +
871
+ `${routeReady.length} route-bound link badge(s), ` +
872
+ `${fallback.length} non-link fallback badge(s), ` +
873
+ `${unresolvedParameters.length} with unresolved route parameter(s).`,
874
+ severity: 'info',
875
+ source: sourceId,
876
+ subject: {
877
+ id: slotId,
878
+ kind: 'view-chrome-slot',
879
+ name: viewName,
880
+ },
881
+ }),
882
+ ]
883
+
884
+ for (const { entry } of routeMissing) {
885
+ diagnostics.push(
886
+ createPresentationProjectionDiagnostic({
887
+ category: 'missing-binding',
888
+ details: {
889
+ badgeId: entry.badgeId,
890
+ fieldId: entry.badgeFieldId,
891
+ role: entry.role,
892
+ slotId,
893
+ viewId: viewId ?? null,
894
+ },
895
+ id: `view.${viewId ?? 'unknown'}.badge-strip.${slotId}.badge.${entry.badgeId}.entity-reference-route-missing`,
896
+ interpretation: {
897
+ status: 'unbound',
898
+ target: 'entity-reference-badge-route-binding',
899
+ },
900
+ message:
901
+ `Entity-reference badge '${entry.badgeName}' has no route target binding; ` +
902
+ 'it renders as a non-link fallback badge.',
903
+ severity: 'warning',
904
+ source: sourceId,
905
+ subject: {
906
+ id: entry.badgeId,
907
+ kind: 'presentation-badge',
908
+ name: entry.badgeName,
909
+ },
910
+ suggestedNextStep:
911
+ 'Add a navigation route target binding for this entity-reference field.',
912
+ }),
913
+ )
914
+ }
915
+
916
+ for (const { entry, projection } of unresolvedParameters) {
917
+ diagnostics.push(
918
+ createPresentationProjectionDiagnostic({
919
+ category: 'unbound',
920
+ details: {
921
+ badgeId: entry.badgeId,
922
+ fieldId: entry.badgeFieldId,
923
+ missingParameterNames: projection?.missingParameterNames ?? [],
924
+ role: entry.role,
925
+ routeId: projection?.routeId,
926
+ slotId,
927
+ viewId: viewId ?? null,
928
+ },
929
+ id: `view.${viewId ?? 'unknown'}.badge-strip.${slotId}.badge.${entry.badgeId}.entity-reference-route-parameters-unresolved`,
930
+ interpretation: {
931
+ status: 'unbound',
932
+ target: 'entity-reference-badge-route-parameters',
933
+ },
934
+ message:
935
+ `Entity-reference badge '${entry.badgeName}' has unresolved route ` +
936
+ `parameter(s): ${(projection?.missingParameterNames ?? []).join(', ')}.`,
937
+ severity: 'warning',
938
+ source: sourceId,
939
+ subject: {
940
+ id: entry.badgeId,
941
+ kind: 'presentation-badge',
942
+ name: entry.badgeName,
943
+ },
944
+ suggestedNextStep:
945
+ 'Update the route parameter binding for this entity-reference field or expose the required source value.',
946
+ }),
947
+ )
948
+ }
949
+
950
+ return diagnostics
951
+ }
952
+
953
+ function countBadgeRenderingModes(
954
+ entries: readonly ProjectedViewBadgeRenderingEntry[],
955
+ ): Record<ProjectedViewBadgeRenderingMode, number> {
956
+ return entries.reduce<Record<ProjectedViewBadgeRenderingMode, number>>(
957
+ (counts, entry) => ({
958
+ ...counts,
959
+ [entry.mode]: counts[entry.mode] + 1,
960
+ }),
961
+ {
962
+ 'role-interpreter': 0,
963
+ 'standard-badge': 0,
964
+ unrendered: 0,
965
+ },
966
+ )
967
+ }
968
+
969
+ function getDocumentViewLabel(
970
+ module: PresentationModuleDefinition | null,
971
+ workspaceView: ViewDefinition | null,
972
+ viewId: string,
973
+ ) {
974
+ const region = workspaceView?.Regions?.find((candidate) =>
975
+ candidate.ViewIds?.includes(viewId),
976
+ )
977
+ return (
978
+ region?.Name ??
979
+ findPresentationView(module, viewId)?.Name ??
980
+ getDocumentViewFallbackLabel(viewId)
981
+ )
982
+ }
983
+
984
+ function getDocumentViewFallbackLabel(viewId: string) {
985
+ const normalized = viewId.toLocaleLowerCase()
986
+ if (normalized.includes('json')) {
987
+ return 'JSON'
988
+ }
989
+
990
+ if (normalized.includes('structure')) {
991
+ return 'Structure'
992
+ }
993
+
994
+ if (normalized.includes('types')) {
995
+ return 'Types'
996
+ }
997
+
998
+ return viewId
999
+ }
1000
+
1001
+ function createFieldBackedBadgeDefinition(fieldId: string): PresentationBadgeDefinition {
1002
+ return {
1003
+ Annotations: [],
1004
+ Content: null,
1005
+ FieldId: fieldId,
1006
+ Id: fieldId,
1007
+ Name: fieldId,
1008
+ OmitWhenEmpty: true,
1009
+ OmitWhenZero: false,
1010
+ Tone: null,
1011
+ Value: null,
1012
+ ValueTemplate: null,
1013
+ }
1014
+ }
1015
+
1016
+ function resolveDocumentViewIcon(label: string, viewId: string) {
1017
+ const normalized = `${label} ${viewId}`.toLocaleLowerCase()
1018
+ if (normalized.includes('json')) {
1019
+ return {
1020
+ fallbackIcon: 'braces',
1021
+ id: viewChromeIconIds.viewJson,
1022
+ }
1023
+ }
1024
+
1025
+ if (normalized.includes('type')) {
1026
+ return {
1027
+ fallbackIcon: 'box',
1028
+ id: viewChromeIconIds.viewTypes,
1029
+ }
1030
+ }
1031
+
1032
+ if (normalized.includes('structure')) {
1033
+ return {
1034
+ fallbackIcon: 'list-tree',
1035
+ id: viewChromeIconIds.viewStructure,
1036
+ }
1037
+ }
1038
+
1039
+ return {
1040
+ fallbackIcon: 'braces',
1041
+ id: viewChromeIconIds.viewDefault,
1042
+ }
1043
+ }
1044
+
1045
+ function renderViewChromeIcon({
1046
+ className = 'size-3.5',
1047
+ fallbackIcon,
1048
+ icon,
1049
+ module,
1050
+ }: {
1051
+ readonly className?: string
1052
+ readonly fallbackIcon: string
1053
+ readonly icon: string
1054
+ readonly module?: PresentationModuleDefinition | null
1055
+ }) {
1056
+ return renderPresentationIcon({
1057
+ className,
1058
+ icon,
1059
+ module,
1060
+ }) ?? renderPresentationIcon({
1061
+ className,
1062
+ icon: fallbackIcon,
1063
+ })
1064
+ }