From 4502f29a04824c0b14a62c54374d7f17885129e4 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Wed, 14 Oct 2026 00:05:23 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20=EC=84=B8?= =?UTF-8?q?=EC=85=98=20=ED=83=80=EC=9E=84=EB=9D=BC=EC=9D=B8=20=EB=A6=AC?= =?UTF-8?q?=EB=B3=B8=EC=97=90=20=ED=82=A4=EB=B3=B4=EB=93=9C=20=ED=8F=AC?= =?UTF-8?q?=EC=BB=A4=EC=8A=A4=20=ED=88=B4=ED=8C=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/palette.md | 3 ++ .../dashboard/session-activity-ribbon.tsx | 29 +++++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 .jules/palette.md diff --git a/.jules/palette.md b/.jules/palette.md new file mode 100644 index 00000000..89cec960 --- /dev/null +++ b/.jules/palette.md @@ -0,0 +1,3 @@ +## 2023-10-13 - 세션 타임라인 리본 툴팁 키보드 접근성 개선 +**Learning:** 마우스 호버(`onMouseEnter`, `onMouseMove`)에만 의존하여 툴팁을 띄우는 커스텀 UI 패턴에서는 키보드 사용자(Tab 키 이동)가 정보를 전혀 확인할 수 없는 문제가 발생할 수 있습니다. +**Action:** 인터랙티브 요소(버튼 등)에 호버 액션을 추가할 때는 항상 `onFocus`와 `onBlur`를 병행하여 키보드 포커스 시에도 동일한 컨텍스트(툴팁 등)가 제공되도록 설계해야 합니다. diff --git a/packages/web/src/components/dashboard/session-activity-ribbon.tsx b/packages/web/src/components/dashboard/session-activity-ribbon.tsx index 12228af3..bc2c5b94 100644 --- a/packages/web/src/components/dashboard/session-activity-ribbon.tsx +++ b/packages/web/src/components/dashboard/session-activity-ribbon.tsx @@ -158,13 +158,21 @@ export function SessionActivityRibbon({ const [hover, setHover] = useState(null) const containerRef = useRef(null) - const trackMouse = useCallback((e: React.MouseEvent) => { + const trackMouse = useCallback((e: React.MouseEvent | React.FocusEvent) => { const rect = containerRef.current?.getBoundingClientRect() - if (!rect) return e.clientX - return e.clientX - rect.left + if (!rect) { + if ('clientX' in e) return e.clientX + return 0 + } + if ('clientX' in e) { + return e.clientX - rect.left + } + // For focus events, calculate the center of the focused element relative to the container + const targetRect = (e.target as HTMLElement).getBoundingClientRect() + return targetRect.left - rect.left + targetRect.width / 2 }, []) - const handleEventHover = useCallback((idx: number) => (e: React.MouseEvent) => { + const handleEventHover = useCallback((idx: number) => (e: React.MouseEvent | React.FocusEvent) => { setHover({ kind: 'event', idx, x: trackMouse(e) }) }, [trackMouse]) @@ -173,7 +181,7 @@ export function SessionActivityRibbon({ toolName: string, count: number, firstEvent: TimelineEvent, - ) => (e: React.MouseEvent) => { + ) => (e: React.MouseEvent | React.FocusEvent) => { setHover({ kind: 'merged', firstIdx, @@ -200,6 +208,8 @@ export function SessionActivityRibbon({ onMouseEnter={handleEventHover(idx)} onMouseMove={handleEventHover(idx)} onMouseLeave={() => setHover(null)} + onFocus={handleEventHover(idx)} + onBlur={() => setHover(null)} aria-label={`Event ${idx + 1}`} aria-current={selected ? 'true' : undefined} className={`h-full ${bg} transition-opacity ${ @@ -225,6 +235,8 @@ export function SessionActivityRibbon({ onMouseEnter={handleEventHover(idx)} onMouseMove={handleEventHover(idx)} onMouseLeave={() => setHover(null)} + onFocus={handleEventHover(idx)} + onBlur={() => setHover(null)} aria-label={`Event ${idx + 1}`} aria-current={selected ? 'true' : undefined} className={`h-full ${bg} transition-opacity ${ @@ -288,6 +300,13 @@ export function SessionActivityRibbon({ group.items[0].event, )} onMouseLeave={() => setHover(null)} + onFocus={handleMergedHover( + firstIdx, + group.toolName, + group.items.length, + group.items[0].event, + )} + onBlur={() => setHover(null)} aria-label={`Expand ${group.toolName} group, ${group.items.length} events`} className={`relative h-full ${segmentVisuals(group.items[0].event).bg} transition-opacity hover:opacity-70`} > From 72ab9e0f38543d8c6dc73ab9c4f51e4be8210bf9 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Wed, 14 Oct 2026 00:11:33 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20=EC=84=B8?= =?UTF-8?q?=EC=85=98=20=ED=83=80=EC=9E=84=EB=9D=BC=EC=9D=B8=20=EB=A6=AC?= =?UTF-8?q?=EB=B3=B8=EC=97=90=20=ED=82=A4=EB=B3=B4=EB=93=9C=20=ED=8F=AC?= =?UTF-8?q?=EC=BB=A4=EC=8A=A4=20=ED=88=B4=ED=8C=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit