feat(web): create/update actions accept delivery window hours

createReminderAction and updateReminderAction now read
deliveryWindowStartHour / deliveryWindowEndHour off the input and
persist them on the reminders row. Both fields are optional in the
input shape (default 6/18) so existing callers don't break, and a
refine validates start < end when provided.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
yiekheng 2026-05-10 14:52:05 +08:00
parent 7039d57a41
commit f50a1fc0a7

View File

@ -248,6 +248,12 @@ const createReminderSchema = z
scheduledAtIso: z.string().datetime({ offset: true }),
rrule: z.string().nullable().optional(),
timezone: z.string().default(DEFAULT_TIMEZONE),
// Delivery window in the operator's timezone. End hour will gate
// the runtime fan-out in a later phase; start is documented but
// not yet enforced. Optional in the input shape for backward
// compatibility — the action body falls back to 6/18.
deliveryWindowStartHour: z.number().int().min(0).max(24).optional(),
deliveryWindowEndHour: z.number().int().min(0).max(24).optional(),
})
.refine(
(d) =>
@ -258,7 +264,11 @@ const createReminderSchema = z
message: "Add a message or attach a file",
path: ["messages"],
},
);
)
.refine((d) => (d.deliveryWindowStartHour ?? 6) < (d.deliveryWindowEndHour ?? 18), {
message: "Delivery window start must be earlier than end",
path: ["deliveryWindowStartHour"],
});
/** Resolve the schema's union of new + legacy fields into a flat list. */
function resolveMessageParts(parsed: z.infer<typeof createReminderSchema>): Array<{
@ -304,7 +314,15 @@ export async function createReminderAction(
if (!parsed.success) {
return { ok: false, error: parsed.error.issues[0]?.message ?? "Invalid input" };
}
const { accountId, groupIds, scheduledAtIso, rrule, timezone } = parsed.data;
const {
accountId,
groupIds,
scheduledAtIso,
rrule,
timezone,
} = parsed.data;
const deliveryWindowStartHour = parsed.data.deliveryWindowStartHour ?? 6;
const deliveryWindowEndHour = parsed.data.deliveryWindowEndHour ?? 18;
const parts = resolveMessageParts(parsed.data);
const op = await getSeededOperator();
@ -358,6 +376,8 @@ export async function createReminderAction(
scheduledAt,
rrule: rrule ?? null,
timezone,
deliveryWindowStartHour,
deliveryWindowEndHour,
status: "active",
createdBy: op.id,
})
@ -407,7 +427,16 @@ export async function updateReminderAction(
if (!parsed.success) {
return { ok: false, error: parsed.error.issues[0]?.message ?? "Invalid input" };
}
const { reminderId, accountId, groupIds, scheduledAtIso, rrule, timezone } = parsed.data;
const {
reminderId,
accountId,
groupIds,
scheduledAtIso,
rrule,
timezone,
} = parsed.data;
const deliveryWindowStartHour = parsed.data.deliveryWindowStartHour ?? 6;
const deliveryWindowEndHour = parsed.data.deliveryWindowEndHour ?? 18;
const parts = resolveMessageParts(parsed.data);
const op = await getSeededOperator();
@ -467,6 +496,8 @@ export async function updateReminderAction(
scheduledAt,
rrule: rrule ?? null,
timezone,
deliveryWindowStartHour,
deliveryWindowEndHour,
// Preserve the lifecycle status. Editing fields shouldn't
// implicitly re-activate a paused or ended reminder — the
// user can use the explicit Restart action for that.