feat: Add opt-out setting for anonymous usage reporting (#743)

* refactor: align VS Code update checks with web runtime parity

- VS Code now uses file-based installId (shared with web server)
- Accepts platform/arch from webview for consistent behavior
- Usage data collection now matches web implementation

* feat: Add opt-out setting for anonymous usage reporting in Appearance

- Add privacy control in Appearance settings to opt-out of anonymous usage reports
- Usage data includes only app version, platform, and runtime - no personal data or code collected
- Setting persists across all runtimes and controls the reportUsage parameter in update checks
This commit is contained in:
Bohdan Triapitsyn
2026-03-22 23:02:53 +02:00
committed by GitHub
parent 53c2a0d919
commit 64b55025e1
9 changed files with 116 additions and 25 deletions
@@ -110,6 +110,7 @@ const VisualSectionContent: React.FC = () => {
'spacing',
'inputBarOffset',
...(!isVSCode ? ['terminalQuickKeys' as const] : []),
'reportUsage',
]} />;
};
@@ -143,7 +143,7 @@ const normalizeUserMessageRenderingMode = (mode: unknown): 'markdown' | 'plain'
return mode === 'markdown' ? 'markdown' : 'plain';
};
export type VisibleSetting = 'theme' | 'pwaInstallName' | 'fontSize' | 'terminalFontSize' | 'spacing' | 'inputBarOffset' | 'mermaidRendering' | 'userMessageRendering' | 'chatRenderMode' | 'activityRenderMode' | 'stickyUserHeader' | 'diffLayout' | 'mobileStatusBar' | 'dotfiles' | 'reasoning' | 'showToolFileIcons' | 'expandedTools' | 'queueMode' | 'terminalQuickKeys' | 'persistDraft' | 'inputSpellcheck';
export type VisibleSetting = 'theme' | 'pwaInstallName' | 'fontSize' | 'terminalFontSize' | 'spacing' | 'inputBarOffset' | 'mermaidRendering' | 'userMessageRendering' | 'chatRenderMode' | 'activityRenderMode' | 'stickyUserHeader' | 'diffLayout' | 'mobileStatusBar' | 'dotfiles' | 'reasoning' | 'showToolFileIcons' | 'expandedTools' | 'queueMode' | 'terminalQuickKeys' | 'persistDraft' | 'inputSpellcheck' | 'reportUsage';
interface OpenChamberVisualSettingsProps {
/** Which settings to show. If undefined, shows all. */
@@ -210,6 +210,14 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
const [themesReloading, setThemesReloading] = React.useState(false);
const [chatRenderPreviewTick, setChatRenderPreviewTick] = React.useState(0);
const reportUsage = useUIStore(state => state.reportUsage);
const setReportUsage = useUIStore(state => state.setReportUsage);
// Sync reportUsage changes to server settings
const handleReportUsageChange = React.useCallback((enabled: boolean) => {
setReportUsage(enabled);
void updateDesktopSettings({ reportUsage: enabled });
}, [setReportUsage]);
const shouldAnimateChatPreview = isSettingsDialogOpen
&& (visibleSettings ? visibleSettings.includes('chatRenderMode') : true);
@@ -1244,6 +1252,42 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
</div>
)}
{/* --- Privacy & Data --- */}
{shouldShow('reportUsage') && (
<div className="space-y-3">
<section className="px-2 pb-2 pt-0">
<h4 className="typography-ui-header font-medium text-foreground mb-2">Privacy</h4>
<div className="flex items-start gap-2 py-1.5">
<Checkbox
checked={reportUsage}
onChange={handleReportUsageChange}
ariaLabel="Send anonymous usage reports"
/>
<div className="flex min-w-0 flex-col gap-0.5">
<div
className="group flex cursor-pointer"
role="button"
tabIndex={0}
aria-pressed={reportUsage}
onClick={() => handleReportUsageChange(!reportUsage)}
onKeyDown={(event) => {
if (event.key === ' ' || event.key === 'Enter') {
event.preventDefault();
handleReportUsageChange(!reportUsage);
}
}}
>
<span className="typography-ui-label text-foreground">Send anonymous usage reports</span>
</div>
<span className="typography-meta text-muted-foreground pointer-events-none">
Helps us understand which app versions are actively used so we can prioritize improvements. Only app version, platform, and runtime are collected - no personal data or code.
</span>
</div>
</div>
</section>
</div>
)}
</div>
);
};