#105 — Feste Standard-Kategorie für Einkaufslisten statt wählbarer Default-Kategorie #108

Closed
opened 2026-08-18 13:14:07 +02:00 by lena · 0 comments
lena commented 2026-08-18 13:14:07 +02:00 (Migrated from git.butzei.de)

Story: Feste Standard-Kategorie für Einkaufslisten statt wählbarer Default-Kategorie

As a Nutzer der Einkaufsliste,
I want to keine Kategorie mehr manuell als "Default" festlegen können,
so that es immer genau eine feste, immer vorhandene Auffang-Kategorie für unkategorisierte Produkte
gibt, ohne dass ich diese Einstellung verwalten muss.

Klärung vom 2026-08-08 (PO-Rückfrage beantwortet): Betrifft ausschließlich Einkaufslisten-Kategorien.
Todo-Listen behalten ihre wählbare Default-Kategorie (SetDefaultCategoryCommand) unverändert — die beiden
Konzepte sind pro Listentyp unabhängig implementiert, es gibt keine geteilte Komponente, die versehentlich
mitbetroffen wäre.

Hintergrund: Aktuell kann jede Kategorie einer Einkaufsliste per SetDefaultShoppingCategoryCommand
(UI: ShoppingCategoryManager.tsx) als Default markiert werden — die erste der 14 vordefinierten Sektionen
ist zwar initial Default, das ist aber änderbar. Diese Story macht die erste Sektion effektiv permanent zur
Default-Kategorie einer Liste.

Acceptance criteria:

  • SetDefaultShoppingCategoryCommand/SetDefaultShoppingCategoryCommandHandler sowie der zugehörige
    UI-Trigger in ShoppingCategoryManager.tsx entfallen ersatzlos.
  • Jede Einkaufsliste behält weiterhin genau eine IsDefault-Kategorie (unverändert von #90's
    Datenmodell/Migration) — sie wird beim Anlegen der Liste gesetzt und ist danach nicht mehr änderbar.
  • Produkte ohne explizite Kategorie landen weiterhin automatisch in dieser festen Default-Kategorie
    (bestehende Logik in ShoppingCategoryResolver/CreateShoppingProductCommandHandler bleibt
    unverändert).
  • Eine bestehende Liste, deren Default-Kategorie zwischenzeitlich manuell umgestellt wurde, behält ihre
    aktuelle IsDefault-Kategorie unverändert (kein rückwirkendes Zurücksetzen auf die ursprünglich erste
    Sektion nötig) — diese Story verhindert nur künftige Änderungen, sie korrigiert keine Bestandsdaten.
  • Löschen einer Kategorie, die zufällig die Default-Kategorie ist: bestehendes Verhalten
    (DeleteShoppingCategoryCommandHandler's Reassignment-Logik) bleibt unverändert und weiterhin nötig,
    da eine Liste nie ganz ohne Default-Kategorie dastehen darf.

Out of scope for this story:

  • Todo-Listen-Kategorien (SetDefaultCategoryCommand) — bleiben wie bisher wählbar.
  • Migration/Bereinigung bereits manuell geänderter Default-Kategorien auf Bestandslisten.

Implementation notes (2026-08-08)

Picked up in a cycle running in parallel with another session working #104 (barcode scan) — chosen
specifically because it shares no files with ScanShoppingProductBarcodeCommandHandler/
ShoppingBarcodeScanDialog.tsx, so the two streams could proceed independently with no merge risk.

Straight deletion, no new code: SetDefaultShoppingCategoryCommand/SetDefaultShoppingCategoryCommandHandler.cs
and its test removed outright — the backend's route table is built by reflecting over every IRequest
type (EndpointRouteBuilderExtensions.MapRequests), so deleting the record type also deletes the
POST /api/SetDefaultShoppingCategoryCommand endpoint, no separate route deregistration needed.
ShoppingCategoryManager.tsx's setDefault() function and its conditional "Set default" button were
removed; the isDefault ? <span>Default</span> : <Button>Set default</Button> ternary collapsed to a
plain isDefault && <span>Default</span>. Regenerated the OpenAPI spec
(SWAGGER_GEN=1 dotnet tool run swagger tofile) and the frontend's generated schema
(npm run generate:api) to confirm ApiRequestMap no longer has an entry for the removed command —
tsc -b passing after the frontend edit is direct proof no other call site still referenced it.

No backend behavior change beyond removing the mutator: CreateShoppingListCommandHandler already sets
IsDefault = true only on the first of the 14 seeded sections and never again;
CreateShoppingCategoryCommandHandler/ShoppingCategoryResolver's auto-created categories already
default IsDefault to false; DeleteShoppingCategoryCommandHandler's refusal to delete the default
category (or the last remaining one) was already unconditional. Removing the only mutator makes the
"exactly one default category per list, never zero" invariant stronger (now immutable post-creation)
rather than introducing a gap — confirmed via the security-review skill (self-invoked per
07_team_coach_memory.md's note that /code-review can't invoke itself), zero findings.

Verified: dotnet build Cqs.sln -c Release (0 errors), dotnet test Cqs.sln -c Release (706/706
passing — Docker was available this cycle, so this includes the real DbTestContext-backed Shopping
suite, not just a Docker-skipped subset), npm run build (tsc + vite, clean), full vitest run
(755/755 passing). See docs/features/done/105_shopping_fixed_default_category.md's move for the
final state.

# Story: Feste Standard-Kategorie für Einkaufslisten statt wählbarer Default-Kategorie **As a** Nutzer der Einkaufsliste, **I want to** keine Kategorie mehr manuell als "Default" festlegen können, **so that** es immer genau eine feste, immer vorhandene Auffang-Kategorie für unkategorisierte Produkte gibt, ohne dass ich diese Einstellung verwalten muss. **Klärung vom 2026-08-08 (PO-Rückfrage beantwortet):** Betrifft ausschließlich Einkaufslisten-Kategorien. Todo-Listen behalten ihre wählbare Default-Kategorie (`SetDefaultCategoryCommand`) unverändert — die beiden Konzepte sind pro Listentyp unabhängig implementiert, es gibt keine geteilte Komponente, die versehentlich mitbetroffen wäre. **Hintergrund:** Aktuell kann jede Kategorie einer Einkaufsliste per `SetDefaultShoppingCategoryCommand` (UI: `ShoppingCategoryManager.tsx`) als Default markiert werden — die erste der 14 vordefinierten Sektionen ist zwar initial Default, das ist aber änderbar. Diese Story macht die erste Sektion effektiv permanent zur Default-Kategorie einer Liste. **Acceptance criteria:** - [x] `SetDefaultShoppingCategoryCommand`/`SetDefaultShoppingCategoryCommandHandler` sowie der zugehörige UI-Trigger in `ShoppingCategoryManager.tsx` entfallen ersatzlos. - [x] Jede Einkaufsliste behält weiterhin genau eine `IsDefault`-Kategorie (unverändert von `#90`'s Datenmodell/Migration) — sie wird beim Anlegen der Liste gesetzt und ist danach nicht mehr änderbar. - [x] Produkte ohne explizite Kategorie landen weiterhin automatisch in dieser festen Default-Kategorie (bestehende Logik in `ShoppingCategoryResolver`/`CreateShoppingProductCommandHandler` bleibt unverändert). - [x] Eine bestehende Liste, deren Default-Kategorie zwischenzeitlich manuell umgestellt wurde, behält ihre aktuelle `IsDefault`-Kategorie unverändert (kein rückwirkendes Zurücksetzen auf die ursprünglich erste Sektion nötig) — diese Story verhindert nur künftige Änderungen, sie korrigiert keine Bestandsdaten. - [x] Löschen einer Kategorie, die zufällig die Default-Kategorie ist: bestehendes Verhalten (`DeleteShoppingCategoryCommandHandler`'s Reassignment-Logik) bleibt unverändert und weiterhin nötig, da eine Liste nie ganz ohne Default-Kategorie dastehen darf. **Out of scope for this story:** - Todo-Listen-Kategorien (`SetDefaultCategoryCommand`) — bleiben wie bisher wählbar. - Migration/Bereinigung bereits manuell geänderter Default-Kategorien auf Bestandslisten. --- ## Implementation notes (2026-08-08) Picked up in a cycle running in parallel with another session working `#104` (barcode scan) — chosen specifically because it shares no files with `ScanShoppingProductBarcodeCommandHandler`/ `ShoppingBarcodeScanDialog.tsx`, so the two streams could proceed independently with no merge risk. Straight deletion, no new code: `SetDefaultShoppingCategoryCommand`/`SetDefaultShoppingCategoryCommandHandler.cs` and its test removed outright — the backend's route table is built by reflecting over every `IRequest` type (`EndpointRouteBuilderExtensions.MapRequests`), so deleting the record type also deletes the `POST /api/SetDefaultShoppingCategoryCommand` endpoint, no separate route deregistration needed. `ShoppingCategoryManager.tsx`'s `setDefault()` function and its conditional "Set default" button were removed; the `isDefault ? <span>Default</span> : <Button>Set default</Button>` ternary collapsed to a plain `isDefault && <span>Default</span>`. Regenerated the OpenAPI spec (`SWAGGER_GEN=1 dotnet tool run swagger tofile`) and the frontend's generated schema (`npm run generate:api`) to confirm `ApiRequestMap` no longer has an entry for the removed command — `tsc -b` passing after the frontend edit is direct proof no other call site still referenced it. No backend behavior change beyond removing the mutator: `CreateShoppingListCommandHandler` already sets `IsDefault = true` only on the first of the 14 seeded sections and never again; `CreateShoppingCategoryCommandHandler`/`ShoppingCategoryResolver`'s auto-created categories already default `IsDefault` to `false`; `DeleteShoppingCategoryCommandHandler`'s refusal to delete the default category (or the last remaining one) was already unconditional. Removing the only mutator makes the "exactly one default category per list, never zero" invariant *stronger* (now immutable post-creation) rather than introducing a gap — confirmed via the `security-review` skill (self-invoked per `07_team_coach_memory.md`'s note that `/code-review` can't invoke itself), zero findings. Verified: `dotnet build Cqs.sln -c Release` (0 errors), `dotnet test Cqs.sln -c Release` (706/706 passing — Docker was available this cycle, so this includes the real `DbTestContext`-backed Shopping suite, not just a Docker-skipped subset), `npm run build` (tsc + vite, clean), full `vitest run` (755/755 passing). See `docs/features/done/105_shopping_fixed_default_category.md`'s move for the final state.
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#108
No description provided.