Files
pad/web/src/lib/components/fields
xarmian feb068a91f feat(tags): tag chip editor on the item detail page (TASK-1654) (#659)
* feat(tags): tag chip editor on the item detail page (TASK-1654)

Tags live on item.tags (a JSON-array string), not the collection schema, so
this adds a TagInput sibling to FieldEditor rather than a field type.

- TagInput.svelte: chip editor — Enter/comma to add, Backspace/× to remove,
  case-insensitive dedupe (stored as typed), autocomplete dropdown sourced
  from the workspace tag set; readonly mode renders plain chips.
- Item detail page: derive `tags` from item.tags (defensive parse),
  load `tagSuggestions` via a workspace-keyed $effect kept separate from the
  item-load path (Svelte 5 effect-splitting convention), and updateTags()
  mirrors updateField() — optimistic with revert-on-failure, PATCHing `tags`.
  The Tags row renders between the schema fields and the Assignment section.

api.items.update already accepted `tags` via ItemUpdate, so no client change
was needed there.

Parent: PLAN-1652.

* fix(tags): guard overlapping tag saves with a sequence counter per Codex review (round 1)

Rapid chip edits can issue overlapping PATCHes; a late-resolving older
request could clobber the newer tag set with stale data or an errant revert.
Only the latest save (by monotonic seq) applies its result or reverts.

* fix(tags): drop stale tag-suggestion results across workspace navigation per Codex review (round 2)

loadTagSuggestions now only assigns when the in-flight workspace still
matches the current one, so a slower old-workspace /tags response can't
overwrite the new workspace's autocomplete.

* fix(tags): dedupe tags + key chips by index per Codex review (round 3)

An item can carry duplicate tags (e.g. ["ux","ux"]) since the write path
doesn't enforce per-item uniqueness, which would collide value-based Svelte
keys. Key chips by index, and dedupe case-insensitively at the source so the
cleaned set persists on the next save.

* fix(tags): gate tag-save completion UI on item freshness per Codex review (round 4)

If the user navigates to another item while a tag save is in flight (no
further edit, so the seq guard doesn't trip), skip showSaved()/toast/refresh
so completion UI can't fire on an unrelated page.

* fix(tags): serialize+coalesce tag saves, revert to last confirmed per Codex review (round 5)

Replace the concurrent-PATCH-with-seq-guard approach with a single
in-flight, coalescing saver scoped per item. Eliminates the overlap class
structurally: no stale completion clobbers a newer set, and `confirmed`
tracks the last server-acknowledged tags so a failed save reverts to server
truth rather than an optimistic unconfirmed value. Subsumes the round-1 race
guard and round-4 navigation gate.

* fix(tags): key tag savers by item id to prevent cross-navigation concurrency per Codex review (round 6)

A single saver slot let navigating away from an item mid-save and back spawn
a second concurrent saver for it. Hold savers in a Map keyed by item id so
edits coalesce into the existing in-flight saver; evict on drain.

* fix(tags): reapply in-flight desired tags after item reload per Codex review (round 7)

Navigating away and back mid-save reloaded stale server tags; a follow-up
edit computed from that stale set could drop the in-flight edit. The saver
now tracks the latest desired set and loadData reapplies it when a save is
still in flight for the reloaded item.

* fix(tags): keep save indicator active across reload so refresh guards hold per Codex review (round 8)

loadData reset saveStatus to idle while a tag PATCH was still in flight,
letting SSE/sync snapshot adoption bypass the saveStatus==='saving' guard and
land stale tags. Restore 'saving' when reapplying an in-flight saver so the
existing refresh guards keep skipping until the save drains.

* fix(tags): overlay in-flight tags at every server-snapshot assignment per Codex review (round 9)

The saveStatus guard is racy (checked before the refresh handlers' own await,
not after), so a concurrent snapshot could still drop optimistic tags. Extract
withInflightTags() and route ALL item = <server snapshot> sites through it
(realtime SSE/sync, initial load, content-save echoes, title/field/assignment/
role update echoes, post-action refresh, version restore). Overlaying the
saver's desired set at assignment time is race-free regardless of the guard.

* fix(tags): overlay tags on field-save + forced-retry echoes per Codex review (round 10)

updateField's success echo (item = fresh) and the forced open-children retry
(item = forced) were the last two un-overlaid server-snapshot assignments;
route both through withInflightTags so a concurrent tag save isn't clobbered.

* fix(tags): preserve unsaved content when reconciling tag-save echo per Codex review (round 11)

flushTagSaver adopted the full tag PATCH response (item = fresh), which
carries server content and could clobber unsaved editor edits. Route it
through adoptServerItem so local content is preserved (non-collab) like the
other snapshot adoption sites.
2026-05-30 01:58:17 -04:00
..