@agent-native/core 0.146.0 → 0.146.2

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 (120) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/turn-into-app/SKILL.md +4 -0
  3. package/corpus/templates/analytics/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  4. package/corpus/templates/analytics/actions/compose-dashboard.ts +2 -27
  5. package/corpus/templates/analytics/actions/get-sql-dashboard.ts +7 -8
  6. package/corpus/templates/analytics/actions/migrate-first-party-analytics-to-bigquery.ts +18 -2
  7. package/corpus/templates/analytics/actions/mutate-dashboard.ts +16 -24
  8. package/corpus/templates/analytics/actions/rename-dashboard.ts +2 -24
  9. package/corpus/templates/analytics/actions/reorder-dashboard-panels.ts +2 -24
  10. package/corpus/templates/analytics/actions/restore-dashboard-revision.ts +2 -25
  11. package/corpus/templates/analytics/actions/update-dashboard.ts +4 -32
  12. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +1 -40
  13. package/corpus/templates/analytics/changelog/2026-08-07-language-selection-now-lives-in-settings-instead-of-the-comm.md +6 -0
  14. package/corpus/templates/analytics/netlify.toml +7 -0
  15. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +13 -13
  16. package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +4 -2
  17. package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +0 -35
  18. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +5 -3
  19. package/corpus/templates/analytics/server/lib/dashboard-collab-sync.ts +77 -0
  20. package/corpus/templates/analytics/server/lib/demo-dashboards.ts +2 -23
  21. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +28 -74
  22. package/corpus/templates/analytics/server/routes/api/dashboard-agent-context.json.get.ts +5 -9
  23. package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +4 -0
  24. package/corpus/templates/assets/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  25. package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +4 -0
  26. package/corpus/templates/brain/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  27. package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +4 -0
  28. package/corpus/templates/calendar/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  29. package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +4 -0
  30. package/corpus/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  31. package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +4 -0
  32. package/corpus/templates/clips/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  33. package/corpus/templates/clips/app/components/player/video-player.tsx +1 -1
  34. package/corpus/templates/clips/changelog/2026-08-07-fixed-trimmed-clips-durations-so-the-main-player-display-mat.md +6 -0
  35. package/corpus/templates/clips/chrome-extension/STORE_DRAFT.md +1 -1
  36. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  37. package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +4 -0
  38. package/corpus/templates/content/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  39. package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +4 -0
  40. package/corpus/templates/crm/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  41. package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +4 -0
  42. package/corpus/templates/design/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  43. package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +4 -0
  44. package/corpus/templates/dispatch/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  45. package/corpus/templates/dispatch/changelog/2026-08-07-language-selection-now-lives-in-settings-instead-of-the-disp.md +6 -0
  46. package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +4 -0
  47. package/corpus/templates/factory/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  48. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +59 -22
  49. package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +4 -0
  50. package/corpus/templates/forms/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  51. package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +4 -0
  52. package/corpus/templates/macros/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  53. package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +4 -0
  54. package/corpus/templates/mail/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  55. package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +4 -0
  56. package/corpus/templates/plan/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  57. package/corpus/templates/slides/.agents/skills/image-generation-via-a2a/SKILL.md +10 -1
  58. package/corpus/templates/slides/.agents/skills/slide-images/SKILL.md +10 -3
  59. package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +4 -0
  60. package/corpus/templates/slides/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  61. package/corpus/templates/slides/actions/generate-image-api.ts +123 -0
  62. package/corpus/templates/slides/actions/import-file.ts +144 -99
  63. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +10 -2
  64. package/corpus/templates/slides/app/components/editor/ImageGenPanel.tsx +1 -1
  65. package/corpus/templates/slides/changelog/2026-08-07-deleting-a-deck-no-longer-freezes-the-slides-browser.md +6 -0
  66. package/corpus/templates/slides/changelog/2026-08-07-slides-verify-generated-images-are-persisted-on-the-target-s.md +6 -0
  67. package/corpus/templates/slides/package.json +1 -0
  68. package/corpus/templates/slides/server/handlers/import/html-converter.ts +9 -0
  69. package/corpus/templates/slides/server/handlers/import/pdf-fidelity-parser.ts +960 -0
  70. package/corpus/templates/slides/server/lib/slide-image-insertion.ts +141 -0
  71. package/corpus/templates/slides/server/plugins/agent-chat.ts +2 -0
  72. package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +4 -0
  73. package/corpus/templates/tasks/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  74. package/dist/client/AgentPanel.d.ts +10 -4
  75. package/dist/client/AgentPanel.js +29 -82
  76. package/dist/collab/awareness.d.ts +2 -2
  77. package/dist/collab/struct-routes.d.ts +1 -1
  78. package/dist/db/migrations.js +22 -4
  79. package/dist/deploy/build.d.ts +1 -0
  80. package/dist/deploy/build.js +27 -22
  81. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  82. package/dist/ingestion/pptx.d.ts +1 -0
  83. package/dist/ingestion/pptx.js +353 -28
  84. package/dist/notifications/routes.d.ts +2 -2
  85. package/dist/observability/routes.d.ts +1 -1
  86. package/dist/progress/routes.d.ts +1 -1
  87. package/dist/resources/handlers.d.ts +1 -1
  88. package/dist/secrets/routes.d.ts +6 -6
  89. package/dist/server/builder-browser.d.ts +34 -0
  90. package/dist/server/builder-browser.js +179 -0
  91. package/dist/server/core-routes-plugin.d.ts +5 -0
  92. package/dist/server/core-routes-plugin.js +24 -8
  93. package/dist/server/index.d.ts +1 -1
  94. package/dist/server/index.js +1 -1
  95. package/dist/shared/cache-control.d.ts +2 -2
  96. package/dist/shared/cache-control.js +2 -2
  97. package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +4 -0
  98. package/dist/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  99. package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +4 -0
  100. package/dist/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  101. package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +4 -0
  102. package/dist/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  103. package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +4 -0
  104. package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  105. package/dist/vite/client.js +13 -1
  106. package/docs/content/drop-in-agent.mdx +3 -3
  107. package/docs/content/getting-started-actions.mdx +1 -2
  108. package/docs/content/getting-started-pages.mdx +1 -2
  109. package/docs/content/getting-started.mdx +0 -4
  110. package/docs/content/what-is-agent-native.mdx +4 -4
  111. package/package.json +3 -3
  112. package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +4 -0
  113. package/src/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  114. package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +4 -0
  115. package/src/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  116. package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +4 -0
  117. package/src/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  118. package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +4 -0
  119. package/src/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +8 -0
  120. package/docs/content/getting-started-database.mdx +0 -173
@@ -23,7 +23,7 @@ import { INTEGRATION_RECOVERY_RUNTIME_MARKER, INTEGRATION_RETRY_SWEEP_PATH, INTE
23
23
  import { isValidCron } from "../jobs/cron.js";
24
24
  import { RECURRING_JOBS_SWEEP_PATH, RECURRING_JOBS_SWEEP_TOKEN_SUBJECT, } from "../jobs/scheduler-dispatch.js";
25
25
  import { normalizeAppBasePath } from "../server/app-base-path.js";
26
- import { DEFAULT_SPECULATION_RULES_PATH, resolveSsrCacheHeaders, } from "../shared/cache-control.js";
26
+ import { DEFAULT_SPECULATION_RULES_PATH, resolveSsrCacheHeaders, resolveSsrCacheKeyHeaders, } from "../shared/cache-control.js";
27
27
  import { mcpEmbedStaticAssetRouteRules } from "../shared/mcp-embed-headers.js";
28
28
  import { AGENT_NATIVE_SOCIAL_IMAGE_ALT, AGENT_NATIVE_SOCIAL_IMAGE_CACHE_BUSTER, AGENT_NATIVE_SOCIAL_IMAGE_HEIGHT, AGENT_NATIVE_SOCIAL_IMAGE_PATH, AGENT_NATIVE_SOCIAL_IMAGE_TYPE, AGENT_NATIVE_SOCIAL_IMAGE_WIDTH, } from "../shared/social-meta.js";
29
29
  import { generateActionRegistryForProject } from "../vite/action-types-plugin.js";
@@ -652,6 +652,7 @@ export function generateWorkerEntry(routes, pluginPaths, defaultPluginStems = []
652
652
  // The worker ships as a static bundle with no access to runtime env, so the
653
653
  // deployment-wide SSR cache policy is baked in from this build's env.
654
654
  const ssrCacheHeaders = resolveSsrCacheHeaders();
655
+ const ssrCacheKeyHeaders = resolveSsrCacheKeyHeaders();
655
656
  const routeImports = [];
656
657
  const routeRegistrations = [];
657
658
  for (let i = 0; i < routes.length; i++) {
@@ -971,9 +972,8 @@ function injectHeadScript(html, script) {
971
972
  }
972
973
 
973
974
  // Resolved from AGENT_NATIVE_SSR_CACHE at build time.
974
- const SSR_CACHE_CONTROL = ${JSON.stringify(ssrCacheHeaders["cache-control"])};
975
- const SSR_CDN_CACHE_CONTROL = ${JSON.stringify(ssrCacheHeaders["cdn-cache-control"])};
976
- const SSR_NETLIFY_CDN_CACHE_CONTROL = ${JSON.stringify(ssrCacheHeaders["netlify-cdn-cache-control"])};
975
+ const SSR_CACHE_HEADERS = ${JSON.stringify(ssrCacheHeaders)};
976
+ const SSR_CACHE_KEY_HEADERS = ${JSON.stringify(ssrCacheKeyHeaders)};
977
977
  const DEFAULT_SPECULATION_RULES_PATH = ${JSON.stringify(DEFAULT_SPECULATION_RULES_PATH)};
978
978
  const IMMUTABLE_ASSET_CACHE_CONTROL = ${JSON.stringify(IMMUTABLE_ASSET_CACHE_CONTROL)};
979
979
  const IMMUTABLE_ASSET_PATHS = new Set(${JSON.stringify([...new Set(immutableAssetPaths)].sort())});
@@ -1057,13 +1057,12 @@ function applyDefaultSsrCacheHeader(headers, status, pathname) {
1057
1057
  else headers.delete("vary");
1058
1058
  }
1059
1059
 
1060
- headers.set("cache-control", SSR_CACHE_CONTROL);
1061
- headers.set("cdn-cache-control", SSR_CDN_CACHE_CONTROL);
1062
- // Netlify function responses are dynamic by default and can otherwise show
1063
- // Cache-Status fwd=bypass even with Cache-Control: public. Keep this
1064
- // Netlify-specific header so SSR HTML/.data are served from the shared
1065
- // durable CDN cache instead of stampeding origin — for every visitor.
1066
- headers.set("netlify-cdn-cache-control", SSR_NETLIFY_CDN_CACHE_CONTROL);
1060
+ for (const [name, value] of Object.entries(SSR_CACHE_HEADERS)) {
1061
+ headers.set(name, value);
1062
+ }
1063
+ for (const [name, value] of Object.entries(SSR_CACHE_KEY_HEADERS)) {
1064
+ headers.set(name, value);
1065
+ }
1067
1066
  }
1068
1067
 
1069
1068
  function applyDefaultSpeculationRulesHeader(headers, status, basePath) {
@@ -3464,6 +3463,22 @@ function createBrowserOnlyServerStubPlugin() {
3464
3463
  },
3465
3464
  };
3466
3465
  }
3466
+ export function resolveNitroBuildReplacements(env = process.env) {
3467
+ return {
3468
+ // Netlify exposes DEPLOY_ID only while building. Embed it into the Nitro
3469
+ // function so preview OAuth relays can target this immutable deployment
3470
+ // even though the value is unavailable in the function runtime.
3471
+ "process.env.AGENT_NATIVE_BUILD_ID": JSON.stringify(env.DEPLOY_ID?.trim() || env.AGENT_NATIVE_BUILD_ID?.trim() || ""),
3472
+ "process.env.AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID": JSON.stringify(env.GA_MEASUREMENT_ID?.trim() || ""),
3473
+ "process.env.AGENT_NATIVE_BUILD_GTM_CONTAINER_ID": JSON.stringify(env.GTM_CONTAINER_ID?.trim() || ""),
3474
+ // Netlify's netlify.toml environment is available while this deploy
3475
+ // build runs, but is not injected into the deployed Function. Nitro is
3476
+ // a separate server build, so embed release migration ownership here as
3477
+ // well as in the Vite server bundle.
3478
+ "process.env.AGENT_NATIVE_RELEASE_MIGRATIONS": JSON.stringify(env.AGENT_NATIVE_RELEASE_MIGRATIONS?.trim() || ""),
3479
+ "process.env.AGENT_NATIVE_BUILD_DEPLOY_CONTEXT": JSON.stringify(env.CONTEXT?.trim() || ""),
3480
+ };
3481
+ }
3467
3482
  async function buildWithNitro() {
3468
3483
  console.log(`[deploy] Building for preset "${preset}" via Nitro...`);
3469
3484
  const appBasePath = normalizeConfiguredAppBasePath();
@@ -3547,17 +3562,7 @@ export default bundle;
3547
3562
  virtual: {
3548
3563
  "virtual:agents-bundle": agentsBundleModuleSource,
3549
3564
  },
3550
- replace: {
3551
- // Netlify exposes DEPLOY_ID only while building. Embed it into the Nitro
3552
- // function so preview OAuth relays can target this immutable deployment
3553
- // even though the value is unavailable in the function runtime.
3554
- "process.env.AGENT_NATIVE_BUILD_ID": JSON.stringify(process.env.DEPLOY_ID?.trim() ||
3555
- process.env.AGENT_NATIVE_BUILD_ID?.trim() ||
3556
- ""),
3557
- "process.env.AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID": JSON.stringify(process.env.GA_MEASUREMENT_ID?.trim() || ""),
3558
- "process.env.AGENT_NATIVE_BUILD_GTM_CONTAINER_ID": JSON.stringify(process.env.GTM_CONTAINER_ID?.trim() || ""),
3559
- "process.env.AGENT_NATIVE_BUILD_DEPLOY_CONTEXT": JSON.stringify(process.env.CONTEXT?.trim() || ""),
3560
- },
3565
+ replace: resolveNitroBuildReplacements(),
3561
3566
  // Replace browser-only renderers (Excalidraw/Mermaid) with an inert proxy in
3562
3567
  // the server bundle. Without this, Nitro's Rolldown build pulls the real
3563
3568
  // Excalidraw into a shared vendor chunk imported statically by the SSR render
@@ -17,11 +17,11 @@ declare const _default: import("../../action.js").ActionDefinition<{
17
17
  id?: undefined;
18
18
  provider?: undefined;
19
19
  } | {
20
+ error?: undefined;
20
21
  configured?: undefined;
21
22
  connectPath?: undefined;
22
23
  url: string;
23
24
  id: string;
24
25
  provider: string;
25
- error?: undefined;
26
26
  }>;
27
27
  export default _default;
@@ -6,6 +6,7 @@ export interface ParsedPptxTextRun {
6
6
  color?: string;
7
7
  fontFamily?: string;
8
8
  underline?: boolean;
9
+ href?: string;
9
10
  }
10
11
  export interface ParsedPptxParagraph {
11
12
  runs: ParsedPptxTextRun[];
@@ -40,7 +40,32 @@ export async function parsePptxPresentation(fileBuffer) {
40
40
  .filter((path) => /^ppt\/slides\/slide\d+\.xml$/.test(path))
41
41
  .sort((a, b) => slideNumber(a) - slideNumber(b)));
42
42
  }
43
+ // Deck-wide fallback, used when a slide's own layout→master chain can't be
44
+ // resolved (missing rels, unusual authoring tools) — see
45
+ // `resolveSlideMasterContext` below for the per-slide resolution that
46
+ // presentations with more than one slide master actually need.
43
47
  const theme = await parseTheme(zip, parseXml);
48
+ const masterColorInfo = slideMasterRelationship
49
+ ? await parseMasterColorInfo({
50
+ zip,
51
+ target: slideMasterRelationship.target,
52
+ parseXml,
53
+ })
54
+ : { clrMap: {}, titleFillByLevel: {}, bodyFillByLevel: {} };
55
+ const colorContext = {
56
+ themeColorsByName: theme.colorsByName,
57
+ clrMap: masterColorInfo.clrMap,
58
+ };
59
+ const resolveFillsByLevel = (fillsByLevel) => Object.fromEntries(Object.entries(fillsByLevel).map(([level, fill]) => [
60
+ Number(level),
61
+ parseColor(fill, colorContext),
62
+ ]));
63
+ const placeholderDefaults = {
64
+ title: resolveFillsByLevel(masterColorInfo.titleFillByLevel),
65
+ body: resolveFillsByLevel(masterColorInfo.bodyFillByLevel),
66
+ };
67
+ const themeCache = new Map();
68
+ const masterInfoCache = new Map();
44
69
  const slides = [];
45
70
  for (const slidePath of slidePaths) {
46
71
  const xml = await zip.file(slidePath)?.async("string");
@@ -63,6 +88,18 @@ export async function parsePptxPresentation(fileBuffer) {
63
88
  const slideRelationships = slideRelationshipsXml
64
89
  ? parseRelationships(parseXml(slideRelationshipsXml))
65
90
  : new Map();
91
+ // Presentations can mix multiple masters (e.g. combined templates), each
92
+ // with its own color map / theme / placeholder defaults — resolve this
93
+ // slide's own layout→master chain instead of reusing whichever master
94
+ // happened to be first in the presentation, falling back to the
95
+ // deck-wide default above only when that chain can't be resolved.
96
+ const slideMasterContext = (await resolveSlideMasterContext({
97
+ zip,
98
+ parseXml,
99
+ slideRelationships,
100
+ themeCache,
101
+ masterInfoCache,
102
+ })) ?? { colorContext, placeholderDefaults };
66
103
  elements = await parseSlideElements({
67
104
  xml,
68
105
  parseXml,
@@ -72,6 +109,8 @@ export async function parsePptxPresentation(fileBuffer) {
72
109
  slideWidthEmu,
73
110
  slideHeightEmu,
74
111
  images,
112
+ colorContext: slideMasterContext.colorContext,
113
+ placeholderDefaults: slideMasterContext.placeholderDefaults,
75
114
  });
76
115
  const texts = flattenElementText(elements);
77
116
  const number = slideNumber(slidePath);
@@ -95,7 +134,7 @@ export async function parsePptxPresentation(fileBuffer) {
95
134
  elements,
96
135
  widthEmu: slideWidthEmu,
97
136
  heightEmu: slideHeightEmu,
98
- backgroundColor: extractSlideBackgroundColor(slide),
137
+ backgroundColor: extractSlideBackgroundColor(slide, slideMasterContext.colorContext),
99
138
  ...(backgroundGrid ? { backgroundGrid } : {}),
100
139
  notes,
101
140
  layoutHint: guessLayoutHint(texts, images.length > 0),
@@ -184,6 +223,123 @@ async function parseMasterGrid(args) {
184
223
  lineWidthEmu,
185
224
  };
186
225
  }
226
+ /** Resolves an OOXML relationship `Target` (package-absolute like "/ppt/foo.xml", or relative like "../slideMasters/slideMaster1.xml") against the directory of the part that declared it. */
227
+ function resolvePptxRelationshipPath(baseDir, target) {
228
+ if (target.startsWith("/"))
229
+ return target.slice(1);
230
+ const segments = `${baseDir}/${target}`.split("/");
231
+ const resolved = [];
232
+ for (const segment of segments) {
233
+ if (segment === "." || segment === "")
234
+ continue;
235
+ if (segment === "..")
236
+ resolved.pop();
237
+ else
238
+ resolved.push(segment);
239
+ }
240
+ return resolved.join("/");
241
+ }
242
+ /** The `_rels/<file>.rels` part that carries relationships for a given OOXML part path. */
243
+ function relsPathForPptxPart(path) {
244
+ const slashIndex = path.lastIndexOf("/");
245
+ const dir = slashIndex >= 0 ? path.slice(0, slashIndex) : "";
246
+ const file = slashIndex >= 0 ? path.slice(slashIndex + 1) : path;
247
+ return `${dir ? `${dir}/` : ""}_rels/${file}.rels`;
248
+ }
249
+ /**
250
+ * Walks this slide's own `slideLayout` → `slideMaster` → `theme` relationship
251
+ * chain so slides that belong to a different master than the deck's first
252
+ * one (a presentation with more than one master) resolve `schemeClr`
253
+ * aliases and placeholder defaults against their own palette instead of an
254
+ * unrelated master's. Returns `undefined` when any hop in that chain is
255
+ * missing, letting the caller fall back to the deck-wide default.
256
+ */
257
+ async function resolveSlideMasterContext(args) {
258
+ const layoutRelationship = [...args.slideRelationships.values()].find((relationship) => relationship.type.endsWith("/slideLayout"));
259
+ if (!layoutRelationship)
260
+ return undefined;
261
+ const layoutPath = resolvePptxRelationshipPath("ppt/slides", layoutRelationship.target);
262
+ const layoutRelsXml = await args.zip
263
+ .file(relsPathForPptxPart(layoutPath))
264
+ ?.async("string");
265
+ if (!layoutRelsXml)
266
+ return undefined;
267
+ const layoutRelationships = parseRelationships(args.parseXml(layoutRelsXml));
268
+ const masterRelationship = [...layoutRelationships.values()].find((relationship) => relationship.type.endsWith("/slideMaster"));
269
+ if (!masterRelationship)
270
+ return undefined;
271
+ const masterPath = resolvePptxRelationshipPath("ppt/slideLayouts", masterRelationship.target);
272
+ let masterInfoPromise = args.masterInfoCache.get(masterPath);
273
+ if (!masterInfoPromise) {
274
+ masterInfoPromise = parseMasterColorInfo({
275
+ zip: args.zip,
276
+ target: masterRelationship.target,
277
+ parseXml: args.parseXml,
278
+ });
279
+ args.masterInfoCache.set(masterPath, masterInfoPromise);
280
+ }
281
+ const masterInfo = await masterInfoPromise;
282
+ const masterRelsXml = await args.zip
283
+ .file(relsPathForPptxPart(masterPath))
284
+ ?.async("string");
285
+ const masterRelationships = masterRelsXml
286
+ ? parseRelationships(args.parseXml(masterRelsXml))
287
+ : new Map();
288
+ const themeRelationship = [...masterRelationships.values()].find((relationship) => relationship.type.endsWith("/theme"));
289
+ if (!themeRelationship)
290
+ return undefined;
291
+ const themePath = resolvePptxRelationshipPath("ppt/slideMasters", themeRelationship.target);
292
+ let themePromise = args.themeCache.get(themePath);
293
+ if (!themePromise) {
294
+ themePromise = parseThemeFromPath(args.zip, args.parseXml, themePath);
295
+ args.themeCache.set(themePath, themePromise);
296
+ }
297
+ const theme = await themePromise;
298
+ const colorContext = {
299
+ themeColorsByName: theme.colorsByName,
300
+ clrMap: masterInfo.clrMap,
301
+ };
302
+ const resolveFillsByLevel = (fillsByLevel) => Object.fromEntries(Object.entries(fillsByLevel).map(([level, fill]) => [
303
+ Number(level),
304
+ parseColor(fill, colorContext),
305
+ ]));
306
+ return {
307
+ colorContext,
308
+ placeholderDefaults: {
309
+ title: resolveFillsByLevel(masterInfo.titleFillByLevel),
310
+ body: resolveFillsByLevel(masterInfo.bodyFillByLevel),
311
+ },
312
+ };
313
+ }
314
+ /** Resolves a slide master's color-alias mapping plus its title/body default text-color fills (from p:txStyles), so placeholder text without its own explicit color can inherit the right one. */
315
+ async function parseMasterColorInfo(args) {
316
+ const empty = { clrMap: {}, titleFillByLevel: {}, bodyFillByLevel: {} };
317
+ const path = args.target.startsWith("/")
318
+ ? args.target.slice(1)
319
+ : "ppt/" + args.target.replace(/^\.\.\//, "");
320
+ const xml = await args.zip.file(path)?.async("string");
321
+ if (!xml)
322
+ return empty;
323
+ const root = record(record(args.parseXml(xml))?.["p:sldMaster"]);
324
+ if (!root)
325
+ return empty;
326
+ const clrMap = parseClrMapNode(record(root["p:clrMap"]));
327
+ const txStyles = record(root["p:txStyles"]);
328
+ return {
329
+ clrMap,
330
+ titleFillByLevel: levelFillsFromTextStyle(record(txStyles?.["p:titleStyle"])),
331
+ bodyFillByLevel: levelFillsFromTextStyle(record(txStyles?.["p:bodyStyle"])),
332
+ };
333
+ }
334
+ /** Reads a `<p:titleStyle>`/`<p:bodyStyle>` node's per-level (`a:lvl1pPr`..`a:lvl9pPr`) default run fill, keyed 0-indexed to match `ParsedPptxParagraph.level` (`a:lvl1pPr` is level 0, `a:lvl2pPr` is level 1, etc.) — using only the first level's default for every nested bullet level silently drops the distinct colors PowerPoint themes commonly assign to deeper levels. */
335
+ function levelFillsFromTextStyle(style) {
336
+ const fills = {};
337
+ for (let level = 0; level < 9; level++) {
338
+ const defRPr = record(record(style?.[`a:lvl${level + 1}pPr`])?.["a:defRPr"]);
339
+ fills[level] = record(defRPr?.["a:solidFill"]);
340
+ }
341
+ return fills;
342
+ }
187
343
  const SHAPE_ELEMENT_NAMES = new Set([
188
344
  "sp",
189
345
  "pic",
@@ -273,9 +429,15 @@ async function parseShapeFragment(fragment, args) {
273
429
  const name = readShapeName(node);
274
430
  const placeholderType = stringValue(record(record(record(node["p:nvSpPr"])?.["p:nvPr"])?.["p:ph"])?.["@_type"]);
275
431
  const shapeProperties = record(node["p:spPr"]);
276
- const text = parseTextBody(node);
277
- const fill = parseShapeFill(shapeProperties);
278
- const line = parseShapeLine(shapeProperties);
432
+ const rawText = parseTextBody(node, args.colorContext);
433
+ const placeholderDefaultColor = placeholderType === "title" || placeholderType === "ctrTitle"
434
+ ? args.placeholderDefaults?.title
435
+ : placeholderType
436
+ ? args.placeholderDefaults?.body
437
+ : undefined;
438
+ const text = applyPlaceholderDefaultColor(rawText, placeholderDefaultColor);
439
+ const fill = parseShapeFill(shapeProperties, args.colorContext);
440
+ const line = parseShapeLine(shapeProperties, args.colorContext);
279
441
  const shapeType = stringValue(record(shapeProperties?.["a:prstGeom"])?.["@_prst"]);
280
442
  if (entry === "pic") {
281
443
  const embedId = stringValue(record(record(node["p:blipFill"])?.["a:blip"])?.["@_r:embed"]);
@@ -446,7 +608,7 @@ function applyTransform(transform, context) {
446
608
  : {}),
447
609
  };
448
610
  }
449
- function parseTextBody(node) {
611
+ function parseTextBody(node, context) {
450
612
  const txBody = record(node["p:txBody"]);
451
613
  if (!txBody)
452
614
  return [];
@@ -460,7 +622,7 @@ function parseTextBody(node) {
460
622
  if (content) {
461
623
  runs.push({
462
624
  content,
463
- ...runProperties(record(run?.["a:rPr"]), {}),
625
+ ...runProperties(record(run?.["a:rPr"]), {}, context),
464
626
  });
465
627
  }
466
628
  }
@@ -470,12 +632,12 @@ function parseTextBody(node) {
470
632
  if (content) {
471
633
  runs.push({
472
634
  content,
473
- ...runProperties(record(field?.["a:rPr"]), {}),
635
+ ...runProperties(record(field?.["a:rPr"]), {}, context),
474
636
  });
475
637
  }
476
638
  }
477
639
  const bullet = record(pPr?.["a:buChar"]);
478
- const bulletColor = parseColor(record(pPr?.["a:buClr"]));
640
+ const bulletColor = parseColor(record(pPr?.["a:buClr"]), context);
479
641
  const bulletFont = stringValue(record(pPr?.["a:buFont"])?.["@_typeface"]);
480
642
  const bulletSize = Number(record(pPr?.["a:buSzPts"])?.["@_val"]);
481
643
  const lineSpacing = parseParagraphSpacing(pPr?.["a:lnSpc"]);
@@ -527,18 +689,18 @@ function parseTextBoxProperties(node) {
527
689
  : { verticalAlign: "top" }),
528
690
  };
529
691
  }
530
- function parseShapeFill(shapeProperties) {
692
+ function parseShapeFill(shapeProperties, context) {
531
693
  if (!shapeProperties)
532
694
  return undefined;
533
695
  if (shapeProperties["a:noFill"] !== undefined)
534
696
  return undefined;
535
- return parseColor(record(shapeProperties["a:solidFill"]));
697
+ return parseColor(record(shapeProperties["a:solidFill"]), context);
536
698
  }
537
- function parseShapeLine(shapeProperties) {
699
+ function parseShapeLine(shapeProperties, context) {
538
700
  const line = record(shapeProperties?.["a:ln"]);
539
701
  if (!line || line["a:noFill"] !== undefined)
540
702
  return undefined;
541
- const color = parseColor(record(line["a:solidFill"]));
703
+ const color = parseColor(record(line["a:solidFill"]), context);
542
704
  if (!color)
543
705
  return undefined;
544
706
  const width = Number(line["@_w"]);
@@ -547,15 +709,163 @@ function parseShapeLine(shapeProperties) {
547
709
  ...(Number.isFinite(width) && width > 0 ? { width } : {}),
548
710
  };
549
711
  }
550
- function parseColor(value) {
712
+ /** Placeholder text has no explicit color of its own when the author relied on the slide master's default run properties — apply that inherited color to any run that didn't resolve one, using each paragraph's own nested-bullet level (falling back to level 0's color when a deeper level has no default of its own). */
713
+ function applyPlaceholderDefaultColor(paragraphs, colorsByLevel) {
714
+ if (!colorsByLevel)
715
+ return paragraphs;
716
+ return paragraphs.map((paragraph) => {
717
+ const level = paragraph.level ?? 0;
718
+ const color = colorsByLevel[level] ?? colorsByLevel[0];
719
+ if (!color)
720
+ return paragraph;
721
+ return {
722
+ ...paragraph,
723
+ runs: paragraph.runs.map((run) => (run.color ? run : { ...run, color })),
724
+ };
725
+ });
726
+ }
727
+ /** PowerPoint's default `bg1`/`tx1`-style alias mapping, used whenever a master doesn't declare its own `<p:clrMap>`. */
728
+ const IDENTITY_CLR_MAP = {
729
+ bg1: "lt1",
730
+ tx1: "dk1",
731
+ bg2: "lt2",
732
+ tx2: "dk2",
733
+ accent1: "accent1",
734
+ accent2: "accent2",
735
+ accent3: "accent3",
736
+ accent4: "accent4",
737
+ accent5: "accent5",
738
+ accent6: "accent6",
739
+ hlink: "hlink",
740
+ folHlink: "folHlink",
741
+ };
742
+ const CLR_MAP_ALIASES = Object.keys(IDENTITY_CLR_MAP);
743
+ /** Reads a `<p:clrMap .../>` or `<a:overrideClrMapping .../>` node's alias attributes into an alias→theme-slot map. */
744
+ function parseClrMapNode(node) {
745
+ const map = {};
746
+ if (!node)
747
+ return map;
748
+ for (const alias of CLR_MAP_ALIASES) {
749
+ const target = stringValue(node[`@_${alias}`]);
750
+ if (target)
751
+ map[alias] = target;
752
+ }
753
+ return map;
754
+ }
755
+ function resolveSchemeColorName(name, context) {
756
+ if (!context)
757
+ return undefined;
758
+ const slot = context.clrMap[name] ?? IDENTITY_CLR_MAP[name] ?? name;
759
+ return context.themeColorsByName[slot];
760
+ }
761
+ function hexToHsl(hex) {
762
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
763
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
764
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
765
+ const max = Math.max(r, g, b);
766
+ const min = Math.min(r, g, b);
767
+ const l = (max + min) / 2;
768
+ if (max === min)
769
+ return [0, 0, l];
770
+ const d = max - min;
771
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
772
+ let h;
773
+ switch (max) {
774
+ case r:
775
+ h = (g - b) / d + (g < b ? 6 : 0);
776
+ break;
777
+ case g:
778
+ h = (b - r) / d + 2;
779
+ break;
780
+ default:
781
+ h = (r - g) / d + 4;
782
+ }
783
+ return [h * 60, s, l];
784
+ }
785
+ function hslToHex(h, s, l) {
786
+ const toByte = (v) => Math.max(0, Math.min(255, Math.round(v * 255)))
787
+ .toString(16)
788
+ .padStart(2, "0");
789
+ if (s === 0) {
790
+ const gray = toByte(l);
791
+ return `#${gray}${gray}${gray}`;
792
+ }
793
+ const hue2rgb = (p, q, t) => {
794
+ let tt = t;
795
+ if (tt < 0)
796
+ tt += 1;
797
+ if (tt > 1)
798
+ tt -= 1;
799
+ if (tt < 1 / 6)
800
+ return p + (q - p) * 6 * tt;
801
+ if (tt < 1 / 2)
802
+ return q;
803
+ if (tt < 2 / 3)
804
+ return p + (q - p) * (2 / 3 - tt) * 6;
805
+ return p;
806
+ };
807
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
808
+ const p = 2 * l - q;
809
+ const hNorm = h / 360;
810
+ const r = hue2rgb(p, q, hNorm + 1 / 3);
811
+ const g = hue2rgb(p, q, hNorm);
812
+ const b = hue2rgb(p, q, hNorm - 1 / 3);
813
+ return `#${toByte(r)}${toByte(g)}${toByte(b)}`;
814
+ }
815
+ function readColorTransforms(node) {
816
+ if (!node)
817
+ return {};
818
+ const percent = (key) => {
819
+ const raw = stringValue(record(node[key])?.["@_val"]);
820
+ return raw !== undefined ? Number(raw) / 100000 : undefined;
821
+ };
822
+ return {
823
+ lumMod: percent("a:lumMod"),
824
+ lumOff: percent("a:lumOff"),
825
+ tint: percent("a:tint"),
826
+ shade: percent("a:shade"),
827
+ };
828
+ }
829
+ function applyColorTransforms(hex, transforms) {
830
+ const { lumMod, lumOff, tint, shade } = transforms;
831
+ if (lumMod === undefined &&
832
+ lumOff === undefined &&
833
+ tint === undefined &&
834
+ shade === undefined) {
835
+ return hex;
836
+ }
837
+ const [h, s, initialL] = hexToHsl(hex);
838
+ let l = initialL;
839
+ if (lumMod !== undefined)
840
+ l *= lumMod;
841
+ if (lumOff !== undefined)
842
+ l += lumOff;
843
+ if (tint !== undefined)
844
+ l = l * tint + (1 - tint);
845
+ if (shade !== undefined)
846
+ l *= shade;
847
+ return hslToHex(h, s, Math.min(1, Math.max(0, l)));
848
+ }
849
+ function parseColor(value, context) {
551
850
  if (!value)
552
851
  return undefined;
553
- const rgb = stringValue(record(value["a:srgbClr"])?.["@_val"]);
554
- if (rgb)
555
- return `#${rgb}`;
556
- const scheme = stringValue(record(value["a:schemeClr"])?.["@_val"]);
852
+ const srgbNode = record(value["a:srgbClr"]);
853
+ const rgb = stringValue(srgbNode?.["@_val"]);
854
+ if (rgb) {
855
+ return applyColorTransforms(`#${rgb}`, readColorTransforms(srgbNode));
856
+ }
857
+ const schemeNode = record(value["a:schemeClr"]);
858
+ const scheme = stringValue(schemeNode?.["@_val"]);
557
859
  if (!scheme)
558
860
  return undefined;
861
+ const transforms = readColorTransforms(schemeNode);
862
+ const resolved = resolveSchemeColorName(scheme, context);
863
+ if (resolved)
864
+ return applyColorTransforms(resolved, transforms);
865
+ // No theme/clrMap available for this slot (or the theme didn't define
866
+ // it) — fall back to a coarse dark/light guess along the standard
867
+ // identity mapping, so tx1/dk1/bg1/lt1 text stays visible rather than
868
+ // silently vanishing.
559
869
  const pptxDarkColor = "#000000"; // guard:allow-raw-color - PPTX dark scheme fallback
560
870
  const pptxLightColor = "#ffffff"; // guard:allow-raw-color - PPTX light scheme fallback
561
871
  const fallback = {
@@ -563,8 +873,15 @@ function parseColor(value) {
563
873
  dk2: pptxDarkColor,
564
874
  lt1: pptxLightColor,
565
875
  lt2: pptxLightColor,
876
+ tx1: pptxDarkColor,
877
+ tx2: pptxDarkColor,
878
+ bg1: pptxLightColor,
879
+ bg2: pptxLightColor,
566
880
  };
567
- return fallback[scheme];
881
+ const fallbackColor = fallback[scheme];
882
+ return fallbackColor
883
+ ? applyColorTransforms(fallbackColor, transforms)
884
+ : undefined;
568
885
  }
569
886
  function parseParagraphSpacing(value) {
570
887
  const node = record(value);
@@ -662,11 +979,11 @@ function flattenElementText(elements) {
662
979
  }
663
980
  return output;
664
981
  }
665
- function extractSlideBackgroundColor(value) {
982
+ function extractSlideBackgroundColor(value, context) {
666
983
  const root = record(value);
667
984
  const cSld = record(record(root?.["p:sld"])?.["p:cSld"] ?? root?.["p:cSld"]);
668
985
  const bgPr = record(record(cSld?.["p:bg"])?.["p:bgPr"]);
669
- return parseColor(record(bgPr?.["a:solidFill"]));
986
+ return parseColor(record(bgPr?.["a:solidFill"]), context);
670
987
  }
671
988
  export function parsePptxSlideMetadata(value) {
672
989
  const slide = record(value)?.["p:sld"] ?? value;
@@ -677,28 +994,36 @@ export function parsePptxSlideMetadata(value) {
677
994
  };
678
995
  }
679
996
  async function parseTheme(zip, parseXml) {
680
- const xml = await zip.file("ppt/theme/theme1.xml")?.async("string");
997
+ return parseThemeFromPath(zip, parseXml, "ppt/theme/theme1.xml");
998
+ }
999
+ async function parseThemeFromPath(zip, parseXml, themePath) {
1000
+ const xml = await zip.file(themePath)?.async("string");
681
1001
  if (!xml)
682
- return undefined;
1002
+ return { colors: [], colorsByName: {}, fonts: [] };
683
1003
  const root = record(parseXml(xml));
684
1004
  const elements = record(record(root?.["a:theme"])?.["a:themeElements"]);
685
1005
  const scheme = record(elements?.["a:clrScheme"]);
686
1006
  const colors = [];
1007
+ const colorsByName = {};
687
1008
  for (const [key, value] of Object.entries(scheme ?? {})) {
688
1009
  if (key.startsWith("@_"))
689
1010
  continue;
690
1011
  const color = record(value);
691
1012
  const rgb = stringValue(record(color?.["a:srgbClr"])?.["@_val"]);
692
1013
  const system = stringValue(record(color?.["a:sysClr"])?.["@_lastClr"]);
693
- if (rgb || system)
694
- colors.push(`#${rgb ?? system}`);
1014
+ if (!rgb && !system)
1015
+ continue;
1016
+ const hex = `#${rgb ?? system}`;
1017
+ colors.push(hex);
1018
+ const slotName = key.replace(/^a:/, "");
1019
+ colorsByName[slotName] = hex;
695
1020
  }
696
1021
  const fontScheme = record(elements?.["a:fontScheme"]);
697
1022
  const fonts = ["a:majorFont", "a:minorFont"].flatMap((key) => {
698
1023
  const value = stringValue(record(record(fontScheme?.[key])?.["a:latin"])?.["@_typeface"]);
699
1024
  return value ? [value] : [];
700
1025
  });
701
- return colors.length || fonts.length ? { colors, fonts } : undefined;
1026
+ return { colors, colorsByName, fonts };
702
1027
  }
703
1028
  function collectTextRuns(value, runs, inherited = {}) {
704
1029
  const node = record(value);
@@ -795,11 +1120,11 @@ function extractBackgroundFillEmbedId(slide) {
795
1120
  const blip = record(record(bgPr?.["a:blipFill"])?.["a:blip"]);
796
1121
  return stringValue(blip?.["@_r:embed"]);
797
1122
  }
798
- function runProperties(value, inherited) {
1123
+ function runProperties(value, inherited, context) {
799
1124
  if (!value)
800
1125
  return inherited;
801
1126
  const size = Number(value["@_sz"]);
802
- const rgb = stringValue(record(record(value["a:solidFill"])?.["a:srgbClr"])?.["@_val"]);
1127
+ const color = parseColor(record(value["a:solidFill"]), context);
803
1128
  const fontFamily = stringValue(record(value["a:latin"])?.["@_typeface"]) ??
804
1129
  stringValue(record(value["a:ea"])?.["@_typeface"]) ??
805
1130
  stringValue(record(value["a:cs"])?.["@_typeface"]);
@@ -812,7 +1137,7 @@ function runProperties(value, inherited) {
812
1137
  ? { italic: true }
813
1138
  : {}),
814
1139
  ...(Number.isFinite(size) && size > 0 ? { fontSize: size / 100 } : {}),
815
- ...(rgb ? { color: `#${rgb}` } : {}),
1140
+ ...(color ? { color } : {}),
816
1141
  ...(fontFamily ? { fontFamily } : {}),
817
1142
  ...(value["@_u"] && value["@_u"] !== "none" ? { underline: true } : {}),
818
1143
  };
@@ -13,13 +13,13 @@
13
13
  export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
14
14
  count: number;
15
15
  updated?: undefined;
16
- error?: undefined;
17
16
  ok?: undefined;
17
+ error?: undefined;
18
18
  } | {
19
19
  count?: undefined;
20
20
  updated: number;
21
- error?: undefined;
22
21
  ok?: undefined;
22
+ error?: undefined;
23
23
  } | {
24
24
  count?: undefined;
25
25
  updated?: undefined;
@@ -62,6 +62,6 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
62
62
  summary?: undefined;
63
63
  spans?: undefined;
64
64
  id?: undefined;
65
- ok: boolean;
66
65
  error?: undefined;
66
+ ok: boolean;
67
67
  }>>;
@@ -15,6 +15,6 @@ export declare function createProgressHandler(): import("h3").EventHandlerWithFe
15
15
  error: string;
16
16
  ok?: undefined;
17
17
  } | {
18
- error?: undefined;
19
18
  ok: boolean;
19
+ error?: undefined;
20
20
  }>>;