Turnus (rotierende Zuweisung) fuer wiederkehrende Todos #157

Closed
opened 2026-09-04 12:32:32 +02:00 by lena · 2 comments
Collaborator

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, aber
RollForwardMissedRecurringTodosCommandHandler/die regulaere Wiederholungs-Erzeugung uebernehmen
aktuell einfach denselben Assignee unveraendert in jede neue Instanz - keine Rotation.

Acceptance criteria:

  • Beim Konfigurieren einer Wiederholung (SetTodoRecurrenceCommand) kann optional eine Rotation
    aktiviert werden ("reihum unter allen Mitgliedern der Liste").
  • Bei jeder neuen Instanz einer rotierenden wiederkehrenden Aufgabe wird automatisch das naechste
    Mitglied zugewiesen statt des vorherigen.
  • Ein manuell geaenderter Assignee auf einer bereits erzeugten Instanz wird durch die Rotation
    nicht rueckwirkend ueberschrieben.
  • Regressionstest: 3 Mitglieder, 3 aufeinanderfolgende Wiederholungen -> jedes Mitglied genau
    einmal zugewiesen, in stabiler Reihenfolge.

Out of scope for this story:

  • Rotation nach Verfuegbarkeit/Abwesenheit (z. B. Urlaub) - moegliche Folge-Story.
  • Rueckwirkende Aenderung bereits erzeugter, noch offener Instanzen beim nachtraeglichen Aktivieren
    der Rotation.

Open questions: (escalate to human if unanswered)

  • Nach welcher Reihenfolge soll rotiert werden, wenn kein explizites Mitglieder-Ranking existiert -
    Beitrittsdatum zur Liste, oder alphabetisch nach Name?
## 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, aber `RollForwardMissedRecurringTodosCommandHandler`/die regulaere Wiederholungs-Erzeugung uebernehmen aktuell einfach denselben Assignee unveraendert in jede neue Instanz - keine Rotation. **Acceptance criteria:** - [ ] Beim Konfigurieren einer Wiederholung (`SetTodoRecurrenceCommand`) kann optional eine Rotation aktiviert werden ("reihum unter allen Mitgliedern der Liste"). - [ ] Bei jeder neuen Instanz einer rotierenden wiederkehrenden Aufgabe wird automatisch das naechste Mitglied zugewiesen statt des vorherigen. - [ ] Ein manuell geaenderter Assignee auf einer bereits erzeugten Instanz wird durch die Rotation nicht rueckwirkend ueberschrieben. - [ ] Regressionstest: 3 Mitglieder, 3 aufeinanderfolgende Wiederholungen -> jedes Mitglied genau einmal zugewiesen, in stabiler Reihenfolge. **Out of scope for this story:** - Rotation nach Verfuegbarkeit/Abwesenheit (z. B. Urlaub) - moegliche Folge-Story. - Rueckwirkende Aenderung bereits erzeugter, noch offener Instanzen beim nachtraeglichen Aktivieren der Rotation. **Open questions:** (escalate to human if unanswered) - Nach welcher Reihenfolge soll rotiert werden, wenn kein explizites Mitglieder-Ranking existiert - Beitrittsdatum zur Liste, oder alphabetisch nach Name?
lena self-assigned this 2026-09-04 17:11:27 +02:00
Author
Collaborator

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:

  • New CreatedAt column on TodoListToUserEntity, set explicitly at insert time (list creation for the owner row, invitation acceptance for member rows). Existing rows get a DB-level now() 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:

  • SetTodoRecurrenceCommand gets an optional RotateAssignee flag (persisted on TodoEntity, 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.
  • No change to 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.
  • A manually-changed assignee on an already-created instance is never touched retroactively - rotation only ever computes the assignee for a new occurrence at creation time, so this AC is satisfied by construction rather than needing extra guard code.
  • Regression test: 3 members, 3 consecutive completions of a rotating recurring todo -> each member assigned exactly once, in stable order.

Frontend: add a "rotate assignee" toggle to the recurrence UI, enabled only when a recurrence rule is selected.

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: - New `CreatedAt` column on `TodoListToUserEntity`, set explicitly at insert time (list creation for the owner row, invitation acceptance for member rows). Existing rows get a DB-level `now()` 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: - `SetTodoRecurrenceCommand` gets an optional `RotateAssignee` flag (persisted on `TodoEntity`, 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. - No change to `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. - A manually-changed assignee on an already-created instance is never touched retroactively - rotation only ever computes the assignee for a *new* occurrence at creation time, so this AC is satisfied by construction rather than needing extra guard code. - Regression test: 3 members, 3 consecutive completions of a rotating recurring todo -> each member assigned exactly once, in stable order. Frontend: add a "rotate assignee" toggle to the recurrence UI, enabled only when a recurrence rule is selected.
Author
Collaborator

Done. Backend commit 1ddc4b1, frontend commit d33804c, memory notes e175331.

Scope delivered:

  • SetTodoRecurrenceCommand gets an optional RotateAssignee flag, persisted on TodoEntity and forced back to false whenever the recurrence rule is cleared.
  • New TodoListToUserEntity.CreatedAt column (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-level now() default in the migration.
  • CheckTodoCommandHandler now 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.
  • A manually-changed assignee on an already-created instance is never touched retroactively by design - rotation only ever computes the assignee for the next occurrence at creation time.
  • Frontend: a "rotate assignee among all members" toggle in RecurrencePicker, applied together with whichever rule the user next picks.

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 test clean (Docker unavailable in this environment for the Testcontainers-backed DB tests all cycle - confirmed every failure was the known DockerUnavailableException shape, not a regression); frontend npm run build and 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 was pending on 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.

Done. Backend commit 1ddc4b1, frontend commit d33804c, memory notes e175331. Scope delivered: - `SetTodoRecurrenceCommand` gets an optional `RotateAssignee` flag, persisted on `TodoEntity` and forced back to false whenever the recurrence rule is cleared. - New `TodoListToUserEntity.CreatedAt` column (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-level `now()` default in the migration. - `CheckTodoCommandHandler` now 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. - A manually-changed assignee on an already-created instance is never touched retroactively by design - rotation only ever computes the assignee for the *next* occurrence at creation time. - Frontend: a "rotate assignee among all members" toggle in RecurrencePicker, applied together with whichever rule the user next picks. 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 test` clean (Docker unavailable in this environment for the Testcontainers-backed DB tests all cycle - confirmed every failure was the known DockerUnavailableException shape, not a regression); frontend `npm run build` and 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 was `pending` on 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.
lena closed this issue 2026-09-04 17:37:44 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
robert/todo#157
No description provided.