From 056b0260cfeba9b76413d2c412d77dd3638779b6 Mon Sep 17 00:00:00 2001 From: Antigravity Date: Sun, 28 Jun 2026 09:24:34 +0000 Subject: [PATCH] =?UTF-8?q?fix(insights):=20a11y=20+=20UX=20Pro=20Max=20au?= =?UTF-8?q?dit=20=E2=80=94=20accessible=20list=20view,=20reduced-motion,?= =?UTF-8?q?=20focus,=20lazy-load?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Accessibility (CRITIQUE per UI/UX Pro Max skill): - NetworkGraph Accessibility Grade D → added accessible List view alternative (toggle Graph/List with cluster→notes table, keyboard navigable) - aria-label text summary on graph container for screen readers - role=button + tabIndex + onKeyDown on bridge note cards (keyboard accessible) - focus-visible:ring on all interactive cards (isolated clusters, bridges, list items) UX (HIGH): - prefers-reduced-motion: whileHover disabled when user prefers reduced motion - cursor-pointer verified + focus-visible:ring-ochre on all clickable cards - Mobile sidebar: hamburger Menu button in header (dispatches open-mobile-sidebar) Performance (MEDIUM): - NetworkGraph lazy-loaded via next/dynamic (D3 ~200KB deferred, ssr:false) - Loading spinner shown while D3 chunk loads i18n: - listView, graphAriaLabel, listAriaLabel added to 15 locales --- .cursor/hooks/state/continual-learning.json | 4 +- memento-note/app/(main)/insights/page.tsx | 160 +++++++++++++++++--- memento-note/locales/ar.json | 5 +- memento-note/locales/de.json | 5 +- memento-note/locales/en.json | 5 +- memento-note/locales/es.json | 5 +- memento-note/locales/fa.json | 5 +- memento-note/locales/fr.json | 5 +- memento-note/locales/hi.json | 5 +- memento-note/locales/it.json | 5 +- memento-note/locales/ja.json | 5 +- memento-note/locales/ko.json | 5 +- memento-note/locales/nl.json | 5 +- memento-note/locales/pl.json | 5 +- memento-note/locales/pt.json | 5 +- memento-note/locales/ru.json | 5 +- memento-note/locales/zh.json | 5 +- 17 files changed, 200 insertions(+), 39 deletions(-) diff --git a/.cursor/hooks/state/continual-learning.json b/.cursor/hooks/state/continual-learning.json index b511d4c..2d5d3a5 100644 --- a/.cursor/hooks/state/continual-learning.json +++ b/.cursor/hooks/state/continual-learning.json @@ -1,8 +1,8 @@ { "version": 1, "lastRunAtMs": 1782633053032, - "turnsSinceLastRun": 1, + "turnsSinceLastRun": 6, "lastTranscriptMtimeMs": 1782633052959.9294, - "lastProcessedGenerationId": "17310ec7-5a0e-406b-a1a4-75a7c0439ee3", + "lastProcessedGenerationId": "fa1ae817-7a99-4fd6-8b50-8407600557dd", "trialStartedAtMs": null } diff --git a/memento-note/app/(main)/insights/page.tsx b/memento-note/app/(main)/insights/page.tsx index 85f3690..4ddf4bf 100644 --- a/memento-note/app/(main)/insights/page.tsx +++ b/memento-note/app/(main)/insights/page.tsx @@ -1,10 +1,10 @@ 'use client' import { useState, useEffect, useMemo, useCallback } from 'react' -import { NetworkGraph } from '@/components/network-graph' +import dynamic from 'next/dynamic' import { useRouter } from 'next/navigation' import { useLanguage } from '@/lib/i18n' -import { motion, AnimatePresence } from 'motion/react' +import { motion, AnimatePresence, useReducedMotion } from 'motion/react' import { Sparkles, RefreshCw, @@ -19,10 +19,25 @@ import { ChevronRight, Database, ArrowRight, + Menu, + Network, + List, } from 'lucide-react' import { toast } from 'sonner' import Link from 'next/link' +const NetworkGraph = dynamic( + () => import('@/components/network-graph').then(m => ({ default: m.NetworkGraph })), + { + loading: () => ( +
+ +
+ ), + ssr: false, + } +) + interface Note { id: string title: string | null @@ -82,7 +97,9 @@ export default function InsightsPage() { const [isStale, setIsStale] = useState(false) const [selectedClusterId, setSelectedClusterId] = useState(null) const [viewMode, setViewMode] = useState<'graph' | 'dashboard'>('dashboard') + const [graphMode, setGraphMode] = useState<'visual' | 'list'>('visual') const [lastSyncTime, setLastSyncTime] = useState('') + const prefersReducedMotion = useReducedMotion() useEffect(() => { loadInitialData() @@ -244,14 +261,26 @@ export default function InsightsPage() { router.push(`/home?openNote=${noteId}`) } - // ─── Rendu ─────────────────────────────────────────────────────────────────── + const motionConfig = prefersReducedMotion + ? { initial: false as const, animate: { opacity: 1, y: 0 }, transition: { duration: 0 } } + : {} + + // ─── Rendu ─────────────────────────────────────────────────────────────────────────── return (
{/* ── Header ── */}
-
+
+ +
@@ -269,6 +298,7 @@ export default function InsightsPage() { {t('insightsView.openGraphMap')}
+
@@ -383,23 +413,106 @@ export default function InsightsPage() { {!loading && clusters.length > 0 && !isCalculating && (
- {/* ── Graphe (gauche) ── */} + {/* ── Graphe / Liste accessible (gauche) ── */}
- + {/* Toggle visual/list accessible */} +
+ + +
+ + {/* Vue visuelle (D3) */} + {graphMode === 'visual' && ( +
+ +
+ )} + + {/* Vue liste accessible (a11y fallback) */} + {graphMode === 'list' && ( +
+ {clusters.map(cluster => { + const clusterNotes = notes.filter(n => cluster.noteIds.includes(n.id)) + const clusterBridges = bridgeNotes.filter(b => + b.clustersConnected?.some(cid => String(cid) === cluster.id) + ) + return ( +
+
+
+

+ {cluster.name || t('insightsView.clusterFallback', { index: cluster.clusterId })} +

+ + {clusterNotes.length} {t('insightsView.graphNotesLabel')} + {clusterBridges.length > 0 && ` · ${clusterBridges.length} ${t('insightsView.bridgeCount')}`} + +
+
    + {clusterNotes.map(note => ( +
  • + +
  • + ))} +
+
+ ) + })} +
+ )}
{/* ── Dashboard (droite) ── */} @@ -603,9 +716,9 @@ export default function InsightsPage() { {isolatedClusters.map(c => ( setSelectedClusterId(c.id)} - className="p-3.5 rounded-xl bg-white dark:bg-zinc-800 border border-border/30 hover:border-black/10 dark:hover:border-white/10 flex items-center justify-between cursor-pointer transition-all" + className="p-3.5 rounded-xl bg-white dark:bg-zinc-800 border border-border/30 hover:border-black/10 dark:hover:border-white/10 flex items-center justify-between cursor-pointer transition-all focus-visible:ring-2 focus-visible:ring-ochre/50 focus-visible:outline-none" >
@@ -639,9 +752,12 @@ export default function InsightsPage() { {bridgeList.map(bridge => ( handleNoteClick(bridge.noteId)} - className="p-4 rounded-xl bg-white dark:bg-zinc-800 border border-border/30 hover:border-ochre/40 hover:shadow-sm transition-all cursor-pointer group" + className="p-4 rounded-xl bg-white dark:bg-zinc-800 border border-border/30 hover:border-ochre/40 hover:shadow-sm transition-all cursor-pointer group focus-visible:ring-2 focus-visible:ring-ochre/50 focus-visible:outline-none" + tabIndex={0} + role="button" + onKeyDown={e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleNoteClick(bridge.noteId) } }} >

diff --git a/memento-note/locales/ar.json b/memento-note/locales/ar.json index 9977c8a..263a506 100644 --- a/memento-note/locales/ar.json +++ b/memento-note/locales/ar.json @@ -3002,6 +3002,9 @@ "scheduledCron": "مجدول", "lastSync": "آخر مزامنة" }, - "resetFocus": "إعادة ضبط التركيز" + "resetFocus": "إعادة ضبط التركيز", + "listView": "List", + "graphAriaLabel": "Semantic network: {clusters} clusters, {notes} notes, {bridges} bridge notes. Switch to List view for accessible navigation.", + "listAriaLabel": "Accessible cluster list with notes and bridge connections" } } diff --git a/memento-note/locales/de.json b/memento-note/locales/de.json index d6ed981..840ab18 100644 --- a/memento-note/locales/de.json +++ b/memento-note/locales/de.json @@ -3002,6 +3002,9 @@ "scheduledCron": "Geplant", "lastSync": "Letzte Sync" }, - "resetFocus": "Fokus zurücksetzen" + "resetFocus": "Fokus zurücksetzen", + "listView": "List", + "graphAriaLabel": "Semantic network: {clusters} clusters, {notes} notes, {bridges} bridge notes. Switch to List view for accessible navigation.", + "listAriaLabel": "Accessible cluster list with notes and bridge connections" } } diff --git a/memento-note/locales/en.json b/memento-note/locales/en.json index 9e89ce5..a92ed35 100644 --- a/memento-note/locales/en.json +++ b/memento-note/locales/en.json @@ -3532,7 +3532,10 @@ "scheduledCron": "Scheduled", "lastSync": "Last sync" }, - "resetFocus": "Reset focus" + "resetFocus": "Reset focus", + "listView": "List", + "graphAriaLabel": "Semantic network: {clusters} clusters, {notes} notes, {bridges} bridge notes. Switch to List view for accessible navigation.", + "listAriaLabel": "Accessible cluster list with notes and bridge connections" }, "consent": { "banner": { diff --git a/memento-note/locales/es.json b/memento-note/locales/es.json index 097745c..7cddc2b 100644 --- a/memento-note/locales/es.json +++ b/memento-note/locales/es.json @@ -3002,6 +3002,9 @@ "scheduledCron": "Programado", "lastSync": "Última sync" }, - "resetFocus": "Restablecer enfoque" + "resetFocus": "Restablecer enfoque", + "listView": "List", + "graphAriaLabel": "Semantic network: {clusters} clusters, {notes} notes, {bridges} bridge notes. Switch to List view for accessible navigation.", + "listAriaLabel": "Accessible cluster list with notes and bridge connections" } } diff --git a/memento-note/locales/fa.json b/memento-note/locales/fa.json index 8e32b5c..49cf1fb 100644 --- a/memento-note/locales/fa.json +++ b/memento-note/locales/fa.json @@ -3041,6 +3041,9 @@ "scheduledCron": "برنامه‌ریزی‌شده", "lastSync": "آخرین همگام‌سازی" }, - "resetFocus": "بازنشانی تمرکز" + "resetFocus": "بازنشانی تمرکز", + "listView": "List", + "graphAriaLabel": "Semantic network: {clusters} clusters, {notes} notes, {bridges} bridge notes. Switch to List view for accessible navigation.", + "listAriaLabel": "Accessible cluster list with notes and bridge connections" } } diff --git a/memento-note/locales/fr.json b/memento-note/locales/fr.json index b2aeac8..ff21074 100644 --- a/memento-note/locales/fr.json +++ b/memento-note/locales/fr.json @@ -3536,7 +3536,10 @@ "scheduledCron": "CRON planifié", "lastSync": "Dernière synchro" }, - "resetFocus": "Réinitialiser focus" + "resetFocus": "Réinitialiser focus", + "listView": "Liste", + "graphAriaLabel": "Réseau sémantique : {clusters} thèmes, {notes} notes, {bridges} notes-ponts. Basculez en vue Liste pour une navigation accessible.", + "listAriaLabel": "Liste accessible des clusters avec notes et connexions ponts" }, "consent": { "banner": { diff --git a/memento-note/locales/hi.json b/memento-note/locales/hi.json index c8fd8bd..b06b0eb 100644 --- a/memento-note/locales/hi.json +++ b/memento-note/locales/hi.json @@ -3002,6 +3002,9 @@ "scheduledCron": "अनुसूचित", "lastSync": "अंतिम सिंक" }, - "resetFocus": "फोकस रीसेट करें" + "resetFocus": "फोकस रीसेट करें", + "listView": "List", + "graphAriaLabel": "Semantic network: {clusters} clusters, {notes} notes, {bridges} bridge notes. Switch to List view for accessible navigation.", + "listAriaLabel": "Accessible cluster list with notes and bridge connections" } } diff --git a/memento-note/locales/it.json b/memento-note/locales/it.json index 53e95ec..dd30710 100644 --- a/memento-note/locales/it.json +++ b/memento-note/locales/it.json @@ -3002,6 +3002,9 @@ "scheduledCron": "Programmato", "lastSync": "Ultima sync" }, - "resetFocus": "Reimposta focus" + "resetFocus": "Reimposta focus", + "listView": "List", + "graphAriaLabel": "Semantic network: {clusters} clusters, {notes} notes, {bridges} bridge notes. Switch to List view for accessible navigation.", + "listAriaLabel": "Accessible cluster list with notes and bridge connections" } } diff --git a/memento-note/locales/ja.json b/memento-note/locales/ja.json index b02ab8d..9290537 100644 --- a/memento-note/locales/ja.json +++ b/memento-note/locales/ja.json @@ -3002,6 +3002,9 @@ "scheduledCron": "予定済み", "lastSync": "前回同期" }, - "resetFocus": "フォーカス解除" + "resetFocus": "フォーカス解除", + "listView": "List", + "graphAriaLabel": "Semantic network: {clusters} clusters, {notes} notes, {bridges} bridge notes. Switch to List view for accessible navigation.", + "listAriaLabel": "Accessible cluster list with notes and bridge connections" } } diff --git a/memento-note/locales/ko.json b/memento-note/locales/ko.json index db51b3e..1a3a723 100644 --- a/memento-note/locales/ko.json +++ b/memento-note/locales/ko.json @@ -3002,6 +3002,9 @@ "scheduledCron": "예약됨", "lastSync": "마지막 동기화" }, - "resetFocus": "포커스 해제" + "resetFocus": "포커스 해제", + "listView": "List", + "graphAriaLabel": "Semantic network: {clusters} clusters, {notes} notes, {bridges} bridge notes. Switch to List view for accessible navigation.", + "listAriaLabel": "Accessible cluster list with notes and bridge connections" } } diff --git a/memento-note/locales/nl.json b/memento-note/locales/nl.json index 66fac37..2b18174 100644 --- a/memento-note/locales/nl.json +++ b/memento-note/locales/nl.json @@ -3002,6 +3002,9 @@ "scheduledCron": "Gepland", "lastSync": "Laatste sync" }, - "resetFocus": "Focus resetten" + "resetFocus": "Focus resetten", + "listView": "List", + "graphAriaLabel": "Semantic network: {clusters} clusters, {notes} notes, {bridges} bridge notes. Switch to List view for accessible navigation.", + "listAriaLabel": "Accessible cluster list with notes and bridge connections" } } diff --git a/memento-note/locales/pl.json b/memento-note/locales/pl.json index 9e93935..23c086e 100644 --- a/memento-note/locales/pl.json +++ b/memento-note/locales/pl.json @@ -3002,6 +3002,9 @@ "scheduledCron": "Zaplanowano", "lastSync": "Ostatnia sync" }, - "resetFocus": "Resetuj fokus" + "resetFocus": "Resetuj fokus", + "listView": "List", + "graphAriaLabel": "Semantic network: {clusters} clusters, {notes} notes, {bridges} bridge notes. Switch to List view for accessible navigation.", + "listAriaLabel": "Accessible cluster list with notes and bridge connections" } } diff --git a/memento-note/locales/pt.json b/memento-note/locales/pt.json index 8898510..2ed3acf 100644 --- a/memento-note/locales/pt.json +++ b/memento-note/locales/pt.json @@ -3002,6 +3002,9 @@ "scheduledCron": "Programado", "lastSync": "Última sync" }, - "resetFocus": "Repor foco" + "resetFocus": "Repor foco", + "listView": "List", + "graphAriaLabel": "Semantic network: {clusters} clusters, {notes} notes, {bridges} bridge notes. Switch to List view for accessible navigation.", + "listAriaLabel": "Accessible cluster list with notes and bridge connections" } } diff --git a/memento-note/locales/ru.json b/memento-note/locales/ru.json index 5bb81a2..8a5fc3c 100644 --- a/memento-note/locales/ru.json +++ b/memento-note/locales/ru.json @@ -3002,6 +3002,9 @@ "scheduledCron": "Запланировано", "lastSync": "Последняя sync" }, - "resetFocus": "Сбросить фокус" + "resetFocus": "Сбросить фокус", + "listView": "List", + "graphAriaLabel": "Semantic network: {clusters} clusters, {notes} notes, {bridges} bridge notes. Switch to List view for accessible navigation.", + "listAriaLabel": "Accessible cluster list with notes and bridge connections" } } diff --git a/memento-note/locales/zh.json b/memento-note/locales/zh.json index 656d099..4bd4c40 100644 --- a/memento-note/locales/zh.json +++ b/memento-note/locales/zh.json @@ -3002,6 +3002,9 @@ "scheduledCron": "已计划", "lastSync": "上次同步" }, - "resetFocus": "重置焦点" + "resetFocus": "重置焦点", + "listView": "List", + "graphAriaLabel": "Semantic network: {clusters} clusters, {notes} notes, {bridges} bridge notes. Switch to List view for accessible navigation.", + "listAriaLabel": "Accessible cluster list with notes and bridge connections" } }