From 811e09416f83d4f5e757e6eece5fe0f5be34055b Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Tue, 20 Oct 2026 15:34:07 +0200 Subject: [PATCH] agenda: a card's date and list without going to its board In the agenda, `D` asks for the highlighted card's due date on the command line (`:due` now works there as well as on the board), and `H` / `L` send the card to the list left or right of the one it is in, on its own board. `L` into the last list is done, as on the board. The command line now rises above an open overlay, and while it is up the overlay's own keys stay out of its way, so typing a date does not walk the agenda. --- README.md | 3 ++- web/src/App.svelte | 23 ++++++++++++++++++----- web/src/lib/Agenda.svelte | 4 ++++ web/src/lib/CommandLine.svelte | 3 ++- web/src/lib/Help.svelte | 2 ++ web/src/lib/commands.ts | 13 +++++++------ web/src/lib/store.svelte.ts | 29 +++++++++++++++++++++++++++++ 7 files changed, 64 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index ef14666..111e790 100644 --- a/README.md +++ b/README.md @@ -230,7 +230,8 @@ file is refused. due within the week, or sitting in a column between the inbox and done — the work already in hand — and says whether the diary has an entry for today. `j`/`k` walk it, `Enter` goes to the card on its own board, `Space` finishes it where -it stands, and `T` writes today's entry. The bar shows `late` and `today` counts +it stands, `D` sets its due date (`:due` on the line), `H` / `L` send it to +the list left / right on its board, and `T` writes today's entry. The bar shows `late` and `today` counts whenever either is above zero, and the agenda opens by itself once a day when something is late or due, so nothing has to be remembered to be looked at. diff --git a/web/src/App.svelte b/web/src/App.svelte index bc7d2ff..90160ca 100644 --- a/web/src/App.svelte +++ b/web/src/App.svelte @@ -307,6 +307,17 @@ case ' ': if (card) void workspace.guard(() => workspace.finishCard(card)) return true + case 'D': + // The agenda stays up behind the line, so `:due` knows which card. + if (card) { + cmdGlobal = false + cmdline = ':due ' + } + return true + case 'H': + case 'L': + if (card) void workspace.guard(() => workspace.sendAgendaCard(card, event.key === 'H' ? -1 : 1)) + return true case 'T': workspace.overlay = 'none' void runCommand('today') @@ -526,25 +537,27 @@ return } - if (workspace.overlay === 'overview' && !event.ctrlKey && !event.metaKey && !event.altKey) { + // The command line over an overlay — `D` in the agenda — keeps its keys. + const listening = cmdline === null && !isTypingTarget(event.target) + if (workspace.overlay === 'overview' && listening && !event.ctrlKey && !event.metaKey && !event.altKey) { if (overviewKey(event)) { event.preventDefault() return } } - if (workspace.overlay === 'agenda' && !event.ctrlKey && !event.metaKey && !event.altKey) { + if (workspace.overlay === 'agenda' && listening && !event.ctrlKey && !event.metaKey && !event.altKey) { if (agendaKey(event)) { event.preventDefault() return } } - if (workspace.overlay === 'media' && !event.ctrlKey && !event.metaKey && !event.altKey) { + if (workspace.overlay === 'media' && listening && !event.ctrlKey && !event.metaKey && !event.altKey) { if (mediaKey(event)) { event.preventDefault() return } } - if (workspace.overlay === 'outline' && !event.ctrlKey && !event.metaKey && !event.altKey) { + if (workspace.overlay === 'outline' && listening && !event.ctrlKey && !event.metaKey && !event.altKey) { if (outlineKey(event)) { event.preventDefault() return @@ -567,7 +580,7 @@ } } - if (workspace.overlay !== 'none') { + if (workspace.overlay !== 'none' && listening) { if (event.key === 'q' || event.key === '?') { event.preventDefault() workspace.overlay = 'none' diff --git a/web/src/lib/Agenda.svelte b/web/src/lib/Agenda.svelte index 57efc46..64e9b5c 100644 --- a/web/src/lib/Agenda.svelte +++ b/web/src/lib/Agenda.svelte @@ -87,6 +87,10 @@ ␣ done + D due + + HL list + T diary tesc close diff --git a/web/src/lib/CommandLine.svelte b/web/src/lib/CommandLine.svelte index 6be78e4..551f939 100644 --- a/web/src/lib/CommandLine.svelte +++ b/web/src/lib/CommandLine.svelte @@ -241,7 +241,8 @@ left: calc(var(--frame) + 12px); right: calc(var(--rail-w) + 12px); bottom: calc(var(--status-h) - 1px); - z-index: 20; + /* Above the overlays, so `D` in the agenda can ask for a date. */ + z-index: 35; display: flex; flex-direction: column; background: color-mix(in oklab, var(--hull) 97%, transparent); diff --git a/web/src/lib/Help.svelte b/web/src/lib/Help.svelte index 88bde39..c4e31ac 100644 --- a/web/src/lib/Help.svelte +++ b/web/src/lib/Help.svelte @@ -70,6 +70,8 @@ ['j / k', 'down / up the cards'], ['Enter', 'to the card, on its own board'], ['Space', 'done, where it stands'], + ['D', 'due date: fri, tm, +3d … — :due on the line'], + ['H / L', 'to the list left / right on its board'], ['T', "today's diary entry"], ['t · q · Esc', 'close the agenda'], ] diff --git a/web/src/lib/commands.ts b/web/src/lib/commands.ts index 4c65203..f3abe9c 100644 --- a/web/src/lib/commands.ts +++ b/web/src/lib/commands.ts @@ -425,17 +425,18 @@ export const commands: CommandSpec[] = [ name: 'due', args: '', group: 'board', - help: 'due date of the card: today, tm, fri, +3d, +2w, 10-31, 2026-10-31 — - clears', + help: 'due date of the card — on the board, or highlighted in the agenda: today, tm, fri, +3d, +2w, 10-31, 2026-10-31 — - clears', complete: () => ['today', 'tm', 'fri', '+3d'], run: async (arg) => { - if (workspace.view !== 'board') return workspace.say('no board open — :board', 'error') + const agenda = workspace.overlay === 'agenda' + if (workspace.view !== 'board' && !agenda) return workspace.say('no board open — :board', 'error') + const set = (due_at: number | null) => + agenda ? workspace.dueAgendaCard(due_at) : workspace.patchCard({ due_at }) const target = arg.trim() - if (target === '-' || target === '') { - return workspace.guard(() => workspace.patchCard({ due_at: null })) - } + if (target === '-' || target === '') return workspace.guard(() => set(null)) const at = parseDue(target) if (at === null) return workspace.say(`not a date: ${target} — try fri, +3d or 2026-09-30`, 'error') - await workspace.guard(() => workspace.patchCard({ due_at: at })) + await workspace.guard(() => set(at)) workspace.say(`due ${formatDay(at)}`) }, }, diff --git a/web/src/lib/store.svelte.ts b/web/src/lib/store.svelte.ts index 796641f..b18d53c 100644 --- a/web/src/lib/store.svelte.ts +++ b/web/src/lib/store.svelte.ts @@ -1160,6 +1160,35 @@ class Workspace { this.say(`done: ${card.title}`) } + /** `:due` with the agenda up: the highlighted card's date, on its own board. */ + async dueAgendaCard(due_at: number | null) { + const card = this.agendaCards[this.agendaCursor] + if (!card) return this.say('no card highlighted', 'error') + await api.updateCard(card.id, { due_at }) + await this.loadAgenda() + // It may have changed group; the cursor goes with it. + const at = this.agendaCards.findIndex((c) => c.id === card.id) + if (at >= 0) this.agendaCursor = at + if (this.view === 'board' && this.spaceId === card.space_id) await this.reloadBoard() + } + + /** + * `H` / `L` in the agenda: the card to the list left or right of the one it + * is in, without going to the board — `L` on the last list but one is done. + */ + async sendAgendaCard(card: AgendaCard, delta: number) { + const lists = (await api.board(card.space_id)).lists + const from = lists.findIndex((list) => list.id === card.list_id) + const target = lists[from + delta] + if (from < 0 || !target) return this.say(`${card.title} is already in the ${delta < 0 ? 'first' : 'last'} list`, 'error') + await api.moveCard(card.id, target.id, target.cards.length) + await this.loadAgenda() + const at = this.agendaCards.findIndex((c) => c.id === card.id) + if (at >= 0) this.agendaCursor = at + if (this.view === 'board' && this.spaceId === card.space_id) await this.reloadBoard() + this.say(from + delta === lists.length - 1 ? `done: ${card.title}` : `"${card.title}" → ${target.name}`) + } + async loadOverview() { this.overview = await api.overview() }