Item tags¶
Backend domain: app/graphql/items/ (Tag, item_tag_table, TagService, TagRepository)
Migration: tag_module
Overview¶
items.tags was a varchar[] column on items. It is now a proper module: a tags table (id, tagName, createdById, createdAt, unique on tagName) plus an item_tags association table (item_id, tag_id, both ON DELETE CASCADE). Items can have any number of tags; tags are tenant-wide and reusable across items.
The migration backfills existing tag values: every distinct string previously stored on items.tags becomes a Tag row (createdById copied from one of the items using it), and the item/tag links populate item_tags. The old column is then dropped.
GraphQL surface¶
Type¶
Item.tags and ItemLite.tags return [Tag!] | null (resolved lazily off the item's relationship; the previous [String!] shape is gone).
Queries¶
Mutations¶
There is no longer a dedicated setItemTags mutation or ItemTagsAssignmentInput. Tag assignment happens through the standard createItem / updateItem mutations.
Item create / update¶
ItemInput carries an inline tags: [TagInput!] list (a richer shape than the old tagIds: [UUID!]):
- Omit
tags(leavenull) on update to leave the item's tag set untouched. - Pass an empty list
[]to clear all tags on the item. - Pass
TagInputobjects withidset to attach existing tags. - Pass
TagInputobjects withid: nulland onlytagNameto auto-create-then-attach — the service creates the tag if no row matches the name, then links it.
This means the UI no longer needs a separate "create tag" round-trip before saving the item.
Behaviour to handle in the UI¶
- The tag picker is a multi-select that loads from
tags(ortagSearchfor incremental search) and emitsTagInputobjects. - For known tags, emit
{ id, tagName }. For freshly typed new tags, emit{ id: null, tagName }— the backend will create them on save. - Editing an item: read existing
item.tags(objects withid+tagName) and pre-select; preserve the existingidwhen re-submitting so the link is kept. deleteTagcascades toitem_tagsso previously tagged items lose the link silently.- For a tag chip editor on a detail page (no full item edit form), reuse
updateItemand pass only the changedtagslist.
Notes¶
tagNameis unique across the tenant —createTagreturnsDuplicateRowErroron collision; surface that as a validation error on the input.- The previous
tags: [String!]field is removed from the schema; any frontend code readingitem.tags[i]as a string must be updated toitem.tags[i].tagName.