Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
157f064
reafctor(styling): add dark mode and refactor design system
gabrielste1n Jan 23, 2026
93ee91c
refactor: remove more hardcoded styles in troubleshooting
gabrielste1n Jan 23, 2026
067a351
refactor: improved design system usage in oboarding flow
gabrielste1n Jan 23, 2026
bde089d
Merge main into dark-mode-toggling, preserve design system changes
gabrielste1n Jan 23, 2026
7f41e9e
refactor: button and badge consistency
gabrielste1n Jan 23, 2026
257dcd4
style: imrpveo contrast in onboarding
gabrielste1n Jan 23, 2026
cfcc7cc
Merge branch 'main' into dark-mode-toggling
gabrielste1n Jan 27, 2026
da624ab
style: improvements
gabrielste1n Jan 27, 2026
37939b0
Merge remote-tracking branch 'origin/main' into dark-mode-toggling
gabrielste1n Jan 27, 2026
42a56d4
feat: add claude skills
gabrielste1n Jan 27, 2026
433dcee
refactor: fix stlye usage
gabrielste1n Jan 27, 2026
73acaaa
refactor: remove inline styles
gabrielste1n Jan 27, 2026
55cf546
chore: remove skills from repo, keep them local-only
gabrielste1n Jan 27, 2026
0b88e22
refactor: remove hardcoded classes
gabrielste1n Jan 27, 2026
aef9c24
refactor(settings): overhaul all settings pages with unified panel sy…
gabrielste1n Jan 28, 2026
44bdd7c
Merge main into dark-mode-toggling
gabrielste1n Jan 29, 2026
88f4aab
Merge remote-tracking branch 'origin/main' into dark-mode-toggling
gabrielste1n Feb 3, 2026
e79522e
fix: remove thinking from qwen3 models on groq
gabrielste1n Feb 3, 2026
18d9400
fix:dedicated ipc for parakeet model status
gabrielste1n Feb 3, 2026
9a20278
refactor: Extend onboarding to support multiple local transcription p…
gabrielste1n Feb 3, 2026
5140555
refactor: Add dynamic window resizing based on command menu and toast…
gabrielste1n Feb 3, 2026
7b40314
feat: cancel processing buton
gabrielste1n Feb 3, 2026
d95902f
feat: Invert monochrome provider icons in dark mode
gabrielste1n Feb 3, 2026
7a28d1d
refactor(ui): polish dark mode with premium button styling, glass mor…
gabrielste1n Feb 3, 2026
5d48ec2
fix: persist local reasoning model
gabrielste1n Feb 3, 2026
8e38fe4
fix(TranscriptionModelPicker): resolve provider tab flashing by extra…
gabrielste1n Feb 3, 2026
a4c95cb
chore: replace more design tokens
gabrielste1n Feb 3, 2026
5b94d1d
chore: docs update
gabrielste1n Feb 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,37 @@ $RECYCLE.BIN/
.agent/
.agents/

# AI agent skills (keep local)
.agents/
.agent/
.claude/skills/
.cline/
.codebuddy/
.codex/
.commandcode/
.continue/
.crush/
.cursor/
.factory/
.gemini/
.github/skills/
.goose/
.kilocode/
.kiro/
.mcpjam/
.mux/
.neovate/
.opencode/
.openhands/
.pi/
.qoder/
.qwen/
.roo/
.trae/
.windsurf/
.zencoder/
skills/

# OS generated files
.DS_Store
.DS_Store?
Expand Down
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,37 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- **Cancel Processing Button**: Added ability to cancel ongoing transcription processing
- **Dark Mode Icon Inversion**: Monochrome provider icons now automatically invert in dark mode for better visibility
- **Dynamic Window Resizing**: Window automatically resizes based on command menu and toast visibility

### Changed
- **Design System Overhaul**: Complete refactor of styling to use design tokens throughout the codebase
- Button component now uses `text-foreground`, `bg-muted`, `border-border` instead of hardcoded hex values
- Removed hardcoded classes and inline styles across components
- Improved button and badge consistency
- **Settings UI Redesign**: Overhauled all settings pages with unified panel system, redesigned sidebar, and extracted permissions section
- **Dark Mode Polish**: Premium button styling, glass morphism toasts, and streamlined visuals
- **Onboarding Improvements**:
- Streamlined to 4-step flow
- Extended to support multiple local transcription providers (Whisper and NVIDIA Parakeet)
- Improved contrast and design system usage

### Fixed
- **Light Mode UI Visibility**: Fixed multiple UI elements that were invisible or hard to see in light mode:
- Settings gear icon in permission cards now uses `text-foreground`
- Troubleshoot button uses proper foreground color
- Reset button in developer settings now correctly shows destructive color
- Settings and Help icons in the toolbar are now properly visible
- Check for Updates button now renders correctly in light mode
- **Provider Tab Flashing**: Resolved TranscriptionModelPicker tab flashing by extracting ModeToggle component and syncing internal state with props
- **Local Reasoning Model Persistence**: Fixed local reasoning model selection not persisting correctly
- **Parakeet Model Status**: Added dedicated IPC channel for Parakeet model status checks
- **Groq Qwen3 Models**: Removed thinking tokens from Qwen3 models on Groq provider

## [1.3.3] - 2026-01-28

### Added
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "open-whispr",
"version": "1.3.3",
"version": "1.3.4",
"description": "A desktop dictation application using whisper.cpp for speech-to-text transcription",
"main": "main.js",
"private": true,
Expand Down
2 changes: 2 additions & 0 deletions preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ contextBridge.exposeInMainWorld("electronAPI", {
stopWindowDrag: () => ipcRenderer.invoke("stop-window-drag"),
setMainWindowInteractivity: (interactive) =>
ipcRenderer.invoke("set-main-window-interactivity", interactive),
resizeMainWindow: (sizeKey) =>
ipcRenderer.invoke("resize-main-window", sizeKey),

// Update functions
checkForUpdates: () => ipcRenderer.invoke("check-for-updates"),
Expand Down
86 changes: 48 additions & 38 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,12 @@ const Tooltip = ({ children, content, emoji }) => {
</div>
{isVisible && (
<div
className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-1 py-1 text-white bg-gradient-to-r from-neutral-800 to-neutral-700 rounded-md whitespace-nowrap z-10 transition-opacity duration-150"
style={{ fontSize: "9.7px" }}
className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-1 py-1 text-popover-foreground bg-popover border border-border rounded-md whitespace-nowrap z-10 transition-opacity duration-150 shadow-lg"
style={{ fontSize: "9.7px", maxWidth: "96px" }}
>
{emoji && <span className="mr-1">{emoji}</span>}
{content}
<div className="absolute top-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-2 border-r-2 border-t-2 border-transparent border-t-neutral-800"></div>
<div className="absolute top-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-2 border-r-2 border-t-2 border-transparent border-t-popover"></div>
</div>
)}
</div>
Expand All @@ -72,7 +72,7 @@ export default function App() {
const [isCommandMenuOpen, setIsCommandMenuOpen] = useState(false);
const commandMenuRef = useRef(null);
const buttonRef = useRef(null);
const { toast } = useToast();
const { toast, toastCount } = useToast();
const { hotkey } = useHotkey();
const { isDragging, handleMouseDown, handleMouseUp } = useWindowDrag();
const [dragStartPos, setDragStartPos] = useState(null);
Expand Down Expand Up @@ -111,21 +111,37 @@ export default function App() {
}, [toast]);

useEffect(() => {
if (isCommandMenuOpen) {
if (isCommandMenuOpen || toastCount > 0) {
setWindowInteractivity(true);
} else if (!isHovered) {
setWindowInteractivity(false);
}
}, [isCommandMenuOpen, isHovered, setWindowInteractivity]);
}, [isCommandMenuOpen, isHovered, toastCount, setWindowInteractivity]);

useEffect(() => {
const resizeWindow = () => {
if (isCommandMenuOpen && toastCount > 0) {
window.electronAPI?.resizeMainWindow?.("EXPANDED");
} else if (isCommandMenuOpen) {
window.electronAPI?.resizeMainWindow?.("WITH_MENU");
} else if (toastCount > 0) {
window.electronAPI?.resizeMainWindow?.("WITH_TOAST");
} else {
window.electronAPI?.resizeMainWindow?.("BASE");
}
};
resizeWindow();
}, [isCommandMenuOpen, toastCount]);

const handleDictationToggle = React.useCallback(() => {
setIsCommandMenuOpen(false);
setWindowInteractivity(false);
}, [setWindowInteractivity]);

const { isRecording, isProcessing, toggleListening, cancelRecording } = useAudioRecording(toast, {
onToggle: handleDictationToggle,
});
const { isRecording, isProcessing, toggleListening, cancelRecording, cancelProcessing } =
useAudioRecording(toast, {
onToggle: handleDictationToggle,
});

const handleClose = () => {
window.electronAPI.hideWindow();
Expand Down Expand Up @@ -182,23 +198,19 @@ export default function App() {

switch (micState) {
case "idle":
return {
className: `${baseClasses} bg-black/50 cursor-pointer`,
tooltip: `Press [${hotkey}] to speak`,
};
case "hover":
return {
className: `${baseClasses} bg-black/50 cursor-pointer`,
tooltip: `Press [${hotkey}] to speak`,
tooltip: `[${hotkey}] to speak`,
};
case "recording":
return {
className: `${baseClasses} bg-blue-600 cursor-pointer`,
className: `${baseClasses} bg-primary cursor-pointer`,
tooltip: "Recording...",
};
case "processing":
return {
className: `${baseClasses} bg-purple-600 cursor-not-allowed`,
className: `${baseClasses} bg-accent cursor-not-allowed`,
tooltip: "Processing...",
};
default:
Expand All @@ -213,8 +225,8 @@ export default function App() {
const micProps = getMicButtonProps();

return (
<>
{/* Fixed bottom-right voice button */}
<div className="dictation-window">
{/* Bottom-right voice button - window expands upward/leftward */}
<div className="fixed bottom-6 right-6 z-50">
<div
className="relative flex items-center gap-2"
Expand All @@ -229,19 +241,17 @@ export default function App() {
}
}}
>
{isRecording && isHovered && (
<Tooltip content="Cancel recording">
<button
aria-label="Cancel recording"
onClick={(e) => {
e.stopPropagation();
cancelRecording();
}}
className="w-7 h-7 rounded-full bg-neutral-800/90 hover:bg-red-500 border border-white/20 hover:border-red-400 flex items-center justify-center transition-all duration-150 shadow-lg backdrop-blur-sm"
>
<X size={12} strokeWidth={2.5} color="white" />
</button>
</Tooltip>
{(isRecording || isProcessing) && isHovered && (
<button
aria-label={isRecording ? "Cancel recording" : "Cancel processing"}
onClick={(e) => {
e.stopPropagation();
isRecording ? cancelRecording() : cancelProcessing();
}}
className="w-5 h-5 rounded-full bg-card/90 hover:bg-destructive border border-border hover:border-destructive flex items-center justify-center transition-all duration-150 shadow-lg backdrop-blur-sm"
>
<X size={10} strokeWidth={2.5} color="white" />
</button>
)}
<Tooltip content={micProps.tooltip}>
<button
Expand Down Expand Up @@ -320,19 +330,19 @@ export default function App() {

{/* State indicator ring for recording */}
{micState === "recording" && (
<div className="absolute inset-0 rounded-full border-2 border-blue-300 animate-pulse"></div>
<div className="absolute inset-0 rounded-full border-2 border-primary/50 animate-pulse"></div>
)}

{/* State indicator ring for processing */}
{micState === "processing" && (
<div className="absolute inset-0 rounded-full border-2 border-purple-300 opacity-50"></div>
<div className="absolute inset-0 rounded-full border-2 border-primary/30 opacity-50"></div>
)}
</button>
</Tooltip>
{isCommandMenuOpen && (
<div
ref={commandMenuRef}
className="absolute bottom-full right-0 mb-3 w-48 rounded-lg border border-white/10 bg-neutral-900/95 text-white shadow-lg backdrop-blur-sm"
className="absolute bottom-full right-0 mb-3 w-48 rounded-lg border border-border bg-popover text-popover-foreground shadow-lg backdrop-blur-sm"
onMouseEnter={() => {
setWindowInteractivity(true);
}}
Expand All @@ -343,16 +353,16 @@ export default function App() {
}}
>
<button
className="w-full px-3 py-2 text-left text-sm font-medium hover:bg-white/10 focus:bg-white/10 focus:outline-none"
className="w-full px-3 py-2 text-left text-sm font-medium hover:bg-muted focus:bg-muted focus:outline-none"
onClick={() => {
toggleListening();
}}
>
{isRecording ? "Stop listening" : "Start listening"}
</button>
<div className="h-px bg-white/10" />
<div className="h-px bg-border" />
<button
className="w-full px-3 py-2 text-left text-sm hover:bg-white/10 focus:bg-white/10 focus:outline-none"
className="w-full px-3 py-2 text-left text-sm hover:bg-muted focus:bg-muted focus:outline-none"
onClick={() => {
setIsCommandMenuOpen(false);
setWindowInteractivity(false);
Expand All @@ -365,6 +375,6 @@ export default function App() {
)}
</div>
</div>
</>
</div>
);
}
Loading
Loading