diff --git a/01_content/src/client/index.tsx b/01_content/src/client/index.tsx index 943b6fa..cc881b0 100644 --- a/01_content/src/client/index.tsx +++ b/01_content/src/client/index.tsx @@ -311,6 +311,11 @@ let sessionBridge: { sessions: any; conversation: any; list: any; workspaces: an const CONSOLE_ID = 'wt-console' const CONSOLE_ICON = '🖥️' +/** 控制室布局是否健全:主窗格首格存在 console 类型标签(旧版关标签的坏存档没有它) */ +function specHasConsoleTab(spec: LayoutSpec | undefined): boolean { + return !!spec?.main?.[0]?.tabs?.some((t) => t.content?.kind === 'builtin' && t.content.type === 'console') +} + /** 控制室默认布局:单一锁死大窗格(项目卡片网格)+ 右侧对话 */ function buildConsoleSpec(t: (key: string) => string): LayoutSpec { return { @@ -1083,6 +1088,16 @@ function WorktableSection(props: any) { }) } + // 自愈:启动时若控制室存档是坏布局(旧版关掉控制室标签造成窗格退化成选择器),立即重建默认面板 + useEffect(() => { + const saved = projects.views[CONSOLE_ID] + if (!specHasConsoleTab(saved)) { + const spec = buildConsoleSpec((k) => t(k as WorktableKey)) + persistProjects((prev) => ({ ...prev, views: { ...prev.views, [CONSOLE_ID]: spec } })) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [projects.views[CONSOLE_ID]]) + // 子座位注册变化 → 刷新 id 序列 useEffect(() => { const sync = () => setRegisteredIds([...registryStore.ids]) @@ -1302,7 +1317,11 @@ function buildCustomLayoutPrompt(req: string): string { /** 打开「工作台」控制室:已绑定 → 打开并切换绑定对话;explicitBound 供强制绑定流程传入刚绑定的会话 */ const openConsole = useCallback((explicitBound?: string | null) => { - const spec = projects.views[CONSOLE_ID] ?? buildConsoleSpec((k) => t(k as WorktableKey)) + const saved = projects.views[CONSOLE_ID] + // 自愈:坏存档(旧版关掉控制室标签导致窗格退化成选择器)→ 重建默认面板并写回 + const broken = !specHasConsoleTab(saved) + const spec = broken ? buildConsoleSpec((k) => t(k as WorktableKey)) : (saved as LayoutSpec) + if (broken) persistProjects((prev) => ({ ...prev, views: { ...prev.views, [CONSOLE_ID]: spec } })) engineIdsRef.current.add(CONSOLE_ID) splitStore.open(spec) if (splitStore.active && splitStore.spec?.id === CONSOLE_ID) { diff --git a/02_process/worklogs/2026-08-16_worktable-v2.md b/02_process/worklogs/2026-08-16_worktable-v2.md index f366ba8..6e5f999 100644 --- a/02_process/worklogs/2026-08-16_worktable-v2.md +++ b/02_process/worklogs/2026-08-16_worktable-v2.md @@ -1220,3 +1220,14 @@ rgba(63,185,80,0.9) + 双层 box-shadow ✓、busy 旋转描边 #5aa0ff ✓;三阶段流程 + 主题 全 ✓;functional-diag 全 20 STEP ERROR_COUNT: 0。 - 备注:纯客户端改动,F5 即生效。 + +## 补记(控制室坏存档自愈:旧版「关标签」坑出不可逆状态) + +- 事故:用户在被修复前点掉了控制室标签的 ✕,窗格退化成选择器并持久化进 + views['wt-console'](tabs 空)——重开控制室也只显示选择器,回不去。 +- 实现:specHasConsoleTab 健全性检查 + 双通道自愈:① 挂载时(useEffect 监听 + views[CONSOLE_ID])发现坏存档立即重建 buildConsoleSpec 并写回;② openConsole 打开时 + 再查一遍,坏则重建 + persistProjects 写回。探测种子改为植入坏存档验证。 +- 验证:probe-batch16 healTabs=1、healType='console' ✓,全流程 ✓;functional-diag + 全 20 STEP ERROR_COUNT: 0。 +- 备注:纯客户端改动,F5 即生效;用户 F5 后坏存档自动修复,点「控制室」卡片即恢复面板。 diff --git a/04_test/probe-batch16.cjs b/04_test/probe-batch16.cjs index 4c5cdfd..7143d49 100644 --- a/04_test/probe-batch16.cjs +++ b/04_test/probe-batch16.cjs @@ -26,7 +26,7 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); await send('Runtime.enable'); await send('Page.enable'); await send('Page.addScriptToEvaluateOnNewDocument', { - source: "try{ if(location.origin==='http://127.0.0.1:3080'){ localStorage.setItem('dsh.worktable.view.v1', JSON.stringify({query:'',searchOpen:false,orderBy:'manual',dock:'footer',floatTop:null,sortMigratedV2:true})); localStorage.setItem('dsh.worktable.projects.v1', JSON.stringify({order:['t-bind'],lastUsed:{},hidden:[],nameOverrides:{},iconOverrides:{},shortcuts:[],layouts:[{id:'t-bind',title:'\u7ed1\u5b9a\u6d4b\u8bd5',icon:'\ud83e\uddea',top:null,left:null,main:[{id:'p1',title:'\u5185\u5bb91',min:200,content:null,tabs:[],active:0}],leftWidth:{default:260,min:160,max:480},chatWidth:{default:360,min:240,max:600},topHeight:{default:200,min:120,max:480},chatSide:'right',chatFullHeight:false}],bindings:{},folders:{},views:{},removed:[]})); } }catch(e){}", + source: "try{ if(location.origin==='http://127.0.0.1:3080'){ localStorage.setItem('dsh.worktable.view.v1', JSON.stringify({query:'',searchOpen:false,orderBy:'manual',dock:'footer',floatTop:null,sortMigratedV2:true})); localStorage.setItem('dsh.worktable.projects.v1', JSON.stringify({order:['t-bind'],lastUsed:{},hidden:[],nameOverrides:{},iconOverrides:{},shortcuts:[],layouts:[{id:'t-bind',title:'\u7ed1\u5b9a\u6d4b\u8bd5',icon:'\ud83e\uddea',top:null,left:null,main:[{id:'p1',title:'\u5185\u5bb91',min:200,content:null,tabs:[],active:0}],leftWidth:{default:260,min:160,max:480},chatWidth:{default:360,min:240,max:600},topHeight:{default:200,min:120,max:480},chatSide:'right',chatFullHeight:false}],bindings:{},folders:{},views:{\"wt-console\":{\"id\":\"wt-console\",\"title\":\"kz\",\"top\":null,\"main\":[{\"id\":\"console\",\"title\":\"kz\",\"min\":240,\"tabs\":[],\"active\":0}],\"chatWidth\":{\"default\":340,\"min\":280,\"max\":600},\"topHeight\":{\"default\":200,\"min\":120,\"max\":480},\"chatSide\":\"right\"}},removed:[]})); } }catch(e){}", }); await send('Page.navigate', { url: 'http://127.0.0.1:3080/' }); const evaluate = async (expr) => { @@ -45,6 +45,11 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); var vis = function(sel){ return [].slice.call(document.querySelectorAll(sel)).find(function(el){ return (getComputedStyle(el).visibility!=='hidden'&&el.getBoundingClientRect().height>0); }); }; var st = window.__dshWorktable.splitStore; try { + // 自愈验证:种子里的坏存档(控制室标签被关掉)应被挂载时修复 + var seeded = JSON.parse(localStorage.getItem('dsh.worktable.projects.v1')); + var cv = seeded && seeded.views && seeded.views['wt-console']; + out.healTabs = cv && cv.main && cv.main[0] && cv.main[0].tabs ? cv.main[0].tabs.length : null; + out.healType = cv && cv.main && cv.main[0] && cv.main[0].tabs && cv.main[0].tabs[0] ? cv.main[0].tabs[0].content.type : null; var cs = getComputedStyle(document.body); out.themeFill = cs.getPropertyValue('--dsw-alias-fill-l1').trim() || null; out.htmlDataTheme = document.documentElement.getAttribute('data-theme');