From fc8c6119c5ef021b8c839d99ea5be26b169897e5 Mon Sep 17 00:00:00 2001 From: Aisland Date: Tue, 18 Aug 2026 00:27:28 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=B7=A5=E4=BD=9C=E5=8F=B0=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=8C=81=E4=B9=85=E5=8C=96(=E8=A7=86=E5=9B=BE?= =?UTF-8?q?=E8=A6=86=E7=9B=96=E5=86=99=E5=9B=9Eviews[id],=E5=B8=83?= =?UTF-8?q?=E5=B1=80=E6=9D=A1=E7=9B=AE=E7=85=A7=E6=97=A7)=20+=20feat:=20ts?= =?UTF-8?q?x/css=E7=AD=89=E4=BB=A3=E7=A0=81=E6=96=87=E4=BB=B6=E7=82=B9?= =?UTF-8?q?=E5=87=BB=E9=A2=84=E8=A7=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 01_content/src/client/index.tsx | 10 ++- 01_content/src/client/split.tsx | 2 +- .../worklogs/2026-08-16_worktable-v2.md | 21 ++--- 04_test/functional-diag.cjs | 80 ++++++++++++++++++- 4 files changed, 100 insertions(+), 13 deletions(-) diff --git a/01_content/src/client/index.tsx b/01_content/src/client/index.tsx index 3e323e6..37c4dd4 100644 --- a/01_content/src/client/index.tsx +++ b/01_content/src/client/index.tsx @@ -380,10 +380,16 @@ function WorktableSection(props: any) { setActiveSplitId(splitStore.active && splitStore.spec ? splitStore.spec.id : null) }), []) - // 引擎内 spec 变更(窗内容/聊天位置/窗位互换)→ 回写布局条目持久化 + // 引擎内 spec 变更(窗内容/聊天位置/窗位互换)→ 回写持久化: + // 布局项目写 layouts 条目;入驻项目(视图覆盖工作区)写 views[id]。每个项目都保持上一次的样子。 useEffect(() => { splitStore.onSpecMutated = (spec) => { - persistProjects((prev) => ({ ...prev, layouts: prev.layouts.map((l) => (l.id === spec.id ? spec : l)) })) + persistProjects((prev) => { + if (prev.layouts.some((l) => l.id === spec.id)) { + return { ...prev, layouts: prev.layouts.map((l) => (l.id === spec.id ? spec : l)) } + } + return { ...prev, views: { ...prev.views, [spec.id]: spec } } + }) } return () => { splitStore.onSpecMutated = null } }, []) diff --git a/01_content/src/client/split.tsx b/01_content/src/client/split.tsx index 9e94270..1e0a5e6 100644 --- a/01_content/src/client/split.tsx +++ b/01_content/src/client/split.tsx @@ -937,7 +937,7 @@ function ExplorerPane(props: { row: PaneRow; index: number }) { url: '/api/worktable/site/' + encodeURIComponent(dir) + '/' + encodeURIComponent(e.name), title: e.name, }) - } else if (/\.(md|markdown|mdown|txt|log|pdf|png|jpe?g|gif|webp|svg|bmp|ico)$/i.test(e.name)) { + } else if (/\.(md|markdown|mdown|txt|log|tsx|ts|jsx|js|css|json|pdf|png|jpe?g|gif|webp|svg|bmp|ico)$/i.test(e.name)) { splitStore.openTab(props.row, props.index, { kind: 'file', path: e.path }) } else { setError(T('pane.openLater')) diff --git a/02_process/worklogs/2026-08-16_worktable-v2.md b/02_process/worklogs/2026-08-16_worktable-v2.md index 469cbe9..6338373 100644 --- a/02_process/worklogs/2026-08-16_worktable-v2.md +++ b/02_process/worklogs/2026-08-16_worktable-v2.md @@ -639,14 +639,17 @@ 原生视图内部是浏览器扩展页,插件注入不了按钮、拦不到鼠标,也驱动不了内部滚动—— 向用户如实说明后按指示回退);② 视图选项改名「设置」,点开直接内嵌「排序方式 + 管理项目 展开列表」,不再二次点击;③ 管理项目里给现有项目加「变更视图」:换预设拓扑, -## 补记(卡片选中态统一判定) +## 补记(工作台状态持久化修复 + tsx/css 代码预览) -- 用户反馈:旅行 Atlas 卡片选中后没有高亮边(其他 3 个项目都有);新建项目也要有。 -- 根因:ta_card 的 data-on 由它自己插件控制(自有引擎状态);用视图覆盖走工作台引擎打开时 - 两边状态对不上 → 不高亮。 -- 处理:DOM 桥 sync 里选中态统一由工作台判定——activeSplitId===id → data-on=true; - 引擎开着别的 → false;引擎空闲(activeSplitId=null)→ 不动(保留卡片自带状态, - 兼容自带分栏插件的打开态)。通用规则覆盖所有注册项目与新建项目。 -- 验证(functional-diag STEP13):布局打开时 ta/pr 卡 false;旅行设置视图覆盖后点卡片 → - engineSpecId=travelatlas 且 taOnAfterOpen=true;全 STEP 回归 ERROR_COUNT: 0。 +- 用户反馈:① 项目里布置好的窗口内容(如上面浏览器/下面终端)切换项目再切回就变回默认, + 每个项目的工作台应保持上一次的样子;② 资源管理器要能点开 tsx/css 文件预览(同 MD)。 +- 根因:onSpecMutated 回写只写 layouts 条目,漏了 views(视图覆盖工作区)→ 常驻项目 + 内容丢失;布局路径本身正常(实测确认)。 +- 处理:① onSpecMutated 按 spec.id 分流:layouts 里有该 id → 更新布局条目;否则写回 + views[id](入驻项目工作区)。所有变更(开标签/关标签/换聊天边/换位)都会触发; + ② 资源管理器文件匹配加 tsx/ts/jsx/js/css/json → kind:file 预览(等宽文本展示,同 TXT 模式)。 +- 验证(functional-diag 重排 STEP14/15 到 STEP10 前 + STEP9 改查 planreview 键 + + 恢复误删的 STEP13 打印行):STEP14 布局 layoutSavedTabs=1/layoutRestoredTabs=1、 + 视图覆盖 taSavedTabs=1/taRestoredTabs=1(切换回来内容保持);STEP15 tsx/css 双标签预览 + 且内容含 WorktableSection;全 15 STEP ERROR_COUNT: 0。 - 备注:纯客户端改动,F5 即生效。 diff --git a/04_test/functional-diag.cjs b/04_test/functional-diag.cjs index 973860a..b81d7e6 100644 --- a/04_test/functional-diag.cjs +++ b/04_test/functional-diag.cjs @@ -186,7 +186,7 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); try{ var vv=JSON.parse(localStorage.getItem('dsh.worktable.projects.v1')).views||{}; out.viewsKeys=Object.keys(vv); - var rv=vv[out.viewsKeys[0]]; + var rv=vv['planreview']; if(rv){ out.resTop=(rv.top||[]).length; out.resMain=rv.main.length; out.resChatFull=rv.chatFullHeight===true; out.resId=rv.id; } }catch(e){ out.resErr=String(e) } var rows2=document.querySelectorAll('.dsh-wt_settings .dsh-wt_manageRow:not(.dsh-wt_manageRowSc)'); @@ -211,6 +211,84 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); })()`); console.log('STEP9:', JSON.stringify(step9)); + // 工作台状态持久化:布局项目与视图覆盖项目切换回来都保持上一次内容 + const step14 = await evaluate(`(async function(){ + var out={}; + try{ + var st=window.__dshWorktable.splitStore; + // A) 布局项目:卡片打开 → 加终端标签 → 关闭 → 再开 → 标签还在 + var layCard=document.querySelector('.dsh-wt_layout'); + if(layCard){ layCard.click(); } + await new Promise(function(r){setTimeout(r,400)}); + out.layoutOpenId=st.spec?st.spec.id:null; + st.openTab('main',0,{kind:'builtin',type:'terminal'}); + await new Promise(function(r){setTimeout(r,300)}); + try{ out.layoutSavedTabs=JSON.parse(localStorage.getItem('dsh.worktable.projects.v1')).layouts[0].main[0].tabs.length }catch(e){ out.layoutSavedTabs='ERR' } + st.close(); + await new Promise(function(r){setTimeout(r,300)}); + layCard=document.querySelector('.dsh-wt_layout'); + if(layCard){ layCard.click(); } + await new Promise(function(r){setTimeout(r,400)}); + out.layoutRestoredTabs=st.spec?st.spec.main[0].tabs.length:null; + st.close(); + await new Promise(function(r){setTimeout(r,300)}); + // B) 视图覆盖项目(旅行):先经 UI 设置视图 → 卡片打开 → 加资源管理器标签 → 关闭 → 再开 → 标签还在 + var btnS=document.querySelector('.dsh-wt_actions .dsh-wt_iconBtn:nth-child(2)'); + if(btnS){ btnS.click(); } + await new Promise(function(r){setTimeout(r,300)}); + var rows=document.querySelectorAll('.dsh-wt_settings .dsh-wt_manageRow:not(.dsh-wt_manageRowSc):not(.dsh-wt_manageRowRemoved)'); + var taRow=rows[1]; + var puzzle=taRow&&taRow.querySelectorAll('.dsh-wt_manageBtn')[taRow.querySelectorAll('.dsh-wt_manageBtn').length-2]; + if(puzzle){ puzzle.click(); } + await new Promise(function(r){setTimeout(r,300)}); + var presets=document.querySelectorAll('.dsh-wt_presets .dsh-wt_preset'); + if(presets[0]){ presets[0].click(); } + await new Promise(function(r){setTimeout(r,400)}); + var bd=document.querySelector('.dsh-wt_popBackdrop'); + if(bd){ bd.click(); } + await new Promise(function(r){setTimeout(r,300)}); + var ta=document.querySelector('.dsh-wt_projects .ta_card'); + if(ta){ ta.click(); } + await new Promise(function(r){setTimeout(r,400)}); + out.taOpenId=st.spec?st.spec.id:null; + st.openTab('main',0,{kind:'builtin',type:'explorer'}); + await new Promise(function(r){setTimeout(r,300)}); + try{ out.taSavedTabs=JSON.parse(localStorage.getItem('dsh.worktable.projects.v1')).views['travelatlas'].main[0].tabs.length }catch(e){ out.taSavedTabs='ERR' } + st.close(); + await new Promise(function(r){setTimeout(r,300)}); + ta=document.querySelector('.dsh-wt_projects .ta_card'); + if(ta){ ta.click(); } + await new Promise(function(r){setTimeout(r,400)}); + out.taRestoredTabs=(st.spec&&st.spec.id==='travelatlas')?st.spec.main[0].tabs.length:null; + st.close(); + await new Promise(function(r){setTimeout(r,300)}); + }catch(err){ out.err=String(err) } + return JSON.stringify(out); + })()`); + console.log('STEP14:', JSON.stringify(step14)); + + // tsx/css 代码文件预览:点击后新标签页显示内容(同 MD 模式) + const step15 = await evaluate(`(async function(){ + var out={}; + try{ + var st=window.__dshWorktable.splitStore; + st.open({id:'t-code',title:'code',top:null,main:[{id:'c1',title:'c',min:200,content:null}],chatWidth:{default:320,min:240,max:600}}); + await new Promise(function(r){setTimeout(r,400)}); + 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; + out.tsxView=!!document.querySelector('.dsh-wt_txt'); + out.tsxHasText=document.querySelector('.dsh-wt_txt')?String(document.querySelector('.dsh-wt_txt').textContent).indexOf('WorktableSection')>=0:false; + st.openTab('main',0,{kind:'file',path:'E:\\\\AI_Workspace\\\\DeepseekHarness\\\\Projects\\\\dsh-worktable\\\\01_content\\\\src\\\\client\\\\styles.ts'}); + await new Promise(function(r){setTimeout(r,900)}); + out.cssTab=st.spec.main[0].tabs[1].title; + out.tabsCount=st.spec.main[0].tabs.length; + st.close(); + }catch(err){ out.err=String(err) } + return JSON.stringify(out); + })()`); + console.log('STEP15:', JSON.stringify(step15)); + // 删除二次确认:常驻项目/布局 ✕ → 警告弹窗 → 取消不删 / 确认删除;emoji 字号与名称字重统一 const step10 = await evaluate(`(async function(){ var out={};