feat: Complete internationalization and code cleanup
## Translation Files - Add 11 new language files (es, de, pt, ru, zh, ja, ko, ar, hi, nl, pl) - Add 100+ missing translation keys across all 15 languages - New sections: notebook, pagination, ai.batchOrganization, ai.autoLabels - Update nav section with workspace, quickAccess, myLibrary keys ## Component Updates - Update 15+ components to use translation keys instead of hardcoded text - Components: notebook dialogs, sidebar, header, note-input, ghost-tags, etc. - Replace 80+ hardcoded English/French strings with t() calls - Ensure consistent UI across all supported languages ## Code Quality - Remove 77+ console.log statements from codebase - Clean up API routes, components, hooks, and services - Keep only essential error handling (no debugging logs) ## UI/UX Improvements - Update Keep logo to yellow post-it style (from-yellow-400 to-amber-500) - Change selection colors to #FEF3C6 (notebooks) and #EFB162 (nav items) - Make "+" button permanently visible in notebooks section - Fix grammar and syntax errors in multiple components ## Bug Fixes - Fix JSON syntax errors in it.json, nl.json, pl.json, zh.json - Fix syntax errors in notebook-suggestion-toast.tsx - Fix syntax errors in use-auto-tagging.ts - Fix syntax errors in paragraph-refactor.service.ts - Fix duplicate "fusion" section in nl.json 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Ou une version plus courte si vous préférez : feat(i18n): Add 15 languages, remove logs, update UI components - Create 11 new translation files (es, de, pt, ru, zh, ja, ko, ar, hi, nl, pl) - Add 100+ translation keys: notebook, pagination, AI features - Update 15+ components to use translations (80+ strings) - Remove 77+ console.log statements from codebase - Fix JSON syntax errors in 4 translation files - Fix component syntax errors (toast, hooks, services) - Update logo to yellow post-it style - Change selection colors (#FEF3C6, #EFB162) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2786
_bmad-output/planning-artifacts/architecture.md
Normal file
2786
_bmad-output/planning-artifacts/architecture.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -31,7 +31,7 @@ workflow_status:
|
||||
|
||||
# Phase 2: Planning
|
||||
prd: _bmad-output/planning-artifacts/prd.md
|
||||
create-ux-design: conditional
|
||||
create-ux-design: _bmad-output/planning-artifacts/ux-design-specification.md
|
||||
|
||||
# Phase 3: Solutioning
|
||||
create-architecture: required
|
||||
@@ -39,5 +39,26 @@ workflow_status:
|
||||
test-design: optional
|
||||
implementation-readiness: _bmad-output/planning-artifacts/implementation-readiness-report-2026-01-09.md
|
||||
|
||||
# PROJECT-SPECIFIC STATUS
|
||||
# ======================
|
||||
|
||||
# Notebooks & Labels Contextuels Project (2026-01-11)
|
||||
notebooks_contextual_labels:
|
||||
prd: _bmad-output/planning-artifacts/notebooks-contextual-labels-prd.md
|
||||
ux_design: _bmad-output/excalidraw-diagrams/notebooks-wireframes.md
|
||||
architecture: _bmad-output/planning-artifacts/notebooks-contextual-labels-architecture.md
|
||||
architecture_status: VALIDATED
|
||||
architecture_validated_date: "2026-01-11"
|
||||
tech_specs: _bmad-output/planning-artifacts/notebooks-tech-specs.md
|
||||
tech_specs_status: COMPLETE
|
||||
tech_specs_created_date: "2026-01-11"
|
||||
epics_stories: _bmad-output/planning-artifacts/notebooks-epics-stories.md
|
||||
epics_status: COMPLETE
|
||||
epics_created_date: "2026-01-11"
|
||||
total_epics: 6
|
||||
total_stories: 34
|
||||
total_points: 97
|
||||
next_phase: "sprint-planning"
|
||||
|
||||
# Phase 4: Implementation
|
||||
sprint-planning: required
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
348
_bmad-output/planning-artifacts/memory-echo-ux-backlog.md
Normal file
348
_bmad-output/planning-artifacts/memory-echo-ux-backlog.md
Normal file
@@ -0,0 +1,348 @@
|
||||
# Memory Echo - UX Improvements Backlog
|
||||
|
||||
**Date:** 2025-01-11
|
||||
**Author:** Sally (UX Designer Agent)
|
||||
**Project:** Keep - Memory Echo Feature
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Problem Statement
|
||||
|
||||
**User:** Ramez has 22+ similar notes and needs better tools to manage semantic connections.
|
||||
|
||||
**Current State:**
|
||||
- Temporary modal showing 2 connected notes side-by-side
|
||||
- Notifications when new connections detected
|
||||
- Basic feedback (thumbs up/down)
|
||||
- Fusion feature exists but needs better integration
|
||||
|
||||
**User Pain Points:**
|
||||
1. *"Once we see 2-3 identical notes, how do we put them side-by-side?"* - Better management of similar notes
|
||||
2. *"Can we have a merge button?"* - Intelligent fusion of similar notes
|
||||
3. *"Can we put them side-by-side on a sketch?"* - Mind-map / graph view of connections
|
||||
|
||||
**Constraints:**
|
||||
- Must remain intuitive and not clutter the UI
|
||||
- Must integrate cleanly with existing Masonry grid
|
||||
- Must handle scale (potentially 22+ similar notes)
|
||||
- Must not overwhelm the user
|
||||
|
||||
---
|
||||
|
||||
## 💡 UX Proposals
|
||||
|
||||
### 1️⃣ Better Connection Display & Management
|
||||
|
||||
#### **Proposal: Persistent Slide-over Panel**
|
||||
|
||||
**Location:** Navigation bar with badge counter
|
||||
|
||||
```
|
||||
[Notes] [Archive] [🔗 Connexions (23)] ← Badge shows total notes with connections
|
||||
```
|
||||
|
||||
**Interaction:**
|
||||
- Click badge → Slide-over panel opens from right
|
||||
- Shows hierarchical list of all connections grouped by similarity
|
||||
- Click on connection → Scroll to & highlight that note in grid
|
||||
- Hover over note in grid → Highlight connections in slide-over
|
||||
|
||||
**UI Layout:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ [Notes] [Archive] [🔗 Connexions (23)] │
|
||||
├─────────────────────────────────────────────────────┤
|
||||
│ Grille Masonry existante │
|
||||
│ ┌──────┐ ┌──────┐ ┌──────┐ │
|
||||
│ │ Note │ │ Note │ │ Note │ │
|
||||
│ │ 1 │ │ 2 │ │ 3 │ │
|
||||
│ └──────┘ └──────┘ └──────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────┐ ← Toggle │
|
||||
│ │ 🔗 Connexions (Slide-over) │ (right side) │
|
||||
│ │ ├─ Note A (3 connexions) │ │
|
||||
│ │ │ ├─ Note B (85%) │ │
|
||||
│ │ │ └─ Note C (72%) │ │
|
||||
│ │ └─ Note D (12 connexions) │ │
|
||||
│ │ ├─ Note E (91%) │ │
|
||||
│ │ └─ ... │ │
|
||||
│ └─────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- **Filter controls:** "Show only notes with 5+ connections", "Similarity 80%+"
|
||||
- **Group by similarity:** Cluster similar notes
|
||||
- **Search:** Search through connections
|
||||
- **Collapse/Expand:** Manage large lists
|
||||
- **Quick actions:** Checkbox multiple notes → "Compare selected" / "Merge selected"
|
||||
|
||||
**Why It Works:**
|
||||
- ✅ Non-intrusive: Doesn't hide the grid
|
||||
- ✅ Overview: See all connections at once
|
||||
- ✅ Navigation: Quick access to any connection
|
||||
- ✅ Scalable: Handles 50+ connections
|
||||
|
||||
---
|
||||
|
||||
### 2️⃣ Intelligent Note Fusion
|
||||
|
||||
#### **Proposal: Grouped Actions + Smart Merge**
|
||||
|
||||
**A. In Slide-over Panel:**
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────┐
|
||||
│ 🔗 Group of similar notes │
|
||||
│ ┌─────────────────────────────────┐ │
|
||||
│ │ ☑ Note A - "Machine Learning" │ │
|
||||
│ │ ☑ Note B - "ML basics" │ │
|
||||
│ │ ☑ Note C - "Intro ML" │ │
|
||||
│ │ │ │
|
||||
│ │ [🔀 Merge 3 notes] │ │ ← Primary button
|
||||
│ └─────────────────────────────────┘ │
|
||||
└──────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**B. Existing Fusion Modal (Already Implemented!)**
|
||||
|
||||
Current modal features:
|
||||
- Preview AI-generated fusion
|
||||
- Select which notes to merge
|
||||
- Custom prompt
|
||||
- Options (archive originals, keep tags, etc.)
|
||||
|
||||
**C. New Feature: "Quick Merge"**
|
||||
|
||||
For very similar notes (90%+ similarity):
|
||||
```
|
||||
[⚡ Quick Merge] → Automatically archives originals
|
||||
→ Creates fused note
|
||||
→ Adds "Fused" badge to originals with link to new note
|
||||
```
|
||||
|
||||
**Workflow:**
|
||||
```
|
||||
1. User opens slide-over
|
||||
2. Sees group of 5 similar notes
|
||||
3. Option A: Check all 5 → Click "Merge" → Opens custom modal
|
||||
Option B: Click "⚡ Quick Merge" → Instant merge with smart defaults
|
||||
4. New note created with "Fused" badge
|
||||
5. Original notes archived with link to fused note
|
||||
```
|
||||
|
||||
**Why It Works:**
|
||||
- ✅ Scale: Handle 22+ notes without selecting one-by-one
|
||||
- ✅ Control: Quick merge for obvious duplicates, custom for nuanced cases
|
||||
- ✅ Visual feedback: "Fused" badge traces history
|
||||
- ✅ Reversible: Archive keeps originals accessible
|
||||
|
||||
---
|
||||
|
||||
### 3️⃣ Mind-Map / Graph View
|
||||
|
||||
#### **Proposal: Toggle Graph View**
|
||||
|
||||
**New Navigation Button:**
|
||||
```
|
||||
[Notes] [Archive] [🕸️ Graph] ← New view
|
||||
```
|
||||
|
||||
**Graph View UI:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ 🔙 Back to Grid 🔍 Zoom 🎨 Clusters │
|
||||
├─────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ [Note A]────────────[Note B] │
|
||||
│ │ \ / │
|
||||
│ 85% 72% 91% │
|
||||
│ │ \ / │
|
||||
│ [Note C]────[Note D]────[Note E] │
|
||||
│ │
|
||||
│ 💡 Cluster "Machine Learning" (5 notes) │
|
||||
│ │ │
|
||||
│ [Note F]────────[Note G] │
|
||||
│ │ │
|
||||
│ 💡 Cluster "React" (3 notes) │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
|
||||
Legend:
|
||||
─ Thick line = 80%+ similarity (highly connected)
|
||||
─ Thin line = 50-79% similarity
|
||||
─ 💡 = Auto-clustered by theme (AI)
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- **Drag & drop:** Reposition notes manually
|
||||
- **Click note:** Opens modal with:
|
||||
- Full note content
|
||||
- Connections with percentages
|
||||
- Actions: "Merge with selected", "View in grid"
|
||||
- **Auto-clusters:** AI groups similar thematically (ML, React, etc.)
|
||||
- **Filters:** "Show only 70%+ connections", "Hide archived"
|
||||
- **Zoom & pan:** Navigate large graphs
|
||||
- **Export:** Save graph as image or JSON
|
||||
|
||||
**Why It Works:**
|
||||
- ✅ Immediate visual: See everything at once
|
||||
- ✅ Scalable: Handles 50+ connections
|
||||
- ✅ Actionable: Click → Compare → Merge
|
||||
- ✅ Discovery: Clusters reveal patterns
|
||||
- ✅ Exploration: Serendipitous connections
|
||||
|
||||
**Tech Stack Recommendations:**
|
||||
- **React Flow** (https://reactflow.dev/) - React-native, excellent performance
|
||||
- **D3.js** (https://d3js.org/) - Powerful but steeper learning curve
|
||||
- **Cytoscape.js** (https://js.cytoscape.org/) - Specialized for graphs
|
||||
|
||||
---
|
||||
|
||||
## 📋 Implementation Phases
|
||||
|
||||
### Phase 1 - Quick Win (1-2 days)
|
||||
|
||||
**Features:**
|
||||
- [ ] Badge "🔗 Connexions (X)" in navigation
|
||||
- [ ] Slide-over panel with connection list
|
||||
- [ ] Checkbox selection + "Merge" button (uses existing modal)
|
||||
- [ ] Filter controls (similarity threshold, count)
|
||||
|
||||
**Files to Create/Modify:**
|
||||
- `components/connections-slide-over.tsx` (NEW)
|
||||
- `components/connections-nav-badge.tsx` (NEW)
|
||||
- Modify navigation to include badge
|
||||
- Integrate with existing `/api/ai/echo/connections` endpoint
|
||||
|
||||
**Effort:** Low
|
||||
**Impact:** High
|
||||
**Risk:** Low
|
||||
|
||||
---
|
||||
|
||||
### Phase 2 - Graph View (3-5 days)
|
||||
|
||||
**Features:**
|
||||
- [ ] Toggle "🕸️ Graph" view
|
||||
- [ ] Basic graph visualization (React Flow)
|
||||
- [ ] Click interactions (open modal, highlight connections)
|
||||
- [ ] Zoom & pan
|
||||
- [ ] Basic filters
|
||||
|
||||
**Files to Create/Modify:**
|
||||
- `app/(main)/connections/page.tsx` (NEW - graph view page)
|
||||
- `components/connections-graph.tsx` (NEW)
|
||||
- Install `reactflow` package
|
||||
- Navigation update
|
||||
|
||||
**Effort:** Medium
|
||||
**Impact:** High
|
||||
**Risk:** Medium (learning curve for React Flow)
|
||||
|
||||
---
|
||||
|
||||
### Phase 3 - Advanced Features (5-7 days)
|
||||
|
||||
**Features:**
|
||||
- [ ] Auto-clustering by theme (AI)
|
||||
- [ ] "Quick Merge" for 90%+ similar notes
|
||||
- [ ] Export graph (image/JSON)
|
||||
- [ ] Advanced filters (date range, labels)
|
||||
- [ ] Graph layouts (force, hierarchical, circular)
|
||||
|
||||
**Files to Create/Modify:**
|
||||
- `/api/ai/echo/clusters` (NEW)
|
||||
- `components/quick-merge-button.tsx` (NEW)
|
||||
- Enhanced graph component with layouts
|
||||
- Export functionality
|
||||
|
||||
**Effort:** High
|
||||
**Impact:** Medium
|
||||
**Risk:** Medium
|
||||
|
||||
---
|
||||
|
||||
## 🎨 UI/UX Considerations
|
||||
|
||||
### Color Scheme
|
||||
- **Connections Badge:** Amber (already used)
|
||||
- **Fused Badge:** Purple (already used)
|
||||
- **Graph Nodes:** Color by cluster/theme
|
||||
- **Graph Edges:** Gradient by similarity (green = high, yellow = medium, gray = low)
|
||||
|
||||
### Responsive Design
|
||||
- **Mobile:** Slide-over becomes bottom sheet
|
||||
- **Tablet:** Slide-over 50% width
|
||||
- **Desktop:** Slide-over 400px fixed width
|
||||
- **Graph:** Touch interactions for mobile
|
||||
|
||||
### Accessibility
|
||||
- Keyboard navigation for all actions
|
||||
- Screen reader support for graph view
|
||||
- High contrast mode support
|
||||
- Focus indicators
|
||||
|
||||
### Performance
|
||||
- Lazy load connection list (pagination)
|
||||
- Virtual scroll for large lists
|
||||
- Debounce graph interactions
|
||||
- Cache graph layout
|
||||
|
||||
---
|
||||
|
||||
## 📊 Success Metrics
|
||||
|
||||
**User Engagement:**
|
||||
- % of users opening connections panel
|
||||
- Average connections viewed per session
|
||||
- Graph view adoption rate
|
||||
|
||||
**Feature Usage:**
|
||||
- Number of merges per week
|
||||
- % of quick merges vs custom merges
|
||||
- Most used similarity threshold
|
||||
|
||||
**User Satisfaction:**
|
||||
- Feedback on graph view usability
|
||||
- Time to merge similar notes
|
||||
- Reduction in duplicate notes over time
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Open Questions
|
||||
|
||||
1. **Default similarity threshold:** What should be the default? (Proposed: 70%)
|
||||
2. **Max connections to display:** Should we cap the list? (Proposed: 50, with pagination)
|
||||
3. **Auto-archival:** Should "Quick Merge" auto-archive or ask user? (Proposed: Auto-archive with undo)
|
||||
4. **Graph layout:** Which layout should be default? (Proposed: Force-directed)
|
||||
5. **Cluster naming:** AI-generated or user-editable? (Proposed: AI-generated with edit option)
|
||||
|
||||
---
|
||||
|
||||
## 📝 Notes
|
||||
|
||||
- All translations already exist in `locales/fr.json` and `locales/en.json`
|
||||
- Fusion modal already implemented and working
|
||||
- Connections API endpoint already exists: `/api/ai/echo/connections`
|
||||
- Badge components already created: `ConnectionsBadge`, `FusionBadge` (inline)
|
||||
- Current UI issue fixed: Badges now at top, labels after content, owner indicator visible
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Files
|
||||
|
||||
- `components/connections-badge.tsx` - Badge component
|
||||
- `components/connections-overlay.tsx` - Overlay component
|
||||
- `components/fusion-modal.tsx` - Fusion modal
|
||||
- `components/note-card.tsx` - Note card with badges
|
||||
- `app/api/ai/echo/connections/route.ts` - Connections API
|
||||
- `app/api/ai/echo/fusion/route.ts` - Fusion API
|
||||
- `locales/fr.json` - French translations
|
||||
- `locales/en.json` - English translations
|
||||
|
||||
---
|
||||
|
||||
**Status:** 📋 Ready for Implementation
|
||||
**Priority:** Phase 1 > Phase 2 > Phase 3
|
||||
**Next Steps:** Review with Ramez, prioritize features, begin Phase 1 implementation
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,990 @@
|
||||
# Product Requirements Document (PRD)
|
||||
## Notebooks & Labels Contextuels avec IA
|
||||
|
||||
**Project:** Keep (Memento Phase 1 MVP AI)
|
||||
**Date:** 2026-01-11
|
||||
**Author:** Sally (UX Designer) + Ramez (Product Owner)
|
||||
**Status:** Draft - Ready for Architecture
|
||||
**Priority:** High - Core Feature Reorganization
|
||||
|
||||
---
|
||||
|
||||
## 📋 Executive Summary
|
||||
|
||||
### Vision
|
||||
Transformer l'organisation de Keep d'un système de tags plat en une structure de **Notebooks avec Labels Contextuels**, où chaque notebook a sa propre taxonomie de labels, permettant une organisation plus naturelle et contextuelle.
|
||||
|
||||
### Objectifs Principaux
|
||||
1. ✅ Introduire les **Notebooks** comme organisation principale
|
||||
2. ✅ Rendre les **Labels contextuels** à chaque notebook
|
||||
3. ✅ Créer une **Inbox** ("Notes générales") pour les notes non organisées
|
||||
4. ✅ Intégrer l'**IA** intelligemment dans cette nouvelle structure
|
||||
5. ✅ Permettre une **migration douce** depuis le système actuel
|
||||
|
||||
---
|
||||
|
||||
## 🎯 User Stories
|
||||
|
||||
### Primary Users
|
||||
- **Ramez (Power User):** Utilise Keep quotidiennement pour organiser voyage, travail, vie perso
|
||||
- **Professionnel:** Gère des projets avec des contextes différents
|
||||
- **Voyageur:** Organise ses préparatifs de voyage avec des notes spécifiques
|
||||
|
||||
### User Journey Exemple
|
||||
|
||||
#### Scénario 1: Création de note dans Notebook
|
||||
```
|
||||
1. Ramez ouvre Keep, navigue vers Notebook "Voyage"
|
||||
2. Il voit les labels contextuels: #hôtels, #vols, #restos
|
||||
3. Il clique "Nouvelle note"
|
||||
4. La note est automatiquement assignée au Notebook "Voyage"
|
||||
5. Il peut tagger avec #hôtels (disponible car dans le bon contexte)
|
||||
```
|
||||
|
||||
#### Scénario 2: Note rapide dans Inbox
|
||||
```
|
||||
1. Ramez a une idée rapide, ouvre Keep (page d'accueil)
|
||||
2. Il tape son idée et sauve
|
||||
3. La note va dans "Notes générales" (Inbox)
|
||||
4. Plus tard, il la déplace vers "Notebook Perso"
|
||||
5. Les labels de "Perso" deviennent disponibles
|
||||
```
|
||||
|
||||
#### Scénario 3: Organisation IA-assistée
|
||||
```
|
||||
1. Ramez a 15 notes dans "Notes générales"
|
||||
2. Il clique "Organiser avec l'IA"
|
||||
3. L'IA analyse les notes et propose:
|
||||
- "3 notes pour Notebook Voyage"
|
||||
- "5 notes pour Notebook Travail"
|
||||
- "7 notes pour Notebook Perso"
|
||||
4. Ramez valide les suggestions
|
||||
5. Les notes sont déplacées automatiquement
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Structure de l'Organisation
|
||||
|
||||
### Hiérarchie
|
||||
|
||||
```
|
||||
KEEP
|
||||
├─ 📥 Notes générales (Inbox)
|
||||
│ └─ Notes sans notebook assigné
|
||||
│ └─ PAS de labels (zone temporaire)
|
||||
│
|
||||
├─ 📚 Notebooks (ordonnés manuellement)
|
||||
│ ├─ ✈️ Voyage
|
||||
│ │ ├─ Labels: #hôtels, #vols, #restos, #à_visiter
|
||||
│ │ └─ Notes assignées à "Voyage"
|
||||
│ │
|
||||
│ ├─ 💼 Travail
|
||||
│ │ ├─ Labels: #réunions, #projets, #urgent, #à_faire
|
||||
│ │ └─ Notes assignées à "Travail"
|
||||
│ │
|
||||
│ └─ 📖 Perso
|
||||
│ ├─ Labels: #idées, #rêves, #objectifs, #réflexions
|
||||
│ └─ Notes assignées à "Perso"
|
||||
│
|
||||
└─ [+] Nouveau Notebook
|
||||
```
|
||||
|
||||
### Règles Métier
|
||||
|
||||
#### R1: Appartenance des Notes
|
||||
- **Une note appartient à UN seul notebook** (ou aucune)
|
||||
- Les notes dans "Notes générales" n'appartiennent à aucun notebook
|
||||
- Une note ne peut être dans plusieurs notebooks simultanément
|
||||
|
||||
#### R2: Labels Contextuels
|
||||
- Chaque notebook a ses propres labels (100% isolés)
|
||||
- Les labels sont créés/supprimés au niveau notebook
|
||||
- Les notes dans "Notes générales" n'ont pas accès aux labels
|
||||
|
||||
#### R3: Ordre des Notebooks
|
||||
- Les notebooks sont ordonnés manuellement (drag & drop)
|
||||
- L'ordre est personnalisé par utilisateur
|
||||
- Drag & drop dans la sidebar pour réorganiser
|
||||
|
||||
#### R4: Vue "Notes générales"
|
||||
- Affiche SEULEMENT les notes sans notebook
|
||||
- PAS de vue "Toutes les notes" (tous notebooks confondus)
|
||||
- C'est une zone temporaire d'organisation
|
||||
|
||||
---
|
||||
|
||||
## 🎨 UX/UI Specifications
|
||||
|
||||
### 1. Navigation - Sidebar
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ KEEP LOGO │
|
||||
├─────────────────────────────────────┤
|
||||
│ 🔍 Search │
|
||||
├─────────────────────────────────────┤
|
||||
│ 📚 NOTEBOOKS │
|
||||
│ ┌───────────────────────────────┐ │
|
||||
│ │ 📥 Notes générales (12) │ │ ← Compteur de notes
|
||||
│ │ │ │
|
||||
│ │ ✈️ Voyage (8) │ │ ← Notebook actif = highlight
|
||||
│ │ ┌─ 🏷️ Labels contextuels │ │
|
||||
│ │ │ • #hôtels (3) │ │ ← Labels seulement si actif
|
||||
│ │ │ • #vols (2) │ │
|
||||
│ │ │ • #restos (3) │ │
|
||||
│ │ │ [+ Nouveau label] │ │
|
||||
│ │ └─────────────────────────────┘ │
|
||||
│ │ │ │
|
||||
│ │ 💼 Travail (15) │ │ ← Handles pour drag & drop
|
||||
│ │ ║ ║ │ │
|
||||
│ │ 📖 Perso (23) │ │
|
||||
│ │ ║ ║ │ │
|
||||
│ └───────────────────────────────┘ │
|
||||
│ │
|
||||
│ [+ Nouveau Notebook] │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Comportements:**
|
||||
- **Click sur notebook** → Navigue vers ce notebook
|
||||
- **Drag & drop des notebooks** → Réorganise l'ordre
|
||||
- **Hover sur notebook** → Affiche les labels contextuels
|
||||
- **[+ Nouveau label]** → Crée un label dans ce notebook
|
||||
- **Compteurs** → Montre le nombre de notes
|
||||
|
||||
### 2. Création de Note
|
||||
|
||||
#### Cas A: Depuis un Notebook
|
||||
```
|
||||
User dans "Voyage" → [Nouvelle note]
|
||||
├─ Note créée avec notebookId = "voyage"
|
||||
├─ Peut utiliser les labels de "Voyage"
|
||||
└─ UI: Badge "Voyage" visible sur la note
|
||||
```
|
||||
|
||||
#### Cas B: Depuis Notes Générales
|
||||
```
|
||||
User sur page d'accueil → [Nouvelle note]
|
||||
├─ Note créée avec notebookId = null
|
||||
├─ PAS de labels disponibles
|
||||
└─ UI: Badge "À trier" visible
|
||||
```
|
||||
|
||||
#### Cas C: Création dans un autre notebook
|
||||
```
|
||||
User dans "Voyage", veut créer pour "Travail"
|
||||
├─ DOIT naviguer vers "Travail" d'abord
|
||||
├─ OU utilise le raccourci clavier (ex: Ctrl+N → chooser)
|
||||
└─ PAS de modal à chaque création
|
||||
```
|
||||
|
||||
### 3. Déplacement de Notes (Option C: A + B)
|
||||
|
||||
#### Méthode A: Drag & Drop
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ 📝 Note à déplacer │
|
||||
│ ┌───────────────────────────────┐ │
|
||||
│ │ Grip handle │ Note content... │ │ ← Drag depuis ici
|
||||
│ └───────────────────────────────┘ │
|
||||
│ ↓ │
|
||||
│ Drop vers sidebar → │
|
||||
│ ┌───────────────────────────────┐ │
|
||||
│ │ ✈️ Voyage [Drop zone] │ │
|
||||
│ │ 💼 Travail [Drop zone] │ │
|
||||
│ └───────────────────────────────┘ │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Méthode B: Menu Contextuel
|
||||
```
|
||||
Sur une note → Click droit → Menu:
|
||||
├─ 📋 Copier
|
||||
├─ ✏️ Modifier
|
||||
├─ 📚 Déplacer vers...
|
||||
│ ├─ 📥 Notes générales
|
||||
│ ├─ ✈️ Voyage
|
||||
│ ├─ 💼 Travail
|
||||
│ └─ 📖 Perso
|
||||
├─ 🏷️ Ajouter un label
|
||||
├─ 📌 Épingler
|
||||
└─ 🗑️ Supprimer
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
- ✅ Drag & drop vers notebook dans sidebar
|
||||
- ✅ Menu contextuel "Déplacer vers..."
|
||||
- ✅ Les deux méthodes disponibles
|
||||
|
||||
### 4. Labels Contextuels
|
||||
|
||||
#### Création de Label
|
||||
```
|
||||
Dans Notebook "Voyage":
|
||||
┌─────────────────────────────────────┐
|
||||
│ 🏷️ Labels │
|
||||
│ • #hôtels • #vols • #restos │
|
||||
│ [+ Nouveau label] │ ← Click
|
||||
├─────────────────────────────────────┤
|
||||
│ Modal: │
|
||||
│ ┌───────────────────────────────┐ │
|
||||
│ │ Nom du label: │ │
|
||||
│ │ [___________] │ │
|
||||
│ │ │ │
|
||||
│ │ Couleur: ○ 🟡 ○ 🔴 ○ 🔵 │ │
|
||||
│ │ │ │
|
||||
│ │ [Annuler] [Créer] │ │
|
||||
│ └───────────────────────────────┘ │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Assignation de Label à Note
|
||||
```
|
||||
Note dans "Voyage" → Click "Ajouter label"
|
||||
├─ Seuls les labels de "Voyage" sont proposés
|
||||
├─ Dropdown avec checkboxes
|
||||
└─ Multi-label possible sur une note
|
||||
|
||||
Exemple:
|
||||
📝 Note: "Hôtel Tokyo Shibuya"
|
||||
├─ Notebook: ✈️ Voyage
|
||||
└─ Labels: #hôtels, #réservations
|
||||
```
|
||||
|
||||
#### Suppression de Label
|
||||
```
|
||||
Options:
|
||||
├─ Supprimer le label (du notebook)
|
||||
│ └─ Warning: "Ce label sera retiré de X notes. Continuer?"
|
||||
└─ Retirer des notes seulement
|
||||
└─ Label existe toujours, mais plus utilisé
|
||||
```
|
||||
|
||||
### 5. Gestion des Notebooks
|
||||
|
||||
#### Création de Notebook
|
||||
```
|
||||
Click [+ Nouveau Notebook]
|
||||
├─ Modal de création
|
||||
│ ├─ Nom: "Voyage"
|
||||
│ ├─ Icône: [Sélecteur d'emoji]
|
||||
│ ├─ Couleur: [Sélecteur de couleur]
|
||||
│ └─ [Créer]
|
||||
└─ Notebook créé à la fin de la liste
|
||||
```
|
||||
|
||||
#### Édition de Notebook
|
||||
```
|
||||
Click droit sur notebook → Menu:
|
||||
├─ ✏️ Modifier
|
||||
│ └─ Modal: Nom, Icône, Couleur
|
||||
├─ 📊 Statistiques
|
||||
│ ├─ Nombre de notes
|
||||
│ ├─ Labels utilisés
|
||||
│ └─ Dernière mise à jour
|
||||
├─ 🗑️ Supprimer
|
||||
│ └─ Warning: "Les notes seront déplacées vers Notes générales"
|
||||
└─ ❌ Fermer
|
||||
```
|
||||
|
||||
#### Réorganisation (Drag & Drop)
|
||||
```
|
||||
✈️ Voyage ║ ║ ← Drag handle
|
||||
💼 Travail ║ ║
|
||||
📖 Perso ║ ║
|
||||
|
||||
Drag "Travail" vers le haut → Réordonne
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🤖 Intégration IA
|
||||
|
||||
C'est la partie CRUCIALE qui rend cette feature vraiment puissante.
|
||||
|
||||
### IA1: Suggestion Automatique de Notebook
|
||||
|
||||
#### Scénario
|
||||
```
|
||||
User crée une note dans "Notes générales":
|
||||
"Rendez-vous dermatologue mardi 15h à Paris"
|
||||
|
||||
IA analyse et suggère:
|
||||
┌─────────────────────────────────────┐
|
||||
│ 💡 Suggestion IA │
|
||||
│ ┌───────────────────────────────┐ │
|
||||
│ │ Cette note semble appartenir │ │
|
||||
│ │ au notebook "Perso". │ │
|
||||
│ │ │ │
|
||||
│ │ [Ignorer] [Déplacer vers Perso]│ │
|
||||
│ └───────────────────────────────┘ │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Prompt IA:**
|
||||
```
|
||||
"Analyse cette note et suggère le notebook le plus approprié:
|
||||
Note: {content}
|
||||
Notebooks disponibles: {notebook_names_with_labels}
|
||||
|
||||
Réponds avec:
|
||||
- notebook_suggéré: string
|
||||
- confiance: 0-1
|
||||
- raisonnement: string"
|
||||
```
|
||||
|
||||
**Déclencheurs:**
|
||||
- Note créée dans "Notes générales"
|
||||
- Note modifiée significativement
|
||||
- 5+ secondes après la fin de frappe (pas en temps réel)
|
||||
|
||||
### IA2: Suggestion de Labels Contextuels
|
||||
|
||||
#### Scénario
|
||||
```
|
||||
Note dans Notebook "Voyage":
|
||||
"Hotel Shibuya Excel - 150€/nuit - Booking confirmé"
|
||||
|
||||
IA suggère:
|
||||
┌─────────────────────────────────────┐
|
||||
│ 💡 Suggestions de labels │
|
||||
│ ┌───────────────────────────────┐ │
|
||||
│ │ ✅ #hôtels (confiance: 95%) │ │ ← Click pour assigner
|
||||
│ │ ✅ #réservations (80%) │ │
|
||||
│ │ ✅ #tokyo (70%) │ │
|
||||
│ │ │ │
|
||||
│ │ [Tout sélectionner] [Ignorer] │ │
|
||||
│ └───────────────────────────────┘ │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Prompt IA:**
|
||||
```
|
||||
"Analyse cette note et suggère les labels appropriés:
|
||||
Note: {content}
|
||||
Notebook actuel: {notebook_name}
|
||||
Labels disponibles dans ce notebook: {available_labels}
|
||||
|
||||
Réponds avec un tableau de:
|
||||
- label: string (doit être dans {available_labels})
|
||||
- confiance: 0-1
|
||||
- raisonnement: string"
|
||||
```
|
||||
|
||||
**Comportement:**
|
||||
- ✅ Maximum 3 suggestions
|
||||
- ✅ Seulement si confiance > 60%
|
||||
- ✅ Labels cliquables pour assignation en 1 clic
|
||||
- ✅ Ne pas déranger si l'utilisateur tape activement
|
||||
|
||||
### IA3: Organisation Intelligente (Batch Processing)
|
||||
|
||||
#### Scénario
|
||||
```
|
||||
User a 20 notes dans "Notes générales"
|
||||
|
||||
Click "Organiser avec l'IA"
|
||||
├─ IA analyse toutes les notes
|
||||
├- Groupe par thématique
|
||||
└─ Présente un plan d'organisation:
|
||||
|
||||
┌─────────────────────────────────────┐
|
||||
│ 📋 Plan d'organisation IA │
|
||||
│ ┌───────────────────────────────┐ │
|
||||
│ │ Notebook: Voyage (5 notes) │ │
|
||||
│ │ • Hotel Tokyo... │ │
|
||||
│ │ • Vols JAL... │ │
|
||||
│ │ • Restaurant Shibuya... │ │
|
||||
│ │ [Tout sélectionner] │ │
|
||||
│ │ │ │
|
||||
│ │ Notebook: Travail (8 notes) │ │
|
||||
│ │ • Réunion lundi... │ │
|
||||
│ │ • Projet Alpha... │ │
|
||||
│ │ ... │ │
|
||||
│ │ │ │
|
||||
│ │ Notebook: Perso (7 notes) │ │
|
||||
│ │ • Idées livre... │ │
|
||||
│ │ ... │ │
|
||||
│ │ │ │
|
||||
│ │ [Annuler] [Appliquer tout] │ │
|
||||
│ └───────────────────────────────┘ │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Prompt IA:**
|
||||
```
|
||||
"Analyse ces {count} notes et propose une organisation:
|
||||
{notes_with_content}
|
||||
|
||||
Notebooks disponibles: {notebooks}
|
||||
|
||||
Pour chaque notebook, indique:
|
||||
- notebook_cible: string
|
||||
- notes: [{note_id, note_title, confidence, raison}]
|
||||
|
||||
Retourne un plan d'organisation optimisé."
|
||||
```
|
||||
|
||||
**Validation:**
|
||||
- ✅ User peut désélectionner des notes
|
||||
- ✅ User peut ajuster les destinations
|
||||
- ✅ Confirmation avant application
|
||||
- ✅ Undo possible (Ctrl+Z)
|
||||
|
||||
### IA4: Création Automatique de Labels
|
||||
|
||||
#### Scénario
|
||||
```
|
||||
Notebook "Voyage" devient peuplé de notes sur le Japon
|
||||
|
||||
IA détecte:
|
||||
- 10+ notes mentionnant "Tokyo"
|
||||
- 8+ notes mentionnant "Kyoto"
|
||||
- 5+ notes mentionnant "Osaka"
|
||||
|
||||
IA suggère:
|
||||
┌─────────────────────────────────────┐
|
||||
│ 💡 Suggestions de nouveaux labels │ │
|
||||
│ ┌───────────────────────────────┐ │
|
||||
│ │ J'ai détecté des thèmes récurrents│
|
||||
│ │ dans vos notes. Créer des labels?│
|
||||
│ │ │ │
|
||||
│ │ ✅ #tokyo (10 notes) │ │
|
||||
│ │ ✅ #kyoto (8 notes) │ │
|
||||
│ │ ✅ #osaka (5 notes) │ │
|
||||
│ │ │ │
|
||||
│ │ [Annuler] [Créer et assigner] │ │
|
||||
│ └───────────────────────────────┘ │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Déclencheur:**
|
||||
- Notebook atteint 15+ notes
|
||||
- IA détecte 3+ mots-clés récurrents (5+ fois chacun)
|
||||
- Ne propose PAS si les labels existent déjà
|
||||
|
||||
### IA5: Recherche Sémantique par Notebook
|
||||
|
||||
#### Scénario
|
||||
```
|
||||
User dans Notebook "Voyage" tape:
|
||||
"endroit pour dormir pas cher"
|
||||
|
||||
IA comprend le contexte "Voyage" et cherche:
|
||||
├─ Semantic search DANS ce notebook seulement
|
||||
├- Priorise les labels #hôtels, #auberges
|
||||
└- Résultats plus pertinents car contextuels
|
||||
|
||||
Résultats:
|
||||
┌─────────────────────────────────────┐
|
||||
│ 🔍 Résultats dans "Voyage" │
|
||||
│ ┌───────────────────────────────┐ │
|
||||
│ │ 📝 Capsule Hotel Shinjuku │ │
|
||||
│ │ #hôtels #tokyo │ │
|
||||
│ │ "Hotel capsule 30€/nuit..." │ │
|
||||
│ │ Correspondance: 87% │ │
|
||||
│ │ │ │
|
||||
│ │ 📝 Airbnb Asakusa │ │
|
||||
│ │ #hôtels #tokyo │ │
|
||||
│ │ "Appartement 45€/nuit..." │ │
|
||||
│ │ Correspondance: 82% │ │
|
||||
│ └───────────────────────────────┘ │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Avantage:**
|
||||
- ✅ Recherche contextuelle au notebook
|
||||
- ✅ Résultats plus pertinents
|
||||
- ✅ Comprend le jargon spécifique (ex: "vol" dans Voyage vs Travail)
|
||||
|
||||
### IA6: Synthèse par Notebook
|
||||
|
||||
#### Scénario
|
||||
```
|
||||
User clique "Résumer ce notebook" dans "Voyage"
|
||||
|
||||
IA génère:
|
||||
┌─────────────────────────────────────┐
|
||||
│ 📊 Synthèse du Notebook Voyage │
|
||||
│ ┌───────────────────────────────┐ │
|
||||
│ │ 🌍 Destinations │ │
|
||||
│ │ • Japon (Tokyo, Kyoto) │ │
|
||||
│ │ • France (Paris) │ │
|
||||
│ │ │ │
|
||||
│ │ 📅 Dates │ │
|
||||
│ │ • 15-25 Mars 2024 │ │
|
||||
│ │ │ │
|
||||
│ │ 🏨 Réservations │ │
|
||||
│ │ • 3 hôtels réservés │ │
|
||||
│ │ • 2 vols confirmés │ │
|
||||
│ │ • 5 restaurants identifiés │ │
|
||||
│ │ │ │
|
||||
│ │ 💰 Budget estimé: 3500€ │ │
|
||||
│ │ │ │
|
||||
│ │ ⚠️ Actions requises │ │
|
||||
│ │ • Réserver visa japonais │ │
|
||||
│ │ • Confirmer assurance voyage │ │
|
||||
│ └───────────────────────────────┘ │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Prompt IA:**
|
||||
```
|
||||
"Génère une synthèse structurée de ce notebook:
|
||||
{notes_with_labels}
|
||||
|
||||
Inclus:
|
||||
- Destinations/Thèmes principaux
|
||||
- Dates importantes
|
||||
- Éléments réservés vs planifiés
|
||||
- Actions requises
|
||||
- Statistiques (nombre de notes, labels utilisés)
|
||||
|
||||
Format: Markdown structuré avec emojis."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🗄️ Structure de Données (Database Schema)
|
||||
|
||||
### Prisma Schema - Nouveaux Modèles
|
||||
|
||||
```prisma
|
||||
// Modèle Notebook
|
||||
model Notebook {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
icon String? // Emoji: "✈️", "💼", "📖"
|
||||
color String? // Hex color: "#FF6B6B"
|
||||
order Int // Ordre manuel dans la sidebar
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
notes Note[]
|
||||
labels Label[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([userId, order])
|
||||
}
|
||||
|
||||
// Modèle Label MODIFIÉ - Ajout notebookId
|
||||
model Label {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
color String? // Couleur du label
|
||||
notebookId String // NOUVEAU: Label appartient à un notebook
|
||||
notebook Notebook @relation(fields: [notebookId], references: [id], onDelete: Cascade)
|
||||
notes Note[] // Relation many-to-many via NoteLabel
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([notebookId, name]) // Un label est unique dans un notebook
|
||||
@@index([notebookId])
|
||||
}
|
||||
|
||||
// Modèle Note MODIFIÉ - Ajout notebookId (optionnel)
|
||||
model Note {
|
||||
id String @id @default(cuid())
|
||||
title String?
|
||||
content String
|
||||
// ... autres champs existants ...
|
||||
|
||||
notebookId String? // NOUVEAU: Optionnel - null = dans "Notes générales"
|
||||
notebook Notebook? @relation(fields: [notebookId], references: [id], onDelete: SetNull)
|
||||
|
||||
// Garantir qu'une note est dans UN SEUL notebook
|
||||
@@index([userId, notebookId])
|
||||
}
|
||||
|
||||
// Table de jonction Note-Label (existante mais gardée)
|
||||
model NoteLabel {
|
||||
noteId String
|
||||
labelId String
|
||||
note Note @relation(fields: [noteId], references: [id], onDelete: Cascade)
|
||||
label Label @relation(fields: [labelId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@id([noteId, labelId])
|
||||
@@index([noteId])
|
||||
@@index([labelId])
|
||||
}
|
||||
```
|
||||
|
||||
### Clés de la Structure
|
||||
|
||||
**Règles d'intégrité:**
|
||||
1. ✅ `Note.notebookId` est **optionnel** (null = Notes générales)
|
||||
2. ✅ `Label.notebookId` est **obligatoire** (labels TOUJOURS contextuels)
|
||||
3. ✅ `@@unique([notebookId, name])` = Unicité des labels DANS un notebook
|
||||
4. ✅ `onDelete: SetNull` sur Note→Notebook = Si notebook supprimé, notes → Notes générales
|
||||
|
||||
### Migration Schema
|
||||
|
||||
```sql
|
||||
-- Étape 1: Ajouter les nouveaux modèles
|
||||
CREATE TABLE "Notebook" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"icon" TEXT,
|
||||
"color" TEXT,
|
||||
"order" INTEGER NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- Étape 2: Ajouter notebookId aux Notes (optionnel)
|
||||
ALTER TABLE "Note" ADD COLUMN "notebookId" TEXT;
|
||||
ALTER TABLE "Note" ADD FOREIGN KEY ("notebookId") REFERENCES "Notebook"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- Étape 3: Ajouter notebookId aux Labels (obligatoire)
|
||||
ALTER TABLE "Label" ADD COLUMN "notebookId" TEXT NOT NULL DEFAULT 'TEMP_MIGRATION';
|
||||
ALTER TABLE "Label" ADD FOREIGN KEY ("notebookId") REFERENCES "Notebook"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- Étape 4: Créer notebook par défaut pour la migration
|
||||
INSERT INTO "Notebook" (id, name, icon, color, "order", "userId")
|
||||
VALUES (
|
||||
'migration_default',
|
||||
'Labels existants',
|
||||
'📦',
|
||||
'#9CA3AF',
|
||||
999,
|
||||
{user_id}
|
||||
);
|
||||
|
||||
-- Étape 5: Assigner tous les labels existants à ce notebook
|
||||
UPDATE "Label" SET "notebookId" = 'migration_default' WHERE "notebookId" = 'TEMP_MIGRATION';
|
||||
|
||||
-- Étape 6: Laisser toutes les notes SANS notebook (Notes générales)
|
||||
-- Rien à faire - notebookId est déjà NULL par défaut
|
||||
|
||||
-- Étape 7: Créer index pour performance
|
||||
CREATE INDEX "Note_userId_notebookId_idx" ON "Note"("userId", "notebookId");
|
||||
CREATE UNIQUE INDEX "Label_notebookId_name_key" ON "Label"("notebookId", "name");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Migration des Données Existantes
|
||||
|
||||
### Stratégie de Migration
|
||||
|
||||
#### Phase 1: Pré-migration (Backend)
|
||||
|
||||
```typescript
|
||||
// app/actions/migration/prepare-notebooks.ts
|
||||
'use server'
|
||||
|
||||
export async function prepareNotebookMigration() {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
||||
|
||||
// 1. Créer notebook "Import" pour les labels existants
|
||||
const importNotebook = await prisma.notebook.create({
|
||||
data: {
|
||||
name: 'Labels existants',
|
||||
icon: '📦',
|
||||
color: '#9CA3AF',
|
||||
order: 999,
|
||||
userId: session.user.id
|
||||
}
|
||||
})
|
||||
|
||||
// 2. Assigner TOUS les labels existants à ce notebook
|
||||
await prisma.label.updateMany({
|
||||
where: { userId: session.user.id },
|
||||
data: { notebookId: importNotebook.id }
|
||||
})
|
||||
|
||||
// 3. Laisser les notes SANS notebook (Notes générales)
|
||||
// Rien à faire - notebookId est NULL par défaut
|
||||
|
||||
return { success: true, importNotebookId: importNotebook.id }
|
||||
}
|
||||
```
|
||||
|
||||
#### Phase 2: Migration Interactive (User Journey)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 🎉 Bienvenue dans les Notebooks ! │
|
||||
│ │
|
||||
│ Nous avons organisé vos étiquettes existantes dans │
|
||||
│ le notebook "Labels existants" pour ne rien perdre. │
|
||||
│ │
|
||||
│ 📊 État actuel: │
|
||||
│ • 15 notes sans notebook (à organiser) │
|
||||
│ • 1 notebook "Labels existants" │
|
||||
│ • 12 étiquettes préservées │
|
||||
│ │
|
||||
│ Que voulez-vous faire ? │
|
||||
│ │
|
||||
│ [1] Laisser l'IA organiser mes notes │
|
||||
│ [2] Explorer et créer mes propres notebooks │
|
||||
│ [3] Tout déplacer vers "Notes générales" │
|
||||
│ │
|
||||
│ [Plus tard] Je déciderai plus tard │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Phase 3: Organisation IA (Option 1)
|
||||
|
||||
Si user choisit "Laisser l'IA organiser":
|
||||
|
||||
```typescript
|
||||
// app/actions/migration/ai-organize.ts
|
||||
'use server'
|
||||
|
||||
export async function organizeWithAI() {
|
||||
const session = await auth()
|
||||
const notesWithoutNotebook = await prisma.note.findMany({
|
||||
where: {
|
||||
userId: session.user.id,
|
||||
notebookId: null
|
||||
}
|
||||
})
|
||||
|
||||
// IA analyse et propose des notebooks
|
||||
const suggestions = await aiService.suggestNotebooks(notesWithoutNotebook)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
suggestions: [
|
||||
{
|
||||
notebookName: 'Voyage',
|
||||
icon: '✈️',
|
||||
color: '#3B82F6',
|
||||
notes: [/* notes suggérées */],
|
||||
confidence: 0.89
|
||||
},
|
||||
{
|
||||
notebookName: 'Travail',
|
||||
icon: '💼',
|
||||
color: '#10B981',
|
||||
notes: [/* notes suggérées */],
|
||||
confidence: 0.92
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Phase 4: Validation User
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 📋 Plan d'organisation proposé par l'IA │
|
||||
│ │
|
||||
│ ✈️ Voyage (5 notes) - Confiance: 89% │
|
||||
│ ☑ Hotel Tokyo Shibuya │
|
||||
│ ☑ Vols JAL Tokyo-Paris │
|
||||
│ ☑ Restaurant Shibuya │
|
||||
│ ☑ Visa japonais │
|
||||
│ ☑ Itinéraire Kyoto │
|
||||
│ │
|
||||
│ 💼 Travail (8 notes) - Confiance: 92% │
|
||||
│ ☑ Réunion lundi │
|
||||
│ ☑ Projet Alpha │
|
||||
│ ☑ ... │
|
||||
│ │
|
||||
│ 📖 Perso (2 notes) - Confiance: 76% │
|
||||
│ ☑ Idées livre │
|
||||
│ ☑ Objectifs 2024 │
|
||||
│ │
|
||||
│ [Désélectionner] [Annuler] [Appliquer] │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Success Metrics
|
||||
|
||||
### KPIs à Mesurer
|
||||
|
||||
**Adoption:**
|
||||
- % d'utilisateurs créant au moins 1 notebook dans les 30 jours
|
||||
- Nombre moyen de notebooks par utilisateur actif
|
||||
- % de notes organisées (avec notebook) vs notes générales
|
||||
|
||||
**Engagement:**
|
||||
- Temps passé par notebook (ex: Voyage plus actif avant un voyage)
|
||||
- Fréquence d'utilisation des labels contextuels
|
||||
- Taux d'utilisation des suggestions IA
|
||||
|
||||
**Satisfaction:**
|
||||
- NPS (Net Promoter Score) sur la feature notebooks
|
||||
- % d'utilisateurs gardant le système par défaut (Import) vs créant les leurs
|
||||
- Taux d'abandon lors de la migration
|
||||
|
||||
**Performance IA:**
|
||||
- Taux d'acceptation des suggestions IA (notebook)
|
||||
- Taux d'acceptation des suggestions IA (labels)
|
||||
- Précision des suggestions (feedback utilisateur)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Implementation Phases
|
||||
|
||||
### Phase 1: MVP (Weeks 1-3)
|
||||
**Objectif:** Structure de base sans IA
|
||||
|
||||
- ✅ Database schema (Notebook, Label modifié, Note modifié)
|
||||
- ✅ API endpoints (CRUD notebooks)
|
||||
- ✅ UI: Sidebar avec notebooks
|
||||
- ✅ UI: Création/édition de notebooks
|
||||
- ✅ UI: Assignation de notebook aux notes
|
||||
- ✅ UI: Labels contextuels (affichage)
|
||||
- ✅ UI: Drag & drop des notebooks
|
||||
- ✅ Migration: Notebook "Import" par défaut
|
||||
- ❌ PAS d'IA encore
|
||||
|
||||
### Phase 2: IA Features (Weeks 4-5)
|
||||
**Objectif:** IA pour organisation intelligente
|
||||
|
||||
- ✅ IA1: Suggestion automatique de notebook
|
||||
- ✅ IA2: Suggestion de labels contextuels
|
||||
- ✅ IA3: Organisation batch (Notes générales → Notebooks)
|
||||
- ✅ UI: Modals de suggestions IA
|
||||
- ✅ Feedback loop (accepter/rejeter suggestions)
|
||||
|
||||
### Phase 3: Advanced IA (Weeks 6-7)
|
||||
**Objectif:** Features IA avancées
|
||||
|
||||
- ✅ IA4: Création automatique de labels
|
||||
- ✅ IA5: Recherche sémantique contextuelle
|
||||
- ✅ IA6: Synthèse par notebook
|
||||
- ✅ Analytics: Dashboard d'utilisation des notebooks
|
||||
|
||||
### Phase 4: Polish & Testing (Week 8)
|
||||
**Objectif:** Finalisation et tests
|
||||
|
||||
- ✅ Playwright E2E tests
|
||||
- ✅ Performance optimization
|
||||
- ✅ Accessibility audit
|
||||
- ✅ Beta testing avec users
|
||||
- ✅ Documentation
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Risques & Mitigations
|
||||
|
||||
### Risque 1: Résistance au changement
|
||||
**Description:** Users habitués aux tags globaux pourraient rejeter les notebooks
|
||||
|
||||
**Mitigation:**
|
||||
- Phase de migration douce (optionnel)
|
||||
- Mode hybride temporaire (garder vue tags pendant transition)
|
||||
- Tutoriels interactifs
|
||||
- Onboarding progressif
|
||||
|
||||
### Risque 2: Performance IA
|
||||
**Description:** Suggestions IA pourraient être lentes ou inexactes
|
||||
|
||||
**Mitigation:**
|
||||
- Cache des suggestions (24h)
|
||||
- Seuils de confiance minimums (>60%)
|
||||
- Feedback loop pour améliorer le modèle
|
||||
- Fallback rapide si IA timeout
|
||||
|
||||
### Risque 3: Migration des données
|
||||
**Description:** Perte de données ou organisation pendant la migration
|
||||
|
||||
**Mitigation:**
|
||||
- Backup automatique avant migration
|
||||
- Migration par défaut (notebook "Import")
|
||||
- Possibilité de revenir en arrière (rollback)
|
||||
- Tests exhaustifs de migration
|
||||
|
||||
### Risque 4: Complexité UX
|
||||
**Description:** Trop de clics pour organiser les notes
|
||||
|
||||
**Mitigation:**
|
||||
- Drag & drop intuitif
|
||||
- Raccourcis clavier
|
||||
- IA pour automatiser l'organisation
|
||||
- Mesures d'usabilité (clics, temps)
|
||||
|
||||
### Risque 5: Labels contextuels = perte de flexibilité
|
||||
**Description:** Users ne peuvent plus utiliser un label global (#urgent partout)
|
||||
|
||||
**Mitigation:**
|
||||
- Éduquer: "Urgent" peut être recréé dans chaque notebook
|
||||
- IA suggère de recréer les labels importants
|
||||
- Option: Labels "favoris" synchronisés (feature future)
|
||||
|
||||
---
|
||||
|
||||
## 📚 Glossaire
|
||||
|
||||
- **Notebook:** Collection de notes sur un thème (ex: Voyage, Travail)
|
||||
- **Labels Contextuels:** Tags spécifiques à un notebook (ex: #hôtels dans Voyage)
|
||||
- **Inbox / Notes générales:** Zone temporaire pour les notes non organisées
|
||||
- **IA:** Intelligence Artificielle (OpenAI ou Ollama)
|
||||
- **Suggestion IA:** Proposition automatique basée sur l'analyse du contenu
|
||||
- **Drag & Drop:** Action de glisser-déposer pour déplacer des éléments
|
||||
- **Migration:** Transition du système de tags vers les notebooks
|
||||
- **Notebook par défaut:** Notebook créé automatiquement pour préserver les tags existants
|
||||
|
||||
---
|
||||
|
||||
## 📝 Notes pour l'Architecture Team
|
||||
|
||||
### Points Critiques à Implémenter
|
||||
|
||||
1. **Database:**
|
||||
- `Note.notebookId` est OPTIONNEL (null = Notes générales)
|
||||
- `Label.notebookId` est OBLIGATOIRE
|
||||
- Contrainte d'unicité: `@@unique([notebookId, name])`
|
||||
|
||||
2. **API:**
|
||||
- Nouveau endpoint: `/api/notebooks` (CRUD complet)
|
||||
- Endpoint modifié: `/api/labels` (filtre par notebook)
|
||||
- Endpoint modifié: `/api/notes` (filtre par notebook)
|
||||
|
||||
3. **UI Components:**
|
||||
- `SidebarNotebooks`: Liste des notebooks avec drag & drop
|
||||
- `NotebookSelector`: Dropdown pour choisir le notebook
|
||||
- `ContextualLabels`: Labels filtrés par notebook actif
|
||||
- `AISuggestions`: Modals pour les suggestions IA
|
||||
|
||||
4. **IA Services:**
|
||||
- `NotebookSuggestionService`: IA pour suggérer un notebook
|
||||
- `LabelSuggestionService`: IA pour suggérer des labels
|
||||
- `BatchOrganizationService`: IA pour organiser en lot
|
||||
- `AutoLabelCreationService`: IA pour créer des labels
|
||||
|
||||
5. **Performance:**
|
||||
- Index sur `Note.userId + Note.notebookId`
|
||||
- Cache des suggestions IA (Redis ou in-memory)
|
||||
- Virtual scrolling pour les notebooks avec 100+ notes
|
||||
|
||||
---
|
||||
|
||||
## ✅ Checklist de Validation
|
||||
|
||||
Avant de passer en développement, confirmer:
|
||||
|
||||
- [ ] Ramez valide l'UX décrite dans ce document
|
||||
- [ ] L'Architecture Team a revu le schéma DB
|
||||
- [ ] L'équipe IA a validé les prompts proposés
|
||||
- [ ] Les risques sont acceptables et des mitigations sont en place
|
||||
- [ ] Le plan de migration est testé sur un dataset de test
|
||||
- [ ] Les mesures de succès (KPIs) sont définies et traçables
|
||||
- [ ] Le wireframe UI est validé par Ramez
|
||||
- [ ] L'implémentation est planifiée sur 8 semaines max
|
||||
|
||||
---
|
||||
|
||||
**Status:** ✅ PRD COMPLET - Prêt pour Architecture et Wireframes
|
||||
|
||||
**Next Steps:**
|
||||
1. Créer les wireframes UX (Option XW)
|
||||
2. Définir l'architecture technique
|
||||
3. Commencer Phase 1 (MVP)
|
||||
|
||||
---
|
||||
|
||||
*Document créé par Sally (UX Designer) avec Ramez (Product Owner)*
|
||||
*Date: 2026-01-11*
|
||||
*Version: 1.0 - Final*
|
||||
1379
_bmad-output/planning-artifacts/notebooks-epics-stories.md
Normal file
1379
_bmad-output/planning-artifacts/notebooks-epics-stories.md
Normal file
File diff suppressed because it is too large
Load Diff
2756
_bmad-output/planning-artifacts/notebooks-tech-specs.md
Normal file
2756
_bmad-output/planning-artifacts/notebooks-tech-specs.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -33,7 +33,7 @@ L'objectif est de créer un flux de travail où la capture reste instantanée, m
|
||||
Contrairement à Google Keep (pas d'IA) et Notion (IA à la demande et complexe), Memento Phase 1 introduit **trois innovations exclusives**:
|
||||
|
||||
1. **Contextual Smart Assistance** - Les fonctionnalités IA n'apparaissent que quand c'est pertinent:
|
||||
- Suggestions de titres uniquement après 50+ mots sans titre
|
||||
- Suggestions de titres uniquement après 10+ mots sans titre
|
||||
- Toast non-intrusive avec options "Voir | Plus tard | Ne plus demander"
|
||||
- L'utilisateur découvre les features naturellement sans être overwhelmed
|
||||
|
||||
@@ -199,7 +199,7 @@ Phase 1 respects existing patterns:
|
||||
**Performance:**
|
||||
- **Recherche sémantique:** < 300ms pour une base de 1000 notes
|
||||
- **Pourquoi:** Ne pas ralentir l'UX, l'utilisateur ne doit pas attendre
|
||||
- **Suggestions titres:** < 2s après détection (50+ mots sans titre)
|
||||
- **Suggestions titres:** < 2s après détection (10+ mots sans titre)
|
||||
- **Pourquoi:** Doit paraître "instantané" pour l'utilisateur
|
||||
- **Memory Echo analysis:** Traitement en arrière-plan, blocage UI < 100ms
|
||||
- **Pourquoi:** L'utilisateur ne doit jamais sentir que l'IA "travaille"
|
||||
@@ -248,7 +248,7 @@ Phase 1 respects existing patterns:
|
||||
**Must-Have Capabilities:**
|
||||
|
||||
**1. Intelligent Title Suggestions**
|
||||
- Déclenchement automatique après 50+ mots sans titre
|
||||
- Déclenchement automatique après 10+ mots sans titre
|
||||
- Toast non-intrusive: "J'ai 3 idées de titres pour ta note, les voir?"
|
||||
- 3 suggestions IA présentées en dropdown
|
||||
- Options: "Voir" | "Plus tard" | "Ne plus demander"
|
||||
@@ -383,7 +383,7 @@ Trois mois plus tard:
|
||||
- **"C'est comme si j'avais un assistant de recherche personnel qui lit tout ce que j'écris"**
|
||||
|
||||
**Journey Requirements Revealed:**
|
||||
- Détection intelligente du moment opportun (50+ mots sans titre)
|
||||
- Détection intelligente du moment opportun (10+ mots sans titre)
|
||||
- Recherche sémantique qui comprend l'intention, pas juste les mots
|
||||
- Memory Echo avec fréquence contrôlable (pas spam)
|
||||
- Feedback utilisateur pour apprentissage (👍👎)
|
||||
@@ -734,7 +734,7 @@ Memento introduces a new interaction pattern for AI features: **context-aware ap
|
||||
**The Pattern:**
|
||||
|
||||
**Example 1 - Title Suggestions:**
|
||||
- **Trigger:** User writes 50+ words without a title
|
||||
- **Trigger:** User writes 10+ words without a title
|
||||
- **Appearance:** Subtle toast: "I have 3 title ideas for this note, view them?"
|
||||
- **Options:** "View" | "Not now" | "Don't ask for this note"
|
||||
- **Outcome:** Feature discovered naturally, not overwhelming
|
||||
@@ -1109,7 +1109,7 @@ Memento Phase 1 MVP IA combines two MVP philosophies:
|
||||
**Core User Journeys Supported:**
|
||||
|
||||
✅ **Journey 1: Alex (Primary User - Success Path)**
|
||||
- Title suggestions when writing 50+ words
|
||||
- Title suggestions when writing 10+ words
|
||||
- Semantic search finds notes by meaning
|
||||
- Memory Echo reveals hidden connections
|
||||
- Complete workflow: capture → search → discover
|
||||
@@ -1127,7 +1127,7 @@ Memento Phase 1 MVP IA combines two MVP philosophies:
|
||||
**Must-Have Capabilities (MVP):**
|
||||
|
||||
**1. Intelligent Title Suggestions**
|
||||
- Déclenchement automatique après 50+ mots sans titre
|
||||
- Déclenchement automatique après 10+ mots sans titre
|
||||
- Toast notification non-intrusive avec options "Voir | Plus tard | Ne plus demander"
|
||||
- 3 suggestions IA générées en < 2s
|
||||
- Application one-click ou saisie manuelle
|
||||
|
||||
687
_bmad-output/planning-artifacts/project-context.md
Normal file
687
_bmad-output/planning-artifacts/project-context.md
Normal file
@@ -0,0 +1,687 @@
|
||||
---
|
||||
project_name: 'Keep (Memento Phase 1 MVP AI)'
|
||||
user_name: 'Ramez'
|
||||
date: '2026-01-10'
|
||||
sections_completed: ['technology_stack', 'language_rules', 'framework_rules', 'testing_rules', 'quality_rules', 'workflow_rules', 'anti_patterns']
|
||||
status: 'complete'
|
||||
rule_count: 50
|
||||
optimized_for_llm: true
|
||||
workflow_type: 'generate-project-context'
|
||||
---
|
||||
|
||||
# Project Context for AI Agents
|
||||
|
||||
_This file contains critical rules and patterns that AI agents must follow when implementing code in this project. Focus on unobvious details that agents might otherwise miss._
|
||||
|
||||
---
|
||||
|
||||
## Technology Stack & Versions
|
||||
|
||||
### Core Framework
|
||||
|
||||
**Frontend:**
|
||||
- **Next.js:** 16.1.1 (App Router)
|
||||
- **React:** 19.2.3
|
||||
- **TypeScript:** 5.x (strict mode enabled)
|
||||
|
||||
**Backend:**
|
||||
- **Next.js API Routes** (REST)
|
||||
- **Server Actions** ('use server' directive)
|
||||
- **Prisma:** 5.22.0 (ORM)
|
||||
- **Database:** SQLite (better-sqlite3)
|
||||
|
||||
**Authentication:**
|
||||
- **NextAuth:** 5.0.0-beta.30
|
||||
- **Adapter:** @auth/prisma-adapter
|
||||
|
||||
**AI/ML:**
|
||||
- **Vercel AI SDK:** 6.0.23
|
||||
- **OpenAI Provider:** @ai-sdk/openai ^3.0.7
|
||||
- **Ollama Provider:** ollama-ai-provider ^1.2.0
|
||||
- **Language Detection:** tinyld (to be installed for Phase 1)
|
||||
|
||||
**UI Components:**
|
||||
- **Radix UI:** Multiple primitives (@radix-ui/react-*)
|
||||
- **Tailwind CSS:** 4.x
|
||||
- **Lucide Icons:** ^0.562.0
|
||||
- **Sonner:** ^2.0.7 (toast notifications)
|
||||
|
||||
**Utilities:**
|
||||
- **Zod:** ^4.3.5 (schema validation)
|
||||
- **date-fns:** ^4.1.0 (date formatting)
|
||||
- **clsx:** ^2.1.1, **tailwind-merge:** ^3.4.0 (CSS utilities)
|
||||
- **katex:** ^0.16.27 (LaTeX rendering)
|
||||
- **react-markdown:** ^10.1.0 (markdown rendering)
|
||||
|
||||
**Drag & Drop:**
|
||||
- **@dnd-kit:** ^6.3.1 (modern DnD library)
|
||||
- **muuri:** ^0.9.5 (masonry grid layout)
|
||||
|
||||
**Testing:**
|
||||
- **Playwright:** ^1.57.0 (E2E tests)
|
||||
|
||||
---
|
||||
|
||||
## Critical Implementation Rules
|
||||
|
||||
### TypeScript Configuration
|
||||
|
||||
**STRICT MODE ENABLED:**
|
||||
```json
|
||||
{
|
||||
"strict": true,
|
||||
"target": "ES2017",
|
||||
"moduleResolution": "bundler",
|
||||
"jsx": "react-jsx",
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**CRITICAL RULES:**
|
||||
- ✅ All files MUST be typed (no `any` without explicit reason)
|
||||
- ✅ Use `interface` for object shapes, `type` for unions/primitives
|
||||
- ✅ Import from `@/` alias (not relative paths like `../`)
|
||||
- ✅ Props MUST be typed with interfaces (PascalCase names)
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
// ✅ GOOD
|
||||
interface NoteCardProps {
|
||||
note: Note
|
||||
onEdit?: (note: Note) => void
|
||||
}
|
||||
|
||||
export function NoteCard({ note, onEdit }: NoteCardProps) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// ❌ BAD - any without reason
|
||||
export function NoteCard({ note, onEdit }: any) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Component Patterns
|
||||
|
||||
**Directives Required:**
|
||||
- ✅ Server Components: No directive (default in Next.js 16 App Router)
|
||||
- ✅ Client Components: `'use client'` at TOP of file (line 1)
|
||||
- ✅ Server Actions: `'use server'` at TOP of file (line 1)
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
// keep-notes/components/ai/ai-suggestion.tsx
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
export function AiSuggestion() {
|
||||
// Interactive component logic
|
||||
}
|
||||
```
|
||||
|
||||
**Component Naming:**
|
||||
- ✅ **PascalCase** for component names: `NoteCard`, `LabelBadge`, `AiSuggestion`
|
||||
- ✅ **kebab-case** for file names: `note-card.tsx`, `label-badge.tsx`, `ai-suggestion.tsx`
|
||||
- ✅ **UI components** in `components/ui/` subdirectory: `button.tsx`, `dialog.tsx`
|
||||
|
||||
**Props Pattern:**
|
||||
```typescript
|
||||
// ✅ GOOD - Interface export
|
||||
export interface NoteCardProps {
|
||||
note: Note
|
||||
onEdit?: (note: Note, readOnly?: boolean) => void
|
||||
isDragging?: boolean
|
||||
}
|
||||
|
||||
export function NoteCard({ note, onEdit, isDragging }: NoteCardProps) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Imports Order:**
|
||||
```typescript
|
||||
// 1. React imports
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
// 2. Third-party libraries
|
||||
import { formatDistanceToNow } from 'date-fns'
|
||||
import { Bell } from 'lucide-react'
|
||||
|
||||
// 3. Local imports (use @/ alias)
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Note } from '@/lib/types'
|
||||
import { deleteNote } from '@/app/actions/notes'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Server Actions Pattern
|
||||
|
||||
**CRITICAL: All server actions MUST follow this pattern:**
|
||||
|
||||
```typescript
|
||||
// keep-notes/app/actions/ai-suggestions.ts
|
||||
'use server'
|
||||
|
||||
import { auth } from '@/auth'
|
||||
import { revalidatePath } from 'next/cache'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function generateTitleSuggestions(noteId: string) {
|
||||
// 1. Authentication check
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
|
||||
try {
|
||||
// 2. Business logic
|
||||
const note = await prisma.note.findUnique({
|
||||
where: { id: noteId }
|
||||
})
|
||||
|
||||
if (!note) {
|
||||
throw new Error('Note not found')
|
||||
}
|
||||
|
||||
// 3. AI processing
|
||||
const titles = await generateTitles(note.content)
|
||||
|
||||
// 4. Revalidate cache
|
||||
revalidatePath('/')
|
||||
|
||||
return { success: true, titles }
|
||||
} catch (error) {
|
||||
console.error('Error generating titles:', error)
|
||||
throw new Error('Failed to generate title suggestions')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**CRITICAL RULES:**
|
||||
- ✅ `'use server'` at line 1 (before imports)
|
||||
- ✅ **ALWAYS** check `auth()` session first
|
||||
- ✅ **ALWAYS** `revalidatePath('/')` after mutations
|
||||
- ✅ Use `try/catch` with `console.error()` logging
|
||||
- ✅ Throw `Error` objects (not strings)
|
||||
- ✅ Return `{ success: true, data }` or throw error
|
||||
|
||||
---
|
||||
|
||||
### API Routes Pattern
|
||||
|
||||
**CRITICAL: All API routes MUST follow this pattern:**
|
||||
|
||||
```typescript
|
||||
// keep-notes/app/api/ai/titles/route.ts
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
|
||||
const requestSchema = z.object({
|
||||
content: z.string().min(1, "Content required"),
|
||||
noteId: z.string().optional()
|
||||
})
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
// 1. Parse and validate request
|
||||
const body = await req.json()
|
||||
const { content, noteId } = requestSchema.parse(body)
|
||||
|
||||
// 2. Business logic
|
||||
const titles = await generateTitles(content)
|
||||
|
||||
// 3. Return success response
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: { titles }
|
||||
})
|
||||
} catch (error) {
|
||||
// 4. Error handling
|
||||
if (error instanceof z.ZodError) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: error.issues },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
console.error('Error generating titles:', error)
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'Failed to generate titles' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**CRITICAL RULES:**
|
||||
- ✅ Use **Zod schemas** for request validation
|
||||
- ✅ Return `{ success: true, data: any }` for success
|
||||
- ✅ Return `{ success: false, error: string }` for errors
|
||||
- ✅ Handle `ZodError` separately (400 status)
|
||||
- ✅ Log errors with `console.error()`
|
||||
- ✅ **NEVER** expose stack traces to clients
|
||||
|
||||
**Response Format:**
|
||||
```typescript
|
||||
// Success
|
||||
{ success: true, data: { ... } }
|
||||
|
||||
// Error
|
||||
{ success: false, error: "Human-readable error message" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Database Access Pattern
|
||||
|
||||
**SINGLE DATA ACCESS LAYER:**
|
||||
- ✅ **ONLY** use Prisma ORM (no raw SQL, no direct database access)
|
||||
- ✅ Import from `@/lib/prisma` (singleton instance)
|
||||
- ✅ Use `findMany`, `findUnique`, `create`, `update`, `delete`
|
||||
|
||||
```typescript
|
||||
// ✅ GOOD
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
const notes = await prisma.note.findMany({
|
||||
where: { userId: session.user.id },
|
||||
orderBy: { createdAt: 'desc' }
|
||||
})
|
||||
```
|
||||
|
||||
**Prisma Schema Conventions:**
|
||||
- ✅ **PascalCase** for model names: `User`, `Note`, `Label`, `AiFeedback`
|
||||
- ✅ **camelCase** for fields: `userId`, `isPinned`, `createdAt`
|
||||
- ✅ Foreign keys: `{model}Id` format: `userId`, `noteId`
|
||||
- ✅ Booleans: prefix `is` for flags: `isPinned`, `isArchived`
|
||||
- ✅ Timestamps: suffix `At` for dates: `createdAt`, `updatedAt`
|
||||
- ✅ All new fields optional (nullable) for backward compatibility
|
||||
|
||||
---
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
**Database:**
|
||||
- Tables: **PascalCase** (`AiFeedback`, `MemoryEchoInsight`)
|
||||
- Columns: **camelCase** (`noteId`, `similarityScore`)
|
||||
- Indexes: Prisma `@@index([...])` annotations
|
||||
|
||||
**API Routes:**
|
||||
- Collections: **plural** (`/api/notes`, `/api/labels`)
|
||||
- Items: **singular** (`/api/notes/[id]`)
|
||||
- Namespace: `/api/ai/*` for AI features
|
||||
|
||||
**Components:**
|
||||
- Component names: **PascalCase** (`NoteCard`, `AiSuggestion`)
|
||||
- File names: **kebab-case** (`note-card.tsx`, `ai-suggestion.tsx`)
|
||||
|
||||
**Functions:**
|
||||
- Functions: **camelCase** (`getNotes`, `createNote`, `togglePin`)
|
||||
- Verbs first: `get`, `create`, `update`, `delete`, `toggle`
|
||||
- Handlers: prefix `handle` (`handleDelete`, `handleTogglePin`)
|
||||
|
||||
**Variables:**
|
||||
- Variables: **camelCase** (`userId`, `isPending`, `noteId`)
|
||||
- Types/interfaces: **PascalCase** (`Note`, `NoteCardProps`)
|
||||
|
||||
---
|
||||
|
||||
### State Management
|
||||
|
||||
**NO GLOBAL STATE LIBRARIES:**
|
||||
- ❌ No Redux, Zustand, or similar
|
||||
- ✅ **React useState** for local component state
|
||||
- ✅ **React Context** for shared state (User session, Theme, Labels)
|
||||
- ✅ **React Cache** for server-side caching
|
||||
- ✅ **useOptimistic** for immediate UI feedback
|
||||
- ✅ **useTransition** for non-blocking updates
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
'use client'
|
||||
|
||||
import { useState, useTransition, useOptimistic } from 'react'
|
||||
|
||||
export function NoteCard({ note }: NoteCardProps) {
|
||||
const [isPending, startTransition] = useTransition()
|
||||
const [optimisticNote, addOptimisticNote] = useOptimistic(
|
||||
note,
|
||||
(state, newProps: Partial<Note>) => ({ ...state, ...newProps })
|
||||
)
|
||||
|
||||
const handleTogglePin = async () => {
|
||||
startTransition(async () => {
|
||||
addOptimisticNote({ isPinned: !note.isPinned })
|
||||
await togglePin(note.id, !note.isPinned)
|
||||
router.refresh()
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Error Handling
|
||||
|
||||
**Global Pattern:**
|
||||
```typescript
|
||||
// API Routes
|
||||
try {
|
||||
// ... code
|
||||
} catch (error) {
|
||||
console.error('Feature name error:', error)
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'Human-readable message' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
|
||||
// Server Actions
|
||||
try {
|
||||
// ... code
|
||||
} catch (error) {
|
||||
console.error('Feature name error:', error)
|
||||
throw new Error('Failed to action')
|
||||
}
|
||||
```
|
||||
|
||||
**CRITICAL RULES:**
|
||||
- ✅ Use `console.error()` for logging (not `console.log`)
|
||||
- ✅ Human-readable error messages (no technical jargon)
|
||||
- ✅ **NEVER** expose stack traces to users
|
||||
- ✅ **NEVER** expose internal error details
|
||||
|
||||
---
|
||||
|
||||
### Import Rules
|
||||
|
||||
**ALWAYS use @/ alias:**
|
||||
```typescript
|
||||
// ✅ GOOD
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Note } from '@/lib/types'
|
||||
import { deleteNote } from '@/app/actions/notes'
|
||||
|
||||
// ❌ BAD - relative paths
|
||||
import { Button } from '../../../components/ui/button'
|
||||
import { Note } from '../lib/types'
|
||||
```
|
||||
|
||||
**Import from Radix UI:**
|
||||
```typescript
|
||||
// ✅ GOOD - use @/components/ui/* wrapper
|
||||
import { Dialog } from '@/components/ui/dialog'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
// ❌ BAD - direct Radix imports
|
||||
import { Dialog } from '@radix-ui/react-dialog'
|
||||
import { Button } from '@radix-ui/react-slot'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### AI Service Pattern
|
||||
|
||||
**All AI services follow this structure:**
|
||||
|
||||
```typescript
|
||||
// keep-notes/lib/ai/services/title-suggestion.service.ts
|
||||
import { getAIProvider } from '@/lib/ai/factory'
|
||||
|
||||
export class TitleSuggestionService {
|
||||
private provider = getAIProvider()
|
||||
|
||||
async generateSuggestions(content: string): Promise<string[]> {
|
||||
try {
|
||||
const response = await this.provider.generateText({
|
||||
prompt: `Generate 3 titles for: ${content}`,
|
||||
maxTokens: 100
|
||||
})
|
||||
|
||||
return response.titles
|
||||
} catch (error) {
|
||||
console.error('TitleSuggestionService error:', error)
|
||||
throw new Error('Failed to generate suggestions')
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**CRITICAL RULES:**
|
||||
- ✅ Use `getAIProvider()` factory (not direct OpenAI/Ollama imports)
|
||||
- ✅ Services are **stateless classes**
|
||||
- ✅ Constructor injection of dependencies
|
||||
- ✅ Methods return `Promise<T>` with error handling
|
||||
- ✅ No direct database access (via Prisma)
|
||||
|
||||
---
|
||||
|
||||
### Testing Rules
|
||||
|
||||
**Playwright E2E Tests:**
|
||||
```typescript
|
||||
// tests/e2e/ai-features.spec.ts
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
test('AI title suggestions appear', async ({ page }) => {
|
||||
await page.goto('/')
|
||||
await page.fill('[data-testid="note-content"]', 'Test content')
|
||||
// Wait for AI suggestions
|
||||
await expect(page.locator('[data-testid="ai-suggestions"]')).toBeVisible()
|
||||
})
|
||||
```
|
||||
|
||||
**CRITICAL RULES:**
|
||||
- ✅ Use `data-testid` attributes for test selectors
|
||||
- ✅ Test critical user flows (not edge cases)
|
||||
- ✅ Use `await expect(...).toBeVisible()` for assertions
|
||||
- ✅ Tests in `tests/e2e/` directory
|
||||
|
||||
---
|
||||
|
||||
### Brownfield Integration Rules
|
||||
|
||||
**ZERO BREAKING CHANGES:**
|
||||
- ✅ **ALL new features must extend, not replace existing functionality**
|
||||
- ✅ Existing components, API routes, and database tables MUST continue working
|
||||
- ✅ New database fields: **optional** (nullable) for backward compatibility
|
||||
- ✅ New features: **additive** only (don't remove existing features)
|
||||
|
||||
**Example:**
|
||||
```prisma
|
||||
// ✅ GOOD - optional new field
|
||||
model Note {
|
||||
// ... existing fields
|
||||
language String? // NEW: optional
|
||||
aiConfidence Int? // NEW: optional
|
||||
}
|
||||
|
||||
// ❌ BAD - breaking change
|
||||
model Note {
|
||||
language String @default("en") // BREAKS: non-optional default
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 1 Specific Rules
|
||||
|
||||
**AI Features to Implement:**
|
||||
1. **Title Suggestions** - 3 suggestions after 50+ words
|
||||
2. **Semantic Search** - Hybrid keyword + vector search with RRF
|
||||
3. **Paragraph Reformulation** - Clarify, Shorten, Improve Style options
|
||||
4. **Memory Echo** - Daily proactive note connections (background job)
|
||||
5. **AI Settings** - Granular ON/OFF controls per feature
|
||||
6. **Language Detection** - TinyLD hybrid (< 50 words: library, ≥ 50 words: AI)
|
||||
|
||||
**Performance Targets:**
|
||||
- ✅ Title suggestions: < 2s after detection
|
||||
- ✅ Semantic search: < 300ms for 1000 notes
|
||||
- ✅ Memory Echo: < 100ms UI freeze (background processing)
|
||||
- ✅ Language detection: ~8ms (TinyLD) or ~200-500ms (AI)
|
||||
|
||||
**Language Support:**
|
||||
- ✅ System prompts: **English** (stability)
|
||||
- ✅ User data: **Local language** (FR, EN, ES, DE, FA/Persian + 57 others)
|
||||
- ✅ TinyLD supports 62 languages including Persian (verified)
|
||||
|
||||
---
|
||||
|
||||
### Security Rules
|
||||
|
||||
**API Keys:**
|
||||
- ✅ **NEVER** expose API keys to client (server-side only)
|
||||
- ✅ Store in environment variables (`OPENAI_API_KEY`, `OLLAMA_ENDPOINT`)
|
||||
- ✅ Use SystemConfig table for provider selection
|
||||
|
||||
**Authentication:**
|
||||
- ✅ **ALL** server actions check `auth()` session first
|
||||
- ✅ **ALL** API routes require valid NextAuth session
|
||||
- ✅ Public routes: `/api/auth/*`, login/register pages only
|
||||
|
||||
**Privacy:**
|
||||
- ✅ Ollama path = 100% local (no external API calls)
|
||||
- ✅ OpenAI path = cloud (verify in DevTools Network tab)
|
||||
- ✅ User data never logged or exposed
|
||||
|
||||
---
|
||||
|
||||
### File Organization
|
||||
|
||||
**AI Services:**
|
||||
```
|
||||
lib/ai/services/
|
||||
├── title-suggestion.service.ts
|
||||
├── semantic-search.service.ts
|
||||
├── paragraph-refactor.service.ts
|
||||
├── memory-echo.service.ts
|
||||
├── language-detection.service.ts
|
||||
└── embedding.service.ts
|
||||
```
|
||||
|
||||
**AI Components:**
|
||||
```
|
||||
components/ai/
|
||||
├── ai-suggestion.tsx
|
||||
├── ai-settings-panel.tsx
|
||||
├── memory-echo-notification.tsx
|
||||
├── confidence-badge.tsx
|
||||
├── feedback-buttons.tsx
|
||||
└── paragraph-refactor.tsx
|
||||
```
|
||||
|
||||
**API Routes:**
|
||||
```
|
||||
app/api/ai/
|
||||
├── titles/route.ts
|
||||
├── search/route.ts
|
||||
├── refactor/route.ts
|
||||
├── echo/route.ts
|
||||
├── feedback/route.ts
|
||||
└── language/route.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Development Workflow
|
||||
|
||||
**Before implementing ANY feature:**
|
||||
1. ✅ Read `_bmad-output/planning-artifacts/architecture.md`
|
||||
2. ✅ Check `project-context.md` for specific rules
|
||||
3. ✅ Follow naming patterns (camelCase, PascalCase, kebab-case)
|
||||
4. ✅ Use response format `{success, data, error}`
|
||||
5. ✅ Add `'use server'` or `'use client'` directives
|
||||
6. ✅ Import from `@/` alias only
|
||||
|
||||
**Quality Checklist:**
|
||||
- [ ] TypeScript strict mode compliance
|
||||
- [ ] Zod validation for API routes
|
||||
- [ ] auth() check in server actions
|
||||
- [ ] revalidatePath('/') after mutations
|
||||
- [ ] Error handling with console.error()
|
||||
- [ ] Response format {success, data/error}
|
||||
- [ ] Import from @/ alias
|
||||
- [ ] Component directives ('use client'/'use server')
|
||||
- [ ] Zero breaking changes
|
||||
- [ ] Performance targets met
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Card
|
||||
|
||||
**For AI Agents implementing features:**
|
||||
|
||||
| Category | Rule | Example |
|
||||
|----------|------|---------|
|
||||
| TypeScript | Strict mode, no `any` | `interface Props { note: Note }` |
|
||||
| Components | 'use client' at top | `export function Comp() { ... }` |
|
||||
| Server Actions | 'use server' + auth() + revalidate | `const session = await auth()` |
|
||||
| API Routes | Zod + {success, data/error} | `return NextResponse.json({ success: true, data })` |
|
||||
| Database | Prisma only, no raw SQL | `await prisma.note.findMany()` |
|
||||
| Naming | camelCase vars, PascalCase types | `const userId: string` |
|
||||
| Imports | @/ alias only | `import { Note } from '@/lib/types'` |
|
||||
| Error Handling | console.error + human message | `throw new Error('Failed to...')` |
|
||||
| AI Services | getAIProvider() factory | `const provider = getAIProvider()` |
|
||||
| Performance | Target < 2s for AI features | `await withTimeout(promise, 2000)` |
|
||||
|
||||
---
|
||||
|
||||
*Generated: 2026-01-10*
|
||||
*Project: Keep (Memento Phase 1 MVP AI)*
|
||||
*Architecture: Based on architecture.md (2784 lines)*
|
||||
*Status: Ready for AI Agent Implementation*
|
||||
|
||||
---
|
||||
|
||||
## Usage Guidelines
|
||||
|
||||
**For AI Agents:**
|
||||
|
||||
- ✅ Read this file **before** implementing any code
|
||||
- ✅ Follow **ALL** rules exactly as documented
|
||||
- ✅ When in doubt, prefer the more restrictive option
|
||||
- ✅ Check `_bmad-output/planning-artifacts/architecture.md` for full context
|
||||
- ✅ Use response format `{success, data, error}` for API routes
|
||||
- ✅ Add `'use server'` or `'use client'` directives at top of files
|
||||
- ✅ Import from `@/` alias only (not relative paths)
|
||||
- ✅ Validate requests with Zod schemas
|
||||
- ✅ Check `auth()` session in server actions
|
||||
- ✅ Call `revalidatePath('/')` after mutations
|
||||
- ✅ Log errors with `console.error()`
|
||||
|
||||
**For Humans:**
|
||||
|
||||
- Keep this file **lean and focused** on agent needs
|
||||
- Update when **technology stack changes**
|
||||
- Review **quarterly** for outdated rules
|
||||
- Remove rules that become **obvious over time**
|
||||
- Add new patterns when they emerge in development
|
||||
|
||||
**Maintenance:**
|
||||
|
||||
1. **Technology Changes:** Update when adding/removing dependencies
|
||||
2. **Pattern Evolution:** Add new patterns as they emerge
|
||||
3. **Bug Prevention:** Add rules when agents make common mistakes
|
||||
4. **Optimization:** Remove redundant or obvious rules periodically
|
||||
5. **Review Cycle:** Check quarterly for outdated information
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2026-01-10
|
||||
|
||||
**Optimization Status:** ✅ Optimized for LLM context (50 critical rules, 490 lines)
|
||||
|
||||
**Readiness:** ✅ Ready for AI Agent Implementation
|
||||
|
||||
---
|
||||
|
||||
*Workflow completed: 2026-01-10*
|
||||
*Generator: Winston (Architect Agent) with Generate Project Context workflow*
|
||||
*Based on: architecture.md (2784 lines) + existing codebase analysis*
|
||||
@@ -1,15 +1,16 @@
|
||||
---
|
||||
stepsCompleted: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
|
||||
stepsCompleted: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
|
||||
inputDocuments:
|
||||
- _bmad-output/planning-artifacts/prd-phase1-mvp-ai.md
|
||||
- docs/component-inventory.md
|
||||
- docs/project-overview.md
|
||||
workflowType: 'ux-design'
|
||||
lastStep: 11
|
||||
lastStep: 14
|
||||
documentTitle: 'UX Design Specification - Phase 1 MVP AI'
|
||||
focusArea: 'AI-Powered Note Taking Features'
|
||||
status: 'in-progress'
|
||||
status: 'complete'
|
||||
createdAt: '2026-01-10'
|
||||
completedAt: '2026-01-10'
|
||||
---
|
||||
|
||||
# UX Design Specification - Phase 1 MVP AI
|
||||
@@ -2632,3 +2633,945 @@ Build 7 custom components on top of Radix primitives:
|
||||
- **MemoryEchoCard:** Defining "Aha!" experience but most complex (background processing, embeddings, feedback learning)
|
||||
|
||||
---
|
||||
|
||||
## UX Consistency Patterns
|
||||
|
||||
### Button Hierarchy
|
||||
|
||||
**When to Use:**
|
||||
- **Primary Buttons:** Main actions (Save note, Apply suggestion, Create Cahier)
|
||||
- **Secondary Buttons:** Alternative actions (Cancel, Keep original)
|
||||
- **Tertiary Buttons:** Low-emphasis actions (Dismiss, Skip, Later)
|
||||
- **AI Buttons:** AI-specific actions (✨ Reformulate, View Connection)
|
||||
|
||||
**Visual Design:**
|
||||
|
||||
| Button Type | Tailwind Classes | Usage | Example |
|
||||
|-------------|------------------|-------|---------|
|
||||
| Primary | `bg-purple-600 hover:bg-purple-700 text-white font-medium py-2 px-4 rounded-lg` | Main action, high emphasis | "Save Note", "Apply Suggestion" |
|
||||
| Secondary | `bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium py-2 px-4 rounded-lg` | Alternative action | "Cancel", "Keep Original" |
|
||||
| Tertiary | `text-gray-500 hover:text-gray-700 font-medium py-2 px-2` | Low emphasis, text-only | "Dismiss", "Skip" |
|
||||
| AI Action | `bg-purple-50 hover:bg-purple-100 text-purple-600 font-medium py-2 px-4 rounded-lg border border-purple-200` | AI-specific action | "✨ Reformulate", "View Connection" |
|
||||
| Success | `bg-green-600 hover:bg-green-700 text-white font-medium py-2 px-4 rounded-lg` | Positive confirmation | "Link Notes", "Accept" |
|
||||
| Destructive | `bg-red-600 hover:bg-red-700 text-white font-medium py-2 px-4 rounded-lg` | Destructive action | "Delete Note", "Remove" |
|
||||
|
||||
**Behavior:**
|
||||
- **Focus:** 2px purple outline (`focus-visible:ring-2 ring-purple-600`)
|
||||
- **Active:** Slightly darker shade (`active:scale-95`)
|
||||
- **Disabled:** `opacity-50 cursor-not-allowed` with `aria-disabled="true"`
|
||||
- **Loading:** Show spinner, disable button, preserve width
|
||||
|
||||
**Accessibility:**
|
||||
- Minimum touch target: 44×44px (WCAG 2.5.5)
|
||||
- Clear visual labels (no icon-only buttons without labels)
|
||||
- Keyboard: Enter/Space to activate
|
||||
- Focus indicators always visible
|
||||
|
||||
**Mobile Considerations:**
|
||||
- Full-width buttons on mobile for primary actions
|
||||
- Minimum 44px height for touch targets
|
||||
- Adequate spacing between buttons (8px minimum)
|
||||
|
||||
---
|
||||
|
||||
### Feedback Patterns
|
||||
|
||||
**When to Use:**
|
||||
- **Success Feedback:** Actions completed successfully (Note saved, Suggestion applied)
|
||||
- **Error Feedback:** Actions failed (AI unavailable, Connection error)
|
||||
- **Warning Feedback:** Caution needed (Last Cahier, AI limit reached)
|
||||
- **Info Feedback:** Neutral information (AI processing, Feature discovery)
|
||||
|
||||
**Visual Design:**
|
||||
|
||||
**Success Feedback:**
|
||||
```tsx
|
||||
// Toast notification
|
||||
<div className="bg-green-50 border-l-4 border-green-600 text-green-800 p-4 rounded-lg shadow-md">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-green-600">✅</span>
|
||||
<p className="font-medium">Note saved successfully</p>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Error Feedback:**
|
||||
```tsx
|
||||
// Toast notification
|
||||
<div className="bg-red-50 border-l-4 border-red-600 text-red-800 p-4 rounded-lg shadow-md">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-red-600">❌</span>
|
||||
<p className="font-medium">AI service unavailable. Please try again.</p>
|
||||
</div>
|
||||
<button className="mt-2 text-sm underline">Retry</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Warning Feedback:**
|
||||
```tsx
|
||||
// Modal or banner
|
||||
<div className="bg-amber-50 border-l-4 border-amber-600 text-amber-800 p-4 rounded-lg">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-amber-600">⚠️</span>
|
||||
<p className="font-medium">You've reached your daily AI limit</p>
|
||||
</div>
|
||||
<p className="text-sm mt-1">Upgrade to Pro for unlimited AI features.</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Info Feedback (AI Processing):**
|
||||
```tsx
|
||||
// Inline indicator
|
||||
<div className="flex items-center gap-2 text-gray-600">
|
||||
<div className="animate-spin w-4 h-4 border-2 border-amber-500 border-t-transparent rounded-full" />
|
||||
<span className="text-sm">AI thinking...</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Behavior:**
|
||||
- **Auto-dismiss:** Success/info toasts auto-dismiss after 5s
|
||||
- **Persistent:** Error/warning toasts require manual dismissal
|
||||
- **Position:** Toasts bottom-right (desktop), bottom-center (mobile)
|
||||
- **Stacking:** Multiple toasts stack vertically with 4px gap
|
||||
|
||||
**Accessibility:**
|
||||
- `role="alert"` for errors/warnings
|
||||
- `role="status"` for success/info
|
||||
- `aria-live="polite"` for non-critical, `aria-live="assertive"` for critical
|
||||
- Screen reader announcements with clear messages
|
||||
|
||||
---
|
||||
|
||||
### Form & Input Patterns
|
||||
|
||||
**When to Use:**
|
||||
- Note editor (main content input)
|
||||
- Title input (with AI suggestions)
|
||||
- Cahier name input
|
||||
- Search bar (unified semantic search)
|
||||
- Settings forms (AI configuration)
|
||||
|
||||
**Visual Design:**
|
||||
|
||||
**Text Input (Title, Cahier Name):**
|
||||
```tsx
|
||||
<input
|
||||
type="text"
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-600 focus:border-transparent outline-none transition-all"
|
||||
placeholder="Note title..."
|
||||
/>
|
||||
```
|
||||
|
||||
**Textarea (Note Content):**
|
||||
```tsx
|
||||
<textarea
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-600 focus:border-transparent outline-none transition-all min-h-[200px] resize-y"
|
||||
placeholder="Start typing..."
|
||||
/>
|
||||
```
|
||||
|
||||
**Search Input (Unified):**
|
||||
```tsx
|
||||
<div className="relative">
|
||||
<input
|
||||
type="search"
|
||||
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-600 focus:border-transparent outline-none transition-all"
|
||||
placeholder="Search notes..."
|
||||
/>
|
||||
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">🔍</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Behavior:**
|
||||
- **Focus:** Purple ring (`focus:ring-2 focus:ring-purple-600`)
|
||||
- **Error state:** Red border + error message below
|
||||
- **Success state:** Green border (briefly, then normal)
|
||||
- **AI Suggestions:** Show dropdown below input (TitleSuggestionsDropdown)
|
||||
- **Debounce:** Search input debounces 300ms before triggering
|
||||
|
||||
**Validation Patterns:**
|
||||
|
||||
| Field | Validation | Error Message |
|
||||
|-------|-----------|---------------|
|
||||
| Cahier Name | Required, min 2 chars | "Cahier name must be at least 2 characters" |
|
||||
| Title | Optional (AI suggests if empty) | - |
|
||||
| Search | Min 2 chars to trigger | "Enter at least 2 characters to search" |
|
||||
|
||||
**Accessibility:**
|
||||
- `aria-label` or `aria-labelledby` for all inputs
|
||||
- `aria-describedby` for help text/error messages
|
||||
- `aria-invalid="true"` for validation errors
|
||||
- Keyboard navigation: Tab to focus, Enter to submit
|
||||
|
||||
---
|
||||
|
||||
### Navigation Patterns
|
||||
|
||||
**When to Use:**
|
||||
- Cahier switching (sidebar navigation)
|
||||
- Breadcrumb navigation (header)
|
||||
- Tab navigation (settings sections)
|
||||
- Pagination (masonry grid infinite scroll)
|
||||
|
||||
**Visual Design:**
|
||||
|
||||
**Cahier Sidebar Navigation:**
|
||||
```tsx
|
||||
<nav className="w-64 bg-white border-r border-gray-200">
|
||||
<ul className="py-4">
|
||||
<li>
|
||||
<button className="w-full flex items-center gap-3 px-4 py-2 text-left bg-purple-50 text-purple-600 border-l-4 border-purple-600 font-medium">
|
||||
📓 Inbox
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button className="w-full flex items-center gap-3 px-4 py-2 text-left text-gray-700 hover:bg-gray-50">
|
||||
📓 Work
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button className="w-full flex items-center gap-3 px-4 py-2 text-left text-gray-700 hover:bg-gray-50">
|
||||
📓 Personal
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
```
|
||||
|
||||
**Active State:**
|
||||
- Background: `bg-purple-50`
|
||||
- Text: `text-purple-600`
|
||||
- Left border: `border-l-4 border-purple-600`
|
||||
- Font weight: `font-medium`
|
||||
|
||||
**Hover State (Inactive):**
|
||||
- Background: `hover:bg-gray-50`
|
||||
- Text: `text-gray-700`
|
||||
- No border
|
||||
|
||||
**Breadcrumb Navigation (Header):**
|
||||
```tsx
|
||||
<nav className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<span className="hover:text-purple-600 cursor-pointer">Memento</span>
|
||||
<span>/</span>
|
||||
<span className="hover:text-purple-600 cursor-pointer">Work</span>
|
||||
<span>/</span>
|
||||
<span className="text-gray-900 font-medium">React Performance Tips</span>
|
||||
</nav>
|
||||
```
|
||||
|
||||
**Behavior:**
|
||||
- **Instant switch:** Cahier switching happens < 100ms (no page reload)
|
||||
- **Active indicator:** Current Cahier highlighted with purple left border
|
||||
- **Keyboard navigation:** ↑↓ to navigate Cahiers, Enter to select
|
||||
- **Mobile:** Hamburger menu (sidebar collapses to off-canvas)
|
||||
|
||||
**Accessibility:**
|
||||
- `role="navigation"` with `aria-label="Cahiers navigation"`
|
||||
- `aria-current="page"` for active Cahier
|
||||
- Keyboard focus visible (2px purple outline)
|
||||
- Screen reader announces Cahier names
|
||||
|
||||
---
|
||||
|
||||
### Modal & Overlay Patterns
|
||||
|
||||
**When to Use:**
|
||||
- Reformulation modal (compare original vs AI suggestion)
|
||||
- Memory Echo details modal (view 2-note connection)
|
||||
- Settings modals (AI configuration)
|
||||
- Confirmation dialogs (delete Cahier)
|
||||
|
||||
**Visual Design:**
|
||||
|
||||
**Modal Container:**
|
||||
```tsx
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||
{/* Backdrop */}
|
||||
<div className="absolute inset-0 bg-black/50 backdrop-blur-sm" />
|
||||
|
||||
{/* Modal */}
|
||||
<div className="relative bg-white rounded-xl shadow-2xl max-w-4xl w-full mx-4 border-2 border-purple-600">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-6 border-b border-gray-200">
|
||||
<h2 className="text-2xl font-semibold text-gray-900">✨ Reformulate this Paragraph</h2>
|
||||
<button className="text-gray-400 hover:text-gray-600">✕</button>
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className="p-6">
|
||||
{/* Modal content */}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex justify-end gap-3 p-6 border-t border-gray-200">
|
||||
<button>Keep Original</button>
|
||||
<button>Apply Suggestion</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Behavior:**
|
||||
- **Opening:** Fade-in backdrop + scale modal (0.2s ease-out)
|
||||
- **Closing:** Fade-out backdrop + scale down (0.1s)
|
||||
- **Focus trap:** Tab stays within modal when open
|
||||
- **ESC to close:** Pressing ESC closes modal
|
||||
- **Click outside:** Clicking backdrop closes modal (optional for confirmation dialogs)
|
||||
|
||||
**Accessibility:**
|
||||
- `role="dialog"` with `aria-modal="true"`
|
||||
- `aria-labelledby` points to modal title
|
||||
- Focus trap (first focusable element receives focus on open)
|
||||
- Returns focus to trigger element on close
|
||||
|
||||
**Mobile Considerations:**
|
||||
- Full-width modals on mobile (mx-0, max-h-screen)
|
||||
- Bottom sheet style for some modals (slide-up from bottom)
|
||||
- Touch-friendly button sizes (min 44px)
|
||||
|
||||
---
|
||||
|
||||
### Search Patterns
|
||||
|
||||
**When to Use:**
|
||||
- Unified semantic search (header search bar)
|
||||
- Cahier-specific search (filtered by current Cahier)
|
||||
- AI-powered semantic matching
|
||||
|
||||
**Visual Design:**
|
||||
|
||||
**Unified Search Bar:**
|
||||
```tsx
|
||||
<div className="relative">
|
||||
<input
|
||||
type="search"
|
||||
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-600 focus:border-transparent outline-none transition-all"
|
||||
placeholder="Search notes..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">🔍</span>
|
||||
|
||||
{/* Loading indicator */}
|
||||
{isSearching && (
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2">
|
||||
<div className="animate-spin w-4 h-4 border-2 border-purple-600 border-t-transparent rounded-full" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
```
|
||||
|
||||
**Search Results with Semantic Badges:**
|
||||
```tsx
|
||||
<div className="space-y-4">
|
||||
{/* Keyword match result */}
|
||||
<div className="p-4 bg-white border border-gray-200 rounded-lg hover:shadow-md transition-shadow">
|
||||
<h3 className="font-semibold text-gray-900">React State Management</h3>
|
||||
</div>
|
||||
|
||||
{/* Semantic match result */}
|
||||
<div className="p-4 bg-white border border-gray-200 rounded-lg hover:shadow-md transition-shadow">
|
||||
<h3 className="font-semibold text-gray-900">Next.js Optimization</h3>
|
||||
<div className="mt-2">
|
||||
<span className="inline-flex items-center gap-1 px-2 py-1 bg-blue-50 text-blue-600 text-xs font-medium rounded">
|
||||
🎯 Semantic Match (Score: 0.82)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Behavior:**
|
||||
- **Debounce:** 300ms debounce before triggering search
|
||||
- **Hybrid search:** Simultaneously runs keyword + semantic search
|
||||
- **Badge indication:** Semantic matches show blue badge with score
|
||||
- **Real-time:** Results update as user types (after debounce)
|
||||
- **No visible toggle:** Users don't choose keyword vs semantic - "it just works"
|
||||
|
||||
**Accessibility:**
|
||||
- `aria-label="Search notes"`
|
||||
- Live region for results: `aria-live="polite"` on results container
|
||||
- Clear announcements: "5 results found, 3 semantic matches"
|
||||
- Keyboard: Enter to navigate to first result
|
||||
|
||||
---
|
||||
|
||||
### Loading & Empty States
|
||||
|
||||
**When to Use:**
|
||||
- AI processing (generating embeddings, reformulating)
|
||||
- Empty Cahiers (no notes yet)
|
||||
- No search results
|
||||
- Loading initial data
|
||||
|
||||
**Visual Design:**
|
||||
|
||||
**AI Processing State:**
|
||||
```tsx
|
||||
<div className="flex flex-col items-center justify-center py-12">
|
||||
<div className="animate-spin w-12 h-12 border-4 border-amber-500 border-t-transparent rounded-full mb-4" />
|
||||
<p className="text-gray-600 font-medium">AI thinking...</p>
|
||||
<p className="text-sm text-gray-500 mt-1">Generating embeddings for semantic search</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Empty Cahier State:**
|
||||
```tsx
|
||||
<div className="flex flex-col items-center justify-center py-16 text-center">
|
||||
<div className="text-6xl mb-4">📓</div>
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-2">No notes in this Cahier yet</h3>
|
||||
<p className="text-gray-600 mb-6">Capture your first idea to get started</p>
|
||||
<button className="bg-purple-600 hover:bg-purple-700 text-white font-medium py-2 px-6 rounded-lg">
|
||||
Create Note
|
||||
</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
**No Search Results:**
|
||||
```tsx
|
||||
<div className="flex flex-col items-center justify-center py-12 text-center">
|
||||
<div className="text-4xl mb-4">🔍</div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-2">No results found</h3>
|
||||
<p className="text-gray-600">Try different keywords or check your spelling</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Skeleton Loading (Masonry Grid):**
|
||||
```tsx
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{[1, 2, 3, 4, 5, 6].map((i) => (
|
||||
<div key={i} className="h-48 bg-gray-200 rounded-lg animate-pulse" />
|
||||
))}
|
||||
</div>
|
||||
```
|
||||
|
||||
**Behavior:**
|
||||
- **AI Processing:** Show spinner + descriptive text (not just "Loading...")
|
||||
- **Empty States:** Provide clear CTA (Create Note, Browse Other Cahiers)
|
||||
- **Skeleton:** Pulse animation while loading actual content
|
||||
- **Progressive enhancement:** Show content as it loads (not all-or-nothing)
|
||||
|
||||
**Accessibility:**
|
||||
- `role="status"` with `aria-live="polite"` for loading states
|
||||
- `aria-busy="true"` when content is loading
|
||||
- Screen readers announce what's happening and why
|
||||
- Empty states have clear headings and explanations
|
||||
|
||||
---
|
||||
|
||||
### Mobile-Specific Patterns
|
||||
|
||||
**When to Use:**
|
||||
- Responsive navigation (collapsible sidebar)
|
||||
- Touch interactions (swipe, long-press)
|
||||
- Mobile-optimized modals (bottom sheets)
|
||||
- Mobile search (expandable search bar)
|
||||
|
||||
**Visual Design:**
|
||||
|
||||
**Collapsible Sidebar (Mobile):**
|
||||
```tsx
|
||||
{/* Desktop: Always visible sidebar */}
|
||||
{/* Mobile: Hamburger menu */}
|
||||
<div className="md:hidden fixed top-4 left-4 z-50">
|
||||
<button className="p-2 bg-white rounded-lg shadow-md">
|
||||
☰
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Off-canvas sidebar on mobile */}
|
||||
<div className={`fixed inset-y-0 left-0 z-50 w-64 bg-white transform transition-transform ${isOpen ? 'translate-x-0' : '-translate-x-full'} md:translate-x-0`}>
|
||||
{/* Sidebar content */}
|
||||
</div>
|
||||
```
|
||||
|
||||
**Bottom Sheet Modal (Mobile):**
|
||||
```tsx
|
||||
<div className="md:hidden fixed inset-x-0 bottom-0 bg-white rounded-t-2xl shadow-2xl transform transition-transform">
|
||||
<div className="p-6">
|
||||
{/* Modal content */}
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Expandable Search (Mobile):**
|
||||
```tsx
|
||||
<div className="relative">
|
||||
<button className="p-2">
|
||||
🔍
|
||||
</button>
|
||||
|
||||
{/* Expands to full-width input on focus/click */}
|
||||
<input
|
||||
type="search"
|
||||
className="fixed inset-x-4 top-16 z-40 px-4 py-3 bg-white border border-gray-300 rounded-lg shadow-lg"
|
||||
placeholder="Search notes..."
|
||||
/>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Touch Interactions:**
|
||||
- **Minimum touch target:** 44×44px (WCAG 2.5.5)
|
||||
- **Swipe to dismiss:** Toast notifications, bottom sheets
|
||||
- **Long-press:** Context menus (show actions on note card)
|
||||
- **Pull-to-refresh:** Refresh masonry grid content
|
||||
|
||||
---
|
||||
|
||||
### Pattern Integration with Radix UI
|
||||
|
||||
**Consistency with Design System:**
|
||||
|
||||
| Pattern Category | Radix Primitive | Custom Styling | Notes |
|
||||
|------------------|-----------------|----------------|-------|
|
||||
| Modals | Dialog | Purple border, AI-specific padding | BaseModal extends Dialog |
|
||||
| Dropdowns | Dropdown Menu | Purple text on hover | Used for TitleSuggestions |
|
||||
| Toasts | Toast | Purple border for AI | AIToast extends Toast |
|
||||
| Navigation | Navigation Menu | Active state = purple left border | Cahiers sidebar |
|
||||
| Forms | - | Custom inputs with purple focus ring | No Radix form primitive |
|
||||
|
||||
**Design Token Integration:**
|
||||
|
||||
```css
|
||||
/* All patterns use consistent design tokens */
|
||||
--primary: #8B5CF6; /* Primary actions, focus rings */
|
||||
--ai-accent: #3B82F6; /* Semantic search badges */
|
||||
--ai-connection: #8B5CF6; /* Memory Echo borders */
|
||||
--success: #10B981; /* Success feedback */
|
||||
--warning: #F59E0B; /* Processing states */
|
||||
--error: #EF4444; /* Error feedback */
|
||||
```
|
||||
|
||||
**Custom Pattern Rules:**
|
||||
|
||||
1. **AI-specific patterns always use purple/blue accent colors**
|
||||
2. **All buttons have 2px purple focus ring** (keyboard navigation)
|
||||
3. **All modals have purple border** (2px solid #8B5CF6)
|
||||
4. **All toasts auto-dismiss after 5s** except errors (manual dismiss)
|
||||
5. **All inputs use purple focus ring** (not default blue)
|
||||
6. **All empty states provide clear CTA** (not just "No results")
|
||||
7. **All loading states show descriptive text** (not just spinners)
|
||||
|
||||
---
|
||||
|
||||
## Responsive Design & Accessibility
|
||||
|
||||
### Responsive Strategy
|
||||
|
||||
**Desktop Strategy (1024px+):**
|
||||
|
||||
- **Layout optimisé:** Sidebar Cahiers (256px) + Masonry grid (3-4 colonnes)
|
||||
- **Espace maximal:** Profiter de l'écran pour afficher plus de notes
|
||||
- **Features desktop:**
|
||||
- Cahier sidebar toujours visible (pas de hamburger)
|
||||
- Masonry grid 4 colonnes (plus de notes visibles)
|
||||
- Memory Echo toast en bas à droite
|
||||
- Hover interactions (✨ Reformulate apparaît au survol)
|
||||
- Drag & drop pour réorganiser les Cahiers (bonus, pas MVP)
|
||||
|
||||
**Tablet Strategy (768px - 1023px):**
|
||||
|
||||
- **Layout adapté:** Sidebar Cahiers réduit (200px) ou collapsible
|
||||
- **Masonry 2 colonnes:** Grid passe de 4 → 2 colonnes
|
||||
- **Touch optimization:**
|
||||
- Cahier sidebar devient collapsible (hamburger menu)
|
||||
- Boutons plus larges (min 44px)
|
||||
- Pas de hover-based interactions (✨ Reformulate bouton permanent)
|
||||
- **Information density:** Moyenne - équilibre entre lisibilité et contenu
|
||||
|
||||
**Mobile Strategy (< 768px):**
|
||||
|
||||
- **Layout simplifié:** Single-column stack
|
||||
- **Navigation:** Hamburger menu (sidebar off-canvas)
|
||||
- **Masonry 1 colonne:** Single column, full-width cards
|
||||
- **Features mobiles:**
|
||||
- Cahier sidebar: Off-canvas (glisse de gauche)
|
||||
- Search: Expandable (icône → input full-width)
|
||||
- Memory Echo: Full-width toast en bas
|
||||
- AI Badge: Compact (✨ icône seule, tap pour menu)
|
||||
- Bottom sheet modals (au lieu de dialogues centrés)
|
||||
- **Critical info only:** Masquer les éléments non-essentiels
|
||||
|
||||
---
|
||||
|
||||
### Breakpoint Strategy
|
||||
|
||||
**Using Tailwind CSS Standard Breakpoints:**
|
||||
|
||||
```javascript
|
||||
// tailwind.config.js
|
||||
module.exports = {
|
||||
theme: {
|
||||
screens: {
|
||||
'sm': '640px', // Mobile large (landscape)
|
||||
'md': '768px', // Tablet portrait
|
||||
'lg': '1024px', // Desktop, laptop
|
||||
'xl': '1280px', // Desktop large
|
||||
'2xl': '1536px', // Extra large desktop
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Layout Adaptations by Breakpoint:**
|
||||
|
||||
| Element | Mobile (< 768px) | Tablet (768-1024px) | Desktop (> 1024px) |
|
||||
|---------|------------------|---------------------|---------------------|
|
||||
| Cahier Sidebar | Off-canvas (hamburger) | 200px or collapsible | 256px always visible |
|
||||
| Masonry Grid | 1 colonne (100%) | 2 colonnes (48% each) | 3-4 colonnes (32-24% each) |
|
||||
| Header | Compact (64px) | Standard (64px) | Standard (64px) |
|
||||
| Search Bar | Expandable on tap | Full-width (standard) | Full-width (standard) |
|
||||
| Memory Echo | Bottom-center toast | Bottom-right toast | Bottom-right toast |
|
||||
| Modals | Bottom sheet | Standard dialog | Standard dialog |
|
||||
|
||||
**Mobile-First Approach:**
|
||||
|
||||
- **Développement:** Commencer par le layout mobile, ajouter des médias queries pour écrans plus larges
|
||||
- **Avantages:** Performance native mobile, progressive enhancement
|
||||
- **Implementation:**
|
||||
```css
|
||||
/* Mobile default: 1 colonne */
|
||||
.masonry-grid { display: grid; grid-template-columns: 1fr; }
|
||||
|
||||
/* Tablet: 2 colonnes */
|
||||
@media (min-width: 768px) {
|
||||
.masonry-grid { grid-template-columns: repeat(2, 1fr); }
|
||||
}
|
||||
|
||||
/* Desktop: 3-4 colonnes */
|
||||
@media (min-width: 1024px) {
|
||||
.masonry-grid { grid-template-columns: repeat(3, 1fr); }
|
||||
}
|
||||
@media (min-width: 1280px) {
|
||||
.masonry-grid { grid-template-columns: repeat(4, 1fr); }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Accessibility Strategy
|
||||
|
||||
**WCAG Compliance Level: AA (Recommended)**
|
||||
|
||||
**Rationale:**
|
||||
- ✅ **Industry standard:** Niveau attendu pour les applications web modernes
|
||||
- ✅ **Legal compliance:** Conforme aux exigences légales (ADA, European Accessibility Act)
|
||||
- ✅ **User experience:** Balance optimal entre accessibilité et design
|
||||
- ✅ **Feasible:** Atteignable sans compromis majeurs sur le design
|
||||
|
||||
**Key Accessibility Requirements:**
|
||||
|
||||
**1. Color Contrast (WCAG 2.1 Level AA):**
|
||||
- Normal text (16px+): Minimum 4.5:1 contrast ratio
|
||||
- Large text (18px+): Minimum 3:1 contrast ratio
|
||||
- UI components (buttons, borders): Minimum 3:1 contrast ratio
|
||||
|
||||
**Implementation:**
|
||||
- Purple primary (#8B5CF6) on white: 4.8:1 ✅
|
||||
- Blue accent (#3B82F6) on white: 4.5:1 ✅
|
||||
- Green success (#10B981) on white: 3.9:1 ✅ (OK for large text)
|
||||
- Gray text (#6B7280) on white: 4.6:1 ✅
|
||||
|
||||
**2. Keyboard Navigation:**
|
||||
- **Full keyboard support:** Tab, Shift+Tab, Enter, Escape, Arrow keys
|
||||
- **Focus indicators:** 2px purple outline (focus-visible:ring-2 ring-purple-600)
|
||||
- **Skip links:** "Skip to main content" link au début de la page
|
||||
- **Focus order:** Logique et prévisible (header → sidebar → main content → footer)
|
||||
- **No keyboard traps:** ESC ferme tous les modals/toasts
|
||||
|
||||
**3. Screen Reader Support:**
|
||||
- **Semantic HTML:** heading hierarchy (h1 → h2 → h3), proper list structures
|
||||
- **ARIA labels:** Labels descriptifs pour tous les composants interactifs
|
||||
- **Live regions:** `aria-live="polite"` pour toasts et mises à jour dynamiques
|
||||
- **Screen reader testing:** NVDA (Windows), VoiceOver (macOS), TalkBack (Android)
|
||||
|
||||
**4. Touch Target Sizes (WCAG 2.5.5):**
|
||||
- Minimum 44×44px pour tous les éléments interactifs
|
||||
- Espacement minimum 8px entre les éléments tactiles
|
||||
- Tests sur appareils mobiles réels (iOS, Android)
|
||||
|
||||
**5. Focus Management:**
|
||||
- **Focus trap:** Dans les modals (Tab ne quitte pas le modal)
|
||||
- **Focus restoration:** Retour au trigger element après fermeture modal
|
||||
- **Visible focus:** Toujours visible (pas de outline: none sauf :focus-visible)
|
||||
|
||||
**6. Accessibility Features for AI Components:**
|
||||
- **AIToast:** `role="alert"` + `aria-live="polite"`
|
||||
- **TitleSuggestions:** `role="listbox"` + `aria-label="AI-generated title suggestions"`
|
||||
- **MemoryEchoCard:** `aria-label="AI notification: Note connection discovered"`
|
||||
- **ProcessingIndicator:** `role="status"` + `aria-busy="true"`
|
||||
|
||||
---
|
||||
|
||||
### Testing Strategy
|
||||
|
||||
**Responsive Testing:**
|
||||
|
||||
**Device Testing:**
|
||||
- **Real devices:**
|
||||
- iPhone 12/13/14 (375px width)
|
||||
- Samsung Galaxy S21 (360px width)
|
||||
- iPad (768px - 1024px)
|
||||
- Desktop (1920px width)
|
||||
- **Browser testing:**
|
||||
- Chrome (primary)
|
||||
- Safari (iOS, macOS)
|
||||
- Firefox (secondary)
|
||||
- Edge (Windows)
|
||||
|
||||
**Network Performance Testing:**
|
||||
- Test sur 3G (slow network)
|
||||
- Test sur WiFi (normal network)
|
||||
- Optimiser images (WebP, lazy loading)
|
||||
- Minifier CSS/JS pour performance mobile
|
||||
|
||||
**Accessibility Testing:**
|
||||
|
||||
**Automated Tools:**
|
||||
- **axe DevTools** (Chrome extension) - Scan automatique
|
||||
- **WAVE** (WebAIM) - Contrast checker, ARIA validation
|
||||
- **Lighthouse** (Chrome) - Accessibility score
|
||||
|
||||
**Manual Testing:**
|
||||
- **Keyboard navigation:** Navigation clavier complète
|
||||
- **Screen reader:** NVDA, VoiceOver, TalkBack
|
||||
- **Zoom:** Test 200% text zoom (pas de horizontal scroll)
|
||||
- **High contrast mode:** Windows High Contrast Mode
|
||||
|
||||
**User Testing:**
|
||||
- **Include users with disabilities:**
|
||||
- Screen reader users
|
||||
- Low vision users
|
||||
- Motor disability users (keyboard-only)
|
||||
- **Test with real assistive technologies**
|
||||
- **Gather feedback on AI features accessibility**
|
||||
|
||||
---
|
||||
|
||||
### Implementation Guidelines
|
||||
|
||||
**Responsive Development:**
|
||||
|
||||
**1. Use Relative Units:**
|
||||
```css
|
||||
/* ✅ GOOD - Relative units */
|
||||
font-size: 1rem; /* 16px base */
|
||||
padding: 1rem; /* 16px */
|
||||
margin: 0.5rem 0; /* 8px top/bottom, 0 sides */
|
||||
width: 100%; /* Full width */
|
||||
max-width: 1200px; /* Max constraint */
|
||||
|
||||
/* ❌ BAD - Fixed pixels */
|
||||
font-size: 16px; /* Not scalable */
|
||||
padding: 16px;
|
||||
width: 375px; /* Mobile fixed width */
|
||||
```
|
||||
|
||||
**2. Mobile-First Media Queries:**
|
||||
```css
|
||||
/* Mobile default */
|
||||
.masonry-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* Tablet+ */
|
||||
@media (min-width: 768px) {
|
||||
.masonry-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop+ */
|
||||
@media (min-width: 1024px) {
|
||||
.masonry-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**3. Touch Target Testing:**
|
||||
```tsx
|
||||
// ✅ GOOD - Minimum 44x44px
|
||||
<button className="min-h-[44px] min-w-[44px] p-4">
|
||||
Click me
|
||||
</button>
|
||||
|
||||
// ❌ BAD - Too small
|
||||
<button className="h-8 w-8 p-1">
|
||||
Click me
|
||||
</button>
|
||||
```
|
||||
|
||||
**4. Image Optimization:**
|
||||
```tsx
|
||||
// Responsive images
|
||||
<Image
|
||||
src="/note-thumbnail.webp"
|
||||
width={400}
|
||||
height={300}
|
||||
loading="lazy" // Lazy load below fold
|
||||
alt="Note thumbnail"
|
||||
/>
|
||||
|
||||
// Background images with fallback
|
||||
<div
|
||||
style={{
|
||||
backgroundImage: 'url(/image.webp), url(/image.jpg)'
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
**Accessibility Development:**
|
||||
|
||||
**1. Semantic HTML:**
|
||||
```tsx
|
||||
// ✅ GOOD - Semantic
|
||||
<header>
|
||||
<nav aria-label="Cahiers navigation">
|
||||
<ul>
|
||||
<li><a href="/inbox" aria-current="page">Inbox</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<article>
|
||||
<h1>Note Title</h1>
|
||||
<p>Note content...</p>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
// ❌ BAD - Non-semantic
|
||||
<div class="header">
|
||||
<div class="nav">
|
||||
<div class="nav-item" onclick="navigate('/inbox')">Inbox</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**2. ARIA Labels and Roles:**
|
||||
```tsx
|
||||
// AIToast component
|
||||
<div
|
||||
role="alert"
|
||||
aria-live="polite"
|
||||
aria-label="AI notification: Note connection discovered"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<span aria-hidden="true">💡</span>
|
||||
<h3>I noticed something...</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// TitleSuggestionsDropdown
|
||||
<ul role="listbox" aria-label="AI-generated title suggestions">
|
||||
<li role="option">✨ Title 1</li>
|
||||
<li role="option">✨ Title 2</li>
|
||||
<li role="option">✨ Title 3</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
**3. Keyboard Navigation:**
|
||||
```tsx
|
||||
// Focus trap in modal
|
||||
const Modal = () => {
|
||||
useEffect(() => {
|
||||
// Trap focus within modal
|
||||
const focusableElements = modalRef.current.querySelectorAll(
|
||||
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
||||
);
|
||||
const firstElement = focusableElements[0];
|
||||
const lastElement = focusableElements[focusableElements.length - 1];
|
||||
|
||||
const handleTab = (e) => {
|
||||
if (e.key === 'Tab') {
|
||||
if (e.shiftKey) {
|
||||
if (document.activeElement === firstElement) {
|
||||
e.preventDefault();
|
||||
lastElement.focus();
|
||||
}
|
||||
} else {
|
||||
if (document.activeElement === lastElement) {
|
||||
e.preventDefault();
|
||||
firstElement.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleTab);
|
||||
return () => document.removeEventListener('keydown', handleTab);
|
||||
}, []);
|
||||
|
||||
// Modal JSX...
|
||||
};
|
||||
```
|
||||
|
||||
**4. Focus Management:**
|
||||
```tsx
|
||||
// Skip link (top of page)
|
||||
<a
|
||||
href="#main-content"
|
||||
className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 bg-purple-600 text-white px-4 py-2 rounded-lg"
|
||||
>
|
||||
Skip to main content
|
||||
</a>
|
||||
|
||||
<main id="main-content" tabIndex={-1}>
|
||||
{/* Main content */}
|
||||
</main>
|
||||
```
|
||||
|
||||
**5. High Contrast Mode Support:**
|
||||
```css
|
||||
/* Respect high contrast mode preference */
|
||||
@media (prefers-contrast: high) {
|
||||
.ai-button {
|
||||
border: 2px solid currentColor;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.note-card {
|
||||
border: 2px solid currentColor;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Component Library Resources
|
||||
|
||||
**Alternative UI Component Libraries (Built on Radix UI + Tailwind):**
|
||||
|
||||
These libraries are compatible with Memento's design system choice (Radix UI + Tailwind CSS):
|
||||
|
||||
1. **Aceternity UI** - https://ui.aceternity.com/components
|
||||
- Modern components built with Radix UI + Tailwind
|
||||
- Animated components, dark mode support
|
||||
- Useful for: Advanced animations, bento grids, particles
|
||||
|
||||
2. **Origin UI** - https://www.originui-ng.com/
|
||||
- Next.js components with Framer Motion animations
|
||||
- shadcn/ui-based with enhanced styling
|
||||
- Useful for: Animated cards, transitions, hero sections
|
||||
|
||||
3. **Magic UI** - https://magicui.design/docs/components
|
||||
- Creative components with unique animations
|
||||
- Built with Radix UI + Tailwind + Framer Motion
|
||||
- Useful for: Special effects, interactive components
|
||||
|
||||
**Recommendation:**
|
||||
- **Base:** Stick with Radix UI primitives (current choice)
|
||||
- **Enhancement:** These libraries can provide inspiration or pre-built components for specific features
|
||||
- **Customization:** All components can be customized to match Memento's design tokens (purple/blue AI colors)
|
||||
|
||||
**Integration Strategy:**
|
||||
1. Start with Radix UI primitives (as planned)
|
||||
2. Reference these libraries for component patterns and animation ideas
|
||||
3. Customize any imported components to use Memento's design tokens
|
||||
4. Maintain consistency with established UX patterns from Step 12
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user