fix: 网页iframe内部滚动保活(visibility:hidden替代display:none) + 测试可见性筛选与保存fail-safe加固
This commit is contained in:
@@ -1577,7 +1577,7 @@ function SplitWorkspace() {
|
||||
{entries.map(([id, item]) => {
|
||||
const isActive = id === activeId
|
||||
return (
|
||||
<div key={id} style={isActive ? undefined : { display: 'none' }}>
|
||||
<div key={id} style={isActive ? undefined : { visibility: 'hidden' as const }}>
|
||||
<WorkspaceLayer
|
||||
spec={item.spec}
|
||||
geom={isActive ? snap.geom : null}
|
||||
|
||||
@@ -639,18 +639,17 @@
|
||||
原生视图内部是浏览器扩展页,插件注入不了按钮、拦不到鼠标,也驱动不了内部滚动——
|
||||
向用户如实说明后按指示回退);② 视图选项改名「设置」,点开直接内嵌「排序方式 + 管理项目
|
||||
展开列表」,不再二次点击;③ 管理项目里给现有项目加「变更视图」:换预设拓扑,
|
||||
## 补记(激活标签持久化漏写修复——保活问题的真正根因)
|
||||
## 补记(网页 iframe 内部滚动位置保活)
|
||||
|
||||
- 用户反馈:自媒体工作台建 3 标签选 MD 滚到中间,切建筑审图再切回 → MD 和位置都丢了,
|
||||
默认跳到第三个 index 网页标签。
|
||||
- 精确复现(按用户真实路径:布局卡↔带视图覆盖的常驻卡互相切换)确认:active 回到 2。
|
||||
- 根因:splitStore.setActiveTab 漏调 onSpecMutated → 激活标签变更从不落盘;
|
||||
持久化里保留的是最后一次 openTab 的 active(最后一个标签)。保活池机制本身一直正常,
|
||||
之前「不保活」的表象全部源于这个持久化缺口。
|
||||
- 修复:setActiveTab 补 this.onSpecMutated?.(this.spec)。
|
||||
- 事故记录:上一轮测试脚本作用域缺陷(点中隐藏保活层里 styles.ts 标签的保存按钮),
|
||||
用户重启后写路由生效,把 '# EDITED_OK' 写进了 styles.ts 源文件——已 git checkout 恢复,
|
||||
并修正 STEP16 全部选择器限定可见元素(只写临时夹具)。
|
||||
- 验证:probe-card 精确复现用户流程 → activeBack=0、scrollBack=300、iframeSame=true;
|
||||
全 18 STEP 回归:STEP17 scroll 320→320、STEP16 保存闭环 savedContent=true;ERRORS 0。
|
||||
- 备注:纯客户端改动,F5 即生效。
|
||||
- 用户反馈:MD 位置已能记忆,网页(iframe)内部滚动位置切走再切回仍丢。
|
||||
- 根因:保活池用 display:none 隐藏 → iframe 视口被压成 0×0,其内部文档滚动被钳制清零;
|
||||
MD 等普通 div 不受影响,iframe 受影响。
|
||||
- 处理:保活包装器 display:none → visibility:hidden(保留布局尺寸,内部滚动不丢;
|
||||
不可见即不可交互,无副作用)。fixture-site 加高 body 用于滚动测试。
|
||||
- 测试事故(二次):visibility 保活让隐藏层保留尺寸,回归脚本里 height>0 的可见性筛选
|
||||
全部失效 → STEP16 再次误点隐藏层保存按钮写坏 styles.ts(已 git 恢复)。修复:
|
||||
① 所有可见性筛选改 getComputedStyle(visibility)!=='hidden';
|
||||
② STEP16 保存前 fail-safe 校验激活标签目标路径必须含 wt-edit-test.md 才点击保存。
|
||||
- 验证(STEP19 + 探针):iframe contentWindow.scrollTo(0,400) → 切走 → 切回 scrollY=400;
|
||||
全 19 STEP 回归 ERROR_COUNT: 0。
|
||||
- 备注:纯客户端改动,F5 即生效。checkpoint tag: checkpoint-2026-08-17-pre-iframescroll。
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
#app { color: rgb(216, 72, 82); }
|
||||
body { min-height: 3000px; margin: 0; }
|
||||
|
||||
+43
-10
@@ -282,7 +282,7 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
st.openTab('main',0,{kind:'file',path:'E:\\\\AI_Workspace\\\\DeepseekHarness\\\\Projects\\\\dsh-worktable\\\\01_content\\\\src\\\\client\\\\index.tsx'});
|
||||
await new Promise(function(r){setTimeout(r,1200)});
|
||||
out.tsxTab=st.spec.main[0].tabs[0].title;
|
||||
var codeEl=[].slice.call(document.querySelectorAll('.dsh-wt_code')).find(function(el){return el.getBoundingClientRect().height>0});
|
||||
var codeEl=[].slice.call(document.querySelectorAll('.dsh-wt_code')).find(function(el){return (getComputedStyle(el).visibility!=='hidden'&&el.getBoundingClientRect().height>0)});
|
||||
out.tsxView=!!codeEl;
|
||||
out.tsxHljs=codeEl?codeEl.querySelectorAll('.hljs-keyword').length:0;
|
||||
out.tsxHasText=codeEl?String(codeEl.textContent).indexOf('WorktableSection')>=0:false;
|
||||
@@ -485,12 +485,12 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
await new Promise(function(r){setTimeout(r,400)});
|
||||
st.openTab('main',0,{kind:'file',path:'C:\\\\Users\\\\SJL\\\\AppData\\\\Local\\\\Temp\\\\wt-edit-test.md'});
|
||||
await new Promise(function(r){setTimeout(r,1200)});
|
||||
out.bar=!![].slice.call(document.querySelectorAll('.dsh-wt_mdBar')).find(function(el){return el.getBoundingClientRect().height>0});
|
||||
var bar=[].slice.call(document.querySelectorAll('.dsh-wt_mdBar')).find(function(el){return el.getBoundingClientRect().height>0});
|
||||
out.bar=!![].slice.call(document.querySelectorAll('.dsh-wt_mdBar')).find(function(el){return (getComputedStyle(el).visibility!=='hidden'&&el.getBoundingClientRect().height>0)});
|
||||
var bar=[].slice.call(document.querySelectorAll('.dsh-wt_mdBar')).find(function(el){return (getComputedStyle(el).visibility!=='hidden'&&el.getBoundingClientRect().height>0)});
|
||||
var btns=bar?bar.querySelectorAll('.dsh-wt_mdBtn'):[];
|
||||
if(btns[1]){ btns[1].click(); }
|
||||
await new Promise(function(r){setTimeout(r,250)});
|
||||
var ta=[].slice.call(document.querySelectorAll('.dsh-wt_mdEdit')).find(function(el){return el.getBoundingClientRect().height>0});
|
||||
var ta=[].slice.call(document.querySelectorAll('.dsh-wt_mdEdit')).find(function(el){return (getComputedStyle(el).visibility!=='hidden'&&el.getBoundingClientRect().height>0)});
|
||||
out.editArea=!!ta;
|
||||
out.draft0=ta?ta.value.slice(0,8):null;
|
||||
if(ta){
|
||||
@@ -499,10 +499,13 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
ta.dispatchEvent(new Event('input',{bubbles:true}));
|
||||
}
|
||||
await new Promise(function(r){setTimeout(r,250)});
|
||||
var saveBtn=[].slice.call(document.querySelectorAll('.dsh-wt_mdSave')).find(function(el){return el.getBoundingClientRect().height>0});
|
||||
if(saveBtn){ saveBtn.click(); }
|
||||
var saveBtn=[].slice.call(document.querySelectorAll('.dsh-wt_mdSave')).find(function(el){return (getComputedStyle(el).visibility!=='hidden'&&el.getBoundingClientRect().height>0)});
|
||||
// fail-safe:保存前强制校验激活标签的目标路径就是临时夹具,防止测试误写真实文件
|
||||
var curTab=st.spec&&st.spec.main[0]?st.spec.main[0].tabs[st.spec.main[0].active||0]:null;
|
||||
out.curPath=curTab?String(curTab.content.path||'').slice(-20):null;
|
||||
if(saveBtn && curTab && String(curTab.content.path).indexOf('wt-edit-test.md')>=0){ saveBtn.click(); }
|
||||
await new Promise(function(r){setTimeout(r,900)});
|
||||
out.previewBack=!![].slice.call(document.querySelectorAll('.dsh-wt_md')).find(function(el){return el.getBoundingClientRect().height>0});
|
||||
out.previewBack=!![].slice.call(document.querySelectorAll('.dsh-wt_md')).find(function(el){return (getComputedStyle(el).visibility!=='hidden'&&el.getBoundingClientRect().height>0)});
|
||||
var chk=await fetch('/api/worktable/file?path='+encodeURIComponent('C:\\\\Users\\\\SJL\\\\AppData\\\\Local\\\\Temp\\\\wt-edit-test.md')).then(function(r){return r.text()}).catch(function(){return null});
|
||||
out.savedContent=chk?String(chk).indexOf('EDITED_OK')>=0:false;
|
||||
st.close();
|
||||
@@ -527,7 +530,7 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
st.setActiveTab('main',0,st.spec.main[0].tabs[1].id);
|
||||
// 轮询等待代码视图渲染(fetch 完成后 .dsh-wt_fileView 才出现;只取可见元素,避开保活池里的隐藏层)
|
||||
var fv=null;
|
||||
for(var w=0;w<20 && !fv;w++){ await new Promise(function(r){setTimeout(r,150)}); fv=[].slice.call(document.querySelectorAll('.dsh-wt_fileView')).find(function(el){return el.getBoundingClientRect().height>0}); }
|
||||
for(var w=0;w<20 && !fv;w++){ await new Promise(function(r){setTimeout(r,150)}); fv=[].slice.call(document.querySelectorAll('.dsh-wt_fileView')).find(function(el){return (getComputedStyle(el).visibility!=='hidden'&&el.getBoundingClientRect().height>0)}); }
|
||||
out.fileViewReady=!!fv;
|
||||
if(fv){ fv.scrollTop=320; }
|
||||
await new Promise(function(r){setTimeout(r,200)});
|
||||
@@ -538,7 +541,7 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
st.open(savedSpec);
|
||||
await new Promise(function(r){setTimeout(r,500)});
|
||||
out.activeAfter=st.spec.main[0].active;
|
||||
var fv2=[].slice.call(document.querySelectorAll('.dsh-wt_fileView')).find(function(el){return el.getBoundingClientRect().height>0});
|
||||
var fv2=[].slice.call(document.querySelectorAll('.dsh-wt_fileView')).find(function(el){return (getComputedStyle(el).visibility!=='hidden'&&el.getBoundingClientRect().height>0)});
|
||||
out.scrollAfter=fv2?fv2.scrollTop:null;
|
||||
// iframe 保活:加一个站点 iframe 标签 → 关闭 → 重开 → 同一 DOM 实例
|
||||
var rootToken=encodeURIComponent('E:\\\\AI_Workspace\\\\DeepseekHarness\\\\Projects\\\\dsh-worktable\\\\04_test\\\\fixture-site');
|
||||
@@ -604,6 +607,36 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
})()`);
|
||||
console.log('STEP18:', JSON.stringify(step18));
|
||||
|
||||
// 网页 iframe 内部滚动位置保活(visibility 保活,尺寸不被压缩)
|
||||
const step19 = await evaluate(`(async function(){
|
||||
var out={};
|
||||
try{
|
||||
var st=window.__dshWorktable.splitStore;
|
||||
var spec={id:'t-webkeep',title:'w',top:null,main:[{id:'p1',title:'p',min:200,content:null}],chatWidth:{default:320,min:240,max:600}};
|
||||
st.open(spec);
|
||||
await new Promise(function(r){setTimeout(r,400)});
|
||||
var rt=encodeURIComponent('E:\\\\AI_Workspace\\\\DeepseekHarness\\\\Projects\\\\dsh-worktable\\\\04_test\\\\fixture-site');
|
||||
st.openTab('main',0,{kind:'iframe',url:'/api/worktable/site/'+rt+'/index.html',title:'index'});
|
||||
await new Promise(function(r){setTimeout(r,1800)});
|
||||
var f=[].slice.call(document.querySelectorAll('.dsh-wt_paneFrame')).find(function(el){return (getComputedStyle(el).visibility!=='hidden'&&el.getBoundingClientRect().height>0)});
|
||||
var doc=f&&f.contentDocument;
|
||||
if(doc&&doc.defaultView){ doc.defaultView.scrollTo(0,400); }
|
||||
await new Promise(function(r){setTimeout(r,200)});
|
||||
out.scrollSet=doc&&doc.defaultView?doc.defaultView.scrollY:null;
|
||||
var saved=st.spec;
|
||||
st.close();
|
||||
await new Promise(function(r){setTimeout(r,300)});
|
||||
st.open(saved);
|
||||
await new Promise(function(r){setTimeout(r,600)});
|
||||
var f2=[].slice.call(document.querySelectorAll('.dsh-wt_paneFrame')).find(function(el){return (getComputedStyle(el).visibility!=='hidden'&&el.getBoundingClientRect().height>0)});
|
||||
var doc2=f2&&f2.contentDocument;
|
||||
out.scrollBack=doc2&&doc2.defaultView?doc2.defaultView.scrollY:null;
|
||||
st.close();
|
||||
}catch(err){ out.err=String(err) }
|
||||
return JSON.stringify(out);
|
||||
})()`);
|
||||
console.log('STEP19:', JSON.stringify(step19));
|
||||
|
||||
// 还原临时夹具
|
||||
try { fs.writeFileSync(TEMP_MD, '# ORIG\n', 'utf8') } catch {}
|
||||
|
||||
@@ -630,4 +663,4 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
ws.close();
|
||||
proc.kill();
|
||||
process.exit(0);
|
||||
})().catch((e) => { console.log('SCRIPT_FAIL:', e); proc.kill(); process.exit(1); });
|
||||
})().catch((e) => { console.log('SCRIPT_FAIL:', e); proc.kill(); process.exit(1); });
|
||||
Reference in New Issue
Block a user