Skip to content

Commit 9b554cc

Browse files
open-design-crew[bot]free666799claude
authored
feat(analytics): instrument plan mode & Excalidraw sketch flows (#4862) (#5243)
* feat(analytics): instrument plan mode and Excalidraw sketch flows Adds PostHog tracking for the two features shipped in #4862. Plan mode: wire the (previously defined-but-never-called) `trackComposerSessionModeClick` at the composer/home session-mode toggle so switching into `plan` (and finally ask/design too) emits `session_mode_toggle` with mode_before/mode_after. Run-level plan usage is already covered via `session_mode='plan'` on run_created/run_finished. Sketch: add `sketch_save_result` (explicit Save button, not autosave) and `sketch_export_result` (PNG export written into the project — the sketch's real output) to complete the flow that starts at `new_sketch`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(analytics): track open_sketch re-engagement entry Per acceptance-doc review: add open_sketch (opening an existing sketch from the Design Files list) alongside new_sketch. Keeps sketch_save_result and the sketch_editor area per the same review; run-source attribution on sketch export is deferred. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: free666799 <293857035+free666799@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 3e495d4 commit 9b554cc

8 files changed

Lines changed: 118 additions & 7 deletions

File tree

apps/web/src/analytics/events.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ import type {
120120
ContextLinkResultProps,
121121
ArtifactExportResultProps,
122122
ArtifactDeployResultProps,
123+
SketchSaveResultProps,
124+
SketchExportResultProps,
123125
FeedbackSubmitResultProps,
124126
SettingsViewProps,
125127
SettingsCliTestResultProps,
@@ -612,6 +614,20 @@ export function trackDesignToolboxClick(
612614
send(track, 'ui_click', props);
613615
}
614616

617+
export function trackSketchSaveResult(
618+
track: Track,
619+
props: SketchSaveResultProps,
620+
): void {
621+
send(track, 'sketch_save_result', props);
622+
}
623+
624+
export function trackSketchExportResult(
625+
track: Track,
626+
props: SketchExportResultProps,
627+
): void {
628+
send(track, 'sketch_export_result', props);
629+
}
630+
615631
export function trackComposerBarClick(
616632
track: Track,
617633
props: ComposerBarClickProps,

apps/web/src/components/ChatComposer.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { useAnalytics } from '../analytics/provider';
2323
import {
2424
trackChatPanelClick,
2525
trackComposerBarClick,
26+
trackComposerSessionModeClick,
2627
trackContextLinkResult,
2728
trackDesignToolboxClick,
2829
trackFigmaHelpModalSurfaceView,
@@ -33,6 +34,7 @@ import type {
3334
ComposerBarClickProps,
3435
DesignToolboxClickProps,
3536
} from '@open-design/contracts/analytics';
37+
import { sessionModeToTracking } from '@open-design/contracts/analytics';
3638
import { deriveUploadCohort } from '../analytics/upload-tracking';
3739
import { projectRawUrl, uploadProjectFiles, openFolderDialog, fetchRecentLinkedDirs, pushRecentLinkedDir, dirExists, applyLibraryAsset, fetchLibraryAssetElementHtml } from "../providers/registry";
3840
import { WorkingDirPicker } from './WorkingDirPicker';
@@ -3002,7 +3004,19 @@ export const ChatComposer = forwardRef<ChatComposerHandle, Props>(
30023004
{footerAccessory}
30033005
<SessionModeToggle
30043006
mode={sessionMode}
3005-
onChange={onSessionModeChange}
3007+
onChange={(next) => {
3008+
if (next !== sessionMode) {
3009+
trackComposerSessionModeClick(analytics.track, {
3010+
page_name: 'chat_panel',
3011+
area: 'chat_composer',
3012+
element: 'session_mode_toggle',
3013+
mode_before: sessionModeToTracking(sessionMode),
3014+
mode_after: sessionModeToTracking(next),
3015+
project_id: projectId ?? undefined,
3016+
});
3017+
}
3018+
onSessionModeChange?.(next);
3019+
}}
30063020
/>
30073021
{showStopButton ? (
30083022
<button

apps/web/src/components/FileWorkspace.tsx

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import {
1616
trackFileUploadResult,
1717
trackPageView,
1818
trackTabLauncherClick,
19+
trackSketchSaveResult,
20+
trackSketchExportResult,
1921
} from '../analytics/events';
2022
import { deriveUploadCohort } from '../analytics/upload-tracking';
2123
import { useT } from '../i18n';
@@ -2796,7 +2798,18 @@ export function FileWorkspace({
27962798
onCurrentDirChange={setUploadDir}
27972799
navState={designFilesNavRef.current}
27982800
onNavStateChange={onDesignFilesNavStateChange}
2799-
onOpenFile={openFile}
2801+
onOpenFile={(name) => {
2802+
// Re-engagement entry: opening an existing sketch from the file
2803+
// list (new_sketch already covers fresh creation).
2804+
if (isSketchName(name)) {
2805+
trackFileManagerClick(analytics.track, {
2806+
page_name: 'file_manager',
2807+
area: 'file_manager',
2808+
element: 'open_sketch',
2809+
});
2810+
}
2811+
openFile(name);
2812+
}}
28002813
onOpenLiveArtifact={(tabId) => openFile(tabId)}
28012814
onRenameFile={handleRename}
28022815
onDeleteFile={(name) => {
@@ -2890,8 +2903,28 @@ export function FileWorkspace({
28902903
}
28912904
onSceneChange={(scene, options) => setSketchScene(activeFile.name, scene, options)}
28922905
onClear={() => clearSketch(activeFile.name)}
2893-
onSave={(scene) => saveSketch(activeFile.name, scene)}
2894-
onExportImage={(base64, fileName) => exportSketchImage(activeFile.name, base64, fileName)}
2906+
onSave={async (scene) => {
2907+
// Fires only on the explicit "Save" button — background
2908+
// autosave calls saveSketch() directly and is not tracked.
2909+
const result = await saveSketch(activeFile.name, scene);
2910+
trackSketchSaveResult(analytics.track, {
2911+
page_name: 'file_manager',
2912+
area: 'sketch_editor',
2913+
result: result === false ? 'failed' : 'success',
2914+
project_id: projectId,
2915+
});
2916+
return result;
2917+
}}
2918+
onExportImage={async (base64, fileName) => {
2919+
const result = await exportSketchImage(activeFile.name, base64, fileName);
2920+
trackSketchExportResult(analytics.track, {
2921+
page_name: 'file_manager',
2922+
area: 'sketch_editor',
2923+
result: result === false ? 'failed' : 'success',
2924+
project_id: projectId,
2925+
});
2926+
return result;
2927+
}}
28952928
onOpenExportedImage={openFile}
28962929
saving={activeSketch.saving}
28972930
dirty={activeSketch.dirty || !activeSketch.persisted}

apps/web/src/components/HomeHero.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@ import type { SkillSummary } from '../types';
3636
import { Icon, type IconName } from './Icon';
3737
import { useAnalytics } from '../analytics/provider';
3838
import {
39+
trackComposerSessionModeClick,
3940
trackContextLinkResult,
4041
trackFigmaHelpModalSurfaceView,
4142
trackHomeChatComposerClick,
4243
trackProjectReferenceModalSurfaceView,
4344
} from '../analytics/events';
45+
import { sessionModeToTracking } from '@open-design/contracts/analytics';
4446
import {
4547
chipsForGroup,
4648
orderedCreateChips,
@@ -1936,7 +1938,18 @@ export const HomeHero = forwardRef<HomeHeroHandle, Props>(function HomeHero(
19361938
<div className="home-hero__mode-switcher">
19371939
<SessionModeToggle
19381940
mode={sessionMode}
1939-
onChange={onSessionModeChange}
1941+
onChange={(next) => {
1942+
if (next !== sessionMode) {
1943+
trackComposerSessionModeClick(analytics.track, {
1944+
page_name: 'home',
1945+
area: 'chat_composer',
1946+
element: 'session_mode_toggle',
1947+
mode_before: sessionModeToTracking(sessionMode),
1948+
mode_after: sessionModeToTracking(next),
1949+
});
1950+
}
1951+
onSessionModeChange?.(next);
1952+
}}
19401953
/>
19411954
</div>
19421955
{executionSwitcher ? (

packages/contracts/src/analytics/events/event-names.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,10 @@ export type AnalyticsEventName =
6363
| 'design_system_status_result'
6464
| 'design_system_apply_result'
6565
// AI optimize (deep enrichment) of a programmatically-extracted DS.
66-
| 'design_system_enrich_result';
66+
| 'design_system_enrich_result'
67+
// Manual save / PNG export from the Excalidraw sketch editor.
68+
| 'sketch_save_result'
69+
| 'sketch_export_result';
6770

6871
// ---- Pages ---------------------------------------------------------------
6972

packages/contracts/src/analytics/events/event-payload.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import type { DesignSystemApplyResultProps, DesignSystemCreateResultProps, DesignSystemEnrichResultProps, DesignSystemReviewResultProps, DesignSystemSourceIngestResultProps, DesignSystemStatusResultProps } from './design-systems.js';
66
import type { OnboardingCompleteResultProps, OnboardingRuntimeScanResultProps } from './onboarding.js';
77
import type { PageViewProps } from './page-view.js';
8-
import type { ArtifactDeployResultProps, ArtifactExportResultProps, AssistantFeedbackClickProps, AssistantFeedbackReasonClickProps, AssistantFeedbackReasonSubmitProps, AssistantFeedbackReasonViewProps, ContextLinkResultProps, FeedbackSubmitResultProps, FileUploadResultProps, FileVersionRestoreResultProps, LangfuseReportResultProps, PackagedRuntimeFailedProps, PluginImportResultProps, PluginReplacementResultProps, ProjectCreateResultProps, RunCreatedProps, RunFinishedProps, RunRetryAttemptedProps, RunRetryFinishedProps, SettingsByokModelsFetchResultProps, SettingsByokTestResultProps, SettingsCliTestResultProps, SettingsConnectorAuthResultProps, SettingsViewProps, UpdateApplyObservedProps, UpdateInstallResultProps } from './result-events.js';
8+
import type { ArtifactDeployResultProps, ArtifactExportResultProps, AssistantFeedbackClickProps, AssistantFeedbackReasonClickProps, AssistantFeedbackReasonSubmitProps, AssistantFeedbackReasonViewProps, ContextLinkResultProps, FeedbackSubmitResultProps, FileUploadResultProps, FileVersionRestoreResultProps, LangfuseReportResultProps, PackagedRuntimeFailedProps, PluginImportResultProps, PluginReplacementResultProps, ProjectCreateResultProps, RunCreatedProps, RunFinishedProps, RunRetryAttemptedProps, RunRetryFinishedProps, SettingsByokModelsFetchResultProps, SettingsByokTestResultProps, SettingsCliTestResultProps, SettingsConnectorAuthResultProps, SettingsViewProps, SketchExportResultProps, SketchSaveResultProps, UpdateApplyObservedProps, UpdateInstallResultProps } from './result-events.js';
99
import type { SurfaceViewProps } from './surface-view.js';
1010
import type { AmrAuthResultProps, UiClickProps } from './ui-click.js';
1111
// ---- Discriminated union of all event payloads ---------------------------
@@ -29,6 +29,8 @@ export type AnalyticsEventPayload =
2929
| { event: 'context_link_result'; props: ContextLinkResultProps }
3030
| { event: 'artifact_export_result'; props: ArtifactExportResultProps }
3131
| { event: 'artifact_deploy_result'; props: ArtifactDeployResultProps }
32+
| { event: 'sketch_save_result'; props: SketchSaveResultProps }
33+
| { event: 'sketch_export_result'; props: SketchExportResultProps }
3234
| { event: 'file_version_restore_result'; props: FileVersionRestoreResultProps }
3335
| { event: 'feedback_submit_result'; props: FeedbackSubmitResultProps }
3436
| { event: 'assistant_feedback_click'; props: AssistantFeedbackClickProps }

packages/contracts/src/analytics/events/result-events.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,32 @@ export interface ArtifactExportResultProps {
448448
project_kind: TrackingProjectKind | null;
449449
}
450450

451+
// Fired when the user explicitly clicks "Save" in the Excalidraw sketch editor
452+
// — NOT the background autosave (which carries no user intent and is not
453+
// tracked). `result` is 'success' once the sketch file is persisted, 'failed'
454+
// on a write error. Together with `sketch_export_result` this is the
455+
// completion signal for the sketch flow that starts at `new_sketch`.
456+
export interface SketchSaveResultProps {
457+
page_name: 'file_manager';
458+
area: 'sketch_editor';
459+
result: TrackingExportResult;
460+
error_code?: string;
461+
project_id: string;
462+
}
463+
464+
// Fired when the user exports a sketch to a PNG from the sketch editor, which
465+
// writes the image into the project's files — the sketch's real "output" (the
466+
// drawing becomes a project asset that can then be attached to a run). This is
467+
// the strongest completion signal for the sketch flow. `result` is 'success'
468+
// once the PNG is written, 'failed' on a write error.
469+
export interface SketchExportResultProps {
470+
page_name: 'file_manager';
471+
area: 'sketch_editor';
472+
result: TrackingExportResult;
473+
error_code?: string;
474+
project_id: string;
475+
}
476+
451477
export type TrackingDeployProvider = 'vercel' | 'cloudflare_pages';
452478

453479
// Fired from the deploy modal when a real publish attempt resolves — NOT when

packages/contracts/src/analytics/events/ui-click.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -931,6 +931,10 @@ export interface FileManagerClickProps {
931931
area: 'file_manager';
932932
element:
933933
| 'new_sketch'
934+
// Opening an existing .sketch.json from the Design Files list — the
935+
// "come back to an earlier sketch" re-engagement entry (distinct from
936+
// `new_sketch` which creates a fresh one).
937+
| 'open_sketch'
934938
| 'new_browser'
935939
| 'create_design_system'
936940
| 'create_design_system_from_project'

0 commit comments

Comments
 (0)