fix: improve frontend accessibility, consistency, and resource cleanup

Fix nested interactive elements in TaskItem (button inside button) by
restructuring as div + button with aria-label. Replace native confirm()
with ConfirmDialog for workspace removal. Store fs-changed event unlisten
function for cleanup. Log file watcher errors instead of swallowing them.
Fix var usage to const. Add Firefox scrollbar-width support.
This commit is contained in:
Tristan Michael 2026-04-02 08:23:38 -07:00
parent 056cd8ee49
commit 54836f14e7
5 changed files with 36 additions and 11 deletions

View file

@ -45,6 +45,13 @@ body {
}
/* Scrollbar styling */
* {
scrollbar-width: thin;
scrollbar-color: #d1d5db transparent;
}
.dark * {
scrollbar-color: #4b5563 transparent;
}
::-webkit-scrollbar {
width: 4px;
}

View file

@ -34,7 +34,7 @@
function debouncedSave(fields: Partial<Task>) {
clearTimeout(saveTimer);
var snapshot = { ...task };
const snapshot = { ...task };
saveTimer = setTimeout(() => {
app.updateTask({ ...snapshot, ...fields, updated_at: new Date().toISOString() });
}, 400);

View file

@ -106,15 +106,16 @@
{/if}
<!-- Task content -->
<button
class="group flex w-full items-start gap-3 bg-surface-light py-3 pr-4 text-left hover:bg-black/5 dark:bg-surface-dark dark:hover:bg-white/5"
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="group flex w-full cursor-pointer items-start gap-3 bg-surface-light py-3 pr-4 text-left hover:bg-black/5 dark:bg-surface-dark dark:hover:bg-white/5"
style="padding-left: {1 + depth * 1.5}rem; transform: translateX({swipeX}px); transition: {swiping ? 'none' : 'transform 0.2s ease-out'}"
onclick={() => onopen?.(task)}
>
<!-- Checkbox -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
<button
onclick={handleToggle}
aria-label={isCompleted ? "Restore task" : "Complete task"}
class="-m-2 flex shrink-0 items-center justify-center p-2"
>
<div
@ -131,7 +132,7 @@
</svg>
{/if}
</div>
</div>
</button>
<!-- Content -->
<div class="min-w-0 flex-1">
@ -160,7 +161,7 @@
<svg class="mt-1 h-4 w-4 shrink-0 opacity-0 transition-opacity group-hover:opacity-30" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" />
</svg>
</button>
</div>
</div>
</div>
</div>

View file

@ -44,7 +44,7 @@
showWorkspacePicker = false;
if (showListMenu && listMenuEl && !listMenuEl.contains(e.target as Node))
showListMenu = false;
var target = e.target as HTMLElement;
const target = e.target as HTMLElement;
if (wsMenuName && !target.closest("[data-ws-menu]")) wsMenuName = null;
}
@ -58,6 +58,7 @@
let listMenuEl = $state<HTMLDivElement | null>(null);
let confirmDeleteList = $state(false);
let confirmDeleteCompleted = $state(false);
let confirmRemoveWorkspace = $state<string | null>(null);
let dragId = $state<string | null>(null);
let dragOverId = $state<string | null>(null);
let resizing = $state(false);
@ -270,7 +271,7 @@
{#if wsMenuName === name}
<div class="absolute right-0 top-full z-40 mt-1 min-w-[140px] rounded-lg border border-border-light bg-surface-light py-1 shadow-lg dark:border-border-dark dark:bg-surface-dark">
<button
onclick={() => { wsMenuName = null; if (confirm(`Remove workspace "${name}"? (Files remain on disk)`)) app.removeWorkspace(name); }}
onclick={() => { wsMenuName = null; confirmRemoveWorkspace = name; }}
class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm text-danger hover:bg-black/5 dark:hover:bg-white/10"
>
<svg class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
@ -651,6 +652,18 @@
/>
{/if}
<!-- Remove workspace confirmation -->
{#if confirmRemoveWorkspace}
<ConfirmDialog
message='Remove workspace "{confirmRemoveWorkspace}"?'
detail="Files remain on disk."
confirmText="Remove"
danger
onconfirm={() => { const name = confirmRemoveWorkspace; confirmRemoveWorkspace = null; if (name) app.removeWorkspace(name); }}
oncancel={() => (confirmRemoveWorkspace = null)}
/>
{/if}
<!-- Delete completed tasks confirmation -->
{#if confirmDeleteCompleted}
<ConfirmDialog

View file

@ -8,9 +8,13 @@ import type {
SyncResult,
} from "../types";
// Listen for file system changes from the backend watcher
// Listen for file system changes from the backend watcher.
// Store the unlisten function so it can be cleaned up if needed.
let _unlistenFs: (() => void) | null = null;
listen("fs-changed", () => {
loadLists();
}).then((unlisten) => {
_unlistenFs = unlisten;
});
// ── Reactive state ───────────────────────────────────────────────────
@ -79,7 +83,7 @@ async function addWorkspace(name: string, path: string) {
await invoke("add_workspace", { name, path });
config = await invoke<AppConfig>("get_config");
await loadLists();
invoke("watch_workspace", { path }).catch(() => {});
invoke("watch_workspace", { path }).catch((e) => console.warn("File watcher failed:", e));
screen = "tasks";
error = null;
} catch (e) {