@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
@@ -36,41 +36,44 @@ async function resume() {
36
36
  re-enables pointer events on it. -->
37
37
  <div v-if="exceeded && spend" class="pointer-events-auto w-full max-w-3xl">
38
38
  <div
39
- class="w-full max-w-3xl rounded-2xl border-2 border-red-500/70 bg-red-950/95 p-5 shadow-2xl backdrop-blur"
39
+ class="w-full max-w-3xl rounded-2xl border-2 border-app-error-500/70 bg-app-error-950/95 p-5 shadow-2xl backdrop-blur"
40
40
  role="alert"
41
41
  >
42
42
  <div class="flex items-start gap-4">
43
- <UIcon name="i-lucide-octagon-alert" class="mt-0.5 h-10 w-10 shrink-0 text-red-400" />
43
+ <UIcon
44
+ name="i-lucide-octagon-alert"
45
+ class="mt-0.5 h-10 w-10 shrink-0 text-app-error-400"
46
+ />
44
47
  <div class="min-w-0 flex-1">
45
- <h2 class="text-lg font-semibold text-red-100">
48
+ <h2 class="text-lg font-semibold text-app-error-100">
46
49
  {{ t('layout.spendWarningBanner.title') }}
47
50
  </h2>
48
- <p class="mt-1 text-sm text-red-200/90">
51
+ <p class="mt-1 text-sm text-app-error-200/90">
49
52
  {{ t('layout.spendWarningBanner.body') }}
50
53
  </p>
51
54
 
52
55
  <dl class="mt-4 grid grid-cols-2 gap-3 sm:grid-cols-3">
53
- <div class="rounded-lg bg-red-900/50 px-3 py-2">
54
- <dt class="text-[11px] uppercase tracking-wide text-red-300/80">
56
+ <div class="rounded-lg bg-app-error-900/50 px-3 py-2">
57
+ <dt class="text-[11px] uppercase tracking-wide text-app-error-300/80">
55
58
  {{ t('layout.spendWarningBanner.spent') }}
56
59
  </dt>
57
- <dd class="text-base font-semibold tabular-nums text-red-50">
60
+ <dd class="text-base font-semibold tabular-nums text-app-error-50">
58
61
  {{ money(spend.costSpent, spend.currency) }}
59
62
  </dd>
60
63
  </div>
61
- <div class="rounded-lg bg-red-900/50 px-3 py-2">
62
- <dt class="text-[11px] uppercase tracking-wide text-red-300/80">
64
+ <div class="rounded-lg bg-app-error-900/50 px-3 py-2">
65
+ <dt class="text-[11px] uppercase tracking-wide text-app-error-300/80">
63
66
  {{ t('layout.spendWarningBanner.budget') }}
64
67
  </dt>
65
- <dd class="text-base font-semibold tabular-nums text-red-50">
68
+ <dd class="text-base font-semibold tabular-nums text-app-error-50">
66
69
  {{ money(spend.costLimit, spend.currency) }}
67
70
  </dd>
68
71
  </div>
69
- <div class="rounded-lg bg-red-900/50 px-3 py-2">
70
- <dt class="text-[11px] uppercase tracking-wide text-red-300/80">
72
+ <div class="rounded-lg bg-app-error-900/50 px-3 py-2">
73
+ <dt class="text-[11px] uppercase tracking-wide text-app-error-300/80">
71
74
  {{ t('layout.spendWarningBanner.tokens') }}
72
75
  </dt>
73
- <dd class="text-base font-semibold tabular-nums text-red-50">{{ tokens }}</dd>
76
+ <dd class="text-base font-semibold tabular-nums text-app-error-50">{{ tokens }}</dd>
74
77
  </div>
75
78
  </dl>
76
79
 
@@ -86,7 +89,7 @@ async function resume() {
86
89
  >
87
90
  {{ t('layout.spendWarningBanner.resume') }}
88
91
  </UButton>
89
- <span class="text-xs text-red-300/70">
92
+ <span class="text-xs text-app-error-300/70">
90
93
  {{ t('layout.spendWarningBanner.resumeHint') }}
91
94
  </span>
92
95
  </div>
@@ -31,19 +31,19 @@ function dismiss() {
31
31
  v-if="show"
32
32
  data-testid="translation-warning"
33
33
  role="alert"
34
- class="flex shrink-0 items-center gap-3 border-b border-amber-500/40 bg-amber-950/95 px-4 py-2 text-[13px] text-amber-100 shadow-lg backdrop-blur"
34
+ class="flex shrink-0 items-center gap-3 border-b border-app-warning-500/40 bg-app-warning-950/95 px-4 py-2 text-[13px] text-app-warning-100 shadow-lg backdrop-blur"
35
35
  >
36
- <UIcon name="i-lucide-languages" class="h-4 w-4 shrink-0 text-amber-400" />
36
+ <UIcon name="i-lucide-languages" class="h-4 w-4 shrink-0 text-app-warning-400" />
37
37
  <p class="min-w-0 flex-1">
38
38
  <span class="font-semibold">{{ t('language.warning.title') }}</span>
39
- <span class="mx-1.5 text-amber-400/60">·</span>
39
+ <span class="mx-1.5 text-app-warning-400/60">·</span>
40
40
  <i18n-t keypath="language.warning.body" tag="span" scope="global">
41
41
  <template #repoLink>
42
42
  <a
43
43
  :href="REPO_URL"
44
44
  target="_blank"
45
45
  rel="noopener noreferrer"
46
- class="inline-flex items-center gap-1 font-medium text-sky-300 hover:underline"
46
+ class="inline-flex items-center gap-1 font-medium text-app-info-300 hover:underline"
47
47
  >
48
48
  {{ t('language.warning.repoLinkLabel') }}
49
49
  <UIcon name="i-lucide-external-link" class="h-3 w-3" />
@@ -50,17 +50,17 @@ const pinnedTitle = computed(
50
50
  v-if="uiMode.envPinned"
51
51
  data-testid="ui-mode-pinned"
52
52
  :title="pinnedTitle"
53
- class="flex w-full items-center gap-2 rounded-lg border border-slate-800 bg-slate-900/40 p-2 text-start"
53
+ class="flex w-full items-center gap-2 rounded-lg border border-default bg-default/40 p-2 text-start"
54
54
  :class="collapsed ? 'justify-center' : ''"
55
55
  >
56
- <UIcon :name="icon" class="h-4 w-4 shrink-0 text-slate-500" />
56
+ <UIcon :name="icon" class="h-4 w-4 shrink-0 text-dimmed" />
57
57
  <div v-if="!collapsed" class="min-w-0 flex-1">
58
- <div class="truncate text-[10px] uppercase tracking-wide text-slate-500">
58
+ <div class="truncate text-[10px] uppercase tracking-wide text-dimmed">
59
59
  {{ t('uiMode.switcher') }}
60
60
  </div>
61
- <div class="truncate text-xs font-medium text-slate-300">{{ currentLabel }}</div>
61
+ <div class="truncate text-xs font-medium text-toned">{{ currentLabel }}</div>
62
62
  </div>
63
- <UIcon v-if="!collapsed" name="i-lucide-lock" class="h-3.5 w-3.5 shrink-0 text-slate-600" />
63
+ <UIcon v-if="!collapsed" name="i-lucide-lock" class="h-3.5 w-3.5 shrink-0 text-app-600" />
64
64
  </div>
65
65
 
66
66
  <!-- Rail: one button, flips the tier. The label rides under the glyph so the rail still says
@@ -72,23 +72,23 @@ const pinnedTitle = computed(
72
72
  data-testid="ui-mode-toggle"
73
73
  :aria-label="t('uiMode.switchTo', { mode: t(MODE_LABELS[otherMode]) })"
74
74
  :title="t('uiMode.switchTo', { mode: t(MODE_LABELS[otherMode]) })"
75
- class="flex w-full flex-col items-center gap-0.5 rounded-lg border border-slate-700 bg-slate-900/60 px-1 py-1.5 transition hover:border-indigo-500/60 hover:bg-slate-800/60"
75
+ class="flex w-full flex-col items-center gap-0.5 rounded-lg border border-muted bg-default/60 px-1 py-1.5 transition hover:border-primary/60 hover:bg-elevated/60"
76
76
  @click="uiMode.toggleMode()"
77
77
  >
78
- <UIcon :name="icon" class="h-4 w-4 shrink-0 text-indigo-400" />
79
- <span class="w-full truncate text-center text-[9px] font-medium uppercase text-slate-300">
78
+ <UIcon :name="icon" class="h-4 w-4 shrink-0 text-primary" />
79
+ <span class="w-full truncate text-center text-[9px] font-medium uppercase text-toned">
80
80
  {{ currentLabel }}
81
81
  </span>
82
82
  </button>
83
83
 
84
84
  <div v-else data-testid="ui-mode-switcher" class="w-full">
85
- <div class="mb-1 px-1 text-[10px] uppercase tracking-wide text-slate-500">
85
+ <div class="mb-1 px-1 text-[10px] uppercase tracking-wide text-dimmed">
86
86
  {{ t('uiMode.switcher') }}
87
87
  </div>
88
88
  <div
89
89
  role="group"
90
90
  :aria-label="t('uiMode.switcher')"
91
- class="flex w-full gap-1 rounded-lg border border-slate-700 bg-slate-900/60 p-1"
91
+ class="flex w-full gap-1 rounded-lg border border-muted bg-default/60 p-1"
92
92
  >
93
93
  <button
94
94
  v-for="mode in UI_MODES"
@@ -99,8 +99,8 @@ const pinnedTitle = computed(
99
99
  class="flex-1 truncate rounded-md px-2 py-1 text-xs font-medium transition"
100
100
  :class="
101
101
  mode === uiMode.mode
102
- ? 'bg-indigo-500/20 text-indigo-200 ring-1 ring-indigo-500/50'
103
- : 'text-slate-400 hover:bg-slate-800/70 hover:text-slate-200'
102
+ ? 'bg-primary/20 text-primary ring-1 ring-primary/50'
103
+ : 'text-muted hover:bg-elevated/70 hover:text-default'
104
104
  "
105
105
  @click="uiMode.setMode(mode)"
106
106
  >
@@ -111,7 +111,7 @@ const pinnedTitle = computed(
111
111
 
112
112
  <!-- The one-line "what this tier gives you", so the choice is self-explanatory. Dropped in
113
113
  the collapsed rail, where the tooltip above carries the mode instead. -->
114
- <p v-if="!collapsed" class="px-1 text-[10px] leading-snug text-slate-500">
114
+ <p v-if="!collapsed" class="px-1 text-[10px] leading-snug text-dimmed">
115
115
  {{ t(MODE_HINTS[uiMode.mode]) }}
116
116
  </p>
117
117
  </template>
@@ -44,10 +44,10 @@ const items = computed(() =>
44
44
  data-testid="ui-role-toggle"
45
45
  :aria-label="`${t('uiRole.switcher')}: ${currentLabel}`"
46
46
  :title="`${t('uiRole.switcher')}: ${currentLabel}`"
47
- class="flex w-full flex-col items-center gap-0.5 rounded-lg border border-slate-700 bg-slate-900/60 px-1 py-1.5 transition hover:border-indigo-500/60 hover:bg-slate-800/60"
47
+ class="flex w-full flex-col items-center gap-0.5 rounded-lg border border-muted bg-default/60 px-1 py-1.5 transition hover:border-primary/60 hover:bg-elevated/60"
48
48
  >
49
- <UIcon :name="current.icon" class="h-4 w-4 shrink-0 text-indigo-400" />
50
- <span class="w-full truncate text-center text-[9px] font-medium uppercase text-slate-300">
49
+ <UIcon :name="current.icon" class="h-4 w-4 shrink-0 text-primary" />
50
+ <span class="w-full truncate text-center text-[9px] font-medium uppercase text-toned">
51
51
  {{ currentLabel }}
52
52
  </span>
53
53
  </button>
@@ -58,16 +58,16 @@ const items = computed(() =>
58
58
  data-testid="ui-role-switcher"
59
59
  :aria-label="t('uiRole.switcher')"
60
60
  :title="t(current.hintKey)"
61
- class="flex w-full items-center gap-2 rounded-lg border border-slate-700 bg-slate-900/60 p-2 text-start transition hover:border-indigo-500/60 hover:bg-slate-800/60"
61
+ class="flex w-full items-center gap-2 rounded-lg border border-muted bg-default/60 p-2 text-start transition hover:border-primary/60 hover:bg-elevated/60"
62
62
  >
63
- <UIcon :name="current.icon" class="h-4 w-4 shrink-0 text-indigo-400" />
63
+ <UIcon :name="current.icon" class="h-4 w-4 shrink-0 text-primary" />
64
64
  <div class="min-w-0 flex-1">
65
- <div class="truncate text-[10px] uppercase tracking-wide text-slate-500">
65
+ <div class="truncate text-[10px] uppercase tracking-wide text-dimmed">
66
66
  {{ t('uiRole.switcher') }}
67
67
  </div>
68
- <div class="truncate text-xs font-medium text-slate-200">{{ currentLabel }}</div>
68
+ <div class="truncate text-xs font-medium text-default">{{ currentLabel }}</div>
69
69
  </div>
70
- <UIcon name="i-lucide-chevron-down" class="h-3.5 w-3.5 shrink-0 text-slate-500" />
70
+ <UIcon name="i-lucide-chevron-down" class="h-3.5 w-3.5 shrink-0 text-dimmed" />
71
71
  </button>
72
72
  </UDropdownMenu>
73
73
  </template>
@@ -139,13 +139,13 @@ function memberLabel(userId: string, name?: string | null, email?: string | null
139
139
  <template>
140
140
  <div class="space-y-6 text-sm" data-testid="workspace-members-settings">
141
141
  <!-- access mode -->
142
- <section class="rounded-md border border-slate-800 bg-slate-800/40 p-4">
142
+ <section class="rounded-md border border-default bg-elevated/40 p-4">
143
143
  <div class="flex items-start justify-between gap-4">
144
144
  <div>
145
- <h3 class="mb-1 font-semibold text-white">
145
+ <h3 class="mb-1 font-semibold text-highlighted">
146
146
  {{ t('layout.workspaceMembers.accessMode.title') }}
147
147
  </h3>
148
- <p class="text-slate-400">
148
+ <p class="text-muted">
149
149
  {{
150
150
  restricted
151
151
  ? t('layout.workspaceMembers.accessMode.restrictedHint')
@@ -161,7 +161,7 @@ function memberLabel(userId: string, name?: string | null, email?: string | null
161
161
  data-testid="workspace-restrict-toggle"
162
162
  @update:model-value="setAccessMode"
163
163
  />
164
- <span class="text-xs text-slate-300">
164
+ <span class="text-xs text-toned">
165
165
  {{ t('layout.workspaceMembers.accessMode.restrictToggle') }}
166
166
  </span>
167
167
  </label>
@@ -170,13 +170,15 @@ function memberLabel(userId: string, name?: string | null, email?: string | null
170
170
 
171
171
  <!-- roster -->
172
172
  <section>
173
- <h3 class="mb-2 font-semibold text-white">{{ t('layout.workspaceMembers.roster.title') }}</h3>
174
- <p v-if="!rosterReady" class="text-slate-500">{{ t('common.loading') }}</p>
173
+ <h3 class="mb-2 font-semibold text-highlighted">
174
+ {{ t('layout.workspaceMembers.roster.title') }}
175
+ </h3>
176
+ <p v-if="!rosterReady" class="text-dimmed">{{ t('common.loading') }}</p>
175
177
  <ul v-else class="space-y-1" data-testid="workspace-members-roster">
176
178
  <li
177
179
  v-for="m in members.members"
178
180
  :key="m.userId"
179
- class="flex items-center justify-between gap-2 rounded-md bg-slate-800/40 px-2 py-1"
181
+ class="flex items-center justify-between gap-2 rounded-md bg-elevated/40 px-2 py-1"
180
182
  data-testid="workspace-member-row"
181
183
  :data-user-id="m.userId"
182
184
  >
@@ -204,7 +206,7 @@ function memberLabel(userId: string, name?: string | null, email?: string | null
204
206
  />
205
207
  </span>
206
208
  </li>
207
- <li v-if="members.members.length === 0" class="text-slate-500">
209
+ <li v-if="members.members.length === 0" class="text-dimmed">
208
210
  {{ t('layout.workspaceMembers.roster.empty') }}
209
211
  </li>
210
212
  </ul>
@@ -212,8 +214,10 @@ function memberLabel(userId: string, name?: string | null, email?: string | null
212
214
 
213
215
  <!-- add member -->
214
216
  <section>
215
- <h3 class="mb-2 font-semibold text-white">{{ t('layout.workspaceMembers.add.title') }}</h3>
216
- <p v-if="!accountId" class="text-slate-500">
217
+ <h3 class="mb-2 font-semibold text-highlighted">
218
+ {{ t('layout.workspaceMembers.add.title') }}
219
+ </h3>
220
+ <p v-if="!accountId" class="text-dimmed">
217
221
  {{ t('layout.workspaceMembers.add.noAccount') }}
218
222
  </p>
219
223
  <template v-else>
@@ -239,7 +243,7 @@ function memberLabel(userId: string, name?: string | null, email?: string | null
239
243
  {{ t('layout.workspaceMembers.add.submit') }}
240
244
  </UButton>
241
245
  </form>
242
- <p v-if="candidates.length === 0" class="mt-2 text-slate-500">
246
+ <p v-if="candidates.length === 0" class="mt-2 text-dimmed">
243
247
  {{ t('layout.workspaceMembers.add.allAdded') }}
244
248
  </p>
245
249
  </template>
@@ -160,7 +160,7 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey, true))
160
160
  v-if="open"
161
161
  ref="dialogRef"
162
162
  tabindex="-1"
163
- class="fixed inset-0 z-[60] flex flex-col bg-slate-950/95 backdrop-blur-sm focus:outline-none"
163
+ class="fixed inset-0 z-[60] flex flex-col bg-app-950/95 backdrop-blur-sm focus:outline-none"
164
164
  role="dialog"
165
165
  aria-modal="true"
166
166
  data-testid="artifact-lightbox"
@@ -172,27 +172,27 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey, true))
172
172
  @click.self="close"
173
173
  >
174
174
  <!-- Toolbar -->
175
- <div class="flex items-center gap-3 border-b border-slate-800/60 px-4 py-2.5">
176
- <span class="min-w-0 flex-1 truncate text-[13px] font-medium text-slate-200">
175
+ <div class="flex items-center gap-3 border-b border-default/60 px-4 py-2.5">
176
+ <span class="min-w-0 flex-1 truncate text-[13px] font-medium text-default">
177
177
  {{ current?.label ?? t('media.lightbox.fallbackTitle') }}
178
178
  </span>
179
- <span v-if="total > 1" class="shrink-0 text-[12px] tabular-nums text-slate-400">
179
+ <span v-if="total > 1" class="shrink-0 text-[12px] tabular-nums text-muted">
180
180
  {{ t('media.lightbox.counter', { current: index + 1, total }) }}
181
181
  </span>
182
182
  <div class="flex shrink-0 items-center gap-1">
183
183
  <button
184
- class="rounded-md p-1.5 text-slate-400 hover:bg-slate-800 hover:text-slate-200 disabled:opacity-40"
184
+ class="rounded-md p-1.5 text-muted hover:bg-elevated hover:text-default disabled:opacity-40"
185
185
  :title="t('media.lightbox.zoomOut')"
186
186
  :disabled="scale <= MIN_SCALE"
187
187
  @click="zoomBy(1 / 1.25)"
188
188
  >
189
189
  <UIcon name="i-lucide-zoom-out" class="h-4 w-4" />
190
190
  </button>
191
- <span class="w-10 text-center text-[11px] tabular-nums text-slate-500">{{
191
+ <span class="w-10 text-center text-[11px] tabular-nums text-dimmed">{{
192
192
  n(scale, 'percent')
193
193
  }}</span>
194
194
  <button
195
- class="rounded-md p-1.5 text-slate-400 hover:bg-slate-800 hover:text-slate-200 disabled:opacity-40"
195
+ class="rounded-md p-1.5 text-muted hover:bg-elevated hover:text-default disabled:opacity-40"
196
196
  :title="t('media.lightbox.zoomIn')"
197
197
  :disabled="scale >= MAX_SCALE"
198
198
  @click="zoomBy(1.25)"
@@ -200,14 +200,14 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey, true))
200
200
  <UIcon name="i-lucide-zoom-in" class="h-4 w-4" />
201
201
  </button>
202
202
  <button
203
- class="rounded-md p-1.5 text-slate-400 hover:bg-slate-800 hover:text-slate-200"
203
+ class="rounded-md p-1.5 text-muted hover:bg-elevated hover:text-default"
204
204
  :title="t('media.lightbox.reset')"
205
205
  @click="resetView"
206
206
  >
207
207
  <UIcon name="i-lucide-maximize" class="h-4 w-4" />
208
208
  </button>
209
209
  <button
210
- class="ms-1 rounded-md p-1.5 text-slate-400 hover:bg-slate-800 hover:text-slate-200"
210
+ class="ms-1 rounded-md p-1.5 text-muted hover:bg-elevated hover:text-default"
211
211
  :title="t('media.lightbox.close')"
212
212
  @click="close"
213
213
  >
@@ -224,7 +224,7 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey, true))
224
224
  >
225
225
  <button
226
226
  v-if="total > 1"
227
- class="absolute start-3 top-1/2 z-10 -translate-y-1/2 rounded-full bg-slate-900/80 p-2 text-slate-300 hover:bg-slate-800 hover:text-white"
227
+ class="absolute start-3 top-1/2 z-10 -translate-y-1/2 rounded-full bg-default/80 p-2 text-toned hover:bg-elevated hover:text-highlighted"
228
228
  :title="t('media.lightbox.prev')"
229
229
  @click="go(-1)"
230
230
  >
@@ -248,7 +248,7 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey, true))
248
248
  @pointerup="onPointerUp"
249
249
  @pointercancel="onPointerUp"
250
250
  />
251
- <div v-else class="flex flex-col items-center gap-2 text-slate-500">
251
+ <div v-else class="flex flex-col items-center gap-2 text-dimmed">
252
252
  <UIcon
253
253
  :name="state === 'error' ? 'i-lucide-image-off' : 'i-lucide-loader'"
254
254
  class="h-8 w-8"
@@ -259,7 +259,7 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey, true))
259
259
  </p>
260
260
  <button
261
261
  v-if="state === 'error' && current"
262
- class="text-[12px] text-amber-300 hover:underline"
262
+ class="text-[12px] text-app-warning-300 hover:underline"
263
263
  @click="props.blobs.retry(current.artifactId)"
264
264
  >
265
265
  {{ t('common.retry') }}
@@ -268,7 +268,7 @@ onBeforeUnmount(() => window.removeEventListener('keydown', onKey, true))
268
268
 
269
269
  <button
270
270
  v-if="total > 1"
271
- class="absolute end-3 top-1/2 z-10 -translate-y-1/2 rounded-full bg-slate-900/80 p-2 text-slate-300 hover:bg-slate-800 hover:text-white"
271
+ class="absolute end-3 top-1/2 z-10 -translate-y-1/2 rounded-full bg-default/80 p-2 text-toned hover:bg-elevated hover:text-highlighted"
272
272
  :title="t('media.lightbox.next')"
273
273
  @click="go(1)"
274
274
  >
@@ -82,6 +82,13 @@ function onSwipeUp() {
82
82
 
83
83
  // --- diff canvas ---
84
84
  const diffCanvas = ref<HTMLCanvasElement | null>(null)
85
+ // A fixed black backing, opting out of the theme on purpose. The CSS background does not take part
86
+ // in the canvas `difference` composite (that runs on the bitmap); it shows through wherever the
87
+ // bitmap is transparent, before the first draw and in any transparent region of the diff. Black
88
+ // there reads as "no difference", the same zero the difference composite produces, in either colour
89
+ // mode, so no mode-following token fits. Bound as a const so the guard's `fixed-colour-ok:` marker
90
+ // stays on the class string; the inline-template form gets reflowed off its own line by oxfmt.
91
+ const DIFF_CANVAS_CLASS = 'w-full rounded border border-default bg-black' // fixed-colour-ok: diff canvas backdrop
85
92
  const CAP = 2000
86
93
  // Bumped on every renderDiff entry so a render whose async work (image decode) is overtaken
87
94
  // by a newer mode/image change bails out instead of drawing stale pixels onto the canvas.
@@ -148,19 +155,19 @@ function onRefInput(e: Event) {
148
155
  </script>
149
156
 
150
157
  <template>
151
- <div class="rounded-lg border border-slate-800 bg-slate-900/60 p-3">
158
+ <div class="rounded-lg border border-default bg-default/60 p-3">
152
159
  <div class="mb-2 flex items-center justify-between gap-2">
153
- <h3 class="min-w-0 truncate text-[12px] font-semibold text-slate-200">{{ view }}</h3>
160
+ <h3 class="min-w-0 truncate text-[12px] font-semibold text-default">{{ view }}</h3>
154
161
  <!-- Mode switch -->
155
162
  <div
156
163
  v-if="MODES.length > 1"
157
- class="flex items-center gap-0.5 rounded-md border border-slate-800 bg-slate-950/60 p-0.5"
164
+ class="flex items-center gap-0.5 rounded-md border border-default bg-app-950/60 p-0.5"
158
165
  >
159
166
  <button
160
167
  v-for="m in MODES"
161
168
  :key="m.id"
162
- class="rounded px-1.5 py-1 text-slate-400 hover:text-slate-200"
163
- :class="mode === m.id ? 'bg-slate-800 text-slate-100' : ''"
169
+ class="rounded px-1.5 py-1 text-muted hover:text-default"
170
+ :class="mode === m.id ? 'bg-elevated text-app-100' : ''"
164
171
  :title="m.label"
165
172
  @click="mode = m.id"
166
173
  >
@@ -172,12 +179,12 @@ function onRefInput(e: Event) {
172
179
  <!-- SIDE BY SIDE -->
173
180
  <div v-if="mode === 'side-by-side'" class="grid grid-cols-2 gap-3">
174
181
  <figure class="space-y-1">
175
- <figcaption class="text-[10px] uppercase tracking-wide text-slate-500">
182
+ <figcaption class="text-[10px] uppercase tracking-wide text-dimmed">
176
183
  {{ t('media.compare.actual') }}
177
184
  </figcaption>
178
185
  <button
179
186
  v-if="actualUrl"
180
- class="block w-full overflow-hidden rounded border border-slate-800 hover:border-slate-600"
187
+ class="block w-full overflow-hidden rounded border border-default hover:border-app-600"
181
188
  @click="actualId && emit('expand', actualId)"
182
189
  >
183
190
  <img
@@ -188,7 +195,7 @@ function onRefInput(e: Event) {
188
195
  </button>
189
196
  <div
190
197
  v-else
191
- class="flex h-32 items-center justify-center rounded border border-dashed border-slate-700 text-[11px] text-slate-600"
198
+ class="flex h-32 items-center justify-center rounded border border-dashed border-muted text-[11px] text-app-600"
192
199
  >
193
200
  {{
194
201
  props.blobs.statusFor(actualId) === 'error'
@@ -201,15 +208,15 @@ function onRefInput(e: Event) {
201
208
  </figure>
202
209
 
203
210
  <figure class="space-y-1">
204
- <figcaption class="text-[10px] uppercase tracking-wide text-slate-500">
211
+ <figcaption class="text-[10px] uppercase tracking-wide text-dimmed">
205
212
  {{ t('media.compare.reference') }}
206
- <span v-if="referenceOrigin === 'design'" class="text-amber-300/80">
213
+ <span v-if="referenceOrigin === 'design'" class="text-app-warning-300/80">
207
214
  {{ t('media.compare.fromLinkedDesign') }}
208
215
  </span>
209
216
  </figcaption>
210
217
  <button
211
218
  v-if="refUrl"
212
- class="group relative block w-full overflow-hidden rounded border border-slate-800 hover:border-slate-600"
219
+ class="group relative block w-full overflow-hidden rounded border border-default hover:border-app-600"
213
220
  @click="referenceId && emit('expand', referenceId)"
214
221
  >
215
222
  <img
@@ -218,7 +225,7 @@ function onRefInput(e: Event) {
218
225
  class="w-full cursor-zoom-in"
219
226
  />
220
227
  <span
221
- class="absolute bottom-1 end-1 rounded bg-slate-950/80 px-1.5 py-0.5 text-[10px] text-slate-300 opacity-0 group-hover:opacity-100"
228
+ class="absolute bottom-1 end-1 rounded bg-app-950/80 px-1.5 py-0.5 text-[10px] text-toned opacity-0 group-hover:opacity-100"
222
229
  @click.stop="refInput?.click()"
223
230
  >
224
231
  {{ t('media.compare.replace') }}
@@ -230,8 +237,8 @@ function onRefInput(e: Event) {
230
237
  class="flex h-32 cursor-pointer flex-col items-center justify-center gap-1 rounded border border-dashed text-[11px] transition"
231
238
  :class="
232
239
  dragOver
233
- ? 'border-amber-500 bg-amber-500/5 text-amber-300'
234
- : 'border-slate-700 text-slate-600 hover:border-slate-500 hover:text-slate-400'
240
+ ? 'border-app-warning-500 bg-app-warning-500/5 text-app-warning-300'
241
+ : 'border-muted text-app-600 hover:border-app-500 hover:text-muted'
235
242
  "
236
243
  @click="refInput?.click()"
237
244
  @dragover.prevent="dragOver = true"
@@ -246,7 +253,7 @@ function onRefInput(e: Event) {
246
253
 
247
254
  <!-- OVERLAY (onion-skin) -->
248
255
  <div v-else-if="mode === 'overlay'" class="space-y-2">
249
- <div class="relative w-full overflow-hidden rounded border border-slate-800">
256
+ <div class="relative w-full overflow-hidden rounded border border-default">
250
257
  <img :src="refUrl" :alt="t('media.compare.referenceAlt', { view })" class="w-full" />
251
258
  <!-- object-contain so a differing aspect ratio onion-skins undistorted over the reference. -->
252
259
  <img
@@ -256,14 +263,14 @@ function onRefInput(e: Event) {
256
263
  :style="{ opacity: overlayOpacity / 100 }"
257
264
  />
258
265
  </div>
259
- <div class="flex items-center gap-2 text-[10px] uppercase tracking-wide text-slate-500">
266
+ <div class="flex items-center gap-2 text-[10px] uppercase tracking-wide text-dimmed">
260
267
  <span>{{ t('media.compare.reference') }}</span>
261
268
  <input
262
269
  v-model.number="overlayOpacity"
263
270
  type="range"
264
271
  min="0"
265
272
  max="100"
266
- class="flex-1 accent-amber-500"
273
+ class="flex-1 accent-app-warning-500"
267
274
  />
268
275
  <span>{{ t('media.compare.actual') }}</span>
269
276
  </div>
@@ -273,7 +280,7 @@ function onRefInput(e: Event) {
273
280
  <div
274
281
  v-else-if="mode === 'swipe'"
275
282
  ref="swipeBox"
276
- class="relative w-full cursor-ew-resize select-none overflow-hidden rounded border border-slate-800"
283
+ class="relative w-full cursor-ew-resize select-none overflow-hidden rounded border border-default"
277
284
  @pointerdown="onSwipeDown"
278
285
  @pointermove="moveSwipe"
279
286
  @pointerup="onSwipeUp"
@@ -297,27 +304,27 @@ function onRefInput(e: Event) {
297
304
  `clientX - rect.left`), so the handle and the corner labels stay PHYSICAL too.
298
305
  Logical (`start-`/`end-`) here would flip the labels under RTL while the image
299
306
  regions stayed put, mislabeling actual vs reference. -->
300
- <div class="absolute inset-y-0 w-0.5 bg-amber-400" :style="{ left: `${splitPct}%` }">
307
+ <div class="absolute inset-y-0 w-0.5 bg-app-warning-400" :style="{ left: `${splitPct}%` }">
301
308
  <span
302
- class="absolute top-1/2 left-1/2 flex h-6 w-6 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-amber-400 text-slate-950 shadow"
309
+ class="absolute top-1/2 left-1/2 flex h-6 w-6 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-app-warning-400 text-app-950 shadow"
303
310
  >
304
311
  <UIcon name="i-lucide-move-horizontal" class="h-3.5 w-3.5" />
305
312
  </span>
306
313
  </div>
307
314
  <span
308
- class="absolute left-1 top-1 rounded bg-slate-950/70 px-1 text-[9px] uppercase text-slate-300"
315
+ class="absolute left-1 top-1 rounded bg-app-950/70 px-1 text-[9px] uppercase text-toned"
309
316
  >{{ t('media.compare.actual') }}</span
310
317
  >
311
318
  <span
312
- class="absolute right-1 top-1 rounded bg-slate-950/70 px-1 text-[9px] uppercase text-slate-300"
319
+ class="absolute right-1 top-1 rounded bg-app-950/70 px-1 text-[9px] uppercase text-toned"
313
320
  >{{ t('media.compare.reference') }}</span
314
321
  >
315
322
  </div>
316
323
 
317
324
  <!-- DIFF (canvas) -->
318
325
  <div v-else-if="mode === 'diff'" class="space-y-1">
319
- <canvas ref="diffCanvas" class="w-full rounded border border-slate-800 bg-black" />
320
- <p class="text-[10px] text-slate-500">{{ t('media.compare.diffHint') }}</p>
326
+ <canvas ref="diffCanvas" :class="DIFF_CANVAS_CLASS" />
327
+ <p class="text-[10px] text-dimmed">{{ t('media.compare.diffHint') }}</p>
321
328
  </div>
322
329
 
323
330
  <!-- Hidden file input shared by replace/drop zone -->
@@ -88,7 +88,7 @@ function pick(effort: ReviewEffort) {
88
88
  <div data-testid="merge-effort-chips" class="mt-2">
89
89
  <div class="flex items-center gap-1.5">
90
90
  <span
91
- class="text-[10px] uppercase tracking-wide text-slate-500"
91
+ class="text-[10px] uppercase tracking-wide text-dimmed"
92
92
  :title="t('merge.effort.promptHint')"
93
93
  >
94
94
  {{ t('merge.effort.prompt') }}
@@ -119,7 +119,7 @@ function pick(effort: ReviewEffort) {
119
119
  {{ chip.label }}
120
120
  </UButton>
121
121
  </div>
122
- <p v-if="history" data-testid="merge-effort-history" class="mt-1 text-[10px] text-slate-500">
122
+ <p v-if="history" data-testid="merge-effort-history" class="mt-1 text-[10px] text-dimmed">
123
123
  {{ history }}
124
124
  </p>
125
125
  </div>
@@ -147,18 +147,18 @@ function resetToDefaults() {
147
147
  </template>
148
148
  <template #body>
149
149
  <div class="space-y-4">
150
- <p class="text-xs text-slate-400">{{ t('notificationSettings.panel.intro') }}</p>
150
+ <p class="text-xs text-muted">{{ t('notificationSettings.panel.intro') }}</p>
151
151
 
152
152
  <div
153
153
  v-if="notifications.settingsStatus === 'unavailable'"
154
- class="rounded-lg border border-slate-700 bg-slate-800/40 p-3 text-xs text-slate-400"
154
+ class="rounded-lg border border-muted bg-elevated/40 p-3 text-xs text-muted"
155
155
  >
156
156
  {{ t('notificationSettings.unavailable') }}
157
157
  </div>
158
158
 
159
159
  <div
160
160
  v-else-if="notifications.settingsStatus === 'failed'"
161
- class="space-y-3 rounded-lg border border-amber-700/60 bg-amber-950/30 p-3 text-xs text-amber-200"
161
+ class="space-y-3 rounded-lg border border-app-warning-700/60 bg-app-warning-950/30 p-3 text-xs text-app-warning-200"
162
162
  >
163
163
  <p>{{ t('notificationSettings.loadFailed') }}</p>
164
164
  <UButton color="neutral" variant="soft" size="xs" icon="i-lucide-rotate-cw" @click="load">
@@ -168,15 +168,13 @@ function resetToDefaults() {
168
168
 
169
169
  <div
170
170
  v-else-if="!editable"
171
- class="rounded-lg border border-slate-700 bg-slate-800/40 p-3 text-xs text-slate-400"
171
+ class="rounded-lg border border-muted bg-elevated/40 p-3 text-xs text-muted"
172
172
  >
173
173
  {{ t('common.loading') }}
174
174
  </div>
175
175
 
176
176
  <template v-else>
177
- <div
178
- class="flex items-center gap-3 px-2 text-[10px] uppercase tracking-wide text-slate-500"
179
- >
177
+ <div class="flex items-center gap-3 px-2 text-[10px] uppercase tracking-wide text-dimmed">
180
178
  <span class="flex-1">{{ t('notificationSettings.column.event') }}</span>
181
179
  <span class="w-16 text-center">{{ t('notificationSettings.column.inApp') }}</span>
182
180
  <span class="w-16 text-center">{{ t('notificationSettings.column.email') }}</span>
@@ -186,9 +184,9 @@ function resetToDefaults() {
186
184
  <div
187
185
  v-for="type in TYPES"
188
186
  :key="type"
189
- class="flex items-center gap-3 rounded-lg border border-slate-700 bg-slate-800/40 p-2"
187
+ class="flex items-center gap-3 rounded-lg border border-muted bg-elevated/40 p-2"
190
188
  >
191
- <span class="flex-1 text-sm text-slate-300">
189
+ <span class="flex-1 text-sm text-toned">
192
190
  {{ t(`notificationSettings.type.${type}`) }}
193
191
  </span>
194
192
  <div class="flex w-16 justify-center">
@@ -200,9 +198,13 @@ function resetToDefaults() {
200
198
  </div>
201
199
  </div>
202
200
 
203
- <p class="text-[11px] text-slate-500">{{ t('notificationSettings.panel.inAppNote') }}</p>
204
- <p class="text-[11px] text-slate-500">{{ t('notificationSettings.panel.emailNote') }}</p>
205
- <p class="text-[11px] text-slate-500">
201
+ <p class="text-[11px] text-dimmed">
202
+ {{ t('notificationSettings.panel.inAppNote') }}
203
+ </p>
204
+ <p class="text-[11px] text-dimmed">
205
+ {{ t('notificationSettings.panel.emailNote') }}
206
+ </p>
207
+ <p class="text-[11px] text-dimmed">
206
208
  {{ t('notificationSettings.panel.otherChannelsNote') }}
207
209
  <UButton
208
210
  v-if="slack.available"