diff --git a/README.md b/README.md index 94e04f3..8474841 100644 --- a/README.md +++ b/README.md @@ -302,6 +302,8 @@ the agenda kept up so `:repeat` acts on the card, `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. +The day is the one in `WORKSPACE_TIMEZONE`, as for the mails, so a browser on a +trip groups the cards the same way. Below the cards it lists what the diary holds from this day in earlier years; `j` walks on into them and `Enter` opens one. A 29 February is remembered on the 28th in the years that have none. @@ -846,7 +848,7 @@ sealed or not. | `DELETE` | `/api/trash/{id}` | delete one thing in the trash for good | | `POST` | `/api/trash/{id}/restore` | back where it was deleted from, or to its space when that folder is gone | | `GET` | `/api/spaces/{id}/board` | the lists and cards of one space | -| `GET` | `/api/agenda` | open cards due within a week or in hand, on every board, whether the diary has today, and its entries `on_this_day` in earlier years | +| `GET` | `/api/agenda` | open cards due within a week or in hand, on every board, whether the diary has today, its entries `on_this_day` in earlier years, and `today` in `WORKSPACE_TIMEZONE`, with its bounds | | `GET` | `/api/diary/days?month=2026-10` | the diary's entries in a month, each with its local `day` | | `GET` | `/api/diary/around/{id}` | the entries before and after a diary entry, by date | | `GET` | `/api/nodes/{id}/templates` | the templates of the space the node is in | diff --git a/docs/wiki/API.md b/docs/wiki/API.md index 8aef60d..b1baa07 100644 --- a/docs/wiki/API.md +++ b/docs/wiki/API.md @@ -20,7 +20,7 @@ sealed or not. | `DELETE` | `/api/trash/{id}` | delete one thing in the trash for good | | `POST` | `/api/trash/{id}/restore` | back where it was deleted from, or to its space when that folder is gone | | `GET` | `/api/spaces/{id}/board` | the lists and cards of one space | -| `GET` | `/api/agenda` | open cards due within a week or in hand, on every board, whether the diary has today, and its entries `on_this_day` in earlier years | +| `GET` | `/api/agenda` | open cards due within a week or in hand, on every board, whether the diary has today, its entries `on_this_day` in earlier years, and `today` in `WORKSPACE_TIMEZONE`, with its bounds | | `GET` | `/api/diary/days?month=2026-10` | the diary's entries in a month, each with its local `day` | | `GET` | `/api/diary/around/{id}` | the entries before and after a diary entry, by date | | `GET` | `/api/nodes/{id}/templates` | the templates of the space the node is in | diff --git a/docs/wiki/Boards-and-Agenda.md b/docs/wiki/Boards-and-Agenda.md index 20c979e..7b4652d 100644 --- a/docs/wiki/Boards-and-Agenda.md +++ b/docs/wiki/Boards-and-Agenda.md @@ -107,7 +107,9 @@ does not make a second copy. The board and the agenda show the rule as | `T` | today's diary entry | | `t`, `q`, `Esc` | close | -The agenda opens by itself once a day when something is late or due. The bar +The agenda opens by itself once a day when something is late or due. "Today" +is the day in `WORKSPACE_TIMEZONE`, the same as for the mails, so a browser in +another timezone groups the cards the same way. The bar shows the `late` and `today` counts whenever either is above zero. With mail set up, cards due today are mailed in the morning too; see [Backup and mail](Backup-and-Mail.md#mail). diff --git a/scripts/screenshots.ts b/scripts/screenshots.ts index 19ec9d3..ea03368 100644 --- a/scripts/screenshots.ts +++ b/scripts/screenshots.ts @@ -356,7 +356,7 @@ await send('Page.enable') await send('Network.enable') // The agenda opens itself once a day; here it opens when asked. await send('Page.addScriptToEvaluateOnNewDocument', { - source: `localStorage.setItem('diary:agenda-seen', new Date().toDateString())`, + source: `localStorage.setItem('diary:agenda-seen', new Intl.DateTimeFormat('en-CA', { timeZone: 'Europe/Berlin' }).format(new Date()))`, }) await viewport(1440, 900) diff --git a/src/routes/boards.rs b/src/routes/boards.rs index 032be50..4c9da81 100644 --- a/src/routes/boards.rs +++ b/src/routes/boards.rs @@ -157,6 +157,21 @@ pub struct Agenda { pub diary_today: Option, /// Diary entries from this day in earlier years, the latest year first. pub on_this_day: Vec, + /// Today in the configured timezone, which is the day the cards are due + /// on and the mails are sent by — not the browser's, which may be + /// somewhere else, and not 24 hours long when the clocks change. + pub today: AgendaDay, +} + +#[derive(Serialize)] +pub struct AgendaDay { + /// `2026-10-21`. + pub date: String, + /// When today starts and ends, in Unix seconds. + pub start: i64, + pub end: i64, + /// The end of the seventh day after today: what is due before it is this week. + pub week_end: i64, } #[derive(Serialize)] @@ -189,10 +204,19 @@ pub async fn agenda(_: Session, State(state): State) -> AppResult({ cards: [], diary_today: null, on_this_day: [] }) + agenda = $state({ cards: [], diary_today: null, on_this_day: [], today: null }) agendaCursor = $state(0) /** `C`: the day the calendar's cursor is on, `2026-10-20`, and its month's entries. */ @@ -386,16 +386,18 @@ class Workspace { return this.list?.cards[this.cardCursor] } - /** The agenda in the order it is drawn and walked, bucketed by urgency. */ + /** + * The agenda in the order it is drawn and walked, bucketed by urgency. The + * day is the server's, in the workspace's timezone, so a browser on a trip + * groups the cards the way the morning mail does. + */ get agendaGroups(): { key: 'overdue' | 'today' | 'week' | 'doing'; cards: AgendaCard[] }[] { - const midnight = new Date() - midnight.setHours(0, 0, 0, 0) - const start = Math.floor(midnight.getTime() / 1000) + const { start, end, week_end } = this.agenda.today ?? { start: 0, end: 0, week_end: 0 } const groups = { overdue: [] as AgendaCard[], today: [] as AgendaCard[], week: [] as AgendaCard[], doing: [] as AgendaCard[] } for (const card of this.agenda.cards) { if (card.due_at !== null && card.due_at < start) groups.overdue.push(card) - else if (card.due_at !== null && card.due_at < start + 86400) groups.today.push(card) - else if (card.due_at !== null && card.due_at < start + 8 * 86400) groups.week.push(card) + else if (card.due_at !== null && card.due_at < end) groups.today.push(card) + else if (card.due_at !== null && card.due_at < week_end) groups.week.push(card) else if (card.role === 'doing') groups.doing.push(card) } return (['overdue', 'today', 'week', 'doing'] as const).map((key) => ({ key, cards: groups[key] })) @@ -1306,8 +1308,8 @@ class Workspace { * point is to be told, not to have to remember to ask. */ async openAgendaIfDue() { - const today = new Date().toDateString() - if (localStorage.getItem('diary:agenda-seen') === today) return + const today = this.agenda.today?.date + if (!today || localStorage.getItem('diary:agenda-seen') === today) return const { late, today: due } = this.dueCounts if (late + due === 0) return localStorage.setItem('diary:agenda-seen', today)