feat(insights): UX gap closure — i18n, network graph, recalc panel, toasts
Some checks failed
CI / Lint, Unit Tests & Build (push) Failing after 1m6s
CI / Deploy production (on server) (push) Has been skipped

Bugs fixes:
- network-graph.tsx: 2 strings hardcoded FR → props i18n (untitledLabel, resetFocusLabel)
- Panneau recalcul: clés i18n réutilisées avec mauvais sens → nouvelles clés recalcSystem.*
- 13 locales: section insightsView.* complète (67 clés × 13 = 871 traductions)

UX features:
- Hint /insights ≠ /graph (semanticGraphLegend + lien vers /graph)
- Toasts succès/échec/zéro-clusters après analyse (analysisSuccess/Failed/NoClusters)
- État vide différencié: emptyNeedMoreNotes si < 10 notes
- Tips contextuels sections Bridge Notes et Suggestions

Sprint:
- 4 epics marqués done (3,4,5,6 — toutes stories terminées)
- sprint-status.yaml last_updated corrigé
This commit is contained in:
Antigravity
2026-06-28 08:06:05 +00:00
parent 96e7902f01
commit b3fb46fc52
23 changed files with 1301 additions and 23 deletions

View File

@@ -18,7 +18,10 @@ import {
AlertCircle,
ChevronRight,
Database,
ArrowRight,
} from 'lucide-react'
import { toast } from 'sonner'
import Link from 'next/link'
interface Note {
id: string
@@ -199,6 +202,12 @@ export default function InsightsPage() {
if (res.ok) {
const data = await res.json()
const clusterCount = data.clusters?.length || 0
if (clusterCount === 0) {
toast.info(t('insightsView.analysisNoClusters'))
} else {
toast.success(t('insightsView.analysisSuccess', { count: clusterCount }))
}
const clustersWithColors = (data.clusters || []).map((c: Cluster, i: number) => ({
...c,
id: c.clusterId.toString(),
@@ -225,6 +234,7 @@ export default function InsightsPage() {
}
} catch (error) {
console.error('Error running analysis:', error)
toast.error(t('insightsView.analysisFailed'))
} finally {
setIsCalculating(false)
}
@@ -253,6 +263,12 @@ export default function InsightsPage() {
<p className="text-[10px] text-concrete tracking-[0.25em] uppercase font-bold">
{t('insightsView.subtitle')}
</p>
<div className="flex items-center gap-1.5 mt-1.5 text-[10px] text-concrete">
<span>{t('insightsView.semanticGraphLegend')}</span>
<Link href="/graph" className="inline-flex items-center gap-0.5 text-ochre hover:underline font-medium">
{t('insightsView.openGraphMap')} <ArrowRight size={9} />
</Link>
</div>
</div>
<div className="flex items-center justify-between sm:justify-end gap-3">
@@ -324,7 +340,9 @@ export default function InsightsPage() {
{t('insightsView.emptyTitle')}
</h3>
<p className="text-sm text-concrete leading-relaxed mb-6">
{t('insightsView.emptyDescription')}
{embeddingStats && embeddingStats.total < 10
? t('insightsView.emptyNeedMoreNotes', { count: 10 - embeddingStats.total })
: t('insightsView.emptyDescription')}
</p>
<button
onClick={performAnalysis}
@@ -378,6 +396,9 @@ export default function InsightsPage() {
onNoteSelect={handleNoteClick}
selectedClusterId={selectedClusterId}
onClusterSelect={setSelectedClusterId}
untitledLabel={t('insightsView.unknownNote')}
resetFocusLabel={t('insightsView.resetFocus')}
fitViewLabel={t('insightsView.fitGraphView')}
/>
</div>
@@ -508,22 +529,22 @@ export default function InsightsPage() {
<div className="flex items-center gap-2">
<Sliders size={15} className="text-ochre" />
<h4 className="text-[11px] font-black uppercase tracking-[0.2em] text-ink dark:text-dark-ink">
{t('insightsView.clusters.title')}
{t('insightsView.recalcSystem.title')}
</h4>
</div>
<span className="flex items-center gap-1 text-[9.5px] font-bold text-emerald-500 uppercase">
<CheckCircle2 size={11} /> {t('insightsView.resync')}
<CheckCircle2 size={11} /> {t('insightsView.recalcSystem.statusSynced')}
</span>
</div>
<div className="grid grid-cols-2 gap-4 pt-1">
<div className="space-y-1">
<span className="text-[9px] text-concrete block">{t('insightsView.mapping')}</span>
<span className="text-[9px] text-concrete block">{t('insightsView.recalcSystem.scheduledCron')}</span>
<p className="text-xs text-ink dark:text-dark-ink font-semibold flex items-center gap-1.5">
<Clock size={12} className="opacity-50" /> 04:00
</p>
</div>
<div className="space-y-1">
<span className="text-[9px] text-concrete block">{t('insightsView.resync')}</span>
<span className="text-[9px] text-concrete block">{t('insightsView.recalcSystem.lastSync')}</span>
<p className="text-xs text-ink dark:text-dark-ink font-bold font-mono">
{lastSyncTime || '—'}
</p>
@@ -613,6 +634,7 @@ export default function InsightsPage() {
{t('insightsView.bridgeNotes.title')}
</h3>
</div>
<p className="text-[10px] text-concrete italic px-1 -mt-2 leading-relaxed">{t('insightsView.tipBridgeNotes')}</p>
<div className="space-y-3">
{bridgeList.map(bridge => (
<motion.div
@@ -672,6 +694,7 @@ export default function InsightsPage() {
{t('insightsView.suggestions.title')}
</h3>
</div>
<p className="text-[10px] text-concrete italic px-1 -mt-2 leading-relaxed">{t('insightsView.tipSuggestions')}</p>
<div className="space-y-4">
{suggestions.map(s => (
<div