Sidebar & Layout

Sidebar navigation, conversation list, TitleBar, AwayScreen, and SpeakerPicker — the chrome components that wrap the main content area.

Overview

The app shell is composed of four layout-level components that sit outside the main content panel:

ComponentFileResponsibility
SidebarLayout/Sidebar.tsxNav, conversation list, mood indicator, clock.
TitleBarLayout/TitleBar.tsxElectron drag region + window controls (min/max/close).
AwayScreenLayout/AwayScreen.tsxOverlay shown when the user is marked away.
SpeakerPickerLayout/SpeakerPicker.tsxAudio output device selector.

Fixed 240 px left panel. Contains the Luna logo, navigation links, conversation history list, current mood emoji, live clock, and the online/offline status indicator for the Ollama backend.

usage
import { Sidebar } from './components/Layout/Sidebar'

// Rendered once at the app root, always visible
<Sidebar />

Each nav item maps a View identifier to an icon and a label. Clicking calls setView(id) on the Zustand store.

View IDLabelIcon
chatChatMessageCircle
memoryMemoryBrain
calendarCalendarCalendar
activitiesActivitiesActivity
agentAgentShieldCheck
sleepSleepMoon
trainTrainDna
extractExtractFlaskConical
settingsSettingsSettings

Active view is highlighted with a left border accent and a dimmed background.

Conversation list

Below the nav links, the sidebar shows the conversation history loaded fromGET /api/conversations. Each item shows the conversation title and a relative timestamp.

Actions:

  • Click — loads the conversation into the chat view.
  • Trash icon (hover to reveal) — deletes the conversation after confirmation.
  • + button (at the top of the list) — starts a new conversation.

Mood display

The bottom of the sidebar shows Luna's current mood as an emoji badge, loaded from the personality field of the Zustand store. The store is populated by the chat hook from SSE personality update events.

mood emoji map
{
  happy:      "😊",
  playful:    "😄",
  thoughtful: "🤔",
  excited:    "✨",
  concerned:  "💙",
  warm:       "🌙",
  neutral:    "😌",
  curious:    "🔍",
  melancholic:"🌧",
}

Defaults to the neutral emoji if personality data hasn't loaded yet.

TitleBar

A 32 px draggable region at the top of the window. Contains the app name and, on the right, traffic-light-style window controls (minimize, maximize, close) implemented via window.electronAPI.

📌
TitleBar is Electron-only. In the web variant (running in a browser), it renders as a simple header without window controls.
usage
import { TitleBar } from './components/Layout/TitleBar'

// Rendered at the very top of the app root, above Sidebar + content
<TitleBar />

AwayScreen

An animated overlay that appears over the entire app when the user is marked away (isAway === true in the store). Shows the current time and a dismissal hint. Clicking anywhere clears the away state.

Away state is set by:

  • The command parser emitting an [AWAY] command (farewell detection).
  • The scheduler's state_aware_proactive() function.
  • Programmatic calls to POST /api/away.
usage
import { AwayScreen } from './components/Layout/AwayScreen'

// Conditionally mounted at the app root
{isAway && <AwayScreen />}

SpeakerPicker

A floating panel that lists available audio output devices and lets the user switch the default speaker. Each device is shown with its name; clicking one calls the switch_audio tool via POST /api/tools/execute.

Appears as a popover triggered from the audio icon in the chat header.

usage
import { SpeakerPicker } from './components/Layout/SpeakerPicker'

// Rendered as an absolute-positioned popover
{showSpeakerPicker && <SpeakerPicker onClose={() => setShowSpeakerPicker(false)} />}