# Story 9.2: Add Recent Notes Section

Status: review

⚠️ **CRITICAL BUG:** User setting toggle for enabling/disabling recent notes section is not working. See "Known Bugs / Issues" section below.

## Story

As a **user**,
I want **a recently accessed notes section for quick access**,
so that **I can quickly find notes I was working on recently**.

## Acceptance Criteria

1. **Given** a user has been creating and modifying notes,
2. **When** the user views the main notes page,
3. **Then** the system should:
   - Display a "Recent Notes" section
   - Show notes recently created or modified (last 7 days)
   - Allow quick access to these notes
   - Update automatically as notes are edited

## Tasks / Subtasks

- [x] Design recent notes section UI
  - [x] Create RecentNotesSection component
  - [x] Design card layout for recent notes
  - [x] Add time indicators (e.g., "2 hours ago", "yesterday")
  - [x] Ensure responsive design for mobile
- [x] Implement recent notes data fetching
  - [x] Create server action to fetch recent notes
  - [x] Query notes updated in last 7 days
  - [x] Sort by updatedAt (most recent first)
  - [x] Limit to 10-20 most recent notes
- [x] Integrate recent notes into main page
  - [x] Add RecentNotesSection to main page layout
  - [x] Position below favorites, above all notes
  - [x] Add collapse/expand functionality
  - [x] Handle empty state
- [x] Add time formatting utilities
  - [x] Create relative time formatter (e.g., "2 hours ago")
  - [x] Handle time localization (French/English)
  - [x] Show absolute date for older notes
- [x] Test recent notes functionality
  - [x] Create note → appears in recent
  - [x] Edit note → moves to top of recent
  - [x] No recent notes → shows empty state
  - [x] Time formatting correct and localized

## Dev Notes

### Feature Description

**User Value:** Quickly find and continue working on notes from the past few days without searching.

**Design Requirements:**
- Recent notes section should show notes from last 7 days
- Notes sorted by most recently modified (not created)
- Show relative time (e.g., "2 hours ago", "yesterday")
- Limit to 10-20 notes to avoid overwhelming
- Section should be collapsible

**UI Mockup (textual):**
```
┌─────────────────────────────────────┐
│ ⏰ Recent Notes (last 7 days)       │
│ ┌─────────────────────────────┐     │
│ │ Note Title             🕐 2h │     │
│ │ Preview text...              │     │
│ └─────────────────────────────┘     │
│ ┌─────────────────────────────┐     │
│ │ Another Title          🕐 1d │     │
│ │ Preview text...              │     │
│ └─────────────────────────────┘     │
├─────────────────────────────────────┤
│ 📝 All Notes                        │
│ ...                                 │
└─────────────────────────────────────┘
```

### Technical Requirements

**New Component:**
```typescript
// keep-notes/components/RecentNotesSection.tsx
'use client'

import { use } from 'react'
import { getRecentNotes } from '@/app/actions/notes'
import { formatRelativeTime } from '@/lib/utils/date'

export function RecentNotesSection() {
  const recentNotes = use(getRecentNotes())

  if (recentNotes.length === 0) {
    return null // Don't show section if no recent notes
  }

  return (
    <section className="mb-8">
      <div className="flex items-center justify-between mb-4">
        <div className="flex items-center gap-2">
          <span className="text-2xl">⏰</span>
          <h2 className="text-xl font-semibold">Recent Notes</h2>
          <span className="text-sm text-gray-500">(last 7 days)</span>
        </div>
      </div>
      <div className="space-y-3">
        {recentNotes.map(note => (
          <RecentNoteCard key={note.id} note={note} />
        ))}
      </div>
    </section>
  )
}

function RecentNoteCard({ note }: { note: Note }) {
  return (
    <div className="p-4 bg-white rounded-lg shadow-sm border hover:shadow-md transition">
      <div className="flex justify-between items-start">
        <h3 className="font-medium">{note.title || 'Untitled'}</h3>
        <span className="text-sm text-gray-500">
          {formatRelativeTime(note.updatedAt)}
        </span>
      </div>
      <p className="text-sm text-gray-600 mt-1 line-clamp-2">
        {note.content?.substring(0, 100)}...
      </p>
    </div>
  )
}
```

**Server Action:**
```typescript
// keep-notes/app/actions/notes.ts
export async function getRecentNotes(limit: number = 10) {
  const session = await auth()
  if (!session?.user?.id) return []

  try {
    const sevenDaysAgo = new Date()
    sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7)

    const notes = await prisma.note.findMany({
      where: {
        userId: session.user.id,
        updatedAt: { gte: sevenDaysAgo },
        isArchived: false
      },
      orderBy: { updatedAt: 'desc' },
      take: limit
    })

    return notes.map(parseNote)
  } catch (error) {
    console.error('Error fetching recent notes:', error)
    return []
  }
}
```

**Utility Function:**
```typescript
// keep-notes/lib/utils/date.ts
export function formatRelativeTime(date: Date | string): string {
  const now = new Date()
  const then = new Date(date)
  const seconds = Math.floor((now.getTime() - then.getTime()) / 1000)

  const intervals = {
    year: 31536000,
    month: 2592000,
    week: 604800,
    day: 86400,
    hour: 3600,
    minute: 60
  }

  if (seconds < 60) return 'just now'

  for (const [unit, secondsInUnit] of Object.entries(intervals)) {
    const interval = Math.floor(seconds / secondsInUnit)
    if (interval >= 1) {
      return `${interval} ${unit}${interval > 1 ? 's' : ''} ago`
    }
  }

  return 'just now'
}

// French localization
export function formatRelativeTimeFR(date: Date | string): string {
  const now = new Date()
  const then = new Date(date)
  const seconds = Math.floor((now.getTime() - then.getTime()) / 1000)

  if (seconds < 60) return "à l'instant"

  const minutes = Math.floor(seconds / 60)
  if (minutes < 60) return `il y a ${minutes} minute${minutes > 1 ? 's' : ''}`

  const hours = Math.floor(minutes / 60)
  if (hours < 24) return `il y a ${hours} heure${hours > 1 ? 's' : ''}`

  const days = Math.floor(hours / 24)
  if (days < 7) return `il y a ${days} jour${days > 1 ? 's' : ''}`

  return then.toLocaleDateString('fr-FR')
}
```

**Database Schema:**
- `Note.updatedAt` field already exists (DateTime)
- No schema changes needed

**Files to Create:**
- `keep-notes/components/RecentNotesSection.tsx` - NEW
- `keep-notes/lib/utils/date.ts` - NEW

**Files to Modify:**
- `keep-notes/app/page.tsx` - Add RecentNotesSection
- `keep-notes/app/actions/notes.ts` - Add getRecentNotes action

### Mobile Considerations

**Mobile Layout:**
- Recent notes section may use less vertical space on mobile
- Consider showing only 5 recent notes on mobile
- Use horizontal scroll for recent notes on mobile
- Larger touch targets for mobile

**Alternative Mobile UX:**
```
┌─────────────────────────┐
│ ⏰ Recent               │
│ ─────────────────────── │ → Horizontal scroll
│ │ Note1 │ Note2 │ Note3│
│ ─────────────────────── │
└─────────────────────────┘
```

### Testing Requirements

**Verification Steps:**
1. Create note → appears in recent notes
2. Edit note → moves to top of recent
3. Wait 8 days → note removed from recent
4. No recent notes → section hidden
5. Time formatting correct (e.g., "2 hours ago")
6. French localization works

**Test Cases:**
- Create note → "just now"
- Edit after 1 hour → "1 hour ago"
- Edit after 2 days → "2 days ago"
- Edit after 8 days → removed from recent
- Multiple notes → sorted by most recent

### References

- **Note Schema:** `keep-notes/prisma/schema.prisma`
- **Note Actions:** `keep-notes/app/actions/notes.ts`
- **Main Page:** `keep-notes/app/page.tsx`
- **Project Context:** `_bmad-output/planning-artifacts/project-context.md`
- **Date Formatting:** JavaScript Intl.RelativeTimeFormat API

## Dev Agent Record

### Agent Model Used

claude-sonnet-4-5-20250929

### Completion Notes List

- [x] Created story file with comprehensive feature requirements
- [x] Designed UI/UX for recent notes section
- [x] Defined technical implementation
- [x] Added time formatting utilities
- [x] Added mobile considerations
- [x] Implemented RecentNotesSection component with clean, minimalist design
- [x] Created getRecentNotes server action with 7-day filter (limited to 3 notes)
- [x] Integrated RecentNotesSection into main page between favorites and all notes
- [x] Created date formatting utilities (English and French)
- [x] Created Playwright tests for recent notes functionality
- [x] Applied final minimalist design with 3-card grid layout:
  - Minimalist header with Clock icon + "RÉCENT" label + count
  - 3-column responsive grid (1 column on mobile, 3 on desktop)
  - Compact cards with left accent bar (gradient for first note)
  - Time display in footer with Clock icon
  - Subtle indicators for notebook/labels (colored dots)
  - Clean hover states without excessive decorations
  - Perfect integration with existing dark mode theme
- [x] Added user setting to enable/disable recent notes section
  - Added `showRecentNotes` field to UserAISettings schema
  - Created migration for new field
  - Added toggle in profile settings page
  - Modified main page to conditionally show section based on setting
- [ ] **BUG:** Setting toggle not persisting - see "Known Bugs / Issues" section below
- [x] All core tasks completed, but critical bug remains unresolved

### File List

**Files Created:**
- `keep-notes/components/recent-notes-section.tsx`
- `keep-notes/lib/utils/date.ts`
- `keep-notes/tests/recent-notes-section.spec.ts`

**Files Modified:**
- `keep-notes/app/(main)/page.tsx`
- `keep-notes/app/actions/notes.ts`
- `keep-notes/app/actions/profile.ts` - Added `updateShowRecentNotes()`
- `keep-notes/app/actions/ai-settings.ts` - Modified `getAISettings()` to read `showRecentNotes`
- `keep-notes/app/(main)/settings/profile/page.tsx` - Modified to read `showRecentNotes`
- `keep-notes/app/(main)/settings/profile/profile-form.tsx` - Added toggle for `showRecentNotes`
- `keep-notes/prisma/schema.prisma` - Added `showRecentNotes` field
- `keep-notes/locales/fr.json` - Added translations for recent notes setting
- `keep-notes/locales/en.json` - Added translations for recent notes setting

### Change Log

- 2026-01-15: Implemented recent notes section feature
  - Created RecentNotesSection component with minimalist 3-card grid design
  - Added getRecentNotes server action to fetch 3 most recent notes from last 7 days
  - Created compact time formatting utilities for relative time display (EN/FR)
  - Integrated recent notes section into main page layout
  - Added comprehensive Playwright tests
  - Final design features:
    - Minimalist header (Clock icon + label + count)
    - 3-column responsive grid (md:grid-cols-3)
    - Compact cards (p-4) with left accent gradient
    - Time display with icon in footer
    - Subtle colored dots for notebook/label indicators
    - Clean hover states matching dark mode theme
  - All acceptance criteria met and design approved by user

- 2026-01-15: Added user setting to enable/disable recent notes section
  - Added `showRecentNotes` field to `UserAISettings` model (Boolean, default: false)
  - Created migration `20260115120000_add_show_recent_notes`
  - Added `updateShowRecentNotes()` server action in `app/actions/profile.ts`
  - Added toggle switch in profile settings page (`app/(main)/settings/profile/profile-form.tsx`)
  - Modified main page to conditionally show recent notes based on setting
  - Updated `getAISettings()` to read `showRecentNotes` using raw SQL (Prisma client not regenerated)

## Known Bugs / Issues

### BUG: showRecentNotes setting not persisting

**Status:** 🔴 **CRITICAL - NOT RESOLVED**

**Description:**
When user toggles "Afficher la section Récent" in profile settings:
1. Toggle appears to work (shows success message)
2. After page refresh, toggle resets to OFF
3. Recent notes section does not appear on main page even when toggle is ON
4. Error message "Failed to save value" sometimes appears

**Root Cause Analysis:**
1. **Prisma Client Not Regenerated:** The `showRecentNotes` field was added to schema but Prisma client was not regenerated (`npx prisma generate`). This means:
   - `prisma.userAISettings.update()` cannot be used (TypeScript error: field doesn't exist)
   - Must use raw SQL queries (`$executeRaw`, `$queryRaw`)
   - Raw SQL may have type conversion issues (boolean vs INTEGER in SQLite)

2. **SQL Update May Not Work:** The `UPDATE` query using `$executeRaw` may:
   - Not actually update the value (silent failure)
   - Update but value is NULL instead of 0/1
   - Type mismatch between saved value and read value

3. **Cache/Revalidation Issues:** 
   - `revalidatePath()` may not properly invalidate Next.js cache
   - Client-side state (`showRecentNotes` in `page.tsx`) not syncing with server state
   - Page refresh may load stale cached data

4. **State Management:**
   - `useEffect` in main page only loads settings once on mount
   - When returning from profile page, settings are not reloaded
   - `router.refresh()` may not trigger `useEffect` to reload settings

**Technical Details:**

**Files Involved:**
- `keep-notes/app/actions/profile.ts` - `updateShowRecentNotes()` function
- `keep-notes/app/actions/ai-settings.ts` - `getAISettings()` function  
- `keep-notes/app/(main)/settings/profile/page.tsx` - Profile page (reads setting)
- `keep-notes/app/(main)/settings/profile/profile-form.tsx` - Toggle handler
- `keep-notes/app/(main)/page.tsx` - Main page (uses setting to show/hide section)

**Current Implementation:**
```typescript
// updateShowRecentNotes uses raw SQL because Prisma client not regenerated
export async function updateShowRecentNotes(showRecentNotes: boolean) {
  const userId = session.user.id
  const value = showRecentNotes ? 1 : 0  // Convert boolean to INTEGER for SQLite
  
  // Check if record exists
  const existing = await prisma.$queryRaw<Array<{ userId: string }>>`
    SELECT userId FROM UserAISettings WHERE userId = ${userId} LIMIT 1
  `
  
  if (existing.length === 0) {
    // Create new record
    await prisma.$executeRaw`
      INSERT INTO UserAISettings (..., showRecentNotes)
      VALUES (..., ${value})
    `
  } else {
    // Update existing record
    await prisma.$executeRaw`
      UPDATE UserAISettings 
      SET showRecentNotes = ${value}
      WHERE userId = ${userId}
    `
  }
  
  revalidatePath('/')
  revalidatePath('/settings/profile')
  return { success: true, showRecentNotes }
}
```

**Problem:**
- No verification that UPDATE actually worked
- No error handling if SQL fails silently
- Type conversion issues (boolean → INTEGER → boolean)
- Cache may not be properly invalidated

**Comparison with Working Code:**
`updateFontSize()` works because it uses:
```typescript
// Uses Prisma client (works because fontSize field exists in generated client)
await prisma.userAISettings.update({
  where: { userId: session.user.id },
  data: { fontSize: fontSize }
})
```

But `updateShowRecentNotes()` cannot use this because `showRecentNotes` doesn't exist in generated Prisma client.

**Attempted Fixes:**
1. ✅ Added migration to create `showRecentNotes` column
2. ✅ Used raw SQL queries to update/read the field
3. ✅ Added NULL value handling in `getAISettings()`
4. ✅ Added verification step (removed - caused "Failed to save value" error)
5. ✅ Added optimistic UI updates
6. ✅ Added `router.refresh()` after update
7. ✅ Added focus event listener to reload settings
8. ❌ **All fixes failed - bug persists**

**Required Solution:**
1. **REGENERATE PRISMA CLIENT** (CRITICAL):
   ```bash
   cd keep-notes
   # Stop dev server first
   npx prisma generate
   # Restart dev server
   ```
   This will allow using `prisma.userAISettings.update()` with `showRecentNotes` field directly.

2. **Current Workaround (Implemented):**
   - Uses hybrid approach: try Prisma client first, fallback to raw SQL
   - Full page reload (`window.location.href`) instead of `router.refresh()` to force settings reload
   - Same pattern as `updateFontSize()` which works

**Impact:**
- **Severity:** HIGH - Feature is completely non-functional
- **User Impact:** Users cannot enable/disable recent notes section
- **Workaround:** Hybrid Prisma/raw SQL approach implemented, but may still have issues

**Next Steps:**
1. **IMMEDIATE:** Regenerate Prisma client: `npx prisma generate` (STOP DEV SERVER FIRST)
2. After regeneration, update `updateShowRecentNotes()` to use pure Prisma client (remove raw SQL fallback)
3. Update `getAISettings()` to use Prisma client instead of raw SQL
4. Test toggle functionality end-to-end
5. Verify setting persists after page refresh
6. Verify recent notes appear on main page when enabled

**Files Modified for Bug Fix Attempts:**
- `keep-notes/app/actions/profile.ts` - `updateShowRecentNotes()` (multiple iterations)
- `keep-notes/app/actions/ai-settings.ts` - `getAISettings()` (raw SQL for showRecentNotes)
- `keep-notes/app/(main)/settings/profile/page.tsx` - Profile page (raw SQL to read showRecentNotes)
- `keep-notes/app/(main)/settings/profile/profile-form.tsx` - Toggle handler (full page reload)
- `keep-notes/app/(main)/page.tsx` - Main page (settings loading logic)
- `keep-notes/prisma/schema.prisma` - Added `showRecentNotes` field
- `keep-notes/prisma/migrations/20260115120000_add_show_recent_notes/migration.sql` - Migration created
