mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
Add Windows support notice modal for Windows users on startup
Shows a modal on first launch for Windows users explaining that Windows support is actively being improved. Features: - Inline toggle to enable beta updates for latest bug fixes - Link to report issues on GitHub (with note that vetted PRs welcome) - Link to join Discord Windows-specific channel for community support - Create Debug Package button for easy bug reporting - Checkbox to suppress the modal for future sessions - Console debug function: window.__showWindowsWarningModal()
This commit is contained in:
250
src/__tests__/renderer/components/WindowsWarningModal.test.tsx
Normal file
250
src/__tests__/renderer/components/WindowsWarningModal.test.tsx
Normal file
@@ -0,0 +1,250 @@
|
||||
/**
|
||||
* Tests for WindowsWarningModal component
|
||||
*
|
||||
* Tests the core behavior of the Windows warning dialog:
|
||||
* - Rendering with correct content
|
||||
* - Button click handlers
|
||||
* - Suppress checkbox functionality
|
||||
* - Debug function exposure
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { WindowsWarningModal, exposeWindowsWarningModalDebug } from '../../../renderer/components/WindowsWarningModal';
|
||||
import { LayerStackProvider } from '../../../renderer/contexts/LayerStackContext';
|
||||
import type { Theme } from '../../../renderer/types';
|
||||
|
||||
// Mock lucide-react
|
||||
vi.mock('lucide-react', () => ({
|
||||
X: () => <svg data-testid="x-icon" />,
|
||||
AlertTriangle: () => <svg data-testid="alert-triangle-icon" />,
|
||||
Bug: () => <svg data-testid="bug-icon" />,
|
||||
Wrench: () => <svg data-testid="wrench-icon" />,
|
||||
ExternalLink: () => <svg data-testid="external-link-icon" />,
|
||||
Command: () => <svg data-testid="command-icon" />,
|
||||
Check: () => <svg data-testid="check-icon" />,
|
||||
MessageCircle: () => <svg data-testid="message-circle-icon" />,
|
||||
}));
|
||||
|
||||
// Create a test theme
|
||||
const testTheme: Theme = {
|
||||
id: 'test-theme',
|
||||
name: 'Test Theme',
|
||||
mode: 'dark',
|
||||
colors: {
|
||||
bgMain: '#1e1e1e',
|
||||
bgSidebar: '#252526',
|
||||
bgActivity: '#333333',
|
||||
textMain: '#d4d4d4',
|
||||
textDim: '#808080',
|
||||
accent: '#007acc',
|
||||
border: '#404040',
|
||||
error: '#f14c4c',
|
||||
warning: '#cca700',
|
||||
success: '#89d185',
|
||||
info: '#3794ff',
|
||||
textInverse: '#000000',
|
||||
accentForeground: '#ffffff',
|
||||
},
|
||||
};
|
||||
|
||||
// Helper to render with LayerStackProvider
|
||||
const renderWithLayerStack = (ui: React.ReactElement) => {
|
||||
return render(<LayerStackProvider>{ui}</LayerStackProvider>);
|
||||
};
|
||||
|
||||
describe('WindowsWarningModal', () => {
|
||||
const defaultProps = {
|
||||
theme: testTheme,
|
||||
isOpen: true,
|
||||
onClose: vi.fn(),
|
||||
onSuppressFuture: vi.fn(),
|
||||
onOpenDebugPackage: vi.fn(),
|
||||
useBetaChannel: false,
|
||||
onSetUseBetaChannel: vi.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
// Mock shell.openExternal for this test suite
|
||||
vi.mocked(window.maestro.shell.openExternal).mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('rendering', () => {
|
||||
it('renders when isOpen is true', () => {
|
||||
renderWithLayerStack(<WindowsWarningModal {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText('Windows Support Notice')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render when isOpen is false', () => {
|
||||
renderWithLayerStack(<WindowsWarningModal {...defaultProps} isOpen={false} />);
|
||||
|
||||
expect(screen.queryByText('Windows Support Notice')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays recommendations section', () => {
|
||||
renderWithLayerStack(<WindowsWarningModal {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText('Recommendations')).toBeInTheDocument();
|
||||
expect(screen.getByText('Enable Beta Updates')).toBeInTheDocument();
|
||||
expect(screen.getByText('Report Issues')).toBeInTheDocument();
|
||||
expect(screen.getByText('Join Discord')).toBeInTheDocument();
|
||||
expect(screen.getByText('Create Debug Package')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays suppress checkbox', () => {
|
||||
renderWithLayerStack(<WindowsWarningModal {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText("Don't show this message again")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays Got it! button', () => {
|
||||
renderWithLayerStack(<WindowsWarningModal {...defaultProps} />);
|
||||
|
||||
expect(screen.getByRole('button', { name: 'Got it!' })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('button handlers', () => {
|
||||
it('calls onClose when Got it! button is clicked', () => {
|
||||
const onClose = vi.fn();
|
||||
renderWithLayerStack(<WindowsWarningModal {...defaultProps} onClose={onClose} />);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Got it!' }));
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls onClose when X button is clicked', () => {
|
||||
const onClose = vi.fn();
|
||||
renderWithLayerStack(<WindowsWarningModal {...defaultProps} onClose={onClose} />);
|
||||
|
||||
// Find the X icon and click its parent button
|
||||
const closeButton = screen.getByTestId('x-icon').closest('button');
|
||||
fireEvent.click(closeButton!);
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls onSetUseBetaChannel with true when Enable Beta Updates is clicked', () => {
|
||||
const onSetUseBetaChannel = vi.fn();
|
||||
renderWithLayerStack(<WindowsWarningModal {...defaultProps} useBetaChannel={false} onSetUseBetaChannel={onSetUseBetaChannel} />);
|
||||
|
||||
fireEvent.click(screen.getByText('Enable Beta Updates'));
|
||||
expect(onSetUseBetaChannel).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('calls onSetUseBetaChannel with false when Beta Updates toggle is clicked while enabled', () => {
|
||||
const onSetUseBetaChannel = vi.fn();
|
||||
renderWithLayerStack(<WindowsWarningModal {...defaultProps} useBetaChannel={true} onSetUseBetaChannel={onSetUseBetaChannel} />);
|
||||
|
||||
fireEvent.click(screen.getByText('Enable Beta Updates'));
|
||||
expect(onSetUseBetaChannel).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it('opens GitHub issues when Report Issues is clicked', () => {
|
||||
renderWithLayerStack(<WindowsWarningModal {...defaultProps} />);
|
||||
|
||||
fireEvent.click(screen.getByText('Report Issues'));
|
||||
expect(window.maestro.shell.openExternal).toHaveBeenCalledWith('https://github.com/pedramamini/Maestro/issues');
|
||||
});
|
||||
|
||||
it('opens Discord when Join Discord is clicked', () => {
|
||||
renderWithLayerStack(<WindowsWarningModal {...defaultProps} />);
|
||||
|
||||
fireEvent.click(screen.getByText('Join Discord'));
|
||||
expect(window.maestro.shell.openExternal).toHaveBeenCalledWith('https://discord.gg/FCAh4EWzfD');
|
||||
});
|
||||
|
||||
it('calls onOpenDebugPackage when Create Debug Package is clicked', () => {
|
||||
const onOpenDebugPackage = vi.fn();
|
||||
renderWithLayerStack(<WindowsWarningModal {...defaultProps} onOpenDebugPackage={onOpenDebugPackage} />);
|
||||
|
||||
fireEvent.click(screen.getByText('Create Debug Package'));
|
||||
expect(onOpenDebugPackage).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('suppress checkbox', () => {
|
||||
it('checkbox is unchecked by default', () => {
|
||||
renderWithLayerStack(<WindowsWarningModal {...defaultProps} />);
|
||||
|
||||
const checkbox = screen.getByRole('checkbox');
|
||||
expect(checkbox).not.toBeChecked();
|
||||
});
|
||||
|
||||
it('checkbox can be toggled', () => {
|
||||
renderWithLayerStack(<WindowsWarningModal {...defaultProps} />);
|
||||
|
||||
const checkbox = screen.getByRole('checkbox');
|
||||
fireEvent.click(checkbox);
|
||||
expect(checkbox).toBeChecked();
|
||||
});
|
||||
|
||||
it('calls onSuppressFuture with false when closed without checking', () => {
|
||||
const onSuppressFuture = vi.fn();
|
||||
renderWithLayerStack(<WindowsWarningModal {...defaultProps} onSuppressFuture={onSuppressFuture} />);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Got it!' }));
|
||||
expect(onSuppressFuture).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it('calls onSuppressFuture with true when closed with checkbox checked', () => {
|
||||
const onSuppressFuture = vi.fn();
|
||||
renderWithLayerStack(<WindowsWarningModal {...defaultProps} onSuppressFuture={onSuppressFuture} />);
|
||||
|
||||
// Check the checkbox first
|
||||
fireEvent.click(screen.getByRole('checkbox'));
|
||||
// Then close
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Got it!' }));
|
||||
expect(onSuppressFuture).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('focus management', () => {
|
||||
it('focuses Got it! button on mount', async () => {
|
||||
renderWithLayerStack(<WindowsWarningModal {...defaultProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(document.activeElement).toBe(screen.getByRole('button', { name: 'Got it!' }));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('accessibility', () => {
|
||||
it('has correct ARIA attributes on dialog', () => {
|
||||
renderWithLayerStack(<WindowsWarningModal {...defaultProps} />);
|
||||
|
||||
const dialog = screen.getByRole('dialog');
|
||||
expect(dialog).toHaveAttribute('aria-modal', 'true');
|
||||
expect(dialog).toHaveAttribute('aria-label', 'Windows Support Notice');
|
||||
});
|
||||
|
||||
it('has tabIndex on dialog for focus', () => {
|
||||
renderWithLayerStack(<WindowsWarningModal {...defaultProps} />);
|
||||
|
||||
expect(screen.getByRole('dialog')).toHaveAttribute('tabIndex', '-1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('exposeWindowsWarningModalDebug', () => {
|
||||
it('exposes __showWindowsWarningModal function to window', () => {
|
||||
const setShowModal = vi.fn();
|
||||
exposeWindowsWarningModalDebug(setShowModal);
|
||||
|
||||
expect((window as any).__showWindowsWarningModal).toBeDefined();
|
||||
});
|
||||
|
||||
it('calls setShowModal with true when __showWindowsWarningModal is invoked', () => {
|
||||
const setShowModal = vi.fn();
|
||||
exposeWindowsWarningModalDebug(setShowModal);
|
||||
|
||||
(window as any).__showWindowsWarningModal();
|
||||
expect(setShowModal).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -29,6 +29,7 @@ import { AppOverlays } from './components/AppOverlays';
|
||||
import { PlaygroundPanel } from './components/PlaygroundPanel';
|
||||
import { DebugWizardModal } from './components/DebugWizardModal';
|
||||
import { DebugPackageModal } from './components/DebugPackageModal';
|
||||
import { WindowsWarningModal, exposeWindowsWarningModalDebug } from './components/WindowsWarningModal';
|
||||
import { GistPublishModal, type GistInfo } from './components/GistPublishModal';
|
||||
import {
|
||||
MaestroWizard,
|
||||
@@ -312,6 +313,9 @@ function MaestroConsoleInner() {
|
||||
// Debug Package Modal
|
||||
debugPackageModalOpen,
|
||||
setDebugPackageModalOpen,
|
||||
// Windows Warning Modal
|
||||
windowsWarningModalOpen,
|
||||
setWindowsWarningModalOpen,
|
||||
// Confirmation Modal
|
||||
confirmModalOpen,
|
||||
setConfirmModalOpen,
|
||||
@@ -571,6 +575,10 @@ function MaestroConsoleInner() {
|
||||
|
||||
// File tab refresh settings
|
||||
fileTabAutoRefreshEnabled,
|
||||
|
||||
// Windows warning suppression
|
||||
suppressWindowsWarning,
|
||||
setSuppressWindowsWarning,
|
||||
} = settings;
|
||||
|
||||
// --- KEYBOARD SHORTCUT HELPERS ---
|
||||
@@ -1392,6 +1400,31 @@ function MaestroConsoleInner() {
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Show Windows warning modal on startup for Windows users (if not suppressed)
|
||||
// Also expose a debug function to trigger the modal from console for testing
|
||||
useEffect(() => {
|
||||
// Expose debug function regardless of platform (for testing)
|
||||
exposeWindowsWarningModalDebug(setWindowsWarningModalOpen);
|
||||
|
||||
// Only check platform when settings have loaded (so we know suppress preference)
|
||||
if (!settingsLoaded) return;
|
||||
|
||||
// Skip if user has suppressed the warning
|
||||
if (suppressWindowsWarning) return;
|
||||
|
||||
// Check if running on Windows using the power API (has platform info)
|
||||
window.maestro.power
|
||||
.getStatus()
|
||||
.then((status) => {
|
||||
if (status.platform === 'win32') {
|
||||
setWindowsWarningModalOpen(true);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('[App] Failed to detect platform for Windows warning:', error);
|
||||
});
|
||||
}, [settingsLoaded, suppressWindowsWarning, setWindowsWarningModalOpen]);
|
||||
|
||||
// Load file gist URLs from settings on startup
|
||||
useEffect(() => {
|
||||
window.maestro.settings
|
||||
@@ -13746,6 +13779,17 @@ You are taking over this conversation. Based on the context above, provide a bri
|
||||
onClose={handleCloseDebugPackage}
|
||||
/>
|
||||
|
||||
{/* --- WINDOWS WARNING MODAL --- */}
|
||||
<WindowsWarningModal
|
||||
theme={theme}
|
||||
isOpen={windowsWarningModalOpen}
|
||||
onClose={() => setWindowsWarningModalOpen(false)}
|
||||
onSuppressFuture={setSuppressWindowsWarning}
|
||||
onOpenDebugPackage={() => setDebugPackageModalOpen(true)}
|
||||
useBetaChannel={enableBetaUpdates}
|
||||
onSetUseBetaChannel={setEnableBetaUpdates}
|
||||
/>
|
||||
|
||||
{/* --- CELEBRATION OVERLAYS --- */}
|
||||
<AppOverlays
|
||||
theme={theme}
|
||||
|
||||
254
src/renderer/components/WindowsWarningModal.tsx
Normal file
254
src/renderer/components/WindowsWarningModal.tsx
Normal file
@@ -0,0 +1,254 @@
|
||||
/**
|
||||
* WindowsWarningModal - Notifies Windows users about platform-specific considerations
|
||||
*
|
||||
* This modal is shown on application startup for Windows users to:
|
||||
* - Inform them that Windows support is newer and may have more bugs
|
||||
* - Encourage opting into the beta channel for latest fixes
|
||||
* - Provide easy access to issue reporting with Debug Package creation
|
||||
* - Allow users to suppress this message for future sessions
|
||||
*/
|
||||
|
||||
import React, { useState, useRef, useCallback } from 'react';
|
||||
import { AlertTriangle, Bug, Wrench, ExternalLink, Command, Check, MessageCircle } from 'lucide-react';
|
||||
import type { Theme } from '../types';
|
||||
import { MODAL_PRIORITIES } from '../constants/modalPriorities';
|
||||
import { Modal, ModalFooter } from './ui/Modal';
|
||||
|
||||
interface WindowsWarningModalProps {
|
||||
theme: Theme;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSuppressFuture: (suppress: boolean) => void;
|
||||
onOpenDebugPackage: () => void;
|
||||
useBetaChannel: boolean;
|
||||
onSetUseBetaChannel: (enabled: boolean) => void;
|
||||
}
|
||||
|
||||
export function WindowsWarningModal({
|
||||
theme,
|
||||
isOpen,
|
||||
onClose,
|
||||
onSuppressFuture,
|
||||
onOpenDebugPackage,
|
||||
useBetaChannel,
|
||||
onSetUseBetaChannel,
|
||||
}: WindowsWarningModalProps) {
|
||||
const [suppressChecked, setSuppressChecked] = useState(false);
|
||||
const continueButtonRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
// Handle close with suppress preference
|
||||
const handleClose = useCallback(() => {
|
||||
onSuppressFuture(suppressChecked);
|
||||
onClose();
|
||||
}, [suppressChecked, onSuppressFuture, onClose]);
|
||||
|
||||
// Handle toggling beta channel
|
||||
const handleToggleBetaChannel = useCallback(() => {
|
||||
onSetUseBetaChannel(!useBetaChannel);
|
||||
}, [useBetaChannel, onSetUseBetaChannel]);
|
||||
|
||||
// Handle opening debug package modal
|
||||
const handleOpenDebugPackage = useCallback(() => {
|
||||
onSuppressFuture(suppressChecked);
|
||||
onOpenDebugPackage();
|
||||
onClose();
|
||||
}, [suppressChecked, onSuppressFuture, onOpenDebugPackage, onClose]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
theme={theme}
|
||||
title="Windows Support Notice"
|
||||
headerIcon={<AlertTriangle className="w-4 h-4" style={{ color: theme.colors.warning }} />}
|
||||
priority={MODAL_PRIORITIES.WINDOWS_WARNING}
|
||||
onClose={handleClose}
|
||||
width={520}
|
||||
initialFocusRef={continueButtonRef}
|
||||
footer={
|
||||
<ModalFooter
|
||||
theme={theme}
|
||||
onCancel={handleClose}
|
||||
onConfirm={handleClose}
|
||||
cancelLabel="Cancel"
|
||||
confirmLabel="Got it!"
|
||||
confirmButtonRef={continueButtonRef}
|
||||
showCancel={false}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
{/* Main message */}
|
||||
<div
|
||||
className="p-4 rounded-lg border-l-4"
|
||||
style={{
|
||||
backgroundColor: `${theme.colors.warning}10`,
|
||||
borderLeftColor: theme.colors.warning,
|
||||
}}
|
||||
>
|
||||
<p className="text-sm leading-relaxed" style={{ color: theme.colors.textMain }}>
|
||||
Windows support in Maestro is actively being improved. You may encounter more bugs
|
||||
compared to Mac and Linux versions. We're working on it!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Recommendations */}
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wider" style={{ color: theme.colors.textDim }}>
|
||||
Recommendations
|
||||
</h3>
|
||||
|
||||
{/* Beta channel toggle */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleToggleBetaChannel}
|
||||
className="w-full flex items-start gap-3 p-3 rounded-lg border hover:bg-white/5 transition-colors text-left"
|
||||
style={{
|
||||
borderColor: useBetaChannel ? theme.colors.accent : theme.colors.border,
|
||||
backgroundColor: useBetaChannel ? `${theme.colors.accent}10` : 'transparent',
|
||||
}}
|
||||
>
|
||||
<Wrench className="w-4 h-4 mt-0.5 shrink-0" style={{ color: theme.colors.accent }} />
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium" style={{ color: theme.colors.textMain }}>
|
||||
Enable Beta Updates
|
||||
</p>
|
||||
<p className="text-xs mt-0.5" style={{ color: theme.colors.textDim }}>
|
||||
Get the latest bug fixes sooner by opting into the beta channel.
|
||||
</p>
|
||||
</div>
|
||||
{/* Toggle indicator */}
|
||||
<div
|
||||
className="w-10 h-5 rounded-full shrink-0 mt-0.5 transition-colors relative"
|
||||
style={{
|
||||
backgroundColor: useBetaChannel ? theme.colors.accent : theme.colors.bgMain,
|
||||
border: `1px solid ${useBetaChannel ? theme.colors.accent : theme.colors.border}`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="absolute w-3.5 h-3.5 rounded-full top-0.5 transition-all flex items-center justify-center"
|
||||
style={{
|
||||
backgroundColor: useBetaChannel ? theme.colors.accentForeground : theme.colors.textDim,
|
||||
left: useBetaChannel ? 'calc(100% - 18px)' : '2px',
|
||||
}}
|
||||
>
|
||||
{useBetaChannel && (
|
||||
<Check className="w-2 h-2" style={{ color: theme.colors.accent }} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* Report issues */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => window.maestro.shell.openExternal('https://github.com/pedramamini/Maestro/issues')}
|
||||
className="w-full flex items-start gap-3 p-3 rounded-lg border hover:bg-white/5 transition-colors text-left"
|
||||
style={{ borderColor: theme.colors.border }}
|
||||
>
|
||||
<Bug className="w-4 h-4 mt-0.5 shrink-0" style={{ color: theme.colors.accent }} />
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium" style={{ color: theme.colors.textMain }}>
|
||||
Report Issues
|
||||
</p>
|
||||
<p className="text-xs mt-0.5" style={{ color: theme.colors.textDim }}>
|
||||
Help improve Windows support by reporting bugs on GitHub. Vetted PRs are welcome!
|
||||
</p>
|
||||
</div>
|
||||
<ExternalLink className="w-3 h-3 shrink-0 mt-1" style={{ color: theme.colors.textDim }} />
|
||||
</button>
|
||||
|
||||
{/* Join Discord */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => window.maestro.shell.openExternal('https://discord.gg/FCAh4EWzfD')}
|
||||
className="w-full flex items-start gap-3 p-3 rounded-lg border hover:bg-white/5 transition-colors text-left"
|
||||
style={{ borderColor: theme.colors.border }}
|
||||
>
|
||||
<MessageCircle className="w-4 h-4 mt-0.5 shrink-0" style={{ color: theme.colors.accent }} />
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium" style={{ color: theme.colors.textMain }}>
|
||||
Join Discord
|
||||
</p>
|
||||
<p className="text-xs mt-0.5" style={{ color: theme.colors.textDim }}>
|
||||
Connect with other users in our Windows-specific channel for tips and support.
|
||||
</p>
|
||||
</div>
|
||||
<ExternalLink className="w-3 h-3 shrink-0 mt-1" style={{ color: theme.colors.textDim }} />
|
||||
</button>
|
||||
|
||||
{/* Create Debug Package */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleOpenDebugPackage}
|
||||
className="w-full flex items-start gap-3 p-3 rounded-lg border hover:bg-white/5 transition-colors text-left"
|
||||
style={{ borderColor: theme.colors.border }}
|
||||
>
|
||||
<Command className="w-4 h-4 mt-0.5 shrink-0" style={{ color: theme.colors.accent }} />
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium" style={{ color: theme.colors.textMain }}>
|
||||
Create Debug Package
|
||||
</p>
|
||||
<p className="text-xs mt-0.5" style={{ color: theme.colors.textDim }}>
|
||||
Accessible anytime via <kbd className="px-1 py-0.5 rounded text-[10px]" style={{ backgroundColor: theme.colors.bgMain }}>Ctrl+K</kbd> → "Create Debug Package" or from the main menu.
|
||||
</p>
|
||||
</div>
|
||||
<ExternalLink className="w-3 h-3 shrink-0 mt-1" style={{ color: theme.colors.textDim }} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Suppress checkbox */}
|
||||
<label
|
||||
className="flex items-center gap-2 pt-2 cursor-pointer group"
|
||||
>
|
||||
<div
|
||||
className="w-4 h-4 rounded border flex items-center justify-center transition-colors"
|
||||
style={{
|
||||
borderColor: suppressChecked ? theme.colors.accent : theme.colors.border,
|
||||
backgroundColor: suppressChecked ? theme.colors.accent : 'transparent',
|
||||
}}
|
||||
>
|
||||
{suppressChecked && (
|
||||
<svg className="w-3 h-3" viewBox="0 0 12 12" fill="none">
|
||||
<path
|
||||
d="M2 6L5 9L10 3"
|
||||
stroke={theme.colors.accentForeground}
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={suppressChecked}
|
||||
onChange={(e) => setSuppressChecked(e.target.checked)}
|
||||
className="sr-only"
|
||||
/>
|
||||
<span
|
||||
className="text-xs group-hover:opacity-100 transition-opacity"
|
||||
style={{ color: theme.colors.textDim, opacity: 0.8 }}
|
||||
>
|
||||
Don't show this message again
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug function to show the Windows warning modal from the console.
|
||||
* Usage: window.__showWindowsWarningModal()
|
||||
*/
|
||||
export function exposeWindowsWarningModalDebug(
|
||||
setShowWindowsWarning: (show: boolean) => void
|
||||
): void {
|
||||
(window as any).__showWindowsWarningModal = () => {
|
||||
setShowWindowsWarning(true);
|
||||
console.log('[WindowsWarningModal] Modal triggered via console command');
|
||||
};
|
||||
}
|
||||
|
||||
export default WindowsWarningModal;
|
||||
@@ -179,6 +179,9 @@ export const MODAL_PRIORITIES = {
|
||||
/** Debug package generation modal */
|
||||
DEBUG_PACKAGE: 605,
|
||||
|
||||
/** Windows warning modal - shown on startup for Windows users */
|
||||
WINDOWS_WARNING: 615,
|
||||
|
||||
/** About/info modal */
|
||||
ABOUT: 600,
|
||||
|
||||
|
||||
@@ -264,6 +264,10 @@ export interface ModalContextValue {
|
||||
// Symphony Modal
|
||||
symphonyModalOpen: boolean;
|
||||
setSymphonyModalOpen: (open: boolean) => void;
|
||||
|
||||
// Windows Warning Modal
|
||||
windowsWarningModalOpen: boolean;
|
||||
setWindowsWarningModalOpen: (open: boolean) => void;
|
||||
}
|
||||
|
||||
// Create context with null as default (will throw if used outside provider)
|
||||
@@ -446,6 +450,9 @@ export function ModalProvider({ children }: ModalProviderProps) {
|
||||
// Symphony Modal
|
||||
const [symphonyModalOpen, setSymphonyModalOpen] = useState(false);
|
||||
|
||||
// Windows Warning Modal
|
||||
const [windowsWarningModalOpen, setWindowsWarningModalOpen] = useState(false);
|
||||
|
||||
// Convenience methods
|
||||
const openSettings = useCallback((tab?: SettingsTab) => {
|
||||
if (tab) setSettingsTab(tab);
|
||||
@@ -696,6 +703,10 @@ export function ModalProvider({ children }: ModalProviderProps) {
|
||||
// Symphony Modal
|
||||
symphonyModalOpen,
|
||||
setSymphonyModalOpen,
|
||||
|
||||
// Windows Warning Modal
|
||||
windowsWarningModalOpen,
|
||||
setWindowsWarningModalOpen,
|
||||
}),
|
||||
[
|
||||
// Settings Modal
|
||||
@@ -811,6 +822,8 @@ export function ModalProvider({ children }: ModalProviderProps) {
|
||||
tourFromWizard,
|
||||
// Symphony Modal
|
||||
symphonyModalOpen,
|
||||
// Windows Warning Modal
|
||||
windowsWarningModalOpen,
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -350,6 +350,10 @@ export interface UseSettingsReturn {
|
||||
// File tab auto-refresh settings
|
||||
fileTabAutoRefreshEnabled: boolean;
|
||||
setFileTabAutoRefreshEnabled: (value: boolean) => void;
|
||||
|
||||
// Windows warning suppression
|
||||
suppressWindowsWarning: boolean;
|
||||
setSuppressWindowsWarning: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export function useSettings(): UseSettingsReturn {
|
||||
@@ -508,6 +512,9 @@ export function useSettings(): UseSettingsReturn {
|
||||
// File tab auto-refresh settings
|
||||
const [fileTabAutoRefreshEnabled, setFileTabAutoRefreshEnabledState] = useState(false); // Default: disabled
|
||||
|
||||
// Windows warning suppression
|
||||
const [suppressWindowsWarning, setSuppressWindowsWarningState] = useState(false); // Default: show warning
|
||||
|
||||
// Wrapper functions that persist to electron-store
|
||||
// PERF: All wrapped in useCallback to prevent re-renders
|
||||
const setLlmProvider = useCallback((value: LLMProvider) => {
|
||||
@@ -1322,6 +1329,12 @@ export function useSettings(): UseSettingsReturn {
|
||||
window.maestro.settings.set('fileTabAutoRefreshEnabled', value);
|
||||
}, []);
|
||||
|
||||
// Windows warning suppression toggle
|
||||
const setSuppressWindowsWarning = useCallback((value: boolean) => {
|
||||
setSuppressWindowsWarningState(value);
|
||||
window.maestro.settings.set('suppressWindowsWarning', value);
|
||||
}, []);
|
||||
|
||||
// Load settings from electron-store
|
||||
// This function is called on mount and on system resume (after sleep/suspend)
|
||||
// PERF: Use batch loading to reduce IPC calls from ~60 to 3
|
||||
@@ -1396,6 +1409,7 @@ export function useSettings(): UseSettingsReturn {
|
||||
const savedSshRemoteHonorGitignore = allSettings['sshRemoteHonorGitignore'];
|
||||
const savedAutomaticTabNamingEnabled = allSettings['automaticTabNamingEnabled'];
|
||||
const savedFileTabAutoRefreshEnabled = allSettings['fileTabAutoRefreshEnabled'];
|
||||
const savedSuppressWindowsWarning = allSettings['suppressWindowsWarning'];
|
||||
|
||||
if (savedEnterToSendAI !== undefined) setEnterToSendAIState(savedEnterToSendAI as boolean);
|
||||
if (savedEnterToSendTerminal !== undefined)
|
||||
@@ -1760,6 +1774,11 @@ export function useSettings(): UseSettingsReturn {
|
||||
if (savedFileTabAutoRefreshEnabled !== undefined) {
|
||||
setFileTabAutoRefreshEnabledState(savedFileTabAutoRefreshEnabled as boolean);
|
||||
}
|
||||
|
||||
// Windows warning suppression
|
||||
if (savedSuppressWindowsWarning !== undefined) {
|
||||
setSuppressWindowsWarningState(savedSuppressWindowsWarning as boolean);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Settings] Failed to load settings:', error);
|
||||
} finally {
|
||||
@@ -1938,6 +1957,8 @@ export function useSettings(): UseSettingsReturn {
|
||||
setAutomaticTabNamingEnabled,
|
||||
fileTabAutoRefreshEnabled,
|
||||
setFileTabAutoRefreshEnabled,
|
||||
suppressWindowsWarning,
|
||||
setSuppressWindowsWarning,
|
||||
}),
|
||||
[
|
||||
// State values
|
||||
@@ -2083,6 +2104,8 @@ export function useSettings(): UseSettingsReturn {
|
||||
setAutomaticTabNamingEnabled,
|
||||
fileTabAutoRefreshEnabled,
|
||||
setFileTabAutoRefreshEnabled,
|
||||
suppressWindowsWarning,
|
||||
setSuppressWindowsWarning,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user