Merge the accepted-routine fix and drop reminder_id from accept

Two merge fixes on top of the branch:

- migrations: keep both new steps, snooze stays #8, the routine columns
  become #9. Both agents had numbered theirs #8.
- accepting no longer takes a reminder id, on the web surface too. The
  web accept path had the same one-shot-reminder bug the voice path did,
  so both now just flip the status and let the tick loop schedule.

The test that asserted "accept creates a reminder and links it" asserted
the bug. It now asserts that accepting creates no reminder.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CGeSZxh1DCtRxmFVSYVGvJ
This commit is contained in:
kami
2026-07-31 10:07:37 +04:00
16 changed files with 398 additions and 85 deletions
+11 -8
View File
@@ -928,7 +928,6 @@ type routineCore struct {
routines []ipc.ProposedRoutine
dismissed int64
acceptedID int64
acceptedRe int64
remCron string
}
@@ -941,8 +940,8 @@ func (c *routineCore) DismissProposedRoutine(_ context.Context, id int64) error
return nil
}
func (c *routineCore) AcceptProposedRoutine(_ context.Context, id, remID int64) error {
c.acceptedID, c.acceptedRe = id, remID
func (c *routineCore) AcceptProposedRoutine(_ context.Context, id int64) error {
c.acceptedID = id
return nil
}
@@ -990,18 +989,22 @@ func TestHandleRoutines_Accept_RequiresStepUp(t *testing.T) {
}
}
func TestHandleRoutines_Accept_CreatesReminderAndLinksIt(t *testing.T) {
// Accepting only flips the status. It used to also create a one-shot reminder,
// which is why a non-weekly routine fired once and then went quiet forever
// (Vikunja #366). The tick loop owns the schedule now, so a reminder here would
// be a second, competing schedule.
func TestHandleRoutines_Accept_FlipsStatusAndMakesNoReminder(t *testing.T) {
core := weeklyRoutineCore()
rr := httptest.NewRecorder()
handleRoutines(rr, postRoutine("accept", "3"), core, stepUpSession(), false)
if rr.Code != http.StatusOK {
t.Fatalf("status = %d, want 200; body=%s", rr.Code, rr.Body.String())
}
if core.acceptedID != 3 || core.acceptedRe != 77 {
t.Fatalf("accepted id=%d reminder=%d, want 3 and 77", core.acceptedID, core.acceptedRe)
if core.acceptedID != 3 {
t.Fatalf("accepted id = %d, want 3", core.acceptedID)
}
if core.remCron == "" {
t.Fatal("a weekly pattern should get a cron expression")
if core.remCron != "" {
t.Fatalf("accepting must not create a reminder, got cron %q", core.remCron)
}
}
+5 -14
View File
@@ -898,20 +898,11 @@ func acceptRoutine(ctx context.Context, core ipc.CoreAPI, id int64) error {
return errors.New("no such proposed routine")
}
fire := time.Now().Add(time.Duration(found.IntervalDays * 24 * float64(time.Hour)))
cron := ""
if found.IntervalDays >= 6.5 && found.IntervalDays <= 7.5 {
cron = fmt.Sprintf("0 %d * * %d", fire.Hour(), int(fire.Weekday()))
}
payload, err := json.Marshal(map[string]string{"text": found.Action + " " + found.Object})
if err != nil {
return err
}
remID, err := core.CreateReminder(ctx, fire, string(payload), cron)
if err != nil {
return err
}
return core.AcceptProposedRoutine(ctx, id, remID)
// No reminder is created here. Accepting only flips the status; the tick
// loop reads accepted routines and nudges on the interval (Vikunja #366).
// The old code made a one-shot reminder, so a non-weekly routine fired
// once and then went quiet forever.
return core.AcceptProposedRoutine(ctx, id)
}
func handleTrace(w http.ResponseWriter, r *http.Request, core ipc.CoreAPI) {