@agent-native/core-corpus 0.206.0-nightly-20261009153146 → 0.206.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/onboarding-journeys/SKILL.md +28 -4
  3. package/corpus/templates/analytics/CHANGELOG.md +17 -0
  4. package/corpus/templates/analytics/actions/get-onboarding-journey.ts +1 -1
  5. package/corpus/templates/analytics/app/pages/sessions/SessionReplayStoryboardExportDialog.tsx +1 -0
  6. package/corpus/templates/analytics/app/pages/sessions/session-replay-screenshot.ts +24 -8
  7. package/corpus/templates/analytics/changelog/2026-10-09-large-onboarding-journeys-keep-their-step-counts-when-follow.md +6 -0
  8. package/corpus/templates/analytics/changelog/2026-10-09-onboarding-journeys-now-count-later-activity-within-a-frozen.md +6 -0
  9. package/corpus/templates/analytics/package.json +1 -0
  10. package/corpus/templates/analytics/server/lib/analytics-mcp.ts +1 -1
  11. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +3 -0
  12. package/corpus/templates/analytics/server/lib/first-party-analytics-pushdown.ts +176 -0
  13. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +161 -18
  14. package/corpus/templates/analytics/server/lib/journey-steps.ts +11 -1
  15. package/corpus/templates/analytics/server/lib/onboarding-journey.ts +358 -17
  16. package/corpus/templates/analytics/vitest.bigquery.config.ts +12 -0
  17. package/corpus/templates/analytics/vitest.config.ts +1 -0
  18. package/corpus/templates/assets/CHANGELOG.md +6 -0
  19. package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +30 -2
  20. package/corpus/templates/clips/AGENTS.md +1 -0
  21. package/corpus/templates/clips/CHANGELOG.md +20 -0
  22. package/corpus/templates/clips/app/components/player/clip-agent-webmcp.tsx +14 -2
  23. package/corpus/templates/clips/app/components/player/share-dialog.tsx +4 -1
  24. package/corpus/templates/clips/app/i18n/en-US.ts +2 -2
  25. package/corpus/templates/clips/app/routes/share.$shareId.tsx +1 -1
  26. package/corpus/templates/clips/changelog/2026-10-09-agents-can-read-clip-transcripts-and-inspect-video-frames-fr.md +6 -0
  27. package/corpus/templates/clips/changelog/2026-10-09-agents-now-get-clear-recovery-steps-when-clip-frames-are-sti.md +6 -0
  28. package/corpus/templates/clips/changelog/2026-10-09-clips-share-links-now-guide-agents-to-read-full-transcripts-.md +6 -0
  29. package/corpus/templates/clips/server/lib/ensure-recording-thumbnail.ts +1 -1
  30. package/corpus/templates/clips/server/lib/public-agent-context.ts +90 -7
  31. package/corpus/templates/clips/server/lib/video-frame.ts +8 -6
  32. package/corpus/templates/clips/server/routes/[...page].get.ts +38 -38
  33. package/corpus/templates/clips/server/routes/api/agent-context.json.get.ts +4 -2
  34. package/corpus/templates/clips/server/routes/api/agent-frame.jpg.get.ts +143 -26
  35. package/corpus/templates/clips/server/routes/api/agent-transcript.json.get.ts +4 -2
  36. package/corpus/templates/clips/shared/agent-context.ts +44 -5
  37. package/corpus/templates/content/CHANGELOG.md +16 -0
  38. package/corpus/templates/content/actions/_database-row-mutation.ts +27 -0
  39. package/corpus/templates/content/actions/_widget-edit-capabilities.ts +39 -0
  40. package/corpus/templates/content/actions/add-database-item.ts +2 -0
  41. package/corpus/templates/content/actions/get-document.ts +19 -1
  42. package/corpus/templates/content/actions/update-database-item.ts +6 -0
  43. package/corpus/templates/content/actions/update-document.ts +46 -2
  44. package/corpus/templates/content/agent-native.app-skill.json +1 -1
  45. package/corpus/templates/content/app/components/editor/DirectoryWidgetFormattingToolbar.tsx +73 -0
  46. package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +11 -1
  47. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +21 -4
  48. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +53 -4
  49. package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +29 -16
  50. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +7 -0
  51. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +338 -200
  52. package/corpus/templates/content/app/components/editor/database/scoped-row-write.ts +57 -0
  53. package/corpus/templates/content/app/components/editor/directory-widget-editability.ts +23 -0
  54. package/corpus/templates/content/app/hooks/use-content-database.ts +25 -0
  55. package/corpus/templates/content/app/lib/content-landing.ts +9 -4
  56. package/corpus/templates/content/changelog/2026-10-08-the-content-editor-fits-chatgpt-s-panel-sizes-and-supports-d.md +6 -0
  57. package/corpus/templates/content/changelog/2026-10-09-content-widgets-keep-document-edits-after-reopening-and-can-.md +6 -0
  58. package/corpus/templates/content/changelog/2026-10-09-document-icons-save-from-chatgpt-widgets.md +6 -0
  59. package/corpus/templates/content/changelog/2026-10-09-format-content-documents-directly-in-chatgpt-widgets.md +6 -0
  60. package/corpus/templates/content/changelog/2026-10-09-pages-without-a-collection-open-faster.md +6 -0
  61. package/corpus/templates/content/public/chatgpt-plugin-icon.svg +5 -7
  62. package/corpus/templates/content/server/lib/chatgpt-directory-tools.ts +90 -0
  63. package/corpus/templates/content/shared/api.ts +2 -0
  64. package/corpus/templates/crm/CHANGELOG.md +6 -0
  65. package/corpus/templates/design/AGENTS.md +1 -1
  66. package/corpus/templates/design/CHANGELOG.md +57 -0
  67. package/corpus/templates/design/FIGMA_INTEROPERABILITY.md +3 -2
  68. package/corpus/templates/design/actions/create-file.ts +4 -0
  69. package/corpus/templates/design/actions/update-design.ts +157 -0
  70. package/corpus/templates/design/actions/update-file.ts +18 -2
  71. package/corpus/templates/design/actions/widget-write-scope.ts +79 -0
  72. package/corpus/templates/design/agent-native.app-skill.json +1 -1
  73. package/corpus/templates/design/app/components/design/DesignImportPanel.tsx +136 -50
  74. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +43 -21
  75. package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +18 -3
  76. package/corpus/templates/design/app/components/design/multi-screen/types.ts +6 -1
  77. package/corpus/templates/design/app/components/editor/HomeImportButton.tsx +39 -6
  78. package/corpus/templates/design/app/i18n/en-US.ts +1 -0
  79. package/corpus/templates/design/app/lib/design-file-upload.ts +4 -0
  80. package/corpus/templates/design/app/lib/fig-client-import.ts +10 -2
  81. package/corpus/templates/design/app/lib/fig-import-worker-session.ts +11 -6
  82. package/corpus/templates/design/app/lib/pending-import.ts +46 -0
  83. package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +31 -73
  84. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-screen.ts +52 -5
  85. package/corpus/templates/design/app/pages/design-editor/created-screen-navigation.ts +30 -0
  86. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-active-screen-and-geometry.ts +4 -0
  87. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-canvas-and-screens.ts +3 -0
  88. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-generation-and-access.ts +17 -8
  89. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-modes.ts +102 -45
  90. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-tools-and-vectors.ts +59 -3
  91. package/corpus/templates/design/app/pages/design-editor/mcp-widget-write-capabilities.ts +13 -0
  92. package/corpus/templates/design/app/pages/design-editor/minimal-inspector.ts +15 -1
  93. package/corpus/templates/design/app/pages/design-editor/screen-command-utils.ts +8 -19
  94. package/corpus/templates/design/app/pages/design-editor/view/editor-canvas-area.tsx +1 -0
  95. package/corpus/templates/design/app/pages/design-editor/view/mobile-inspector-sheet.tsx +11 -9
  96. package/corpus/templates/design/app/pages/design-editor/view/overview-canvas.tsx +3 -2
  97. package/corpus/templates/design/app/pages/design-editor/view/single-screen-canvas.tsx +6 -3
  98. package/corpus/templates/design/changelog/2026-10-07-figma-fig-files-up-to-2-gb-now-import-in-the-browser-includi.md +6 -0
  99. package/corpus/templates/design/changelog/2026-10-08-a-progress-toast-now-stays-on-screen-while-a-fig-file-is-ana.md +6 -0
  100. package/corpus/templates/design/changelog/2026-10-08-the-overview-canvas-keeps-screen-selections-centered-without.md +6 -0
  101. package/corpus/templates/design/changelog/2026-10-09-chatgpt-widget-screen-duplication-preserves-layout.md +6 -0
  102. package/corpus/templates/design/changelog/2026-10-09-design-widget-edits-check-the-file-version-before-saving-pro.md +6 -0
  103. package/corpus/templates/design/changelog/2026-10-09-design-widget-edits-continue-after-reopening-a-chat.md +6 -0
  104. package/corpus/templates/design/changelog/2026-10-09-design-widgets-report-a-conflict-when-a-stale-edit-cannot-be.md +6 -0
  105. package/corpus/templates/design/changelog/2026-10-09-edit-screens-from-chatgpt-widgets.md +6 -0
  106. package/corpus/templates/design/public/chatgpt-plugin-icon.svg +5 -9
  107. package/corpus/templates/design/server/lib/chatgpt-directory-tools.ts +86 -1
  108. package/corpus/templates/design/server/lib/design-versions.ts +22 -8
  109. package/corpus/templates/design/server/lib/fig-file-decoder.ts +140 -103
  110. package/corpus/templates/design/server/lib/fig-file-limits.ts +66 -2
  111. package/corpus/templates/design/server/lib/fig-file-to-html.ts +17 -11
  112. package/corpus/templates/design/shared/fig-bytes.ts +2 -0
  113. package/corpus/templates/design/shared/fig-to-frames.ts +80 -37
  114. package/corpus/templates/dispatch/CHANGELOG.md +6 -0
  115. package/corpus/templates/forms/CHANGELOG.md +6 -0
  116. package/corpus/templates/slides/CHANGELOG.md +35 -0
  117. package/corpus/templates/slides/actions/get-deck.ts +10 -3
  118. package/corpus/templates/slides/actions/patch-deck.ts +302 -2
  119. package/corpus/templates/slides/agent-native.app-skill.json +1 -1
  120. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +91 -85
  121. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +7 -4
  122. package/corpus/templates/slides/app/components/editor/slide-style.ts +25 -0
  123. package/corpus/templates/slides/app/components/layout/Layout.tsx +7 -2
  124. package/corpus/templates/slides/app/context/DeckContext.tsx +8 -1
  125. package/corpus/templates/slides/app/global.css +15 -0
  126. package/corpus/templates/slides/app/hooks/use-deck-role.ts +12 -5
  127. package/corpus/templates/slides/app/lib/slide-agent-context.ts +20 -4
  128. package/corpus/templates/slides/app/pages/DeckEditor.tsx +15 -6
  129. package/corpus/templates/slides/app/routes/chat.tsx +7 -2
  130. package/corpus/templates/slides/changelog/2026-10-08-the-slides-editor-fits-chatgpt-s-panel-sizes-and-supports-di.md +6 -0
  131. package/corpus/templates/slides/changelog/2026-10-09-deck-tweaks-save-from-chatgpt-widgets.md +6 -0
  132. package/corpus/templates/slides/changelog/2026-10-09-slide-text-editing-keeps-formatting-controls-responsive-whil.md +6 -0
  133. package/corpus/templates/slides/changelog/2026-10-09-slides-widgets-keep-edits-after-reopening-and-can-save-avail.md +6 -0
  134. package/corpus/templates/slides/changelog/2026-10-09-the-slides-chat-stays-responsive-while-you-type-on-a-slide.md +6 -0
  135. package/corpus/templates/slides/public/chatgpt-plugin-icon.svg +5 -8
  136. package/corpus/templates/slides/scripts/edit-fidelity/README.md +1 -0
  137. package/corpus/templates/slides/scripts/edit-fidelity/run-options.ts +9 -0
  138. package/corpus/templates/slides/scripts/edit-fidelity/run.ts +75 -63
  139. package/corpus/templates/slides/server/lib/chatgpt-directory-tools.ts +47 -0
  140. package/package.json +1 -1
package/corpus/README.md CHANGED
@@ -32,4 +32,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core-corpus/corpu
32
32
 
33
33
  ## Generated Counts
34
34
 
35
- - template files: 11674
35
+ - template files: 11704
@@ -2,9 +2,10 @@
2
2
  name: onboarding-journeys
3
3
  description: >-
4
4
  Build the onboarding journey tree (signup to first output, with % splits,
5
- last-observed-step counts, and example replays) and render a screenshot per
6
- step. Use when asked for an onboarding storyboard, where users stop being
7
- observed, or frames for each onboarding step.
5
+ last-observed-step counts, window-bounded later activity, and example
6
+ replays) and render a screenshot per step. Use when asked for an onboarding
7
+ storyboard, each cohort's last recorded step or later activity within a
8
+ window, or frames for each onboarding step.
8
9
  scope: dev
9
10
  ---
10
11
 
@@ -36,7 +37,8 @@ window, `app`, `maxDepth`, `minNodeSessions` (small branches merge into an
36
37
  ```ts
37
38
  type JourneyExample = { sessionId: string; recordingId: string | null; ts: string; offsetMs: number | null; viewport: { width: number; height: number } | null; viewportReason?: string; replayUrl?: string };
38
39
  type JourneyNode = { key: string; label: string; parentKey: string | null; depth: number; kind: "step" | "other"; n: number; pctOfRoot: number; pctOfParent: number; dropoffN: number; dropoffPct: number; deeperN: number; examples: JourneyExample[] };
39
- type JourneyTree = { window: { from: string; to: string }; app: string; rootN: number; coverage: { sessionsWithEvents: number; sessionsWithReplay: number; truncated: boolean }; nodes: JourneyNode[]; standaloneSetup?: { rootN: number; coverage: { sessionsWithEvents: number; sessionsWithReplay: number; truncated: boolean }; nodes: JourneyNode[] }; notes?: string[] };
40
+ type JourneyFollowup = { status: "complete" | "incomplete"; incompleteReason?: "journey_event_read_truncated" | "journey_event_read_invalid" | "journey_event_read_may_have_shifted" | "terminal_cohort_query_too_large" | "followup_aggregate_truncated" | "followup_aggregate_invalid" | "terminal_cohort_mismatch"; observationCutoff: string; observationFollowupDurationMs: { min: number; max: number; mean: number } | null; rightCensoredAtWindowEnd: true; coverage: { journeyEventRead: { rows: number; pages: number; truncated: boolean; paginationConsistency: "stable" | "may_have_shifted" }; followupAggregateRead: { rows: number | null; queries: number; truncated: boolean }; cohortSessions: number | null }; laterRecordedActivityWithinWindow: { total: number | null; byTerminalStepKey: Record<string, number> | null }; noLaterRecordedActivityWithinWindow: { total: number | null; byTerminalStepKey: Record<string, number> | null } };
41
+ type JourneyTree = { window: { from: string; to: string }; app: string; rootN: number; coverage: { sessionsWithEvents: number; sessionsWithReplay: number; truncated: boolean }; nodes: JourneyNode[]; followUp: JourneyFollowup; standaloneSetup?: { rootN: number; coverage: { sessionsWithEvents: number; sessionsWithReplay: number; truncated: boolean }; nodes: JourneyNode[] }; notes?: string[] };
40
42
  ```
41
43
 
42
44
  Analytics returns cohort nodes with counts. When extending a Design storyboard
@@ -65,6 +67,28 @@ top-level tree to `journey:capture`; the capture CLI reads top-level `nodes`.
65
67
  exit: a blocked tracker or an event outside the window reads the same.
66
68
  `deeperN` counts sessions with a later observed step that is not represented
67
69
  as a child, including paths past `maxDepth` or a node-list cap.
70
+ - `followUp` groups onboarding sessions by the key of their terminal selected
71
+ step. `laterRecordedActivityWithinWindow` counts sessions with any later
72
+ native Analytics event in the same session; `noLaterRecordedActivityWithinWindow`
73
+ counts the remaining sessions. The read applies the same authenticated
74
+ user/org scope, date window, app, identity bridge, test exclusion, and
75
+ Builder.io email filter as the journey read. It uses one
76
+ frozen `observationCutoff` for every event page and the single aggregate
77
+ query. `observationFollowupDurationMs`
78
+ summarizes the time from each terminal selected step to that cutoff.
79
+ `rightCensoredAtWindowEnd: true` means the no-later count is right-censored:
80
+ no later event was recorded before the cutoff; this is not an abandonment or
81
+ churn outcome. The aggregate contains counts only and does not expose the
82
+ session IDs or member identity keys used internally. The consistency field
83
+ reports `may_have_shifted` when the event read uses multiple `OFFSET` pages;
84
+ a late-arriving event can change page membership inside a historical window
85
+ too. If either read truncates, page boundaries may have shifted, or the
86
+ terminal cohort cannot be covered in one query under the 800,000-character
87
+ SQL limit or 50,000-token parser limit, `status` is `incomplete` and all new
88
+ cohort counts and follow-up duration are `null`; `incompleteReason`
89
+ identifies the limiting read. Do not report percentages from that partial
90
+ result. Existing journey counts and denominators remain independent of this
91
+ follow-up read.
68
92
  - `maxDepth` defaults to 8 and is bounded at 40. Request `maxDepth: 40` for a
69
93
  deeper pass. When sessions continue past the requested depth,
70
94
  `coverage.truncated` is true and the boundary node's `deeperN` says how many
@@ -3,6 +3,20 @@
3
3
  All notable user-facing changes to Agent-Native Analytics are documented here. Open it any
4
4
  time from the command menu (Cmd+K → "What's new") or from Settings.
5
5
 
6
+ ## 2026-10-09
7
+
8
+ ### Improved
9
+
10
+ - Onboarding journeys include Builder connection and custom-key outcomes through deeper paths
11
+ - Onboarding journeys now show later activity within a chosen window, mark no-later results as right-censored, and identify when follow-up counts are incomplete.
12
+ - You can add app origins to a public key's replay allowlist without replacing its existing origins.
13
+
14
+ ### Fixed
15
+
16
+ - Journey captures now flag visible previews missing from the recording instead of saving blank screenshots
17
+ - Journey recordings flag iframe content when clipping, masks, or filters make its visibility uncertain.
18
+ - Large onboarding journeys keep their step counts when follow-up coverage exceeds query limits
19
+
6
20
  ## 2026-10-08
7
21
 
8
22
  ### Added
@@ -19,6 +33,9 @@ time from the command menu (Cmd+K → "What's new") or from Settings.
19
33
 
20
34
  ### Fixed
21
35
 
36
+ - Analytics journey paths group short resource IDs consistently across apps
37
+ - Analytics keeps onboarding counts complete when only standalone setup events exceed the journey read limit.
38
+ - Onboarding journey trees show Home chat setup choices in a separately counted tree and group equivalent artifact screens.
22
39
  - Agent-shared recording links now load across batched playback chunks
23
40
  - Fixed onboarding journey queries against BigQuery.
24
41
  - Session replay agent links can read their recording manifest and playback chunks.
@@ -34,7 +34,7 @@ const isoDate = z
34
34
 
35
35
  export default defineAction({
36
36
  description:
37
- 'Build the onboarding journey tree for a date window: per-session step sequences (signup, onboarding questions, setup method, app entry, first action, first output) folded into a prefix tree. format "tree" returns { window, app, rootN, coverage, nodes } for sessions that entered onboarding, plus standaloneSetup for Home chat integration events in sessions without onboarding; its root and percentages have a separate denominator. Each node has n, pctOfRoot, pctOfParent, dropoffN/dropoffPct (last observed step, not a confirmed exit), deeperN (sessions with a later observed step omitted below this node), and example sessions with recordingId, a recording-start-relative offsetMs, and viewport. format "summary" returns the same counts as indented outlines with no examples. Check each tree\'s coverage.truncated first: true means its event sample, requested depth, or node list cut that tree; notes explain why. Use it to plan onboarding storyboards or find where users stop. For a standalone storyboard, pass standaloneSetup as the top-level tree to journey:capture.',
37
+ 'Build the onboarding journey tree for a date window: per-session step sequences (signup, onboarding questions, setup method, app entry, first action, first output) folded into a prefix tree. format "tree" returns { window, app, rootN, coverage, nodes } for sessions that entered onboarding, plus standaloneSetup for Home chat integration events in sessions without onboarding; its root and percentages have a separate denominator. Each node has n, pctOfRoot, pctOfParent, dropoffN/dropoffPct (last observed step, not a confirmed exit), deeperN (sessions with a later observed step omitted below this node), and example sessions with recordingId, a recording-start-relative offsetMs, and viewport. Both formats include followUp counts of sessions with and without any later native Analytics event in the same session after each terminal selected step, observed before one frozen observationCutoff. followUp.rightCensoredAtWindowEnd is true: no later event means none was recorded within that bounded window, not abandonment or churn. If the event read may be incomplete, the aggregate query truncates or exceeds the SQL size or parser-token limit, or cohort coverage mismatches, followUp.status is incomplete, incompleteReason names the cause, and its cohort counts and duration are null. format "summary" returns the same tree counts as indented outlines with no examples. Check each tree\'s coverage.truncated first: true means its event sample, requested depth, or node list cut that tree; notes explain why. Use it to plan onboarding storyboards or see each cohort\'s last recorded selected step. For a standalone storyboard, pass standaloneSetup as the top-level tree to journey:capture.',
38
38
  schema: z.object({
39
39
  dateFrom: isoDate.describe(
40
40
  "Inclusive UTC start date, YYYY-MM-DD. Sessions that began earlier appear mid-journey, so start a day before the period you care about.",
@@ -185,6 +185,7 @@ export function SessionReplayStoryboardExportDialog({
185
185
 
186
186
  function captureErrorMessage(error: unknown): string {
187
187
  if (error instanceof ReplayScreenshotAssetError) {
188
+ console.warn("Replay screenshot capture rejected", error.reason);
188
189
  return t("sessions.screenshotUnsupportedAssets");
189
190
  }
190
191
  return error instanceof Error
@@ -1,5 +1,15 @@
1
1
  export class ReplayScreenshotAssetError extends Error {
2
- constructor() {
2
+ constructor(
3
+ readonly reason:
4
+ | "unsupportedAsset"
5
+ | "fontReadiness"
6
+ | "cloneDocument"
7
+ | "duplicateCloneMarker"
8
+ | "cloneRoot"
9
+ | "cloneElement"
10
+ | "cloneStageFrame"
11
+ | "clonePseudoElement" = "unsupportedAsset",
12
+ ) {
3
13
  super("Replay contains media or images that cannot be captured safely");
4
14
  this.name = "ReplayScreenshotAssetError";
5
15
  }
@@ -914,7 +924,7 @@ export function inlineReplayAssets(
914
924
  const originalDocuments = [originalDocument];
915
925
  const clonedDocuments = markedReplayDocuments(clonedDocument, captureId);
916
926
  if (clonedDocuments.length !== 1) {
917
- throw new ReplayScreenshotAssetError();
927
+ throw new ReplayScreenshotAssetError("cloneDocument");
918
928
  }
919
929
 
920
930
  const clonesByMarker = new Map<string, Element>();
@@ -922,7 +932,9 @@ export function inlineReplayAssets(
922
932
  for (const element of renderedElements(cloned)) {
923
933
  const marker = element.getAttribute(REPLAY_SCREENSHOT_MARKER);
924
934
  if (!marker?.startsWith(`${captureId}-`)) continue;
925
- if (clonesByMarker.has(marker)) throw new ReplayScreenshotAssetError();
935
+ if (clonesByMarker.has(marker)) {
936
+ throw new ReplayScreenshotAssetError("duplicateCloneMarker");
937
+ }
926
938
  clonesByMarker.set(marker, element);
927
939
  }
928
940
  }
@@ -948,7 +960,7 @@ export function inlineReplayAssets(
948
960
  rootMarker,
949
961
  );
950
962
  if (!cloned || rootMarker !== `${captureId}-${documentIndex}-0`) {
951
- throw new ReplayScreenshotAssetError();
963
+ throw new ReplayScreenshotAssetError("cloneRoot");
952
964
  }
953
965
  const documentAssets = assets.get(original) ?? new Map();
954
966
 
@@ -966,7 +978,7 @@ export function inlineReplayAssets(
966
978
  originalElement.tagName !== clonedElement.tagName &&
967
979
  !originalElement.localName.includes("-"))
968
980
  ) {
969
- throw new ReplayScreenshotAssetError();
981
+ throw new ReplayScreenshotAssetError("cloneElement");
970
982
  }
971
983
  if (originalElement instanceof view.HTMLIFrameElement) {
972
984
  const screenshot = iframeScreenshots.get(originalElement);
@@ -1059,7 +1071,9 @@ export function inlineReplayAssets(
1059
1071
  : "___html2canvas___pseudoelement_after";
1060
1072
  if (!clonedElement.classList.contains(generatedClass)) continue;
1061
1073
  const pseudoElement = pseudoElements[pseudoElementIndex++];
1062
- if (!pseudoElement) throw new ReplayScreenshotAssetError();
1074
+ if (!pseudoElement) {
1075
+ throw new ReplayScreenshotAssetError("clonePseudoElement");
1076
+ }
1063
1077
  if (
1064
1078
  pseudoStyles.display === "none" ||
1065
1079
  pseudoStyles.visibility === "hidden" ||
@@ -1119,7 +1133,7 @@ async function assertDocumentFontsReady(document: Document): Promise<void> {
1119
1133
  fontSet.ready,
1120
1134
  new Promise<never>((_resolve, reject) => {
1121
1135
  timeoutId = window.setTimeout(
1122
- () => reject(new ReplayScreenshotAssetError()),
1136
+ () => reject(new ReplayScreenshotAssetError("fontReadiness")),
1123
1137
  REPLAY_FONT_TIMEOUT_MS,
1124
1138
  );
1125
1139
  }),
@@ -1339,7 +1353,9 @@ export async function captureReplayScreenshot(
1339
1353
  const clonedFrame = clonedStageRoot.querySelector(
1340
1354
  `iframe[${REPLAY_SCREENSHOT_MARKER}="${stageFrameMarker}"]`,
1341
1355
  );
1342
- if (!clonedFrame) throw new ReplayScreenshotAssetError();
1356
+ if (!clonedFrame) {
1357
+ throw new ReplayScreenshotAssetError("cloneStageFrame");
1358
+ }
1343
1359
  const image = clonedStageRoot.ownerDocument.createElement("img");
1344
1360
  image.alt = "";
1345
1361
  image.src = replayImageUrl;
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-10-09
4
+ ---
5
+
6
+ Large onboarding journeys keep their step counts when follow-up coverage exceeds query limits
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-10-09
4
+ ---
5
+
6
+ Onboarding journeys now show later activity within a chosen window, mark no-later results as right-censored, and identify when follow-up counts are incomplete.
@@ -8,6 +8,7 @@
8
8
  "build": "agent-native build && tsx scripts/emit-netlify-dashboard-report-cron.ts",
9
9
  "start": "agent-native start",
10
10
  "test": "vitest --run",
11
+ "test:bigquery-emulator": "vitest --run --config vitest.bigquery.config.ts",
11
12
  "format.fix": "oxfmt --write .",
12
13
  "typecheck": "agent-native typecheck",
13
14
  "migrate:production": "tsx scripts/migrate-production.ts",
@@ -15,5 +15,5 @@ export const ANALYTICS_MCP = {
15
15
  "list-error-issues",
16
16
  ],
17
17
  instructions:
18
- "Find saved dashboards with list-sql-dashboards or search-dashboard-references and read one with get-sql-dashboard; saved analyses with list-analyses and get-analysis. Find metric definitions and saved SQL with search-analytics-query-catalog and list-data-dictionary. Call data-source-status to see which providers are connected. List published blog articles with builder-blog-articles. CRM and calls: hubspot-deals, hubspot-records, hubspot-metrics, hubspot-pipelines, gong-calls, account-deep-dive; gong-native-insights lists Gong's paid synthesis operations. Product behavior: list-session-recordings, get-session-replay-summary, get-session-replay-timeline, list-error-issues, get-error-issue, match-error-issues. Onboarding funnel tree with drop-off and example replays: get-onboarding-journey.",
18
+ "Find saved dashboards with list-sql-dashboards or search-dashboard-references and read one with get-sql-dashboard; saved analyses with list-analyses and get-analysis. Find metric definitions and saved SQL with search-analytics-query-catalog and list-data-dictionary. Call data-source-status to see which providers are connected. List published blog articles with builder-blog-articles. CRM and calls: hubspot-deals, hubspot-records, hubspot-metrics, hubspot-pipelines, gong-calls, account-deep-dive; gong-native-insights lists Gong's paid synthesis operations. Product behavior: list-session-recordings, get-session-replay-summary, get-session-replay-timeline, list-error-issues, get-error-issue, match-error-issues. Onboarding journey tree with window-bounded later-activity counts, observation cutoff, right-censoring, and example replays: get-onboarding-journey.",
19
19
  } satisfies AgentChatMcpOptions;
@@ -15,6 +15,7 @@ import {
15
15
  type BigQueryTableRef,
16
16
  } from "./bigquery.js";
17
17
  import { requireRequestCredentialContext } from "./credentials-context.js";
18
+ import { firstPartyEventPushdownPredicates } from "./first-party-analytics-pushdown.js";
18
19
  import { validateAnalyticsSqlFunctions } from "./first-party-analytics-sql-policy.js";
19
20
  import { fetchGoogleWithRetry, getAccessToken } from "./gcloud.js";
20
21
  import {
@@ -1302,8 +1303,10 @@ function addPartitionPrunedEventDeduplication(
1302
1303
  }
1303
1304
  // ponytail: insertAll is at-least-once; staging + MERGE is the upgrade path
1304
1305
  // for physical exactly-once if the warehouse contract requires it.
1306
+ const predicates = firstPartyEventPushdownPredicates(sql, sourceIndex);
1305
1307
  result +=
1306
1308
  sql.slice(cursor, predicateEnd) +
1309
+ predicates.map((predicate) => ` AND (${predicate})`).join("") +
1307
1310
  " QUALIFY ROW_NUMBER() OVER (PARTITION BY id ORDER BY received_at DESC) = 1" +
1308
1311
  (predicateEnd < sql.length ? " " : "");
1309
1312
  cursor = predicateEnd;
@@ -0,0 +1,176 @@
1
+ import { lexAgentSql, type AgentSqlToken } from "@agent-native/core/agent-sql";
2
+
3
+ const CONSTANT_WORDS = new Set([
4
+ "date",
5
+ "timestamp",
6
+ "cast",
7
+ "as",
8
+ "string",
9
+ "interval",
10
+ "day",
11
+ "week",
12
+ "month",
13
+ "year",
14
+ "current_date",
15
+ "current_timestamp",
16
+ "date_sub",
17
+ "date_add",
18
+ "timestamp_sub",
19
+ "timestamp_add",
20
+ "null",
21
+ ]);
22
+
23
+ function safePredicate(tokens: AgentSqlToken[], alias: string): string | null {
24
+ while (tokens[0]?.text === "(" && tokens[tokens.length - 1]?.text === ")") {
25
+ let depth = 0;
26
+ const encloses = tokens.every((token, index) => {
27
+ if (token.text === "(") depth++;
28
+ if (token.text === ")") depth--;
29
+ return depth > 0 || index === tokens.length - 1;
30
+ });
31
+ if (!encloses) break;
32
+ tokens = tokens.slice(1, -1);
33
+ }
34
+ let offset = 0;
35
+ if (tokens[1]?.text === ".") {
36
+ if (tokens[0].value.toLowerCase() !== alias.toLowerCase()) return null;
37
+ offset = 2;
38
+ }
39
+ const column = tokens[offset]?.value.toLowerCase();
40
+ const operator = tokens[offset + 1]?.value.toLowerCase();
41
+ if (column !== "event_date" && column !== "event_name") return null;
42
+ if (
43
+ !(column === "event_date"
44
+ ? ["=", ">", ">=", "<", "<=", "between"].includes(operator)
45
+ : ["=", "in"].includes(operator))
46
+ )
47
+ return null;
48
+ const rhs = tokens.slice(offset + 2);
49
+ if (
50
+ !rhs.length ||
51
+ rhs.some(
52
+ (token, index) =>
53
+ token.kind === "parameter" ||
54
+ token.kind === "quoted-identifier" ||
55
+ (token.kind === "word" &&
56
+ ["timestamp", "date", "string"].includes(token.value) &&
57
+ !(
58
+ rhs[index + 1]?.text === "(" ||
59
+ rhs[index + 1]?.kind === "string" ||
60
+ rhs[index - 1]?.value === "as"
61
+ )) ||
62
+ (token.kind === "word" &&
63
+ !CONSTANT_WORDS.has(token.value) &&
64
+ !(operator === "between" && token.value === "and")) ||
65
+ (token.kind === "punctuation" &&
66
+ !["(", ")", ","].includes(token.text)) ||
67
+ (token.kind === "operator" && !["+", "-"].includes(token.text)),
68
+ )
69
+ )
70
+ return null;
71
+ return [column, ...tokens.slice(offset + 1).map((token) => token.text)].join(
72
+ " ",
73
+ );
74
+ }
75
+
76
+ // Only immutable delivery fields commute with latest-receipt deduplication.
77
+ // Payload, identity and timestamp filters must remain outside it.
78
+ export function firstPartyEventPushdownPredicates(
79
+ sql: string,
80
+ sourceIndex: number,
81
+ ): string[] {
82
+ const tokens = lexAgentSql(sql, { dialect: "bigquery" });
83
+ const depths: number[] = [];
84
+ const stack: number[] = [];
85
+ const pairs = new Map<number, number>();
86
+ let wrapper: number | undefined;
87
+ for (let index = 0; index < tokens.length; index++) {
88
+ const token = tokens[index];
89
+ if (token.start <= sourceIndex && token.end > sourceIndex)
90
+ wrapper = stack[stack.length - 1];
91
+ depths[index] = stack.length;
92
+ if (token.text === "(") stack.push(index);
93
+ if (token.text === ")") {
94
+ const open = stack.pop();
95
+ if (open !== undefined) pairs.set(open, index);
96
+ }
97
+ }
98
+ if (wrapper === undefined || tokens[wrapper - 1]?.value !== "from") return [];
99
+ const close = pairs.get(wrapper);
100
+ if (close === undefined) return [];
101
+ let aliasIndex = close + 1;
102
+ if (tokens[aliasIndex]?.value === "as") aliasIndex++;
103
+ const alias = tokens[aliasIndex];
104
+ if (!alias || !["word", "quoted-identifier"].includes(alias.kind)) return [];
105
+ const depth = depths[wrapper];
106
+ let select = wrapper - 2;
107
+ while (
108
+ select >= 0 &&
109
+ !(depths[select] === depth && tokens[select].value === "select")
110
+ )
111
+ select--;
112
+ if (select < 0) return [];
113
+ // A preceding FROM means this derived source belongs to a join/comma list.
114
+ if (
115
+ tokens
116
+ .slice(select, wrapper - 1)
117
+ .some(
118
+ (token, index) =>
119
+ depths[select + index] === depth && token.value === "from",
120
+ )
121
+ )
122
+ return [];
123
+ const where = aliasIndex + 1;
124
+ if (tokens[where]?.value !== "where" || depths[where] !== depth) return [];
125
+ const predicates: AgentSqlToken[][] = [[]];
126
+ let between = false;
127
+ for (let index = where + 1; index < tokens.length; index++) {
128
+ const token = tokens[index];
129
+ if (
130
+ depths[index] < depth ||
131
+ (depths[index] === depth && token.text === ")") ||
132
+ (depths[index] === depth &&
133
+ token.kind === "word" &&
134
+ [
135
+ "group",
136
+ "having",
137
+ "qualify",
138
+ "order",
139
+ "limit",
140
+ "union",
141
+ "except",
142
+ "intersect",
143
+ "window",
144
+ ].includes(token.value))
145
+ )
146
+ break;
147
+ if (
148
+ depths[index] === depth &&
149
+ token.kind === "word" &&
150
+ token.value === "or"
151
+ )
152
+ return [];
153
+ if (
154
+ depths[index] === depth &&
155
+ token.kind === "word" &&
156
+ token.value === "between"
157
+ )
158
+ between = true;
159
+ if (
160
+ depths[index] === depth &&
161
+ token.kind === "word" &&
162
+ token.value === "and"
163
+ ) {
164
+ if (between) between = false;
165
+ else {
166
+ predicates.push([]);
167
+ continue;
168
+ }
169
+ }
170
+ predicates[predicates.length - 1].push(token);
171
+ }
172
+ return predicates.flatMap((predicate) => {
173
+ const safe = safePredicate(predicate, alias.value);
174
+ return safe === null ? [] : [safe];
175
+ });
176
+ }