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

@@ -1,40 +1,131 @@
import { test } from 'magnitude-test';
test('[Happy Path] User can record voice and see transcript', async (agent) => {
// Act: Go to chat page
await agent.act('Navigate to /chat');
test('[Happy Path] User can have a full voice conversation with AI', async (agent) => {
// Act: Navigate to chat page (assumes user is already authenticated)
await agent.open('http://localhost:3000/chat');
// Check: Verify initial state
await agent.check('The chat input field is empty');
await agent.check('A "Start Recording" button is visible');
// Check: Initial state - voice button shows "Start Voice Conversation"
await agent.check('A button with text "Start Voice Conversation" is visible');
// Act: Click the record button
// Note: This will require mocking the /api/voice-token response and the
// MediaDevices/WebSocket browser APIs in a real test environment
await agent.act('Click the "Start Recording" button');
// Act: Click to start voice mode
await agent.act('Click the "Start Voice Conversation" button');
// Check: UI updates to recording state
await agent.check('A "Stop Recording" button is visible');
// Act: Simulate receiving a transcript from the (mocked) Deepgram WebSocket
await agent.act(
'Simulate an interim transcript "Hello world" from the Deepgram WebSocket'
// Check: Button text changes to indicate checking or generating state
// Could be "Checking for greeting..." or "Generating speech..." or "Listening..."
await agent.check(
'The button text has changed from "Start Voice Conversation" to indicate an active state'
);
// Check: The input field is updated
await agent.check('The chat input field contains "Hello world"');
// Act: If there's a Skip button visible (greeting is playing), click it
await agent.act('Click the Skip button if it is visible');
// Act: Simulate a final transcript
await agent.act(
'Simulate a final transcript "Hello world." from the Deepgram WebSocket'
);
// Check: Should transition to listening state
await agent.check('The button shows "Listening... Start speaking"');
// Check: The "Stop Recording" button is gone
await agent.check('A "Start Recording" button is visible again');
// Check: Development test controls should be visible (in dev mode)
await agent.check('A section with text "DEV: State Machine Testing" is visible');
// Check: The chat input is cleared (because it was submitted)
await agent.check('The chat input field is empty');
// Act: Use dev button to simulate user starting to speak
await agent.act('Click the "Simulate Speech" button in the dev controls');
// Check: The finalized transcript appears as a user message
await agent.check('The message "Hello world." appears in the chat list');
// Check: Button shows speaking state
await agent.check('The button text contains "Speaking"');
// Act: Add a phrase using the dev button
await agent.act('Click the "Add Phrase" button in the dev controls');
// Check: A message bubble appears showing the transcript being spoken
await agent.check('A message with text "You (speaking...)" is visible');
await agent.check('The message contains the text "Test message"');
// Check: Button shows timing out state
await agent.check('The button text contains "auto-submit"');
// Act: Trigger the timeout using dev button
await agent.act('Click the "Trigger Timeout" button in the dev controls');
// Check: Button shows submitting or waiting state
await agent.check('The button text contains "Submitting" or "Waiting for AI"');
// Check: The user message appears in the chat
await agent.check('A message with text "Test message" appears in the chat history');
// Wait for AI response (this takes a few seconds)
await agent.wait(10000);
// Check: AI message appears
await agent.check('An AI message appears in the chat');
// Check: Button shows generating or playing TTS state
await agent.check('The button text contains "Generating speech" or "AI is speaking"');
// Check: Skip button is visible during TTS
await agent.check('A "Skip" button is visible');
// Act: Skip the AI audio
await agent.act('Click the Skip button');
// Check: Returns to listening state
await agent.check('The button shows "Listening... Start speaking"');
// Act: Stop voice mode
await agent.act('Click the main voice button to stop');
// Check: Returns to idle state
await agent.check('The button shows "Start Voice Conversation"');
});
test('[Unhappy Path] Voice mode handles errors gracefully', async (agent) => {
await agent.open('http://localhost:3000/chat');
// Act: Start voice mode
await agent.act('Click the "Start Voice Conversation" button');
// Simulate an error scenario (e.g., microphone permission denied)
// Note: In a real test, this would involve mocking the getUserMedia API to reject
await agent.act('Simulate a microphone permission error');
// Check: Error message is displayed
await agent.check('An error message is shown to the user');
// Check: Voice mode returns to idle state
await agent.check('The button shows "Start Voice Conversation"');
});
test('[Happy Path] Text input is disabled during voice mode', async (agent) => {
await agent.open('http://localhost:3000/chat');
// Check: Text input is enabled initially
await agent.check('The text input field "Or type your thoughts here..." is enabled');
// Act: Start voice mode
await agent.act('Click the "Start Voice Conversation" button');
// Check: Text input is disabled
await agent.check('The text input field is disabled');
// Act: Stop voice mode
await agent.act('Click the main voice button to stop');
// Check: Text input is enabled again
await agent.check('The text input field is enabled');
});
test('[Happy Path] User can type a message while voice mode is idle', async (agent) => {
await agent.open('http://localhost:3000/chat');
// Act: Type a message in the text input
await agent.act('Type "This is a text message" into the text input field');
// Act: Submit the message
await agent.act('Press Enter or click the Send button');
// Check: Message appears in chat
await agent.check('The message "This is a text message" appears as a user message');
// Wait for AI response
await agent.wait(5000);
// Check: AI responds
await agent.check('An AI response appears in the chat');
});

View File

@@ -0,0 +1,37 @@
/**
* Magnitude Test: Cache Success
*
* This test verifies that node publishing succeeds with full cache write,
* not just a degraded state with warnings.
*/
import { test } from 'magnitude-test';
test('Node publishes successfully with cache (no warnings)', async (agent) => {
await agent.open('http://localhost:3000');
// Login
await agent.act('Click the "Log in with Bluesky" button');
await agent.act('Fill in credentials and submit')
.data({
username: process.env.TEST_BLUESKY_USERNAME || 'test-user.bsky.social',
password: process.env.TEST_BLUESKY_PASSWORD || 'test-password',
});
await agent.check('Logged in successfully');
// Start conversation
await agent.act('Type "Test cache write success" and press Enter');
await agent.check('AI responds');
// Create and publish node
await agent.act('Click "Create Node"');
await agent.check('On edit page with draft');
await agent.act('Click "Publish Node"');
// CRITICAL: Should get green success notification, NOT yellow warning
await agent.check('Success notification is GREEN (not yellow warning)');
await agent.check('Notification says "Your node has been published to your Bluesky account"');
await agent.check('Notification does NOT mention "cache update failed"');
await agent.check('Notification does NOT mention "Advanced features may be unavailable"');
});

View File

@@ -0,0 +1,220 @@
/**
* Magnitude Tests: Node Publishing Flow
*
* Tests for the complete node creation, editing, and publishing workflow.
* Covers both happy path and error scenarios.
*/
import { test } from 'magnitude-test';
// ============================================================================
// HAPPY PATH TESTS
// ============================================================================
test('User can publish a node from conversation', async (agent) => {
await agent.open('http://localhost:3000');
// Step 1: Login with Bluesky
await agent.act('Click the "Log in with Bluesky" button');
await agent.check('Redirected to Bluesky login page');
await agent.act('Fill in username and password')
.data({
username: process.env.TEST_BLUESKY_USERNAME || 'test-user.bsky.social',
password: process.env.TEST_BLUESKY_PASSWORD || 'test-password',
});
await agent.act('Click the login submit button');
await agent.check('Redirected back to app and logged in');
await agent.check('Chat interface is visible');
// Step 2: Start a conversation
await agent.act('Type "Let\'s discuss the philosophy of decentralized social networks" into the chat input and press Enter');
await agent.check('Message appears in chat');
await agent.check('AI response appears');
// Step 3: Create node draft
await agent.act('Click the "Create Node" button');
await agent.check('Navigated to edit page');
await agent.check('Title input has AI-generated content');
await agent.check('Content textarea has AI-generated content');
await agent.check('Conversation context is visible at the bottom');
// Step 4: Publish the node
await agent.act('Click the "Publish Node" button');
await agent.check('Success notification appears with "Node published!"');
await agent.check('Returned to conversation view');
});
test('User can edit node draft before publishing', async (agent) => {
// Assumes user is already logged in from previous test
await agent.open('http://localhost:3000/chat');
// Start conversation
await agent.act('Type "Testing the edit flow" and press Enter');
await agent.check('AI responds');
// Create draft
await agent.act('Click "Create Node"');
await agent.check('On edit page with draft content');
// Edit the content
await agent.act('Clear the title input and type "My Custom Title"');
await agent.act('Modify the content textarea to add "This is my edited content."');
await agent.check('Title shows "My Custom Title"');
await agent.check('Content includes "This is my edited content."');
// Publish
await agent.act('Click "Publish Node"');
await agent.check('Success notification appears');
});
test('User can cancel node draft without publishing', async (agent) => {
await agent.open('http://localhost:3000/chat');
// Start conversation
await agent.act('Type "Test cancellation" and press Enter');
await agent.check('AI responds');
// Create draft
await agent.act('Click "Create Node"');
await agent.check('On edit page');
// Cancel instead of publishing
await agent.act('Click the "Cancel" button');
await agent.check('Returned to conversation view');
await agent.check('Draft was not published'); // Verify no success notification
});
// ============================================================================
// UNHAPPY PATH TESTS
// ============================================================================
test('Cannot publish node without authentication', async (agent) => {
// Open edit page directly without being logged in
await agent.open('http://localhost:3000/edit');
await agent.check('Shows empty state message');
await agent.check('Message says "No Node Draft"');
await agent.check('Suggests to start a conversation');
});
test('Cannot publish node with empty title', async (agent) => {
await agent.open('http://localhost:3000/chat');
// Create draft
await agent.act('Type "Test empty title validation" and press Enter');
await agent.check('AI responds');
await agent.act('Click "Create Node"');
await agent.check('On edit page');
// Clear the title
await agent.act('Clear the title input completely');
await agent.check('Publish button is disabled');
});
test('Cannot publish node with empty content', async (agent) => {
await agent.open('http://localhost:3000/chat');
// Create draft
await agent.act('Type "Test empty content validation" and press Enter');
await agent.check('AI responds');
await agent.act('Click "Create Node"');
await agent.check('On edit page');
// Clear the content
await agent.act('Clear the content textarea completely');
await agent.check('Publish button is disabled');
});
test('Shows error notification if publish fails', async (agent) => {
await agent.open('http://localhost:3000/chat');
// Create draft
await agent.act('Type "Test error handling" and press Enter');
await agent.check('AI responds');
await agent.act('Click "Create Node"');
await agent.check('On edit page');
// Simulate network failure by disconnecting (this is a mock scenario)
// In real test, this would require mocking the API
await agent.act('Click "Publish Node"');
// If there's a network error, should see error notification
// Note: This test may need to mock the fetch call to force an error
await agent.check('Either success or error notification appears');
});
test('Handles long content with truncation', async (agent) => {
await agent.open('http://localhost:3000/chat');
// Create a very long message
const longMessage = 'A'.repeat(500) + ' This is a test of long content truncation for Bluesky posts.';
await agent.act(`Type "${longMessage}" and press Enter`);
await agent.check('AI responds');
await agent.act('Click "Create Node"');
await agent.check('On edit page');
await agent.act('Click "Publish Node"');
// Should still publish successfully (with truncation)
await agent.check('Success notification appears');
await agent.check('May show warning about cache or truncation');
});
test('Shows warning when cache fails but publish succeeds', async (agent) => {
await agent.open('http://localhost:3000/chat');
await agent.act('Type "Test cache failure graceful degradation" and press Enter');
await agent.check('AI responds');
await agent.act('Click "Create Node"');
await agent.check('On edit page');
await agent.act('Click "Publish Node"');
// The system should succeed even if cache/embeddings fail
await agent.check('Success notification appears');
// May show yellow warning notification instead of green success
await agent.check('Notification says "Node published"');
});
// ============================================================================
// INTEGRATION TESTS
// ============================================================================
test('Complete user journey: Login → Converse → Publish → View', async (agent) => {
// Full end-to-end test
await agent.open('http://localhost:3000');
// Login
await agent.act('Login with Bluesky')
.data({
username: process.env.TEST_BLUESKY_USERNAME,
password: process.env.TEST_BLUESKY_PASSWORD,
});
await agent.check('Logged in successfully');
// Have a meaningful conversation
await agent.act('Type "I want to explore the concept of digital gardens" and send');
await agent.check('AI responds with insights');
await agent.act('Reply with "How do digital gardens differ from blogs?"');
await agent.check('AI provides detailed explanation');
// Create and publish
await agent.act('Click "Create Node"');
await agent.check('Draft generated from conversation');
await agent.act('Review the draft and click "Publish Node"');
await agent.check('Node published successfully');
// Verify we can continue the conversation
await agent.check('Back in conversation view');
await agent.check('Can type new messages');
});