diff --git a/.memory/worklog.json b/.memory/worklog.json index 8693e4f..974f79a 100644 --- a/.memory/worklog.json +++ b/.memory/worklog.json @@ -3037,6 +3037,19 @@ "type": "session-heartbeat", "message": "Codex 会话活跃 · 最近命令:codex · 3 项未提交变更 · 最近提交:auto-save 2026-05-14 03:14 (~2)", "files_changed": 3 + }, + { + "ts": "2026-05-14T03:20:46+08:00", + "type": "commit", + "message": "auto-save 2026-05-14 03:20 (~4)", + "hash": "2144c37", + "files_changed": 4 + }, + { + "ts": "2026-05-13T19:23:12Z", + "type": "session-heartbeat", + "message": "Claude 会话活跃 · 最近命令:claude · 1 项未提交变更 · 最近提交:auto-save 2026-05-14 03:20 (~4)", + "files_changed": 1 } ] } diff --git a/docs/source-analysis.html b/docs/source-analysis.html index 3e4aef7..c2209fd 100644 --- a/docs/source-analysis.html +++ b/docs/source-analysis.html @@ -817,6 +817,18 @@ api/main.py

变更记录

这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。

+
+
+

2026-05-14 · 吸附工作面板贴近视口边缘

+ Canvas Panel + Dock +
+
+

问题:视频抽帧面板吸附左侧 / 右侧时顶部仍留出明显空白;这不是实际板块遮挡,而是面板吸附样式里硬编码了 top: 72 和对应的高度预留。关键帧面板也保留了旧 storyboard 顶栏避让逻辑。

+

改动:新增统一吸附边距常量,视频抽帧面板和关键帧详情面板吸附时都贴近视口边缘,仅保留 8px 安全边距;移除关键帧面板对旧 data-storyboard-dock / data-storyboard-bar 的避让查询。

+

影响:web/components/nodes/index.tsxdocs/source-analysis.html。后续画布工作面板的吸附语义统一为“贴边”,不是为顶部旧板块预留空间。

+
+

2026-05-14 · 输入视频双击改为画布抽帧面板

diff --git a/web/components/nodes/index.tsx b/web/components/nodes/index.tsx index 9e62a49..35aa44c 100644 --- a/web/components/nodes/index.tsx +++ b/web/components/nodes/index.tsx @@ -115,6 +115,7 @@ function clamp(value: number, min: number, max: number) { } const THUMBNAIL_HEIGHT = 176 +const FLOATING_PANEL_EDGE_INSET = 8 function canvasThumbnailAnchor(root: HTMLDivElement | null, target: HTMLElement) { if (!root) return { x: 160, y: 0 } @@ -727,7 +728,7 @@ export function VideoFramePanelNode({ data }: any) { width: panelWidth, height: panelHeight, maxWidth: "calc(100vw - 32px)", - maxHeight: "calc(100vh - 84px)", + maxHeight: `calc(100vh - ${FLOATING_PANEL_EDGE_INSET * 2}px)`, boxShadow: "0 30px 80px -20px rgba(0,0,0,0.75), 0 0 0 1px rgba(255,255,255,0.05)", }} > @@ -858,10 +859,10 @@ export function VideoFramePanelNode({ data }: any) { if (docked && typeof document !== "undefined") { const fixedStyle = dock === "left" - ? { left: 16, top: 72 } + ? { left: FLOATING_PANEL_EDGE_INSET, top: FLOATING_PANEL_EDGE_INSET } : dock === "right" - ? { right: 16, top: 72 } - : { left: "50%", bottom: 16, transform: "translateX(-50%)" } + ? { right: FLOATING_PANEL_EDGE_INSET, top: FLOATING_PANEL_EDGE_INSET } + : { left: "50%", bottom: FLOATING_PANEL_EDGE_INSET, transform: "translateX(-50%)" } return createPortal(
{panel} @@ -1486,39 +1487,16 @@ export function KeyframePanelNode({ data }: any) { const d: NodeData = data const { getZoom } = useReactFlow() const panelRef = useRef(null) - const [pinRect, setPinRect] = useState<{ left: number; top: number }>({ left: 24, top: 72 }) + const [pinRect, setPinRect] = useState<{ left: number; top: number }>({ + left: FLOATING_PANEL_EDGE_INSET, + top: FLOATING_PANEL_EDGE_INSET, + }) const scale = d.framePanelScale ?? 1 const pinned = d.framePanelPinned ?? false - const getStoryboardDockTop = () => { - if (typeof window === "undefined") return 64 - const dock = document.querySelector('[data-storyboard-dock="true"]') - const bar = document.querySelector('[data-storyboard-bar="true"]') - const bottom = (dock ?? bar)?.getBoundingClientRect().bottom ?? 52 - return Math.max(56, Math.min(window.innerHeight - 120, bottom + 10)) - } - useEffect(() => { - if (!pinned || typeof window === "undefined") return - - const syncDock = () => { - setPinRect({ left: 16, top: getStoryboardDockTop() }) - } - - syncDock() - const bar = document.querySelector('[data-storyboard-dock="true"]') - ?? document.querySelector('[data-storyboard-bar="true"]') - let observer: ResizeObserver | null = null - if (bar && "ResizeObserver" in window) { - observer = new ResizeObserver(syncDock) - observer.observe(bar) - } - window.addEventListener("resize", syncDock) - - return () => { - observer?.disconnect() - window.removeEventListener("resize", syncDock) - } + if (!pinned) return + setPinRect({ left: FLOATING_PANEL_EDGE_INSET, top: FLOATING_PANEL_EDGE_INSET }) }, [pinned]) if (!d.job || d.expandedFrame === null) return null @@ -1535,7 +1513,7 @@ export function KeyframePanelNode({ data }: any) { if (!pinned) { const zoom = getZoom() setScale(scale * zoom) - setPinRect({ left: 16, top: getStoryboardDockTop() }) + setPinRect({ left: FLOATING_PANEL_EDGE_INSET, top: FLOATING_PANEL_EDGE_INSET }) } d.onFramePanelPinnedChange?.(!pinned) }