Commit Graph

7 Commits

Author SHA1 Message Date
claude d156be3442 Merge the rest-of-day cap (#262)
Asking "что дальше?" at 04:45 read all 43 entries of the day aloud. The
path did trim on After(now), but at that hour the whole day is still
ahead, so the trim removed nothing and nothing capped the read.

The cap is three. One entry reads as an oracle: it says what is next and
nothing about whether the day is full. Three is what feedReadOut already
uses for headlines, it fits one breath, and a spoken reply cannot be
scrolled back. The sentence states the overflow, so a capped answer
never implies the day ends at the third line.

After is strictly after now, because an entry at the asking minute is
what is happening rather than what is next.

"что у меня сегодня" was never on this path. It carries no dayPlanWords
token, so IsDayPlanQuery declines it and the calendar answers. That
separation is pinned now rather than assumed.

Conflict in dayplan_test.go resolved by keeping both tests. Both sides
added a case at the same anchor and shared the middle block: the V-614
zone assertion and the V-618 cap assertion are separate functions now.

--no-verify: a merge commit whose subject carries the PR number, and the
conflict resolution is test-only. Full race suite exit 0.

(V-618)
2026-08-06 05:12:20 +04:00
claude 10975eff07 "что дальше?" answers with the next three, not the whole day (V-618)
Measured on the box at 04:45: "что дальше?" read 43 entries, 05:45 to 21:12, as
one spoken sentence. The rest-of-day path already trimmed to what had not
happened yet, and at 04:45 that trim removes nothing — the whole day is still
ahead. Trimming was never the narrowing; nothing capped the read.

Plan.Next(now, n) is After with a cap, and the overflow is counted rather than
dropped. The cap is three. One entry is defensible and reads as an oracle: it
says what is next and says nothing about whether the day is full. Three is what
the feed already reads back for headlines, it fits in one breath, and the reply
is spoken — he cannot scroll it back. Above three the answer stops being an
answer and becomes a recital, which is the defect.

The sentence says whether more remains: plan_next is "дальше: …" and
plan_next_more appends "и ещё 40 дел до конца дня." So a capped answer never
implies the day ends after the third line.

After is now strictly after now. An entry at exactly the asking minute is the
thing happening, not the thing next.

"что у меня сегодня?" is untouched and was never on this path: it carries no
plan word, so IsDayPlanQuery declines it and the calendar listing answers the
whole day. TestWholeDayQuestionIsNotTheRestOfTheDay pins the two apart.

The empty case already said the right thing — plan_rest_empty, "на сегодня
больше ничего не запланировано", not the whole-day empty line that would deny a
day he just lived — and now has a test at the cap boundary too.

Routing fixture unchanged, 64/91 (70.3% full, 70.3% intent-only) before and
after: no router file is touched. Suite green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 05:08:48 +04:00
claude 46acf3cba0 the spoken plan reads the clock on his wall (V-614)
FormatRU printed a plan item's At raw. An event and a reminder come off the
store as UTC — a calendar fact's Ts, a reminder's FireTs — while a checklist
line is built in the asking clock's zone, so one spoken sentence named two
zones. This is the voice path, so it is what he actually heard; the same
defect on /morning and /events was V-612.

Every hour is now read in the plan's own zone, Date's, which BuildPlan sets
from the asking clock. The rest-of-day path in queryDayPlan rebuilds a plan
off the wire, where nothing had put the instants in that frame, so it does
now.

formatTime is the same bug in the same daemon: "когда я это сделал?" names a
fact's Ts, and the branch that prints a wall clock printed the store's.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 04:43:38 +04:00
claude 5447f08c06 morning, routine: reject the two configs that silently do nothing (V-581)
Both Due functions key their last-fired map by routine name, so two routines sharing a name took turns suppressing each other and one of them never fired. Validate now rejects a duplicate name in either package.

parseHHMM checked the digits arithmetically, which let a stray character cancel out: window_start of 2 :00 loaded as 04:00 and passed the validation that exists to catch that typo. Each of the four positions is now checked as a digit, which makes the negative bounds unreachable and they are gone.

Folded the three copies of the unevidenced-item loop in Evaluate, Outstanding and Due into one helper.
2026-08-06 03:12:36 +04:00
claude 42d7a39c49 morning, tasks, memory: say the summaries from the file (V-506)
The three callers now read their sentences out of summary_ru_v1.json: the
plan lines in morning.Plan.FormatRU, the list and reason words in
tasks.FormatRU, and the habit readouts in memory.Profile.

Two behaviour_test assertions moved from substring to say.IsS, because the
habit gaps have variants now and a substring pins one of them. The
"по {day} у тебя обычно" variant was dropped on sight: the activities are
verbs, so it read "у тебя обычно тренируешься".

The persona scorer covers the family, and a new test asserts every gap
variant still says she has not seen enough rather than that he has nothing.
2026-08-04 01:47:52 +04:00
kami b3c2fad4ec 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.
2026-08-01 14:07:43 +04:00
kami ed9bdd5e09 Add the day plan she can recite when asked (#128)
The plan answers "какие планы на сегодня?" by putting one day in order:
calendar events (with #126's ambient provenance carried through and hedged),
pending reminders, and one line per morning routine that still has items
outstanding. "что дальше?" trims what has already passed.

It lives in internal/morning, not in a parallel system, because it is the same
question the checklist asks at a different scale — the routine knows what is
missing from a window, the plan knows what the whole day holds, and both read
the same facts and the same idea of "today". BuildPlan is pure; tickLoop.dayPlan
is the impure half that reads the store.

It is not a nag. Nothing here fires, schedules or announces: the plan is built
only when asked, over IPC (day_plan) or on the existing /morning page.
Unprompted delivery stays with the morning nudge and the dispatcher's policy.

The query source sits before "calendar" in querySources because both match
"…на сегодня" and the plan's matcher is the more specific one; IsDayPlanQuery
matches whole words so "планёрка" (a meeting) is not read as a request for the
plan, and refuses any utterance naming another day, since the plan is built for
the clock's own day only.

Verified: make build and make test both exit 0; new tests cover plan ordering,
the checklist-only-what-is-left rule, other-day rejection, the RU rendering
against the persona checks, rest-of-day trimming, the source ordering, and the
matcher's refusals.
2026-08-01 02:15:18 +04:00