diff --git a/.env.example b/.env.example
index 7e4dab3..0f16266 100644
--- a/.env.example
+++ b/.env.example
@@ -14,3 +14,5 @@ FEATURE_FLAG_NEW_UI=true
OPENAI_API_KEY=your_openai_api_key_here
LLM_MODEL=gpt-4o-mini
LLM_TEMPERATURE=0.7
+# Security
+APP_PASSWORD=password
diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml
index 416b271..010062f 100644
--- a/_bmad-output/implementation-artifacts/sprint-status.yaml
+++ b/_bmad-output/implementation-artifacts/sprint-status.yaml
@@ -1,6 +1,6 @@
-# generated: 2026-01-24
-# project: Test01
-# project_key: TEST01
+# generated: 2026-01-27
+# project: Brachnha Insights
+# project_key: BRACHNHA
# tracking_system: file-system
# story_location: /home/maximilienmao/Projects/Test01/_bmad-output/implementation-artifacts
@@ -33,41 +33,43 @@
# - SM typically creates next story after previous one is 'done' to incorporate learnings
# - Dev moves story to 'review', then runs code-review (fresh context, different LLM recommended)
-generated: 2026-01-24
-project: Test01
-project_key: TEST01
+generated: 2026-01-27
+project: Brachnha Insights
+project_key: BRACHNHA
tracking_system: file-system
story_location: /home/maximilienmao/Projects/Test01/_bmad-output/implementation-artifacts
development_status:
- # Epic 1: "Active Listening" - Core Chat & Teacher Agent
+ # Epic 1: Gatekeeper Security
epic-1: done
- 1-1-local-first-setup-chat-storage: done
- 1-2-chat-interface-implementation: done
- 1-3-teacher-agent-logic-intent-detection: done
- 1-4-fast-track-mode: done
+ 1-1-security-middleware-lock-screen: done
+ 1-2-server-side-validation-app-password: done
+ 1-3-session-persistence: done
epic-1-retrospective: done
- # Epic 2: "The Magic Mirror" - Ghostwriter & Draft Refinement
+ # Epic 2: Project Calibration
epic-2: done
- 2-1-ghostwriter-agent-markdown-generation: done
- 2-2-draft-view-ui-the-slide-up: done
- 2-3-refinement-loop-regeneration: done
- 2-4-export-copy-actions: done
+ 2-1-settings-feature-shell: done
+ 2-2-provider-management-crud: done
+ 2-3-secure-credentials-storage: done
+ 2-4-connection-validation: done
+ 2-5-active-provider-switcher: done
epic-2-retrospective: done
- # Epic 3: "My Legacy" - History, Offline Sync & PWA Polish
+ # Epic 3: The Venting Ritual
epic-3: done
- 3-1-history-feed-ui: done
- 3-2-deletion-management: done
- 3-3-offline-sync-queue: done
- 3-4-pwa-install-prompt-manifest: done
- epic-3-retrospective: optional
+ 3-1-chat-interface-state: done
+ 3-2-teacher-agent-elicitation-logic: done
+ 3-3-ghostwriter-agent-draft-generation: done
+ 3-4-draft-review-ui-slide-up: done
+ 3-5-regeneration-loop-refinement: done
+ epic-3-retrospective: done
- # Epic 4: "Power User Settings" - BYOD & Configuration
+ # Epic 4: Journey Management
epic-4: done
- 4-1-api-provider-configuration-ui: done
- 4-2-connection-validation: done
- 4-3-model-selection-configuration: done
- 4-4-provider-switching: done
- epic-4-retrospective: optional
+ 4-1-history-feed-ui: done
+ 4-2-detailed-artifact-view: done
+ 4-3-action-menu-export-delete: done
+ 4-4-offline-sync-queue: done
+ 4-5-data-export-utility: done
+ epic-4-retrospective: done
diff --git a/_bmad-output/planning-artifacts/epics.md b/_bmad-output/planning-artifacts/epics.md
index e97c0d2..818677c 100644
--- a/_bmad-output/planning-artifacts/epics.md
+++ b/_bmad-output/planning-artifacts/epics.md
@@ -1,450 +1,373 @@
---
-stepsCompleted:
- - step-01-validate-prerequisites.md
- - step-02-design-epics.md
- - step-03-create-stories.md
- - step-04-final-validation.md
+stepsCompleted: ['step-01-validate-prerequisites', 'step-02-design-epics', 'step-03-create-stories', 'step-04-final-validation']
inputDocuments:
- - file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/prd.md
- - file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/architecture.md
- - file:///home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/ux-design-specification.md
+ - /home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/prd.md
+ - /home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/architecture.md
+ - /home/maximilienmao/Projects/Test01/_bmad-output/planning-artifacts/ux-design-specification.md
---
-# Test01 - Epic Breakdown
+# Brachnha - Epic Breakdown
## Overview
-This document provides the complete epic and story breakdown for Test01, decomposing the requirements from the PRD, UX Design if it exists, and Architecture requirements into implementable stories.
+This document provides the complete epic and story breakdown for Brachnha, decomposing the requirements from the PRD, UX Design, and Architecture requirements into implementable stories.
## Requirements Inventory
### Functional Requirements
-FR-01: System can detect "Venting" vs. "Insight" intent from initial user input.
-FR-02: "Teacher Agent" can generate probing questions to elicit specific missing details based on the user's initial input.
-FR-03: "Ghostwriter Agent" can transform the structured interview data into a grammatically correct and structured "Enlightenment" artifact (e.g., Markdown post).
-FR-04: Users can "Regenerate" the outcome with specific critique (e.g., "Make it less corporate", "Focus more on the technical solution").
-FR-05: System provides a "Fast Track" option to bypass the interview and go straight to generation for advanced users.
-FR-06: Users can view a chronological feed of past "Enlightenments" (history).
-FR-07: Users can "One-Click Copy" the formatted text to clipboard.
-FR-08: Users can delete past entries.
-FR-09: Users can edit the generated draft manually before exporting.
-FR-10: Users can access the app and view history while offline.
-FR-11: Users can complete a full "Venting Session" offline; system queues generation for reconnection.
-FR-12: System actively prompts users to "Add to Home Screen" (A2HS) upon meeting engagement criteria.
-FR-13: System stores all chat history locally (persistent client-side storage) by default.
-FR-14: Users can export their entire history as a JSON/Markdown file.
+FR1: System can detect "Venting" vs. "Insight" intent from initial user input.
+FR2: "Teacher Agent" can generate probing questions to elicit specific missing details based on the user's initial input.
+FR3: "Ghostwriter Agent" can transform the structured interview data into a grammatically correct and structured "Enlightenment" artifact (e.g., Markdown post).
+FR4: Users can "Regenerate" the outcome with specific critique (e.g., "Make it less corporate", "Focus more on the technical solution").
+FR5: System provides a "Fast Track" option to bypass the interview and go straight to generation for advanced users.
+FR6: Users can view a chronological feed of past "Enlightenments" (history).
+FR7: Users can "One-Click Copy" the formatted text to clipboard.
+FR8: Users can delete past entries.
+FR9: Users can edit the generated draft manually before exporting.
+FR10: Users can access the app and view history while offline.
+FR11: Users can complete a full "Venting Session" offline; system queues generation for reconnection.
+FR12: System actively prompts users to "Add to Home Screen" (A2HS) upon meeting engagement criteria.
+FR13: System stores all chat history locally (persistent client-side storage) by default.
+FR14: Users can export their entire history as a JSON/Markdown file.
+FR15: Users can configure a custom OpenAI-compatible Base URL (e.g., `https://api.deepseek.com/v1`).
+FR16: Users can securely save API Credentials (stored in local storage, never transmitted to backend).
+FR17: Users can specify the Model Name (e.g., `gpt-4o`, `deepseek-chat`).
+FR18: System validates the connection to the custom provider upon saving.
+FR19: Users can switch between configured providers globally.
+FR20: System presents a lock screen upon initial load if not authenticated.
+FR21: System validates user-entered password against server-side `APP_PASSWORD`.
+FR22: Authenticated session persists (via secure cookie) to prevent frequent logouts on personal devices.
### NonFunctional Requirements
-NFR-01 (Chat Latency): The "Teacher" agent must generate the first follow-up question within < 3 seconds to maintain conversational flow.
-NFR-02 (App Load Time): The app must be interactive (Time to Interactive) in < 1.5 seconds on 4G networks.
-NFR-03 (Data Sovereignty): User chat logs are stored 100% Client-Side (persistent client-side storage) in the MVP. No user content is sent to the cloud except for the temporary API inference call.
-NFR-04 (Inference Privacy): Data sent to the LLM API must be stateless (not used for training).
-NFR-05 (Offline Behavior): The app shell and local history must remain accessible in Aeroplane Mode. Active Chat interactions will be unavailable offline as they require live LLM access.
-NFR-06 (Data Persistence): Drafts must be auto-saved locally every 2 seconds to prevent data loss.
-NFR-07 (Visual Accessibility): Dark Mode is the default. Contrast ratios must meet WCAG AA standards to reduce eye strain for late-night users.
+NFR1: (Chat Latency) The "Teacher" agent must generate the first follow-up question within **< 3 seconds** to maintain conversational flow.
+NFR2: (App Load Time) The app must be interactive (Time to Interactive) in **< 1.5 seconds** on 4G networks.
+NFR3: (Data Sovereignty) User chat logs AND API Keys are stored **100% Client-Side** (persistent client-side storage). No user content or keys are sent to any middle-man server.
+NFR4: (Inference Privacy) Data sent to the user-configured LLM API must be stateless (not used for training, subject to provider terms).
+NFR5: (Offline Behavior) The app shell and local history must remain accessible in Aeroplane Mode. Active Chat interactions will be unavailable offline as they require live LLM access.
+NFR6: (Data Persistence) Drafts must be auto-saved locally every **2 seconds** to prevent data loss.
+NFR7: (Visual Accessibility) Dark Mode is the default. Contrast ratios must meet **WCAG AA** standards to reduce eye strain for late-night users.
+NFR8: (Secure Key Storage) API Keys must be encrypted at rest or stored in secure local storage capabilities where possible, and never included in exports/logs.
+NFR9: (Gatekeeper Security) The app must restrict access to the UI via a simple, high-protection login screen backed by a server-side `APP_PASSWORD` environment variable. This protects personal deployments (VPS) from unauthorized public access.
### Additional Requirements
-- [Arch] Use Next.js 14+ App Router + ShadCN UI starter template
-- [Arch] Implement "Local-First" architecture with Dexie.js (IndexedDB)
-- [Arch] Implement Vercel Edge Functions for secure LLM API proxy
-- [Arch] Use Zustand for global state management
-- [Arch] Implement Service Worker for offline support and sync queue
-- [UX] Implement "Morning Mist" theme with Inter (UI) and Merriweather (Content) fonts
-- [UX] Implement "Chat" vs "Draft" view split pattern/slide-up sheet
-- [UX] Ensure mobile-first responsive design (375px+) with centered container for desktop
-- [UX] Adhere to WCAG AA accessibility standards (contrast, focus, zoom)
+- [Architecture] Use Next.js 14+ (App Router) with ShadCN UI and Tailwind CSS.
+- [Architecture] Use Zustand v5 for Global State Management.
+- [Architecture] Use Dexie.js v4.2.1 for Client-Side Database (IndexedDB).
+- [Architecture] Use Service Workers for Offline capabilities.
+- [Architecture] Implement "Logic Sandwich" Service Layer Pattern (UI -> Store -> Service -> DB).
+- [Architecture] Vercel Edge Runtime for API Routes (Proxy).
+- [UX] Mobile-First Design targeting 375px+ screens; Desktop centered max 600px.
+- [UX] "Morning Mist" Theme (Pastel/Calm colors).
+- [UX] Custom Chat Bubbles (Telegram-style).
+- [UX] Slide-Up Draft View for "Magic Moment".
+- [UX] Accessibility: WCAG AA Compliance, High Refresh Rate support.
### FR Coverage Map
-FR-01: Epic 1 - Initial intent detection logic in the main chat loop.
-FR-02: Epic 1 - Teacher agent logic and prompt engineering for elicitation.
-FR-03: Epic 2 - Ghostwriter agent logic and Markdown artifact generation.
-FR-04: Epic 2 - Regeneration workflow for draft refinement.
-FR-05: Epic 1 - Option to skip straight to generation (Fast Track).
-FR-06: Epic 3 - History feed UI and data retrieval.
-FR-07: Epic 2 - Copy to clipboard functionality in draft view.
-FR-08: Epic 3 - Deletion management in history feed.
-FR-09: Epic 2 - Manual editing capabilities for generated drafts.
-FR-10: Epic 3 - Offline history access via IndexedDB.
-FR-11: Epic 3 - Offline/Online sync queue for venting sessions.
-FR-12: Epic 3 - PWA installation prompt logic.
-FR-13: Epic 1 - Chat storage infrastructure (Dexie.js).
-FR-14: Epic 3 - Data export functionality.
-FR-15: Epic 4 (Story 4.1) - Custom API URL configuration.
-FR-16: Epic 4 (Story 4.1) - Secure local credential storage.
-FR-17: Epic 4 (Story 4.3) - Model selection logic.
-FR-18: Epic 4 (Story 4.2) - Connection validation.
-FR-19: Epic 4 (Story 4.4) - Provider switching logic.
+FR1: Epic 3 - Venting Intent Detection
+FR2: Epic 3 - Teacher Agent Elicitation
+FR3: Epic 3 - Ghostwriter Artifact Generation
+FR4: Epic 3 - Regeneration & Critique
+FR5: Epic 3 - Fast Track Mode
+FR6: Epic 4 - History Feed
+FR7: Epic 4 - One-Click Copy
+FR8: Epic 4 - Delete Entry
+FR9: Epic 4 - Manual Editing
+FR10: Epic 4 - Offline Access
+FR11: Epic 4 - Offline Queueing
+FR12: Epic 4 - A2HS Prompt
+FR13: Epic 4 - Local Storage Persistence
+FR14: Epic 4 - Data Export
+FR15: Epic 2 - Custom Base URL
+FR16: Epic 2 - Secure Credential Storage
+FR17: Epic 2 - Model Selection
+FR18: Epic 2 - Connection Validation
+FR19: Epic 2 - Provider Switching
+FR20: Epic 1 - Lock Screen UI
+FR21: Epic 1 - Password Validation
+FR22: Epic 1 - Session Persistence
## Epic List
-### Epic 1: "Active Listening" - Core Chat & Teacher Agent
-**Goal:** Enable users to start a session, "vent" their raw thoughts, and have the system "Active Listen" (store chat) and "Teach" (probe for details) using a local-first architecture.
-**User Outcome:** Users can open the app, chat safely (locally), and get probing questions from the AI.
-**FRs covered:** FR-01, FR-02, FR-05, FR-13
-**NFRs:** NFR-01, NFR-03, NFR-04
+### Epic 1: Gatekeeper Security
-### Epic 2: "The Magic Mirror" - Ghostwriter & Draft Refinement
-**Goal:** Transform the structured chat context into a tangible "Enlightenment" artifact (the post) that users can review, refine, and export.
-**User Outcome:** Users get a high-quality post from their vent, which they can edit and ultimately copy for publishing.
-**FRs covered:** FR-03, FR-04, FR-07, FR-09
-**NFRs:** NFR-07 (Visuals), NFR-04
+Establish a secure perimeter for the application to prevent unauthorized access in public deployment scenarios (VPS).
-### Epic 3: "My Legacy" - History, Offline Action Replay & PWA Polish
-**Goal:** Turn single sessions into a persistent "Journal" of growth, ensuring the app works flawlessly offline and behaves like a native app.
-**User Outcome:** Users can view past wins, use the app on the subway (offline), and install it to their home screen.
-**FRs covered:** FR-06, FR-08, FR-10, FR-11, FR-12, FR-14
-**NFRs:** NFR-02, NFR-05, NFR-06
+### Story 1.1: Security Middleware & Lock Screen
-### Epic 4: "Power User Settings" - BYOD & Configuration
-**Goal:** Enable users to bring their own Intelligence (BYOD) by configuring custom API providers, models, and keys, satisfying the "Privacy-First" and "Vendor Independence" requirements.
-**User Outcome:** Users can configure and switch between different AI providers with their own API keys, ensuring data privacy and vendor flexibility.
-**FRs covered:** FR-15, FR-16, FR-17, FR-18, FR-19
-**NFRs:** NFR-03 (Data Sovereignty), NFR-08 (Secure Key Storage)
-
-
-## Epic 1: "Active Listening" - Core Chat & Teacher Agent
-
-**Goal:** Enable users to start a session, "vent" their raw thoughts, and have the system "Active Listen" (store chat) and "Teach" (probe for details) using a local-first architecture.
-
-### Story 1.1: Local-First Setup & Chat Storage
-
-As a user,
-I want my chat sessions to be saved locally on my device,
-So that my data is private and accessible offline.
+As a Personal User,
+I want the application to block all access until I log in,
+So that my private journal remains secure on the public web.
**Acceptance Criteria:**
-**Given** a new user visits the app
-**When** they load the page
-**Then** a Dexie.js database is initialized with the correct schema
-**And** no data is sent to the server without explicit action
+**Given** I am an unauthenticated user accessing any route
+**When** I load the page
+**Then** I should be redirected to `/login`
+**And** I should see a simple "Enter Password" screen
+**And** I should not see any application UI or data
-**Given** the user sends a message
-**When** the message is sent
-**Then** it is stored in the `chatLogs` table in IndexedDB with a timestamp
-**And** is immediately displayed in the UI
+### Story 1.2: Server-Side Validation (APP_PASSWORD)
-**Given** the user reloads the page
-**When** the page loads
-**Then** the previous chat history is retrieved from IndexedDB and displayed correctly
-**And** the session state is restored
-
-**Given** the device is offline
-**When** the user opens the app
-**Then** the app loads successfully and shows stored history from the local database
-
-### Story 1.2: Chat Interface Implementation
-
-As a user,
-I want a clean, familiar chat interface,
-So that I can focus on venting without fighting the UI.
+As a System Admin (User),
+I want to secure the app with a server-side environment variable,
+So that I don't need to manage a database of users.
**Acceptance Criteria:**
-**Given** a user is on the main chat screen
-**When** they look at the UI
-**Then** they see a "Morning Mist" themed interface with distinct bubbles for User (Right) and AI (Left)
-**And** the design matches the "Telegram-style" visual specification
+**Given** I have set `APP_PASSWORD` in my `.env`
+**When** I enter the matching password into the login form
+**Then** The server should validate it
+**And** Return a secure HTTP-only cookie
+**And** Allow access to the app
-**Given** the user is typing
-**When** they press "Send"
-**Then** the input field clears and the message appears in the chat
-**And** the view scrolls to the bottom
+**Given** I enter the wrong password
+**When** I submit the form
+**Then** I should see an invalid password error
-**Given** the user is on a mobile device
-**When** they view the chat
-**Then** the layout is responsive and all touch targets are at least 44px
-**And** the text size is legible (Inter font)
+### Story 1.3: Session Persistence
-**Given** the AI is processing
-**When** the user waits
-**Then** a "Teacher is typing..." indicator is visible
-**And** the UI remains responsive
-
-### Story 1.3: Teacher Agent Logic & Intent Detection
-
-As a user,
-I want the AI to understand if I'm venting or sharing an insight,
-So that it responds appropriately.
+As a Daily User,
+I want my login to be remembered for 30 days,
+So that I don't have to type the password every time I open the app on my phone.
**Acceptance Criteria:**
-**Given** a user sends a first message
-**When** the AI processes it
-**Then** it classifies the intent as "Venting" or "Insight"
-**And** stores this context in the session state
+**Given** I have successfully logged in
+**When** I close and reopen the browser
+**Then** I should remain logged in without re-entering the password
-**Given** the intent is "Venting"
-**When** the AI responds
-**Then** it validates the emotion first
-**And** asks a probing question to uncover the underlying lesson
+**Given** I click "Logout" in settings
+**When** I confirm
+**Then** My session cookie should be destroyed
+**And** I should be redirected to the login screen
-**Given** the AI is generating a response
-**When** the request is sent
-**Then** it makes a direct client-side request to the configured Provider
-**And** the user's stored API key is retrieved from local secure storage
+### Epic 2: Project Calibration (BYOD Setup)
-**Given** the API response takes time
-**When** the user waits
-**Then** the response time is optimized to be under 3 seconds for the first token (if streaming)
+Enable users to configure and manage their own AI provider connections, ensuring privacy and operational capability.
-### Story 1.4: Fast Track Mode
+### Story 2.1: Settings Feature Shell
+
+As a User,
+I want a dedicated settings area,
+So that I can manage my application preferences and configurations.
+
+**Acceptance Criteria:**
+
+**Given** I am on the home screen
+**When** I tap the "Settings" icon
+**Then** A settings sheet or page should open
+**And** I should see navigation tabs (General, AI Providers)
+**And** I should see a Theme Toggle (Light/Dark)
+
+### Story 2.2: Provider Management (CRUD)
As a Power User,
-I want to bypass the interview questions,
-So that I can generate a post immediately if I already have the insight.
+I want to add my own custom LLM provider (like DeepSeek or OpenAI),
+So that I can control the cost and intelligence behind the app.
**Acceptance Criteria:**
-**Given** a user is in the chat
-**When** they toggle "Fast Track" or press a specific "Just Draft It" button
-**Then** the AI skips the probing phase
-**And** proceeds directly to the "Ghostwriter" generation phase (transition to Epic 2 workflow)
+**Given** I am in the AI Providers settings tab
+**When** I tap "Add Provider"
+**Then** I should see a form for Name, Base URL, API Key, and Model Name
+**And** I should be able to save this configuration to my local device
-**Given** "Fast Track" is active
-**When** the user sends their input
-**Then** the system interprets it as the final insight
-**And** immediately triggers the draft generation
+**Given** I have an existing provider
+**When** I edit it
+**Then** The changes should be saved locally
+### Story 2.3: Secure Credentials Storage
-## Epic 2: "The Magic Mirror" - Ghostwriter & Draft Refinement
-
-**Goal:** Transform the structured chat context into a tangible "Enlightenment" artifact (the post) that users can review, refine, and export.
-
-### Story 2.1: Ghostwriter Agent & Markdown Generation
-
-As a user,
-I want the system to draft a polished post based on my chat,
-So that I can see my raw thoughts transformed into value.
+As a Privacy-Conscious User,
+I want my API keys to be stored securely on my device,
+So that they are never exposed to a third-party server.
**Acceptance Criteria:**
-**Given** the user has completed the interview or used "Fast Track"
-**When** the "Ghostwriter" agent is triggered
-**Then** it consumes the entire chat history and the "Lesson" context
-**And** generates a structured Markdown artifact (Title, Body, Tags)
+**Given** I save a new provider with an API Key
+**When** The data is persisted to localStorage
+**Then** The API Key should be obfuscated (e.g., Base64 or encrypted)
+**And** It should NOT be visible in plain text in the storage inspector
+**And** It should never be logged in the console
-**Given** the generation is processing
-**When** the user waits
-**Then** they see a distinct "Drafting" animation (different from "Typing")
-**And** the tone of the output matches the "Professional/LinkedIn" persona
+### Story 2.4: Connection Validation
-### Story 2.2: Draft View UI (The Slide-Up)
-
-As a user,
-I want to view the generated draft in a clean, reading-focused interface,
-So that I can review it without the distraction of the chat.
+As a User,
+I want to know if my API key works before I save it,
+So that I don't get errors later when trying to chat.
**Acceptance Criteria:**
-**Given** the draft generation is complete
-**When** the result is ready
-**Then** a "Sheet" or modal slides up from the bottom
-**And** it displays the post in "Medium-style" typography (Merriweather font)
+**Given** I am adding a provider
+**When** I fill in the details and tap "Test Connection"
+**Then** The system should make a call to the provider's API (e.g., list models)
+**And** Show a "Success" or "Error" message appropriately
+**And** Block saving if the validation fails (optional, but recommended warning)
-**Given** the draft view is open
-**When** the user scrolls
-**Then** the reading experience is comfortable with appropriate whitespace
-**And** the "Thumbs Up" and "Thumbs Down" actions are sticky or easily accessible
+### Story 2.5: Active Provider Switcher
-### Story 2.3: Refinement Loop (Regeneration)
-
-As a user,
-I want to provide feedback if the draft isn't right,
-So that I can get a better version.
+As a User,
+I want to easily switch between my configured providers,
+So that I can use a cheaper model for simple tasks and a smarter one for complex vents.
**Acceptance Criteria:**
-**Given** the user is viewing a draft
-**When** they click "Thumbs Down"
-**Then** the draft sheet closes and returns to the Chat UI
-**And** the AI proactively asks "What should we change?"
+**Given** I have multiple providers configured
+**When** I select a different provider as "Active"
+**Then** All future chat requests should use that provider's credentials
+**And** The UI should reflect the currently active provider
-**Given** the user provides specific critique (e.g., "Make it shorter")
-**When** they send the feedback
-**Then** the "Ghostwriter" regenerates the draft respecting the new constraint
-**And** the new draft replaces the old one in the Draft View
+### Epic 3: The Venting Ritual (Core)
-### Story 2.4: Export & Copy Actions
+Implement the core dual-agent pipeline that transforms user stress into structured insights via a guided chat interface.
-As a user,
-I want to copy the text or save the post,
-So that I can publish it on LinkedIn or save it for later.
+### Story 3.1: Chat Interface & State
+
+As a User,
+I want a familiar chat interface,
+So that I can express myself naturally without learning a new tool.
**Acceptance Criteria:**
-**Given** the user likes the draft
-**When** they click "Thumbs Up" or "Copy"
-**Then** the full Markdown text is copied to the clipboard
-**And** a success toast/animation confirms the action
+**Given** I open the app
+**When** I am on the home screen
+**Then** I should see a chat input at the bottom
+**And** I should simply tap to start typing
+**And** My messages should appear in "User Bubbles" (Right aligned)
+**And** AI responses should appear in "AI Bubbles" (Left aligned) with a typing indicator
-**Given** the draft is finalized
-**When** the user saves it
-**Then** it is marked as "Completed" in the local database
-**And** the user is returned to the Home/History screen
+### Story 3.2: Teacher Agent (Elicitation Logic)
-
-## Epic 3: "My Legacy" - History, Offline Sync & PWA Polish
-
-**Goal:** Turn single sessions into a persistent "Journal" of growth, ensuring the app works flawlessly offline and behaves like a native app.
-
-### Story 3.1: History Feed UI
-
-As a user,
-I want to see a list of my past growing moments,
-So that I can reflect on my journey.
+As a Learner,
+I want the AI to ask me probing questions,
+So that I can uncover the deeper lesson behind my frustration.
**Acceptance Criteria:**
-**Given** the user is on the Home screen
-**When** they view the feed
-**Then** they see a chronological list of past "Completed" sessions (Title, Date, Tags)
-**And** the list supports lazy loading/pagination for performance
+**Given** I send a message like "I feel stupid"
+**When** The "Teacher" agent processes it
+**Then** It should NOT just say "It's okay"
+**And** It SHOULD ask a follow-up question like "What specifically made you feel that way?"
+**And** It should maintain a supportive, non-judgmental tone
-**Given** the user clicks a history card
-**When** the card opens
-**Then** the full "Enlightenment" artifact allows for reading
-**And** the "Copy" action is available
+### Story 3.3: Ghostwriter Agent (Draft Generation)
-### Story 3.2: Deletion & Management
-
-As a user,
-I want to delete old entries,
-So that I can control my private data.
+As a User,
+I want a focused "Drafting" moment,
+So that I know when the venting is over and the value is created.
**Acceptance Criteria:**
-**Given** the user is viewing a past entry
-**When** they select "Delete"
-**Then** they are prompted with a confirmation dialog (Destructive Action)
-**And** the action cannot be undone
+**Given** I have answered the Teacher's questions
+**When** Sufficient context is gathered OR I tap "Draft It"
+**Then** The system should trigger the "Ghostwriter" agent
+**And** It should consume the chat history
+**And** It should generate a structured markdown artifact (Title, Insight, Lesson)
-**Given** the deletion is confirmed
-**When** the action completes
-**Then** the entry is permanently removed from IndexedDB
-**And** the History Feed updates immediately to remove the item
+### Story 3.4: Draft Review UI (Slide-Up)
-### Story 3.3: Offline Action Replay
-
-As a user,
-I want my actions to be queued when offline,
-So that I don't lose work on the subway.
+As a User,
+I want to see the generated insight clearly,
+So that I can feel a sense of accomplishment.
**Acceptance Criteria:**
-**Given** the device is offline
-**When** the user performs an LLM-dependent action (e.g., Send message, Regenerate draft)
-**Then** the action is added to a persistent "Action Queue" in Dexie
-**And** the UI shows a subtle "Offline - Queued" indicator
+**Given** The Ghostwriter has finished
+**When** The draft is ready
+**Then** A "Slide-Up" sheet (or modal) should appear
+**And** It should display the content with nice typography (Serif headers)
+**And** It should have "Thumbs Up" (Keep) and "Thumbs Down" (Refine) buttons
-**Given** connection is restored
-**When** the app detects the network
-**Then** the Sync Manager replays queued actions to the LLM API
-**And** the indicator updates to "Processed"
+### Story 3.5: Regeneration Loop (Refinement)
-### Story 3.4: PWA Install Prompt & Manifest
-
-As a user,
-I want to install the app to my home screen,
-So that it feels like a native app.
+As a User,
+I want to critique the draft if it's wrong,
+So that the final result feels authentic to me.
**Acceptance Criteria:**
-**Given** the user visits the web app
-**When** the browser parses the site
-**Then** it finds a valid `manifest.json` with correct icons, name ("Test01"), and `display: standalone` settings
+**Given** I see a draft I don't like
+**When** I tap "Thumbs Down"
+**Then** The sheet should close
+**And** The AI should ask "What needs to be changed?"
+**And** My response should trigger a regeneration of the draft
-**Given** the user has engaged with the app (e.g., completed 1 session)
-**When** the browser supports it (beforeinstallprompt event)
-**Then** a custom "Install App" UI element appears (non-intrusive)
-**And** clicking it triggers the native install prompt
+### Epic 4: Journey Management (History & Offline)
-**Given** the app is installed
-**When** it launches from Home Screen
-**Then** it opens without the browser URL bar (Standalone mode)
+Provide long-term value through history management, offline reliability, and data portability.
+### Story 4.1: History Feed UI
-## Epic 4: "Power User Settings" - BYOD & Configuration
-
-**Goal:** Enable users to bring their own Intelligence (BYOD) by configuring custom API providers, models, and keys, satisfying the "Privacy-First" and "Vendor Independence" requirements.
-
-### Story 4.1: API Provider Configuration UI
-
-As a user,
-I want to enter my own API Key and Base URL,
-So that I can use my own LLM account (e.g., DeepSeek, OpenAI).
+As a User,
+I want to browse my past "Legacy Logs",
+So that I can reflect on my growth over time.
**Acceptance Criteria:**
-**Given** the user navigates to "Settings"
-**When** they select "AI Provider"
-**Then** they see a form to enter: "Base URL" (Default: OpenAI), "API Key", and "Model Name"
+**Given** I tap the "History" tab
+**When** The list loads
+**Then** I should see a chronological list of cards
+**And** Each card should show Date, Title, and a short summary
+**And** It should support infinite scroll or pagination
-**Given** the user enters a key
-**When** they save
-**Then** the key is stored in `localStorage` with basic encoding (not plain text)
-**And** it is NEVER sent to the app backend (Client-Side only)
+### Story 4.2: Detailed Artifact View
-**Given** the user has saved a provider
-**When** they return to chat
-**Then** the new settings are active immediately
-
-### Story 4.2: Connection Validation
-
-As a user,
-I want to know if my key works,
-So that I don't get errors in the middle of a chat.
+As a User,
+I want to read a specific past insight,
+So that I can reuse the content for my blog or resume.
**Acceptance Criteria:**
-**Given** the user enters new credentials
-**When** they click "Connect" or "Save"
-**Then** the system sends a tiny "Hello" request to the provider
-**And** shows "Connected ✅" if successful, or the error message if failed
+**Given** I am on the History Feed
+**When** I tap a card
+**Then** The "Detailed View" (similar to the Draft View) should open
+**And** I should see the full formatted content
+**And** I should NOT be able to "Regenerate" (it is read-only history)
-### Story 4.3: Model Selection & Configuration
+### Story 4.3: Action Menu (Export/Delete)
-As a user,
-I want to specify which AI model to use,
-So that I can choose between different capabilities (e.g., fast vs. smart).
+As a User,
+I want to manage my individual entries,
+So that I can delete bad ones or copy good ones.
**Acceptance Criteria:**
-**Given** the user is in the API Provider settings
-**When** they view the form
-**Then** they see a "Model Name" field with examples (e.g., "gpt-4o", "deepseek-chat")
+**Given** I am viewing a History Card
+**When** I tap the "..." menu
+**Then** I should see "Copy to Clipboard" and "Delete"
+**And** Tapping "Delete" should prompt for confirmation
+**And** Confirming should remove it from the database immediately
-**Given** the user enters a custom model name
-**When** they save
-**Then** the model name is stored alongside the API key and base URL
-**And** all future LLM requests use this model identifier
+### Story 4.4: Offline Sync Queue
-**Given** the user doesn't specify a model
-**When** they save provider settings
-**Then** a sensible default is used (e.g., "gpt-3.5-turbo" for OpenAI endpoints)
-
-### Story 4.4: Provider Switching
-
-As a user,
-I want to switch between different saved providers,
-So that I can use different AI services for different needs.
+As a Commuter,
+I want to vent even when I have no signal (Offline),
+So that I don't lose the thought.
**Acceptance Criteria:**
-**Given** the user has configured multiple providers
-**When** they open Settings
-**Then** they see a list of saved providers with labels (e.g., "OpenAI GPT-4", "DeepSeek Chat")
+**Given** I am offline (Airplane Mode)
+**When** I attempt to start a chat or send a message
+**Then** The UI should allow it
+**And** The system should queue the action in `syncQueue` (IndexedDB)
+**And** It should show a "Waiting for connection..." status
+**When** Connection is restored
+**Then** The queue should auto-process
-**Given** the user selects a different provider
-**When** they confirm the switch
-**Then** the app immediately uses the new provider for all LLM requests
-**And** the active provider is persisted in local storage
+### Story 4.5: Data Export Utility
-**Given** the user starts a new chat session
-**When** they send messages
-**Then** the currently active provider is used
-**And** the provider selection is maintained across page reloads
+As a User,
+I want to download all my data,
+So that I have a backup independent of this browser.
+
+**Acceptance Criteria:**
+
+**Given** I am in Settings
+**When** I tap "Export All Data"
+**Then** The system should gather all Chat Logs and Drafts
+**And** Generate a downloadable JSON or Markdown file
+**And** Trigger the browser download prompt
diff --git a/_bmad-output/planning-artifacts/prd.md b/_bmad-output/planning-artifacts/prd.md
index baed7a8..6a75234 100644
--- a/_bmad-output/planning-artifacts/prd.md
+++ b/_bmad-output/planning-artifacts/prd.md
@@ -30,16 +30,16 @@ editHistory:
changes: 'Added "Bring Your Own AI" (BYOD) Support: Custom Providers, API Key Management, and Settings.'
---
-# Product Requirements Document - Test01
+# Product Requirements Document - Brachnha
**Author:** Max
**Date:** 2026-01-20
## Executive Summary
-**Product Vision:** "Test01" (The Pocket Mentor) is a Progressive Web App (PWA) designed to transform the daily struggles of learning into a polished "Legacy Log" of insights. It targets bootcamp graduates and self-learners who need to document their growth for recruiters but lack the energy to write from scratch.
+**Product Vision:** "Brachnha" (The Pocket Mentor) is a Progressive Web App (PWA) designed to transform the daily struggles of learning into a polished "Journey Log" of insights. It targets bootcamp graduates and self-learners who need to document their growth for recruiters but lack the energy to write from scratch.
-**Core Innovation:** Unlike passive note apps or raw AI writers, Test01 uses a **Dual-Agent Pipeline** ("Teacher" + "Ghostwriter"). It actively interviews the user to extract the "Lesson" from the "Complaint" ("Venting"), then synthesizing it into high-quality personal branding content.
+**Core Innovation:** Unlike passive note apps or raw AI writers, Brachnha uses a **Dual-Agent Pipeline** ("Teacher" + "Ghostwriter"). It actively interviews the user to extract the "Lesson" from the "Complaint" ("Venting"), then synthesizing it into high-quality personal branding content.
**Key Value:** Turns "I feel stupid today" into "Here is what I learned today."
@@ -117,6 +117,10 @@ The goal is to prove that the *experience* of "guided enlightenment" is cleaner,
* **Risk:** Users find the "Teacher" questions annoying/blocking.
* **Mitigation:** Implement a "Fast Track" / "Just Write It" button in the UI to skip the interview if the user is ready.
+**Security Risks:**
+* **Risk:** Public deployment on VPS exposes personal journal.
+* **Mitigation:** Implement "Gatekeeper" Authentication (NFR-09) to lock the app via `APP_PASSWORD`.
+
**Usability Risks:**
* **Risk:** "Bring Your Own AI" configuration is too complex for non-technical users.
* **Mitigation:** Provide clear, step-by-step guides for getting API keys. Pre-fill common provider templates (DeepSeek, OpenAI) so users only paste the key.
@@ -128,7 +132,7 @@ The goal is to prove that the *experience* of "guided enlightenment" is cleaner,
```mermaid
sequenceDiagram
participant User as Alex (Learner)
- participant UI as Test01 App
+ participant UI as Brachnha App
participant Teacher as Teacher Agent
participant Ghost as Ghostwriter Agent
@@ -151,7 +155,7 @@ sequenceDiagram
### Journey 1: The "Legacy Log" (Primary Success)
* **User:** Alex (The Exhausted Learner).
* **Scene:** Alex finishes a deep study session at 11 PM. He's tired but feels a "click" of understanding after hours of struggle.
-* **Action:** Opens Test01 to capture the win, not just to vent, but to immortalize the lesson: *"I finally get why dependency injection matters."*
+* **Action:** Opens Brachnha to capture the win, not just to vent, but to immortalize the lesson: *"I finally get why dependency injection matters."*
* **System Response:** The "Teacher" agent validates the insight and probes deeper: *"That's a huge breakthrough. What was the 'before' and 'after' mental model in your head?"*
* **Transformation:** Alex articulates the specific shift in his thinking.
* **Result:** The "Ghostwriter" agent drafts: *"The Moment Dependency Injection Clicked for Me."*
@@ -208,9 +212,9 @@ sequenceDiagram
* **Guided Transformation:** The UX pattern of transforming a raw, negative "Complaint" into a structured, positive "Insight" via a conversational interview is a novel interaction model for note-taking apps.
### Market Context & Competitive Landscape
-* **vs. Passive Note Apps (Notion/Obsidian):** These require the user to do all the cognitive heaving lifting (synthesis). Test01 is "Active" and pulls the synthesis out of the user.
-* **vs. Raw AI Writers (ChatGPT):** ChatGPT requires specific prompting and intent. Test01 acts as a partner that helps the user discover their intent ("What did I actually learn?").
-* **vs. Social Schedulers (Buffer/Hootsuite):** These manage distribution. Test01 manages *Creation* and *Ideation*.
+* **vs. Passive Note Apps (Notion/Obsidian):** These require the user to do all the cognitive heaving lifting (synthesis). Brachnha is "Active" and pulls the synthesis out of the user.
+* **vs. Raw AI Writers (ChatGPT):** ChatGPT requires specific prompting and intent. Brachnha acts as a partner that helps the user discover their intent ("What did I actually learn?").
+* **vs. Social Schedulers (Buffer/Hootsuite):** These manage distribution. Brachnha manages *Creation* and *Ideation*.
### Validation Approach
* **The "Edit Distance" Metric:** Success is measured by how little the user has to edit the final draft. If the "Teacher" interview is effective, the "Ghostwriter" draft should be >90% ready. High edit rates indicate a failure in the elicitation phase.
@@ -218,7 +222,7 @@ sequenceDiagram
## Web App Specific Requirements
### Project-Type Overview
-Test01 is a **Progressive Web App (PWA)**. It must deliver a native-app-like experience in the browser, specifically designed for mobile usage during "in-between moments" (commuting, breaks).
+Brachnha is a **Progressive Web App (PWA)**. It must deliver a native-app-like experience in the browser, specifically designed for mobile usage during "in-between moments" (commuting, breaks).
### Technical Architecture Considerations
* **PWA Mechanics:**
@@ -270,6 +274,11 @@ Test01 is a **Progressive Web App (PWA)**. It must deliver a native-app-like exp
* **FR-18:** System validates the connection to the custom provider upon saving.
* **FR-19:** Users can switch between configured providers globally.
+### Security & Access Control
+* **FR-20 (Gatekeeper):** System presents a lock screen upon initial load if not authenticated.
+* **FR-21:** System validates user-entered password against server-side `APP_PASSWORD`.
+* **FR-22:** Authenticated session persists (via secure cookie) to prevent frequent logouts on personal devices.
+
## Non-Functional Requirements
### Performance & Responsiveness
@@ -284,6 +293,7 @@ Test01 is a **Progressive Web App (PWA)**. It must deliver a native-app-like exp
### Reliability & Offline
* **NFR-05 (Offline Behavior):** The app shell and local history must remain accessible in Aeroplane Mode. **Note:** Active Chat interactions will be unavailable offline as they require live LLM access.
* **NFR-06 (Data Persistence):** Drafts must be auto-saved locally every **2 seconds** to prevent data loss.
+* **NFR-09 (Gatekeeper Security):** The app must restrict access to the UI via a simple, high-protection login screen backed by a server-side `APP_PASSWORD` environment variable. This protects personal deployments (VPS) from unauthorized public access.
### Accessibility
* **NFR-07 (Visual Accessibility):** Dark Mode is the default. Contrast ratios must meet **WCAG AA** standards to reduce eye strain for late-night users.
diff --git a/_bmad-output/project-context.md b/_bmad-output/project-context.md
index e045fc2..4944919 100644
--- a/_bmad-output/project-context.md
+++ b/_bmad-output/project-context.md
@@ -1,5 +1,5 @@
---
-project_name: 'Brachnha Insights'
+project_name: 'Brachnha'
user_name: 'Max'
date: '2026-01-21'
sections_completed: ['technology_stack', 'implementation_rules', 'naming_conventions', 'project_structure']
diff --git a/package-lock.json b/package-lock.json
index 2562024..d6c3c8f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,7 @@
"@ai-sdk/openai-compatible": "^2.0.18",
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-dialog": "^1.1.15",
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-slot": "^1.2.4",
"@testing-library/user-event": "^14.6.1",
@@ -24,6 +25,7 @@
"lucide-react": "^0.562.0",
"next": "16.1.4",
"next-pwa": "^5.6.0",
+ "next-themes": "^0.4.6",
"react": "19.2.3",
"react-dom": "19.2.3",
"react-markdown": "^10.1.0",
@@ -2504,6 +2506,44 @@
"npm": ">=10"
}
},
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
+ "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz",
+ "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.3",
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/react-dom": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz",
+ "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.7.4"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.10",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
+ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
+ "license": "MIT"
+ },
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -3357,6 +3397,73 @@
}
}
},
+ "node_modules/@radix-ui/react-arrow": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
+ "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collection": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
+ "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
@@ -3441,6 +3548,21 @@
}
}
},
+ "node_modules/@radix-ui/react-direction": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
+ "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-dismissable-layer": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz",
@@ -3468,6 +3590,35 @@
}
}
},
+ "node_modules/@radix-ui/react-dropdown-menu": {
+ "version": "2.1.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz",
+ "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-menu": "2.1.16",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-focus-guards": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz",
@@ -3572,6 +3723,96 @@
}
}
},
+ "node_modules/@radix-ui/react-menu": {
+ "version": "2.1.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz",
+ "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.11",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popper": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
+ "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react-dom": "^2.0.0",
+ "@radix-ui/react-arrow": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-rect": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1",
+ "@radix-ui/rect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-portal": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz",
@@ -3661,6 +3902,37 @@
}
}
},
+ "node_modules/@radix-ui/react-roving-focus": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz",
+ "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-slot": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz",
@@ -3764,6 +4036,48 @@
}
}
},
+ "node_modules/@radix-ui/react-use-rect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz",
+ "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/rect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-size": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz",
+ "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/rect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz",
+ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
+ "license": "MIT"
+ },
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.53",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz",
@@ -10957,6 +11271,16 @@
"next": ">=9.0.0"
}
},
+ "node_modules/next-themes": {
+ "version": "0.4.6",
+ "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
+ "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
+ }
+ },
"node_modules/next/node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
diff --git a/package.json b/package.json
index cd2ca4e..85404ee 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"@ai-sdk/openai-compatible": "^2.0.18",
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-dialog": "^1.1.15",
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-slot": "^1.2.4",
"@testing-library/user-event": "^14.6.1",
@@ -27,6 +28,7 @@
"lucide-react": "^0.562.0",
"next": "16.1.4",
"next-pwa": "^5.6.0",
+ "next-themes": "^0.4.6",
"react": "19.2.3",
"react-dom": "19.2.3",
"react-markdown": "^10.1.0",
diff --git a/playwright.config.ts b/playwright.config.ts
index 57c9be6..ea5479d 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -2,7 +2,7 @@ import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
- testIgnore: '**/component/**',
+ testIgnore: ['**/component/**', '**/unit/**'],
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
diff --git a/src/app/(main)/history/page.tsx b/src/app/(main)/history/page.tsx
new file mode 100644
index 0000000..38455f1
--- /dev/null
+++ b/src/app/(main)/history/page.tsx
@@ -0,0 +1,29 @@
+'use client';
+
+import { HistoryFeed } from '@/components/features/journal/HistoryFeed';
+import { HistoryDetailSheet } from '@/components/features/journal/HistoryDetailSheet';
+import { useHistoryStore } from '@/lib/store/history-store';
+
+export default function HistoryPage() {
+ const selectedDraft = useHistoryStore((s) => s.selectedDraft);
+ const closeDetail = useHistoryStore((s) => s.closeDetail);
+
+ return (
+