| author | |
| committer | |
| log | 9afef94a61466422128da9c3b723b633d4c7ed1d |
| tree | 053a20dd99e24d55ce7a733a2dd59f8a2d4de058 |
| parent | a7b0366cbe1e9eabc9e37eb079a38f6b6691f999 |
| signature | Signed by PGP key B5690EEEBB952194 |
3 files changed, 22 insertions(+), 6 deletions(-)
apps/web/src/components/Sidebar.snooze.test.ts+6-3| ... | @@ -8,15 +8,18 @@ function localDate(year: number, month: number, day: number, hour: number, minut | ... | @@ -8,15 +8,18 @@ function localDate(year: number, month: number, day: number, hour: number, minut |
| 8 | } | 8 | } |
| 9 | 9 | ||
| 10 | describe("resolveSnoozePresets", () => { | 10 | describe("resolveSnoozePresets", () => { |
| 11 | it("offers hour, evening, tomorrow, next week in the morning", () => { | 11 | it("offers one hour, three hours, evening, tomorrow, and next week in the morning", () => { |
| 12 | // Wednesday 2026-04-08 10:00 local. | 12 | // Wednesday 2026-04-08 10:00 local. |
| 13 | const presets = resolveSnoozePresets(localDate(2026, 4, 8, 10), "locale"); | 13 | const presets = resolveSnoozePresets(localDate(2026, 4, 8, 10), "locale"); |
| 14 | expect(presets.map((preset) => preset.id)).toEqual([ | 14 | expect(presets.map((preset) => preset.id)).toEqual([ |
| 15 | "hour", | 15 | "hour", |
| 16 | "three-hours", | ||
| 16 | "evening", | 17 | "evening", |
| 17 | "tomorrow", | 18 | "tomorrow", |
| 18 | "next-week", | 19 | "next-week", |
| 19 | ]); | 20 | ]); |
| 21 | const threeHours = presets.find((preset) => preset.id === "three-hours"); | ||
| 22 | expect(new Date(threeHours!.snoozedUntil).getHours()).toBe(13); | ||
| 20 | const evening = presets.find((preset) => preset.id === "evening"); | 23 | const evening = presets.find((preset) => preset.id === "evening"); |
| 21 | expect(new Date(evening!.snoozedUntil).getHours()).toBe(18); | 24 | expect(new Date(evening!.snoozedUntil).getHours()).toBe(18); |
| 22 | const tomorrow = presets.find((preset) => preset.id === "tomorrow"); | 25 | const tomorrow = presets.find((preset) => preset.id === "tomorrow"); |
| ... | @@ -45,10 +48,10 @@ describe("resolveSnoozePresets", () => { | ... | @@ -45,10 +48,10 @@ describe("resolveSnoozePresets", () => { |
| 45 | it("drops the evening preset once evening is near or past", () => { | 48 | it("drops the evening preset once evening is near or past", () => { |
| 46 | expect( | 49 | expect( |
| 47 | resolveSnoozePresets(localDate(2026, 4, 8, 17, 30), "locale").map((preset) => preset.id), | 50 | resolveSnoozePresets(localDate(2026, 4, 8, 17, 30), "locale").map((preset) => preset.id), |
| 48 | ).toEqual(["hour", "tomorrow", "next-week"]); | 51 | ).toEqual(["hour", "three-hours", "tomorrow", "next-week"]); |
| 49 | expect( | 52 | expect( |
| 50 | resolveSnoozePresets(localDate(2026, 4, 8, 21), "locale").map((preset) => preset.id), | 53 | resolveSnoozePresets(localDate(2026, 4, 8, 21), "locale").map((preset) => preset.id), |
| 51 | ).toEqual(["hour", "tomorrow", "next-week"]); | 54 | ).toEqual(["hour", "three-hours", "tomorrow", "next-week"]); |
| 52 | }); | 55 | }); |
| 53 | 56 | ||
| 54 | it("puts next week a full week out when today is Monday", () => { | 57 | it("puts next week a full week out when today is Monday", () => { |
packages/client-runtime/src/state/threadSettled.ts+10-3| ... | @@ -284,7 +284,7 @@ const HOUR_MS = 60 * 60 * 1_000; | ... | @@ -284,7 +284,7 @@ const HOUR_MS = 60 * 60 * 1_000; |
| 284 | const EVENING_HOUR = 18; | 284 | const EVENING_HOUR = 18; |
| 285 | const MORNING_HOUR = 9; | 285 | const MORNING_HOUR = 9; |
| 286 | 286 | ||
| 287 | export type SnoozePresetId = "hour" | "evening" | "tomorrow" | "next-week"; | 287 | export type SnoozePresetId = "hour" | "three-hours" | "evening" | "tomorrow" | "next-week"; |
| 288 | 288 | ||
| 289 | export interface SnoozePreset { | 289 | export interface SnoozePreset { |
| 290 | readonly id: SnoozePresetId; | 290 | readonly id: SnoozePresetId; |
| ... | @@ -317,11 +317,12 @@ function addSnoozeDays(base: Date, days: number): Date { | ... | @@ -317,11 +317,12 @@ function addSnoozeDays(base: Date, days: number): Date { |
| 317 | 317 | ||
| 318 | /** | 318 | /** |
| 319 | * Shared "snooze until" choices for every client. "This evening" only | 319 | * Shared "snooze until" choices for every client. "This evening" only |
| 320 | * appears while it is meaningfully before evening; after that the list | 320 | * appears while it is meaningfully before evening; after that the calendar |
| 321 | * starts at "Tomorrow". | 321 | * choices start at "Tomorrow". |
| 322 | */ | 322 | */ |
| 323 | export function resolveSnoozePresets(now: Date): ReadonlyArray<SnoozePreset> { | 323 | export function resolveSnoozePresets(now: Date): ReadonlyArray<SnoozePreset> { |
| 324 | const inAnHour = new Date(now.getTime() + HOUR_MS); | 324 | const inAnHour = new Date(now.getTime() + HOUR_MS); |
| 325 | const inThreeHours = new Date(now.getTime() + 3 * HOUR_MS); | ||
| 325 | const presets: SnoozePreset[] = [ | 326 | const presets: SnoozePreset[] = [ |
| 326 | { | 327 | { |
| 327 | id: "hour", | 328 | id: "hour", |
| ... | @@ -329,6 +330,12 @@ export function resolveSnoozePresets(now: Date): ReadonlyArray<SnoozePreset> { | ... | @@ -329,6 +330,12 @@ export function resolveSnoozePresets(now: Date): ReadonlyArray<SnoozePreset> { |
| 329 | whenLabel: snoozeTimeOfDayLabel(inAnHour), | 330 | whenLabel: snoozeTimeOfDayLabel(inAnHour), |
| 330 | snoozedUntil: inAnHour.toISOString(), | 331 | snoozedUntil: inAnHour.toISOString(), |
| 331 | }, | 332 | }, |
| 333 | { | ||
| 334 | id: "three-hours", | ||
| 335 | label: "In 3 hours", | ||
| 336 | whenLabel: snoozeTimeOfDayLabel(inThreeHours), | ||
| 337 | snoozedUntil: inThreeHours.toISOString(), | ||
| 338 | }, | ||
| 332 | ]; | 339 | ]; |
| 333 | 340 | ||
| 334 | const evening = snoozeAtHour(now, EVENING_HOUR); | 341 | const evening = snoozeAtHour(now, EVENING_HOUR); |
packages/client-runtime/src/state/threadSnoozed.test.ts+6| ... | @@ -265,10 +265,15 @@ describe("resolveSnoozePresets", () => { | ... | @@ -265,10 +265,15 @@ describe("resolveSnoozePresets", () => { |
| 265 | const presets = resolveSnoozePresets(localDate(2026, 4, 8, 10)); | 265 | const presets = resolveSnoozePresets(localDate(2026, 4, 8, 10)); |
| 266 | expect(presets.map((preset) => preset.id)).toEqual([ | 266 | expect(presets.map((preset) => preset.id)).toEqual([ |
| 267 | "hour", | 267 | "hour", |
| 268 | "three-hours", | ||
| 268 | "evening", | 269 | "evening", |
| 269 | "tomorrow", | 270 | "tomorrow", |
| 270 | "next-week", | 271 | "next-week", |
| 271 | ]); | 272 | ]); |
| 273 | expect(presets.find((preset) => preset.id === "three-hours")?.snoozedUntil).toBe( | ||
| 274 | localDate(2026, 4, 8, 13).toISOString(), | ||
| 275 | ); | ||
| 276 | expect(presets.find((preset) => preset.id === "three-hours")?.label).toBe("In 3 hours"); | ||
| 272 | expect(presets.find((preset) => preset.id === "evening")?.label).toBe("This evening"); | 277 | expect(presets.find((preset) => preset.id === "evening")?.label).toBe("This evening"); |
| 273 | expect( | 278 | expect( |
| 274 | new Date(presets.find((preset) => preset.id === "tomorrow")!.snoozedUntil).getHours(), | 279 | new Date(presets.find((preset) => preset.id === "tomorrow")!.snoozedUntil).getHours(), |
| ... | @@ -278,6 +283,7 @@ describe("resolveSnoozePresets", () => { | ... | @@ -278,6 +283,7 @@ describe("resolveSnoozePresets", () => { |
| 278 | it("drops the evening choice once evening is near or past", () => { | 283 | it("drops the evening choice once evening is near or past", () => { |
| 279 | expect(resolveSnoozePresets(localDate(2026, 4, 8, 17, 30)).map((preset) => preset.id)).toEqual([ | 284 | expect(resolveSnoozePresets(localDate(2026, 4, 8, 17, 30)).map((preset) => preset.id)).toEqual([ |
| 280 | "hour", | 285 | "hour", |
| 286 | "three-hours", | ||
| 281 | "tomorrow", | 287 | "tomorrow", |
| 282 | "next-week", | 288 | "next-week", |
| 283 | ]); | 289 | ]); |