feat(ui): implement 'Twilight Velvet' dark theme and fix visibility issues

- Add 'Twilight Velvet' color palette to globals.css with OKLCH values
- Update SettingsPage headers, cards, and dialogs to use semantic theme variables
- Update HistoryCard, HistoryFeed, and DraftContent to support dark mode
- Update ProviderSelector and ProviderList to use custom card background (#2A2A3D)
- Add ThemeToggle component with improved visibility
- Ensure consistent use of 'bg-card', 'text-foreground', and 'text-muted-foreground'
This commit is contained in:
Max
2026-01-27 11:03:55 +07:00
parent e9e6fadb1d
commit 9b79856827
49 changed files with 2411 additions and 878 deletions

View File

@@ -1,46 +1,88 @@
import { test, expect } from '@playwright/test';
test('Chat Flow with Mocked LLM', async ({ page }) => {
// 1. Setup Mock API - must be set before navigation
await page.route('**/v1/chat/completions', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
choices: [{
message: { content: "This is a mock AI response." }
}]
})
test.describe('The Venting Ritual', () => {
test.beforeEach(async ({ page }) => {
// Mock Auth
await page.context().addCookies([{
name: 'auth-token',
value: 'authenticated',
domain: 'localhost',
path: '/',
httpOnly: true,
secure: false,
sameSite: 'Lax'
}]);
// Mock Settings (Active Provider) via localStorage
// Since we are mocking network anyway, we just need the app to think it's configured
await page.goto('/settings');
// Actually, we can just use the UI to configure a dummy provider
await page.getByRole('button', { name: 'Add New Provider' }).click();
await page.fill('input[placeholder="My OpenAI Key"]', 'Test Provider');
await page.fill('input[placeholder="https://api.openai.com/v1"]', 'https://api.example.com/v1');
await page.fill('input[placeholder="gpt-4o"]', 'test-model');
await page.fill('input[placeholder="sk-..."]', 'sk-test-key');
// Mock Validation
await page.route('/api/llm', async route => {
const body = route.request().postDataJSON();
// Validation Check
if (body.messages.length === 1 && body.messages[0].content === 'hello') {
await route.fulfill({ json: { success: true, data: { text: 'Hello' } } });
return;
}
// Teacher Response
if (body.messages.some((m: any) => m.role === 'system' && m.content.includes('"Teacher"'))) {
await route.fulfill({ json: { success: true, data: { text: 'That sounds difficult. Tell me more.' } } });
return;
}
// Ghostwriter Response
if (body.messages.some((m: any) => m.role === 'system' && m.content.includes('"Ghostwriter"'))) {
await route.fulfill({
json: {
success: true, data: {
text: JSON.stringify({
title: "The Test Epiphany",
insight: "Testing is crucial for confidence.",
lesson: "Always verify your assumptions."
})
}
}
});
return;
}
});
await page.getByRole('button', { name: 'Save as New Provider' }).click();
});
// 2. Configure Settings
await page.goto('/settings');
await page.getByLabel('API Key').fill('sk-test-key');
await page.getByLabel('Base URL').fill('https://api.mock.com/v1');
await page.getByLabel('Model Name').fill('gpt-mock');
test('should compare venting flow: Input -> Teacher -> Draft -> Insight', async ({ page }) => {
await page.goto('/chat?new=true');
// Wait for settings to be saved (Zustand persist uses localStorage)
await page.waitForTimeout(500);
// 1. User Vents
await page.fill('textarea', 'I am stressed about testing.');
await page.click('button:has-text("Send"), button:has(.lucide-send)');
// Note: Icon button might not have text, use selector for icon or aria-label if added
// The button has <Send> icon inside.
// 3. Go to Chat
await page.goto('/chat');
// 2. Teacher Responds
await expect(page.getByText('That sounds difficult. Tell me more.')).toBeVisible();
// Wait for empty state to appear (indicates session is ready)
await expect(page.getByRole('heading', { name: /frustrating you/i })).toBeVisible({ timeout: 5000 });
// 3. Contextual "Draft" button should appear (phase: elicitation)
// Wait for it because typing might take a moment (50ms per token simulation)
await expect(page.getByRole('button', { name: 'Summarize & Draft' })).toBeVisible();
// 4. Send Message
const input = page.getByRole('textbox');
await input.fill('I hate writing tests.');
// 4. Trigger Drafting
await page.click('button:has-text("Summarize & Draft")');
// Wait for button to be enabled
const sendButton = page.getByRole('button').first();
await expect(sendButton).toBeEnabled({ timeout: 3000 });
await sendButton.click();
// 5. Draft Sheet appears
await expect(page.getByText('The Test Epiphany')).toBeVisible();
await expect(page.getByText('Testing is crucial for confidence.')).toBeVisible();
// 5. Verify User Message - wait for it to appear in the chat
await expect(page.getByText('I hate writing tests.')).toBeVisible({ timeout: 10000 });
// 6. Keep It
await page.getByRole('button', { name: 'Keep It' }).click();
// 6. Verify AI Response
await expect(page.getByText('This is a mock AI response.')).toBeVisible({ timeout: 15000 });
// Should reset or navigate (Story 4.1) - for now just check sheet closed
await expect(page.getByText('The Test Epiphany')).toBeHidden();
});
});