refactor(space): scene 도메인과 current session 계약을 정리

This commit is contained in:
2026-03-09 13:05:44 +09:00
parent 8184915cb1
commit 675014166a
19 changed files with 243 additions and 208 deletions

View File

@@ -5,7 +5,7 @@ import type { PlanTier } from '@/entities/plan';
import {
PRO_FEATURE_CARDS,
} from '@/entities/plan';
import { getRoomCardBackgroundStyle, type RoomTheme } from '@/entities/room';
import { getSceneCardBackgroundStyle, type SceneTheme } from '@/entities/scene';
import { SOUND_PRESETS, type TimerPreset } from '@/entities/session';
import { cn } from '@/shared/lib/cn';
import { useReducedMotion } from '@/shared/lib/useReducedMotion';
@@ -13,8 +13,8 @@ import { Toggle } from '@/shared/ui';
interface ControlCenterSheetWidgetProps {
plan: PlanTier;
rooms: RoomTheme[];
selectedRoomId: string;
scenes: SceneTheme[];
selectedSceneId: string;
selectedTimerLabel: string;
selectedSoundPresetId: string;
sceneRecommendedSoundLabel: string;
@@ -22,7 +22,7 @@ interface ControlCenterSheetWidgetProps {
timerPresets: TimerPreset[];
autoHideControls: boolean;
onAutoHideControlsChange: (next: boolean) => void;
onSelectRoom: (roomId: string) => void;
onSelectScene: (sceneId: string) => void;
onSelectTimer: (timerLabel: string) => void;
onSelectSound: (presetId: string) => void;
onSelectProFeature: (featureId: string) => void;
@@ -40,8 +40,8 @@ const SectionTitle = ({ title, description }: { title: string; description: stri
export const ControlCenterSheetWidget = ({
plan,
rooms,
selectedRoomId,
scenes,
selectedSceneId,
selectedTimerLabel,
selectedSoundPresetId,
sceneRecommendedSoundLabel,
@@ -49,7 +49,7 @@ export const ControlCenterSheetWidget = ({
timerPresets,
autoHideControls,
onAutoHideControlsChange,
onSelectRoom,
onSelectScene,
onSelectTimer,
onSelectSound,
onSelectProFeature,
@@ -64,14 +64,14 @@ export const ControlCenterSheetWidget = ({
? 'transition-none'
: 'transition-colors duration-[220ms] ease-out';
const selectedRoom = useMemo(() => {
return rooms.find((room) => room.id === selectedRoomId) ?? rooms[0];
}, [rooms, selectedRoomId]);
const selectedScene = useMemo(() => {
return scenes.find((scene) => scene.id === selectedSceneId) ?? scenes[0];
}, [scenes, selectedSceneId]);
return (
<div className="space-y-4">
<section className="space-y-2.5 rounded-2xl border border-white/12 bg-black/22 p-3.5 backdrop-blur-md">
<SectionTitle title="Scene" description={selectedRoom?.name ?? '공간'} />
<SectionTitle title="Background" description={selectedScene?.name ?? '기본 배경'} />
<div
className={cn(
'-mx-1 flex gap-2.5 overflow-x-auto px-1 pb-1.5 snap-x snap-mandatory scrollbar-none',
@@ -79,15 +79,15 @@ export const ControlCenterSheetWidget = ({
)}
style={{ scrollBehavior: reducedMotion ? 'auto' : 'smooth' }}
>
{rooms.slice(0, 6).map((room) => {
const selected = room.id === selectedRoomId;
{scenes.slice(0, 6).map((scene) => {
const selected = scene.id === selectedSceneId;
return (
<button
key={room.id}
key={scene.id}
type="button"
onClick={() => {
onSelectRoom(room.id);
onSelectScene(scene.id);
}}
className={cn(
'relative h-24 w-[130px] shrink-0 snap-start overflow-hidden rounded-xl border text-left',
@@ -96,11 +96,11 @@ export const ControlCenterSheetWidget = ({
selected ? 'border-sky-200/44 shadow-[0_8px_16px_rgba(56,189,248,0.18)]' : 'border-white/16',
)}
>
<div aria-hidden className="absolute inset-0 bg-cover bg-center" style={getRoomCardBackgroundStyle(room)} />
<div aria-hidden className="absolute inset-0 bg-cover bg-center" style={getSceneCardBackgroundStyle(scene)} />
<div aria-hidden className="absolute inset-0 bg-gradient-to-t from-black/56 via-black/18 to-black/6" />
<div className="absolute inset-x-2 bottom-2 min-w-0">
<p className="truncate text-sm font-medium text-white/90">{room.name}</p>
<p className="truncate text-[11px] text-white/66">{room.vibeLabel}</p>
<p className="truncate text-sm font-medium text-white/90">{scene.name}</p>
<p className="truncate text-[11px] text-white/66">{scene.vibeLabel}</p>
</div>
</button>
);

View File

@@ -1,9 +1,9 @@
'use client';
import { useEffect, useMemo, useRef, useState, type FormEvent } from 'react';
import type { RoomTheme } from '@/entities/room';
import type { SceneTheme } from '@/entities/scene';
import type { GoalChip, SoundPreset, TimerPreset } from '@/entities/session';
import { SpaceSelectCarousel } from '@/features/space-select';
import { SceneSelectCarousel } from '@/features/scene-select';
import { SessionGoalField } from '@/features/session-goal';
import { Button } from '@/shared/ui';
import { cn } from '@/shared/lib/cn';
@@ -12,8 +12,8 @@ type RitualPopover = 'space' | 'timer' | 'sound';
interface SpaceSetupDrawerWidgetProps {
open: boolean;
rooms: RoomTheme[];
selectedRoomId: string;
scenes: SceneTheme[];
selectedSceneId: string;
selectedTimerLabel: string;
selectedSoundPresetId: string;
goalInput: string;
@@ -22,7 +22,7 @@ interface SpaceSetupDrawerWidgetProps {
soundPresets: SoundPreset[];
timerPresets: TimerPreset[];
canStart: boolean;
onRoomSelect: (roomId: string) => void;
onSceneSelect: (sceneId: string) => void;
onTimerSelect: (timerLabel: string) => void;
onSoundSelect: (soundPresetId: string) => void;
onGoalChange: (value: string) => void;
@@ -63,8 +63,8 @@ const SummaryChip = ({ label, value, open, onClick }: SummaryChipProps) => {
export const SpaceSetupDrawerWidget = ({
open,
rooms,
selectedRoomId,
scenes,
selectedSceneId,
selectedTimerLabel,
selectedSoundPresetId,
goalInput,
@@ -73,7 +73,7 @@ export const SpaceSetupDrawerWidget = ({
soundPresets,
timerPresets,
canStart,
onRoomSelect,
onSceneSelect,
onTimerSelect,
onSoundSelect,
onGoalChange,
@@ -84,9 +84,9 @@ export const SpaceSetupDrawerWidget = ({
const [openPopover, setOpenPopover] = useState<RitualPopover | null>(null);
const panelRef = useRef<HTMLDivElement | null>(null);
const selectedRoom = useMemo(() => {
return rooms.find((room) => room.id === selectedRoomId) ?? rooms[0];
}, [rooms, selectedRoomId]);
const selectedScene = useMemo(() => {
return scenes.find((scene) => scene.id === selectedSceneId) ?? scenes[0];
}, [scenes, selectedSceneId]);
const selectedSoundLabel = useMemo(() => {
return (
@@ -183,8 +183,8 @@ export const SpaceSetupDrawerWidget = ({
<div className="relative mb-3">
<div className="flex flex-wrap gap-1.5">
<SummaryChip
label="공간"
value={selectedRoom?.name ?? '기본 공간'}
label="배경"
value={selectedScene?.name ?? '기본 배경'}
open={openPopover === 'space'}
onClick={() => togglePopover('space')}
/>
@@ -204,11 +204,11 @@ export const SpaceSetupDrawerWidget = ({
{openPopover === 'space' ? (
<div className="absolute left-0 right-0 top-[calc(100%+0.5rem)] z-20 rounded-2xl border border-white/14 bg-slate-950/80 p-2.5 shadow-[0_18px_44px_rgba(2,6,23,0.4)] backdrop-blur-xl animate-[popover-rise_220ms_ease-out] motion-reduce:animate-none">
<SpaceSelectCarousel
rooms={rooms.slice(0, 4)}
selectedRoomId={selectedRoomId}
onSelect={(roomId) => {
onRoomSelect(roomId);
<SceneSelectCarousel
scenes={scenes.slice(0, 4)}
selectedSceneId={selectedSceneId}
onSelect={(sceneId) => {
onSceneSelect(sceneId);
setOpenPopover(null);
}}
/>

View File

@@ -1,7 +1,7 @@
'use client';
import { useEffect, useMemo, useRef, useState, type KeyboardEvent as ReactKeyboardEvent } from 'react';
import type { PlanTier } from '@/entities/plan';
import type { RoomTheme } from '@/entities/room';
import type { SceneTheme } from '@/entities/scene';
import { SOUND_PRESETS, type RecentThought, type TimerPreset } from '@/entities/session';
import { ExitHoldButton } from '@/features/exit-hold';
import { ManagePlanSheetContent, PaywallSheetContent } from '@/features/paywall-sheet';
@@ -19,8 +19,8 @@ import { QuickSoundPopover } from './popovers/QuickSoundPopover';
import { InboxToolPanel } from './panels/InboxToolPanel';
interface SpaceToolsDockWidgetProps {
isFocusMode: boolean;
rooms: RoomTheme[];
selectedRoomId: string;
scenes: SceneTheme[];
selectedSceneId: string;
selectedTimerLabel: string;
timerPresets: TimerPreset[];
selectedPresetId: string;
@@ -32,7 +32,7 @@ interface SpaceToolsDockWidgetProps {
thoughtCount: number;
sceneRecommendedSoundLabel: string;
sceneRecommendedTimerLabel: string;
onRoomSelect: (roomId: string) => void;
onSceneSelect: (sceneId: string) => void;
onTimerSelect: (timerLabel: string) => void;
onQuickSoundSelect: (presetId: string) => void;
onCaptureThought: (note: string) => RecentThought | null;
@@ -47,8 +47,8 @@ interface SpaceToolsDockWidgetProps {
export const SpaceToolsDockWidget = ({
isFocusMode,
rooms,
selectedRoomId,
scenes,
selectedSceneId,
selectedTimerLabel,
timerPresets,
selectedPresetId,
@@ -60,7 +60,7 @@ export const SpaceToolsDockWidget = ({
thoughtCount,
sceneRecommendedSoundLabel,
sceneRecommendedTimerLabel,
onRoomSelect,
onSceneSelect,
onTimerSelect,
onQuickSoundSelect,
onCaptureThought,
@@ -495,8 +495,8 @@ export const SpaceToolsDockWidget = ({
{utilityPanel === 'control-center' ? (
<ControlCenterSheetWidget
plan={plan}
rooms={rooms}
selectedRoomId={selectedRoomId}
scenes={scenes}
selectedSceneId={selectedSceneId}
selectedTimerLabel={selectedTimerLabel}
selectedSoundPresetId={selectedPresetId}
sceneRecommendedSoundLabel={sceneRecommendedSoundLabel}
@@ -504,8 +504,8 @@ export const SpaceToolsDockWidget = ({
timerPresets={timerPresets}
autoHideControls={autoHideControls}
onAutoHideControlsChange={setAutoHideControls}
onSelectRoom={(roomId) => {
onRoomSelect(roomId);
onSelectScene={(sceneId) => {
onSceneSelect(sceneId);
}}
onSelectTimer={(label) => {
onTimerSelect(label);

View File

@@ -1,26 +1,26 @@
'use client';
import { useState } from 'react';
import type { RoomTheme } from '@/entities/room';
import type { SceneTheme } from '@/entities/scene';
import type { TimerPreset } from '@/entities/session';
import { DEFAULT_PRESET_OPTIONS } from '@/shared/config/settingsOptions';
import { cn } from '@/shared/lib/cn';
interface SettingsToolPanelProps {
rooms: RoomTheme[];
selectedRoomId: string;
scenes: SceneTheme[];
selectedSceneId: string;
selectedTimerLabel: string;
timerPresets: TimerPreset[];
onSelectRoom: (roomId: string) => void;
onSelectScene: (sceneId: string) => void;
onSelectTimer: (timerLabel: string) => void;
}
export const SettingsToolPanel = ({
rooms,
selectedRoomId,
scenes,
selectedSceneId,
selectedTimerLabel,
timerPresets,
onSelectRoom,
onSelectScene,
onSelectTimer,
}: SettingsToolPanelProps) => {
const [reduceMotion, setReduceMotion] = useState(false);
@@ -59,17 +59,17 @@ export const SettingsToolPanel = ({
</section>
<section className="rounded-2xl border border-white/14 bg-white/7 px-3.5 py-3">
<p className="text-sm font-medium text-white"></p>
<p className="mt-1 text-xs text-white/58"> .</p>
<p className="text-sm font-medium text-white"></p>
<p className="mt-1 text-xs text-white/58"> scene .</p>
<div className="mt-2 flex flex-wrap gap-2">
{rooms.slice(0, 4).map((room) => {
const selected = room.id === selectedRoomId;
{scenes.slice(0, 4).map((scene) => {
const selected = scene.id === selectedSceneId;
return (
<button
key={room.id}
key={scene.id}
type="button"
onClick={() => onSelectRoom(room.id)}
onClick={() => onSelectScene(scene.id)}
className={cn(
'rounded-full border px-3 py-1.5 text-xs transition-colors',
selected
@@ -77,7 +77,7 @@ export const SettingsToolPanel = ({
: 'border-white/18 bg-white/8 text-white/80 hover:bg-white/14',
)}
>
{room.name}
{scene.name}
</button>
);
})}

View File

@@ -3,10 +3,10 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useSearchParams } from 'next/navigation';
import {
getRoomBackgroundStyle,
getRoomById,
ROOM_THEMES,
} from '@/entities/room';
getSceneBackgroundStyle,
getSceneById,
SCENE_THEMES,
} from '@/entities/scene';
import {
GOAL_CHIPS,
SOUND_PRESETS,
@@ -62,16 +62,16 @@ const readStoredWorkspaceSelection = (): StoredWorkspaceSelection => {
}
};
const resolveInitialRoomId = (roomIdFromQuery: string | null, storedSceneId?: string) => {
if (roomIdFromQuery && getRoomById(roomIdFromQuery)) {
return roomIdFromQuery;
const resolveInitialSceneId = (sceneIdFromQuery: string | null, storedSceneId?: string) => {
if (sceneIdFromQuery && getSceneById(sceneIdFromQuery)) {
return sceneIdFromQuery;
}
if (storedSceneId && getRoomById(storedSceneId)) {
if (storedSceneId && getSceneById(storedSceneId)) {
return storedSceneId;
}
return ROOM_THEMES[0].id;
return SCENE_THEMES[0].id;
};
const resolveInitialSoundPreset = (
@@ -150,11 +150,11 @@ const resolveFocusTimeDisplayFromTimerLabel = (timerLabel: string) => {
export const SpaceWorkspaceWidget = () => {
const searchParams = useSearchParams();
const roomQuery = searchParams.get('room');
const sceneQuery = searchParams.get('scene') ?? searchParams.get('room');
const goalQuery = searchParams.get('goal')?.trim() ?? '';
const soundQuery = searchParams.get('sound');
const timerQuery = searchParams.get('timer');
const hasQueryOverrides = Boolean(roomQuery || goalQuery || soundQuery || timerQuery);
const hasQueryOverrides = Boolean(sceneQuery || goalQuery || soundQuery || timerQuery);
const {
thoughts,
thoughtCount,
@@ -166,22 +166,22 @@ export const SpaceWorkspaceWidget = () => {
setThoughtCompleted,
} = useThoughtInbox();
const initialRoomId = resolveInitialRoomId(roomQuery, undefined);
const initialRoom = getRoomById(initialRoomId) ?? ROOM_THEMES[0];
const initialSceneId = resolveInitialSceneId(sceneQuery, undefined);
const initialScene = getSceneById(initialSceneId) ?? SCENE_THEMES[0];
const initialGoal = goalQuery;
const initialSoundPresetId = resolveInitialSoundPreset(
soundQuery,
undefined,
initialRoom.recommendedSoundPresetId,
initialScene.recommendedSoundPresetId,
);
const initialTimerLabel = resolveInitialTimerLabel(
timerQuery,
undefined,
initialRoom.recommendedTimerPresetId,
initialScene.recommendedTimerPresetId,
);
const [workspaceMode, setWorkspaceMode] = useState<WorkspaceMode>('setup');
const [selectedRoomId, setSelectedRoomId] = useState(initialRoomId);
const [selectedSceneId, setSelectedSceneId] = useState(initialSceneId);
const [selectedTimerLabel, setSelectedTimerLabel] = useState(initialTimerLabel);
const [goalInput, setGoalInput] = useState(initialGoal);
const [selectedGoalId, setSelectedGoalId] = useState<string | null>(null);
@@ -216,19 +216,19 @@ export const SpaceWorkspaceWidget = () => {
abandonSession,
} = useFocusSessionEngine();
const selectedRoom = useMemo(() => {
return getRoomById(selectedRoomId) ?? ROOM_THEMES[0];
}, [selectedRoomId]);
const selectedScene = useMemo(() => {
return getSceneById(selectedSceneId) ?? SCENE_THEMES[0];
}, [selectedSceneId]);
const setupRooms = useMemo(() => {
const visibleRooms = ROOM_THEMES.slice(0, 6);
const setupScenes = useMemo(() => {
const visibleScenes = SCENE_THEMES.slice(0, 6);
if (visibleRooms.some((room) => room.id === selectedRoom.id)) {
return visibleRooms;
if (visibleScenes.some((scene) => scene.id === selectedScene.id)) {
return visibleScenes;
}
return [selectedRoom, ...visibleRooms].slice(0, 6);
}, [selectedRoom]);
return [selectedScene, ...visibleScenes].slice(0, 6);
}, [selectedScene]);
const canStart = goalInput.trim().length > 0;
const isFocusMode = workspaceMode === 'focus';
@@ -237,17 +237,17 @@ export const SpaceWorkspaceWidget = () => {
const resolvedTimeDisplay = timeDisplay ?? resolveFocusTimeDisplayFromTimerLabel(selectedTimerLabel);
const applyRecommendedSelections = useCallback((
roomId: string,
sceneId: string,
overrideState: SelectionOverride = selectionOverride,
) => {
const room = getRoomById(roomId);
const scene = getSceneById(sceneId);
if (!room) {
if (!scene) {
return;
}
if (!overrideState.timer) {
const recommendedTimerLabel = resolveTimerLabelFromPresetId(room.recommendedTimerPresetId);
const recommendedTimerLabel = resolveTimerLabelFromPresetId(scene.recommendedTimerPresetId);
if (recommendedTimerLabel) {
setSelectedTimerLabel(recommendedTimerLabel);
@@ -256,9 +256,9 @@ export const SpaceWorkspaceWidget = () => {
if (
!overrideState.sound &&
SOUND_PRESETS.some((preset) => preset.id === room.recommendedSoundPresetId)
SOUND_PRESETS.some((preset) => preset.id === scene.recommendedSoundPresetId)
) {
setSelectedPresetId(room.recommendedSoundPresetId);
setSelectedPresetId(scene.recommendedSoundPresetId);
}
}, [selectionOverride, setSelectedPresetId]);
@@ -268,8 +268,8 @@ export const SpaceWorkspaceWidget = () => {
sound: Boolean(storedSelection.override?.sound),
timer: Boolean(storedSelection.override?.timer),
};
const restoredRoomId =
!roomQuery && storedSelection.sceneId && getRoomById(storedSelection.sceneId)
const restoredSceneId =
!sceneQuery && storedSelection.sceneId && getSceneById(storedSelection.sceneId)
? storedSelection.sceneId
: null;
const restoredTimerLabel = !timerQuery
@@ -283,8 +283,8 @@ export const SpaceWorkspaceWidget = () => {
const rafId = window.requestAnimationFrame(() => {
setSelectionOverride(restoredSelectionOverride);
if (restoredRoomId) {
setSelectedRoomId(restoredRoomId);
if (restoredSceneId) {
setSelectedSceneId(restoredSceneId);
}
if (restoredTimerLabel) {
@@ -306,7 +306,7 @@ export const SpaceWorkspaceWidget = () => {
return () => {
window.cancelAnimationFrame(rafId);
};
}, [goalQuery, hasQueryOverrides, roomQuery, setSelectedPresetId, soundQuery, timerQuery]);
}, [goalQuery, hasQueryOverrides, sceneQuery, setSelectedPresetId, soundQuery, timerQuery]);
useEffect(() => {
if (!currentSession) {
@@ -321,7 +321,7 @@ export const SpaceWorkspaceWidget = () => {
? currentSession.soundPresetId
: selectedPresetId;
const rafId = window.requestAnimationFrame(() => {
setSelectedRoomId(currentSession.roomId);
setSelectedSceneId(currentSession.sceneId);
setSelectedTimerLabel(nextTimerLabel);
setSelectedPresetId(nextSoundPresetId);
setGoalInput(currentSession.goal);
@@ -336,9 +336,9 @@ export const SpaceWorkspaceWidget = () => {
};
}, [currentSession, selectedPresetId, selectedTimerLabel, setSelectedPresetId]);
const handleSelectRoom = (roomId: string) => {
setSelectedRoomId(roomId);
applyRecommendedSelections(roomId);
const handleSelectScene = (sceneId: string) => {
setSelectedSceneId(sceneId);
applyRecommendedSelections(sceneId);
};
const handleSelectTimer = (timerLabel: string, markOverride = false) => {
@@ -407,7 +407,7 @@ export const SpaceWorkspaceWidget = () => {
setWorkspaceMode('focus');
const startedSession = await startSession({
roomId: selectedRoomId,
sceneId: selectedSceneId,
goal: trimmedGoal,
timerPresetId,
soundPresetId: selectedPresetId,
@@ -552,21 +552,21 @@ export const SpaceWorkspaceWidget = () => {
window.localStorage.setItem(
WORKSPACE_SELECTION_STORAGE_KEY,
JSON.stringify({
sceneId: selectedRoomId,
sceneId: selectedSceneId,
timerPresetId,
soundPresetId: selectedPresetId,
goal: normalizedGoal,
override: selectionOverride,
}),
);
}, [goalInput, hasHydratedSelection, resumeGoal, selectedRoomId, selectedTimerLabel, selectedPresetId, selectionOverride, showResumePrompt]);
}, [goalInput, hasHydratedSelection, resumeGoal, selectedSceneId, selectedTimerLabel, selectedPresetId, selectionOverride, showResumePrompt]);
return (
<div className="relative h-dvh overflow-hidden text-white">
<div
aria-hidden
className="absolute inset-0 bg-cover bg-center will-change-transform animate-[space-stage-pan_42s_ease-in-out_infinite_alternate] motion-reduce:animate-none"
style={getRoomBackgroundStyle(selectedRoom)}
style={getSceneBackgroundStyle(selectedScene)}
/>
<div className="relative z-10 flex h-full flex-col">
@@ -575,8 +575,8 @@ export const SpaceWorkspaceWidget = () => {
<SpaceSetupDrawerWidget
open={!isFocusMode}
rooms={setupRooms}
selectedRoomId={selectedRoom.id}
scenes={setupScenes}
selectedSceneId={selectedScene.id}
selectedTimerLabel={selectedTimerLabel}
selectedSoundPresetId={selectedPresetId}
goalInput={goalInput}
@@ -585,7 +585,7 @@ export const SpaceWorkspaceWidget = () => {
soundPresets={SOUND_PRESETS}
timerPresets={TIMER_SELECTION_PRESETS}
canStart={canStart}
onRoomSelect={handleSelectRoom}
onSceneSelect={handleSelectScene}
onTimerSelect={(timerLabel) => handleSelectTimer(timerLabel, true)}
onSoundSelect={(presetId) => handleSelectSound(presetId, true)}
onGoalChange={handleGoalChange}
@@ -641,23 +641,23 @@ export const SpaceWorkspaceWidget = () => {
<SpaceToolsDockWidget
isFocusMode={isFocusMode}
rooms={setupRooms}
selectedRoomId={selectedRoom.id}
scenes={setupScenes}
selectedSceneId={selectedScene.id}
selectedTimerLabel={selectedTimerLabel}
timerPresets={TIMER_SELECTION_PRESETS}
thoughts={thoughts}
thoughtCount={thoughtCount}
selectedPresetId={selectedPresetId}
onRoomSelect={handleSelectRoom}
onSceneSelect={handleSelectScene}
onTimerSelect={(timerLabel) => handleSelectTimer(timerLabel, true)}
onQuickSoundSelect={(presetId) => handleSelectSound(presetId, true)}
sceneRecommendedSoundLabel={selectedRoom.recommendedSound}
sceneRecommendedTimerLabel={resolveTimerLabelFromPresetId(selectedRoom.recommendedTimerPresetId) ?? selectedTimerLabel}
sceneRecommendedSoundLabel={selectedScene.recommendedSound}
sceneRecommendedTimerLabel={resolveTimerLabelFromPresetId(selectedScene.recommendedTimerPresetId) ?? selectedTimerLabel}
soundVolume={masterVolume}
onSetSoundVolume={setMasterVolume}
isSoundMuted={isMuted}
onSetSoundMuted={setMuted}
onCaptureThought={(note) => addThought(note, selectedRoom.name)}
onCaptureThought={(note) => addThought(note, selectedScene.name)}
onDeleteThought={removeThought}
onSetThoughtCompleted={setThoughtCompleted}
onRestoreThought={restoreThought}