You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
失败是间歇的,说明更可能是时序而非纯确定性裁剪:几何变化后行 version 先更新、内容投影稍后才稳定,reconcile 在中间态比较到差异便永久失效。
建议的下一步(不改产品行为):在 reconcile 的各个失效分支加一次性诊断输出(epoch / id / version / text 不一致,及当时网格宽高),在 hosted 失败时抓取实际分支;若确认是中间态,再讨论"settle 后再 reconcile 一次"是否属于原版行为还是自研修补。
Activity
JavaZeroo commentedon Oct 28, 2026
追加离线事实: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代码;没有执行新重放或修改源码。
JavaZeroo commentedon Oct 29, 2026
本项的现象在今天的一次 push 回归中再次出现(PR #182,仅文档改动):
c1d4a2a。text_actions(7.77s):test_actual_system_copy_paste_and_shortcuts_twice(repeat=0)→ValueError: shell probe failed: actions geometry lost terminal selection。这与 #163 记录的四-case 机制诊断一致(动作几何恢复后选区失效),说明它在 hosted windows-2025 上是可复现的间歇失败,不是一次性事件。原有红例、机制见证与产品策略保持;本项继续开放,下一步仍是定位修复后的真实切点/根因。
JavaZeroo commentedon Oct 29, 2026
机制定位(代码级假设,待插桩验证)
已知失败签名(本轮再次出现):
actions geometry lost terminal selection,来自experiments/shell/workspace_actions_scenario.hpp:200–205——在该处current_find_range()仍存在,但has_selection()为 false,即 Find 命中在、选区丢了。沿着选区生命周期读代码,得到一个具体假设:
experiments/core/text_selection.hpp的TextSelection::reconcile(第 66 行起)维护:epoch变化 → 直接invalidate;行id/version/joins_next变化 → 重新读取并与保存的投影逐项比较,文字/跨度完全一致才修复。Screen::buffer_epoch只在invalidate_buffer_readers(第 194–198 行)与activate_buffer(第 204–221 行)递增;resize/DPI/zoom 本身不会改 epoch。transition(144) → 192 → 96 → zoom 75%之后立即检查选区(第 200 行)。这些几何变化会改变网格宽度,使行的投影宽度/裁剪状态变化;reconcile若此时看到的投影与保存的不一致,就按设计把整个选区置为失效。version先更新、内容投影稍后才稳定,reconcile在中间态比较到差异便永久失效。建议的下一步(不改产品行为):在
reconcile的各个失效分支加一次性诊断输出(epoch / id / version / text 不一致,及当时网格宽高),在 hosted 失败时抓取实际分支;若确认是中间态,再讨论"settle 后再 reconcile 一次"是否属于原版行为还是自研修补。本轮不做产品改动,#156 保持开放;该假设补充进本项的机制记录。
diagnose(shell): point the selection diagnostic at the preserved arti…
diagnose(shell): point the selection diagnostic at the preserved arti…
diagnose(core): keep the selection header newline refs #156
diagnose(core): keep the selection header newline refs #156
Merge pull request #186 from JavaZeroo/diag/selection-discard-reason
JavaZeroo commentedon Oct 29, 2026
诊断已合入并本地跑通,抓到了第一个分支:row-text-changed
PR #186 合入后(main
4b7d487),我在本地用 Release 构建跑了actions场景:产物目录里出现诊断文件:
含义
TERMFORGE_SELECTION_DIAG,失败时该文件随build/test-artifacts/保留;row-text-changed丢弃 —— 说明几何变化(DPI/zoom)过程中确有一次“行投影文本与保存快照不一致”的 reconcile;下一步(待 hosted 再红时执行)
selection-discard.jsonl,确认分支是row-text-changed还是其它;actions-progress.jsonl阶段时间对照;本条不改变产品行为;#156 保持开放。
JavaZeroo commentedon Oct 29, 2026
补充:诊断现在连行号一起记录(PR #187),本地 actions 场景每次运行的记录都是
{"schema":"termforge-selection-discard-v1","reason":"row-text-changed","row":0}即:几何变化过程中,选区第 0 行的投影文本与保存快照不一致,触发整体失效;场景仍通过说明这次丢弃发生在几何检查之后。
这对 #156 的意义:
row 0,则更可能是该行在 DPI/zoom 切换时确实被改写/裁剪,而不是任意行写入的干扰;actions-progress.jsonl的阶段记录对齐,确定丢弃发生在哪个 transition 之前/之后。仍未改 reconcile 行为,#156 保持开放。
Merge pull request #187 from JavaZeroo/diag/selection-row-detail
2 remaining items
Merge pull request #188 from JavaZeroo/diag/selection-timeline
JavaZeroo commentedon Oct 29, 2026
已按需复现,并拿到"丢弃发生在哪一步"的时序证据。
用 dispatch-only 作业在 hosted windows-2025 上重复跑
text_actions|selection_context(ctest --repeat until-fail:20),第 8 次text_actions即复现:失败那一轮的阶段时间线(
actions-progress.jsonl,t_ms):对照:其余 14 轮全部只出现一次丢弃,且都落在
live-selected与invalidated-copy-rejected之间(场景自己制造失效选区,row=0,属预期)。结论
geometry-zoom75之后、检查之前,即reset_zoom()把 75% 还原为 100% 的那次几何变化;find_range{id:97,version:134,0..11}),只是选区被置为 invalidated;reconcile按设计整段失效 → 场景在下一阶段断言失败。下一步
证据:run 40897620314 的
actions-repeat-evidence工件(含两份 JSONL);分支ci/actions-repeat只含这个复现作业的定义。Merge pull request #189 from JavaZeroo/diag/selection-details
JavaZeroo commentedon Oct 29, 2026
机制收敛:这不是"随机竞态",而是"物理裁剪导致选区失效"在特定网格宽度下被触发。
证据链:
row-text-changed,行号 78(选区末行),发生在geometry-zoom75之后、检查之前;TextSelection::reconcile只在投影文本与跨度逐项相同时修复,否则整段失效;experiments/core/selection_test.cpp中 "real crop invalidates range"(第 82 行)断言裁剪必然使选区失效;这解释了为什么本地(大屏、列数多)跑 20 次全绿,而 hosted 的 1024×768 桌面列数少、更容易裁剪:失败取决于环境网格宽度,不是计时运气。
由此得到的两个方向(都需用户确认,属重要行为)
在确认前不改产品代码;诊断(含 saved/current bytes+columns)已就绪,下一次命中即可确认失败帧确实是"列数变化 + 文本截断"。
JavaZeroo commentedon Oct 29, 2026
抓到失败帧的行级数据后,线索指向产品侧:resize 后"行 id 不变但内容被换掉"。
失败帧(run 40897620314,
run-x25yyqjk/system-0)关键对比,bottom 终端:TF_FIND_078TF_FIND_079TF_FIND_READY 080TF_FIND_078TF_FIND_079TF_FIND_READY 080TF_FIND_075TF_FIND_076TF_FIND_077TF_FIND_078也就是说:回到 100% 后,id 98–101 这些行的内容整体上移了 3 行,同时末尾出现 id 105–107。
选区快照里第 78 行(对应 id 98 的内容
TF_FIND_078)因此比对失败 → 整段失效。为什么这值得当成产品问题
ADR-0005 的既定语义是物理行裁剪、不 reflow:缩放/DPI 变化应当只改变可见裁剪范围,不应改写既有行 id 的内容。
现在观察到的却是"id 保留、内容被替换",这更像 resize 路径上的行重建/复用,而不是"内容真的变了"。
下一步(建议)
Screen::resize后各行 id 与文本是否保持;诊断字段(saved/current bytes+columns)已在 main,下一次命中可再确认是否为"列数变化 + 文本截断"。
JavaZeroo commentedon Oct 29, 2026
核心层验证:
Screen::resize保持行 id 与内容正确;异常来自 shell/滚动层(或 resize 期间并发输出)。复现方案(本地、不依赖托管):构造
Parser(7, 111, 2000),写入 82 行TF_FIND_%03d needle abc,然后按失败帧的几何序列 resize:结论:核心 resize 不会出现"id 保留、内容被换";行 id 与文本在 111→149→111 往返后完全保持,新增可见行获得新 id,回收时被丢弃。
因此失败帧里看到的"id 98–101 内容整体位移 3 行 + 末尾出现 id 105–107",不是核心 Screen 的 resize 行为,更可能来自:
下一步
termforge_shell做同样的 111→149→111 变化(可复用现有 scenario 的 transition 序列),对比document_lines的 id/文本;本轮结论把 #156 的排查范围从"核心"缩小到"shell/并发",且给出了可重复的本地核心对照。
JavaZeroo commentedon Oct 29, 2026
核心层第二次对照:带历史的 resize 也不产生重复/错位行。
构造
Parser(7, 111, 2000),写入 200 行后按失败帧序列 resize:即:核心层在有历史的情况下,resize 往返同样保持行 id/内容、不产生重复行。
因此失败帧里看到的"id 98–101 内容与前面 3 行重复"更可能来自观测路径的采样时机(
observed_document_lines()在 resize/publish 过程中读到过渡态),而不是终端状态本身。这改变了 #156 的下一步
document_lines(或用visible_lines交叉校验),若两次不一致,说明现象是采样竞态;row-text-changed row=78)本身是真实的,仍需要决定 A(容忍裁剪差异)或 B(调整场景期望)。本地对照程序在
local/resize_id_test.cpp与local/resize_scroll_test.cpp(未入库)。JavaZeroo commentedon Oct 29, 2026
失败帧的可见行与文档行一致,但文档里出现了"重复行"——更像 resize 缩小时把可见行既推入历史又留在原位。
同一失败帧(
run-x25yyqjk/system-0,phase=geometry-failure,grid 111×7):visible_lines正好等于document_lines的最后 7 行 → 观测路径一致,不是采样错位;document_lines里 075/076/077 出现两份(id 95–97 与 id 98–100)→ 文档层面确实出现了重复行;我上一轮的核心对照(
Parser(7,111)写 200 行后 10 行→7 行)没有复现重复;差别很可能在光标/滚动状态:对照里光标始终在底部,场景里底部终端由子进程持续输出、光标位置与滚动起点不同。下一步(本地可做):把核心对照改成"先向上滚动若干行/把光标移出底部"再做 10→7 缩小,检查是否出现重复行;若复现,则是
Screen::resize在"非底部光标 + 缩小高度"路径下的真实缺陷,可直接修并加回归测试。选区丢弃(row 78)在形式上仍是
row-text-changed,但根因若是这个重复行,则属于产品缺陷而非场景期望问题。JavaZeroo commentedon Oct 29, 2026
尝试用现场录制的字节流在核心层复现(进展 + 一个限制)
失败帧目录里有底部终端的录制流
actions-geometry-bottom.transport.bin(1071 B),内容确实是原始 VT:但把它喂给核心
Parser会在第一条序列就抛错:即核心只接受
?1049与?7两种私有 CSI,而子进程实际会发?25l(隐藏光标)。说明 shell 的输入路径上还有一层过滤/忽略逻辑,直接回放录制流无法等价复现 shell 的实际状态。下一步
JavaZeroo commentedon Oct 29, 2026
定位完成:失败由子进程重复重绘引起,不是产品缺陷
决定性复现(本地、同源字节)
失败帧目录里保存了底部终端的原始输出流
actions-geometry-bottom.transport.bin(946 字符)。直接统计:即:子进程在这段时间里从原点重绘了 5 次(每次
ESC[?25l ESC[H后重打整块内容)。把这个流用
Parser(7,111,2000,ParseMode::recovery)回放(recovery 模式与 shell 一致),立刻复现出重复行:在 resize 之前就已经如此,说明几何变化只是"重绘落点"的时机,不是原因。
与失败帧吻合
失败帧文档尾部出现 075/076/077 各两份(id 95–97 与 id 98–100),与重绘产生的历史重复完全一致;选区第 78 行正是被重绘改写的那一行,于是
row-text-changed整段失效。结论
TextSelection::reconcile的既定正确行为:文档内容确实变了;ESC[H重画整块),不是 resize/DPI/zoom 逻辑,也不是核心行身份缺陷;后续(不属于产品缺陷)
场景侧需要避免在子进程可能重绘时断言"选区跨几何变化存活":可在几何阶段前让子进程静默,或把断言改为"内容未变时才要求选区存活"。这条作为测试卫生问题跟踪(#38 的失败形状记录里已多次出现同类时序抖动)。
据此关闭本项:定位目标已达成,产品代码不需要修改。
JavaZeroo commentedon Oct 29, 2026
补充修正(关闭后新增证据):这份"重复重绘"其实是ConPTY 在 resize 后的重绘,不是子进程自己重画。
把录制流按转义符拆开后,结构非常规整——5 次完全相同的尾部重绘,与 5 次几何变化(144/192/96/zoom75/restored)一一对应:
而场景里的文本夹具
text_client.cpp只在启动时打印一次(ESC[2J ESC[H+ 81 行 + READY),之后只在收到+ # ~ -时输出。它不会周期性重绘。所以这 5 次"从 home 重打尾部 6 行"更可能是 ConPTY/conhost 在窗口尺寸变化后发出的重绘(光标隐藏→回 home→重打可见尾部→恢复光标)。
这对结论的影响:
reconcile判 invalid 正确);我先保持 #156 关闭(定位目标已达成),把这条修正留在这里,避免后续误判归因。
diagnose(shell): capture the full output stream for replay refs #156
Merge pull request #190 from JavaZeroo/diag/output-capture