@cohesivesystems/presentation-core 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 (447) hide show
  1. package/LICENSE +55 -0
  2. package/dist/api-scope-request.d.ts +43 -0
  3. package/dist/api-scope-request.d.ts.map +1 -0
  4. package/dist/api-scope-request.js +96 -0
  5. package/dist/api-scope-request.js.map +1 -0
  6. package/dist/collection-chrome-icons.d.ts +18 -0
  7. package/dist/collection-chrome-icons.d.ts.map +1 -0
  8. package/dist/collection-chrome-icons.js +57 -0
  9. package/dist/collection-chrome-icons.js.map +1 -0
  10. package/dist/collection-chrome-runtime.d.ts +25 -0
  11. package/dist/collection-chrome-runtime.d.ts.map +1 -0
  12. package/dist/collection-chrome-runtime.js +72 -0
  13. package/dist/collection-chrome-runtime.js.map +1 -0
  14. package/dist/collection-chrome-slot-renderer-registry.d.ts +110 -0
  15. package/dist/collection-chrome-slot-renderer-registry.d.ts.map +1 -0
  16. package/dist/collection-chrome-slot-renderer-registry.js +136 -0
  17. package/dist/collection-chrome-slot-renderer-registry.js.map +1 -0
  18. package/dist/collection-selection-state.d.ts +18 -0
  19. package/dist/collection-selection-state.d.ts.map +1 -0
  20. package/dist/collection-selection-state.js +2 -0
  21. package/dist/collection-selection-state.js.map +1 -0
  22. package/dist/data-source-projection.d.ts +475 -0
  23. package/dist/data-source-projection.d.ts.map +1 -0
  24. package/dist/data-source-projection.js +880 -0
  25. package/dist/data-source-projection.js.map +1 -0
  26. package/dist/data-source-query-lowering.d.ts +35 -0
  27. package/dist/data-source-query-lowering.d.ts.map +1 -0
  28. package/dist/data-source-query-lowering.js +104 -0
  29. package/dist/data-source-query-lowering.js.map +1 -0
  30. package/dist/date-time-filter-model.d.ts +20 -0
  31. package/dist/date-time-filter-model.d.ts.map +1 -0
  32. package/dist/date-time-filter-model.js +115 -0
  33. package/dist/date-time-filter-model.js.map +1 -0
  34. package/dist/document-badge-icons.d.ts +4 -0
  35. package/dist/document-badge-icons.d.ts.map +1 -0
  36. package/dist/document-badge-icons.js +4 -0
  37. package/dist/document-badge-icons.js.map +1 -0
  38. package/dist/document-module.d.ts +268 -0
  39. package/dist/document-module.d.ts.map +1 -0
  40. package/dist/document-module.js +347 -0
  41. package/dist/document-module.js.map +1 -0
  42. package/dist/document-summary-model.d.ts +2 -0
  43. package/dist/document-summary-model.d.ts.map +1 -0
  44. package/dist/document-summary-model.js +2 -0
  45. package/dist/document-summary-model.js.map +1 -0
  46. package/dist/document-workspace-chrome-projection.d.ts +18 -0
  47. package/dist/document-workspace-chrome-projection.d.ts.map +1 -0
  48. package/dist/document-workspace-chrome-projection.js +35 -0
  49. package/dist/document-workspace-chrome-projection.js.map +1 -0
  50. package/dist/document-workspace-surface-slots.d.ts +75 -0
  51. package/dist/document-workspace-surface-slots.d.ts.map +1 -0
  52. package/dist/document-workspace-surface-slots.js +268 -0
  53. package/dist/document-workspace-surface-slots.js.map +1 -0
  54. package/dist/endpoint-executor.d.ts +62 -0
  55. package/dist/endpoint-executor.d.ts.map +1 -0
  56. package/dist/endpoint-executor.js +35 -0
  57. package/dist/endpoint-executor.js.map +1 -0
  58. package/dist/field-display-resolution.d.ts +5 -0
  59. package/dist/field-display-resolution.d.ts.map +1 -0
  60. package/dist/field-display-resolution.js +50 -0
  61. package/dist/field-display-resolution.js.map +1 -0
  62. package/dist/index.d.ts +90 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +89 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/module.d.ts +48 -0
  67. package/dist/module.d.ts.map +1 -0
  68. package/dist/module.js +108 -0
  69. package/dist/module.js.map +1 -0
  70. package/dist/navigation-shell-frame-renderer-registry.d.ts +12 -0
  71. package/dist/navigation-shell-frame-renderer-registry.d.ts.map +1 -0
  72. package/dist/navigation-shell-frame-renderer-registry.js +29 -0
  73. package/dist/navigation-shell-frame-renderer-registry.js.map +1 -0
  74. package/dist/navigation-shell-layout-runtime.d.ts +29 -0
  75. package/dist/navigation-shell-layout-runtime.d.ts.map +1 -0
  76. package/dist/navigation-shell-layout-runtime.js +50 -0
  77. package/dist/navigation-shell-layout-runtime.js.map +1 -0
  78. package/dist/navigation-shell-region-component-registry.d.ts +96 -0
  79. package/dist/navigation-shell-region-component-registry.d.ts.map +1 -0
  80. package/dist/navigation-shell-region-component-registry.js +108 -0
  81. package/dist/navigation-shell-region-component-registry.js.map +1 -0
  82. package/dist/navigation-shell-runtime.d.ts +48 -0
  83. package/dist/navigation-shell-runtime.d.ts.map +1 -0
  84. package/dist/navigation-shell-runtime.js +457 -0
  85. package/dist/navigation-shell-runtime.js.map +1 -0
  86. package/dist/navigation-shell-slot-renderer-registry.d.ts +15 -0
  87. package/dist/navigation-shell-slot-renderer-registry.d.ts.map +1 -0
  88. package/dist/navigation-shell-slot-renderer-registry.js +48 -0
  89. package/dist/navigation-shell-slot-renderer-registry.js.map +1 -0
  90. package/dist/navigation.d.ts +84 -0
  91. package/dist/navigation.d.ts.map +1 -0
  92. package/dist/navigation.js +167 -0
  93. package/dist/navigation.js.map +1 -0
  94. package/dist/object-path.d.ts +23 -0
  95. package/dist/object-path.d.ts.map +1 -0
  96. package/dist/object-path.js +63 -0
  97. package/dist/object-path.js.map +1 -0
  98. package/dist/page-host-renderer-registry.d.ts +119 -0
  99. package/dist/page-host-renderer-registry.d.ts.map +1 -0
  100. package/dist/page-host-renderer-registry.js +305 -0
  101. package/dist/page-host-renderer-registry.js.map +1 -0
  102. package/dist/presentation-action-endpoint-request-projection.d.ts +23 -0
  103. package/dist/presentation-action-endpoint-request-projection.d.ts.map +1 -0
  104. package/dist/presentation-action-endpoint-request-projection.js +117 -0
  105. package/dist/presentation-action-endpoint-request-projection.js.map +1 -0
  106. package/dist/presentation-action-endpoint.d.ts +24 -0
  107. package/dist/presentation-action-endpoint.d.ts.map +1 -0
  108. package/dist/presentation-action-endpoint.js +56 -0
  109. package/dist/presentation-action-endpoint.js.map +1 -0
  110. package/dist/presentation-action-icons.d.ts +4 -0
  111. package/dist/presentation-action-icons.d.ts.map +1 -0
  112. package/dist/presentation-action-icons.js +4 -0
  113. package/dist/presentation-action-icons.js.map +1 -0
  114. package/dist/presentation-action-result-state.d.ts +48 -0
  115. package/dist/presentation-action-result-state.d.ts.map +1 -0
  116. package/dist/presentation-action-result-state.js +109 -0
  117. package/dist/presentation-action-result-state.js.map +1 -0
  118. package/dist/presentation-action-runtime-binding.d.ts +48 -0
  119. package/dist/presentation-action-runtime-binding.d.ts.map +1 -0
  120. package/dist/presentation-action-runtime-binding.js +52 -0
  121. package/dist/presentation-action-runtime-binding.js.map +1 -0
  122. package/dist/presentation-action-runtime-diagnostics.d.ts +18 -0
  123. package/dist/presentation-action-runtime-diagnostics.d.ts.map +1 -0
  124. package/dist/presentation-action-runtime-diagnostics.js +69 -0
  125. package/dist/presentation-action-runtime-diagnostics.js.map +1 -0
  126. package/dist/presentation-action-runtime-labels.d.ts +15 -0
  127. package/dist/presentation-action-runtime-labels.d.ts.map +1 -0
  128. package/dist/presentation-action-runtime-labels.js +28 -0
  129. package/dist/presentation-action-runtime-labels.js.map +1 -0
  130. package/dist/presentation-action-runtime-projection.d.ts +17 -0
  131. package/dist/presentation-action-runtime-projection.d.ts.map +1 -0
  132. package/dist/presentation-action-runtime-projection.js +31 -0
  133. package/dist/presentation-action-runtime-projection.js.map +1 -0
  134. package/dist/presentation-badge-resolution.d.ts +8 -0
  135. package/dist/presentation-badge-resolution.d.ts.map +1 -0
  136. package/dist/presentation-badge-resolution.js +73 -0
  137. package/dist/presentation-badge-resolution.js.map +1 -0
  138. package/dist/presentation-collection-pagination.d.ts +32 -0
  139. package/dist/presentation-collection-pagination.d.ts.map +1 -0
  140. package/dist/presentation-collection-pagination.js +275 -0
  141. package/dist/presentation-collection-pagination.js.map +1 -0
  142. package/dist/presentation-content-diagnostics.d.ts +17 -0
  143. package/dist/presentation-content-diagnostics.d.ts.map +1 -0
  144. package/dist/presentation-content-diagnostics.js +61 -0
  145. package/dist/presentation-content-diagnostics.js.map +1 -0
  146. package/dist/presentation-content-resolution.d.ts +9 -0
  147. package/dist/presentation-content-resolution.d.ts.map +1 -0
  148. package/dist/presentation-content-resolution.js +19 -0
  149. package/dist/presentation-content-resolution.js.map +1 -0
  150. package/dist/presentation-data-source-binding-model.d.ts +81 -0
  151. package/dist/presentation-data-source-binding-model.d.ts.map +1 -0
  152. package/dist/presentation-data-source-binding-model.js +56 -0
  153. package/dist/presentation-data-source-binding-model.js.map +1 -0
  154. package/dist/presentation-data-source-runtime.d.ts +62 -0
  155. package/dist/presentation-data-source-runtime.d.ts.map +1 -0
  156. package/dist/presentation-data-source-runtime.js +80 -0
  157. package/dist/presentation-data-source-runtime.js.map +1 -0
  158. package/dist/presentation-design-intent-diagnostics.d.ts +25 -0
  159. package/dist/presentation-design-intent-diagnostics.d.ts.map +1 -0
  160. package/dist/presentation-design-intent-diagnostics.js +55 -0
  161. package/dist/presentation-design-intent-diagnostics.js.map +1 -0
  162. package/dist/presentation-icon-diagnostics.d.ts +45 -0
  163. package/dist/presentation-icon-diagnostics.d.ts.map +1 -0
  164. package/dist/presentation-icon-diagnostics.js +235 -0
  165. package/dist/presentation-icon-diagnostics.js.map +1 -0
  166. package/dist/presentation-icon-registry.d.ts +36 -0
  167. package/dist/presentation-icon-registry.d.ts.map +1 -0
  168. package/dist/presentation-icon-registry.js +81 -0
  169. package/dist/presentation-icon-registry.js.map +1 -0
  170. package/dist/presentation-navigation-action-runtime-bindings.d.ts +13 -0
  171. package/dist/presentation-navigation-action-runtime-bindings.d.ts.map +1 -0
  172. package/dist/presentation-navigation-action-runtime-bindings.js +29 -0
  173. package/dist/presentation-navigation-action-runtime-bindings.js.map +1 -0
  174. package/dist/presentation-pagination.d.ts +98 -0
  175. package/dist/presentation-pagination.d.ts.map +1 -0
  176. package/dist/presentation-pagination.js +427 -0
  177. package/dist/presentation-pagination.js.map +1 -0
  178. package/dist/presentation-projection-coverage.d.ts +38 -0
  179. package/dist/presentation-projection-coverage.d.ts.map +1 -0
  180. package/dist/presentation-projection-coverage.js +2429 -0
  181. package/dist/presentation-projection-coverage.js.map +1 -0
  182. package/dist/presentation-projection-diagnostics.d.ts +33 -0
  183. package/dist/presentation-projection-diagnostics.d.ts.map +1 -0
  184. package/dist/presentation-projection-diagnostics.js +17 -0
  185. package/dist/presentation-projection-diagnostics.js.map +1 -0
  186. package/dist/presentation-projection-trace.d.ts +97 -0
  187. package/dist/presentation-projection-trace.d.ts.map +1 -0
  188. package/dist/presentation-projection-trace.js +110 -0
  189. package/dist/presentation-projection-trace.js.map +1 -0
  190. package/dist/presentation-query-form-state.d.ts +15 -0
  191. package/dist/presentation-query-form-state.d.ts.map +1 -0
  192. package/dist/presentation-query-form-state.js +20 -0
  193. package/dist/presentation-query-form-state.js.map +1 -0
  194. package/dist/presentation-renderer-registry.d.ts +86 -0
  195. package/dist/presentation-renderer-registry.d.ts.map +1 -0
  196. package/dist/presentation-renderer-registry.js +108 -0
  197. package/dist/presentation-renderer-registry.js.map +1 -0
  198. package/dist/presentation-semantics.d.ts +288 -0
  199. package/dist/presentation-semantics.d.ts.map +1 -0
  200. package/dist/presentation-semantics.js +560 -0
  201. package/dist/presentation-semantics.js.map +1 -0
  202. package/dist/presentation-standards.d.ts +8 -0
  203. package/dist/presentation-standards.d.ts.map +1 -0
  204. package/dist/presentation-standards.js +9 -0
  205. package/dist/presentation-standards.js.map +1 -0
  206. package/dist/presentation-test-selectors.d.ts +146 -0
  207. package/dist/presentation-test-selectors.d.ts.map +1 -0
  208. package/dist/presentation-test-selectors.js +113 -0
  209. package/dist/presentation-test-selectors.js.map +1 -0
  210. package/dist/presentation-value-formatting.d.ts +12 -0
  211. package/dist/presentation-value-formatting.d.ts.map +1 -0
  212. package/dist/presentation-value-formatting.js +33 -0
  213. package/dist/presentation-value-formatting.js.map +1 -0
  214. package/dist/presentation-value-resolution.d.ts +7 -0
  215. package/dist/presentation-value-resolution.d.ts.map +1 -0
  216. package/dist/presentation-value-resolution.js +69 -0
  217. package/dist/presentation-value-resolution.js.map +1 -0
  218. package/dist/presentation-view-tree.d.ts +6 -0
  219. package/dist/presentation-view-tree.d.ts.map +1 -0
  220. package/dist/presentation-view-tree.js +15 -0
  221. package/dist/presentation-view-tree.js.map +1 -0
  222. package/dist/process-task-model.d.ts +80 -0
  223. package/dist/process-task-model.d.ts.map +1 -0
  224. package/dist/process-task-model.js +52 -0
  225. package/dist/process-task-model.js.map +1 -0
  226. package/dist/process-task-projection.d.ts +46 -0
  227. package/dist/process-task-projection.d.ts.map +1 -0
  228. package/dist/process-task-projection.js +233 -0
  229. package/dist/process-task-projection.js.map +1 -0
  230. package/dist/projected-action-status-projection.d.ts +18 -0
  231. package/dist/projected-action-status-projection.d.ts.map +1 -0
  232. package/dist/projected-action-status-projection.js +19 -0
  233. package/dist/projected-action-status-projection.js.map +1 -0
  234. package/dist/projected-activity-state-model.d.ts +22 -0
  235. package/dist/projected-activity-state-model.d.ts.map +1 -0
  236. package/dist/projected-activity-state-model.js +87 -0
  237. package/dist/projected-activity-state-model.js.map +1 -0
  238. package/dist/projected-collection-action-runtime-bindings.d.ts +12 -0
  239. package/dist/projected-collection-action-runtime-bindings.d.ts.map +1 -0
  240. package/dist/projected-collection-action-runtime-bindings.js +37 -0
  241. package/dist/projected-collection-action-runtime-bindings.js.map +1 -0
  242. package/dist/projected-collection-action-runtime-diagnostics.d.ts +23 -0
  243. package/dist/projected-collection-action-runtime-diagnostics.d.ts.map +1 -0
  244. package/dist/projected-collection-action-runtime-diagnostics.js +168 -0
  245. package/dist/projected-collection-action-runtime-diagnostics.js.map +1 -0
  246. package/dist/projected-collection-runtime.d.ts +475 -0
  247. package/dist/projected-collection-runtime.d.ts.map +1 -0
  248. package/dist/projected-collection-runtime.js +832 -0
  249. package/dist/projected-collection-runtime.js.map +1 -0
  250. package/dist/projected-document-action-diagnostics.d.ts +16 -0
  251. package/dist/projected-document-action-diagnostics.d.ts.map +1 -0
  252. package/dist/projected-document-action-diagnostics.js +113 -0
  253. package/dist/projected-document-action-diagnostics.js.map +1 -0
  254. package/dist/projected-document-action-enablements.d.ts +25 -0
  255. package/dist/projected-document-action-enablements.d.ts.map +1 -0
  256. package/dist/projected-document-action-enablements.js +38 -0
  257. package/dist/projected-document-action-enablements.js.map +1 -0
  258. package/dist/projected-document-action-projection.d.ts +45 -0
  259. package/dist/projected-document-action-projection.d.ts.map +1 -0
  260. package/dist/projected-document-action-projection.js +201 -0
  261. package/dist/projected-document-action-projection.js.map +1 -0
  262. package/dist/projected-document-action-runtime-profile-diagnostics.d.ts +16 -0
  263. package/dist/projected-document-action-runtime-profile-diagnostics.d.ts.map +1 -0
  264. package/dist/projected-document-action-runtime-profile-diagnostics.js +168 -0
  265. package/dist/projected-document-action-runtime-profile-diagnostics.js.map +1 -0
  266. package/dist/projected-document-action-runtime-profile.d.ts +64 -0
  267. package/dist/projected-document-action-runtime-profile.d.ts.map +1 -0
  268. package/dist/projected-document-action-runtime-profile.js +33 -0
  269. package/dist/projected-document-action-runtime-profile.js.map +1 -0
  270. package/dist/projected-document-action-status-notice-diagnostics.d.ts +10 -0
  271. package/dist/projected-document-action-status-notice-diagnostics.d.ts.map +1 -0
  272. package/dist/projected-document-action-status-notice-diagnostics.js +64 -0
  273. package/dist/projected-document-action-status-notice-diagnostics.js.map +1 -0
  274. package/dist/projected-document-badge-formatting.d.ts +2 -0
  275. package/dist/projected-document-badge-formatting.d.ts.map +1 -0
  276. package/dist/projected-document-badge-formatting.js +7 -0
  277. package/dist/projected-document-badge-formatting.js.map +1 -0
  278. package/dist/projected-document-editor-model.d.ts +4 -0
  279. package/dist/projected-document-editor-model.d.ts.map +1 -0
  280. package/dist/projected-document-editor-model.js +2 -0
  281. package/dist/projected-document-editor-model.js.map +1 -0
  282. package/dist/projected-document-field-navigation.d.ts +72 -0
  283. package/dist/projected-document-field-navigation.d.ts.map +1 -0
  284. package/dist/projected-document-field-navigation.js +143 -0
  285. package/dist/projected-document-field-navigation.js.map +1 -0
  286. package/dist/projected-document-field-semantics.d.ts +5 -0
  287. package/dist/projected-document-field-semantics.d.ts.map +1 -0
  288. package/dist/projected-document-field-semantics.js +16 -0
  289. package/dist/projected-document-field-semantics.js.map +1 -0
  290. package/dist/projected-document-workspace-data-source-bindings.d.ts +69 -0
  291. package/dist/projected-document-workspace-data-source-bindings.d.ts.map +1 -0
  292. package/dist/projected-document-workspace-data-source-bindings.js +81 -0
  293. package/dist/projected-document-workspace-data-source-bindings.js.map +1 -0
  294. package/dist/projected-document-workspace-notice-diagnostics.d.ts +12 -0
  295. package/dist/projected-document-workspace-notice-diagnostics.d.ts.map +1 -0
  296. package/dist/projected-document-workspace-notice-diagnostics.js +13 -0
  297. package/dist/projected-document-workspace-notice-diagnostics.js.map +1 -0
  298. package/dist/projected-input-form-runtime.d.ts +62 -0
  299. package/dist/projected-input-form-runtime.d.ts.map +1 -0
  300. package/dist/projected-input-form-runtime.js +2 -0
  301. package/dist/projected-input-form-runtime.js.map +1 -0
  302. package/dist/projected-process-task-notice-diagnostics.d.ts +13 -0
  303. package/dist/projected-process-task-notice-diagnostics.d.ts.map +1 -0
  304. package/dist/projected-process-task-notice-diagnostics.js +170 -0
  305. package/dist/projected-process-task-notice-diagnostics.js.map +1 -0
  306. package/dist/projected-query-form-runtime.d.ts +46 -0
  307. package/dist/projected-query-form-runtime.d.ts.map +1 -0
  308. package/dist/projected-query-form-runtime.js +2 -0
  309. package/dist/projected-query-form-runtime.js.map +1 -0
  310. package/dist/prompt-document-preview-projection.d.ts +46 -0
  311. package/dist/prompt-document-preview-projection.d.ts.map +1 -0
  312. package/dist/prompt-document-preview-projection.js +173 -0
  313. package/dist/prompt-document-preview-projection.js.map +1 -0
  314. package/dist/prompt-icons.d.ts +4 -0
  315. package/dist/prompt-icons.d.ts.map +1 -0
  316. package/dist/prompt-icons.js +4 -0
  317. package/dist/prompt-icons.js.map +1 -0
  318. package/dist/prompt-status-message-resolution.d.ts +18 -0
  319. package/dist/prompt-status-message-resolution.d.ts.map +1 -0
  320. package/dist/prompt-status-message-resolution.js +83 -0
  321. package/dist/prompt-status-message-resolution.js.map +1 -0
  322. package/dist/query-form-lowering.d.ts +16 -0
  323. package/dist/query-form-lowering.d.ts.map +1 -0
  324. package/dist/query-form-lowering.js +38 -0
  325. package/dist/query-form-lowering.js.map +1 -0
  326. package/dist/query-form-url-state.d.ts +44 -0
  327. package/dist/query-form-url-state.d.ts.map +1 -0
  328. package/dist/query-form-url-state.js +197 -0
  329. package/dist/query-form-url-state.js.map +1 -0
  330. package/dist/query-offset-page.d.ts +13 -0
  331. package/dist/query-offset-page.d.ts.map +1 -0
  332. package/dist/query-offset-page.js +24 -0
  333. package/dist/query-offset-page.js.map +1 -0
  334. package/dist/relation-query-input-form-target.d.ts +10 -0
  335. package/dist/relation-query-input-form-target.d.ts.map +1 -0
  336. package/dist/relation-query-input-form-target.js +30 -0
  337. package/dist/relation-query-input-form-target.js.map +1 -0
  338. package/dist/scope-selection.d.ts +94 -0
  339. package/dist/scope-selection.d.ts.map +1 -0
  340. package/dist/scope-selection.js +95 -0
  341. package/dist/scope-selection.js.map +1 -0
  342. package/dist/target-bindings.d.ts +39 -0
  343. package/dist/target-bindings.d.ts.map +1 -0
  344. package/dist/target-bindings.js +40 -0
  345. package/dist/target-bindings.js.map +1 -0
  346. package/dist/view-chrome-content-diagnostics.d.ts +8 -0
  347. package/dist/view-chrome-content-diagnostics.d.ts.map +1 -0
  348. package/dist/view-chrome-content-diagnostics.js +84 -0
  349. package/dist/view-chrome-content-diagnostics.js.map +1 -0
  350. package/dist/view-chrome-icons.d.ts +12 -0
  351. package/dist/view-chrome-icons.d.ts.map +1 -0
  352. package/dist/view-chrome-icons.js +50 -0
  353. package/dist/view-chrome-icons.js.map +1 -0
  354. package/dist/view-chrome-slot-renderer-registry.d.ts +19 -0
  355. package/dist/view-chrome-slot-renderer-registry.d.ts.map +1 -0
  356. package/dist/view-chrome-slot-renderer-registry.js +62 -0
  357. package/dist/view-chrome-slot-renderer-registry.js.map +1 -0
  358. package/package.json +53 -0
  359. package/src/api-scope-request.ts +186 -0
  360. package/src/collection-chrome-icons.ts +89 -0
  361. package/src/collection-chrome-runtime.ts +151 -0
  362. package/src/collection-chrome-slot-renderer-registry.ts +227 -0
  363. package/src/collection-selection-state.ts +32 -0
  364. package/src/data-source-projection.ts +2149 -0
  365. package/src/data-source-query-lowering.ts +217 -0
  366. package/src/date-time-filter-model.ts +156 -0
  367. package/src/document-badge-icons.ts +3 -0
  368. package/src/document-module.ts +741 -0
  369. package/src/document-summary-model.ts +1 -0
  370. package/src/document-workspace-chrome-projection.ts +62 -0
  371. package/src/document-workspace-surface-slots.ts +458 -0
  372. package/src/endpoint-executor.ts +122 -0
  373. package/src/field-display-resolution.ts +99 -0
  374. package/src/index.ts +104 -0
  375. package/src/module.ts +281 -0
  376. package/src/navigation-shell-frame-renderer-registry.ts +65 -0
  377. package/src/navigation-shell-layout-runtime.ts +124 -0
  378. package/src/navigation-shell-region-component-registry.ts +242 -0
  379. package/src/navigation-shell-runtime.ts +579 -0
  380. package/src/navigation-shell-slot-renderer-registry.ts +94 -0
  381. package/src/navigation.ts +364 -0
  382. package/src/object-path.ts +99 -0
  383. package/src/page-host-renderer-registry.ts +615 -0
  384. package/src/presentation-action-endpoint-request-projection.ts +223 -0
  385. package/src/presentation-action-endpoint.ts +130 -0
  386. package/src/presentation-action-icons.ts +3 -0
  387. package/src/presentation-action-result-state.ts +189 -0
  388. package/src/presentation-action-runtime-binding.ts +177 -0
  389. package/src/presentation-action-runtime-diagnostics.ts +120 -0
  390. package/src/presentation-action-runtime-labels.ts +57 -0
  391. package/src/presentation-action-runtime-projection.ts +67 -0
  392. package/src/presentation-badge-resolution.ts +138 -0
  393. package/src/presentation-collection-pagination.ts +436 -0
  394. package/src/presentation-content-diagnostics.ts +121 -0
  395. package/src/presentation-content-resolution.ts +46 -0
  396. package/src/presentation-data-source-binding-model.ts +140 -0
  397. package/src/presentation-data-source-runtime.ts +190 -0
  398. package/src/presentation-design-intent-diagnostics.ts +104 -0
  399. package/src/presentation-icon-diagnostics.ts +368 -0
  400. package/src/presentation-icon-registry.ts +181 -0
  401. package/src/presentation-navigation-action-runtime-bindings.ts +54 -0
  402. package/src/presentation-pagination.ts +694 -0
  403. package/src/presentation-projection-coverage.ts +3257 -0
  404. package/src/presentation-projection-diagnostics.ts +71 -0
  405. package/src/presentation-projection-trace.ts +293 -0
  406. package/src/presentation-query-form-state.ts +54 -0
  407. package/src/presentation-renderer-registry.ts +269 -0
  408. package/src/presentation-semantics.ts +1028 -0
  409. package/src/presentation-standards.ts +14 -0
  410. package/src/presentation-test-selectors.ts +312 -0
  411. package/src/presentation-value-formatting.ts +65 -0
  412. package/src/presentation-value-resolution.ts +100 -0
  413. package/src/presentation-view-tree.ts +33 -0
  414. package/src/process-task-model.ts +158 -0
  415. package/src/process-task-projection.ts +398 -0
  416. package/src/projected-action-status-projection.ts +43 -0
  417. package/src/projected-activity-state-model.ts +123 -0
  418. package/src/projected-collection-action-runtime-bindings.ts +76 -0
  419. package/src/projected-collection-action-runtime-diagnostics.ts +289 -0
  420. package/src/projected-collection-runtime.ts +1772 -0
  421. package/src/projected-document-action-diagnostics.ts +196 -0
  422. package/src/projected-document-action-enablements.ts +99 -0
  423. package/src/projected-document-action-projection.ts +464 -0
  424. package/src/projected-document-action-runtime-profile-diagnostics.ts +250 -0
  425. package/src/projected-document-action-runtime-profile.ts +133 -0
  426. package/src/projected-document-action-status-notice-diagnostics.ts +116 -0
  427. package/src/projected-document-badge-formatting.ts +7 -0
  428. package/src/projected-document-editor-model.ts +7 -0
  429. package/src/projected-document-field-navigation.ts +300 -0
  430. package/src/projected-document-field-semantics.ts +27 -0
  431. package/src/projected-document-workspace-data-source-bindings.ts +182 -0
  432. package/src/projected-document-workspace-notice-diagnostics.ts +41 -0
  433. package/src/projected-input-form-runtime.ts +86 -0
  434. package/src/projected-process-task-notice-diagnostics.ts +283 -0
  435. package/src/projected-query-form-runtime.ts +62 -0
  436. package/src/prompt-document-preview-projection.ts +345 -0
  437. package/src/prompt-icons.ts +3 -0
  438. package/src/prompt-status-message-resolution.ts +158 -0
  439. package/src/query-form-lowering.ts +77 -0
  440. package/src/query-form-url-state.ts +346 -0
  441. package/src/query-offset-page.ts +50 -0
  442. package/src/relation-query-input-form-target.ts +61 -0
  443. package/src/scope-selection.ts +201 -0
  444. package/src/target-bindings.ts +123 -0
  445. package/src/view-chrome-content-diagnostics.ts +139 -0
  446. package/src/view-chrome-icons.ts +81 -0
  447. package/src/view-chrome-slot-renderer-registry.ts +128 -0
@@ -0,0 +1,3257 @@
1
+ import type {
2
+ ActionDefinition,
3
+ ActionPlacementDefinition,
4
+ CollectionChromeSlotDefinition,
5
+ CollectionRowActionDefinition,
6
+ CollectionSelectionActionDefinition,
7
+ InputFormDefinition,
8
+ PresentationModuleDefinition,
9
+ PresentationValueDefinition,
10
+ QueryFormDefinition,
11
+ ViewChromeSlotDefinition,
12
+ ViewDefinition,
13
+ } from './module'
14
+ import {
15
+ findPresentationAction,
16
+ findPresentationDataSource,
17
+ findPresentationField,
18
+ findPresentationFlow,
19
+ findPresentationInputForm,
20
+ findPresentationQueryForm,
21
+ findPresentationView,
22
+ } from './module'
23
+ import type {
24
+ PresentationDataSourceTargetInterpretation,
25
+ } from './data-source-projection'
26
+ import type {
27
+ PresentationDataSourceBinding,
28
+ } from './presentation-data-source-binding-model'
29
+ import {
30
+ presentationDataSourceBindingKinds,
31
+ } from './presentation-data-source-binding-model'
32
+ import {
33
+ getCollectionChromeSlotRendererCandidateKeys,
34
+ hasCollectionChromeSlotRendererBinding,
35
+ } from './collection-chrome-slot-renderer-registry'
36
+ import {
37
+ getViewChromeSlotRendererCandidateKeys,
38
+ hasViewChromeSlotRendererBinding,
39
+ isViewChromeSlotKind,
40
+ } from './view-chrome-slot-renderer-registry'
41
+ import {
42
+ createCollectionChromeRuntime,
43
+ isCollectionChromeSlotKind,
44
+ isCollectionChromeSlotPlacement,
45
+ } from './collection-chrome-runtime'
46
+ import {
47
+ getPresentationViewProjectedActions,
48
+ getPresentationViewProjectedFieldRefs,
49
+ type PresentationViewProjectedActionRef,
50
+ } from './presentation-semantics'
51
+ import type {
52
+ PresentationProjectionDiagnostic,
53
+ } from './presentation-projection-diagnostics'
54
+ import {
55
+ collectionChromeProjectionModes,
56
+ isCollectionChromeSlotProjectionMode,
57
+ readCollectionChromeProjectionMode,
58
+ resolveProjectedCollectionChromeSlots,
59
+ } from './projected-collection-runtime'
60
+ import type {
61
+ PresentationProjectionTrace,
62
+ PresentationProjectionTraceView,
63
+ } from './presentation-projection-trace'
64
+ import {
65
+ actionKinds,
66
+ collectionChromeSlotKinds,
67
+ collectionChromeSlotPlacements,
68
+ collectionDetailActivations,
69
+ collectionRowActionKinds,
70
+ collectionSelectionActionParameterSources,
71
+ collectionSelectionModes,
72
+ dataSourceKinds,
73
+ presentationValueKinds,
74
+ pageHostComponentRoles,
75
+ presentationBindingKinds,
76
+ viewKinds,
77
+ viewChromeSlotKinds,
78
+ } from '@cohesivesystems/presentation-contracts'
79
+
80
+ export interface ProjectPresentationTraceCoverageDiagnosticsOptions {
81
+ readonly collectionChromeSlotRendererKeys?: readonly string[]
82
+ readonly collectionSelectionStateRuntimeBound?: boolean
83
+ readonly module: PresentationProjectionCoverageModule | null
84
+ readonly queryFormStateAdapterIds?: readonly string[]
85
+ readonly trace: PresentationProjectionTrace
86
+ readonly viewChromeSlotRendererKeys?: readonly string[]
87
+ }
88
+
89
+ export interface ProjectPresentationDataSourceCoverageDiagnosticsOptions {
90
+ readonly bindings: readonly PresentationDataSourceBinding[]
91
+ readonly dataSourceIds: readonly string[]
92
+ readonly module: Pick<PresentationModuleDefinition, 'DataSources'> | null
93
+ readonly routeParameters?: Readonly<Record<string, string | undefined>>
94
+ readonly sourceId: string
95
+ readonly targetInterpretation?: Pick<PresentationDataSourceTargetInterpretation, 'queryLowering'>
96
+ }
97
+
98
+ export type PresentationProjectionCoverageModule =
99
+ {
100
+ readonly Actions?: readonly PresentationModuleDefinition['Actions'][number][]
101
+ readonly DataSources?: readonly PresentationModuleDefinition['DataSources'][number][]
102
+ readonly Fields?: readonly PresentationModuleDefinition['Fields'][number][]
103
+ readonly Flows?: readonly PresentationModuleDefinition['Flows'][number][]
104
+ readonly InputForms?: readonly PresentationModuleDefinition['InputForms'][number][]
105
+ readonly QueryForms?: readonly PresentationModuleDefinition['QueryForms'][number][]
106
+ readonly Views: readonly PresentationModuleDefinition['Views'][number][]
107
+ }
108
+
109
+ /**
110
+ * Checks active route, page-host, view renderer, action, and input-form coverage
111
+ * using the same semantic trace that the developer toolbar displays.
112
+ */
113
+ export function projectPresentationTraceCoverageDiagnostics({
114
+ collectionChromeSlotRendererKeys = [],
115
+ collectionSelectionStateRuntimeBound = false,
116
+ module,
117
+ queryFormStateAdapterIds = [],
118
+ trace,
119
+ viewChromeSlotRendererKeys = [],
120
+ }: ProjectPresentationTraceCoverageDiagnosticsOptions): readonly PresentationProjectionDiagnostic[] {
121
+ return [
122
+ ...projectRouteCoverageDiagnostics(trace),
123
+ ...projectViewRendererCoverageDiagnostics(trace),
124
+ ...projectViewModelCoverageDiagnostics(
125
+ module,
126
+ trace,
127
+ queryFormStateAdapterIds,
128
+ collectionSelectionStateRuntimeBound,
129
+ collectionChromeSlotRendererKeys,
130
+ viewChromeSlotRendererKeys,
131
+ ),
132
+ ]
133
+ }
134
+
135
+ /** Checks concrete data-source bindings projected for a routed surface. */
136
+ export function projectPresentationDataSourceCoverageDiagnostics({
137
+ bindings,
138
+ dataSourceIds,
139
+ module,
140
+ routeParameters = {},
141
+ sourceId,
142
+ targetInterpretation,
143
+ }: ProjectPresentationDataSourceCoverageDiagnosticsOptions): readonly PresentationProjectionDiagnostic[] {
144
+ const bindingByDataSourceId = new Map(bindings.map((binding) => [binding.dataSourceId, binding]))
145
+
146
+ return Array.from(new Set(dataSourceIds)).flatMap((dataSourceId) => {
147
+ const dataSource = findPresentationDataSource(module, dataSourceId)
148
+ if (!dataSource) {
149
+ return [
150
+ createDiagnostic({
151
+ id: `data-source.${dataSourceId}.missing-definition`,
152
+ message: `Data source '${dataSourceId}' is referenced by the active surface but is not present in the presentation module.`,
153
+ severity: 'error',
154
+ source: sourceId,
155
+ subject: {
156
+ id: dataSourceId,
157
+ kind: 'data-source',
158
+ },
159
+ }),
160
+ ]
161
+ }
162
+
163
+ const binding = bindingByDataSourceId.get(dataSourceId)
164
+ if (!binding) {
165
+ return [
166
+ createDiagnostic({
167
+ category: 'missing-binding',
168
+ id: `data-source.${dataSourceId}.missing-binding`,
169
+ interpretation: {
170
+ status: 'unbound',
171
+ target: 'data-source-binding',
172
+ },
173
+ message: `Data source '${dataSource.Name}' has no projected frontend binding.`,
174
+ severity: 'warning',
175
+ source: sourceId,
176
+ subject: {
177
+ id: dataSource.Id,
178
+ kind: 'data-source',
179
+ name: dataSource.Name,
180
+ },
181
+ suggestedNextStep:
182
+ 'Register a data-source binding factory or add a generic target interpreter for this data source kind.',
183
+ }),
184
+ ]
185
+ }
186
+
187
+ const diagnostics: PresentationProjectionDiagnostic[] = []
188
+ diagnostics.push(
189
+ ...projectDataSourceQueryLoweringTransformDiagnostics({
190
+ dataSource,
191
+ sourceId,
192
+ targetInterpretation,
193
+ }),
194
+ )
195
+
196
+ if (isUnboundDataSourceBinding(binding)) {
197
+ diagnostics.push(createDiagnostic({
198
+ category: 'missing-binding',
199
+ details: { bindingKind: binding.kind },
200
+ id: `data-source.${dataSource.Id}.unbound-frontend-binding`,
201
+ interpretation: {
202
+ status: 'unbound',
203
+ target: 'data-source-binding-factory',
204
+ },
205
+ message: `Data source '${dataSource.Name}' is declared by the IR but has no frontend binding factory.`,
206
+ severity: 'warning',
207
+ source: sourceId,
208
+ subject: {
209
+ id: dataSource.Id,
210
+ kind: 'data-source',
211
+ name: dataSource.Name,
212
+ },
213
+ suggestedNextStep:
214
+ 'Bind this data source through backend-declared query lowering or an explicit frontend adapter.',
215
+ }))
216
+ }
217
+
218
+ const missingRequiredParameters = dataSource.Parameters.filter((parameter) =>
219
+ parameter.IsRequired &&
220
+ !parameter.DefaultValue &&
221
+ !routeParameters[parameter.Name])
222
+ if (
223
+ missingRequiredParameters.length > 0 &&
224
+ binding.kind === presentationDataSourceBindingKinds.tanstackQuery &&
225
+ binding.enabled === false
226
+ ) {
227
+ diagnostics.push(createDiagnostic({
228
+ details: {
229
+ missingParameters: missingRequiredParameters.map((parameter) => parameter.Name),
230
+ },
231
+ id: `data-source.${dataSource.Id}.missing-route-parameters`,
232
+ message:
233
+ `Data source '${dataSource.Name}' is bound but disabled because required ` +
234
+ `route parameter(s) are missing: ${missingRequiredParameters
235
+ .map((parameter) => parameter.Name)
236
+ .join(', ')}.`,
237
+ severity: 'warning',
238
+ source: sourceId,
239
+ subject: {
240
+ id: dataSource.Id,
241
+ kind: 'data-source',
242
+ name: dataSource.Name,
243
+ },
244
+ }))
245
+ }
246
+
247
+ return diagnostics
248
+ })
249
+ }
250
+
251
+ function projectRouteCoverageDiagnostics(
252
+ trace: PresentationProjectionTrace,
253
+ ): readonly PresentationProjectionDiagnostic[] {
254
+ const diagnostics: PresentationProjectionDiagnostic[] = []
255
+ if (!trace.moduleAvailable) {
256
+ diagnostics.push(createDiagnostic({
257
+ id: 'route.missing-module',
258
+ message: 'Presentation module is not available for the active route.',
259
+ severity: 'error',
260
+ source: 'presentation-route-coverage',
261
+ subject: {
262
+ id: trace.pathname,
263
+ kind: 'route',
264
+ },
265
+ }))
266
+ }
267
+
268
+ if (!trace.route) {
269
+ diagnostics.push(createDiagnostic({
270
+ id: `route.${trace.pathname}.unmatched`,
271
+ message: `No presentation route matched '${trace.pathname}'.`,
272
+ severity: 'error',
273
+ source: 'presentation-route-coverage',
274
+ subject: {
275
+ id: trace.pathname,
276
+ kind: 'route',
277
+ },
278
+ }))
279
+ return diagnostics
280
+ }
281
+
282
+ if (!trace.pageHost) {
283
+ diagnostics.push(createDiagnostic({
284
+ id: `route.${trace.route.id}.missing-page-host`,
285
+ message: `Route '${trace.route.id}' resolves to page host '${trace.route.pageHostId}', but the page host is not available.`,
286
+ severity: 'error',
287
+ source: 'presentation-route-coverage',
288
+ subject: {
289
+ id: trace.route.id,
290
+ kind: 'route',
291
+ name: trace.route.label,
292
+ },
293
+ }))
294
+ } else if (!trace.pageHostRenderer?.resolutionSource) {
295
+ diagnostics.push(createDiagnostic({
296
+ category: 'missing-binding',
297
+ details: {
298
+ componentKey: trace.pageHostRenderer?.componentKey ?? null,
299
+ componentRole: trace.pageHostRenderer?.componentRole ?? null,
300
+ rendererKey: trace.pageHostRenderer?.rendererKey ?? null,
301
+ semanticRole: trace.pageHostRenderer?.semanticRole ?? null,
302
+ targetBindingSource: trace.pageHostRenderer?.targetBindingSource ?? null,
303
+ },
304
+ id: `page-host.${trace.pageHost.id}.missing-renderer`,
305
+ interpretation: {
306
+ status: 'unbound',
307
+ target: 'page-host-renderer',
308
+ },
309
+ message: `Page host '${trace.pageHost.id}' has no frontend renderer binding.`,
310
+ severity: 'warning',
311
+ source: 'presentation-route-coverage',
312
+ subject: {
313
+ id: trace.pageHost.id,
314
+ kind: 'page-host',
315
+ },
316
+ suggestedNextStep:
317
+ 'Add a page-host renderer binding or project this host through the generic routed-surface interpreter.',
318
+ }))
319
+ } else if (
320
+ trace.pageHostRenderer?.resolutionSource &&
321
+ isPageHostRendererEscapeHatchSource(trace.pageHostRenderer.resolutionSource)
322
+ ) {
323
+ diagnostics.push(createDiagnostic({
324
+ category: 'escape-hatch',
325
+ details: {
326
+ componentKey: trace.pageHostRenderer.componentKey,
327
+ componentRole: trace.pageHostRenderer.componentRole,
328
+ rendererKey: trace.pageHostRenderer.rendererKey,
329
+ resolutionSource: trace.pageHostRenderer.resolutionSource,
330
+ semanticRole: trace.pageHostRenderer.semanticRole,
331
+ targetBindingSource: trace.pageHostRenderer.targetBindingSource,
332
+ },
333
+ id: `page-host.${trace.pageHost.id}.${trace.pageHostRenderer.resolutionSource}-escape-hatch`,
334
+ interpretation: {
335
+ status: 'escape-hatch',
336
+ target: 'page-host-renderer',
337
+ },
338
+ message:
339
+ `Page host '${trace.pageHost.id}' rendered through ` +
340
+ `'${trace.pageHostRenderer.resolutionSource}' as an escape hatch.`,
341
+ severity: 'warning',
342
+ source: 'presentation-route-coverage',
343
+ subject: {
344
+ id: trace.pageHost.id,
345
+ kind: 'page-host',
346
+ },
347
+ suggestedNextStep:
348
+ 'Move this page host onto ComponentRole, workspace, root-view semantic role, or view kind resolution.',
349
+ }))
350
+ }
351
+
352
+ if (trace.pageHost && trace.pageHostRenderer?.rendererKey) {
353
+ diagnostics.push(createDiagnostic({
354
+ category: 'escape-hatch',
355
+ details: {
356
+ rendererKey: trace.pageHostRenderer.rendererKey,
357
+ resolutionSource: trace.pageHostRenderer.resolutionSource,
358
+ targetBindingSource: trace.pageHostRenderer.targetBindingSource,
359
+ },
360
+ id: `page-host.${trace.pageHost.id}.legacy-renderer-key`,
361
+ interpretation: {
362
+ status: 'escape-hatch',
363
+ target: 'page-host-component-role',
364
+ },
365
+ message:
366
+ `Page host '${trace.pageHost.id}' declares legacy renderer key ` +
367
+ `'${trace.pageHostRenderer.rendererKey}'. Page-host dispatch now uses ` +
368
+ 'component roles and semantic target interpretation.',
369
+ severity: 'warning',
370
+ source: 'presentation-route-coverage',
371
+ subject: {
372
+ id: trace.pageHost.id,
373
+ kind: 'page-host',
374
+ },
375
+ suggestedNextStep:
376
+ 'Replace the renderer key with a PageHostComponent target binding that declares ComponentRole.',
377
+ }))
378
+ }
379
+
380
+ return diagnostics
381
+ }
382
+
383
+ function isPageHostRendererEscapeHatchSource(source: string) {
384
+ return source === 'component-key' ||
385
+ source === 'page-host-id' ||
386
+ source === 'route-id' ||
387
+ source === 'view-id' ||
388
+ source === 'workspace-id'
389
+ }
390
+
391
+ function projectDataSourceQueryLoweringTransformDiagnostics({
392
+ dataSource,
393
+ sourceId,
394
+ targetInterpretation,
395
+ }: {
396
+ readonly dataSource: PresentationModuleDefinition['DataSources'][number]
397
+ readonly sourceId: string
398
+ readonly targetInterpretation?: Pick<PresentationDataSourceTargetInterpretation, 'queryLowering'>
399
+ }): readonly PresentationProjectionDiagnostic[] {
400
+ const usedTransformIds = collectDataSourceQueryLoweringTransformIds(dataSource)
401
+ if (usedTransformIds.length === 0) {
402
+ return []
403
+ }
404
+
405
+ const boundTransformIds = new Set(
406
+ Object.keys(targetInterpretation?.queryLowering?.transformsById ?? {}),
407
+ )
408
+ return usedTransformIds
409
+ .filter((transformId) => !boundTransformIds.has(transformId))
410
+ .map((transformId) =>
411
+ createDiagnostic({
412
+ category: 'unbound',
413
+ details: {
414
+ transformId,
415
+ },
416
+ id: `data-source.${dataSource.Id}.query-transform.${transformId}.unbound`,
417
+ interpretation: {
418
+ status: 'unbound',
419
+ target: 'query-lowering-transform',
420
+ },
421
+ message:
422
+ `Data source '${dataSource.Name}' uses query lowering transform ` +
423
+ `'${transformId}', but the active frontend target interpretation does not bind it.`,
424
+ severity: 'warning',
425
+ source: sourceId,
426
+ subject: {
427
+ id: dataSource.Id,
428
+ kind: 'data-source',
429
+ name: dataSource.Name,
430
+ },
431
+ suggestedNextStep:
432
+ 'Bind this transform id on the data-source target interpretation or replace it with a projected expression.',
433
+ }),
434
+ )
435
+ }
436
+
437
+ function collectDataSourceQueryLoweringTransformIds(
438
+ dataSource: PresentationModuleDefinition['DataSources'][number],
439
+ ) {
440
+ const transformIds = new Set<string>()
441
+ for (const endpointBinding of dataSource.Query?.EndpointBindings ?? []) {
442
+ for (const lowering of endpointBinding.Lowerings) {
443
+ for (const fieldBinding of lowering.FieldBindings) {
444
+ const transform = fieldBinding.Transform?.trim()
445
+ if (transform) {
446
+ transformIds.add(transform)
447
+ }
448
+ }
449
+ }
450
+ }
451
+
452
+ return Array.from(transformIds).sort()
453
+ }
454
+
455
+ function projectViewRendererCoverageDiagnostics(
456
+ trace: PresentationProjectionTrace,
457
+ ): readonly PresentationProjectionDiagnostic[] {
458
+ return trace.views.flatMap((view) => {
459
+ if (isViewRendererCoverageOwnedByDocumentWorkspaceHost(trace, view)) {
460
+ return []
461
+ }
462
+
463
+ if (!view.rendererResolved) {
464
+ return [
465
+ createViewDiagnostic({
466
+ category: 'missing-binding',
467
+ details: {
468
+ componentKey: view.componentKey,
469
+ componentRole: view.componentRole,
470
+ semanticRole: view.semanticRole,
471
+ viewKind: view.kind,
472
+ },
473
+ interpretation: {
474
+ status: 'unbound',
475
+ target: 'view-renderer',
476
+ },
477
+ message: `View '${view.name}' has no frontend renderer binding.`,
478
+ reason: 'missing-renderer',
479
+ severity: 'warning',
480
+ source: 'presentation-view-coverage',
481
+ suggestedNextStep:
482
+ 'Add a semantic renderer binding or expand the standard view interpreter for this view kind.',
483
+ view,
484
+ }),
485
+ ]
486
+ }
487
+
488
+ if (view.componentKey && view.resolutionSource === 'component-key') {
489
+ return [
490
+ createViewDiagnostic({
491
+ category: 'escape-hatch',
492
+ details: {
493
+ componentKey: view.componentKey,
494
+ componentRole: view.componentRole,
495
+ resolutionSource: view.resolutionSource,
496
+ },
497
+ interpretation: {
498
+ status: 'escape-hatch',
499
+ target: 'view-renderer',
500
+ },
501
+ message:
502
+ `View '${view.name}' rendered through concrete component key ` +
503
+ `'${view.componentKey}' as an escape hatch.`,
504
+ reason: 'component-key-escape-hatch',
505
+ severity: 'warning',
506
+ source: 'presentation-view-coverage',
507
+ suggestedNextStep:
508
+ 'Prefer ComponentRole or semantic renderer resolution; keep ComponentKey only for adapter-specific overrides.',
509
+ view,
510
+ }),
511
+ ]
512
+ }
513
+
514
+ return []
515
+ })
516
+ }
517
+
518
+ function isViewRendererCoverageOwnedByDocumentWorkspaceHost(
519
+ trace: PresentationProjectionTrace,
520
+ view: PresentationProjectionTraceView,
521
+ ) {
522
+ const pageHostComponentRole = trace.pageHostRenderer?.componentRole
523
+ const isDocumentWorkspaceHost =
524
+ pageHostComponentRole === pageHostComponentRoles.documentWorkspace ||
525
+ Boolean(trace.pageHost?.documentProfileId)
526
+
527
+ if (!isDocumentWorkspaceHost) {
528
+ return false
529
+ }
530
+
531
+ return (
532
+ view.semanticRole === 'workspace-view' ||
533
+ view.semanticRole === 'document-view' ||
534
+ isTraceViewKind(view, viewKinds.documentWorkspace, 'DocumentWorkspace') ||
535
+ isTraceViewKind(view, viewKinds.panel, 'Panel')
536
+ )
537
+ }
538
+
539
+ function isTraceViewKind(
540
+ view: PresentationProjectionTraceView,
541
+ kind: string | number,
542
+ label: string,
543
+ ) {
544
+ return view.kind === String(kind) || view.kind === label
545
+ }
546
+
547
+ function projectViewModelCoverageDiagnostics(
548
+ module: PresentationProjectionCoverageModule | null,
549
+ trace: PresentationProjectionTrace,
550
+ queryFormStateAdapterIds: readonly string[],
551
+ collectionSelectionStateRuntimeBound: boolean,
552
+ collectionChromeSlotRendererKeys: readonly string[],
553
+ viewChromeSlotRendererKeys: readonly string[],
554
+ ): readonly PresentationProjectionDiagnostic[] {
555
+ if (!module) {
556
+ return []
557
+ }
558
+ const coverageModule = normalizeCoverageModule(module)
559
+
560
+ return trace.views.flatMap((viewTrace) => {
561
+ const view = findPresentationView<ViewDefinition>(coverageModule, viewTrace.id)
562
+ if (!view) {
563
+ return [
564
+ createViewDiagnostic({
565
+ message: `View '${viewTrace.name}' is in the active trace but is not present in the presentation module.`,
566
+ reason: 'missing-definition',
567
+ severity: 'error',
568
+ source: 'presentation-view-coverage',
569
+ view: viewTrace,
570
+ }),
571
+ ]
572
+ }
573
+
574
+ return [
575
+ ...projectViewChromeCoverageDiagnostics(
576
+ coverageModule,
577
+ view,
578
+ viewTrace,
579
+ viewChromeSlotRendererKeys,
580
+ ),
581
+ ...projectCollectionCoverageDiagnostics(
582
+ coverageModule,
583
+ view,
584
+ viewTrace,
585
+ collectionSelectionStateRuntimeBound,
586
+ collectionChromeSlotRendererKeys,
587
+ ),
588
+ ...projectFieldCoverageDiagnostics(coverageModule, view, viewTrace),
589
+ ...projectActionCoverageDiagnostics(coverageModule, view, viewTrace),
590
+ ...projectInputFormCoverageDiagnostics(
591
+ coverageModule,
592
+ view,
593
+ viewTrace,
594
+ queryFormStateAdapterIds,
595
+ ),
596
+ ]
597
+ })
598
+ }
599
+
600
+ function projectFieldCoverageDiagnostics(
601
+ module: PresentationModuleDefinition,
602
+ view: ViewDefinition,
603
+ viewTrace: PresentationProjectionTraceView,
604
+ ): readonly PresentationProjectionDiagnostic[] {
605
+ return resolveViewProjectedFieldRefs(view).flatMap(({ fieldId, source }) => {
606
+ const field = findPresentationField(module, fieldId)
607
+ return field
608
+ ? []
609
+ : [
610
+ createViewDiagnostic({
611
+ details: { fieldId, source },
612
+ message: `View '${view.Name}' references field '${fieldId}' from ${source}, but the field is not present in the presentation module.`,
613
+ reason: `missing-field.${source}.${fieldId}`,
614
+ severity: 'error',
615
+ source: 'presentation-view-coverage',
616
+ view: viewTrace,
617
+ }),
618
+ ]
619
+ })
620
+ }
621
+
622
+ function projectViewChromeCoverageDiagnostics(
623
+ module: PresentationModuleDefinition,
624
+ view: ViewDefinition,
625
+ viewTrace: PresentationProjectionTraceView,
626
+ viewChromeSlotRendererKeys: readonly string[],
627
+ ): readonly PresentationProjectionDiagnostic[] {
628
+ return [
629
+ ...(view.Chrome?.Slots ?? []).flatMap((slot) => [
630
+ ...projectViewChromeSlotReferenceDiagnostics(module, view, viewTrace, slot),
631
+ ...projectViewChromeSlotInterpretationDiagnostics(
632
+ view,
633
+ viewTrace,
634
+ slot,
635
+ viewChromeSlotRendererKeys,
636
+ ),
637
+ ]),
638
+ ...projectPromptStatusChromeCoverageDiagnostics(view, viewTrace),
639
+ ...projectPromptStatusMessageContentDiagnostics(view, viewTrace),
640
+ ...projectPromptDocumentPreviewContentDiagnostics(view, viewTrace),
641
+ ...projectPromptDocumentPreviewBadgeContentDiagnostics(module, view, viewTrace),
642
+ ]
643
+ }
644
+
645
+ function projectPromptStatusChromeCoverageDiagnostics(
646
+ view: ViewDefinition,
647
+ viewTrace: PresentationProjectionTraceView,
648
+ ): readonly PresentationProjectionDiagnostic[] {
649
+ const statusMessages = view.PromptStatusMessages ?? []
650
+ if (statusMessages.length === 0) {
651
+ return []
652
+ }
653
+
654
+ const statusSlotRegionIds = new Set(
655
+ (view.Chrome?.Slots ?? [])
656
+ .filter((slot) => isViewChromeSlotKind(slot, viewChromeSlotKinds.status))
657
+ .map((slot) => slot.StateId ?? slot.Id),
658
+ )
659
+ const messageRegionIds = Array.from(new Set(
660
+ statusMessages.map((message) => message.Region).filter(Boolean),
661
+ ))
662
+
663
+ return messageRegionIds
664
+ .filter((regionId) => !statusSlotRegionIds.has(regionId))
665
+ .map((regionId) =>
666
+ createViewDiagnostic({
667
+ category: 'missing-binding',
668
+ details: { promptStatusRegionId: regionId },
669
+ interpretation: {
670
+ status: 'unbound',
671
+ target: 'view-chrome-status-slot',
672
+ },
673
+ message:
674
+ `Prompt view '${view.Name}' declares status messages for region ` +
675
+ `'${regionId}', but no Status chrome slot consumes that region.`,
676
+ reason: `unbound-prompt-status-region.${regionId}`,
677
+ severity: 'warning',
678
+ source: 'presentation-view-coverage',
679
+ suggestedNextStep:
680
+ 'Declare a View.Chrome Status slot whose id or state id matches this prompt status region.',
681
+ view: viewTrace,
682
+ }))
683
+ }
684
+
685
+ function projectPromptStatusMessageContentDiagnostics(
686
+ view: ViewDefinition,
687
+ viewTrace: PresentationProjectionTraceView,
688
+ ): readonly PresentationProjectionDiagnostic[] {
689
+ return (view.PromptStatusMessages ?? []).flatMap((message) => {
690
+ const hasLegacyMessage = Boolean(message.Message)
691
+ const hasLegacyTemplate = Boolean(message.MessageTemplate)
692
+ if (!hasLegacyMessage && !hasLegacyTemplate) {
693
+ return []
694
+ }
695
+
696
+ const content = message.Content
697
+ if (!content) {
698
+ return [
699
+ createViewDiagnostic({
700
+ category: 'incomplete-projection',
701
+ details: {
702
+ promptStatusMessageId: message.Id,
703
+ promptStatusMessageName: message.Name,
704
+ region: message.Region,
705
+ },
706
+ interpretation: {
707
+ status: 'locally-interpreted',
708
+ target: 'prompt-status-message-content',
709
+ },
710
+ message:
711
+ `Prompt status message '${message.Name}' on view '${view.Name}' still declares ` +
712
+ 'legacy message fields without Content; the frontend will use legacy text fallback semantics.',
713
+ reason: `legacy-prompt-status-content.${message.Id}`,
714
+ severity: 'warning',
715
+ source: 'presentation-view-coverage',
716
+ suggestedNextStep:
717
+ 'Declare PromptStatusMessage.Content with PresentationContentDefinition and remove legacy Message/MessageTemplate fallback fields.',
718
+ view: viewTrace,
719
+ }),
720
+ ]
721
+ }
722
+
723
+ const hasContentLabel = Boolean(
724
+ content.Description ??
725
+ content.DescriptionTemplate ??
726
+ content.Title ??
727
+ content.Subtitle,
728
+ )
729
+ return hasContentLabel
730
+ ? []
731
+ : [
732
+ createViewDiagnostic({
733
+ category: 'incomplete-projection',
734
+ details: {
735
+ promptStatusMessageId: message.Id,
736
+ promptStatusMessageName: message.Name,
737
+ region: message.Region,
738
+ },
739
+ interpretation: {
740
+ status: 'locally-interpreted',
741
+ target: 'prompt-status-message-content',
742
+ },
743
+ message:
744
+ `Prompt status message '${message.Name}' on view '${view.Name}' declares Content ` +
745
+ 'without label content; the frontend will use legacy text fallback semantics.',
746
+ reason: `legacy-prompt-status-content-label.${message.Id}`,
747
+ severity: 'warning',
748
+ source: 'presentation-view-coverage',
749
+ suggestedNextStep:
750
+ 'Declare PromptStatusMessage.Content title, subtitle, description, or description template.',
751
+ view: viewTrace,
752
+ }),
753
+ ]
754
+ })
755
+ }
756
+
757
+ function projectPromptDocumentPreviewContentDiagnostics(
758
+ view: ViewDefinition,
759
+ viewTrace: PresentationProjectionTraceView,
760
+ ): readonly PresentationProjectionDiagnostic[] {
761
+ const preview = view.PromptDocumentPreview
762
+ if (!preview) {
763
+ return []
764
+ }
765
+
766
+ if (!preview.Content) {
767
+ return [
768
+ createViewDiagnostic({
769
+ category: 'incomplete-projection',
770
+ details: {
771
+ previewViewId: preview.PreviewViewId,
772
+ workspacePageViewId: preview.WorkspacePageViewId,
773
+ },
774
+ interpretation: {
775
+ status: 'locally-interpreted',
776
+ target: 'prompt-document-preview-content',
777
+ },
778
+ message:
779
+ `Prompt document preview on view '${view.Name}' still declares legacy title/document text ` +
780
+ 'without Content; the frontend will use legacy preview content fallback semantics.',
781
+ reason: 'legacy-prompt-document-preview-content',
782
+ severity: 'warning',
783
+ source: 'presentation-view-coverage',
784
+ suggestedNextStep:
785
+ 'Declare PromptDocumentPreview.Content with PresentationContentDefinition and remove legacy Title/DocumentText fallback fields.',
786
+ view: viewTrace,
787
+ }),
788
+ ]
789
+ }
790
+
791
+ const diagnostics: PresentationProjectionDiagnostic[] = []
792
+ if (!preview.Content.Title) {
793
+ diagnostics.push(
794
+ createViewDiagnostic({
795
+ category: 'incomplete-projection',
796
+ details: {
797
+ previewViewId: preview.PreviewViewId,
798
+ workspacePageViewId: preview.WorkspacePageViewId,
799
+ },
800
+ interpretation: {
801
+ status: 'locally-interpreted',
802
+ target: 'prompt-document-preview-content',
803
+ },
804
+ message:
805
+ `Prompt document preview on view '${view.Name}' declares Content without title content; ` +
806
+ 'the frontend will use legacy preview title fallback semantics.',
807
+ reason: 'legacy-prompt-document-preview-title',
808
+ severity: 'warning',
809
+ source: 'presentation-view-coverage',
810
+ suggestedNextStep:
811
+ 'Declare PromptDocumentPreview.Content.Title.',
812
+ view: viewTrace,
813
+ }),
814
+ )
815
+ }
816
+
817
+ const hasDocumentTextContent = Boolean(
818
+ preview.Content.Description ??
819
+ preview.Content.DescriptionTemplate ??
820
+ preview.Content.Subtitle,
821
+ )
822
+ if (!hasDocumentTextContent) {
823
+ diagnostics.push(
824
+ createViewDiagnostic({
825
+ category: 'incomplete-projection',
826
+ details: {
827
+ previewViewId: preview.PreviewViewId,
828
+ workspacePageViewId: preview.WorkspacePageViewId,
829
+ },
830
+ interpretation: {
831
+ status: 'locally-interpreted',
832
+ target: 'prompt-document-preview-content',
833
+ },
834
+ message:
835
+ `Prompt document preview on view '${view.Name}' declares Content without document text content; ` +
836
+ 'the frontend will use legacy preview document text fallback semantics.',
837
+ reason: 'legacy-prompt-document-preview-text',
838
+ severity: 'warning',
839
+ source: 'presentation-view-coverage',
840
+ suggestedNextStep:
841
+ 'Declare PromptDocumentPreview.Content.Description, DescriptionTemplate, or Subtitle.',
842
+ view: viewTrace,
843
+ }),
844
+ )
845
+ }
846
+
847
+ return diagnostics
848
+ }
849
+
850
+ function projectPromptDocumentPreviewBadgeContentDiagnostics(
851
+ module: PresentationModuleDefinition,
852
+ view: ViewDefinition,
853
+ viewTrace: PresentationProjectionTraceView,
854
+ ): readonly PresentationProjectionDiagnostic[] {
855
+ const preview = view.PromptDocumentPreview
856
+ if (!preview) {
857
+ return []
858
+ }
859
+
860
+ return preview.Badges.flatMap((badge) => {
861
+ const diagnostics: PresentationProjectionDiagnostic[] = []
862
+ const hasLegacyValue = Boolean(badge.Value || badge.ValueTemplate)
863
+ const hasContentLabel = Boolean(
864
+ badge.Content?.Description ??
865
+ badge.Content?.DescriptionTemplate ??
866
+ badge.Content?.Title ??
867
+ badge.Content?.Subtitle,
868
+ )
869
+
870
+ if (!badge.Content) {
871
+ diagnostics.push(createViewDiagnostic({
872
+ category: 'incomplete-projection',
873
+ details: {
874
+ badgeId: badge.Id,
875
+ badgeName: badge.Name,
876
+ previewViewId: preview.PreviewViewId,
877
+ workspacePageViewId: preview.WorkspacePageViewId,
878
+ },
879
+ interpretation: {
880
+ status: 'locally-interpreted',
881
+ target: 'presentation-badge-content',
882
+ },
883
+ message:
884
+ `Prompt document preview badge '${badge.Name}' on view '${view.Name}' does not ` +
885
+ 'declare Content; the frontend will use badge value or name fallback semantics.',
886
+ reason: `legacy-prompt-document-preview-badge-content.${badge.Id}`,
887
+ severity: 'warning',
888
+ source: 'presentation-view-coverage',
889
+ suggestedNextStep:
890
+ 'Declare PresentationBadge.Content with title, subtitle, description, or description template.',
891
+ view: viewTrace,
892
+ }))
893
+ } else if (hasLegacyValue && !hasContentLabel) {
894
+ diagnostics.push(createViewDiagnostic({
895
+ category: 'incomplete-projection',
896
+ details: {
897
+ badgeId: badge.Id,
898
+ badgeName: badge.Name,
899
+ previewViewId: preview.PreviewViewId,
900
+ workspacePageViewId: preview.WorkspacePageViewId,
901
+ },
902
+ interpretation: {
903
+ status: 'locally-interpreted',
904
+ target: 'presentation-badge-content',
905
+ },
906
+ message:
907
+ `Prompt document preview badge '${badge.Name}' on view '${view.Name}' declares ` +
908
+ 'Content without label content; the frontend will use badge value fallback semantics.',
909
+ reason: `legacy-prompt-document-preview-badge-content-label.${badge.Id}`,
910
+ severity: 'warning',
911
+ source: 'presentation-view-coverage',
912
+ suggestedNextStep:
913
+ 'Declare PresentationBadge.Content title, subtitle, description, or description template.',
914
+ view: viewTrace,
915
+ }))
916
+ }
917
+
918
+ if (badge.FieldId && !findPresentationField(module, badge.FieldId)) {
919
+ diagnostics.push(createViewDiagnostic({
920
+ category: 'missing-definition',
921
+ details: {
922
+ badgeId: badge.Id,
923
+ badgeName: badge.Name,
924
+ fieldId: badge.FieldId,
925
+ previewViewId: preview.PreviewViewId,
926
+ workspacePageViewId: preview.WorkspacePageViewId,
927
+ },
928
+ interpretation: {
929
+ status: 'unbound',
930
+ target: 'presentation-field',
931
+ },
932
+ message:
933
+ `Prompt document preview badge '${badge.Name}' on view '${view.Name}' references ` +
934
+ `field '${badge.FieldId}', but the field is not present in the presentation module.`,
935
+ reason: `missing-prompt-document-preview-badge-field.${badge.Id}.${badge.FieldId}`,
936
+ severity: 'error',
937
+ source: 'presentation-view-coverage',
938
+ view: viewTrace,
939
+ }))
940
+ }
941
+
942
+ if (badge.Tone && !isSupportedPresentationBadgeTone(badge.Tone)) {
943
+ diagnostics.push(createViewDiagnostic({
944
+ category: 'unsupported',
945
+ details: {
946
+ badgeId: badge.Id,
947
+ badgeName: badge.Name,
948
+ supportedTones: presentationBadgeToneValues,
949
+ tone: badge.Tone,
950
+ },
951
+ interpretation: {
952
+ status: 'unsupported',
953
+ target: 'presentation-badge-tone',
954
+ },
955
+ message:
956
+ `Prompt document preview badge '${badge.Name}' on view '${view.Name}' declares ` +
957
+ `tone '${badge.Tone}', but the active frontend badge design interpreter does not ` +
958
+ 'declare that tone.',
959
+ reason: `unsupported-prompt-document-preview-badge-tone.${badge.Id}.${badge.Tone}`,
960
+ severity: 'warning',
961
+ source: 'presentation-view-coverage',
962
+ suggestedNextStep:
963
+ 'Use a standard presentation badge tone or extend the design-system badge tone interpreter.',
964
+ view: viewTrace,
965
+ }))
966
+ }
967
+
968
+ return diagnostics
969
+ })
970
+ }
971
+
972
+ const presentationBadgeToneValues = [
973
+ 'accent',
974
+ 'danger',
975
+ 'info',
976
+ 'muted',
977
+ 'neutral',
978
+ 'success',
979
+ 'warning',
980
+ ] as const
981
+
982
+ const presentationBadgeTones = new Set<string>(presentationBadgeToneValues)
983
+
984
+ function isSupportedPresentationBadgeTone(tone: string) {
985
+ return presentationBadgeTones.has(tone.trim().toLowerCase())
986
+ }
987
+
988
+ function projectViewChromeSlotReferenceDiagnostics(
989
+ module: PresentationModuleDefinition,
990
+ view: ViewDefinition,
991
+ viewTrace: PresentationProjectionTraceView,
992
+ slot: ViewChromeSlotDefinition,
993
+ ): readonly PresentationProjectionDiagnostic[] {
994
+ const diagnostics: PresentationProjectionDiagnostic[] = []
995
+
996
+ if (
997
+ isViewChromeSlotKind(slot, viewChromeSlotKinds.badgeStrip) &&
998
+ slot.FieldIds.length > 0 &&
999
+ (slot.Badges?.length ?? 0) === 0
1000
+ ) {
1001
+ diagnostics.push(createViewChromeSlotDiagnostic({
1002
+ category: 'incomplete-projection',
1003
+ details: {
1004
+ fieldIds: slot.FieldIds,
1005
+ },
1006
+ interpretation: {
1007
+ status: 'locally-interpreted',
1008
+ target: 'view-chrome-badge-strip-field-ids',
1009
+ },
1010
+ message:
1011
+ `Badge strip slot '${slot.Id}' on '${view.Name}' still declares FieldIds without ` +
1012
+ 'first-class badge definitions; the frontend will synthesize field-backed badges.',
1013
+ reason: `legacy-badge-strip-field-ids.${slot.Id}`,
1014
+ severity: 'warning',
1015
+ slot,
1016
+ suggestedNextStep:
1017
+ 'Declare ViewChromeSlot.Badges with PresentationBadgeDefinition entries and remove BadgeStrip FieldIds.',
1018
+ view: viewTrace,
1019
+ }))
1020
+ }
1021
+
1022
+ for (const fieldId of slot.FieldIds) {
1023
+ if (!findPresentationField(module, fieldId)) {
1024
+ diagnostics.push(createViewChromeSlotDiagnostic({
1025
+ message:
1026
+ `View chrome slot '${slot.Id}' on '${view.Name}' references ` +
1027
+ `field '${fieldId}', but the field is not present in the presentation module.`,
1028
+ reason: `missing-view-chrome-slot-field.${slot.Id}.${fieldId}`,
1029
+ severity: 'error',
1030
+ slot,
1031
+ view: viewTrace,
1032
+ }))
1033
+ }
1034
+ }
1035
+
1036
+ for (const badge of slot.Badges ?? []) {
1037
+ if (badge.FieldId && !findPresentationField(module, badge.FieldId)) {
1038
+ diagnostics.push(createViewChromeSlotDiagnostic({
1039
+ category: 'missing-definition',
1040
+ details: {
1041
+ badgeId: badge.Id,
1042
+ badgeName: badge.Name,
1043
+ fieldId: badge.FieldId,
1044
+ },
1045
+ interpretation: {
1046
+ status: 'unbound',
1047
+ target: 'presentation-field',
1048
+ },
1049
+ message:
1050
+ `View chrome slot '${slot.Id}' on '${view.Name}' declares badge ` +
1051
+ `'${badge.Name}' for field '${badge.FieldId}', but the field is not present ` +
1052
+ 'in the presentation module.',
1053
+ reason: `missing-view-chrome-slot-badge-field.${slot.Id}.${badge.Id}.${badge.FieldId}`,
1054
+ severity: 'error',
1055
+ slot,
1056
+ view: viewTrace,
1057
+ }))
1058
+ }
1059
+
1060
+ if (badge.Tone && !isSupportedPresentationBadgeTone(badge.Tone)) {
1061
+ diagnostics.push(createViewChromeSlotDiagnostic({
1062
+ category: 'unsupported',
1063
+ details: {
1064
+ badgeId: badge.Id,
1065
+ badgeName: badge.Name,
1066
+ supportedTones: presentationBadgeToneValues,
1067
+ tone: badge.Tone,
1068
+ },
1069
+ interpretation: {
1070
+ status: 'unsupported',
1071
+ target: 'presentation-badge-tone',
1072
+ },
1073
+ message:
1074
+ `View chrome slot '${slot.Id}' on '${view.Name}' declares badge ` +
1075
+ `'${badge.Name}' with tone '${badge.Tone}', but the active frontend badge ` +
1076
+ 'design interpreter does not declare that tone.',
1077
+ reason: `unsupported-view-chrome-slot-badge-tone.${slot.Id}.${badge.Id}.${badge.Tone}`,
1078
+ severity: 'warning',
1079
+ slot,
1080
+ suggestedNextStep:
1081
+ 'Use a standard presentation badge tone or extend the design-system badge tone interpreter.',
1082
+ view: viewTrace,
1083
+ }))
1084
+ }
1085
+ }
1086
+
1087
+ for (const actionPlacement of slot.Actions) {
1088
+ if (!findPresentationAction(module, actionPlacement.ActionId)) {
1089
+ diagnostics.push(createViewChromeSlotDiagnostic({
1090
+ message:
1091
+ `View chrome slot '${slot.Id}' on '${view.Name}' references ` +
1092
+ `action '${actionPlacement.ActionId}', but the action is not present in the presentation module.`,
1093
+ reason: `missing-view-chrome-slot-action.${slot.Id}.${actionPlacement.ActionId}`,
1094
+ severity: 'error',
1095
+ slot,
1096
+ view: viewTrace,
1097
+ }))
1098
+ }
1099
+ }
1100
+
1101
+ for (const viewId of slot.ViewIds) {
1102
+ if (!findPresentationView(module, viewId)) {
1103
+ diagnostics.push(createViewChromeSlotDiagnostic({
1104
+ message:
1105
+ `View chrome slot '${slot.Id}' on '${view.Name}' references ` +
1106
+ `view '${viewId}', but the view is not present in the presentation module.`,
1107
+ reason: `missing-view-chrome-slot-view.${slot.Id}.${viewId}`,
1108
+ severity: 'error',
1109
+ slot,
1110
+ view: viewTrace,
1111
+ }))
1112
+ }
1113
+ }
1114
+
1115
+ if (slot.StateId && !view.State.some((state) => state.Id === slot.StateId)) {
1116
+ diagnostics.push(createViewChromeSlotDiagnostic({
1117
+ message:
1118
+ `View chrome slot '${slot.Id}' on '${view.Name}' references ` +
1119
+ `state '${slot.StateId}', but the state is not declared by the view.`,
1120
+ reason: `missing-view-chrome-slot-state.${slot.Id}.${slot.StateId}`,
1121
+ severity: 'error',
1122
+ slot,
1123
+ view: viewTrace,
1124
+ }))
1125
+ }
1126
+
1127
+ return diagnostics
1128
+ }
1129
+
1130
+ function projectViewChromeSlotInterpretationDiagnostics(
1131
+ view: ViewDefinition,
1132
+ viewTrace: PresentationProjectionTraceView,
1133
+ slot: ViewChromeSlotDefinition,
1134
+ viewChromeSlotRendererKeys: readonly string[],
1135
+ ): readonly PresentationProjectionDiagnostic[] {
1136
+ const candidateRendererKeys = getViewChromeSlotRendererCandidateKeys(slot)
1137
+ if (hasViewChromeSlotRendererBinding(viewChromeSlotRendererKeys, slot)) {
1138
+ return [
1139
+ createViewChromeSlotDiagnostic({
1140
+ category: 'local-interpretation',
1141
+ details: {
1142
+ candidateRendererKeys,
1143
+ slotId: slot.Id,
1144
+ slotKind: slot.Kind,
1145
+ slotPlacement: slot.Placement,
1146
+ },
1147
+ interpretation: {
1148
+ status: 'bound',
1149
+ target: 'view-chrome-slot-renderer-registry',
1150
+ },
1151
+ message:
1152
+ `View chrome slot '${slot.Id}' on '${view.Name}' is bound ` +
1153
+ 'by the view chrome slot renderer registry.',
1154
+ reason: `bound-view-chrome-slot-renderer.${slot.Id}`,
1155
+ severity: 'info',
1156
+ slot,
1157
+ view: viewTrace,
1158
+ }),
1159
+ ]
1160
+ }
1161
+
1162
+ if (isViewChromeSlotKind(slot, viewChromeSlotKinds.custom)) {
1163
+ return [
1164
+ createViewChromeSlotDiagnostic({
1165
+ category: 'missing-binding',
1166
+ details: {
1167
+ candidateRendererKeys,
1168
+ slotId: slot.Id,
1169
+ slotKind: slot.Kind,
1170
+ slotPlacement: slot.Placement,
1171
+ },
1172
+ interpretation: {
1173
+ status: 'unbound',
1174
+ target: 'view-chrome-slot-renderer-registry',
1175
+ },
1176
+ message:
1177
+ `View chrome slot '${slot.Id}' on '${view.Name}' is Custom, ` +
1178
+ 'but no frontend view chrome-slot interpreter is registered for it.',
1179
+ reason: `unbound-view-chrome-custom-slot.${slot.Id}`,
1180
+ severity: 'warning',
1181
+ slot,
1182
+ suggestedNextStep:
1183
+ 'Register a view chrome-slot renderer for this custom slot or model it with a first-class slot kind.',
1184
+ view: viewTrace,
1185
+ }),
1186
+ ]
1187
+ }
1188
+
1189
+ if (isViewChromeSlotRendererExpected(slot)) {
1190
+ return [
1191
+ createViewChromeSlotDiagnostic({
1192
+ category: 'missing-binding',
1193
+ details: {
1194
+ candidateRendererKeys,
1195
+ slotId: slot.Id,
1196
+ slotKind: slot.Kind,
1197
+ slotPlacement: slot.Placement,
1198
+ },
1199
+ interpretation: {
1200
+ status: 'unbound',
1201
+ target: 'view-chrome-slot-renderer-registry',
1202
+ },
1203
+ message:
1204
+ `View chrome slot '${slot.Id}' on '${view.Name}' has no ` +
1205
+ 'registered frontend renderer for its kind and placement.',
1206
+ reason: `unbound-view-chrome-slot-renderer.${slot.Id}`,
1207
+ severity: 'warning',
1208
+ slot,
1209
+ suggestedNextStep:
1210
+ 'Register a view chrome-slot renderer or add a generic interpreter for this slot kind.',
1211
+ view: viewTrace,
1212
+ }),
1213
+ ]
1214
+ }
1215
+
1216
+ return []
1217
+ }
1218
+
1219
+ function isViewChromeSlotRendererExpected(
1220
+ slot: ViewChromeSlotDefinition,
1221
+ ) {
1222
+ return isViewChromeSlotKind(slot, viewChromeSlotKinds.actions) ||
1223
+ isViewChromeSlotKind(slot, viewChromeSlotKinds.badgeStrip) ||
1224
+ isViewChromeSlotKind(slot, viewChromeSlotKinds.metricStrip) ||
1225
+ isViewChromeSlotKind(slot, viewChromeSlotKinds.viewSwitch) ||
1226
+ isViewChromeSlotKind(slot, viewChromeSlotKinds.layoutSwitch) ||
1227
+ isViewChromeSlotKind(slot, viewChromeSlotKinds.headingTrailing) ||
1228
+ isViewChromeSlotKind(slot, viewChromeSlotKinds.status)
1229
+ }
1230
+
1231
+ function createViewChromeSlotDiagnostic({
1232
+ category,
1233
+ details,
1234
+ interpretation,
1235
+ message,
1236
+ reason,
1237
+ severity,
1238
+ slot,
1239
+ suggestedNextStep,
1240
+ view,
1241
+ }: {
1242
+ readonly category?: PresentationProjectionDiagnostic['category']
1243
+ readonly details?: Readonly<Record<string, unknown>>
1244
+ readonly interpretation?: PresentationProjectionDiagnostic['interpretation']
1245
+ readonly message: string
1246
+ readonly reason: string
1247
+ readonly severity: PresentationProjectionDiagnostic['severity']
1248
+ readonly slot: ViewChromeSlotDefinition
1249
+ readonly suggestedNextStep?: string
1250
+ readonly view: PresentationProjectionTraceView
1251
+ }) {
1252
+ return createViewDiagnostic({
1253
+ category,
1254
+ details: {
1255
+ viewChromeSlotId: slot.Id,
1256
+ viewChromeSlotKind: slot.Kind,
1257
+ viewChromeSlotPlacement: slot.Placement,
1258
+ ...details,
1259
+ },
1260
+ interpretation,
1261
+ message,
1262
+ reason,
1263
+ severity,
1264
+ source: 'presentation-view-chrome-coverage',
1265
+ suggestedNextStep,
1266
+ view,
1267
+ })
1268
+ }
1269
+
1270
+ function projectCollectionCoverageDiagnostics(
1271
+ module: PresentationModuleDefinition,
1272
+ view: ViewDefinition,
1273
+ viewTrace: PresentationProjectionTraceView,
1274
+ collectionSelectionStateRuntimeBound: boolean,
1275
+ collectionChromeSlotRendererKeys: readonly string[],
1276
+ ): readonly PresentationProjectionDiagnostic[] {
1277
+ const collection = view.Collection
1278
+ if (!collection) {
1279
+ return []
1280
+ }
1281
+
1282
+ const diagnostics: PresentationProjectionDiagnostic[] = []
1283
+ const bodySlot = resolveCollectionChromeBodySlot(collection)
1284
+ const detailSlot = resolveCollectionChromeDetailSlot(collection)
1285
+ const rowIdentityPath = bodySlot?.RowIdentityPath ?? null
1286
+ const selectionMode = bodySlot?.SelectionMode ?? collectionSelectionModes.none
1287
+ const selectionStateId = bodySlot?.StateId ?? null
1288
+ const rowActions = resolveCollectionChromeRowActions(collection)
1289
+ const selectionActions = resolveCollectionChromeSelectionActions(collection)
1290
+ const projectedActions = getPresentationViewProjectedActions(view)
1291
+
1292
+ diagnostics.push(
1293
+ ...projectCollectionOuterViewDuplicationDiagnostics(view, viewTrace),
1294
+ ...projectProjectedCollectionActionContextDiagnostics({
1295
+ bodySlot,
1296
+ collectionChromeSlotRendererKeys,
1297
+ projectedActions,
1298
+ selectionMode,
1299
+ selectionStateId,
1300
+ view,
1301
+ viewTrace,
1302
+ }),
1303
+ ...projectCollectionChromeCoverageDiagnostics(
1304
+ module,
1305
+ view,
1306
+ viewTrace,
1307
+ collectionChromeSlotRendererKeys,
1308
+ ),
1309
+ )
1310
+
1311
+ if (bodySlot || detailSlot) {
1312
+ const isSelectionEnabled = isCollectionSelectionEnabled(selectionMode)
1313
+ if (isSelectionEnabled) {
1314
+ if (!rowIdentityPath) {
1315
+ diagnostics.push(createViewDiagnostic({
1316
+ details: {
1317
+ bodySlotId: bodySlot?.Id ?? null,
1318
+ selectionMode,
1319
+ selectionStateId,
1320
+ },
1321
+ message:
1322
+ `Collection view '${view.Name}' enables row selection, ` +
1323
+ 'but its Body chrome slot does not declare a row identity path.',
1324
+ reason: `missing-collection-row-identity.${view.Id}`,
1325
+ severity: 'error',
1326
+ source: 'presentation-collection-coverage',
1327
+ view: viewTrace,
1328
+ }))
1329
+ }
1330
+
1331
+ if (!selectionStateId) {
1332
+ diagnostics.push(createViewDiagnostic({
1333
+ details: {
1334
+ bodySlotId: bodySlot?.Id ?? null,
1335
+ selectionMode,
1336
+ },
1337
+ interpretation: {
1338
+ status: 'unbound',
1339
+ target: 'collection-selection-state',
1340
+ },
1341
+ message:
1342
+ `Collection view '${view.Name}' enables row selection, ` +
1343
+ 'but its Body chrome slot does not declare a selection state id.',
1344
+ reason: `missing-collection-selection-state-id.${view.Id}`,
1345
+ severity: 'warning',
1346
+ source: 'presentation-collection-coverage',
1347
+ suggestedNextStep:
1348
+ 'Declare StateId on the collection Body chrome slot so frontend targets can bind the selected row identities.',
1349
+ view: viewTrace,
1350
+ }))
1351
+ } else if (!collectionSelectionStateRuntimeBound) {
1352
+ diagnostics.push(createViewDiagnostic({
1353
+ category: 'missing-binding',
1354
+ details: {
1355
+ bodySlotId: bodySlot?.Id ?? null,
1356
+ selectionMode,
1357
+ selectionStateId,
1358
+ },
1359
+ interpretation: {
1360
+ status: 'unbound',
1361
+ target: 'collection-selection-state-runtime',
1362
+ },
1363
+ message:
1364
+ `Collection view '${view.Name}' declares selection state ` +
1365
+ `'${selectionStateId}', but no frontend selection-state runtime is bound.`,
1366
+ reason: `missing-collection-selection-state-runtime.${selectionStateId}`,
1367
+ severity: 'warning',
1368
+ source: 'presentation-collection-coverage',
1369
+ suggestedNextStep:
1370
+ 'Install a generic collection selection-state runtime or bind this state id through a target adapter.',
1371
+ view: viewTrace,
1372
+ }))
1373
+ }
1374
+ }
1375
+
1376
+ const activatedRowActionId = bodySlot?.ActivatedRowActionId ?? null
1377
+ const activatedRowAction = activatedRowActionId
1378
+ ? rowActions.find((rowAction) =>
1379
+ rowAction.Id === activatedRowActionId ||
1380
+ rowAction.ActionId === activatedRowActionId)
1381
+ : rowActions.find(isPrimaryCollectionRowAction)
1382
+ if (activatedRowActionId && !activatedRowAction) {
1383
+ diagnostics.push(createViewDiagnostic({
1384
+ details: { activatedRowActionId },
1385
+ message:
1386
+ `Collection view '${view.Name}' declares activated row action ` +
1387
+ `'${activatedRowActionId}', but no row action with that id or action id exists.`,
1388
+ reason: `missing-activated-collection-row-action.${activatedRowActionId}`,
1389
+ severity: 'error',
1390
+ source: 'presentation-collection-coverage',
1391
+ view: viewTrace,
1392
+ }))
1393
+ }
1394
+
1395
+ if (bodySlot?.ActivateOnRowClick && !activatedRowAction) {
1396
+ diagnostics.push(createViewDiagnostic({
1397
+ category: 'missing-binding',
1398
+ details: { activatedRowActionId: activatedRowActionId ?? null },
1399
+ interpretation: {
1400
+ status: 'unbound',
1401
+ target: 'collection-body-row-activation',
1402
+ },
1403
+ message:
1404
+ `Collection view '${view.Name}' enables row activation, ` +
1405
+ 'but no activated or primary row action can be resolved from the Body chrome slot.',
1406
+ reason: `missing-row-click-activation-action.${view.Id}`,
1407
+ severity: 'error',
1408
+ source: 'presentation-collection-coverage',
1409
+ view: viewTrace,
1410
+ }))
1411
+ }
1412
+
1413
+ if (bodySlot?.SelectOnRowClick && !isSelectionEnabled) {
1414
+ diagnostics.push(createViewDiagnostic({
1415
+ category: 'missing-binding',
1416
+ details: {
1417
+ bodySlotId: bodySlot.Id,
1418
+ selectionMode,
1419
+ selectOnRowClick: bodySlot.SelectOnRowClick,
1420
+ },
1421
+ interpretation: {
1422
+ status: 'unbound',
1423
+ target: 'collection-row-click-selection',
1424
+ },
1425
+ message:
1426
+ `Collection view '${view.Name}' enables row-click selection, ` +
1427
+ 'but row selection is disabled.',
1428
+ reason: `row-click-selection-without-selection.${view.Id}`,
1429
+ severity: 'warning',
1430
+ source: 'presentation-collection-coverage',
1431
+ suggestedNextStep:
1432
+ 'Enable single or multiple selection, or disable SelectOnRowClick.',
1433
+ view: viewTrace,
1434
+ }))
1435
+ }
1436
+
1437
+ if (detailSlot?.DetailViewId) {
1438
+ if (isCollectionDetailActivationUnsupported(detailSlot.DetailActivation)) {
1439
+ diagnostics.push(createViewDiagnostic({
1440
+ category: 'missing-binding',
1441
+ details: {
1442
+ detailActivation: detailSlot.DetailActivation,
1443
+ detailSlotId: detailSlot.Id,
1444
+ detailViewId: detailSlot.DetailViewId,
1445
+ },
1446
+ interpretation: {
1447
+ status: 'unbound',
1448
+ target: 'collection-detail-activation',
1449
+ },
1450
+ message:
1451
+ `Collection view '${view.Name}' declares detail activation ` +
1452
+ `'${detailSlot.DetailActivation}', but the frontend interpreter currently binds selection and row-activation detail context only.`,
1453
+ reason: `unsupported-collection-detail-activation.${detailSlot.DetailViewId}`,
1454
+ severity: 'warning',
1455
+ source: 'presentation-collection-coverage',
1456
+ suggestedNextStep:
1457
+ 'Use Selection or RowActivation, or add a frontend interpretation for this activation mode.',
1458
+ view: viewTrace,
1459
+ }))
1460
+ }
1461
+
1462
+ if (
1463
+ isCollectionDetailActivationSelection(detailSlot.DetailActivation) &&
1464
+ !bodySlot?.SelectOnRowClick
1465
+ ) {
1466
+ diagnostics.push(createViewDiagnostic({
1467
+ category: 'missing-binding',
1468
+ details: {
1469
+ bodySlotId: bodySlot?.Id ?? null,
1470
+ detailActivation: detailSlot.DetailActivation,
1471
+ detailSlotId: detailSlot.Id,
1472
+ detailViewId: detailSlot.DetailViewId,
1473
+ selectOnRowClick: bodySlot?.SelectOnRowClick ?? false,
1474
+ },
1475
+ interpretation: {
1476
+ status: 'unbound',
1477
+ target: 'collection-selection-detail-context',
1478
+ },
1479
+ message:
1480
+ `Collection view '${view.Name}' renders detail from selection, ` +
1481
+ 'but row-click selection is disabled.',
1482
+ reason: `selection-detail-without-row-click-selection.${detailSlot.DetailViewId}`,
1483
+ severity: 'warning',
1484
+ source: 'presentation-collection-coverage',
1485
+ suggestedNextStep:
1486
+ 'Enable SelectOnRowClick for selection-driven detail previews, or change the detail activation mode.',
1487
+ view: viewTrace,
1488
+ }))
1489
+ }
1490
+
1491
+ if (
1492
+ isCollectionDetailActivationSelection(detailSlot.DetailActivation) &&
1493
+ bodySlot?.SelectOnRowClick &&
1494
+ bodySlot.ActivateOnRowClick
1495
+ ) {
1496
+ diagnostics.push(createViewDiagnostic({
1497
+ category: 'unsupported',
1498
+ details: {
1499
+ bodySlotId: bodySlot.Id,
1500
+ detailActivation: detailSlot.DetailActivation,
1501
+ detailSlotId: detailSlot.Id,
1502
+ detailViewId: detailSlot.DetailViewId,
1503
+ },
1504
+ interpretation: {
1505
+ status: 'unsupported',
1506
+ target: 'collection-row-click-interaction',
1507
+ },
1508
+ message:
1509
+ `Collection view '${view.Name}' both selects and activates rows on click; ` +
1510
+ 'selection-driven detail previews may be bypassed by navigation.',
1511
+ reason: `selection-detail-and-row-click-activation.${detailSlot.DetailViewId}`,
1512
+ severity: 'warning',
1513
+ source: 'presentation-collection-coverage',
1514
+ suggestedNextStep:
1515
+ 'Use SelectOnRowClick for preview-first rows and expose navigation through row actions.',
1516
+ view: viewTrace,
1517
+ }))
1518
+ }
1519
+
1520
+ if (!findPresentationView(module, detailSlot.DetailViewId)) {
1521
+ diagnostics.push(createViewDiagnostic({
1522
+ details: {
1523
+ detailSlotId: detailSlot.Id,
1524
+ detailViewId: detailSlot.DetailViewId,
1525
+ },
1526
+ message:
1527
+ `Collection view '${view.Name}' references detail view ` +
1528
+ `'${detailSlot.DetailViewId}', but that view is not present in the presentation module.`,
1529
+ reason: `missing-collection-detail-view.${detailSlot.DetailViewId}`,
1530
+ severity: 'error',
1531
+ source: 'presentation-collection-coverage',
1532
+ view: viewTrace,
1533
+ }))
1534
+ }
1535
+
1536
+ if (!isSelectionEnabled) {
1537
+ diagnostics.push(createViewDiagnostic({
1538
+ category: 'missing-binding',
1539
+ details: {
1540
+ detailSlotId: detailSlot.Id,
1541
+ detailViewId: detailSlot.DetailViewId,
1542
+ },
1543
+ interpretation: {
1544
+ status: 'unbound',
1545
+ target: 'collection-detail-selection-context',
1546
+ },
1547
+ message:
1548
+ `Collection view '${view.Name}' declares detail view ` +
1549
+ `'${detailSlot.DetailViewId}', but row selection is not enabled for the collection.`,
1550
+ reason: `missing-collection-detail-selection-context.${detailSlot.DetailViewId}`,
1551
+ severity: 'warning',
1552
+ source: 'presentation-collection-coverage',
1553
+ suggestedNextStep:
1554
+ 'Enable collection row selection and declare a row identity path so the detail view can bind to selected-row context.',
1555
+ view: viewTrace,
1556
+ }))
1557
+ }
1558
+ }
1559
+ }
1560
+
1561
+ for (const rowAction of rowActions) {
1562
+ diagnostics.push(
1563
+ ...projectCollectionRowActionPredicateCoverageDiagnostics(rowAction, view, viewTrace),
1564
+ )
1565
+
1566
+ const action = findPresentationAction<ActionDefinition>(module, rowAction.ActionId)
1567
+ if (!action) {
1568
+ continue
1569
+ }
1570
+
1571
+ const boundParameterNames = new Set(rowAction.Parameters.map((parameter) => parameter.Name))
1572
+ const missingRequiredParameters = action.Parameters.filter((parameter) =>
1573
+ parameter.IsRequired && !boundParameterNames.has(parameter.Name))
1574
+ if (missingRequiredParameters.length > 0) {
1575
+ diagnostics.push(createViewDiagnostic({
1576
+ details: {
1577
+ actionId: action.Id,
1578
+ missingParameters: missingRequiredParameters.map((parameter) => parameter.Name),
1579
+ rowActionId: rowAction.Id,
1580
+ },
1581
+ message:
1582
+ `Collection row action '${rowAction.Id}' invokes '${action.Name}', ` +
1583
+ `but does not bind required parameter(s): ${missingRequiredParameters
1584
+ .map((parameter) => parameter.Name)
1585
+ .join(', ')}.`,
1586
+ reason: `missing-collection-row-action-parameters.${rowAction.Id}`,
1587
+ severity: 'error',
1588
+ source: 'presentation-collection-coverage',
1589
+ view: viewTrace,
1590
+ }))
1591
+ }
1592
+
1593
+ for (const parameter of rowAction.Parameters) {
1594
+ if (parameter.FieldId && !findPresentationField(module, parameter.FieldId)) {
1595
+ diagnostics.push(createViewDiagnostic({
1596
+ details: {
1597
+ actionId: action.Id,
1598
+ fieldId: parameter.FieldId,
1599
+ parameter: parameter.Name,
1600
+ rowActionId: rowAction.Id,
1601
+ },
1602
+ message:
1603
+ `Collection row action '${rowAction.Id}' parameter '${parameter.Name}' ` +
1604
+ `references field '${parameter.FieldId}', but that field is not present in the presentation module.`,
1605
+ reason: `missing-collection-row-action-parameter-field.${rowAction.Id}.${parameter.Name}`,
1606
+ severity: 'error',
1607
+ source: 'presentation-collection-coverage',
1608
+ view: viewTrace,
1609
+ }))
1610
+ }
1611
+ }
1612
+ }
1613
+
1614
+ for (const selectionAction of selectionActions) {
1615
+ diagnostics.push(
1616
+ ...projectCollectionSelectionActionPredicateCoverageDiagnostics(
1617
+ selectionAction,
1618
+ view,
1619
+ viewTrace,
1620
+ ),
1621
+ )
1622
+
1623
+ if (!rowIdentityPath) {
1624
+ diagnostics.push(createViewDiagnostic({
1625
+ details: {
1626
+ actionId: selectionAction.ActionId,
1627
+ bodySlotId: bodySlot?.Id ?? null,
1628
+ selectionActionId: selectionAction.Id,
1629
+ },
1630
+ message:
1631
+ `Collection selection action '${selectionAction.Id}' on '${view.Name}' ` +
1632
+ 'requires selected row identity context, but the Body chrome slot does not declare a row identity path.',
1633
+ reason: `missing-selection-action-row-identity.${selectionAction.Id}`,
1634
+ severity: 'error',
1635
+ source: 'presentation-collection-coverage',
1636
+ view: viewTrace,
1637
+ }))
1638
+ }
1639
+
1640
+ if (!isCollectionSelectionEnabled(selectionMode)) {
1641
+ diagnostics.push(createViewDiagnostic({
1642
+ details: {
1643
+ actionId: selectionAction.ActionId,
1644
+ selectionActionId: selectionAction.Id,
1645
+ },
1646
+ message:
1647
+ `Collection selection action '${selectionAction.Id}' on '${view.Name}' ` +
1648
+ 'is declared, but row selection is not enabled for the collection.',
1649
+ reason: `missing-selection-action-selection-mode.${selectionAction.Id}`,
1650
+ severity: 'warning',
1651
+ source: 'presentation-collection-coverage',
1652
+ view: viewTrace,
1653
+ }))
1654
+ }
1655
+
1656
+ const action = findPresentationAction<ActionDefinition>(module, selectionAction.ActionId)
1657
+ if (!action) {
1658
+ continue
1659
+ }
1660
+
1661
+ const boundParameterNames = new Set(selectionAction.Parameters.map((parameter) => parameter.Name))
1662
+ const missingRequiredParameters = action.Parameters.filter((parameter) =>
1663
+ parameter.IsRequired && !boundParameterNames.has(parameter.Name))
1664
+ if (missingRequiredParameters.length > 0) {
1665
+ diagnostics.push(createViewDiagnostic({
1666
+ details: {
1667
+ actionId: action.Id,
1668
+ missingParameters: missingRequiredParameters.map((parameter) => parameter.Name),
1669
+ selectionActionId: selectionAction.Id,
1670
+ },
1671
+ message:
1672
+ `Collection selection action '${selectionAction.Id}' invokes '${action.Name}', ` +
1673
+ `but does not bind required parameter(s): ${missingRequiredParameters
1674
+ .map((parameter) => parameter.Name)
1675
+ .join(', ')}.`,
1676
+ reason: `missing-collection-selection-action-parameters.${selectionAction.Id}`,
1677
+ severity: 'error',
1678
+ source: 'presentation-collection-coverage',
1679
+ view: viewTrace,
1680
+ }))
1681
+ }
1682
+
1683
+ for (const parameter of selectionAction.Parameters) {
1684
+ if (parameter.FieldId && !findPresentationField(module, parameter.FieldId)) {
1685
+ diagnostics.push(createViewDiagnostic({
1686
+ details: {
1687
+ actionId: action.Id,
1688
+ fieldId: parameter.FieldId,
1689
+ parameter: parameter.Name,
1690
+ selectionActionId: selectionAction.Id,
1691
+ },
1692
+ message:
1693
+ `Collection selection action '${selectionAction.Id}' parameter '${parameter.Name}' ` +
1694
+ `references field '${parameter.FieldId}', but that field is not present in the presentation module.`,
1695
+ reason: `missing-collection-selection-action-parameter-field.${selectionAction.Id}.${parameter.Name}`,
1696
+ severity: 'error',
1697
+ source: 'presentation-collection-coverage',
1698
+ view: viewTrace,
1699
+ }))
1700
+ }
1701
+
1702
+ if (isSelectedRowValueSelectionActionParameter(parameter.Source) && !parameter.ValuePath) {
1703
+ diagnostics.push(createViewDiagnostic({
1704
+ details: {
1705
+ actionId: action.Id,
1706
+ parameter: parameter.Name,
1707
+ parameterSource: parameter.Source,
1708
+ selectionActionId: selectionAction.Id,
1709
+ },
1710
+ message:
1711
+ `Collection selection action '${selectionAction.Id}' parameter '${parameter.Name}' ` +
1712
+ 'uses selected row values but does not declare a value path.',
1713
+ reason: `missing-collection-selection-action-parameter-value-path.${selectionAction.Id}.${parameter.Name}`,
1714
+ severity: 'error',
1715
+ source: 'presentation-collection-coverage',
1716
+ view: viewTrace,
1717
+ }))
1718
+ }
1719
+ }
1720
+ }
1721
+
1722
+ return diagnostics
1723
+ }
1724
+
1725
+ function projectCollectionChromeCoverageDiagnostics(
1726
+ module: PresentationModuleDefinition,
1727
+ view: ViewDefinition,
1728
+ viewTrace: PresentationProjectionTraceView,
1729
+ collectionChromeSlotRendererKeys: readonly string[],
1730
+ ): readonly PresentationProjectionDiagnostic[] {
1731
+ const collection = view.Collection
1732
+ if (!collection) {
1733
+ return []
1734
+ }
1735
+
1736
+ const diagnostics: PresentationProjectionDiagnostic[] = []
1737
+ const slots = resolveProjectedCollectionChromeSlots(collection)
1738
+ diagnostics.push(
1739
+ ...projectCollectionChromeCompatibilityDiagnostics(view, viewTrace, collection, slots),
1740
+ ...projectCollectionChromeRequiredSlotDiagnostics(module, view, viewTrace, collection),
1741
+ )
1742
+ const seenSlotIds = new Set<string>()
1743
+
1744
+ for (const slot of slots) {
1745
+ if (seenSlotIds.has(slot.Id)) {
1746
+ diagnostics.push(createCollectionChromeSlotDiagnostic({
1747
+ details: { slotId: slot.Id },
1748
+ message:
1749
+ `Collection view '${view.Name}' declares collection chrome slot ` +
1750
+ `'${slot.Id}' more than once.`,
1751
+ reason: `duplicate-collection-chrome-slot.${slot.Id}`,
1752
+ severity: 'warning',
1753
+ slot,
1754
+ view: viewTrace,
1755
+ }))
1756
+ }
1757
+ seenSlotIds.add(slot.Id)
1758
+
1759
+ diagnostics.push(
1760
+ ...projectCollectionChromeSlotReferenceDiagnostics(module, view, viewTrace, slot),
1761
+ ...projectCollectionChromeSlotInterpretationDiagnostics(
1762
+ view,
1763
+ viewTrace,
1764
+ slot,
1765
+ collectionChromeSlotRendererKeys,
1766
+ ),
1767
+ )
1768
+ }
1769
+
1770
+ return diagnostics
1771
+ }
1772
+
1773
+ function projectCollectionOuterViewDuplicationDiagnostics(
1774
+ view: ViewDefinition,
1775
+ viewTrace: PresentationProjectionTraceView,
1776
+ ): readonly PresentationProjectionDiagnostic[] {
1777
+ if (!view.Collection) {
1778
+ return []
1779
+ }
1780
+
1781
+ const duplicatedFields = [
1782
+ view.Subject.DataSourceId ? 'Subject.DataSourceId' : null,
1783
+ view.DataSourceIds.length > 0 ? 'DataSourceIds' : null,
1784
+ view.FieldIds.length > 0 ? 'FieldIds' : null,
1785
+ view.Actions.length > 0 ? 'Actions' : null,
1786
+ ].filter((field): field is string => Boolean(field))
1787
+
1788
+ if (duplicatedFields.length === 0) {
1789
+ return []
1790
+ }
1791
+
1792
+ return [
1793
+ createViewDiagnostic({
1794
+ category: 'incomplete-projection',
1795
+ details: { duplicatedFields },
1796
+ interpretation: {
1797
+ status: 'unsupported',
1798
+ target: 'collection-chrome-single-source-of-truth',
1799
+ },
1800
+ message:
1801
+ `Collection view '${view.Name}' still declares collection-specific ` +
1802
+ `outer view data: ${duplicatedFields.join(', ')}.`,
1803
+ reason: `duplicated-collection-outer-view-data.${view.Id}`,
1804
+ severity: 'warning',
1805
+ source: 'presentation-collection-coverage',
1806
+ suggestedNextStep:
1807
+ 'Move collection data sources, fields, actions, and query/detail semantics into Collection.Chrome slots.',
1808
+ view: viewTrace,
1809
+ }),
1810
+ ]
1811
+ }
1812
+
1813
+ function projectProjectedCollectionActionContextDiagnostics({
1814
+ bodySlot,
1815
+ collectionChromeSlotRendererKeys,
1816
+ projectedActions,
1817
+ selectionMode,
1818
+ selectionStateId,
1819
+ view,
1820
+ viewTrace,
1821
+ }: {
1822
+ readonly bodySlot: CollectionChromeSlotDefinition | null
1823
+ readonly collectionChromeSlotRendererKeys: readonly string[]
1824
+ readonly projectedActions: readonly PresentationViewProjectedActionRef[]
1825
+ readonly selectionMode: unknown
1826
+ readonly selectionStateId: string | null
1827
+ readonly view: ViewDefinition
1828
+ readonly viewTrace: PresentationProjectionTraceView
1829
+ }): readonly PresentationProjectionDiagnostic[] {
1830
+ const diagnostics: PresentationProjectionDiagnostic[] = []
1831
+ const bodySlotRendererBound = bodySlot
1832
+ ? hasCollectionChromeSlotRendererBinding(collectionChromeSlotRendererKeys, bodySlot)
1833
+ : false
1834
+
1835
+ for (const actionRef of projectedActions) {
1836
+ if (actionRef.contextKind === 'collection-row') {
1837
+ if (!bodySlot) {
1838
+ diagnostics.push(createViewDiagnostic({
1839
+ category: 'missing-binding',
1840
+ details: {
1841
+ actionId: actionRef.actionId,
1842
+ actionSource: actionRef.source,
1843
+ collectionActionContextKind: actionRef.contextKind,
1844
+ rowActionId: actionRef.rowAction?.Id ?? null,
1845
+ slotId: actionRef.slotId ?? null,
1846
+ },
1847
+ interpretation: {
1848
+ status: 'unbound',
1849
+ target: 'collection-row-action-context',
1850
+ },
1851
+ message:
1852
+ `Collection row action '${actionRef.actionId}' on '${view.Name}' ` +
1853
+ 'requires row context, but the collection does not declare a Body chrome slot.',
1854
+ reason: `missing-collection-row-action-context.${actionRef.source}`,
1855
+ severity: 'error',
1856
+ source: 'presentation-action-context-coverage',
1857
+ suggestedNextStep:
1858
+ 'Declare a Body chrome slot so row actions can be interpreted against collection rows.',
1859
+ view: viewTrace,
1860
+ }))
1861
+ } else if (!bodySlotRendererBound) {
1862
+ diagnostics.push(createViewDiagnostic({
1863
+ category: 'missing-binding',
1864
+ details: {
1865
+ actionId: actionRef.actionId,
1866
+ actionSource: actionRef.source,
1867
+ bodySlotId: bodySlot.Id,
1868
+ collectionActionContextKind: actionRef.contextKind,
1869
+ rowActionId: actionRef.rowAction?.Id ?? null,
1870
+ slotId: actionRef.slotId ?? null,
1871
+ },
1872
+ interpretation: {
1873
+ status: 'unbound',
1874
+ target: 'collection-row-action-context-renderer',
1875
+ },
1876
+ message:
1877
+ `Collection row action '${actionRef.actionId}' on '${view.Name}' ` +
1878
+ `requires row context from Body chrome slot '${bodySlot.Id}', but no Body slot renderer is bound.`,
1879
+ reason: `unbound-collection-row-action-context-renderer.${actionRef.source}`,
1880
+ severity: 'warning',
1881
+ source: 'presentation-action-context-coverage',
1882
+ suggestedNextStep:
1883
+ 'Bind a Body chrome slot renderer that supplies row context to collection row actions.',
1884
+ view: viewTrace,
1885
+ }))
1886
+ }
1887
+
1888
+ const missingRowValueParameters = actionRef.rowAction?.Parameters
1889
+ .filter((parameter) => !parameter.ValuePath)
1890
+ .map((parameter) => parameter.Name) ?? []
1891
+ if (missingRowValueParameters.length > 0) {
1892
+ diagnostics.push(createViewDiagnostic({
1893
+ details: {
1894
+ actionId: actionRef.actionId,
1895
+ actionSource: actionRef.source,
1896
+ missingRowValueParameters,
1897
+ rowActionId: actionRef.rowAction?.Id ?? null,
1898
+ },
1899
+ message:
1900
+ `Collection row action '${actionRef.rowAction?.Id ?? actionRef.actionId}' ` +
1901
+ `on '${view.Name}' has parameter(s) without row value paths: ${missingRowValueParameters.join(', ')}.`,
1902
+ reason: `missing-collection-row-action-value-path.${actionRef.source}`,
1903
+ severity: 'error',
1904
+ source: 'presentation-action-context-coverage',
1905
+ view: viewTrace,
1906
+ }))
1907
+ }
1908
+ }
1909
+
1910
+ if (actionRef.contextKind === 'collection-selection') {
1911
+ if (!isCollectionSelectionEnabled(selectionMode)) {
1912
+ diagnostics.push(createViewDiagnostic({
1913
+ category: 'missing-binding',
1914
+ details: {
1915
+ actionId: actionRef.actionId,
1916
+ actionSource: actionRef.source,
1917
+ collectionActionContextKind: actionRef.contextKind,
1918
+ selectionActionId: actionRef.selectionAction?.Id ?? null,
1919
+ selectionMode,
1920
+ slotId: actionRef.slotId ?? null,
1921
+ },
1922
+ interpretation: {
1923
+ status: 'unbound',
1924
+ target: 'collection-selection-action-context',
1925
+ },
1926
+ message:
1927
+ `Collection selection action '${actionRef.actionId}' on '${view.Name}' ` +
1928
+ 'requires selection context, but row selection is not enabled.',
1929
+ reason: `missing-collection-selection-action-context.${actionRef.source}`,
1930
+ severity: 'warning',
1931
+ source: 'presentation-action-context-coverage',
1932
+ suggestedNextStep:
1933
+ 'Enable collection row selection, or model the action as a plain view/slot action.',
1934
+ view: viewTrace,
1935
+ }))
1936
+ }
1937
+
1938
+ if (!selectionStateId) {
1939
+ diagnostics.push(createViewDiagnostic({
1940
+ category: 'missing-binding',
1941
+ details: {
1942
+ actionId: actionRef.actionId,
1943
+ actionSource: actionRef.source,
1944
+ collectionActionContextKind: actionRef.contextKind,
1945
+ selectionActionId: actionRef.selectionAction?.Id ?? null,
1946
+ slotId: actionRef.slotId ?? null,
1947
+ },
1948
+ interpretation: {
1949
+ status: 'unbound',
1950
+ target: 'collection-selection-action-state',
1951
+ },
1952
+ message:
1953
+ `Collection selection action '${actionRef.actionId}' on '${view.Name}' ` +
1954
+ 'requires selected-row state, but no selection StateId is declared.',
1955
+ reason: `missing-collection-selection-action-state.${actionRef.source}`,
1956
+ severity: 'warning',
1957
+ source: 'presentation-action-context-coverage',
1958
+ suggestedNextStep:
1959
+ 'Declare StateId on the collection Body or SelectionActions chrome slot.',
1960
+ view: viewTrace,
1961
+ }))
1962
+ }
1963
+
1964
+ if (!actionRef.context.selectedRowIdentityPath) {
1965
+ diagnostics.push(createViewDiagnostic({
1966
+ details: {
1967
+ actionId: actionRef.actionId,
1968
+ actionSource: actionRef.source,
1969
+ collectionActionContextKind: actionRef.contextKind,
1970
+ selectionActionId: actionRef.selectionAction?.Id ?? null,
1971
+ slotId: actionRef.slotId ?? null,
1972
+ },
1973
+ message:
1974
+ `Collection selection action '${actionRef.actionId}' on '${view.Name}' ` +
1975
+ 'requires selected-row context, but the collection Body slot does not declare RowIdentityPath.',
1976
+ reason: `missing-collection-selection-action-row-identity.${actionRef.source}`,
1977
+ severity: 'error',
1978
+ source: 'presentation-action-context-coverage',
1979
+ view: viewTrace,
1980
+ }))
1981
+ }
1982
+ }
1983
+ }
1984
+
1985
+ return diagnostics
1986
+ }
1987
+
1988
+ function resolveCollectionChromeBodySlot(
1989
+ collection: NonNullable<ViewDefinition['Collection']>,
1990
+ ) {
1991
+ return resolveProjectedCollectionChromeSlots(collection)
1992
+ .find((slot) => isCollectionChromeSlotKind(slot, collectionChromeSlotKinds.body)) ?? null
1993
+ }
1994
+
1995
+ function resolveCollectionChromeDetailSlot(
1996
+ collection: NonNullable<ViewDefinition['Collection']>,
1997
+ ) {
1998
+ return resolveProjectedCollectionChromeSlots(collection)
1999
+ .find((slot) => isCollectionChromeSlotKind(slot, collectionChromeSlotKinds.detail)) ?? null
2000
+ }
2001
+
2002
+ function resolveCollectionChromeRowActions(
2003
+ collection: NonNullable<ViewDefinition['Collection']>,
2004
+ ) {
2005
+ return sortCollectionActionBindings(
2006
+ uniqueCollectionActionBindings(
2007
+ resolveProjectedCollectionChromeSlots(collection)
2008
+ .filter((slot) => isCollectionChromeSlotKind(slot, collectionChromeSlotKinds.rowActions))
2009
+ .flatMap((slot) => slot.RowActions),
2010
+ ),
2011
+ )
2012
+ }
2013
+
2014
+ function resolveCollectionChromeSelectionActions(
2015
+ collection: NonNullable<ViewDefinition['Collection']>,
2016
+ ) {
2017
+ return sortCollectionActionBindings(
2018
+ uniqueCollectionActionBindings(
2019
+ resolveProjectedCollectionChromeSlots(collection)
2020
+ .filter((slot) =>
2021
+ isCollectionChromeSlotKind(slot, collectionChromeSlotKinds.selectionActions))
2022
+ .flatMap((slot) => slot.SelectionActions),
2023
+ ),
2024
+ )
2025
+ }
2026
+
2027
+ function uniqueCollectionActionBindings<TAction extends { readonly Id: string }>(
2028
+ actions: readonly TAction[],
2029
+ ) {
2030
+ const seen = new Set<string>()
2031
+ return actions.filter((action) => {
2032
+ if (seen.has(action.Id)) {
2033
+ return false
2034
+ }
2035
+
2036
+ seen.add(action.Id)
2037
+ return true
2038
+ })
2039
+ }
2040
+
2041
+ function sortCollectionActionBindings<TAction extends { readonly Order: number }>(
2042
+ actions: readonly TAction[],
2043
+ ) {
2044
+ return actions.slice().sort((left, right) => left.Order - right.Order)
2045
+ }
2046
+
2047
+ function projectCollectionChromeCompatibilityDiagnostics(
2048
+ view: ViewDefinition,
2049
+ viewTrace: PresentationProjectionTraceView,
2050
+ collection: NonNullable<ViewDefinition['Collection']>,
2051
+ slots: readonly CollectionChromeSlotDefinition[],
2052
+ ): readonly PresentationProjectionDiagnostic[] {
2053
+ const diagnostics: PresentationProjectionDiagnostic[] = []
2054
+ const declaredSlots = collection.Chrome?.Slots ?? []
2055
+ const projectionMode = readCollectionChromeProjectionMode(collection.Chrome)
2056
+ const synthesizedSlots = slots.filter((slot) =>
2057
+ isCollectionChromeSlotProjectionMode(
2058
+ slot,
2059
+ collectionChromeProjectionModes.compatibilitySynthesized,
2060
+ ))
2061
+
2062
+ if (
2063
+ projectionMode === collectionChromeProjectionModes.compatibilitySynthesized ||
2064
+ synthesizedSlots.length > 0
2065
+ ) {
2066
+ diagnostics.push(createViewDiagnostic({
2067
+ category: 'incomplete-projection',
2068
+ details: {
2069
+ declaredSlotIds: declaredSlots.map((slot) => slot.Id),
2070
+ projectionMode:
2071
+ projectionMode ?? collectionChromeProjectionModes.compatibilitySynthesized,
2072
+ synthesizedSlotIds: synthesizedSlots.map((slot) => slot.Id),
2073
+ },
2074
+ interpretation: {
2075
+ status: 'unsupported',
2076
+ target: 'projected-collection-runtime.chrome',
2077
+ },
2078
+ message:
2079
+ `Collection view '${view.Name}' uses compatibility-synthesized collection chrome; ` +
2080
+ 'the runtime no longer synthesizes chrome from legacy collection fields.',
2081
+ reason: 'synthesized-collection-chrome',
2082
+ severity: 'warning',
2083
+ source: 'presentation-collection-chrome-coverage',
2084
+ suggestedNextStep:
2085
+ 'Declare Collection.Chrome slots on the backend, including Body, QueryForm, Summary, RowActions, Detail, SelectionActions, and Pagination as needed.',
2086
+ view: viewTrace,
2087
+ }))
2088
+ }
2089
+
2090
+ if (declaredSlots.length === 0) {
2091
+ diagnostics.push(createViewDiagnostic({
2092
+ category: 'incomplete-projection',
2093
+ details: {
2094
+ declaredSlotIds: declaredSlots.map((slot) => slot.Id),
2095
+ projectionMode: projectionMode ?? null,
2096
+ },
2097
+ interpretation: {
2098
+ status: 'unbound',
2099
+ target: 'projected-collection-runtime.chrome',
2100
+ },
2101
+ message:
2102
+ `Collection view '${view.Name}' does not declare Collection.Chrome slots; ` +
2103
+ 'collection rendering now requires declared chrome, including a Body slot.',
2104
+ reason: 'missing-collection-chrome',
2105
+ severity: 'error',
2106
+ source: 'presentation-collection-chrome-coverage',
2107
+ suggestedNextStep:
2108
+ 'Declare Collection.Chrome slots on the backend, including Body, QueryForm, Summary, RowActions, Detail, SelectionActions, and Pagination as needed.',
2109
+ view: viewTrace,
2110
+ }))
2111
+ }
2112
+
2113
+ if (projectionMode === collectionChromeProjectionModes.mixed) {
2114
+ diagnostics.push(createViewDiagnostic({
2115
+ category: 'incomplete-projection',
2116
+ details: {
2117
+ declaredSlotIds: declaredSlots.map((slot) => slot.Id),
2118
+ projectionMode,
2119
+ },
2120
+ interpretation: {
2121
+ status: 'locally-interpreted',
2122
+ target: 'projected-collection-runtime.chrome.mixed',
2123
+ },
2124
+ message:
2125
+ `Collection view '${view.Name}' declares mixed collection chrome projection; ` +
2126
+ 'some collection surface behavior may still rely on frontend compatibility interpretation.',
2127
+ reason: 'mixed-collection-chrome-projection',
2128
+ severity: 'info',
2129
+ source: 'presentation-collection-chrome-coverage',
2130
+ suggestedNextStep:
2131
+ 'Replace mixed collection chrome with fully declared slots once the remaining compatibility behavior is modeled.',
2132
+ view: viewTrace,
2133
+ }))
2134
+ }
2135
+
2136
+ if (
2137
+ declaredSlots.length > 0 &&
2138
+ projectionMode !== collectionChromeProjectionModes.compatibilitySynthesized &&
2139
+ !declaredSlots.some((slot) =>
2140
+ isCollectionChromeSlotKind(slot, collectionChromeSlotKinds.body))
2141
+ ) {
2142
+ const isMixedProjection = projectionMode === collectionChromeProjectionModes.mixed
2143
+ diagnostics.push(createViewDiagnostic({
2144
+ category: 'incomplete-projection',
2145
+ details: {
2146
+ declaredSlotIds: declaredSlots.map((slot) => slot.Id),
2147
+ projectionMode: projectionMode ?? null,
2148
+ },
2149
+ interpretation: {
2150
+ status: 'unbound',
2151
+ target: 'projected-collection-view.body-slot',
2152
+ },
2153
+ message:
2154
+ `Collection view '${view.Name}' declares collection chrome without a Body slot; ` +
2155
+ 'the collection body will not render until Body is declared.',
2156
+ reason: 'missing-collection-body-slot',
2157
+ severity: isMixedProjection ? 'warning' : 'error',
2158
+ source: 'presentation-collection-chrome-coverage',
2159
+ suggestedNextStep:
2160
+ 'Add a Collection.Chrome Body slot so the collection body is projected through the chrome slot interpreter.',
2161
+ view: viewTrace,
2162
+ }))
2163
+ }
2164
+
2165
+ return diagnostics
2166
+ }
2167
+
2168
+ function projectCollectionChromeRequiredSlotDiagnostics(
2169
+ module: PresentationModuleDefinition,
2170
+ view: ViewDefinition,
2171
+ viewTrace: PresentationProjectionTraceView,
2172
+ collection: NonNullable<ViewDefinition['Collection']>,
2173
+ ): readonly PresentationProjectionDiagnostic[] {
2174
+ const diagnostics: PresentationProjectionDiagnostic[] = []
2175
+ const chrome = createCollectionChromeRuntime(collection)
2176
+
2177
+ if (chrome.bodySlot && chrome.bodySlot.DataSourceIds.length === 0) {
2178
+ diagnostics.push(createCollectionChromeSlotDiagnostic({
2179
+ details: { slotId: chrome.bodySlot.Id },
2180
+ interpretation: {
2181
+ status: 'unbound',
2182
+ target: 'projected-collection-runtime.body-data-source',
2183
+ },
2184
+ message:
2185
+ `Collection view '${view.Name}' declares Body chrome slot ` +
2186
+ `'${chrome.bodySlot.Id}', but the slot does not declare a data source.`,
2187
+ reason: `missing-collection-body-slot-data-source.${chrome.bodySlot.Id}`,
2188
+ severity: 'error',
2189
+ slot: chrome.bodySlot,
2190
+ suggestedNextStep:
2191
+ 'Declare the collection row data source id on the Body chrome slot.',
2192
+ view: viewTrace,
2193
+ }))
2194
+ }
2195
+
2196
+ for (const queryFormSlot of chrome.findSlots(collectionChromeSlotKinds.queryForm)) {
2197
+ if (!queryFormSlot.QueryFormId) {
2198
+ diagnostics.push(createCollectionChromeSlotDiagnostic({
2199
+ details: { slotId: queryFormSlot.Id },
2200
+ interpretation: {
2201
+ status: 'unbound',
2202
+ target: 'projected-collection-runtime.query-form-slot',
2203
+ },
2204
+ message:
2205
+ `Collection view '${view.Name}' declares QueryForm chrome slot ` +
2206
+ `'${queryFormSlot.Id}', but the slot does not declare QueryFormId.`,
2207
+ reason: `missing-collection-query-form-slot-query-form.${queryFormSlot.Id}`,
2208
+ severity: 'error',
2209
+ slot: queryFormSlot,
2210
+ suggestedNextStep:
2211
+ 'Declare QueryFormId on the QueryForm chrome slot.',
2212
+ view: viewTrace,
2213
+ }))
2214
+ }
2215
+ }
2216
+
2217
+ for (const paginationSlot of chrome.findSlots(collectionChromeSlotKinds.pagination)) {
2218
+ if (paginationSlot.DataSourceIds.length === 0) {
2219
+ diagnostics.push(createCollectionChromeSlotDiagnostic({
2220
+ details: { slotId: paginationSlot.Id },
2221
+ interpretation: {
2222
+ status: 'unbound',
2223
+ target: 'projected-collection-runtime.pagination-data-source',
2224
+ },
2225
+ message:
2226
+ `Collection view '${view.Name}' declares Pagination chrome slot ` +
2227
+ `'${paginationSlot.Id}', but the slot does not declare a paginated data source.`,
2228
+ reason: `missing-collection-pagination-slot-data-source.${paginationSlot.Id}`,
2229
+ severity: 'error',
2230
+ slot: paginationSlot,
2231
+ suggestedNextStep:
2232
+ 'Declare the paginated collection-query data source id on the Pagination chrome slot.',
2233
+ view: viewTrace,
2234
+ }))
2235
+ continue
2236
+ }
2237
+
2238
+ for (const dataSourceId of paginationSlot.DataSourceIds) {
2239
+ const dataSource = findPresentationDataSource(module, dataSourceId)
2240
+ if (!dataSource) {
2241
+ continue
2242
+ }
2243
+
2244
+ if (!isCollectionQueryDataSource(dataSource)) {
2245
+ diagnostics.push(createCollectionChromeSlotDiagnostic({
2246
+ details: {
2247
+ dataSourceId,
2248
+ dataSourceKind: dataSource.Kind,
2249
+ slotId: paginationSlot.Id,
2250
+ },
2251
+ interpretation: {
2252
+ status: 'unsupported',
2253
+ target: 'projected-collection-runtime.pagination-data-source',
2254
+ },
2255
+ message:
2256
+ `Collection view '${view.Name}' declares Pagination chrome slot ` +
2257
+ `'${paginationSlot.Id}' for data source '${dataSourceId}', but that data source is not a collection query.`,
2258
+ reason: `unsupported-collection-pagination-data-source-kind.${paginationSlot.Id}.${dataSourceId}`,
2259
+ severity: 'error',
2260
+ slot: paginationSlot,
2261
+ suggestedNextStep:
2262
+ 'Point the Pagination chrome slot at a collection-query data source with pagination metadata.',
2263
+ view: viewTrace,
2264
+ }))
2265
+ continue
2266
+ }
2267
+
2268
+ if (!dataSource.Query?.Pagination) {
2269
+ diagnostics.push(createCollectionChromeSlotDiagnostic({
2270
+ details: { dataSourceId, slotId: paginationSlot.Id },
2271
+ interpretation: {
2272
+ status: 'unbound',
2273
+ target: 'projected-collection-runtime.pagination-window',
2274
+ },
2275
+ message:
2276
+ `Collection view '${view.Name}' declares Pagination chrome slot ` +
2277
+ `'${paginationSlot.Id}' for '${dataSourceId}', but the data source does not declare query pagination.`,
2278
+ reason: `missing-collection-pagination-data-source-policy.${paginationSlot.Id}.${dataSourceId}`,
2279
+ severity: 'error',
2280
+ slot: paginationSlot,
2281
+ suggestedNextStep:
2282
+ 'Declare DataSource.Query.Pagination so the collection pagination window can be interpreted.',
2283
+ view: viewTrace,
2284
+ }))
2285
+ }
2286
+ }
2287
+ }
2288
+
2289
+ for (const detailSlot of chrome.findSlots(collectionChromeSlotKinds.detail)) {
2290
+ if (!detailSlot.DetailViewId) {
2291
+ diagnostics.push(createCollectionChromeSlotDiagnostic({
2292
+ details: { slotId: detailSlot.Id },
2293
+ interpretation: {
2294
+ status: 'unbound',
2295
+ target: 'projected-collection-runtime.detail-slot',
2296
+ },
2297
+ message:
2298
+ `Collection view '${view.Name}' declares Detail chrome slot ` +
2299
+ `'${detailSlot.Id}', but the slot does not declare DetailViewId.`,
2300
+ reason: `missing-collection-detail-slot-view.${detailSlot.Id}`,
2301
+ severity: 'error',
2302
+ slot: detailSlot,
2303
+ suggestedNextStep:
2304
+ 'Declare DetailViewId on the Detail chrome slot.',
2305
+ view: viewTrace,
2306
+ }))
2307
+ continue
2308
+ }
2309
+
2310
+ if (
2311
+ detailSlot.DetailActivation == null ||
2312
+ isCollectionDetailActivationNone(detailSlot.DetailActivation)
2313
+ ) {
2314
+ diagnostics.push(createCollectionChromeSlotDiagnostic({
2315
+ details: {
2316
+ detailActivation: detailSlot.DetailActivation ?? null,
2317
+ detailViewId: detailSlot.DetailViewId,
2318
+ slotId: detailSlot.Id,
2319
+ },
2320
+ interpretation: {
2321
+ status: 'unbound',
2322
+ target: 'projected-collection-runtime.detail-activation',
2323
+ },
2324
+ message:
2325
+ `Collection view '${view.Name}' declares Detail chrome slot ` +
2326
+ `'${detailSlot.Id}', but the slot does not declare a detail activation mode.`,
2327
+ reason: `missing-collection-detail-slot-activation.${detailSlot.Id}`,
2328
+ severity: 'error',
2329
+ slot: detailSlot,
2330
+ suggestedNextStep:
2331
+ 'Declare DetailActivation on the Detail chrome slot so the frontend can supply row context.',
2332
+ view: viewTrace,
2333
+ }))
2334
+ }
2335
+
2336
+ if (
2337
+ isCollectionDetailActivationStateful(detailSlot.DetailActivation) &&
2338
+ !detailSlot.StateId &&
2339
+ !chrome.bodySlot?.StateId
2340
+ ) {
2341
+ diagnostics.push(createCollectionChromeSlotDiagnostic({
2342
+ details: {
2343
+ detailActivation: detailSlot.DetailActivation,
2344
+ detailViewId: detailSlot.DetailViewId,
2345
+ slotId: detailSlot.Id,
2346
+ },
2347
+ interpretation: {
2348
+ status: 'unbound',
2349
+ target: 'projected-collection-runtime.detail-state',
2350
+ },
2351
+ message:
2352
+ `Collection view '${view.Name}' declares stateful Detail chrome slot ` +
2353
+ `'${detailSlot.Id}', but neither the Detail nor Body slot declares StateId.`,
2354
+ reason: `missing-collection-detail-slot-state.${detailSlot.Id}`,
2355
+ severity: 'warning',
2356
+ slot: detailSlot,
2357
+ suggestedNextStep:
2358
+ 'Declare StateId on the Body or Detail chrome slot so detail context can be coordinated with selection state.',
2359
+ view: viewTrace,
2360
+ }))
2361
+ }
2362
+ }
2363
+
2364
+ return diagnostics
2365
+ }
2366
+
2367
+ function projectCollectionChromeSlotReferenceDiagnostics(
2368
+ module: PresentationModuleDefinition,
2369
+ view: ViewDefinition,
2370
+ viewTrace: PresentationProjectionTraceView,
2371
+ slot: CollectionChromeSlotDefinition,
2372
+ ): readonly PresentationProjectionDiagnostic[] {
2373
+ const diagnostics: PresentationProjectionDiagnostic[] = []
2374
+
2375
+ for (const dataSourceId of slot.DataSourceIds) {
2376
+ if (!findPresentationDataSource(module, dataSourceId)) {
2377
+ diagnostics.push(createCollectionChromeSlotDiagnostic({
2378
+ details: { dataSourceId, slotId: slot.Id },
2379
+ message:
2380
+ `Collection chrome slot '${slot.Id}' on '${view.Name}' references ` +
2381
+ `data source '${dataSourceId}', but that data source is not present in the presentation module.`,
2382
+ reason: `missing-collection-chrome-slot-data-source.${slot.Id}.${dataSourceId}`,
2383
+ severity: 'error',
2384
+ slot,
2385
+ view: viewTrace,
2386
+ }))
2387
+ }
2388
+ }
2389
+
2390
+ if (slot.QueryFormId && !findPresentationQueryForm(module, slot.QueryFormId)) {
2391
+ diagnostics.push(createCollectionChromeSlotDiagnostic({
2392
+ details: { queryFormId: slot.QueryFormId, slotId: slot.Id },
2393
+ message:
2394
+ `Collection chrome slot '${slot.Id}' on '${view.Name}' references ` +
2395
+ `query form '${slot.QueryFormId}', but that query form is not present in the presentation module.`,
2396
+ reason: `missing-collection-chrome-slot-query-form.${slot.Id}.${slot.QueryFormId}`,
2397
+ severity: 'error',
2398
+ slot,
2399
+ view: viewTrace,
2400
+ }))
2401
+ }
2402
+
2403
+ if (slot.DetailViewId && !findPresentationView(module, slot.DetailViewId)) {
2404
+ diagnostics.push(createCollectionChromeSlotDiagnostic({
2405
+ details: { detailViewId: slot.DetailViewId, slotId: slot.Id },
2406
+ message:
2407
+ `Collection chrome slot '${slot.Id}' on '${view.Name}' references ` +
2408
+ `detail view '${slot.DetailViewId}', but that view is not present in the presentation module.`,
2409
+ reason: `missing-collection-chrome-slot-detail-view.${slot.Id}.${slot.DetailViewId}`,
2410
+ severity: 'error',
2411
+ slot,
2412
+ view: viewTrace,
2413
+ }))
2414
+ }
2415
+
2416
+ for (const fieldId of slot.FieldIds) {
2417
+ if (!findPresentationField(module, fieldId)) {
2418
+ diagnostics.push(createCollectionChromeSlotDiagnostic({
2419
+ details: { fieldId, slotId: slot.Id },
2420
+ message:
2421
+ `Collection chrome slot '${slot.Id}' on '${view.Name}' references ` +
2422
+ `field '${fieldId}', but that field is not present in the presentation module.`,
2423
+ reason: `missing-collection-chrome-slot-field.${slot.Id}.${fieldId}`,
2424
+ severity: 'error',
2425
+ slot,
2426
+ view: viewTrace,
2427
+ }))
2428
+ }
2429
+ }
2430
+
2431
+ for (const column of slot.Columns) {
2432
+ if (!findPresentationField(module, column.FieldId)) {
2433
+ diagnostics.push(createCollectionChromeSlotDiagnostic({
2434
+ details: {
2435
+ columnId: column.Id,
2436
+ fieldId: column.FieldId,
2437
+ slotId: slot.Id,
2438
+ },
2439
+ message:
2440
+ `Collection chrome slot '${slot.Id}' on '${view.Name}' declares ` +
2441
+ `column '${column.Id}' with field '${column.FieldId}', but that field is not present in the presentation module.`,
2442
+ reason: `missing-collection-chrome-slot-column-field.${slot.Id}.${column.Id}.${column.FieldId}`,
2443
+ severity: 'error',
2444
+ slot,
2445
+ view: viewTrace,
2446
+ }))
2447
+ }
2448
+ }
2449
+
2450
+ return diagnostics
2451
+ }
2452
+
2453
+ function projectCollectionChromeSlotInterpretationDiagnostics(
2454
+ view: ViewDefinition,
2455
+ viewTrace: PresentationProjectionTraceView,
2456
+ slot: CollectionChromeSlotDefinition,
2457
+ collectionChromeSlotRendererKeys: readonly string[],
2458
+ ): readonly PresentationProjectionDiagnostic[] {
2459
+ const candidateRendererKeys = getCollectionChromeSlotRendererCandidateKeys(slot)
2460
+ if (hasCollectionChromeSlotRendererBinding(collectionChromeSlotRendererKeys, slot)) {
2461
+ return [
2462
+ createCollectionChromeSlotDiagnostic({
2463
+ category: 'local-interpretation',
2464
+ details: {
2465
+ candidateRendererKeys,
2466
+ slotId: slot.Id,
2467
+ slotKind: slot.Kind,
2468
+ slotPlacement: slot.Placement,
2469
+ },
2470
+ interpretation: {
2471
+ status: 'bound',
2472
+ target: 'collection-chrome-slot-renderer-registry',
2473
+ },
2474
+ message:
2475
+ `Collection chrome slot '${slot.Id}' on '${view.Name}' is bound ` +
2476
+ 'by the collection chrome slot renderer registry.',
2477
+ reason: `bound-collection-chrome-slot-renderer.${slot.Id}`,
2478
+ severity: 'info',
2479
+ slot,
2480
+ view: viewTrace,
2481
+ }),
2482
+ ]
2483
+ }
2484
+
2485
+ if (isCollectionChromeSlotKind(slot, collectionChromeSlotKinds.custom)) {
2486
+ return [
2487
+ createCollectionChromeSlotDiagnostic({
2488
+ category: 'missing-binding',
2489
+ details: {
2490
+ slotId: slot.Id,
2491
+ slotKind: slot.Kind,
2492
+ slotPlacement: slot.Placement,
2493
+ },
2494
+ interpretation: {
2495
+ status: 'unbound',
2496
+ target: 'projected-collection-runtime.chrome.custom-slot',
2497
+ },
2498
+ message:
2499
+ `Collection chrome slot '${slot.Id}' on '${view.Name}' is Custom, ` +
2500
+ 'but no frontend collection chrome-slot interpreter is registered for it.',
2501
+ reason: `unbound-collection-chrome-custom-slot.${slot.Id}`,
2502
+ severity: 'warning',
2503
+ slot,
2504
+ suggestedNextStep:
2505
+ 'Register a collection chrome-slot renderer for this custom slot or model it with a first-class slot kind.',
2506
+ view: viewTrace,
2507
+ }),
2508
+ ]
2509
+ }
2510
+
2511
+ if (
2512
+ isCollectionChromeSlotKind(slot, collectionChromeSlotKinds.pagination) &&
2513
+ !isCollectionChromeSlotPlacement(slot, collectionChromeSlotPlacements.footer)
2514
+ ) {
2515
+ return [
2516
+ createCollectionChromeSlotDiagnostic({
2517
+ category: 'missing-binding',
2518
+ details: {
2519
+ slotId: slot.Id,
2520
+ slotKind: slot.Kind,
2521
+ slotPlacement: slot.Placement,
2522
+ },
2523
+ interpretation: {
2524
+ status: 'unbound',
2525
+ target: 'projected-collection-runtime.chrome.pagination-slot',
2526
+ },
2527
+ message:
2528
+ `Collection chrome slot '${slot.Id}' on '${view.Name}' places pagination ` +
2529
+ 'outside the footer, but the current collection chrome interpreter only binds footer pagination.',
2530
+ reason: `unbound-collection-pagination-slot-placement.${slot.Id}`,
2531
+ severity: 'warning',
2532
+ slot,
2533
+ suggestedNextStep:
2534
+ 'Add a collection chrome-slot renderer for this pagination placement or use Footer placement.',
2535
+ view: viewTrace,
2536
+ }),
2537
+ ]
2538
+ }
2539
+
2540
+ if (isCollectionChromeSlotRendererExpected(slot)) {
2541
+ return [
2542
+ createCollectionChromeSlotDiagnostic({
2543
+ category: 'missing-binding',
2544
+ details: {
2545
+ candidateRendererKeys,
2546
+ slotId: slot.Id,
2547
+ slotKind: slot.Kind,
2548
+ slotPlacement: slot.Placement,
2549
+ },
2550
+ interpretation: {
2551
+ status: 'unbound',
2552
+ target: 'collection-chrome-slot-renderer-registry',
2553
+ },
2554
+ message:
2555
+ `Collection chrome slot '${slot.Id}' on '${view.Name}' has no ` +
2556
+ 'registered frontend renderer for its kind and placement.',
2557
+ reason: `unbound-collection-chrome-slot-renderer.${slot.Id}`,
2558
+ severity: 'warning',
2559
+ slot,
2560
+ suggestedNextStep:
2561
+ 'Register a collection chrome-slot renderer or add a generic interpreter for this slot kind.',
2562
+ view: viewTrace,
2563
+ }),
2564
+ ]
2565
+ }
2566
+
2567
+ return []
2568
+ }
2569
+
2570
+ function isCollectionChromeSlotRendererExpected(
2571
+ slot: CollectionChromeSlotDefinition,
2572
+ ) {
2573
+ return isCollectionChromeSlotKind(slot, collectionChromeSlotKinds.queryForm) ||
2574
+ isCollectionChromeSlotKind(slot, collectionChromeSlotKinds.pagination) ||
2575
+ isCollectionChromeSlotKind(slot, collectionChromeSlotKinds.selectionActions) ||
2576
+ isCollectionChromeSlotKind(slot, collectionChromeSlotKinds.rowActions) ||
2577
+ isCollectionChromeSlotKind(slot, collectionChromeSlotKinds.detail) ||
2578
+ isCollectionChromeSlotKind(slot, collectionChromeSlotKinds.summary) ||
2579
+ isCollectionChromeSlotKind(slot, collectionChromeSlotKinds.body)
2580
+ }
2581
+
2582
+ function createCollectionChromeSlotDiagnostic({
2583
+ category,
2584
+ details,
2585
+ interpretation,
2586
+ message,
2587
+ reason,
2588
+ severity,
2589
+ slot,
2590
+ suggestedNextStep,
2591
+ view,
2592
+ }: {
2593
+ readonly category?: PresentationProjectionDiagnostic['category']
2594
+ readonly details?: Readonly<Record<string, unknown>>
2595
+ readonly interpretation?: PresentationProjectionDiagnostic['interpretation']
2596
+ readonly message: string
2597
+ readonly reason: string
2598
+ readonly severity: PresentationProjectionDiagnostic['severity']
2599
+ readonly slot: CollectionChromeSlotDefinition
2600
+ readonly suggestedNextStep?: string
2601
+ readonly view: PresentationProjectionTraceView
2602
+ }) {
2603
+ return createViewDiagnostic({
2604
+ category,
2605
+ details: {
2606
+ collectionChromeSlotId: slot.Id,
2607
+ collectionChromeSlotKind: slot.Kind,
2608
+ collectionChromeSlotPlacement: slot.Placement,
2609
+ ...details,
2610
+ },
2611
+ interpretation,
2612
+ message,
2613
+ reason,
2614
+ severity,
2615
+ source: 'presentation-collection-chrome-coverage',
2616
+ suggestedNextStep,
2617
+ view,
2618
+ })
2619
+ }
2620
+
2621
+ function projectCollectionRowActionPredicateCoverageDiagnostics(
2622
+ rowAction: CollectionRowActionDefinition,
2623
+ view: ViewDefinition,
2624
+ viewTrace: PresentationProjectionTraceView,
2625
+ ): readonly PresentationProjectionDiagnostic[] {
2626
+ return [
2627
+ ...projectCollectionRowActionPredicateDiagnostic({
2628
+ predicate: rowAction.IsEnabled,
2629
+ predicateName: 'IsEnabled',
2630
+ rowAction,
2631
+ view,
2632
+ viewTrace,
2633
+ }),
2634
+ ...projectCollectionRowActionPredicateDiagnostic({
2635
+ predicate: rowAction.IsVisible,
2636
+ predicateName: 'IsVisible',
2637
+ rowAction,
2638
+ view,
2639
+ viewTrace,
2640
+ }),
2641
+ ]
2642
+ }
2643
+
2644
+ function projectCollectionSelectionActionPredicateCoverageDiagnostics(
2645
+ selectionAction: CollectionSelectionActionDefinition,
2646
+ view: ViewDefinition,
2647
+ viewTrace: PresentationProjectionTraceView,
2648
+ ): readonly PresentationProjectionDiagnostic[] {
2649
+ return [
2650
+ ...projectCollectionSelectionActionPredicateDiagnostic({
2651
+ predicate: selectionAction.IsEnabled,
2652
+ predicateName: 'IsEnabled',
2653
+ selectionAction,
2654
+ view,
2655
+ viewTrace,
2656
+ }),
2657
+ ...projectCollectionSelectionActionPredicateDiagnostic({
2658
+ predicate: selectionAction.IsVisible,
2659
+ predicateName: 'IsVisible',
2660
+ selectionAction,
2661
+ view,
2662
+ viewTrace,
2663
+ }),
2664
+ ]
2665
+ }
2666
+
2667
+ function projectCollectionRowActionPredicateDiagnostic({
2668
+ predicate,
2669
+ predicateName,
2670
+ rowAction,
2671
+ view,
2672
+ viewTrace,
2673
+ }: {
2674
+ readonly predicate: PresentationValueDefinition | null | undefined
2675
+ readonly predicateName: 'IsEnabled' | 'IsVisible'
2676
+ readonly rowAction: CollectionRowActionDefinition
2677
+ readonly view: ViewDefinition
2678
+ readonly viewTrace: PresentationProjectionTraceView
2679
+ }): readonly PresentationProjectionDiagnostic[] {
2680
+ if (!predicate || isInterpretableCollectionRowActionPredicate(predicate)) {
2681
+ return []
2682
+ }
2683
+
2684
+ return [
2685
+ createViewDiagnostic({
2686
+ category: 'missing-binding',
2687
+ details: {
2688
+ actionId: rowAction.ActionId,
2689
+ expression: predicate.Expression,
2690
+ field: predicate.Field,
2691
+ predicateKind: predicate.Kind,
2692
+ predicateName,
2693
+ rowActionId: rowAction.Id,
2694
+ stateId: predicate.StateId,
2695
+ },
2696
+ interpretation: {
2697
+ status: 'unbound',
2698
+ target: 'collection-row-action-predicate',
2699
+ },
2700
+ message:
2701
+ `Collection row action '${rowAction.Id}' on '${view.Name}' declares ` +
2702
+ `${predicateName}, but the frontend collection interpreter only binds literal and row-field predicates.`,
2703
+ reason: `unbound-collection-row-action-predicate.${rowAction.Id}.${predicateName}`,
2704
+ severity: 'warning',
2705
+ source: 'presentation-collection-coverage',
2706
+ suggestedNextStep:
2707
+ 'Bind this predicate through a target expression interpreter or express it as a literal/row-field predicate.',
2708
+ view: viewTrace,
2709
+ }),
2710
+ ]
2711
+ }
2712
+
2713
+ function projectCollectionSelectionActionPredicateDiagnostic({
2714
+ predicate,
2715
+ predicateName,
2716
+ selectionAction,
2717
+ view,
2718
+ viewTrace,
2719
+ }: {
2720
+ readonly predicate: PresentationValueDefinition | null | undefined
2721
+ readonly predicateName: 'IsEnabled' | 'IsVisible'
2722
+ readonly selectionAction: CollectionSelectionActionDefinition
2723
+ readonly view: ViewDefinition
2724
+ readonly viewTrace: PresentationProjectionTraceView
2725
+ }): readonly PresentationProjectionDiagnostic[] {
2726
+ if (!predicate || isInterpretableCollectionRowActionPredicate(predicate)) {
2727
+ return []
2728
+ }
2729
+
2730
+ return [
2731
+ createViewDiagnostic({
2732
+ category: 'missing-binding',
2733
+ details: {
2734
+ actionId: selectionAction.ActionId,
2735
+ expression: predicate.Expression,
2736
+ field: predicate.Field,
2737
+ predicateKind: predicate.Kind,
2738
+ predicateName,
2739
+ selectionActionId: selectionAction.Id,
2740
+ stateId: predicate.StateId,
2741
+ },
2742
+ interpretation: {
2743
+ status: 'unbound',
2744
+ target: 'collection-selection-action-predicate',
2745
+ },
2746
+ message:
2747
+ `Collection selection action '${selectionAction.Id}' on '${view.Name}' declares ` +
2748
+ `${predicateName}, but the frontend collection interpreter only binds literal and selected-row-field predicates.`,
2749
+ reason: `unbound-collection-selection-action-predicate.${selectionAction.Id}.${predicateName}`,
2750
+ severity: 'warning',
2751
+ source: 'presentation-collection-coverage',
2752
+ suggestedNextStep:
2753
+ 'Bind this predicate through a target expression interpreter or express it as a literal/selected-row-field predicate.',
2754
+ view: viewTrace,
2755
+ }),
2756
+ ]
2757
+ }
2758
+
2759
+ function isCollectionSelectionEnabled(selectionMode: unknown) {
2760
+ return selectionMode === collectionSelectionModes.single ||
2761
+ selectionMode === collectionSelectionModes.multiple ||
2762
+ String(selectionMode).toLocaleLowerCase() === 'single' ||
2763
+ String(selectionMode).toLocaleLowerCase() === 'multiple'
2764
+ }
2765
+
2766
+ function isInterpretableCollectionRowActionPredicate(predicate: PresentationValueDefinition) {
2767
+ return predicate.Kind === presentationValueKinds.literal ||
2768
+ predicate.Kind === presentationValueKinds.field ||
2769
+ String(predicate.Kind).toLocaleLowerCase() === 'literal' ||
2770
+ String(predicate.Kind).toLocaleLowerCase() === 'field'
2771
+ }
2772
+
2773
+ function isSelectedRowValueSelectionActionParameter(source: unknown) {
2774
+ return source === collectionSelectionActionParameterSources.selectedRowValue ||
2775
+ source === collectionSelectionActionParameterSources.selectedRowValueList ||
2776
+ String(source).toLocaleLowerCase() === 'selectedrowvalue' ||
2777
+ String(source).toLocaleLowerCase() === 'selectedrowvaluelist'
2778
+ }
2779
+
2780
+ function isCollectionDetailActivationUnsupported(detailActivation: unknown) {
2781
+ return detailActivation === collectionDetailActivations.none ||
2782
+ detailActivation === collectionDetailActivations.hover ||
2783
+ String(detailActivation).toLocaleLowerCase() === 'none' ||
2784
+ String(detailActivation).toLocaleLowerCase() === 'hover'
2785
+ }
2786
+
2787
+ function isCollectionDetailActivationNone(detailActivation: unknown) {
2788
+ return detailActivation === collectionDetailActivations.none ||
2789
+ String(detailActivation).toLocaleLowerCase() === 'none'
2790
+ }
2791
+
2792
+ function isCollectionDetailActivationSelection(detailActivation: unknown) {
2793
+ return detailActivation === collectionDetailActivations.selection ||
2794
+ String(detailActivation).toLocaleLowerCase() === 'selection'
2795
+ }
2796
+
2797
+ function isCollectionDetailActivationStateful(detailActivation: unknown) {
2798
+ return isCollectionDetailActivationSelection(detailActivation) ||
2799
+ detailActivation === collectionDetailActivations.rowActivation ||
2800
+ String(detailActivation).toLocaleLowerCase() === 'rowactivation'
2801
+ }
2802
+
2803
+ function isPrimaryCollectionRowAction(rowAction: CollectionRowActionDefinition) {
2804
+ return rowAction.Kind === collectionRowActionKinds.primary ||
2805
+ String(rowAction.Kind).toLocaleLowerCase() === 'primary'
2806
+ }
2807
+
2808
+ function isCollectionQueryDataSource(
2809
+ dataSource: PresentationModuleDefinition['DataSources'][number],
2810
+ ) {
2811
+ return dataSource.Kind === dataSourceKinds.collectionQuery ||
2812
+ String(dataSource.Kind).toLocaleLowerCase() === 'collectionquery'
2813
+ }
2814
+
2815
+ function resolveViewProjectedFieldRefs(view: ViewDefinition) {
2816
+ const refs = getPresentationViewProjectedFieldRefs(view)
2817
+ const seen = new Set<string>()
2818
+ return refs.filter((ref) => {
2819
+ const key = `${ref.source}:${ref.fieldId}`
2820
+ if (seen.has(key)) {
2821
+ return false
2822
+ }
2823
+
2824
+ seen.add(key)
2825
+ return true
2826
+ })
2827
+ }
2828
+
2829
+ function projectActionCoverageDiagnostics(
2830
+ module: PresentationModuleDefinition,
2831
+ view: ViewDefinition,
2832
+ viewTrace: PresentationProjectionTraceView,
2833
+ ): readonly PresentationProjectionDiagnostic[] {
2834
+ return getPresentationViewProjectedActions(view).flatMap((actionRef) =>
2835
+ projectActionPlacementCoverageDiagnostics(module, actionRef.placement, viewTrace, {
2836
+ actionKind: actionRef.kind,
2837
+ actionContextKind: actionRef.contextKind,
2838
+ actionSource: actionRef.source,
2839
+ collectionSlotId: actionRef.slotId ?? null,
2840
+ }),
2841
+ )
2842
+ }
2843
+
2844
+ function projectActionPlacementCoverageDiagnostics(
2845
+ module: PresentationModuleDefinition,
2846
+ placement: ActionPlacementDefinition,
2847
+ viewTrace: PresentationProjectionTraceView,
2848
+ details?: Readonly<Record<string, unknown>>,
2849
+ ): readonly PresentationProjectionDiagnostic[] {
2850
+ const action = findPresentationAction<ActionDefinition>(module, placement.ActionId)
2851
+ if (!action) {
2852
+ return [
2853
+ createViewDiagnostic({
2854
+ details: {
2855
+ actionId: placement.ActionId,
2856
+ ...details,
2857
+ placementRegion: placement.Region,
2858
+ },
2859
+ message: `View '${viewTrace.name}' places action '${placement.ActionId}', but the action is not present in the action catalog.`,
2860
+ reason: `missing-action.${placement.ActionId}`,
2861
+ severity: 'error',
2862
+ source: 'presentation-action-coverage',
2863
+ view: viewTrace,
2864
+ }),
2865
+ ]
2866
+ }
2867
+
2868
+ const diagnostics: PresentationProjectionDiagnostic[] = []
2869
+ const flowId = action.Preparation?.FlowId
2870
+ if (flowId && !findPresentationFlow(module, flowId)) {
2871
+ diagnostics.push(createViewDiagnostic({
2872
+ details: {
2873
+ actionId: action.Id,
2874
+ flowId,
2875
+ },
2876
+ message: `Action '${action.Name}' references flow '${flowId}', but that flow is not present in the presentation module.`,
2877
+ reason: `missing-action-flow.${action.Id}.${flowId}`,
2878
+ severity: 'error',
2879
+ source: 'presentation-action-coverage',
2880
+ view: viewTrace,
2881
+ }))
2882
+ }
2883
+
2884
+ for (const request of action.EndpointRequests) {
2885
+ if (
2886
+ request.DataSourceId &&
2887
+ !findPresentationDataSource(module, request.DataSourceId)
2888
+ ) {
2889
+ diagnostics.push(createViewDiagnostic({
2890
+ details: {
2891
+ actionId: action.Id,
2892
+ dataSourceId: request.DataSourceId,
2893
+ },
2894
+ message: `Action '${action.Name}' references data source '${request.DataSourceId}', but that data source is not present in the presentation module.`,
2895
+ reason: `missing-action-data-source.${action.Id}.${request.DataSourceId}`,
2896
+ severity: 'error',
2897
+ source: 'presentation-action-coverage',
2898
+ view: viewTrace,
2899
+ }))
2900
+ }
2901
+ }
2902
+
2903
+ if (
2904
+ isLocalStateAction(action) &&
2905
+ !action.Semantics &&
2906
+ !action.Annotations.some((annotation) =>
2907
+ String(annotation.Name).toLocaleLowerCase().includes('frontend'))
2908
+ ) {
2909
+ diagnostics.push(createViewDiagnostic({
2910
+ category: 'local-interpretation',
2911
+ details: {
2912
+ actionId: action.Id,
2913
+ bindingKind: action.Binding.Kind,
2914
+ },
2915
+ interpretation: {
2916
+ status: 'locally-interpreted',
2917
+ target: 'local-action-runtime',
2918
+ },
2919
+ message: `Local action '${action.Name}' has no first-class semantics; it requires an implicit frontend binding.`,
2920
+ reason: `local-action-without-semantics.${action.Id}`,
2921
+ severity: 'info',
2922
+ source: 'presentation-action-coverage',
2923
+ suggestedNextStep:
2924
+ 'Declare action semantics in the IR and leave only the component-specific interpretation in the frontend adapter.',
2925
+ view: viewTrace,
2926
+ }))
2927
+ }
2928
+
2929
+ return diagnostics
2930
+ }
2931
+
2932
+ function projectInputFormCoverageDiagnostics(
2933
+ module: PresentationModuleDefinition,
2934
+ view: ViewDefinition,
2935
+ viewTrace: PresentationProjectionTraceView,
2936
+ queryFormStateAdapterIds: readonly string[],
2937
+ ): readonly PresentationProjectionDiagnostic[] {
2938
+ const inputFormRefs = resolveViewInputFormRefs(module, view)
2939
+ return inputFormRefs.flatMap(({ form, formId, queryFormId, source }) => {
2940
+ if (!form) {
2941
+ return [
2942
+ createViewDiagnostic({
2943
+ details: { formId, source },
2944
+ message: `View '${view.Name}' references input form '${formId}', but that form is not present in the presentation module.`,
2945
+ reason: `missing-input-form.${formId}`,
2946
+ severity: 'error',
2947
+ source: 'presentation-input-form-coverage',
2948
+ view: viewTrace,
2949
+ }),
2950
+ ]
2951
+ }
2952
+
2953
+ return [
2954
+ ...projectInputFormFieldCoverageDiagnostics(module, form, viewTrace),
2955
+ ...projectInputFormActionCoverageDiagnostics(module, form, viewTrace),
2956
+ ...projectQueryFormStateAdapterCoverageDiagnostics({
2957
+ queryFormId,
2958
+ queryFormStateAdapterIds,
2959
+ viewTrace,
2960
+ }),
2961
+ ]
2962
+ })
2963
+ }
2964
+
2965
+ function resolveViewInputFormRefs(
2966
+ module: PresentationModuleDefinition,
2967
+ view: ViewDefinition,
2968
+ ): readonly {
2969
+ readonly form: InputFormDefinition | null
2970
+ readonly formId: string
2971
+ readonly queryFormId: string | null
2972
+ readonly source: string
2973
+ }[] {
2974
+ const directInputFormId = view.Subject.InputFormId
2975
+ if (directInputFormId) {
2976
+ return [{
2977
+ form: findPresentationInputForm<InputFormDefinition>(module, directInputFormId),
2978
+ formId: directInputFormId,
2979
+ queryFormId: null,
2980
+ source: 'view-subject',
2981
+ }]
2982
+ }
2983
+
2984
+ const queryFormRefs = [
2985
+ ...(view.Subject.QueryFormId
2986
+ ? [{ queryFormId: view.Subject.QueryFormId, source: 'view-subject' }]
2987
+ : []),
2988
+ ...createCollectionChromeRuntime(view.Collection)
2989
+ .findSlots(collectionChromeSlotKinds.queryForm)
2990
+ .flatMap((slot) =>
2991
+ slot.QueryFormId
2992
+ ? [{
2993
+ queryFormId: slot.QueryFormId,
2994
+ source: `collection-chrome-slot:${slot.Id}`,
2995
+ }]
2996
+ : []),
2997
+ ]
2998
+ const seenQueryFormIds = new Set<string>()
2999
+
3000
+ return queryFormRefs.flatMap(({ queryFormId, source }) => {
3001
+ if (seenQueryFormIds.has(queryFormId)) {
3002
+ return []
3003
+ }
3004
+ seenQueryFormIds.add(queryFormId)
3005
+
3006
+ const queryForm = findPresentationQueryForm<QueryFormDefinition>(module, queryFormId)
3007
+ if (!queryForm) {
3008
+ return [{
3009
+ form: null,
3010
+ formId: queryFormId,
3011
+ queryFormId,
3012
+ source,
3013
+ }]
3014
+ }
3015
+
3016
+ return [{
3017
+ form: findPresentationInputForm<InputFormDefinition>(module, queryForm.FormId),
3018
+ formId: queryForm.FormId,
3019
+ queryFormId,
3020
+ source,
3021
+ }]
3022
+ })
3023
+ }
3024
+
3025
+ function projectInputFormFieldCoverageDiagnostics(
3026
+ module: PresentationModuleDefinition,
3027
+ form: InputFormDefinition,
3028
+ viewTrace: PresentationProjectionTraceView,
3029
+ ): readonly PresentationProjectionDiagnostic[] {
3030
+ const inputFieldIds = new Set(form.Fields.map((field) => field.Id))
3031
+ const diagnostics = form.Fields.flatMap((field) => {
3032
+ const presentationField = findPresentationField(module, field.FieldId)
3033
+ const choiceDataSourceId = field.ChoiceSource?.DataSourceId
3034
+ return [
3035
+ ...(presentationField
3036
+ ? []
3037
+ : [
3038
+ createViewDiagnostic({
3039
+ details: {
3040
+ fieldId: field.FieldId,
3041
+ formId: form.Id,
3042
+ inputFieldId: field.Id,
3043
+ },
3044
+ message: `Input form '${form.Name}' references field '${field.FieldId}', but that field is not present in the presentation module.`,
3045
+ reason: `missing-input-form-field.${form.Id}.${field.Id}`,
3046
+ severity: 'error' as const,
3047
+ source: 'presentation-input-form-coverage',
3048
+ view: viewTrace,
3049
+ }),
3050
+ ]),
3051
+ ...(choiceDataSourceId && !findPresentationDataSource(module, choiceDataSourceId)
3052
+ ? [
3053
+ createViewDiagnostic({
3054
+ details: {
3055
+ choiceDataSourceId,
3056
+ formId: form.Id,
3057
+ inputFieldId: field.Id,
3058
+ },
3059
+ message: `Input form '${form.Name}' field '${field.Name}' references choice data source '${choiceDataSourceId}', but that data source is not present in the presentation module.`,
3060
+ reason: `missing-input-form-choice-data-source.${form.Id}.${field.Id}`,
3061
+ severity: 'error' as const,
3062
+ source: 'presentation-input-form-coverage',
3063
+ view: viewTrace,
3064
+ }),
3065
+ ]
3066
+ : []),
3067
+ ]
3068
+ })
3069
+
3070
+ return [
3071
+ ...diagnostics,
3072
+ ...form.Groups.flatMap((group) =>
3073
+ group.FieldIds.filter((fieldId) => !inputFieldIds.has(fieldId)).map((fieldId) =>
3074
+ createViewDiagnostic({
3075
+ details: {
3076
+ formId: form.Id,
3077
+ groupId: group.Id,
3078
+ inputFieldId: fieldId,
3079
+ },
3080
+ message: `Input form '${form.Name}' group '${group.Name}' references field '${fieldId}', but that input field is not present in the form.`,
3081
+ reason: `missing-input-form-group-field.${form.Id}.${group.Id}.${fieldId}`,
3082
+ severity: 'error',
3083
+ source: 'presentation-input-form-coverage',
3084
+ view: viewTrace,
3085
+ }),
3086
+ ),
3087
+ ),
3088
+ ]
3089
+ }
3090
+
3091
+ function projectInputFormActionCoverageDiagnostics(
3092
+ module: PresentationModuleDefinition,
3093
+ form: InputFormDefinition,
3094
+ viewTrace: PresentationProjectionTraceView,
3095
+ ): readonly PresentationProjectionDiagnostic[] {
3096
+ return form.Actions.flatMap((placement) =>
3097
+ findPresentationAction(module, placement.ActionId)
3098
+ ? []
3099
+ : [
3100
+ createViewDiagnostic({
3101
+ details: {
3102
+ actionId: placement.ActionId,
3103
+ formId: form.Id,
3104
+ placementRegion: placement.Region,
3105
+ },
3106
+ message: `Input form '${form.Name}' places action '${placement.ActionId}', but the action is not present in the action catalog.`,
3107
+ reason: `missing-input-form-action.${form.Id}.${placement.ActionId}`,
3108
+ severity: 'error',
3109
+ source: 'presentation-input-form-coverage',
3110
+ view: viewTrace,
3111
+ }),
3112
+ ],
3113
+ )
3114
+ }
3115
+
3116
+ function projectQueryFormStateAdapterCoverageDiagnostics({
3117
+ queryFormId,
3118
+ queryFormStateAdapterIds,
3119
+ viewTrace,
3120
+ }: {
3121
+ readonly queryFormId: string | null
3122
+ readonly queryFormStateAdapterIds: readonly string[]
3123
+ readonly viewTrace: PresentationProjectionTraceView
3124
+ }): readonly PresentationProjectionDiagnostic[] {
3125
+ if (!queryFormId || queryFormStateAdapterIds.includes(queryFormId)) {
3126
+ return []
3127
+ }
3128
+
3129
+ return [
3130
+ createViewDiagnostic({
3131
+ category: 'missing-binding',
3132
+ details: { queryFormId },
3133
+ interpretation: {
3134
+ status: 'unbound',
3135
+ target: 'query-form-state-adapter',
3136
+ },
3137
+ message:
3138
+ `Query form '${queryFormId}' is projected into view '${viewTrace.name}', ` +
3139
+ 'but no frontend state adapter is registered for it.',
3140
+ reason: `missing-query-form-state-adapter.${queryFormId}`,
3141
+ severity: 'warning',
3142
+ source: 'presentation-query-form-coverage',
3143
+ suggestedNextStep:
3144
+ 'Register a query-form state adapter or project a generic relation-query state runtime for this form.',
3145
+ view: viewTrace,
3146
+ }),
3147
+ ]
3148
+ }
3149
+
3150
+ function isLocalStateAction(action: ActionDefinition) {
3151
+ return (
3152
+ action.Kind === actionKinds.localStateAction ||
3153
+ String(action.Kind).toLocaleLowerCase() === 'localstateaction' ||
3154
+ action.Binding.Kind === presentationBindingKinds.localState ||
3155
+ String(action.Binding.Kind).toLocaleLowerCase() === 'localstate'
3156
+ )
3157
+ }
3158
+
3159
+ function isUnboundDataSourceBinding(binding: PresentationDataSourceBinding) {
3160
+ const authorization = binding.authorization
3161
+ const blockedLabel =
3162
+ authorization.kind === 'required'
3163
+ ? authorization.blockedLabel ?? binding.blockedLabel
3164
+ : binding.blockedLabel
3165
+
3166
+ return (
3167
+ binding.kind === presentationDataSourceBindingKinds.localValue &&
3168
+ authorization.kind === 'required' &&
3169
+ authorization.isAuthorized === false &&
3170
+ Boolean(blockedLabel?.startsWith('No frontend binding is registered'))
3171
+ )
3172
+ }
3173
+
3174
+ function createViewDiagnostic({
3175
+ category,
3176
+ details,
3177
+ interpretation,
3178
+ message,
3179
+ reason,
3180
+ severity,
3181
+ source,
3182
+ suggestedNextStep,
3183
+ view,
3184
+ }: {
3185
+ readonly category?: PresentationProjectionDiagnostic['category']
3186
+ readonly details?: Readonly<Record<string, unknown>>
3187
+ readonly interpretation?: PresentationProjectionDiagnostic['interpretation']
3188
+ readonly message: string
3189
+ readonly reason: string
3190
+ readonly severity: PresentationProjectionDiagnostic['severity']
3191
+ readonly source: string
3192
+ readonly suggestedNextStep?: string
3193
+ readonly view: PresentationProjectionTraceView
3194
+ }) {
3195
+ return createDiagnostic({
3196
+ category,
3197
+ details,
3198
+ id: `view.${view.id}.${reason}`,
3199
+ interpretation,
3200
+ message,
3201
+ severity,
3202
+ source,
3203
+ subject: {
3204
+ id: view.id,
3205
+ kind: 'view',
3206
+ name: view.name,
3207
+ },
3208
+ suggestedNextStep,
3209
+ })
3210
+ }
3211
+
3212
+ function createDiagnostic({
3213
+ category,
3214
+ details,
3215
+ id,
3216
+ interpretation,
3217
+ message,
3218
+ severity,
3219
+ source,
3220
+ subject,
3221
+ suggestedNextStep,
3222
+ }: PresentationProjectionDiagnostic): PresentationProjectionDiagnostic {
3223
+ return {
3224
+ category,
3225
+ details,
3226
+ id,
3227
+ interpretation,
3228
+ message,
3229
+ severity,
3230
+ source,
3231
+ subject,
3232
+ suggestedNextStep,
3233
+ }
3234
+ }
3235
+
3236
+ function normalizeCoverageModule(
3237
+ module: PresentationProjectionCoverageModule,
3238
+ ): PresentationModuleDefinition {
3239
+ return {
3240
+ Actions: [...(module.Actions ?? [])],
3241
+ Annotations: [],
3242
+ DataSources: [...(module.DataSources ?? [])],
3243
+ DesignSystems: [],
3244
+ Expressions: [],
3245
+ Fields: [...(module.Fields ?? [])],
3246
+ Flows: [...(module.Flows ?? [])],
3247
+ Id: 'projection-coverage',
3248
+ InputForms: [...(module.InputForms ?? [])],
3249
+ Name: 'Projection Coverage',
3250
+ Navigation: [],
3251
+ QueryForms: [...(module.QueryForms ?? [])],
3252
+ Targets: [],
3253
+ Version: null,
3254
+ Views: [...module.Views],
3255
+ Workspaces: [],
3256
+ }
3257
+ }