Turnus (rotierende Zuweisung) fuer wiederkehrende Todos #157
Labels
No labels
priority/could
priority/must
priority/should
priority/wont
status/blocked
status/claimed
status/done-migrated
type/bug
type/feature
type/infra
type/tech-debt
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
robert/todo#157
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Story: Turnus (rotierende Zuweisung) fuer wiederkehrende Todos
As a Bewohner einer WG, der eine wiederkehrende Aufgabe (z. B. "Muell rausbringen") mit mehreren
Mitgliedern teilt,
I want to dass die Zuweisung bei jeder neuen Wiederholung automatisch zum naechsten Mitglied der
Liste wechselt,
so that die Aufgabe nicht dauerhaft an einer Person haengen bleibt, sondern fair unter allen
rotiert.
Background
Wiederkehrende Todos (
#20) und Zuweisung (Assignee) existieren bereits, aberRollForwardMissedRecurringTodosCommandHandler/die regulaere Wiederholungs-Erzeugung uebernehmenaktuell einfach denselben Assignee unveraendert in jede neue Instanz - keine Rotation.
Acceptance criteria:
SetTodoRecurrenceCommand) kann optional eine Rotationaktiviert werden ("reihum unter allen Mitgliedern der Liste").
Mitglied zugewiesen statt des vorherigen.
nicht rueckwirkend ueberschrieben.
einmal zugewiesen, in stabiler Reihenfolge.
Out of scope for this story:
der Rotation.
Open questions: (escalate to human if unanswered)
Beitrittsdatum zur Liste, oder alphabetisch nach Name?
Claimed. Starting implementation.
Decision on the open question (rotation order when no explicit member ranking exists): rotate by list-join order (oldest member first), tie-broken by user id for determinism. Reasoning: alphabetical-by-name is arbitrary from a fairness standpoint and would silently reorder itself if someone renames their display name; join order matches the plain-language framing in the story ("reihum unter allen Mitgliedern") and is stable once set.
There is currently no join-date field on the list-membership table (TodoListToUserEntity) to order by, so this adds one:
CreatedAtcolumn onTodoListToUserEntity, set explicitly at insert time (list creation for the owner row, invitation acceptance for member rows). Existing rows get a DB-levelnow()default via the migration (pre-existing memberships all sort together, tie-broken by user id - acceptable since there's no real historical join order to recover).Planned implementation:
SetTodoRecurrenceCommandgets an optionalRotateAssigneeflag (persisted onTodoEntity, meaningful only while a recurrence rule is set - cleared when recurrence is cleared, same pattern as the existing anchor-day field).CheckTodoCommandHandler.CreateNextOccurrenceAsync(where the next occurrence's assignee is currently always copied unchanged from the completed todo) computes the next member in join order instead, when rotation is enabled. If the current assignee is no longer a list member, falls back to the first member in join order.RollForwardMissedRecurringTodosCommandHandler- it advances the due date on the same row for a missed occurrence rather than creating a new one, so there's nothing to rotate there.Frontend: add a "rotate assignee" toggle to the recurrence UI, enabled only when a recurrence rule is selected.
Done. Backend commit
1ddc4b1, frontend commitd33804c, memory notese175331.Scope delivered:
SetTodoRecurrenceCommandgets an optionalRotateAssigneeflag, persisted onTodoEntityand forced back to false whenever the recurrence rule is cleared.TodoListToUserEntity.CreatedAtcolumn (join-date order) as the rotation key - there was no existing membership ordering field. Set at insert time for both the owner row (list creation) and member rows (invitation acceptance); pre-existing rows backfilled via a DB-levelnow()default in the migration.CheckTodoCommandHandlernow computes the next member in join order (wrapping, falling back to the first member if the previous assignee has since left the list) when rotation is enabled, instead of always carrying the same assignee forward.Decision on the story's own open question (rotation order when no explicit ranking exists): list-join order, not alphabetical - see the start comment above for the reasoning.
Tests: SetTodoRecurrenceCommandHandler (persists/forces-false the flag) and CheckTodoCommandHandler (single-step rotation, the 3-member/3-completion round-robin regression matching this issue's own acceptance criterion, fallback on a departed assignee, and that a manual reassignment between completions is respected rather than overridden by a predicted rotation chain) plus a RecurrencePicker frontend test for the new toggle.
Verification: backend
dotnet build/dotnet testclean (Docker unavailable in this environment for the Testcontainers-backed DB tests all cycle - confirmed every failure was the known DockerUnavailableException shape, not a regression); frontendnpm run buildand full vitest suite green (125 files / 1151 tests). A dedicated security-review subagent found zero issues (new rotation query stays scoped to the already-authorized TodoListId, no raw SQL, no new field exposes anything beyond what a list member can already see). Real CI waspendingon the whole matrix for this repo's entire queue throughout the cycle (known runner-congestion pattern) - not yet confirmed green on the actual pipeline as of this comment.