@agent-native/core 0.187.0 → 0.187.1-nightly-20260925003003

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 (158) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +4 -3
  3. package/corpus/templates/analytics/changelog/2026-09-24-analytics-sidebar-footer-aligns-consistently.md +6 -0
  4. package/corpus/templates/analytics/changelog/2026-09-24-dashboard-emails-match-stacked-area-totals.md +6 -0
  5. package/corpus/templates/analytics/server/lib/report-chart-svg.ts +47 -20
  6. package/corpus/templates/calendar/app/components/calendar/AttendeeAutocomplete.tsx +1 -1
  7. package/corpus/templates/calendar/changelog/2026-09-24-guest-inputs-wrap-beneath-existing-guests-so-the-active-inpu.md +6 -0
  8. package/corpus/templates/clips/app/components/editor/stitch-manager.tsx +8 -3
  9. package/corpus/templates/clips/app/lib/recording-link.ts +21 -0
  10. package/corpus/templates/clips/app/routes/record.tsx +27 -8
  11. package/corpus/templates/clips/changelog/2026-09-24-the-camera-bubble-stays-visible-while-you-reposition-it-befo.md +6 -0
  12. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +7 -0
  13. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +63 -55
  14. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +251 -74
  15. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +66 -56
  16. package/corpus/templates/design/app/components/design/EditPanel.tsx +19 -0
  17. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +59 -9
  18. package/corpus/templates/design/app/components/design/edit-panel/interaction-state-helpers.ts +4 -0
  19. package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +119 -0
  20. package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +48 -51
  21. package/corpus/templates/design/app/components/design/edit-panel/style-change-types.ts +1 -0
  22. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +298 -187
  23. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +2 -2
  24. package/corpus/templates/design/app/components/design/inspector/index.ts +4 -0
  25. package/corpus/templates/design/app/components/design/multi-screen/types.ts +4 -2
  26. package/corpus/templates/design/app/i18n/en-US.ts +6 -0
  27. package/corpus/templates/design/app/pages/Present.tsx +101 -87
  28. package/corpus/templates/design/app/pages/design-editor/clone-and-pen-edit.ts +75 -14
  29. package/corpus/templates/design/app/pages/design-editor/commands/README.md +1 -0
  30. package/corpus/templates/design/app/pages/design-editor/commands/commit-relative-style-delta-to-selected-layers.ts +69 -56
  31. package/corpus/templates/design/app/pages/design-editor/commands/mode-change.ts +16 -0
  32. package/corpus/templates/design/app/pages/design-editor/commands/scale-selection.ts +17 -2
  33. package/corpus/templates/design/app/pages/design-editor/commands/styles-change.ts +23 -30
  34. package/corpus/templates/design/app/pages/design-editor/commands/swap-fill-stroke.ts +105 -0
  35. package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +21 -0
  36. package/corpus/templates/design/app/pages/present-review-state.ts +7 -1
  37. package/corpus/templates/design/changelog/2026-09-24-a-vector-s-stroke-row-shows-position-beside-weight-instead-o.md +6 -0
  38. package/corpus/templates/design/changelog/2026-09-24-css-margins-can-be-adjusted-on-the-canvas-and-in-the-layout-.md +6 -0
  39. package/corpus/templates/design/changelog/2026-09-24-design-reviews-show-the-real-canvas-beside-its-conversation.md +6 -0
  40. package/corpus/templates/design/changelog/2026-09-24-double-clicking-text-or-a-vector-on-the-board-starts-editing.md +6 -0
  41. package/corpus/templates/design/changelog/2026-09-24-interact-opens-a-focused-responsive-preview-without-reloadin.md +6 -0
  42. package/corpus/templates/design/changelog/2026-09-24-keep-gradient-values-visible-and-limit-canvas-paints-to-supp.md +6 -0
  43. package/corpus/templates/design/changelog/2026-09-24-open-pen-paths-no-longer-paint-a-filled-triangle-after-shift.md +6 -0
  44. package/corpus/templates/design/changelog/2026-09-24-re-clicking-the-active-responsive-preview-returns-to-the-can.md +6 -0
  45. package/corpus/templates/design/changelog/2026-09-24-resized-vectors-can-be-point-edited-and-the-k-scale-tool-sho.md +6 -0
  46. package/corpus/templates/design/changelog/2026-09-24-while-editing-text-click-to-place-the-caret-and-double-click.md +6 -0
  47. package/corpus/templates/design/e2e/helpers.ts +19 -2
  48. package/corpus/templates/mail/app/components/email/ComposeModal.tsx +1 -1
  49. package/corpus/templates/mail/changelog/2026-09-24-gmail-push-ack.md +6 -0
  50. package/corpus/templates/mail/changelog/2026-09-24-the-composer-keeps-its-taller-layout-in-a-narrower-window.md +6 -0
  51. package/corpus/templates/mail/server/db/schema.ts +26 -1
  52. package/corpus/templates/mail/server/lib/inbox-store.ts +68 -23
  53. package/corpus/templates/mail/server/lib/inbox-sync.ts +43 -17
  54. package/corpus/templates/mail/server/plugins/db.ts +19 -0
  55. package/corpus/templates/mail/server/routes/api/gmail/push.post.ts +4 -15
  56. package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +45 -30
  57. package/corpus/templates/slides/.agents/skills/slide-editing/SKILL.md +18 -2
  58. package/corpus/templates/slides/actions/export-html.ts +28 -17
  59. package/corpus/templates/slides/app/hooks/use-deck-design-system.ts +27 -15
  60. package/corpus/templates/slides/app/pages/DeckEditor.tsx +1 -1
  61. package/corpus/templates/slides/changelog/2026-09-23-decks-with-no-design-system-now-follow-a-theme-chosen-for-th.md +6 -0
  62. package/corpus/templates/slides/shared/slide-background.ts +3 -1
  63. package/dist/agent/engine/builder-engine.js +11 -0
  64. package/dist/agent/engine/types.d.ts +1 -0
  65. package/dist/agent/production-agent.d.ts +2 -0
  66. package/dist/agent/production-agent.js +32 -4
  67. package/dist/agent/run-loop-with-resume.js +6 -0
  68. package/dist/cli/index.js +4 -4
  69. package/dist/cli/process.d.ts +10 -1
  70. package/dist/cli/process.js +66 -0
  71. package/dist/cli/workspace-dev.js +2 -0
  72. package/dist/client/agent-chat.js +13 -14
  73. package/dist/client/error-format.js +4 -4
  74. package/dist/client/observability/ObservabilityDashboard.js +95 -65
  75. package/dist/client/observability/OutputPreview.d.ts +2 -1
  76. package/dist/client/observability/OutputPreview.js +64 -7
  77. package/dist/client/observability/useObservability.d.ts +2 -1
  78. package/dist/client/observability/useObservability.js +5 -1
  79. package/dist/client/settings/UsageSection.js +105 -17
  80. package/dist/collab/routes.d.ts +1 -1
  81. package/dist/collab/struct-routes.d.ts +1 -1
  82. package/dist/db/client.d.ts +44 -0
  83. package/dist/db/client.js +62 -3
  84. package/dist/db/create-get-db.js +23 -1
  85. package/dist/db/embedded-runtime.d.ts +21 -0
  86. package/dist/db/embedded-runtime.js +27 -0
  87. package/dist/db/migrations.js +2 -0
  88. package/dist/db/server-runtime.d.ts +22 -0
  89. package/dist/db/server-runtime.js +26 -0
  90. package/dist/deploy/build.d.ts +29 -0
  91. package/dist/deploy/build.js +204 -121
  92. package/dist/feature-flags/index.d.ts +1 -1
  93. package/dist/feature-flags/index.js +1 -1
  94. package/dist/feature-flags/registry.d.ts +1 -0
  95. package/dist/feature-flags/registry.js +5 -0
  96. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  97. package/dist/index.d.ts +1 -1
  98. package/dist/index.js +1 -1
  99. package/dist/integrations/webhook-handler.js +4 -1
  100. package/dist/jobs/background-automation-runner.js +6 -1
  101. package/dist/localization/core-messages/ar-SA.js +9 -1
  102. package/dist/localization/core-messages/de-DE.js +9 -1
  103. package/dist/localization/core-messages/en-US.d.ts +9 -1
  104. package/dist/localization/core-messages/en-US.js +9 -1
  105. package/dist/localization/core-messages/es-ES.js +9 -1
  106. package/dist/localization/core-messages/fr-FR.js +9 -1
  107. package/dist/localization/core-messages/hi-IN.js +9 -1
  108. package/dist/localization/core-messages/ja-JP.js +9 -1
  109. package/dist/localization/core-messages/ko-KR.js +9 -1
  110. package/dist/localization/core-messages/pt-BR.js +9 -1
  111. package/dist/localization/core-messages/supplemental/ar-SA.js +1 -0
  112. package/dist/localization/core-messages/supplemental/de-DE.js +1 -0
  113. package/dist/localization/core-messages/supplemental/en-US.js +1 -0
  114. package/dist/localization/core-messages/supplemental/es-ES.js +1 -0
  115. package/dist/localization/core-messages/supplemental/fr-FR.js +1 -0
  116. package/dist/localization/core-messages/supplemental/hi-IN.js +1 -0
  117. package/dist/localization/core-messages/supplemental/ja-JP.js +1 -0
  118. package/dist/localization/core-messages/supplemental/ko-KR.js +1 -0
  119. package/dist/localization/core-messages/supplemental/pt-BR.js +1 -0
  120. package/dist/localization/core-messages/supplemental/zh-CN.js +1 -0
  121. package/dist/localization/core-messages/supplemental/zh-TW.js +1 -0
  122. package/dist/localization/core-messages/zh-CN.js +9 -1
  123. package/dist/localization/core-messages/zh-TW.js +9 -1
  124. package/dist/localization/core-messages.d.ts +9 -1
  125. package/dist/localization/mcp-settings-messages.d.ts +1 -0
  126. package/dist/mcp/connect-route.js +45 -8
  127. package/dist/mcp/connect-store.d.ts +7 -5
  128. package/dist/mcp/connect-store.js +30 -39
  129. package/dist/notifications/routes.d.ts +3 -3
  130. package/dist/observability/actions/get-observability-review-detail.d.ts +7 -0
  131. package/dist/observability/actions/get-observability-review-detail.js +25 -0
  132. package/dist/observability/reviews.d.ts +9 -1
  133. package/dist/observability/reviews.js +20 -0
  134. package/dist/observability/routes.d.ts +3 -3
  135. package/dist/observability/types.d.ts +8 -0
  136. package/dist/org/handlers.d.ts +1 -1
  137. package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
  138. package/dist/provider-api/actions/provider-api.d.ts +9 -9
  139. package/dist/scripts/db/query.js +6 -1
  140. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  141. package/dist/server/agent-engine-ollama-models-route.d.ts +2 -2
  142. package/dist/server/agent-teams.js +4 -1
  143. package/dist/server/auth.js +60 -20
  144. package/dist/server/better-auth-instance.js +7 -1
  145. package/dist/server/core-routes-plugin.d.ts +1 -1
  146. package/dist/server/core-routes-plugin.js +2 -0
  147. package/dist/server/embedded.js +6 -0
  148. package/dist/server/framework-request-handler.js +6 -0
  149. package/dist/server/realtime-token.d.ts +1 -1
  150. package/dist/usage/actions/get-usage-metrics.js +4 -1
  151. package/dist/usage/metrics-store.d.ts +19 -0
  152. package/dist/usage/metrics-store.js +106 -14
  153. package/dist/usage/store.d.ts +7 -2
  154. package/dist/usage/store.js +25 -4
  155. package/dist/vite/action-types-plugin.js +4 -0
  156. package/dist/vite/client.d.ts +6 -1
  157. package/dist/vite/client.js +40 -1
  158. package/package.json +4 -3
@@ -27,17 +27,18 @@ import {
27
27
  } from "./google-auth.js";
28
28
  import { classifyAutomated } from "./inbox-classify.js";
29
29
  import {
30
- assertSyncClaimHeld,
31
30
  claimSyncAccount,
32
31
  deleteInboxThreadRow,
33
32
  ensureSyncAccountRow,
34
33
  markThreadsOutOfInboxBeforeSync,
35
34
  patchSyncAccount,
35
+ readInboxPushGeneration,
36
36
  readSyncAccounts,
37
37
  releaseSyncAccount,
38
38
  resetSyncAccountProgress,
39
39
  SyncClaimLostError,
40
40
  upsertInboxThreadRows,
41
+ withSyncClaim,
41
42
  type CachedGmailLabel,
42
43
  type SyncAccountPatch,
43
44
  type SyncAccountRow,
@@ -297,12 +298,17 @@ async function hydrateAndApply(
297
298
  else deletes.push({ id: part.id, readStartedAt });
298
299
  }
299
300
  }
300
- // Fenced immediately before this step's row writes: a claim lost to a
301
- // newer worker during the Gmail round trips above must not land here.
302
- await assertSyncClaimHeld(ownerEmail, accountEmail, claimId);
303
- if (upserts.length > 0) await upsertInboxThreadRows(upserts);
304
- for (const { id, readStartedAt } of deletes)
305
- await deleteInboxThreadRow(ownerEmail, accountEmail, id, readStartedAt);
301
+ await withSyncClaim(ownerEmail, accountEmail, claimId, async (tx) => {
302
+ if (upserts.length > 0) await upsertInboxThreadRows(upserts, tx);
303
+ for (const { id, readStartedAt } of deletes)
304
+ await deleteInboxThreadRow(
305
+ ownerEmail,
306
+ accountEmail,
307
+ id,
308
+ readStartedAt,
309
+ tx,
310
+ );
311
+ });
306
312
  }
307
313
 
308
314
  async function runFullSyncStep(
@@ -348,10 +354,9 @@ async function runFullSyncStep(
348
354
  accountEmail,
349
355
  connected,
350
356
  );
351
- // Fenced immediately before the page's row writes: a claim lost to a
352
- // newer worker during the Gmail round trips above must not land here.
353
- await assertSyncClaimHeld(ownerEmail, accountEmail, claimId);
354
- await upsertInboxThreadRows(rows);
357
+ await withSyncClaim(ownerEmail, accountEmail, claimId, (tx) =>
358
+ upsertInboxThreadRows(rows, tx),
359
+ );
355
360
  }
356
361
  pageToken = page.nextPageToken;
357
362
  await patchProgress(ownerEmail, accountEmail, claimId, {
@@ -359,11 +364,13 @@ async function runFullSyncStep(
359
364
  });
360
365
 
361
366
  if (!pageToken) {
362
- await assertSyncClaimHeld(ownerEmail, accountEmail, claimId);
363
- await markThreadsOutOfInboxBeforeSync(
364
- ownerEmail,
365
- accountEmail,
366
- fullSyncStartedAt!,
367
+ await withSyncClaim(ownerEmail, accountEmail, claimId, (tx) =>
368
+ markThreadsOutOfInboxBeforeSync(
369
+ ownerEmail,
370
+ accountEmail,
371
+ fullSyncStartedAt!,
372
+ tx,
373
+ ),
367
374
  );
368
375
  await patchProgress(ownerEmail, accountEmail, claimId, {
369
376
  historyId: fullSyncHistoryId,
@@ -555,6 +562,7 @@ export async function syncInboxAccount(
555
562
  budgetMs?: number;
556
563
  force?: boolean;
557
564
  connectedAccountEmails?: readonly string[];
565
+ pushGeneration?: number;
558
566
  },
559
567
  ): Promise<InboxSyncAccountStatus> {
560
568
  const budgetMs = opts?.budgetMs ?? DEFAULT_BUDGET_MS;
@@ -573,6 +581,9 @@ export async function syncInboxAccount(
573
581
 
574
582
  let row = claim.row;
575
583
  try {
584
+ const pushGeneration =
585
+ opts?.pushGeneration ??
586
+ (await readInboxPushGeneration(ownerEmail, accountEmail));
576
587
  let client: { accessToken: string; email: string } | null;
577
588
  try {
578
589
  client = await getClientForConnectedAccount(ownerEmail, accountEmail);
@@ -647,6 +658,14 @@ export async function syncInboxAccount(
647
658
  : "idle";
648
659
  if (syncResult.changed) invalidateHistoryCacheForAccount(accountEmail);
649
660
  invalidateListCacheForOwner(ownerEmail);
661
+ if (
662
+ accountStatus.state === "ready" &&
663
+ pushGeneration > row.lastPushGeneration
664
+ ) {
665
+ await patchProgress(ownerEmail, accountEmail, claim.claimId, {
666
+ lastPushGeneration: pushGeneration,
667
+ });
668
+ }
650
669
  await releaseSyncAccount(ownerEmail, accountEmail, claim.claimId, dbStatus);
651
670
  return accountStatus;
652
671
  } catch (err) {
@@ -692,8 +711,14 @@ export async function ensureInboxFresh(
692
711
  const statuses = await Promise.all(
693
712
  emails.map(async (accountEmail) => {
694
713
  const row = await ensureSyncAccountRow(ownerEmail, accountEmail);
714
+ const pushGeneration = await readInboxPushGeneration(
715
+ ownerEmail,
716
+ accountEmail,
717
+ );
695
718
  const fresh =
696
- row.lastSyncedAt != null && now - row.lastSyncedAt < maxAgeMs;
719
+ row.lastPushGeneration >= pushGeneration &&
720
+ row.lastSyncedAt != null &&
721
+ now - row.lastSyncedAt < maxAgeMs;
697
722
  if (fresh) return statusFromRow(row);
698
723
  try {
699
724
  // Accounts run concurrently and share nothing — each has its own
@@ -701,6 +726,7 @@ export async function ensureInboxFresh(
701
726
  return await syncInboxAccount(ownerEmail, accountEmail, {
702
727
  budgetMs,
703
728
  connectedAccountEmails: accounts,
729
+ pushGeneration,
704
730
  });
705
731
  } catch (err) {
706
732
  // Belt-and-suspenders: syncInboxAccount already records failures on
@@ -281,6 +281,25 @@ ADD COLUMN IF NOT EXISTS local_mutation_history_id TEXT;
281
281
  ALTER TABLE mail_inbox_threads
282
282
  ADD COLUMN IF NOT EXISTS local_mutation_fields INTEGER`,
283
283
  },
284
+ {
285
+ version: 25,
286
+ name: "mail-inbox-push-invalidations",
287
+ sql: `CREATE TABLE IF NOT EXISTS mail_inbox_push_invalidations (
288
+ id TEXT PRIMARY KEY,
289
+ owner_email TEXT NOT NULL,
290
+ account_email TEXT NOT NULL
291
+ );
292
+ CREATE INDEX IF NOT EXISTS idx_mail_inbox_push_invalidations_owner_account
293
+ ON mail_inbox_push_invalidations(owner_email, account_email);`,
294
+ },
295
+ {
296
+ version: 26,
297
+ name: "mail-inbox-push-generation",
298
+ sql: `ALTER TABLE mail_inbox_push_invalidations
299
+ ADD COLUMN IF NOT EXISTS generation BIGINT NOT NULL DEFAULT 1;
300
+ ALTER TABLE mail_sync_accounts
301
+ ADD COLUMN IF NOT EXISTS last_push_generation BIGINT NOT NULL DEFAULT 0;`,
302
+ },
284
303
  ],
285
304
  { table: "mail_migrations" },
286
305
  );
@@ -12,7 +12,7 @@ import {
12
12
  bumpHistoryWatermark,
13
13
  invalidateListCacheForOwner,
14
14
  } from "../../../lib/google-auth.js";
15
- import { syncInboxAccount } from "../../../lib/inbox-sync.js";
15
+ import { recordInboxPushInvalidation } from "../../../lib/inbox-store.js";
16
16
 
17
17
  // Cache Google's public keys for OIDC verification. jose handles TTL + refresh.
18
18
  // https://cloud.google.com/pubsub/docs/push#validate_tokens
@@ -123,23 +123,12 @@ export default defineEventHandler(async (event: H3Event) => {
123
123
  bumpHistoryWatermark(emailAddress, historyId);
124
124
  if (owner) {
125
125
  invalidateListCacheForOwner(owner);
126
- // Best-effort: drive the synced inbox store off the same push instead
127
- // of waiting for the next 15s freshness check. A sync failure here
128
- // must never fail the push ack (Pub/Sub would just redeliver it).
129
- // Budget stays well under Pub/Sub's default 10s ack deadline — an
130
- // incremental history sync (the common case here) is ~1-2s, and a full
131
- // sync just resumes on the next request/poll since its progress is
132
- // persisted, so there's no need to risk redelivery chasing it to
133
- // completion synchronously (fire-and-forget after responding is killed
134
- // on Netlify Lambdas, so this can't run past the response either way).
135
- await syncInboxAccount(owner, emailAddress, { budgetMs: 4_000 }).catch(
136
- (syncErr: any) => {
137
- console.warn(`[gmail-push] inbox resync failed: ${syncErr?.message}`);
138
- },
139
- );
126
+ await recordInboxPushInvalidation(owner, emailAddress);
140
127
  }
141
128
  } catch (err: any) {
142
129
  console.warn(`[gmail-push] processing failed: ${err.message}`);
130
+ setResponseStatus(event, 500);
131
+ return { ok: false, error: "processing failed" };
143
132
  }
144
133
 
145
134
  return { ok: true };
@@ -192,20 +192,38 @@ pnpm action add-slide --deckId=<id> --layout content --content "..."
192
192
 
193
193
  ## Slide Wrapper
194
194
 
195
- Every slide's `content` must use this exact outer div:
195
+ Every slide's `content` must use the semantic `--deck-*` contract on its outer
196
+ div. Which of the two forms you use depends on whether a design system is
197
+ linked.
198
+
199
+ **A design system is linked.** Inherit the renderer's hydrated variables
200
+ (`--ds-bg`, `--ds-text`, `--ds-text-muted`, `--ds-accent`, `--ds-surface`,
201
+ `--ds-heading-font`, `--ds-body-font`, `--ds-radius`) through the contract
202
+ rather than copying values into individual elements:
203
+
204
+ ```html
205
+ <div class="fmd-slide" style="--deck-bg: var(--ds-bg); --deck-ink: var(--ds-text); --deck-muted: var(--ds-text-muted); --deck-accent: var(--ds-accent); --deck-surface: var(--ds-surface); --deck-heading-font: var(--ds-heading-font); --deck-body-font: var(--ds-body-font); --deck-radius: var(--ds-radius); background: var(--deck-bg); color: var(--deck-ink); padding: 64px 80px; display: flex; flex-direction: column; justify-content: flex-start; font-family: var(--deck-body-font);">
206
+ <!-- slide content here -->
207
+ </div>
208
+ ```
209
+
210
+ **No design system is linked.** There is no house style to fall back to, and
211
+ the renderer publishes no `--ds-*` tokens beyond the slide's own background.
212
+ Derive a contract from the deck's subject and write it as literal values:
196
213
 
197
214
  ```html
198
- <div class="fmd-slide" style="--deck-bg: var(--ds-bg, Canvas); --deck-ink: var(--ds-text, CanvasText); --deck-muted: var(--ds-text-muted, GrayText); --deck-accent: var(--ds-accent, currentColor); --deck-surface: var(--ds-surface, transparent); --deck-heading-font: var(--ds-heading-font, sans-serif); --deck-body-font: var(--ds-body-font, sans-serif); --deck-radius: var(--ds-radius, 0px); background: var(--deck-bg); color: var(--deck-ink); padding: 64px 80px; display: flex; flex-direction: column; justify-content: flex-start; font-family: var(--deck-body-font);">
215
+ <div class="fmd-slide" style="--deck-bg: #10261C; --deck-ink: #F2EFE6; --deck-muted: #A8B8AC; --deck-accent: #7FB069; --deck-surface: rgba(255,255,255,0.05); --deck-heading-font: 'Fraunces', Georgia, serif; --deck-body-font: 'Inter', sans-serif; --deck-radius: 4px; background: var(--deck-bg); color: var(--deck-ink); padding: 64px 80px; display: flex; flex-direction: column; justify-content: flex-start; font-family: var(--deck-body-font);">
199
216
  <!-- slide content here -->
200
217
  </div>
201
218
  ```
202
219
 
203
- When a system is linked, use its hydrated values or the renderer variables
204
- (`--ds-accent`, `--ds-bg`, `--ds-text`, `--ds-text-muted`, `--ds-heading-font`,
205
- `--ds-body-font`, `--ds-radius`) through the semantic `--deck-*` contract
206
- instead of copying values into individual elements. With no linked system,
207
- choose the contract once from the subject and repeat it exactly; do not import
208
- a stock presentation palette, font, or component language.
220
+ That example is a nature-related deck, so colors match the topic of deck. Pick the values once, before the first slide, and repeat the identical contract on every wrapper.
221
+
222
+ Inheriting when nothing is linked is the failure mode to avoid: a
223
+ `var(--ds-accent, currentColor)` reference on an unlinked deck silently
224
+ resolves to a browser default, so the deck reads as unstyled rather than as the
225
+ direction you chose. Never import a stock presentation palette, font, or
226
+ component language as a substitute for choosing.
209
227
 
210
228
  ## Fit budget
211
229
 
@@ -219,13 +237,9 @@ stack. Keep body text at or above 16px. Never hide overflow with zoom,
219
237
  may reduce the slide's explicit padding, and that padding must remain intact
220
238
  when the saved HTML is rendered.
221
239
 
222
- When no reference deck or hydrated design system is available, derive the
223
- fallback direction from the subject instead of using a fixed palette. Lock one
224
- background family, readable text/surface roles, type pairing, spacing scale,
225
- radius, and accent treatment as semantic `--deck-*` values, then repeat them on
226
- every wrapper. Build an intentional composition beyond a text dump: use a title
227
- block, two-column split, metric treatment, rule, callout, visual placeholder,
228
- or simple diagram where it fits the message. Keep the canvas stable across the
240
+ Build an intentional composition beyond a text dump: use a title block,
241
+ two-column split, metric treatment, rule, callout, visual placeholder, or
242
+ simple diagram where it fits the message. Keep the canvas stable across the
229
243
  deck, use accents only for hierarchy or meaning, and do not add decorative
230
244
  cards, gradients, fake logos, or shapes without a semantic role.
231
245
 
@@ -241,33 +255,34 @@ was rendered and compared.
241
255
 
242
256
  ## Ready-to-Use Templates
243
257
 
244
- Copy and fill in the bracketed values. Each example includes the complete
245
- semantic wrapper contract. If no design system is linked, replace the
246
- `--ds-*` fallbacks with the one subject-appropriate contract chosen for this
247
- deck before copying the wrapper to another slide.
258
+ Copy and fill in the bracketed values. `[WRAPPER STYLE]` stands for the deck
259
+ contract chosen in "Slide Wrapper" - the inherited `var(--ds-*)` form when a
260
+ system is linked, the literal form when none is. It is identical on every
261
+ slide. Inside the slide, read the contract directly (`var(--deck-accent)`), not
262
+ through another layer of fallbacks; the wrapper has already defined every role.
248
263
 
249
264
  ### Title Slide
250
265
 
251
266
  ```html
252
- <div class="fmd-slide" style="--deck-bg: var(--ds-bg, Canvas); --deck-ink: var(--ds-text, CanvasText); --deck-muted: var(--ds-text-muted, GrayText); --deck-accent: var(--ds-accent, currentColor); --deck-surface: var(--ds-surface, transparent); --deck-heading-font: var(--ds-heading-font, sans-serif); --deck-body-font: var(--ds-body-font, sans-serif); --deck-radius: var(--ds-radius, 0px); background: var(--deck-bg); color: var(--deck-ink); padding: 64px 80px; display: flex; flex-direction: column; justify-content: center; align-items: flex-start; gap: 18px; font-family: var(--deck-body-font);">
253
- <div style="font-size: 14px; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; color: var(--deck-accent, var(--ds-accent, currentColor));">[LABEL OR DATE]</div>
254
- <h1 style="font-size: 56px; font-weight: 750; color: var(--deck-ink, var(--ds-text, currentColor)); font-family: var(--deck-heading-font, var(--ds-heading-font, var(--deck-body-font, sans-serif))); line-height: 1.05; letter-spacing: -0.04em; margin: 0; max-width: 760px;">[TITLE]</h1>
255
- <p style="font-size: 20px; color: var(--deck-muted, var(--ds-text-muted, currentColor)); margin: 4px 0 0;">[SUBTITLE OR PRESENTER]</p>
267
+ <div class="fmd-slide" style="[WRAPPER STYLE] justify-content: center; align-items: flex-start; gap: 18px;">
268
+ <div style="font-size: 14px; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; color: var(--deck-accent);">[LABEL OR DATE]</div>
269
+ <h1 style="font-size: 56px; font-weight: 750; color: var(--deck-ink); font-family: var(--deck-heading-font); line-height: 1.05; letter-spacing: -0.04em; margin: 0; max-width: 760px;">[TITLE]</h1>
270
+ <p style="font-size: 20px; color: var(--deck-muted); margin: 4px 0 0;">[SUBTITLE OR PRESENTER]</p>
256
271
  </div>
257
272
  ```
258
273
 
259
274
  ### Content or Two-Column Slide
260
275
 
261
276
  ```html
262
- <div class="fmd-slide" style="--deck-bg: var(--ds-bg, Canvas); --deck-ink: var(--ds-text, CanvasText); --deck-muted: var(--ds-text-muted, GrayText); --deck-accent: var(--ds-accent, currentColor); --deck-surface: var(--ds-surface, transparent); --deck-heading-font: var(--ds-heading-font, sans-serif); --deck-body-font: var(--ds-body-font, sans-serif); --deck-radius: var(--ds-radius, 0px); background: var(--deck-bg); color: var(--deck-ink); padding: 64px 80px; display: flex; flex-direction: column; justify-content: flex-start; gap: 18px; font-family: var(--deck-body-font);">
263
- <div style="font-size: 13px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--deck-accent, var(--ds-accent, currentColor));">[SECTION LABEL]</div>
264
- <h2 style="font-size: 34px; font-weight: 750; color: var(--deck-ink, var(--ds-text, currentColor)); font-family: var(--deck-heading-font, var(--ds-heading-font, var(--deck-body-font, sans-serif))); line-height: 1.12; letter-spacing: -0.03em; margin: 0 0 18px;">[SLIDE HEADING]</h2>
277
+ <div class="fmd-slide" style="[WRAPPER STYLE] justify-content: flex-start; gap: 18px;">
278
+ <div style="font-size: 13px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--deck-accent);">[SECTION LABEL]</div>
279
+ <h2 style="font-size: 34px; font-weight: 750; color: var(--deck-ink); font-family: var(--deck-heading-font); line-height: 1.12; letter-spacing: -0.03em; margin: 0 0 18px;">[SLIDE HEADING]</h2>
265
280
  <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 24px; align-items: start;">
266
281
  <div style="display: flex; flex-direction: column; gap: 14px;">
267
- <div style="border-left: 3px solid var(--deck-accent, var(--ds-accent, currentColor)); padding: 12px 16px; background: var(--deck-surface, var(--ds-surface, transparent)); border-radius: var(--deck-radius, var(--ds-radius, 0px)); font-size: 18px; line-height: 1.4;">[KEY POINT]</div>
268
- <div style="border-left: 3px solid var(--deck-accent, var(--ds-accent, currentColor)); padding: 12px 16px; background: var(--deck-surface, var(--ds-surface, transparent)); border-radius: var(--deck-radius, var(--ds-radius, 0px)); font-size: 18px; line-height: 1.4;">[KEY POINT]</div>
282
+ <div style="border-left: 3px solid var(--deck-accent); padding: 12px 16px; background: var(--deck-surface); border-radius: var(--deck-radius); font-size: 18px; line-height: 1.4;">[KEY POINT]</div>
283
+ <div style="border-left: 3px solid var(--deck-accent); padding: 12px 16px; background: var(--deck-surface); border-radius: var(--deck-radius); font-size: 18px; line-height: 1.4;">[KEY POINT]</div>
269
284
  </div>
270
- <div class="fmd-img-placeholder" style="min-height: 220px; border-radius: var(--deck-radius, var(--ds-radius, 0px));">[VISUAL OR IMAGE DESCRIPTION]</div>
285
+ <div class="fmd-img-placeholder" style="min-height: 220px; border-radius: var(--deck-radius);">[VISUAL OR IMAGE DESCRIPTION]</div>
271
286
  </div>
272
287
  </div>
273
288
  ```
@@ -282,7 +297,7 @@ When a slide needs a visual, use this div. It renders as a styled placeholder
282
297
  and can later be replaced with a generated image:
283
298
 
284
299
  ```html
285
- <div class="fmd-img-placeholder" style="width: 100%; min-height: 220px; border-radius: var(--deck-radius, var(--ds-radius, 0px));">[Description of what image should show]</div>
300
+ <div class="fmd-img-placeholder" style="width: 100%; min-height: 220px; border-radius: var(--deck-radius);">[Description of what image should show]</div>
286
301
  ```
287
302
 
288
303
  ## Bulk Replacement Only
@@ -16,14 +16,30 @@ registry; do not assume a fixed 1920x1080 canvas.
16
16
 
17
17
  ## Slide HTML Structure
18
18
 
19
- Every slide uses this wrapper:
19
+ Every slide uses the same `--deck-*` wrapper contract. What changes is where
20
+ those values come from.
21
+
22
+ **A design system is linked** - inherit its tokens:
23
+
24
+ ```html
25
+ <div class="fmd-slide" style="--deck-bg: var(--ds-bg); --deck-ink: var(--ds-text); --deck-muted: var(--ds-text-muted); --deck-accent: var(--ds-accent); --deck-surface: var(--ds-surface); --deck-heading-font: var(--ds-heading-font); --deck-body-font: var(--ds-body-font); --deck-radius: var(--ds-radius); background: var(--deck-bg); color: var(--deck-ink); padding: 64px 80px; display: flex; flex-direction: column; justify-content: flex-start; font-family: var(--deck-body-font);">
26
+ <!-- Slide content here -->
27
+ </div>
28
+ ```
29
+
30
+ **No design system is linked** - write the deck's chosen values as literals:
20
31
 
21
32
  ```html
22
- <div class="fmd-slide" style="--deck-bg: var(--ds-bg, Canvas); --deck-ink: var(--ds-text, CanvasText); --deck-muted: var(--ds-text-muted, GrayText); --deck-accent: var(--ds-accent, currentColor); --deck-surface: var(--ds-surface, transparent); --deck-heading-font: var(--ds-heading-font, sans-serif); --deck-body-font: var(--ds-body-font, sans-serif); --deck-radius: var(--ds-radius, 0px); background: var(--deck-bg); color: var(--deck-ink); padding: 64px 80px; display: flex; flex-direction: column; justify-content: flex-start; font-family: var(--deck-body-font);">
33
+ <div class="fmd-slide" style="--deck-bg: #10261C; --deck-ink: #F2EFE6; --deck-muted: #A8B8AC; --deck-accent: #7FB069; --deck-surface: rgba(255,255,255,0.05); --deck-heading-font: 'Fraunces', Georgia, serif; --deck-body-font: 'Inter', sans-serif; --deck-radius: 4px; background: var(--deck-bg); color: var(--deck-ink); padding: 64px 80px; display: flex; flex-direction: column; justify-content: flex-start; font-family: var(--deck-body-font);">
23
34
  <!-- Slide content here -->
24
35
  </div>
25
36
  ```
26
37
 
38
+ The renderer publishes `--ds-bg` from the slide's own background, and nothing
39
+ else, when no system is linked. Every other `var(--ds-*, ...)` reference
40
+ resolves to its fallback, so an unlinked deck that inherits instead of baking
41
+ renders as unstyled browser defaults. Bake the values.
42
+
27
43
  ## Styling Rules
28
44
 
29
45
  These are fallback defaults only. When a design system is linked, its hydrated
@@ -244,25 +244,36 @@ function standaloneDesignSystemVars(
244
244
  builderTokenValues,
245
245
  );
246
246
  const darkBackground = isDarkStandaloneBackground(safeBackground);
247
+ // Must match SlideRenderer: publishing a token an unlinked deck never chose
248
+ // overrides the theme baked into its slide HTML.
249
+ const token = (name: string, value: unknown): string | null => {
250
+ if (typeof value !== "string" || !value.trim()) return null;
251
+ const resolved = safeCssToken(value, "", builderTokenValues);
252
+ return resolved ? `${name}: ${resolved}` : null;
253
+ };
254
+ const designSystemTokens = designSystem
255
+ ? [
256
+ token("--ds-text", colors?.text),
257
+ token("--ds-text-muted", colors?.textMuted),
258
+ token("--ds-accent", colors?.accent),
259
+ token("--ds-primary", colors?.primary),
260
+ token("--ds-secondary", colors?.secondary),
261
+ token("--ds-surface", colors?.surface),
262
+ token("--ds-heading-font", typography?.headingFont),
263
+ token("--ds-body-font", typography?.bodyFont),
264
+ token("--ds-radius", borders?.radius),
265
+ ].filter((entry): entry is string => entry !== null)
266
+ : [];
267
+ const needsDarkTextOverride =
268
+ darkBackground &&
269
+ (!colors?.text || isDarkStandaloneBackground(colors.text));
247
270
  return [
248
271
  `--ds-bg: ${safeBackground}`,
249
- // guard:allow-raw-color - standalone export fallback palette
250
- // guard:allow-raw-color - standalone export dark-background fallback
251
- `--ds-text: ${safeCssToken(colors?.text, darkBackground ? "#FFFFFF" : "#1F2933", builderTokenValues)}`,
252
- // guard:allow-raw-color - standalone export fallback palette
253
- // guard:allow-raw-color - standalone export dark-background fallback
254
- `--ds-text-muted: ${safeCssToken(colors?.textMuted, darkBackground ? "rgba(255, 255, 255, 0.72)" : "#667085", builderTokenValues)}`,
255
- // guard:allow-raw-color - standalone export fallback palette
256
- `--ds-accent: ${safeCssToken(colors?.accent, "#2457D6", builderTokenValues)}`,
257
- // guard:allow-raw-color - standalone export fallback palette
258
- `--ds-primary: ${safeCssToken(colors?.primary, "#2457D6", builderTokenValues)}`,
259
- // guard:allow-raw-color - standalone export fallback palette
260
- `--ds-secondary: ${safeCssToken(colors?.secondary, "#C85C3A", builderTokenValues)}`,
261
- // guard:allow-raw-color - standalone export fallback palette
262
- `--ds-surface: ${safeCssToken(colors?.surface, "#FFFFFF", builderTokenValues)}`,
263
- `--ds-heading-font: ${safeCssToken(typography?.headingFont, "Inter, sans-serif", builderTokenValues)}`,
264
- `--ds-body-font: ${safeCssToken(typography?.bodyFont, "Inter, sans-serif", builderTokenValues)}`,
265
- `--ds-radius: ${safeCssToken(borders?.radius, "14px", builderTokenValues)}`,
272
+ ...designSystemTokens,
273
+ ...(needsDarkTextOverride
274
+ ? // guard:allow-raw-color - readable defaults on explicit dark backgrounds
275
+ [`--ds-text: #FFFFFF`, `--ds-text-muted: rgba(255, 255, 255, 0.72)`]
276
+ : []),
266
277
  ...Object.entries(builderTokenValues ?? {})
267
278
  .filter(
268
279
  ([name, value]) =>
@@ -79,27 +79,24 @@ export function mergeDesignSystemData(value: unknown): DesignSystemData {
79
79
  }
80
80
 
81
81
  export interface DeckDesignSystemResult {
82
- designSystem: DesignSystemData;
82
+ designSystem: DesignSystemData | undefined;
83
83
  designSystemTitle: string | null;
84
84
  imageStyleReferenceUrls: string[];
85
85
  isLoading: boolean;
86
86
  }
87
87
 
88
- export function useDeckDesignSystem(designSystemId?: string | null) {
89
- const { data, isLoading } = useActionQuery<{
90
- id: string;
91
- title: string;
92
- data: string;
93
- }>("get-design-system", designSystemId ? { id: designSystemId } : undefined, {
94
- enabled: Boolean(designSystemId),
95
- });
96
-
88
+ // Returning a stock palette for an unlinked deck publishes `--ds-*` values the
89
+ // slide's own `var(--ds-*, ...)` fallbacks can never override, so "no design
90
+ // system" renders as one nobody picked.
91
+ export function resolveDeckDesignSystem(
92
+ designSystemId: string | null | undefined,
93
+ data: { title?: string | null; data?: string } | undefined,
94
+ ): Omit<DeckDesignSystemResult, "isLoading"> {
97
95
  if (!designSystemId || !data?.data) {
98
96
  return {
99
- designSystem: DEFAULT_DESIGN_SYSTEM,
97
+ designSystem: undefined,
100
98
  designSystemTitle: null,
101
99
  imageStyleReferenceUrls: [],
102
- isLoading: false,
103
100
  };
104
101
  }
105
102
 
@@ -109,16 +106,31 @@ export function useDeckDesignSystem(designSystemId?: string | null) {
109
106
  designSystem: parsed,
110
107
  designSystemTitle: data.title ?? null,
111
108
  imageStyleReferenceUrls: getDesignSystemImageStyleReferenceUrls(parsed),
112
- isLoading,
113
109
  };
114
110
  } catch {
115
111
  return {
116
- designSystem: DEFAULT_DESIGN_SYSTEM,
112
+ designSystem: undefined,
117
113
  designSystemTitle: data.title ?? null,
118
114
  imageStyleReferenceUrls: [],
119
- isLoading,
120
115
  };
121
116
  }
122
117
  }
123
118
 
119
+ export function useDeckDesignSystem(
120
+ designSystemId?: string | null,
121
+ ): DeckDesignSystemResult {
122
+ const { data, isLoading } = useActionQuery<{
123
+ id: string;
124
+ title: string;
125
+ data: string;
126
+ }>("get-design-system", designSystemId ? { id: designSystemId } : undefined, {
127
+ enabled: Boolean(designSystemId),
128
+ });
129
+
130
+ return {
131
+ ...resolveDeckDesignSystem(designSystemId, data),
132
+ isLoading: designSystemId ? isLoading : false,
133
+ };
134
+ }
135
+
124
136
  export { DEFAULT_DESIGN_SYSTEM };
@@ -2817,7 +2817,7 @@ export default function DeckEditor() {
2817
2817
  questions={questionFlowQuestions ?? []}
2818
2818
  onSubmit={handleQuestionSubmit}
2819
2819
  onSkip={handleQuestionSkip}
2820
- designSystem={deck.designSystemId ? designSystem : undefined}
2820
+ designSystem={designSystem}
2821
2821
  title={questionFlowTitle}
2822
2822
  description={questionFlowDescription}
2823
2823
  skipLabel={questionFlowSkipLabel}
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-09-23
4
+ ---
5
+
6
+ Decks with no design system now follow a theme chosen for their subject instead of a fixed default palette
@@ -1,4 +1,6 @@
1
- export const DEFAULT_SLIDE_BACKGROUND = "#F5F2EA"; // guard:allow-raw-color - default slide canvas fallback
1
+ // Neutral on purpose: a tinted canvas default reads as a theme the user never
2
+ // chose.
3
+ export const DEFAULT_SLIDE_BACKGROUND = "#FFFFFF"; // guard:allow-raw-color - default slide canvas fallback
2
4
 
3
5
  // `slide.background` holds either a raw CSS value or a Tailwind arbitrary
4
6
  // class (`bg-[...]`), which SlideRenderer applies as a class rather than
@@ -744,6 +744,14 @@ async function* parseJsonlStream(reader, model, captureContext = {}) {
744
744
  }
745
745
  case "usage": {
746
746
  const cacheWrite = (event.cacheCreatedTokens ?? 0) + (event.cacheCreated1hTokens ?? 0);
747
+ if (event.creditsUsed !== undefined &&
748
+ (!Number.isFinite(event.creditsUsed) || event.creditsUsed < 0)) {
749
+ yield gatewayErrorStop({
750
+ error: "Builder gateway returned invalid credit usage",
751
+ errorCode: "builder_gateway_error",
752
+ }, captureContext.creditsLane, captureContext.requestShape);
753
+ return;
754
+ }
747
755
  yield {
748
756
  type: "usage",
749
757
  inputTokens: event.inputTokens ?? 0,
@@ -752,6 +760,9 @@ async function* parseJsonlStream(reader, model, captureContext = {}) {
752
760
  ? { cacheReadTokens: event.cacheInputTokens }
753
761
  : {}),
754
762
  ...(cacheWrite > 0 ? { cacheWriteTokens: cacheWrite } : {}),
763
+ ...(event.creditsUsed !== undefined
764
+ ? { builderCreditsUsed: event.creditsUsed }
765
+ : {}),
755
766
  };
756
767
  break;
757
768
  }
@@ -196,6 +196,7 @@ export type EngineEvent = {
196
196
  cacheWriteTokens?: number;
197
197
  totalTokens?: number;
198
198
  reasoningTokens?: number;
199
+ builderCreditsUsed?: number;
199
200
  } | {
200
201
  /** Final assistant content for the turn. Engines MUST emit this
201
202
  * exactly once, immediately before the terminal `stop` event. */
@@ -571,6 +571,8 @@ export interface AgentLoopUsage {
571
571
  outputTokens: number;
572
572
  cacheReadTokens: number;
573
573
  cacheWriteTokens: number;
574
+ builderCreditsUsed?: number;
575
+ engineName?: string;
574
576
  model: string;
575
577
  /** Number of provider model-stream attempts, including retries. */
576
578
  llmCalls?: number;