System Intent
- What this is: A React Native component that renders the bottom action bar of the main screen. It composes three interactive elements: a device-picker button (left), the
CaptureButton (center), and a chat-navigation button (right). It owns the DevicePickerSheet and exposes openDeviceSheet to both the device-picker button and the CaptureButton via the onGlassesUnavailable prop — so that pressing record while glasses are unavailable automatically opens the device picker rather than failing silently. When hideRecordButtons is true the component returns null immediately, hiding all record controls; this is used by memory-viewer-modal so that the record buttons do not appear while the user is viewing a memory.
Mermaid Diagram
flowchart TD
HideCheck{hideRecordButtons?}
HideCheck -->|true| ReturnNull["return null<br/>no rendering"]
HideCheck -->|false| FooterMount([Footer renders])
FooterMount --> CB[CaptureButton\nonGlassesUnavailable=openDeviceSheet]
FooterMount --> DevBtn[Device picker button\nonPress=openDeviceSheet]
FooterMount --> ChatBtn[Chat button\nonPress=router.push /chat]
DevBtn -->|idle only| OpenSheet[openDeviceSheet]
CB -->|glasses mode + not connected| OpenSheet
OpenSheet --> Log[log device_picker_open]
Log --> RefreshAvail[isGlassesDeviceAvailable — refresh]
RefreshAvail --> ShowSheet[setSheetVisible true]
ShowSheet --> Sheet[DevicePickerSheet]
Sheet -->|select device| SelectDevice[setSelectedRecordingDevice\nsetSheetVisible false\nlog device_select]
Sheet -->|close| CloseSheet[setSheetVisible false\nlog device_picker_close]
Flows
- Test files:
main/app/__tests__/memory-viewer-modal.test.tsx - Core files:
main/app/components/footer.tsx
Paths
| path | input | output | path-type | notes |
hideRecordButtons.earlyReturn | hideRecordButtons={true} is passed (e.g. by memory-viewer-modal) | component returns null; no record controls rendered | happy path | used wherever record buttons must not appear, such as when a memory detail is open |
Flow: openDeviceSheet
- Test files:
main/app/__tests__/footer.test.tsx - Core files:
main/app/components/footer.tsx
Types
FooterProps {
bottomInset?: number (default 0; added to the 24 px bottom offset)
onLayout?: (event: LayoutChangeEvent) => void
hideRecordButtons?: boolean (default false; when true the component renders null — no record controls are shown)
}
RecordingDevice = "glasses" | "glasses-audio" | "phone" | ...
Paths
| path | input | output | path-type | notes |
openDeviceSheet.fromDeviceButton | user presses device-picker button while idle | sheet opens; glassesAvailable refreshed; device_picker_open logged | happy path | device-picker button is disabled when not idle |
openDeviceSheet.fromCaptureButton | CaptureButton fires onGlassesUnavailable (glasses mode + not connected) | same as above — sheet opens; glassesAvailable refreshed; device_picker_open logged | happy path | fail-fast path from CaptureButton; no recording was attempted |
hideRecordButtons.earlyReturn | hideRecordButtons={true} is passed (e.g. by memory-viewer-modal) | component returns null; no record controls rendered | happy path | used wherever record buttons must not appear, such as when a memory is open |
Flow: handleSelectDevice
- Core files:
main/app/components/footer.tsx
Paths
| path | input | output | path-type | notes |
handleSelectDevice.success | user selects a device in the sheet | setSelectedRecordingDevice(device) persisted; sheet closed; device_select logged | happy path | |
Flow: handleSheetClose
- Core files:
main/app/components/footer.tsx
Paths
| path | input | output | path-type | notes |
handleSheetClose.success | user dismisses sheet without selecting | sheet closed; device_picker_close logged | happy path | |
Logs
| Source | Location |
device_picker_open | useLogging() frontend logger — includes { selectedDevice, glassesAvailable } |
device_picker_close | useLogging() frontend logger — includes { selectedDevice } |
device_select | useLogging() frontend logger — includes { device, previousDevice } |
Deployment
- Mechanism:
local only (shipped as part of the React Native app bundle) - Deploy command:
cd main && npx expo start
- Notes: The device-picker button is disabled (
disabled={!idle}) while a recording is in progress or transitioning, preventing device changes mid-session. glassesAvailable state is refreshed synchronously on every openDeviceSheet call so the sheet always reflects the current Bluetooth connection state at the moment it opens. When hideRecordButtons={true} is passed, the component returns null without rendering anything, used to hide footer buttons in contexts like the memory detail viewer modal.