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 db199346..d206bc87 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 @@ -190,3 +190,11 @@ UI / 浏览器可见项必须有真实浏览器截图或结构化 smoke 证据 - 修改:`rust/spikes/leptos-tiptap-spike/src/editor_runtime/overlays.rs`、`rust/spikes/leptos-tiptap-spike/src/lib.rs`。 - 已验证:`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`。 - 未迁:`send_selection_state_to_target` / `selection_payload` bridge 链路、`runtime_block_id_from_index`、selection bridge event、`drop_indicator_from_target`、拖拽 listener 和 block reorder 行为仍留在 `lib.rs`。 +- 2026-05-25:Batch V 只派发 1 个只读 Reasonix worker 审计 selection bridge 迁出边界,使用独立 worktree `/mnt/Data1T/mnote-worktrees/0525-v-worker-a-5-29-selection-bridge-readonly`,run id `reasonix-2026-05-24T21-22-36-118Z-1e489f85`,并显式使用 `--no-memory-recall`。 + - Codex 已读取 Hindsight recall、`process-handoff.md/json`、`result.json`、`final.md`,并核对 worktree `git status --short` / `git diff --stat` 为空;Reasonix 未修改源码。 + - 审计结论经 Codex 复核后采纳:selection bridge 的事件分发层必须留在 `lib.rs`,但 `SelectionPayload`、`selection_payload`、`runtime_block_id_from_index` 及其 block id 读取 helper 属于可迁出的 selection 数据层。 + - Codex 主控基于只读审计结论在主工作区完成最小实现;本批没有实现 worker 候选 patch,因此不记录虚构 worktree 采纳链。 + - 已合入最小切片:`SelectionPayload`、`selection_payload`、`runtime_block_id_from_index`、`block_id_from_element`、`block_id_from_json_node` 迁入 `editor_runtime/dom_selection.rs`;`send_selection_state_to_target`、`dispatch_selection_event_to_target`、`CustomEvent` bridge 和 `selection_event_target` 壳层调用继续留在 `lib.rs`。 + - 修改:`rust/spikes/leptos-tiptap-spike/src/editor_runtime/dom_selection.rs`、`rust/spikes/leptos-tiptap-spike/src/lib.rs`。 + - 已验证:`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`。 + - 未迁/未删:`send_selection_state_to_target`、selection event dispatch、`send_selection_state`、`selection_event_payload`、`runtime_state_payload`、`runtime_status_payload` 等未使用 bridge/helper 函数;死代码清理后续单独切片处理。 diff --git a/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island.js b/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island.js index 800f9387..ae69855a 100644 --- a/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island.js +++ b/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island.js @@ -1259,47 +1259,47 @@ function __wbg_get_imports() { } }, arguments); }, __wbindgen_cast_0000000000000001: function(arg0, arg1) { - // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1144, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1145, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h396914cf76a9e7a5); return ret; }, __wbindgen_cast_0000000000000002: function(arg0, arg1) { - // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1196, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`. + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1197, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`. const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h3a3182d847094e12); return ret; }, __wbindgen_cast_0000000000000003: function(arg0, arg1) { - // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 972, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`. + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 973, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`. const ret = makeClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__had771ddc65647798); return ret; }, __wbindgen_cast_0000000000000004: function(arg0, arg1) { - // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 1094, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 1095, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h0abd2b2fe4652e2a); return ret; }, __wbindgen_cast_0000000000000005: function(arg0, arg1) { - // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 1144, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 1145, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h396914cf76a9e7a5_4); return ret; }, __wbindgen_cast_0000000000000006: function(arg0, arg1) { - // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("KeyboardEvent")], shim_idx: 789, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("KeyboardEvent")], shim_idx: 856, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__hd90af689bc3e71bf); return ret; }, __wbindgen_cast_0000000000000007: function(arg0, arg1) { - // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1096, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1097, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h794babeffd4f821f); return ret; }, __wbindgen_cast_0000000000000008: function(arg0, arg1) { - // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1111, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`. + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1112, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`. const ret = makeClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h2b648e7ac8ec1fb5); return ret; }, __wbindgen_cast_0000000000000009: function(arg0, arg1) { - // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1147, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1148, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h0b7fb40a8610550c); return ret; }, 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 fde8c97b..d09ac4ad 100644 Binary files a/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island_bg.wasm and b/rust/spikes/leptos-tiptap-spike/generated/island/mnote-leptos-tiptap-spike-island_bg.wasm differ diff --git a/rust/spikes/leptos-tiptap-spike/src/editor_runtime/dom_selection.rs b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/dom_selection.rs index 575ebbcb..60d3a865 100644 --- a/rust/spikes/leptos-tiptap-spike/src/editor_runtime/dom_selection.rs +++ b/rust/spikes/leptos-tiptap-spike/src/editor_runtime/dom_selection.rs @@ -1,8 +1,21 @@ use leptos_tiptap::TiptapSelectionState; +use serde::Serialize; +use serde_json::Value; +use wasm_bindgen::{JsCast, JsValue}; use web_sys::{window, Element, Node}; use crate::editor_root_element; +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct SelectionPayload { + pub(crate) selection: TiptapSelectionState, + pub(crate) summary: String, + pub(crate) editor_focused: bool, + pub(crate) current_block_index: Option, + pub(crate) current_block_id: Option, +} + pub(crate) fn node_is_within_root(node: Node, root: &Element) -> bool { let mut current = Some(node); while let Some(node) = current { @@ -103,3 +116,59 @@ pub(crate) fn selection_has_rich_marks(selection: &TiptapSelectionState) -> bool || selection.text_style || selection.link } + +pub(crate) fn block_id_from_element(block: &Element) -> Option { + block + .get_attribute("data-block-id") + .map(|value| value.trim().to_string()) + .filter(|value| !value.is_empty()) +} + +pub(crate) fn block_id_from_json_node(node: &Value) -> Option { + node.get("attrs") + .and_then(|attrs| attrs.get("blockId")) + .and_then(Value::as_str) + .map(str::trim) + .filter(|value| !value.is_empty()) + .map(str::to_string) +} + +pub(crate) fn runtime_block_id_from_index(index: usize) -> Option { + editor_root_element() + .and_then(|root| root.children().item(index as u32)) + .and_then(|block| block_id_from_element(&block)) + .or_else(|| { + editor_root_element() + .and_then(|root| { + js_sys::Reflect::get(root.as_ref(), &JsValue::from_str("editor")).ok() + }) + .and_then(|editor_value| { + js_sys::Reflect::get(&editor_value, &JsValue::from_str("getJSON")) + .ok() + .and_then(|callback| callback.dyn_into::().ok()) + .and_then(|callback| callback.call0(&editor_value).ok()) + }) + .and_then(|value| serde_wasm_bindgen::from_value::(value).ok()) + .and_then(|document| { + document + .get("content") + .and_then(Value::as_array) + .and_then(|content| content.get(index)) + .and_then(block_id_from_json_node) + }) + }) +} + +pub(crate) fn selection_payload( + selection: &TiptapSelectionState, + editor_focused: bool, + current_block_index: Option, +) -> SelectionPayload { + SelectionPayload { + selection: selection.clone(), + summary: selection_summary(selection), + editor_focused, + current_block_index, + current_block_id: current_block_index.and_then(runtime_block_id_from_index), + } +} diff --git a/rust/spikes/leptos-tiptap-spike/src/lib.rs b/rust/spikes/leptos-tiptap-spike/src/lib.rs index a5cb82c3..2cc5337d 100644 --- a/rust/spikes/leptos-tiptap-spike/src/lib.rs +++ b/rust/spikes/leptos-tiptap-spike/src/lib.rs @@ -36,7 +36,7 @@ use editor_runtime::command_sync::{ read_editor_snapshot, sync_editor_outputs, sync_persisted_editor_command, }; use editor_runtime::dom_selection::{ - selection_summary, + runtime_block_id_from_index, selection_payload, selection_summary, SelectionPayload, }; use editor_runtime::editor_focus::{active_editor_stage, body_has_focus, schedule_editor_focus}; use editor_runtime::overlays::{ @@ -3088,16 +3088,6 @@ struct StatePayload { read_only: bool, } -#[derive(Clone, Debug, Serialize)] -#[serde(rename_all = "camelCase")] -struct SelectionPayload { - selection: TiptapSelectionState, - summary: String, - editor_focused: bool, - current_block_index: Option, - current_block_id: Option, -} - #[derive(Clone, Debug, Serialize)] #[serde(rename_all = "camelCase")] struct ChangeMetaPayload { @@ -3539,48 +3529,6 @@ fn runtime_event_target() -> Option { .map(|body| body.into()) } -fn block_id_from_element(block: &Element) -> Option { - block - .get_attribute("data-block-id") - .map(|value| value.trim().to_string()) - .filter(|value| !value.is_empty()) -} - -fn block_id_from_json_node(node: &Value) -> Option { - node.get("attrs") - .and_then(|attrs| attrs.get("blockId")) - .and_then(Value::as_str) - .map(str::trim) - .filter(|value| !value.is_empty()) - .map(str::to_string) -} - -fn runtime_block_id_from_index(index: usize) -> Option { - editor_root_element() - .and_then(|root| root.children().item(index as u32)) - .and_then(|block| block_id_from_element(&block)) - .or_else(|| { - editor_root_element() - .and_then(|root| { - js_sys::Reflect::get(root.as_ref(), &JsValue::from_str("editor")).ok() - }) - .and_then(|editor_value| { - js_sys::Reflect::get(&editor_value, &JsValue::from_str("getJSON")) - .ok() - .and_then(|callback| callback.dyn_into::().ok()) - .and_then(|callback| callback.call0(&editor_value).ok()) - }) - .and_then(|value| serde_wasm_bindgen::from_value::(value).ok()) - .and_then(|document| { - document - .get("content") - .and_then(Value::as_array) - .and_then(|content| content.get(index)) - .and_then(block_id_from_json_node) - }) - }) -} - fn current_runtime_block_info() -> CurrentBlockInfo { hovered_block_from_selection() .and_then(|block| { @@ -5860,20 +5808,6 @@ fn current_block_info_from_index(index: Option) -> CurrentBlockInfo { }) } -fn selection_payload( - selection: &TiptapSelectionState, - editor_focused: bool, - current_block_index: Option, -) -> SelectionPayload { - SelectionPayload { - selection: selection.clone(), - summary: selection_summary(selection), - editor_focused, - current_block_index, - current_block_id: current_block_index.and_then(runtime_block_id_from_index), - } -} - fn state_payload( document_id: Option, workspace_id: Option,