* feat: show routines the bot sets up from chat as one quiet line
When someone mentions something recurring in passing ("every Sunday I plan the
week"), the bot now creates the routine right away and the conversation shows a
single line with the schedule, Edit and Undo, on web and mobile.
- New `event` message block for routine lines, kept in history so later turns
know what was set up or removed.
- schedule_create reuses an active repeating routine of the same name instead
of adding a duplicate, caps a bot at 50 active schedules, and falls back to
default times when none was given.
- Removing a routine (Undo, the editor, or schedule_cancel) marks its lines
removed via the new routines.noticeMessageId column.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UmSCnoCgXqBp2fU3vwz4o1
* test: give the routine notice test a complete job publisher
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UmSCnoCgXqBp2fU3vwz4o1
---------
Co-authored-by: Claude <noreply@anthropic.com>
57 lines
2.0 KiB
TypeScript
57 lines
2.0 KiB
TypeScript
import type { RoutineEventBlock } from "@rakazo/contracts";
|
|
import { routineScheduleShape } from "@rakazo/core";
|
|
import { dateLocaleForUi, t } from "./i18n";
|
|
|
|
/** 2024-01-07 is a Sunday, so day n of that week has weekday n. */
|
|
const weekdayName = (weekday: number, locale: string) =>
|
|
new Intl.DateTimeFormat(locale, { weekday: "long", timeZone: "UTC" }).format(
|
|
new Date(Date.UTC(2024, 0, 7 + weekday)),
|
|
);
|
|
|
|
const clock = (hour: number, minute: number, locale: string) =>
|
|
new Intl.DateTimeFormat(locale, { hour: "numeric", minute: "2-digit", timeZone: "UTC" }).format(
|
|
new Date(Date.UTC(2024, 0, 1, hour, minute)),
|
|
);
|
|
|
|
/** When a routine runs, in a few words of the active locale. */
|
|
export function routineLineSchedule(
|
|
block: Pick<RoutineEventBlock, "crons" | "nextRunAt" | "timezone">,
|
|
locale = dateLocaleForUi(),
|
|
): string {
|
|
const shape = routineScheduleShape(block.crons);
|
|
switch (shape.kind) {
|
|
case "hourly":
|
|
return t("Every hour");
|
|
case "minutes":
|
|
return t("Every {count} min", { count: shape.every });
|
|
case "hours":
|
|
return t("Every {count} h", { count: shape.every });
|
|
case "daily":
|
|
return t("Daily, {time}", { time: clock(shape.hour, shape.minute, locale) });
|
|
case "weekdays":
|
|
return t("Weekdays, {time}", { time: clock(shape.hour, shape.minute, locale) });
|
|
case "weekly":
|
|
return t("Every {day}, {time}", {
|
|
day: weekdayName(shape.weekday, locale),
|
|
time: clock(shape.hour, shape.minute, locale),
|
|
});
|
|
case "monthly":
|
|
return t("Monthly on day {day}, {time}", {
|
|
day: shape.day,
|
|
time: clock(shape.hour, shape.minute, locale),
|
|
});
|
|
default: {
|
|
if (!block.nextRunAt) return "";
|
|
const date = new Intl.DateTimeFormat(locale, {
|
|
weekday: "short",
|
|
day: "numeric",
|
|
month: "short",
|
|
hour: "numeric",
|
|
minute: "2-digit",
|
|
timeZone: block.timezone,
|
|
}).format(new Date(block.nextRunAt));
|
|
return shape.kind === "once" ? date : t("Next {date}", { date });
|
|
}
|
|
}
|
|
}
|