Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 30 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,34 @@ follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/); versions
correspond to [GitHub Releases](https://github.com/archdex-art/SuperSearch/releases)
and their published installers.

## [0.1.12] — 2026-07-17

Fixes two settings-window bugs found during a UI pass, and adds a real
light/dark base theme alongside the existing accent picker.

### Fixed
- **Behavior toggle switch overflowed its track.** The switch thumb had no
base horizontal anchor (`left`), so the browser fell back to a
static-position heuristic that rendered it flush against — and partly
outside — the track's right edge in the checked state. Anchored with
`left-0` so `translate-x-*` offsets are relative to a known origin.
- **Global hotkey capture looked stuck on "Listening…".** The bound toggle
shortcut stays registered as an OS-level global hotkey the entire time the
settings window is open, so pressing that combo (or anything already
intercepted) while recording a new one never reached the capture
`keydown` listener — the OS grabbed it first. The settings window now
suspends the active global hotkey for the duration of a capture session
(`suspend_toggle_shortcut`) and re-arms it on cancel, failure, or unmount
(`resume_toggle_shortcut`); a successful capture re-registers the new
combo instead, via the existing `update_settings` → `rebind_toggle` path.

### Added
- **Base theme selector.** Appearance now has a Dark/Light theme picker
alongside Accent Color, independent of it. Every settings-window surface
reads its ink/canvas colors from CSS variables (`--ink-rgb`,
`--canvas-rgb`) flipped by `theme.ts:applyTheme()`, so switching themes
repaints the whole window instantly — no reload.

## [0.1.11] — 2026-07-16

Completes the 0.1.10 settings window: the accent picker now actually
Expand Down Expand Up @@ -282,7 +310,8 @@ First cross-platform release — macOS, Linux, and Windows.

---

[Unreleased]: https://github.com/archdex-art/SuperSearch/compare/v0.1.11...HEAD
[Unreleased]: https://github.com/archdex-art/SuperSearch/compare/v0.1.12...HEAD
[0.1.12]: https://github.com/archdex-art/SuperSearch/releases/tag/v0.1.12
[0.1.11]: https://github.com/archdex-art/SuperSearch/releases/tag/v0.1.11
[0.1.10]: https://github.com/archdex-art/SuperSearch/releases/tag/v0.1.10
[0.1.9]: https://github.com/archdex-art/SuperSearch/releases/tag/v0.1.9
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

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

21 changes: 12 additions & 9 deletions react-command-palette/settings/SettingsApp.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useState } from "react";
import { motion } from "framer-motion";
import { listen } from "../bridge";
import { applyAccent } from "../theme";
import { applyAccent, applyTheme } from "../theme";
import type { Settings } from "./types";
import { getSettings, updateSettings } from "./ipc";
import { GeneralPane } from "./panes/General";
Expand All @@ -27,12 +27,14 @@ export function SettingsApp() {
void getSettings().then((s) => {
setSettings(s);
applyAccent(s.accent_color);
applyTheme(s.theme);
});
let un: undefined | (() => void);
let cancelled = false;
listen<Settings>("supersearch://settings-changed", (s) => {
setSettings(s);
applyAccent(s.accent_color);
applyTheme(s.theme);
}).then((fn) => {
if (cancelled) fn();
else un = fn;
Expand All @@ -48,21 +50,22 @@ export function SettingsApp() {
if (!prev) return prev;
const next = { ...prev, ...patch };
if ("accent_color" in patch) applyAccent(next.accent_color);
if ("theme" in patch) applyTheme(next.theme);
setSaving(true);
void updateSettings(next).finally(() => setSaving(false));
return next;
});
}, []);

return (
<div className="flex h-screen w-screen overflow-hidden bg-[hsl(32,14%,7%)] text-white">
<div className="flex h-screen w-screen overflow-hidden bg-canvas text-ink">
{/* Sidebar */}
<nav aria-label="Settings sections" className="flex w-[196px] shrink-0 flex-col gap-0.5 border-r border-white/[0.06] bg-black/[0.15] px-3 py-4">
<nav aria-label="Settings sections" className="flex w-[196px] shrink-0 flex-col gap-0.5 border-r border-ink/[0.06] bg-ink/[0.04] px-3 py-4">
<div className="mb-3 flex items-center gap-2 px-2">
<span className="relative flex h-5 w-5 shrink-0 items-center justify-center rounded-[5px] border border-accent/40 bg-accent/[0.07]">
<span className="h-[5px] w-[5px] rounded-full bg-accent" />
</span>
<span className="font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-white/70">
<span className="font-mono text-[11px] font-semibold uppercase tracking-[0.12em] text-ink/70">
Settings
</span>
</div>
Expand All @@ -74,7 +77,7 @@ export function SettingsApp() {
onClick={() => setSection(s.id)}
aria-current={section === s.id ? "page" : undefined}
className={`relative flex items-center gap-2.5 rounded-lg px-2.5 py-2 text-left text-[13px] font-medium transition-colors ${
section === s.id ? "text-white/95" : "text-white/50 hover:bg-white/[0.04] hover:text-white/80"
section === s.id ? "text-ink/95" : "text-ink/50 hover:bg-ink/[0.04] hover:text-ink/80"
}`}
>
{section === s.id && (
Expand All @@ -91,20 +94,20 @@ export function SettingsApp() {
</button>
))}

<div aria-live="polite" className="mt-auto flex items-center gap-1.5 px-2 pt-3 text-[11px] text-white/25">
<span className={`h-1.5 w-1.5 rounded-full transition-colors ${saving ? "bg-accent" : "bg-white/15"}`} />
<div aria-live="polite" className="mt-auto flex items-center gap-1.5 px-2 pt-3 text-[11px] text-ink/25">
<span className={`h-1.5 w-1.5 rounded-full transition-colors ${saving ? "bg-accent" : "bg-ink/15"}`} />
{saving ? "Saving…" : "Saved"}
</div>
</nav>

{/* Content */}
<main aria-label="Settings content" className="flex-1 overflow-y-auto px-8 py-7">
<div className="mx-auto max-w-[540px]">
<h1 className="mb-5 text-[18px] font-semibold text-white/95">
<h1 className="mb-5 text-[18px] font-semibold text-ink/95">
{SECTIONS.find((s) => s.id === section)?.label}
</h1>
{!settings ? (
<div className="py-10 text-center text-[13px] text-white/35">Loading…</div>
<div className="py-10 text-center text-[13px] text-ink/35">Loading…</div>
) : (
<motion.div
key={section}
Expand Down
14 changes: 14 additions & 0 deletions react-command-palette/settings/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,20 @@ export async function validateShortcut(shortcut: string): Promise<ShortcutCheck>
: { ok: true };
}

/** Unregister the currently-bound global hotkey for the duration of a
* capture session, so every keystroke (including the combo already bound)
* reaches the settings window instead of being swallowed by the OS-level
* shortcut hook. No-op in browser dev, where no real hotkey is registered. */
export async function suspendToggleShortcut(): Promise<void> {
if (isTauri) await tauriInvoke("suspend_toggle_shortcut");
}

/** Re-arm the persisted toggle hotkey once a capture session ends
* (committed, cancelled, or the pane unmounts mid-capture). */
export async function resumeToggleShortcut(): Promise<void> {
if (isTauri) await tauriInvoke("resume_toggle_shortcut");
}

// ── Extensions ───────────────────────────────────────────────────────────────

export async function listExtensions(): Promise<ExtensionInfo[]> {
Expand Down
8 changes: 4 additions & 4 deletions react-command-palette/settings/panes/About.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,16 @@ export function AboutPane() {
<span className="h-2.5 w-2.5 rounded-full bg-accent shadow-[0_0_10px_1px_rgb(var(--accent-rgb)/0.6)]" />
</span>
<div className="flex flex-col gap-0.5">
<span className="text-[16px] font-semibold text-white/95">SuperSearch</span>
<span className="font-mono text-[12px] text-white/40">v{version}</span>
<span className="text-[16px] font-semibold text-ink/95">SuperSearch</span>
<span className="font-mono text-[12px] text-ink/40">v{version}</span>
</div>
</div>

<div>
<SectionHeading>Updates</SectionHeading>
<Card>
<Row>
<span className="text-[13.5px] text-white/80">{result ?? "Check for the latest release"}</span>
<span className="text-[13.5px] text-ink/80">{result ?? "Check for the latest release"}</span>
<Button onClick={checkForUpdates} disabled={checking}>
{checking ? "Checking…" : "Check now"}
</Button>
Expand All @@ -64,7 +64,7 @@ export function AboutPane() {
{ label: "Report an Issue", url: "https://github.com/archdex-art/SuperSearch/issues" },
].map((link) => (
<Row key={link.url}>
<span className="text-[13.5px] text-white/80">{link.label}</span>
<span className="text-[13.5px] text-ink/80">{link.label}</span>
<a
href={link.url}
target="_blank"
Expand Down
50 changes: 43 additions & 7 deletions react-command-palette/settings/panes/Appearance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ const PRESETS: { id: string; label: string; hex: string }[] = [
{ id: "violet", label: "Violet", hex: "#a78bfa" },
];

/** Base UI theme options — independent of accent color, so a user can pair
* any accent with either surface. */
const THEMES: { id: "dark" | "light"; label: string }[] = [
{ id: "dark", label: "Dark" },
{ id: "light", label: "Light" },
];

export function AppearancePane({
settings,
onChange,
Expand All @@ -22,10 +29,39 @@ export function AppearancePane({
onChange: (patch: Partial<Settings>) => void;
}) {
const current = settings.accent_color ?? DEFAULT_ACCENT_HEX;
const activeTheme = settings.theme === "light" ? "light" : "dark";
const [customOpen, setCustomOpen] = useState(false);

return (
<div className="flex flex-col gap-6">
<div>
<SectionHeading>Theme</SectionHeading>
<Card>
<div className="flex items-center gap-3 py-4">
{THEMES.map((t) => (
<button
key={t.id}
type="button"
onClick={() => onChange({ theme: t.id })}
aria-pressed={activeTheme === t.id}
className={`flex items-center gap-2 rounded-lg border px-3.5 py-1.5 text-[12.5px] font-medium transition-colors ${
activeTheme === t.id
? "border-accent/50 bg-accent/[0.12] text-accent"
: "border-ink/[0.1] bg-ink/[0.05] text-ink/75 hover:bg-ink/[0.08] hover:text-ink/95"
}`}
>
<span
className={`h-2.5 w-2.5 shrink-0 rounded-full ring-1 ring-inset ring-ink/20 ${
t.id === "dark" ? "bg-[#141210]" : "bg-[#faf8f5]"
}`}
/>
{t.label}
</button>
))}
</div>
</Card>
</div>

<div>
<SectionHeading>Accent Color</SectionHeading>
<Card>
Expand All @@ -37,8 +73,8 @@ export function AppearancePane({
onClick={() => onChange({ accent_color: p.id === "amber" ? null : p.hex })}
aria-label={p.label}
title={p.label}
className={`relative h-8 w-8 shrink-0 rounded-full ring-2 ring-offset-2 ring-offset-[hsl(32,14%,7%)] transition-transform hover:scale-110 ${
current.toLowerCase() === p.hex.toLowerCase() ? "ring-white/70" : "ring-transparent"
className={`relative h-8 w-8 shrink-0 rounded-full ring-2 ring-offset-2 ring-offset-canvas transition-transform hover:scale-110 ${
current.toLowerCase() === p.hex.toLowerCase() ? "ring-ink/70" : "ring-transparent"
}`}
style={{ backgroundColor: p.hex }}
/>
Expand All @@ -51,8 +87,8 @@ export function AppearancePane({
onClick={() => setCustomOpen((o) => !o)}
aria-label="Custom color"
title="Custom color"
className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-[15px] leading-none ring-2 ring-offset-2 ring-offset-[hsl(32,14%,7%)] transition-transform hover:scale-110 ${
customOpen ? "ring-white/70" : "ring-white/[0.15]"
className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-[15px] leading-none ring-2 ring-offset-2 ring-offset-canvas transition-transform hover:scale-110 ${
customOpen ? "ring-ink/70" : "ring-ink/[0.15]"
}`}
style={
!PRESETS.some((p) => p.hex.toLowerCase() === current.toLowerCase())
Expand All @@ -71,7 +107,7 @@ export function AppearancePane({
onChange={(hex) => onChange({ accent_color: hex })}
style={{ width: "100%", height: 140 }}
/>
<span className="font-mono text-[11px] uppercase tracking-wide text-white/40">{current}</span>
<span className="font-mono text-[11px] uppercase tracking-wide text-ink/40">{current}</span>
</div>
)}
</Card>
Expand All @@ -88,8 +124,8 @@ export function AppearancePane({
🎵
</span>
<div className="flex min-w-0 flex-col">
<span className="truncate text-[14px] font-medium text-white/90">Apple Music</span>
<span className="truncate text-[12px] text-white/40">/Applications/Apple Music.app</span>
<span className="truncate text-[14px] font-medium text-ink/90">Apple Music</span>
<span className="truncate text-[12px] text-ink/40">/Applications/Apple Music.app</span>
</div>
<kbd
className="ml-auto rounded-md border px-1.5 py-0.5 font-mono text-[11px]"
Expand Down
20 changes: 10 additions & 10 deletions react-command-palette/settings/panes/Extensions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,11 @@ export function ExtensionsPane() {
)}

{extensions === null ? (
<div className="py-10 text-center text-[13px] text-white/35">Loading…</div>
<div className="py-10 text-center text-[13px] text-ink/35">Loading…</div>
) : extensions.length === 0 ? (
<div className="flex flex-col items-center gap-2 py-14 text-center text-white/35">
<div className="flex flex-col items-center gap-2 py-14 text-center text-ink/35">
<span className="text-[13px]">No extensions installed</span>
<span className="text-[12px] text-white/25">
<span className="text-[12px] text-ink/25">
Install a directory containing a manifest.toml to add one
</span>
</div>
Expand All @@ -119,20 +119,20 @@ export function ExtensionsPane() {
<div className="flex items-start justify-between gap-4 py-3.5">
<div className="flex min-w-0 flex-col gap-1">
<div className="flex items-center gap-2">
<span className="truncate text-[14px] font-medium text-white/90">{ext.name}</span>
<span className="font-mono text-[11px] text-white/30">v{ext.version}</span>
<span className="truncate text-[14px] font-medium text-ink/90">{ext.name}</span>
<span className="font-mono text-[11px] text-ink/30">v{ext.version}</span>
{ext.needs_trust && <Pill tone="rose">Needs trust</Pill>}
{ext.enabled && !ext.needs_trust && <Pill tone="amber">Active</Pill>}
</div>
{ext.description && (
<span className="truncate text-[12.5px] text-white/45">{ext.description}</span>
<span className="truncate text-[12.5px] text-ink/45">{ext.description}</span>
)}
{ext.author && <span className="text-[11.5px] text-white/30">by {ext.author}</span>}
{ext.author && <span className="text-[11.5px] text-ink/30">by {ext.author}</span>}
{ext.permissions.length > 0 && (
<ul className="mt-1 flex flex-col gap-0.5">
{ext.permissions.map((p) => (
<li key={p.permission} className="flex items-baseline gap-1.5 text-[11.5px] text-white/35">
<span className="font-mono text-white/50">{p.permission}</span>
<li key={p.permission} className="flex items-baseline gap-1.5 text-[11.5px] text-ink/35">
<span className="font-mono text-ink/50">{p.permission}</span>
<span>— {p.justification}</span>
</li>
))}
Expand Down Expand Up @@ -163,7 +163,7 @@ export function ExtensionsPane() {
<button
type="button"
onClick={() => setConfirmUninstall(ext.id)}
className="text-[11.5px] text-white/30 transition-colors hover:text-rose-300"
className="text-[11.5px] text-ink/30 transition-colors hover:text-rose-300"
>
Uninstall
</button>
Expand Down
Loading
Loading