From dddf9eb9c1cbe5af4f2349fc7d9c8c7dca78b745 Mon Sep 17 00:00:00 2001 From: lix-2026 Date: Mon, 25 May 2026 05:06:28 +0800 Subject: [PATCH] refactor: extract drop indicator geometry --- ...0-browser-runtime-followup-checklist-v1.md | 2 + ...29-editor-runtime-followup-checklist-v1.md | 13 +++++- .../mnote-leptos-tiptap-spike-island_bg.wasm | Bin 2122747 -> 2122725 bytes .../src/editor_runtime/block_dnd.rs | 39 +++++++++++++++++- rust/spikes/leptos-tiptap-spike/src/lib.rs | 19 ++++----- 5 files changed, 61 insertions(+), 12 deletions(-) diff --git a/design/03-rust-web/process/3-20-browser-runtime-followup-checklist-v1.md b/design/03-rust-web/process/3-20-browser-runtime-followup-checklist-v1.md index 09cf49ff..c41284e5 100644 --- a/design/03-rust-web/process/3-20-browser-runtime-followup-checklist-v1.md +++ b/design/03-rust-web/process/3-20-browser-runtime-followup-checklist-v1.md @@ -57,6 +57,7 @@ UI / 浏览器可见项必须有真实浏览器截图或结构化 smoke 证据 - Reasonix 结果必须先查 Hindsight `reasonix` recall,再读 `process-handoff.md/json`、`result.json`、diff 和验证证据。 - 只采纳限定文件范围内的最小 diff;越界重构、回滚用户改动、修改 git 状态的一律拒绝。 +- 实现 worker 必须在独立 worktree 产出候选 patch;Codex 主工作区只维护事实源、复核证据和最终选择性合入。 - browser smoke worker 必须 `modified_files=[]`。 - 本文件只记录真实完成项;未验证项保持未勾选。 @@ -144,6 +145,7 @@ UI / 浏览器可见项必须有真实浏览器截图或结构化 smoke 证据 - 2026-05-25:Batch J Codex 复核结论:Worker C / D 对应边界已在当前 HEAD 中具备,未重复合入新 diff。Worker C 已验证 `node --check rust/crates/mnote-web/browser/filetree-runtime.js`、`cargo check --manifest-path rust/Cargo.toml -p mnote-web`、`cargo test --manifest-path rust/Cargo.toml -p mnote-web filetree_runtime_contains_row_accessor_helpers -- --test-threads=1`;Worker D 已验证 `node --check rust/crates/mnote-web/browser/document-conflict-panel-runtime.js`、`cargo check --manifest-path rust/Cargo.toml -p mnote-web`、`cargo test --manifest-path rust/Cargo.toml -p mnote-web document_conflict_panel_runtime_contains_dom_helpers -- --test-threads=1`。 - 主工作区 smoke:`NODE_PATH=/mnt/Data1T/mnote/node_modules node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js` 通过,截图证据 `/tmp/mnote-task479-lifecycle-xlAZrs/screenshots-1779650673787/final-state.png`;`NODE_PATH=/mnt/Data1T/mnote/node_modules node scripts/task451-local-markdown-conflict-resolution-ui-smoke.js` 通过,结果 `/mnt/Data1T/mnote/tmp/task451-local-markdown-conflict-resolution-ui-smoke/result.json`。 - 2026-05-25:当前 goal 明确为每批最多 2 个 worker;后续 3-20 派发以 2 个为硬上限,历史记录中超过 2 个 worker 的批次保留为异常记录。 +- 2026-05-25:用户纠正 B/C/D 等实现任务也应在新的 worktree 中修改后再由 Codex 合并;主控已采用该口径:实现 worker 一律独立 worktree,主工作区不再直接扩大实现 diff,只保留事实源、复核和最终采纳。 - 2026-05-25:Batch L Worker B `reasonix-2026-05-24T19-30-30-509Z-18f62949` 只读审计 `refreshLocalFolderSidebarSnapshot`;Codex 已读取 Hindsight recall、`process-handoff.md/json`、`result.json`,并核对 worktree diff 为空。 - 审计结论:`refreshLocalFolderSidebarSnapshot` 仍是 shell 编排,包含 workspace/root/document 状态、两条 projection fetch、render 后副作用和 watcher 标记,不应整体迁入 `filetree-runtime.js`。 - 下一刀建议:优先迁投影数据读取器层(如 `readProjection`、`readDatasetProjection`、`projectionItems`、row/id/title/file accessor、`groupRowsByParent`),或更保守地先迁未使用的 `replaceSidebarTreeFromDocument`;Codex 本轮不直接实现 3-20 代码,只记录候选。 diff --git a/design/05-editor-mainline/process/5-29-editor-runtime-followup-checklist-v1.md b/design/05-editor-mainline/process/5-29-editor-runtime-followup-checklist-v1.md index 233a453d..7db6a646 100644 --- a/design/05-editor-mainline/process/5-29-editor-runtime-followup-checklist-v1.md +++ b/design/05-editor-mainline/process/5-29-editor-runtime-followup-checklist-v1.md @@ -56,7 +56,9 @@ UI / 浏览器可见项必须有真实浏览器截图或结构化 smoke 证据 ## 5. 采纳规则 - Reasonix 结果必须先查 Hindsight `reasonix` recall,再读 `process-handoff.md/json`、`result.json`、diff 和验证证据。 -- 实现 worker 只能改允许范围;只读和 smoke worker 必须没有源码 diff。 +- 实现 worker 必须在独立 worktree 产出候选 patch;只读和 smoke worker 必须没有源码 diff。 +- Codex 主工作区只维护事实源、复核证据和最终选择性合入,不直接承接 B/C/D 等实现 worker 的原始修改过程。 +- 实现 worker 只能改允许范围。 - `lib.rs` 不能新增附件/history/DOM bridge 大块逻辑。 - 本文件只记录真实完成项;未验证项保持未勾选。 @@ -135,6 +137,7 @@ UI / 浏览器可见项必须有真实浏览器截图或结构化 smoke 证据 - 已验证:在独立 worktree 中运行 `cargo fmt --manifest-path rust/Cargo.toml --all --check`、`cargo check --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml`、wasm release build、`wasm-bindgen`;主工作区复跑 `NODE_PATH=/mnt/Data1T/mnote/node_modules node scripts/task489-block-menu-delete-undo-smoke.js`、`NODE_PATH=/mnt/Data1T/mnote/node_modules node scripts/task488-local-attachment-link-delete-undo-smoke.js` 均通过,结果分别位于 `/mnt/Data1T/mnote/tmp/task489-block-menu-delete-undo-smoke/result.json`、`/mnt/Data1T/mnote/tmp/task488-local-attachment-link-delete-undo-smoke/result.json`。 - 未完成:`try_sync_editor_overlay_state`、`on_selection_change` overlay 同步、block menu signal 编排仍未迁出;本轮未扩大迁移边界。 - 2026-05-25:当前 goal 明确为每批最多 2 个 worker;后续 Batch 计划和 Reasonix 派发以 2 个为硬上限,历史记录中超过 2 个 worker 的批次保留为异常记录。 +- 2026-05-25:用户纠正 B/C/D 等实现任务也应在新的 worktree 中修改后再由 Codex 合并;主控已采用该口径:实现 worker 一律独立 worktree,主工作区不再直接扩大实现 diff,只保留事实源、复核和最终采纳。 - 2026-05-25:Batch K 只派发 1 个只读 Reasonix worker 审计 `close_editor_floating_overlays` 迁出边界,使用独立 worktree `/mnt/Data1T/mnote-worktrees/0525-k-5-29-close-overlays-readonly`,run id `reasonix-2026-05-24T19-18-51-566Z-90f11977`,并显式使用 `--no-memory-recall` 降低任务串线风险。 - Codex 已读取 Hindsight recall、`process-handoff.md/json`、`result.json`,并核对 worktree `git status --short` / `git diff --stat` 为空。 - 审计结论:先把 `ImageToolbarAnchor`、`TableOverlayAnchor`、`TableSelectionKind`、`TableSelectionOverlayState` 迁入 `editor_runtime/overlays.rs`,再迁出纯 WriteSignal setter `close_editor_floating_overlays`;`close_editor_floating_overlays_if_escape`、`open_slash_menu_overlay`、`open_image_toolbar_overlay`、`open_block_menu_overlay`、`try_sync_editor_overlay_state` 和 `on_selection_change` overlay 同步继续留在 `lib.rs`。 @@ -172,3 +175,11 @@ UI / 浏览器可见项必须有真实浏览器截图或结构化 smoke 证据 - 已验证:`cargo fmt --manifest-path rust/Cargo.toml --all --check`、`cargo check --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml`、wasm release build、`wasm-bindgen`、`MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3001 NODE_PATH=/mnt/Data1T/mnote/node_modules node scripts/task489-block-menu-delete-undo-smoke.js`、`MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3001 NODE_PATH=/mnt/Data1T/mnote/node_modules node scripts/task488-local-attachment-link-delete-undo-smoke.js`、`MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3001 NODE_PATH=/mnt/Data1T/mnote/node_modules node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js`。 - 环境阻塞:`MNOTE_UI_BASE_URL=http://127.0.0.1:3001 NODE_PATH=/mnt/Data1T/mnote/node_modules node scripts/task158-e30-menu-state-smoke.js` 在创建 cloud 临时文档时返回 `503 convex_unavailable`,当前 3001 服务未配置 Convex mutation;本地文件夹型 editor smoke 已通过。 - 未迁:`on_selection_change` overlay 同步、`drop_indicator_from_target`、拖拽 listener、block reorder 行为和 selection bridge 仍留在 `lib.rs`。 +- 2026-05-25:Batch T 只派发 1 个只读 Reasonix worker 审计 `drop_indicator_from_target` 边界,使用独立 worktree `/mnt/Data1T/mnote-worktrees/0525-t-worker-a-5-29-drop-indicator-readonly`,run id `reasonix-2026-05-24T20-56-16-647Z-35de7d21`,并显式使用 `--no-memory-recall`。 + - Codex 已读取 Hindsight recall、`process-handoff.md/json`、`result.json`、`final.md`,并核对 worktree `git status --short` / `git diff --stat` 为空;Reasonix 未修改源码。runner 完成后残留父进程未自行退出,主控已终止。 + - 审计结论经 Codex 复核后采纳:`drop_indicator_from_target` 不能整体迁入 `editor_runtime/block_dnd.rs`,因为仍依赖 `hovered_block_from_target`、`target_element`、`direct_block_from_element`、`top_level_block_index`、`block_state_from_index`、`HANDLE_SHELL_SELECTOR` 等 `lib.rs` DOM -> block 状态解析链。 + - Codex 主控完成最小几何切片:`block_dnd.rs` 新增 `drop_indicator_from_geometry(block_index, block_top, block_height, stage_top, client_y)` 和单元测试;`lib.rs::drop_indicator_from_target` 仍负责 DOM 查询与 hovered block 解析,只委托新 helper 计算 `DropIndicatorState`。 + - 修改:`rust/spikes/leptos-tiptap-spike/src/editor_runtime/block_dnd.rs`、`rust/spikes/leptos-tiptap-spike/src/lib.rs`、generated island wasm。 + - 已验证:`cargo fmt --manifest-path rust/Cargo.toml --all --check`、`cargo check --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml`、wasm release build、`wasm-bindgen`、`MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3001 NODE_PATH=/mnt/Data1T/mnote/node_modules node scripts/task489-block-menu-delete-undo-smoke.js`、`MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3001 NODE_PATH=/mnt/Data1T/mnote/node_modules node scripts/task488-local-attachment-link-delete-undo-smoke.js`、`MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3001 NODE_PATH=/mnt/Data1T/mnote/node_modules node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js`。 + - 单元测试阻塞:`cargo test --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml block_dnd -- --test-threads=1` 被既有 `STYLE` 测试编译错误阻断,未进入本次新增 helper 测试;该阻断在此前 `block_dnd` 批次已存在。 + - 未迁:`hovered_block_from_target`、`drop_indicator_from_target`、`drop_indicator_from_point`、拖拽 listener、block reorder 行为和 DOM -> block 状态解析链仍留在 `lib.rs`。 diff --git a/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island_bg.wasm b/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island_bg.wasm index e3e479b3f88a499210750a7761f54c748f3260ee..05eb73cc8ef5524a9a9903aea362b1c9f3341a10 100644 GIT binary patch delta 8009 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 R3B#u=KH=t~IL8Z_{|C4k$^`%b delta 8234 zcmZA63shBA8VB%w?u9EN2$zR<#|6E-D;4CSfT99wAm#%t@tp*ym`e*AdpOQ9QAsl? zO?HW6YWA{~XcelFX@B6;J&))m&v+t4G zK6=FX4WB)p($JuQfFQd)ARxeQ53>6O1ne77Ix5=OvTw=a;;lwYZIjcmnw+*@9yD65 zx7OOe@H5WW*4Npr`Z{~|dfOR?dc@_Ipn|_MLR51vKZn)5x8K&8##!s7rG6u48LhQf zTm7u7t^OegjnAy>PdU!bG>+A_{Oqt=e(n}tXq>J6At%84A*cIi6VZR;y6#6d8ZBz2 zCvcl_(n^dC46G6U1^sv(7gNA0J>w8OszKuYB+Jc}BcNnLvtA~Sf4dax$^}V1Y z;I;RH{HNPKcUbwJ;8w#r-Y0m^0moiHo4@I=9{(-)oT_RMPKdPU9~JXM5A}}i;ra8A zjlE-o)VlWIriPqkrCDp)5Bg_hE`9vhF!13&-&rvo?_#S zWvvPS;UVJ-Yo|S8i-ncUKNx{eq?LcL*ToVurI!#%LWr8gP|*WFy^bNL!hOgCw0t^z zE9Eobe&j4Tg`5p%cG(m;T7x0*&0Pf|7ruo&RLkdS&WA@*eF4k^ybbP}peaNl0==oh zXrBTh#=suR-wsEU$7=a;a4hA=YxxPV8~L9Ot!sN*ZSCnwHKwX#JzYbL87eRYDzgTM zxE?UfVJ;y;P`KL_`u`OPz@G!Dn>Fs{{kYb`jOd;kt3AA~({+wOP2 zdF$Y1HNfdgG(J|7ovsXQ!nICUji0ec_3Y((%{ZtUd%04b7ovn1Ks|h|en;5&My0r1 zg=rU&<%K;D$5FK#tH4_;TD{Uh zFmHt2Fz?jYU>@!sn4gTja02zW5AIIh4^#i9cvCAl>{}q3;B@NXJuUw}%&YqWJdEm( zz}e&veT<#`5dyhX@G;CI{#5f(t^OY{U+@^rk7hGWGZx3$xW4!d0iMALm}h)a%b$Yz zus975rU{&Z2a(Uh++PdaF=5ktnzbTOKn>2p8RYYD7Ws3S_rw=)M~B}2zJz&2F2Fp& zi#PIDUR|S}&T_?=Um-xpowx)S(FMPTc?NATFX{I%FZsVO`V%`Jc90;ydA@dnI$>rE@rA!~XX zcC|i2#+P_-Sn*=V!rVL#=Di;e^ZXLvG`d`pvPH^NGYjG26wZP96dDZk2Fiu|Qhhee z!zhLeDL)?OVNZbR45$(%S_iY?A=E(`%(wY&nAf=+E};4fcr>{Z_CBI&@2xW2>eow- z^~)cM7*BE-%xgCs=0(Ya`PM|K15t8>`8XbB9@=`CFI@-UNke%8o=PtD_+DlTTtfM& zFt=X;)0Q&DY6N)kABFkmJO=X~@cItXwPzb(ZvQ;Y?O%eqz1Lms%Z1pZb+BJ+;B{96 zAr8TOfp=lvl*hF8C)BKHnQA!H`e>P92I3&#Q>rIS>mfp5-m9T74?hg%>4w8RnlEv6 z?*9Tz+ag_DM1VK*S*<~fZv)Y4RmaGEwwX@Ey?gZD%7qnG3+Gl>mQ_5|aRR3zSB6}N z2g1Y1*)Z>KuX&QWYK2GkHHNF*9+~RQLv|ds?yG+E$hx%Ym?<4TVy5OY4YIm$G zELp3St<(It)~pVmOjo}I^Jub%gvJfbyf+F7nF+uZWt$wbSU!?gjTK(!9`L>3}D{E?1O}y-7Y*L%!@inv&sRA0x z37DUl(=gwe7MR!0Yu+wj!&A}U-0G@|jx7}!|L3{VX1#v7VgKd&J^$Rku5IQ4EA;()g2Fd9* zW03kiTNauVl6{ZvdGI7EUkT6B-1Q&}u?qQ8%CCkw{}?=<^6TIQT{guN2=GmQ66Q^@ z!MA~U3ZB>1f!G9Bk?XbmGnzNUD?95=AsP_i5pIXQ2YSb@+X3@QwNuN#0MDTM7h!&o zU()g~!<}Eg6==9iD|iLwCEXA6jNgEHzz1L+;Gvs++aI=)hRA!m<5!v+M#=_1!>Lk6 z%i+eY*22;9f|379wC~@ucEg!8pS>{lQ0IE=gMH7|^<(fp^rkv6Mm}qPgDlR8&gyHL z8{t&Se*p85d;}gz`48biY13M-Vfj>N_73@^sjg@W{ zIZh^+y!7cYzB4O?@nY9`>%1PUncr!Pkmr?{279P}y4K%}7~B{xn1zBkYTz~Rg}dNB zl%H)~9w++-7|T^yDgKi3yecS_g|Yil#lJ_eU|!F+)UHyw&p4)LO~H5lDP(zdgL?b! z*I=0c3KI#Z&=?Y6JYSuQln5t~leBy?d<*h7ehD2W0)Rkj&`-rpmC2*)Q5Q+!t1yq_ zCzyA^HQ2i?M=dTdyBA-I9j8@)wPC899(xGI=lW*uU=)UnrmPdC84i8g6|8s>SffTO6tTA0VM z*4JU;CIom_$Ee@S<@k~aggc)K^!GN_i_4mBtY62vy$ADs{Q%~19Z_2=WV!K=tHxZ#}$JE;7s@ z41nhv4D;Oy(eh51_i`A_dn8=TN5H%$k-mAG=%wZ_lVkkj5g(=&E|Yy!>oVyvV%4w9 zr>Kn(LO5c!%5 z;2Bgu0_FjZhIzeUygD(4JL%gD*d zEYBHOKB)YJL#6yRxwXw?u*4Z7ALsVy#G_U=~PsXx`33?qOK*lC)-MB@{KPj7tsSc{WA)w%xzN=@*$ diff --git a/rust/spikes/leptos-tiptap-spike/src/editor_runtime/block_dnd.rs b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/block_dnd.rs index 1e949e4c..bfdeafd1 100644 --- a/rust/spikes/leptos-tiptap-spike/src/editor_runtime/block_dnd.rs +++ b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/block_dnd.rs @@ -3,7 +3,7 @@ //! 本模块只承接拖拽命中所需的窄 helper,不处理 Leptos signal, //! 也不分发 editor command;调用方负责保留编辑器状态编排。 -use crate::editor_runtime::block_hover_state::DropPlacement; +use crate::editor_runtime::block_hover_state::{DropIndicatorState, DropPlacement}; use web_sys::{window, EventTarget}; pub(crate) struct HandleCorridorGeometry { @@ -57,6 +57,23 @@ pub(crate) fn drop_indicator_top( } } +pub(crate) fn drop_indicator_from_geometry( + block_index: usize, + block_top: f64, + block_height: f64, + stage_top: f64, + client_y: i32, +) -> DropIndicatorState { + let placement = drop_placement_from_block_point(client_y, block_top, block_height); + let block_bottom = block_top + block_height; + let top = drop_indicator_top(stage_top, block_top, block_bottom, placement); + DropIndicatorState { + index: block_index, + top, + placement, + } +} + pub(crate) fn event_target_from_point(client_x: i32, client_y: i32) -> Option { window() .and_then(|win| win.document()) @@ -97,4 +114,24 @@ mod tests { DropPlacement::After ); } + + #[test] + fn drop_indicator_geometry_returns_stage_relative_top() { + assert_eq!( + drop_indicator_from_geometry(3, 140.0, 40.0, 100.0, 150), + DropIndicatorState { + index: 3, + top: 40.0, + placement: DropPlacement::Before, + } + ); + assert_eq!( + drop_indicator_from_geometry(3, 140.0, 40.0, 100.0, 161), + DropIndicatorState { + index: 3, + top: 80.0, + placement: DropPlacement::After, + } + ); + } } diff --git a/rust/spikes/leptos-tiptap-spike/src/lib.rs b/rust/spikes/leptos-tiptap-spike/src/lib.rs index 5f3ab8b3..78f10c0b 100644 --- a/rust/spikes/leptos-tiptap-spike/src/lib.rs +++ b/rust/spikes/leptos-tiptap-spike/src/lib.rs @@ -22,8 +22,8 @@ use web_sys::{ use editor_runtime::attachment_upload::dispatch_editor_upload_request; use editor_runtime::block_dnd::{ - drop_indicator_top, drop_placement_from_block_point, event_target_from_point, - pointer_in_handle_corridor_geometry, HandleCorridorGeometry, + drop_indicator_from_geometry, event_target_from_point, pointer_in_handle_corridor_geometry, + HandleCorridorGeometry, }; use editor_runtime::block_menu_overlay; use editor_runtime::block_menu_legacy_html::{ @@ -5419,14 +5419,13 @@ fn drop_indicator_from_target( let stage = editor_stage_element()?; let block_rect = block.get_bounding_client_rect(); let stage_rect = stage.get_bounding_client_rect(); - let placement = drop_placement_from_block_point(client_y, block_rect.top(), block_rect.height()); - let top = drop_indicator_top(stage_rect.top(), block_rect.top(), block_rect.bottom(), placement); - - Some(DropIndicatorState { - index: hovered.index, - top, - placement, - }) + Some(drop_indicator_from_geometry( + hovered.index, + block_rect.top(), + block_rect.height(), + stage_rect.top(), + client_y, + )) } fn drop_indicator_from_point(client_x: i32, client_y: i32) -> Option {