feat: editor improvements and architectural grid prototype

Multiple feature additions and improvements across the application:

- NextGen Editor: drag handles, smart paste, block actions
- Structured views: Kanban and table layouts for notes
- Architectural Grid: new brainstorming/agent interface prototype
- Flashcards: SM-2 revision algorithm with AI generation
- MCP server: robustness improvements
- Graph/PDF chat: fix click propagation and copy behavior
- Various UI/UX enhancements and bug fixes

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Antigravity
2026-05-27 19:45:15 +00:00
parent 2de66a863d
commit f46654f574
99 changed files with 29948 additions and 919 deletions

View File

@@ -0,0 +1,78 @@
'use client'
/**
* Poignée drag globale (Novel / tiptap-extension-global-drag-handle).
* Lextension TipTap positionne cet élément ; le clic ouvre le menu bloc.
*/
import { memo, useEffect, useRef } from 'react'
import type { Editor } from '@tiptap/core'
import { BLOCK_DRAG_HANDLE_ID, resolveBlockAtDragHandle } from '@/lib/editor/block-at-drag-handle'
type EditorBlockDragHandleProps = {
editor: Editor | null
onOpenMenu: (anchorRect: DOMRect) => void
}
export const EditorBlockDragHandle = memo(function EditorBlockDragHandle({
editor,
onOpenMenu,
}: EditorBlockDragHandleProps) {
const dragStartedRef = useRef(false)
const onOpenMenuRef = useRef(onOpenMenu)
onOpenMenuRef.current = onOpenMenu
useEffect(() => {
if (!editor || editor.isDestroyed) return
const el = document.getElementById(BLOCK_DRAG_HANDLE_ID)
if (!el) return
const onPointerDown = () => {
dragStartedRef.current = false
}
const onDragStart = () => {
dragStartedRef.current = true
}
const onClick = (e: MouseEvent) => {
if (dragStartedRef.current) {
dragStartedRef.current = false
return
}
e.preventDefault()
e.stopPropagation()
const block = resolveBlockAtDragHandle(editor)
if (!block) return
onOpenMenuRef.current(el.getBoundingClientRect())
}
el.addEventListener('pointerdown', onPointerDown)
el.addEventListener('dragstart', onDragStart)
el.addEventListener('click', onClick)
return () => {
el.removeEventListener('pointerdown', onPointerDown)
el.removeEventListener('dragstart', onDragStart)
el.removeEventListener('click', onClick)
}
}, [editor])
return (
<div
id={BLOCK_DRAG_HANDLE_ID}
className="drag-handle hide"
role="button"
tabIndex={-1}
aria-hidden="true"
>
<svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor" aria-hidden="true">
<circle cx="4" cy="3" r="1.2" />
<circle cx="10" cy="3" r="1.2" />
<circle cx="4" cy="7" r="1.2" />
<circle cx="10" cy="7" r="1.2" />
<circle cx="4" cy="11" r="1.2" />
<circle cx="10" cy="11" r="1.2" />
</svg>
</div>
)
})