refactor(web,bot,db): rename reminder status 'ended' → 'inactive'
The 'ended' label read like a terminal failure state ("the reminder
gave up") when in practice it just means "this reminder isn't going
to fire on its own — restart it if you want it back". 'inactive' is
the more accurate read.
* SQL migration 0009 backfills existing rows.
* Bot fire-reminder writes 'inactive' on one-off completion / no
further occurrences.
* Web actions, queries, filters, and reminder lifecycle gates updated.
* Dashboard counter card label "Active / Paused / Ended / Total"
becomes "Active / Paused / Inactive / Total".
* Reminders list filter tab "Ended" becomes "Inactive".
* Status pill style key renamed to match.
* Tests updated alongside the runtime changes.
Also: the "Pause sending by" deadline opt-in now renders as a
visible card-shaped row with hover state + Set/Off label on the
right, so the toggle is discoverable instead of a tiny native
checkbox tucked next to the label.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2e1defaef6
commit
be3f28a1e6
@ -82,7 +82,7 @@ describe("fireReminder", () => {
|
|||||||
getReminderMock.mockResolvedValue({
|
getReminderMock.mockResolvedValue({
|
||||||
id: "r-1",
|
id: "r-1",
|
||||||
accountId: "acct-A",
|
accountId: "acct-A",
|
||||||
status: "ended",
|
status: "inactive",
|
||||||
targets: [],
|
targets: [],
|
||||||
messages: [],
|
messages: [],
|
||||||
createdBy: "op-1",
|
createdBy: "op-1",
|
||||||
|
|||||||
@ -394,7 +394,7 @@ async function fireReminderInner(
|
|||||||
if (reminder.scheduleKind === "one_off") {
|
if (reminder.scheduleKind === "one_off") {
|
||||||
await db
|
await db
|
||||||
.update(reminders)
|
.update(reminders)
|
||||||
.set({ status: "ended", updatedAt: new Date() })
|
.set({ status: "inactive", updatedAt: new Date() })
|
||||||
.where(eq(reminders.id, reminder.id));
|
.where(eq(reminders.id, reminder.id));
|
||||||
} else if (reminder.scheduleKind === "recurring" && reminder.rrule) {
|
} else if (reminder.scheduleKind === "recurring" && reminder.rrule) {
|
||||||
const next = nextOccurrence(reminder.rrule, reminder.timezone, new Date());
|
const next = nextOccurrence(reminder.rrule, reminder.timezone, new Date());
|
||||||
@ -417,7 +417,7 @@ async function fireReminderInner(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.info({ reminderId: reminder.id }, "fire-reminder: no further occurrences, ending");
|
logger.info({ reminderId: reminder.id }, "fire-reminder: no further occurrences, ending");
|
||||||
await db.update(reminders).set({ status: "ended" }).where(eq(reminders.id, reminder.id));
|
await db.update(reminders).set({ status: "inactive" }).where(eq(reminders.id, reminder.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -206,6 +206,6 @@ describe("cancelReminderRunAction", () => {
|
|||||||
await cancelReminderRunAction({ runId: PAUSED_RUN.id });
|
await cancelReminderRunAction({ runId: PAUSED_RUN.id });
|
||||||
const calls = setSpy.mock.calls;
|
const calls = setSpy.mock.calls;
|
||||||
const lastPayload = calls[calls.length - 1]?.[0] as Record<string, unknown>;
|
const lastPayload = calls[calls.length - 1]?.[0] as Record<string, unknown>;
|
||||||
expect(lastPayload.status).toBe("ended");
|
expect(lastPayload.status).toBe("inactive");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -660,7 +660,7 @@ export async function cancelReminderRunAction(input: {
|
|||||||
await tx
|
await tx
|
||||||
.update(reminders)
|
.update(reminders)
|
||||||
.set({
|
.set({
|
||||||
status: reminder.scheduleKind === "recurring" ? "active" : "ended",
|
status: reminder.scheduleKind === "recurring" ? "active" : "inactive",
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
})
|
})
|
||||||
.where(eq(reminders.id, reminder.id));
|
.where(eq(reminders.id, reminder.id));
|
||||||
|
|||||||
@ -182,9 +182,9 @@ export default async function DashboardPage() {
|
|||||||
/>
|
/>
|
||||||
<StatCard
|
<StatCard
|
||||||
title="Reminders"
|
title="Reminders"
|
||||||
value={`${stats.activeReminders} / ${stats.pausedReminders} / ${stats.endedReminders} / ${stats.totalReminders}`}
|
value={`${stats.activeReminders} / ${stats.pausedReminders} / ${stats.inactiveReminders} / ${stats.totalReminders}`}
|
||||||
icon={BellIcon}
|
icon={BellIcon}
|
||||||
description="Active / Paused / Ended / Total"
|
description="Active / Paused / Inactive / Total"
|
||||||
href="/reminders"
|
href="/reminders"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -41,9 +41,9 @@ describe("ActionsBar — card visibility by status", () => {
|
|||||||
expect(html).not.toMatch(/aria-label="Pause"/);
|
expect(html).not.toMatch(/aria-label="Pause"/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("ended: shows Restart and Delete (no Pause)", () => {
|
it("inactive: shows Restart and Delete (no Pause)", () => {
|
||||||
const html = renderToStaticMarkup(
|
const html = renderToStaticMarkup(
|
||||||
<ActionsBar reminderId="r-1" status="ended" isRecurring={false} />,
|
<ActionsBar reminderId="r-1" status="inactive" isRecurring={false} />,
|
||||||
);
|
);
|
||||||
expect(html).toMatch(/aria-label="Restart"/);
|
expect(html).toMatch(/aria-label="Restart"/);
|
||||||
expect(html).toMatch(/aria-label="Delete"/);
|
expect(html).toMatch(/aria-label="Delete"/);
|
||||||
|
|||||||
@ -38,7 +38,7 @@ interface ActionsBarProps {
|
|||||||
* on desktop, stacked on mobile:
|
* on desktop, stacked on mobile:
|
||||||
*
|
*
|
||||||
* - Pause — only when status === "active"
|
* - Pause — only when status === "active"
|
||||||
* - Restart — when status is "paused" or "ended"
|
* - Restart — when status is "paused" or "inactive"
|
||||||
* - Delete — always available (terminal)
|
* - Delete — always available (terminal)
|
||||||
*
|
*
|
||||||
* Each Dialog confirms before firing the corresponding server action.
|
* Each Dialog confirms before firing the corresponding server action.
|
||||||
@ -46,7 +46,7 @@ interface ActionsBarProps {
|
|||||||
*/
|
*/
|
||||||
export function ActionsBar({ reminderId, status, isRecurring }: ActionsBarProps) {
|
export function ActionsBar({ reminderId, status, isRecurring }: ActionsBarProps) {
|
||||||
const canPause = status === "active";
|
const canPause = status === "active";
|
||||||
const canRestart = status === "paused" || status === "ended";
|
const canRestart = status === "paused" || status === "inactive";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-2">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-2">
|
||||||
|
|||||||
@ -48,7 +48,7 @@ function formatWhen(date: Date | null, tz: string): string {
|
|||||||
const STATUS_STYLES: Record<string, string> = {
|
const STATUS_STYLES: Record<string, string> = {
|
||||||
active:
|
active:
|
||||||
"bg-emerald-500/15 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-400 border-transparent",
|
"bg-emerald-500/15 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-400 border-transparent",
|
||||||
ended:
|
inactive:
|
||||||
"bg-slate-200/60 text-slate-500 dark:bg-slate-700/40 dark:text-slate-400 border-transparent",
|
"bg-slate-200/60 text-slate-500 dark:bg-slate-700/40 dark:text-slate-400 border-transparent",
|
||||||
paused:
|
paused:
|
||||||
"bg-amber-500/15 text-amber-700 dark:bg-amber-500/20 dark:text-amber-400 border-transparent",
|
"bg-amber-500/15 text-amber-700 dark:bg-amber-500/20 dark:text-amber-400 border-transparent",
|
||||||
|
|||||||
@ -32,7 +32,7 @@ import {
|
|||||||
restartReminderAction,
|
restartReminderAction,
|
||||||
} from "@/actions/reminders";
|
} from "@/actions/reminders";
|
||||||
|
|
||||||
type FilterValue = "all" | "active" | "ended" | "paused";
|
type FilterValue = "all" | "active" | "inactive" | "paused";
|
||||||
|
|
||||||
function formatWhen(date: Date | null, tz: string): string {
|
function formatWhen(date: Date | null, tz: string): string {
|
||||||
if (!date) return "—";
|
if (!date) return "—";
|
||||||
@ -48,7 +48,7 @@ function formatWhen(date: Date | null, tz: string): string {
|
|||||||
const STATUS_STYLES: Record<string, string> = {
|
const STATUS_STYLES: Record<string, string> = {
|
||||||
active:
|
active:
|
||||||
"bg-emerald-500/15 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-400 border-transparent",
|
"bg-emerald-500/15 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-400 border-transparent",
|
||||||
ended:
|
inactive:
|
||||||
"bg-slate-200/60 text-slate-500 dark:bg-slate-700/40 dark:text-slate-400 border-transparent",
|
"bg-slate-200/60 text-slate-500 dark:bg-slate-700/40 dark:text-slate-400 border-transparent",
|
||||||
paused:
|
paused:
|
||||||
"bg-amber-500/15 text-amber-700 dark:bg-amber-500/20 dark:text-amber-400 border-transparent",
|
"bg-amber-500/15 text-amber-700 dark:bg-amber-500/20 dark:text-amber-400 border-transparent",
|
||||||
@ -104,7 +104,7 @@ function StatusPill({ status }: { status: string }) {
|
|||||||
const FILTER_TABS: { value: FilterValue; label: string }[] = [
|
const FILTER_TABS: { value: FilterValue; label: string }[] = [
|
||||||
{ value: "all", label: "All" },
|
{ value: "all", label: "All" },
|
||||||
{ value: "active", label: "Active" },
|
{ value: "active", label: "Active" },
|
||||||
{ value: "ended", label: "Ended" },
|
{ value: "inactive", label: "Inactive" },
|
||||||
{ value: "paused", label: "Paused" },
|
{ value: "paused", label: "Paused" },
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -127,7 +127,7 @@ interface PageProps {
|
|||||||
export default async function RemindersPage({ searchParams }: PageProps) {
|
export default async function RemindersPage({ searchParams }: PageProps) {
|
||||||
const sp = await searchParams;
|
const sp = await searchParams;
|
||||||
const status: FilterValue =
|
const status: FilterValue =
|
||||||
sp.filter === "active" || sp.filter === "ended" || sp.filter === "paused"
|
sp.filter === "active" || sp.filter === "inactive" || sp.filter === "paused"
|
||||||
? sp.filter
|
? sp.filter
|
||||||
: "all";
|
: "all";
|
||||||
// Sort is now fixed to `created_desc`. Reordering on every status flip
|
// Sort is now fixed to `created_desc`. Reordering on every status flip
|
||||||
@ -225,7 +225,7 @@ export default async function RemindersPage({ searchParams }: PageProps) {
|
|||||||
{visible.map((reminder) => {
|
{visible.map((reminder) => {
|
||||||
const canPause = reminder.status === "active";
|
const canPause = reminder.status === "active";
|
||||||
const canRestart =
|
const canRestart =
|
||||||
reminder.status === "paused" || reminder.status === "ended";
|
reminder.status === "paused" || reminder.status === "inactive";
|
||||||
const cardBody = (
|
const cardBody = (
|
||||||
<Link
|
<Link
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
|||||||
@ -170,8 +170,8 @@ export function EditWhenForm({
|
|||||||
|
|
||||||
<RecurrencePicker firstFire={previewDt} value={spec} onChange={setSpec} />
|
<RecurrencePicker firstFire={previewDt} value={spec} onChange={setSpec} />
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-2">
|
||||||
<label className="flex items-center gap-2 cursor-pointer select-none">
|
<label className="flex items-center gap-3 rounded-lg border border-input bg-card px-3 py-2.5 cursor-pointer select-none transition-colors hover:bg-accent/40">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={useDeadline}
|
checked={useDeadline}
|
||||||
@ -179,17 +179,20 @@ export function EditWhenForm({
|
|||||||
setUseDeadline(e.target.checked);
|
setUseDeadline(e.target.checked);
|
||||||
setError(null);
|
setError(null);
|
||||||
}}
|
}}
|
||||||
className="size-4 rounded border-input accent-primary"
|
className="size-5 rounded border-input accent-primary"
|
||||||
aria-label="Set a delivery deadline"
|
aria-label="Set a delivery deadline"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm font-medium flex items-center gap-1.5">
|
<span className="flex-1 flex items-center gap-1.5 text-sm font-medium">
|
||||||
<ClockIcon className="size-3.5" />
|
<ClockIcon className="size-3.5" />
|
||||||
Pause sending by
|
Pause sending by
|
||||||
<span className="text-xs font-normal text-muted-foreground">(optional)</span>
|
<span className="text-xs font-normal text-muted-foreground">(optional)</span>
|
||||||
</span>
|
</span>
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{useDeadline ? "Set" : "Off"}
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
{useDeadline && (
|
{useDeadline && (
|
||||||
<div className="flex flex-wrap items-center gap-2 pl-6">
|
<div className="flex flex-wrap items-center gap-2 pl-3">
|
||||||
<HourSelect
|
<HourSelect
|
||||||
ariaPrefix="Delivery deadline"
|
ariaPrefix="Delivery deadline"
|
||||||
value={deliveryEndHour}
|
value={deliveryEndHour}
|
||||||
|
|||||||
@ -181,8 +181,8 @@ export function WhenFormClient({
|
|||||||
deadline are paused so the operator can resume them later.
|
deadline are paused so the operator can resume them later.
|
||||||
The whole control is opt-in: tick the box to surface the hour
|
The whole control is opt-in: tick the box to surface the hour
|
||||||
picker, untick to remove the deadline entirely. */}
|
picker, untick to remove the deadline entirely. */}
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-2">
|
||||||
<label className="flex items-center gap-2 cursor-pointer select-none">
|
<label className="flex items-center gap-3 rounded-lg border border-input bg-card px-3 py-2.5 cursor-pointer select-none transition-colors hover:bg-accent/40">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={useDeadline}
|
checked={useDeadline}
|
||||||
@ -190,17 +190,20 @@ export function WhenFormClient({
|
|||||||
setUseDeadline(e.target.checked);
|
setUseDeadline(e.target.checked);
|
||||||
setError(null);
|
setError(null);
|
||||||
}}
|
}}
|
||||||
className="size-4 rounded border-input accent-primary"
|
className="size-5 rounded border-input accent-primary"
|
||||||
aria-label="Set a delivery deadline"
|
aria-label="Set a delivery deadline"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm font-medium flex items-center gap-1.5">
|
<span className="flex-1 flex items-center gap-1.5 text-sm font-medium">
|
||||||
<ClockIcon className="size-3.5" />
|
<ClockIcon className="size-3.5" />
|
||||||
Pause sending by
|
Pause sending by
|
||||||
<span className="text-xs font-normal text-muted-foreground">(optional)</span>
|
<span className="text-xs font-normal text-muted-foreground">(optional)</span>
|
||||||
</span>
|
</span>
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{useDeadline ? "Set" : "Off"}
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
{useDeadline && (
|
{useDeadline && (
|
||||||
<div className="flex flex-wrap items-center gap-2 pl-6">
|
<div className="flex flex-wrap items-center gap-2 pl-3">
|
||||||
<HourSelect
|
<HourSelect
|
||||||
ariaPrefix="Delivery deadline"
|
ariaPrefix="Delivery deadline"
|
||||||
value={deliveryEndHour}
|
value={deliveryEndHour}
|
||||||
|
|||||||
@ -34,7 +34,7 @@ export async function getDashboardStats(operatorId: string) {
|
|||||||
totalAccounts: accounts.length,
|
totalAccounts: accounts.length,
|
||||||
activeReminders: allReminders.filter((r) => r.status === "active").length,
|
activeReminders: allReminders.filter((r) => r.status === "active").length,
|
||||||
pausedReminders: allReminders.filter((r) => r.status === "paused").length,
|
pausedReminders: allReminders.filter((r) => r.status === "paused").length,
|
||||||
endedReminders: allReminders.filter((r) => r.status === "ended").length,
|
inactiveReminders: allReminders.filter((r) => r.status === "inactive").length,
|
||||||
totalReminders: allReminders.length,
|
totalReminders: allReminders.length,
|
||||||
recentRuns: recentRuns.rows as Array<{
|
recentRuns: recentRuns.rows as Array<{
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@ -73,7 +73,7 @@ describe("applyReminderFilter — account / group filters", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("status='all' or unset includes every status", () => {
|
it("status='all' or unset includes every status", () => {
|
||||||
const rows = [mk({ id: "a", status: "active" }), mk({ id: "b", status: "ended" })];
|
const rows = [mk({ id: "a", status: "active" }), mk({ id: "b", status: "inactive" })];
|
||||||
expect(applyReminderFilter(rows, { status: "all" }).map((r) => r.id)).toEqual(["a", "b"]);
|
expect(applyReminderFilter(rows, { status: "all" }).map((r) => r.id)).toEqual(["a", "b"]);
|
||||||
expect(applyReminderFilter(rows, {}).map((r) => r.id)).toEqual(["a", "b"]);
|
expect(applyReminderFilter(rows, {}).map((r) => r.id)).toEqual(["a", "b"]);
|
||||||
});
|
});
|
||||||
@ -81,7 +81,7 @@ describe("applyReminderFilter — account / group filters", () => {
|
|||||||
it("status filters to the matching value", () => {
|
it("status filters to the matching value", () => {
|
||||||
const rows = [
|
const rows = [
|
||||||
mk({ id: "a", status: "active" }),
|
mk({ id: "a", status: "active" }),
|
||||||
mk({ id: "b", status: "ended" }),
|
mk({ id: "b", status: "inactive" }),
|
||||||
mk({ id: "c", status: "paused" }),
|
mk({ id: "c", status: "paused" }),
|
||||||
];
|
];
|
||||||
expect(applyReminderFilter(rows, { status: "paused" }).map((r) => r.id)).toEqual(["c"]);
|
expect(applyReminderFilter(rows, { status: "paused" }).map((r) => r.id)).toEqual(["c"]);
|
||||||
@ -152,7 +152,7 @@ describe("applyReminderFilter — combined", () => {
|
|||||||
mk({ id: "match", name: "Daily ping", accountId: "acc-1", groupIds: ["g-1"], status: "active", ...base }),
|
mk({ id: "match", name: "Daily ping", accountId: "acc-1", groupIds: ["g-1"], status: "active", ...base }),
|
||||||
mk({ id: "wrong-acc", name: "Daily ping", accountId: "acc-2", groupIds: ["g-1"], status: "active", ...base }),
|
mk({ id: "wrong-acc", name: "Daily ping", accountId: "acc-2", groupIds: ["g-1"], status: "active", ...base }),
|
||||||
mk({ id: "wrong-group", name: "Daily ping", accountId: "acc-1", groupIds: ["g-9"], status: "active", ...base }),
|
mk({ id: "wrong-group", name: "Daily ping", accountId: "acc-1", groupIds: ["g-9"], status: "active", ...base }),
|
||||||
mk({ id: "wrong-status", name: "Daily ping", accountId: "acc-1", groupIds: ["g-1"], status: "ended", ...base }),
|
mk({ id: "wrong-status", name: "Daily ping", accountId: "acc-1", groupIds: ["g-1"], status: "inactive", ...base }),
|
||||||
mk({ id: "wrong-q", name: "Lunch", accountId: "acc-1", groupIds: ["g-1"], status: "active", ...base }),
|
mk({ id: "wrong-q", name: "Lunch", accountId: "acc-1", groupIds: ["g-1"], status: "active", ...base }),
|
||||||
];
|
];
|
||||||
expect(
|
expect(
|
||||||
|
|||||||
@ -26,7 +26,7 @@ export interface ReminderFilter {
|
|||||||
q?: string;
|
q?: string;
|
||||||
accountId?: string;
|
accountId?: string;
|
||||||
groupId?: string;
|
groupId?: string;
|
||||||
status?: string; // "all" | "active" | "ended" | "paused"
|
status?: string; // "all" | "active" | "inactive" | "paused"
|
||||||
sort?: SortKey;
|
sort?: SortKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -59,11 +59,11 @@ describe("validateUpdateScheduledAt", () => {
|
|||||||
if (r.ok) expect(r.scheduledAt.getTime()).toBe(PAST.getTime());
|
if (r.ok) expect(r.scheduledAt.getTime()).toBe(PAST.getTime());
|
||||||
});
|
});
|
||||||
|
|
||||||
it("ended one-off, past timestamp matching existing → ALLOWED", () => {
|
it("inactive one-off, past timestamp matching existing → ALLOWED", () => {
|
||||||
const r = validateUpdateScheduledAt({
|
const r = validateUpdateScheduledAt({
|
||||||
iso: isoOf(PAST),
|
iso: isoOf(PAST),
|
||||||
timezone: TZ,
|
timezone: TZ,
|
||||||
existingStatus: "ended",
|
existingStatus: "inactive",
|
||||||
existingScheduledAt: PAST,
|
existingScheduledAt: PAST,
|
||||||
now: NOW,
|
now: NOW,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -34,7 +34,7 @@ export function validateUpdateScheduledAt(args: {
|
|||||||
if (Number.isNaN(dt.getTime())) {
|
if (Number.isNaN(dt.getTime())) {
|
||||||
return { ok: false, error: "Invalid date" };
|
return { ok: false, error: "Invalid date" };
|
||||||
}
|
}
|
||||||
const isPaused = args.existingStatus === "paused" || args.existingStatus === "ended";
|
const isPaused = args.existingStatus === "paused" || args.existingStatus === "inactive";
|
||||||
const sameAsExisting =
|
const sameAsExisting =
|
||||||
args.existingScheduledAt !== null &&
|
args.existingScheduledAt !== null &&
|
||||||
Math.abs(args.existingScheduledAt.getTime() - dt.getTime()) < 1000;
|
Math.abs(args.existingScheduledAt.getTime() - dt.getTime()) < 1000;
|
||||||
|
|||||||
4
packages/db/migrations/0009_rename_ended_to_inactive.sql
Normal file
4
packages/db/migrations/0009_rename_ended_to_inactive.sql
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
-- Rename the reminders.status enum value 'ended' → 'inactive'.
|
||||||
|
-- The column is plain text (no DB-level enum), so this is purely a
|
||||||
|
-- data migration. Code path renames in the same commit.
|
||||||
|
UPDATE reminders SET status = 'inactive' WHERE status = 'ended';
|
||||||
@ -64,6 +64,13 @@
|
|||||||
"when": 1778395584234,
|
"when": 1778395584234,
|
||||||
"tag": "0008_greedy_matthew_murdock",
|
"tag": "0008_greedy_matthew_murdock",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 9,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1778464000000,
|
||||||
|
"tag": "0009_rename_ended_to_inactive",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user