Bestehende Liste duplizieren #159

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

Story: Bestehende Liste duplizieren

As a Nutzer, der eine wiederkehrende Situation (z. B. eine regelmaessige Einkaufsliste oder ein
wiederkehrendes Projekt) neu starten moechte,
I want to eine bestehende Liste als Vorlage duplizieren koennen, um eine neue, unabhaengige Liste
mit denselben Eintraegen zu erhalten,
so that ich nicht jedes Mal alle Eintraege von Hand neu anlegen muss.

Background

Es gibt bereits feste, kuratierte Listen-Vorlagen (TodoListTemplateCatalog, #24), aber keine
Moeglichkeit, eine der eigenen bestehenden Listen als Ausgangspunkt fuer eine neue zu verwenden.

Acceptance criteria:

  • Ueber das bestehende Listen-Menue gibt es eine Aktion "Liste duplizieren".
  • Die neue Liste erhaelt denselben Listentyp und dieselben Eintraege (Titel), aber nicht deren
    Erledigt-Status, Zuweisung oder Kommentare.
  • Die neue Liste hat den duplizierenden Nutzer als alleinigen Owner - bestehende Mitgliedschaften
    der Originalliste werden nicht uebernommen.
  • Funktioniert fuer alle Listentypen (Todo, Einkaufsliste, Vorratsschrank, Priorisierte Liste,
    Masterpackliste), soweit fuer den jeweiligen Typ sinnvoll.

Out of scope for this story:

  • Laufende Synchronisation zwischen Original und Kopie.
  • Duplizieren einzelner Eintraege (nur ganze Listen).

Open questions: (escalate to human if unanswered)

  • Sollen listeneigene Kategorien/Labels (falls pro Liste angelegt, nicht global) mitdupliziert
    werden, oder verweist die Kopie auf dieselben?
## Story: Bestehende Liste duplizieren **As a** Nutzer, der eine wiederkehrende Situation (z. B. eine regelmaessige Einkaufsliste oder ein wiederkehrendes Projekt) neu starten moechte, **I want to** eine bestehende Liste als Vorlage duplizieren koennen, um eine neue, unabhaengige Liste mit denselben Eintraegen zu erhalten, **so that** ich nicht jedes Mal alle Eintraege von Hand neu anlegen muss. ## Background Es gibt bereits feste, kuratierte Listen-Vorlagen (`TodoListTemplateCatalog`, `#24`), aber keine Moeglichkeit, eine der eigenen bestehenden Listen als Ausgangspunkt fuer eine neue zu verwenden. **Acceptance criteria:** - [ ] Ueber das bestehende Listen-Menue gibt es eine Aktion "Liste duplizieren". - [ ] Die neue Liste erhaelt denselben Listentyp und dieselben Eintraege (Titel), aber nicht deren Erledigt-Status, Zuweisung oder Kommentare. - [ ] Die neue Liste hat den duplizierenden Nutzer als alleinigen Owner - bestehende Mitgliedschaften der Originalliste werden nicht uebernommen. - [ ] Funktioniert fuer alle Listentypen (Todo, Einkaufsliste, Vorratsschrank, Priorisierte Liste, Masterpackliste), soweit fuer den jeweiligen Typ sinnvoll. **Out of scope for this story:** - Laufende Synchronisation zwischen Original und Kopie. - Duplizieren einzelner Eintraege (nur ganze Listen). **Open questions:** (escalate to human if unanswered) - Sollen listeneigene Kategorien/Labels (falls pro Liste angelegt, nicht global) mitdupliziert werden, oder verweist die Kopie auf dieselben?
lena self-assigned this 2026-09-04 18:19:29 +02:00
Author
Collaborator

Claimed. Starting implementation.

Decision on the open question (whether list-own categories/labels should be duplicated or the copy should reference the originals): duplicated, into new rows owned by the new list. This isn't really a toss-up given the schema - TodoCategoryEntity/ShoppingCategoryEntity/PantryCategoryEntity and the *LabelEntity types are all strictly scoped to a single owning list via a non-nullable FK, so a duplicated entry literally cannot reference the original list's category/label rows across the list boundary; the only valid option is to create equivalent rows under the new list and remap entries onto them.

A few additional scope decisions, since the AC's "nicht deren Erledigt-Status, Zuweisung oder Kommentare" line doesn't enumerate every field on every entity across 5 list types:

  • New list title: original title + a " (Kopie)" suffix (translated per locale), so it's visibly distinct in the list overview rather than colliding with the original's name.
  • Todo: copies Title, Description, Priority, and (Priority-matrix lists only) Urgency/Importance - these are content/classification, not collaborative workflow state. Resets DoneDate (open), AssigneeId, Comments, RecurrenceRule (a duplicate shouldn't inherit the original's active recurring schedule), and DueDate (a restarted list has no due dates from the old run).
  • Shopping: copies product Name, sets IsOnList=true (freshly active - restarting a shopping list means the products are on it again), resets IsInCart and Quantity (last-used-quantity carries no meaning for a fresh activation).
  • Pantry: copies product Name and TargetQuantity (the "how much I want on hand" target is a stable structural property worth reusing), resets current Quantity to 0 (a new pantry instance starts empty, to be stocked).
  • MasterPacking: copies everything on the item (Name, Category text, Priority, Quantity, QuantityType) unchanged - none of it is collaborative workflow state, the whole entity is reference data with nothing to reset.
  • New list's sole member is the duplicating user as Owner, per the AC - existing members are never carried over.

Implementation composes the existing single-item Create*Command per entry inside one DbTransactionDecorator, mirroring #83's CSV-import precedent and #63's template-seeding precedent, rather than a raw bulk insert - same reasoning: each item gets identical validation and change-publishing side effects as if the user had created it by hand.

Claimed. Starting implementation. Decision on the open question (whether list-own categories/labels should be duplicated or the copy should reference the originals): duplicated, into new rows owned by the new list. This isn't really a toss-up given the schema - TodoCategoryEntity/ShoppingCategoryEntity/PantryCategoryEntity and the *LabelEntity types are all strictly scoped to a single owning list via a non-nullable FK, so a duplicated entry literally cannot reference the original list's category/label rows across the list boundary; the only valid option is to create equivalent rows under the new list and remap entries onto them. A few additional scope decisions, since the AC's "nicht deren Erledigt-Status, Zuweisung oder Kommentare" line doesn't enumerate every field on every entity across 5 list types: - New list title: original title + a " (Kopie)" suffix (translated per locale), so it's visibly distinct in the list overview rather than colliding with the original's name. - Todo: copies Title, Description, Priority, and (Priority-matrix lists only) Urgency/Importance - these are content/classification, not collaborative workflow state. Resets DoneDate (open), AssigneeId, Comments, RecurrenceRule (a duplicate shouldn't inherit the original's active recurring schedule), and DueDate (a restarted list has no due dates from the old run). - Shopping: copies product Name, sets IsOnList=true (freshly active - restarting a shopping list means the products are on it again), resets IsInCart and Quantity (last-used-quantity carries no meaning for a fresh activation). - Pantry: copies product Name and TargetQuantity (the "how much I want on hand" target is a stable structural property worth reusing), resets current Quantity to 0 (a new pantry instance starts empty, to be stocked). - MasterPacking: copies everything on the item (Name, Category text, Priority, Quantity, QuantityType) unchanged - none of it is collaborative workflow state, the whole entity is reference data with nothing to reset. - New list's sole member is the duplicating user as Owner, per the AC - existing members are never carried over. Implementation composes the existing single-item Create*Command per entry inside one DbTransactionDecorator, mirroring #83's CSV-import precedent and #63's template-seeding precedent, rather than a raw bulk insert - same reasoning: each item gets identical validation and change-publishing side effects as if the user had created it by hand.
lena referenced this issue from a commit 2026-09-04 21:56:29 +02:00
Author
Collaborator

Done. Backend commit 1c82c9a, frontend commit 77e3e12, memory notes 76cbdcd.

Scope delivered:

  • Duplicate{Todo,Shopping,MasterPacking}ListCommand / DuplicatePantryCommand: each creates a new, independent list of the same type from an existing one, copying categories/labels/entries but resetting collaborative state (done-status, assignee, comments), with the duplicating user as sole owner.
  • Available to any member with access to the source list, not just its owner - duplicating only reads the source.
  • "Duplicate list" added to all 4 list types' menus (next to Rename), no confirmation dialog since there's nothing to confirm; navigates straight to the new list on success.

Decision on the issue's own open question (duplicate categories/labels, or reference the originals): duplicate, into new rows owned by the new list. Not really a toss-up given the schema - every category/label entity is FK-scoped to exactly one owning list, so referencing the original across the list boundary isn't a valid option at all.

Per-type scope decisions, since the AC's exclusion list (done-status/assignee/comments) doesn't cover every field on every entity across 4 types:

  • Todo: carries Title/Description/Priority and (Priority-matrix lists only) Urgency/Importance - content/classification, not workflow state. Resets DoneDate/AssigneeId/Comments/RecurrenceRule/DueDate.
  • Shopping: preserves each product's active/inactive (on-list) state, resets last-used quantity.
  • Pantry: resets current stock to 0, keeps each product's TargetQuantity. Since #94 gives Pantry no membership of its own, the duplicate links to the same target shopping list as the source rather than also duplicating that list - the caller already needs access to it to see/duplicate the pantry at all.
  • MasterPacking: every field carries over unchanged - none of it is collaborative state (items are never checkable, per #93's own design).

New list title: computed client-side via i18n ("{{title}} (Copy)"/"{{title}} (Kopie)") rather than backend-generated, since every other backend-authored user-facing string in this app is plain English.

Tests: one handler test per type (category/label remapping, per-type reset rules) plus a dedicated Priority-matrix carry-forward test for Todo, plus 5 frontend menu-interaction tests (one per type/flow, including a non-owner-can-still-duplicate check).

Verification: backend dotnet build/dotnet test clean (Docker unavailable in this environment all cycle - confirmed all 4 new handlers' tests fail with the known DockerUnavailableException shape, not a DI/setup error); frontend npm run build and full vitest suite green (125 files / 1158 tests). A dedicated security-review subagent traced authorization and cross-user data-mixing risk across all 4 handlers end to end (source-list access checked before any read, every query scoped to ids derived from that authorized source, new membership always keyed to the actual caller) and found nothing. Real CI was pending on the whole matrix throughout the cycle (known runner-congestion pattern, now observed continuously across 3 consecutive cycles) - not yet confirmed green on the actual pipeline as of this comment.

Done. Backend commit 1c82c9a, frontend commit 77e3e12, memory notes 76cbdcd. Scope delivered: - `Duplicate{Todo,Shopping,MasterPacking}ListCommand` / `DuplicatePantryCommand`: each creates a new, independent list of the same type from an existing one, copying categories/labels/entries but resetting collaborative state (done-status, assignee, comments), with the duplicating user as sole owner. - Available to any member with access to the source list, not just its owner - duplicating only reads the source. - "Duplicate list" added to all 4 list types' menus (next to Rename), no confirmation dialog since there's nothing to confirm; navigates straight to the new list on success. Decision on the issue's own open question (duplicate categories/labels, or reference the originals): duplicate, into new rows owned by the new list. Not really a toss-up given the schema - every category/label entity is FK-scoped to exactly one owning list, so referencing the original across the list boundary isn't a valid option at all. Per-type scope decisions, since the AC's exclusion list (done-status/assignee/comments) doesn't cover every field on every entity across 4 types: - Todo: carries Title/Description/Priority and (Priority-matrix lists only) Urgency/Importance - content/classification, not workflow state. Resets DoneDate/AssigneeId/Comments/RecurrenceRule/DueDate. - Shopping: preserves each product's active/inactive (on-list) state, resets last-used quantity. - Pantry: resets current stock to 0, keeps each product's TargetQuantity. Since #94 gives Pantry no membership of its own, the duplicate links to the *same* target shopping list as the source rather than also duplicating that list - the caller already needs access to it to see/duplicate the pantry at all. - MasterPacking: every field carries over unchanged - none of it is collaborative state (items are never checkable, per #93's own design). New list title: computed client-side via i18n ("{{title}} (Copy)"/"{{title}} (Kopie)") rather than backend-generated, since every other backend-authored user-facing string in this app is plain English. Tests: one handler test per type (category/label remapping, per-type reset rules) plus a dedicated Priority-matrix carry-forward test for Todo, plus 5 frontend menu-interaction tests (one per type/flow, including a non-owner-can-still-duplicate check). Verification: backend `dotnet build`/`dotnet test` clean (Docker unavailable in this environment all cycle - confirmed all 4 new handlers' tests fail with the known DockerUnavailableException shape, not a DI/setup error); frontend `npm run build` and full vitest suite green (125 files / 1158 tests). A dedicated security-review subagent traced authorization and cross-user data-mixing risk across all 4 handlers end to end (source-list access checked before any read, every query scoped to ids derived from that authorized source, new membership always keyed to the actual caller) and found nothing. Real CI was `pending` on the whole matrix throughout the cycle (known runner-congestion pattern, now observed continuously across 3 consecutive cycles) - not yet confirmed green on the actual pipeline as of this comment.
lena closed this issue 2026-09-04 21:57:03 +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#159
No description provided.