Skip to content

Commit b9f4947

Browse files
committed
🤖 feat: simplify dev telemetry env var and track experiment overrides
- Makefile: MUX_ENABLE_TELEMETRY_IN_DEV=1 now sufficient (no need to also set MUX_DISABLE_TELEMETRY=0) - ExperimentsSection: hide non-overridable experiments, remove PostHog info line - Add experiment_overridden telemetry event with experimentId, assignedVariant, userChoice - Update oRPC schema, payload types, tracking functions, and useTelemetry hook Signed-off-by: Thomas Kosiewski <[email protected]> --- _Generated with `mux` • Model: `anthropic:claude-opus-4-5` • Thinking: `high`_ Change-Id: I3582117b82c1025bcfd94d1361bb11c46cb8ff9e
1 parent fd97c9a commit b9f4947

File tree

7 files changed

+67
-26
lines changed

7 files changed

+67
-26
lines changed

Makefile

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,13 @@ dev: node_modules/.installed build-main ## Start development server (Vite + node
131131
@echo "Starting dev mode (3 watchers: nodemon for main process, esbuild for api, vite for renderer)..."
132132
# On Windows, use npm run because bunx doesn't correctly pass arguments to concurrently
133133
# https://github.com/oven-sh/bun/issues/18275
134-
@MUX_DISABLE_TELEMETRY=$(or $(MUX_DISABLE_TELEMETRY),1) NODE_OPTIONS="--max-old-space-size=4096" npm x concurrently -k --raw \
134+
@MUX_DISABLE_TELEMETRY=$(if $(MUX_ENABLE_TELEMETRY_IN_DEV),,$(or $(MUX_DISABLE_TELEMETRY),1)) NODE_OPTIONS="--max-old-space-size=4096" npm x concurrently -k --raw \
135135
"bun x nodemon --watch src --watch tsconfig.main.json --watch tsconfig.json --ext ts,tsx,json --ignore dist --ignore node_modules --exec node scripts/build-main-watch.js" \
136136
"npx esbuild src/cli/api.ts --bundle --format=esm --platform=node --target=node20 --outfile=dist/cli/api.mjs --external:zod --external:commander --external:@trpc/server --watch" \
137137
"vite"
138138
else
139139
dev: node_modules/.installed build-main build-preload ## Start development server (Vite + tsgo watcher for 10x faster type checking)
140-
@MUX_DISABLE_TELEMETRY=$(or $(MUX_DISABLE_TELEMETRY),1) bun x concurrently -k \
140+
@MUX_DISABLE_TELEMETRY=$(if $(MUX_ENABLE_TELEMETRY_IN_DEV),,$(or $(MUX_DISABLE_TELEMETRY),1)) bun x concurrently -k \
141141
"bun x concurrently \"$(TSGO) -w -p tsconfig.main.json\" \"bun x tsc-alias -w -p tsconfig.main.json\"" \
142142
"bun x esbuild src/cli/api.ts --bundle --format=esm --platform=node --target=node20 --outfile=dist/cli/api.mjs --external:zod --external:commander --external:@trpc/server --watch" \
143143
"vite"
@@ -151,7 +151,7 @@ dev-server: node_modules/.installed build-main ## Start server mode with hot rel
151151
@echo ""
152152
@echo "For remote access: make dev-server VITE_HOST=0.0.0.0 BACKEND_HOST=0.0.0.0"
153153
@# On Windows, use npm run because bunx doesn't correctly pass arguments
154-
@MUX_DISABLE_TELEMETRY=$(or $(MUX_DISABLE_TELEMETRY),1) npmx concurrently -k \
154+
@MUX_DISABLE_TELEMETRY=$(if $(MUX_ENABLE_TELEMETRY_IN_DEV),,$(or $(MUX_DISABLE_TELEMETRY),1)) npmx concurrently -k \
155155
"npmx nodemon --watch src --watch tsconfig.main.json --watch tsconfig.json --ext ts,tsx,json --ignore dist --ignore node_modules --exec node scripts/build-main-watch.js" \
156156
"npx esbuild src/cli/api.ts --bundle --format=esm --platform=node --target=node20 --outfile=dist/cli/api.mjs --external:zod --external:commander --external:@trpc/server --watch" \
157157
"npmx nodemon --watch dist/cli/index.js --watch dist/cli/server.js --delay 500ms --exec \"node dist/cli/index.js server --host $(or $(BACKEND_HOST),localhost) --port $(or $(BACKEND_PORT),3000)\"" \
@@ -163,7 +163,7 @@ dev-server: node_modules/.installed build-main ## Start server mode with hot rel
163163
@echo " Frontend (with HMR): http://$(or $(VITE_HOST),localhost):$(or $(VITE_PORT),5173)"
164164
@echo ""
165165
@echo "For remote access: make dev-server VITE_HOST=0.0.0.0 BACKEND_HOST=0.0.0.0"
166-
@MUX_DISABLE_TELEMETRY=$(or $(MUX_DISABLE_TELEMETRY),1) bun x concurrently -k \
166+
@MUX_DISABLE_TELEMETRY=$(if $(MUX_ENABLE_TELEMETRY_IN_DEV),,$(or $(MUX_DISABLE_TELEMETRY),1)) bun x concurrently -k \
167167
"bun x concurrently \"$(TSGO) -w -p tsconfig.main.json\" \"bun x tsc-alias -w -p tsconfig.main.json\"" \
168168
"bun x esbuild src/cli/api.ts --bundle --format=esm --platform=node --target=node20 --outfile=dist/cli/api.mjs --external:zod --external:commander --external:@trpc/server --watch" \
169169
"bun x nodemon --watch dist/cli/index.js --watch dist/cli/server.js --delay 500ms --exec 'NODE_ENV=development node dist/cli/index.js server --host $(or $(BACKEND_HOST),localhost) --port $(or $(BACKEND_PORT),3000)'" \
@@ -173,7 +173,7 @@ endif
173173

174174

175175
start: node_modules/.installed build-main build-preload build-static ## Build and start Electron app
176-
@NODE_ENV=development MUX_DISABLE_TELEMETRY=$(or $(MUX_DISABLE_TELEMETRY),1) bunx electron --remote-debugging-port=9222 .
176+
@NODE_ENV=development MUX_DISABLE_TELEMETRY=$(if $(MUX_ENABLE_TELEMETRY_IN_DEV),,$(or $(MUX_DISABLE_TELEMETRY),1)) bunx electron --remote-debugging-port=9222 .
177177

178178
## Build targets (can run in parallel)
179179
build: node_modules/.installed src/version.ts build-renderer build-main build-preload build-icons build-static ## Build all targets

src/browser/components/Settings/sections/ExperimentsSection.tsx

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import React, { useCallback, useMemo } from "react";
22
import { useExperiment, useRemoteExperimentValue } from "@/browser/contexts/ExperimentsContext";
33
import {
4-
EXPERIMENTS,
54
getExperimentList,
65
EXPERIMENT_IDS,
76
type ExperimentId,
87
} from "@/common/constants/experiments";
98
import { Switch } from "@/browser/components/ui/switch";
109
import { useWorkspaceContext } from "@/browser/contexts/WorkspaceContext";
10+
import { useTelemetry } from "@/browser/hooks/useTelemetry";
1111

1212
interface ExperimentRowProps {
1313
experimentId: ExperimentId;
@@ -17,41 +17,29 @@ interface ExperimentRowProps {
1717
}
1818

1919
function ExperimentRow(props: ExperimentRowProps) {
20-
const experiment = EXPERIMENTS[props.experimentId];
2120
const [enabled, setEnabled] = useExperiment(props.experimentId);
2221
const remote = useRemoteExperimentValue(props.experimentId);
23-
const isRemoteControlled = remote ? remote.source !== "disabled" : false;
24-
const canOverride = experiment.userOverridable === true;
25-
const { onToggle } = props;
22+
const telemetry = useTelemetry();
23+
const { onToggle, experimentId } = props;
2624

2725
const handleToggle = useCallback(
2826
(value: boolean) => {
29-
// Allow toggle if not remote-controlled OR if user can override
30-
if (isRemoteControlled && !canOverride) {
31-
return;
32-
}
33-
3427
setEnabled(value);
28+
// Track the override for analytics
29+
telemetry.experimentOverridden(experimentId, remote?.value ?? null, value);
3530
onToggle?.(value);
3631
},
37-
[isRemoteControlled, canOverride, setEnabled, onToggle]
32+
[setEnabled, telemetry, experimentId, remote?.value, onToggle]
3833
);
3934

4035
return (
4136
<div className="flex items-center justify-between py-3">
4237
<div className="flex-1 pr-4">
4338
<div className="text-foreground text-sm font-medium">{props.name}</div>
4439
<div className="text-muted mt-0.5 text-xs">{props.description}</div>
45-
{isRemoteControlled ? (
46-
<div className="text-muted mt-0.5 text-xs">
47-
PostHog: {String(remote?.value ?? "loading")} ({remote?.source})
48-
{canOverride ? " • overridable" : null}
49-
</div>
50-
) : null}
5140
</div>
5241
<Switch
5342
checked={enabled}
54-
disabled={isRemoteControlled && !canOverride}
5543
onCheckedChange={handleToggle}
5644
aria-label={`Toggle ${props.name}`}
5745
/>
@@ -63,9 +51,10 @@ export function ExperimentsSection() {
6351
const allExperiments = getExperimentList();
6452
const { refreshWorkspaceMetadata } = useWorkspaceContext();
6553

66-
// Filter to only show experiments where showInSettings !== false
54+
// Only show user-overridable experiments (non-overridable ones are hidden since users can't change them)
6755
const experiments = useMemo(
68-
() => allExperiments.filter((exp) => exp.showInSettings !== false),
56+
() =>
57+
allExperiments.filter((exp) => exp.showInSettings !== false && exp.userOverridable === true),
6958
[allExperiments]
7059
);
7160

src/browser/hooks/useTelemetry.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
trackCommandUsed,
99
trackVoiceTranscription,
1010
trackErrorOccurred,
11+
trackExperimentOverridden,
1112
} from "@/common/telemetry";
1213
import type {
1314
ErrorContext,
@@ -35,6 +36,7 @@ import type {
3536
* telemetry.commandUsed(commandType);
3637
* telemetry.voiceTranscription(audioDurationSecs, success);
3738
* telemetry.errorOccurred(errorType, context);
39+
* telemetry.experimentOverridden(experimentId, assignedVariant, userChoice);
3840
* ```
3941
*/
4042
export function useTelemetry() {
@@ -83,6 +85,13 @@ export function useTelemetry() {
8385
trackErrorOccurred(errorType, context);
8486
}, []);
8587

88+
const experimentOverridden = useCallback(
89+
(experimentId: string, assignedVariant: string | boolean | null, userChoice: boolean) => {
90+
trackExperimentOverridden(experimentId, assignedVariant, userChoice);
91+
},
92+
[]
93+
);
94+
8695
return {
8796
workspaceSwitched,
8897
workspaceCreated,
@@ -92,5 +101,6 @@ export function useTelemetry() {
92101
commandUsed,
93102
voiceTranscription,
94103
errorOccurred,
104+
experimentOverridden,
95105
};
96106
}

src/common/orpc/schemas/telemetry.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ const ErrorOccurredPropertiesSchema = z.object({
9797
context: ErrorContextSchema,
9898
});
9999

100+
const ExperimentOverriddenPropertiesSchema = z.object({
101+
experimentId: z.string(),
102+
assignedVariant: z.union([z.string(), z.boolean(), z.null()]),
103+
userChoice: z.boolean(),
104+
});
105+
100106
// Union of all telemetry events
101107
export const TelemetryEventSchema = z.discriminatedUnion("event", [
102108
z.object({
@@ -135,6 +141,10 @@ export const TelemetryEventSchema = z.discriminatedUnion("event", [
135141
event: z.literal("error_occurred"),
136142
properties: ErrorOccurredPropertiesSchema,
137143
}),
144+
z.object({
145+
event: z.literal("experiment_overridden"),
146+
properties: ExperimentOverriddenPropertiesSchema,
147+
}),
138148
]);
139149

140150
// API schemas - only track endpoint, enabled state controlled by env var

src/common/telemetry/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export {
2424
trackCommandUsed,
2525
trackVoiceTranscription,
2626
trackErrorOccurred,
27+
trackExperimentOverridden,
2728
} from "./tracking";
2829

2930
// Utility for converting RuntimeConfig to telemetry-safe runtime type

src/common/telemetry/payload.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,19 @@ export interface ErrorOccurredPayload {
188188
context: ErrorContext;
189189
}
190190

191+
/**
192+
* Experiment override event - tracks when users manually toggle experiments
193+
* This helps measure opt-out rates and understand user preferences
194+
*/
195+
export interface ExperimentOverriddenPayload {
196+
/** Experiment identifier (e.g., 'post-compaction-context') */
197+
experimentId: string;
198+
/** The variant PostHog assigned (null if not remote-controlled) */
199+
assignedVariant: string | boolean | null;
200+
/** What the user chose (true = enabled, false = disabled) */
201+
userChoice: boolean;
202+
}
203+
191204
/**
192205
* Union type of all telemetry event payloads
193206
* Frontend sends these; backend adds BaseTelemetryProperties before forwarding to PostHog
@@ -201,4 +214,5 @@ export type TelemetryEventPayload =
201214
| { event: "provider_configured"; properties: ProviderConfiguredPayload }
202215
| { event: "command_used"; properties: CommandUsedPayload }
203216
| { event: "voice_transcription"; properties: VoiceTranscriptionPayload }
204-
| { event: "error_occurred"; properties: ErrorOccurredPayload };
217+
| { event: "error_occurred"; properties: ErrorOccurredPayload }
218+
| { event: "experiment_overridden"; properties: ExperimentOverriddenPayload };

src/common/telemetry/tracking.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,20 @@ export function trackErrorOccurred(
162162
properties: { errorType, context },
163163
});
164164
}
165+
166+
/**
167+
* Track experiment override - when a user manually toggles an experiment
168+
* @param experimentId - The experiment identifier
169+
* @param assignedVariant - What PostHog assigned (null if not remote-controlled)
170+
* @param userChoice - What the user chose (true = enabled, false = disabled)
171+
*/
172+
export function trackExperimentOverridden(
173+
experimentId: string,
174+
assignedVariant: string | boolean | null,
175+
userChoice: boolean
176+
): void {
177+
trackEvent({
178+
event: "experiment_overridden",
179+
properties: { experimentId, assignedVariant, userChoice },
180+
});
181+
}

0 commit comments

Comments
 (0)