Refactor: Dokument-Tags über IDs statt Namen round-trippen (Follow-up zu #730) #732

Open
opened 2026-06-06 10:54:55 +02:00 by marcel · 0 comments
Owner

Summary

Explicitly out-of-scope follow-up from #730. Today the document edit form round-trips tag names (tags.map(t => t.name).join(','), frontend/src/routes/documents/[id]/edit/+page.server.ts:89), and the backend re-resolves each name to a tag on every save via TagService.findOrCreate. #730 made that resolution unambiguous and non-throwing (exact-case → lowest-id CI → create), but name-as-key is still a smell: under a case collision, free-text authoring binds the bare word weihnachten to the deep child and Weihnachten to the parent container — correct for the edit round-trip, but the author can't see which they'll get.

Round-tripping tag IDs instead of names removes the ambiguity at the source: resolution becomes a direct findById, no name matching, no collision handling needed on the edit path.

Scope

  • Frontend: send tag ids (not names) from the edit form / bulk-edit / new-doc forms; the typeahead already has the full tag objects.
  • Backend: accept tag ids on DocumentUpdateDTO / DocumentBulkEditDTO and resolve via tagRepository.findById; keep findOrCreate(name) only for genuinely new free-text tags (create-by-name path).
  • Decide the contract for newly-typed tags that have no id yet (create-then-attach, or a mixed ids+new-names payload).
  • Separate follow-up (also noted in #730): disambiguate the typeahead/chips by showing the tree path so an author can tell a container from its same-named child.

Notes

  • Larger change than #730 (frontend surface + DTO/API regen via npm run generate:api). #730's lookup fix is the minimal correct unblock; this is the cleaner long-term shape.
## Summary Explicitly out-of-scope follow-up from #730. Today the document edit form round-trips tag **names** (`tags.map(t => t.name).join(',')`, `frontend/src/routes/documents/[id]/edit/+page.server.ts:89`), and the backend re-resolves each name to a tag on every save via `TagService.findOrCreate`. #730 made that resolution unambiguous and non-throwing (exact-case → lowest-id CI → create), but **name-as-key is still a smell**: under a case collision, free-text authoring binds the bare word `weihnachten` to the deep child and `Weihnachten` to the parent container — correct for the edit round-trip, but the author can't see which they'll get. Round-tripping tag **IDs** instead of names removes the ambiguity at the source: resolution becomes a direct `findById`, no name matching, no collision handling needed on the edit path. ## Scope - Frontend: send tag **ids** (not names) from the edit form / bulk-edit / new-doc forms; the typeahead already has the full tag objects. - Backend: accept tag ids on `DocumentUpdateDTO` / `DocumentBulkEditDTO` and resolve via `tagRepository.findById`; keep `findOrCreate(name)` only for genuinely new free-text tags (create-by-name path). - Decide the contract for newly-typed tags that have no id yet (create-then-attach, or a mixed ids+new-names payload). ## Related - Separate follow-up (also noted in #730): disambiguate the typeahead/chips by showing the tree path so an author can tell a container from its same-named child. ## Notes - Larger change than #730 (frontend surface + DTO/API regen via `npm run generate:api`). #730's lookup fix is the minimal correct unblock; this is the cleaner long-term shape.
marcel added the P3-laterrefactor labels 2026-06-06 10:55:02 +02:00
Sign in to join this conversation.
No Label P3-later refactor
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#732