@cat-factory/app 0.302.2 → 0.303.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 (328) hide show
  1. package/README.md +16 -0
  2. package/app/app.config.ts +19 -10
  3. package/app/assets/css/main.css +13 -13
  4. package/app/assets/css/prose.css +22 -22
  5. package/app/assets/css/tokens.css +298 -0
  6. package/app/components/assistant/AssistantModal.vue +22 -22
  7. package/app/components/auth/AuthGate.vue +1 -1
  8. package/app/components/auth/BackendMisconfiguredScreen.vue +10 -10
  9. package/app/components/auth/LoginScreen.vue +34 -36
  10. package/app/components/auth/ResetPasswordScreen.vue +12 -12
  11. package/app/components/auth/UserMenu.vue +4 -4
  12. package/app/components/binaryCandidates/BinaryCandidatesWindow.vue +27 -25
  13. package/app/components/binaryOutput/BinaryOutputReport.vue +15 -15
  14. package/app/components/binaryOutput/StoredAssetView.vue +5 -5
  15. package/app/components/board/AddTaskModal.vue +17 -20
  16. package/app/components/board/AgentFailureCard.vue +11 -11
  17. package/app/components/board/AgentFailureHistory.vue +2 -2
  18. package/app/components/board/BoardCanvas.vue +6 -5
  19. package/app/components/board/CreateInitiativeModal.vue +8 -10
  20. package/app/components/board/DependencyConnectOverlay.vue +1 -1
  21. package/app/components/board/FailureHistoryList.vue +7 -7
  22. package/app/components/board/RecurringPipelineModal.vue +17 -17
  23. package/app/components/board/ReviewFrictionDialog.vue +6 -6
  24. package/app/components/board/StepExecutionHistory.vue +13 -13
  25. package/app/components/board/TaskDependencyEdges.vue +8 -8
  26. package/app/components/board/nodes/AgentChip.vue +10 -10
  27. package/app/components/board/nodes/BlockNode.vue +29 -29
  28. package/app/components/board/nodes/DecisionBadge.vue +2 -2
  29. package/app/components/board/nodes/EpicNode.vue +14 -10
  30. package/app/components/board/nodes/FrameSwimlanes.vue +7 -9
  31. package/app/components/board/nodes/InitiativeCard.vue +8 -8
  32. package/app/components/board/nodes/LaneGroup.vue +1 -1
  33. package/app/components/board/nodes/LaneTask.vue +2 -2
  34. package/app/components/board/nodes/ResizeGrips.vue +4 -4
  35. package/app/components/board/nodes/TaskCard.vue +18 -18
  36. package/app/components/board/nodes/TaskLane.vue +5 -5
  37. package/app/components/board/nodes/TaskPipelineMini.vue +18 -18
  38. package/app/components/bootstrap/AdoptionReviewModal.vue +29 -26
  39. package/app/components/bootstrap/BootstrapModal.vue +21 -23
  40. package/app/components/bootstrap/BootstrapRunSteps.vue +1 -1
  41. package/app/components/brainstorm/BrainstormWindow.vue +30 -33
  42. package/app/components/bugFishing/BugFishingWindow.vue +49 -49
  43. package/app/components/clarity/ClarityReviewWindow.vue +30 -30
  44. package/app/components/common/AsyncViewError.vue +5 -5
  45. package/app/components/common/ClarificationItem.vue +8 -13
  46. package/app/components/common/ConfirmDialog.vue +4 -2
  47. package/app/components/common/DescriptorFields.vue +1 -1
  48. package/app/components/common/EmptyState.vue +3 -3
  49. package/app/components/common/InterviewGateNotice.vue +7 -7
  50. package/app/components/common/KeyboardShortcutsHelp.vue +1 -1
  51. package/app/components/common/MarkdownProse.vue +14 -13
  52. package/app/components/consensus/ConsensusSessionWindow.vue +26 -26
  53. package/app/components/context/ContextAttachmentFields.vue +17 -17
  54. package/app/components/docs/DocInterviewWindow.vue +14 -14
  55. package/app/components/documents/ContextDocumentPicker.vue +17 -17
  56. package/app/components/documents/DocumentImportModal.vue +7 -7
  57. package/app/components/documents/DocumentSourceConnectModal.vue +3 -3
  58. package/app/components/documents/DocumentSyncState.vue +5 -5
  59. package/app/components/documents/DocumentTemplatesModal.vue +15 -15
  60. package/app/components/documents/RepoContextDocPicker.vue +5 -5
  61. package/app/components/documents/SpawnPreviewModal.vue +12 -12
  62. package/app/components/documents/StartFromDesignModal.vue +11 -11
  63. package/app/components/documents/TaskContextDocs.vue +5 -5
  64. package/app/components/environments/EnvSetupStepper.vue +6 -6
  65. package/app/components/environments/EnvironmentStatusPanel.vue +17 -17
  66. package/app/components/environments/steps/EnvPickStep.vue +3 -3
  67. package/app/components/environments/steps/EnvPreflightStep.vue +9 -9
  68. package/app/components/environments/steps/EnvReviewStep.vue +25 -22
  69. package/app/components/environments/steps/EnvSaveStep.vue +7 -5
  70. package/app/components/environments/steps/JourneyStepNav.vue +1 -1
  71. package/app/components/focus/BlockFocusView.vue +17 -19
  72. package/app/components/followUp/FollowUpWindow.vue +23 -23
  73. package/app/components/forkDecision/ForkDecisionWindow.vue +37 -39
  74. package/app/components/foundational/FoundationalContractSummary.vue +7 -7
  75. package/app/components/foundational/FoundationalServiceCatalogList.vue +11 -11
  76. package/app/components/foundational/FoundationalServiceManager.vue +1 -1
  77. package/app/components/foundational/FoundationalServiceRegistry.vue +13 -13
  78. package/app/components/foundational/FoundationalServiceSources.vue +13 -13
  79. package/app/components/foundational/FoundationalSuppressions.vue +7 -7
  80. package/app/components/foundational/ServiceCatalogConnection.vue +10 -10
  81. package/app/components/fragments/FragmentLibraryManager.vue +41 -44
  82. package/app/components/fragments/FragmentSelector.vue +13 -13
  83. package/app/components/fragments/GitHubDocUrlImport.vue +1 -1
  84. package/app/components/gates/GateFailingCheckList.vue +5 -5
  85. package/app/components/gates/GateResultView.vue +50 -47
  86. package/app/components/github/AddServiceFromRepoModal.vue +14 -14
  87. package/app/components/github/BranchProtectionPreflight.vue +13 -13
  88. package/app/components/github/GitHubConnect.vue +7 -7
  89. package/app/components/github/GitHubOnboarding.vue +7 -7
  90. package/app/components/github/GitHubPanel.vue +37 -40
  91. package/app/components/github/RepoTreeBrowser.vue +18 -18
  92. package/app/components/humanTest/HumanTestWindow.vue +30 -33
  93. package/app/components/initiative/InitiativePlanNotice.vue +5 -5
  94. package/app/components/initiative/InitiativePlanReview.vue +27 -29
  95. package/app/components/initiative/InitiativePlanningWindow.vue +10 -10
  96. package/app/components/initiative/InitiativeTrackerWindow.vue +71 -65
  97. package/app/components/inputGate/InputGateNotice.vue +2 -4
  98. package/app/components/judge/JudgeResultView.vue +30 -32
  99. package/app/components/kaizen/KaizenPanel.vue +40 -36
  100. package/app/components/kaizen/KaizenStepStatus.vue +21 -21
  101. package/app/components/layout/AccountAuditLog.vue +10 -10
  102. package/app/components/layout/AccountDeploymentSettings.vue +20 -20
  103. package/app/components/layout/AccountFailureKindRules.vue +13 -13
  104. package/app/components/layout/AccountFoundationalSettings.vue +1 -1
  105. package/app/components/layout/AccountFragmentSettings.vue +1 -1
  106. package/app/components/layout/AccountModelPolicySettings.vue +9 -9
  107. package/app/components/layout/AccountPlatformAlertSettings.vue +11 -11
  108. package/app/components/layout/AccountRiskPolicySettings.vue +2 -2
  109. package/app/components/layout/AccountRunCredentialSettings.vue +5 -5
  110. package/app/components/layout/AccountSkillSettings.vue +1 -1
  111. package/app/components/layout/AccountTeamSettings.vue +23 -15
  112. package/app/components/layout/AiProvidersBanner.vue +11 -8
  113. package/app/components/layout/AppearanceSwitcher.vue +108 -0
  114. package/app/components/layout/BoardSwitcher.vue +8 -11
  115. package/app/components/layout/BoardToolbar.vue +3 -3
  116. package/app/components/layout/CommandBar.vue +7 -9
  117. package/app/components/layout/ConnectionStatusBanner.vue +2 -2
  118. package/app/components/layout/DefaultTestEnvBanner.vue +5 -5
  119. package/app/components/layout/GitHubPatBanner.vue +5 -5
  120. package/app/components/layout/GitHubPatPermissionsBanner.vue +16 -10
  121. package/app/components/layout/InfraSetupBanner.vue +6 -6
  122. package/app/components/layout/IntegrationsHub.vue +15 -15
  123. package/app/components/layout/LanguageSwitcher.vue +5 -5
  124. package/app/components/layout/ModelProvidersHub.vue +13 -16
  125. package/app/components/layout/NotificationsInbox.vue +10 -10
  126. package/app/components/layout/PersonalSetupModal.vue +8 -8
  127. package/app/components/layout/ProviderConfigBanner.vue +4 -4
  128. package/app/components/layout/RolePrompt.vue +7 -7
  129. package/app/components/layout/SideBar.vue +6 -4
  130. package/app/components/layout/SpendWarningBanner.vue +17 -14
  131. package/app/components/layout/TranslationWarningBanner.vue +4 -4
  132. package/app/components/layout/UiModeSwitcher.vue +13 -13
  133. package/app/components/layout/UiRoleSwitcher.vue +8 -8
  134. package/app/components/layout/WorkspaceMembersSettings.vue +15 -11
  135. package/app/components/media/ArtifactLightbox.vue +13 -13
  136. package/app/components/media/ImageCompare.vue +31 -24
  137. package/app/components/merge/MergeEffortChips.vue +2 -2
  138. package/app/components/notifications/NotificationSettingsPanel.vue +14 -12
  139. package/app/components/observability/OutcomeFilterChips.vue +6 -6
  140. package/app/components/observability/RunFailureSummary.vue +28 -25
  141. package/app/components/observability/StepMetricsBar.vue +26 -28
  142. package/app/components/observability/StepModelActivity.vue +2 -2
  143. package/app/components/observability/ToolCallList.vue +24 -24
  144. package/app/components/outcome/OutcomeSummaryWindow.vue +47 -47
  145. package/app/components/palettes/AgentPalette.vue +8 -8
  146. package/app/components/palettes/AgentTierSelect.vue +1 -1
  147. package/app/components/palettes/PipelinePurposeSelect.vue +1 -1
  148. package/app/components/panels/AgentStepDetail.vue +52 -51
  149. package/app/components/panels/AttemptEntryHeader.vue +4 -6
  150. package/app/components/panels/DecisionModal.vue +7 -7
  151. package/app/components/panels/GenericStructuredResultView.vue +7 -7
  152. package/app/components/panels/InspectorPanel.vue +17 -17
  153. package/app/components/panels/MergerResultView.vue +19 -19
  154. package/app/components/panels/ObservabilityPanel.vue +115 -123
  155. package/app/components/panels/OperatorDashboardPanel.vue +89 -97
  156. package/app/components/panels/ReportsPanel.vue +69 -66
  157. package/app/components/panels/ReportsSpendBreakdown.vue +9 -9
  158. package/app/components/panels/ResultWindowShell.vue +38 -38
  159. package/app/components/panels/RunDetailLoadState.vue +2 -2
  160. package/app/components/panels/StepContainerStatus.vue +9 -9
  161. package/app/components/panels/StepDelegatedStatus.logic.spec.ts +135 -0
  162. package/app/components/panels/StepDelegatedStatus.logic.ts +160 -0
  163. package/app/components/panels/StepDelegatedStatus.vue +181 -0
  164. package/app/components/panels/StepEffortReport.vue +16 -15
  165. package/app/components/panels/StepFragmentAdherence.vue +14 -10
  166. package/app/components/panels/StepMetadataCard.vue +74 -45
  167. package/app/components/panels/StepReproductionReport.vue +25 -21
  168. package/app/components/panels/StepRunMeta.vue +20 -20
  169. package/app/components/panels/StepTestReport.logic.spec.ts +8 -3
  170. package/app/components/panels/StepTestReport.logic.ts +4 -4
  171. package/app/components/panels/StepTestReport.vue +28 -28
  172. package/app/components/panels/StepToolServers.vue +14 -11
  173. package/app/components/panels/StepValidationReport.vue +7 -7
  174. package/app/components/panels/inspector/ContainerSummary.vue +9 -9
  175. package/app/components/panels/inspector/DocReferenceRepos.vue +3 -3
  176. package/app/components/panels/inspector/EnvProbeReport.vue +24 -27
  177. package/app/components/panels/inspector/EpicChildren.vue +8 -12
  178. package/app/components/panels/inspector/FrontendBindingsResolved.vue +11 -11
  179. package/app/components/panels/inspector/FrontendConfig.vue +37 -41
  180. package/app/components/panels/inspector/InitiativeInspector.vue +3 -3
  181. package/app/components/panels/inspector/InspectorSection.vue +6 -6
  182. package/app/components/panels/inspector/RecurringScheduleSettings.vue +16 -19
  183. package/app/components/panels/inspector/ServiceConnections.vue +2 -2
  184. package/app/components/panels/inspector/ServiceReleaseHealthConfig.vue +2 -2
  185. package/app/components/panels/inspector/ServiceSelfTests.vue +21 -17
  186. package/app/components/panels/inspector/ServiceTestConfig.vue +51 -59
  187. package/app/components/panels/inspector/ServiceTestSecrets.vue +5 -5
  188. package/app/components/panels/inspector/ServiceTestingContext.vue +3 -3
  189. package/app/components/panels/inspector/ServiceValidationConfig.vue +2 -2
  190. package/app/components/panels/inspector/TaskAgentConfig.vue +3 -3
  191. package/app/components/panels/inspector/TaskAprioriBranches.vue +8 -5
  192. package/app/components/panels/inspector/TaskDependencies.vue +1 -1
  193. package/app/components/panels/inspector/TaskEstimateBadge.vue +11 -11
  194. package/app/components/panels/inspector/TaskExecution.vue +17 -17
  195. package/app/components/panels/inspector/TaskReviewTarget.vue +2 -2
  196. package/app/components/panels/inspector/TaskRunSettings.vue +35 -37
  197. package/app/components/panels/inspector/TaskStructure.vue +3 -3
  198. package/app/components/pipeline/AgentKindIcon.vue +1 -1
  199. package/app/components/pipeline/AgentPromptEditor.vue +17 -17
  200. package/app/components/pipeline/BinaryOutputStepPicker.vue +42 -56
  201. package/app/components/pipeline/EstimateThresholdFields.vue +4 -4
  202. package/app/components/pipeline/GateConfigFields.vue +13 -10
  203. package/app/components/pipeline/IterationCapPrompt.vue +4 -2
  204. package/app/components/pipeline/PipelineBuilder.vue +47 -55
  205. package/app/components/pipeline/PipelineHealthModal.vue +27 -29
  206. package/app/components/pipeline/PipelinePicker.vue +10 -10
  207. package/app/components/pipeline/PipelinePreview.vue +10 -10
  208. package/app/components/pipeline/PipelineProgress.vue +64 -67
  209. package/app/components/prReview/PrReviewPhaseBadge.vue +7 -7
  210. package/app/components/prReview/PrReviewWindow.vue +75 -77
  211. package/app/components/providers/AiPresetMismatchDialog.vue +5 -5
  212. package/app/components/providers/AiProviderOnboardingModal.vue +9 -9
  213. package/app/components/providers/ApiKeysSection.vue +13 -13
  214. package/app/components/providers/PersonalCredentialModal.vue +2 -2
  215. package/app/components/providers/PersonalSubscriptionSection.vue +10 -10
  216. package/app/components/providers/SignInRequiredNotice.vue +1 -1
  217. package/app/components/providers/VendorCredentialsModal.vue +10 -10
  218. package/app/components/provisioning/ProvisioningLogsDrawer.vue +13 -13
  219. package/app/components/ralph/RalphLoopResultView.vue +28 -30
  220. package/app/components/recurring/RecurrenceEditor.vue +2 -2
  221. package/app/components/requirements/RequirementsReviewWindow.vue +51 -58
  222. package/app/components/riskPolicy/RiskPolicyPicker.vue +12 -15
  223. package/app/components/riskPolicy/RiskPolicyPreview.vue +14 -14
  224. package/app/components/sandbox/SandboxPanel.vue +58 -59
  225. package/app/components/settings/AccountSettingsPanel.vue +1 -1
  226. package/app/components/settings/ApiTokensPanel.vue +10 -10
  227. package/app/components/settings/BudgetSettings.vue +18 -22
  228. package/app/components/settings/CapabilityCredentialsPanel.vue +15 -15
  229. package/app/components/settings/CloudflareHandlerSection.vue +20 -20
  230. package/app/components/settings/ComposeEnvironmentSetupSection.vue +13 -13
  231. package/app/components/settings/ConnectionTestVerdict.vue +3 -3
  232. package/app/components/settings/ConnectionWarnings.vue +1 -1
  233. package/app/components/settings/ConsensusGroupsSection.vue +27 -35
  234. package/app/components/settings/CustomManifestTypeEditor.vue +10 -10
  235. package/app/components/settings/DefaultProvisionTypeSection.vue +6 -6
  236. package/app/components/settings/InfraHandlersConfigurator.vue +24 -22
  237. package/app/components/settings/InfrastructureBackendPicker.vue +6 -6
  238. package/app/components/settings/InfrastructureWindow.vue +5 -5
  239. package/app/components/settings/IssueTrackerPanel.vue +29 -23
  240. package/app/components/settings/KubernetesEngineForm.vue +11 -11
  241. package/app/components/settings/KubernetesEnvironmentForm.vue +4 -4
  242. package/app/components/settings/LocalContainerPoolSettings.vue +6 -11
  243. package/app/components/settings/LocalModelEndpointsPanel.vue +20 -18
  244. package/app/components/settings/McpAuthorizeScreen.vue +13 -15
  245. package/app/components/settings/McpOAuthCallbackScreen.vue +16 -10
  246. package/app/components/settings/MergeClassRulesEditor.vue +6 -9
  247. package/app/components/settings/MergeRolePolicyEditor.vue +16 -16
  248. package/app/components/settings/ModelConfigurationPanel.vue +23 -30
  249. package/app/components/settings/ModelPresetHealthModal.vue +15 -13
  250. package/app/components/settings/ObservabilityConnectionPanel.vue +4 -4
  251. package/app/components/settings/OpenRouterCatalogPanel.vue +23 -19
  252. package/app/components/settings/PackageRegistriesPanel.vue +8 -8
  253. package/app/components/settings/ProviderConnectionTab.vue +8 -11
  254. package/app/components/settings/ProviderManifestEditor.vue +9 -9
  255. package/app/components/settings/ProviderPreferenceEditor.vue +8 -8
  256. package/app/components/settings/RiskPolicyCreateForm.vue +10 -10
  257. package/app/components/settings/RiskPolicyEditorRow.vue +14 -14
  258. package/app/components/settings/RiskPolicyHealthModal.vue +15 -13
  259. package/app/components/settings/RiskPolicyInheritedRow.vue +1 -1
  260. package/app/components/settings/RiskPolicyPanel.vue +10 -15
  261. package/app/components/settings/ServiceFragmentDefaultsPanel.vue +7 -7
  262. package/app/components/settings/SharedStacksPanel.vue +9 -12
  263. package/app/components/settings/TaskSourceCard.vue +4 -4
  264. package/app/components/settings/TaskTypeSuppressionsPanel.vue +6 -6
  265. package/app/components/settings/ToolServerChecklist.vue +36 -29
  266. package/app/components/settings/UsageSettings.vue +15 -15
  267. package/app/components/settings/UserSecretsSection.vue +9 -12
  268. package/app/components/settings/WorkspaceMetadataSettings.vue +5 -5
  269. package/app/components/settings/WorkspaceSettingsPanel.vue +47 -47
  270. package/app/components/skills/ReviewSkillQueue.vue +10 -10
  271. package/app/components/skills/SkillLibraryManager.vue +23 -20
  272. package/app/components/slack/SlackPanel.vue +13 -17
  273. package/app/components/spec/ServiceSpecWindow.vue +45 -42
  274. package/app/components/tasks/BugHuntModal.vue +24 -27
  275. package/app/components/tasks/ContextIssuePicker.vue +11 -11
  276. package/app/components/tasks/TaskContextIssues.vue +3 -3
  277. package/app/components/tasks/TaskImportModal.vue +7 -7
  278. package/app/components/tasks/TaskSourceConnectModal.vue +10 -8
  279. package/app/components/testing/TestReportWindow.vue +99 -82
  280. package/app/components/theme/ThemeImportModal.vue +115 -0
  281. package/app/components/tutorial/TutorialCatalogue.vue +9 -9
  282. package/app/components/tutorial/TutorialNudge.vue +5 -5
  283. package/app/components/tutorial/TutorialOverlay.vue +12 -12
  284. package/app/components/tutorial/TutorialPrompt.vue +6 -9
  285. package/app/components/vcs/GitLabConnect.vue +4 -4
  286. package/app/components/vcs/VcsConnectSurfaces.vue +2 -2
  287. package/app/components/visualConfirm/VisualConfirmationWindow.vue +36 -36
  288. package/app/composables/api/client.ts +6 -1
  289. package/app/composables/usePipelineErrorToast.ts +17 -0
  290. package/app/modular/agent-kinds.ts +24 -0
  291. package/app/pages/index.vue +5 -5
  292. package/app/plugins/theme.client.ts +125 -0
  293. package/app/spa-loading-template.html +52 -14
  294. package/app/stores/agents.ts +1 -1
  295. package/app/stores/theme.spec.ts +56 -0
  296. package/app/stores/theme.ts +87 -0
  297. package/app/types/domain.ts +19 -0
  298. package/app/types/execution.ts +4 -0
  299. package/app/utils/agentOutput.ts +1 -1
  300. package/app/utils/binaryOutput.ts +6 -6
  301. package/app/utils/bootstrapSteps.ts +14 -14
  302. package/app/utils/catalog.ts +87 -83
  303. package/app/utils/colorTint.ts +10 -0
  304. package/app/utils/failureKinds.ts +1 -0
  305. package/app/utils/observability.ts +3 -3
  306. package/app/utils/pipelineRender.ts +13 -17
  307. package/app/utils/swimlanes.ts +4 -4
  308. package/app/utils/theme/bootPalette.spec.ts +54 -0
  309. package/app/utils/theme/bootPalette.ts +108 -0
  310. package/app/utils/theme/builtins.spec.ts +38 -0
  311. package/app/utils/theme/builtins.ts +55 -0
  312. package/app/utils/theme/css.ts +162 -0
  313. package/app/utils/theme/doc.ts +353 -0
  314. package/app/utils/theme/link.ts +93 -0
  315. package/app/utils/theme/presets.ts +509 -0
  316. package/app/utils/theme/theme.spec.ts +224 -0
  317. package/i18n/locales/de.json +61 -4
  318. package/i18n/locales/en.json +70 -4
  319. package/i18n/locales/es.json +61 -4
  320. package/i18n/locales/fr.json +61 -4
  321. package/i18n/locales/he.json +61 -4
  322. package/i18n/locales/it.json +61 -4
  323. package/i18n/locales/ja.json +61 -4
  324. package/i18n/locales/pl.json +61 -4
  325. package/i18n/locales/tr.json +61 -4
  326. package/i18n/locales/uk.json +61 -4
  327. package/nuxt.config.ts +37 -11
  328. package/package.json +9 -8
@@ -145,13 +145,17 @@ const STATUS_META = computed<
145
145
  >(() => ({
146
146
  passed: {
147
147
  icon: 'i-lucide-circle-check',
148
- text: 'text-emerald-400',
148
+ text: 'text-app-success-400',
149
149
  label: t('testing.status.passed'),
150
150
  },
151
- failed: { icon: 'i-lucide-circle-x', text: 'text-rose-400', label: t('testing.status.failed') },
151
+ failed: {
152
+ icon: 'i-lucide-circle-x',
153
+ text: 'text-app-error-400',
154
+ label: t('testing.status.failed'),
155
+ },
152
156
  skipped: {
153
157
  icon: 'i-lucide-circle-minus',
154
- text: 'text-slate-500',
158
+ text: 'text-dimmed',
155
159
  label: t('testing.status.skipped'),
156
160
  },
157
161
  }))
@@ -167,10 +171,18 @@ const SEVERITY_LABELS = computed<Record<TestConcern['severity'], string>>(() =>
167
171
 
168
172
  const SEVERITY_META: Record<TestConcern['severity'], { text: string; chip: string; rank: number }> =
169
173
  {
170
- critical: { text: 'text-rose-300', chip: 'bg-rose-500/15 text-rose-300', rank: 0 },
171
- high: { text: 'text-rose-300', chip: 'bg-rose-500/15 text-rose-300', rank: 1 },
172
- medium: { text: 'text-amber-300', chip: 'bg-amber-500/15 text-amber-300', rank: 2 },
173
- low: { text: 'text-slate-300', chip: 'bg-slate-500/15 text-slate-300', rank: 3 },
174
+ critical: {
175
+ text: 'text-app-error-300',
176
+ chip: 'bg-app-error-500/15 text-app-error-300',
177
+ rank: 0,
178
+ },
179
+ high: { text: 'text-app-error-300', chip: 'bg-app-error-500/15 text-app-error-300', rank: 1 },
180
+ medium: {
181
+ text: 'text-app-warning-300',
182
+ chip: 'bg-app-warning-500/15 text-app-warning-300',
183
+ rank: 2,
184
+ },
185
+ low: { text: 'text-toned', chip: 'bg-app-500/15 text-toned', rank: 3 },
174
186
  }
175
187
 
176
188
  /** Case-insensitive "these two labels refer to the same thing" heuristic. */
@@ -318,11 +330,11 @@ function toggle(key: string) {
318
330
  }
319
331
 
320
332
  const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: string }> = {
321
- passed: { icon: 'i-lucide-circle-check', text: 'text-emerald-400' },
322
- failed: { icon: 'i-lucide-circle-x', text: 'text-rose-400' },
323
- skipped: { icon: 'i-lucide-circle-minus', text: 'text-slate-500' },
324
- mixed: { icon: 'i-lucide-circle-dot', text: 'text-amber-400' },
325
- empty: { icon: 'i-lucide-circle-dashed', text: 'text-slate-500' },
333
+ passed: { icon: 'i-lucide-circle-check', text: 'text-app-success-400' },
334
+ failed: { icon: 'i-lucide-circle-x', text: 'text-app-error-400' },
335
+ skipped: { icon: 'i-lucide-circle-minus', text: 'text-dimmed' },
336
+ mixed: { icon: 'i-lucide-circle-dot', text: 'text-app-warning-400' },
337
+ empty: { icon: 'i-lucide-circle-dashed', text: 'text-dimmed' },
326
338
  }
327
339
  </script>
328
340
 
@@ -330,7 +342,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
330
342
  <ResultWindowShell
331
343
  :open="open"
332
344
  icon="i-lucide-flask-conical"
333
- icon-class="bg-amber-500/15 text-amber-300"
345
+ icon-class="bg-app-warning-500/15 text-app-warning-300"
334
346
  :title="headerTitle"
335
347
  :subtitle="t('testing.subtitle')"
336
348
  :step-ref="{ instanceId, stepIndex }"
@@ -349,7 +361,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
349
361
  </UBadge>
350
362
  <span
351
363
  v-if="testState && testState.attempts > 0"
352
- class="text-[11px] text-slate-400"
364
+ class="text-[11px] text-muted"
353
365
  :title="t('testing.fixerAttempts')"
354
366
  >
355
367
  {{ t('testing.fixCount', { attempts: testState.attempts, max: testState.maxAttempts }) }}
@@ -372,7 +384,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
372
384
  data-testid="tester-infrastructure"
373
385
  class="space-y-3"
374
386
  >
375
- <h3 class="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
387
+ <h3 class="text-[11px] font-semibold uppercase tracking-wide text-dimmed">
376
388
  {{ t('testing.infrastructure') }}
377
389
  </h3>
378
390
  <StepContainerStatus :step="step" :run-failed="runFailed" :run-active="runActive" />
@@ -392,20 +404,20 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
392
404
  class="rounded-lg border px-3 py-2"
393
405
  :class="
394
406
  infraSetup.started
395
- ? 'border-slate-800 bg-slate-900/60'
396
- : 'border-rose-500/40 bg-rose-500/10'
407
+ ? 'border-default bg-default/60'
408
+ : 'border-app-error-500/40 bg-app-error-500/10'
397
409
  "
398
410
  >
399
411
  <div class="flex items-center gap-2">
400
412
  <UIcon
401
413
  :name="infraSetup.started ? 'i-lucide-container' : 'i-lucide-circle-x'"
402
414
  class="h-3.5 w-3.5 shrink-0"
403
- :class="infraSetup.started ? 'text-emerald-400' : 'text-rose-400'"
415
+ :class="infraSetup.started ? 'text-app-success-400' : 'text-app-error-400'"
404
416
  />
405
- <span class="text-[13px] font-medium text-slate-200">
417
+ <span class="text-[13px] font-medium text-default">
406
418
  {{ standupHeadline }}
407
419
  </span>
408
- <span v-if="infraSetup.durationMs != null" class="ms-auto text-[11px] text-slate-500">
420
+ <span v-if="infraSetup.durationMs != null" class="ms-auto text-[11px] text-dimmed">
409
421
  {{
410
422
  t('testing.standup.took', {
411
423
  seconds: n(infraSetup.durationMs / 1000, 'decimal'),
@@ -413,12 +425,12 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
413
425
  }}
414
426
  </span>
415
427
  </div>
416
- <p v-if="infraSetup.composePath" class="mt-1 font-mono text-[11px] text-slate-500">
428
+ <p v-if="infraSetup.composePath" class="mt-1 font-mono text-[11px] text-dimmed">
417
429
  {{ infraSetup.composePath }}
418
430
  </p>
419
431
  <p
420
432
  v-if="infraSetup.error"
421
- class="mt-1 text-[12px] leading-snug text-rose-300"
433
+ class="mt-1 text-[12px] leading-snug text-app-error-300"
422
434
  data-testid="tester-infra-setup-error"
423
435
  >
424
436
  {{ infraSetup.error }}
@@ -443,7 +455,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
443
455
  <pre
444
456
  v-if="showInfraSetupLogs"
445
457
  data-testid="tester-infra-setup-logs"
446
- class="mt-2 max-h-64 overflow-auto rounded bg-slate-950/70 p-2 font-mono text-[11px] leading-relaxed text-slate-300"
458
+ class="mt-2 max-h-64 overflow-auto rounded bg-app-950/70 p-2 font-mono text-[11px] leading-relaxed text-toned"
447
459
  >{{ infraSetup.logs }}</pre>
448
460
  </template>
449
461
  </div>
@@ -455,9 +467,9 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
455
467
  <div
456
468
  v-if="infraReady"
457
469
  data-testid="tester-env-ready"
458
- class="flex items-center gap-2 rounded-lg border border-emerald-500/30 bg-emerald-500/10 px-3 py-2 text-[13px] text-emerald-200"
470
+ class="flex items-center gap-2 rounded-lg border border-app-success-500/30 bg-app-success-500/10 px-3 py-2 text-[13px] text-app-success-200"
459
471
  >
460
- <UIcon name="i-lucide-rocket" class="h-4 w-4 shrink-0 text-emerald-400" />
472
+ <UIcon name="i-lucide-rocket" class="h-4 w-4 shrink-0 text-app-success-400" />
461
473
  <span>{{ t('testing.readyBanner') }}</span>
462
474
  </div>
463
475
 
@@ -492,7 +504,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
492
504
  how it ended), so the otherwise-opaque fixer sub-jobs have a surface — the
493
505
  analogue of the polling gate's attempt history. -->
494
506
  <section v-if="fixerAttempts.length" data-testid="tester-fixer-attempts" class="space-y-2">
495
- <h3 class="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
507
+ <h3 class="text-[11px] font-semibold uppercase tracking-wide text-dimmed">
496
508
  {{ t('testing.fixerAttempts') }}
497
509
  </h3>
498
510
  <ol class="space-y-2">
@@ -500,7 +512,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
500
512
  v-for="a in fixerAttempts"
501
513
  :key="a.attempt"
502
514
  data-testid="tester-fixer-attempt"
503
- class="rounded-lg border border-slate-800 bg-slate-900/60 px-3 py-2"
515
+ class="rounded-lg border border-default bg-default/60 px-3 py-2"
504
516
  >
505
517
  <AttemptEntryHeader
506
518
  :label="t('testing.fixerTimeline.attempt', { n: a.attempt })"
@@ -512,22 +524,24 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
512
524
  "
513
525
  :at="a.at"
514
526
  :icon="a.outcome === 'completed' ? 'i-lucide-wrench' : 'i-lucide-circle-x'"
515
- :icon-class="a.outcome === 'completed' ? 'text-amber-300' : 'text-rose-400'"
527
+ :icon-class="
528
+ a.outcome === 'completed' ? 'text-app-warning-300' : 'text-app-error-400'
529
+ "
516
530
  />
517
531
  <MarkdownProse
518
532
  v-if="a.summary"
519
533
  :text="a.summary"
520
- class="mt-1 max-w-3xl text-[12px] leading-snug text-slate-400"
534
+ class="mt-1 max-w-3xl text-[12px] leading-snug text-muted"
521
535
  />
522
536
  <div v-if="a.concerns && a.concerns.length" class="mt-1.5">
523
- <p class="text-[11px] text-slate-500">
537
+ <p class="text-[11px] text-dimmed">
524
538
  {{ t('testing.fixerTimeline.addressed') }}
525
539
  </p>
526
540
  <ul class="mt-1 space-y-0.5">
527
541
  <li
528
542
  v-for="(c, ci) in a.concerns"
529
543
  :key="`fa${a.attempt}-c${ci}`"
530
- class="flex items-center gap-1.5 text-[12px] text-slate-300"
544
+ class="flex items-center gap-1.5 text-[12px] text-toned"
531
545
  >
532
546
  <span
533
547
  class="rounded px-1 text-[10px] uppercase"
@@ -552,12 +566,12 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
552
566
  class="space-y-2"
553
567
  >
554
568
  <div class="flex items-center gap-2">
555
- <h3 class="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
569
+ <h3 class="text-[11px] font-semibold uppercase tracking-wide text-dimmed">
556
570
  {{ t('testing.quality.heading') }}
557
571
  </h3>
558
572
  <span
559
573
  v-if="quality.attempts"
560
- class="text-[11px] text-slate-400"
574
+ class="text-[11px] text-muted"
561
575
  :title="t('testing.quality.reruns')"
562
576
  >
563
577
  {{
@@ -582,40 +596,43 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
582
596
  v-for="(vd, vi) in qualityVerdicts"
583
597
  :key="`qc${vi}`"
584
598
  data-testid="tester-quality-verdict"
585
- class="rounded-lg border border-slate-800 bg-slate-900/60 px-3 py-2"
599
+ class="rounded-lg border border-default bg-default/60 px-3 py-2"
586
600
  >
587
601
  <div class="flex items-center gap-2">
588
602
  <UIcon
589
603
  :name="vd.adequate ? 'i-lucide-shield-check' : 'i-lucide-shield-alert'"
590
604
  class="h-3.5 w-3.5 shrink-0"
591
- :class="vd.adequate ? 'text-emerald-400' : 'text-amber-300'"
605
+ :class="vd.adequate ? 'text-app-success-400' : 'text-app-warning-300'"
592
606
  />
593
- <span class="text-[13px] font-medium text-slate-200">
607
+ <span class="text-[13px] font-medium text-default">
594
608
  {{
595
609
  vd.adequate ? t('testing.quality.adequate') : t('testing.quality.inadequate')
596
610
  }}
597
611
  </span>
598
- <span v-if="vd.model" class="ms-auto font-mono text-[10px] text-slate-500">{{
612
+ <span v-if="vd.model" class="ms-auto font-mono text-[10px] text-dimmed">{{
599
613
  vd.model
600
614
  }}</span>
601
- <span class="text-[11px] text-slate-500" :class="{ 'ms-auto': !vd.model }">{{
615
+ <span class="text-[11px] text-dimmed" :class="{ 'ms-auto': !vd.model }">{{
602
616
  d(new Date(vd.at), 'short')
603
617
  }}</span>
604
618
  </div>
605
619
  <MarkdownProse
606
620
  v-if="vd.feedback"
607
621
  :text="vd.feedback"
608
- class="mt-1 text-[12px] leading-snug text-slate-400"
622
+ class="mt-1 text-[12px] leading-snug text-muted"
609
623
  />
610
624
  <div v-if="vd.gaps.length" class="mt-1.5">
611
- <p class="text-[11px] text-slate-500">{{ t('testing.quality.gaps') }}</p>
625
+ <p class="text-[11px] text-dimmed">{{ t('testing.quality.gaps') }}</p>
612
626
  <ul class="mt-1 space-y-0.5">
613
627
  <li
614
628
  v-for="(gap, gi) in vd.gaps"
615
629
  :key="`qc${vi}-g${gi}`"
616
- class="flex items-start gap-1.5 text-[12px] text-slate-300"
630
+ class="flex items-start gap-1.5 text-[12px] text-toned"
617
631
  >
618
- <UIcon name="i-lucide-dot" class="mt-0.5 h-3.5 w-3.5 shrink-0 text-amber-400" />
632
+ <UIcon
633
+ name="i-lucide-dot"
634
+ class="mt-0.5 h-3.5 w-3.5 shrink-0 text-app-warning-400"
635
+ />
619
636
  <span>{{ gap }}</span>
620
637
  </li>
621
638
  </ul>
@@ -626,11 +643,11 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
626
643
 
627
644
  <div
628
645
  v-if="!report"
629
- class="flex flex-col items-center justify-center gap-2 py-12 text-center text-slate-400"
646
+ class="flex flex-col items-center justify-center gap-2 py-12 text-center text-muted"
630
647
  >
631
648
  <UIcon name="i-lucide-flask-conical" class="h-8 w-8 opacity-40" />
632
649
  <p class="text-sm">{{ t('testing.empty.title') }}</p>
633
- <p class="max-w-sm text-[11px] text-slate-500">
650
+ <p class="max-w-sm text-[11px] text-dimmed">
634
651
  {{ t('testing.empty.hint') }}
635
652
  </p>
636
653
  </div>
@@ -642,25 +659,25 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
642
659
  <MarkdownProse
643
660
  v-if="report.summary"
644
661
  :text="report.summary"
645
- class="mb-4 max-w-3xl text-[13px] leading-relaxed text-slate-300"
662
+ class="mb-4 max-w-3xl text-[13px] leading-relaxed text-toned"
646
663
  />
647
664
 
648
- <h3 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
665
+ <h3 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-dimmed">
649
666
  {{ t('testing.scenariosOutcomes') }}
650
667
  </h3>
651
668
  <ul class="space-y-2">
652
669
  <li
653
670
  v-for="g in groups"
654
671
  :key="g.key"
655
- class="overflow-hidden rounded-lg border border-slate-800 bg-slate-900/60"
672
+ class="overflow-hidden rounded-lg border border-default bg-default/60"
656
673
  >
657
674
  <button
658
- class="flex w-full items-center gap-2 px-3 py-2 text-start hover:bg-slate-800/40"
675
+ class="flex w-full items-center gap-2 px-3 py-2 text-start hover:bg-elevated/40"
659
676
  @click="toggle(g.key)"
660
677
  >
661
678
  <UIcon
662
679
  :name="collapsed.has(g.key) ? 'i-lucide-chevron-right' : 'i-lucide-chevron-down'"
663
- class="h-3.5 w-3.5 shrink-0 text-slate-500"
680
+ class="h-3.5 w-3.5 shrink-0 text-dimmed"
664
681
  />
665
682
  <UIcon
666
683
  :name="GROUP_STATUS_META[g.status].icon"
@@ -669,14 +686,14 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
669
686
  />
670
687
  <span
671
688
  class="min-w-0 flex-1 truncate text-[13px]"
672
- :class="g.other ? 'text-slate-400' : 'font-medium text-slate-200'"
689
+ :class="g.other ? 'text-muted' : 'font-medium text-default'"
673
690
  >
674
691
  {{ g.title }}
675
692
  </span>
676
693
  <UIcon
677
694
  v-if="g.screenshots.length"
678
695
  name="i-lucide-camera"
679
- class="h-3.5 w-3.5 shrink-0 text-slate-500"
696
+ class="h-3.5 w-3.5 shrink-0 text-dimmed"
680
697
  :title="
681
698
  t(
682
699
  'testing.screenshotCount',
@@ -685,7 +702,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
685
702
  )
686
703
  "
687
704
  />
688
- <span class="shrink-0 text-[11px] text-slate-500">
705
+ <span class="shrink-0 text-[11px] text-dimmed">
689
706
  {{ t('testing.checkCount', { count: g.outcomes.length }, g.outcomes.length) }}
690
707
  <template v-if="g.concerns.length">
691
708
  ·
@@ -707,15 +724,15 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
707
724
  :class="STATUS_META[o.status].text"
708
725
  />
709
726
  <div class="min-w-0">
710
- <span class="text-[13px] text-slate-200">{{ o.name }}</span>
727
+ <span class="text-[13px] text-default">{{ o.name }}</span>
711
728
  <MarkdownProse
712
729
  v-if="o.detail"
713
730
  :text="o.detail"
714
- class="max-w-3xl text-[12px] leading-snug text-slate-400"
731
+ class="max-w-3xl text-[12px] leading-snug text-muted"
715
732
  />
716
733
  </div>
717
734
  </div>
718
- <p v-if="!g.outcomes.length" class="py-0.5 text-[12px] italic text-slate-500">
735
+ <p v-if="!g.outcomes.length" class="py-0.5 text-[12px] italic text-dimmed">
719
736
  {{ t('testing.noDiscreteCheck') }}
720
737
  </p>
721
738
 
@@ -723,7 +740,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
723
740
  <div
724
741
  v-for="(c, ci) in g.concerns"
725
742
  :key="`c${ci}`"
726
- class="mt-1 flex items-start gap-2 rounded-md border border-slate-800 bg-slate-950/40 px-2 py-1.5"
743
+ class="mt-1 flex items-start gap-2 rounded-md border border-default bg-app-950/40 px-2 py-1.5"
727
744
  >
728
745
  <UIcon
729
746
  name="i-lucide-alert-triangle"
@@ -732,7 +749,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
732
749
  />
733
750
  <div class="min-w-0">
734
751
  <div class="flex items-center gap-1.5">
735
- <span class="text-[12px] font-medium text-slate-200">{{ c.title }}</span>
752
+ <span class="text-[12px] font-medium text-default">{{ c.title }}</span>
736
753
  <span
737
754
  class="rounded px-1 text-[10px] uppercase"
738
755
  :class="SEVERITY_META[c.severity].chip"
@@ -743,7 +760,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
743
760
  <MarkdownProse
744
761
  v-if="c.detail"
745
762
  :text="c.detail"
746
- class="max-w-3xl text-[12px] leading-snug text-slate-400"
763
+ class="max-w-3xl text-[12px] leading-snug text-muted"
747
764
  />
748
765
  </div>
749
766
  </div>
@@ -754,7 +771,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
754
771
  v-for="(s, si) in g.screenshots"
755
772
  :key="`shot${si}`"
756
773
  data-testid="tester-screenshot"
757
- class="group relative h-20 w-28 shrink-0 overflow-hidden rounded border border-slate-800 bg-slate-950/60 hover:border-slate-600"
774
+ class="group relative h-20 w-28 shrink-0 overflow-hidden rounded border border-default bg-app-950/60 hover:border-app-600"
758
775
  :title="s.view"
759
776
  @click="openShot(s.artifactId)"
760
777
  >
@@ -766,7 +783,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
766
783
  />
767
784
  <span
768
785
  v-else
769
- class="flex h-full w-full items-center justify-center text-[10px] text-slate-600"
786
+ class="flex h-full w-full items-center justify-center text-[10px] text-app-600"
770
787
  >
771
788
  {{
772
789
  blobs.statusFor(s.artifactId) === 'error'
@@ -775,7 +792,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
775
792
  }}
776
793
  </span>
777
794
  <span
778
- class="absolute inset-x-0 bottom-0 truncate bg-slate-950/80 px-1 py-0.5 text-[9px] text-slate-300"
795
+ class="absolute inset-x-0 bottom-0 truncate bg-app-950/80 px-1 py-0.5 text-[9px] text-toned"
779
796
  >{{ s.view }}</span
780
797
  >
781
798
  </button>
@@ -786,14 +803,14 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
786
803
 
787
804
  <!-- Standalone gallery: any captures not mapped to a scenario above -->
788
805
  <section v-if="ungroupedScreenshots.length" class="mt-5">
789
- <h3 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
806
+ <h3 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-dimmed">
790
807
  {{ t('testing.screenshots') }}
791
808
  </h3>
792
809
  <div class="grid grid-cols-2 gap-3 sm:grid-cols-3">
793
810
  <button
794
811
  v-for="(s, si) in ungroupedScreenshots"
795
812
  :key="`gal${si}`"
796
- class="group relative aspect-video overflow-hidden rounded-lg border border-slate-800 bg-slate-950/60 hover:border-slate-600"
813
+ class="group relative aspect-video overflow-hidden rounded-lg border border-default bg-app-950/60 hover:border-app-600"
797
814
  :title="s.view"
798
815
  @click="openShot(s.artifactId)"
799
816
  >
@@ -805,7 +822,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
805
822
  />
806
823
  <span
807
824
  v-else
808
- class="flex h-full w-full items-center justify-center text-[11px] text-slate-600"
825
+ class="flex h-full w-full items-center justify-center text-[11px] text-app-600"
809
826
  >
810
827
  {{
811
828
  blobs.statusFor(s.artifactId) === 'error'
@@ -814,7 +831,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
814
831
  }}
815
832
  </span>
816
833
  <span
817
- class="absolute inset-x-0 bottom-0 truncate bg-slate-950/80 px-1.5 py-0.5 text-[10px] text-slate-300"
834
+ class="absolute inset-x-0 bottom-0 truncate bg-app-950/80 px-1.5 py-0.5 text-[10px] text-toned"
818
835
  >{{ s.view }}</span
819
836
  >
820
837
  </button>
@@ -825,44 +842,44 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
825
842
 
826
843
  <!-- Sidebar: metadata -->
827
844
  <aside
828
- class="hidden w-60 shrink-0 flex-col gap-4 border-s border-slate-800 bg-slate-900/50 px-4 py-4 lg:flex"
845
+ class="hidden w-60 shrink-0 flex-col gap-4 border-s border-default bg-default/50 px-4 py-4 lg:flex"
829
846
  >
830
847
  <div v-if="report">
831
- <h4 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
848
+ <h4 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-dimmed">
832
849
  {{ t('testing.verdict.heading') }}
833
850
  </h4>
834
851
  <div class="flex items-center gap-2 text-[13px]">
835
852
  <UIcon
836
853
  :name="report.greenlight ? 'i-lucide-circle-check' : 'i-lucide-circle-x'"
837
854
  class="h-4 w-4"
838
- :class="report.greenlight ? 'text-emerald-400' : 'text-rose-400'"
855
+ :class="report.greenlight ? 'text-app-success-400' : 'text-app-error-400'"
839
856
  />
840
- <span :class="report.greenlight ? 'text-emerald-300' : 'text-rose-300'">
857
+ <span :class="report.greenlight ? 'text-app-success-300' : 'text-app-error-300'">
841
858
  {{ report.greenlight ? t('testing.verdict.safe') : t('testing.verdict.withheld') }}
842
859
  </span>
843
860
  </div>
844
861
  </div>
845
862
 
846
863
  <div v-if="report">
847
- <h4 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
864
+ <h4 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-dimmed">
848
865
  {{ t('testing.outcomes.heading') }}
849
866
  </h4>
850
867
  <dl class="space-y-1 text-[12px]">
851
868
  <div class="flex items-center justify-between">
852
- <dt class="text-slate-400">{{ t('testing.outcomes.passed') }}</dt>
853
- <dd class="text-emerald-300">{{ counts.passed }}</dd>
869
+ <dt class="text-muted">{{ t('testing.outcomes.passed') }}</dt>
870
+ <dd class="text-app-success-300">{{ counts.passed }}</dd>
854
871
  </div>
855
872
  <div class="flex items-center justify-between">
856
- <dt class="text-slate-400">{{ t('testing.outcomes.failed') }}</dt>
857
- <dd class="text-rose-300">{{ counts.failed }}</dd>
873
+ <dt class="text-muted">{{ t('testing.outcomes.failed') }}</dt>
874
+ <dd class="text-app-error-300">{{ counts.failed }}</dd>
858
875
  </div>
859
876
  <div class="flex items-center justify-between">
860
- <dt class="text-slate-400">{{ t('testing.outcomes.skipped') }}</dt>
861
- <dd class="text-slate-300">{{ counts.skipped }}</dd>
877
+ <dt class="text-muted">{{ t('testing.outcomes.skipped') }}</dt>
878
+ <dd class="text-toned">{{ counts.skipped }}</dd>
862
879
  </div>
863
- <div class="flex items-center justify-between border-t border-slate-800 pt-1">
864
- <dt class="text-slate-400">{{ t('testing.outcomes.concerns') }}</dt>
865
- <dd class="text-amber-300">
880
+ <div class="flex items-center justify-between border-t border-default pt-1">
881
+ <dt class="text-muted">{{ t('testing.outcomes.concerns') }}</dt>
882
+ <dd class="text-app-warning-300">
866
883
  {{ counts.concerns
867
884
  }}<template v-if="counts.blocking">
868
885
  {{
@@ -875,10 +892,10 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
875
892
  </div>
876
893
 
877
894
  <div v-if="report?.environment">
878
- <h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
895
+ <h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-dimmed">
879
896
  {{ t('testing.environment') }}
880
897
  </h4>
881
- <p class="text-[12px] capitalize text-slate-300">{{ report.environment }}</p>
898
+ <p class="text-[12px] capitalize text-toned">{{ report.environment }}</p>
882
899
  </div>
883
900
 
884
901
  <!-- Shared run metadata + embedded observability (model, run id, timing,
@@ -893,7 +910,7 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
893
910
  :failure-at="instance?.failure?.occurredAt"
894
911
  />
895
912
 
896
- <p class="mt-auto text-[10px] leading-relaxed text-slate-600">
913
+ <p class="mt-auto text-[10px] leading-relaxed text-app-600">
897
914
  {{ t('testing.footer') }}
898
915
  </p>
899
916
  </aside>
@@ -0,0 +1,115 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref, watch } from 'vue'
3
+ import { useThemeStore } from '~/stores/theme'
4
+ import { decodeThemeLink, type ThemeLinkDecodeResult } from '~/utils/theme/link'
5
+
6
+ // Import a theme built in the Nuxt UI theme editor (https://ui.nuxt.com/theme): the user pastes
7
+ // the editor's share link (or the raw document JSON), names it, and the theme is stored in this
8
+ // browser and applied. The document format is the editor's own (`utils/theme/doc.ts`), so there
9
+ // is no translation step to get wrong: what the editor previewed is what the app renders.
10
+ const open = defineModel<boolean>('open', { default: false })
11
+
12
+ const { t } = useI18n()
13
+ const theme = useThemeStore()
14
+
15
+ const link = ref('')
16
+ const name = ref('')
17
+ const error = ref<Extract<ThemeLinkDecodeResult, { ok: false }>['reason'] | null>(null)
18
+ const busy = ref(false)
19
+
20
+ // The name the theme will actually be saved under. Shown as help text only when it differs from
21
+ // what was typed, so a taken name is explained before the import rather than discovered after.
22
+ const resolvedName = computed(() => {
23
+ const requested = name.value.trim()
24
+ return requested ? theme.uniqueName(requested) : ''
25
+ })
26
+ const nameTaken = computed(
27
+ () => resolvedName.value !== '' && resolvedName.value !== name.value.trim(),
28
+ )
29
+
30
+ // Static literal keys per decode outcome, so the typed-message-keys check sees them.
31
+ const ERROR_KEYS: Record<NonNullable<typeof error.value>, string> = {
32
+ not_a_link: 'appearance.import.error.notALink',
33
+ malformed: 'appearance.import.error.malformed',
34
+ preset_shorthand: 'appearance.import.error.presetShorthand',
35
+ unsupported_version: 'appearance.import.error.unsupportedVersion',
36
+ }
37
+
38
+ watch(open, (isOpen) => {
39
+ if (!isOpen) return
40
+ link.value = ''
41
+ name.value = ''
42
+ error.value = null
43
+ })
44
+
45
+ async function submit() {
46
+ busy.value = true
47
+ try {
48
+ const result = await decodeThemeLink(link.value)
49
+ if (!result.ok) {
50
+ error.value = result.reason
51
+ return
52
+ }
53
+ error.value = null
54
+ theme.addCustom(name.value, result.doc)
55
+ open.value = false
56
+ } finally {
57
+ busy.value = false
58
+ }
59
+ }
60
+ </script>
61
+
62
+ <template>
63
+ <UModal
64
+ v-model:open="open"
65
+ :title="t('appearance.import.title')"
66
+ :description="t('appearance.import.description')"
67
+ >
68
+ <template #body>
69
+ <form class="space-y-4" data-testid="theme-import-form" @submit.prevent="submit">
70
+ <UFormField
71
+ :label="t('appearance.import.linkLabel')"
72
+ :error="error ? t(ERROR_KEYS[error]) : undefined"
73
+ >
74
+ <UTextarea
75
+ v-model="link"
76
+ :rows="3"
77
+ autoresize
78
+ class="w-full font-mono text-xs"
79
+ :placeholder="t('appearance.import.linkPlaceholder')"
80
+ data-testid="theme-import-link"
81
+ />
82
+ </UFormField>
83
+ <UFormField
84
+ :label="t('appearance.import.nameLabel')"
85
+ :help="
86
+ nameTaken
87
+ ? t('appearance.import.nameTaken', { typed: name.trim(), name: resolvedName })
88
+ : undefined
89
+ "
90
+ >
91
+ <UInput
92
+ v-model="name"
93
+ class="w-full"
94
+ :placeholder="t('appearance.import.namePlaceholder')"
95
+ data-testid="theme-import-name"
96
+ />
97
+ </UFormField>
98
+ <p class="text-xs text-muted">{{ t('appearance.import.fontNote') }}</p>
99
+ <div class="flex justify-end gap-2">
100
+ <UButton color="neutral" variant="ghost" @click="open = false">
101
+ {{ t('appearance.import.cancel') }}
102
+ </UButton>
103
+ <UButton
104
+ type="submit"
105
+ :loading="busy"
106
+ :disabled="!link.trim()"
107
+ data-testid="theme-import-submit"
108
+ >
109
+ {{ t('appearance.import.submit') }}
110
+ </UButton>
111
+ </div>
112
+ </form>
113
+ </template>
114
+ </UModal>
115
+ </template>