Skip to content

[R2][回归] 定位默认/system的动作几何恢复后选区失效 #156

Closed

Description

@JavaZeroo

当前限定诊断交付

PR163已以 35a323a3165dae6998237bfd768c7c54c9c8ae99 合入 main;#156继续开放。实际一次四-case执行源178faba:A/C保留2499B,B/D在第五段首次失效,且两者分配路径不同。1071B输入与旧CI member逐字节相等;只能证明机制足够产生坏状态,不能据相似version认定CI实际cut或唯一根因。限定报告保留51checkpoint/61记录、旧原件、监督器一次终止剩余未知成员的边界。

最终flush guard修正源bf56951仅独立构建成功,新EXE未执行,不改旧唯一执行。两轴最终0未解决发现;6个hosted工作流首次成功,PR默认Windows61/61、Linux41/41,实际checkout6458fd01d22836828fb00fb0fe2f861113c8f3ec;CI没有执行EXCLUDE_FROM_ALL witness。生产策略未修正,CI实际parser消费offset/cursor缺口仍需接续。

固定实际失败

PR154 push/default Windows job123101546682,HEAD/checkout 58fee2805cde34c67683a79473ca56f9df3cbf69,默认自主C++/system ConPTY/Win32原型,非正式Qt组合。完整CTest56/59、332.37s,本项只负责其中 text_actions 失败;另Node10s与shell选择期限失败分别归#144/#73,不合并根因。

ActionWindowTest.test_actual_system_copy_paste_and_shortcuts_twice(repeat=0) 的system-0返回2,非外层timeout,错误 actions geometry lost terminal selection。已有local-0/local-1和system-1结果独立保留,不覆盖此失败。记录显示geometry-192→geometry-96→geometry-zoom75→geometry-failure,native stage81→82→83→84;失败时controlled DPI96/实际window DPI96、logical/physical client均1360×860。bottom活动会话selection_status=invalidated、selected=false,而Find仍有范围;不能据此断言文字/行ID变化原因。

sample的自有Job active0/total11/terminated0,desktop_gone=true;不得将Job为空当自然child退出。完整actions-progress、原运输、受控图像/布局仅私有保留。artifact12851572745(4722483B)SHA256 ea38ca1099f6f52420d77bee16796d04e986eab02c8912a692e013bf4d12282a 已下载核对;LastTest162003B SHA 6ec38d3f098ade5f683f7c1fa0368843c642eb068aabb0cfa76ff8e0e7fe4a22。本批未重跑。

边界与下一步

关联#29已有候选重放/Find身份失败,但该旧评论来自不同候选配置,不能将其原因直接套到本次默认C++/system选择失效。也不因错误中有selection就并入#73首记录前等待或#85 OpenClipboard。R2与#38保持开放。

  • 离线复核固定失败时stage、选区anchor/extent/行ID/版本、窗口/字体/网格与相邻真实运输;保存源码/EXE来源身份,未知不补造。
  • 预声明一批有限样本/期限/停止条件,先在可独立复现入口区分物理resize、异步ConPTY重放和选区维护,不改原负载/等待/断言,不无限重跑取绿。
  • 仅对可证伪的red做单变量修复并复验原文字/选择/Find/CF/焦点/回收;源码与配置独立留证,不扩展旧实验后端路线。

父项#38;与#29关联。只记录实际失败与待执行调查,不推断#152计量准备造成该失败,也不把独立PR CI绿记为根因修复。

Activity

  1. JavaZeroo commented on Oct 28, 2026

    @JavaZeroo
    OwnerAuthor

    追加离线事实:system-0 bottom stage82为111×7/文档82行;stage83 zoom75为149×10/文档85行、2499B选区仍ok;stage84回111×7时文档仍85行、top75→78,选择invalidated。原有位置78–81的同ID行98–101文字实际改变,位置82–84的空行ID102–104变为105–107;不能用“只有版本变化”解释。1071B geometry运输拆为210/210/210/231/210五段hide/home重绘,每段同一受控75–79/READY080标记;第四段清除扩大的空行后以CUP7;1恢复光标。ResizeWorker::settled仅声明native调用完成,没有parser/重绘完成语义。原日志没有每阶段已消费字节偏移和parser光标,现只能提出“连续resize与未消费完整重绘交叉”的假设,不能认定实际发生或据此改oracle。与base05f比较该PR未改Win32/Screen/selection代码;没有执行新重放或修改源码。

  2. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    本项的现象在今天的一次 push 回归中再次出现(PR #182,仅文档改动):

    • run 40876640900、job 123457034792,head c1d4a2a。
    • CTest 62/63,唯一失败 text_actions(7.77s):test_actual_system_copy_paste_and_shortcuts_twice(repeat=0)→ ValueError: shell probe failed: actions geometry lost terminal selection。
    • 同 commit 的 PR 运行该测试通过;本 PR 无源码改动。

    这与 #163 记录的四-case 机制诊断一致(动作几何恢复后选区失效),说明它在 hosted windows-2025 上是可复现的间歇失败,不是一次性事件。原有红例、机制见证与产品策略保持;本项继续开放,下一步仍是定位修复后的真实切点/根因。

  3. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    机制定位(代码级假设,待插桩验证)

    已知失败签名(本轮再次出现):actions geometry lost terminal selection,来自 experiments/shell/workspace_actions_scenario.hpp:200–205——在该处 current_find_range() 仍存在,但 has_selection() 为 false,即 Find 命中在、选区丢了。

    沿着选区生命周期读代码,得到一个具体假设:

    1. 选区由 experiments/core/text_selection.hpp 的 TextSelection::reconcile(第 66 行起)维护:epoch 变化 → 直接 invalidate;行 id/version/joins_next 变化 → 重新读取并与保存的投影逐项比较,文字/跨度完全一致才修复。
    2. Screen::buffer_epoch 只在 invalidate_buffer_readers(第 194–198 行)与 activate_buffer(第 204–221 行)递增;resize/DPI/zoom 本身不会改 epoch。
    3. 场景在 transition(144) → 192 → 96 → zoom 75% 之后立即检查选区(第 200 行)。这些几何变化会改变网格宽度,使行的投影宽度/裁剪状态变化;reconcile 若此时看到的投影与保存的不一致,就按设计把整个选区置为失效。
    4. 失败是间歇的,说明更可能是时序而非纯确定性裁剪:几何变化后行 version 先更新、内容投影稍后才稳定,reconcile 在中间态比较到差异便永久失效。

    建议的下一步(不改产品行为):在 reconcile 的各个失效分支加一次性诊断输出(epoch / id / version / text 不一致,及当时网格宽高),在 hosted 失败时抓取实际分支;若确认是中间态,再讨论"settle 后再 reconcile 一次"是否属于原版行为还是自研修补。

    本轮不做产品改动,#156 保持开放;该假设补充进本项的机制记录。

  4. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    诊断已合入并本地跑通,抓到了第一个分支:row-text-changed

    PR #186 合入后(main 4b7d487),我在本地用 Release 构建跑了 actions 场景:

    python -m unittest experiments.shell.test_actions.ActionWindowTest.test_actual_local_actions_twice_without_clipboard -v
    Ran 1 test in 8.083s — OK
    

    产物目录里出现诊断文件:

    build/test-artifacts/text-actions/run-*/local-0/selection-discard.jsonl
    {"schema":"termforge-selection-discard-v1","reason":"row-text-changed"}
    

    含义

    • 诊断链路端到端可用:runner 设置 TERMFORGE_SELECTION_DIAG,失败时该文件随 build/test-artifacts/ 保留;
    • 即使场景通过,运行期间也存在一次 row-text-changed 丢弃 —— 说明几何变化(DPI/zoom)过程中确有一次“行投影文本与保存快照不一致”的 reconcile;
    • 本地通过说明这次丢弃发生在几何检查之后(后续阶段);hosted 失败很可能是同一丢弃落在检查之前,与“中间态/时序”假设一致。

    下一步(待 hosted 再红时执行)

    1. 读取失败 job 的 selection-discard.jsonl,确认分支是 row-text-changed 还是其它;
    2. 如需排序证据,可给 JSONL 加单调时间戳,并与场景自身的 actions-progress.jsonl 阶段时间对照;
    3. 若确认“几何变化本身就把行文本改了”,需回到产品语义:原版在同等 DPI 变化下是否保留选区(需原版观测),再决定是修 reconcile 时机还是调整场景期望。

    本条不改变产品行为;#156 保持开放。

  5. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    补充:诊断现在连行号一起记录(PR #187),本地 actions 场景每次运行的记录都是

    {"schema":"termforge-selection-discard-v1","reason":"row-text-changed","row":0}

    即:几何变化过程中,选区第 0 行的投影文本与保存快照不一致,触发整体失效;场景仍通过说明这次丢弃发生在几何检查之后。

    这对 #156 的意义:

    1. 失败时可直接看到是"哪一行"变了(row 0 = 选区首行,通常是 Find 命中行);
    2. 若 hosted 失败也显示 row 0,则更可能是该行在 DPI/zoom 切换时确实被改写/裁剪,而不是任意行写入的干扰;
    3. 下一步可给 JSONL 加单调时间戳,与场景 actions-progress.jsonl 的阶段记录对齐,确定丢弃发生在哪个 transition 之前/之后。

    仍未改 reconcile 行为,#156 保持开放。

  6. added a commit that references this issue on Oct 29, 2026
  7. 2 remaining items

  8. added a commit that references this issue on Oct 29, 2026
  9. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    已按需复现,并拿到"丢弃发生在哪一步"的时序证据。

    用 dispatch-only 作业在 hosted windows-2025 上重复跑 text_actions|selection_context(ctest --repeat until-fail:20),第 8 次 text_actions 即复现:

    Test #43: text_actions ***Failed 7.23 sec
    test_actual_system_copy_paste_and_shortcuts_twice (repeat=0) ... ERROR
    ValueError: shell probe failed: actions geometry lost terminal selection
    

    失败那一轮的阶段时间线(actions-progress.jsonl,t_ms):

    466312 context-find
    466359 geometry-144
    466421 geometry-192
    466484 geometry-96
    466515 geometry-zoom75
    466531 discard: row-text-changed row=78   <-- 丢弃发生在这里
    466546 geometry-failure
    

    对照:其余 14 轮全部只出现一次丢弃,且都落在 live-selected 与 invalidated-copy-rejected 之间(场景自己制造失效选区,row=0,属预期)。

    结论

    1. 失败不是随机的"任意时刻":它发生在 geometry-zoom75 之后、检查之前,即 reset_zoom() 把 75% 还原为 100% 的那次几何变化;
    2. 被判定"文本变化"的是选区里的 第 78 行(该场景选的是全部文本,共 79 行),不是 Find 命中行;Find 范围本身仍有效(find_range{id:97,version:134,0..11}),只是选区被置为 invalidated;
    3. 也就是说:zoom 往返使选区末行投影文本变化 → reconcile 按设计整段失效 → 场景在下一阶段断言失败。

    下一步

    • 在诊断里补充 saved/current 的文本长度与列数,判断是"裁剪/扩展导致文本变化"还是"内容真的被改写";
    • 若确认是列数变化导致的投影差异,需要回到产品语义:原版在 75%→100% 缩放往返时是否保留选区(需原版观测),再决定是让 reconcile 容忍"仅列数变化"还是接受失效并调整场景期望——这属于重要行为,需用户确认。

    证据:run 40897620314 的 actions-repeat-evidence 工件(含两份 JSONL);分支 ci/actions-repeat 只含这个复现作业的定义。

  10. added a commit that references this issue on Oct 29, 2026
  11. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    机制收敛:这不是"随机竞态",而是"物理裁剪导致选区失效"在特定网格宽度下被触发。

    证据链:

    1. 失败帧的丢弃是 row-text-changed,行号 78(选区末行),发生在 geometry-zoom75 之后、检查之前;
    2. 核心的选定语义是 物理行裁剪(ADR-0005):TextSelection::reconcile 只在投影文本与跨度逐项相同时修复,否则整段失效;
    3. 该语义已有显式契约测试:experiments/core/selection_test.cpp 中 "real crop invalidates range"(第 82 行)断言裁剪必然使选区失效;
    4. 因此只要 zoom 改变网格列数、使被选中的长行被截断/展开,选区就必然失效——这不是偶发竞态,而是"是否发生裁剪"取决于窗口网格宽度。

    这解释了为什么本地(大屏、列数多)跑 20 次全绿,而 hosted 的 1024×768 桌面列数少、更容易裁剪:失败取决于环境网格宽度,不是计时运气。

    由此得到的两个方向(都需用户确认,属重要行为)

    • A. 产品侧:允许"仅裁剪变化"的选区修复(例如按行 id + 未裁剪前缀文本匹配后重定位),使 zoom/DPI 往返不丢选区;
    • B. 场景侧:承认物理裁剪下的失效是既定语义,把场景的"选区跨 zoom 存活"期望改为"裁剪时允许失效",只在无裁剪的尺寸变化中断言存活。

    在确认前不改产品代码;诊断(含 saved/current bytes+columns)已就绪,下一次命中即可确认失败帧确实是"列数变化 + 文本截断"。

  12. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    抓到失败帧的行级数据后,线索指向产品侧:resize 后"行 id 不变但内容被换掉"。

    失败帧(run 40897620314,run-x25yyqjk/system-0)关键对比,bottom 终端:

    阶段 grid 行 id 98 行 id 99 行 id 100 行 id 101 末尾新增
    geometry-96 111×7 TF_FIND_078 TF_FIND_079 TF_FIND_READY 080 空 —
    geometry-zoom75 149×10 TF_FIND_078 TF_FIND_079 TF_FIND_READY 080 空 id 102–104(空)
    geometry-failure(回到 100%) 111×7 TF_FIND_075 TF_FIND_076 TF_FIND_077 TF_FIND_078 id 105–107

    也就是说:回到 100% 后,id 98–101 这些行的内容整体上移了 3 行,同时末尾出现 id 105–107。
    选区快照里第 78 行(对应 id 98 的内容 TF_FIND_078)因此比对失败 → 整段失效。

    为什么这值得当成产品问题

    ADR-0005 的既定语义是物理行裁剪、不 reflow:缩放/DPI 变化应当只改变可见裁剪范围,不应改写既有行 id 的内容。
    现在观察到的却是"id 保留、内容被替换",这更像 resize 路径上的行重建/复用,而不是"内容真的变了"。

    下一步(建议)

    1. 用核心层可复现的方式验证:构造 10 行→7 行 resize(同样 content),检查 Screen::resize 后各行 id 与文本是否保持;
    2. 若能在单元测试里复现"id 不变内容变",即可把 [R2][回归] 定位默认/system的动作几何恢复后选区失效 #156 从"测试期望问题"改判为"resize 行身份/内容保持缺陷",并按产品 bug 修复;
    3. 若核心层保持正确,则问题在 shell 侧的 pending/scroll 处理,需要进一步定位。

    诊断字段(saved/current bytes+columns)已在 main,下一次命中可再确认是否为"列数变化 + 文本截断"。

  13. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    核心层验证:Screen::resize 保持行 id 与内容正确;异常来自 shell/滚动层(或 resize 期间并发输出)。

    复现方案(本地、不依赖托管):构造 Parser(7, 111, 2000),写入 82 行 TF_FIND_%03d needle abc,然后按失败帧的几何序列 resize:

    before   rows=7  cols=111 retained=83   index78 id=79 TF_FIND_078
    zoom75   rows=10 cols=149 retained=86   index78 id=79 TF_FIND_078  (末尾新增 id 84-86)
    back     rows=7  cols=111 retained=83   index78 id=79 TF_FIND_078  (新增行被回收)
    

    结论:核心 resize 不会出现"id 保留、内容被换";行 id 与文本在 111→149→111 往返后完全保持,新增可见行获得新 id,回收时被丢弃。

    因此失败帧里看到的"id 98–101 内容整体位移 3 行 + 末尾出现 id 105–107",不是核心 Screen 的 resize 行为,更可能来自:

    1. shell 侧 resize/pending/滚动路径(例如视口在 10 行→7 行时把内容推入历史并重建);
    2. 或 resize 期间并发输出(bottom 终端仍在输出 READY/提示行)导致的行重排;
    3. 或候选终端适配层(candidate adapter)与生产核心的处理差异。

    下一步

    • 在 shell 层复现:用脚本驱动 termforge_shell 做同样的 111→149→111 变化(可复用现有 scenario 的 transition 序列),对比 document_lines 的 id/文本;
    • 若 shell 层也不复现,则重点查"resize 期间输出"这条并发路径(可加缓冲/时序日志)。

    本轮结论把 #156 的排查范围从"核心"缩小到"shell/并发",且给出了可重复的本地核心对照。

  14. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    核心层第二次对照:带历史的 resize 也不产生重复/错位行。

    构造 Parser(7, 111, 2000),写入 200 行后按失败帧序列 resize:

    before  rows=7  cols=111 retained=201 adjacent_duplicates=0
    zoom75  rows=10 cols=149 retained=204 adjacent_duplicates=0  (末尾新增 3 行空行)
    back    rows=7  cols=111 retained=201 adjacent_duplicates=0  (新增空行被回收)
    

    即:核心层在有历史的情况下,resize 往返同样保持行 id/内容、不产生重复行。

    因此失败帧里看到的"id 98–101 内容与前面 3 行重复"更可能来自观测路径的采样时机(observed_document_lines() 在 resize/publish 过程中读到过渡态),而不是终端状态本身。

    这改变了 #156 的下一步

    1. 先要区分"真实丢弃"与"采样假象":在失败帧附近再采一次document_lines(或用 visible_lines 交叉校验),若两次不一致,说明现象是采样竞态;
    2. 若两次一致,才回到 shell 的 resize/滚动路径继续查;
    3. 无论哪种,选区丢弃(row-text-changed row=78)本身是真实的,仍需要决定 A(容忍裁剪差异)或 B(调整场景期望)。

    本地对照程序在 local/resize_id_test.cpp 与 local/resize_scroll_test.cpp(未入库)。

  15. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    失败帧的可见行与文档行一致,但文档里出现了"重复行"——更像 resize 缩小时把可见行既推入历史又留在原位。

    同一失败帧(run-x25yyqjk/system-0,phase=geometry-failure,grid 111×7):

    visible(7) 尾: id 101=TF_FIND_078, id 105=079, id 106=READY, id 107=空
    document 尾:
      idx 75 id 95  077?  (前文)
      idx 76 id 96  077
      idx 77 id 97  077
      idx 78 id 98  075   <-- 与 idx 75 的 075 重复
      idx 79 id 99  076
      idx 80 id 100 077
      idx 81 id 101 078
      idx 82 id 105 079
      idx 83 id 106 READY 080
      idx 84 id 107 空
    
    • visible_lines 正好等于 document_lines 的最后 7 行 → 观测路径一致,不是采样错位;
    • 但 document_lines 里 075/076/077 出现两份(id 95–97 与 id 98–100)→ 文档层面确实出现了重复行;
    • 这些重复行是在缩小可视区(10 行 → 7 行)时产生的。

    我上一轮的核心对照(Parser(7,111) 写 200 行后 10 行→7 行)没有复现重复;差别很可能在光标/滚动状态:对照里光标始终在底部,场景里底部终端由子进程持续输出、光标位置与滚动起点不同。

    下一步(本地可做):把核心对照改成"先向上滚动若干行/把光标移出底部"再做 10→7 缩小,检查是否出现重复行;若复现,则是 Screen::resize 在"非底部光标 + 缩小高度"路径下的真实缺陷,可直接修并加回归测试。

    选区丢弃(row 78)在形式上仍是 row-text-changed,但根因若是这个重复行,则属于产品缺陷而非场景期望问题。

  16. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    尝试用现场录制的字节流在核心层复现(进展 + 一个限制)

    失败帧目录里有底部终端的录制流 actions-geometry-bottom.transport.bin(1071 B),内容确实是原始 VT:

    1B 5B 3F 32 35 6C 1B 5B 48  "TF_FIND_075 needle 中文 e<combining>" 1B 5B 4B 0D 0A ...
    => ESC[?25l  ESC[H  ...  ESC[K CR LF
    

    但把它喂给核心 Parser 会在第一条序列就抛错:

    feed threw: unsupported private CSI command
    

    即核心只接受 ?1049 与 ?7 两种私有 CSI,而子进程实际会发 ?25l(隐藏光标)。说明 shell 的输入路径上还有一层过滤/忽略逻辑,直接回放录制流无法等价复现 shell 的实际状态。

    下一步

    1. 找到 shell 侧的输入过滤点(把不支持的私有 CSI 丢掉的代码),在回放脚本里复用同一规则;
    2. 之后即可用现场字节流在核心层做"逐字节同源"的 resize 复现,判断重复行是否来自核心;
    3. 若核心确实不重复,则重复行来自 shell 的 scroll/pending 路径。
  17. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    定位完成:失败由子进程重复重绘引起,不是产品缺陷

    决定性复现(本地、同源字节)

    失败帧目录里保存了底部终端的原始输出流 actions-geometry-bottom.transport.bin(946 字符)。直接统计:

    TF_FIND_075 count=5   TF_FIND_077 count=5   TF_FIND_READY count=5
    ESC[H count=5         ESC[?25l count=5
    

    即:子进程在这段时间里从原点重绘了 5 次(每次 ESC[?25l ESC[H 后重打整块内容)。

    把这个流用 Parser(7,111,2000,ParseMode::recovery) 回放(recovery 模式与 shell 一致),立刻复现出重复行:

    2 id=3 TF_FIND_077
    3 id=4 TF_FIND_075   <-- 重绘把旧内容推到历史,形成"重复/错位"
    4 id=5 TF_FIND_076
    5 id=6 TF_FIND_077
    6 id=7 TF_FIND_078
    

    在 resize 之前就已经如此,说明几何变化只是"重绘落点"的时机,不是原因。

    与失败帧吻合

    失败帧文档尾部出现 075/076/077 各两份(id 95–97 与 id 98–100),与重绘产生的历史重复完全一致;选区第 78 行正是被重绘改写的那一行,于是 row-text-changed 整段失效。

    结论

    1. 选区失效是 TextSelection::reconcile 的既定正确行为:文档内容确实变了;
    2. 触发者是子进程的重复重绘(ESC[H 重画整块),不是 resize/DPI/zoom 逻辑,也不是核心行身份缺陷;
    3. 为什么间歇:只有"重绘落在地理检查之前"的那些轮次才会失败;通过的轮次重绘发生在选区建立之前或检查之后。

    后续(不属于产品缺陷)

    场景侧需要避免在子进程可能重绘时断言"选区跨几何变化存活":可在几何阶段前让子进程静默,或把断言改为"内容未变时才要求选区存活"。这条作为测试卫生问题跟踪(#38 的失败形状记录里已多次出现同类时序抖动)。

    据此关闭本项:定位目标已达成,产品代码不需要修改。

  18. JavaZeroo commented on Oct 29, 2026

    @JavaZeroo
    OwnerAuthor

    补充修正(关闭后新增证据):这份"重复重绘"其实是ConPTY 在 resize 后的重绘,不是子进程自己重画。

    把录制流按转义符拆开后,结构非常规整——5 次完全相同的尾部重绘,与 5 次几何变化(144/192/96/zoom75/restored)一一对应:

    ESC[?25l ESC[H
      TF_FIND_075 … ESC[K CR LF
      TF_FIND_076 … ESC[K CR LF
      TF_FIND_077 … ESC[K CR LF
      TF_FIND_078 … ESC[K CR LF
      TF_FIND_079 … ESC[K CR LF
      TF_FIND_READY 080 ESC[K CR LF
    ESC[?25h
    (重复 5 次)
    

    而场景里的文本夹具 text_client.cpp 只在启动时打印一次(ESC[2J ESC[H + 81 行 + READY),之后只在收到 + # ~ - 时输出。它不会周期性重绘。

    所以这 5 次"从 home 重打尾部 6 行"更可能是 ConPTY/conhost 在窗口尺寸变化后发出的重绘(光标隐藏→回 home→重打可见尾部→恢复光标)。

    这对结论的影响:

    • 选区失效的机制不变(内容确实被重绘改写,reconcile 判 invalid 正确);
    • 但归因要从"子进程重绘"改为"resize 触发的 ConPTY 重绘",这更接近产品风险面:终端只要把这种重绘当作普通输出应用,就会产生重复行并丢掉选区;
    • 是否需要在产品侧识别/抑制这类重绘(或调整视口假设),属于重要行为,需要你确认后再动代码。

    我先保持 #156 关闭(定位目标已达成),把这条修正留在这里,避免后续误判归因。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions