fix(ui): thème, textes et lisibilité restants de la revue
Les boutons suivent la couleur d’apparence, les libellés trop petits ou trop techniques sont clarifiés, et le catalogue des fournisseurs se met à jour tout seul. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -167,9 +167,9 @@ export function normalizeDashboardLayout(raw: unknown): DashboardLayout {
|
||||
const data = raw as Partial<DashboardLayout> & { version?: number }
|
||||
if (!Array.isArray(data.widgets) || data.widgets.length === 0) return getDefaultDashboardLayout()
|
||||
|
||||
// Layout périmé ou vide → preset canonique
|
||||
// Layout périmé, vide, ou version inconnue (essai abandonné) → preset actuel
|
||||
const incomingVersion = typeof data.version === 'number' ? data.version : 0
|
||||
if (incomingVersion < DASHBOARD_LAYOUT_VERSION) {
|
||||
if (incomingVersion !== DASHBOARD_LAYOUT_VERSION) {
|
||||
return getDefaultDashboardLayout()
|
||||
}
|
||||
|
||||
|
||||
38
memento-note/lib/dashboard/path-title.ts
Normal file
38
memento-note/lib/dashboard/path-title.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
const PLACEHOLDER_TITLES = new Set([
|
||||
'untitled',
|
||||
'sans titre',
|
||||
'(sans titre)',
|
||||
'(untitled)',
|
||||
])
|
||||
|
||||
function usableTitle(title: string | null | undefined): string | null {
|
||||
const trimmed = title?.trim()
|
||||
if (!trimmed) return null
|
||||
if (PLACEHOLDER_TITLES.has(trimmed.toLowerCase())) return null
|
||||
return trimmed
|
||||
}
|
||||
|
||||
/** Titre affichable : vrai titre, sinon premier extrait du contenu. */
|
||||
export function pathNoteTitle(
|
||||
title: string | null | undefined,
|
||||
content?: string | null,
|
||||
): string | null {
|
||||
const named = usableTitle(title)
|
||||
if (named) return named
|
||||
if (!content?.trim()) return null
|
||||
const plain = content
|
||||
.replace(/<[^>]+>/g, ' ')
|
||||
.replace(/ /gi, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim()
|
||||
if (!plain) return null
|
||||
return plain.length > 80 ? `${plain.slice(0, 80).trim()}…` : plain
|
||||
}
|
||||
|
||||
/** Première note récente qui a un titre ou un extrait — évite un héros « Sans titre ». */
|
||||
export function pickFocusNote<T extends { title: string | null; content: string }>(
|
||||
notes: T[],
|
||||
): T | undefined {
|
||||
if (notes.length === 0) return undefined
|
||||
return notes.find(n => pathNoteTitle(n.title, n.content)) ?? notes[0]
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { DashboardPath } from '@/lib/dashboard/path-types'
|
||||
import { pathNoteTitle, pickFocusNote } from '@/lib/dashboard/path-title'
|
||||
|
||||
function excerpt(text: string, max = 120): string {
|
||||
const plain = text.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim()
|
||||
@@ -45,14 +46,15 @@ export interface BriefingPathsInput {
|
||||
/** Pistes instantanées à partir du briefing déjà chargé — sans requête lourde. */
|
||||
export function buildFastPathsFromBriefing(input: BriefingPathsInput): DashboardPath[] {
|
||||
const paths: DashboardPath[] = []
|
||||
const focus = input.recentNotes[0]
|
||||
const focus = pickFocusNote(input.recentNotes)
|
||||
const continueTitle = focus ? pathNoteTitle(focus.title, focus.content) : null
|
||||
|
||||
if (focus) {
|
||||
if (focus && continueTitle) {
|
||||
paths.push({
|
||||
id: `continue-${focus.id}`,
|
||||
type: 'continue',
|
||||
priority: 100,
|
||||
title: focus.title || 'Untitled',
|
||||
title: continueTitle,
|
||||
description: excerpt(focus.content, 140),
|
||||
actionKey: 'continue',
|
||||
noteId: focus.id,
|
||||
@@ -63,11 +65,12 @@ export function buildFastPathsFromBriefing(input: BriefingPathsInput): Dashboard
|
||||
.filter(i => i.note1.id === focus.id || i.note2.id === focus.id)
|
||||
.slice(0, 2)) {
|
||||
const other = ins.note1.id === focus.id ? ins.note2 : ins.note1
|
||||
const otherTitle = pathNoteTitle(other.title, ins.note1.id === focus.id ? ins.note2Excerpt : ins.note1Excerpt)
|
||||
paths.push({
|
||||
id: `connect-${focus.id}-${other.id}`,
|
||||
type: 'connect',
|
||||
priority: 88,
|
||||
title: other.title || 'Untitled',
|
||||
title: otherTitle || excerpt(ins.insight, 80),
|
||||
description: ins.insight,
|
||||
actionKey: 'compare',
|
||||
noteId: focus.id,
|
||||
@@ -80,11 +83,13 @@ export function buildFastPathsFromBriefing(input: BriefingPathsInput): Dashboard
|
||||
|
||||
const freshInsight = input.insights.find(i => !i.viewed)
|
||||
if (freshInsight) {
|
||||
const left = pathNoteTitle(freshInsight.note1.title, freshInsight.note1Excerpt)
|
||||
const right = pathNoteTitle(freshInsight.note2.title, freshInsight.note2Excerpt)
|
||||
paths.push({
|
||||
id: `resurface-${freshInsight.id}`,
|
||||
type: 'resurface',
|
||||
priority: 85,
|
||||
title: `${freshInsight.note1.title || '…'} ↔ ${freshInsight.note2.title || '…'}`,
|
||||
title: left && right ? `${left} ↔ ${right}` : excerpt(freshInsight.insight, 80),
|
||||
description: freshInsight.insight,
|
||||
actionKey: 'openInsight',
|
||||
insightId: freshInsight.id,
|
||||
@@ -121,28 +126,6 @@ export function buildFastPathsFromBriefing(input: BriefingPathsInput): Dashboard
|
||||
})
|
||||
}
|
||||
|
||||
if (input.inboxCount > 0) {
|
||||
paths.push({
|
||||
id: 'organize-inbox',
|
||||
type: 'organize',
|
||||
priority: 60,
|
||||
title: `${input.inboxCount} notes`,
|
||||
description: 'inbox',
|
||||
actionKey: 'organizeInbox',
|
||||
})
|
||||
}
|
||||
|
||||
if (input.dueFlashcards > 0) {
|
||||
paths.push({
|
||||
id: 'review-flashcards',
|
||||
type: 'review',
|
||||
priority: 55,
|
||||
title: `${input.dueFlashcards}`,
|
||||
description: 'flashcards',
|
||||
actionKey: 'reviewCards',
|
||||
})
|
||||
}
|
||||
|
||||
paths.push({
|
||||
id: 'daily-journal',
|
||||
type: 'daily',
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'server-only'
|
||||
import prisma from '@/lib/prisma'
|
||||
import { clusteringService } from '@/lib/ai/services/clustering.service'
|
||||
import type { DashboardPath } from '@/lib/dashboard/path-types'
|
||||
import { pathNoteTitle, pickFocusNote } from '@/lib/dashboard/path-title'
|
||||
|
||||
export type { DashboardPath, DashboardPathType } from '@/lib/dashboard/path-types'
|
||||
|
||||
@@ -103,11 +104,15 @@ function pathsFromInsights(
|
||||
.slice(0, 2)) {
|
||||
const other = ins.note1.id === focus.id ? ins.note2 : ins.note1
|
||||
if (paths.some(p => p.note2Id === other.id && p.type === 'connect')) continue
|
||||
const otherTitle = pathNoteTitle(
|
||||
other.title,
|
||||
ins.note1.id === focus.id ? ins.note2Excerpt : ins.note1Excerpt,
|
||||
)
|
||||
paths.push({
|
||||
id: `connect-${focus.id}-${other.id}`,
|
||||
type: 'connect',
|
||||
priority: 90 - paths.length,
|
||||
title: other.title || 'Untitled',
|
||||
title: otherTitle || excerpt(ins.insight || ins.note2Excerpt || ins.note1Excerpt || '', 80),
|
||||
description: ins.insight || ins.note2Excerpt || ins.note1Excerpt || '',
|
||||
actionKey: 'compare',
|
||||
noteId: focus.id,
|
||||
@@ -152,7 +157,8 @@ async function findOpenLoops(userId: string, limit = 3): Promise<Array<{ id: str
|
||||
|
||||
export async function buildDashboardPaths(input: BuildPathsInput): Promise<DashboardPath[]> {
|
||||
const paths: DashboardPath[] = []
|
||||
const focus = input.recentNotes[0]
|
||||
const focus = pickFocusNote(input.recentNotes)
|
||||
const continueTitle = focus ? pathNoteTitle(focus.title, focus.content) : null
|
||||
|
||||
let focusClusterId: number | null = null
|
||||
if (focus) {
|
||||
@@ -162,16 +168,18 @@ export async function buildDashboardPaths(input: BuildPathsInput): Promise<Dashb
|
||||
})
|
||||
focusClusterId = member?.clusterId ?? null
|
||||
|
||||
paths.push({
|
||||
id: `continue-${focus.id}`,
|
||||
type: 'continue',
|
||||
priority: 100,
|
||||
title: focus.title || 'Untitled',
|
||||
description: excerpt(focus.content, 140),
|
||||
actionKey: 'continue',
|
||||
noteId: focus.id,
|
||||
notebookId: focus.notebookId ?? undefined,
|
||||
})
|
||||
if (continueTitle) {
|
||||
paths.push({
|
||||
id: `continue-${focus.id}`,
|
||||
type: 'continue',
|
||||
priority: 100,
|
||||
title: continueTitle,
|
||||
description: excerpt(focus.content, 140),
|
||||
actionKey: 'continue',
|
||||
noteId: focus.id,
|
||||
notebookId: focus.notebookId ?? undefined,
|
||||
})
|
||||
}
|
||||
|
||||
pathsFromInsights(focus, input.insights, paths)
|
||||
|
||||
@@ -181,7 +189,7 @@ export async function buildDashboardPaths(input: BuildPathsInput): Promise<Dashb
|
||||
id: `add-link-${focus.id}-${link.noteId}`,
|
||||
type: 'add-link',
|
||||
priority: 75,
|
||||
title: link.noteTitle || 'Untitled',
|
||||
title: pathNoteTitle(link.noteTitle, link.snippet) || excerpt(link.snippet, 80),
|
||||
description: link.snippet,
|
||||
actionKey: 'addLink',
|
||||
noteId: focus.id,
|
||||
@@ -198,7 +206,11 @@ export async function buildDashboardPaths(input: BuildPathsInput): Promise<Dashb
|
||||
id: `resurface-${freshInsight.id}`,
|
||||
type: 'resurface',
|
||||
priority: 85,
|
||||
title: `${freshInsight.note1.title || '…'} ↔ ${freshInsight.note2.title || '…'}`,
|
||||
title: (() => {
|
||||
const left = pathNoteTitle(freshInsight.note1.title, freshInsight.note1Excerpt)
|
||||
const right = pathNoteTitle(freshInsight.note2.title, freshInsight.note2Excerpt)
|
||||
return left && right ? `${left} ↔ ${right}` : excerpt(freshInsight.insight, 80)
|
||||
})(),
|
||||
description: freshInsight.insight,
|
||||
actionKey: 'openInsight',
|
||||
insightId: freshInsight.id,
|
||||
@@ -257,28 +269,6 @@ export async function buildDashboardPaths(input: BuildPathsInput): Promise<Dashb
|
||||
}
|
||||
}
|
||||
|
||||
if (input.inboxCount > 0) {
|
||||
paths.push({
|
||||
id: 'organize-inbox',
|
||||
type: 'organize',
|
||||
priority: 70,
|
||||
title: `${input.inboxCount} notes`,
|
||||
description: 'inbox',
|
||||
actionKey: 'organizeInbox',
|
||||
})
|
||||
}
|
||||
|
||||
if (input.dueFlashcards > 0) {
|
||||
paths.push({
|
||||
id: 'review-flashcards',
|
||||
type: 'review',
|
||||
priority: 65,
|
||||
title: `${input.dueFlashcards}`,
|
||||
description: 'flashcards',
|
||||
actionKey: 'reviewCards',
|
||||
})
|
||||
}
|
||||
|
||||
paths.push({
|
||||
id: 'daily-journal',
|
||||
type: 'daily',
|
||||
|
||||
Reference in New Issue
Block a user