From a056fc841e24c111717830aa42e3707287b2096c Mon Sep 17 00:00:00 2001 From: corpi Date: Thu, 5 Mar 2026 17:03:00 +0900 Subject: [PATCH] =?UTF-8?q?refactor(feedback):=20Focus=20=ED=86=A0?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=EC=83=81=EB=8B=A8=20=EC=A4=91?= =?UTF-8?q?=EC=95=99=20=EB=8B=A8=EC=9D=BC=20=EC=B1=84=EB=84=90=EB=A1=9C=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 맥락: - Focus 화면에서 토스트 위치가 Notes 저장/Goal 완료 상황마다 달라져 가독성과 일관성이 떨어졌습니다. 변경사항: - HUD 내부 status line 렌더를 제거하고 상단 중앙 고정 토스트 컴포넌트를 추가했습니다. - /space 루트에서 activeStatus를 상단 중앙 토스트로만 표시하도록 피드백 채널을 단일화했습니다. - Undo 액션은 상단 중앙 토스트 내부 링크로 동일하게 노출되도록 유지했습니다. 검증: - npx tsc --noEmit 세션-상태: Focus 피드백 채널이 상단 중앙 1곳으로 통일됨 세션-다음: work.md 작업 1부터 Pro/플랜 잠금 정책 재구성 진행 세션-리스크: 기존 lint 규칙의 set-state-in-effect 오류는 별도 축으로 남아 있음 --- .../ui/SpaceFocusHudWidget.tsx | 15 +------ .../ui/SpaceTimerHudWidget.tsx | 31 ------------- .../space-workspace/ui/FocusTopToast.tsx | 44 +++++++++++++++++++ .../ui/SpaceWorkspaceWidget.tsx | 10 ++++- 4 files changed, 53 insertions(+), 47 deletions(-) create mode 100644 src/widgets/space-workspace/ui/FocusTopToast.tsx diff --git a/src/widgets/space-focus-hud/ui/SpaceFocusHudWidget.tsx b/src/widgets/space-focus-hud/ui/SpaceFocusHudWidget.tsx index 65d207f..7bfb307 100644 --- a/src/widgets/space-focus-hud/ui/SpaceFocusHudWidget.tsx +++ b/src/widgets/space-focus-hud/ui/SpaceFocusHudWidget.tsx @@ -1,5 +1,5 @@ import { useCallback, useEffect, useRef, useState } from 'react'; -import type { HudStatusLineItem, HudStatusLinePayload } from '@/shared/lib/useHudStatusLine'; +import type { HudStatusLinePayload } from '@/shared/lib/useHudStatusLine'; import { useReducedMotion } from '@/shared/lib/useReducedMotion'; import { SpaceTimerHudWidget } from '@/widgets/space-timer-hud'; import { GoalCompleteSheet } from './GoalCompleteSheet'; @@ -10,8 +10,6 @@ interface SpaceFocusHudWidgetProps { timerLabel: string; visible: boolean; onGoalUpdate: (nextGoal: string) => void; - statusLine: HudStatusLineItem | null; - onStatusAction: () => void; onStatusMessage: (payload: HudStatusLinePayload) => void; } @@ -20,8 +18,6 @@ export const SpaceFocusHudWidget = ({ timerLabel, visible, onGoalUpdate, - statusLine, - onStatusAction, onStatusMessage, }: SpaceFocusHudWidgetProps) => { const reducedMotion = useReducedMotion(); @@ -100,18 +96,9 @@ export const SpaceFocusHudWidget = ({ { if (reducedMotion) { playbackStateRef.current = state; diff --git a/src/widgets/space-timer-hud/ui/SpaceTimerHudWidget.tsx b/src/widgets/space-timer-hud/ui/SpaceTimerHudWidget.tsx index 91fffc2..d662968 100644 --- a/src/widgets/space-timer-hud/ui/SpaceTimerHudWidget.tsx +++ b/src/widgets/space-timer-hud/ui/SpaceTimerHudWidget.tsx @@ -12,13 +12,8 @@ interface SpaceTimerHudWidgetProps { goal: string; className?: string; isImmersionMode?: boolean; - statusLine?: { - message: string; - actionLabel?: string; - } | null; onPlaybackStateChange?: (state: 'running' | 'paused') => void; onGoalCompleteRequest?: () => void; - onStatusAction?: () => void; } const HUD_ACTIONS = [ @@ -32,10 +27,8 @@ export const SpaceTimerHudWidget = ({ goal, className, isImmersionMode = false, - statusLine = null, onPlaybackStateChange, onGoalCompleteRequest, - onStatusAction, }: SpaceTimerHudWidgetProps) => { const { isBreatheMode, triggerRestart } = useRestart30s(); const normalizedGoal = goal.trim().length > 0 ? goal.trim() : '이번 한 조각을 설정해 주세요.'; @@ -132,30 +125,6 @@ export const SpaceTimerHudWidget = ({ /> -
-
- {statusLine?.message ?? ''} - {statusLine?.actionLabel ? ( - - ) : null} -
-
); diff --git a/src/widgets/space-workspace/ui/FocusTopToast.tsx b/src/widgets/space-workspace/ui/FocusTopToast.tsx new file mode 100644 index 0000000..a225d91 --- /dev/null +++ b/src/widgets/space-workspace/ui/FocusTopToast.tsx @@ -0,0 +1,44 @@ +import { cn } from '@/shared/lib/cn'; + +interface FocusTopToastProps { + visible: boolean; + message: string; + actionLabel?: string; + onAction?: () => void; +} + +export const FocusTopToast = ({ + visible, + message, + actionLabel, + onAction, +}: FocusTopToastProps) => { + return ( +
+
+ {message} + {actionLabel ? ( + + ) : null} +
+
+ ); +}; diff --git a/src/widgets/space-workspace/ui/SpaceWorkspaceWidget.tsx b/src/widgets/space-workspace/ui/SpaceWorkspaceWidget.tsx index 1118f11..b7111e4 100644 --- a/src/widgets/space-workspace/ui/SpaceWorkspaceWidget.tsx +++ b/src/widgets/space-workspace/ui/SpaceWorkspaceWidget.tsx @@ -20,6 +20,7 @@ import { useHudStatusLine } from '@/shared/lib/useHudStatusLine'; import { SpaceFocusHudWidget } from '@/widgets/space-focus-hud'; import { SpaceSetupDrawerWidget } from '@/widgets/space-setup-drawer'; import { SpaceToolsDockWidget } from '@/widgets/space-tools-dock'; +import { FocusTopToast } from './FocusTopToast'; type WorkspaceMode = 'setup' | 'focus'; type SelectionOverride = { @@ -378,8 +379,6 @@ export const SpaceWorkspaceWidget = () => { goal={goalInput.trim()} timerLabel={selectedTimerLabel} visible={isFocusMode} - statusLine={activeStatus} - onStatusAction={runActiveAction} onStatusMessage={pushStatusLine} onGoalUpdate={(nextGoal) => { setGoalInput(nextGoal); @@ -387,6 +386,13 @@ export const SpaceWorkspaceWidget = () => { }} /> + +