@pgcorp/ui-kit 0.1.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 (251) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -0
  3. package/docs/accessibility.md +27 -0
  4. package/docs/getting-started.md +80 -0
  5. package/docs/licensing.md +30 -0
  6. package/docs/public-api.md +39 -0
  7. package/docs/security.md +21 -0
  8. package/docs/theming.md +35 -0
  9. package/docs/versioning.md +23 -0
  10. package/index.ts +3 -0
  11. package/package.json +592 -0
  12. package/src/components/icons/_internal/generatedSputnigUiIconMarkup.ts +971 -0
  13. package/src/components/icons/sputnigUiIcons.ts +530 -0
  14. package/src/components/layout/SAppShell.vue +232 -0
  15. package/src/components/layout/SDockRegion.vue +200 -0
  16. package/src/components/layout/SResizeHandle.vue +251 -0
  17. package/src/components/layout/SSideMenuButton.vue +104 -0
  18. package/src/components/layout/SSideMenuRail.vue +47 -0
  19. package/src/components/layout/SStack.css +25 -0
  20. package/src/components/layout/SStack.vue +57 -0
  21. package/src/components/layout/SStatusBarShell.css +15 -0
  22. package/src/components/layout/SStatusBarShell.vue +26 -0
  23. package/src/components/layout/SWorkbenchLayout.vue +53 -0
  24. package/src/components/layout/SWorkbenchPanelShell.css +161 -0
  25. package/src/components/layout/SWorkbenchPanelShell.vue +286 -0
  26. package/src/components/layout/_internal/SAppBrand.css +45 -0
  27. package/src/components/layout/_internal/SAppBrand.vue +37 -0
  28. package/src/components/layout/_internal/SAppBrandContent.vue +30 -0
  29. package/src/components/layout/types.ts +28 -0
  30. package/src/components/shared/_internal/SDialogSurface.css +127 -0
  31. package/src/components/shared/_internal/SDialogSurface.vue +117 -0
  32. package/src/components/shared/_internal/SDisclosureSummary.css +92 -0
  33. package/src/components/shared/_internal/SDisclosureSummary.vue +121 -0
  34. package/src/components/shared/_internal/SListboxGroup.css +18 -0
  35. package/src/components/shared/_internal/SListboxGroup.vue +24 -0
  36. package/src/components/shared/_internal/SMenuItem.css +112 -0
  37. package/src/components/shared/_internal/SMenuItem.vue +213 -0
  38. package/src/components/shared/_internal/SMenuLayer.css +21 -0
  39. package/src/components/shared/_internal/SMenuLayer.vue +48 -0
  40. package/src/components/shared/_internal/SMenuSeparator.css +5 -0
  41. package/src/components/shared/_internal/SMenuSeparator.vue +13 -0
  42. package/src/components/shared/_internal/SToastItem.css +65 -0
  43. package/src/components/shared/_internal/SToastItem.vue +58 -0
  44. package/src/components/shared/_internal/STreeNode.vue +761 -0
  45. package/src/components/shared/_internal/dialogSurface.ts +105 -0
  46. package/src/components/shared/_internal/focusTrap.ts +40 -0
  47. package/src/components/shared/_internal/listboxContext.ts +16 -0
  48. package/src/components/shared/_internal/markerContract.ts +10 -0
  49. package/src/components/shared/_internal/menuContext.ts +23 -0
  50. package/src/components/shared/_internal/selectionContract.ts +239 -0
  51. package/src/components/shared/_internal/sidebarGroupContext.ts +31 -0
  52. package/src/components/shared/_internal/tabsContext.ts +57 -0
  53. package/src/components/shared/_internal/treeRuntime.ts +203 -0
  54. package/src/components/shared/_internal/useSelectionRoving.ts +162 -0
  55. package/src/components/shared/codeLanguages.ts +168 -0
  56. package/src/components/shared/complex/SKanbanCard.css +8 -0
  57. package/src/components/shared/complex/SKanbanCard.vue +76 -0
  58. package/src/components/shared/complex/SKanbanLane.vue +82 -0
  59. package/src/components/shared/complex/STree.css +24 -0
  60. package/src/components/shared/complex/STree.vue +288 -0
  61. package/src/components/shared/complex/treeAdapter.ts +210 -0
  62. package/src/components/shared/complex/types.ts +102 -0
  63. package/src/components/shared/containers/SCloudChrome.css +103 -0
  64. package/src/components/shared/containers/SCloudChrome.vue +87 -0
  65. package/src/components/shared/containers/SCloudChromeEmpty.css +10 -0
  66. package/src/components/shared/containers/SCloudChromeEmpty.vue +18 -0
  67. package/src/components/shared/containers/SCloudChromeRow.css +78 -0
  68. package/src/components/shared/containers/SCloudChromeRow.vue +108 -0
  69. package/src/components/shared/containers/SCollapsiblePanel.css +57 -0
  70. package/src/components/shared/containers/SCollapsiblePanel.vue +360 -0
  71. package/src/components/shared/containers/SDrawer.css +60 -0
  72. package/src/components/shared/containers/SDrawer.vue +86 -0
  73. package/src/components/shared/containers/SFormGrid.css +26 -0
  74. package/src/components/shared/containers/SFormGrid.vue +46 -0
  75. package/src/components/shared/containers/SFormGridItem.css +18 -0
  76. package/src/components/shared/containers/SFormGridItem.vue +29 -0
  77. package/src/components/shared/containers/SLeftSidebar.vue +74 -0
  78. package/src/components/shared/containers/SModal.css +27 -0
  79. package/src/components/shared/containers/SModal.vue +105 -0
  80. package/src/components/shared/containers/SPageHeader.css +66 -0
  81. package/src/components/shared/containers/SPageHeader.vue +45 -0
  82. package/src/components/shared/containers/SPanel.css +134 -0
  83. package/src/components/shared/containers/SPanel.vue +169 -0
  84. package/src/components/shared/containers/SPopover.css +35 -0
  85. package/src/components/shared/containers/SPopover.vue +551 -0
  86. package/src/components/shared/containers/SSidebarGroup.css +12 -0
  87. package/src/components/shared/containers/SSidebarGroup.vue +443 -0
  88. package/src/components/shared/containers/SSidebarSection.css +41 -0
  89. package/src/components/shared/containers/SSidebarSection.vue +268 -0
  90. package/src/components/shared/containers/disclosureSummary.ts +48 -0
  91. package/src/components/shared/containers/popover.ts +72 -0
  92. package/src/components/shared/containers/sidebar.ts +206 -0
  93. package/src/components/shared/controls/SButton.css +274 -0
  94. package/src/components/shared/controls/SButton.vue +211 -0
  95. package/src/components/shared/controls/SCheckbox.css +65 -0
  96. package/src/components/shared/controls/SCheckbox.vue +130 -0
  97. package/src/components/shared/controls/SCheckboxGroup.vue +134 -0
  98. package/src/components/shared/controls/SChoiceCards.css +29 -0
  99. package/src/components/shared/controls/SChoiceCards.vue +145 -0
  100. package/src/components/shared/controls/SColorSelect.vue +146 -0
  101. package/src/components/shared/controls/SComboboxTrigger.css +35 -0
  102. package/src/components/shared/controls/SComboboxTrigger.vue +122 -0
  103. package/src/components/shared/controls/SContextMenu.css +11 -0
  104. package/src/components/shared/controls/SContextMenu.vue +186 -0
  105. package/src/components/shared/controls/SContextToggleButton.vue +64 -0
  106. package/src/components/shared/controls/SDragHandle.vue +100 -0
  107. package/src/components/shared/controls/SDropdownMenu.css +5 -0
  108. package/src/components/shared/controls/SDropdownMenu.vue +188 -0
  109. package/src/components/shared/controls/SField.css +79 -0
  110. package/src/components/shared/controls/SField.vue +89 -0
  111. package/src/components/shared/controls/SFieldGroup.css +58 -0
  112. package/src/components/shared/controls/SFieldGroup.vue +117 -0
  113. package/src/components/shared/controls/SFieldLabel.css +12 -0
  114. package/src/components/shared/controls/SFieldLabel.vue +51 -0
  115. package/src/components/shared/controls/SInlineTokenEditor.css +61 -0
  116. package/src/components/shared/controls/SInlineTokenEditor.vue +1081 -0
  117. package/src/components/shared/controls/SInputText.css +66 -0
  118. package/src/components/shared/controls/SInputText.vue +321 -0
  119. package/src/components/shared/controls/SInteractiveSurface.css +72 -0
  120. package/src/components/shared/controls/SInteractiveSurface.vue +326 -0
  121. package/src/components/shared/controls/SLink.css +114 -0
  122. package/src/components/shared/controls/SLink.ts +85 -0
  123. package/src/components/shared/controls/SLink.vue +114 -0
  124. package/src/components/shared/controls/SListbox.css +31 -0
  125. package/src/components/shared/controls/SListbox.vue +388 -0
  126. package/src/components/shared/controls/SListboxOption.css +63 -0
  127. package/src/components/shared/controls/SListboxOption.vue +85 -0
  128. package/src/components/shared/controls/SMarker.css +19 -0
  129. package/src/components/shared/controls/SMarker.vue +33 -0
  130. package/src/components/shared/controls/SMenuSurface.css +20 -0
  131. package/src/components/shared/controls/SMenuSurface.vue +333 -0
  132. package/src/components/shared/controls/SSegmentedControl.css +38 -0
  133. package/src/components/shared/controls/SSegmentedControl.vue +134 -0
  134. package/src/components/shared/controls/SSelect.css +12 -0
  135. package/src/components/shared/controls/SSelect.vue +338 -0
  136. package/src/components/shared/controls/STextarea.css +31 -0
  137. package/src/components/shared/controls/STextarea.vue +316 -0
  138. package/src/components/shared/controls/_internal/SInlineTokenSurface.vue +224 -0
  139. package/src/components/shared/controls/menu.ts +178 -0
  140. package/src/components/shared/data-display/SActionCard.css +6 -0
  141. package/src/components/shared/data-display/SActionCard.vue +49 -0
  142. package/src/components/shared/data-display/SActionList.css +15 -0
  143. package/src/components/shared/data-display/SActionList.vue +34 -0
  144. package/src/components/shared/data-display/SActionListItem.css +114 -0
  145. package/src/components/shared/data-display/SActionListItem.vue +230 -0
  146. package/src/components/shared/data-display/SBadge.css +73 -0
  147. package/src/components/shared/data-display/SBadge.vue +52 -0
  148. package/src/components/shared/data-display/SCodeBlock.css +56 -0
  149. package/src/components/shared/data-display/SCodeBlock.vue +188 -0
  150. package/src/components/shared/data-display/SCodeEditor.css +178 -0
  151. package/src/components/shared/data-display/SCodeEditor.vue +274 -0
  152. package/src/components/shared/data-display/SCodeSearchPanel.vue +327 -0
  153. package/src/components/shared/data-display/SCopyField.vue +77 -0
  154. package/src/components/shared/data-display/SDocBlock.css +136 -0
  155. package/src/components/shared/data-display/SDocBlock.vue +358 -0
  156. package/src/components/shared/data-display/SEmptyState.css +42 -0
  157. package/src/components/shared/data-display/SEmptyState.vue +79 -0
  158. package/src/components/shared/data-display/SExpandableText.vue +314 -0
  159. package/src/components/shared/data-display/SJsonTree.css +6 -0
  160. package/src/components/shared/data-display/SJsonTree.vue +111 -0
  161. package/src/components/shared/data-display/SKeyValueGrid.css +37 -0
  162. package/src/components/shared/data-display/SKeyValueGrid.vue +277 -0
  163. package/src/components/shared/data-display/SLinkedSystemsList.vue +270 -0
  164. package/src/components/shared/data-display/SMessage.css +88 -0
  165. package/src/components/shared/data-display/SMessage.vue +152 -0
  166. package/src/components/shared/data-display/SMetricCard.css +32 -0
  167. package/src/components/shared/data-display/SMetricCard.vue +117 -0
  168. package/src/components/shared/data-display/SProgressBar.css +84 -0
  169. package/src/components/shared/data-display/SProgressBar.vue +114 -0
  170. package/src/components/shared/data-display/SProgressIndicator.css +59 -0
  171. package/src/components/shared/data-display/SProgressIndicator.vue +104 -0
  172. package/src/components/shared/data-display/SSectionHeader.css +84 -0
  173. package/src/components/shared/data-display/SSectionHeader.vue +58 -0
  174. package/src/components/shared/data-display/SStatus.css +90 -0
  175. package/src/components/shared/data-display/SStatus.vue +79 -0
  176. package/src/components/shared/data-display/STable.css +83 -0
  177. package/src/components/shared/data-display/STable.vue +339 -0
  178. package/src/components/shared/data-display/SToastContainer.css +53 -0
  179. package/src/components/shared/data-display/SToastContainer.vue +31 -0
  180. package/src/components/shared/data-display/STooltip.css +114 -0
  181. package/src/components/shared/data-display/STooltip.vue +550 -0
  182. package/src/components/shared/data-display/STooltipAnchor.css +21 -0
  183. package/src/components/shared/data-display/STooltipAnchor.vue +119 -0
  184. package/src/components/shared/data-display/SVirtualList.css +24 -0
  185. package/src/components/shared/data-display/SVirtualList.ts +19 -0
  186. package/src/components/shared/data-display/SVirtualList.vue +361 -0
  187. package/src/components/shared/data-display/json.ts +136 -0
  188. package/src/components/shared/data-display/table.ts +76 -0
  189. package/src/components/shared/database/SDataGrid.css +3 -0
  190. package/src/components/shared/database/SDataGrid.vue +464 -0
  191. package/src/components/shared/database/SSqlEditor.vue +363 -0
  192. package/src/components/shared/database/dataGrid.ts +61 -0
  193. package/src/components/shared/feedback/SAsyncState.css +26 -0
  194. package/src/components/shared/feedback/SAsyncState.vue +190 -0
  195. package/src/components/shared/graph/SGraphViewport.ts +1 -0
  196. package/src/components/shared/graph/SGraphViewport.vue +858 -0
  197. package/src/components/shared/graph/_internal/graphViewportContract.ts +362 -0
  198. package/src/components/shared/markdown.ts +169 -0
  199. package/src/components/shared/navigation/SBottomNav.vue +692 -0
  200. package/src/components/shared/navigation/SBreadcrumbs.css +26 -0
  201. package/src/components/shared/navigation/SBreadcrumbs.vue +155 -0
  202. package/src/components/shared/navigation/SCatalogNavigator.css +100 -0
  203. package/src/components/shared/navigation/SCatalogNavigator.vue +308 -0
  204. package/src/components/shared/navigation/STab.vue +363 -0
  205. package/src/components/shared/navigation/STabList.vue +442 -0
  206. package/src/components/shared/navigation/STabPanel.vue +165 -0
  207. package/src/components/shared/navigation/STabPanels.vue +33 -0
  208. package/src/components/shared/navigation/STabs.vue +617 -0
  209. package/src/components/shared/navigation/SWizardSteps.css +69 -0
  210. package/src/components/shared/navigation/SWizardSteps.vue +171 -0
  211. package/src/components/shared/navigation/_internal/SCatalogNavigatorGroup.vue +63 -0
  212. package/src/components/shared/persona/SPersonaProjectPicker.css +36 -0
  213. package/src/components/shared/persona/SPersonaProjectPicker.vue +361 -0
  214. package/src/components/shared/persona/SPersonaRunWorkbench.css +109 -0
  215. package/src/components/shared/persona/SPersonaRunWorkbench.vue +549 -0
  216. package/src/components/shared/persona/_internal/SPersonaRunConsole.css +40 -0
  217. package/src/components/shared/persona/_internal/SPersonaRunConsole.vue +298 -0
  218. package/src/components/shared/persona/_internal/inventory.ts +110 -0
  219. package/src/components/shared/persona/_internal/labels.ts +79 -0
  220. package/src/components/shared/persona/presentation.ts +371 -0
  221. package/src/components/shared/persona/types.ts +256 -0
  222. package/src/composables/uiPreferencesReset.ts +52 -0
  223. package/src/composables/useClipboard.ts +37 -0
  224. package/src/composables/useDialogOverlay.ts +85 -0
  225. package/src/composables/useDisclosure.ts +40 -0
  226. package/src/composables/useFloatingPosition.ts +245 -0
  227. package/src/composables/useNotifier.ts +26 -0
  228. package/src/composables/useSidebarPanelState.ts +610 -0
  229. package/src/internal/codeEditorContract.ts +110 -0
  230. package/src/internal/codeEditorExtensions.ts +27 -0
  231. package/src/internal/feedbackPresentation.ts +136 -0
  232. package/src/internal/fieldSurface.ts +59 -0
  233. package/src/internal/focusNavigation.ts +69 -0
  234. package/src/internal/inlineTokenEditorContract.ts +136 -0
  235. package/src/internal/interactiveElement.ts +33 -0
  236. package/src/internal/layerStack.ts +202 -0
  237. package/src/internal/linkTarget.ts +256 -0
  238. package/src/internal/ownedAttrs.ts +464 -0
  239. package/src/internal/passiveContentContract.ts +129 -0
  240. package/src/internal/pointerInteractionLease.ts +81 -0
  241. package/src/internal/runtimeContract.ts +194 -0
  242. package/src/internal/runtimeTheme.ts +159 -0
  243. package/src/internal/semanticSizing.ts +60 -0
  244. package/src/internal/useMenuLayer.ts +129 -0
  245. package/src/stores/useNotifierStore.ts +69 -0
  246. package/src/styles/reference.css +4 -0
  247. package/src/styles/style.css +151 -0
  248. package/src/styles/tokens.css +836 -0
  249. package/src/theme.ts +449 -0
  250. package/src/types/highlight-js-lib.d.ts +76 -0
  251. package/src/variants.ts +15 -0
@@ -0,0 +1,371 @@
1
+ import { validateExactString } from '../../../internal/runtimeContract'
2
+
3
+ export type PersonaStatusSeverity = 'secondary' | 'success' | 'info' | 'warn' | 'danger'
4
+
5
+ export type PersonaParameterPresentation = {
6
+ name: string
7
+ type?: string | null
8
+ required?: boolean
9
+ default?: unknown
10
+ }
11
+
12
+ export type PersonaDiagnosticPresentationInput = {
13
+ context?: string | null
14
+ reason?: string | null
15
+ severity: string
16
+ }
17
+
18
+ export type PersonaDiagnosticPresentation = {
19
+ category: 'scenario_import' | 'component_analysis' | 'support_quality' | 'unclassified'
20
+ title: string
21
+ ownerLabel: string
22
+ impactLabel: string
23
+ remediation: string
24
+ statusLabel: string
25
+ statusSeverity: PersonaStatusSeverity
26
+ }
27
+
28
+ type PersonaDiagnosticOverviewItem = {
29
+ category: PersonaDiagnosticPresentation['category']
30
+ }
31
+
32
+ export function personaDiagnosticPresentation(
33
+ input: PersonaDiagnosticPresentationInput,
34
+ ): PersonaDiagnosticPresentation {
35
+ const isError = input.severity === 'error' || input.severity === 'blocking'
36
+ const statusLabel = isError ? 'ошибка' : input.severity === 'warning' ? 'предупреждение' : 'информация'
37
+ const statusSeverity: PersonaStatusSeverity = isError ? 'danger' : input.severity === 'warning' ? 'warn' : 'info'
38
+
39
+ if (input.context === 'native_scenarios') {
40
+ return {
41
+ category: 'scenario_import',
42
+ title: 'Сценарий не импортирован',
43
+ ownerLabel: 'Исправить в тестовом проекте',
44
+ impactLabel: 'Сценарий недоступен для запуска',
45
+ remediation: 'Восстановить отсутствующий модуль или исправить импорт в сценарии.',
46
+ statusLabel,
47
+ statusSeverity,
48
+ }
49
+ }
50
+ if (input.context === 'components') {
51
+ return {
52
+ category: 'component_analysis',
53
+ title: 'Компонент не проанализирован',
54
+ ownerLabel: 'Исправить в тестовом проекте',
55
+ impactLabel: 'Структура связанных сценариев может быть неполной',
56
+ remediation: 'Исправить импорт компонента и его зависимостей.',
57
+ statusLabel,
58
+ statusSeverity,
59
+ }
60
+ }
61
+ if (input.context === 'support_quality') {
62
+ const remediation = input.reason === 'page_support_import_non_maintained'
63
+ ? 'Перенести используемый PageObject из пробной области в поддерживаемую область проекта.'
64
+ : 'Дать элементу или компоненту устойчивое предметное имя в коде проекта.'
65
+ return {
66
+ category: 'support_quality',
67
+ title: 'Качество PageObject',
68
+ ownerLabel: 'Исправить в тестовом проекте',
69
+ impactLabel: 'Не блокирует доступные сценарии',
70
+ remediation,
71
+ statusLabel,
72
+ statusSeverity,
73
+ }
74
+ }
75
+ return {
76
+ category: 'unclassified',
77
+ title: 'Неклассифицированное сообщение Persona',
78
+ ownerLabel: 'Требуется классификация Persona',
79
+ impactLabel: 'Влияние не указано',
80
+ remediation: 'Persona должна передать машинно-читаемые владельца, влияние и способ исправления.',
81
+ statusLabel,
82
+ statusSeverity,
83
+ }
84
+ }
85
+
86
+ export function personaDiagnosticOverviewMessage(
87
+ availableTests: number,
88
+ unavailableTests: number,
89
+ diagnostics: readonly PersonaDiagnosticOverviewItem[],
90
+ ): string {
91
+ const count = (category: PersonaDiagnosticOverviewItem['category']) => (
92
+ diagnostics.filter((diagnostic) => diagnostic.category === category).length
93
+ )
94
+ const totalTests = availableTests + unavailableTests
95
+ const scenarioImports = count('scenario_import')
96
+ const componentAnalysis = count('component_analysis')
97
+ const supportQuality = count('support_quality')
98
+ const parts = [
99
+ `Доступно ${availableTests} из ${totalTests} сценариев.`,
100
+ scenarioImports > 0 ? `${scenarioImports} сценариев не импортируются из-за ошибок в зависимостях тестового проекта.` : null,
101
+ componentAnalysis > 0
102
+ ? `${componentAnalysis} сообщений относятся к анализу компонентов, остановленному отсутствующими модулями тестового проекта.`
103
+ : null,
104
+ supportQuality > 0 ? `${supportQuality} замечаний качества PageObject не блокируют доступные сценарии.` : null,
105
+ ]
106
+ return parts.filter((part): part is string => Boolean(part)).join(' ')
107
+ }
108
+
109
+ const PERSONA_RUN_STATUSES = [
110
+ 'cancelled',
111
+ 'cancelling',
112
+ 'completed',
113
+ 'degraded',
114
+ 'error',
115
+ 'failed',
116
+ 'passed',
117
+ 'paused',
118
+ 'queued',
119
+ 'running',
120
+ 'skipped',
121
+ 'timed_out',
122
+ 'timeout',
123
+ 'info',
124
+ 'warn',
125
+ 'warning',
126
+ ] as const
127
+ type PersonaRunStatus = typeof PERSONA_RUN_STATUSES[number]
128
+ const PERSONA_RUN_STATUS_PRESENTATIONS = {
129
+ cancelled: { label: 'отменён', severity: 'secondary' },
130
+ cancelling: { label: 'отменяется', severity: 'warn' },
131
+ completed: { label: 'завершён', severity: 'success' },
132
+ degraded: { label: 'требует внимания', severity: 'warn' },
133
+ error: { label: 'ошибка', severity: 'danger' },
134
+ failed: { label: 'ошибка', severity: 'danger' },
135
+ passed: { label: 'пройден', severity: 'success' },
136
+ paused: { label: 'приостановлен', severity: 'info' },
137
+ queued: { label: 'в очереди', severity: 'warn' },
138
+ running: { label: 'выполняется', severity: 'info' },
139
+ skipped: { label: 'пропущен', severity: 'warn' },
140
+ timed_out: { label: 'превышено время', severity: 'danger' },
141
+ timeout: { label: 'превышено время', severity: 'danger' },
142
+ info: { label: 'информация', severity: 'info' },
143
+ warn: { label: 'предупреждение', severity: 'warn' },
144
+ warning: { label: 'предупреждение', severity: 'warn' },
145
+ } as const satisfies Record<PersonaRunStatus, {
146
+ label: string
147
+ severity: PersonaStatusSeverity
148
+ }>
149
+
150
+ function resolvePersonaRunStatus(status: unknown): PersonaRunStatus {
151
+ return validateExactString('Persona presentation', 'run status', status, PERSONA_RUN_STATUSES)
152
+ }
153
+
154
+ export function personaRunStatusLabel(status: unknown): string {
155
+ return PERSONA_RUN_STATUS_PRESENTATIONS[resolvePersonaRunStatus(status)].label
156
+ }
157
+
158
+ export function personaStatusSeverity(status: unknown): PersonaStatusSeverity {
159
+ return PERSONA_RUN_STATUS_PRESENTATIONS[resolvePersonaRunStatus(status)].severity
160
+ }
161
+
162
+ const PERSONA_SEMANTIC_KINDS = [
163
+ 'Action',
164
+ 'CombinedStep',
165
+ 'Expectation',
166
+ 'Fact',
167
+ 'Goal',
168
+ 'Ops',
169
+ 'Step',
170
+ 'action',
171
+ 'check',
172
+ 'combined_step',
173
+ 'expectation',
174
+ 'fact',
175
+ 'get',
176
+ 'goal',
177
+ 'make',
178
+ 'persona_call',
179
+ 'step',
180
+ ] as const
181
+ type PersonaSemanticKind = typeof PERSONA_SEMANTIC_KINDS[number]
182
+ const PERSONA_SEMANTIC_KIND_LABELS = {
183
+ Action: 'Действие',
184
+ CombinedStep: 'Составной шаг',
185
+ Expectation: 'Проверка',
186
+ Fact: 'Данные',
187
+ Goal: 'Цель',
188
+ Ops: 'Служебная операция',
189
+ Step: 'Шаг',
190
+ action: 'Действие',
191
+ check: 'Проверка',
192
+ combined_step: 'Составной шаг',
193
+ expectation: 'Проверка',
194
+ fact: 'Данные',
195
+ get: 'Получение данных',
196
+ goal: 'Цель',
197
+ make: 'Действие',
198
+ persona_call: 'Вызов Persona',
199
+ step: 'Шаг',
200
+ } as const satisfies Record<PersonaSemanticKind, string>
201
+
202
+ export function personaSemanticKindLabel(kind: unknown): string {
203
+ if (kind === null || kind === undefined) return ''
204
+ const resolvedKind = validateExactString(
205
+ 'Persona presentation',
206
+ 'semantic kind',
207
+ kind,
208
+ PERSONA_SEMANTIC_KINDS,
209
+ )
210
+ return PERSONA_SEMANTIC_KIND_LABELS[resolvedKind]
211
+ }
212
+
213
+ export function personaParameterSummary(parameters: readonly PersonaParameterPresentation[]): string {
214
+ return parameters.map((parameter) => {
215
+ const type = parameter.type ? `: ${parameter.type}` : ''
216
+ const hasPrimitiveDefault = typeof parameter.default === 'string'
217
+ || typeof parameter.default === 'number'
218
+ || typeof parameter.default === 'boolean'
219
+ const defaultValue = hasPrimitiveDefault ? ` = ${String(parameter.default)}` : ''
220
+ return `${parameter.name}${type}${defaultValue}${parameter.required ? ' (обязательный)' : ''}`
221
+ }).join(', ')
222
+ }
223
+
224
+ const PERSONA_SEVERITIES = ['blocker', 'critical', 'minor', 'normal', 'trivial'] as const
225
+ type PersonaSeverity = typeof PERSONA_SEVERITIES[number]
226
+ const PERSONA_SEVERITY_PRESENTATIONS = {
227
+ blocker: { label: 'блокирующий', severity: 'danger' },
228
+ critical: { label: 'критический', severity: 'danger' },
229
+ minor: { label: 'низкая критичность', severity: 'secondary' },
230
+ normal: { label: 'обычная критичность', severity: 'info' },
231
+ trivial: { label: 'минимальная критичность', severity: 'secondary' },
232
+ } as const satisfies Record<PersonaSeverity, {
233
+ label: string
234
+ severity: PersonaStatusSeverity
235
+ }>
236
+
237
+ function resolvePersonaSeverity(value: unknown): PersonaSeverity {
238
+ return validateExactString('Persona presentation', 'severity', value, PERSONA_SEVERITIES)
239
+ }
240
+
241
+ export function personaSeverityLabel(value: unknown): string {
242
+ return PERSONA_SEVERITY_PRESENTATIONS[resolvePersonaSeverity(value)].label
243
+ }
244
+
245
+ export function personaSeverityBadge(value: unknown): PersonaStatusSeverity {
246
+ return PERSONA_SEVERITY_PRESENTATIONS[resolvePersonaSeverity(value)].severity
247
+ }
248
+
249
+ const PERSONA_REPORT_STATUSES = ['broken', 'failed', 'passed', 'skipped'] as const
250
+ type PersonaReportStatus = typeof PERSONA_REPORT_STATUSES[number]
251
+ const PERSONA_REPORT_STATUS_LABELS = {
252
+ broken: 'сломан',
253
+ failed: 'ошибка',
254
+ passed: 'пройден',
255
+ skipped: 'пропущен',
256
+ } as const satisfies Record<PersonaReportStatus, string>
257
+
258
+ export function personaReportStatusLabel(status: unknown): string {
259
+ const resolvedStatus = validateExactString(
260
+ 'Persona presentation',
261
+ 'report status',
262
+ status,
263
+ PERSONA_REPORT_STATUSES,
264
+ )
265
+ return PERSONA_REPORT_STATUS_LABELS[resolvedStatus]
266
+ }
267
+
268
+ const PERSONA_LIFECYCLE_STAGES = [
269
+ 'run_after_scenario',
270
+ 'run_after_test',
271
+ 'run_before_scenario',
272
+ 'run_before_test',
273
+ ] as const
274
+ type PersonaLifecycleStage = typeof PERSONA_LIFECYCLE_STAGES[number]
275
+ const PERSONA_LIFECYCLE_STAGE_LABELS = {
276
+ run_after_scenario: 'после сценария',
277
+ run_after_test: 'после теста',
278
+ run_before_scenario: 'перед сценарием',
279
+ run_before_test: 'перед тестом',
280
+ } as const satisfies Record<PersonaLifecycleStage, string>
281
+
282
+ export function personaLifecycleStageLabel(stage: unknown): string {
283
+ const resolvedStage = validateExactString(
284
+ 'Persona presentation',
285
+ 'lifecycle stage',
286
+ stage,
287
+ PERSONA_LIFECYCLE_STAGES,
288
+ )
289
+ return PERSONA_LIFECYCLE_STAGE_LABELS[resolvedStage]
290
+ }
291
+
292
+ const PERSONA_EVENT_TYPES = [
293
+ 'cancelled',
294
+ 'completed',
295
+ 'failed',
296
+ 'log',
297
+ 'queued',
298
+ 'running',
299
+ 'started',
300
+ 'timeout',
301
+ 'report.runtime_log',
302
+ 'scenario.accepted',
303
+ 'scenario.cancelled',
304
+ 'scenario.finished',
305
+ 'scenario.started',
306
+ 'worker.started',
307
+ 'worker.stopped',
308
+ ] as const
309
+ type PersonaEventType = typeof PERSONA_EVENT_TYPES[number]
310
+ const PERSONA_EVENT_TYPE_LABELS = {
311
+ cancelled: 'Запуск отменён',
312
+ completed: 'Запуск завершён',
313
+ failed: 'Запуск завершён с ошибкой',
314
+ log: 'Сообщение выполнения',
315
+ queued: 'Запуск поставлен в очередь',
316
+ running: 'Запуск начат',
317
+ started: 'Запуск начат',
318
+ timeout: 'Время запуска превышено',
319
+ 'report.runtime_log': 'Сообщение выполнения',
320
+ 'scenario.accepted': 'Тест поставлен в очередь',
321
+ 'scenario.cancelled': 'Тест отменён',
322
+ 'scenario.finished': 'Тест завершён',
323
+ 'scenario.started': 'Тест запущен',
324
+ 'worker.started': 'Исполнитель подключён',
325
+ 'worker.stopped': 'Исполнитель остановлен',
326
+ } as const satisfies Record<PersonaEventType, string>
327
+
328
+ export function personaEventTypeLabel(eventType: unknown): string {
329
+ const resolvedEventType = validateExactString(
330
+ 'Persona presentation',
331
+ 'event type',
332
+ eventType,
333
+ PERSONA_EVENT_TYPES,
334
+ )
335
+ return PERSONA_EVENT_TYPE_LABELS[resolvedEventType]
336
+ }
337
+
338
+ export function personaPayloadMessage(payload: Record<string, unknown> | null): string {
339
+ if (!payload) return ''
340
+ for (const key of ['message', 'error_message', 'error', 'reason', 'detail', 'stderr', 'stdout']) {
341
+ const value = payload[key]
342
+ if (typeof value === 'string' && value.trim()) return value.trim()
343
+ }
344
+ return ''
345
+ }
346
+
347
+ export function personaDistinctMessage(title: string, message: string | null | undefined): string | null {
348
+ const normalizedMessage = message?.trim()
349
+ if (!normalizedMessage) return null
350
+ const comparable = (value: string) => value.toLocaleLowerCase('ru-RU').replace(/[.!?]+$/u, '').trim()
351
+ return comparable(title) === comparable(normalizedMessage) ? null : normalizedMessage
352
+ }
353
+
354
+ export function personaDurationLabel(durationMs: number | null | undefined): string {
355
+ if (durationMs === null || durationMs === undefined) return 'длительность не определена'
356
+ if (durationMs < 1000) return `${durationMs} мс`
357
+ return `${(durationMs / 1000).toLocaleString('ru-RU', { maximumFractionDigits: 2 })} с`
358
+ }
359
+
360
+ export function personaCountLabel(count: number, forms: [string, string, string]): string {
361
+ const remainder100 = count % 100
362
+ const remainder10 = count % 10
363
+ const form = remainder100 >= 11 && remainder100 <= 14
364
+ ? forms[2]
365
+ : remainder10 === 1
366
+ ? forms[0]
367
+ : remainder10 >= 2 && remainder10 <= 4
368
+ ? forms[1]
369
+ : forms[2]
370
+ return `${count} ${form}`
371
+ }
@@ -0,0 +1,256 @@
1
+ import type { BadgeSeverity } from '../data-display/SBadge.vue'
2
+ import type { ActionListItemBadge } from '../data-display/SActionListItem.vue'
3
+ import type { Severity as MessageSeverity } from '../data-display/SMessage.vue'
4
+ import type { SAsyncStateContract } from '../feedback/SAsyncState.vue'
5
+
6
+ export type PersonaRunConsoleDensity = 'compact' | 'comfortable'
7
+
8
+ /** Состояние точного инвентаря без одновременных loading/error/ready веток. / Exact inventory state without concurrent loading/error/ready branches. */
9
+ export type PersonaInventoryPendingState = Extract<
10
+ SAsyncStateContract,
11
+ { status: 'loading' | 'error' }
12
+ >
13
+
14
+ export type PersonaInventoryState<Item> =
15
+ | PersonaInventoryPendingState
16
+ | { status: 'ready'; items: Item[] }
17
+
18
+ /** Состояние точного paginated inventory с явной фазой страницы. / Exact paginated inventory state with an explicit page phase. */
19
+ export type PersonaPaginatedInventoryState<Item> =
20
+ | PersonaInventoryPendingState
21
+ | { status: 'ready'; items: Item[]; pagination: 'complete' | 'has-more' }
22
+ | { status: 'ready'; items: [Item, ...Item[]]; pagination: 'loading-more' }
23
+
24
+ /** Явная доступность действия без boolean capability fallback. / Explicit action availability without a boolean capability fallback. */
25
+ export type PersonaActionAvailability = 'enabled' | 'disabled'
26
+
27
+ /** Единственная активная операция private run console. / The single active private run-console operation. */
28
+ export type PersonaRunConsoleOperation =
29
+ | 'idle'
30
+ | 'refreshing-events'
31
+ | 'cancelling-run'
32
+ | 'appending-events'
33
+
34
+ /** Capability inventory private run console. / Private run-console capability inventory. */
35
+ export type PersonaRunConsoleCapabilities = {
36
+ cancelRun: PersonaActionAvailability
37
+ }
38
+
39
+ /** Стабильный badge-контракт canonical action-list owner. / Stable badge contract shared with the canonical action-list owner. */
40
+ export type PersonaRunConsoleBadge = ActionListItemBadge
41
+
42
+ export type PersonaRunConsoleFact = {
43
+ /** Стабильный доменный id, независимый от presentation label и позиции. / Stable domain id independent of presentation label and position. */
44
+ id: string
45
+ label: string
46
+ value: string | number
47
+ }
48
+
49
+ /** Возвращает стабильный доменный id факта. / Returns the stable domain id of a fact. */
50
+ export function getPersonaRunConsoleFactKey(fact: PersonaRunConsoleFact): string {
51
+ return fact.id
52
+ }
53
+
54
+ export type PersonaRunConsoleRun = {
55
+ id: string
56
+ title: string
57
+ meta: string
58
+ detail?: string | null
59
+ statusLabel: string
60
+ statusSeverity: BadgeSeverity
61
+ artifactLabel?: string | null
62
+ facts?: PersonaRunConsoleFact[]
63
+ }
64
+
65
+ export type PersonaRunConsoleItem = {
66
+ id: string
67
+ title: string
68
+ meta: string
69
+ detail?: string | null
70
+ statusLabel: string
71
+ statusSeverity: BadgeSeverity
72
+ badges?: PersonaRunConsoleBadge[]
73
+ }
74
+
75
+ export type PersonaRunConsoleEvent = {
76
+ id: string
77
+ title: string
78
+ meta: string
79
+ message?: string | null
80
+ badges?: PersonaRunConsoleBadge[]
81
+ }
82
+
83
+ export type PersonaRunConsoleLabels = {
84
+ runListTitle: string
85
+ selectedRunTitle: string
86
+ itemsTitle: string
87
+ eventsTitle: string
88
+ loadMoreRuns: string
89
+ loadMoreItems: string
90
+ appendEvents: string
91
+ loadMoreEvents: string
92
+ refreshEvents: string
93
+ cancelRun: string
94
+ reportSummary: string
95
+ emptyRun: string
96
+ noRuns: string
97
+ noItems: string
98
+ noEvents: string
99
+ }
100
+
101
+ export type PersonaCatalogStep = {
102
+ id: string
103
+ title: string
104
+ meta?: string | null
105
+ detail?: string | null
106
+ kind?: string | null
107
+ depth: number
108
+ badges?: PersonaRunConsoleBadge[]
109
+ }
110
+
111
+ export type PersonaCatalogLink = {
112
+ id: string
113
+ label: string
114
+ url: string
115
+ }
116
+
117
+ export type PersonaCatalogTest = {
118
+ id: string
119
+ title: string
120
+ meta: string
121
+ description?: string | null
122
+ detail?: string | null
123
+ workItemId: string
124
+ scenarioId: string
125
+ variantId?: string | null
126
+ sourcePath: string
127
+ tags?: string[]
128
+ badges?: PersonaRunConsoleBadge[]
129
+ facts?: PersonaRunConsoleFact[]
130
+ links?: PersonaCatalogLink[]
131
+ statusLabel?: string | null
132
+ statusSeverity?: BadgeSeverity
133
+ structureNotice?: PersonaRunWorkbenchNotice | null
134
+ steps: PersonaCatalogStep[]
135
+ }
136
+
137
+ export type PersonaRunWorkbenchBinding = {
138
+ id: string
139
+ title: string
140
+ root: string
141
+ statusLabel: string
142
+ statusSeverity: BadgeSeverity
143
+ meta?: string | null
144
+ }
145
+
146
+ export type PersonaRunWorkbenchWatcher = {
147
+ statusLabel: string
148
+ statusSeverity: BadgeSeverity
149
+ message?: string | null
150
+ }
151
+
152
+ export type PersonaRunWorkbenchCatalogOverview = {
153
+ unavailableTests?: number
154
+ components: number
155
+ pages: number
156
+ diagnosticErrors?: number
157
+ diagnosticWarnings?: number
158
+ environments?: string[]
159
+ executionModel?: string | null
160
+ }
161
+
162
+ export type PersonaRunWorkbenchDiagnostic = {
163
+ id: string
164
+ category: 'scenario_import' | 'component_analysis' | 'support_quality' | 'unclassified'
165
+ title: string
166
+ message: string
167
+ meta?: string | null
168
+ ownerLabel: string
169
+ impactLabel: string
170
+ remediation: string
171
+ statusLabel: string
172
+ statusSeverity: BadgeSeverity
173
+ }
174
+
175
+ export type PersonaRunWorkbenchNotice = {
176
+ title: string
177
+ message: string
178
+ severity: MessageSeverity
179
+ }
180
+
181
+ export type PersonaRunWorkbenchLabels = PersonaRunConsoleLabels & {
182
+ projectTitle: string
183
+ discoveryTitle: string
184
+ catalogTitle: string
185
+ structureTitle: string
186
+ runSelected: string
187
+ runAll: string
188
+ discover: string
189
+ watch: string
190
+ searchPlaceholder: string
191
+ noCatalogTests: string
192
+ emptyStructure: string
193
+ selectedTestTitle: string
194
+ }
195
+
196
+ /** Единственная активная операция workbench и его private console family. / The single active workbench/private-console-family operation. */
197
+ export type PersonaRunWorkbenchOperation =
198
+ | PersonaRunConsoleOperation
199
+ | 'discovering'
200
+ | 'watching'
201
+ | 'starting-run'
202
+
203
+ /** Полный capability inventory workbench. / Complete workbench capability inventory. */
204
+ export type PersonaRunWorkbenchCapabilities = PersonaRunConsoleCapabilities & {
205
+ discover: PersonaActionAvailability
206
+ watch: PersonaActionAvailability
207
+ runSelected: PersonaActionAvailability
208
+ runAll: PersonaActionAvailability
209
+ }
210
+
211
+ export type PersonaProjectPickerRoot = {
212
+ id: string
213
+ label: string
214
+ path: string
215
+ }
216
+
217
+ export type PersonaProjectPickerEntry = {
218
+ /** Стабильный id проекта/директории в полном listing inventory. / Stable project/directory id in the complete listing inventory. */
219
+ id: string
220
+ rootId: string
221
+ name: string
222
+ projectName?: string | null
223
+ relativePath: string
224
+ requestedRoot: string
225
+ displayPath: string
226
+ readiness: 'ready' | 'not_persona' | 'invalid' | 'unreadable'
227
+ isSelectable: boolean
228
+ isHidden: boolean
229
+ diagnosticMessage?: string | null
230
+ }
231
+
232
+ export type PersonaProjectPickerListing = {
233
+ root: PersonaProjectPickerRoot
234
+ current: PersonaProjectPickerEntry
235
+ parentRelativePath: string | null
236
+ directories: PersonaProjectPickerEntry[]
237
+ }
238
+
239
+ export type PersonaProjectPickerConnected = {
240
+ id: string
241
+ /** Id соответствующей PersonaProjectPickerEntry. / Id of the matching PersonaProjectPickerEntry. */
242
+ projectId: string
243
+ title: string
244
+ path: string
245
+ active: boolean
246
+ }
247
+
248
+ /** Состояние каталога picker; pagination loading допустим только при существующем listing. / Picker directory state; pagination loading is valid only with an existing listing. */
249
+ export type PersonaProjectPickerState =
250
+ | PersonaInventoryPendingState
251
+ | { status: 'ready'; listing: null; pagination: 'complete' }
252
+ | {
253
+ status: 'ready'
254
+ listing: PersonaProjectPickerListing
255
+ pagination: 'complete' | 'has-more' | 'loading-more'
256
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Типизированный scoped-controller для сброса UI-предпочтений.
3
+ * Typed scoped controller for resetting UI preferences.
4
+ */
5
+ export interface UiPreferencesResetController {
6
+ /**
7
+ * Подписывает owner на сброс и возвращает функцию отписки.
8
+ * Subscribes an owner to reset requests and returns an unsubscribe function.
9
+ */
10
+ subscribe(listener: () => void): () => void;
11
+
12
+ /**
13
+ * Синхронно уведомляет всех active owners о сбросе.
14
+ * Synchronously notifies every active owner about a reset.
15
+ */
16
+ requestReset(): void;
17
+ }
18
+
19
+ /**
20
+ * Создаёт isolated reset scope без document events и глобальных строковых каналов.
21
+ * Creates an isolated reset scope without document events or global string channels.
22
+ */
23
+ export function createUiPreferencesResetController(): UiPreferencesResetController {
24
+ const listeners = new Set<() => void>();
25
+
26
+ return {
27
+ subscribe(listener) {
28
+ listeners.add(listener);
29
+ return () => {
30
+ listeners.delete(listener);
31
+ };
32
+ },
33
+
34
+ requestReset() {
35
+ const errors: unknown[] = [];
36
+ for (const listener of [...listeners]) {
37
+ try {
38
+ listener();
39
+ } catch (error) {
40
+ errors.push(error);
41
+ }
42
+ }
43
+
44
+ if (errors.length === 1) {
45
+ throw errors[0];
46
+ }
47
+ if (errors.length > 1) {
48
+ throw new AggregateError(errors, 'Не все owners UI-предпочтений смогли обработать сброс.');
49
+ }
50
+ },
51
+ };
52
+ }