ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Serial Studio 项目编辑器事务式 Undo/Redo:从任务清单到源码落地的完整实现解析

Serial Studio 项目编辑器事务式 Undo/Redo:从任务清单到源码落地的完整实现解析 Serial Studio 项目编辑器事务式 Undo/Redo从任务清单到源码落地的完整实现解析【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio本文以 Serial Studio 规格 0031 的第三阶段任务清单tasks.md为主体完整拆解项目编辑器事务式撤销/重做这一特性从 12 个可独立验证的任务T1–T12、构建期偏差记录到多 Agent 代码评审修复的全过程并结合当前仓库中ProjectHistory、ProjectModel、API 命令注册与集成测试的真实源码说明每一类任务背后的实现细节与验证手段帮助你理解一个大型 Qt/C 项目如何把全量文档快照式 undo/redo安全地嵌入既有数据模型。一、文档定位规格 0031 四阶段流程中的 tasks 阶段0031 任务清单 属于该特性规格文档的第三阶段Phase 3 of 4其职责是把第二阶段的 技术方案 plan.md 分解为小而有序、可独立验证的工作单元供/ss-implement自顶向下逐条执行并保持状态框实时更新。任务的上下文是第一阶段的需求文档 spec.md项目编辑此前不可逆删除 group 会连带丢弃 datasetsbatch 误操作无法恢复唯一的撤销手段只有文本编辑器内置的文本 undo因此规格定义了 R1–R8 八项需求全变异点覆盖、复合操作原子性、标准快捷键交互、按键连击合并、API undo 动词、历史边界、派生状态一致性、内存有界与 AC1–AC7 验收标准。tasks.md 开篇给出三条编写约定这三条约定本身也值得作为工程实践参考One task one focused, reviewable change一个任务只对应一个聚焦的、可评审的变更Verify说明本单元在继续之前如何被确认静态检查命令、行数比对、回读比对Deps列出必须先落地的任务 ID构成显式依赖图。二、任务总览T1–T12 与依赖关系任务内容涉及文件任务清单记录的规划期路径依赖T1ProjectHistory 核心类app/src/DataModel/Project/ProjectHistory.h/.cpp新无T2ProjectModel 门面属性、槽、作用域管道app/src/DataModel/ProjectModel.h/.cppT1T3Apply 路径重构 loadFromJsonDocument实现 undo/redo 应用ProjectModelLoading.cpp、ProjectModelPersistence.cppT2T4Undo 作用域ProjectModel.cpp Crud 编译单元ProjectModel.cpp、ProjectModelCrud.cppT2T5Undo 作用域Sources / Tables / Workspaces / Folders对应 4 个编译单元T2T6编辑器按键提示与批量帧ProjectEditorCommit.cpp、ProjectEditorMultiSelect.cppT4T7API 标签帧 project.undo/redo 动词CommandRegistry.cpp、ProjectHandler.cpp、ProjectHandlerFile.cppT3, T4T8StandardKey 表 命令清单UI/CommandRegistry.cpp、scripts/registry-verify.py、app/rcc/commands/projecteditor.json、project-toolbar.json无可并行T9QML 绑定app/qml/Commands/ProjectEditorCommandBindings.qmlT2, T8T10code-verify 漂移规则scripts/code-verify.pyT4, T5T11pytest 覆盖tests/integration/test_project_undo.py新T7T12文档 自评审doc/claude/architecture/project.md等全部从依赖关系看T1→T2 是主干T3/T4/T5 并行展开T6/T7 依赖 T4T8 是唯一无依赖、可与 QML 绑定解耦并行的任务T10 的静态规则必须等 T4/T5 铺完作用域后才能达到零错误T11 的集成测试依赖 T7 注册的 API 动词。清单内每个任务都标记为[x] done即该特性已完整落地。三、逐任务拆解与源码佐证T1 — ProjectHistory 核心类纯值类型 有界快照栈任务要求实现一个步骤栈 {label, coalesceKey, preState, postState, timestamp} 帧栈标签帧 vs 捕获帧、深度计数的核心类并明确要求无 QObject、无单例调用——它是普通类通过构造闭包把序列化/应用回调交给宿主从而保证在ProjectModel受保护的构造闭包内此时其他单例尚不存在也能安全构造。边界值为100 步 / 64 MiB超界丢弃最旧步骤并维护保存位置哨兵。当前仓库中该实现位于 ProjectHistory.h 与 ProjectHistory.cpp。源码中的边界常量与任务描述逐一对应ProjectHistory.cppstatic constexpr int kMaxUndoSteps 100; static constexpr qint64 kMaxUndoBytes 64 * 1024 * 1024; static constexpr qint64 kCoalesceWindowMs 1000; static constexpr qint64 kSaveUnreachable -1;Step结构体的五个字段ProjectHistory.h与任务清单的描述完全一致label、coalesceKey、preState、postStateQByteArray、timestampMs。类的注释也保留了任务要求的设计意图plain value type (no QObject, no singleton access) so ProjectModel can own it inside its protected ctor closure。头文件还定义了任务所要求的两个 RAII 辅助类型ProjectUndoFrame标签帧只命名不捕获所以包裹只读操作零成本ProjectUndoScope变异作用域只有最外层作用域才序列化 pre-state嵌套作用域与合并后的按键连击跳过捕获。合并coalescing规则的实现见enterScope()ProjectHistory.cpp当入栈步骤的 key 等于栈顶 key、栈顶步骤距今不超过kCoalesceWindowMs1 秒、且期间未发生 undo/redonoRedoTail时返回ScopeAction::Coalesce——新捕获被跳过连击延长既有步骤。这正是任务清单中same key 1 s no intervening undo → skip capture的代码级体现。T2 — ProjectModel 门面属性、槽与绑定不变量任务要求为ProjectModel增加Q_PROPERTYcanUndo/canRedo/undoText/redoTextNOTIFYprojectHistoryChanged、public slotsundo()/redo()/setNextUndoHint()以及一个对 Project 编译单元可见的作用域 RAII 辅助类型同时捕获被m_initialized且非 applying 双门控。其中两条绑定不变量是该任务的核心约束ProjectModel构造闭包受保护spec 0001history 构造不得触碰其他单例newJsonFile()在闭包内运行因此必须用m_initialized门控。在当前的 ProjectModel.h 中可确认四个属性均带NOTIFY projectHistoryChangedcanUndo()/canRedo()直接转发给m_historyProjectModel.h且构造函数闭包结束后才启用历史m_history.setEnabled(true)ProjectModel.cpp与任务描述constructible before any other singleton exists的设计吻合。T3 — Apply 路径抽取共享核心undo/redo 走历史专用入口任务是 T 系列中最复杂的一项把loadFromJsonDocument抽取为共享的applyJsonDocumentCore()——它保留m_filePath与文件监视器、运行全套净化器enforceGplSingleSource、transform/virtual 解析、uniqueId 播种、发射加载信号集减去jsonFileChanged然后触发运行时同步与自动保存applying标志抑制重入捕获groupsChanged的自动工作区再生 lambda 会二次变异文档。任务还记录了三条绑定不变量历史应用绝不发射jsonFileChanged否则会触发选择重置 AppState 重载监视器重挂载仍归writeProjectFile/加载路径所有空文档的setModified(false)语义不变。当前实现中该核心已位于 ProjectLoader.cppapplyJsonDocumentCore(document.object())前后用m_model.m_history.setApplying(true/false)包住ProjectLoader.cpp普通加载路径则在应用后执行m_model.m_history.clear()ProjectLoader.cpp分别对应任务描述的applying 标志抑制重入与loadFromJsonDocument 清空历史两个要求。ProjectModel的undo()实现同样印证了先 peek 后 apply、干净应用后才移动位置的两阶段提交ProjectModel.cppif (!m_history.canUndo() || m_history.applying()) ... const auto state m_history.peekUndoState(); // apply ... m_history.confirmUndo(current); setModified(!m_history.isAtSavePoint());注意setModified(!m_history.isAtSavePoint())一行正是 R6modified 标志始终反映文档与磁盘最后保存状态之差的实现落点。T4/T5 — 机械式作用域铺设每个变异槽一行T4 要求在ProjectModel.cpp与 Crud 编译单元中每个变异槽顶部加一行作用域约 24 约 32 处label 为人类可读的操作名Delete Group、Add Dataset嵌套变异器通过深度计数加入外层作用域提示包装器与瞬态选择 setter 不加作用域它们不变异文档。T5 以同样方式处理 Sources/Tables/Workspaces/Folders 四个编译单元约 16/8/17/21 处并明确加载期静默变异器seedNextUniqueIdFromGroups、迁移、sanitize*Folders不加作用域——它们运行于加载时而加载本就会清空历史。任务清单的Build deviations一节修正了原计划的铺设计划工作区 CRUD、工作区文件夹族、hideGroup/showGroup与展示 blob setter均不加作用域——规格把它们排除在撤销历史之外spec 非目标且无作用域的展示写入还能保护 redo 尾部不被排队中的buildTreeModel→setTreeExpansion写回污染该豁免通过 code-verify 的undo-scope-missing白名单强制执行。最终实际作用域行数为93 行ProjectModel 13、Crud 42、Sources 19、Tables 7、Folders 12。验证手段是grep -c作用域行数与计划点数比对加code-verify.py --check。T6 — 编辑器提示hints与批量帧label framesProjectEditor的每个按键提交处理器约 8 个在调用模型方法之前调用setNextUndoHint(label, key)key 形如entity-field:id如group-title:3多选扇出循环约 4 处与既有setAutoSaveSuspended配对处相同位置包裹标签帧使 N 项批量编辑只形成一个步骤。绑定不变量按键级提交仍以rebuildTreefalse调用updateGroup/updateDatasethints 不得改变该路径。源码中ProjectHistory::enterScope()对 hints 的裁决逻辑给出了精确语义ProjectHistory.cpp编辑器的 hint key优先于槽自身的 keythe editor hint key wins over the slots own key when both exist。这与评审轮发现的代码编辑器合并缺口见下文共同构成按键不逐字符序列化的保障。T7 — API 标签帧与 project.undo/redo 动词API::CommandRegistry::execute()在每个命令旁紧邻ExecuteDepthGuard打开一个以命令名命名的标签帧使project.batch的 1024 个内部操作嵌套后只形成一个名为project.batch的步骤纯读命令永不触发捕获帧是免费的。同时注册project.undo/project.redoemptySchema()实现返回{performed, undone|redone}或空历史时的{performed:false, reason}成功而非错误并附带projectEpoch两个动词加入destructiveCommandSet()使既有的变异前备份快照机制生效。当前仓库中对应证据命令执行处构造标签帧const DataModel::ProjectUndoFrame undoFrame{projectModel, name};CommandRegistry.cpp且 undo/redo 在 epoch 钩子中被特别处理以跳过二次重建CommandRegistry.cpp两个动词的注册位于 ProjectFileCommands.cpp。T8/T9 — 标准键表、命令清单与 QML 绑定T8 向 C 侧kStandardKeys与 Python 侧KNOWN_STANDARD_KEYS增加Undo/Redo使QKeySequence::keyBindings免费得到平台正确键集macOS 的 CmdZ / CmdShiftZWindows 的 CtrlZ / CtrlY CtrlShiftZ命令清单条目editor.undo/editor.redokind action、category project、contexts[editor]、图标code/undo/code/redo与工具栏两个节点。验证命令为python scripts/registry-verify.py干净通过与python scripts/generate-command-strings.py --check。当前仓库中 projecteditor.json 已含editor.undo/editor.redo两个条目。T9 在 ProjectEditorCommandBindings.qml 落地绑定cmdEditorUndo/cmdEditorRedo两个 QtObject 的run()调Cpp_JSON_ProjectModel.undo()/redo()enabled绑定canUndo/canRedo动态提示用qsTr(Undo: %1).arg(Cpp_JSON_ProjectModel.undoText)只允许数字占位符禁用%n。绑定不变量快捷键 Instantiator 已按editorInteractive门控enabled中不得重复模式/锁定检查——R3显示将撤销的内容因此落在动态 tooltip 上静态标题保持 Undo/Redo。T10 — code-verify 漂移规则变异点无法悄悄绕过历史该任务是 R1全变异点覆盖的长期防线新增静态规则——ProjectModel 编译单元中函数体内出现setModified(true)就必须打开 undo 作用域或携带显式抑制错误级别作用域限定于 ProjectModel 各 .cpp已知非步骤变异器加载期迁移、resolveDiskFileChange的直接标志写入进白名单。当前 code-verify.py 中该规则以undo-scope-missing报错落地注释明确must open a ProjectUndoScope so the mutation lands in the历史。验证方法本身也写进了任务全量检查零错误后本地种入一个临时违例确认规则会触发再还原。T11 — 集成测试随机 50 变异往返新建 tests/integration/test_project_undo.py覆盖规格的四项验收标准AC1 随机 50 变异往返project.exportJson字节比对undo N 次与起点字节一致、redo N 次与终点一致AC2 含 5 个 dataset 的 group 删除/撤销保真顺序、uniqueId、字段AC5 batch 整体撤销 空历史{performed:false} 新变异丢弃 redo 尾部AC6 保存点 modified 标志语义经project.getStatus。测试辅助函数体现了不少实操细节_export()会剔除treeExpansion——它是按当前项目标题键控的编辑器视图状态被防抖的树重建按自己的时钟重写与 undo 保真度无关_pace()每条命令间隔 7ms以避开 API 服务器每客户端 200 条/秒的限流超限会被断连表现为EXECUTION_ERROR: API rate limit exceeded。执行方式为维护者带活应用运行本地仅做py_compile与pytest --collect-only收集验证。T12 — 文档与自评审要求把Undo history一节写入架构文档捕获模型、边界、工作区回滚怪癖、漂移规则并在交付时做一次反事实检查说出本 diff 最容易违反的规则 给出它未被违反的证据最后通读完整 diff 确认范围。四、构建期偏差记录2026-07-25tasks.md 最宝贵的部分是Build deviations——实施过程中对原计划的四条正式修正全部回写进清单两阶段捕获作用域只stage暂存快照setModified(true)才commit它ProjectHistory::stageCapture()/commitPending()。这补上了捕获于入口设计的一个洞guard 提前返回的 setter、被取消的对话框本会留下垃圾步骤。源码印证enterScope()返回Capture后仅stageCapture(json)ProjectHistory.cppcommitPending()才真正建步骤ProjectHistory.cpp且其注释明确only when the slot actually mutates (signalled by the setModified(true) hook)。T5 作用域计数修正如上所述工作区族与展示 setter 全部排除实际 93 行白名单强制执行。Apply 路径重同步不直接调syncRuntime()——frameDetectionChanged已接线到AppState::onProjectLoaded后者既重建 FrameBuilder 又重新推导 frameConfig而裸syncRuntime()会漏掉后者。保存点autoSave()同样调用markSaved()以磁盘为真而非只记显式保存使 modified 标志始终反映文档 vs 磁盘。当前 ProjectPersistence.cpp 中m_model.m_history.markSaved()出现在两处保存路径与之一致。清单还记录了计划外的触碰文件app/CMakeLists.txt编译单元注册、app/src/UI/CommandStrings.cpp重新生成、tests/README.md测试目录行、app/src/DataModel/ProjectEditor.cpp两处计划遗漏的按键级合并提示painter 代码、transmit 函数。五、qt-cpp-review 评审轮六 Agent 修复清单多 Agent 代码评审2026-07-25确认并修复了一批高编号缺陷每条都对应一个真实的撤销系统陷阱保存点陈旧88 确认commitPending()截断 redo 尾部时会使其中的保存位置失效置为kSaveUnreachablemarkSaved()会打破栈顶步骤的合并链使按键突发中途的保存保持字节精确。redo 后合并导致的数据丢失85 确认合并分支清除陈旧的postState及其字节记账使 redo 能重新物化真实终态。先移位置后应用的失步stateForUndo/Redo被替换为peekUndoState/peekRedoStateconfirmUndo/confirmRedo两段式undo()/redo()仅在干净应用后才移动位置API 处理器在应用失败时报告performed:false。代码编辑器合并缺口92 确认ProjectUndoScope增加可选槽级合并 keysetControlScriptCode/setFrameParserCode/storeFrameParserCode/updateSourceFrameParser自合并painter/transmit 编辑器经ProjectEditor.cpp发 hint——打字不再逐字符序列化或淹没历史。无操作捕获成本等值/边界守卫前移到 13 个标量 setter 与 parser-code 槽的作用域之前。对话框重入deleteCurrent{Group,Action,Dataset,OutputWidget}、deleteSource、importTableFromCsv中的 undo 作用域下移到确认对话框之下删除类包装器丢掉冗余外层作用域内层槽作用域承载步骤。setGroupWidget有意保留顶部作用域因为confirmGroupWidgetChange在对话框周围直接变异活 grouppre-capture 必须先于它。API 双重重建execute()的 epoch 钩子跳过project.undo/project.redo应用路径已同步 排程自动保存避免第二次池失效。已接受/延后全文档快照会连带回滚展示状态 记录在案的设计怪癖plan 的Known quirk无操作update*调用仍可能记录空步骤需要实体operator延后解析中途apiCall(project.undo)与project.open共享既有的同步拆除风险类修复归属 ScriptApiCall 延迟机制超出本规格边界已上报维护者。六、完成定义Definition of Done清单尾部的 DoD 给出了该规格完成的可核查标准且全部勾选spec.md 中每条验收标准已满足或以具名运行时检查移交维护者AC3/AC4/AC7 观察项AC1/AC2/AC5/AC6 有 pytestcode-verify.py --check对所有变更文件干净C diff 跑过qt-cpp-review--benchmark-hotpath已识别给维护者无热路径编辑邻接门控sanitize-commit.py已运行、lint 债务清零diff 严格限于被要求的内容且仅有该范围spec.md 状态置为done。七、任务清单与当前仓库的对照需要说明的一点是tasks.md 中的文件路径如app/src/DataModel/Project/...反映的是规格编写时的目录布局从源码结构看对应实现现已位于core/Pipeline/DataModel/下ProjectHistory、ProjectLoader、ProjectPersistenceundo/redo的 QML 与命令清单路径则与清单一致。按任务清单Verify列给出的命令复验当前状态是阅读本文最直接的实操入口python scripts/code-verify.py --check # 含 undo-scope-missing 漂移规则 python scripts/registry-verify.py # 绑定/清单 join 检查 pytest --collect-only tests/integration/test_project_undo.py # 本地仅验证收集总结来看规格 0031 的这份任务清单展示了一个可复用的实现组织方式先用 T1 建一个零依赖的纯值核心类锁定不变量再用机械式的一行作用域T4/T5把历史铺进既有数据模型用标签帧与 hintsT6/T7处理复合操作与按键连击用静态规则T10与字节级往返测试T11双网兜住变异点遗漏这一最难靠评审发现的缺陷最后以构建偏差回写和多 Agent 评审修复保证计划与现实的偏差全部显性化。【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表