agent: dogfood polish 2026/04/19 — Linux native chrome + History redesign + mic picker cleanup
Second dogfood sprint. Headline fix: Linux now uses native KWin/Mutter decorations instead of fragile frameless `startResizeDragging`, which collapsed diagonal corner resize to a single axis and made drag feel laggy. macOS / Windows keep custom chrome via `useCustomChrome` gate. Other changes: - Cross-window preferences sync via `kon:preferences-changed` Tauri event — theme and font changes propagate live to float/viewer. - Hotkey recorder rewritten to use capture-phase document listener gated by $effect; button focus was unreliable in webkit2gtk. - History page redesigned for cognitive-load hygiene: title-first compact row, inline title input, Edit popout opening /viewer in edit mode, clipboard export as .md with YAML frontmatter, manual tag chips + + Add tag input, header tag filter (cap 7), global Starred filter, `tag:xyz` search syntax. - `deriveAutoTags` kept as empty hook for post-Task-7 LLM topic tags; research found all previous auto-tag chips redundant with row UI. - Viewer window adds edit mode with debounced-save textarea; native title renamed to "Kon - Transcription Editor". - Window minimums updated per GNOME HIG + WCAG reflow research: main 960x600, float 360x480, editor 560x520. - Microphone picker filters raw ALSA strings (hw:, plughw:, front:, sysdefault:, null) and dedupes by CARD=X. New `description` field on DeviceInfo reads /proc/asound/cards so Blue Yeti shows as "Blue Microphones" instead of the short "Microphones" card name. - GPU reporting fixed: get_runtime_capabilities now returns accelerators=[cpu,vulkan] and whisper.supports_gpu=true, matching the transcribe-rs whisper-vulkan feature linked unconditionally. - ResizeHandles kept for macOS/Windows frameless: 12px edges, 20px corners via CSS vars, pointerdown + setPointerCapture, corners above edges in z-order, rendered as sibling (not child) of the animated layout root so `position: fixed` is viewport-relative. - Dueling drag-region handlers removed — `data-tauri-drag-region` and manual `startDragging()` were stacked on the same elements; kept the manual handler which has the button/input early-return logic. See HANDOVER.md for the full session log and deferred items. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,64 @@
|
||||
let audioDevices = $state([]);
|
||||
let audioDevicesError = $state(null);
|
||||
|
||||
// ALSA enumeration leaks raw device strings (hw:, plughw:, front:,
|
||||
// sysdefault:, back:, surround:, iec958:, dmix:, usbstream:, plus a
|
||||
// bogus "null" device). These are kernel-level aliases — no user
|
||||
// needs to pick between "hw:CARD=2,DEV=0" and "plughw:CARD=2,DEV=0".
|
||||
//
|
||||
// The strategy: keep a small set of well-known sentinel devices
|
||||
// (default/pipewire/pulse), then pull a single entry per unique
|
||||
// sound card by parsing CARD=X from the sysdefault: alias. That
|
||||
// gives us "Microphones" / "C920" / "Generic" as friendly labels
|
||||
// while mapping them to a reliable ALSA path.
|
||||
const SENTINEL_DEVICES = new Set(["default", "pipewire", "pulse"]);
|
||||
|
||||
function parseCardName(name) {
|
||||
const match = String(name || "").match(/CARD=([^,]+)/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
function buildVisibleDevices(devices) {
|
||||
const out = [];
|
||||
const seenCards = new Set();
|
||||
|
||||
for (const dev of devices) {
|
||||
const name = dev?.name || "";
|
||||
if (!name || name === "null") continue;
|
||||
if (SENTINEL_DEVICES.has(name)) {
|
||||
out.push(dev);
|
||||
continue;
|
||||
}
|
||||
if (name.startsWith("sysdefault:CARD=")) {
|
||||
const card = parseCardName(name);
|
||||
if (card && !seenCards.has(card)) {
|
||||
seenCards.add(card);
|
||||
out.push(dev);
|
||||
}
|
||||
}
|
||||
// Everything else (hw:, plughw:, front:, dmix:, etc.) is silently
|
||||
// dropped — cpal will resolve the sysdefault:CARD= form fine.
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function friendlyLabel(dev) {
|
||||
const name = dev?.name || "";
|
||||
if (name === "default") return "System default";
|
||||
if (name === "pipewire") return "PipeWire";
|
||||
if (name === "pulse") return "PulseAudio";
|
||||
// Prefer the rich product description from /proc/asound/cards
|
||||
// (e.g. "Blue Microphones" for the Yeti). Falls back to the raw
|
||||
// CARD=X short name if we couldn't load descriptions.
|
||||
const desc = (dev?.description || "").trim();
|
||||
if (desc) return desc;
|
||||
const card = parseCardName(name);
|
||||
if (card) return card;
|
||||
return name;
|
||||
}
|
||||
|
||||
let visibleAudioDevices = $derived(buildVisibleDevices(audioDevices));
|
||||
|
||||
async function refreshAudioDevices() {
|
||||
audioDevicesError = null;
|
||||
try {
|
||||
@@ -448,11 +506,9 @@
|
||||
onfocus={refreshAudioDevices}
|
||||
>
|
||||
<option value="">Auto (recommended) — let Kon pick the working mic</option>
|
||||
{#each audioDevices as dev}
|
||||
{#each visibleAudioDevices as dev}
|
||||
<option value={dev.name} disabled={dev.is_likely_monitor}>
|
||||
{dev.name}
|
||||
{dev.is_default ? " (system default)" : ""}
|
||||
{dev.is_likely_monitor ? " — speaker monitor, skip" : ""}
|
||||
{friendlyLabel(dev)}{#if dev.is_default && dev.name !== "default"} (system default){/if}{#if dev.is_likely_monitor} — speaker monitor, skip{/if}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
@@ -464,7 +520,7 @@
|
||||
</div>
|
||||
{#if audioDevicesError}
|
||||
<p class="text-[11px] text-error mt-2">{audioDevicesError}</p>
|
||||
{:else if audioDevices.length === 0}
|
||||
{:else if visibleAudioDevices.length === 0}
|
||||
<p class="text-[11px] text-text-tertiary mt-2">No input devices detected. Check that a microphone is connected and PulseAudio/PipeWire is running.</p>
|
||||
{:else}
|
||||
<p class="text-[11px] text-text-tertiary mt-2">
|
||||
|
||||
Reference in New Issue
Block a user