From 478159f787b9adee45f4c97271e4ced94184897e Mon Sep 17 00:00:00 2001 From: corpi Date: Thu, 5 Mar 2026 12:22:12 +0900 Subject: [PATCH] =?UTF-8?q?chore(state):=20/space=20=EC=84=A0=ED=83=9D=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EC=99=80=20override=20=EB=A1=9C=EC=BB=AC=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5/=EB=B3=B5=EC=9B=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 맥락: - 새로고침 시 Scene/Timer/Sound와 override가 초기화되면 추천 자동화 체감이 끊겨 사용성이 떨어졌습니다. 변경사항: - /space workspace에 localStorage 기반 상태 저장/복원 키(viberoom:workspace-selection:v1)를 추가했습니다. - sceneId, timerPresetId, soundPresetId, override.sound/timer를 저장하고 진입 시 복원하도록 반영했습니다. - 복원 우선순위는 쿼리 파라미터 > 저장 상태 > Scene 추천값 순으로 정리했습니다. 검증: - npx tsc --noEmit 세션-상태: 새로고침 후에도 마지막 Scene/Timer/Sound 및 override 상태가 유지됩니다. 세션-다음: 세션 문서(90_current_state/session_brief) 업데이트와 최종 정리를 진행합니다. 세션-리스크: 저장 포맷 변경 시 이전 localStorage 데이터와의 호환성 점검이 필요합니다. --- .../ui/SpaceWorkspaceWidget.tsx | 93 ++++++++++++++++++- 1 file changed, 88 insertions(+), 5 deletions(-) diff --git a/src/widgets/space-workspace/ui/SpaceWorkspaceWidget.tsx b/src/widgets/space-workspace/ui/SpaceWorkspaceWidget.tsx index 90c8375..b62feb4 100644 --- a/src/widgets/space-workspace/ui/SpaceWorkspaceWidget.tsx +++ b/src/widgets/space-workspace/ui/SpaceWorkspaceWidget.tsx @@ -26,20 +26,64 @@ type SelectionOverride = { sound: boolean; timer: boolean; }; +interface StoredWorkspaceSelection { + sceneId?: string; + timerPresetId?: string; + soundPresetId?: string; + override?: Partial; +} -const resolveInitialRoomId = (roomIdFromQuery: string | null) => { +const WORKSPACE_SELECTION_STORAGE_KEY = 'viberoom:workspace-selection:v1'; + +const readStoredWorkspaceSelection = (): StoredWorkspaceSelection => { + if (typeof window === 'undefined') { + return {}; + } + + const raw = window.localStorage.getItem(WORKSPACE_SELECTION_STORAGE_KEY); + + if (!raw) { + return {}; + } + + try { + const parsed = JSON.parse(raw); + + if (!parsed || typeof parsed !== 'object') { + return {}; + } + + return parsed as StoredWorkspaceSelection; + } catch { + return {}; + } +}; + +const resolveInitialRoomId = (roomIdFromQuery: string | null, storedSceneId?: string) => { if (roomIdFromQuery && getRoomById(roomIdFromQuery)) { return roomIdFromQuery; } + if (storedSceneId && getRoomById(storedSceneId)) { + return storedSceneId; + } + return ROOM_THEMES[0].id; }; -const resolveInitialSoundPreset = (presetIdFromQuery: string | null, recommendedPresetId?: string) => { +const resolveInitialSoundPreset = ( + presetIdFromQuery: string | null, + storedPresetId: string | undefined, + recommendedPresetId?: string, +) => { if (presetIdFromQuery && SOUND_PRESETS.some((preset) => preset.id === presetIdFromQuery)) { return presetIdFromQuery; } + if (storedPresetId && SOUND_PRESETS.some((preset) => preset.id === storedPresetId)) { + return storedPresetId; + } + if (recommendedPresetId && SOUND_PRESETS.some((preset) => preset.id === recommendedPresetId)) { return recommendedPresetId; } @@ -66,11 +110,26 @@ const resolveTimerLabelFromPresetId = (presetId?: string) => { return preset.label; }; -const resolveInitialTimerLabel = (timerLabelFromQuery: string | null, recommendedPresetId?: string) => { +const resolveTimerPresetIdFromLabel = (timerLabel: string) => { + const preset = TIMER_SELECTION_PRESETS.find((candidate) => candidate.label === timerLabel); + return preset?.id; +}; + +const resolveInitialTimerLabel = ( + timerLabelFromQuery: string | null, + storedPresetId?: string, + recommendedPresetId?: string, +) => { if (timerLabelFromQuery && TIMER_SELECTION_PRESETS.some((preset) => preset.label === timerLabelFromQuery)) { return timerLabelFromQuery; } + const storedLabel = resolveTimerLabelFromPresetId(storedPresetId); + + if (storedLabel) { + return storedLabel; + } + const recommendedLabel = resolveTimerLabelFromPresetId(recommendedPresetId); if (recommendedLabel) { @@ -82,6 +141,7 @@ const resolveInitialTimerLabel = (timerLabelFromQuery: string | null, recommende export const SpaceWorkspaceWidget = () => { const searchParams = useSearchParams(); + const storedSelection = useMemo(() => readStoredWorkspaceSelection(), []); const { thoughts, thoughtCount, @@ -92,15 +152,17 @@ export const SpaceWorkspaceWidget = () => { setThoughtCompleted, } = useThoughtInbox(); - const initialRoomId = resolveInitialRoomId(searchParams.get('room')); + const initialRoomId = resolveInitialRoomId(searchParams.get('room'), storedSelection.sceneId); const initialRoom = getRoomById(initialRoomId) ?? ROOM_THEMES[0]; const initialGoal = searchParams.get('goal')?.trim() ?? ''; const initialSoundPresetId = resolveInitialSoundPreset( searchParams.get('sound'), + storedSelection.soundPresetId, initialRoom.recommendedSoundPresetId, ); const initialTimerLabel = resolveInitialTimerLabel( searchParams.get('timer'), + storedSelection.timerPresetId, initialRoom.recommendedTimerPresetId, ); @@ -109,7 +171,10 @@ export const SpaceWorkspaceWidget = () => { const [selectedTimerLabel, setSelectedTimerLabel] = useState(initialTimerLabel); const [goalInput, setGoalInput] = useState(initialGoal); const [selectedGoalId, setSelectedGoalId] = useState(null); - const [selectionOverride, setSelectionOverride] = useState({ sound: false, timer: false }); + const [selectionOverride, setSelectionOverride] = useState({ + sound: Boolean(storedSelection.override?.sound), + timer: Boolean(storedSelection.override?.timer), + }); const { selectedPresetId, @@ -258,6 +323,24 @@ export const SpaceWorkspaceWidget = () => { }; }, []); + useEffect(() => { + if (typeof window === 'undefined') { + return; + } + + const timerPresetId = resolveTimerPresetIdFromLabel(selectedTimerLabel); + + window.localStorage.setItem( + WORKSPACE_SELECTION_STORAGE_KEY, + JSON.stringify({ + sceneId: selectedRoomId, + timerPresetId, + soundPresetId: selectedPresetId, + override: selectionOverride, + }), + ); + }, [selectedRoomId, selectedTimerLabel, selectedPresetId, selectionOverride]); + return (