@happyvertical/smrt-content 0.30.0

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 (291) hide show
  1. package/AGENTS.md +194 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +634 -0
  5. package/dist/__smrt-register__.d.ts +2 -0
  6. package/dist/__smrt-register__.d.ts.map +1 -0
  7. package/dist/asset-associable.d.ts +115 -0
  8. package/dist/asset-associable.d.ts.map +1 -0
  9. package/dist/body-format.d.ts +29 -0
  10. package/dist/body-format.d.ts.map +1 -0
  11. package/dist/body-format.js +604 -0
  12. package/dist/body-format.js.map +1 -0
  13. package/dist/content-asset.d.ts +17 -0
  14. package/dist/content-asset.d.ts.map +1 -0
  15. package/dist/content-assets.d.ts +10 -0
  16. package/dist/content-assets.d.ts.map +1 -0
  17. package/dist/content-chat-handlers.d.ts +115 -0
  18. package/dist/content-chat-handlers.d.ts.map +1 -0
  19. package/dist/content-chat-prompts.d.ts +3 -0
  20. package/dist/content-chat-prompts.d.ts.map +1 -0
  21. package/dist/content-chat-session.d.ts +26 -0
  22. package/dist/content-chat-session.d.ts.map +1 -0
  23. package/dist/content-contribution-attachment.d.ts +42 -0
  24. package/dist/content-contribution-attachment.d.ts.map +1 -0
  25. package/dist/content-contribution-attachments.d.ts +8 -0
  26. package/dist/content-contribution-attachments.d.ts.map +1 -0
  27. package/dist/content-contribution-config.d.ts +84 -0
  28. package/dist/content-contribution-config.d.ts.map +1 -0
  29. package/dist/content-contribution-revision.d.ts +38 -0
  30. package/dist/content-contribution-revision.d.ts.map +1 -0
  31. package/dist/content-contribution-revisions.d.ts +8 -0
  32. package/dist/content-contribution-revisions.d.ts.map +1 -0
  33. package/dist/content-contribution-type.d.ts +51 -0
  34. package/dist/content-contribution-type.d.ts.map +1 -0
  35. package/dist/content-contribution-types.d.ts +7 -0
  36. package/dist/content-contribution-types.d.ts.map +1 -0
  37. package/dist/content-contribution.d.ts +161 -0
  38. package/dist/content-contribution.d.ts.map +1 -0
  39. package/dist/content-contributions.d.ts +53 -0
  40. package/dist/content-contributions.d.ts.map +1 -0
  41. package/dist/content-contributor.d.ts +30 -0
  42. package/dist/content-contributor.d.ts.map +1 -0
  43. package/dist/content-contributors.d.ts +13 -0
  44. package/dist/content-contributors.d.ts.map +1 -0
  45. package/dist/content-correction.d.ts +39 -0
  46. package/dist/content-correction.d.ts.map +1 -0
  47. package/dist/content-corrections.d.ts +9 -0
  48. package/dist/content-corrections.d.ts.map +1 -0
  49. package/dist/content-editor-assistant.d.ts +68 -0
  50. package/dist/content-editor-assistant.d.ts.map +1 -0
  51. package/dist/content-editor-assistant.js +97 -0
  52. package/dist/content-editor-assistant.js.map +1 -0
  53. package/dist/content-feed-parser.d.ts +19 -0
  54. package/dist/content-feed-parser.d.ts.map +1 -0
  55. package/dist/content-feed-source.d.ts +52 -0
  56. package/dist/content-feed-source.d.ts.map +1 -0
  57. package/dist/content-feed-sources.d.ts +11 -0
  58. package/dist/content-feed-sources.d.ts.map +1 -0
  59. package/dist/content-feed-sync.d.ts +23 -0
  60. package/dist/content-feed-sync.d.ts.map +1 -0
  61. package/dist/content-governance-assignment.d.ts +42 -0
  62. package/dist/content-governance-assignment.d.ts.map +1 -0
  63. package/dist/content-governance-assignments.d.ts +11 -0
  64. package/dist/content-governance-assignments.d.ts.map +1 -0
  65. package/dist/content-governance-policies.d.ts +7 -0
  66. package/dist/content-governance-policies.d.ts.map +1 -0
  67. package/dist/content-governance-policy.d.ts +29 -0
  68. package/dist/content-governance-policy.d.ts.map +1 -0
  69. package/dist/content-governance-profile.d.ts +31 -0
  70. package/dist/content-governance-profile.d.ts.map +1 -0
  71. package/dist/content-governance-profiles.d.ts +7 -0
  72. package/dist/content-governance-profiles.d.ts.map +1 -0
  73. package/dist/content-governance.d.ts +188 -0
  74. package/dist/content-governance.d.ts.map +1 -0
  75. package/dist/content-prompts.d.ts +10 -0
  76. package/dist/content-prompts.d.ts.map +1 -0
  77. package/dist/content-reference.d.ts +17 -0
  78. package/dist/content-reference.d.ts.map +1 -0
  79. package/dist/content-references.d.ts +55 -0
  80. package/dist/content-references.d.ts.map +1 -0
  81. package/dist/content-review.d.ts +34 -0
  82. package/dist/content-review.d.ts.map +1 -0
  83. package/dist/content-reviews.d.ts +21 -0
  84. package/dist/content-reviews.d.ts.map +1 -0
  85. package/dist/content-transparency.d.ts +72 -0
  86. package/dist/content-transparency.d.ts.map +1 -0
  87. package/dist/content-types.d.ts +51 -0
  88. package/dist/content-types.d.ts.map +1 -0
  89. package/dist/content-version.d.ts +38 -0
  90. package/dist/content-version.d.ts.map +1 -0
  91. package/dist/content-versions.d.ts +16 -0
  92. package/dist/content-versions.d.ts.map +1 -0
  93. package/dist/content.d.ts +736 -0
  94. package/dist/content.d.ts.map +1 -0
  95. package/dist/contents.d.ts +292 -0
  96. package/dist/contents.d.ts.map +1 -0
  97. package/dist/database-utils.d.ts +3 -0
  98. package/dist/database-utils.d.ts.map +1 -0
  99. package/dist/index.d.ts +78 -0
  100. package/dist/index.d.ts.map +1 -0
  101. package/dist/index.js +11602 -0
  102. package/dist/index.js.map +1 -0
  103. package/dist/manifest.json +12308 -0
  104. package/dist/mock-smrt-client.d.ts +493 -0
  105. package/dist/mock-smrt-client.d.ts.map +1 -0
  106. package/dist/mock-smrt-client.js +390 -0
  107. package/dist/mock-smrt-client.js.map +1 -0
  108. package/dist/playground.d.ts +2 -0
  109. package/dist/playground.d.ts.map +1 -0
  110. package/dist/playground.js +454 -0
  111. package/dist/playground.js.map +1 -0
  112. package/dist/publish-readiness.d.ts +30 -0
  113. package/dist/publish-readiness.d.ts.map +1 -0
  114. package/dist/publish-readiness.js +74 -0
  115. package/dist/publish-readiness.js.map +1 -0
  116. package/dist/safe-remote-url.d.ts +52 -0
  117. package/dist/safe-remote-url.d.ts.map +1 -0
  118. package/dist/serialization.d.ts +78 -0
  119. package/dist/serialization.d.ts.map +1 -0
  120. package/dist/smrt-knowledge.json +6130 -0
  121. package/dist/svelte/api.d.ts +3 -0
  122. package/dist/svelte/api.d.ts.map +1 -0
  123. package/dist/svelte/api.js +10 -0
  124. package/dist/svelte/components/ArticleCard.svelte +159 -0
  125. package/dist/svelte/components/ArticleCard.svelte.d.ts +17 -0
  126. package/dist/svelte/components/ArticleCard.svelte.d.ts.map +1 -0
  127. package/dist/svelte/components/ArticleList.svelte +75 -0
  128. package/dist/svelte/components/ArticleList.svelte.d.ts +21 -0
  129. package/dist/svelte/components/ArticleList.svelte.d.ts.map +1 -0
  130. package/dist/svelte/components/ContentAgentChat.svelte +652 -0
  131. package/dist/svelte/components/ContentAgentChat.svelte.d.ts +17 -0
  132. package/dist/svelte/components/ContentAgentChat.svelte.d.ts.map +1 -0
  133. package/dist/svelte/components/ContentBodyEditor.svelte +1446 -0
  134. package/dist/svelte/components/ContentBodyEditor.svelte.d.ts +25 -0
  135. package/dist/svelte/components/ContentBodyEditor.svelte.d.ts.map +1 -0
  136. package/dist/svelte/components/ContentBodyRenderer.svelte +152 -0
  137. package/dist/svelte/components/ContentBodyRenderer.svelte.d.ts +10 -0
  138. package/dist/svelte/components/ContentBodyRenderer.svelte.d.ts.map +1 -0
  139. package/dist/svelte/components/ContentClaimAuditTool.svelte +441 -0
  140. package/dist/svelte/components/ContentClaimAuditTool.svelte.d.ts +12 -0
  141. package/dist/svelte/components/ContentClaimAuditTool.svelte.d.ts.map +1 -0
  142. package/dist/svelte/components/ContentContributionForm.svelte +226 -0
  143. package/dist/svelte/components/ContentContributionForm.svelte.d.ts +23 -0
  144. package/dist/svelte/components/ContentContributionForm.svelte.d.ts.map +1 -0
  145. package/dist/svelte/components/ContentContributionInbox.svelte +322 -0
  146. package/dist/svelte/components/ContentContributionInbox.svelte.d.ts +22 -0
  147. package/dist/svelte/components/ContentContributionInbox.svelte.d.ts.map +1 -0
  148. package/dist/svelte/components/ContentContributionPortal.svelte +182 -0
  149. package/dist/svelte/components/ContentContributionPortal.svelte.d.ts +12 -0
  150. package/dist/svelte/components/ContentContributionPortal.svelte.d.ts.map +1 -0
  151. package/dist/svelte/components/ContentContributionTypeManager.svelte +281 -0
  152. package/dist/svelte/components/ContentContributionTypeManager.svelte.d.ts +10 -0
  153. package/dist/svelte/components/ContentContributionTypeManager.svelte.d.ts.map +1 -0
  154. package/dist/svelte/components/ContentContributorManager.svelte +140 -0
  155. package/dist/svelte/components/ContentContributorManager.svelte.d.ts +10 -0
  156. package/dist/svelte/components/ContentContributorManager.svelte.d.ts.map +1 -0
  157. package/dist/svelte/components/ContentCorrectionsTool.svelte +361 -0
  158. package/dist/svelte/components/ContentCorrectionsTool.svelte.d.ts +11 -0
  159. package/dist/svelte/components/ContentCorrectionsTool.svelte.d.ts.map +1 -0
  160. package/dist/svelte/components/ContentEditor.svelte +2166 -0
  161. package/dist/svelte/components/ContentEditor.svelte.d.ts +26 -0
  162. package/dist/svelte/components/ContentEditor.svelte.d.ts.map +1 -0
  163. package/dist/svelte/components/ContentGovernanceAssignmentEditor.svelte +199 -0
  164. package/dist/svelte/components/ContentGovernanceAssignmentEditor.svelte.d.ts +11 -0
  165. package/dist/svelte/components/ContentGovernanceAssignmentEditor.svelte.d.ts.map +1 -0
  166. package/dist/svelte/components/ContentGovernanceManager.svelte +340 -0
  167. package/dist/svelte/components/ContentGovernanceManager.svelte.d.ts +11 -0
  168. package/dist/svelte/components/ContentGovernanceManager.svelte.d.ts.map +1 -0
  169. package/dist/svelte/components/ContentGovernancePanel.svelte +2244 -0
  170. package/dist/svelte/components/ContentGovernancePanel.svelte.d.ts +26 -0
  171. package/dist/svelte/components/ContentGovernancePanel.svelte.d.ts.map +1 -0
  172. package/dist/svelte/components/ContentGovernancePolicyEditor.svelte +110 -0
  173. package/dist/svelte/components/ContentGovernancePolicyEditor.svelte.d.ts +10 -0
  174. package/dist/svelte/components/ContentGovernancePolicyEditor.svelte.d.ts.map +1 -0
  175. package/dist/svelte/components/ContentGovernanceProfileEditor.svelte +185 -0
  176. package/dist/svelte/components/ContentGovernanceProfileEditor.svelte.d.ts +11 -0
  177. package/dist/svelte/components/ContentGovernanceProfileEditor.svelte.d.ts.map +1 -0
  178. package/dist/svelte/components/ContentGovernanceTool.svelte +56 -0
  179. package/dist/svelte/components/ContentGovernanceTool.svelte.d.ts +13 -0
  180. package/dist/svelte/components/ContentGovernanceTool.svelte.d.ts.map +1 -0
  181. package/dist/svelte/components/ContentImageBrowser.svelte +243 -0
  182. package/dist/svelte/components/ContentImageBrowser.svelte.d.ts +18 -0
  183. package/dist/svelte/components/ContentImageBrowser.svelte.d.ts.map +1 -0
  184. package/dist/svelte/components/ContentImageChooser.svelte +134 -0
  185. package/dist/svelte/components/ContentImageChooser.svelte.d.ts +11 -0
  186. package/dist/svelte/components/ContentImageChooser.svelte.d.ts.map +1 -0
  187. package/dist/svelte/components/ContentList.svelte +906 -0
  188. package/dist/svelte/components/ContentList.svelte.d.ts +16 -0
  189. package/dist/svelte/components/ContentList.svelte.d.ts.map +1 -0
  190. package/dist/svelte/components/ContentMetadataFields.svelte +107 -0
  191. package/dist/svelte/components/ContentMetadataFields.svelte.d.ts +8 -0
  192. package/dist/svelte/components/ContentMetadataFields.svelte.d.ts.map +1 -0
  193. package/dist/svelte/components/ContentReferencesPanel.svelte +221 -0
  194. package/dist/svelte/components/ContentReferencesPanel.svelte.d.ts +20 -0
  195. package/dist/svelte/components/ContentReferencesPanel.svelte.d.ts.map +1 -0
  196. package/dist/svelte/components/ContentReviewStatusTray.svelte +151 -0
  197. package/dist/svelte/components/ContentReviewStatusTray.svelte.d.ts +20 -0
  198. package/dist/svelte/components/ContentReviewStatusTray.svelte.d.ts.map +1 -0
  199. package/dist/svelte/components/ContentStatusFields.svelte +85 -0
  200. package/dist/svelte/components/ContentStatusFields.svelte.d.ts +8 -0
  201. package/dist/svelte/components/ContentStatusFields.svelte.d.ts.map +1 -0
  202. package/dist/svelte/components/ContentTitleField.svelte +54 -0
  203. package/dist/svelte/components/ContentTitleField.svelte.d.ts +10 -0
  204. package/dist/svelte/components/ContentTitleField.svelte.d.ts.map +1 -0
  205. package/dist/svelte/components/ContentTransparencyReport.svelte +322 -0
  206. package/dist/svelte/components/ContentTransparencyReport.svelte.d.ts +10 -0
  207. package/dist/svelte/components/ContentTransparencyReport.svelte.d.ts.map +1 -0
  208. package/dist/svelte/components/ContentTransparencyTool.svelte +314 -0
  209. package/dist/svelte/components/ContentTransparencyTool.svelte.d.ts +10 -0
  210. package/dist/svelte/components/ContentTransparencyTool.svelte.d.ts.map +1 -0
  211. package/dist/svelte/components/ContentVersionsTool.svelte +291 -0
  212. package/dist/svelte/components/ContentVersionsTool.svelte.d.ts +10 -0
  213. package/dist/svelte/components/ContentVersionsTool.svelte.d.ts.map +1 -0
  214. package/dist/svelte/components/GovernedContentEditor.svelte +409 -0
  215. package/dist/svelte/components/GovernedContentEditor.svelte.d.ts +35 -0
  216. package/dist/svelte/components/GovernedContentEditor.svelte.d.ts.map +1 -0
  217. package/dist/svelte/components/ImageThumbnail.cache.d.ts +14 -0
  218. package/dist/svelte/components/ImageThumbnail.cache.d.ts.map +1 -0
  219. package/dist/svelte/components/ImageThumbnail.cache.js +36 -0
  220. package/dist/svelte/components/ImageThumbnail.svelte +159 -0
  221. package/dist/svelte/components/ImageThumbnail.svelte.d.ts +8 -0
  222. package/dist/svelte/components/ImageThumbnail.svelte.d.ts.map +1 -0
  223. package/dist/svelte/components/Markdown.svelte +125 -0
  224. package/dist/svelte/components/Markdown.svelte.d.ts +11 -0
  225. package/dist/svelte/components/Markdown.svelte.d.ts.map +1 -0
  226. package/dist/svelte/content-editor-form.d.ts +63 -0
  227. package/dist/svelte/content-editor-form.d.ts.map +1 -0
  228. package/dist/svelte/content-editor-form.js +94 -0
  229. package/dist/svelte/content-editor-media.d.ts +12 -0
  230. package/dist/svelte/content-editor-media.d.ts.map +1 -0
  231. package/dist/svelte/content-editor-media.js +84 -0
  232. package/dist/svelte/content-editor-state.svelte.d.ts +35 -0
  233. package/dist/svelte/content-editor-state.svelte.d.ts.map +1 -0
  234. package/dist/svelte/content-editor-state.svelte.js +141 -0
  235. package/dist/svelte/governance-manager-client.d.ts +22 -0
  236. package/dist/svelte/governance-manager-client.d.ts.map +1 -0
  237. package/dist/svelte/governance-manager-client.js +1 -0
  238. package/dist/svelte/i18n.contribution.d.ts +57 -0
  239. package/dist/svelte/i18n.contribution.d.ts.map +1 -0
  240. package/dist/svelte/i18n.contribution.js +64 -0
  241. package/dist/svelte/i18n.editor.d.ts +71 -0
  242. package/dist/svelte/i18n.editor.d.ts.map +1 -0
  243. package/dist/svelte/i18n.editor.js +87 -0
  244. package/dist/svelte/i18n.governance.d.ts +66 -0
  245. package/dist/svelte/i18n.governance.d.ts.map +1 -0
  246. package/dist/svelte/i18n.governance.js +66 -0
  247. package/dist/svelte/i18n.routes.d.ts +66 -0
  248. package/dist/svelte/i18n.routes.d.ts.map +1 -0
  249. package/dist/svelte/i18n.routes.js +75 -0
  250. package/dist/svelte/i18n.tools.d.ts +81 -0
  251. package/dist/svelte/i18n.tools.d.ts.map +1 -0
  252. package/dist/svelte/i18n.tools.js +90 -0
  253. package/dist/svelte/index.d.ts +101 -0
  254. package/dist/svelte/index.d.ts.map +1 -0
  255. package/dist/svelte/index.js +63 -0
  256. package/dist/svelte/playground.d.ts +281 -0
  257. package/dist/svelte/playground.d.ts.map +1 -0
  258. package/dist/svelte/playground.js +438 -0
  259. package/dist/svelte/routes/ContentContributionsRoute.svelte +809 -0
  260. package/dist/svelte/routes/ContentContributionsRoute.svelte.d.ts +10 -0
  261. package/dist/svelte/routes/ContentContributionsRoute.svelte.d.ts.map +1 -0
  262. package/dist/svelte/routes/ContentFactsRoute.svelte +612 -0
  263. package/dist/svelte/routes/ContentFactsRoute.svelte.d.ts +11 -0
  264. package/dist/svelte/routes/ContentFactsRoute.svelte.d.ts.map +1 -0
  265. package/dist/svelte/routes/ContentGovernanceRoute.svelte +218 -0
  266. package/dist/svelte/routes/ContentGovernanceRoute.svelte.d.ts +10 -0
  267. package/dist/svelte/routes/ContentGovernanceRoute.svelte.d.ts.map +1 -0
  268. package/dist/svelte/routes/ContentWorkspaceRoute.svelte +431 -0
  269. package/dist/svelte/routes/ContentWorkspaceRoute.svelte.d.ts +12 -0
  270. package/dist/svelte/routes/ContentWorkspaceRoute.svelte.d.ts.map +1 -0
  271. package/dist/svelte/routes/PublishedArticleRoute.svelte +194 -0
  272. package/dist/svelte/routes/PublishedArticleRoute.svelte.d.ts +10 -0
  273. package/dist/svelte/routes/PublishedArticleRoute.svelte.d.ts.map +1 -0
  274. package/dist/svelte/routes/index.d.ts +8 -0
  275. package/dist/svelte/routes/index.d.ts.map +1 -0
  276. package/dist/svelte/routes/index.js +6 -0
  277. package/dist/svelte/routes/shared.d.ts +90 -0
  278. package/dist/svelte/routes/shared.d.ts.map +1 -0
  279. package/dist/svelte/routes/shared.js +104 -0
  280. package/dist/svelte/types.d.ts +69 -0
  281. package/dist/svelte/types.d.ts.map +1 -0
  282. package/dist/svelte/types.js +6 -0
  283. package/dist/thumbnail-generator.d.ts +174 -0
  284. package/dist/thumbnail-generator.d.ts.map +1 -0
  285. package/dist/ui.d.ts +10 -0
  286. package/dist/ui.d.ts.map +1 -0
  287. package/dist/ui.js +42 -0
  288. package/dist/ui.js.map +1 -0
  289. package/dist/utils.d.ts +18 -0
  290. package/dist/utils.d.ts.map +1 -0
  291. package/package.json +119 -0
@@ -0,0 +1,2244 @@
1
+ <script lang="ts">
2
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
3
+ import {
4
+ type ContentCorrectionData,
5
+ type ContentGovernanceDefinitionsData,
6
+ type ContentGovernanceProfileData,
7
+ type ContentGovernanceStateData,
8
+ type ContentReviewData,
9
+ type ContentReviewPolicyData,
10
+ type ContentReviewProfileData,
11
+ type ContentTransparencyData,
12
+ type ContentVersionData,
13
+ createClient,
14
+ type FactAuditClaimData,
15
+ type FactAuditStateData,
16
+ type FactData,
17
+ type ResolvedContentGovernanceData,
18
+ } from '../../mock-smrt-client';
19
+ import { normalizeApiBaseUrl } from '../api';
20
+ import { M } from '../i18n.governance.js';
21
+
22
+ const { t } = useI18n();
23
+
24
+ export interface Props {
25
+ apiBaseUrl?: string;
26
+ contentId?: string;
27
+ draftType?: string | null;
28
+ draftVariant?: string | null;
29
+ selectedFactIds?: string[];
30
+ selectedFacts?: FactData[];
31
+ defaultRelationship?: string;
32
+ reviewProfileKey?: string;
33
+ customReviewLabel?: string;
34
+ customReviewInstructions?: string;
35
+ customReviewPolicyKey?: string;
36
+ showFactCatalog?: boolean;
37
+ onFactsChange?: (factIds: string[], facts: FactData[]) => void;
38
+ onGovernanceStateChange?: (state: ContentGovernanceStateData | null) => void;
39
+ onFactAuditChange?: (state: FactAuditStateData | null) => void;
40
+ hiddenSections?: ContentGovernancePanelSection[];
41
+ }
42
+
43
+ type ReviewKind = 'facts' | 'safety' | 'custom';
44
+ export type ContentGovernancePanelSection =
45
+ | 'factAudit'
46
+ | 'facts'
47
+ | 'reviews'
48
+ | 'transparency'
49
+ | 'corrections'
50
+ | 'versions';
51
+
52
+ interface ReviewAction {
53
+ kind: ReviewKind;
54
+ label: string;
55
+ policyKey?: string;
56
+ instructions?: string;
57
+ }
58
+
59
+ const FACT_CATALOG_PAGE_SIZE = 12;
60
+
61
+ let {
62
+ apiBaseUrl = '/api/v1',
63
+ contentId = 'new',
64
+ draftType = null,
65
+ draftVariant = null,
66
+ selectedFactIds = [],
67
+ selectedFacts = [],
68
+ defaultRelationship = 'supports',
69
+ reviewProfileKey = 'publication',
70
+ customReviewLabel = 'Custom Review',
71
+ customReviewInstructions = '',
72
+ customReviewPolicyKey = 'custom',
73
+ showFactCatalog = false,
74
+ onFactsChange = undefined,
75
+ onGovernanceStateChange = undefined,
76
+ onFactAuditChange = undefined,
77
+ hiddenSections = [],
78
+ }: Props = $props();
79
+
80
+ const client = $derived(createClient(normalizeApiBaseUrl(apiBaseUrl)));
81
+
82
+ let factQuery = $state('');
83
+ let catalogFacts = $state<FactData[]>([]);
84
+ let reviews = $state<ContentReviewData[]>([]);
85
+ let corrections = $state<ContentCorrectionData[]>([]);
86
+ let versions = $state<ContentVersionData[]>([]);
87
+ let transparencyPreview = $state<ContentTransparencyData | null>(null);
88
+ let publishedTransparency = $state<ContentTransparencyData | null>(null);
89
+ let factAudit = $state<FactAuditStateData | null>(null);
90
+ let governanceState = $state<ContentGovernanceStateData | null>(null);
91
+ let reviewProfiles = $state<ContentReviewProfileData[]>([]);
92
+ let governanceDefinitions = $state<ContentGovernanceDefinitionsData | null>(
93
+ null,
94
+ );
95
+ let activeReviewProfileKey = $state('');
96
+ let activeCustomPolicyKey = $state('');
97
+
98
+ let catalogLoading = $state(false);
99
+ let catalogPage = $state(1);
100
+ let catalogHasNextPage = $state(false);
101
+ let catalogBrowseQuery = $state('');
102
+ let syncingFacts = $state(false);
103
+ let workflowLoading = $state(false);
104
+ let reviewBusy = $state<string | null>(null);
105
+ let correctionBusy = $state(false);
106
+ let versionBusy = $state(false);
107
+ let transparencyLoading = $state(false);
108
+ let factAuditBusy = $state(false);
109
+ let selectedClaimIds = $state<string[]>([]);
110
+
111
+ let catalogError = $state<string | null>(null);
112
+ let workflowError = $state<string | null>(null);
113
+ let workflowNotice = $state<string | null>(null);
114
+ let catalogLoaded = $state(false);
115
+ let loadedContentId = $state<string | null>(null);
116
+ let resolvedDraftGovernanceKey = $state<string | null>(null);
117
+
118
+ let correctionSummary = $state('');
119
+ let correctionFactId = $state('');
120
+ let correctedFactText = $state('');
121
+ let correctionPublicNote = $state('');
122
+ let publishCorrection = $state(true);
123
+ let customReviewText = $state('');
124
+
125
+ const savedContentId = $derived(
126
+ contentId && contentId !== 'new' ? contentId : null,
127
+ );
128
+ const draftGovernanceKey = $derived(
129
+ draftType ? `${draftType}::${draftVariant || ''}` : null,
130
+ );
131
+ const hiddenSectionSet = $derived(new Set(hiddenSections));
132
+
133
+ function isSectionVisible(section: ContentGovernancePanelSection): boolean {
134
+ return !hiddenSectionSet.has(section);
135
+ }
136
+
137
+ function getFactId(fact: FactData): string | null {
138
+ return typeof fact.id === 'string' && fact.id.length > 0 ? fact.id : null;
139
+ }
140
+
141
+ function getClaimId(claim: FactAuditClaimData): string | null {
142
+ return typeof claim.id === 'string' && claim.id.length > 0 ? claim.id : null;
143
+ }
144
+
145
+ function isClaimSelected(claim: FactAuditClaimData): boolean {
146
+ const claimId = getClaimId(claim);
147
+ return Boolean(claimId && selectedClaimIds.includes(claimId));
148
+ }
149
+
150
+ function toggleClaimSelection(claim: FactAuditClaimData) {
151
+ const claimId = getClaimId(claim);
152
+ if (!claimId) {
153
+ return;
154
+ }
155
+
156
+ selectedClaimIds = selectedClaimIds.includes(claimId)
157
+ ? selectedClaimIds.filter((id) => id !== claimId)
158
+ : [...selectedClaimIds, claimId];
159
+ }
160
+
161
+ const factAuditGroups = $derived({
162
+ contradicted:
163
+ factAudit?.claims.filter(
164
+ (claim) => claim.supportStatus === 'contradicted',
165
+ ) || [],
166
+ unsupported:
167
+ factAudit?.claims.filter(
168
+ (claim) => claim.supportStatus === 'unsupported',
169
+ ) || [],
170
+ needs_review:
171
+ factAudit?.claims.filter(
172
+ (claim) => claim.supportStatus === 'needs_review',
173
+ ) || [],
174
+ supported:
175
+ factAudit?.claims.filter((claim) => claim.supportStatus === 'supported') ||
176
+ [],
177
+ });
178
+ const actionableFactAuditWarnings = $derived(
179
+ (factAudit?.warnings ?? []).filter(
180
+ (warning) => !isNonActionableFactAuditWarning(warning),
181
+ ),
182
+ );
183
+ const selectedClaimCount = $derived(selectedClaimIds.length);
184
+
185
+ function createFactMap(facts: FactData[]) {
186
+ return new Map(
187
+ facts
188
+ .map((fact) => {
189
+ const factId = getFactId(fact);
190
+ return factId ? [factId, fact] : null;
191
+ })
192
+ .filter((entry): entry is [string, FactData] => entry !== null),
193
+ );
194
+ }
195
+
196
+ const selectedFactsMap = $derived(createFactMap(selectedFacts));
197
+ const selectedFactsResolved = $derived(
198
+ selectedFactIds
199
+ .map((factId: string) => selectedFactsMap.get(factId))
200
+ .filter((fact: FactData | undefined): fact is FactData => Boolean(fact)),
201
+ );
202
+ const activeReviewProfile = $derived(
203
+ reviewProfiles.find(
204
+ (profile) => profile.profileKey === activeReviewProfileKey,
205
+ ) ?? null,
206
+ );
207
+ const activeUnsatisfiedRequirements = $derived(
208
+ (activeReviewProfile?.requirements ?? []).filter(
209
+ (requirement) => !requirement.satisfied,
210
+ ),
211
+ );
212
+ const activeProfileReviewActions = $derived(
213
+ getReviewActions(activeReviewProfile),
214
+ );
215
+ const availableCustomPolicies = $derived(
216
+ (governanceState?.reviewPolicies ?? []).filter(
217
+ (policy) => policy.kind === 'custom',
218
+ ),
219
+ );
220
+ const activeCustomPolicy = $derived(
221
+ availableCustomPolicies.find(
222
+ (policy) => policy.key === activeCustomPolicyKey,
223
+ ) ?? null,
224
+ );
225
+ const customReviewButtonLabel = $derived(
226
+ activeCustomPolicy?.label || customReviewLabel,
227
+ );
228
+ const factCatalogRangeStart = $derived(
229
+ catalogFacts.length > 0 ? (catalogPage - 1) * FACT_CATALOG_PAGE_SIZE + 1 : 0,
230
+ );
231
+ const factCatalogRangeEnd = $derived(
232
+ catalogFacts.length > 0 ? factCatalogRangeStart + catalogFacts.length - 1 : 0,
233
+ );
234
+
235
+ $effect(() => {
236
+ customReviewText = customReviewInstructions;
237
+ });
238
+
239
+ $effect(() => {
240
+ activeReviewProfileKey = reviewProfileKey;
241
+ });
242
+
243
+ $effect(() => {
244
+ activeCustomPolicyKey = customReviewPolicyKey;
245
+ });
246
+
247
+ $effect(() => {
248
+ if (showFactCatalog && !catalogLoaded) {
249
+ catalogLoaded = true;
250
+ void searchFacts();
251
+ }
252
+ });
253
+
254
+ $effect(() => {
255
+ if (!savedContentId) {
256
+ loadedContentId = null;
257
+ reviews = [];
258
+ corrections = [];
259
+ versions = [];
260
+ transparencyPreview = null;
261
+ publishedTransparency = null;
262
+ factAudit = null;
263
+ onFactAuditChange?.(null);
264
+ reviewProfiles = [];
265
+ workflowError = null;
266
+ workflowNotice = null;
267
+ } else if (savedContentId !== loadedContentId) {
268
+ loadedContentId = savedContentId;
269
+ void loadSavedWorkflow();
270
+ }
271
+ });
272
+
273
+ $effect(() => {
274
+ if (savedContentId) {
275
+ return;
276
+ }
277
+
278
+ if (!draftType) {
279
+ governanceState = null;
280
+ governanceDefinitions = null;
281
+ onGovernanceStateChange?.(null);
282
+ return;
283
+ }
284
+
285
+ if (draftGovernanceKey !== resolvedDraftGovernanceKey) {
286
+ resolvedDraftGovernanceKey = draftGovernanceKey;
287
+ void loadDraftGovernance();
288
+ }
289
+ });
290
+
291
+ $effect(() => {
292
+ if (selectedFactIds.length === 0) {
293
+ correctionFactId = '';
294
+ return;
295
+ }
296
+
297
+ if (!correctionFactId || !selectedFactIds.includes(correctionFactId)) {
298
+ correctionFactId = selectedFactIds[0];
299
+ }
300
+ });
301
+
302
+ function updateSelectedFacts(nextFacts: FactData[]) {
303
+ const deduped = [
304
+ ...new Map(
305
+ nextFacts
306
+ .map((fact) => {
307
+ const factId = getFactId(fact);
308
+ return factId ? [factId, fact] : null;
309
+ })
310
+ .filter((entry): entry is [string, FactData] => entry !== null),
311
+ ).values(),
312
+ ];
313
+
314
+ onFactsChange?.(
315
+ deduped
316
+ .map((fact) => getFactId(fact))
317
+ .filter((factId): factId is string => Boolean(factId)),
318
+ deduped,
319
+ );
320
+ }
321
+
322
+ function formatProfileLabel(profileKey: string) {
323
+ return profileKey
324
+ .split(/[-_]/)
325
+ .filter(Boolean)
326
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
327
+ .join(' ');
328
+ }
329
+
330
+ function getRequirementStateLabel(requirement: {
331
+ missing: boolean;
332
+ stale?: boolean;
333
+ satisfied: boolean;
334
+ latestStatus: string | null;
335
+ }) {
336
+ if (requirement.satisfied) {
337
+ return 'Satisfied';
338
+ }
339
+
340
+ if (requirement.stale) {
341
+ return 'Stale';
342
+ }
343
+
344
+ if (requirement.missing) {
345
+ return 'Missing';
346
+ }
347
+
348
+ if (requirement.latestStatus === 'failed') {
349
+ return 'Failed';
350
+ }
351
+
352
+ if (requirement.latestStatus === 'flagged') {
353
+ return 'Flagged';
354
+ }
355
+
356
+ if (requirement.latestStatus === 'waived') {
357
+ return 'Waived';
358
+ }
359
+
360
+ return 'Pending';
361
+ }
362
+
363
+ function getRequirementStateTone(requirement: {
364
+ missing: boolean;
365
+ stale?: boolean;
366
+ satisfied: boolean;
367
+ latestStatus: string | null;
368
+ }) {
369
+ if (requirement.satisfied) {
370
+ return 'passed';
371
+ }
372
+
373
+ if (requirement.latestStatus === 'failed') {
374
+ return 'failed';
375
+ }
376
+
377
+ return 'flagged';
378
+ }
379
+
380
+ function getRequirementStateCopy(requirement: {
381
+ missing: boolean;
382
+ stale?: boolean;
383
+ latestStatus: string | null;
384
+ latestSummary: string | null;
385
+ }) {
386
+ if (requirement.missing) {
387
+ return 'No review run yet.';
388
+ }
389
+
390
+ if (requirement.stale) {
391
+ return 'This review is stale because the content or linked evidence changed. Rerun required.';
392
+ }
393
+
394
+ if (requirement.latestSummary) {
395
+ return requirement.latestSummary;
396
+ }
397
+
398
+ if (requirement.latestStatus) {
399
+ return `Latest status: ${requirement.latestStatus}.`;
400
+ }
401
+
402
+ return 'Review pending.';
403
+ }
404
+
405
+ function getReviewTone(review: ContentReviewData) {
406
+ switch (review.status) {
407
+ case 'passed':
408
+ case 'waived':
409
+ return 'passed';
410
+ case 'failed':
411
+ return 'failed';
412
+ default:
413
+ return 'flagged';
414
+ }
415
+ }
416
+
417
+ function getFindingTone(severity: string | undefined) {
418
+ if (severity === 'error') {
419
+ return 'failed';
420
+ }
421
+
422
+ if (severity === 'info') {
423
+ return 'neutral';
424
+ }
425
+
426
+ return 'flagged';
427
+ }
428
+
429
+ function getTransparencyReferenceLabel(reference: {
430
+ title?: string | null;
431
+ url?: string | null;
432
+ }) {
433
+ return reference.title || reference.url || 'Untitled reference';
434
+ }
435
+
436
+ function isNonActionableFactAuditWarning(warning: string) {
437
+ return /^Asset .+ has no extracted text\.$/.test(warning.trim());
438
+ }
439
+
440
+ function resolveActiveReviewProfileKey(
441
+ profiles: ContentReviewProfileData[],
442
+ preferredKey: string | null | undefined,
443
+ fallbackKey: string,
444
+ ) {
445
+ if (
446
+ preferredKey &&
447
+ profiles.some((profile) => profile.profileKey === preferredKey)
448
+ ) {
449
+ return preferredKey;
450
+ }
451
+
452
+ if (
453
+ fallbackKey &&
454
+ profiles.some((profile) => profile.profileKey === fallbackKey)
455
+ ) {
456
+ return fallbackKey;
457
+ }
458
+
459
+ return profiles[0]?.profileKey ?? fallbackKey;
460
+ }
461
+
462
+ function resolveActiveCustomPolicyKey(
463
+ policies: ContentReviewPolicyData[],
464
+ preferredKey: string | null | undefined,
465
+ fallbackKey: string,
466
+ ) {
467
+ const customPolicies = policies.filter((policy) => policy.kind === 'custom');
468
+
469
+ if (
470
+ preferredKey &&
471
+ customPolicies.some((policy) => policy.key === preferredKey)
472
+ ) {
473
+ return preferredKey;
474
+ }
475
+
476
+ if (
477
+ fallbackKey &&
478
+ customPolicies.some((policy) => policy.key === fallbackKey)
479
+ ) {
480
+ return fallbackKey;
481
+ }
482
+
483
+ return customPolicies[0]?.key ?? fallbackKey;
484
+ }
485
+
486
+ function createReviewAction(
487
+ kind: ReviewKind,
488
+ policyKey: string,
489
+ label: string,
490
+ instructions?: string,
491
+ ): ReviewAction {
492
+ return {
493
+ kind,
494
+ label,
495
+ policyKey,
496
+ instructions,
497
+ };
498
+ }
499
+
500
+ function normalizeReviewKind(kind: string | null | undefined): ReviewKind {
501
+ switch (kind) {
502
+ case 'facts':
503
+ case 'safety':
504
+ case 'custom':
505
+ return kind;
506
+ default:
507
+ return 'custom';
508
+ }
509
+ }
510
+
511
+ function getReviewActions(
512
+ profile: ContentReviewProfileData | null,
513
+ ): ReviewAction[] {
514
+ const requirements = profile?.requirements ?? [];
515
+ if (requirements.length === 0) {
516
+ return [
517
+ createReviewAction('facts', 'facts', 'Facts Review'),
518
+ createReviewAction('safety', 'safety', 'Safety Review'),
519
+ ];
520
+ }
521
+
522
+ const seen = new Set<string>();
523
+ return requirements.flatMap((requirement) => {
524
+ if (seen.has(requirement.policyKey)) {
525
+ return [];
526
+ }
527
+
528
+ seen.add(requirement.policyKey);
529
+ return [
530
+ createReviewAction(
531
+ normalizeReviewKind(requirement.kind),
532
+ requirement.policyKey,
533
+ requirement.label || formatProfileLabel(requirement.policyKey),
534
+ ),
535
+ ];
536
+ });
537
+ }
538
+
539
+ function getReviewActionBusyKey(action: ReviewAction) {
540
+ return action.policyKey || action.kind;
541
+ }
542
+
543
+ async function searchFacts(query = factQuery, page = 1) {
544
+ const nextPage = Math.max(1, Math.floor(page));
545
+ if (governanceState && !governanceState.factLinkingEnabled) {
546
+ catalogFacts = [];
547
+ catalogPage = 1;
548
+ catalogHasNextPage = false;
549
+ catalogBrowseQuery = '';
550
+ return;
551
+ }
552
+
553
+ catalogLoading = true;
554
+ catalogError = null;
555
+
556
+ try {
557
+ const response = await client.contents.browseFacts(query, {
558
+ limit: FACT_CATALOG_PAGE_SIZE + 1,
559
+ offset: (nextPage - 1) * FACT_CATALOG_PAGE_SIZE,
560
+ latestOnly: true,
561
+ });
562
+ const rows = response.data || [];
563
+ catalogFacts = rows.slice(0, FACT_CATALOG_PAGE_SIZE);
564
+ catalogPage = nextPage;
565
+ catalogHasNextPage = rows.length > FACT_CATALOG_PAGE_SIZE;
566
+ catalogBrowseQuery = query;
567
+ } catch (err: any) {
568
+ catalogFacts = [];
569
+ catalogHasNextPage = false;
570
+ catalogError = err.message || 'Failed to browse facts';
571
+ } finally {
572
+ catalogLoading = false;
573
+ }
574
+ }
575
+
576
+ function searchFactsFirstPage() {
577
+ void searchFacts(factQuery, 1);
578
+ }
579
+
580
+ function browsePreviousFacts() {
581
+ if (catalogPage <= 1 || catalogLoading) {
582
+ return;
583
+ }
584
+
585
+ void searchFacts(catalogBrowseQuery, catalogPage - 1);
586
+ }
587
+
588
+ function browseNextFacts() {
589
+ if (!catalogHasNextPage || catalogLoading) {
590
+ return;
591
+ }
592
+
593
+ void searchFacts(catalogBrowseQuery, catalogPage + 1);
594
+ }
595
+
596
+ async function syncFactsIfSaved(nextFacts: FactData[]) {
597
+ updateSelectedFacts(nextFacts);
598
+
599
+ if (governanceState && !governanceState.factLinkingEnabled) {
600
+ return;
601
+ }
602
+
603
+ if (!savedContentId) {
604
+ return;
605
+ }
606
+
607
+ syncingFacts = true;
608
+ workflowError = null;
609
+
610
+ try {
611
+ const response = await client.contents.syncFacts(savedContentId, {
612
+ factIds: nextFacts
613
+ .map((fact) => getFactId(fact))
614
+ .filter((factId): factId is string => Boolean(factId)),
615
+ relationship: defaultRelationship,
616
+ });
617
+ updateSelectedFacts(response.data.facts || nextFacts);
618
+ await Promise.all([refreshGovernanceState(), refreshTransparency()]);
619
+ workflowNotice = 'Saved fact associations.';
620
+ } catch (err: any) {
621
+ workflowError = err.message || 'Failed to sync facts';
622
+ } finally {
623
+ syncingFacts = false;
624
+ }
625
+ }
626
+
627
+ async function repairFactAudit() {
628
+ if (!savedContentId || factAuditBusy) {
629
+ return;
630
+ }
631
+
632
+ factAuditBusy = true;
633
+ workflowError = null;
634
+ workflowNotice = null;
635
+
636
+ try {
637
+ const response = await client.contents.repairFactAudit(savedContentId);
638
+ factAudit = response.data;
639
+ workflowNotice = `Fact audit repaired: ${response.data.counts.total} claim(s) checked.`;
640
+ onFactAuditChange?.(response.data);
641
+ const factsResponse = await client.contents.getFacts(savedContentId);
642
+ updateSelectedFacts(factsResponse.data.facts || []);
643
+ } catch (err: any) {
644
+ workflowError = err.message || 'Failed to repair fact audit';
645
+ } finally {
646
+ factAuditBusy = false;
647
+ }
648
+ }
649
+
650
+ async function recheckFactClaims(claimIds: string[]) {
651
+ if (!savedContentId || factAuditBusy || claimIds.length === 0) {
652
+ return;
653
+ }
654
+
655
+ factAuditBusy = true;
656
+ workflowError = null;
657
+ workflowNotice = null;
658
+
659
+ try {
660
+ const response = await client.contents.recheckFactClaims(savedContentId, {
661
+ claimFactIds: claimIds,
662
+ });
663
+ factAudit = response.data;
664
+ workflowNotice = `Rechecked ${claimIds.length} claim${claimIds.length === 1 ? '' : 's'} against current evidence.`;
665
+ selectedClaimIds = selectedClaimIds.filter((id) => !claimIds.includes(id));
666
+ onFactAuditChange?.(response.data);
667
+ } catch (err: any) {
668
+ workflowError = err.message || 'Failed to recheck claim support';
669
+ } finally {
670
+ factAuditBusy = false;
671
+ }
672
+ }
673
+
674
+ async function recheckSelectedClaims() {
675
+ await recheckFactClaims(selectedClaimIds);
676
+ }
677
+
678
+ function addFact(fact: FactData) {
679
+ const factId = getFactId(fact);
680
+ if (!factId || selectedFactIds.includes(factId)) {
681
+ return;
682
+ }
683
+
684
+ void syncFactsIfSaved([...selectedFactsResolved, fact]);
685
+ }
686
+
687
+ function removeFact(factId: string) {
688
+ void syncFactsIfSaved(
689
+ selectedFactsResolved.filter(
690
+ (fact: FactData) => getFactId(fact) !== factId,
691
+ ),
692
+ );
693
+ }
694
+
695
+ async function loadDraftGovernance() {
696
+ if (!draftType) {
697
+ governanceState = null;
698
+ governanceDefinitions = null;
699
+ reviewProfiles = [];
700
+ onGovernanceStateChange?.(null);
701
+ return;
702
+ }
703
+
704
+ workflowLoading = true;
705
+ workflowError = null;
706
+
707
+ try {
708
+ const [definitionsResponse, resolvedResponse] = await Promise.all([
709
+ client.contents.getGovernanceDefinitions(),
710
+ client.contents.resolveGovernance({
711
+ type: draftType,
712
+ variant: draftVariant,
713
+ }),
714
+ ]);
715
+
716
+ governanceDefinitions = definitionsResponse.data;
717
+ governanceState = {
718
+ ...resolvedResponse.data,
719
+ reviewProfiles: [],
720
+ };
721
+ reviewProfiles = [];
722
+ onGovernanceStateChange?.(governanceState);
723
+ activeReviewProfileKey =
724
+ governanceState.publicationProfileKey || reviewProfileKey;
725
+ activeCustomPolicyKey = resolveActiveCustomPolicyKey(
726
+ governanceState.reviewPolicies || [],
727
+ activeCustomPolicyKey,
728
+ customReviewPolicyKey,
729
+ );
730
+ } catch (err: any) {
731
+ governanceState = null;
732
+ governanceDefinitions = null;
733
+ reviewProfiles = [];
734
+ onGovernanceStateChange?.(null);
735
+ workflowError = err.message || 'Failed to resolve content governance';
736
+ } finally {
737
+ workflowLoading = false;
738
+ }
739
+ }
740
+
741
+ async function loadSavedWorkflow() {
742
+ if (!savedContentId) {
743
+ return;
744
+ }
745
+
746
+ workflowLoading = true;
747
+ workflowError = null;
748
+
749
+ try {
750
+ const [
751
+ factsResponse,
752
+ reviewsResponse,
753
+ correctionsResponse,
754
+ versionsResponse,
755
+ governanceResponse,
756
+ governanceDefinitionsResponse,
757
+ factAuditResponse,
758
+ transparencyPreviewResponse,
759
+ publishedTransparencyResponse,
760
+ ] = await Promise.all([
761
+ client.contents.getFacts(savedContentId),
762
+ client.contents.getReviews(savedContentId),
763
+ client.contents.getCorrections(savedContentId),
764
+ client.contents.getVersions(savedContentId),
765
+ client.contents.getGovernanceState(savedContentId),
766
+ client.contents.getGovernanceDefinitions(),
767
+ client.contents.getFactAudit(savedContentId),
768
+ client.contents.getTransparencyPreview(savedContentId),
769
+ client.contents.getPublishedTransparency(savedContentId),
770
+ ]);
771
+
772
+ updateSelectedFacts(factsResponse.data.facts || []);
773
+ reviews = reviewsResponse.data;
774
+ corrections = correctionsResponse.data;
775
+ versions = versionsResponse.data;
776
+ transparencyPreview = transparencyPreviewResponse.data;
777
+ publishedTransparency = publishedTransparencyResponse.data;
778
+ factAudit = factAuditResponse.data;
779
+ onFactAuditChange?.(factAudit);
780
+ governanceState = governanceResponse.data;
781
+ governanceDefinitions = governanceDefinitionsResponse.data;
782
+ reviewProfiles = governanceResponse.data.reviewProfiles || [];
783
+ onGovernanceStateChange?.(governanceResponse.data);
784
+ activeReviewProfileKey = resolveActiveReviewProfileKey(
785
+ governanceResponse.data.reviewProfiles || [],
786
+ activeReviewProfileKey,
787
+ reviewProfileKey,
788
+ );
789
+ activeCustomPolicyKey = resolveActiveCustomPolicyKey(
790
+ governanceResponse.data.reviewPolicies || [],
791
+ activeCustomPolicyKey,
792
+ customReviewPolicyKey,
793
+ );
794
+ } catch (err: any) {
795
+ workflowError = err.message || 'Failed to load governance workflow state';
796
+ } finally {
797
+ workflowLoading = false;
798
+ }
799
+ }
800
+
801
+ export async function triggerReview(actionKind: string) {
802
+ const action = activeProfileReviewActions.find(
803
+ (a) =>
804
+ a.kind === actionKind ||
805
+ a.label.toLowerCase().includes(actionKind.toLowerCase()),
806
+ );
807
+ if (action) {
808
+ await runReview(action);
809
+ } else if (actionKind.toLowerCase() === 'custom') {
810
+ await runReview(
811
+ createReviewAction(
812
+ 'custom',
813
+ activeCustomPolicy?.key || customReviewPolicyKey,
814
+ customReviewButtonLabel,
815
+ customReviewText || undefined,
816
+ ),
817
+ );
818
+ }
819
+ }
820
+
821
+ async function runReview(action: ReviewAction) {
822
+ if (!savedContentId) {
823
+ return;
824
+ }
825
+
826
+ reviewBusy = getReviewActionBusyKey(action);
827
+ workflowError = null;
828
+ workflowNotice = null;
829
+
830
+ try {
831
+ const payload: Record<string, any> = {
832
+ kind: action.kind,
833
+ factIds: selectedFactIds,
834
+ };
835
+
836
+ if (action.policyKey) {
837
+ payload.policyKey = action.policyKey;
838
+ }
839
+
840
+ if (action.kind === 'custom') {
841
+ payload.policyKey = action.policyKey || customReviewPolicyKey;
842
+ payload.instructions = action.instructions ?? customReviewText;
843
+ }
844
+
845
+ const response = await client.contents.runReview(savedContentId, payload);
846
+ reviews = [response.data, ...reviews];
847
+ await Promise.all([refreshGovernanceState(), refreshTransparency()]);
848
+ workflowNotice = `${action.label} completed.`;
849
+ await refreshVersions();
850
+ } catch (err: any) {
851
+ workflowError = err.message || 'Failed to run content review';
852
+ } finally {
853
+ reviewBusy = null;
854
+ }
855
+ }
856
+
857
+ async function issueCorrection() {
858
+ if (!savedContentId) {
859
+ return;
860
+ }
861
+
862
+ correctionBusy = true;
863
+ workflowError = null;
864
+ workflowNotice = null;
865
+
866
+ try {
867
+ await client.contents.issueCorrection(savedContentId, {
868
+ summary: correctionSummary,
869
+ factId: correctionFactId || undefined,
870
+ correctedFactText: correctedFactText || undefined,
871
+ publicNote: correctionPublicNote || undefined,
872
+ publish: publishCorrection,
873
+ });
874
+
875
+ correctionSummary = '';
876
+ correctedFactText = '';
877
+ correctionPublicNote = '';
878
+ publishCorrection = true;
879
+
880
+ workflowNotice = 'Correction issued.';
881
+ await loadSavedWorkflow();
882
+ } catch (err: any) {
883
+ workflowError = err.message || 'Failed to issue correction';
884
+ } finally {
885
+ correctionBusy = false;
886
+ }
887
+ }
888
+
889
+ async function createSnapshot() {
890
+ if (!savedContentId) {
891
+ return;
892
+ }
893
+
894
+ versionBusy = true;
895
+ workflowError = null;
896
+ workflowNotice = null;
897
+
898
+ try {
899
+ await client.contents.createVersion(savedContentId, {
900
+ kind: 'manual',
901
+ summary: 'Manual editorial snapshot',
902
+ });
903
+ workflowNotice = 'Snapshot created.';
904
+ await Promise.all([refreshVersions(), refreshTransparency()]);
905
+ } catch (err: any) {
906
+ workflowError = err.message || 'Failed to create version snapshot';
907
+ } finally {
908
+ versionBusy = false;
909
+ }
910
+ }
911
+
912
+ async function restoreVersion(versionNumber: number) {
913
+ if (!savedContentId) {
914
+ return;
915
+ }
916
+
917
+ if (!confirm(`Restore content version ${versionNumber}?`)) {
918
+ return;
919
+ }
920
+
921
+ versionBusy = true;
922
+ workflowError = null;
923
+ workflowNotice = null;
924
+
925
+ try {
926
+ const response = await client.contents.restoreVersion(
927
+ savedContentId,
928
+ versionNumber,
929
+ );
930
+ workflowNotice = `Restored version ${versionNumber}.`;
931
+ await loadSavedWorkflow();
932
+ } catch (err: any) {
933
+ workflowError = err.message || 'Failed to restore version';
934
+ } finally {
935
+ versionBusy = false;
936
+ }
937
+ }
938
+
939
+ async function refreshVersions() {
940
+ if (!savedContentId) {
941
+ return;
942
+ }
943
+
944
+ const response = await client.contents.getVersions(savedContentId);
945
+ versions = response.data;
946
+ }
947
+
948
+ async function refreshTransparency() {
949
+ if (!savedContentId) {
950
+ transparencyPreview = null;
951
+ publishedTransparency = null;
952
+ return;
953
+ }
954
+
955
+ transparencyLoading = true;
956
+
957
+ try {
958
+ const [previewResponse, publishedResponse] = await Promise.all([
959
+ client.contents.getTransparencyPreview(savedContentId),
960
+ client.contents.getPublishedTransparency(savedContentId),
961
+ ]);
962
+ transparencyPreview = previewResponse.data;
963
+ publishedTransparency = publishedResponse.data;
964
+ } finally {
965
+ transparencyLoading = false;
966
+ }
967
+ }
968
+
969
+ async function refreshGovernanceState() {
970
+ if (!savedContentId) {
971
+ await loadDraftGovernance();
972
+ return;
973
+ }
974
+
975
+ const response = await client.contents.getGovernanceState(savedContentId);
976
+ governanceState = response.data;
977
+ reviewProfiles = response.data.reviewProfiles || [];
978
+ onGovernanceStateChange?.(response.data);
979
+ activeReviewProfileKey = resolveActiveReviewProfileKey(
980
+ response.data.reviewProfiles || [],
981
+ activeReviewProfileKey,
982
+ reviewProfileKey,
983
+ );
984
+ activeCustomPolicyKey = resolveActiveCustomPolicyKey(
985
+ response.data.reviewPolicies || [],
986
+ activeCustomPolicyKey,
987
+ customReviewPolicyKey,
988
+ );
989
+ }
990
+
991
+ function formatPercent(value: number | null | undefined) {
992
+ if (value === null || value === undefined || Number.isNaN(value)) {
993
+ return 'n/a';
994
+ }
995
+
996
+ return `${Math.round(value * 100)}%`;
997
+ }
998
+
999
+ function formatTimestamp(value: string | null | undefined) {
1000
+ if (!value) {
1001
+ return 'Not published';
1002
+ }
1003
+
1004
+ return new Date(value).toLocaleString();
1005
+ }
1006
+
1007
+ function canRunCustomReview() {
1008
+ return Boolean(activeCustomPolicy) || customReviewText.trim().length > 0;
1009
+ }
1010
+
1011
+ function getCorrectionProvenanceCopy(correction: ContentCorrectionData) {
1012
+ const metadata = correction.metadata || {};
1013
+
1014
+ if (metadata.draftVersionNumber) {
1015
+ return `Auto-created draft v${metadata.draftVersionNumber} for editorial follow-up.`;
1016
+ }
1017
+
1018
+ return null;
1019
+ }
1020
+
1021
+ function getVersionProvenanceCopy(version: ContentVersionData) {
1022
+ const metadata = version.metadata || {};
1023
+
1024
+ if (metadata.sourceCorrectionVersionNumber) {
1025
+ return `Generated from correction version v${metadata.sourceCorrectionVersionNumber}.`;
1026
+ }
1027
+
1028
+ if (metadata.policyKey) {
1029
+ return `Created from the ${metadata.policyKey} review flow.`;
1030
+ }
1031
+
1032
+ if (version.kind === 'publication') {
1033
+ return 'Frozen public transparency snapshot.';
1034
+ }
1035
+
1036
+ return null;
1037
+ }
1038
+ </script>
1039
+
1040
+ <div class="factual-workflow">
1041
+ {#if isSectionVisible('factAudit')}
1042
+ <details class="editor-drawer" open={(factAudit?.counts.total ?? 0) > 0}>
1043
+ <summary class="editor-drawer-header">
1044
+ <div style="display: flex; align-items: center; gap: 0.5rem;">
1045
+ {t(M['content.governance_panel.article_claim_audit'])}
1046
+ {#if factAuditBusy}
1047
+ <span class="section-status" style="font-size: 0.875rem; font-weight: 400; color: var(--smrt-color-outline);">Repairing...</span>
1048
+ {/if}
1049
+ </div>
1050
+ <svg class="drawer-icon" viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
1051
+ </summary>
1052
+ <div class="editor-drawer-content">
1053
+ {#if !savedContentId}
1054
+ <p class="empty-copy">{t(M['content.governance_panel.save_to_audit_claims'])}</p>
1055
+ {:else}
1056
+ <div class="claim-audit-toolbar">
1057
+ <div class="claim-audit-counts">
1058
+ <span><strong>{factAudit?.counts.total ?? 0}</strong> {t(M['content.governance_panel.article_claims'])}</span>
1059
+ <span><strong>{factAudit?.counts.supported ?? 0}</strong> supported</span>
1060
+ <span><strong>{factAudit?.counts.unsupported ?? 0}</strong> unsupported</span>
1061
+ <span><strong>{factAudit?.counts.contradicted ?? 0}</strong> contradicted</span>
1062
+ <span><strong>{factAudit?.counts.needs_review ?? 0}</strong> review</span>
1063
+ </div>
1064
+ <button
1065
+ type="button"
1066
+ class="secondary-button"
1067
+ disabled={factAuditBusy || selectedClaimCount === 0}
1068
+ onclick={() => void recheckSelectedClaims()}
1069
+ >
1070
+ {factAuditBusy ? 'Checking...' : `Recheck selected (${selectedClaimCount})`}
1071
+ </button>
1072
+ <button
1073
+ type="button"
1074
+ class="secondary-button"
1075
+ disabled={factAuditBusy}
1076
+ onclick={() => void repairFactAudit()}
1077
+ >
1078
+ {factAuditBusy ? 'Repairing...' : 'Repair audit'}
1079
+ </button>
1080
+ </div>
1081
+ <p class="claim-audit-help">
1082
+ {t(M['content.governance_panel.article_claims_help'])}
1083
+ </p>
1084
+
1085
+ {#if actionableFactAuditWarnings.length}
1086
+ <div class="claim-audit-warnings">
1087
+ {#each actionableFactAuditWarnings as warning}
1088
+ <p>{warning}</p>
1089
+ {/each}
1090
+ </div>
1091
+ {/if}
1092
+
1093
+ {#if !factAudit || factAudit.counts.total === 0}
1094
+ <p class="empty-copy">
1095
+ {t(M['content.governance_panel.no_claim_audit_generated'])}
1096
+ </p>
1097
+ {:else}
1098
+ {#each [
1099
+ ['contradicted', 'Contradicted'],
1100
+ ['unsupported', 'Unsupported'],
1101
+ ['needs_review', 'Needs review'],
1102
+ ['supported', 'Supported']
1103
+ ] as group}
1104
+ {@const groupKey = group[0] as keyof typeof factAuditGroups}
1105
+ {@const groupClaims = factAuditGroups[groupKey]}
1106
+ {#if groupClaims.length > 0}
1107
+ <div class="claim-audit-group">
1108
+ <div class="section-caption">{t(M['content.governance_panel.group_article_claims'], { group: group[1] })}</div>
1109
+ {#each groupClaims as claim (claim.id ?? claim.claimQuote ?? claim.fact.textRefined)}
1110
+ <details class="claim-audit-item">
1111
+ <summary>
1112
+ <label class="claim-audit-select">
1113
+ <input
1114
+ type="checkbox"
1115
+ checked={isClaimSelected(claim)}
1116
+ disabled={!getClaimId(claim)}
1117
+ onchange={() => toggleClaimSelection(claim)}
1118
+ />
1119
+ </label>
1120
+ <span class={`pill pill--${claim.supportStatus === 'supported' ? 'passed' : claim.supportStatus === 'contradicted' ? 'failed' : 'flagged'}`}>
1121
+ {claim.supportStatus.replace('_', ' ')}
1122
+ </span>
1123
+ <strong>{claim.fact.textRefined || claim.claimQuote || claim.id}</strong>
1124
+ </summary>
1125
+ <div class="claim-audit-item__body">
1126
+ <div class="claim-audit-item__actions">
1127
+ <button
1128
+ type="button"
1129
+ class="secondary-button"
1130
+ disabled={factAuditBusy || !getClaimId(claim)}
1131
+ onclick={() => {
1132
+ const claimId = getClaimId(claim);
1133
+ if (claimId) void recheckFactClaims([claimId]);
1134
+ }}
1135
+ >
1136
+ {t(M['content.governance_panel.recheck_support'])}
1137
+ </button>
1138
+ </div>
1139
+ {#if claim.claimQuote}
1140
+ <p><strong>{t(M['content.governance_panel.article_claim_label'])}</strong> {claim.claimQuote}</p>
1141
+ {/if}
1142
+ {#if claim.rationale}
1143
+ <p><strong>{t(M['content.governance_panel.support_assessment_label'])}</strong> {claim.rationale}</p>
1144
+ {/if}
1145
+ {#if claim.evidence?.length}
1146
+ <div class="claim-audit-evidence">
1147
+ <div class="section-caption">{t(M['content.governance_panel.claim_excerpt'])}</div>
1148
+ {#each claim.evidence as evidence (evidence.id ?? evidence.evidenceKey)}
1149
+ <p>{evidence.quote || evidence.locator || evidence.sourceTitle}</p>
1150
+ {/each}
1151
+ </div>
1152
+ {/if}
1153
+ {#if claim.matchedFacts?.length}
1154
+ <div class="claim-audit-evidence">
1155
+ <div class="section-caption">{t(M['content.governance_panel.based_on_source_evidence'])}</div>
1156
+ {#each claim.matchedFacts as matched (matched.fact.id ?? matched.fact.textRefined)}
1157
+ <div class="claim-audit-match">
1158
+ <strong>{t(M['content.governance_panel.source_claim_label'])} {matched.fact.textRefined || matched.fact.textRaw || matched.fact.id}</strong>
1159
+ {#each matched.evidence ?? [] as evidence (evidence.id ?? evidence.evidenceKey)}
1160
+ <span>
1161
+ {evidence.sourceTitle || 'Source'}
1162
+ {#if evidence.locator}
1163
+ · {evidence.locator}
1164
+ {/if}
1165
+ {#if evidence.quote}
1166
+ · {evidence.quote}
1167
+ {/if}
1168
+ </span>
1169
+ {/each}
1170
+ </div>
1171
+ {/each}
1172
+ </div>
1173
+ {:else}
1174
+ <p><strong>{t(M['content.governance_panel.based_on_label'])}</strong> {t(M['content.governance_panel.no_supporting_source_evidence'])}</p>
1175
+ {/if}
1176
+ </div>
1177
+ </details>
1178
+ {/each}
1179
+ </div>
1180
+ {/if}
1181
+ {/each}
1182
+ {/if}
1183
+ {/if}
1184
+ </div>
1185
+ </details>
1186
+ {/if}
1187
+
1188
+ {#if isSectionVisible('facts') && (showFactCatalog || selectedFactsResolved.length > 0)}
1189
+ <details class="editor-drawer" open={selectedFactsResolved.length > 0}>
1190
+ <summary class="editor-drawer-header">
1191
+ <div style="display: flex; align-items: center; gap: 0.5rem;">
1192
+ {t(M['content.governance_panel.manual_fact_links'])}
1193
+ {#if syncingFacts}
1194
+ <span class="section-status" style="font-size: 0.875rem; font-weight: 400; color: var(--smrt-color-outline);">{t(M['content.governance_panel.saving_links'])}</span>
1195
+ {/if}
1196
+ </div>
1197
+ <svg class="drawer-icon" viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
1198
+ </summary>
1199
+ <div class="editor-drawer-content">
1200
+ {#if governanceState && !governanceState.factLinkingEnabled}
1201
+ <p class="empty-copy">
1202
+ {t(M['content.governance_panel.fact_linking_not_enabled'])}
1203
+ </p>
1204
+ {:else}
1205
+ <div class="selected-facts">
1206
+ <div class="section-caption">{t(M['content.governance_panel.manually_linked_facts'])}</div>
1207
+ {#if selectedFactsResolved.length === 0}
1208
+ <p class="empty-copy">{t(M['content.governance_panel.no_manually_linked_facts'])}</p>
1209
+ {:else}
1210
+ <div class="fact-chip-list">
1211
+ {#each selectedFactsResolved as fact (fact.id ?? fact.textRefined)}
1212
+ <div class="fact-chip">
1213
+ <div class="fact-chip__body">
1214
+ <strong>{fact.textRefined}</strong>
1215
+ <span>
1216
+ {fact.status} · confidence {formatPercent(fact.confidence)}
1217
+ </span>
1218
+ </div>
1219
+ <button
1220
+ type="button"
1221
+ class="fact-chip__remove"
1222
+ onclick={() => fact.id && removeFact(fact.id)}
1223
+ >
1224
+ Remove
1225
+ </button>
1226
+ </div>
1227
+ {/each}
1228
+ </div>
1229
+ {/if}
1230
+ </div>
1231
+
1232
+ {#if showFactCatalog}
1233
+ <div class="fact-search">
1234
+ <input
1235
+ type="text"
1236
+ bind:value={factQuery}
1237
+ placeholder={t(M['content.governance_panel.search_fact_catalog'])}
1238
+ />
1239
+ <button type="button" onclick={searchFactsFirstPage}>
1240
+ Search
1241
+ </button>
1242
+ </div>
1243
+
1244
+ {#if catalogError}
1245
+ <p class="workflow-error">{catalogError}</p>
1246
+ {/if}
1247
+
1248
+ <div class="fact-catalog">
1249
+ <div class="section-caption">{t(M['content.governance_panel.fact_catalog'])}</div>
1250
+ {#if catalogLoading}
1251
+ <p class="empty-copy">{t(M['content.governance_panel.loading_facts'])}</p>
1252
+ {:else if catalogFacts.length === 0}
1253
+ <p class="empty-copy">{t(M['content.governance_panel.no_facts_matched'])}</p>
1254
+ {:else}
1255
+ <div class="fact-catalog__list">
1256
+ {#each catalogFacts as fact (fact.id ?? fact.textRefined)}
1257
+ <div class="fact-result">
1258
+ <div class="fact-result__body">
1259
+ <strong>{fact.textRefined}</strong>
1260
+ <span>
1261
+ {fact.status} · {fact.domain || 'general'} · confidence {formatPercent(fact.confidence)}
1262
+ </span>
1263
+ </div>
1264
+ <button
1265
+ type="button"
1266
+ disabled={!fact.id || selectedFactIds.includes(fact.id ?? '')}
1267
+ onclick={() => addFact(fact)}
1268
+ >
1269
+ {!fact.id || selectedFactIds.includes(fact.id ?? '') ? 'Selected' : 'Add'}
1270
+ </button>
1271
+ </div>
1272
+ {/each}
1273
+ </div>
1274
+ {/if}
1275
+
1276
+ {#if catalogFacts.length > 0 || catalogPage > 1}
1277
+ <div class="fact-pagination" aria-label={t(M['content.governance_panel.fact_catalog_pagination'])}>
1278
+ <span>
1279
+ Page {catalogPage}
1280
+ {#if catalogFacts.length > 0}
1281
+ · {factCatalogRangeStart}-{factCatalogRangeEnd}
1282
+ {/if}
1283
+ </span>
1284
+ <div class="fact-pagination__actions">
1285
+ <button
1286
+ type="button"
1287
+ class="secondary-button"
1288
+ disabled={catalogLoading || catalogPage <= 1}
1289
+ onclick={browsePreviousFacts}
1290
+ >
1291
+ Previous
1292
+ </button>
1293
+ <button
1294
+ type="button"
1295
+ class="secondary-button"
1296
+ disabled={catalogLoading || !catalogHasNextPage}
1297
+ onclick={browseNextFacts}
1298
+ >
1299
+ Next
1300
+ </button>
1301
+ </div>
1302
+ </div>
1303
+ {/if}
1304
+ </div>
1305
+ {/if}
1306
+ {/if}
1307
+ </div>
1308
+ </details>
1309
+ {/if}
1310
+
1311
+ {#if isSectionVisible('reviews')}
1312
+ <details class="editor-drawer">
1313
+ <summary class="editor-drawer-header">
1314
+ <div style="display: flex; align-items: center; gap: 0.5rem;">
1315
+ Review
1316
+ {#if workflowLoading}
1317
+ <span class="section-status" style="font-size: 0.875rem; font-weight: 400; color: var(--smrt-color-outline);">Loading...</span>
1318
+ {/if}
1319
+ </div>
1320
+ <svg class="drawer-icon" viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
1321
+ </summary>
1322
+ <div class="editor-drawer-content">
1323
+
1324
+ {#if workflowError}
1325
+ <p class="workflow-error">{workflowError}</p>
1326
+ {/if}
1327
+
1328
+ {#if workflowNotice}
1329
+ <p class="workflow-notice">{workflowNotice}</p>
1330
+ {/if}
1331
+
1332
+ {#if !savedContentId}
1333
+ <p class="empty-copy">
1334
+ {t(M['content.governance_panel.save_to_run_governance'])}
1335
+ </p>
1336
+ {:else}
1337
+ {#if reviewProfiles.length > 0}
1338
+ <label class="workflow-field">
1339
+ {t(M['content.governance_panel.review_profile'])}
1340
+ <select bind:value={activeReviewProfileKey}>
1341
+ {#each reviewProfiles as profile (profile.profileKey)}
1342
+ <option value={profile.profileKey}>
1343
+ {formatProfileLabel(profile.profileKey)}
1344
+ </option>
1345
+ {/each}
1346
+ </select>
1347
+ </label>
1348
+ {/if}
1349
+
1350
+ {#if activeReviewProfile}
1351
+ <div class="review-profile-card">
1352
+ <div class="review-profile-card__header">
1353
+ <strong>{formatProfileLabel(activeReviewProfile.profileKey)}</strong>
1354
+ <div class="review-profile-card__badges">
1355
+ <span class={`pill ${activeReviewProfile.ready ? 'pill--passed' : 'pill--failed'}`}>
1356
+ {activeReviewProfile.ready ? 'Blocking-ready' : 'Blocking issues'}
1357
+ </span>
1358
+ <span class={`pill ${activeReviewProfile.complete ? 'pill--neutral' : 'pill--flagged'}`}>
1359
+ {activeReviewProfile.complete ? 'All reviews run' : 'Reviews missing'}
1360
+ </span>
1361
+ </div>
1362
+ </div>
1363
+
1364
+ {#if activeUnsatisfiedRequirements.length > 0}
1365
+ <div class="review-profile-callout">
1366
+ <strong>{t(M['content.governance_panel.needs_attention'])}</strong>
1367
+ <span>
1368
+ {t(M['content.governance_panel.requirements_need_attention'], { count: activeUnsatisfiedRequirements.length })}
1369
+ </span>
1370
+ </div>
1371
+ {/if}
1372
+
1373
+ {#if activeReviewProfile.requirements?.length === 0}
1374
+ <p class="empty-copy">{t(M['content.governance_panel.no_review_requirements'])}</p>
1375
+ {:else}
1376
+ <div class="review-profile-list">
1377
+ {#each activeReviewProfile.requirements as requirement (requirement.policyKey)}
1378
+ <div class="review-profile-item">
1379
+ <div class="review-profile-item__body">
1380
+ <strong>{requirement.label}</strong>
1381
+ <span>{getRequirementStateCopy(requirement)}</span>
1382
+ </div>
1383
+ <div class="review-profile-item__meta">
1384
+ {#if requirement.blocking}
1385
+ <span class="pill pill--neutral">Blocking</span>
1386
+ {/if}
1387
+ <span class={`pill pill--${getRequirementStateTone(requirement)}`}>
1388
+ {getRequirementStateLabel(requirement)}
1389
+ </span>
1390
+ </div>
1391
+ </div>
1392
+ {/each}
1393
+ </div>
1394
+ {/if}
1395
+ </div>
1396
+ {/if}
1397
+
1398
+ <div class="review-actions">
1399
+ {#each activeProfileReviewActions as action (action.policyKey ?? action.kind)}
1400
+ <button
1401
+ type="button"
1402
+ disabled={reviewBusy !== null}
1403
+ onclick={() => void runReview(action)}
1404
+ >
1405
+ {#if reviewBusy === getReviewActionBusyKey(action)}
1406
+ Running {action.label.toLowerCase()}...
1407
+ {:else}
1408
+ Run {action.label}
1409
+ {/if}
1410
+ </button>
1411
+ {/each}
1412
+ </div>
1413
+
1414
+ <div class="workflow-field-group">
1415
+ {#if availableCustomPolicies.length > 0}
1416
+ <label class="workflow-field">
1417
+ {t(M['content.governance_panel.app_review_policy'])}
1418
+ <select bind:value={activeCustomPolicyKey}>
1419
+ {#each availableCustomPolicies as policy (policy.key)}
1420
+ <option value={policy.key}>
1421
+ {policy.label}
1422
+ </option>
1423
+ {/each}
1424
+ </select>
1425
+ </label>
1426
+ {/if}
1427
+
1428
+ <label class="workflow-field">
1429
+ {customReviewButtonLabel}
1430
+ <textarea
1431
+ rows="3"
1432
+ bind:value={customReviewText}
1433
+ placeholder={t(M['content.governance_panel.optional_review_instructions'])}
1434
+ ></textarea>
1435
+ </label>
1436
+ <button
1437
+ type="button"
1438
+ disabled={reviewBusy !== null || !canRunCustomReview()}
1439
+ onclick={() =>
1440
+ void runReview(
1441
+ createReviewAction(
1442
+ 'custom',
1443
+ activeCustomPolicy?.key || customReviewPolicyKey,
1444
+ customReviewButtonLabel,
1445
+ customReviewText || undefined,
1446
+ ),
1447
+ )}
1448
+ >
1449
+ {#if reviewBusy === (activeCustomPolicy?.key || customReviewPolicyKey)}
1450
+ Running {customReviewButtonLabel.toLowerCase()}...
1451
+ {:else}
1452
+ Run {customReviewButtonLabel}
1453
+ {/if}
1454
+ </button>
1455
+ </div>
1456
+
1457
+ <div class="review-list">
1458
+ <div class="section-caption">{t(M['content.governance_panel.recent_reviews'])}</div>
1459
+ {#if reviews.length === 0}
1460
+ <p class="empty-copy">{t(M['content.governance_panel.no_reviews_run'])}</p>
1461
+ {:else}
1462
+ {#each reviews as review (review.id ?? `${review.kind}-${review.createdAt}`)}
1463
+ <div class="review-card">
1464
+ <div class="review-card__header">
1465
+ <strong>{review.policyKey || review.kind}</strong>
1466
+ <span class={`pill pill--${getReviewTone(review)}`}>
1467
+ {review.status || 'pending'}
1468
+ </span>
1469
+ </div>
1470
+ <p>{review.summary || 'Review completed.'}</p>
1471
+ {#if review.findings && review.findings.length > 0}
1472
+ <div class="review-findings">
1473
+ {#each review.findings.slice(0, 2) as finding (`${review.id}-${finding.title ?? finding.detail}`)}
1474
+ <div class="review-finding">
1475
+ <span class={`pill pill--${getFindingTone(finding.severity)}`}>
1476
+ {finding.severity || 'warning'}
1477
+ </span>
1478
+ <div class="review-finding__body">
1479
+ <strong>{finding.title || 'Finding'}</strong>
1480
+ <span>{finding.detail || 'No detail provided.'}</span>
1481
+ </div>
1482
+ </div>
1483
+ {/each}
1484
+ {#if review.findings.length > 2}
1485
+ <span>{t(M['content.governance_panel.more_findings'], { count: review.findings.length - 2 })}</span>
1486
+ {/if}
1487
+ </div>
1488
+ {:else}
1489
+ <span>0 finding(s)</span>
1490
+ {/if}
1491
+ </div>
1492
+ {/each}
1493
+ {/if}
1494
+ </div>
1495
+ {/if}
1496
+ </div>
1497
+ </details>
1498
+ {/if}
1499
+
1500
+ {#if isSectionVisible('transparency')}
1501
+ <details class="editor-drawer">
1502
+ <summary class="editor-drawer-header">
1503
+ <div style="display: flex; align-items: center; gap: 0.5rem;">
1504
+ Transparency
1505
+ {#if transparencyLoading}
1506
+ <span class="section-status" style="font-size: 0.875rem; font-weight: 400; color: var(--smrt-color-outline);">Loading...</span>
1507
+ {/if}
1508
+ </div>
1509
+ <svg class="drawer-icon" viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
1510
+ </summary>
1511
+ <div class="editor-drawer-content">
1512
+
1513
+ {#if !savedContentId}
1514
+ <p class="empty-copy">
1515
+ {t(M['content.governance_panel.save_to_preview_transparency'])}
1516
+ </p>
1517
+ {:else if governanceState && !governanceState.transparencyEnabled}
1518
+ <p class="empty-copy">
1519
+ {t(M['content.governance_panel.transparency_snapshots_not_enabled'])}
1520
+ </p>
1521
+ {:else if !transparencyPreview}
1522
+ <p class="empty-copy">{t(M['content.governance_panel.no_transparency_preview'])}</p>
1523
+ {:else}
1524
+ <p class="section-caption">
1525
+ {t(M['content.governance_panel.transparency_preview_explainer'])}
1526
+ </p>
1527
+
1528
+ <div class="transparency-stats">
1529
+ <div class="transparency-stat">
1530
+ <strong>{transparencyPreview.factsUsed.length}</strong>
1531
+ <span>{t(M['content.governance_panel.facts_used'])}</span>
1532
+ </div>
1533
+ <div class="transparency-stat">
1534
+ <strong>{transparencyPreview.references.length}</strong>
1535
+ <span>References</span>
1536
+ </div>
1537
+ <div class="transparency-stat">
1538
+ <strong>{transparencyPreview.otherExtractedFacts.length}</strong>
1539
+ <span>{t(M['content.governance_panel.other_extracted_facts'])}</span>
1540
+ </div>
1541
+ <div class="transparency-stat">
1542
+ <strong>{transparencyPreview.corrections.length}</strong>
1543
+ <span>{t(M['content.governance_panel.public_corrections'])}</span>
1544
+ </div>
1545
+ </div>
1546
+
1547
+ <div class="transparency-grid">
1548
+ <div class="transparency-card">
1549
+ <div class="transparency-card__header">
1550
+ <strong>{t(M['content.governance_panel.current_preview'])}</strong>
1551
+ <span class="pill pill--neutral">{transparencyPreview.snapshotKind}</span>
1552
+ </div>
1553
+ <span>
1554
+ {#if transparencyPreview.generation.publicPrompt}
1555
+ {t(M['content.governance_panel.public_prompt_set'])}
1556
+ {:else}
1557
+ {t(M['content.governance_panel.no_public_prompt_exposed'])}
1558
+ {/if}
1559
+ </span>
1560
+ {#if transparencyPreview.generation.publicPrompt}
1561
+ <p class="transparency-card__prompt">
1562
+ {transparencyPreview.generation.publicPrompt}
1563
+ </p>
1564
+ {/if}
1565
+
1566
+ <div class="transparency-list">
1567
+ <div class="transparency-list__section">
1568
+ <div class="section-caption">{t(M['content.governance_panel.facts_used_in_article'])}</div>
1569
+ {#if transparencyPreview.factsUsed.length === 0}
1570
+ <p class="empty-copy">{t(M['content.governance_panel.no_used_facts_public'])}</p>
1571
+ {:else}
1572
+ {#each transparencyPreview.factsUsed.slice(0, 3) as fact (fact.id ?? fact.textRefined ?? fact.textRaw)}
1573
+ <div class="transparency-list__item">
1574
+ <strong>{fact.textRefined || fact.textRaw || fact.id}</strong>
1575
+ <span>
1576
+ {fact.relationship || 'linked'}
1577
+ {#if fact.sources && fact.sources.length > 0}
1578
+ · {fact.sources.length} source(s)
1579
+ {/if}
1580
+ </span>
1581
+ </div>
1582
+ {/each}
1583
+ {/if}
1584
+ </div>
1585
+
1586
+ <div class="transparency-list__section">
1587
+ <div class="section-caption">{t(M['content.governance_panel.source_material'])}</div>
1588
+ {#if transparencyPreview.references.length === 0}
1589
+ <p class="empty-copy">{t(M['content.governance_panel.no_references_public'])}</p>
1590
+ {:else}
1591
+ {#each transparencyPreview.references.slice(0, 3) as reference (reference.id ?? reference.url ?? reference.title)}
1592
+ <div class="transparency-list__item">
1593
+ <strong>{getTransparencyReferenceLabel(reference)}</strong>
1594
+ <span>
1595
+ {t(M['content.governance_panel.reference_fact_counts'], { used: reference.usedFactIds.length, extracted: reference.extractedFacts.length })}
1596
+ </span>
1597
+ </div>
1598
+ {/each}
1599
+ {/if}
1600
+ </div>
1601
+ </div>
1602
+ </div>
1603
+
1604
+ <div class="transparency-card">
1605
+ <div class="transparency-card__header">
1606
+ <strong>{t(M['content.governance_panel.latest_published_snapshot'])}</strong>
1607
+ {#if publishedTransparency?.publicationVersion?.version !== null && publishedTransparency?.publicationVersion?.version !== undefined}
1608
+ <span class="pill pill--passed">v{publishedTransparency.publicationVersion.version}</span>
1609
+ {/if}
1610
+ </div>
1611
+
1612
+ {#if publishedTransparency}
1613
+ <span>
1614
+ Published {formatTimestamp(publishedTransparency.publicationVersion?.createdAt || publishedTransparency.generatedAt)}
1615
+ </span>
1616
+ <span>
1617
+ {t(M['content.governance_panel.timeline_and_corrections'], { events: publishedTransparency.versionHistory.length, corrections: publishedTransparency.corrections.length })}
1618
+ </span>
1619
+ {:else}
1620
+ <p class="empty-copy">
1621
+ {t(M['content.governance_panel.no_published_snapshot'])}
1622
+ </p>
1623
+ {/if}
1624
+ </div>
1625
+ </div>
1626
+ {/if}
1627
+ </div>
1628
+ </details>
1629
+ {/if}
1630
+
1631
+ {#if savedContentId && isSectionVisible('corrections')}
1632
+ <details class="editor-drawer">
1633
+ <summary class="editor-drawer-header">
1634
+ <div style="display: flex; align-items: center; gap: 0.5rem;">
1635
+ Corrections
1636
+ </div>
1637
+ <svg class="drawer-icon" viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
1638
+ </summary>
1639
+ <div class="editor-drawer-content">
1640
+
1641
+ <label class="workflow-field">
1642
+ Summary
1643
+ <input
1644
+ type="text"
1645
+ bind:value={correctionSummary}
1646
+ placeholder={t(M['content.governance_panel.what_was_wrong'])}
1647
+ />
1648
+ </label>
1649
+
1650
+ <label class="workflow-field">
1651
+ {t(M['content.governance_panel.related_fact'])}
1652
+ <select bind:value={correctionFactId}>
1653
+ <option value="">{t(M['content.governance_panel.general_correction'])}</option>
1654
+ {#each selectedFactsResolved as fact (fact.id)}
1655
+ <option value={fact.id ?? ''}>{fact.textRefined}</option>
1656
+ {/each}
1657
+ </select>
1658
+ </label>
1659
+
1660
+ <label class="workflow-field">
1661
+ {t(M['content.governance_panel.corrected_fact_text'])}
1662
+ <textarea
1663
+ rows="4"
1664
+ bind:value={correctedFactText}
1665
+ placeholder={t(M['content.governance_panel.provide_corrected_wording'])}
1666
+ ></textarea>
1667
+ </label>
1668
+
1669
+ <label class="workflow-field">
1670
+ {t(M['content.governance_panel.public_note'])}
1671
+ <textarea
1672
+ rows="3"
1673
+ bind:value={correctionPublicNote}
1674
+ placeholder={t(M['content.governance_panel.optional_public_correction_note'])}
1675
+ ></textarea>
1676
+ </label>
1677
+
1678
+ <label class="checkbox-row">
1679
+ <input type="checkbox" bind:checked={publishCorrection} />
1680
+ {t(M['content.governance_panel.publish_immediately'])}
1681
+ </label>
1682
+
1683
+ <button
1684
+ type="button"
1685
+ disabled={correctionBusy || correctionSummary.trim().length === 0}
1686
+ onclick={() => void issueCorrection()}
1687
+ >
1688
+ {correctionBusy ? 'Issuing correction...' : 'Issue Correction'}
1689
+ </button>
1690
+
1691
+ <div class="review-list">
1692
+ <div class="section-caption">{t(M['content.governance_panel.published_history'])}</div>
1693
+ {#if corrections.length === 0}
1694
+ <p class="empty-copy">{t(M['content.governance_panel.no_corrections_issued'])}</p>
1695
+ {:else}
1696
+ {#each corrections as correction (correction.id ?? `${correction.correctionType}-${correction.createdAt}`)}
1697
+ <div class="review-card">
1698
+ <div class="review-card__header">
1699
+ <strong>{correction.correctionType}</strong>
1700
+ <span class={`pill pill--${correction.status}`}>{correction.status}</span>
1701
+ </div>
1702
+ <p>{correction.summary}</p>
1703
+ <span>{formatTimestamp(correction.publishedAt)}</span>
1704
+ {#if getCorrectionProvenanceCopy(correction)}
1705
+ <span>{getCorrectionProvenanceCopy(correction)}</span>
1706
+ {/if}
1707
+ </div>
1708
+ {/each}
1709
+ {/if}
1710
+ </div>
1711
+ </div>
1712
+ </details>
1713
+ {/if}
1714
+
1715
+ {#if savedContentId && isSectionVisible('versions')}
1716
+ <details class="editor-drawer">
1717
+ <summary class="editor-drawer-header">
1718
+ <div style="display: flex; align-items: center; justify-content: space-between; flex: 1;">
1719
+ <div style="display: flex; align-items: center; gap: 0.5rem;">
1720
+ Versions
1721
+ </div>
1722
+ <button
1723
+ type="button"
1724
+ class="secondary-button"
1725
+ disabled={versionBusy}
1726
+ onclick={(e) => { e.preventDefault(); void createSnapshot(); }}
1727
+ >
1728
+ {versionBusy ? 'Working...' : 'Create Snapshot'}
1729
+ </button>
1730
+ </div>
1731
+ <svg class="drawer-icon" style="margin-left: 1rem;" viewBox="0 0 24 24" width="20" height="20" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>
1732
+ </summary>
1733
+ <div class="editor-drawer-content">
1734
+
1735
+ {#if versions.length === 0}
1736
+ <p class="empty-copy">{t(M['content.governance_panel.no_versions_saved'])}</p>
1737
+ {:else}
1738
+ <div class="review-list">
1739
+ {#each versions as version (version.id ?? `version-${version.version ?? 0}`)}
1740
+ <div class="review-card">
1741
+ <div class="review-card__header">
1742
+ <strong>v{version.version}</strong>
1743
+ <span class="pill pill--neutral">{version.kind}</span>
1744
+ </div>
1745
+ <p>{version.summary || 'Snapshot saved'}</p>
1746
+ {#if getVersionProvenanceCopy(version)}
1747
+ <span>{getVersionProvenanceCopy(version)}</span>
1748
+ {/if}
1749
+ <div class="version-card__footer">
1750
+ <span>{formatTimestamp(version.createdAt)}</span>
1751
+ <button
1752
+ type="button"
1753
+ class="secondary-button"
1754
+ disabled={versionBusy || version.version === null || version.version === undefined}
1755
+ onclick={() => {
1756
+ if (
1757
+ version.version !== null &&
1758
+ version.version !== undefined
1759
+ ) {
1760
+ void restoreVersion(version.version);
1761
+ }
1762
+ }}
1763
+ >
1764
+ Restore
1765
+ </button>
1766
+ </div>
1767
+ </div>
1768
+ {/each}
1769
+ </div>
1770
+ {/if}
1771
+ </div>
1772
+ </details>
1773
+ {/if}
1774
+ </div>
1775
+
1776
+ <style>
1777
+ .editor-drawer {
1778
+ margin: 0 0 2rem 0;
1779
+ padding: 0;
1780
+ }
1781
+
1782
+ .editor-drawer-header {
1783
+ display: flex;
1784
+ justify-content: space-between;
1785
+ align-items: center;
1786
+ padding: 0 0 1.25rem 0;
1787
+ font-size: var(--smrt-typography-headline-small-size, 1.5rem);
1788
+ font-weight: var(--smrt-typography-weight-bold, 700);
1789
+ color: var(--smrt-color-on-surface);
1790
+ cursor: pointer;
1791
+ list-style: none; /* Hide default triangle */
1792
+ user-select: none;
1793
+ transition: color 0.2s;
1794
+ margin: 0;
1795
+ }
1796
+
1797
+ /* Hide the default details marker */
1798
+ .editor-drawer-header::-webkit-details-marker {
1799
+ display: none;
1800
+ }
1801
+
1802
+ .editor-drawer-header:hover {
1803
+ color: var(--smrt-color-primary);
1804
+ }
1805
+
1806
+ .drawer-icon {
1807
+ color: var(--smrt-color-outline);
1808
+ transition: transform 0.3s ease;
1809
+ }
1810
+
1811
+ .editor-drawer[open] .drawer-icon {
1812
+ transform: rotate(180deg);
1813
+ }
1814
+
1815
+ .editor-drawer-content {
1816
+ padding: 0;
1817
+ display: flex;
1818
+ flex-direction: column;
1819
+ gap: 1.5rem;
1820
+ }
1821
+
1822
+ .factual-workflow {
1823
+ display: flex;
1824
+ flex-direction: column;
1825
+ gap: 1.25rem;
1826
+ }
1827
+
1828
+ .workflow-grid {
1829
+ display: grid;
1830
+ gap: 1.25rem;
1831
+ }
1832
+
1833
+ @media (min-width: 900px) {
1834
+ .workflow-grid {
1835
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1836
+ }
1837
+ }
1838
+
1839
+ .workflow-section {
1840
+ background: var(--smrt-color-surface-container-low);
1841
+ border: 1px solid var(--smrt-color-outline-variant);
1842
+ border-radius: 0.75rem;
1843
+ padding: 1rem;
1844
+ display: flex;
1845
+ flex-direction: column;
1846
+ gap: 0.9rem;
1847
+ }
1848
+
1849
+ .workflow-section__header {
1850
+ display: flex;
1851
+ align-items: center;
1852
+ justify-content: space-between;
1853
+ gap: 1rem;
1854
+ }
1855
+
1856
+ .workflow-field-group {
1857
+ display: flex;
1858
+ flex-direction: column;
1859
+ gap: 0.75rem;
1860
+ }
1861
+
1862
+ .section-status,
1863
+ .section-caption,
1864
+ .empty-copy,
1865
+ .claim-audit-help,
1866
+ .review-card span,
1867
+ .claim-audit-counts,
1868
+ .claim-audit-item span,
1869
+ .fact-result span,
1870
+ .fact-chip span {
1871
+ color: var(--smrt-color-on-surface-variant);
1872
+ font-size: var(--smrt-typography-body-medium-size, 0.85rem);
1873
+ }
1874
+
1875
+ .fact-search,
1876
+ .fact-pagination,
1877
+ .fact-pagination__actions,
1878
+ .claim-audit-toolbar,
1879
+ .claim-audit-counts,
1880
+ .review-actions,
1881
+ .version-card__footer {
1882
+ display: flex;
1883
+ gap: 0.75rem;
1884
+ align-items: center;
1885
+ }
1886
+
1887
+ .fact-pagination {
1888
+ justify-content: space-between;
1889
+ flex-wrap: wrap;
1890
+ color: var(--smrt-color-on-surface-variant);
1891
+ font-size: var(--smrt-typography-body-medium-size, 0.85rem);
1892
+ }
1893
+
1894
+ .fact-search input,
1895
+ .workflow-field input,
1896
+ .workflow-field textarea,
1897
+ .workflow-field select {
1898
+ width: 100%;
1899
+ box-sizing: border-box;
1900
+ padding: 0.75rem;
1901
+ border-radius: 0.5rem;
1902
+ border: 1px solid var(--smrt-color-outline);
1903
+ background: var(--smrt-color-surface);
1904
+ color: var(--smrt-color-on-surface);
1905
+ font-family: inherit;
1906
+ }
1907
+
1908
+ .workflow-field {
1909
+ display: flex;
1910
+ flex-direction: column;
1911
+ gap: 0.4rem;
1912
+ color: var(--smrt-color-on-surface-variant);
1913
+ font-size: var(--smrt-typography-label-large-size, 0.875rem);
1914
+ font-weight: var(--smrt-typography-weight-medium, 500);
1915
+ }
1916
+
1917
+ .fact-search button,
1918
+ .review-actions button {
1919
+ border: none;
1920
+ border-radius: 0.5rem;
1921
+ padding: 0.7rem 0.95rem;
1922
+ background: var(--smrt-color-primary);
1923
+ color: var(--smrt-color-on-primary, white);
1924
+ cursor: pointer;
1925
+ font-weight: var(--smrt-typography-weight-semibold, 600);
1926
+ }
1927
+
1928
+ .fact-search button:disabled,
1929
+ .review-actions button:disabled,
1930
+ .fact-pagination button:disabled {
1931
+ cursor: not-allowed;
1932
+ opacity: 0.65;
1933
+ }
1934
+
1935
+ .secondary-button {
1936
+ background: var(--smrt-color-surface);
1937
+ color: var(--smrt-color-on-surface);
1938
+ border: 1px solid var(--smrt-color-outline-variant);
1939
+ }
1940
+
1941
+ .fact-chip-list,
1942
+ .fact-catalog__list,
1943
+ .claim-audit-group,
1944
+ .claim-audit-item__body,
1945
+ .claim-audit-evidence,
1946
+ .claim-audit-match,
1947
+ .review-list {
1948
+ display: flex;
1949
+ flex-direction: column;
1950
+ gap: 0.75rem;
1951
+ }
1952
+
1953
+ .claim-audit-toolbar {
1954
+ justify-content: space-between;
1955
+ flex-wrap: wrap;
1956
+ }
1957
+
1958
+ .claim-audit-counts {
1959
+ flex-wrap: wrap;
1960
+ }
1961
+
1962
+ .claim-audit-warnings {
1963
+ border-radius: 0.6rem;
1964
+ background: color-mix(in srgb, var(--smrt-color-warning) 12%, transparent);
1965
+ color: var(--smrt-color-on-warning-container);
1966
+ padding: 0.75rem 0.9rem;
1967
+ }
1968
+
1969
+ .claim-audit-warnings p,
1970
+ .claim-audit-item p {
1971
+ margin: 0;
1972
+ }
1973
+
1974
+ .claim-audit-item {
1975
+ border: 1px solid var(--smrt-color-outline-variant);
1976
+ border-radius: 0.6rem;
1977
+ background: var(--smrt-color-surface);
1978
+ padding: 0.75rem;
1979
+ }
1980
+
1981
+ .claim-audit-item summary {
1982
+ cursor: pointer;
1983
+ display: flex;
1984
+ align-items: center;
1985
+ gap: 0.6rem;
1986
+ }
1987
+
1988
+ .claim-audit-item__body {
1989
+ padding-top: 0.75rem;
1990
+ }
1991
+
1992
+ .claim-audit-select {
1993
+ display: inline-flex;
1994
+ line-height: 1;
1995
+ }
1996
+
1997
+ .claim-audit-item__actions {
1998
+ display: flex;
1999
+ justify-content: flex-end;
2000
+ }
2001
+
2002
+ .review-profile-list {
2003
+ display: flex;
2004
+ flex-direction: column;
2005
+ gap: 0.6rem;
2006
+ }
2007
+
2008
+ .fact-chip,
2009
+ .fact-result,
2010
+ .review-card {
2011
+ background: var(--smrt-color-surface);
2012
+ border: 1px solid var(--smrt-color-outline-variant);
2013
+ border-radius: 0.75rem;
2014
+ padding: 0.85rem;
2015
+ }
2016
+
2017
+ .fact-chip,
2018
+ .fact-result,
2019
+ .review-card__header,
2020
+ .version-card__footer {
2021
+ display: flex;
2022
+ align-items: flex-start;
2023
+ justify-content: space-between;
2024
+ gap: 0.75rem;
2025
+ }
2026
+
2027
+ .fact-chip__body,
2028
+ .fact-result__body {
2029
+ display: flex;
2030
+ flex-direction: column;
2031
+ gap: 0.25rem;
2032
+ }
2033
+
2034
+ .fact-chip__remove {
2035
+ background: transparent !important;
2036
+ color: var(--smrt-color-error) !important;
2037
+ padding: 0 !important;
2038
+ }
2039
+
2040
+ .review-card {
2041
+ display: flex;
2042
+ flex-direction: column;
2043
+ gap: 0.4rem;
2044
+ }
2045
+
2046
+ .review-card__header {
2047
+ align-items: center;
2048
+ }
2049
+
2050
+ .review-profile-card {
2051
+ display: flex;
2052
+ flex-direction: column;
2053
+ gap: 0.75rem;
2054
+ background: var(--smrt-color-surface);
2055
+ border: 1px solid var(--smrt-color-outline-variant);
2056
+ border-radius: 0.75rem;
2057
+ padding: 0.85rem;
2058
+ }
2059
+
2060
+ .review-profile-card__header,
2061
+ .review-profile-item,
2062
+ .review-profile-item__meta {
2063
+ display: flex;
2064
+ align-items: center;
2065
+ justify-content: space-between;
2066
+ gap: 0.75rem;
2067
+ }
2068
+
2069
+ .review-profile-card__badges {
2070
+ display: flex;
2071
+ align-items: center;
2072
+ gap: 0.5rem;
2073
+ flex-wrap: wrap;
2074
+ justify-content: flex-end;
2075
+ }
2076
+
2077
+ .review-profile-callout {
2078
+ display: flex;
2079
+ flex-direction: column;
2080
+ gap: 0.3rem;
2081
+ border-radius: 0.65rem;
2082
+ border: 1px solid color-mix(in srgb, var(--smrt-color-warning) 28%, transparent);
2083
+ background: color-mix(in srgb, var(--smrt-color-warning) 10%, transparent);
2084
+ padding: 0.75rem;
2085
+ }
2086
+
2087
+ .review-profile-item {
2088
+ padding: 0.65rem 0.75rem;
2089
+ border: 1px solid var(--smrt-color-outline-variant);
2090
+ border-radius: 0.65rem;
2091
+ background: var(--smrt-color-surface-container-low);
2092
+ }
2093
+
2094
+ .review-profile-item__body {
2095
+ display: flex;
2096
+ flex-direction: column;
2097
+ gap: 0.2rem;
2098
+ }
2099
+
2100
+ .review-findings {
2101
+ display: flex;
2102
+ flex-direction: column;
2103
+ gap: 0.5rem;
2104
+ }
2105
+
2106
+ .review-finding {
2107
+ display: flex;
2108
+ align-items: flex-start;
2109
+ gap: 0.6rem;
2110
+ }
2111
+
2112
+ .review-finding__body {
2113
+ display: flex;
2114
+ flex-direction: column;
2115
+ gap: 0.15rem;
2116
+ }
2117
+
2118
+ .transparency-grid {
2119
+ display: grid;
2120
+ gap: 1rem;
2121
+ }
2122
+
2123
+ @media (min-width: 900px) {
2124
+ .transparency-grid {
2125
+ grid-template-columns: repeat(2, minmax(0, 1fr));
2126
+ }
2127
+ }
2128
+
2129
+ .transparency-stats {
2130
+ display: grid;
2131
+ gap: 0.75rem;
2132
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
2133
+ }
2134
+
2135
+ .transparency-stat,
2136
+ .transparency-card,
2137
+ .transparency-list__item {
2138
+ background: var(--smrt-color-surface);
2139
+ border: 1px solid var(--smrt-color-outline-variant);
2140
+ border-radius: 0.75rem;
2141
+ }
2142
+
2143
+ .transparency-stat {
2144
+ padding: 0.8rem;
2145
+ display: flex;
2146
+ flex-direction: column;
2147
+ gap: 0.25rem;
2148
+ }
2149
+
2150
+ .transparency-card {
2151
+ padding: 0.9rem;
2152
+ display: flex;
2153
+ flex-direction: column;
2154
+ gap: 0.75rem;
2155
+ }
2156
+
2157
+ .transparency-card__header {
2158
+ display: flex;
2159
+ align-items: center;
2160
+ justify-content: space-between;
2161
+ gap: 0.75rem;
2162
+ }
2163
+
2164
+ .transparency-card__prompt {
2165
+ margin: 0;
2166
+ color: var(--smrt-color-on-surface-variant);
2167
+ white-space: pre-wrap;
2168
+ }
2169
+
2170
+ .transparency-list {
2171
+ display: flex;
2172
+ flex-direction: column;
2173
+ gap: 0.75rem;
2174
+ }
2175
+
2176
+ .transparency-list__section {
2177
+ display: flex;
2178
+ flex-direction: column;
2179
+ gap: 0.5rem;
2180
+ }
2181
+
2182
+ .transparency-list__item {
2183
+ padding: 0.7rem 0.8rem;
2184
+ display: flex;
2185
+ flex-direction: column;
2186
+ gap: 0.2rem;
2187
+ }
2188
+
2189
+ .pill {
2190
+ border-radius: var(--smrt-radius-full, 9999px);
2191
+ padding: 0.2rem 0.55rem;
2192
+ font-size: var(--smrt-typography-label-medium-size, 0.75rem);
2193
+ font-weight: var(--smrt-typography-weight-bold, 700);
2194
+ text-transform: capitalize;
2195
+ }
2196
+
2197
+ .pill--passed {
2198
+ background: color-mix(in srgb, var(--smrt-color-success) 14%, transparent);
2199
+ color: var(--smrt-color-on-success-container);
2200
+ }
2201
+
2202
+ .pill--flagged {
2203
+ background: color-mix(in srgb, var(--smrt-color-warning) 16%, transparent);
2204
+ color: var(--smrt-color-on-warning-container);
2205
+ }
2206
+
2207
+ .pill--failed,
2208
+ .pill--draft,
2209
+ .pill--retracted {
2210
+ background: color-mix(in srgb, var(--smrt-color-error) 14%, transparent);
2211
+ color: var(--smrt-color-on-error-container);
2212
+ }
2213
+
2214
+ .pill--published,
2215
+ .pill--neutral {
2216
+ background: color-mix(in srgb, var(--smrt-color-primary) 14%, transparent);
2217
+ color: var(--smrt-color-on-primary-container);
2218
+ }
2219
+
2220
+ .checkbox-row {
2221
+ display: flex;
2222
+ align-items: center;
2223
+ gap: 0.55rem;
2224
+ color: var(--smrt-color-on-surface);
2225
+ }
2226
+
2227
+ .workflow-error,
2228
+ .workflow-notice {
2229
+ margin: 0;
2230
+ border-radius: 0.6rem;
2231
+ padding: 0.75rem 0.9rem;
2232
+ font-size: var(--smrt-typography-body-medium-size, 0.9rem);
2233
+ }
2234
+
2235
+ .workflow-error {
2236
+ background: color-mix(in srgb, var(--smrt-color-error) 10%, transparent);
2237
+ color: var(--smrt-color-on-error-container);
2238
+ }
2239
+
2240
+ .workflow-notice {
2241
+ background: color-mix(in srgb, var(--smrt-color-primary) 10%, transparent);
2242
+ color: var(--smrt-color-on-primary-container);
2243
+ }
2244
+ </style>