auto-save 2026-05-14 03:26 (~3)

This commit is contained in:
2026-05-14 03:26:17 +08:00
parent 2144c374bd
commit 65f81efec8
3 changed files with 37 additions and 34 deletions

View File

@@ -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
}
]
}

View File

@@ -817,6 +817,18 @@ api/main.py
<h2>变更记录</h2>
<p>这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。</p>
<div class="changelog">
<article class="change">
<header>
<h3>2026-05-14 · 吸附工作面板贴近视口边缘</h3>
<span class="tag violet">Canvas Panel</span>
<span class="tag blue">Dock</span>
</header>
<div class="body">
<p><strong>问题:</strong>视频抽帧面板吸附左侧 / 右侧时顶部仍留出明显空白;这不是实际板块遮挡,而是面板吸附样式里硬编码了 <code>top: 72</code> 和对应的高度预留。关键帧面板也保留了旧 storyboard 顶栏避让逻辑。</p>
<p><strong>改动:</strong>新增统一吸附边距常量,视频抽帧面板和关键帧详情面板吸附时都贴近视口边缘,仅保留 8px 安全边距;移除关键帧面板对旧 <code>data-storyboard-dock</code> / <code>data-storyboard-bar</code> 的避让查询。</p>
<p><strong>影响:</strong><code>web/components/nodes/index.tsx</code><code>docs/source-analysis.html</code>。后续画布工作面板的吸附语义统一为“贴边”,不是为顶部旧板块预留空间。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-14 · 输入视频双击改为画布抽帧面板</h3>

View File

@@ -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(
<div className="fixed z-[240]" style={fixedStyle}>
{panel}
@@ -1486,39 +1487,16 @@ export function KeyframePanelNode({ data }: any) {
const d: NodeData = data
const { getZoom } = useReactFlow()
const panelRef = useRef<HTMLDivElement>(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<HTMLElement>('[data-storyboard-dock="true"]')
const bar = document.querySelector<HTMLElement>('[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<HTMLElement>('[data-storyboard-dock="true"]')
?? document.querySelector<HTMLElement>('[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)
}