@agent-native/core-corpus 0.206.0 → 0.206.1-nightly-20261009213000
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +8 -0
- package/corpus/templates/analytics/.agents/skills/onboarding-journeys/SKILL.md +55 -4
- package/corpus/templates/analytics/CHANGELOG.md +2 -0
- package/corpus/templates/analytics/actions/get-onboarding-journey.ts +9 -1
- package/corpus/templates/analytics/actions/update-dashboard.ts +57 -0
- package/corpus/templates/analytics/agent-native.json +8 -1
- package/corpus/templates/analytics/app/i18n/en-US.ts +1 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +101 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/filter-vars.ts +17 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/interpolate.ts +25 -14
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +1 -0
- package/corpus/templates/analytics/changelog/2026-10-09-compare-onboarding-follow-up-across-first-party-apps.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-09-dashboards-can-filter-by-several-values-at-once-with-multi-s.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-09-journey-screenshots-account-for-legacy-clipping-when-checkin.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-09-journey-trees-remain-available-when-a-matching-recording-is-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-09-onboarding-journeys-can-find-recordings-from-exact-replay-li.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-09-onboarding-journeys-count-saved-clips-and-completed-deck-gen.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-09-session-recordings-keep-their-full-history-and-performance-i.md +6 -0
- package/corpus/templates/analytics/scripts/journey-capture-iframe-audit.ts +110 -14
- package/corpus/templates/analytics/server/db/index.ts +19 -0
- package/corpus/templates/analytics/server/db/schema.ts +68 -35
- package/corpus/templates/analytics/server/jobs/analytics-alerts.ts +123 -9
- package/corpus/templates/analytics/server/lib/analytics-alerts.ts +383 -2
- package/corpus/templates/analytics/server/lib/bigquery.ts +65 -1
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +23 -3
- package/corpus/templates/analytics/server/lib/error-capture.ts +111 -5
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +2 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +4 -1
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +338 -5
- package/corpus/templates/analytics/server/lib/journey-steps.ts +105 -9
- package/corpus/templates/analytics/server/lib/onboarding-journey.ts +628 -6
- package/corpus/templates/analytics/server/lib/replay-link-timestamp.ts +53 -0
- package/corpus/templates/analytics/server/lib/session-event-index.ts +137 -16
- package/corpus/templates/analytics/server/lib/session-friction.ts +396 -96
- package/corpus/templates/analytics/server/lib/session-performance.ts +273 -51
- package/corpus/templates/analytics/server/lib/session-recording-associations.ts +28 -0
- package/corpus/templates/analytics/server/lib/session-replay.ts +405 -102
- package/corpus/templates/analytics/server/plugins/db.ts +111 -49
- package/corpus/templates/brain/.agents/skills/ingestion-and-connectors/SKILL.md +20 -0
- package/corpus/templates/brain/actions/_source-config.ts +2 -1
- package/corpus/templates/brain/actions/list-connection-providers.ts +1 -1
- package/corpus/templates/brain/app/i18n-data.ts +10 -0
- package/corpus/templates/brain/app/routes/sources.tsx +22 -1
- package/corpus/templates/brain/changelog/2026-10-07-zoom-sources-can-import-ai-companion-summaries.md +6 -0
- package/corpus/templates/brain/server/lib/connectors.ts +142 -14
- package/corpus/templates/brain/server/lib/zoom.ts +126 -0
- package/corpus/templates/content/actions/_content-save-outcomes.ts +280 -0
- package/corpus/templates/content/actions/_document-edit-mutation.ts +91 -24
- package/corpus/templates/content/actions/create-document.ts +9 -3
- package/corpus/templates/content/actions/edit-document.ts +43 -2
- package/corpus/templates/content/actions/record-editor-mount.ts +55 -0
- package/corpus/templates/content/actions/resolve-preview-document-draft.ts +6 -2
- package/corpus/templates/content/actions/update-document.ts +110 -14
- package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +660 -273
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +76 -44
- package/corpus/templates/content/app/components/editor/DocumentEditorSkeleton.tsx +1 -6
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +344 -257
- package/corpus/templates/content/app/components/editor/PageDraftRecovery.tsx +1 -1
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +60 -7
- package/corpus/templates/content/app/components/editor/editor-mount-telemetry.ts +76 -0
- package/corpus/templates/content/app/components/layout/ContentStartupShell.tsx +3 -2
- package/corpus/templates/content/app/components/layout/Layout.tsx +2 -1
- package/corpus/templates/content/app/hooks/use-documents.ts +5 -1
- package/corpus/templates/content/app/i18n-data.ts +2 -0
- package/corpus/templates/content/app/lib/content-save-telemetry.ts +17 -0
- package/corpus/templates/content/changelog/2026-10-09-chatgpt-and-codex-widgets-show-the-document-s-top-bar-with-a.md +6 -0
- package/corpus/templates/content/e2e/helpers.ts +104 -14
- package/corpus/templates/content/e2e/save-lineage.ts +519 -0
- package/corpus/templates/content/parity/matrix.md +1 -1
- package/corpus/templates/content/parity/matrix.ts +5 -1
- package/corpus/templates/content/server/lib/chatgpt-directory-tools.ts +35 -1
- package/corpus/templates/content/shared/editor-mount-outcomes.ts +13 -0
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +5 -6
- package/corpus/templates/design/.agents/skills/journey-storyboards/SKILL.md +17 -0
- package/corpus/templates/design/AGENTS.md +1 -1
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +4 -0
- package/corpus/templates/design/actions/connect-localhost.ts +16 -0
- package/corpus/templates/design/actions/create-file.ts +346 -39
- package/corpus/templates/design/actions/create-journey-canvas.ts +2 -1
- package/corpus/templates/design/actions/delete-file.ts +56 -10
- package/corpus/templates/design/actions/generate-design.ts +70 -28
- package/corpus/templates/design/actions/open-visual-edit.ts +1 -2
- package/corpus/templates/design/actions/present-design-variants.ts +5 -16
- package/corpus/templates/design/actions/refresh-localhost-preview-token.ts +111 -80
- package/corpus/templates/design/actions/update-design.ts +561 -95
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +58 -7
- package/corpus/templates/design/app/components/design/EditPanel.tsx +4 -1
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +277 -222
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +311 -269
- package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbenchLoader.tsx +0 -7
- package/corpus/templates/design/app/components/design/editor/DesignWorkspaceRail.tsx +131 -163
- package/corpus/templates/design/app/components/design/editor/EditorTopBar.tsx +87 -7
- package/corpus/templates/design/app/components/design/editor/LeftPanelHeader.tsx +25 -0
- package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +4 -1
- package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +8 -0
- package/corpus/templates/design/app/global.css +1 -1
- package/corpus/templates/design/app/hooks/use-question-flow.ts +23 -7
- package/corpus/templates/design/app/i18n/en-US.ts +14 -3
- package/corpus/templates/design/app/lib/design-save-outbox.ts +203 -1
- package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +31 -7
- package/corpus/templates/design/app/pages/design-editor/commands/delete-files.ts +2 -2
- package/corpus/templates/design/app/pages/design-editor/commands/duplicate-screen.ts +29 -12
- package/corpus/templates/design/app/pages/design-editor/commands/undo.ts +36 -6
- package/corpus/templates/design/app/pages/design-editor/design-editor-view.tsx +42 -22
- package/corpus/templates/design/app/pages/design-editor/domains/localhost-preview-recovery.ts +62 -0
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-active-screen-and-geometry.ts +415 -39
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-canvas-and-screens.ts +73 -4
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-edit-commands.ts +13 -13
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-files-and-saving.ts +34 -5
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-screen-rendering.tsx +84 -17
- package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +18 -4
- package/corpus/templates/design/app/pages/design-editor/history.ts +8 -0
- package/corpus/templates/design/app/pages/design-editor/mcp-widget-write-capabilities.ts +16 -0
- package/corpus/templates/design/app/pages/design-editor/minimal-inspector.ts +23 -0
- package/corpus/templates/design/app/pages/design-editor/top-bar.ts +12 -1
- package/corpus/templates/design/app/pages/design-editor/types.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/view/editor-canvas-area.tsx +4 -6
- package/corpus/templates/design/app/pages/design-editor/view/left-sidebar.tsx +39 -58
- package/corpus/templates/design/app/pages/design-editor/view/project-menu.tsx +2 -4
- package/corpus/templates/design/app/pages/design-editor/view/project-title-control.tsx +52 -41
- package/corpus/templates/design/app/pages/design-editor/view/right-rail.tsx +30 -10
- package/corpus/templates/design/app/root.tsx +18 -7
- package/corpus/templates/design/changelog/2026-10-08-fixed-local-design-edit-and-preview-regressions.md +6 -0
- package/corpus/templates/design/changelog/2026-10-09-ads-and-social-posts-keep-their-exact-size-with-no-extra-mob.md +6 -0
- package/corpus/templates/design/changelog/2026-10-09-chatgpt-widgets-show-design-title-and-sharing-controls.md +6 -0
- package/corpus/templates/design/changelog/2026-10-09-design-in-chatgpt-opens-on-the-whole-canvas-with-every-scree.md +6 -0
- package/corpus/templates/design/changelog/2026-10-09-design-widgets-include-editor-controls.md +6 -0
- package/corpus/templates/design/changelog/2026-10-09-journey-recording-gaps.md +6 -0
- package/corpus/templates/design/changelog/2026-10-09-left-rail-is-now-a-56px-file-agents-and-tokens-rail-with-lab.md +6 -0
- package/corpus/templates/design/changelog/2026-10-09-widget-interact-controls-clear-header.md +6 -0
- package/corpus/templates/design/e2e/loopback-design-provider.ts +44 -0
- package/corpus/templates/design/e2e/sql-inline-bytes-scan.ts +328 -0
- package/corpus/templates/design/playwright.config.ts +1 -0
- package/corpus/templates/design/server/db/schema.ts +21 -0
- package/corpus/templates/design/server/lib/chatgpt-directory-tools.ts +51 -26
- package/corpus/templates/design/server/lib/localhost-connection.ts +9 -9
- package/corpus/templates/design/server/lib/screen-restore-claims.ts +29 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/design/server/plugins/core-routes.ts +15 -3
- package/corpus/templates/design/server/plugins/db.ts +15 -0
- package/corpus/templates/design/shared/canvas-dimensions.ts +24 -13
- package/corpus/templates/design/shared/generation-provenance.ts +66 -0
- package/corpus/templates/design/shared/journey-canvas-messages.ts +4 -0
- package/corpus/templates/design/shared/journey-canvas.ts +256 -5
- package/corpus/templates/design/shared/local-visual-edit-principal.ts +11 -0
- package/corpus/templates/design/shared/specified-design-prompt.ts +15 -0
- package/corpus/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +20 -21
- package/corpus/templates/slides/.agents/skills/slide-design/SKILL.md +73 -98
- package/corpus/templates/slides/.agents/skills/slide-editing/SKILL.md +108 -186
- package/corpus/templates/slides/.agents/skills/slide-images/SKILL.md +6 -0
- package/corpus/templates/slides/app/components/editor/EditorActionCluster.tsx +2 -1
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +89 -47
- package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +179 -158
- package/corpus/templates/slides/app/global.css +27 -0
- package/corpus/templates/slides/app/hooks/use-narrow-element.ts +30 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +410 -228
- package/corpus/templates/slides/app/pages/Index.tsx +1 -0
- package/corpus/templates/slides/changelog/2026-10-09-slides-keep-generating-when-image-search-is-unavailable.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-09-the-slides-widget-in-chatgpt-and-codex-now-has-the-editor-s-.md +6 -0
- package/corpus/templates/slides/server/lib/chatgpt-directory-tools.ts +46 -14
- package/corpus/templates/slides/server/lib/generation-completion.ts +4 -5
- package/package.json +1 -1
- package/corpus/templates/content/app/components/editor/DirectoryWidgetFormattingToolbar.tsx +0 -73
package/corpus/README.md
CHANGED
|
@@ -328,6 +328,14 @@ Use conditional blocks for optional filters:
|
|
|
328
328
|
{{?country}}AND country = '{{country}}'{{/country}}
|
|
329
329
|
```
|
|
330
330
|
|
|
331
|
+
**Use `type: "multi-select"` for a pick list where several options can apply at once.** Give it `options` like a `select`. Its value is the selected option values joined by commas. Interpolate it as `IN ({{<id>:list}})`, which expands to one quoted literal per selected value, and wrap the clause in a conditional so an empty selection drops the filter:
|
|
332
|
+
|
|
333
|
+
```sql
|
|
334
|
+
{{?plan}}AND plan IN ({{plan:list}}){{/plan}}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
An unwrapped `{{<id>:list}}` with no selection fails the query on purpose. Option values must not contain commas.
|
|
338
|
+
|
|
331
339
|
Filters auto-apply on change — there is no Apply button. Each filter change writes to the URL and re-runs the affected panels. Other filters are preserved (the URL update is functional, not destructive). If you see a filter "reset" itself when another filter changes, look for a duplicate `id` first.
|
|
332
340
|
|
|
333
341
|
## Modifying A Dashboard
|
|
@@ -34,11 +34,16 @@ same counts as an indented `outline` with no examples. Use it first to choose a
|
|
|
34
34
|
window, `app`, `maxDepth`, `minNodeSessions` (small branches merge into an
|
|
35
35
|
`other` node), and `maxNodes`.
|
|
36
36
|
|
|
37
|
+
To include the bounded cross-session estimate from the repository root, run:
|
|
38
|
+
`pnpm --filter analytics action get-onboarding-journey --dateFrom=2026-08-01 --dateTo=2026-08-31 --app=all --emailFilter=exclude_builder --followUpMode=person --format=summary`.
|
|
39
|
+
|
|
37
40
|
```ts
|
|
38
41
|
type JourneyExample = { sessionId: string; recordingId: string | null; ts: string; offsetMs: number | null; viewport: { width: number; height: number } | null; viewportReason?: string; replayUrl?: string };
|
|
39
42
|
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[] };
|
|
40
43
|
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
|
|
44
|
+
type JourneyPersonFollowupCounts = { canonicalPeople: number; laterActivityInSelectedSession: number; laterActivityOutsideSelectedSessionOrApp: number; laterActivityInBothSelectedAndOutside: number; laterActivityObservedAnywhere: number; noActivityObservedWithinHorizon: number; rightCensoredHorizon: number; fullyObservedCanonicalPeople: number; noActivityObservedWithinHorizonPctOfFullyObservedCanonicalPeople: number | null; identityUnavailableSessions: number; identityUnavailableSessionEvidence: { laterActivityInSelectedSession: number; laterActivityOutsideSelectedSessionOrApp: number } };
|
|
45
|
+
type JourneyPersonFollowup = { status: "complete" | "incomplete"; incompleteReason?: "journey_event_read_truncated" | "journey_event_read_invalid" | "journey_event_read_may_have_shifted" | "terminal_cohort_too_large" | "terminal_cohort_invalid" | "person_followup_aggregate_truncated" | "person_followup_query_cost_limited" | "person_followup_aggregate_invalid" | "person_followup_terminal_cohort_mismatch"; horizonDays: 30; horizonMs: number; observationWatermark: string; observationFollowupDurationMs: { min: number; max: number; mean: number } | null; coverage: { journeyEventRead: { rows: number; pages: number; truncated: boolean; paginationConsistency: "stable" | "may_have_shifted" }; followupAggregateRead: { status: "complete" | "truncated" | "incomplete" | "not_run"; rows: number | null; queries: number; truncated: boolean }; terminalSessions: number | null; sessionsWithoutSelectedStep: number | null; identityJoin: { status: "complete" | "partial" | "unavailable" | "not_applicable" | "unknown"; terminalSessions: number | null; sessionsWithCanonicalIdentity: number | null; sessionsWithoutCanonicalIdentity: number | null; uniqueCanonicalPeople: number | null; coveragePct: number | null } }; total: JourneyPersonFollowupCounts | null; byTerminalStepKey: Record<string, JourneyPersonFollowupCounts> | null };
|
|
46
|
+
type JourneyTree = { window: { from: string; to: string }; app: string; rootN: number; coverage: { sessionsWithEvents: number; sessionsWithReplay: number; truncated: boolean }; nodes: JourneyNode[]; followUp: JourneyFollowup; personFollowUp?: JourneyPersonFollowup; standaloneSetup?: { rootN: number; coverage: { sessionsWithEvents: number; sessionsWithReplay: number; truncated: boolean }; nodes: JourneyNode[] }; notes?: string[] };
|
|
42
47
|
```
|
|
43
48
|
|
|
44
49
|
Analytics returns cohort nodes with counts. When extending a Design storyboard
|
|
@@ -47,6 +52,25 @@ only to the Design input, set `referenceOnly: true`, and omit the cohort metric
|
|
|
47
52
|
fields. This annotation is for visual references and is not emitted by
|
|
48
53
|
`get-onboarding-journey`.
|
|
49
54
|
|
|
55
|
+
Saved outputs require source-confirmed completion events. Clips uses
|
|
56
|
+
`recording_ready`; `recording_started`, transcript-only `recording_completed`,
|
|
57
|
+
and `clip_viewed` are not saved outputs. Slides uses explicit
|
|
58
|
+
`generation_completed`; `generation_request_accepted`,
|
|
59
|
+
`generation_outcome_unresolved`, `generation_failed`, `generation_stuck`,
|
|
60
|
+
`generation_cancelled`, and `generation_abandoned` remain distinct attempt
|
|
61
|
+
steps. An unresolved event can carry `persisted_output: true`, but it still does
|
|
62
|
+
not substitute for `generation_completed`. `deck_edited`, `output_viewed`, and
|
|
63
|
+
current deck state also cannot substitute for that event. A missing terminal
|
|
64
|
+
event stays absent. Generation and recording starts remain attempt steps, and
|
|
65
|
+
retry attempts that reuse a deck's output ID remain separate by their exact
|
|
66
|
+
attempt ID. A sessionless Clips completion or Slides lifecycle event is
|
|
67
|
+
attached only when its exact output and attempt pair maps to one distinct
|
|
68
|
+
eligible onboarding session (`recording_started` for Clips;
|
|
69
|
+
`generation_started`, `generation_request_accepted`, or `output_viewed` for
|
|
70
|
+
Slides); missing or ambiguous
|
|
71
|
+
matches stay unattributed. Output and attempt IDs are never included in the
|
|
72
|
+
returned tree.
|
|
73
|
+
|
|
50
74
|
The journey projection also retains `integration_setup_exposed`,
|
|
51
75
|
`integration_method_clicked`, and `integration_method_outcome` as separate
|
|
52
76
|
`integration:<flow>:...` steps. These record setup exposure, the selected
|
|
@@ -89,6 +113,32 @@ top-level tree to `journey:capture`; the capture CLI reads top-level `nodes`.
|
|
|
89
113
|
identifies the limiting read. Do not report percentages from that partial
|
|
90
114
|
result. Existing journey counts and denominators remain independent of this
|
|
91
115
|
follow-up read.
|
|
116
|
+
- Set `followUpMode: "person"` to add `personFollowUp` without changing the
|
|
117
|
+
session tree or same-session `followUp`. Each direct `properties.auth_user_id`
|
|
118
|
+
is counted once and assigned to that person's latest terminal selected step
|
|
119
|
+
in the requested cohort. No email, anonymous, user-key, or organization
|
|
120
|
+
pseudo-ID fallback is used for cross-session matching. The query searches
|
|
121
|
+
later events across first-party apps under the same authenticated org,
|
|
122
|
+
Builder.io email, and test-identity filters. Selected-session/app and
|
|
123
|
+
outside-session/app activity are overlapping evidence classes;
|
|
124
|
+
`laterActivityInBothSelectedAndOutside` reports their overlap, and
|
|
125
|
+
`laterActivityObservedAnywhere` is their union. A member with activity in
|
|
126
|
+
either class is not counted as having no activity. The result also separates
|
|
127
|
+
no activity after a fully observed 30-day horizon, right-censored horizons,
|
|
128
|
+
and identity-unavailable sessions. Identity-unavailable session evidence can
|
|
129
|
+
report activity known from that exact session ID, but those sessions are not
|
|
130
|
+
people and never enter person percentages.
|
|
131
|
+
- `personFollowUp.observationWatermark` freezes both event and receive time at a UTC minute boundary, so repeated requests in that minute use the same read boundary.
|
|
132
|
+
`observationFollowupDurationMs` summarizes capped follow-up among canonical
|
|
133
|
+
people. `noActivityObservedWithinHorizonPctOfFullyObservedCanonicalPeople`
|
|
134
|
+
uses only identified people whose complete 30-day horizon elapsed. It
|
|
135
|
+
measures bounded Analytics coverage, never abandonment or permanent churn.
|
|
136
|
+
Sessions without a selected journey step appear in
|
|
137
|
+
`coverage.sessionsWithoutSelectedStep` and are not eligible no-activity
|
|
138
|
+
observations; a missing next step does not mean no later activity. Read
|
|
139
|
+
`coverage.identityJoin` and `coverage.followupAggregateRead`; if a raw read
|
|
140
|
+
is incomplete or BigQuery rejects its billed-byte cap, counts and percentages are null with
|
|
141
|
+
`status: "incomplete"`.
|
|
92
142
|
- `maxDepth` defaults to 8 and is bounded at 40. Request `maxDepth: 40` for a
|
|
93
143
|
deeper pass. When sessions continue past the requested depth,
|
|
94
144
|
`coverage.truncated` is true and the boundary node's `deeperN` says how many
|
|
@@ -145,9 +195,10 @@ chunk. Fix failures or report them; do not paint over a missing frame.
|
|
|
145
195
|
the PNG. Every visible iframe must have a corresponding recorded child document
|
|
146
196
|
by the requested replay time. If the child document is missing, the frame is
|
|
147
197
|
listed as a failure with code `replay_iframe_content_unavailable` and counts in
|
|
148
|
-
`diagnostics`. If unsupported 3D projection, rounded ancestor clipping,
|
|
149
|
-
clip
|
|
150
|
-
|
|
198
|
+
`diagnostics`. If unsupported 3D projection, rounded ancestor clipping,
|
|
199
|
+
unsupported CSS `clip-path` shapes, unrecognized legacy CSS `clip` values,
|
|
200
|
+
masks, or visibility-altering filters prevent the audit from verifying
|
|
201
|
+
visibility, the frame is listed with code
|
|
151
202
|
`replay_iframe_visibility_unverifiable`; do not treat uncertainty as either
|
|
152
203
|
missing content or a successful audit. The check covers every visible iframe
|
|
153
204
|
because replay can omit its original source attribute while rebuilding an
|
|
@@ -15,6 +15,8 @@ time from the command menu (Cmd+K → "What's new") or from Settings.
|
|
|
15
15
|
|
|
16
16
|
- Journey captures now flag visible previews missing from the recording instead of saving blank screenshots
|
|
17
17
|
- Journey recordings flag iframe content when clipping, masks, or filters make its visibility uncertain.
|
|
18
|
+
- Onboarding journeys count saved clips and completed deck generations as outputs while showing Slides attempt outcomes separately
|
|
19
|
+
- Session recordings keep their full history and performance insights across browser session changes and large replays.
|
|
18
20
|
- Large onboarding journeys keep their step counts when follow-up coverage exceeds query limits
|
|
19
21
|
|
|
20
22
|
## 2026-10-08
|
|
@@ -34,7 +34,7 @@ const isoDate = z
|
|
|
34
34
|
|
|
35
35
|
export default defineAction({
|
|
36
36
|
description:
|
|
37
|
-
'
|
|
37
|
+
'Return an access-scoped per-session onboarding tree through explicit saved-output events, preserving session counts and direct-parent denominators. followUpMode "session" keeps existing bounded same-session counts. "person" adds aggregate-only activity joined by direct canonical auth_user_id across first-party sessions and apps over a fixed 30-day horizon. It separates selected-session activity, outside-session/app activity and their overlap, no activity after a fully observed horizon, right-censoring, unknown identity, identity coverage, and read completeness. Its minute-aligned observationWatermark freezes event and receive time. Unknown identity and sessions without a selected step never count as inactive; no-activity is window-bounded evidence, not permanent churn. Output and attempt IDs are never returned. Check coverage.truncated before interpreting counts.',
|
|
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.",
|
|
@@ -56,6 +56,13 @@ export default defineAction({
|
|
|
56
56
|
.describe(
|
|
57
57
|
'Builder-employee scope, as in the onboarding metrics: "exclude_builder" (default), "only_builder", or "all". Test identities are always excluded.',
|
|
58
58
|
),
|
|
59
|
+
followUpMode: z
|
|
60
|
+
.enum(["session", "person"])
|
|
61
|
+
.optional()
|
|
62
|
+
.default("session")
|
|
63
|
+
.describe(
|
|
64
|
+
'Follow-up grain: "session" (default) keeps existing same-session counts; "person" adds a scoped 30-day cross-session, cross-app estimate joined only by direct canonical auth_user_id.',
|
|
65
|
+
),
|
|
59
66
|
format: z
|
|
60
67
|
.enum(["tree", "summary"])
|
|
61
68
|
.optional()
|
|
@@ -184,6 +191,7 @@ export default defineAction({
|
|
|
184
191
|
dateTo: args.dateTo,
|
|
185
192
|
app: args.app,
|
|
186
193
|
emailFilter: args.emailFilter,
|
|
194
|
+
followUpMode: args.followUpMode,
|
|
187
195
|
format: args.format,
|
|
188
196
|
maxNodes: args.maxNodes,
|
|
189
197
|
maxDepth: args.maxDepth,
|
|
@@ -9,6 +9,10 @@ import {
|
|
|
9
9
|
import { track } from "@agent-native/core/tracking";
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
|
|
12
|
+
import {
|
|
13
|
+
MULTI_SELECT_EMPTY,
|
|
14
|
+
normalizeMultiSelectValue,
|
|
15
|
+
} from "../app/pages/adhoc/sql-dashboard/filter-vars";
|
|
12
16
|
import {
|
|
13
17
|
interpolate,
|
|
14
18
|
interpolateDashboardPanelSql,
|
|
@@ -292,6 +296,59 @@ function collectDashboardConfigIssues(
|
|
|
292
296
|
}
|
|
293
297
|
const id = typeof f.id === "string" ? f.id.trim() : "";
|
|
294
298
|
if (!id) return dashboardIssue(`config.filters[${i}].id is required`);
|
|
299
|
+
if (f.type === "multi-select") {
|
|
300
|
+
// The selection is comma-joined in the URL, so a value cannot be empty or contain ",". MULTI_SELECT_EMPTY is reserved for the cleared state.
|
|
301
|
+
if (f.options !== undefined && !Array.isArray(f.options)) {
|
|
302
|
+
return dashboardIssue(
|
|
303
|
+
`config.filters[${i}].options must be an array`,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
const options: unknown[] = Array.isArray(f.options) ? f.options : [];
|
|
307
|
+
for (let j = 0; j < options.length; j++) {
|
|
308
|
+
const option = options[j] as {
|
|
309
|
+
value?: unknown;
|
|
310
|
+
label?: unknown;
|
|
311
|
+
} | null;
|
|
312
|
+
if (
|
|
313
|
+
!option ||
|
|
314
|
+
typeof option !== "object" ||
|
|
315
|
+
typeof option.value !== "string" ||
|
|
316
|
+
typeof option.label !== "string"
|
|
317
|
+
) {
|
|
318
|
+
return dashboardIssue(
|
|
319
|
+
`config.filters[${i}].options[${j}] must be an object with string value and label`,
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
if (
|
|
323
|
+
option.value === "" ||
|
|
324
|
+
option.value.includes(",") ||
|
|
325
|
+
option.value === MULTI_SELECT_EMPTY
|
|
326
|
+
) {
|
|
327
|
+
return dashboardIssue(
|
|
328
|
+
`config.filters[${i}].options[${j}].value must be non-empty, cannot contain ",", and cannot be "${MULTI_SELECT_EMPTY}" in a multi-select filter`,
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (typeof f.default === "string" && f.default !== "") {
|
|
333
|
+
const named = normalizeMultiSelectValue(f.default);
|
|
334
|
+
// A default that normalizes to nothing would show All while the query still gets a non-empty value.
|
|
335
|
+
if (named === "") {
|
|
336
|
+
return dashboardIssue(
|
|
337
|
+
`config.filters[${i}].default must name at least one option value in a multi-select filter`,
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
// Each default token must be a configured option: a token with no checkbox would be applied to SQL unseen.
|
|
341
|
+
const values = new Set(
|
|
342
|
+
options.map((option) => (option as { value: string }).value),
|
|
343
|
+
);
|
|
344
|
+
const unknown = named.split(",").find((token) => !values.has(token));
|
|
345
|
+
if (unknown !== undefined) {
|
|
346
|
+
return dashboardIssue(
|
|
347
|
+
`config.filters[${i}].default names "${unknown}", which is not one of its options`,
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
295
352
|
if (seen.has(id)) continue;
|
|
296
353
|
seen.add(id);
|
|
297
354
|
deduped.push(f);
|
|
@@ -7,6 +7,13 @@
|
|
|
7
7
|
}
|
|
8
8
|
},
|
|
9
9
|
"doctor": {
|
|
10
|
-
"failOnBuild": false
|
|
10
|
+
"failOnBuild": false,
|
|
11
|
+
"dbToolScopingDenylist": {
|
|
12
|
+
"bigquery_cache": "provider cache, not a user-facing resource",
|
|
13
|
+
"dashboard_name_locks": "internal dashboard-name serialization rows, never exposed to raw DB tools",
|
|
14
|
+
"dashboard_views": "view telemetry, scoped by dashboard or action",
|
|
15
|
+
"first_party_analytics_cache": "internal query cache, accessed through scoped analytics queries",
|
|
16
|
+
"session_recording_session_associations": "child rows are scoped through authorized session recordings"
|
|
17
|
+
}
|
|
11
18
|
}
|
|
12
19
|
}
|
|
@@ -24,6 +24,11 @@ import {
|
|
|
24
24
|
DialogFooter,
|
|
25
25
|
} from "@/components/ui/dialog";
|
|
26
26
|
import { Input } from "@/components/ui/input";
|
|
27
|
+
import {
|
|
28
|
+
Popover,
|
|
29
|
+
PopoverContent,
|
|
30
|
+
PopoverTrigger,
|
|
31
|
+
} from "@/components/ui/popover";
|
|
27
32
|
import {
|
|
28
33
|
Select,
|
|
29
34
|
SelectContent,
|
|
@@ -37,6 +42,7 @@ import { DateRangeInput } from "../_shared/components/DateRangeInput";
|
|
|
37
42
|
import {
|
|
38
43
|
FILTER_PARAM_PREFIX,
|
|
39
44
|
isDateRangePresetFilter,
|
|
45
|
+
MULTI_SELECT_EMPTY,
|
|
40
46
|
resolveDefault,
|
|
41
47
|
resolveFilterVars,
|
|
42
48
|
} from "./filter-vars";
|
|
@@ -340,6 +346,30 @@ export function DashboardFilterBar({
|
|
|
340
346
|
);
|
|
341
347
|
}
|
|
342
348
|
|
|
349
|
+
function MultiSelectOption({
|
|
350
|
+
label,
|
|
351
|
+
checked,
|
|
352
|
+
onCheckedChange,
|
|
353
|
+
}: {
|
|
354
|
+
label: string;
|
|
355
|
+
checked: boolean;
|
|
356
|
+
onCheckedChange: (checked: boolean) => void;
|
|
357
|
+
}) {
|
|
358
|
+
const id = useId();
|
|
359
|
+
return (
|
|
360
|
+
<div className="flex items-center gap-2 rounded-sm px-2 py-1.5 text-xs hover:bg-accent">
|
|
361
|
+
<Checkbox
|
|
362
|
+
id={id}
|
|
363
|
+
checked={checked}
|
|
364
|
+
onCheckedChange={(next) => onCheckedChange(next === true)}
|
|
365
|
+
/>
|
|
366
|
+
<label htmlFor={id} className="min-w-0 flex-1 cursor-pointer truncate">
|
|
367
|
+
{label}
|
|
368
|
+
</label>
|
|
369
|
+
</div>
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
343
373
|
interface FilterControlProps {
|
|
344
374
|
filter: DashboardFilter;
|
|
345
375
|
vars: Record<string, string>;
|
|
@@ -354,6 +384,8 @@ function FilterControl({
|
|
|
354
384
|
setValue,
|
|
355
385
|
}: FilterControlProps) {
|
|
356
386
|
const t = useT();
|
|
387
|
+
const labelId = useId();
|
|
388
|
+
const triggerId = useId();
|
|
357
389
|
if (filter.type === "date-range") {
|
|
358
390
|
const startKey = `${filter.id}Start`;
|
|
359
391
|
const endKey = `${filter.id}End`;
|
|
@@ -447,6 +479,75 @@ function FilterControl({
|
|
|
447
479
|
return selectControl;
|
|
448
480
|
}
|
|
449
481
|
|
|
482
|
+
if (filter.type === "multi-select") {
|
|
483
|
+
// Values are comma-joined in the URL, so option values must not contain ",".
|
|
484
|
+
const options = filter.options ?? [];
|
|
485
|
+
const selected = (vars[filter.id] || "").split(",").filter(Boolean);
|
|
486
|
+
// Label the values the query receives, including URL values with no matching option, so the trigger never reads "All" over a filtered query.
|
|
487
|
+
const selectedLabels = selected.map(
|
|
488
|
+
(value) => options.find((opt) => opt.value === value)?.label ?? value,
|
|
489
|
+
);
|
|
490
|
+
const setSelected = (next: string[]) =>
|
|
491
|
+
setValue({
|
|
492
|
+
[filter.id]: next.length > 0 ? next.join(",") : MULTI_SELECT_EMPTY,
|
|
493
|
+
});
|
|
494
|
+
const toggle = (value: string, checked: boolean) =>
|
|
495
|
+
setSelected(
|
|
496
|
+
options
|
|
497
|
+
.filter((opt) =>
|
|
498
|
+
opt.value === value ? checked : selected.includes(opt.value),
|
|
499
|
+
)
|
|
500
|
+
.map((opt) => opt.value),
|
|
501
|
+
);
|
|
502
|
+
return (
|
|
503
|
+
<div className="flex flex-col gap-1">
|
|
504
|
+
<label
|
|
505
|
+
id={labelId}
|
|
506
|
+
className="text-xs text-muted-foreground font-medium"
|
|
507
|
+
>
|
|
508
|
+
{filter.label}
|
|
509
|
+
</label>
|
|
510
|
+
<Popover>
|
|
511
|
+
<PopoverTrigger asChild>
|
|
512
|
+
<Button
|
|
513
|
+
id={triggerId}
|
|
514
|
+
aria-labelledby={`${labelId} ${triggerId}`}
|
|
515
|
+
variant="outline"
|
|
516
|
+
size="sm"
|
|
517
|
+
className="w-[160px] justify-start text-xs font-normal"
|
|
518
|
+
>
|
|
519
|
+
<span className="min-w-0 truncate">
|
|
520
|
+
{selectedLabels.length > 0
|
|
521
|
+
? selectedLabels.join(", ")
|
|
522
|
+
: t("sqlDashboard.allValues")}
|
|
523
|
+
</span>
|
|
524
|
+
</Button>
|
|
525
|
+
</PopoverTrigger>
|
|
526
|
+
<PopoverContent align="start" className="w-[200px] p-1">
|
|
527
|
+
{options.map((opt) => (
|
|
528
|
+
<MultiSelectOption
|
|
529
|
+
key={opt.value}
|
|
530
|
+
label={opt.label}
|
|
531
|
+
checked={selected.includes(opt.value)}
|
|
532
|
+
onCheckedChange={(checked) => toggle(opt.value, checked)}
|
|
533
|
+
/>
|
|
534
|
+
))}
|
|
535
|
+
{selected.length > 0 && (
|
|
536
|
+
<Button
|
|
537
|
+
variant="ghost"
|
|
538
|
+
size="sm"
|
|
539
|
+
className="mt-1 h-6 w-full justify-start px-2 text-xs text-muted-foreground"
|
|
540
|
+
onClick={() => setSelected([])}
|
|
541
|
+
>
|
|
542
|
+
{t("sqlDashboard.clearAll")}
|
|
543
|
+
</Button>
|
|
544
|
+
)}
|
|
545
|
+
</PopoverContent>
|
|
546
|
+
</Popover>
|
|
547
|
+
</div>
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
|
|
450
551
|
if (filter.type === "toggle") {
|
|
451
552
|
const active = hasParam(filter.id) && !!vars[filter.id];
|
|
452
553
|
return (
|
|
@@ -2,6 +2,17 @@ import type { DashboardFilter, FilterType } from "./types";
|
|
|
2
2
|
|
|
3
3
|
export const FILTER_PARAM_PREFIX = "f_";
|
|
4
4
|
|
|
5
|
+
// URL marker for a multi-select with every option unchecked. An absent param
|
|
6
|
+
// falls back to the filter default, so an empty selection needs its own value.
|
|
7
|
+
export const MULTI_SELECT_EMPTY = "__empty__";
|
|
8
|
+
|
|
9
|
+
// The one reduction of a multi-select value: the page, saved reports, and
|
|
10
|
+
// save-time validation all read the selection through it.
|
|
11
|
+
export function normalizeMultiSelectValue(raw: string): string {
|
|
12
|
+
if (raw === MULTI_SELECT_EMPTY) return "";
|
|
13
|
+
return raw.split(",").filter(Boolean).join(",");
|
|
14
|
+
}
|
|
15
|
+
|
|
5
16
|
const ALL_TIME_START = "1970-01-01";
|
|
6
17
|
const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
7
18
|
const DATE_FILTER_TYPES: ReadonlySet<FilterType> = new Set([
|
|
@@ -13,6 +24,7 @@ const FILTER_TYPES: DashboardFilter["type"][] = [
|
|
|
13
24
|
"date",
|
|
14
25
|
"date-range",
|
|
15
26
|
"select",
|
|
27
|
+
"multi-select",
|
|
16
28
|
"toggle",
|
|
17
29
|
"text",
|
|
18
30
|
"toggle-date",
|
|
@@ -187,6 +199,11 @@ export function resolveFilterVars(
|
|
|
187
199
|
fallbackEnd,
|
|
188
200
|
fallbackEnd,
|
|
189
201
|
);
|
|
202
|
+
} else if (filter.type === "multi-select") {
|
|
203
|
+
const value = getParam(filter.id);
|
|
204
|
+
out[filter.id] = normalizeMultiSelectValue(
|
|
205
|
+
value || resolveDefault(filter.default, filter.type),
|
|
206
|
+
);
|
|
190
207
|
} else {
|
|
191
208
|
const value = getParam(filter.id);
|
|
192
209
|
out[filter.id] =
|
|
@@ -183,18 +183,29 @@ export function interpolate(
|
|
|
183
183
|
},
|
|
184
184
|
);
|
|
185
185
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
186
|
+
const escapeValue = options.googleSqlValues
|
|
187
|
+
? escapeGoogleSqlValue
|
|
188
|
+
: escapeSqlValue;
|
|
189
|
+
// One pass, so a value that itself contains "{{x}}" is never re-expanded.
|
|
190
|
+
return withConditionals.replace(
|
|
191
|
+
/\{\{(\w+)(:list)?\}\}/g,
|
|
192
|
+
(_match, name, list) => {
|
|
193
|
+
const value = vars[name];
|
|
194
|
+
if (list) {
|
|
195
|
+
// Multi-select values are comma-joined in the URL, so an option value containing "," splits into two items.
|
|
196
|
+
const items = (value ?? "").split(",").filter(Boolean);
|
|
197
|
+
if (items.length === 0) return "__empty_list_filter__";
|
|
198
|
+
return items.map((item) => `'${escapeValue(item)}'`).join(", ");
|
|
199
|
+
}
|
|
200
|
+
if (
|
|
201
|
+
options.failClosedTimeVariables &&
|
|
202
|
+
isTimeVariable(name) &&
|
|
203
|
+
(value == null || value.length === 0)
|
|
204
|
+
) {
|
|
205
|
+
return "__missing_dashboard_time_filter__";
|
|
206
|
+
}
|
|
207
|
+
if (value == null) return "";
|
|
208
|
+
return escapeValue(String(value));
|
|
209
|
+
},
|
|
210
|
+
);
|
|
200
211
|
}
|