Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0db8043
feat: pivot to point-to-point planning, add multi-page navigation, an…
GehDoc Jun 16, 2026
3903da5
feat: refine rigid workspace layout, restore api key visibility, and …
GehDoc Jun 16, 2026
cd33cf9
feat: implement immersive split-dashboard layout with adaptive respon…
GehDoc Jun 16, 2026
133101d
feat: simplify responsive breakpoints and fix horizontal truncation w…
GehDoc Jun 16, 2026
d5e052a
feat: restore chat icon in planning form label
GehDoc Jun 16, 2026
0c9f89e
style: restore standard button height in planning form
GehDoc Jun 16, 2026
0a18274
style: unify trip viewer grid split to prevent sudden column shrinkage
GehDoc Jun 16, 2026
14f5c51
style: finalize global standard UI scale with proper formatting
GehDoc Jun 16, 2026
87e7251
fix: allow vertical scrolling for static pages in RootLayout
GehDoc Jun 16, 2026
7ba42f5
style: restore label legibility and increase UI contrast across the w…
GehDoc Jun 17, 2026
792be08
feat: integrate plan button into textarea for unified command input
GehDoc Jun 17, 2026
b54598a
style: delay dashboard split to 2xl breakpoint to prevent cramped UI
GehDoc Jun 18, 2026
7f4f27b
style: widen arrival details column for better readability on large s…
GehDoc Jun 18, 2026
cd4ece2
style: clamp map height in stacked view to prevent details from being…
GehDoc Jun 18, 2026
f7acbc9
feat: complete Umami analytics tracking and documentation; final spec…
GehDoc Jun 18, 2026
65d276a
feat: implement conditional TripHistory/TripNavigator rendering in si…
GehDoc Jun 18, 2026
bffc475
style: refine TripNavigator styling for visual integration into sidebar
GehDoc Jun 18, 2026
9cb086c
feat: complete ui-refinement-v1 spec with responsive TripNavigator im…
GehDoc Jun 18, 2026
06fc2ec
API key secrutiry warning
GehDoc Jun 19, 2026
2de8121
fix E2E test
GehDoc Jun 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ Thank you for your interest in contributing! We follow a **Spec-Driven Developme
- **No Inline Styles**: To ensure maintainability and style consistency, inline styles (`style={{ ... }}`) are prohibited in production components. Use Tailwind classes exclusively.
- **Markdown Security**: All AI-generated content MUST be passed through the sanitization pipeline in `TripViewer.tsx`. Never use `dangerouslySetInnerHTML` or disable sanitization without a security review. Any new component added to the markdown renderer must be explicitly whitelisted in the sanitization schema.

## 📊 Analytics & Privacy

This project uses **Umami Analytics** for anonymous usage data collection. Our commitment to user privacy is paramount:

- **Purpose**: We collect aggregated, anonymous data (like page views and feature usage events) to understand how the application is used and to guide future improvements.
- **No PII**: No personally identifiable information (PII) is ever collected, stored, or shared. We do not track IP addresses, use cookies, or create persistent user profiles.
- **Production Only**: Analytics are only active in the production build of the application.
- **Transparency**: Refer to `docs/ARCHITECTURE.md` for a detailed breakdown of the specific events we track and our data governance principles.

## 🔄 Workflow & Automation

### 🚦 Type Safety & Commit Hooks
Expand Down
89 changes: 25 additions & 64 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,96 +5,57 @@
[![Hosting](https://img.shields.io/badge/Hosted_on-GitHub_Pages-blue?logo=github)](https://gehdoc.github.io/quick-tripper)
[![Framework](https://img.shields.io/badge/Built_with-React-61DAFB?logo=react)](https://react.dev)
[![AI](https://img.shields.io/badge/AI_Model-Llama_3-7B1FA2?logo=meta)](https://huggingface.co)
[![License](https://img.shields.io/badge/License-MIT-green)](./LICENSE)

**Quick-tripper** is a privacy-first, zero-backend travel companion web app that generates detailed road trip itineraries using AI. It operates entirely in your browser—no databases, no logins, no tracking. Now powered by **Llama 3.1** via the Hugging Face Serverless API.
**Quick-tripper** is a privacy-first, zero-backend "Point-to-Point" trip planner. Instead of overwhelming you with complex itineraries, it helps you plan specific journeys one-by-one—from a starting point to an arrival—leveraging native Google Maps route proposals and AI-powered arrival formatting.

## ✨ Features

- **🌍 Smart Itineraries**: Generate day-by-day travel plans using Llama 3.1.
- **🗺️ Interactive Maps**: Embedded Google Maps for every journey, rendered securely from AI-generated routes.
- **🔒 Privacy-First**: "Bring Your Own Key" (BYOK) model. Your API token is stored locally in your browser and never sent to our servers.
- **🔗 Compressed Sharing**: Share your entire itinerary via a single, ultra-compressed URL (powered by LZString).
- **💾 Local Persistence**: Automatic synchronization with your browser's local storage.
- **🛡️ Secure Rendering**: Strict markdown sanitization to prevent XSS and ensure your browser environment remains safe.
- **📍 Point-to-Point Planning**: Focus on one trip at a time. Define where you start and where you want to go.
- **🗺️ Native Route Proposals**: Directly integrates with Google Maps to provide the best routes, alternatives, and live navigation.
- **📝 Formatted Arrival Insights**: Use your own notes about the arrival or point of interest; our AI ensures they are cleanly formatted and grammatically correct.
- **🔒 Privacy-First**: "Bring Your Own Key" (BYOK) model. Your API token and trip data stay in your browser. No databases, no tracking, no accounts.
- **🔗 Compressed Sharing**: Share any trip via a single, ultra-compressed URL (powered by LZString).
- **💾 Local Workspace**: Your trip history is automatically synchronized with your browser's local storage.

## 📖 How-to Use

1. **Enter your Hugging Face API Token**: Get a free "User Access Token" from your [Hugging Face Settings](https://huggingface.co/settings/tokens).
2. **Describe your trip**: Use the prompt area to describe where you want to go, for how long, and what you like.
3. **Generate**: Click "Generate Itinerary" and wait a few seconds.
4. **Explore**: Use the interactive map and read the day-by-day suggestions.
5. **Manage**: Your trips are saved automatically. You can export them as JSON or share them via a unique link.

## 🔒 Privacy & BYOK (Bring Your Own Key)
## 🔒 The "Free & Private" Philosophy

Quick-tripper is designed to be **serverless and private**. We don't have a backend to store your data or your keys.
Quick-tripper is built on three core pillars:

- **Local Storage**: Your API token and trip history are stored ONLY in your browser's local storage.
- **Direct AI Calls**: The app communicates directly with Hugging Face APIs from your browser.
- **No Tracking**: We don't use cookies, analytics, or tracking scripts.
1. **Free to Use**: Open-source under the MIT license. It leverages free-tier AI inference via Hugging Face.
2. **Privacy as a Right**: We don't want your data. There is no backend, no login, and no telemetry.
3. **User Empowerment**: You bring your own API key, giving you full control over your AI usage and privacy.

To use the app, you need a **Hugging Face User Access Token**.
## 📖 How-to Use

- Visit [Hugging Face Settings](https://huggingface.co/settings/tokens).
- Create a new token (Read access is enough).
- Paste it into the top bar of Quick-tripper.
1. **Enter your Hugging Face API Token**: Get a free "User Access Token" from [Hugging Face](https://huggingface.co/settings/tokens).
2. **Plan your trip**: Enter your starting point and arrival. Add some notes about what you want to do at the destination.
3. **Generate**: Click "Plan Trip". The AI will extract the locations and format your notes.
4. **Navigate**: Use the direct Google Maps link to explore route proposals and start your journey.

## 🛠️ Tech Stack

- **Framework**: [Next.js 15+](https://nextjs.org) (App Router)
- **Styling**: [TailwindCSS 4](https://tailwindcss.com) + [DaisyUI 5](https://daisyui.com)
- **AI Integration**: [Llama 3.1 8B](https://huggingface.co/meta-llama/Llama-3.1-8B-Instruct) via Hugging Face Router.
- **Data Compression**: [LZString](https://pieroxy.net/lua/lz-string/index.html)
- **Testing**: [Vitest](https://vitest.dev) + [React Testing Library](https://testing-library.com)
- **Automation**: [Husky](https://typicode.github.io/husky/) + [lint-staged](https://github.com/lint-staged/lint-staged)
- **Analytics**: [Umami](https://umami.is) (Privacy-focused, cookieless)

## 🔄 Development Workflow (SDD)

This project follows **Spec-Driven Development (SDD)** to maintain a clear roadmap and high technical standards. Before implementing a new feature, a technical specification must be drafted and approved.

- **For Contributors**: See **[CONTRIBUTING.md](./CONTRIBUTING.md)** for workflow and commands.
- **For AI Agents**: Follow the **[AGENTS.md](./AGENTS.md)** protocol.
This project follows **Spec-Driven Development (SDD)**. See **[AGENTS.md](./AGENTS.md)** for protocol.

## 🚀 Getting Started

1. **Clone the Repo**:
```bash
git clone https://github.com/gehdoc/quick-tripper.git
cd quick-tripper
```
2. **Install Dependencies**:
```bash
npm install
```
3. **Run Locally**:
```bash
npm run dev
```
4. **Open the App**: Visit `http://localhost:3000` (development) or `http://localhost:3000/quick-tripper` (production-emulated) and enter your API key in the top bar.
```bash
npm install
npm run dev
```

## 🏗️ Architecture

For a detailed look at the system design, core modules, and data contract versioning, see: **[docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md)**.

## 🧪 Quality & Integrity Strategy

To maintain a robust "zero-backend" application, we rely on a multi-tiered validation strategy:

1. **Architectural Design**: High-level designs and data contracts are documented in `ARCHITECTURE.md`.
2. **Static Analysis**: ESLint and Prettier ensure code consistency and catch early errors.
3. **Type Safety**: Strict TypeScript is enforced at the commit level via Husky hooks.
4. **Unit Testing**: Vitest and JSDOM validate core logic, utility functions, and presentational components (co-located tests).
5. **E2E Testing**: Playwright validates full user workflows and "golden paths" in a real browser environment.
6. **Spec-First Implementation**: Every change is traced back to a technical specification in `specs/`, ensuring architectural alignment.
For a detailed look at the system design, see: **[docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md)**.

## 📄 License

MIT © [Quick-tripper](https://github.com/gehdoc/quick-tripper)

## 💖 Support the Project

If you find this tool helpful, please consider supporting its development:

[![Donate](https://img.shields.io/badge/Donate-PayPal-blue.svg)](https://paypal.me/GehDoc)

Your support helps cover maintenance and further development of the tool. Thank you!
26 changes: 26 additions & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,32 @@ We enforce a JSON schema through strict system prompts. This allows us to reliab
- `start`/`stop`: Geocodable endpoints for map rendering and future filtering.
- `content`: The markdown-formatted itinerary.

## Analytics Integration

The application utilizes **Umami Analytics** for privacy-focused, cookieless event tracking. This aligns with the "Privacy-First" philosophy by providing essential usage insights without compromising user data or privacy.

### Core Principles

- **Production-Only**: The Umami tracking script is loaded exclusively in the production environment.
- **No Personal Data**: Only aggregated, anonymous event data is collected. No personally identifiable information (PII) is ever transmitted.
- **Cookieless**: Umami operates without the use of cookies or persistent identifiers across sessions.

### Tracked Events

The following user interactions are monitored to understand application usage and feature adoption:

- `trip_generation_started`: Fired when a user initiates the AI trip generation process.
- `trip_generation_failed`: Fired when the AI trip generation process encounters an error (e.g., missing API key, empty prompt, AI service error).
- Associated data: `reason` (for missing API key or prompt), `error_message` (for AI service errors).
- `trip_generation_success`: Fired when a new trip is successfully generated by the AI.
- Associated data: `title` (of the generated trip).
- `trip_shared`: Fired when a user initiates sharing of an active trip.
- Associated data: `trip_id` (ID of the shared trip).
- `trip_exported`: Fired when a user exports their saved trip history.
- Associated data: `count` (number of trips exported).
- `trip_deleted`: Fired when a user removes a trip from their history.
- Associated data: `trip_id` (ID of the deleted trip).

## Security Architecture

### Markdown Sanitization (XSS Prevention)
Expand Down
12 changes: 7 additions & 5 deletions e2e/golden-path.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { test, expect } from '@playwright/test';

test('golden path - generate trip', async ({ page }) => {
await page.goto('/');

await page.waitForLoadState('networkidle'); // Ensure page is fully loaded and stable
// Mock Hugging Face API (OpenAI compatible)
await page.route('**/v1/chat/completions', (route) =>
route.fulfill({
Expand All @@ -25,14 +25,16 @@ test('golden path - generate trip', async ({ page }) => {
}),
);

// Enter API Key (Restored Hugging Face placeholder)
await page.getByPlaceholder('HuggingFace API Token').fill('hf_dummy-key');
// Enter API Key
await page.getByPlaceholder('HF Token').fill('hf_dummy-key');

// Enter Prompt (Textarea)
await page.getByPlaceholder(/Ex: A 4-day hike itinerary/i).fill('Swiss Alps');
const promptTextarea = page.getByPlaceholder('Ex: From Paris to Mont Saint-Michel...');
await expect(promptTextarea).toBeVisible(); // Explicitly wait for visibility
await promptTextarea.fill('Swiss Alps');

// Send
await page.getByRole('button', { name: /Generate Itinerary/i }).click();
await page.getByRole('button', { name: 'Plan Trip' }).click();

// Verify Heading (Rendered in Navigator) - Using .first() or specific role to avoid ambiguity
await expect(page.getByRole('heading', { name: 'Golden Trip' })).toBeVisible();
Expand Down
6 changes: 4 additions & 2 deletions e2e/smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { test, expect } from '@playwright/test';

test('homepage should have correct branding and show EmptyState', async ({ page }) => {
await page.goto('/');
await expect(page.getByText('Quick-tripper')).toBeVisible();
await expect(page.getByText('No active itineraries loaded')).toBeVisible();
await expect(page.getByRole('link', { name: /Quick-tripper/i })).toBeVisible();
// Ensure EmptyState is visible, allowing for layout shifts
const emptyStateLocator = page.getByText('No trips planned yet');
await expect(emptyStateLocator).toBeVisible({ timeout: 10000 }); // Increase timeout and rely on toBeVisible's implicit scroll
});
5 changes: 3 additions & 2 deletions next.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/** @type {import('next').NextConfig} */
const isProd = process.env.NODE_ENV === 'production';
const isPlaywrightTest = process.env.PLAYWRIGHT_TEST === 'true';

const nextConfig = {
output: 'export', // Indispensable pour GitHub Pages
basePath: isProd ? '/quick-tripper' : '',
assetPrefix: isProd ? '/quick-tripper/' : '',
basePath: isPlaywrightTest ? '' : isProd ? '/quick-tripper' : '',
assetPrefix: isPlaywrightTest ? '' : isProd ? '/quick-tripper/' : '',
images: {
unoptimized: true, // Recommandé pour l'export statique
},
Expand Down
Loading
Loading