@ternent/core 0.0.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 (313) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +17 -0
  3. package/.github/workflows/deploy-armour.yml +42 -0
  4. package/.github/workflows/deploy-identity.yml +42 -0
  5. package/.github/workflows/deploy-seal.yml +42 -0
  6. package/.github/workflows/deploy-ui.yml +42 -0
  7. package/.github/workflows/deploy-utils.yml +42 -0
  8. package/.github/workflows/release-create.yml +59 -0
  9. package/.github/workflows/release-publish.yml +54 -0
  10. package/.nvmrc +1 -0
  11. package/.ops/publish.mjs +31 -0
  12. package/package.json +16 -0
  13. package/packages/README.md +0 -0
  14. package/packages/armour/CHANGELOG.md +66 -0
  15. package/packages/armour/CLAUDE.md +8 -0
  16. package/packages/armour/README.md +103 -0
  17. package/packages/armour/SPEC.md +92 -0
  18. package/packages/armour/package.json +45 -0
  19. package/packages/armour/src/constants.ts +5 -0
  20. package/packages/armour/src/deps.d.ts +56 -0
  21. package/packages/armour/src/errors.ts +172 -0
  22. package/packages/armour/src/files.ts +73 -0
  23. package/packages/armour/src/identity.ts +72 -0
  24. package/packages/armour/src/index.ts +56 -0
  25. package/packages/armour/src/init.ts +10 -0
  26. package/packages/armour/src/passphrase.ts +33 -0
  27. package/packages/armour/src/recipients.ts +73 -0
  28. package/packages/armour/src/text.ts +68 -0
  29. package/packages/armour/src/types.ts +93 -0
  30. package/packages/armour/test/armour.test.ts +270 -0
  31. package/packages/armour/tsconfig.build.json +12 -0
  32. package/packages/armour/tsconfig.json +12 -0
  33. package/packages/armour/vite.config.ts +29 -0
  34. package/packages/concord/CHANGELOG.md +83 -0
  35. package/packages/concord/CLAUDE.md +9 -0
  36. package/packages/concord/README.md +146 -0
  37. package/packages/concord/SPEC.md +287 -0
  38. package/packages/concord/package.json +51 -0
  39. package/packages/concord/src/app.ts +717 -0
  40. package/packages/concord/src/errors.ts +9 -0
  41. package/packages/concord/src/index.ts +20 -0
  42. package/packages/concord/src/types.ts +127 -0
  43. package/packages/concord/test/concord.test.ts +978 -0
  44. package/packages/concord/tsconfig.json +12 -0
  45. package/packages/concord/vite.browser.config.ts +27 -0
  46. package/packages/concord/vite.config.ts +35 -0
  47. package/packages/concord/vite.config.ts.timestamp-1774262297922-ffd76e35ea668.mjs +83 -0
  48. package/packages/identity/CHANGELOG.md +47 -0
  49. package/packages/identity/README.md +236 -0
  50. package/packages/identity/package.json +41 -0
  51. package/packages/identity/src/index.ts +538 -0
  52. package/packages/identity/test/identity.test.ts +172 -0
  53. package/packages/identity/tsconfig.build.json +12 -0
  54. package/packages/identity/vite.config.ts +17 -0
  55. package/packages/ledger/CHANGELOG.md +69 -0
  56. package/packages/ledger/CLAUDE.md +9 -0
  57. package/packages/ledger/SPEC.md +304 -0
  58. package/packages/ledger/package.json +48 -0
  59. package/packages/ledger/src/index.ts +2 -0
  60. package/packages/ledger/src/ledger.ts +1286 -0
  61. package/packages/ledger/src/seal-cli.d.ts +25 -0
  62. package/packages/ledger/src/types.ts +294 -0
  63. package/packages/ledger/test/ledger.test.ts +838 -0
  64. package/packages/ledger/tsconfig.json +12 -0
  65. package/packages/ledger/vite.browser.config.ts +27 -0
  66. package/packages/ledger/vite.config.ts +39 -0
  67. package/packages/seal/CHANGELOG.md +137 -0
  68. package/packages/seal/CLAUDE.md +8 -0
  69. package/packages/seal/README.md +258 -0
  70. package/packages/seal/bin/seal +6 -0
  71. package/packages/seal/package.json +59 -0
  72. package/packages/seal/src/artifact.ts +380 -0
  73. package/packages/seal/src/cli.ts +372 -0
  74. package/packages/seal/src/commands/identity.ts +52 -0
  75. package/packages/seal/src/commands/manifest.ts +71 -0
  76. package/packages/seal/src/commands/publicKey.ts +7 -0
  77. package/packages/seal/src/commands/sign.ts +56 -0
  78. package/packages/seal/src/commands/verify.ts +54 -0
  79. package/packages/seal/src/crypto.ts +85 -0
  80. package/packages/seal/src/errors.ts +88 -0
  81. package/packages/seal/src/index.ts +5 -0
  82. package/packages/seal/src/manifest.ts +114 -0
  83. package/packages/seal/src/node.ts +18 -0
  84. package/packages/seal/src/proof.ts +344 -0
  85. package/packages/seal/test/artifact.test.ts +86 -0
  86. package/packages/seal/test/cli.test.ts +208 -0
  87. package/packages/seal/test/crypto.test.ts +21 -0
  88. package/packages/seal/test/manifest.test.ts +32 -0
  89. package/packages/seal/test/proof.test.ts +60 -0
  90. package/packages/seal/tsconfig.json +12 -0
  91. package/packages/seal/vite.config.ts +54 -0
  92. package/packages/ui/CHANGELOG.md +393 -0
  93. package/packages/ui/README.md +57 -0
  94. package/packages/ui/jsconfig.json +19 -0
  95. package/packages/ui/package.json +64 -0
  96. package/packages/ui/scripts/check-tokens.js +56 -0
  97. package/packages/ui/scripts/generate-theme-css.mjs +85 -0
  98. package/packages/ui/src/design-system/base.css +8 -0
  99. package/packages/ui/src/design-system/docs/ACCESSIBILITY_RULES.md +186 -0
  100. package/packages/ui/src/design-system/docs/AI_SYSTEM.md +281 -0
  101. package/packages/ui/src/design-system/docs/PATTERN_RULES.md +83 -0
  102. package/packages/ui/src/design-system/docs/PRIMITIVE_RULES.md +258 -0
  103. package/packages/ui/src/design-system/docs/TOKEN_RULES.md +235 -0
  104. package/packages/ui/src/design-system/docs/VISUAL_DIRECTION.md +68 -0
  105. package/packages/ui/src/design-system/foundation.js +420 -0
  106. package/packages/ui/src/design-system/tokens.css +140 -0
  107. package/packages/ui/src/design-system/tokens.js +327 -0
  108. package/packages/ui/src/design-system/utils.js +246 -0
  109. package/packages/ui/src/main.js +4 -0
  110. package/packages/ui/src/patterns/FeatureCard/FeatureCard.spec.md +24 -0
  111. package/packages/ui/src/patterns/FeatureCard/FeatureCard.types.ts +8 -0
  112. package/packages/ui/src/patterns/FeatureCard/FeatureCard.vue +175 -0
  113. package/packages/ui/src/patterns/FormField/FormField.spec.md +65 -0
  114. package/packages/ui/src/patterns/FormField/FormField.types.ts +11 -0
  115. package/packages/ui/src/patterns/FormField/FormField.vue +87 -0
  116. package/packages/ui/src/patterns/IdentityGlyph/IdentityGlyph.vue +61 -0
  117. package/packages/ui/src/patterns/IdentityGlyph/IdentityHandle.vue +58 -0
  118. package/packages/ui/src/patterns/IdentityGlyph/identityGlyph.types.ts +36 -0
  119. package/packages/ui/src/patterns/IdentityGlyph/identityGlyph.utils.ts +585 -0
  120. package/packages/ui/src/patterns/IdentityGlyph/index.ts +5 -0
  121. package/packages/ui/src/patterns/KeyValueList/KeyValueList.spec.md +28 -0
  122. package/packages/ui/src/patterns/KeyValueList/KeyValueList.types.ts +16 -0
  123. package/packages/ui/src/patterns/KeyValueList/KeyValueList.vue +50 -0
  124. package/packages/ui/src/patterns/LandingPage/LandingIcon.vue +90 -0
  125. package/packages/ui/src/patterns/LandingPage/LandingPage.spec.md +24 -0
  126. package/packages/ui/src/patterns/LandingPage/LandingPage.types.ts +212 -0
  127. package/packages/ui/src/patterns/LandingPage/LandingPage.vue +599 -0
  128. package/packages/ui/src/patterns/ListWorkspaceLayout/ListWorkspaceLayout.test.ts +33 -0
  129. package/packages/ui/src/patterns/ListWorkspaceLayout/ListWorkspaceLayout.vue +44 -0
  130. package/packages/ui/src/patterns/Logo/Logo.spec.md +22 -0
  131. package/packages/ui/src/patterns/Logo/Logo.vue +160 -0
  132. package/packages/ui/src/patterns/PageSurface/PageSurface.spec.md +15 -0
  133. package/packages/ui/src/patterns/PageSurface/PageSurface.vue +85 -0
  134. package/packages/ui/src/patterns/PanelChrome/PanelChrome.spec.md +39 -0
  135. package/packages/ui/src/patterns/PanelChrome/PanelChrome.types.ts +1 -0
  136. package/packages/ui/src/patterns/PanelChrome/PanelChrome.vue +187 -0
  137. package/packages/ui/src/patterns/PreviewPanel/PreviewPanel.spec.md +31 -0
  138. package/packages/ui/src/patterns/PreviewPanel/PreviewPanel.types.ts +23 -0
  139. package/packages/ui/src/patterns/PreviewPanel/PreviewPanel.vue +354 -0
  140. package/packages/ui/src/patterns/RecordList/RecordList.spec.md +35 -0
  141. package/packages/ui/src/patterns/RecordList/RecordList.test.ts +42 -0
  142. package/packages/ui/src/patterns/RecordList/RecordList.types.ts +9 -0
  143. package/packages/ui/src/patterns/RecordList/RecordList.utils.ts +5 -0
  144. package/packages/ui/src/patterns/RecordList/RecordList.vue +134 -0
  145. package/packages/ui/src/patterns/SectionClarifier/SectionClarifier.vue +85 -0
  146. package/packages/ui/src/patterns/SectionIntro/SectionIntro.spec.md +25 -0
  147. package/packages/ui/src/patterns/SectionIntro/SectionIntro.types.ts +7 -0
  148. package/packages/ui/src/patterns/SectionIntro/SectionIntro.vue +141 -0
  149. package/packages/ui/src/patterns/SidebarNav/SidebarNav.spec.md +34 -0
  150. package/packages/ui/src/patterns/SidebarNav/SidebarNav.types.ts +17 -0
  151. package/packages/ui/src/patterns/SidebarNav/SidebarNav.vue +110 -0
  152. package/packages/ui/src/patterns/SplitView/SplitView.spec.md +28 -0
  153. package/packages/ui/src/patterns/SplitView/SplitView.test.ts +22 -0
  154. package/packages/ui/src/patterns/SplitView/SplitView.types.ts +3 -0
  155. package/packages/ui/src/patterns/SplitView/SplitView.utils.ts +13 -0
  156. package/packages/ui/src/patterns/SplitView/SplitView.vue +39 -0
  157. package/packages/ui/src/patterns/StepList/StepList.spec.md +15 -0
  158. package/packages/ui/src/patterns/StepList/StepList.types.ts +4 -0
  159. package/packages/ui/src/patterns/StepList/StepList.vue +91 -0
  160. package/packages/ui/src/patterns/Verification/VerificationBadge.vue +97 -0
  161. package/packages/ui/src/patterns/Verification/VerificationComponents.test.ts +153 -0
  162. package/packages/ui/src/patterns/Verification/VerificationDetailsPanel.vue +270 -0
  163. package/packages/ui/src/patterns/Verification/VerificationSummary.vue +171 -0
  164. package/packages/ui/src/patterns/Verification/index.ts +6 -0
  165. package/packages/ui/src/patterns/Verification/verification.types.ts +8 -0
  166. package/packages/ui/src/patterns/Verification/verification.utils.test.ts +37 -0
  167. package/packages/ui/src/patterns/Verification/verification.utils.ts +75 -0
  168. package/packages/ui/src/patterns/index.ts +25 -0
  169. package/packages/ui/src/primitives/Accordian/Accordian.vue +11 -0
  170. package/packages/ui/src/primitives/Accordian/AccordianItem.vue +14 -0
  171. package/packages/ui/src/primitives/Accordion/Accordion.props.ts +21 -0
  172. package/packages/ui/src/primitives/Accordion/Accordion.spec.md +50 -0
  173. package/packages/ui/src/primitives/Accordion/Accordion.types.ts +4 -0
  174. package/packages/ui/src/primitives/Accordion/Accordion.variants.ts +12 -0
  175. package/packages/ui/src/primitives/Accordion/Accordion.vue +71 -0
  176. package/packages/ui/src/primitives/Accordion/AccordionItem.props.ts +14 -0
  177. package/packages/ui/src/primitives/Accordion/AccordionItem.vue +40 -0
  178. package/packages/ui/src/primitives/Badge/Badge.props.ts +17 -0
  179. package/packages/ui/src/primitives/Badge/Badge.spec.md +17 -0
  180. package/packages/ui/src/primitives/Badge/Badge.types.ts +15 -0
  181. package/packages/ui/src/primitives/Badge/Badge.variants.ts +48 -0
  182. package/packages/ui/src/primitives/Badge/Badge.vue +31 -0
  183. package/packages/ui/src/primitives/Button/Button.props.ts +29 -0
  184. package/packages/ui/src/primitives/Button/Button.spec.md +139 -0
  185. package/packages/ui/src/primitives/Button/Button.types.ts +19 -0
  186. package/packages/ui/src/primitives/Button/Button.variants.ts +72 -0
  187. package/packages/ui/src/primitives/Button/Button.vue +90 -0
  188. package/packages/ui/src/primitives/Card/Card.props.ts +17 -0
  189. package/packages/ui/src/primitives/Card/Card.spec.md +29 -0
  190. package/packages/ui/src/primitives/Card/Card.types.ts +12 -0
  191. package/packages/ui/src/primitives/Card/Card.variants.ts +27 -0
  192. package/packages/ui/src/primitives/Card/Card.vue +37 -0
  193. package/packages/ui/src/primitives/Checkbox/Checkbox.props.ts +21 -0
  194. package/packages/ui/src/primitives/Checkbox/Checkbox.spec.md +51 -0
  195. package/packages/ui/src/primitives/Checkbox/Checkbox.types.ts +4 -0
  196. package/packages/ui/src/primitives/Checkbox/Checkbox.variants.ts +34 -0
  197. package/packages/ui/src/primitives/Checkbox/Checkbox.vue +92 -0
  198. package/packages/ui/src/primitives/Dialog/Dialog.props.ts +29 -0
  199. package/packages/ui/src/primitives/Dialog/Dialog.spec.md +52 -0
  200. package/packages/ui/src/primitives/Dialog/Dialog.types.ts +3 -0
  201. package/packages/ui/src/primitives/Dialog/Dialog.variants.ts +27 -0
  202. package/packages/ui/src/primitives/Dialog/Dialog.vue +78 -0
  203. package/packages/ui/src/primitives/Drawer/Drawer.props.ts +33 -0
  204. package/packages/ui/src/primitives/Drawer/Drawer.spec.md +50 -0
  205. package/packages/ui/src/primitives/Drawer/Drawer.types.ts +5 -0
  206. package/packages/ui/src/primitives/Drawer/Drawer.variants.ts +35 -0
  207. package/packages/ui/src/primitives/Drawer/Drawer.vue +88 -0
  208. package/packages/ui/src/primitives/FieldMessage/FieldMessage.props.ts +17 -0
  209. package/packages/ui/src/primitives/FieldMessage/FieldMessage.spec.md +35 -0
  210. package/packages/ui/src/primitives/FieldMessage/FieldMessage.types.ts +5 -0
  211. package/packages/ui/src/primitives/FieldMessage/FieldMessage.variants.ts +14 -0
  212. package/packages/ui/src/primitives/FieldMessage/FieldMessage.vue +40 -0
  213. package/packages/ui/src/primitives/FileInput/FileInput.props.ts +41 -0
  214. package/packages/ui/src/primitives/FileInput/FileInput.types.ts +6 -0
  215. package/packages/ui/src/primitives/FileInput/FileInput.variants.ts +46 -0
  216. package/packages/ui/src/primitives/FileInput/FileInput.vue +163 -0
  217. package/packages/ui/src/primitives/Input/Input.props.ts +29 -0
  218. package/packages/ui/src/primitives/Input/Input.spec.md +79 -0
  219. package/packages/ui/src/primitives/Input/Input.types.ts +13 -0
  220. package/packages/ui/src/primitives/Input/Input.variants.ts +54 -0
  221. package/packages/ui/src/primitives/Input/Input.vue +99 -0
  222. package/packages/ui/src/primitives/Label/Label.props.ts +25 -0
  223. package/packages/ui/src/primitives/Label/Label.spec.md +31 -0
  224. package/packages/ui/src/primitives/Label/Label.types.ts +3 -0
  225. package/packages/ui/src/primitives/Label/Label.variants.ts +17 -0
  226. package/packages/ui/src/primitives/Label/Label.vue +38 -0
  227. package/packages/ui/src/primitives/Menu/Menu.props.ts +17 -0
  228. package/packages/ui/src/primitives/Menu/Menu.spec.md +38 -0
  229. package/packages/ui/src/primitives/Menu/Menu.types.ts +10 -0
  230. package/packages/ui/src/primitives/Menu/Menu.variants.ts +10 -0
  231. package/packages/ui/src/primitives/Menu/Menu.vue +57 -0
  232. package/packages/ui/src/primitives/Popover/Popover.props.ts +25 -0
  233. package/packages/ui/src/primitives/Popover/Popover.spec.md +49 -0
  234. package/packages/ui/src/primitives/Popover/Popover.types.ts +3 -0
  235. package/packages/ui/src/primitives/Popover/Popover.variants.ts +18 -0
  236. package/packages/ui/src/primitives/Popover/Popover.vue +74 -0
  237. package/packages/ui/src/primitives/RadioGroup/RadioGroup.props.ts +29 -0
  238. package/packages/ui/src/primitives/RadioGroup/RadioGroup.spec.md +50 -0
  239. package/packages/ui/src/primitives/RadioGroup/RadioGroup.types.ts +12 -0
  240. package/packages/ui/src/primitives/RadioGroup/RadioGroup.variants.ts +48 -0
  241. package/packages/ui/src/primitives/RadioGroup/RadioGroup.vue +87 -0
  242. package/packages/ui/src/primitives/Separator/Separator.props.ts +9 -0
  243. package/packages/ui/src/primitives/Separator/Separator.spec.md +15 -0
  244. package/packages/ui/src/primitives/Separator/Separator.types.ts +3 -0
  245. package/packages/ui/src/primitives/Separator/Separator.variants.ts +8 -0
  246. package/packages/ui/src/primitives/Separator/Separator.vue +23 -0
  247. package/packages/ui/src/primitives/Skeleton/Skeleton.props.ts +21 -0
  248. package/packages/ui/src/primitives/Skeleton/Skeleton.spec.md +18 -0
  249. package/packages/ui/src/primitives/Skeleton/Skeleton.types.ts +5 -0
  250. package/packages/ui/src/primitives/Skeleton/Skeleton.variants.ts +18 -0
  251. package/packages/ui/src/primitives/Skeleton/Skeleton.vue +37 -0
  252. package/packages/ui/src/primitives/Spinner/Spinner.props.ts +13 -0
  253. package/packages/ui/src/primitives/Spinner/Spinner.spec.md +16 -0
  254. package/packages/ui/src/primitives/Spinner/Spinner.types.ts +5 -0
  255. package/packages/ui/src/primitives/Spinner/Spinner.variants.ts +15 -0
  256. package/packages/ui/src/primitives/Spinner/Spinner.vue +33 -0
  257. package/packages/ui/src/primitives/SplitButton/SplitButton.vue +108 -0
  258. package/packages/ui/src/primitives/Switch/Switch.props.ts +21 -0
  259. package/packages/ui/src/primitives/Switch/Switch.spec.md +49 -0
  260. package/packages/ui/src/primitives/Switch/Switch.types.ts +3 -0
  261. package/packages/ui/src/primitives/Switch/Switch.variants.ts +34 -0
  262. package/packages/ui/src/primitives/Switch/Switch.vue +71 -0
  263. package/packages/ui/src/primitives/Tabs/Tabs.props.ts +25 -0
  264. package/packages/ui/src/primitives/Tabs/Tabs.spec.md +48 -0
  265. package/packages/ui/src/primitives/Tabs/Tabs.types.ts +11 -0
  266. package/packages/ui/src/primitives/Tabs/Tabs.variants.ts +28 -0
  267. package/packages/ui/src/primitives/Tabs/Tabs.vue +59 -0
  268. package/packages/ui/src/primitives/Textarea/Textarea.props.ts +33 -0
  269. package/packages/ui/src/primitives/Textarea/Textarea.spec.md +59 -0
  270. package/packages/ui/src/primitives/Textarea/Textarea.types.ts +5 -0
  271. package/packages/ui/src/primitives/Textarea/Textarea.variants.ts +27 -0
  272. package/packages/ui/src/primitives/Textarea/Textarea.vue +74 -0
  273. package/packages/ui/src/primitives/Tooltip/Tooltip.props.ts +21 -0
  274. package/packages/ui/src/primitives/Tooltip/Tooltip.spec.md +45 -0
  275. package/packages/ui/src/primitives/Tooltip/Tooltip.types.ts +3 -0
  276. package/packages/ui/src/primitives/Tooltip/Tooltip.variants.ts +4 -0
  277. package/packages/ui/src/primitives/Tooltip/Tooltip.vue +31 -0
  278. package/packages/ui/src/primitives/TreeView/TreeView.types.ts +10 -0
  279. package/packages/ui/src/primitives/TreeView/TreeView.vue +113 -0
  280. package/packages/ui/src/primitives/TreeView/TreeViewNode.vue +190 -0
  281. package/packages/ui/src/primitives/index.ts +29 -0
  282. package/packages/ui/src/style.css +7 -0
  283. package/packages/ui/src/style.js +1 -0
  284. package/packages/ui/src/themes/armour.css +147 -0
  285. package/packages/ui/src/themes/aurora.css +147 -0
  286. package/packages/ui/src/themes/citrine-ash.css +147 -0
  287. package/packages/ui/src/themes/concord.css +147 -0
  288. package/packages/ui/src/themes/garnet-honey.css +147 -0
  289. package/packages/ui/src/themes/harbor-rose.css +147 -0
  290. package/packages/ui/src/themes/ledger.css +147 -0
  291. package/packages/ui/src/themes/neon-noir.css +74 -0
  292. package/packages/ui/src/themes/obsidian-iris.css +147 -0
  293. package/packages/ui/src/themes/pixpax.css +147 -0
  294. package/packages/ui/src/themes/print.css +147 -0
  295. package/packages/ui/src/themes/prism.css +147 -0
  296. package/packages/ui/src/themes/proof.css +145 -0
  297. package/packages/ui/src/themes/semanticThemeContract.js +2256 -0
  298. package/packages/ui/src/themes/spruce-ink.css +147 -0
  299. package/packages/ui/src/themes/sunset.css +147 -0
  300. package/packages/ui/tailwind.config.js +64 -0
  301. package/packages/ui/vite.config.js +35 -0
  302. package/packages/ui/vite.config.js.timestamp-1780697224943-89fbc929987bc.mjs +38 -0
  303. package/packages/utils/CHANGELOG.md +111 -0
  304. package/packages/utils/README.md +3 -0
  305. package/packages/utils/package.json +46 -0
  306. package/packages/utils/src/index.test.js +39 -0
  307. package/packages/utils/src/index.ts +289 -0
  308. package/packages/utils/tsconfig.build.json +12 -0
  309. package/packages/utils/vite.config.js +28 -0
  310. package/pnpm-workspace.yaml +8 -0
  311. package/scripts/vite/package-lib-config.ts +59 -0
  312. package/tsconfig.json +24 -0
  313. package/tsconfig.node.json +9 -0
@@ -0,0 +1,153 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { defineComponent, h } from "vue";
3
+ import { describe, expect, it, vi } from "vitest";
4
+
5
+ vi.mock("../../primitives/Accordion/Accordion.vue", () => ({
6
+ default: defineComponent({
7
+ name: "AccordionStub",
8
+ setup(_, { slots, attrs }) {
9
+ return () => h("div", { ...attrs, "data-stub": "accordion" }, slots.default?.());
10
+ },
11
+ }),
12
+ }));
13
+
14
+ vi.mock("../../primitives/Accordion/AccordionItem.vue", () => ({
15
+ default: defineComponent({
16
+ name: "AccordionItemStub",
17
+ props: {
18
+ title: {
19
+ type: String,
20
+ default: "",
21
+ },
22
+ },
23
+ setup(props, { slots, attrs }) {
24
+ return () =>
25
+ h("section", { ...attrs, "data-stub": "accordion-item" }, [
26
+ h("button", { type: "button" }, props.title),
27
+ slots.default?.(),
28
+ ]);
29
+ },
30
+ }),
31
+ }));
32
+
33
+ vi.mock("../../primitives/Badge/Badge.vue", () => ({
34
+ default: defineComponent({
35
+ name: "BadgeStub",
36
+ setup(_, { slots, attrs }) {
37
+ return () => h("span", { ...attrs, "data-stub": "badge" }, slots.default?.());
38
+ },
39
+ }),
40
+ }));
41
+
42
+ vi.mock("../../primitives/Button/Button.vue", () => ({
43
+ default: defineComponent({
44
+ name: "ButtonStub",
45
+ setup(_, { slots, attrs }) {
46
+ return () => h("button", attrs, slots.default?.());
47
+ },
48
+ }),
49
+ }));
50
+
51
+ vi.mock("../../primitives/Card/Card.vue", () => ({
52
+ default: defineComponent({
53
+ name: "CardStub",
54
+ setup(_, { slots, attrs }) {
55
+ return () => h("section", { ...attrs, "data-stub": "card" }, slots.default?.());
56
+ },
57
+ }),
58
+ }));
59
+
60
+ vi.mock("../../primitives/Separator/Separator.vue", () => ({
61
+ default: defineComponent({
62
+ name: "SeparatorStub",
63
+ setup(_, { attrs }) {
64
+ return () => h("hr", { ...attrs, "data-stub": "separator" });
65
+ },
66
+ }),
67
+ }));
68
+
69
+ vi.mock("../PreviewPanel/PreviewPanel.vue", () => ({
70
+ default: defineComponent({
71
+ name: "PreviewPanelStub",
72
+ props: {
73
+ code: {
74
+ type: String,
75
+ default: "",
76
+ },
77
+ title: {
78
+ type: String,
79
+ default: "",
80
+ },
81
+ },
82
+ setup(props, { attrs }) {
83
+ return () =>
84
+ h("pre", { ...attrs, "data-stub": "preview-panel" }, `${props.title}\n${props.code}`);
85
+ },
86
+ }),
87
+ }));
88
+
89
+ import VerificationBadge from "./VerificationBadge.vue";
90
+ import VerificationSummary from "./VerificationSummary.vue";
91
+ import VerificationDetailsPanel from "./VerificationDetailsPanel.vue";
92
+
93
+ describe("verification components", () => {
94
+ it("renders badge label, context, and truncated subject", () => {
95
+ const wrapper = mount(VerificationBadge, {
96
+ props: {
97
+ status: "verified",
98
+ context: { surface: "browser" },
99
+ subject: "dist-manifest.json",
100
+ },
101
+ });
102
+
103
+ expect(wrapper.text()).toContain("Verified");
104
+ expect(wrapper.text()).toContain("Browser");
105
+ expect(wrapper.text()).toContain("dist-manifest.json");
106
+ });
107
+
108
+ it("renders summary copy actions", async () => {
109
+ vi.stubGlobal("navigator", {
110
+ clipboard: {
111
+ writeText: vi.fn().mockResolvedValue(undefined),
112
+ },
113
+ });
114
+
115
+ const wrapper = mount(VerificationSummary, {
116
+ props: {
117
+ status: "verified",
118
+ subject: "dist-manifest.json",
119
+ hash: "sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
120
+ signer: "signer-key-id-1234567890",
121
+ algorithm: "ECDSA-P256-SHA256",
122
+ timestamp: "2026-03-14T00:00:00.000Z",
123
+ version: "seal/v1",
124
+ },
125
+ });
126
+
127
+ expect(wrapper.text()).toContain("Proof summary");
128
+ expect(wrapper.find('[aria-label*="Copy hash"]').exists()).toBe(true);
129
+ expect(wrapper.find('[aria-label*="Copy signer"]').exists()).toBe(true);
130
+ expect(wrapper.find('[data-status="verified"]').exists()).toBe(true);
131
+ });
132
+
133
+ it("renders details panel raw proof accordion and compact variant", () => {
134
+ const wrapper = mount(VerificationDetailsPanel, {
135
+ props: {
136
+ status: "verified",
137
+ subject: "dist-manifest.json",
138
+ hash: "sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
139
+ signer: "signer-key-id-1234567890",
140
+ algorithm: "ECDSA-P256-SHA256",
141
+ timestamp: "2026-03-14T00:00:00.000Z",
142
+ version: "seal/v1",
143
+ signature: "abcdefg123456",
144
+ rawProof: '{"type":"seal-proof"}',
145
+ variant: "compact",
146
+ },
147
+ });
148
+
149
+ expect(wrapper.attributes("data-variant")).toBe("compact");
150
+ expect(wrapper.text()).toContain("View raw proof");
151
+ expect(wrapper.find('[aria-label*="Copy hash"]').exists()).toBe(true);
152
+ });
153
+ });
@@ -0,0 +1,270 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref } from "vue";
3
+ import Button from "../../primitives/Button/Button.vue";
4
+ import Card from "../../primitives/Card/Card.vue";
5
+ import Separator from "../../primitives/Separator/Separator.vue";
6
+ import PreviewPanel from "../PreviewPanel/PreviewPanel.vue";
7
+ import VerificationBadge from "./VerificationBadge.vue";
8
+ import type { VerificationContext, VerificationStatus } from "./verification.types.js";
9
+ import {
10
+ copyToClipboard,
11
+ getVerificationContextSubtext,
12
+ getVerificationHeadline,
13
+ truncateMiddle,
14
+ } from "./verification.utils.js";
15
+
16
+ const props = withDefaults(
17
+ defineProps<{
18
+ status: VerificationStatus;
19
+ subject: string;
20
+ hash: string;
21
+ signer: string;
22
+ algorithm: string;
23
+ timestamp: string;
24
+ version: string;
25
+ signature?: string;
26
+ proofSize?: string;
27
+ context?: VerificationContext;
28
+ variant?: "full" | "compact";
29
+ rawProof?: string;
30
+ copyable?: boolean;
31
+ headline?: string;
32
+ subtext?: string;
33
+ }>(),
34
+ {
35
+ signature: undefined,
36
+ proofSize: undefined,
37
+ context: undefined,
38
+ variant: "full",
39
+ rawProof: undefined,
40
+ copyable: true,
41
+ headline: undefined,
42
+ subtext: undefined,
43
+ },
44
+ );
45
+
46
+ const copiedKey = ref<string | null>(null);
47
+ const isRawProofOpen = ref(false);
48
+
49
+ async function handleCopy(field: string, value: string) {
50
+ if (!props.copyable) return;
51
+ const copied = await copyToClipboard(value);
52
+ if (!copied) return;
53
+ copiedKey.value = field;
54
+ window.setTimeout(() => {
55
+ if (copiedKey.value === field) {
56
+ copiedKey.value = null;
57
+ }
58
+ }, 1200);
59
+ }
60
+
61
+ function copyLabel(field: string) {
62
+ return copiedKey.value === field ? "Copied" : "Copy";
63
+ }
64
+
65
+ const cardPadding = computed(() => (props.variant === "full" ? "md" : "sm"));
66
+ const headlineText = computed(() => props.headline ?? getVerificationHeadline(props.status));
67
+ const subtextText = computed(
68
+ () => props.subtext ?? getVerificationContextSubtext(props.status, props.context),
69
+ );
70
+ const subjectClass = computed(() =>
71
+ props.variant === "full" ? "text-2xl leading-tight md:text-[2.15rem]" : "text-xl leading-tight",
72
+ );
73
+ const hashPreview = computed(() =>
74
+ props.variant === "full"
75
+ ? truncateMiddle(props.hash, 20, 18)
76
+ : truncateMiddle(props.hash, 14, 12),
77
+ );
78
+ const signerPreview = computed(() => truncateMiddle(props.signer, 16, 14));
79
+ const metadataItems = computed(() => {
80
+ const items = [props.version, props.timestamp];
81
+ if (props.proofSize) items.push(props.proofSize);
82
+ if (props.signature) items.push(`${props.signature.length} chars`);
83
+ return items;
84
+ });
85
+ </script>
86
+
87
+ <template>
88
+ <Card
89
+ variant="panel"
90
+ :padding="cardPadding"
91
+ class="min-w-0 max-w-full space-y-5"
92
+ :data-variant="props.variant"
93
+ >
94
+ <div class="space-y-3">
95
+ <div class="flex flex-wrap items-start justify-between gap-3">
96
+ <div class="space-y-1">
97
+ <h3 class="m-0 text-xl font-medium tracking-[-0.03em] text-[var(--ui-fg)]">
98
+ {{ headlineText }}
99
+ </h3>
100
+ <p v-if="subtextText" class="m-0 text-sm leading-6 text-[var(--ui-fg-muted)]">
101
+ {{ subtextText }}
102
+ </p>
103
+ </div>
104
+
105
+ <VerificationBadge
106
+ :status="props.status"
107
+ :context="props.context"
108
+ :size="props.variant === 'full' ? 'md' : 'sm'"
109
+ />
110
+ </div>
111
+ </div>
112
+
113
+ <Separator />
114
+
115
+ <section class="space-y-3">
116
+ <p class="m-0 text-[11px] font-medium uppercase tracking-[0.24em] text-[var(--ui-fg-muted)]">
117
+ Subject
118
+ </p>
119
+ <p :class="['m-0 font-medium tracking-[-0.03em] text-[var(--ui-fg)]', subjectClass]">
120
+ {{ props.subject }}
121
+ </p>
122
+
123
+ <div class="space-y-2">
124
+ <p
125
+ class="m-0 text-[11px] font-medium uppercase tracking-[0.24em] text-[var(--ui-fg-muted)]"
126
+ >
127
+ Hash
128
+ </p>
129
+ <div class="flex flex-wrap items-center gap-2">
130
+ <code
131
+ class="min-w-0 flex-1 truncate rounded-[var(--ui-radius-md)] border border-[var(--ui-border)] bg-[var(--ui-tonal-tertiary)] px-3 py-2 font-mono text-sm text-[var(--ui-fg)]"
132
+ :title="props.hash"
133
+ >
134
+ {{ hashPreview }}
135
+ </code>
136
+ <Button
137
+ v-if="props.copyable"
138
+ size="xs"
139
+ variant="plain-secondary"
140
+ :aria-label="`Copy hash ${props.hash}`"
141
+ @click="handleCopy('hash', props.hash)"
142
+ >
143
+ {{ copyLabel("hash") }}
144
+ </Button>
145
+ </div>
146
+ </div>
147
+ </section>
148
+
149
+ <Separator />
150
+
151
+ <section class="grid gap-4 md:grid-cols-2">
152
+ <div class="space-y-2">
153
+ <p
154
+ class="m-0 text-[11px] font-medium uppercase tracking-[0.24em] text-[var(--ui-fg-muted)]"
155
+ >
156
+ Signed by
157
+ </p>
158
+ <div class="flex flex-wrap items-center gap-2">
159
+ <code
160
+ class="min-w-0 flex-1 truncate rounded-[var(--ui-radius-sm)] border border-[var(--ui-border)] bg-[var(--ui-tonal-tertiary)] px-2.5 py-2 font-mono text-sm text-[var(--ui-fg)]"
161
+ :title="props.signer"
162
+ >
163
+ {{ signerPreview }}
164
+ </code>
165
+ <Button
166
+ v-if="props.copyable"
167
+ size="xs"
168
+ variant="plain-secondary"
169
+ :aria-label="`Copy signer ${props.signer}`"
170
+ @click="handleCopy('signer', props.signer)"
171
+ >
172
+ {{ copyLabel("signer") }}
173
+ </Button>
174
+ </div>
175
+ </div>
176
+
177
+ <div class="space-y-2">
178
+ <p
179
+ class="m-0 text-[11px] font-medium uppercase tracking-[0.24em] text-[var(--ui-fg-muted)]"
180
+ >
181
+ Algorithm
182
+ </p>
183
+ <div class="flex flex-wrap items-center gap-2">
184
+ <code
185
+ class="min-w-0 flex-1 truncate rounded-[var(--ui-radius-sm)] border border-[var(--ui-border)] bg-[var(--ui-tonal-tertiary)] px-2.5 py-2 font-mono text-sm text-[var(--ui-fg)]"
186
+ :title="props.algorithm"
187
+ >
188
+ {{ props.algorithm }}
189
+ </code>
190
+ <Button
191
+ v-if="props.copyable"
192
+ size="xs"
193
+ variant="plain-secondary"
194
+ :aria-label="`Copy algorithm ${props.algorithm}`"
195
+ @click="handleCopy('algorithm', props.algorithm)"
196
+ >
197
+ {{ copyLabel("algorithm") }}
198
+ </Button>
199
+ </div>
200
+ </div>
201
+ </section>
202
+
203
+ <Separator />
204
+
205
+ <section class="flex flex-wrap items-center gap-2 text-xs text-[var(--ui-fg-muted)]">
206
+ <span
207
+ v-for="item in metadataItems"
208
+ :key="item"
209
+ class="rounded-full border border-[var(--ui-border)] px-2 py-1"
210
+ >
211
+ {{ item }}
212
+ </span>
213
+ </section>
214
+
215
+ <section
216
+ v-if="props.rawProof"
217
+ class="min-w-0 max-w-full overflow-hidden rounded-[var(--ui-radius-md)] border border-[var(--ui-border)] bg-[var(--ui-tonal-secondary)]"
218
+ >
219
+ <button
220
+ type="button"
221
+ class="flex w-full min-w-0 items-center justify-between gap-3 px-4 py-3 text-left text-sm font-medium text-[var(--ui-fg)] transition-[color] duration-[var(--ui-duration-normal)] ease-[var(--ui-ease-out)] hover:text-[var(--ui-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-ring)]"
222
+ :aria-expanded="isRawProofOpen"
223
+ @click="isRawProofOpen = !isRawProofOpen"
224
+ >
225
+ <span class="min-w-0 truncate">View raw proof</span>
226
+ <svg
227
+ aria-hidden="true"
228
+ viewBox="0 0 20 20"
229
+ fill="none"
230
+ :class="[
231
+ 'size-4 shrink-0 text-[var(--ui-fg-muted)] transition-transform duration-[var(--ui-duration-normal)] ease-[var(--ui-ease-out)]',
232
+ isRawProofOpen ? 'rotate-180' : '',
233
+ ]"
234
+ >
235
+ <path
236
+ d="m5 7.5 5 5 5-5"
237
+ stroke="currentColor"
238
+ stroke-width="1.75"
239
+ stroke-linecap="round"
240
+ stroke-linejoin="round"
241
+ />
242
+ </svg>
243
+ </button>
244
+
245
+ <div
246
+ v-if="isRawProofOpen"
247
+ class="min-w-0 max-w-full overflow-hidden border-t border-[var(--ui-border)] p-4 pt-4"
248
+ >
249
+ <div class="min-w-0 max-w-full overflow-hidden">
250
+ <pre class="ui-preview-panel__code"><code>{{ props.rawProof }}</code></pre>
251
+ </div>
252
+ </div>
253
+ </section>
254
+ </Card>
255
+ </template>
256
+ <style>
257
+ .ui-preview-panel__code {
258
+ margin: 0;
259
+ min-width: 0;
260
+ max-width: 100%;
261
+ padding: 1rem 1.125rem;
262
+ overflow-x: auto;
263
+ border: 1px solid color-mix(in srgb, var(--ui-border) 72%, transparent);
264
+ border-radius: var(--ui-radius-md);
265
+ background: color-mix(in srgb, var(--ui-bg) 55%, var(--ui-surface));
266
+ color: var(--ui-fg);
267
+ font-size: 0.92rem;
268
+ line-height: 1.8;
269
+ }
270
+ </style>
@@ -0,0 +1,171 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref } from "vue";
3
+ import Button from "../../primitives/Button/Button.vue";
4
+ import Card from "../../primitives/Card/Card.vue";
5
+ import Separator from "../../primitives/Separator/Separator.vue";
6
+ import VerificationBadge from "./VerificationBadge.vue";
7
+ import type { VerificationContext, VerificationStatus } from "./verification.types.js";
8
+ import { copyToClipboard, getVerificationHeadline, truncateMiddle } from "./verification.utils.js";
9
+
10
+ const props = withDefaults(
11
+ defineProps<{
12
+ status: VerificationStatus;
13
+ subject: string;
14
+ hash: string;
15
+ signer: string;
16
+ algorithm: string;
17
+ timestamp: string;
18
+ version: string;
19
+ context?: VerificationContext;
20
+ variant?: "compact" | "full";
21
+ copyable?: boolean;
22
+ }>(),
23
+ {
24
+ context: undefined,
25
+ variant: "compact",
26
+ copyable: true,
27
+ },
28
+ );
29
+
30
+ const copiedKey = ref<string | null>(null);
31
+
32
+ async function handleCopy(field: string, value: string) {
33
+ if (!props.copyable) return;
34
+ const copied = await copyToClipboard(value);
35
+ if (!copied) return;
36
+ copiedKey.value = field;
37
+ window.setTimeout(() => {
38
+ if (copiedKey.value === field) {
39
+ copiedKey.value = null;
40
+ }
41
+ }, 1200);
42
+ }
43
+
44
+ function copyLabel(field: string) {
45
+ return copiedKey.value === field ? "Copied" : "Copy";
46
+ }
47
+
48
+ const cardPadding = computed(() => (props.variant === "full" ? "md" : "sm"));
49
+ const subjectClass = computed(() =>
50
+ props.variant === "full" ? "text-2xl leading-tight md:text-[2rem]" : "text-xl leading-tight",
51
+ );
52
+ const headerTitle = computed(() => getVerificationHeadline(props.status));
53
+ const hashPreview = computed(() => truncateMiddle(props.hash, 16, 12));
54
+ const signerPreview = computed(() => truncateMiddle(props.signer, 14, 12));
55
+ </script>
56
+
57
+ <template>
58
+ <Card variant="panel" :padding="cardPadding" class="space-y-4" :data-variant="props.variant">
59
+ <div class="flex flex-wrap items-start justify-between gap-3">
60
+ <div class="space-y-1">
61
+ <p
62
+ class="m-0 text-[11px] font-medium uppercase tracking-[0.24em] text-[var(--ui-fg-muted)]"
63
+ >
64
+ Proof summary
65
+ </p>
66
+ <h3 class="m-0 text-lg font-medium tracking-[-0.02em] text-[var(--ui-fg)]">
67
+ {{ headerTitle }}
68
+ </h3>
69
+ </div>
70
+ <VerificationBadge
71
+ :status="props.status"
72
+ :context="props.context"
73
+ :size="props.variant === 'full' ? 'md' : 'sm'"
74
+ />
75
+ </div>
76
+
77
+ <div class="space-y-3">
78
+ <p class="m-0 text-[11px] font-medium uppercase tracking-[0.24em] text-[var(--ui-fg-muted)]">
79
+ Subject
80
+ </p>
81
+ <p :class="['m-0 font-medium tracking-[-0.03em] text-[var(--ui-fg)]', subjectClass]">
82
+ {{ props.subject }}
83
+ </p>
84
+ <div class="flex flex-wrap items-center gap-2">
85
+ <span class="text-[11px] font-medium uppercase tracking-[0.24em] text-[var(--ui-fg-muted)]">
86
+ Hash
87
+ </span>
88
+ <code
89
+ class="min-w-0 flex-1 truncate rounded-[var(--ui-radius-sm)] border border-[var(--ui-border)] bg-[var(--ui-tonal-tertiary)] px-2.5 py-1 font-mono text-xs text-[var(--ui-fg)]"
90
+ :title="props.hash"
91
+ >
92
+ {{ hashPreview }}
93
+ </code>
94
+ <Button
95
+ v-if="props.copyable"
96
+ size="xs"
97
+ variant="plain-secondary"
98
+ :aria-label="`Copy hash ${props.hash}`"
99
+ @click="handleCopy('hash', props.hash)"
100
+ >
101
+ {{ copyLabel("hash") }}
102
+ </Button>
103
+ </div>
104
+ </div>
105
+
106
+ <Separator />
107
+
108
+ <div class="grid gap-4 md:grid-cols-2">
109
+ <div class="space-y-2">
110
+ <p
111
+ class="m-0 text-[11px] font-medium uppercase tracking-[0.24em] text-[var(--ui-fg-muted)]"
112
+ >
113
+ Signed by
114
+ </p>
115
+ <div class="flex flex-wrap items-center gap-2">
116
+ <code
117
+ class="min-w-0 flex-1 truncate font-mono text-sm text-[var(--ui-fg)]"
118
+ :title="props.signer"
119
+ >
120
+ {{ signerPreview }}
121
+ </code>
122
+ <Button
123
+ v-if="props.copyable"
124
+ size="xs"
125
+ variant="plain-secondary"
126
+ :aria-label="`Copy signer ${props.signer}`"
127
+ @click="handleCopy('signer', props.signer)"
128
+ >
129
+ {{ copyLabel("signer") }}
130
+ </Button>
131
+ </div>
132
+ </div>
133
+
134
+ <div class="space-y-2">
135
+ <p
136
+ class="m-0 text-[11px] font-medium uppercase tracking-[0.24em] text-[var(--ui-fg-muted)]"
137
+ >
138
+ Algorithm
139
+ </p>
140
+ <div class="flex flex-wrap items-center gap-2">
141
+ <code
142
+ class="min-w-0 flex-1 truncate font-mono text-sm text-[var(--ui-fg)]"
143
+ :title="props.algorithm"
144
+ >
145
+ {{ props.algorithm }}
146
+ </code>
147
+ <Button
148
+ v-if="props.copyable"
149
+ size="xs"
150
+ variant="plain-secondary"
151
+ :aria-label="`Copy algorithm ${props.algorithm}`"
152
+ @click="handleCopy('algorithm', props.algorithm)"
153
+ >
154
+ {{ copyLabel("algorithm") }}
155
+ </Button>
156
+ </div>
157
+ </div>
158
+ </div>
159
+
160
+ <Separator />
161
+
162
+ <div class="flex flex-wrap items-center gap-2 text-xs text-[var(--ui-fg-muted)]">
163
+ <span class="rounded-full border border-[var(--ui-border)] px-2 py-1">
164
+ {{ props.version }}
165
+ </span>
166
+ <span class="rounded-full border border-[var(--ui-border)] px-2 py-1">
167
+ {{ props.timestamp }}
168
+ </span>
169
+ </div>
170
+ </Card>
171
+ </template>
@@ -0,0 +1,6 @@
1
+ export { default as VerificationBadge } from "./VerificationBadge.vue";
2
+ export { default as VerificationSummary } from "./VerificationSummary.vue";
3
+ export { default as VerificationDetailsPanel } from "./VerificationDetailsPanel.vue";
4
+
5
+ export * from "./verification.types.js";
6
+ export * from "./verification.utils.js";
@@ -0,0 +1,8 @@
1
+ export type VerificationStatus = "verified" | "failed" | "unknown";
2
+ export type VerificationSurface = "browser" | "cli" | "ci";
3
+ export type VerificationLocation = "hero" | "app" | "footer" | "embed";
4
+ export type VerificationContext = {
5
+ surface?: VerificationSurface;
6
+ location?: VerificationLocation;
7
+ };
8
+ export type VerificationVariant = "full" | "compact" | "embed";
@@ -0,0 +1,37 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ getVerificationBadgeTone,
4
+ getVerificationContextSubtext,
5
+ getVerificationHeadline,
6
+ getVerificationStatusLabel,
7
+ truncateMiddle,
8
+ } from "./verification.utils";
9
+
10
+ describe("verification utils", () => {
11
+ it("maps status labels and tones", () => {
12
+ expect(getVerificationStatusLabel("verified")).toBe("Verified");
13
+ expect(getVerificationStatusLabel("failed")).toBe("Failed");
14
+ expect(getVerificationStatusLabel("unknown")).toBe("Unknown");
15
+
16
+ expect(getVerificationHeadline("verified")).toBe("Verified proof");
17
+ expect(getVerificationHeadline("failed")).toBe("Failed proof");
18
+ expect(getVerificationHeadline("unknown")).toBe("Unknown proof");
19
+
20
+ expect(getVerificationBadgeTone("verified")).toBe("success");
21
+ expect(getVerificationBadgeTone("failed")).toBe("critical");
22
+ expect(getVerificationBadgeTone("unknown")).toBe("neutral");
23
+ });
24
+
25
+ it("derives context subtext and truncates long values", () => {
26
+ expect(getVerificationContextSubtext("verified", { surface: "browser" })).toBe(
27
+ "Verified in browser",
28
+ );
29
+ expect(getVerificationContextSubtext("failed", { surface: "cli" })).toBe(
30
+ "Verification failed via CLI",
31
+ );
32
+ expect(getVerificationContextSubtext("unknown", { surface: "ci" })).toBeUndefined();
33
+
34
+ expect(truncateMiddle("abcdef")).toBe("abcdef");
35
+ expect(truncateMiddle("abcdefghijklmnopqrstuvwxyz", 4, 4)).toBe("abcd...wxyz");
36
+ });
37
+ });