feat: Improve UI layout and navigation

- Increase logo size (48x48 desktop, 56x56 mobile) for better visibility
- Add logo as favicon
- Add logo to mobile header
- Move user menu to navigation bars (sidebar on desktop, bottom bar on mobile)
- Fix desktop chat layout - container structure prevents voice controls cutoff
- Fix mobile bottom bar - use icon-only ActionIcons instead of truncated text buttons
- Hide Create Node/New Conversation buttons on mobile to save header space
- Make fixed header and voice controls work properly with containers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-09 14:43:11 +00:00
parent 47b35b9caf
commit 0ed2d6c0b3
57 changed files with 6996 additions and 629 deletions

View File

@@ -0,0 +1,127 @@
'use client';
/**
* Desktop Sidebar Navigation
*
* Vertical sidebar navigation for desktop (≥ 768px).
* Shows three navigation links: Convo, Edit, Galaxy
* Highlights the active mode based on app state machine.
*/
import { Stack, NavLink, Box, Text, Group, Image, Divider } from '@mantine/core';
import { IconMessageCircle, IconEdit, IconUniverse } from '@tabler/icons-react';
import { useSelector } from '@xstate/react';
import { useAppMachine } from '@/hooks/useAppMachine';
import { UserMenu } from '@/components/UserMenu';
export function DesktopSidebar() {
const actor = useAppMachine();
const state = useSelector(actor, (state) => state);
const send = actor.send;
const handleNavigation = (target: 'convo' | 'edit' | 'galaxy') => {
console.log('[Desktop Nav] Navigating to:', target);
if (target === 'convo') {
send({ type: 'NAVIGATE_TO_CONVO' });
} else if (target === 'edit') {
send({ type: 'NAVIGATE_TO_EDIT' });
} else if (target === 'galaxy') {
send({ type: 'NAVIGATE_TO_GALAXY' });
}
};
const isConvo = state.matches('convo');
const isEdit = state.matches('edit');
const isGalaxy = state.matches('galaxy');
console.log('[Desktop Nav] Current state:', state.value, {
isConvo,
isEdit,
isGalaxy,
});
return (
<Box
style={{
width: '100%',
height: '100%',
borderRight: '1px solid #dee2e6',
padding: '1rem',
}}
>
<Stack gap="xs">
<Group gap="sm" mb="md" align="center">
<Image
src="/logo.svg"
alt="Ponderants logo"
w={48}
h={48}
style={{ flexShrink: 0 }}
/>
<Text fw={700} size="md" c="dimmed">
Ponderants
</Text>
</Group>
<NavLink
label="Convo"
leftSection={<IconMessageCircle size={20} />}
active={isConvo}
onClick={() => handleNavigation('convo')}
variant="filled"
/>
<NavLink
label="Manual"
leftSection={<IconEdit size={20} />}
active={isEdit}
onClick={() => handleNavigation('edit')}
variant="filled"
/>
<NavLink
label="Galaxy"
leftSection={<IconUniverse size={20} />}
active={isGalaxy}
onClick={() => handleNavigation('galaxy')}
variant="filled"
/>
<Divider my="md" />
<Box style={{ padding: '0.5rem' }}>
<UserMenu />
</Box>
{/* Development state panel */}
{process.env.NODE_ENV === 'development' && (
<Box mt="xl" p="sm" style={{ border: '1px solid #495057', borderRadius: '4px' }}>
<Text size="xs" fw={700} c="dimmed" mb="xs">
DEV: App State
</Text>
<Text size="xs" c="dimmed">
State: {JSON.stringify(state.value)}
</Text>
<Text size="xs" c="dimmed">
Tags: {Array.from(state.tags).join(', ')}
</Text>
<Text size="xs" c="dimmed">
Mode: {state.context.mode}
</Text>
{state.context.pendingNodeDraft && (
<Text size="xs" c="dimmed">
Draft: {state.context.pendingNodeDraft.title || '(untitled)'}
</Text>
)}
{state.context.currentNodeId && (
<Text size="xs" c="dimmed">
Node: {state.context.currentNodeId}
</Text>
)}
</Box>
)}
</Stack>
</Box>
);
}

View File

@@ -0,0 +1,95 @@
'use client';
/**
* Mobile Bottom Bar Navigation
*
* Fixed bottom navigation for mobile devices (< 768px).
* Shows three buttons: Convo, Edit, Galaxy
* Highlights the active mode based on app state machine.
*/
import { Group, Button, Paper, ActionIcon, Box } from '@mantine/core';
import { IconMessageCircle, IconEdit, IconUniverse, IconUser } from '@tabler/icons-react';
import { useSelector } from '@xstate/react';
import { useAppMachine } from '@/hooks/useAppMachine';
import { UserMenu } from '@/components/UserMenu';
export function MobileBottomBar() {
const actor = useAppMachine();
const state = useSelector(actor, (state) => state);
const send = actor.send;
const handleNavigation = (target: 'convo' | 'edit' | 'galaxy') => {
console.log('[Mobile Nav] Navigating to:', target);
if (target === 'convo') {
send({ type: 'NAVIGATE_TO_CONVO' });
} else if (target === 'edit') {
send({ type: 'NAVIGATE_TO_EDIT' });
} else if (target === 'galaxy') {
send({ type: 'NAVIGATE_TO_GALAXY' });
}
};
const isConvo = state.matches('convo');
const isEdit = state.matches('edit');
const isGalaxy = state.matches('galaxy');
console.log('[Mobile Nav] Current state:', state.value, {
isConvo,
isEdit,
isGalaxy,
});
return (
<Paper
withBorder
p="md"
radius={0}
style={{
position: 'fixed',
bottom: 0,
left: 0,
right: 0,
zIndex: 100,
borderTop: '1px solid #dee2e6',
}}
>
<Group justify="space-around" grow>
<ActionIcon
variant={isConvo ? 'filled' : 'subtle'}
color={isConvo ? 'blue' : 'gray'}
onClick={() => handleNavigation('convo')}
size={48}
radius="md"
>
<IconMessageCircle size={24} />
</ActionIcon>
<ActionIcon
variant={isEdit ? 'filled' : 'subtle'}
color={isEdit ? 'blue' : 'gray'}
onClick={() => handleNavigation('edit')}
size={48}
radius="md"
>
<IconEdit size={24} />
</ActionIcon>
<ActionIcon
variant={isGalaxy ? 'filled' : 'subtle'}
color={isGalaxy ? 'blue' : 'gray'}
onClick={() => handleNavigation('galaxy')}
size={48}
radius="md"
>
<IconUniverse size={24} />
</ActionIcon>
<Box style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<UserMenu />
</Box>
</Group>
</Paper>
);
}

View File

@@ -0,0 +1,40 @@
'use client';
/**
* Mobile Header
*
* Fixed header for mobile devices showing the Ponderants logo.
*/
import { Group, Image, Text, Paper } from '@mantine/core';
export function MobileHeader() {
return (
<Paper
withBorder
p="md"
radius={0}
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
zIndex: 100,
borderBottom: '1px solid #dee2e6',
}}
>
<Group gap="sm" align="center">
<Image
src="/logo.svg"
alt="Ponderants logo"
w={56}
h={56}
style={{ flexShrink: 0 }}
/>
<Text fw={700} size="xl">
Ponderants
</Text>
</Group>
</Paper>
);
}