760 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			760 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # 🎯 Prompt Windsurf — ObsiViewer Nimbus UI (Desktop + Mobile)
 | ||
| 
 | ||
| ## ObsiViewer → UI/UX "Nimbus-like" (simple, dense, rapide)
 | ||
| 
 | ||
| **Rôle & mode :** Agis comme **Staff Frontend Engineer Angular 20 + UX designer**. Raisonnement détaillé autorisé. Tu as les pleins pouvoirs de refactor UI, d'ajout de composants, et de migration CSS vers Tailwind. Conserve la compatibilité de toutes features existantes.
 | ||
| 
 | ||
| **Contrainte majeure :** L'interface doit être **100% responsive** (Desktop + Mobile). Un **bouton toggle** dans la navbar permet de basculer entre l'ancienne interface et la nouvelle sans perte d'état.
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ## Contexte rapide
 | ||
| 
 | ||
| * Projet : **ObsiViewer** (Angular 20 + Tailwind, Node/Express backend).
 | ||
| * Objectif : Refondre l'interface selon un design **Nimbus Notes**-like.
 | ||
| * Cœurs d'usage : navigation par **dossiers**, **tags**, **recherche**, **lecture markdown** plein écran, **ToC** à droite, **tri et filtres** rapides.
 | ||
| * **Nouveauté** : Design adaptatif complet (Desktop/Mobile/Tablet) avec UI toggle persisté.
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ## 🎯 Objectif final (résumé)
 | ||
| 
 | ||
| ### Desktop (≥1024px)
 | ||
| Refondre l'interface ObsiViewer en **3 colonnes** :
 | ||
| 
 | ||
| 1. **Sidebar gauche** (Quick Links, Dossiers arborescents, Tags) — Redimensionnable.
 | ||
| 2. **Colonne centrale** Liste des pages (recherche, filtres dossiers/tags, tris, résultats virtualisés).
 | ||
| 3. **Vue de page** à droite (lecture markdown, barre d'actions, **panneau sommaire/ToC** docké à l'extrême droite).
 | ||
| 
 | ||
| Le tout **compact, performant, thème clair/sombre**, navigation au clavier, états persistés localement.
 | ||
| 
 | ||
| ### Mobile/Tablet (<1024px)
 | ||
| Une navigation **par onglets/drawer** intelligente :
 | ||
| 
 | ||
| - **Tab 1 : Sidebar** (dossiers, tags, recherche) — Panneau full-width ou drawer collapsible.
 | ||
| - **Tab 2 : Liste** (résultats de recherche) — Full-width scrollable.
 | ||
| - **Tab 3 : Page** (markdown) — Full-width avec ToC inline collapsible ou drawer.
 | ||
| 
 | ||
| **Gestures** : Swipe horizontal pour navigation onglets, pull-to-refresh, tap = open item.
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ## 📋 Architecture Feature Flag & Toggle
 | ||
| 
 | ||
| ### 1) Toggle UI dans la NavBar
 | ||
| 
 | ||
| Ajouter un **bouton toggle** dans `src/app/layout/app-navbar/app-navbar.component.ts` :
 | ||
| 
 | ||
| ```html
 | ||
| <!-- app-navbar.component.html (snippet) -->
 | ||
| <div class="flex items-center gap-2">
 | ||
|   <!-- Autres boutons -->
 | ||
|   <button 
 | ||
|     (click)="toggleUIMode()"
 | ||
|     [attr.aria-label]="'Toggle ' + (isNimbusMode$ | async ? 'legacy' : 'nimbus') + ' UI'"
 | ||
|     class="p-2 rounded hover:bg-gray-100 dark:hover:bg-gray-800">
 | ||
|     <span *ngIf="(isNimbusMode$ | async)">✨ Nimbus</span>
 | ||
|     <span *ngIf="!(isNimbusMode$ | async)">🔧 Legacy</span>
 | ||
|   </button>
 | ||
| </div>
 | ||
| ```
 | ||
| 
 | ||
| ### 2) Service de gestion du mode UI
 | ||
| 
 | ||
| Créer `src/app/shared/services/ui-mode.service.ts` :
 | ||
| 
 | ||
| ```typescript
 | ||
| import { Injectable, signal } from '@angular/core';
 | ||
| 
 | ||
| @Injectable({ providedIn: 'root' })
 | ||
| export class UiModeService {
 | ||
|   // Signal pour réactivité fine-grained
 | ||
|   isNimbusMode = signal<boolean>(this.loadUIMode());
 | ||
| 
 | ||
|   constructor() {}
 | ||
| 
 | ||
|   toggleUIMode() {
 | ||
|     const newMode = !this.isNimbusMode();
 | ||
|     this.isNimbusMode.set(newMode);
 | ||
|     localStorage.setItem('obsiviewer-ui-mode', newMode ? 'nimbus' : 'legacy');
 | ||
|   }
 | ||
| 
 | ||
|   private loadUIMode(): boolean {
 | ||
|     if (typeof localStorage === 'undefined') return false;
 | ||
|     const saved = localStorage.getItem('obsiviewer-ui-mode');
 | ||
|     return saved ? saved === 'nimbus' : true; // Nimbus par défaut
 | ||
|   }
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| ### 3) Layout wrapper avec feature flag
 | ||
| 
 | ||
| Créer `src/app/layout/app-shell-adaptive/app-shell-adaptive.component.ts` :
 | ||
| 
 | ||
| ```typescript
 | ||
| import { Component, inject } from '@angular/core';
 | ||
| import { UiModeService } from '@app/shared/services/ui-mode.service';
 | ||
| import { AppShellNimbusLayoutComponent } from '../app-shell-nimbus/app-shell-nimbus.component';
 | ||
| import { AppShellLegacyLayoutComponent } from '../app-shell-legacy/app-shell-legacy.component';
 | ||
| 
 | ||
| @Component({
 | ||
|   selector: 'app-shell-adaptive',
 | ||
|   template: `
 | ||
|     @if (uiMode.isNimbusMode()) {
 | ||
|       <app-shell-nimbus-layout></app-shell-nimbus-layout>
 | ||
|     } @else {
 | ||
|       <app-shell-legacy-layout></app-shell-legacy-layout>
 | ||
|     }
 | ||
|   `,
 | ||
|   standalone: true,
 | ||
|   imports: [AppShellNimbusLayoutComponent, AppShellLegacyLayoutComponent],
 | ||
| })
 | ||
| export class AppShellAdaptiveComponent {
 | ||
|   uiMode = inject(UiModeService);
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ## 🎨 Responsive Design Strategy
 | ||
| 
 | ||
| ### Breakpoints Tailwind (standard)
 | ||
| 
 | ||
| ```typescript
 | ||
| // tailwind.config.js
 | ||
| module.exports = {
 | ||
|   theme: {
 | ||
|     screens: {
 | ||
|       'xs': '320px',   // iPhone SE
 | ||
|       'sm': '640px',   // Petites tablettes
 | ||
|       'md': '768px',   // iPad, tablettes
 | ||
|       'lg': '1024px',  // Desktop compact
 | ||
|       'xl': '1280px',  // Desktop standard
 | ||
|       '2xl': '1536px', // Larges écrans
 | ||
|     },
 | ||
|   },
 | ||
| };
 | ||
| ```
 | ||
| 
 | ||
| ### Mobile First Approach
 | ||
| 
 | ||
| **Développer pour mobile d'abord, puis enrichir pour desktop.**
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ## 📱 Layouts Responsifs
 | ||
| 
 | ||
| ### Desktop Layout (≥1024px)
 | ||
| 
 | ||
| ```
 | ||
| ┌─────────────────────────────────────────────────────────┐
 | ||
| │ NAVBAR (Dark, fixed, h-14)                              │
 | ||
| ├────────────────┬──────────────────┬──────────────────────┤
 | ||
| │                │                  │                      │
 | ||
| │  SIDEBAR       │   RESULT LIST    │   NOTE VIEW + TOC    │
 | ||
| │  (240-440px)   │   (virtualized)  │   (Resizable)        │
 | ||
| │  Resizable     │                  │                      │
 | ||
| │                │                  │                      │
 | ||
| │  - Quick       │  - Search bar    │  - Markdown          │
 | ||
| │    Links       │  - Filters       │  - ToC drawer        │
 | ||
| │  - Folders     │  - Items (80px)  │  - Actions bar       │
 | ||
| │  - Tags        │  - Pagination    │                      │
 | ||
| │                │                  │                      │
 | ||
| └────────────────┴──────────────────┴──────────────────────┘
 | ||
| ```
 | ||
| 
 | ||
| ### Tablet Layout (768px ≤ width < 1024px)
 | ||
| 
 | ||
| ```
 | ||
| ┌──────────────────────────────────────┐
 | ||
| │ NAVBAR + Toggle (fixed, h-14)        │
 | ||
| ├──────────────────────────────────────┤
 | ||
| │  TAB NAVIGATION (fixed, bottom)      │
 | ||
| │  [Sidebar] [List] [Page] [ToC]       │
 | ||
| ├──────────────────────────────────────┤
 | ||
| │                                      │
 | ||
| │  ACTIVE TAB CONTENT (scrollable)     │
 | ||
| │  - Drawer si besoin                  │
 | ||
| │  - Full-width panels                 │
 | ||
| │                                      │
 | ||
| └──────────────────────────────────────┘
 | ||
| ```
 | ||
| 
 | ||
| ### Mobile Layout (<768px)
 | ||
| 
 | ||
| ```
 | ||
| ┌──────────────────────────────────┐
 | ||
| │ NAVBAR (compact, h-12)           │
 | ||
| │ [Menu] [Search] [Toggle]         │
 | ||
| ├──────────────────────────────────┤
 | ||
| │                                  │
 | ||
| │  TAB/DRAWER NAVIGATION           │
 | ||
| │  [≡] [🔍] [📄] [📋]             │
 | ||
| │                                  │
 | ||
| │  CONTENT AREA (Full-width)       │
 | ||
| │  - Drawer sidebar (80vw left)    │
 | ||
| │  - Swipeable list (list tab)     │
 | ||
| │  - Markdown full-screen (page)   │
 | ||
| │  - Inline ToC (toggle button)    │
 | ||
| │                                  │
 | ||
| ├──────────────────────────────────┤
 | ||
| │ Bottom Navigation (sticky)       │
 | ||
| │ Tab buttons (4 icônes)           │
 | ||
| └──────────────────────────────────┘
 | ||
| ```
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ## 🎬 Composants Nimbus Responsifs
 | ||
| 
 | ||
| ### Desktop/Mobile Variants
 | ||
| 
 | ||
| Chaque composant doit avoir des **variants responsifs** :
 | ||
| 
 | ||
| ```
 | ||
| app-left-sidebar/
 | ||
| ├── app-left-sidebar.component.ts          # Logique partagée
 | ||
| ├── app-left-sidebar.desktop.component.ts  # ≥1024px (fixed, resizable)
 | ||
| └── app-left-sidebar.mobile.component.ts   # <1024px (drawer)
 | ||
| 
 | ||
| app-center-list/
 | ||
| ├── app-center-list.component.ts
 | ||
| ├── app-center-list.desktop.component.ts   # ≥1024px (2 colonnes)
 | ||
| └── app-center-list.mobile.component.ts    # <1024px (full-width)
 | ||
| 
 | ||
| app-note-view/
 | ||
| ├── app-note-view.component.ts
 | ||
| ├── app-note-view.desktop.component.ts     # ≥1024px (3 colonnes + ToC)
 | ||
| └── app-note-view.mobile.component.ts      # <1024px (full-width + ToC inline)
 | ||
| 
 | ||
| app-toc-drawer/
 | ||
| ├── app-toc-drawer.component.ts
 | ||
| ├── app-toc-drawer.desktop.component.ts    # ≥1024px (Fixed right)
 | ||
| └── app-toc-drawer.mobile.component.ts     # <1024px (Collapsible, inline)
 | ||
| ```
 | ||
| 
 | ||
| ### Détection et Injection
 | ||
| 
 | ||
| ```typescript
 | ||
| // app-left-sidebar.component.ts
 | ||
| import { Component, inject } from '@angular/core';
 | ||
| import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
 | ||
| 
 | ||
| @Component({
 | ||
|   selector: 'app-left-sidebar',
 | ||
|   standalone: true,
 | ||
|   template: `
 | ||
|     @if (isDesktop$ | async) {
 | ||
|       <ng-container *ngComponentOutlet="DesktopSidebarComponent"></ng-container>
 | ||
|     } @else {
 | ||
|       <ng-container *ngComponentOutlet="MobileSidebarDrawerComponent"></ng-container>
 | ||
|     }
 | ||
|   `,
 | ||
| })
 | ||
| export class AppLeftSidebarComponent {
 | ||
|   private breakpoint = inject(BreakpointObserver);
 | ||
|   isDesktop$ = this.breakpoint.observe(Breakpoints.Large).pipe(
 | ||
|     map(result => result.matches)
 | ||
|   );
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ## 📱 Navigation Mobile Avancée
 | ||
| 
 | ||
| ### Tab/Drawer Navigation
 | ||
| 
 | ||
| ```typescript
 | ||
| // src/shared/services/mobile-nav.service.ts
 | ||
| @Injectable({ providedIn: 'root' })
 | ||
| export class MobileNavService {
 | ||
|   activeTab = signal<'sidebar' | 'list' | 'page' | 'toc'>('list');
 | ||
|   
 | ||
|   setTab(tab: 'sidebar' | 'list' | 'page' | 'toc') {
 | ||
|     this.activeTab.set(tab);
 | ||
|     // Persist if needed
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| // Usage in component
 | ||
| <app-bottom-nav [activeTab]="mobileNav.activeTab()" 
 | ||
|                 (tabChange)="mobileNav.setTab($event)">
 | ||
| </app-bottom-nav>
 | ||
| ```
 | ||
| 
 | ||
| ### Swipe Navigation (Gestures)
 | ||
| 
 | ||
| ```typescript
 | ||
| // Directive pour détection de swipe
 | ||
| import { Directive, Output, EventEmitter, HostListener } from '@angular/core';
 | ||
| 
 | ||
| @Directive({
 | ||
|   selector: '[appSwipeNav]',
 | ||
|   standalone: true,
 | ||
| })
 | ||
| export class SwipeNavDirective {
 | ||
|   @Output() swipeLeft = new EventEmitter<void>();
 | ||
|   @Output() swipeRight = new EventEmitter<void>();
 | ||
| 
 | ||
|   private startX = 0;
 | ||
| 
 | ||
|   @HostListener('touchstart', ['$event'])
 | ||
|   onTouchStart(e: TouchEvent) {
 | ||
|     this.startX = e.touches[0].clientX;
 | ||
|   }
 | ||
| 
 | ||
|   @HostListener('touchend', ['$event'])
 | ||
|   onTouchEnd(e: TouchEvent) {
 | ||
|     const endX = e.changedTouches[0].clientX;
 | ||
|     const diff = this.startX - endX;
 | ||
|     
 | ||
|     if (Math.abs(diff) > 50) { // Seuil minimum
 | ||
|       if (diff > 0) this.swipeLeft.emit();
 | ||
|       else this.swipeRight.emit();
 | ||
|     }
 | ||
|   }
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ## 🎨 Composants Spécifiques (Mobile-First)
 | ||
| 
 | ||
| ### 1) Bottom Navigation (Mobile)
 | ||
| 
 | ||
| ```html
 | ||
| <!-- app-bottom-nav.component.html -->
 | ||
| <nav class="fixed bottom-0 left-0 right-0 bg-white dark:bg-gray-900 border-t 
 | ||
|             border-gray-200 dark:border-gray-800 h-16 flex justify-around md:hidden">
 | ||
|   <button *ngFor="let tab of tabs"
 | ||
|           (click)="selectTab(tab.id)"
 | ||
|           [class.active]="activeTab === tab.id"
 | ||
|           class="flex-1 flex flex-col items-center justify-center gap-1 
 | ||
|                  text-xs hover:bg-gray-50 dark:hover:bg-gray-800">
 | ||
|     <span class="text-lg">{{ tab.icon }}</span>
 | ||
|     <span>{{ tab.label }}</span>
 | ||
|   </button>
 | ||
| </nav>
 | ||
| ```
 | ||
| 
 | ||
| ### 2) Drawer Sidebar (Mobile)
 | ||
| 
 | ||
| ```html
 | ||
| <!-- app-left-sidebar.mobile.component.html -->
 | ||
| <aside class="fixed left-0 top-0 bottom-0 w-80vw max-w-xs 
 | ||
|               bg-white dark:bg-gray-900 shadow-lg z-50
 | ||
|               transform transition-transform duration-300"
 | ||
|        [class.-translate-x-full]="!isOpen">
 | ||
|   <!-- Contenu sidebar -->
 | ||
|   <button (click)="close()" class="absolute top-4 right-4">✕</button>
 | ||
| </aside>
 | ||
| 
 | ||
| <!-- Backdrop -->
 | ||
| <div *ngIf="isOpen" 
 | ||
|      (click)="close()"
 | ||
|      class="fixed inset-0 bg-black/50 z-40 md:hidden"></div>
 | ||
| ```
 | ||
| 
 | ||
| ### 3) Search Bar Compact (Mobile)
 | ||
| 
 | ||
| ```html
 | ||
| <!-- app-search-bar.mobile.component.html -->
 | ||
| <div class="sticky top-0 bg-white dark:bg-gray-900 p-2 shadow-sm z-10">
 | ||
|   <div class="flex gap-2">
 | ||
|     <!-- Menu toggle -->
 | ||
|     <button (click)="toggleSidebar()" 
 | ||
|             class="p-2 rounded hover:bg-gray-100">☰</button>
 | ||
|     
 | ||
|     <!-- Search input (full-width on mobile) -->
 | ||
|     <input type="text" 
 | ||
|            placeholder="Search..." 
 | ||
|            class="flex-1 px-3 py-2 rounded border dark:border-gray-700">
 | ||
|     
 | ||
|     <!-- Filters button (mobile: popover instead of dropdown) -->
 | ||
|     <button (click)="openFilters()" 
 | ||
|             class="p-2 rounded hover:bg-gray-100">⚙️</button>
 | ||
|   </div>
 | ||
|   
 | ||
|   <!-- Active badges (scrollable horizontally) -->
 | ||
|   <div class="flex gap-1 mt-2 overflow-x-auto">
 | ||
|     <span *ngFor="let badge of activeBadges" 
 | ||
|           class="badge badge-sm">
 | ||
|       {{ badge }} ✕
 | ||
|     </span>
 | ||
|   </div>
 | ||
| </div>
 | ||
| ```
 | ||
| 
 | ||
| ### 4) Result List Item (Mobile-Optimized)
 | ||
| 
 | ||
| ```html
 | ||
| <!-- app-result-list-item.component.html -->
 | ||
| <div class="p-3 border-b hover:bg-gray-50 dark:hover:bg-gray-800 cursor-pointer">
 | ||
|   <!-- Title -->
 | ||
|   <h3 class="font-semibold text-sm sm:text-base truncate">
 | ||
|     {{ item.title }}
 | ||
|   </h3>
 | ||
|   
 | ||
|   <!-- Date + Tags (stacked on mobile) -->
 | ||
|   <div class="flex flex-col sm:flex-row sm:items-center gap-1 mt-1 text-xs text-gray-600">
 | ||
|     <span>{{ item.modified | date:'short' }}</span>
 | ||
|     <div class="flex gap-1 flex-wrap">
 | ||
|       <span *ngFor="let tag of item.tags" 
 | ||
|             class="badge badge-sm">{{ tag }}</span>
 | ||
|     </div>
 | ||
|   </div>
 | ||
|   
 | ||
|   <!-- Excerpt (truncated) -->
 | ||
|   <p class="mt-2 text-xs text-gray-600 line-clamp-2">
 | ||
|     {{ item.excerpt }}
 | ||
|   </p>
 | ||
| </div>
 | ||
| ```
 | ||
| 
 | ||
| ### 5) Markdown Viewer (Mobile-Responsive)
 | ||
| 
 | ||
| ```html
 | ||
| <!-- app-markdown-viewer.component.html -->
 | ||
| <article class="prose dark:prose-invert max-w-none 
 | ||
|                  prose-sm sm:prose-base 
 | ||
|                  prose-img:max-w-full prose-img:h-auto
 | ||
|                  px-3 sm:px-6 py-4 sm:py-8">
 | ||
|   <!-- Markdown content -->
 | ||
|   <div [innerHTML]="markdownHTML"></div>
 | ||
|   
 | ||
|   <!-- ToC (mobile: inline toggle) -->
 | ||
|   <button *ngIf="headings.length > 0"
 | ||
|           (click)="showToC = !showToC"
 | ||
|           class="lg:hidden fixed bottom-20 right-4 p-3 rounded-full bg-blue-500 text-white shadow-lg">
 | ||
|     📋
 | ||
|   </button>
 | ||
|   
 | ||
|   <nav *ngIf="showToC" class="lg:hidden fixed inset-0 bg-white dark:bg-gray-900 
 | ||
|                               z-40 overflow-y-auto p-4">
 | ||
|     <!-- ToC content -->
 | ||
|   </nav>
 | ||
| </article>
 | ||
| ```
 | ||
| 
 | ||
| ### 6) ToC Drawer (Desktop Fixed, Mobile Inline)
 | ||
| 
 | ||
| ```typescript
 | ||
| // app-toc-drawer.component.ts
 | ||
| @Component({
 | ||
|   selector: 'app-toc-drawer',
 | ||
|   template: `
 | ||
|     <!-- Desktop: Fixed right panel (≥1024px) -->
 | ||
|     <aside class="hidden lg:flex fixed right-0 top-14 bottom-0 w-64 
 | ||
|                   bg-gray-50 dark:bg-gray-800 border-l 
 | ||
|                   border-gray-200 dark:border-gray-700
 | ||
|                   flex-col overflow-y-auto">
 | ||
|       <app-toc-content [headings]="headings"></app-toc-content>
 | ||
|     </aside>
 | ||
|     
 | ||
|     <!-- Mobile: Collapsible inline (< 1024px) -->
 | ||
|     <div *ngIf="(isMobile$ | async)" 
 | ||
|          [@slideDown]="showTocMobile ? 'in' : 'out'"
 | ||
|          class="bg-gray-50 dark:bg-gray-800 border-t p-3 max-h-96 overflow-y-auto">
 | ||
|       <app-toc-content [headings]="headings"></app-toc-content>
 | ||
|     </div>
 | ||
|   `,
 | ||
|   animations: [
 | ||
|     trigger('slideDown', [
 | ||
|       state('in', style({ height: '*' })),
 | ||
|       state('out', style({ height: '0px' })),
 | ||
|       transition('in <=> out', animate('200ms ease-in-out')),
 | ||
|     ]),
 | ||
|   ],
 | ||
|   standalone: true,
 | ||
| })
 | ||
| export class AppTocDrawerComponent {
 | ||
|   @Input() headings: Heading[] = [];
 | ||
|   showTocMobile = false;
 | ||
|   isMobile$ = this.breakpoint.observe('(max-width: 1023px)').pipe(
 | ||
|     map(r => r.matches)
 | ||
|   );
 | ||
| }
 | ||
| ```
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ## 🎯 Livrables Attendus
 | ||
| 
 | ||
| ### Code
 | ||
| 1. **Toggle UI** : `UiModeService`, bouton navbar, `AppShellAdaptiveComponent`
 | ||
| 2. **Responsive Wrappers** : Variants pour chaque composant (Desktop/Mobile)
 | ||
| 3. **Mobile Components** : Bottom nav, drawer sidebar, inline ToC, mobile search
 | ||
| 4. **Gesture Handling** : Swipe navigation directive, touch-friendly interactions
 | ||
| 5. **Breakpoint Utilities** : Service CDK layout, reactive signals
 | ||
| 
 | ||
| ### Styling
 | ||
| 1. **Tailwind Config** : Breakpoints personnalisés, tokens tokens clair/sombre
 | ||
| 2. **Mobile-First CSS** : Base mobile, enrichissements desktop via `md:`, `lg:`
 | ||
| 3. **Touch-Friendly** : Boutons ≥44x44px, padding adéquat, hover states
 | ||
| 
 | ||
| ### Documentation
 | ||
| 1. **README_UI.md** : Schémas responsive, breakpoints, guide toggle
 | ||
| 2. **MOBILE_GUIDE.md** : Navigation gestures, bottom nav flow, drawer patterns
 | ||
| 3. **RESPONSIVE_CHECKLIST.md** : Tests par breakpoint, checklist A11y mobile
 | ||
| 
 | ||
| ### Tests
 | ||
| 1. **E2E** : Toggle persistence, layout switch, gesture navigation
 | ||
| 2. **Visual Regression** : Screenshots desktop/tablet/mobile
 | ||
| 3. **Accessibility** : Touch targets, ARIA labels, keyboard nav (Tab key)
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ## ⚡ Performance & Mobile Optimizations
 | ||
| 
 | ||
| ### Critical Optimizations
 | ||
| 
 | ||
| - **Lazy-load images** : `loading="lazy"`, responsive `srcset`
 | ||
| - **Virtual scroll** : CDK virtual scroll adapté mobile (item height ≈ 70–80px)
 | ||
| - **Debounce search** : 300ms sur mobile, 150ms sur desktop
 | ||
| - **Avoid layout shift** : Aspect ratios, skeleton loaders
 | ||
| - **Network awareness** : `navigator.connection.effectiveType` pour adapt qualité
 | ||
| - **Battery saver** : Réduire animations, throttle updates en mode saver
 | ||
| 
 | ||
| ### Lighthouse Mobile Targets
 | ||
| 
 | ||
| - Performance ≥ 85
 | ||
| - Accessibility ≥ 95
 | ||
| - Best Practices ≥ 90
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ## 🎮 Raccourcis Clavier & Gestures
 | ||
| 
 | ||
| ### Desktop
 | ||
| - `Ctrl/Cmd + K` : Palette commandes
 | ||
| - `Ctrl/Cmd + F` : Focus recherche
 | ||
| - `[` `]` : Replier/ouvrir ToC
 | ||
| - `Alt + ←/→` : Navigation historique
 | ||
| 
 | ||
| ### Mobile/Tablet
 | ||
| - **Tap** : Ouvrir note/item
 | ||
| - **Swipe left/right** : Changer onglet (list → page → sidebar)
 | ||
| - **Long-press** : Menu contextuel (favoris, open in new tab)
 | ||
| - **Pull-to-refresh** : Rafraîchir liste (optionnel)
 | ||
| - **Double-tap** : Zoom ToC (mobile)
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ## 📋 Critères d'Acceptation
 | ||
| 
 | ||
| ### Desktop (≥1024px)
 | ||
| - [x] Layout 3 colonnes (sidebar fixe/resizable, liste, page+ToC)
 | ||
| - [x] Changement dossier/tag/tri reflété en URL
 | ||
| - [x] 1000+ items fluide (60fps virtual scroll)
 | ||
| - [x] ToC synchronisé + repliable
 | ||
| - [x] Tous les flux clavier-seuls possibles
 | ||
| 
 | ||
| ### Tablet (768–1023px)
 | ||
| - [x] Navigation par onglets (Sidebar / List / Page)
 | ||
| - [x] Drawer sidebar (80vw, swipeable)
 | ||
| - [x] Bottom navigation sticky (4 icônes)
 | ||
| - [x] Contenu full-width par onglet
 | ||
| - [x] ToC inline collapsible
 | ||
| 
 | ||
| ### Mobile (<768px)
 | ||
| - [x] Drawer sidebar (80vw max)
 | ||
| - [x] Bottom nav (4 onglets)
 | ||
| - [x] Search bar compact (menu + search + filters)
 | ||
| - [x] List items optimisés (titre + date + excerpt)
 | ||
| - [x] Markdown full-screen
 | ||
| - [x] ToC overlay ou inline toggle
 | ||
| - [x] Touch targets ≥ 44x44px
 | ||
| 
 | ||
| ### Feature Flag
 | ||
| - [x] Toggle UI visible dans navbar
 | ||
| - [x] État persisté (localStorage)
 | ||
| - [x] Pas de perte d'état lors du switch
 | ||
| - [x] Legacy UI reste intacte
 | ||
| 
 | ||
| ### Accessibility
 | ||
| - [x] WCAG 2.1 AA sur tous les breakpoints
 | ||
| - [x] Keyboard navigation complète (Tab, Arrow, Enter)
 | ||
| - [x] ARIA labels pour navigation tactile
 | ||
| - [x] Focus visible partout
 | ||
| - [x] Zoom ≤ 200% sans horizontal scroll
 | ||
| 
 | ||
| ### Performance
 | ||
| - [x] TTI < 2.5s cold start
 | ||
| - [x] Scroll 60fps sur 1000+ items
 | ||
| - [x] Lighthouse Mobile ≥ 85 perf, ≥ 95 a11y
 | ||
| - [x] ImageOptimizations (lazy, srcset, format next-gen)
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ## 🗂️ Arborescence Fichiers
 | ||
| 
 | ||
| ```
 | ||
| src/app/
 | ||
| ├── layout/
 | ||
| │   ├── app-shell-adaptive/
 | ||
| │   │   └── app-shell-adaptive.component.ts      # Feature flag wrapper
 | ||
| │   ├── app-shell-nimbus/
 | ||
| │   │   ├── app-shell-nimbus.component.ts        # 3 colonnes (desktop)
 | ||
| │   │   ├── app-shell-nimbus.desktop.component.ts
 | ||
| │   │   └── app-shell-nimbus.mobile.component.ts
 | ||
| │   └── app-navbar/
 | ||
| │       ├── app-navbar.component.ts
 | ||
| │       └── [Bouton toggle UI intégré]
 | ||
| │
 | ||
| ├── features/
 | ||
| │   ├── sidebar/
 | ||
| │   │   ├── app-left-sidebar.component.ts
 | ||
| │   │   ├── app-left-sidebar.desktop.component.ts
 | ||
| │   │   └── app-left-sidebar.mobile.component.ts
 | ||
| │   │
 | ||
| │   ├── search-bar/
 | ||
| │   │   ├── app-search-bar.component.ts
 | ||
| │   │   ├── app-search-bar.desktop.component.ts
 | ||
| │   │   └── app-search-bar.mobile.component.ts
 | ||
| │   │
 | ||
| │   ├── result-list/
 | ||
| │   │   ├── app-result-list.component.ts
 | ||
| │   │   ├── app-result-list.desktop.component.ts
 | ||
| │   │   ├── app-result-list.mobile.component.ts
 | ||
| │   │   └── app-result-list-item.component.ts
 | ||
| │   │
 | ||
| │   ├── note-view/
 | ||
| │   │   ├── app-note-view.component.ts
 | ||
| │   │   ├── app-note-view.desktop.component.ts
 | ||
| │   │   └── app-note-view.mobile.component.ts
 | ||
| │   │
 | ||
| │   ├── toc-drawer/
 | ||
| │   │   ├── app-toc-drawer.component.ts
 | ||
| │   │   └── app-toc-content.component.ts
 | ||
| │   │
 | ||
| │   └── bottom-nav/ [NEW]
 | ||
| │       ├── app-bottom-nav.component.ts
 | ||
| │       └── app-bottom-nav.component.html
 | ||
| │
 | ||
| ├── shared/
 | ||
| │   ├── services/
 | ||
| │   │   ├── ui-mode.service.ts                   # [NEW] Toggle management
 | ||
| │   │   ├── mobile-nav.service.ts                # [NEW] Tab/drawer state
 | ||
| │   │   └── breakpoint.service.ts                # [NEW] Responsive helper
 | ||
| │   │
 | ||
| │   ├── directives/
 | ||
| │   │   └── swipe-nav.directive.ts               # [NEW] Gesture detection
 | ||
| │   │
 | ||
| │   └── components/
 | ||
| │       └── resizable-handle/
 | ||
| │
 | ||
| └── styles/
 | ||
|     ├── tokens.css                               # Tailwind tokens
 | ||
|     ├── responsive.css                           # Breakpoint utilities
 | ||
|     └── mobile.css                               # Mobile-specific (touches, etc.)
 | ||
| ```
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ## 📅 Plan d'Implémentation (ordre conseillé)
 | ||
| 
 | ||
| 1. **Feature Flag Infrastructure** (1-2j)
 | ||
|    - `UiModeService` + localStorage persistence
 | ||
|    - `AppShellAdaptiveComponent` wrapper
 | ||
|    - Toggle button dans navbar
 | ||
| 
 | ||
| 2. **Responsive Shell & Breakpoints** (2-3j)
 | ||
|    - Desktop layout 3 colonnes (>=1024px)
 | ||
|    - Tailwind breakpoints & tokens
 | ||
|    - Resizable sidebar logic
 | ||
| 
 | ||
| 3. **Mobile Navigation & Bottom Nav** (2-3j)
 | ||
|    - `BottomNavComponent` (4 onglets)
 | ||
|    - `MobileNavService` (state management)
 | ||
|    - Tab/drawer routing
 | ||
| 
 | ||
| 4. **Mobile Sidebar Drawer** (1-2j)
 | ||
|    - Drawer animé (translate, backdrop)
 | ||
|    - Swipe dismiss directive
 | ||
|    - Z-index management
 | ||
| 
 | ||
| 5. **Responsive Components** (3-4j)
 | ||
|    - Search bar variants (desktop/mobile)
 | ||
|    - Result list item responsive
 | ||
|    - Markdown viewer mobile optimizations
 | ||
| 
 | ||
| 6. **ToC Drawer Adaptive** (1-2j)
 | ||
|    - Fixed right panel (desktop)
 | ||
|    - Inline toggle (mobile)
 | ||
|    - Animations smooth
 | ||
| 
 | ||
| 7. **Gestures & Touch** (1-2j)
 | ||
|    - Swipe nav directive
 | ||
|    - Long-press menu
 | ||
|    - Pull-to-refresh (optionnel)
 | ||
| 
 | ||
| 8. **Accessibility & Testing** (2-3j)
 | ||
|    - WCAG 2.1 AA audit
 | ||
|    - Keyboard nav (Tab, Arrow)
 | ||
|    - E2E tests (toggle, breakpoints)
 | ||
|    - Visual regression (3 breakpoints)
 | ||
| 
 | ||
| **Total estimé** : 13–21 jours (équipe 1 FE engineer)
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ## 🚀 Scripts NPM
 | ||
| 
 | ||
| ```bash
 | ||
| # Dev complet (Nimbus activé par défaut)
 | ||
| npm run dev
 | ||
| 
 | ||
| # Build production
 | ||
| npm run build
 | ||
| 
 | ||
| # Tests responsifs (plusieurs breakpoints)
 | ||
| npm run test:responsive
 | ||
| 
 | ||
| # Lighthouse audit mobile
 | ||
| npm run audit:lighthouse:mobile
 | ||
| 
 | ||
| # Feature flag (override)
 | ||
| NIMBUS_UI=false npm run dev        # Force legacy UI
 | ||
| ```
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ## ✅ Checklist Livraison
 | ||
| 
 | ||
| - [ ] Toggle UI visible, fonctionnel, persisté
 | ||
| - [ ] Desktop (≥1024px) : 3 colonnes, interactions fluides
 | ||
| - [ ] Tablet (768–1023px) : Onglets + drawer, full-width contenu
 | ||
| - [ ] Mobile (<768px) : Bottom nav, drawer, touch-friendly
 | ||
| - [ ] Tous les flux clavier-seuls réalisables
 | ||
| - [ ] Lighthouse mobile ≥ 85 perf, ≥ 95 a11y
 | ||
| - [ ] Virtual scroll 60fps sur 1000+ items
 | ||
| - [ ] Tests E2E (toggle, breakpoints, gestures)
 | ||
| - [ ] Documentation complète (README_UI.md, MOBILE_GUIDE.md, RESPONSIVE_CHECKLIST.md)
 | ||
| - [ ] Zéro régression : legacy UI inchangée, Wikilinks, bookmarks, graph intacts
 | ||
| - [ ] Screenshots before/after 3 breakpoints
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ## 📖 Documentation à Produire
 | ||
| 
 | ||
| 1. **README_UI.md** : Overview, architecture, screenshots 3 breakpoints
 | ||
| 2. **MOBILE_GUIDE.md** : Navigation onglets, gestures, drawer patterns
 | ||
| 3. **RESPONSIVE_CHECKLIST.md** : Tests par breakpoint, device testing
 | ||
| 4. **DEPLOYMENT.md** : Feature flag pour bascule progressive
 | ||
| 5. **ARCHITECTURE_DIAGRAM.md** : Schémas adaptatifs (Mermaid)
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| ## 💡 Notes Importantes
 | ||
| 
 | ||
| - **Mobile First** : Développer pour mobile en premier, puis enrichir desktop.
 | ||
| - **Persistent State** : Le toggle UI et les filtres actifs doivent persister via localStorage (sans browser storage, utiliser sessionStorage ou service state).
 | ||
| - **Zero Regression** : L'ancienne interface reste intacte et fonctionnelle.
 | ||
| - **Performance** : Virtual scroll adapté mobile (40+ items à l'écran), lazy-load images.
 | ||
| - **Accessibility** : 44x44px touch targets minimum, ARIA labels complets, keyboard nav.
 | ||
| - **Testing** : Visual regression sur breakpoints clés (375px / 768px / 1440px).
 | ||
| 
 | ||
| ---
 | ||
| 
 | ||
| **Exécute maintenant ce plan** : crée les composants, adapte les routes/états, ajoute les styles Tailwind responsifs, branche la recherche et livre le MR conforme aux critères ci-dessus avec toggle UI et compatibilité 100% Desktop/Mobile. |