From 75cf14f1cb3cdf463a30f8fb064a267eb58c82c5 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Wed, 10 Jun 2026 18:13:04 +0200 Subject: [PATCH] tickets panel: per-type filter chips (T-343) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A row of toggle chips below the filter box — one per pql ticket type, ordered large→small (Initiative, Epic, Story, Task, Bug), each with its TicketTypeColors dot + border. Single-click toggles a type; double-click isolates it (chart-legend solo, fully reversible); disabling the last enabled type snaps all back on so the list is never blank. ANDed with the text filter; all on by default, nothing persisted. One GestureDetector owns both onTap + onDoubleTap so Flutter disambiguates single vs double. Wireframe updated + approved. Co-Authored-By: Claude Opus 4.8 (1M context) --- .pql/changelog/ticket_history/2026-06.sql | 64 +++++++ .pql/changelog/tickets/2026-06.sql | 121 ++++++++++++ CHANGELOG.md | 5 + .../tickets/ticket-type-filters.json | 176 ++++++++++-------- .../tickets/ticket-type-filters.png | Bin 31826 -> 34164 bytes lib/builtin/tickets/src/tickets_view.dart | 131 ++++++++++++- test/builtin/tickets/tickets_view_test.dart | 63 +++++++ 7 files changed, 477 insertions(+), 83 deletions(-) diff --git a/.pql/changelog/ticket_history/2026-06.sql b/.pql/changelog/ticket_history/2026-06.sql index ebb67c00..24fcd052 100644 --- a/.pql/changelog/ticket_history/2026-06.sql +++ b/.pql/changelog/ticket_history/2026-06.sql @@ -3436,3 +3436,67 @@ SCOPE CLARIFICATION (2026-06-10, from user): Test additions: (a) two concurrent agents whose sidechain items interleave -> each agent''s run items land under its own card, none cross-attributed; (b) an unattributable sidechain item (no parent_tool_use_id, broken chain) is NOT swept into the nearest agent''s card; (c) regression: consecutive Bash/Read calls still form one Activity cluster.', NULL, '2026-06-10 15:28:52', '2026-06-10 15:28:52', '2026-06-10 15:28:52', NULL, '0352b24d88a4bf24ede628880fe9b502', 2) ON CONFLICT(hash) DO NOTHING; INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB4FDREHRYRR7B9ER72KCQKC', 'status', 'backlog', 'in_progress', NULL, '2026-06-10 15:54:23', '2026-06-10 15:54:23', '2026-06-10 15:54:23', NULL, '8061a1d449acdb1e467239de0dce5d1d', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB4FDREHRYRR7B9ER72KCQKC', 'status', 'in_progress', 'in_progress', NULL, '2026-06-10 16:02:55', '2026-06-10 16:02:55', '2026-06-10 16:02:55', NULL, '9c00a4d5fefdc3cf8f227b040bc78acb', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB4FDREHRYRR7B9ER72KCQKC', 'description', 'Add a row of type-filter toggle chips at the top of the tickets panel, directly below the "Filter tickets…" box (no section header — the chips read on their own). One chip per ticket type the user thinks in: Bug, Ticket, Epic, Initiative. All four ON by default. + +## Behaviour +- Single-click a chip → toggle that type in/out of the list. +- Double-click a chip → isolate (solo) that type: turns it ON and all others OFF. Double-click the same chip again → restore all to ON. This is the chart-legend solo pattern (Plotly/Tableau/Grafana) — learnable and fully reversible. +- Last-off resets to all-on: disabling the final remaining type snaps all chips back ON. An empty type filter means "no filter", so the list is never mysteriously blank. +- Tooltip per chip: "Click to toggle · double-click to isolate". + +## Type mapping (pql → chip) +pql ticket types are initiative, epic, story, task, bug (see lib/builtin/tickets/src/ticket_colors.dart). The four chips map as: +- Bug → bug +- Ticket → story + task (leaf work items) +- Epic → epic +- Initiative → initiative + +Each chip carries its type-colored dot + border using TicketTypeColors (bug #E87D7D, story/task green/grey, epic #78A0F8, initiative #C792EA). + +## Filtering +- Type filter is ANDed with the existing text filter in _TicketsViewState (lib/builtin/tickets/src/tickets_view.dart): a ticket shows only if its type is enabled AND it matches the text filter. +- When the type filter hides all items in a status section, that section collapses out (same as text-filter behaviour today). + +## Implementation notes +- Active-chip visual: filled tint + type-colored border (active) vs muted/no border (inactive) — reuse the _Toggle pattern from lib/builtin/search/src/search_panel_view.dart and ClideTappable. +- Persist nothing across sessions for v1 (always all-on on load); revisit if requested. +- a11y: Semantics(button, toggled) per chip, mirroring the search-panel toggle. + +## Wireframe +docs/design/wireframes/tickets/ticket-type-filters.json (+ .png export)', 'Add a row of type-filter toggle chips at the top of the tickets panel, directly below the "Filter tickets…" box (no section header — the chips read on their own). One chip per ticket type the user thinks in: Bug, Ticket, Epic, Initiative. All four ON by default. + +## Behaviour +- Single-click a chip → toggle that type in/out of the list. +- Double-click a chip → isolate (solo) that type: turns it ON and all others OFF. Double-click the same chip again → restore all to ON. This is the chart-legend solo pattern (Plotly/Tableau/Grafana) — learnable and fully reversible. +- Last-off resets to all-on: disabling the final remaining type snaps all chips back ON. An empty type filter means "no filter", so the list is never mysteriously blank. +- Tooltip per chip: "Click to toggle · double-click to isolate". + +## Type mapping (pql → chip) +pql ticket types are initiative, epic, story, task, bug (see lib/builtin/tickets/src/ticket_colors.dart). The four chips map as: +- Bug → bug +- Ticket → story + task (leaf work items) +- Epic → epic +- Initiative → initiative + +Each chip carries its type-colored dot + border using TicketTypeColors (bug #E87D7D, story/task green/grey, epic #78A0F8, initiative #C792EA). + +## Filtering +- Type filter is ANDed with the existing text filter in _TicketsViewState (lib/builtin/tickets/src/tickets_view.dart): a ticket shows only if its type is enabled AND it matches the text filter. +- When the type filter hides all items in a status section, that section collapses out (same as text-filter behaviour today). + +## Implementation notes +- Active-chip visual: filled tint + type-colored border (active) vs muted/no border (inactive) — reuse the _Toggle pattern from lib/builtin/search/src/search_panel_view.dart and ClideTappable. +- Persist nothing across sessions for v1 (always all-on on load); revisit if requested. +- a11y: Semantics(button, toggled) per chip, mirroring the search-panel toggle. + +## Wireframe +docs/design/wireframes/tickets/ticket-type-filters.json (+ .png export) + +Design revision (2026-06-10, supersedes the chip set/order above): FIVE chips, one per pql type — no story+task grouping. Ordered LARGE→SMALL left to right: Initiative, Epic, Story, Task, Bug. Each maps 1:1 to its pql type (initiative/epic/story/task/bug) with its TicketTypeColors dot+border (initiative #C792EA, epic #78A0F8, story #7DD3A8, task #9AA0AA grey, bug #E87D7D). All five ON by default. Toggle/solo(double-click)/last-off-reset behaviour unchanged. Wireframe updated + approved.', NULL, '2026-06-10 16:06:25', '2026-06-10 16:06:25', '2026-06-10 16:06:25', NULL, '26b5b4d5cf2a48fe6aeacd7b83968848', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5TWC00GW0P3X02HZW', 'status', 'backlog', 'ready', NULL, '2026-06-10 16:09:21', '2026-06-10 16:09:21', '2026-06-10 16:09:21', NULL, 'c89b4ddcbdeea95483c53b1b76220842', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5TWC00GW0P3X02HZW', 'status', 'ready', 'in_progress', NULL, '2026-06-10 16:09:26', '2026-06-10 16:09:26', '2026-06-10 16:09:26', NULL, 'c36e6343d44f59ff4af0eea3178b5a94', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5TWC00GW0P3X02HZW', 'status', 'in_progress', 'backlog', NULL, '2026-06-10 16:09:57', '2026-06-10 16:09:57', '2026-06-10 16:09:57', NULL, 'be22c7ff64f106d600b4a5bd70ced1d2', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB4J5E6W983P1S7BE0FDPSMM', 'status', 'backlog', 'ready', NULL, '2026-06-10 16:10:20', '2026-06-10 16:10:20', '2026-06-10 16:10:20', NULL, '6f4b3ce9e2167200ebdde0c1511d4a91', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB4J5E6W983P1S7BE0FDPSMM', 'status', 'ready', 'in_progress', NULL, '2026-06-10 16:10:22', '2026-06-10 16:10:22', '2026-06-10 16:10:22', NULL, 'cce7ce3f17ae087f753f4d6d072a702e', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB4FDREHRYRR7B9ER72KCQKC', 'status', 'in_progress', 'done', NULL, '2026-06-10 16:12:15', '2026-06-10 16:12:15', '2026-06-10 16:12:15', NULL, '4cb2daa831a115888a9456461bf53bdf', 2) ON CONFLICT(hash) DO NOTHING; diff --git a/.pql/changelog/tickets/2026-06.sql b/.pql/changelog/tickets/2026-06.sql index 3bef95e0..66fe8b9a 100644 --- a/.pql/changelog/tickets/2026-06.sql +++ b/.pql/changelog/tickets/2026-06.sql @@ -2959,3 +2959,124 @@ Each chip carries its type-colored dot + border using TicketTypeColors (bug #E87 ## Wireframe docs/design/wireframes/tickets/ticket-type-filters.json (+ .png export)', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-10 15:54:09', '2026-06-10 15:54:23', NULL, 'f5614424c00196aabab868db75483bf2', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB4FDREHRYRR7B9ER72KCQKC', 'story', '06FB0TNQM5TWC00GW0P3X02HZW', 'Ticket panel: per-type filter chips (Bug / Ticket / Epic / Initiative)', 'Add a row of type-filter toggle chips at the top of the tickets panel, directly below the "Filter tickets…" box (no section header — the chips read on their own). One chip per ticket type the user thinks in: Bug, Ticket, Epic, Initiative. All four ON by default. + +## Behaviour +- Single-click a chip → toggle that type in/out of the list. +- Double-click a chip → isolate (solo) that type: turns it ON and all others OFF. Double-click the same chip again → restore all to ON. This is the chart-legend solo pattern (Plotly/Tableau/Grafana) — learnable and fully reversible. +- Last-off resets to all-on: disabling the final remaining type snaps all chips back ON. An empty type filter means "no filter", so the list is never mysteriously blank. +- Tooltip per chip: "Click to toggle · double-click to isolate". + +## Type mapping (pql → chip) +pql ticket types are initiative, epic, story, task, bug (see lib/builtin/tickets/src/ticket_colors.dart). The four chips map as: +- Bug → bug +- Ticket → story + task (leaf work items) +- Epic → epic +- Initiative → initiative + +Each chip carries its type-colored dot + border using TicketTypeColors (bug #E87D7D, story/task green/grey, epic #78A0F8, initiative #C792EA). + +## Filtering +- Type filter is ANDed with the existing text filter in _TicketsViewState (lib/builtin/tickets/src/tickets_view.dart): a ticket shows only if its type is enabled AND it matches the text filter. +- When the type filter hides all items in a status section, that section collapses out (same as text-filter behaviour today). + +## Implementation notes +- Active-chip visual: filled tint + type-colored border (active) vs muted/no border (inactive) — reuse the _Toggle pattern from lib/builtin/search/src/search_panel_view.dart and ClideTappable. +- Persist nothing across sessions for v1 (always all-on on load); revisit if requested. +- a11y: Semantics(button, toggled) per chip, mirroring the search-panel toggle. + +## Wireframe +docs/design/wireframes/tickets/ticket-type-filters.json (+ .png export)', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-10 15:54:09', '2026-06-10 16:02:55', NULL, '83c5d82ad411e2827733928cdb81c6e0', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB4J5E6W983P1S7BE0FDPSMM', 'bug', '06FB0TNQM5TWC00GW0P3X02HZW', 'Conversation card font sizes inconsistent: ClideCollapserCard vs ConversationCard', 'Two collapsible-card primitives sit adjacent in the Claude conversation stream but use different font-size tokens for the same visual roles, so labels/summaries are visibly 1-2px off between neighbouring cards. + +- ClideCollapserCard (lib/widgets/src/clide_collapser_card.dart) — the ''Activity'' / agent-run / edit-run cards: header label, collapsedSummary, and counter all use clideFontCaption (14). +- ConversationCard (lib/builtin/claude/src/conversation_card.dart) — tool-use / result / ''denied'' cards: header label uses clideFontSmall (12), collapsedSummary uses clideFontMeta (13). + +Result: an ''Activity Bash …'' collapser and a neighbouring ''Bash · denied'' tool card render their label + collapsed summary at different sizes in the same list. Spotted in a screenshot of three stacked cards (collapser Bash, denied tool result, collapser Read). + +Fix: pick one set of tokens for the shared header/label/summary role across both primitives (likely align ConversationCard''s label to clideFontCaption and standardise the collapsed-summary size), or extract a shared card-header style. Tokens: clideFontCaption=14, clideFontMeta=13, clideFontSmall=12 (lib/widgets/src/typography.dart).', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-10 16:06:08', '2026-06-10 16:06:08', NULL, 'c858a9b7c474697611c3ff8240c61a2d', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB4FDREHRYRR7B9ER72KCQKC', 'story', '06FB0TNQM5TWC00GW0P3X02HZW', 'Ticket panel: per-type filter chips (Bug / Ticket / Epic / Initiative)', 'Add a row of type-filter toggle chips at the top of the tickets panel, directly below the "Filter tickets…" box (no section header — the chips read on their own). One chip per ticket type the user thinks in: Bug, Ticket, Epic, Initiative. All four ON by default. + +## Behaviour +- Single-click a chip → toggle that type in/out of the list. +- Double-click a chip → isolate (solo) that type: turns it ON and all others OFF. Double-click the same chip again → restore all to ON. This is the chart-legend solo pattern (Plotly/Tableau/Grafana) — learnable and fully reversible. +- Last-off resets to all-on: disabling the final remaining type snaps all chips back ON. An empty type filter means "no filter", so the list is never mysteriously blank. +- Tooltip per chip: "Click to toggle · double-click to isolate". + +## Type mapping (pql → chip) +pql ticket types are initiative, epic, story, task, bug (see lib/builtin/tickets/src/ticket_colors.dart). The four chips map as: +- Bug → bug +- Ticket → story + task (leaf work items) +- Epic → epic +- Initiative → initiative + +Each chip carries its type-colored dot + border using TicketTypeColors (bug #E87D7D, story/task green/grey, epic #78A0F8, initiative #C792EA). + +## Filtering +- Type filter is ANDed with the existing text filter in _TicketsViewState (lib/builtin/tickets/src/tickets_view.dart): a ticket shows only if its type is enabled AND it matches the text filter. +- When the type filter hides all items in a status section, that section collapses out (same as text-filter behaviour today). + +## Implementation notes +- Active-chip visual: filled tint + type-colored border (active) vs muted/no border (inactive) — reuse the _Toggle pattern from lib/builtin/search/src/search_panel_view.dart and ClideTappable. +- Persist nothing across sessions for v1 (always all-on on load); revisit if requested. +- a11y: Semantics(button, toggled) per chip, mirroring the search-panel toggle. + +## Wireframe +docs/design/wireframes/tickets/ticket-type-filters.json (+ .png export) + +Design revision (2026-06-10, supersedes the chip set/order above): FIVE chips, one per pql type — no story+task grouping. Ordered LARGE→SMALL left to right: Initiative, Epic, Story, Task, Bug. Each maps 1:1 to its pql type (initiative/epic/story/task/bug) with its TicketTypeColors dot+border (initiative #C792EA, epic #78A0F8, story #7DD3A8, task #9AA0AA grey, bug #E87D7D). All five ON by default. Toggle/solo(double-click)/last-off-reset behaviour unchanged. Wireframe updated + approved.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-10 15:54:09', '2026-06-10 16:06:25', NULL, '93554cd5afe5bd881b12bef2892e25b8', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5TWC00GW0P3X02HZW', 'epic', NULL, 'UI tweaks & fixes', 'Ongoing umbrella for small, standalone UI polish, cosmetic tweaks, and visual/interaction bug fixes that don''t belong to a feature epic — color/token corrections, control placement, status surfaces, micro-interactions, and the wireframes that frame them. Children are independently shippable; the epic stays open as a rolling home for this class of work. + +**PERMANENT — never close.** This is a standing rolling tracker for loose UI/UX work and bugs, not a deliverable epic. It stays open indefinitely; only its children are completed/closed. Do not mark T-276 done even when all current children are closed — new tweaks/fixes get filed here on an ongoing basis.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-08 07:46:23', '2026-06-10 16:09:21', NULL, 'd9e663378cc6deec9bc85f95f5552db8', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5TWC00GW0P3X02HZW', 'epic', NULL, 'UI tweaks & fixes', 'Ongoing umbrella for small, standalone UI polish, cosmetic tweaks, and visual/interaction bug fixes that don''t belong to a feature epic — color/token corrections, control placement, status surfaces, micro-interactions, and the wireframes that frame them. Children are independently shippable; the epic stays open as a rolling home for this class of work. + +**PERMANENT — never close.** This is a standing rolling tracker for loose UI/UX work and bugs, not a deliverable epic. It stays open indefinitely; only its children are completed/closed. Do not mark T-276 done even when all current children are closed — new tweaks/fixes get filed here on an ongoing basis.', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-08 07:46:23', '2026-06-10 16:09:26', NULL, 'fc45344f2d71e9b71573100825c2bce7', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5TWC00GW0P3X02HZW', 'epic', NULL, 'UI tweaks & fixes', 'Ongoing umbrella for small, standalone UI polish, cosmetic tweaks, and visual/interaction bug fixes that don''t belong to a feature epic — color/token corrections, control placement, status surfaces, micro-interactions, and the wireframes that frame them. Children are independently shippable; the epic stays open as a rolling home for this class of work. + +**PERMANENT — never close.** This is a standing rolling tracker for loose UI/UX work and bugs, not a deliverable epic. It stays open indefinitely; only its children are completed/closed. Do not mark T-276 done even when all current children are closed — new tweaks/fixes get filed here on an ongoing basis.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-08 07:46:23', '2026-06-10 16:09:57', NULL, 'a1ab5ae76714ede6d6a473db9b5d8aa9', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB4J5E6W983P1S7BE0FDPSMM', 'bug', '06FB0TNQM5TWC00GW0P3X02HZW', 'Conversation card font sizes inconsistent: ClideCollapserCard vs ConversationCard', 'Two collapsible-card primitives sit adjacent in the Claude conversation stream but use different font-size tokens for the same visual roles, so labels/summaries are visibly 1-2px off between neighbouring cards. + +- ClideCollapserCard (lib/widgets/src/clide_collapser_card.dart) — the ''Activity'' / agent-run / edit-run cards: header label, collapsedSummary, and counter all use clideFontCaption (14). +- ConversationCard (lib/builtin/claude/src/conversation_card.dart) — tool-use / result / ''denied'' cards: header label uses clideFontSmall (12), collapsedSummary uses clideFontMeta (13). + +Result: an ''Activity Bash …'' collapser and a neighbouring ''Bash · denied'' tool card render their label + collapsed summary at different sizes in the same list. Spotted in a screenshot of three stacked cards (collapser Bash, denied tool result, collapser Read). + +Fix: pick one set of tokens for the shared header/label/summary role across both primitives (likely align ConversationCard''s label to clideFontCaption and standardise the collapsed-summary size), or extract a shared card-header style. Tokens: clideFontCaption=14, clideFontMeta=13, clideFontSmall=12 (lib/widgets/src/typography.dart).', 'ready', 'medium', NULL, NULL, NULL, '2026-06-10 16:06:08', '2026-06-10 16:10:20', NULL, '140190d844d98929ecc83e30cdcc0745', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB4J5E6W983P1S7BE0FDPSMM', 'bug', '06FB0TNQM5TWC00GW0P3X02HZW', 'Conversation card font sizes inconsistent: ClideCollapserCard vs ConversationCard', 'Two collapsible-card primitives sit adjacent in the Claude conversation stream but use different font-size tokens for the same visual roles, so labels/summaries are visibly 1-2px off between neighbouring cards. + +- ClideCollapserCard (lib/widgets/src/clide_collapser_card.dart) — the ''Activity'' / agent-run / edit-run cards: header label, collapsedSummary, and counter all use clideFontCaption (14). +- ConversationCard (lib/builtin/claude/src/conversation_card.dart) — tool-use / result / ''denied'' cards: header label uses clideFontSmall (12), collapsedSummary uses clideFontMeta (13). + +Result: an ''Activity Bash …'' collapser and a neighbouring ''Bash · denied'' tool card render their label + collapsed summary at different sizes in the same list. Spotted in a screenshot of three stacked cards (collapser Bash, denied tool result, collapser Read). + +Fix: pick one set of tokens for the shared header/label/summary role across both primitives (likely align ConversationCard''s label to clideFontCaption and standardise the collapsed-summary size), or extract a shared card-header style. Tokens: clideFontCaption=14, clideFontMeta=13, clideFontSmall=12 (lib/widgets/src/typography.dart).', 'in_progress', 'medium', NULL, NULL, NULL, '2026-06-10 16:06:08', '2026-06-10 16:10:22', NULL, 'f02a03592e970322a82bf1be7002b7fa', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB4FDREHRYRR7B9ER72KCQKC', 'story', '06FB0TNQM5TWC00GW0P3X02HZW', 'Ticket panel: per-type filter chips (Bug / Ticket / Epic / Initiative)', 'Add a row of type-filter toggle chips at the top of the tickets panel, directly below the "Filter tickets…" box (no section header — the chips read on their own). One chip per ticket type the user thinks in: Bug, Ticket, Epic, Initiative. All four ON by default. + +## Behaviour +- Single-click a chip → toggle that type in/out of the list. +- Double-click a chip → isolate (solo) that type: turns it ON and all others OFF. Double-click the same chip again → restore all to ON. This is the chart-legend solo pattern (Plotly/Tableau/Grafana) — learnable and fully reversible. +- Last-off resets to all-on: disabling the final remaining type snaps all chips back ON. An empty type filter means "no filter", so the list is never mysteriously blank. +- Tooltip per chip: "Click to toggle · double-click to isolate". + +## Type mapping (pql → chip) +pql ticket types are initiative, epic, story, task, bug (see lib/builtin/tickets/src/ticket_colors.dart). The four chips map as: +- Bug → bug +- Ticket → story + task (leaf work items) +- Epic → epic +- Initiative → initiative + +Each chip carries its type-colored dot + border using TicketTypeColors (bug #E87D7D, story/task green/grey, epic #78A0F8, initiative #C792EA). + +## Filtering +- Type filter is ANDed with the existing text filter in _TicketsViewState (lib/builtin/tickets/src/tickets_view.dart): a ticket shows only if its type is enabled AND it matches the text filter. +- When the type filter hides all items in a status section, that section collapses out (same as text-filter behaviour today). + +## Implementation notes +- Active-chip visual: filled tint + type-colored border (active) vs muted/no border (inactive) — reuse the _Toggle pattern from lib/builtin/search/src/search_panel_view.dart and ClideTappable. +- Persist nothing across sessions for v1 (always all-on on load); revisit if requested. +- a11y: Semantics(button, toggled) per chip, mirroring the search-panel toggle. + +## Wireframe +docs/design/wireframes/tickets/ticket-type-filters.json (+ .png export) + +Design revision (2026-06-10, supersedes the chip set/order above): FIVE chips, one per pql type — no story+task grouping. Ordered LARGE→SMALL left to right: Initiative, Epic, Story, Task, Bug. Each maps 1:1 to its pql type (initiative/epic/story/task/bug) with its TicketTypeColors dot+border (initiative #C792EA, epic #78A0F8, story #7DD3A8, task #9AA0AA grey, bug #E87D7D). All five ON by default. Toggle/solo(double-click)/last-off-reset behaviour unchanged. Wireframe updated + approved.', 'done', 'medium', NULL, NULL, NULL, '2026-06-10 15:54:09', '2026-06-10 16:12:15', NULL, '3dd40475245455459d9f09d26ef34316', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash); diff --git a/CHANGELOG.md b/CHANGELOG.md index 005c40cd..fa03348b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,11 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit. ### Added +- **Per-type filter chips on the tickets panel.** A row of toggle chips + (Initiative · Epic · Story · Task · Bug, large→small) below the filter box. + Click a chip to toggle that type; double-click to isolate it (chart-legend + solo); disabling the last one snaps all back on. ANDed with the text filter. + All on by default. (T-343) - **VS Code keybinding preset.** A `vscode` keymap mapping VS Code's default shortcuts (Ctrl+P, Ctrl+Shift+P, Ctrl+B, Ctrl+J, Ctrl+`, zoom, …) to clide. Activate via the "Keymap: VS Code" command or `app.keymap.preset = vscode`. (T-64) diff --git a/docs/design/wireframes/tickets/ticket-type-filters.json b/docs/design/wireframes/tickets/ticket-type-filters.json index e25aafc9..32cfac99 100644 --- a/docs/design/wireframes/tickets/ticket-type-filters.json +++ b/docs/design/wireframes/tickets/ticket-type-filters.json @@ -42,82 +42,10 @@ "fontSize": 13 }, - "chip-bug": { - "type": "Rectangle", - "parent": "panel", - "left": 96, "top": 118, "width": 66, "height": 24, - "fillColor": "#211519", - "strokeColor": "#E87D7D", - "corners": [4, 4, 4, 4] - }, - "chip-bug-dot": { - "type": "Ellipse", - "parent": "chip-bug", - "left": 104, "top": 126, "width": 8, "height": 8, - "fillColor": "#E87D7D", - "strokeColor": "#E87D7D" - }, - "chip-bug-label": { - "type": "Text", - "parent": "chip-bug", - "left": 117, "top": 124, - "text": "Bug", - "fontColor": "#e8e8ee", - "fontSize": 11 - }, - - "chip-ticket": { - "type": "Rectangle", - "parent": "panel", - "left": 168, "top": 118, "width": 78, "height": 24, - "fillColor": "#15191d", - "strokeColor": "#7DD3A8", - "corners": [4, 4, 4, 4] - }, - "chip-ticket-dot": { - "type": "Ellipse", - "parent": "chip-ticket", - "left": 176, "top": 126, "width": 8, "height": 8, - "fillColor": "#7DD3A8", - "strokeColor": "#7DD3A8" - }, - "chip-ticket-label": { - "type": "Text", - "parent": "chip-ticket", - "left": 189, "top": 124, - "text": "Ticket", - "fontColor": "#e8e8ee", - "fontSize": 11 - }, - - "chip-epic": { - "type": "Rectangle", - "parent": "panel", - "left": 252, "top": 118, "width": 68, "height": 24, - "fillColor": "#14171f", - "strokeColor": "#78A0F8", - "corners": [4, 4, 4, 4] - }, - "chip-epic-dot": { - "type": "Ellipse", - "parent": "chip-epic", - "left": 260, "top": 126, "width": 8, "height": 8, - "fillColor": "#78A0F8", - "strokeColor": "#78A0F8" - }, - "chip-epic-label": { - "type": "Text", - "parent": "chip-epic", - "left": 273, "top": 124, - "text": "Epic", - "fontColor": "#e8e8ee", - "fontSize": 11 - }, - "chip-init": { "type": "Rectangle", "parent": "panel", - "left": 326, "top": 118, "width": 86, "height": 24, + "left": 96, "top": 118, "width": 86, "height": 24, "fillColor": "#1a151f", "strokeColor": "#C792EA", "corners": [4, 4, 4, 4] @@ -125,19 +53,115 @@ "chip-init-dot": { "type": "Ellipse", "parent": "chip-init", - "left": 334, "top": 126, "width": 8, "height": 8, + "left": 104, "top": 126, "width": 8, "height": 8, "fillColor": "#C792EA", "strokeColor": "#C792EA" }, "chip-init-label": { "type": "Text", "parent": "chip-init", - "left": 347, "top": 124, + "left": 117, "top": 124, "text": "Initiative", "fontColor": "#e8e8ee", "fontSize": 11 }, + "chip-epic": { + "type": "Rectangle", + "parent": "panel", + "left": 188, "top": 118, "width": 58, "height": 24, + "fillColor": "#14171f", + "strokeColor": "#78A0F8", + "corners": [4, 4, 4, 4] + }, + "chip-epic-dot": { + "type": "Ellipse", + "parent": "chip-epic", + "left": 196, "top": 126, "width": 8, "height": 8, + "fillColor": "#78A0F8", + "strokeColor": "#78A0F8" + }, + "chip-epic-label": { + "type": "Text", + "parent": "chip-epic", + "left": 209, "top": 124, + "text": "Epic", + "fontColor": "#e8e8ee", + "fontSize": 11 + }, + + "chip-story": { + "type": "Rectangle", + "parent": "panel", + "left": 252, "top": 118, "width": 62, "height": 24, + "fillColor": "#15191d", + "strokeColor": "#7DD3A8", + "corners": [4, 4, 4, 4] + }, + "chip-story-dot": { + "type": "Ellipse", + "parent": "chip-story", + "left": 260, "top": 126, "width": 8, "height": 8, + "fillColor": "#7DD3A8", + "strokeColor": "#7DD3A8" + }, + "chip-story-label": { + "type": "Text", + "parent": "chip-story", + "left": 273, "top": 124, + "text": "Story", + "fontColor": "#e8e8ee", + "fontSize": 11 + }, + + "chip-task": { + "type": "Rectangle", + "parent": "panel", + "left": 320, "top": 118, "width": 56, "height": 24, + "fillColor": "#17181a", + "strokeColor": "#9AA0AA", + "corners": [4, 4, 4, 4] + }, + "chip-task-dot": { + "type": "Ellipse", + "parent": "chip-task", + "left": 328, "top": 126, "width": 8, "height": 8, + "fillColor": "#9AA0AA", + "strokeColor": "#9AA0AA" + }, + "chip-task-label": { + "type": "Text", + "parent": "chip-task", + "left": 341, "top": 124, + "text": "Task", + "fontColor": "#e8e8ee", + "fontSize": 11 + }, + + "chip-bug": { + "type": "Rectangle", + "parent": "panel", + "left": 382, "top": 118, "width": 54, "height": 24, + "fillColor": "#211519", + "strokeColor": "#E87D7D", + "corners": [4, 4, 4, 4] + }, + "chip-bug-dot": { + "type": "Ellipse", + "parent": "chip-bug", + "left": 390, "top": 126, "width": 8, "height": 8, + "fillColor": "#E87D7D", + "strokeColor": "#E87D7D" + }, + "chip-bug-label": { + "type": "Text", + "parent": "chip-bug", + "left": 403, "top": 124, + "text": "Bug", + "fontColor": "#e8e8ee", + "fontSize": 11 + }, + "divider": { "type": "Line", "parent": "panel", @@ -222,7 +246,7 @@ "type": "Text", "parent": "panel", "left": 96, "top": 540, - "text": "All four type filters ON by default — tap a chip to toggle that type out.", + "text": "All five type filters ON by default — click a chip to toggle, double-click to isolate.", "fontColor": "#56565f", "fontSize": 10 } diff --git a/docs/design/wireframes/tickets/ticket-type-filters.png b/docs/design/wireframes/tickets/ticket-type-filters.png index e92fd3822697051d2757c70abf37c5ab5d2f7293..72a8d7e33b9e88a7cb8bc0a9854d590279eaf7b2 100644 GIT binary patch literal 34164 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
}}- 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 jfP*E@ literal 31826 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

9MH@SYdM2KvDt-+G_F&6aL?_GOvRnDaHr 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 E06>*6$p8QV diff --git a/lib/builtin/tickets/src/tickets_view.dart b/lib/builtin/tickets/src/tickets_view.dart index 5c735a1a..320fd2ce 100644 --- a/lib/builtin/tickets/src/tickets_view.dart +++ b/lib/builtin/tickets/src/tickets_view.dart @@ -21,6 +21,19 @@ class _TicketsViewState extends State { String? _focusedId; final _focusedKey = GlobalKey(); final Set _pinned = {'in_progress', 'ready', 'backlog'}; + + /// Type-filter chips (T-343), ordered large→small. Each maps 1:1 to a pql + /// ticket type; all on by default. An empty set never persists — toggling off + /// the last one snaps all back on, so the list is never mysteriously blank. + static const _allTypes = {'initiative', 'epic', 'story', 'task', 'bug'}; + static const _typeOrder = [ + ('initiative', 'Initiative'), + ('epic', 'Epic'), + ('story', 'Story'), + ('task', 'Task'), + ('bug', 'Bug'), + ]; + final Set _enabledTypes = {..._allTypes}; StreamSubscription? _focusSub; StreamSubscription? _schedulerSub; StreamSubscription? _changedSub; @@ -44,6 +57,31 @@ class _TicketsViewState extends State { }); } + /// Single-click a chip: toggle that type in/out. Removing the last enabled + /// type resets all back on (T-343). + void _toggleType(String type) { + setState(() { + if (_enabledTypes.contains(type)) { + _enabledTypes.remove(type); + if (_enabledTypes.isEmpty) _enabledTypes.addAll(_allTypes); + } else { + _enabledTypes.add(type); + } + }); + } + + /// Double-click a chip: isolate (solo) that type — it on, all others off. + /// Double-clicking the already-soloed chip restores all-on (chart-legend + /// solo pattern, T-343). + void _soloType(String type) { + setState(() { + final soloed = _enabledTypes.length == 1 && _enabledTypes.contains(type); + _enabledTypes + ..clear() + ..addAll(soloed ? _allTypes : {type}); + }); + } + static String _sectionForStatus(String? status) => status ?? 'backlog'; void _onFocus(Message msg) { @@ -132,12 +170,14 @@ class _TicketsViewState extends State { if (_tickets.isEmpty) return const Padding(padding: EdgeInsets.all(12), child: ClideText('No tickets.\nRun `pql ticket new` to create one.', muted: true)); final lf = _filter.toLowerCase(); - final hasFilter = lf.isNotEmpty; - final filtered = hasFilter - ? _tickets - .where((t) => t.id.toLowerCase().contains(lf) || t.title.toLowerCase().contains(lf) || (t.status ?? '').contains(lf) || (t.type ?? '').contains(lf)) - .toList() - : _tickets; + final hasTextFilter = lf.isNotEmpty; + // All-on = "no type filter" (so a full set never hides null-type tickets). + final allTypesOn = _enabledTypes.length == _allTypes.length; + final filtering = hasTextFilter || !allTypesOn; + bool textMatch(_TicketEntry t) => + t.id.toLowerCase().contains(lf) || t.title.toLowerCase().contains(lf) || (t.status ?? '').contains(lf) || (t.type ?? '').contains(lf); + bool typeMatch(_TicketEntry t) => allTypesOn || _enabledTypes.contains(t.type); + final filtered = _tickets.where((t) => (!hasTextFilter || textMatch(t)) && typeMatch(t)).toList(); const sections = [ ('in_progress', 'IN PROGRESS'), @@ -172,6 +212,25 @@ class _TicketsViewState extends State { ), ], ), + // Per-type filter chips (T-343): large→small, all on by default. + Padding( + padding: const EdgeInsets.fromLTRB(8, 2, 8, 6), + child: Wrap( + spacing: 6, + runSpacing: 4, + children: [ + for (final (type, label) in _typeOrder) + _TypeChip( + label: label, + color: typeColors.forType(type), + active: _enabledTypes.contains(type), + onToggle: () => _toggleType(type), + onSolo: () => _soloType(type), + tokens: tokens, + ), + ], + ), + ), Expanded( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), @@ -183,7 +242,7 @@ class _TicketsViewState extends State { ClideAccordion( label: label, count: items.length, - expanded: hasFilter || _isSectionExpanded(status), + expanded: filtering || _isSectionExpanded(status), onToggle: () => _toggle(status), children: [ for (final t in items) @@ -205,6 +264,64 @@ class _TicketsViewState extends State { } } +/// A type-filter chip (T-343): a type-colored dot + label. Active = filled +/// tint + colored border; inactive = muted, no fill. Single-click toggles the +/// type; double-click isolates it (chart-legend solo). One [GestureDetector] +/// owns both so Flutter disambiguates single vs double. +class _TypeChip extends StatelessWidget { + const _TypeChip({ + required this.label, + required this.color, + required this.active, + required this.onToggle, + required this.onSolo, + required this.tokens, + }); + + final String label; + final Color color; + final bool active; + final VoidCallback onToggle; + final VoidCallback onSolo; + final SurfaceTokens tokens; + + @override + Widget build(BuildContext context) { + final dotColor = active ? color : tokens.globalTextMuted; + return Semantics( + button: true, + toggled: active, + label: '$label type filter', + child: ClideTooltip( + message: 'Click to toggle · double-click to isolate', + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: onToggle, + onDoubleTap: onSolo, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2), + decoration: BoxDecoration( + color: active ? color.withAlpha(0x22) : null, + border: Border.all(color: active ? color : tokens.buttonBorder), + borderRadius: BorderRadius.circular(4), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container(width: 8, height: 8, decoration: BoxDecoration(color: dotColor, shape: BoxShape.circle)), + const SizedBox(width: 6), + ClideText(label, fontSize: clideFontSmall, fontFamily: clideMonoFamily, color: active ? tokens.globalForeground : tokens.globalTextMuted), + ], + ), + ), + ), + ), + ), + ); + } +} + class _TicketEntry { const _TicketEntry({required this.id, required this.title, this.type, this.status, this.priority, this.parentId}); final String id; diff --git a/test/builtin/tickets/tickets_view_test.dart b/test/builtin/tickets/tickets_view_test.dart index e6161f0b..6a6889a6 100644 --- a/test/builtin/tickets/tickets_view_test.dart +++ b/test/builtin/tickets/tickets_view_test.dart @@ -136,4 +136,67 @@ void main() { await pumpAsync(tester); expect(calls, greaterThan(before)); }); + + // -- Type-filter chips (T-343) ------------------------------------------ + // The chips own one GestureDetector for both onTap (toggle) and onDoubleTap + // (solo), so a single tap's onTap only fires after the ~300ms double-tap + // window — hence the 350ms pumps below. + + Future loadTwoTypes(WidgetTester tester) async { + f.ipc.stub( + 'pql.tickets.list', + (_) async => _list([ + _t('T-1', 'a bug item', 'backlog', type: 'bug'), + _t('T-2', 'a task item', 'backlog', type: 'task'), + ])); + await pumpView(tester); + } + + testWidgets('renders a chip per type, large→small (T-343)', (tester) async { + f.ipc.stub('pql.tickets.list', (_) async => _list([_t('T-1', 'x', 'backlog')])); + await pumpView(tester); + for (final label in ['Initiative', 'Epic', 'Story', 'Task', 'Bug']) { + expect(find.text(label), findsOneWidget); + } + }); + + testWidgets('single-click toggles a type out, others stay (T-343)', (tester) async { + await loadTwoTypes(tester); + expect(find.text('a bug item'), findsOneWidget); + + await tester.tap(find.text('Bug')); + await tester.pump(const Duration(milliseconds: 350)); + + expect(find.text('a bug item'), findsNothing); + expect(find.text('a task item'), findsOneWidget); + }); + + testWidgets('double-click isolates a type (solo) (T-343)', (tester) async { + await loadTwoTypes(tester); + + await tester.tap(find.text('Bug')); + await tester.pump(const Duration(milliseconds: 50)); + await tester.tap(find.text('Bug')); + await tester.pump(const Duration(milliseconds: 350)); + + expect(find.text('a bug item'), findsOneWidget); + expect(find.text('a task item'), findsNothing); + }); + + testWidgets('toggling off the last enabled type resets all on (T-343)', (tester) async { + await loadTwoTypes(tester); + + // Solo Bug → task hidden. + await tester.tap(find.text('Bug')); + await tester.pump(const Duration(milliseconds: 50)); + await tester.tap(find.text('Bug')); + await tester.pump(const Duration(milliseconds: 350)); + expect(find.text('a task item'), findsNothing); + + // Toggle the only-remaining Bug off → snaps all back on → task returns. + await tester.tap(find.text('Bug')); + await tester.pump(const Duration(milliseconds: 350)); + expect(find.text('a task item'), findsOneWidget); + expect(find.text('a bug item'), findsOneWidget); + }); }