feat: platform abstraction layer (Windows foundation)#15
Closed
feat: platform abstraction layer (Windows foundation)#15
Conversation
Introduces src/main/platform/ — a thin interface layer that separates
macOS-specific native code from the rest of the app.
- interface.ts defines PlatformCapabilities (microphone, audio probe,
TTS backend, hotkey monitor, snippet expander, color picker)
- darwin.ts macOS implementation, self-contained (mirrors logic currently
inline in main.ts so main.ts can migrate incrementally)
- windows.ts Windows stubs — every method returns a safe value so the app
runs without crashing; real implementations follow in later PRs
- index.ts exports `platform` (darwin on macOS, windows on win32)
main.ts is untouched — no behaviour changes on macOS.
Replaces inline macOS-guarded implementations in main.ts with calls to the platform layer (platform/darwin.ts on macOS, platform/windows.ts stubs on Windows): - readMicrophoneAccessStatus() → platform.readMicrophoneAccessStatus() - probeAudioDurationMs() → platform.probeAudioDurationMs() - resolveLocalSpeakBackend() → platform.resolveSpeakBackend() - native-pick-color IPC handler → platform.pickColor() Types MicrophoneAccessStatus, MicrophonePermissionResult, LocalSpeakBackend are now imported from platform/interface.ts instead of being redeclared. No behaviour change on macOS. On Windows the app no longer crashes on these code paths — each returns a safe stub value.
…m layer - refreshSnippetExpander: binary resolution + spawn moved to platform.spawnSnippetExpander() - startWhisperHoldWatcher: removes ensureWhisperHoldWatcherBinary(), uses platform.spawnHotkeyHoldMonitor() - startFnSpeakToggleWatcher: same — uses platform.spawnHotkeyHoldMonitor() - HotkeyModifiers: added fn field to match Swift binary arg order (keyCode cmd ctrl alt shift fn) - darwin.ts: folds in compile-on-demand logic from ensureWhisperHoldWatcherBinary Event handlers remain in main.ts since they reference main.ts-local state. No behaviour change on macOS. On Windows both features return null and are skipped gracefully.
- requestMicrophoneAccessViaNative() → platform.requestMicrophoneAccessViaNative() - ensureMicrophoneAccess(): remove process.platform guard — readMicrophoneAccessStatus() already returns 'granted' on Windows via the platform layer, so the guard was redundant All platform-specific native code in main.ts is now behind platform.*
…build script - supercmd.ico: Windows icon with 6 sizes (16/32/48/64/128/256px) - scripts/build-native.js: replaces the inline swiftc chain in package.json; compiles Swift helpers on macOS and exits cleanly (no-op) on Windows - package.json: build:native now calls the script; adds electron-builder win target (nsis installer, x64 + arm64) and nsis config
- test job: runs unit tests on ubuntu-latest (same as feat/add-test-coverage) - build-windows job: runs on windows-latest, compiles TypeScript + Vite, skips Swift (build:native is a no-op on Windows), then does an unsigned electron-builder dir build to confirm the app packages without errors Triggers on push/PR to main and feat/windows-foundation.
- Add vitest@^4.0.0 to devDependencies and a `test` script so `npm test` works in the Tests CI job - Regenerate package-lock.json so `npm ci` can install vitest in CI - Create vitest.config.ts (node environment, src/main/__tests__ glob) - Exclude __tests__ / *.test.ts files from tsconfig.main.json so the Windows build job no longer fails with TS2307 (cannot find 'vitest') - Guard icons.test.ts with describe.runIf(darwin) — iconutil is macOS-only and would error on the Linux test runner
Setting WIN_CSC_LINK to an empty string causes electron-builder to resolve it as a relative path (→ cwd), which fails with "not a file". Leave it unset — CSC_IDENTITY_AUTO_DISCOVERY=false is sufficient to skip signing when no certificate is available.
Color picker
- Implements platform.pickColor() using a small Electron BrowserWindow
with an inlined <input type="color"> UI. Returns the hex string on OK,
null on Cancel/Escape. No native binary required.
Hotkey hold monitor
- Adds src/native/hotkey-hold-monitor.c — a Win32 WH_KEYBOARD_LL hook
that follows the exact same JSON-over-stdout protocol as the macOS
Swift binary ({"ready"}, {"pressed"}, {"released"}).
- Maps macOS CGKeyCodes (used by parseHoldShortcutConfig) to Windows VKs.
Command and Fn args are accepted but ignored (no Win32 equivalent).
- Updates scripts/build-native.js to compile it with gcc (MinGW) on
Windows; existing macOS Swift compilation is unchanged.
- Wires platform/windows.ts to spawn hotkey-hold-monitor.exe from
dist/native/, falling back gracefully if the binary is absent.
CI artifact
- Uploads out/win-unpacked/ as "SuperCmd-win-x64-portable" (14-day
retention) so testers can download, extract, and run SuperCmd.exe
directly from any passing CI run without a full NSIS installer.
- Updates the stale comment that said build:native was a no-op on Windows.
Three issues blocked running SuperCmd on Windows when launched from VS Code
(or any Electron-based IDE):
1. ELECTRON_RUN_AS_NODE=1 is inherited from the VS Code parent process,
causing Electron to run as plain Node.js instead of an Electron app.
This made `require('electron')` resolve to the npm package path string
instead of the Electron API, crashing at app.getVersion(). Fix: add
scripts/launch-electron.js which explicitly deletes ELECTRON_RUN_AS_NODE
from the env before spawning the Electron binary.
2. Alt+Space is reserved by Windows (opens the window system menu) and
cannot be registered as a global shortcut. Fix: use Ctrl+Space as the
default on Windows, with an automatic migration for existing settings
that saved Alt+Space.
3. build:native crashed with a non-zero exit code when gcc/clang/cl were
not in PATH, blocking `npm run build`. Fix: probe for available compilers
and warn+skip instead of crashing; the app still runs, the hold-hotkey
feature is gracefully disabled.
- Step 0: show Windows-appropriate 'what gets configured' list - Step 2: hide macOS 'Replace Spotlight' section; show Ctrl instead of Cmd - Step 3: replace 4 macOS permission rows with simplified Windows screen (Windows manages Accessibility/Input Monitoring automatically; only Microphone is noted as needed for Whisper) - Step 4: show Windows-specific hint about Fn key and edge-tts - Step 5: show Ctrl+Shift+S keycap instead of ⌘Cmd+Shift+S - Fix default shortcut fallback to Ctrl+Space on win32 - Add 'platform' and 'homeDir' to ElectronAPI type definition
- Remove forced migration of Alt+Space → Ctrl+Space in settings-store.ts; fresh installs still default to Ctrl+Space via DEFAULT_GLOBAL_SHORTCUT - Replace macOS Spotlight section in onboarding Step 2 with a PowerToys-aware hint explaining how to use Alt+Space when PowerToys Run is disabled first
Reduce ONBOARDING_WINDOW_WIDTH from 1120→900 and ONBOARDING_WINDOW_HEIGHT from 740→600. Position the window vertically centered (workArea height / 2) instead of at the fixed topFactor offset near the top.
900px width broke the lg: Tailwind breakpoints causing all two-column layouts to collapse vertically. Keep width at 1120 (above 1024px lg threshold) and reduce height from 740 to 680 for a shorter window that still fits all content. Vertical centering from previous commit retained.
…tion - Raise text opacity from white/55 to white/75 so the hint is readable - Explain that Windows routes Alt+Space to PowerToys/system before SuperCmd can capture it, so users must disable PowerToys Run first - Highlight the action step in white/90 so the fix is immediately clear
7837ef3 to
b67e367
Compare
- Step 3 onboarding: add Request Access button that triggers the real Windows microphone permission dialog via getUserMedia, with granted/ error feedback instead of passive static text - Default hold-to-talk key changed from Fn to Ctrl+Shift+Space on Windows — Fn is handled by keyboard firmware and never reaches Win32 - Step 4 hint updated to explain why Fn is unsupported and what to use
The default speechToTextModel was 'native' on all platforms. On Windows there is no native Swift speech recognizer, so the whisper-transcribe IPC handler silently returned an empty string — the UI appeared but nothing was transcribed. - settings-store: default speechToTextModel to '' on Windows so fresh installs fall through to OpenAI (gpt-4o-transcribe) automatically - main: when sttModel === 'native' on Windows, redirect to OpenAI instead of returning '' — gives a proper 'API key not configured' error rather than silent failure for users with saved native setting Users need an OpenAI API key set in Settings -> AI for dictation.
…oard On Windows osascript is unavailable so getSelectedTextForSpeak always returned empty string, causing startSpeakFromSelection to return false and Read mode to never open. Add a Windows-specific path that simulates Ctrl+C on the foreground window (which still has focus because speak mode runs showWindow:false), polls the clipboard until it changes, captures the selection, then restores the previous clipboard — same pattern as the macOS fallback but using PowerShell SendKeys instead of osascript.
Text selection: - Replace Ctrl+C (SendKeys) with UIAutomation as primary method — reads selected text directly from the focused element without sending any keystrokes to the user's app, like macOS AXSelectedText - Keep Ctrl+C only as fallback for apps that don't expose TextPattern - Add windowsHide:true to all PowerShell spawns to prevent focus theft Audio playback (ENOENT fix): - /usr/bin/afplay does not exist on Windows; replace with PowerShell -STA + System.Windows.Media.MediaPlayer which handles MP3 natively - Pass audio path via SUPERCMD_AUDIO_PATH env var to avoid escaping - Applied to both the main playAudioFile loop and speak-preview-voice
…indow is focused When the onboarding window (or any SuperCmd BrowserWindow) has focus, UIAutomation and Ctrl+C cannot read text selected inside the Chromium renderer. Add a first-priority check that calls executeJavaScript to get window.getSelection().toString() directly from the focused window, falling through to UIAutomation/clipboard only for external apps.
…s blur backdrop-filter compositing on Windows causes a visible fade-in delay and the semi-transparent rgba values (0.74–0.80) look far more see-through than on macOS where vibrancy provides the backing material. - Stamp data-platform attribute on <body> at startup - CSS: [data-platform="win32"] overrides .glass-effect and .cursor-prompt-surface to rgba(12,12,18,0.97) with no backdrop-filter - OnboardingExtension: Windows uses fully opaque gradient wrapper and opaque contentBackground base (no "transparent" bleed-through) - App: actions dropdown and context menu inline styles conditioned on isWindows — remove backdropFilter, lift background to 0.99 opacity
…dows On Windows there is no SFSpeechRecognizer (macOS-only Swift binary). Rather than falling back to the native binary path (which silently fails), introduce a 'webspeech' backend that uses Chromium's built-in Web Speech API — the same engine Chrome uses, requiring no API key. - resolveSessionConfig picks 'webspeech' on win32 when no cloud key is set - startListening: new webspeech branch creates a SpeechRecognition instance with continuous=true, interimResults=true; accumulates onresult events into combinedTranscriptRef (same store used by the native path) - finalizeAndClose: webspeech treated as isNativeBackend; stop() called on release, 250 ms drain for trailing callbacks, then text is pasted - forceStopCapture: abort() the recognition if still active - Network error shown with a helpful fallback message (configure OpenAI key)
… onboarding hotkey - build-native.js: add findMsvcCl() to search VS 2017/2019/2022 install locations for cl.exe when it is not on PATH; add makeMsvcCompiler() to build a compiler entry with the correct MSVC include/lib paths and Windows SDK headers so hotkey-hold-monitor.exe compiles without a Developer Command Prompt. Path calculation fixed to go exactly 3 levels up from the x64 bin dir to reach the MSVC version root. - OnboardingExtension.tsx: show 'Ctrl+Shift+Space' as the default hold-to-talk hotkey on Windows instead of 'Fn' (which is a firmware key on Windows and cannot be registered as a global shortcut). - .gitignore: add *.obj to suppress MSVC intermediate object files. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On Windows, openPermissionTarget was always trying to open x-apple.systempreferences:... URLs after requesting microphone access, causing a Windows dialog: 'How do you want to open this?' - Skip all x-apple.systempreferences URLs on Windows entirely - On Windows, only open ms-settings:privacy-microphone when access is explicitly denied/restricted (so the user can unblock it in Settings) - Fix error note messages to say 'Settings → Privacy & Security → Microphone' instead of 'System Settings' on Windows Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… paths
Root causes of broken Windows dictation:
1. hotkey-hold-monitor.exe path wrong: windows.ts compiles to
dist/main/platform/ so __dirname/../native = dist/main/native (not found).
Fixed to go up two levels so the path resolves to dist/native/.
2. Web Speech API ('webspeech' backend) fails with 'network' error in
Electron because Electron builds do not include Google's API keys that
Chrome embeds. Switched to the 'native' backend on all platforms.
3. Windows has no native speech binary. Added speech-recognizer.cs — a
C# console app using System.Speech.Recognition (built into .NET
Framework 4.x, available on every Windows 10/11 machine, works fully
offline with no API key). Protocol: emits {"ready":true},
{"transcript":"...","isFinal":true|false}, {"error":"..."} JSON lines
to stdout, same as the macOS Swift binary.
4. whisper-start-native in main.ts used 'speech-recognizer' (no .exe) on
all platforms; Windows needs 'speech-recognizer.exe'. Added platform
check. On-demand compile fallback now uses csc.exe + the WPF-subfolder
System.Speech.dll path on Windows.
build-native.js changes:
- Add C# compilation step using C:\Windows\Microsoft.NET\Framework64\
v4.0.30319\csc.exe with System.Speech.dll from the WPF subfolder.
- Both hotkey-hold-monitor.exe and speech-recognizer.exe are now built
by `npm run build:native`.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- index.html: add <meta name="color-scheme" content="dark"> so Chromium
renders all native form elements (select dropdowns, scrollbars, inputs)
in dark mode regardless of the Windows system theme.
- main.ts: set nativeTheme.themeSource = 'dark' so Electron's own chrome
and any OS-level dialogs also use dark styling.
- main.ts: call app.setAppUserModelId('com.supercmd.app') on Windows so
all SuperCmd windows are grouped under one taskbar entry with the
correct app name.
- main.ts: set icon: path.join(__dirname, '../../supercmd.ico') on all
three BrowserWindows (main launcher, Settings, Extension Store) on
Windows so the taskbar and alt-tab switcher show the SuperCmd icon
instead of the default Electron icon.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Set color-scheme: dark directly on select elements so Chromium renders the OS-level popup list in dark mode on Windows. Also make all select backgrounds fully opaque (replacing semi-transparent rgba values that composite to white against the transparent window). Covers both the Settings tab selects and the SuperCmd Read widget voice/speed selects. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- discoverWindowsApplications(): scans Start Menu .lnk shortcuts via a single PowerShell invocation (WScript.Shell COM), resolves each to its .exe target path, deduplicates by path, filters uninstallers. Populates the 'app' category in the launcher. - extractWindowsIcons(): batch-extracts .exe icons via PowerShell + System.Drawing.Icon.ExtractAssociatedIcon; results cached to disk so subsequent launches are instant. Runs in batches of 20. - discoverWindowsSystemSettings(): returns a static curated list of 37 Windows Settings panels (ms-settings: URIs) with keywords and emoji icons. Populates the 'settings' category in the launcher. - openAppByPath(): uses Electron shell.openPath() on Windows (ShellExecute). - openSettingsPane(): uses shell.openExternal(ms-settings:...) on Windows. - discoverAndBuildCommands(): guards NSWorkspace block behind !win32 and runs Windows icon extraction instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rewrites filterCommands() with a new scoreWord() engine that scores queries across 8 tiers: exact → prefix → word-boundary substring → substring → acronym-exact → acronym-prefix → acronym-contains → fuzzy subsequence (consecutive/word-start bonuses). Multi-word queries now split on whitespace and require every word to match somewhere in title or keywords (e.g. "dark mode" finds "Toggle Dark / Light Mode"). Also adds proper Lucide icons for the 7 new PowerToys-parity system commands in getSystemCommandFallbackIcon. Verified working: global hotkey (Ctrl+Space), fuzzy search across all 169+ commands on Windows. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix context menu mousedown handler to only close when clicking outside the popup (was firing before onClick, preventing actions) - Replace all e.metaKey checks with cmdOrCtrl (metaKey || ctrlKey on Windows) so Ctrl+K, Ctrl+Shift+P, Ctrl+Shift+D, Ctrl+Alt+Up/Down all work correctly on Windows - Update shortcut labels in context menu to show Ctrl on Windows instead of Cmd Verified: pin/unpin via right-click and Ctrl+Shift+P both working. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s can be re-enabled Added 'Applications' and 'System Settings' groups to the left panel in Settings → Extensions. Previously only 'system', 'extension', and 'script' category commands were listed, making it impossible to re-enable Windows apps or Settings panels that had been disabled from the launcher. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tc.)
Previously only Start Menu .lnk → .exe shortcuts were indexed, missing all
UWP/packaged apps. Now a second PowerShell pass via Get-StartApps finds every
app whose AppID contains '!' (PackageFamilyName!AppId), adds them as 'app'
category commands with a shell:AppsFolder\{AUMID} path, and deduplicates
against the traditional apps already found.
openAppByPath() now routes shell: URIs through shell.openExternal() so UWP
apps (Settings, Calculator, Store, Xbox, Mail, etc.) launch correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ows ✅ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The backend (applyOpenAtLogin, set-open-at-login IPC, setOpenAtLogin bridge) was already fully implemented but the UI toggle was never added to GeneralTab. Added a checkbox card that calls window.electron.setOpenAtLogin() and updates local state immediately. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs:
1. The .lnk scan was picking up Calculator and other UWP apps as
C:\Program Files\WindowsApps\...\*.exe paths — these pass Test-Path but
fail to open because the WindowsApps folder is access-restricted. Added
`-notmatch 'WindowsApps'` to exclude them; Get-StartApps then picks
them up with the correct shell:AppsFolder\{AUMID} path.
2. shell.openExternal() is unreliable for shell: URIs on Windows. Replaced
with PowerShell Start-Process which handles shell:AppsFolder\ URIs natively.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
will this be continued? CUrrently a file is missing... |
…feat/windows-foundation # Conflicts: # src/main/__tests__/icons.test.ts # src/main/main.ts # src/main/settings-store.ts # src/renderer/src/App.tsx # src/renderer/src/settings/ExtensionsTab.tsx # src/renderer/src/settings/GeneralTab.tsx
…arity, and feature matrix
|
Installing extesnsion does not work as it searches git with brew |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
Introduces
src/main/platform/— a clean separation between macOS-specific native code and the rest of the app. This is the foundation for Windows support.Files added
platform/interface.tsplatform/darwin.tsmain.ts)platform/windows.tsplatform/index.tsplatform— picks darwin on macOS, windows on win32What was migrated out of
main.tsplatform.readMicrophoneAccessStatus()platform.requestMicrophoneAccessViaNative()afinfocallplatform.probeAudioDurationMs()platform.resolveSpeakBackend()execFileblockplatform.pickColor()platform.spawnSnippetExpander()platform.spawnHotkeyHoldMonitor()Duplicate type declarations (
MicrophoneAccessStatus,MicrophonePermissionResult,LocalSpeakBackend) removed frommain.tsand imported fromplatforminstead.Safety
darwin.tscontains identical logicnull,false, or'granted'as appropriate with a comment pointing to the follow-up PR that will implement itWhat's next (follow-up PRs on this branch)
SetWindowsHookEx)ChooseColoror JS fallback)electron-builderwintarget +.icoicon