chore: initial checkpoint - Tesla Roadtrip planner

- Proactive Grok integration (xAI API + local CLI fallback)
- Real road routing via OSRM (no more bird's-eye lines)
- Heavy structured logging for fast iteration
- Strong sanitization + geocoding + ErrorBoundary (no black screens)
- Playwright E2E tests (API diagnostic + full UI flow)
- scripts/dev.sh for one-command startup
- Clean .env.example + documentation

This is a stable checkpoint before further prompt/UI refinement.
This commit is contained in:
2026-05-15 19:24:35 +01:00
commit d516e93323
29 changed files with 11927 additions and 0 deletions
+44
View File
@@ -0,0 +1,44 @@
import { test, expect } from '@playwright/test';
test.describe('Grok API Diagnostic (Backend Only)', () => {
test('backend should successfully call xAI Grok and return a response', async ({ request }) => {
test.setTimeout(120000); // Grok can be slow
const payload = {
message: "Plan a short day trip from Milton Keynes to Telford in a Model Y. Include one Supercharger stop.",
vehicle: { name: "Model Y Long Range", rangeKm: 514 },
itinerary: null,
history: []
};
const response = await request.post('http://localhost:3000/api/chat', {
data: payload,
headers: { 'Content-Type': 'application/json' }
});
expect(response.ok()).toBeTruthy();
const body = await response.json();
console.log('[Diagnostic] Grok reply length:', body.reply?.length);
console.log('[Diagnostic] Has itinerary update:', !!body.itinerary);
expect(body.reply).toBeDefined();
expect(body.reply.length).toBeGreaterThan(50);
// It should NOT be the generic error message
expect(body.reply).not.toContain('having trouble reaching Grok');
// If it produced an itinerary, validate basic shape
if (body.itinerary) {
expect(body.itinerary.days).toBeDefined();
expect(Array.isArray(body.itinerary.days)).toBe(true);
expect(body.itinerary.days.length).toBeGreaterThan(0);
console.log('[Diagnostic] Itinerary days:', body.itinerary.days.length);
}
console.log('✅ Backend successfully called real Grok!');
});
});
+54
View File
@@ -0,0 +1,54 @@
import { test, expect } from '@playwright/test';
test.describe('Tesla Roadtrip - Full Chat to Map Flow', () => {
test.setTimeout(180000);
test('should send a trip request, get real Grok response, and render itinerary + map', async ({ page }) => {
await page.goto('http://localhost:5173');
// Better header selector
const grokHeader = page.locator('div.font-semibold.text-lg.tracking-tight:has-text("Grok Drive")').first();
await expect(grokHeader).toBeVisible({ timeout: 15000 });
const chatInput = page.locator('input[placeholder*="Tell me where you want to drive"], input[placeholder*="drive"]');
await expect(chatInput).toBeVisible();
const tripRequest = 'I want to go from Milton Keynes to Telford in my Model Y';
await chatInput.fill(tripRequest);
const sendButton = page.locator('button:has(svg)').last();
await sendButton.click();
// Wait for thinking state
const thinking = page.locator('text=GROK IS PLANNING YOUR ROUTE');
await thinking.waitFor({ state: 'visible', timeout: 20000 }).catch(() => {});
// Wait for Grok to finish
await thinking.waitFor({ state: 'hidden', timeout: 120000 });
const lastAssistant = page.locator('.chat-bubble-assistant').last();
// === DIAGNOSTIC: Detect the common failure mode early ===
const messageText = await lastAssistant.textContent();
if (messageText?.includes('having trouble reaching Grok')) {
await page.screenshot({ path: `test-results/grok-failed-${Date.now()}.png`, fullPage: true });
throw new Error('Grok call failed (got error message). Check backend logs + make sure XAI_API_KEY is loaded correctly (use ./scripts/dev.sh).');
}
// Success path
const dayOne = page.locator('text=DAY 1').first();
await expect(dayOne).toBeVisible({ timeout: 30000 });
const stop = page.locator('.group:has-text("Supercharger"), .group:has-text("Hotel")').first();
await expect(stop).toBeVisible({ timeout: 15000 });
await page.screenshot({
path: `test-results/success-milton-keynes-telford-${Date.now()}.png`,
fullPage: true
});
console.log('✅ Full flow succeeded: Real Grok itinerary rendered on map!');
});
});