@cat-factory/app 0.302.2 → 0.303.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -14
- package/app/app.config.ts +19 -10
- package/app/assets/css/main.css +13 -13
- package/app/assets/css/prose.css +22 -22
- package/app/assets/css/tokens.css +298 -0
- package/app/components/assistant/AssistantModal.vue +22 -22
- package/app/components/auth/AuthGate.vue +1 -1
- package/app/components/auth/BackendMisconfiguredScreen.vue +10 -10
- package/app/components/auth/LoginScreen.vue +34 -36
- package/app/components/auth/ResetPasswordScreen.vue +12 -12
- package/app/components/auth/UserMenu.vue +4 -4
- package/app/components/binaryCandidates/BinaryCandidatesWindow.vue +27 -25
- package/app/components/binaryOutput/BinaryOutputReport.vue +15 -15
- package/app/components/binaryOutput/StoredAssetView.vue +5 -5
- package/app/components/board/AddTaskModal.vue +17 -20
- package/app/components/board/AgentFailureCard.vue +11 -11
- package/app/components/board/AgentFailureHistory.vue +2 -2
- package/app/components/board/BoardCanvas.vue +6 -5
- package/app/components/board/CreateInitiativeModal.vue +8 -10
- package/app/components/board/DependencyConnectOverlay.vue +1 -1
- package/app/components/board/FailureHistoryList.vue +7 -7
- package/app/components/board/RecurringPipelineModal.vue +17 -17
- package/app/components/board/ReviewFrictionDialog.vue +6 -6
- package/app/components/board/StepExecutionHistory.vue +13 -13
- package/app/components/board/TaskDependencyEdges.vue +8 -8
- package/app/components/board/nodes/AgentChip.vue +10 -10
- package/app/components/board/nodes/BlockNode.vue +29 -29
- package/app/components/board/nodes/DecisionBadge.vue +2 -2
- package/app/components/board/nodes/EpicNode.vue +14 -10
- package/app/components/board/nodes/FrameSwimlanes.vue +7 -9
- package/app/components/board/nodes/InitiativeCard.vue +8 -8
- package/app/components/board/nodes/LaneGroup.vue +1 -1
- package/app/components/board/nodes/LaneTask.vue +2 -2
- package/app/components/board/nodes/ResizeGrips.vue +4 -4
- package/app/components/board/nodes/TaskCard.vue +18 -18
- package/app/components/board/nodes/TaskLane.vue +5 -5
- package/app/components/board/nodes/TaskPipelineMini.vue +18 -18
- package/app/components/bootstrap/AdoptionReviewModal.vue +29 -26
- package/app/components/bootstrap/BootstrapModal.vue +21 -23
- package/app/components/bootstrap/BootstrapRunSteps.vue +1 -1
- package/app/components/brainstorm/BrainstormWindow.vue +30 -33
- package/app/components/bugFishing/BugFishingWindow.vue +49 -49
- package/app/components/clarity/ClarityReviewWindow.vue +30 -30
- package/app/components/common/AsyncViewError.vue +5 -5
- package/app/components/common/ClarificationItem.vue +8 -13
- package/app/components/common/ConfirmDialog.vue +4 -2
- package/app/components/common/DescriptorFields.vue +1 -1
- package/app/components/common/EmptyState.vue +3 -3
- package/app/components/common/InterviewGateNotice.vue +7 -7
- package/app/components/common/KeyboardShortcutsHelp.vue +1 -1
- package/app/components/common/MarkdownProse.vue +14 -13
- package/app/components/consensus/ConsensusSessionWindow.vue +26 -26
- package/app/components/context/ContextAttachmentFields.vue +17 -17
- package/app/components/docs/DocInterviewWindow.vue +14 -14
- package/app/components/documents/ContextDocumentPicker.vue +17 -17
- package/app/components/documents/DocumentImportModal.vue +7 -7
- package/app/components/documents/DocumentSourceConnectModal.vue +3 -3
- package/app/components/documents/DocumentSyncState.vue +5 -5
- package/app/components/documents/DocumentTemplatesModal.vue +15 -15
- package/app/components/documents/RepoContextDocPicker.vue +5 -5
- package/app/components/documents/SpawnPreviewModal.vue +12 -12
- package/app/components/documents/StartFromDesignModal.vue +11 -11
- package/app/components/documents/TaskContextDocs.vue +5 -5
- package/app/components/environments/EnvSetupStepper.vue +6 -6
- package/app/components/environments/EnvironmentStatusPanel.vue +17 -17
- package/app/components/environments/steps/EnvPickStep.vue +3 -3
- package/app/components/environments/steps/EnvPreflightStep.vue +9 -9
- package/app/components/environments/steps/EnvReviewStep.vue +25 -22
- package/app/components/environments/steps/EnvSaveStep.vue +7 -5
- package/app/components/environments/steps/JourneyStepNav.vue +1 -1
- package/app/components/focus/BlockFocusView.vue +17 -19
- package/app/components/followUp/FollowUpWindow.vue +23 -23
- package/app/components/forkDecision/ForkDecisionWindow.vue +37 -39
- package/app/components/foundational/FoundationalContractSummary.vue +7 -7
- package/app/components/foundational/FoundationalServiceCatalogList.vue +11 -11
- package/app/components/foundational/FoundationalServiceManager.vue +1 -1
- package/app/components/foundational/FoundationalServiceRegistry.vue +13 -13
- package/app/components/foundational/FoundationalServiceSources.vue +13 -13
- package/app/components/foundational/FoundationalSuppressions.vue +7 -7
- package/app/components/foundational/ServiceCatalogConnection.vue +10 -10
- package/app/components/fragments/FragmentLibraryManager.vue +41 -44
- package/app/components/fragments/FragmentSelector.vue +13 -13
- package/app/components/fragments/GitHubDocUrlImport.vue +1 -1
- package/app/components/gates/GateFailingCheckList.vue +5 -5
- package/app/components/gates/GateResultView.vue +50 -47
- package/app/components/github/AddServiceFromRepoModal.vue +14 -14
- package/app/components/github/BranchProtectionPreflight.vue +13 -13
- package/app/components/github/GitHubConnect.vue +7 -7
- package/app/components/github/GitHubOnboarding.vue +7 -7
- package/app/components/github/GitHubPanel.vue +37 -40
- package/app/components/github/RepoTreeBrowser.vue +18 -18
- package/app/components/humanTest/HumanTestWindow.vue +30 -33
- package/app/components/initiative/InitiativePlanNotice.vue +5 -5
- package/app/components/initiative/InitiativePlanReview.vue +27 -29
- package/app/components/initiative/InitiativePlanningWindow.vue +10 -10
- package/app/components/initiative/InitiativeTrackerWindow.vue +71 -65
- package/app/components/inputGate/InputGateNotice.vue +2 -4
- package/app/components/judge/JudgeResultView.vue +30 -32
- package/app/components/kaizen/KaizenPanel.vue +40 -36
- package/app/components/kaizen/KaizenStepStatus.vue +21 -21
- package/app/components/layout/AccountAuditLog.vue +10 -10
- package/app/components/layout/AccountDeploymentSettings.vue +20 -20
- package/app/components/layout/AccountFailureKindRules.vue +13 -13
- package/app/components/layout/AccountFoundationalSettings.vue +1 -1
- package/app/components/layout/AccountFragmentSettings.vue +1 -1
- package/app/components/layout/AccountModelPolicySettings.vue +9 -9
- package/app/components/layout/AccountPlatformAlertSettings.vue +11 -11
- package/app/components/layout/AccountRiskPolicySettings.vue +2 -2
- package/app/components/layout/AccountRunCredentialSettings.vue +5 -5
- package/app/components/layout/AccountSkillSettings.vue +1 -1
- package/app/components/layout/AccountTeamSettings.vue +23 -15
- package/app/components/layout/AiProvidersBanner.vue +11 -8
- package/app/components/layout/AppearanceSwitcher.vue +108 -0
- package/app/components/layout/BoardSwitcher.vue +8 -11
- package/app/components/layout/BoardToolbar.vue +3 -3
- package/app/components/layout/CommandBar.vue +7 -9
- package/app/components/layout/ConnectionStatusBanner.vue +2 -2
- package/app/components/layout/DefaultTestEnvBanner.vue +5 -5
- package/app/components/layout/GitHubPatBanner.vue +5 -5
- package/app/components/layout/GitHubPatPermissionsBanner.vue +16 -10
- package/app/components/layout/InfraSetupBanner.vue +6 -6
- package/app/components/layout/IntegrationsHub.vue +15 -15
- package/app/components/layout/LanguageSwitcher.vue +5 -5
- package/app/components/layout/ModelProvidersHub.vue +13 -16
- package/app/components/layout/NotificationsInbox.vue +10 -10
- package/app/components/layout/PersonalSetupModal.vue +8 -8
- package/app/components/layout/ProviderConfigBanner.vue +4 -4
- package/app/components/layout/RolePrompt.vue +7 -7
- package/app/components/layout/SideBar.vue +6 -4
- package/app/components/layout/SpendWarningBanner.vue +17 -14
- package/app/components/layout/TranslationWarningBanner.vue +4 -4
- package/app/components/layout/UiModeSwitcher.vue +13 -13
- package/app/components/layout/UiRoleSwitcher.vue +8 -8
- package/app/components/layout/WorkspaceMembersSettings.vue +15 -11
- package/app/components/media/ArtifactLightbox.vue +13 -13
- package/app/components/media/ImageCompare.vue +31 -24
- package/app/components/merge/MergeEffortChips.vue +2 -2
- package/app/components/notifications/NotificationSettingsPanel.vue +14 -12
- package/app/components/observability/OutcomeFilterChips.vue +6 -6
- package/app/components/observability/RunFailureSummary.vue +28 -25
- package/app/components/observability/StepMetricsBar.vue +26 -28
- package/app/components/observability/StepModelActivity.vue +2 -2
- package/app/components/observability/ToolCallList.vue +24 -24
- package/app/components/outcome/OutcomeSummaryWindow.vue +47 -47
- package/app/components/palettes/AgentPalette.vue +8 -8
- package/app/components/palettes/AgentTierSelect.vue +1 -1
- package/app/components/palettes/PipelinePurposeSelect.vue +1 -1
- package/app/components/panels/AgentStepDetail.vue +52 -51
- package/app/components/panels/AttemptEntryHeader.vue +4 -6
- package/app/components/panels/DecisionModal.vue +7 -7
- package/app/components/panels/GenericStructuredResultView.vue +7 -7
- package/app/components/panels/InspectorPanel.vue +17 -17
- package/app/components/panels/MergerResultView.vue +19 -19
- package/app/components/panels/ObservabilityPanel.vue +115 -123
- package/app/components/panels/OperatorDashboardPanel.vue +89 -97
- package/app/components/panels/ReportsPanel.vue +69 -66
- package/app/components/panels/ReportsSpendBreakdown.vue +9 -9
- package/app/components/panels/ResultWindowShell.vue +38 -38
- package/app/components/panels/RunDetailLoadState.vue +2 -2
- package/app/components/panels/StepContainerStatus.vue +9 -9
- package/app/components/panels/StepDelegatedStatus.logic.spec.ts +135 -0
- package/app/components/panels/StepDelegatedStatus.logic.ts +160 -0
- package/app/components/panels/StepDelegatedStatus.vue +181 -0
- package/app/components/panels/StepEffortReport.vue +16 -15
- package/app/components/panels/StepFragmentAdherence.vue +14 -10
- package/app/components/panels/StepMetadataCard.vue +74 -45
- package/app/components/panels/StepReproductionReport.vue +25 -21
- package/app/components/panels/StepRunMeta.vue +20 -20
- package/app/components/panels/StepTestReport.logic.spec.ts +8 -3
- package/app/components/panels/StepTestReport.logic.ts +4 -4
- package/app/components/panels/StepTestReport.vue +28 -28
- package/app/components/panels/StepToolServers.vue +14 -11
- package/app/components/panels/StepValidationReport.vue +7 -7
- package/app/components/panels/inspector/ContainerSummary.vue +9 -9
- package/app/components/panels/inspector/DocReferenceRepos.vue +3 -3
- package/app/components/panels/inspector/EnvProbeReport.vue +24 -27
- package/app/components/panels/inspector/EpicChildren.vue +8 -12
- package/app/components/panels/inspector/FrontendBindingsResolved.vue +11 -11
- package/app/components/panels/inspector/FrontendConfig.vue +37 -41
- package/app/components/panels/inspector/InitiativeInspector.vue +3 -3
- package/app/components/panels/inspector/InspectorSection.vue +6 -6
- package/app/components/panels/inspector/RecurringScheduleSettings.vue +16 -19
- package/app/components/panels/inspector/ServiceConnections.vue +2 -2
- package/app/components/panels/inspector/ServiceReleaseHealthConfig.vue +2 -2
- package/app/components/panels/inspector/ServiceSelfTests.vue +21 -17
- package/app/components/panels/inspector/ServiceTestConfig.vue +51 -59
- package/app/components/panels/inspector/ServiceTestSecrets.vue +5 -5
- package/app/components/panels/inspector/ServiceTestingContext.vue +3 -3
- package/app/components/panels/inspector/ServiceValidationConfig.vue +2 -2
- package/app/components/panels/inspector/TaskAgentConfig.vue +3 -3
- package/app/components/panels/inspector/TaskAprioriBranches.vue +8 -5
- package/app/components/panels/inspector/TaskDependencies.vue +1 -1
- package/app/components/panels/inspector/TaskEstimateBadge.vue +11 -11
- package/app/components/panels/inspector/TaskExecution.vue +17 -17
- package/app/components/panels/inspector/TaskReviewTarget.vue +2 -2
- package/app/components/panels/inspector/TaskRunSettings.vue +35 -37
- package/app/components/panels/inspector/TaskStructure.vue +3 -3
- package/app/components/pipeline/AgentKindIcon.vue +1 -1
- package/app/components/pipeline/AgentPromptEditor.vue +17 -17
- package/app/components/pipeline/BinaryOutputStepPicker.vue +42 -56
- package/app/components/pipeline/EstimateThresholdFields.vue +4 -4
- package/app/components/pipeline/GateConfigFields.vue +13 -10
- package/app/components/pipeline/IterationCapPrompt.vue +4 -2
- package/app/components/pipeline/PipelineBuilder.vue +47 -55
- package/app/components/pipeline/PipelineHealthModal.vue +27 -29
- package/app/components/pipeline/PipelinePicker.vue +10 -10
- package/app/components/pipeline/PipelinePreview.vue +10 -10
- package/app/components/pipeline/PipelineProgress.vue +64 -67
- package/app/components/prReview/PrReviewPhaseBadge.vue +7 -7
- package/app/components/prReview/PrReviewWindow.vue +75 -77
- package/app/components/providers/AiPresetMismatchDialog.vue +5 -5
- package/app/components/providers/AiProviderOnboardingModal.vue +9 -9
- package/app/components/providers/ApiKeysSection.vue +13 -13
- package/app/components/providers/PersonalCredentialModal.vue +2 -2
- package/app/components/providers/PersonalSubscriptionSection.vue +10 -10
- package/app/components/providers/SignInRequiredNotice.vue +1 -1
- package/app/components/providers/VendorCredentialsModal.vue +10 -10
- package/app/components/provisioning/ProvisioningLogsDrawer.vue +13 -13
- package/app/components/ralph/RalphLoopResultView.vue +28 -30
- package/app/components/recurring/RecurrenceEditor.vue +2 -2
- package/app/components/requirements/RequirementsReviewWindow.vue +51 -58
- package/app/components/riskPolicy/RiskPolicyPicker.vue +12 -15
- package/app/components/riskPolicy/RiskPolicyPreview.vue +14 -14
- package/app/components/sandbox/SandboxPanel.vue +58 -59
- package/app/components/settings/AccountSettingsPanel.vue +1 -1
- package/app/components/settings/ApiTokensPanel.vue +10 -10
- package/app/components/settings/BudgetSettings.vue +18 -22
- package/app/components/settings/CapabilityCredentialsPanel.vue +15 -15
- package/app/components/settings/CloudflareHandlerSection.vue +20 -20
- package/app/components/settings/ComposeEnvironmentSetupSection.vue +13 -13
- package/app/components/settings/ConnectionTestVerdict.vue +3 -3
- package/app/components/settings/ConnectionWarnings.vue +1 -1
- package/app/components/settings/ConsensusGroupsSection.vue +27 -35
- package/app/components/settings/CustomManifestTypeEditor.vue +10 -10
- package/app/components/settings/DefaultProvisionTypeSection.vue +6 -6
- package/app/components/settings/InfraHandlersConfigurator.vue +24 -22
- package/app/components/settings/InfrastructureBackendPicker.vue +6 -6
- package/app/components/settings/InfrastructureWindow.vue +5 -5
- package/app/components/settings/IssueTrackerPanel.vue +29 -23
- package/app/components/settings/KubernetesEngineForm.vue +11 -11
- package/app/components/settings/KubernetesEnvironmentForm.vue +4 -4
- package/app/components/settings/LocalContainerPoolSettings.vue +6 -11
- package/app/components/settings/LocalModelEndpointsPanel.vue +20 -18
- package/app/components/settings/McpAuthorizeScreen.vue +13 -15
- package/app/components/settings/McpOAuthCallbackScreen.vue +16 -10
- package/app/components/settings/MergeClassRulesEditor.vue +6 -9
- package/app/components/settings/MergeRolePolicyEditor.vue +16 -16
- package/app/components/settings/ModelConfigurationPanel.vue +23 -30
- package/app/components/settings/ModelPresetHealthModal.vue +15 -13
- package/app/components/settings/ObservabilityConnectionPanel.vue +4 -4
- package/app/components/settings/OpenRouterCatalogPanel.vue +26 -19
- package/app/components/settings/PackageRegistriesPanel.vue +8 -8
- package/app/components/settings/ProviderConnectionTab.vue +8 -11
- package/app/components/settings/ProviderManifestEditor.vue +9 -9
- package/app/components/settings/ProviderPreferenceEditor.vue +8 -8
- package/app/components/settings/RiskPolicyCreateForm.vue +10 -10
- package/app/components/settings/RiskPolicyEditorRow.vue +14 -14
- package/app/components/settings/RiskPolicyHealthModal.vue +15 -13
- package/app/components/settings/RiskPolicyInheritedRow.vue +1 -1
- package/app/components/settings/RiskPolicyPanel.vue +10 -15
- package/app/components/settings/ServiceFragmentDefaultsPanel.vue +7 -7
- package/app/components/settings/SharedStacksPanel.vue +9 -12
- package/app/components/settings/TaskSourceCard.vue +4 -4
- package/app/components/settings/TaskTypeSuppressionsPanel.vue +6 -6
- package/app/components/settings/ToolServerChecklist.vue +36 -29
- package/app/components/settings/UsageSettings.vue +15 -15
- package/app/components/settings/UserSecretsSection.vue +9 -12
- package/app/components/settings/WorkspaceMetadataSettings.vue +5 -5
- package/app/components/settings/WorkspaceSettingsPanel.vue +47 -47
- package/app/components/skills/ReviewSkillQueue.vue +10 -10
- package/app/components/skills/SkillLibraryManager.vue +23 -20
- package/app/components/slack/SlackPanel.vue +13 -17
- package/app/components/spec/ServiceSpecWindow.vue +45 -42
- package/app/components/tasks/BugHuntModal.vue +24 -27
- package/app/components/tasks/ContextIssuePicker.vue +11 -11
- package/app/components/tasks/TaskContextIssues.vue +3 -3
- package/app/components/tasks/TaskImportModal.vue +7 -7
- package/app/components/tasks/TaskSourceConnectModal.vue +10 -8
- package/app/components/testing/TestReportWindow.vue +99 -82
- package/app/components/theme/ThemeImportModal.vue +115 -0
- package/app/components/tutorial/TutorialCatalogue.vue +9 -9
- package/app/components/tutorial/TutorialNudge.vue +5 -5
- package/app/components/tutorial/TutorialOverlay.vue +12 -12
- package/app/components/tutorial/TutorialPrompt.vue +6 -9
- package/app/components/vcs/GitLabConnect.vue +4 -4
- package/app/components/vcs/VcsConnectSurfaces.vue +2 -2
- package/app/components/visualConfirm/VisualConfirmationWindow.vue +36 -36
- package/app/composables/api/client.ts +6 -1
- package/app/composables/usePipelineErrorToast.ts +17 -0
- package/app/modular/agent-kinds.ts +24 -0
- package/app/pages/index.vue +5 -5
- package/app/plugins/theme.client.ts +125 -0
- package/app/spa-loading-template.html +52 -14
- package/app/stores/agents.ts +1 -1
- package/app/stores/theme.spec.ts +56 -0
- package/app/stores/theme.ts +87 -0
- package/app/types/domain.ts +19 -0
- package/app/types/execution.ts +4 -0
- package/app/utils/agentOutput.ts +1 -1
- package/app/utils/binaryOutput.ts +6 -6
- package/app/utils/bootstrapSteps.ts +14 -14
- package/app/utils/catalog.ts +87 -83
- package/app/utils/colorTint.ts +10 -0
- package/app/utils/failureKinds.ts +1 -0
- package/app/utils/observability.ts +3 -3
- package/app/utils/pipelineRender.ts +13 -17
- package/app/utils/swimlanes.ts +4 -4
- package/app/utils/theme/bootPalette.spec.ts +54 -0
- package/app/utils/theme/bootPalette.ts +108 -0
- package/app/utils/theme/builtins.spec.ts +38 -0
- package/app/utils/theme/builtins.ts +55 -0
- package/app/utils/theme/css.ts +162 -0
- package/app/utils/theme/doc.ts +353 -0
- package/app/utils/theme/link.ts +93 -0
- package/app/utils/theme/presets.ts +509 -0
- package/app/utils/theme/theme.spec.ts +224 -0
- package/i18n/locales/de.json +61 -4
- package/i18n/locales/en.json +70 -4
- package/i18n/locales/es.json +61 -4
- package/i18n/locales/fr.json +61 -4
- package/i18n/locales/he.json +61 -4
- package/i18n/locales/it.json +61 -4
- package/i18n/locales/ja.json +61 -4
- package/i18n/locales/pl.json +61 -4
- package/i18n/locales/tr.json +61 -4
- package/i18n/locales/uk.json +61 -4
- package/nuxt.config.ts +37 -11
- 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
|
+
}
|