feat(web): open a task

TaskRow has had an onedit prop that nothing passed. There was no way to
see a task's description, sub-tasks, comments, files or reminders, all of
which the API has supported from the start.

A drawer rather than a page, so the list you came from stays behind it
and ticking a sub-task off and closing lands you where you were rather
than at the top of a re-fetched list. Mounted in the root layout and
driven by an id in the store, so a task opens the same way from Today, a
project, a label or a saved filter — and so an edit arriving over the
socket reaches the drawer without a second source of truth to keep in
step.

Text fields save on blur. A PATCH per keystroke is forty requests for one
sentence, and the last to arrive is not necessarily the last one sent.

The navigation shortcuts are suppressed while it is open: with a task in
front of you, "t" means the letter t.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kristian Wind
2026-08-17 10:56:33 +02:00
committed by Kristian Wind
co-authored by Claude Opus 5
parent 9d9444d0ac
commit d288606827
3 changed files with 778 additions and 1 deletions
+10
View File
@@ -6,6 +6,7 @@
import Sidebar from '$lib/components/Sidebar.svelte';
import CommandPalette from '$lib/components/CommandPalette.svelte';
import SignIn from '$lib/components/SignIn.svelte';
import TaskDetail from '$lib/components/TaskDetail.svelte';
let { children } = $props();
@@ -34,6 +35,9 @@
return;
}
if (typing || event.metaKey || event.ctrlKey || event.altKey) return;
// The navigation shortcuts are for the list. With a task open, "t" means the
// letter t — the drawer is a place you are, not a place you pass through.
if (app.detailId) return;
switch (event.key) {
case 'q':
@@ -111,6 +115,12 @@
<CommandPalette bind:open={paletteOpen} />
<!-- Mounted here rather than in each view, so a task opens the same way from
Today, a project, a label or a saved filter. -->
{#if app.detailTask}
<TaskDetail task={app.detailTask} onclose={() => app.closeDetail()} />
{/if}
<!-- Toasts are for things that failed after the interface already said they
had succeeded. Nothing else earns an interruption. -->
<div class="toasts" role="status" aria-live="polite">