@agent-native/core 0.141.2 → 0.141.4

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 (157) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/analysis-workspace/SKILL.md +1 -1
  3. package/corpus/templates/analytics/actions/gong-calls.ts +56 -18
  4. package/corpus/templates/analytics/changelog/2026-08-05-analytics-queries-keep-every-table-tenant-scoped.md +6 -0
  5. package/corpus/templates/analytics/changelog/2026-08-05-durable-a2a-workers-avoid-startup-migration-contention.md +6 -0
  6. package/corpus/templates/analytics/changelog/2026-08-05-fixed-sign-in-and-dashboard-requests-failing-when-background.md +6 -0
  7. package/corpus/templates/analytics/changelog/2026-08-05-historical-rollups-stay-complete-while-live-events-arrive.md +6 -0
  8. package/corpus/templates/analytics/server/db/schema.ts +1 -0
  9. package/corpus/templates/analytics/server/lib/dashboard-report.ts +2 -0
  10. package/corpus/templates/analytics/server/lib/emails.ts +51 -0
  11. package/corpus/templates/analytics/server/lib/first-party-analytics-health.ts +8 -12
  12. package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +9 -0
  13. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +262 -13
  14. package/corpus/templates/analytics/server/lib/gong.ts +132 -33
  15. package/corpus/templates/analytics/server/plugins/agent-chat.ts +1 -1
  16. package/corpus/templates/analytics/server/plugins/auth.ts +21 -2
  17. package/corpus/templates/analytics/server/plugins/db.ts +35 -10
  18. package/corpus/templates/analytics/server/plugins/transactional-emails.ts +5 -0
  19. package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +17 -7
  20. package/corpus/templates/calendar/server/lib/booking-emails.ts +154 -70
  21. package/corpus/templates/calendar/server/lib/emails.ts +167 -0
  22. package/corpus/templates/calendar/server/lib/event-guest-notifications.ts +64 -28
  23. package/corpus/templates/calendar/server/plugins/transactional-emails.ts +5 -0
  24. package/corpus/templates/clips/actions/invite-member.ts +39 -16
  25. package/corpus/templates/clips/app/components/player/share-dialog.tsx +73 -1
  26. package/corpus/templates/clips/app/i18n/en-US.ts +3 -0
  27. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +10 -25
  28. package/corpus/templates/clips/app/routes/share.$shareId.tsx +2 -0
  29. package/corpus/templates/clips/changelog/2026-08-05-activity-is-now-the-default-clips-recording-tab-and-ai-setup.md +6 -0
  30. package/corpus/templates/clips/changelog/2026-08-05-clips-can-copy-a-linked-email-preview-for-public-recordings.md +6 -0
  31. package/corpus/templates/clips/changelog/2026-08-05-preparing-recording-now-uses-a-clean-shadow-free-status-card.md +6 -0
  32. package/corpus/templates/clips/desktop/src/styles.css +0 -3
  33. package/corpus/templates/clips/server/lib/emails.ts +256 -0
  34. package/corpus/templates/clips/server/lib/transactional-email-templates.ts +24 -0
  35. package/corpus/templates/clips/server/plugins/transactional-email-catalog.ts +5 -0
  36. package/corpus/templates/clips/server/routes/api/thumbnail/[recordingId].get.ts +4 -1
  37. package/corpus/templates/clips/shared/email-preview.ts +61 -0
  38. package/corpus/templates/content/server/lib/comment-notifications.ts +57 -26
  39. package/corpus/templates/content/server/lib/emails.ts +70 -0
  40. package/corpus/templates/content/server/plugins/transactional-emails.ts +5 -0
  41. package/corpus/templates/design/actions/import-design-tokens.ts +11 -8
  42. package/corpus/templates/design/actions/index-design-tokens.ts +13 -6
  43. package/corpus/templates/design/changelog/2026-08-05-design-token-imports-classify-typography-dimensions-correctly.md +6 -0
  44. package/corpus/templates/dispatch/app/routes/transactional-email.$appId.$id.tsx +4 -0
  45. package/corpus/templates/dispatch/app/routes/transactional-email.tsx +4 -0
  46. package/corpus/templates/dispatch/changelog/2026-08-05-transactional-email-catalogs-and-delivery-activity-are-available-again.md +6 -0
  47. package/corpus/templates/factory/AGENTS.md +18 -17
  48. package/corpus/templates/factory/actions/get-factory-automation-health.ts +50 -0
  49. package/corpus/templates/factory/actions/get-triage-config.ts +8 -0
  50. package/corpus/templates/factory/actions/save-triage-config.ts +26 -0
  51. package/corpus/templates/factory/app/i18n/en-US.ts +27 -0
  52. package/corpus/templates/factory/app/routes/factory.tsx +376 -206
  53. package/corpus/templates/factory/changelog/2026-08-05-factory-now-keeps-production-automations-running-shows-sched.md +6 -0
  54. package/corpus/templates/factory/server/db/schema.ts +6 -0
  55. package/corpus/templates/factory/server/plugins/factory-migrations.ts +10 -0
  56. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +141 -2
  57. package/corpus/templates/forms/app/pages/FormsListPage.tsx +41 -21
  58. package/corpus/templates/forms/changelog/2026-08-05-form-links-can-now-be-cmd-clicked-to-open-in-a-new-tab.md +6 -0
  59. package/corpus/templates/forms/server/lib/emails.ts +52 -0
  60. package/corpus/templates/forms/server/lib/response-email.ts +2 -0
  61. package/corpus/templates/forms/server/plugins/transactional-emails.ts +5 -0
  62. package/corpus/templates/plan/actions/request-plan-access.ts +33 -12
  63. package/corpus/templates/plan/server/lib/comment-notifications.ts +53 -27
  64. package/corpus/templates/plan/server/lib/emails.ts +67 -0
  65. package/corpus/templates/plan/server/plugins/transactional-emails.ts +5 -0
  66. package/corpus/templates/slides/actions/import-from-url.ts +1 -0
  67. package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +492 -381
  68. package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +8 -7
  69. package/corpus/templates/slides/app/components/layout/Layout.tsx +27 -23
  70. package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +313 -0
  71. package/corpus/templates/slides/app/components/ui/command.tsx +1 -0
  72. package/corpus/templates/slides/app/i18n/en-US.ts +8 -2
  73. package/corpus/templates/slides/app/lib/create-deck-generation.ts +305 -0
  74. package/corpus/templates/slides/app/pages/DeckEditor.tsx +12 -12
  75. package/corpus/templates/slides/app/pages/Index.tsx +127 -137
  76. package/corpus/templates/slides/app/root.tsx +8 -0
  77. package/corpus/templates/slides/changelog/2026-08-05-deck-browsing-keeps-all-and-mine-in-the-header-with-a-tighte.md +6 -0
  78. package/corpus/templates/slides/changelog/2026-08-05-new-deck-creation-now-flows-through-a-clean-full-screen-refe.md +6 -0
  79. package/corpus/templates/slides/changelog/2026-08-05-website-imports-now-include-linked-stylesheets-for-more-accu.md +6 -0
  80. package/corpus/templates/slides/server/lib/comment-notifications.ts +44 -21
  81. package/corpus/templates/slides/server/lib/emails.ts +40 -0
  82. package/corpus/templates/slides/server/plugins/transactional-emails.ts +5 -0
  83. package/dist/brand-kit/index.d.ts +1 -0
  84. package/dist/brand-kit/index.js +1 -0
  85. package/dist/brand-kit/tokens.d.ts +91 -0
  86. package/dist/brand-kit/tokens.js +279 -0
  87. package/dist/brand-kit/types.d.ts +25 -0
  88. package/dist/client/AssistantChat.js +7 -7
  89. package/dist/client/onboarding/FirstRunOnboarding.js +26 -2
  90. package/dist/client/onboarding/first-run-registry.d.ts +25 -0
  91. package/dist/client/onboarding/first-run-registry.js +18 -0
  92. package/dist/client/onboarding/index.d.ts +2 -0
  93. package/dist/client/onboarding/index.js +1 -0
  94. package/dist/collab/awareness.d.ts +2 -2
  95. package/dist/collab/routes.d.ts +1 -1
  96. package/dist/collab/struct-routes.d.ts +1 -1
  97. package/dist/db/index.d.ts +1 -1
  98. package/dist/db/index.js +1 -1
  99. package/dist/db/migrations.d.ts +13 -3
  100. package/dist/db/migrations.js +19 -4
  101. package/dist/deploy/build.d.ts +8 -0
  102. package/dist/deploy/build.js +92 -3
  103. package/dist/deploy/workspace-deploy.js +86 -3
  104. package/dist/email-catalog/actions/list-email-activity.d.ts +5 -0
  105. package/dist/email-catalog/actions/list-email-activity.js +19 -0
  106. package/dist/email-catalog/actions/list-email-engagement.d.ts +5 -0
  107. package/dist/email-catalog/actions/list-email-engagement.js +15 -0
  108. package/dist/email-catalog/actions/list-email-log.d.ts +7 -0
  109. package/dist/email-catalog/actions/list-email-log.js +23 -0
  110. package/dist/email-catalog/actions/list-transactional-emails.d.ts +22 -0
  111. package/dist/email-catalog/actions/list-transactional-emails.js +67 -0
  112. package/dist/email-catalog/actions/render-transactional-email-preview.d.ts +9 -0
  113. package/dist/email-catalog/actions/render-transactional-email-preview.js +25 -0
  114. package/dist/email-catalog/authorize.d.ts +1 -0
  115. package/dist/email-catalog/authorize.js +9 -0
  116. package/dist/email-catalog/log.d.ts +60 -0
  117. package/dist/email-catalog/log.js +142 -0
  118. package/dist/email-catalog/provider-metrics.d.ts +34 -0
  119. package/dist/email-catalog/provider-metrics.js +182 -0
  120. package/dist/email-catalog/registry.d.ts +78 -0
  121. package/dist/email-catalog/registry.js +70 -0
  122. package/dist/email-catalog/schema.d.ts +231 -0
  123. package/dist/email-catalog/schema.js +53 -0
  124. package/dist/email-catalog/system-emails.d.ts +13 -0
  125. package/dist/email-catalog/system-emails.js +67 -0
  126. package/dist/jobs/index.d.ts +2 -0
  127. package/dist/jobs/index.js +2 -0
  128. package/dist/jobs/run-history.js +48 -1
  129. package/dist/jobs/scheduler-dispatch.d.ts +7 -0
  130. package/dist/jobs/scheduler-dispatch.js +7 -0
  131. package/dist/jobs/scheduler-health.d.ts +22 -0
  132. package/dist/jobs/scheduler-health.js +172 -0
  133. package/dist/jobs/scheduler.js +67 -2
  134. package/dist/localization/default-messages.d.ts +45 -0
  135. package/dist/localization/default-messages.js +45 -0
  136. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  137. package/dist/notifications/routes.d.ts +3 -3
  138. package/dist/observability/routes.d.ts +3 -3
  139. package/dist/org/handlers.js +9 -1
  140. package/dist/progress/routes.d.ts +1 -1
  141. package/dist/provider-api/actions/custom-provider-registration.d.ts +10 -10
  142. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  143. package/dist/server/action-discovery.js +22 -0
  144. package/dist/server/agent-chat/recurring-jobs-runtime.d.ts +7 -1
  145. package/dist/server/agent-chat/recurring-jobs-runtime.js +12 -0
  146. package/dist/server/agent-chat-plugin.d.ts +2 -1
  147. package/dist/server/agent-chat-plugin.js +69 -22
  148. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  149. package/dist/server/better-auth-instance.js +17 -2
  150. package/dist/server/design-token-utils.d.ts +6 -1
  151. package/dist/server/design-token-utils.js +252 -59
  152. package/dist/server/email.d.ts +11 -0
  153. package/dist/server/email.js +54 -3
  154. package/dist/server/index.d.ts +3 -2
  155. package/dist/server/index.js +3 -2
  156. package/dist/vite/client.js +20 -14
  157. package/package.json +2 -1
@@ -0,0 +1,91 @@
1
+ /**
2
+ * `@agent-native/core/brand-kit/tokens`
3
+ *
4
+ * Pure helpers for the named-token vocabulary a Brand Kit stores alongside its
5
+ * seven color roles. Kept on its own subpath (no `design-token-utils` re-export)
6
+ * so browser code can import the CSS-safety predicates without pulling the
7
+ * server-side extraction stack into the bundle.
8
+ */
9
+ import type { BrandKitToken, BrandKitTokenType } from "./types.js";
10
+ /**
11
+ * Upper bound on a single kit's stored vocabulary. Large systems legitimately
12
+ * define hundreds of tokens; this only stops an unbounded scrape landing in SQL.
13
+ */
14
+ export declare const MAX_BRAND_KIT_TOKENS = 500;
15
+ export declare function isSafeCssVarName(value: string): boolean;
16
+ /**
17
+ * Token values are spliced raw into `:root { … }` declarations, so anything that
18
+ * could terminate a declaration or break out of a `<style>` element is unsafe.
19
+ */
20
+ export declare function isSafeCssTokenValue(value: string): boolean;
21
+ export declare function isColorTokenValue(value: string): boolean;
22
+ /**
23
+ * Bucket a token, trusting the value's shape over the name's keywords.
24
+ *
25
+ * Name-first classification called Primer's `--text-body-size-medium: 1rem` a
26
+ * color because the name contains "text", which then rendered a swatch with
27
+ * `background: 1rem`. A dimension is never a color however the token is named.
28
+ */
29
+ export declare function classifyBrandKitToken(name: string, value: string): BrandKitTokenType;
30
+ /** Title-case display name derived from a CSS var, e.g. `--primary-color`. */
31
+ export declare function friendlyTokenName(cssVar: string): string;
32
+ export type BrandKitTokenRejectionReason = "malformed" | "unsafe-css-var" | "unsafe-value" | "over-limit";
33
+ export interface BrandKitTokenRejection {
34
+ reason: BrandKitTokenRejectionReason;
35
+ /** Best-effort identifier for the offending entry, for error messages. */
36
+ label: string;
37
+ }
38
+ export interface NormalizedBrandKitTokens {
39
+ tokens: BrandKitToken[];
40
+ /**
41
+ * Entries that could not be stored. Callers must surface these - a kit that
42
+ * silently kept 4 of 40 tokens reads downstream as a 4-token design system.
43
+ */
44
+ rejected: BrandKitTokenRejection[];
45
+ }
46
+ /**
47
+ * Coerce untrusted token JSON into storable tokens, reporting every entry it
48
+ * could not accept. Later duplicates of a `cssVar` win, matching the overlay
49
+ * order importers use.
50
+ */
51
+ export declare function normalizeBrandKitTokens(input: unknown): NormalizedBrandKitTokens;
52
+ /** Human-readable summary of why tokens were refused, for thrown errors. */
53
+ export declare function describeBrandKitTokenRejections(rejected: readonly BrandKitTokenRejection[]): string;
54
+ /**
55
+ * The seven colour roles plus radius/spacing, as tokens. Derived from a kit's
56
+ * summary fields rather than named by the source system, so they carry no
57
+ * `group`.
58
+ */
59
+ export declare function brandKitRoleTokens(data: {
60
+ colors?: object | null;
61
+ borders?: {
62
+ radius?: string;
63
+ } | null;
64
+ spacing?: object | null;
65
+ } | null | undefined, source?: string): BrandKitToken[];
66
+ /**
67
+ * Read a kit's named tokens out of a raw CSS block. A kit's `customCSS` already
68
+ * spells out the source system's real token names. Declaration order wins on
69
+ * duplicates, matching the cascade.
70
+ */
71
+ export declare function parseBrandKitTokensFromCss(css: string, source?: string): BrandKitToken[];
72
+ /**
73
+ * The kit's named vocabulary: its stored `tokens` when it has them, else the
74
+ * names its `customCSS` declares. Kits predating `tokens` still carry real names
75
+ * in CSS, so reading only `tokens` would report them as having none.
76
+ */
77
+ export declare function resolveBrandKitTokens(data: {
78
+ tokens?: unknown;
79
+ customCSS?: unknown;
80
+ } | null | undefined, cssSource?: string): BrandKitToken[];
81
+ export interface BrandKitTokenGroup {
82
+ /** The source's own group path when it has one, else the token type. */
83
+ label: string;
84
+ type: BrandKitTokenType;
85
+ tokens: BrandKitToken[];
86
+ }
87
+ /**
88
+ * Group tokens for display, preserving the source's collection paths. Groups sort
89
+ * by token type so colors lead, then alphabetically within a type.
90
+ */
91
+ export declare function groupBrandKitTokens(tokens: readonly BrandKitToken[]): BrandKitTokenGroup[];
@@ -0,0 +1,279 @@
1
+ /**
2
+ * `@agent-native/core/brand-kit/tokens`
3
+ *
4
+ * Pure helpers for the named-token vocabulary a Brand Kit stores alongside its
5
+ * seven color roles. Kept on its own subpath (no `design-token-utils` re-export)
6
+ * so browser code can import the CSS-safety predicates without pulling the
7
+ * server-side extraction stack into the bundle.
8
+ */
9
+ const BRAND_KIT_TOKEN_TYPES = [
10
+ "color",
11
+ "typography",
12
+ "spacing",
13
+ "radius",
14
+ "shadow",
15
+ "other",
16
+ ];
17
+ /**
18
+ * Upper bound on a single kit's stored vocabulary. Large systems legitimately
19
+ * define hundreds of tokens; this only stops an unbounded scrape landing in SQL.
20
+ */
21
+ export const MAX_BRAND_KIT_TOKENS = 500;
22
+ const CSS_CUSTOM_PROPERTY_NAME = /^--[-_a-zA-Z0-9]+$/;
23
+ const COLOR_FUNCTION_OR_HEX = /^(#[0-9a-fA-F]{3,8}|rgba?\(|hsla?\(|oklch\(|color\()/i;
24
+ const NAMED_CSS_COLORS = new Set([
25
+ "red",
26
+ "blue",
27
+ "green",
28
+ "yellow",
29
+ "orange",
30
+ "purple",
31
+ "pink",
32
+ "cyan",
33
+ "magenta",
34
+ "teal",
35
+ "navy",
36
+ "maroon",
37
+ "coral",
38
+ "salmon",
39
+ "gold",
40
+ "silver",
41
+ "gray",
42
+ "grey",
43
+ "indigo",
44
+ "violet",
45
+ "lime",
46
+ "olive",
47
+ "aqua",
48
+ "fuchsia",
49
+ "crimson",
50
+ "turquoise",
51
+ "ivory",
52
+ "beige",
53
+ "lavender",
54
+ "tan",
55
+ "khaki",
56
+ "plum",
57
+ "orchid",
58
+ "sienna",
59
+ ]);
60
+ export function isSafeCssVarName(value) {
61
+ return CSS_CUSTOM_PROPERTY_NAME.test(value);
62
+ }
63
+ /**
64
+ * Token values are spliced raw into `:root { … }` declarations, so anything that
65
+ * could terminate a declaration or break out of a `<style>` element is unsafe.
66
+ */
67
+ export function isSafeCssTokenValue(value) {
68
+ return (value.length > 0 &&
69
+ value.length <= 300 &&
70
+ !/[;{}<>]/.test(value) &&
71
+ !/\/\*/.test(value) &&
72
+ !/[\u0000-\u001f\u007f]/.test(value));
73
+ }
74
+ export function isColorTokenValue(value) {
75
+ const normalized = value.trim().toLowerCase();
76
+ return (COLOR_FUNCTION_OR_HEX.test(normalized) || NAMED_CSS_COLORS.has(normalized));
77
+ }
78
+ const COLOR_NAME_HINT = /color|bg|background|text|border|accent|primary|secondary|surface|muted|foreground|fill|stroke/i;
79
+ const DIMENSION_VALUE = /^-?\d*\.?\d+(px|rem|em|ex|ch|%|vh|vw|vmin|vmax|pt|pc|cm|mm|in|s|ms|deg|fr)?$/i;
80
+ /**
81
+ * Bucket a token, trusting the value's shape over the name's keywords.
82
+ *
83
+ * Name-first classification called Primer's `--text-body-size-medium: 1rem` a
84
+ * color because the name contains "text", which then rendered a swatch with
85
+ * `background: 1rem`. A dimension is never a color however the token is named.
86
+ */
87
+ export function classifyBrandKitToken(name, value) {
88
+ const n = name.toLowerCase();
89
+ if (isColorTokenValue(value))
90
+ return "color";
91
+ if (/radius|rounded/i.test(n))
92
+ return "radius";
93
+ // `line-?height` and `letter` run before the spacing check so
94
+ // `letter-spacing` is type metrics, not layout spacing.
95
+ if (/font|size|leading|line-?height|letter|tracking|weight|heading|body|type/i.test(n)) {
96
+ return "typography";
97
+ }
98
+ if (/spacing|gap|padding|margin|space/i.test(n))
99
+ return "spacing";
100
+ if (/shadow|blur|drop/i.test(n))
101
+ return "shadow";
102
+ if (COLOR_NAME_HINT.test(n) && !DIMENSION_VALUE.test(value.trim())) {
103
+ return "color";
104
+ }
105
+ return "other";
106
+ }
107
+ /** Title-case display name derived from a CSS var, e.g. `--primary-color`. */
108
+ export function friendlyTokenName(cssVar) {
109
+ return cssVar
110
+ .replace(/^--/, "")
111
+ .replace(/[-_]/g, " ")
112
+ .replace(/\b\w/g, (c) => c.toUpperCase());
113
+ }
114
+ function readString(source, key) {
115
+ const value = source[key];
116
+ return typeof value === "string" ? value.trim() : "";
117
+ }
118
+ /**
119
+ * Coerce untrusted token JSON into storable tokens, reporting every entry it
120
+ * could not accept. Later duplicates of a `cssVar` win, matching the overlay
121
+ * order importers use.
122
+ */
123
+ export function normalizeBrandKitTokens(input) {
124
+ if (input === undefined || input === null) {
125
+ return { tokens: [], rejected: [] };
126
+ }
127
+ if (!Array.isArray(input)) {
128
+ return { tokens: [], rejected: [{ reason: "malformed", label: "tokens" }] };
129
+ }
130
+ const byCssVar = new Map();
131
+ const rejected = [];
132
+ for (const entry of input) {
133
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
134
+ rejected.push({ reason: "malformed", label: String(entry) });
135
+ continue;
136
+ }
137
+ const record = entry;
138
+ const cssVar = readString(record, "cssVar");
139
+ const value = readString(record, "value");
140
+ const name = readString(record, "name") || friendlyTokenName(cssVar);
141
+ if (!cssVar || !isSafeCssVarName(cssVar)) {
142
+ rejected.push({ reason: "unsafe-css-var", label: cssVar || name });
143
+ continue;
144
+ }
145
+ if (!isSafeCssTokenValue(value)) {
146
+ rejected.push({ reason: "unsafe-value", label: cssVar });
147
+ continue;
148
+ }
149
+ const declaredType = readString(record, "type");
150
+ const group = readString(record, "group");
151
+ const source = readString(record, "source");
152
+ byCssVar.set(cssVar, {
153
+ name,
154
+ cssVar,
155
+ value,
156
+ type: BRAND_KIT_TOKEN_TYPES.includes(declaredType)
157
+ ? declaredType
158
+ : classifyBrandKitToken(cssVar, value),
159
+ ...(group ? { group } : {}),
160
+ ...(source ? { source } : {}),
161
+ });
162
+ }
163
+ const tokens = [...byCssVar.values()];
164
+ if (tokens.length > MAX_BRAND_KIT_TOKENS) {
165
+ for (const token of tokens.slice(MAX_BRAND_KIT_TOKENS)) {
166
+ rejected.push({ reason: "over-limit", label: token.cssVar });
167
+ }
168
+ }
169
+ return { tokens: tokens.slice(0, MAX_BRAND_KIT_TOKENS), rejected };
170
+ }
171
+ /** Human-readable summary of why tokens were refused, for thrown errors. */
172
+ export function describeBrandKitTokenRejections(rejected) {
173
+ return rejected
174
+ .slice(0, 10)
175
+ .map((entry) => `${entry.label} (${entry.reason})`)
176
+ .join(", ");
177
+ }
178
+ /** The CSS variable each colour role is published under. */
179
+ const BRAND_KIT_COLOR_ROLE_VARS = {
180
+ primary: "--color-primary",
181
+ secondary: "--color-secondary",
182
+ accent: "--color-accent",
183
+ background: "--color-background",
184
+ surface: "--color-surface",
185
+ text: "--color-text",
186
+ textMuted: "--color-text-muted",
187
+ };
188
+ /**
189
+ * The seven colour roles plus radius/spacing, as tokens. Derived from a kit's
190
+ * summary fields rather than named by the source system, so they carry no
191
+ * `group`.
192
+ */
193
+ export function brandKitRoleTokens(data, source = "Brand Kit") {
194
+ if (!data)
195
+ return [];
196
+ const out = [];
197
+ const push = (cssVar, value) => {
198
+ if (typeof value !== "string" || !value.trim())
199
+ return;
200
+ if (!isSafeCssVarName(cssVar) || !isSafeCssTokenValue(value.trim()))
201
+ return;
202
+ out.push({
203
+ name: friendlyTokenName(cssVar),
204
+ cssVar,
205
+ value: value.trim(),
206
+ type: classifyBrandKitToken(cssVar, value.trim()),
207
+ source,
208
+ });
209
+ };
210
+ const colors = (data.colors ?? {});
211
+ const spacing = (data.spacing ?? {});
212
+ for (const [role, cssVar] of Object.entries(BRAND_KIT_COLOR_ROLE_VARS)) {
213
+ push(cssVar, colors[role]);
214
+ }
215
+ push("--radius", data.borders?.radius);
216
+ push("--spacing-element-gap", spacing.elementGap);
217
+ push("--spacing-page-padding", spacing.pagePadding);
218
+ return out;
219
+ }
220
+ const CSS_CUSTOM_PROPERTY_DECLARATION = /(--[-_a-zA-Z0-9]+)\s*:\s*([^;{}]+)[;}]/g;
221
+ /**
222
+ * Read a kit's named tokens out of a raw CSS block. A kit's `customCSS` already
223
+ * spells out the source system's real token names. Declaration order wins on
224
+ * duplicates, matching the cascade.
225
+ */
226
+ export function parseBrandKitTokensFromCss(css, source) {
227
+ const byCssVar = new Map();
228
+ for (const match of css.matchAll(CSS_CUSTOM_PROPERTY_DECLARATION)) {
229
+ const cssVar = match[1];
230
+ const value = match[2].trim();
231
+ if (!isSafeCssVarName(cssVar) || !isSafeCssTokenValue(value))
232
+ continue;
233
+ byCssVar.set(cssVar, {
234
+ name: cssVar.replace(/^--/, ""),
235
+ cssVar,
236
+ value,
237
+ type: classifyBrandKitToken(cssVar, value),
238
+ ...(source ? { source } : {}),
239
+ });
240
+ }
241
+ return [...byCssVar.values()].slice(0, MAX_BRAND_KIT_TOKENS);
242
+ }
243
+ /**
244
+ * The kit's named vocabulary: its stored `tokens` when it has them, else the
245
+ * names its `customCSS` declares. Kits predating `tokens` still carry real names
246
+ * in CSS, so reading only `tokens` would report them as having none.
247
+ */
248
+ export function resolveBrandKitTokens(data, cssSource) {
249
+ if (!data)
250
+ return [];
251
+ const stored = normalizeBrandKitTokens(data.tokens).tokens;
252
+ if (stored.length > 0)
253
+ return stored;
254
+ return typeof data.customCSS === "string"
255
+ ? parseBrandKitTokensFromCss(data.customCSS, cssSource)
256
+ : [];
257
+ }
258
+ /**
259
+ * Group tokens for display, preserving the source's collection paths. Groups sort
260
+ * by token type so colors lead, then alphabetically within a type.
261
+ */
262
+ export function groupBrandKitTokens(tokens) {
263
+ const groups = new Map();
264
+ for (const token of tokens) {
265
+ const label = token.group?.trim() || token.type;
266
+ const key = `${token.type}::${label}`;
267
+ const existing = groups.get(key);
268
+ if (existing) {
269
+ existing.tokens.push(token);
270
+ continue;
271
+ }
272
+ groups.set(key, { label, type: token.type, tokens: [token] });
273
+ }
274
+ return [...groups.values()].sort((a, b) => {
275
+ const byType = BRAND_KIT_TOKEN_TYPES.indexOf(a.type) -
276
+ BRAND_KIT_TOKEN_TYPES.indexOf(b.type);
277
+ return byType !== 0 ? byType : a.label.localeCompare(b.label);
278
+ });
279
+ }
@@ -23,6 +23,25 @@ export interface BrandKitColors {
23
23
  text: string;
24
24
  textMuted: string;
25
25
  }
26
+ /** Semantic category a {@link BrandKitToken} belongs to. */
27
+ export type BrandKitTokenType = "color" | "typography" | "spacing" | "radius" | "shadow" | "other";
28
+ /**
29
+ * One token under the name its source design system uses. The seven
30
+ * {@link BrandKitColors} roles are a summary view, not a substitute: collapsing
31
+ * `interactive-01` into `secondary` discards the only name the design team has.
32
+ */
33
+ export interface BrandKitToken {
34
+ name: string;
35
+ /** e.g. `--cds-interactive-01`. */
36
+ cssVar: string;
37
+ /** Resolved value, e.g. a hex color or `0.5rem`. */
38
+ value: string;
39
+ type: BrandKitTokenType;
40
+ /** Collection path as the source organises it, e.g. `Colors/Interactive`. */
41
+ group?: string;
42
+ /** e.g. `Carbon v11` or `globals.css`. */
43
+ source?: string;
44
+ }
26
45
  /** Brand typography system. */
27
46
  export interface BrandKitTypography {
28
47
  headingFont: string;
@@ -76,6 +95,12 @@ export interface BrandKitData {
76
95
  spacing: BrandKitSpacing;
77
96
  borders: BrandKitBorders;
78
97
  logos: BrandKitLogo[];
98
+ /**
99
+ * The source system's full named vocabulary. Optional because kits predating
100
+ * it store only the role summary - absent means "never extracted", not "the
101
+ * system has none".
102
+ */
103
+ tokens?: BrandKitToken[];
79
104
  imageStyle?: BrandKitImageStyle;
80
105
  customCSS?: string;
81
106
  notes?: string;
@@ -1440,16 +1440,16 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
1440
1440
  const agentEngineConfigured = useAgentEngineConfigured(providerStatusChecksEnabled, { tabId, threadId });
1441
1441
  const modelCatalogMissing = modelCatalogConfirmsMissing(availableModels, modelListLoading);
1442
1442
  // The model picker has already resolved the same provider status shown in
1443
- // its setup choices. Use that settled signal to surface setup before a slow
1444
- // readiness request can accept a doomed submit; a later canonical configured
1445
- // response always wins over a stale catalog.
1443
+ // its setup choices. Keep the signal available for the authoritative missing
1444
+ // state, but do not surface setup while the readiness request is unresolved.
1446
1445
  const missingApiKey = agentEngineConfigured.state !== "configured" &&
1447
1446
  (agentEngineConfigured.missing || modelCatalogMissing);
1448
- // Unknown and unavailable are setup-required for this surface. Letting a
1449
- // submit through while the status route is still unreachable creates a run
1450
- // that can sit on "Thinking" forever when no provider is configured.
1447
+ // Unknown and unavailable mean we do not know yet. Only an authoritative
1448
+ // missing response may replace the composer with the setup card; otherwise
1449
+ // connected users briefly see a false "Connect AI" state on first mount.
1451
1450
  const engineSetupRequired = providerStatusChecksEnabled &&
1452
- (agentEngineConfigured.state !== "configured" || missingApiKey);
1451
+ agentEngineConfigured.state === "missing" &&
1452
+ missingApiKey;
1453
1453
  const isComposerDisabled = composerDisabled;
1454
1454
  const [missingKeySetupOpen, setMissingKeySetupOpen] = useState(false);
1455
1455
  const requestMissingKeySetup = useCallback(() => {
@@ -10,6 +10,7 @@ import { formatMcpServerError, useCreateMcpServer, useMcpServers, } from "../res
10
10
  import { useBuilderConnectFlow } from "../settings/useBuilderStatus.js";
11
11
  import { cn } from "../utils.js";
12
12
  import { shouldSkipFirstRunIntegrations } from "./first-run-enabled.js";
13
+ import { listFirstRunOnboardingExtensions } from "./first-run-registry.js";
13
14
  import { useOnboarding } from "./use-onboarding.js";
14
15
  import { useOnboardingPreviewMode } from "./use-preview-mode.js";
15
16
  const BUILDER_MORE_SERVICES = [
@@ -26,10 +27,12 @@ export function FirstRunOnboarding({ skipIntegrations = shouldSkipFirstRunIntegr
26
27
  const previewMode = useOnboardingPreviewMode();
27
28
  const { firstRun, loading, error, profile, completeFirstRun } = useOnboarding({ preview: previewMode });
28
29
  const [screen, setScreen] = useState("intro");
30
+ const [extensionIndex, setExtensionIndex] = useState(0);
29
31
  const [integrationQuery, setIntegrationQuery] = useState("");
30
32
  const [integrationDialogId, setIntegrationDialogId] = useState(null);
31
33
  const [connectingIntegrationId, setConnectingIntegrationId] = useState(null);
32
34
  const [connectError, setConnectError] = useState(null);
35
+ const extensions = useMemo(() => listFirstRunOnboardingExtensions(), []);
33
36
  const mcpCatalog = useMemo(() => getDefaultMcpIntegrations(), []);
34
37
  const mcpServersQuery = useMcpServers();
35
38
  const createMcpServer = useCreateMcpServer();
@@ -86,8 +89,13 @@ export function FirstRunOnboarding({ skipIntegrations = shouldSkipFirstRunIntegr
86
89
  }));
87
90
  await completeFirstRun();
88
91
  };
89
- const handleFinish = async () => {
90
- await completeFirstRun();
92
+ const handleFinish = () => {
93
+ if (extensions.length === 0) {
94
+ void completeFirstRun();
95
+ return;
96
+ }
97
+ setExtensionIndex(0);
98
+ setScreen("extension");
91
99
  };
92
100
  const returnUrl = typeof window === "undefined"
93
101
  ? "/"
@@ -137,6 +145,22 @@ export function FirstRunOnboarding({ skipIntegrations = shouldSkipFirstRunIntegr
137
145
  }
138
146
  setIntegrationDialogId(integration.id);
139
147
  };
148
+ if (screen === "extension") {
149
+ const extension = extensions[extensionIndex];
150
+ if (!extension) {
151
+ void completeFirstRun();
152
+ return null;
153
+ }
154
+ const Extension = extension.component;
155
+ const advanceExtension = () => {
156
+ if (extensionIndex < extensions.length - 1) {
157
+ setExtensionIndex((current) => current + 1);
158
+ return;
159
+ }
160
+ void completeFirstRun();
161
+ };
162
+ return (_jsx(Extension, { onComplete: advanceExtension, onSkip: () => void completeFirstRun() }));
163
+ }
140
164
  if (screen === "intro") {
141
165
  return (_jsx(OnboardingShell, { profile: profile, screen: "intro", children: _jsxs("div", { className: "mx-auto flex w-full max-w-lg flex-col items-center text-center", children: [_jsxs("h1", { className: "text-3xl font-semibold tracking-[-0.05em] sm:text-4xl", children: ["Free forever.", _jsx("br", {}), _jsx("span", { className: "text-primary", children: "Open source for life." })] }), _jsxs("div", { className: "mt-7 grid w-full gap-2 text-left sm:grid-cols-3", children: [_jsxs("div", { className: "rounded-lg border border-border bg-card px-3 py-3", children: [_jsx("p", { className: "text-xs font-medium", children: "Fully customizable" }), _jsx("p", { className: "mt-1 text-[11px] leading-4 text-muted-foreground", children: "Change the UI, code, and behavior." })] }), _jsxs("div", { className: "rounded-lg border border-border bg-card px-3 py-3", children: [_jsx("p", { className: "text-xs font-medium", children: "Bring your own keys" }), _jsx("p", { className: "mt-1 text-[11px] leading-4 text-muted-foreground", children: "Use your own providers and accounts." })] }), _jsxs("div", { className: "rounded-lg border border-border bg-card px-3 py-3", children: [_jsx("p", { className: "text-xs font-medium", children: "Build your own" }), _jsx("p", { className: "mt-1 text-[11px] leading-4 text-muted-foreground", children: "Mix and match toolkit pieces in your own apps." })] })] }), _jsxs("button", { type: "button", className: cn(primaryButtonClass, "mt-6"), onClick: () => setScreen("choice"), children: ["Continue", _jsx(IconArrowRight, { size: 15 })] }), _jsxs("a", { href: "https://github.com/builderio/agent-native", target: "_blank", rel: "noreferrer", className: "mt-3 inline-flex items-center gap-1.5 text-xs text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: [_jsx(IconBrandGithub, { size: 14 }), _jsx("span", { children: "View source" }), _jsx(IconExternalLink, { size: 13 })] })] }) }));
142
166
  }
@@ -0,0 +1,25 @@
1
+ import type { ComponentType } from "react";
2
+ /**
3
+ * Props passed to an app-specific first-run onboarding step.
4
+ *
5
+ * Register one component per screen when an app needs a continuous, full-screen
6
+ * flow after the shared connection and agent-introduction screens.
7
+ */
8
+ export interface FirstRunOnboardingExtensionProps {
9
+ /** Finish this step and open the next registered step, if any. */
10
+ onComplete: () => void;
11
+ /** Abort first-run onboarding without opening another app-specific step. */
12
+ onSkip: () => void;
13
+ }
14
+ /** A full-screen app step that follows the framework's connection setup. */
15
+ export interface FirstRunOnboardingExtension {
16
+ id: string;
17
+ component: ComponentType<FirstRunOnboardingExtensionProps>;
18
+ }
19
+ /**
20
+ * Register the app-owned part of first-run onboarding. Registration is
21
+ * replaceable by id so Vite reloads do not stack duplicate screens.
22
+ */
23
+ export declare function registerFirstRunOnboardingExtension(extension: FirstRunOnboardingExtension): void;
24
+ /** Return app-specific first-run screens in registration order. */
25
+ export declare function listFirstRunOnboardingExtensions(): readonly FirstRunOnboardingExtension[];
@@ -0,0 +1,18 @@
1
+ let extensions = [];
2
+ /**
3
+ * Register the app-owned part of first-run onboarding. Registration is
4
+ * replaceable by id so Vite reloads do not stack duplicate screens.
5
+ */
6
+ export function registerFirstRunOnboardingExtension(extension) {
7
+ if (!extension.id.trim()) {
8
+ throw new Error("registerFirstRunOnboardingExtension: extension.id is required");
9
+ }
10
+ extensions = [
11
+ ...extensions.filter((current) => current.id !== extension.id),
12
+ extension,
13
+ ];
14
+ }
15
+ /** Return app-specific first-run screens in registration order. */
16
+ export function listFirstRunOnboardingExtensions() {
17
+ return extensions;
18
+ }
@@ -9,5 +9,7 @@ export { OnboardingPanel } from "./OnboardingPanel.js";
9
9
  export { OnboardingBanner } from "./OnboardingBanner.js";
10
10
  export { SetupButton } from "./SetupButton.js";
11
11
  export { FirstRunOnboarding } from "./FirstRunOnboarding.js";
12
+ export { listFirstRunOnboardingExtensions, registerFirstRunOnboardingExtension, } from "./first-run-registry.js";
13
+ export type { FirstRunOnboardingExtension, FirstRunOnboardingExtensionProps, } from "./first-run-registry.js";
12
14
  export type { OnboardingStep, OnboardingMethod, OnboardingMethodBadge, OnboardingFormField, OnboardingStepStatus, OnboardingCapability, OnboardingAppProfile, } from "../../onboarding/types.js";
13
15
  export { registerOnboardingStep, listOnboardingSteps, } from "../../onboarding/registry.js";
@@ -9,4 +9,5 @@ export { OnboardingPanel } from "./OnboardingPanel.js";
9
9
  export { OnboardingBanner } from "./OnboardingBanner.js";
10
10
  export { SetupButton } from "./SetupButton.js";
11
11
  export { FirstRunOnboarding } from "./FirstRunOnboarding.js";
12
+ export { listFirstRunOnboardingExtensions, registerFirstRunOnboardingExtension, } from "./first-run-registry.js";
12
13
  export { registerOnboardingStep, listOnboardingSteps, } from "../../onboarding/registry.js";
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
62
62
  error: string;
63
63
  states?: undefined;
64
64
  } | {
65
- error?: undefined;
66
65
  states: {
67
66
  clientId: number;
68
67
  state: string;
69
68
  }[];
69
+ error?: undefined;
70
70
  }>>;
71
71
  /**
72
72
  * GET /_agent-native/collab/:docId/users
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
77
77
  error: string;
78
78
  users?: undefined;
79
79
  } | {
80
- error?: undefined;
81
80
  users: {
82
81
  clientId: number;
83
82
  lastSeen: number;
84
83
  }[];
84
+ error?: undefined;
85
85
  }>>;
@@ -26,8 +26,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
26
26
  * Body: { update: string (base64), requestSource?: string }
27
27
  */
28
28
  export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
29
- error: string;
30
29
  ok?: undefined;
30
+ error: string;
31
31
  } | {
32
32
  error?: undefined;
33
33
  ok: boolean;
@@ -13,8 +13,8 @@
13
13
  * Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
14
14
  */
15
15
  export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
16
- error: string;
17
16
  ok?: undefined;
17
+ error: string;
18
18
  } | {
19
19
  error?: undefined;
20
20
  ok: boolean;
@@ -21,7 +21,7 @@ export declare function createDb(config: DbConfig): Promise<(import("drizzle-orm
21
21
  })>;
22
22
  export type DrizzleDb = Awaited<ReturnType<typeof createDb>>;
23
23
  export { createGetDb } from "./create-get-db.js";
24
- export { runMigrations, type MigrationEntry, type MigrationSql, } from "./migrations.js";
24
+ export { deferMigration, MIGRATION_DEFERRED, runMigrations, type MigrationEntry, type MigrationRunResult, type MigrationSql, } from "./migrations.js";
25
25
  export { getDbExec, createDbExec, getDatabaseUrl, getDialect, isLocalDatabase, isPostgres, intType, closeDbExec, type DbExec, type DbExecConfig, type DbExecQuery, type DbExecStatement, type Dialect, } from "./client.js";
26
26
  export { table, text, integer, now } from "./schema.js";
27
27
  export { ensureAdditiveColumns, type EnsureAdditiveColumnsOptions, type EnsureAdditiveColumnsResult, type EnsureAdditiveColumnsLogger, } from "./ensure-additive-columns.js";
package/dist/db/index.js CHANGED
@@ -20,7 +20,7 @@ export async function createDb(config) {
20
20
  throw new Error(`Unsupported driver: ${config.driver}`);
21
21
  }
22
22
  export { createGetDb } from "./create-get-db.js";
23
- export { runMigrations, } from "./migrations.js";
23
+ export { deferMigration, MIGRATION_DEFERRED, runMigrations, } from "./migrations.js";
24
24
  export { getDbExec, createDbExec, getDatabaseUrl, getDialect, isLocalDatabase, isPostgres, intType, closeDbExec, } from "./client.js";
25
25
  export { table, text, integer, now } from "./schema.js";
26
26
  export { ensureAdditiveColumns, } from "./ensure-additive-columns.js";
@@ -53,6 +53,14 @@ export type MigrationSql = string | {
53
53
  postgres?: string;
54
54
  sqlite?: string;
55
55
  };
56
+ /**
57
+ * A run-only migration can defer its work without recording itself as applied.
58
+ * This is for serialized backfills where another instance owns the advisory
59
+ * lock; throwing would turn an expected loser into a startup retry storm.
60
+ */
61
+ export declare const MIGRATION_DEFERRED: unique symbol;
62
+ export declare function deferMigration(): typeof MIGRATION_DEFERRED;
63
+ export type MigrationRunResult = void | typeof MIGRATION_DEFERRED;
56
64
  export interface MigrationEntry {
57
65
  version: number;
58
66
  sql: MigrationSql;
@@ -68,11 +76,13 @@ export interface MigrationEntry {
68
76
  * JS step for a backfill SQL cannot express — e.g. a repair whose value comes
69
77
  * from application-level parsing of a stored blob. Runs BEFORE this entry's
70
78
  * SQL and before the bookkeeping row is written, so a throw leaves the
71
- * migration unrecorded and it retries on the next boot. Pass `sql: {}` for a
72
- * run-only entry. Give run-only entries a `name`: the legacy version gate
79
+ * migration unrecorded and it retries on the next boot. Return
80
+ * `deferMigration()` when another instance owns a serialized backfill; the
81
+ * entry stays pending without logging a startup failure. Pass `sql: {}` for
82
+ * a run-only entry. Give run-only entries a `name`: the legacy version gate
73
83
  * would otherwise mark them applied via any later migration's MAX advance.
74
84
  */
75
- run?: () => Promise<void>;
85
+ run?: () => Promise<MigrationRunResult>;
76
86
  }
77
87
  /**
78
88
  * Runs a list of migrations against the configured database, gated by a