diff --git a/docs/README.md b/docs/README.md index a1a8f3f10..60422d268 100644 --- a/docs/README.md +++ b/docs/README.md @@ -260,6 +260,7 @@ above fails otherwise. - [`SUBAGENTS.md`](SUBAGENTS.md) — Sub-agents: default-on, parent decides, typed children (#1043) - [`TASK-SCHEDULE-UX.md`](TASK-SCHEDULE-UX.md) — Create Task schedule controls - [`TASK-SERIALIZATION.md`](TASK-SERIALIZATION.md) — Task serialization — opaque `serialization_key` mutual exclusion (#709) +- [`TASK-TAGS.md`](TASK-TAGS.md) — Task tags on the board — chips, the tag filter, and the catalogue TTL - [`TASK-TITLES.md`](TASK-TITLES.md) — Task titles - [`TEAM-SHARING.md`](TEAM-SHARING.md) — Sharing work inside a project — team-shared chats and team learnings - [`TESTING.md`](TESTING.md) — Testing fleet diff --git a/docs/TASK-TAGS.md b/docs/TASK-TAGS.md new file mode 100644 index 000000000..a5769f405 --- /dev/null +++ b/docs/TASK-TAGS.md @@ -0,0 +1,89 @@ +# Task tags on the board + +What shipped when tags stopped being write-only, what deviated, and what was +deliberately left out. The user-facing description lives in the Operations +Center guide ("Finding things"); this note is for whoever changes the code. + +## The gap + +Tags (#212) were storable and queryable but invisible. The create form accepted +them, `models.Task` carried them, `TaskFilter.Tags` filtered on them, +`GET /tasks?tag=a&tag=b` narrowed to tasks carrying **both**, and +`GET /tasks/tags` returned the whole catalogue with per-tag counts — and no +surface in the web app ever rendered a tag again. So the one thing a tag is +for, finding the rest of its group, could not be done from the UI at all. + +The gap was found while writing the user guide, which is worth recording: the +guide had to describe tags as "stored metadata rather than a control on that +screen", and a sentence that awkward is usually a defect wearing prose. + +## What shipped + +- **Chips.** A task's tags render on its table row and its phone card, coloured + from the same hashed palette (`shared/lib/labelColors`) as the chat + conversation labels, so one tag reads the same everywhere it appears. +- **Every chip is a control.** Clicking one adds that tag to the board's + filter; clicking a selected one removes it. Tags AND server-side, so each + addition narrows and each removal widens. +- **A Tags group in the filter bar** — a select that *adds* a tag, plus a + removable chip per selected tag. The select never holds a value: the board is + filtered by every chip beside it, not by the last one chosen, and a select + reading `ops` while `ops + urgent` were applied would misstate the board. +- **Tags count as an active filter**, so **Clear filters** appears and clears + them. Without that the only way back to the full board was a page reload. +- **`/api/orchestrator/tasks/tags`**, a thin proxy to the existing catalogue + endpoint. The static `tags` segment wins over the sibling `[taskId]` route, + so it does not shadow `GET /tasks/{id}` — the same ordering `cmd/fleet/main.go` + spells out explicitly for the Go router. + +Two things underneath had to change: + +- **`passThroughQuery` forwards every value of a repeated parameter.** It read + only the first, which is right for every single-valued filter and wrong for + `tag`: dropping the second of `?tag=a&tag=b` *widens* the result instead of + narrowing it — the one direction a filter must never fail in. Single-valued + parameters behave exactly as before. +- **The phone card's box moved from its ` + ))} + + ) : null} + + + ) : null}