From 627bd827068d7163ad87784b4f23a972af3e482f Mon Sep 17 00:00:00 2001 From: corpi Date: Mon, 16 Mar 2026 15:37:58 +0900 Subject: [PATCH] =?UTF-8?q?fix(space):=20stale=20hud=20=EC=B0=B8=EC=A1=B0?= =?UTF-8?q?=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/SpaceFocusHudWidget.tsx | 151 +++--------------- .../ui/SpaceWorkspaceWidget.tsx | 80 +--------- 2 files changed, 27 insertions(+), 204 deletions(-) diff --git a/src/widgets/space-focus-hud/ui/SpaceFocusHudWidget.tsx b/src/widgets/space-focus-hud/ui/SpaceFocusHudWidget.tsx index 2652938..e5f24ed 100644 --- a/src/widgets/space-focus-hud/ui/SpaceFocusHudWidget.tsx +++ b/src/widgets/space-focus-hud/ui/SpaceFocusHudWidget.tsx @@ -2,10 +2,10 @@ import { useEffect, useRef, useState } from 'react'; import { copy } from '@/shared/i18n'; import { cn } from '@/shared/lib/cn'; import type { HudStatusLinePayload } from '@/shared/lib/useHudStatusLine'; -import { SpaceTimerHudWidget } from '@/widgets/space-timer-hud'; +import { ExitHoldButton } from '@/features/exit-hold'; import { GoalCompleteSheet } from './GoalCompleteSheet'; import { InlineMicrostep } from './InlineMicrostep'; -import { ReturnPrompt } from './ReturnPrompt'; +import { ThoughtOrb } from './ThoughtOrb'; interface SpaceFocusHudWidgetProps { goal: string; @@ -14,21 +14,12 @@ interface SpaceFocusHudWidgetProps { hasActiveSession?: boolean; playbackState?: 'running' | 'paused'; sessionPhase?: 'focus' | 'break' | null; - isSessionActionPending?: boolean; - canStartSession?: boolean; - canPauseSession?: boolean; - canRestartSession?: boolean; - entryOverlayIntent?: 'resume-refocus' | null; - returnPromptMode?: 'focus' | 'break' | null; - onEntryOverlayIntentHandled?: () => void; - onStartRequested?: () => void; - onPauseRequested?: () => void; - onRestartRequested?: () => void; - onDismissReturnPrompt?: () => void; onIntentUpdate: (payload: { goal?: string; microStep?: string | null }) => boolean | Promise; onGoalUpdate: (nextGoal: string) => boolean | Promise; onGoalFinish: () => boolean | Promise; onStatusMessage: (payload: HudStatusLinePayload) => void; + onCaptureThought: (note: string) => void; + onExitRequested: () => void; } export const SpaceFocusHudWidget = ({ @@ -38,42 +29,20 @@ export const SpaceFocusHudWidget = ({ hasActiveSession = false, playbackState = 'paused', sessionPhase = 'focus', - isSessionActionPending = false, - canStartSession = false, - canPauseSession = false, - canRestartSession = false, - entryOverlayIntent = null, - returnPromptMode = null, - onEntryOverlayIntentHandled, - onStartRequested, - onPauseRequested, - onRestartRequested, - onDismissReturnPrompt, onIntentUpdate, onGoalUpdate, onGoalFinish, onStatusMessage, + onCaptureThought, + onExitRequested, }: SpaceFocusHudWidgetProps) => { - const [overlay, setOverlay] = useState<'none' | 'return' | 'complete'>('none'); + const [overlay, setOverlay] = useState<'none' | 'complete'>('none'); const [completePreferredView, setCompletePreferredView] = useState<'choice' | 'next'>('choice'); const [isSavingIntent, setSavingIntent] = useState(false); const visibleRef = useRef(false); - const resumePlaybackStateRef = useRef<'running' | 'paused'>(playbackState); - const restReminderTimerRef = useRef(null); const normalizedGoal = goal.trim().length > 0 ? goal.trim() : copy.space.focusHud.goalFallback; - const isReturnPromptOpen = overlay === 'return'; const isCompleteOpen = overlay === 'complete'; - const isIntentOverlayOpen = overlay !== 'none'; - - useEffect(() => { - return () => { - if (restReminderTimerRef.current) { - window.clearTimeout(restReminderTimerRef.current); - restReminderTimerRef.current = null; - } - }; - }, []); useEffect(() => { if (!hasActiveSession) { @@ -83,21 +52,6 @@ export const SpaceFocusHudWidget = ({ } }, [hasActiveSession]); - useEffect(() => { - if (!returnPromptMode) { - if (overlay === 'return') { - setOverlay('none'); - } - return; - } - - if (overlay === 'complete') { - return; - } - - setOverlay('return'); - }, [overlay, returnPromptMode]); - useEffect(() => { if (!visibleRef.current && playbackState === 'running') { onStatusMessage({ @@ -108,36 +62,11 @@ export const SpaceFocusHudWidget = ({ visibleRef.current = true; }, [normalizedGoal, onStatusMessage, playbackState]); - useEffect(() => { - if (resumePlaybackStateRef.current === 'paused' && playbackState === 'running') { - onStatusMessage({ - message: copy.space.focusHud.goalToast(normalizedGoal), - }); - } - - resumePlaybackStateRef.current = playbackState; - }, [normalizedGoal, onStatusMessage, playbackState]); - - useEffect(() => { - if (entryOverlayIntent !== 'resume-refocus' || !hasActiveSession || overlay !== 'none') { - return; - } - // With inline microsteps, we just handle the intent and let the user click if they want. - onEntryOverlayIntentHandled?.(); - }, [entryOverlayIntent, hasActiveSession, onEntryOverlayIntentHandled, overlay]); - const handleOpenCompleteSheet = (preferredView: 'choice' | 'next' = 'choice') => { setCompletePreferredView(preferredView); setOverlay('complete'); }; - const handleDismissReturnPrompt = () => { - onDismissReturnPrompt?.(); - if (overlay === 'return') { - setOverlay('none'); - } - }; - const handleInlineMicrostepUpdate = async (nextStep: string | null) => { if (isSavingIntent) return false; @@ -159,18 +88,19 @@ export const SpaceFocusHudWidget = ({ return ( <> + +
{/* The Monolith (Central Hub) */}
- {/* Massive Central Timer */} -
playbackState === 'running' ? onPauseRequested?.() : onStartRequested?.()}> + {/* Massive Unstoppable Timer */} +

{timeDisplay}

@@ -206,28 +136,6 @@ export const SpaceFocusHudWidget = ({
- { - handleDismissReturnPrompt(); - }} - onRefocus={() => { - handleDismissReturnPrompt(); - }} - onRest={() => { - handleDismissReturnPrompt(); - }} - onNextGoal={() => { - handleDismissReturnPrompt(); - handleOpenCompleteSheet('next'); - }} - onFinish={() => { - handleDismissReturnPrompt(); - handleOpenCompleteSheet('choice'); - }} - /> Promise.resolve(onGoalFinish())} onRest={() => { setOverlay('none'); - onPauseRequested?.(); - - if (restReminderTimerRef.current) { - window.clearTimeout(restReminderTimerRef.current); - } - - restReminderTimerRef.current = window.setTimeout(() => { - onStatusMessage({ message: copy.space.focusHud.restReminder }); - restReminderTimerRef.current = null; - }, 5 * 60 * 1000); + // The timer doesn't pause, they just rest within the flow. }} onConfirm={(nextGoal) => { return Promise.resolve(onGoalUpdate(nextGoal)); @@ -253,18 +152,16 @@ export const SpaceFocusHudWidget = ({ />
- + {/* Emergency Tether (Exit) */} +
+
+ +
+
); }; diff --git a/src/widgets/space-workspace/ui/SpaceWorkspaceWidget.tsx b/src/widgets/space-workspace/ui/SpaceWorkspaceWidget.tsx index 069df17..05d2467 100644 --- a/src/widgets/space-workspace/ui/SpaceWorkspaceWidget.tsx +++ b/src/widgets/space-workspace/ui/SpaceWorkspaceWidget.tsx @@ -19,11 +19,9 @@ import { useHudStatusLine } from "@/shared/lib/useHudStatusLine"; import { copy } from "@/shared/i18n"; import { SpaceFocusHudWidget } from "@/widgets/space-focus-hud"; import { SpaceSetupDrawerWidget } from "@/widgets/space-setup-drawer"; -import { SpaceToolsDockWidget } from "@/widgets/space-tools-dock"; import { useRouter, useSearchParams } from "next/navigation"; import { useEffect, useMemo, useRef, useState } from "react"; import type { SessionEntryPoint, WorkspaceMode } from "../model/types"; -import { useAwayReturnRecovery } from "../model/useAwayReturnRecovery"; import { useSpaceWorkspaceSelection } from "../model/useSpaceWorkspaceSelection"; import { useSpaceWorkspaceSessionControls } from "../model/useSpaceWorkspaceSessionControls"; import { @@ -31,7 +29,6 @@ import { resolveInitialSceneId, resolveInitialSoundPreset, resolveInitialTimerLabel, - resolveTimerLabelFromPresetId, TIMER_SELECTION_PRESETS, } from "../model/workspaceSelection"; import { FocusTopToast } from "./FocusTopToast"; @@ -49,14 +46,7 @@ export const SpaceWorkspaceWidget = () => { ); const { - thoughts, - thoughtCount, addThought, - removeThought, - clearThoughts, - restoreThought, - restoreThoughts, - setThoughtCompleted, } = useThoughtInbox(); const { @@ -108,14 +98,11 @@ export const SpaceWorkspaceWidget = () => { selectedPresetId, setSelectedPresetId, masterVolume, - setMasterVolume, isMuted, - setMuted, } = useSoundPresetSelection(initialSoundPresetId); const { currentSession, isBootstrapping, - isMutating: isSessionMutating, timeDisplay, phase, startSession, @@ -127,7 +114,6 @@ export const SpaceWorkspaceWidget = () => { completeSession, advanceGoal, abandonSession, - syncCurrentSession, } = useFocusSessionEngine(); const isFocusMode = workspaceMode === "focus"; @@ -208,13 +194,7 @@ export const SpaceWorkspaceWidget = () => { setSelectedGoalId: selection.setSelectedGoalId, setShowResumePrompt: selection.setShowResumePrompt, }); - const handleStartRequested = controls.handleStartRequested; - const awayReturnRecovery = useAwayReturnRecovery({ - currentSession, - isBootstrapping, - syncCurrentSession, - }); const hasEnoughWeeklyData = weeklySummary.last7Days.startedSessions >= 3 && (weeklySummary.last7Days.completedSessions >= 2 || @@ -349,22 +329,6 @@ export const SpaceWorkspaceWidget = () => { hasActiveSession={Boolean(currentSession)} playbackState={resolvedPlaybackState} sessionPhase={phase ?? 'focus'} - isSessionActionPending={isSessionMutating} - canStartSession={controls.canStartSession} - canPauseSession={controls.canPauseSession} - canRestartSession={controls.canRestartSession} - returnPromptMode={awayReturnRecovery.returnPromptMode} - onStartRequested={() => { - void handleStartRequested(); - }} - onPauseRequested={() => { - void controls.handlePauseRequested(); - }} - onRestartRequested={() => { - void controls.handleRestartRequested(); - }} - onDismissReturnPrompt={awayReturnRecovery.dismissReturnPrompt} - onStatusMessage={pushStatusLine} onIntentUpdate={controls.handleIntentUpdate} onGoalFinish={async () => { const didFinish = await controls.handleGoalComplete(); @@ -376,6 +340,9 @@ export const SpaceWorkspaceWidget = () => { return didFinish; }} onGoalUpdate={controls.handleGoalAdvance} + onStatusMessage={pushStatusLine} + onCaptureThought={(note) => addThought(note, selection.selectedScene.name)} + onExitRequested={() => void controls.handleExitRequested()} /> ) : null} @@ -385,47 +352,6 @@ export const SpaceWorkspaceWidget = () => { actionLabel={activeStatus?.action?.label} onAction={runActiveAction} /> - - - selection.handleSelectTimer(timerLabel, true) - } - onQuickSoundSelect={(presetId) => - selection.handleSelectSound(presetId, true) - } - sceneRecommendedSoundLabel={selection.selectedScene.recommendedSound} - sceneRecommendedTimerLabel={ - resolveTimerLabelFromPresetId( - selection.selectedScene.recommendedTimerPresetId, - ) ?? selection.selectedTimerLabel - } - soundVolume={masterVolume} - onSetSoundVolume={setMasterVolume} - isSoundMuted={isMuted} - onSetSoundMuted={setMuted} - onCaptureThought={(note) => - addThought(note, selection.selectedScene.name) - } - onDeleteThought={removeThought} - onSetThoughtCompleted={setThoughtCompleted} - onRestoreThought={restoreThought} - onRestoreThoughts={restoreThoughts} - onClearInbox={clearThoughts} - onStatusMessage={pushStatusLine} - onExitRequested={() => { - void controls.handleExitRequested(); - }} - />
); };