morning: recite the day the store actually holds

Four defects in the plan, all of them in what it reads or how it prints
it. The checklist line was keyed on Status.Active, which Evaluate reports
only inside the window, so a morning routine skipped and asked about at
14:00 said nothing. Outstanding answers the question the plan asks, "what
did today still not get done", and the line stays placed at the nudge
time so it sorts to the top of the day. Nothing before the window opens
counts, so 06:00 is not a complaint.

The event text kept the "@ 14:00-14:30" tail FactValue writes, next to a
line that prints the hour itself, so every event said its time twice.
Reminders came off ListReminders, which orders by creation, so the 500
row cap dropped a reminder stated long ago for today and kept one stated
this morning for next year. PendingReminders bounds by fire time instead.
The pending filter used a string literal, one typo from matching nothing.

After now marks the plan it trimmed. "что дальше?" past the last item
answered "на 03.08.2026 ничего не запланировано", which denies a day he
just lived through.

The surface the plan belongs on is still open, tracked as Vikunja #431;
the comment in actions_query.go points at it.
Found in review of #58.
This commit is contained in:
kami
2026-08-01 14:07:43 +04:00
parent 38b09ded95
commit b3c2fad4ec
7 changed files with 234 additions and 27 deletions
+50 -2
View File
@@ -163,7 +163,55 @@ func TestPlanAfter(t *testing.T) {
if len(empty.Items) != 0 {
t.Errorf("got %+v", empty.Items)
}
if !strings.Contains(empty.FormatRU(), "ничего не запланировано") {
t.Errorf("empty plan reads %q", empty.FormatRU())
// An empty rest-of-day is not an empty day. Saying "на 03.08.2026 ничего
// не запланировано" at 23:00 denies the day he just lived.
if got, want := empty.FormatRU(), "на сегодня больше ничего не запланировано."; got != want {
t.Errorf("empty rest-of-day reads %q, want %q", got, want)
}
}
// The plan says what today still has not got done, and a closed window does not
// make a skipped routine untrue. Evaluate reports Active only inside the
// window, so keying the checklist line off it meant the one thing the plan can
// tell him that the calendar cannot went silent at 11:00.
func TestBuildPlanKeepsAClosedWindowOutstanding(t *testing.T) {
now := time.Date(2026, 8, 3, 14, 0, 0, 0, time.UTC)
routines := []Routine{{
Name: "утро", WindowStart: "07:00", WindowEnd: "11:00", NudgeAt: "10:30",
Items: []Item{
{Key: "water", FactKey: "drank_water", Label: "выпить воды"},
{Key: "pills", FactKey: "took_pills", Label: "витамины"},
},
}}
facts := map[string]store.Fact{"drank_water": {Ts: planAt(now, 8, 0)}}
p := BuildPlan(routines, facts, nil, nil, now)
if len(p.Items) != 1 {
t.Fatalf("got %+v, want the unfinished morning routine", p.Items)
}
it := p.Items[0]
if it.Kind != PlanChecklist {
t.Errorf("kind = %q", it.Kind)
}
// Placed at the nudge time, so it sorts to the top of the day rather than
// to the moment of asking.
if got := it.At.Format("15:04"); got != "10:30" {
t.Errorf("placed at %s, want 10:30", got)
}
if !strings.Contains(it.Text, "витамины") || strings.Contains(it.Text, "выпить воды") {
t.Errorf("line = %q", it.Text)
}
}
// A routine whose window has not opened yet is not outstanding. Nothing has
// been skipped at 06:00.
func TestBuildPlanIgnoresAnUnopenedWindow(t *testing.T) {
now := time.Date(2026, 8, 3, 6, 0, 0, 0, time.UTC)
routines := []Routine{{
Name: "утро", WindowStart: "07:00", WindowEnd: "11:00",
Items: []Item{{Key: "water", FactKey: "drank_water", Label: "выпить воды"}},
}}
if p := BuildPlan(routines, nil, nil, nil, now); len(p.Items) != 0 {
t.Fatalf("got %+v", p.Items)
}
}