鸿蒙 PC Markdown 编辑器自由窗口验证:从 220 vp 侧栏到动态编辑区预算 鸿蒙 PC Markdown 编辑器自由窗口验证从 220 vp 侧栏到动态编辑区预算优先适配鸿蒙 PC不能只在一个固定模拟器窗口截图通过。用户会拖动窗口、最大化、调整侧栏、切换中文与英文、打开多个标签和冲突栏。每一种变化都重新分配活动栏、上下文面板、标签轨道、工具按钮、Web 编辑器和状态栏的空间。自由窗口验证的目标不是寻找一张最漂亮的截图而是证明布局在一组边界上保持任务可用。本文基于公开仓库 https://gitcode.com/VON-/codex_md_oh 的当前实现。窗口与侧栏核心提交为358eb3f搜索最小宽度修复为0d8d38b语言资源基线为ed13ee0。证据来自 HarmonyOS MateBook Pro 2in1 模拟器、辅助功能树、Playwright、ArkTS 构建与 ohosTest。本文不会把未执行的所有显示缩放、外接屏和真机窗口管理路径写成已完成。自由窗口测试的对象是约束系统工作台横向由活动栏、可选侧栏、调整柄和编辑工作区组成编辑工作区纵向由标签栏、可选冲突栏、Web 编辑器和状态栏组成。每个区域有自己的稳定尺寸与弹性活动栏固定侧栏有限可调调整柄固定 8 vp编辑区吸收剩余空间标签与状态栏固定高度Web 区域吸收剩余高度。build(){Row(){this.activityRail()if(this.shouldShowSidebar()){this.contextualPanel()this.sidebarResizeHandle()}this.editorWorkspace()}.width(100%).height(100%).constraintSize({minWidth:640,minHeight:480})}验证不能只看根 Row 是否填满。需要确认最小编辑区、侧栏边界、工具栏文字、标签滚动、搜索换行、状态栏字段和 Webview 内容都没有重叠。约束系统中的任何硬编码都可能只在某个组合暴露。先建立尺寸不变量当前产品不变量是窗口最小约束 640 × 480窗口宽度至少 900 vp 时才显示侧栏侧栏产品范围 220-480 vp调整命中区 8 vp编辑工作区希望至少保留 520 vp标签宽 156 vp搜索选项 300 vp 以下两行。这些数值不是彼此孤立。900 vp 阈值必须能容纳活动栏、最小侧栏、调整柄和编辑区动态 maximum 不能超过 480根最小宽度低于侧栏阈值时侧栏自动隐藏。测试矩阵应围绕不变量的临界点而不是随机拖几个大小。稳定尺寸让验证可重复。若字体随窗口宽度缩放、按钮按内容随意变高截图通过也难以推断下一个尺寸。OhMarkdown 不使用 viewport 字体缩放文本过长由省略、分行或容器模式处理。动态 clamp 保住编辑区侧栏的静态最大值 480 只适合足够宽的窗口。WorkspaceShell 根据窗口宽度减去活动栏、调整柄和最小编辑区得到当前最大值再与 480 取较小。privateclampSidebarWidth(width:number,windowWidth:numberthis.windowWidth):number{constavailableWidthwindowWidth-ACTIVITY_RAIL_WIDTH-SIDEBAR_RESIZE_HANDLE_WIDTH-MIN_EDITOR_WORKSPACE_WIDTH;constmaximumWidthMath.max(MIN_SIDEBAR_WIDTH,Math.min(MAX_SIDEBAR_WIDTH,availableWidth));returnMath.max(MIN_SIDEBAR_WIDTH,Math.min(maximumWidth,width));}这条约束在拖动 update、键盘调整、设置加载和窗口 areaChange 中复用。不同入口不能各自实现范围否则拖动能到 480、重启却恢复 450用户会感到不可预测。编辑区最小预算不是 Web 文档的绝对最小渲染宽度而是产品为工具栏和输入体验保留的正常工作空间。窗口再窄时与其同时展示两个残缺区域不如隐藏侧栏。areaChange 是窗口变化入口根 Row 的onAreaChange读取新宽度更新windowWidth并立即重新 clamp 侧栏。它不重建文档、重新读 Preferences 或向 Web 发 setDocument。.onAreaChange((_oldArea:Area,newArea:Area){constwidthnewArea.widthasnumber;this.windowWidthwidth;this.sidebarWidththis.clampSidebarWidth(this.sidebarWidth,width);})窗口最大化、拖边改变大小和恢复窗口都会走同一路径。Webview 作为剩余空间子项自动布局CodeMirror 响应容器变化。没有每帧 Bridge resize 消息避免原生与 Web 之间形成高频同步。当前将钳制后的有效宽度直接写回sidebarWidth。大窗口保存 480缩小后可能降到更小再放大不会自动恢复 480。它保证稳定但不保存独立“偏好宽度”这是已知取舍后续可引入 preferred/effective 双状态。900 vp 以下采用侧栏折叠privateshouldShowSidebar():boolean{returnthis.sidebarOpenthis.windowWidth900;}窄窗口中活动栏仍在编辑工作区获得剩余宽度。侧栏状态sidebarOpen没被清空窗口重新变宽时可再次显示。这样的响应式策略比把侧栏压到 120 vp 更符合文件工具语义。折叠阈值与侧栏调整是两种不同机制。220 vp 是“侧栏可见时的最小可用宽度”900 vp 是“整个工作台是否足够同时显示侧栏与编辑区”。测试报告应分别记录不能说侧栏可以从 0 自由拖到 480。当前活动栏点击可手动开关侧栏窗口过窄时即使 opentrue 也不显示。后续可增加抽屉式覆盖面板但当前没有实现文章不将其列为已完成。220、300、408、480 是关键检查点220 vp 检查最小侧栏搜索中文标签完整、空状态按钮不溢出、设置分段控件可操作、长文件名省略。299/300 检查搜索两行到单行断点。408 vp 是设备实际扩展证据三项搜索恢复单行。480 vp 检查最大侧栏和最小编辑预算。默认 264 vp 也必须保留因为它是新用户首屏和设置失效回退。392 vp 是重启持久化证据拖动到该值、杀进程、重启辅助树与 Web 边界重新匹配。这些尺寸比只测“窄、中、宽”更有诊断价值。失败发生在 299 而 300 通过可以直接定位断点480 仍挤压编辑器则检查动态 clamp392 重启变 264 则检查 Preferences而非布局。标签轨道随编辑区变化但保持起点旧版本横向 Scroll 未声明对齐单标签在宽 viewport 中居中形成用户指出的空白。自由窗口和侧栏拖动会改变 viewport 宽度导致空白长度不断变化。修复后内容始终从逻辑起点开始。Scroll(){Row(){ForEach(this.documentSessions,(session:DocumentSession){this.documentTab(session)})this.iconButton($r(sys.symbol.plus),$r(app.string.new_document),()this.requestEditorCommand(new))}}.layoutWeight(1).align(Alignment.Start).scrollable(ScrollDirection.Horizontal)内容超过轨道时仍水平滚动右侧打开、保存和视图模式保持固定。测试要覆盖一个标签和多个标签前者发现起点后者发现溢出。侧栏 220 与 480 下首标签都应贴合各自编辑区边界。系统原生标题栏上方空白仍是窗口拖动空间与应用标签轨道无关。自由窗口测试反而更需要保留它否则用户没有稳定区域移动窗口。搜索面板需要局部响应式工作区整体响应式不足以保证子组件可用。侧栏缩到 220 后三个中文搜索选项原来固定同一行Regex 被裁切。0d8d38b增加 300 vp 局部断点。BuilderprivatesearchOptions(){if(this.sidebarWidthSEARCH_OPTIONS_SINGLE_ROW_MIN_WIDTH){Column({space:4}){// 前两项第一行正则表达式第二行。}.height(52)}else{Row(){// 三项恢复单行。}.height(24)}}局部组件使用真实父容器宽度不使用全窗口宽度。窗口很宽、侧栏很窄时仍两行窗口变窄侧栏隐藏时不渲染。布局分支不改变搜索 State也不取消搜索任务。这是自由窗口验收的重要方法先验证大轨道再逐个检查内部工具。容器可缩放会暴露此前所有“固定宽度刚好够”的假设。中文与英文必须分别检查英文Case / Whole / Regex较短中文“区分大小写 / 全词匹配 / 正则表达式”较长英文Start writing Markdown...与中文占位宽度不同设置中的“跟随系统”和English长度也不同。只在一种语言验证无法证明另一种不会裁切。当前布局以更长中文选项决定断点所以两种语言使用相同结构。语言切换不会改变侧栏宽度和标签状态ArkWeb 不重载。测试顺序可以在 220 vp 下切换中英文确认控件结构、辅助名称和 Web 占位同步。资源集合一致性保证键不缺不能保证尺寸模拟器截图和辅助功能边界负责尺寸证据。更多语言尚未支持不能从中英文推断德语或 RTL。高度方向也有约束根最小高度 480编辑工作区包括标签栏、可选冲突栏、Web 区和 26 vp 状态栏。搜索选项在窄侧栏增加 28 vp 高度会减少结果列表但不会覆盖。设置面板内容较多当前 Column 与 Blank 需要在较矮窗口检查可访问项是否滚动或被截断。三方冲突栏出现时挤占 Web 高度但不能覆盖标签或状态栏。大文档模式、图片预览和命令面板 overlay 也要在最小高度检查。当前本轮截图主要覆盖常用 PC 高度极端 480 高度下全部设置项滚动路径仍需专项设备记录。自由窗口不能只测宽。只是本次用户问题集中在横向空白、侧栏与搜索裁切所以实现提交没有顺便重构纵向设置滚动。报告需要清楚区分已解决和仍待验证。真实默认与调整后截图默认状态下侧栏约 261-264 vp首标签已左对齐Web 区域从横坐标 1125 开始拖动到 392 vp 后Web 起点移动到 1374编辑内容没有重载两张图共同证明轨道重新分配而不是单独改变一条线。继续向右停在 480向左停在 220392 强制重启后恢复。截图没有裁掉系统标题栏窗口控制和拖动空间仍可见。辅助功能树是几何证据截图适合整体判断辅助功能树提供数值边界。392 重启后返回调整柄 Stack bounds[1358,351][1374,1675]、description392 vpWeb bounds[1374,431][2605,1626]。调整柄右边界等于 Web 左边界说明没有不可解释空隙。220 vp 搜索证据中三个文本节点边界完整Regex 位于第二行408 vp 三项纵坐标一致。selected 状态还证明语言按钮没有因宽度变化丢失状态。几何断言比像素截图更适合自动化可以检查边界相等、节点不超过父容器、纵坐标关系和 description。后续应把这些检查纳入 ArkUI UI 测试而不是长期依赖人工读取。自动化和构建防止跨层回归自由窗口修改发生在 ArkUI但 Webview 尺寸变化可能影响 CodeMirror、预览和命令 overlay所以仍执行 Playwright30/30。ArkTSUnitTestBuild覆盖 sidebar parse 的默认、有限值和上下限。Debug HAP、ohosTest HAP 构建通过模拟器 ohosTest7/7。最终 Debug HAP 大小 1,520,352 字节SHA-256367ab8650479aa1fa8fe73bd1ebadd9a53f46659c850c2e388fc799d5cb88e5bohosTest HAP 大小 2,360,824 字节SHA-256b7230037b51044fe16168d2c835fb891e1c70f675941a1046165bc895217592c。两份为未签名测试包。git diff --check与中英文资源键一致也通过。它们不验证视觉但能防止布局修复夹带格式和资源回归。完整证据必须结合代码、单元、Web、HAP、设备和截图。失败注入与恢复检查Preferences 存入字符串、NaN 或越界数值时parse 回默认或钳制窗口预算不足时二次 clamp设置读取失败时用默认 264保存失败时当前宽度保留并提示重启可能回旧值。手势取消恢复起始宽度不保存中间状态。Web 编辑器不响应容器变化时应检查父 Web bounds 与内部 CSS而不是重复 setDocument。搜索标签仍裁切时应先读取 sidebarWidth 与断点分支再检查资源和字体。首标签空白则检查 Scroll align不要改系统标题栏。自由窗口的诊断价值来自可分层设置值、ArkUI 轨道、子组件布局、Web viewport、内部编辑器。每层都有独立证据避免用一个视觉现象猜全部原因。性能与拖动连续性拖动每帧更新一个 StateArkUI 重新布局侧栏与剩余编辑区。没有每帧 Preferences flush只有 end 写一次没有 Bridge resize 消息搜索和文件树数据不重新加载。布局成本与当前组件树相关不与 Markdown 字符数线性增长。CodeMirror 由浏览器处理容器 Resize文档和撤销历史留在同一 EditorView。模拟器从 261 拖到 392 未重载内容。极高刷新率、超大 DOM 预览和真机 GPU 下的帧稳定性尚未量化后续可采集性能轨迹。断点跨越 300 时搜索选项结构重建一次成本固定。动画不是当前目标直接响应减少延迟和误命中。后续验证矩阵下一轮应加入 640×480 最小窗口、900 vp 阈值两侧、最大化与恢复、外接显示器缩放、系统大字体、深色主题、多标签溢出、冲突栏、设置长内容、图片预览、中文和英文、纯键盘与触控板。每项记录预期不变量和实际边界。真机需要验证窗口拖动区、物理指针光标、触控命中、系统缩放和屏幕阅读器。模拟器可以复现多数布局但不能代替硬件输入与显示管线。若引入 preferred/effective 侧栏双值还要测试窗口缩小后再放大恢复偏好若增加抽屉式侧栏则测试 900 以下的覆盖、焦点陷阱和 Escape 关闭。用例记录必须能复现而不是只写“正常”自由窗口测试报告应记录设备、应用提交、HAP 哈希、起始窗口、操作顺序、目标宽度、辅助功能边界和截图文件。以 392 vp 持久化为例可复现步骤是启动应用确认默认侧栏在调整柄上水平拖动读取 description强制停止进程重新启动再次读取调整柄与 Web bounds。只写“侧栏重启正常”无法判断测试的是内存状态还是持久化。搜索断点也要记录两组数值而不是一句“响应式正常”220 vp 时三个标签的纵坐标分成两组408 vp 时回到同一组。标签对齐则记录首标签或 Web 起点确认空白发生在系统标题栏而非应用 Scroll。结构化证据让以后修改字体、资源或侧栏常量时可以做前后比较。截图文件名要包含主题不用临时编号替代语义图片应保留完整应用窗口和系统标题栏避免裁切隐藏重叠。辅助树输出可放在测试报告二进制 HAP 用 SHA-256 绑定。这样一项视觉修复也能拥有接近代码测试的可追溯性。设备测试失败时同样需要记录实际边界。例如 Regex 右边界超过侧栏不应只写“文字不全”而应保存 sidebarWidth、Text bounds 和当前语言。诊断数据越接近约束修复越不容易退化成像素补丁。测试记录还应区分“布局改变”和“业务重置”。拖动侧栏后搜索结果仍在、文档脏状态不变、CodeMirror 光标未跳动才能证明只发生空间重排如果截图只展示空白文档许多状态回归不会显现。后续自由窗口用例应预先打开真实文档、建立搜索结果并保留未保存标记再执行窗口与侧栏操作。结论OhMarkdown 的自由窗口收口建立了明确约束640×480 根最小、900 vp 侧栏显隐、220-480 vp 可调范围、520 vp 编辑预算、300 vp 搜索局部断点、标签逻辑起点和持久化恢复。默认、220、392、408、480 等关键尺寸都有代码或设备证据。真正的鸿蒙 PC 适配不是把移动界面放大而是让每个区域在用户改变窗口和工作空间时仍保持可见、可操作、可解释。当前提交已解决用户指出的标签留白、侧栏不可调和搜索裁切同时清楚保留真机显示缩放、极端高度和偏好恢复等后续边界。