From a40fef5c94edbbd06385e76f4dba6617544e71b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Flor=C3=AAncio?= Date: Sun, 23 Aug 2026 17:39:09 +0000 Subject: [PATCH] feat: add quick chat activity indicators (#2952) --- .../app-sidebar/app-sidebar-nav-item.tsx | 19 +-- .../app-sidebar-new-task-item.test.tsx | 54 ++++++ .../app-sidebar/app-sidebar-new-task-item.tsx | 25 +-- .../app-sidebar-primary-nav.test.tsx | 45 ++++- .../app-sidebar/app-sidebar-primary-nav.tsx | 11 +- .../kanban/kanban-header-mobile.tsx | 15 +- apps/web/components/kanban/kanban-header.tsx | 18 +- .../quick-chat-activity-indicator.test.tsx | 25 +++ .../quick-chat-activity-indicator.tsx | 29 ++++ .../quick-chat/quick-chat-modal.test.tsx | 43 ++++- .../quick-chat/quick-chat-modal.tsx | 31 +++- .../quick-chat/quick-chat-tab-item.test.tsx | 14 ++ .../quick-chat/quick-chat-tab-item.tsx | 4 + .../quick-chat/use-quick-chat-activity.ts | 23 +++ .../task/mobile/quick-chat-sheet-button.tsx | 17 +- .../chat/mobile-quick-chat-idle-dot.spec.ts | 83 +++++++--- .../chat/mobile-quick-chat-repository.spec.ts | 10 +- apps/web/e2e/tests/chat/quick-chat-helpers.ts | 50 ++++++ .../tests/chat/quick-chat-idle-dot.spec.ts | 71 +++++--- .../domains/session/use-session-state.ts | 3 +- apps/web/lib/session-working.test.ts | 34 ++++ apps/web/lib/session-working.ts | 13 ++ .../ui/quick-chat-activity-selectors.test.ts | 156 ++++++++++++++++++ .../ui/quick-chat-activity-selectors.ts | 32 ++++ apps/web/src/locales/en/sidebar.json | 1 + apps/web/src/locales/pseudo/sidebar.json | 1 + apps/web/src/locales/pt-pt/sidebar.json | 1 + apps/web/src/locales/zh-cn/sidebar.json | 1 + apps/web/src/locales/zh-hk/sidebar.json | 1 + apps/web/src/locales/zh-tw/sidebar.json | 1 + .../quick-chat-activity-indicators/plan.md | 115 +++++++++++++ .../task-01-activity-selectors.md | 47 ++++++ .../task-02-activity-ui.md | 70 ++++++++ .../task-03-activity-e2e.md | 43 +++++ docs/specs/INDEX.md | 2 +- docs/specs/quick-chat-idle-dot/spec.md | 153 ++++++----------- 36 files changed, 1027 insertions(+), 234 deletions(-) create mode 100644 apps/web/components/quick-chat/quick-chat-activity-indicator.test.tsx create mode 100644 apps/web/components/quick-chat/quick-chat-activity-indicator.tsx create mode 100644 apps/web/components/quick-chat/use-quick-chat-activity.ts create mode 100644 apps/web/lib/session-working.test.ts create mode 100644 apps/web/lib/session-working.ts create mode 100644 apps/web/lib/state/slices/ui/quick-chat-activity-selectors.test.ts create mode 100644 apps/web/lib/state/slices/ui/quick-chat-activity-selectors.ts create mode 100644 docs/plans/quick-chat-activity-indicators/plan.md create mode 100644 docs/plans/quick-chat-activity-indicators/task-01-activity-selectors.md create mode 100644 docs/plans/quick-chat-activity-indicators/task-02-activity-ui.md create mode 100644 docs/plans/quick-chat-activity-indicators/task-03-activity-e2e.md diff --git a/apps/web/components/app-sidebar/app-sidebar-nav-item.tsx b/apps/web/components/app-sidebar/app-sidebar-nav-item.tsx index 24b09c10d2..2247e7c5e1 100644 --- a/apps/web/components/app-sidebar/app-sidebar-nav-item.tsx +++ b/apps/web/components/app-sidebar/app-sidebar-nav-item.tsx @@ -6,6 +6,8 @@ import type { DestinationIcon } from "@/lib/navigation/types"; import { Badge } from "@kandev/ui/badge"; import { Tooltip, TooltipContent, TooltipTrigger } from "@kandev/ui/tooltip"; import { cn } from "@/lib/utils"; +import { QuickChatActivityIndicator } from "@/components/quick-chat/quick-chat-activity-indicator"; +import type { QuickChatActivityState } from "@/lib/state/slices/ui/quick-chat-activity-selectors"; import { SIDEBAR_ITEM_ACTIVE, SIDEBAR_ITEM_INACTIVE } from "./app-sidebar-constants"; type AppSidebarNavItemProps = { @@ -14,7 +16,7 @@ type AppSidebarNavItemProps = { href?: string; badge?: number; badgeVariant?: "primary" | "muted"; - dot?: boolean; + activity?: QuickChatActivityState; onClick?: () => void; collapsed: boolean; /** Override the auto-derived active-state from pathname. */ @@ -77,17 +79,6 @@ function isPathActive(pathname: string, href: string | undefined, exactMatch: bo return hrefPathname !== "/" && pathname.startsWith(`${hrefPathname}/`); } -function renderUnseenDot(dot: boolean) { - if (!dot) return null; - return ( -