@cat-factory/app 0.302.1 → 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 +10 -9
@@ -0,0 +1,353 @@
1
+ /**
2
+ * The theme document: the sparse `ThemeDoc` the Nuxt UI theme editor (https://ui.nuxt.com/theme)
3
+ * edits and shares as a `?doc=` link. The app speaks THAT format rather than one of its own, so a
4
+ * theme built in the editor is importable as-is and a built-in theme is a document a user could
5
+ * have exported. Everything absent inherits the Nuxt UI defaults, so an empty document IS the
6
+ * stock library theme.
7
+ *
8
+ * The tables and the two expansions (`styleTokens`, `styleComponents`) are ported from the editor
9
+ * engine (`docs/app/utils/theme/engine/types.ts` in nuxt/ui, MIT). They are kept byte-compatible in
10
+ * MEANING with the editor's export, because the promise to the user is "what you saw in the editor
11
+ * is what the app renders". Fields the app does not act on (`icons`, `font.weights`, the body
12
+ * typography flags) are typed so an imported document round-trips unchanged, and ignored on apply.
13
+ */
14
+
15
+ export const SHADES = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950] as const
16
+ export type Shade = (typeof SHADES)[number]
17
+ export type ShadeStop = 'white' | Shade | 'black'
18
+
19
+ export type ColorAlias =
20
+ | 'primary'
21
+ | 'secondary'
22
+ | 'success'
23
+ | 'info'
24
+ | 'warning'
25
+ | 'error'
26
+ | 'neutral'
27
+
28
+ export const COLOR_ALIASES: readonly ColorAlias[] = [
29
+ 'primary',
30
+ 'secondary',
31
+ 'success',
32
+ 'info',
33
+ 'warning',
34
+ 'error',
35
+ 'neutral',
36
+ ]
37
+
38
+ export interface ThemePalette {
39
+ shades: Partial<Record<Shade, string>>
40
+ }
41
+
42
+ export type DefaultVariant =
43
+ | 'default'
44
+ | 'solid'
45
+ | 'outline'
46
+ | 'soft'
47
+ | 'subtle'
48
+ | 'ghost'
49
+ | 'link'
50
+ | 'none'
51
+ export type DefaultSize = 'default' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'
52
+ export type DefaultColor = 'default' | ColorAlias
53
+ export type VariantGroup = 'buttons' | 'panels' | 'inputs'
54
+
55
+ export interface StyleOptions {
56
+ defaults?: {
57
+ variant?: DefaultVariant
58
+ size?: DefaultSize
59
+ variants?: Partial<Record<VariantGroup, DefaultVariant>>
60
+ colors?: Partial<Record<VariantGroup, DefaultColor>>
61
+ }
62
+ /** Semantic token → ramp shade per mode; keys whitelisted in `TOKEN_SHADE_TARGETS`. */
63
+ tokenShades?: Record<string, { light?: ShadeStop; dark?: ShadeStop }>
64
+ }
65
+
66
+ export interface ThemeDoc {
67
+ version: 1
68
+ /** Custom palettes, injected as `--color-{name}-{shade}`. */
69
+ palettes?: Record<string, ThemePalette>
70
+ /** Alias → palette name (a Tailwind colour or a key of `palettes`). */
71
+ colors?: Partial<Record<ColorAlias, string>>
72
+ blackAsPrimary?: boolean
73
+ /** Explicit `--ui-*` (or any `--*`) token overrides per mode. */
74
+ tokens?: {
75
+ light?: Record<string, string>
76
+ dark?: Record<string, string>
77
+ }
78
+ radius?: number
79
+ /** Root font size in px. */
80
+ fontSize?: number
81
+ font?: {
82
+ sans?: string
83
+ serif?: string
84
+ mono?: string
85
+ weights?: { normal?: number; medium?: number; semibold?: number; bold?: number }
86
+ uppercase?: boolean
87
+ italic?: boolean
88
+ letterSpacing?: number
89
+ lineHeight?: number
90
+ }
91
+ icons?: string
92
+ style?: StyleOptions
93
+ /** Per-component overrides merged into `app.config` `ui.<component>`. */
94
+ components?: Record<string, Record<string, unknown>>
95
+ }
96
+
97
+ /** A editor link's document. `preset` is the short form for an untouched preset. */
98
+ export type ThemeLink = ThemeDoc & { preset?: string }
99
+
100
+ export const DEFAULT_COLORS: Record<ColorAlias, string> = {
101
+ primary: 'green',
102
+ secondary: 'blue',
103
+ success: 'green',
104
+ info: 'blue',
105
+ warning: 'yellow',
106
+ error: 'red',
107
+ neutral: 'slate',
108
+ }
109
+
110
+ export const THEME_DEFAULTS = { radius: 0.25, fontSize: 16, font: 'Public Sans' } as const
111
+
112
+ /* ---------------------------------------------------------------- style -- */
113
+
114
+ const FIELD_VARIANTS = ['outline', 'soft', 'subtle', 'ghost', 'none']
115
+ const FIELD_COMPONENTS = [
116
+ 'input',
117
+ 'select',
118
+ 'textarea',
119
+ 'selectMenu',
120
+ 'inputMenu',
121
+ 'inputNumber',
122
+ 'inputTags',
123
+ 'inputDate',
124
+ 'inputTime',
125
+ 'pinInput',
126
+ ]
127
+
128
+ /** Which components support which default variant values. */
129
+ export const VARIANT_SUPPORT: Record<string, string[]> = {
130
+ button: ['solid', 'outline', 'soft', 'subtle', 'ghost', 'link'],
131
+ badge: ['solid', 'outline', 'soft', 'subtle'],
132
+ alert: ['solid', 'outline', 'soft', 'subtle'],
133
+ card: ['solid', 'outline', 'soft', 'subtle'],
134
+ empty: ['solid', 'outline', 'soft', 'subtle'],
135
+ ...Object.fromEntries(FIELD_COMPONENTS.map((component) => [component, FIELD_VARIANTS])),
136
+ }
137
+
138
+ /** Components the app-wide size default scales (exactly the xs–xl axis). */
139
+ export const SIZE_SUPPORT = [
140
+ 'button',
141
+ 'badge',
142
+ ...FIELD_COMPONENTS,
143
+ 'inputRating',
144
+ 'tabs',
145
+ 'checkbox',
146
+ 'checkboxGroup',
147
+ 'radioGroup',
148
+ 'switch',
149
+ 'slider',
150
+ 'stepper',
151
+ 'calendar',
152
+ 'colorPicker',
153
+ 'fileUpload',
154
+ 'formField',
155
+ 'fieldGroup',
156
+ 'dropdownMenu',
157
+ 'contextMenu',
158
+ 'commandPalette',
159
+ 'listbox',
160
+ ]
161
+
162
+ /** Components with a colour prop; the panels group has no colour axis. */
163
+ export const COLOR_SUPPORT = ['button', 'badge', ...FIELD_COMPONENTS]
164
+
165
+ export const VARIANT_GROUPS: Record<VariantGroup, string[]> = {
166
+ buttons: ['button', 'badge'],
167
+ panels: ['card', 'alert', 'empty'],
168
+ inputs: FIELD_COMPONENTS,
169
+ }
170
+
171
+ export type TokenRamp = ColorAlias
172
+
173
+ /**
174
+ * The semantic tokens the editor exposes as shade sliders, with the LIBRARY's resting values.
175
+ * Some rest on a literal ladder end (light `--ui-bg` is `white`, not a ramp stop).
176
+ */
177
+ export const TOKEN_SHADE_TARGETS: ReadonlyArray<{
178
+ token: string
179
+ ramp: TokenRamp
180
+ defaults: { light: ShadeStop; dark: ShadeStop }
181
+ }> = [
182
+ { token: '--ui-primary', ramp: 'primary', defaults: { light: 500, dark: 400 } },
183
+ { token: '--ui-secondary', ramp: 'secondary', defaults: { light: 500, dark: 400 } },
184
+ { token: '--ui-success', ramp: 'success', defaults: { light: 500, dark: 400 } },
185
+ { token: '--ui-info', ramp: 'info', defaults: { light: 500, dark: 400 } },
186
+ { token: '--ui-warning', ramp: 'warning', defaults: { light: 500, dark: 400 } },
187
+ { token: '--ui-error', ramp: 'error', defaults: { light: 500, dark: 400 } },
188
+ { token: '--ui-bg', ramp: 'neutral', defaults: { light: 'white', dark: 900 } },
189
+ { token: '--ui-bg-muted', ramp: 'neutral', defaults: { light: 50, dark: 800 } },
190
+ { token: '--ui-bg-elevated', ramp: 'neutral', defaults: { light: 100, dark: 800 } },
191
+ { token: '--ui-bg-accented', ramp: 'neutral', defaults: { light: 200, dark: 700 } },
192
+ { token: '--ui-bg-inverted', ramp: 'neutral', defaults: { light: 900, dark: 'white' } },
193
+ { token: '--ui-text-dimmed', ramp: 'neutral', defaults: { light: 400, dark: 500 } },
194
+ { token: '--ui-text-muted', ramp: 'neutral', defaults: { light: 500, dark: 400 } },
195
+ { token: '--ui-text-toned', ramp: 'neutral', defaults: { light: 600, dark: 300 } },
196
+ { token: '--ui-text', ramp: 'neutral', defaults: { light: 700, dark: 200 } },
197
+ { token: '--ui-text-highlighted', ramp: 'neutral', defaults: { light: 900, dark: 'white' } },
198
+ { token: '--ui-text-inverted', ramp: 'neutral', defaults: { light: 'white', dark: 900 } },
199
+ { token: '--ui-border', ramp: 'neutral', defaults: { light: 200, dark: 800 } },
200
+ { token: '--ui-border-muted', ramp: 'neutral', defaults: { light: 200, dark: 700 } },
201
+ { token: '--ui-border-accented', ramp: 'neutral', defaults: { light: 300, dark: 700 } },
202
+ { token: '--ui-border-inverted', ramp: 'neutral', defaults: { light: 900, dark: 'white' } },
203
+ ]
204
+
205
+ /** The stop closest to a value, where an off-ladder shade reference lands. */
206
+ export function nearestShade(value: number): Shade {
207
+ return SHADES.reduce((best, stop) =>
208
+ Math.abs(stop - value) < Math.abs(best - value) ? stop : best,
209
+ )
210
+ }
211
+
212
+ /** A ramp shade reference, or the literal for white/black. */
213
+ export function shadeRef(ramp: string, stop: ShadeStop | number): string {
214
+ if (stop === 'white' || stop === 'black') return stop
215
+ return `var(--ui-color-${ramp}-${nearestShade(stop)})`
216
+ }
217
+
218
+ export interface ModeTokens {
219
+ light: Record<string, string>
220
+ dark: Record<string, string>
221
+ }
222
+
223
+ /** Semantic token shades: the only CSS variables a style choice emits. */
224
+ export function styleTokens(style: StyleOptions | undefined): ModeTokens {
225
+ const light: Record<string, string> = {}
226
+ const dark: Record<string, string> = {}
227
+ for (const [token, shade] of Object.entries(style?.tokenShades ?? {})) {
228
+ const target = TOKEN_SHADE_TARGETS.find((candidate) => candidate.token === token)
229
+ if (!target || !shade) continue
230
+ if (shade.light !== undefined) light[token] = shadeRef(target.ramp, shade.light)
231
+ if (shade.dark !== undefined) dark[token] = shadeRef(target.ramp, shade.dark)
232
+ }
233
+ return { light, dark }
234
+ }
235
+
236
+ export type ComponentOverrides = Record<string, Record<string, unknown>>
237
+
238
+ type DefaultVariants = Record<string, string>
239
+
240
+ function withDefaultVariants(
241
+ fragments: ComponentOverrides,
242
+ component: string,
243
+ patch: DefaultVariants,
244
+ ): void {
245
+ const current = fragments[component]?.defaultVariants as DefaultVariants | undefined
246
+ fragments[component] = {
247
+ ...fragments[component],
248
+ defaultVariants: { ...current, ...patch },
249
+ }
250
+ }
251
+
252
+ /** Expand the default variant/size/colour choices into `ui.<component>.defaultVariants`. */
253
+ export function styleComponents(style: StyleOptions | undefined): ComponentOverrides {
254
+ const fragments: ComponentOverrides = {}
255
+ const defaults = style?.defaults
256
+ if (!defaults) return fragments
257
+
258
+ const set = (value?: string): value is string => !!value && value !== 'default'
259
+
260
+ if (set(defaults.variant)) {
261
+ for (const [component, supported] of Object.entries(VARIANT_SUPPORT)) {
262
+ if (supported.includes(defaults.variant)) {
263
+ withDefaultVariants(fragments, component, { variant: defaults.variant })
264
+ }
265
+ }
266
+ }
267
+ for (const [group, components] of Object.entries(VARIANT_GROUPS) as Array<
268
+ [VariantGroup, string[]]
269
+ >) {
270
+ const groupVariant = defaults.variants?.[group]
271
+ if (set(groupVariant)) {
272
+ for (const component of components) {
273
+ if (VARIANT_SUPPORT[component]?.includes(groupVariant)) {
274
+ withDefaultVariants(fragments, component, { variant: groupVariant })
275
+ }
276
+ }
277
+ }
278
+ const groupColor = defaults.colors?.[group]
279
+ if (set(groupColor)) {
280
+ for (const component of components) {
281
+ if (COLOR_SUPPORT.includes(component)) {
282
+ withDefaultVariants(fragments, component, { color: groupColor })
283
+ }
284
+ }
285
+ }
286
+ }
287
+ if (set(defaults.size)) {
288
+ for (const component of SIZE_SUPPORT) {
289
+ withDefaultVariants(fragments, component, { size: defaults.size })
290
+ }
291
+ }
292
+ return fragments
293
+ }
294
+
295
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
296
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
297
+ }
298
+
299
+ /**
300
+ * Merge two `ui.<component>` override fragments so BOTH take effect: slot class strings
301
+ * concatenate (extra last, so it wins the tailwind-merge), `compoundVariants` append,
302
+ * `defaultVariants` replace per key. A spread would silently drop whichever side loses.
303
+ */
304
+ export function mergeComponentOverrides(
305
+ base: Record<string, unknown> | undefined,
306
+ extra: Record<string, unknown> | undefined,
307
+ ): Record<string, unknown> | undefined {
308
+ if (!base || Object.keys(base).length === 0) return extra
309
+ if (!extra || Object.keys(extra).length === 0) return base
310
+ const result: Record<string, unknown> = {}
311
+ for (const key of new Set([...Object.keys(base), ...Object.keys(extra)])) {
312
+ const a = base[key]
313
+ const b = extra[key]
314
+ if (a === undefined) result[key] = b
315
+ else if (b === undefined) result[key] = a
316
+ else if (key === 'compoundVariants' && Array.isArray(a) && Array.isArray(b))
317
+ result[key] = [...a, ...b]
318
+ else if (key === 'defaultVariants' && isPlainObject(a) && isPlainObject(b))
319
+ result[key] = { ...a, ...b }
320
+ else if (typeof a === 'string' && typeof b === 'string') result[key] = `${a} ${b}`
321
+ else if (isPlainObject(a) && isPlainObject(b)) result[key] = mergeComponentOverrides(a, b)
322
+ else result[key] = b
323
+ }
324
+ return result
325
+ }
326
+
327
+ /** Merge two whole `ui` records component-wise. */
328
+ export function mergeUi(
329
+ base: ComponentOverrides | undefined,
330
+ extra: ComponentOverrides | undefined,
331
+ ): ComponentOverrides {
332
+ const result: ComponentOverrides = {}
333
+ for (const key of new Set([...Object.keys(base ?? {}), ...Object.keys(extra ?? {})])) {
334
+ const merged = mergeComponentOverrides(base?.[key], extra?.[key])
335
+ if (merged && Object.keys(merged).length > 0) result[key] = merged
336
+ }
337
+ return result
338
+ }
339
+
340
+ /** The `ui.<component>` overrides a document asks for: its style expansion plus explicit ones. */
341
+ export function docComponentOverrides(doc: ThemeDoc): ComponentOverrides {
342
+ return mergeUi(styleComponents(doc.style), doc.components)
343
+ }
344
+
345
+ /** The alias → palette map a document resolves to, defaults filled in. */
346
+ export function docColors(doc: ThemeDoc): Record<ColorAlias, string> {
347
+ const colors = { ...DEFAULT_COLORS }
348
+ for (const alias of COLOR_ALIASES) {
349
+ const palette = doc.colors?.[alias]
350
+ if (palette) colors[alias] = palette
351
+ }
352
+ return colors
353
+ }
@@ -0,0 +1,93 @@
1
+ import type { ThemeDoc, ThemeLink } from '~/utils/theme/doc'
2
+ import { presets } from '~/utils/theme/presets'
3
+
4
+ /**
5
+ * Decoding a Nuxt UI theme editor link (`https://ui.nuxt.com/theme?doc=<payload>`). The payload
6
+ * is the theme document as JSON, deflate-raw compressed, base64url encoded; an uncompressed
7
+ * JSON payload is the editor's own fallback for a browser without `CompressionStream`, so both are
8
+ * read here. Mirrors `docs/app/utils/theme/link.ts` in nuxt/ui.
9
+ *
10
+ * The result is a DISCRIMINATED outcome, not a nullable: the import dialog shows a different remedy
11
+ * for "this is not a editor link" than for "this link is a preset shorthand", and a bare `null`
12
+ * would make the two indistinguishable.
13
+ */
14
+
15
+ export type ThemeLinkDecodeResult =
16
+ | { ok: true; doc: ThemeDoc }
17
+ | { ok: false; reason: 'not_a_link' | 'malformed' | 'preset_shorthand' | 'unsupported_version' }
18
+
19
+ /** The `doc` payload out of a editor URL, a bare payload, or raw document JSON. */
20
+ export function extractThemePayload(input: string): string | null {
21
+ const trimmed = input.trim()
22
+ if (!trimmed) return null
23
+ if (trimmed.startsWith('{')) return trimmed
24
+ try {
25
+ const url = new URL(trimmed)
26
+ return url.searchParams.get('doc')
27
+ } catch {
28
+ // Not a URL: accept a copied payload on its own as long as it is base64url-shaped.
29
+ return /^[\w-]+$/.test(trimmed) ? trimmed : null
30
+ }
31
+ }
32
+
33
+ async function inflateRaw(bytes: Uint8Array): Promise<Uint8Array | undefined> {
34
+ if (typeof DecompressionStream === 'undefined') return undefined
35
+ try {
36
+ const stream = new Blob([bytes as BlobPart])
37
+ .stream()
38
+ .pipeThrough(new DecompressionStream('deflate-raw'))
39
+ return new Uint8Array(await new Response(stream).arrayBuffer())
40
+ } catch {
41
+ return undefined
42
+ }
43
+ }
44
+
45
+ function parseDoc(bytes: Uint8Array | undefined): ThemeLink | undefined {
46
+ if (!bytes) return undefined
47
+ try {
48
+ const parsed: unknown = JSON.parse(new TextDecoder().decode(bytes))
49
+ return parsed && typeof parsed === 'object' ? (parsed as ThemeLink) : undefined
50
+ } catch {
51
+ return undefined
52
+ }
53
+ }
54
+
55
+ /**
56
+ * The editor collapses a link whose document is exactly one of its presets to a bare
57
+ * `{ version, preset }`; `presets.ts` carries their documents so such a link rebuilds here. A
58
+ * preset id this copy does not know (the editor added one since) is reported, not guessed.
59
+ */
60
+ function presetDoc(id: string): ThemeDoc | undefined {
61
+ return presets.find((preset) => preset.id === id)?.doc
62
+ }
63
+
64
+ /** Decode a editor link, payload or raw JSON into a theme document. */
65
+ export async function decodeThemeLink(input: string): Promise<ThemeLinkDecodeResult> {
66
+ const payload = extractThemePayload(input)
67
+ if (!payload) return { ok: false, reason: 'not_a_link' }
68
+
69
+ let link: ThemeLink | undefined
70
+ if (payload.startsWith('{')) {
71
+ link = parseDoc(new TextEncoder().encode(payload))
72
+ } else {
73
+ let bytes: Uint8Array
74
+ try {
75
+ bytes = Uint8Array.from(atob(payload.replace(/-/g, '+').replace(/_/g, '/')), (char) =>
76
+ char.charCodeAt(0),
77
+ )
78
+ } catch {
79
+ return { ok: false, reason: 'malformed' }
80
+ }
81
+ link = parseDoc(await inflateRaw(bytes)) ?? parseDoc(bytes)
82
+ }
83
+ if (!link) return { ok: false, reason: 'malformed' }
84
+ if (link.version !== 1) return { ok: false, reason: 'unsupported_version' }
85
+
86
+ const { preset, ...doc } = link
87
+ if (preset) {
88
+ const rebuilt = presetDoc(preset)
89
+ if (!rebuilt) return { ok: false, reason: 'preset_shorthand' }
90
+ return { ok: true, doc: { ...structuredClone(rebuilt), ...doc } }
91
+ }
92
+ return { ok: true, doc }
93
+ }