Epic 6: Stories 6-2 (Markdown roundtrip) + 6-3 (Brainstorm PPTX + Canvas)
Some checks failed
CI / Lint, Unit Tests & Build (push) Successful in 5m37s
CI / Deploy production (on server) (push) Has been cancelled

Story 6-2 — Markdown roundtrip export/import:
- lib/editor/markdown-export.ts: tiptapHTMLToMarkdown, markdownToHTML, looksLikeMarkdown
- lib/editor/markdown-paste-extension.ts: TipTap extension paste Markdown → blocs
- note-editor-toolbar.tsx: export .md + import .md (file picker)
- rich-text-editor.tsx: intégration MarkdownPasteExtension
- 40 tests unitaires markdown-export.test.ts

Story 6-3 — Brainstorm PPTX + Canvas:
- lib/brainstorm/export-pptx.ts: génération PPTX 5 slides (pptxgenjs)
- app/api/brainstorm/[sessionId]/export-pptx/route.ts: route POST protégée
- brainstorm-page.tsx: bouton PPTX, auto-select session, fix emoji, fix router.replace
- wave-canvas.tsx: fitTrigger recentrage, légende bas-droite

Onboarding activation wizard (Story 6-1):
- components/onboarding/: wizard multi-étapes, hints éditeur
- app/api/onboarding/: route PATCH onboarding
- prisma/migrations: champs onboarding user

Locales: 15 langues mises à jour (brainstorm, markdown, onboarding keys)
Sprint: 6-1 done, 6-2 review, 6-3 review

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Antigravity
2026-05-29 11:24:56 +00:00
parent dae56187fc
commit 6b4ed8514f
49 changed files with 5215 additions and 66 deletions

View File

@@ -14,6 +14,7 @@ interface WaveCanvasProps {
remoteMove?: { ideaId: string; x: number; y: number; _seq: number } | null
manualEditTrigger?: number
playbackIdeas?: any[] | null
fitTrigger?: number
}
const WAVE_COLORS: Record<number, string> = {
@@ -31,6 +32,7 @@ export const WaveCanvas: React.FC<WaveCanvasProps> = ({
remoteMove,
manualEditTrigger,
playbackIdeas,
fitTrigger,
}) => {
const { t, language } = useLanguage()
const svgRef = useRef<SVGSVGElement>(null)
@@ -39,6 +41,8 @@ export const WaveCanvas: React.FC<WaveCanvasProps> = ({
const linkRef = useRef<d3.Selection<SVGLineElement, any, SVGGElement, unknown> | null>(null)
const simulationRef = useRef<d3.Simulation<any, any> | null>(null)
const transformRef = useRef<d3.ZoomTransform>(d3.zoomIdentity)
const zoomRef = useRef<d3.ZoomBehavior<SVGSVGElement, unknown> | null>(null)
const centerRef = useRef<{ x: number; y: number; scale: number }>({ x: 0, y: 0, scale: 0.8 })
const onNodeSelectRef = useRef(onNodeSelect)
onNodeSelectRef.current = onNodeSelect
@@ -167,6 +171,9 @@ export const WaveCanvas: React.FC<WaveCanvasProps> = ({
transformRef.current = event.transform
})
zoomRef.current = zoom
centerRef.current = { x: centerX, y: centerY, scale: 0.8 }
svg.call(zoom)
svg.call(zoom.transform, d3.zoomIdentity.translate(centerX, centerY).scale(0.8))
@@ -517,6 +524,16 @@ export const WaveCanvas: React.FC<WaveCanvasProps> = ({
node.attr('transform', (d: any) => `translate(${d.x},${d.y})`)
}, [remoteMove])
// Fit-to-screen: re-center when fitTrigger increments
useEffect(() => {
if (!fitTrigger || !svgRef.current || !zoomRef.current) return
const { x, y, scale } = centerRef.current
d3.select(svgRef.current)
.transition()
.duration(500)
.call(zoomRef.current.transform, d3.zoomIdentity.translate(x, y).scale(scale))
}, [fitTrigger])
return (
<div
ref={containerRef}
@@ -524,6 +541,21 @@ export const WaveCanvas: React.FC<WaveCanvasProps> = ({
>
<svg ref={svgRef} className="w-full h-full" />
{/* Legend overlay — bottom right */}
<div className="absolute bottom-6 right-6 pointer-events-none flex flex-col gap-1 bg-white/80 dark:bg-[#1A1A1A]/80 backdrop-blur-sm rounded-xl px-3 py-2.5 shadow-sm border border-black/[0.06] dark:border-white/[0.06]">
{[
{ color: '#fb923c', label: t('brainstorm.legendWave1') || 'Variations' },
{ color: '#60a5fa', label: t('brainstorm.legendWave2') || 'Analogies' },
{ color: '#a78bfa', label: t('brainstorm.legendWave3') || 'Disruptions' },
{ color: '#10b981', label: t('brainstorm.legendConverted') || 'Convertie' },
].map(({ color, label }) => (
<div key={label} className="flex items-center gap-2">
<div className="w-2.5 h-2.5 rounded-full flex-shrink-0" style={{ backgroundColor: color }} />
<span className="text-[10px] text-foreground/50 font-medium">{label}</span>
</div>
))}
</div>
{editingNode && (
<div
className="absolute z-50 pointer-events-auto"