fix(schedule): snooze keeps status pending so items re-fire
- snoozeItem: write status:"pending" + snoozed_at (audit trail) instead of status:"snoozed", which was invisible to findDue/findUpcoming - findDue/findUpcoming: include status==="snoozed" for backward compat with any pre-existing snoozed entries in the store - listItems default filter: show snoozed entries (they are active) - _findEntry: remove dead exact-match branch (exact ⊆ startsWith) - ScheduleEntry typedef: add optional snoozed_at field - Tests: add coverage for snoozed-entry visibility in findDue, findUpcoming, and the list command Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d4b3e0f2b0
commit
8571ef702d
5 changed files with 55 additions and 12 deletions
|
|
@ -81,12 +81,7 @@ function _formatTable(rows) {
|
|||
|
||||
function _findEntry(store, scope, idPrefix) {
|
||||
const entries = store.readEntries(scope);
|
||||
const match = entries.find((e) => e.id.startsWith(idPrefix));
|
||||
if (!match) {
|
||||
const exact = entries.find((e) => e.id === idPrefix);
|
||||
if (exact) return { entry: exact, entries };
|
||||
}
|
||||
return { entry: match, entries };
|
||||
return { entry: entries.find((e) => e.id.startsWith(idPrefix)) ?? null, entries };
|
||||
}
|
||||
|
||||
// ─── Subcommands ────────────────────────────────────────────────────────────
|
||||
|
|
@ -206,7 +201,7 @@ async function listItems(args, ctx) {
|
|||
} else if (showAll) {
|
||||
entries = store.readEntries(scope);
|
||||
} else {
|
||||
entries = store.readEntries(scope).filter((e) => e.status === "pending");
|
||||
entries = store.readEntries(scope).filter((e) => e.status === "pending" || e.status === "snoozed");
|
||||
}
|
||||
|
||||
entries.sort((a, b) => new Date(a.due_at) - new Date(b.due_at));
|
||||
|
|
@ -310,12 +305,14 @@ async function snoozeItem(args, ctx) {
|
|||
return;
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const newDue = new Date(new Date(entry.due_at).getTime() + offsetMs).toISOString();
|
||||
const updated = {
|
||||
...entry,
|
||||
status: "snoozed",
|
||||
status: "pending",
|
||||
due_at: newDue,
|
||||
created_at: new Date().toISOString(),
|
||||
created_at: now,
|
||||
snoozed_at: now,
|
||||
};
|
||||
store.appendEntry("project", updated);
|
||||
ctx.ui.notify(`Snoozed: ${entry.id}\nNew due: ${newDue}`, "success");
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ function _findDue(basePath, scope, now) {
|
|||
const entries = _readEntries(basePath, scope);
|
||||
const nowMs = _toTimestamp(now);
|
||||
return entries
|
||||
.filter((e) => e.status === "pending" && _toTimestamp(e.due_at) <= nowMs)
|
||||
.filter((e) => (e.status === "pending" || e.status === "snoozed") && _toTimestamp(e.due_at) <= nowMs)
|
||||
.sort((a, b) => _toTimestamp(a.due_at) - _toTimestamp(b.due_at));
|
||||
}
|
||||
|
||||
|
|
@ -187,7 +187,7 @@ function _findUpcoming(basePath, scope, now, windowDays) {
|
|||
return entries
|
||||
.filter(
|
||||
(e) =>
|
||||
e.status === "pending" &&
|
||||
(e.status === "pending" || e.status === "snoozed") &&
|
||||
_toTimestamp(e.due_at) > nowMs &&
|
||||
_toTimestamp(e.due_at) <= cutoff,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@
|
|||
* @property {SchedulePayload} payload Kind-specific data
|
||||
* @property {ScheduleCreatedBy} created_by Who created the entry
|
||||
* @property {boolean} [auto_dispatch] If true and kind='reminder', surface as dispatch input in auto-mode when due. Defaults false.
|
||||
* @property {string} [snoozed_at] ISO-8601 timestamp; set when the entry was last snoozed
|
||||
*/
|
||||
|
||||
// ─── Guards ─────────────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -154,6 +154,22 @@ describe("handleSchedule", () => {
|
|||
assert.equal(ctx2.notifications[0].type, "success");
|
||||
assert.ok(ctx2.notifications[0].msg.includes("Snoozed"));
|
||||
});
|
||||
|
||||
it("snoozed item still appears in list", async () => {
|
||||
const ctx1 = mockCtx();
|
||||
await handleSchedule("add --in 1d item that will be snoozed", ctx1);
|
||||
const id = ctx1.notifications[0].msg.match(/Scheduled: (\S+)/)?.[1];
|
||||
assert.ok(id);
|
||||
|
||||
await handleSchedule(`snooze ${id.slice(0, 8)} --by 2d`, mockCtx());
|
||||
|
||||
const ctx3 = mockCtx();
|
||||
await handleSchedule("list", ctx3);
|
||||
assert.ok(
|
||||
ctx3.notifications[0].msg.includes("item that will be snoozed"),
|
||||
"snoozed item must still appear in default list",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("run", () => {
|
||||
|
|
|
|||
|
|
@ -187,6 +187,18 @@ describe("schedule-store", () => {
|
|||
assert.equal(due[0].id, past.id);
|
||||
});
|
||||
|
||||
it("returns snoozed entries whose new due_at has passed", () => {
|
||||
const snoozed = makeEntry({
|
||||
due_at: "2024-01-01T00:00:00.000Z",
|
||||
status: "snoozed",
|
||||
snoozed_at: "2023-12-01T00:00:00.000Z",
|
||||
});
|
||||
store.appendEntry("project", snoozed);
|
||||
const due = store.findDue("project", "2024-06-01T00:00:00.000Z");
|
||||
assert.equal(due.length, 1);
|
||||
assert.equal(due[0].id, snoozed.id);
|
||||
});
|
||||
|
||||
it("sorts results by due_at ascending", () => {
|
||||
const e1 = makeEntry({ due_at: "2024-01-02T00:00:00.000Z" });
|
||||
const e2 = makeEntry({ due_at: "2024-01-01T00:00:00.000Z" });
|
||||
|
|
@ -226,7 +238,7 @@ describe("schedule-store", () => {
|
|||
assert.equal(upcoming[0].id, soon.id);
|
||||
});
|
||||
|
||||
it("excludes non-pending entries", () => {
|
||||
it("excludes done/cancelled entries", () => {
|
||||
const soonDone = makeEntry({
|
||||
due_at: "2024-01-02T00:00:00.000Z",
|
||||
status: "done",
|
||||
|
|
@ -241,6 +253,23 @@ describe("schedule-store", () => {
|
|||
assert.equal(upcoming.length, 0);
|
||||
});
|
||||
|
||||
it("includes snoozed entries within the window", () => {
|
||||
const snoozed = makeEntry({
|
||||
due_at: "2024-01-02T00:00:00.000Z",
|
||||
status: "snoozed",
|
||||
snoozed_at: "2023-12-01T00:00:00.000Z",
|
||||
});
|
||||
store.appendEntry("project", snoozed);
|
||||
|
||||
const upcoming = store.findUpcoming(
|
||||
"project",
|
||||
"2024-01-01T00:00:00.000Z",
|
||||
7,
|
||||
);
|
||||
assert.equal(upcoming.length, 1);
|
||||
assert.equal(upcoming[0].id, snoozed.id);
|
||||
});
|
||||
|
||||
it("sorts results by due_at ascending", () => {
|
||||
const e1 = makeEntry({ due_at: "2024-01-03T00:00:00.000Z" });
|
||||
const e2 = makeEntry({ due_at: "2024-01-02T00:00:00.000Z" });
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue