fix: unclosable console tab, compact grid with neon outline glow, naming fix

This commit is contained in:
Aisland
2026-08-21 01:15:59 +08:00
parent 142860b15a
commit 14c21a10bd
6 changed files with 65 additions and 26 deletions
+2 -2
View File
@@ -6,7 +6,7 @@
/** Simplified Chinese dictionary (the key-set source of truth). */
export const zh = {
'title': '控制室',
'title': '工作台',
'rail.title': '工作台(展开侧边栏查看)',
'handle.title': '按住拖动位置 · 双击复位',
'handle.aria': '拖动工作台位置',
@@ -167,7 +167,7 @@ export type WorktableKey = keyof typeof zh
/** English dictionary, checked complete against the zh key set. */
export const en = {
'title': 'Console',
'title': 'Worktable',
'rail.title': 'Worktable (expand sidebar to view)',
'handle.title': 'Drag to move · double-click to reset',
'handle.aria': 'Drag worktable position',
+15 -9
View File
@@ -1742,12 +1742,15 @@ function PaneBody(props: { pane: SplitPane; row: PaneRow; index: number }) {
return (
<>
<div className="dsh-wt_tabBar">
{tabs.map((t, i) => (
{tabs.map((t, i) => {
// 控制室标签不可关闭:关掉后窗格变选择器,用户回不到控制室(不可逆操作)
const locked = t.content?.kind === 'builtin' && t.content.type === 'console'
return (
<span
key={t.id}
className={'dsh-wt_tab' + (i === active ? ' dsh-wt_tabOn' : '')}
title={t.title}
draggable
draggable={!locked}
onDragStart={(e: any) => { dragTab = { row, index, tabId: t.id }; try { e.dataTransfer.effectAllowed = 'move' } catch {} }}
onDragEnd={() => { dragTab = null; setDropTarget(null) }}
onClick={() => splitStore.setActiveTab(row, index, t.id)}
@@ -1762,14 +1765,17 @@ function PaneBody(props: { pane: SplitPane; row: PaneRow; index: number }) {
>↻</button>
)}
<span className="dsh-wt_tabTitle">{t.title}</span>
<button
type="button"
className="dsh-wt_tabClose"
title={T('pane.closeTab')}
onClick={(e) => { e.stopPropagation(); splitStore.closeTab(row, index, t.id) }}
>✕</button>
{!locked && (
<button
type="button"
className="dsh-wt_tabClose"
title={T('pane.closeTab')}
onClick={(e) => { e.stopPropagation(); splitStore.closeTab(row, index, t.id) }}
>✕</button>
)}
</span>
))}
)
})}
</div>
<PaneTabBody tab={tabs[active]} row={row} index={index} paneTitle={pane.title} reloadKey={reloadKeys[tabs[active].id] ?? 0} />
</>
+7 -8
View File
@@ -382,7 +382,7 @@ export const css = xtermCss + '\n' + [
'.dsh-wt_subSection{flex:none;padding:6px 8px 2px;font-size:10px;font-weight:600;letter-spacing:.05em;color:var(--dsw-alias-label-tertiary,#6e7683)}',
// 控制室面板:项目卡片网格(每行 3 张、超出换行;DSH 简洁 + 苹果式卡片)。
// 主题变量自带作用域:dark 缺省;data-wt-theme=light 换浅色;system 由面板读宿主 color-scheme 落成 dark/light。
'.dsh-wt_console{--wt-bg:#10141b;--wt-card:#171c25;--wt-cardHover:#1d232e;--wt-border:#2a3140;--wt-borderHover:#39445a;--wt-text:#e6e8eb;--wt-text2:#9aa4b2;--wt-text3:#6e7683;--wt-chip:rgba(255,255,255,.06);--wt-shadow:0 6px 20px rgba(0,0,0,.35);flex:1;min-height:0;overflow:auto;padding:14px;background:var(--wt-bg)}',
'.dsh-wt_console{--wt-bg:#10141b;--wt-card:#171c25;--wt-cardHover:#1d232e;--wt-border:#2a3140;--wt-borderHover:#39445a;--wt-text:#e6e8eb;--wt-text2:#9aa4b2;--wt-text3:#6e7683;--wt-chip:rgba(255,255,255,.06);--wt-shadow:0 6px 20px rgba(0,0,0,.35);flex:1;min-height:0;overflow:auto;padding:22px;background:var(--wt-bg)}',
'.dsh-wt_console[data-wt-theme=light]{--wt-bg:#f2f4f7;--wt-card:#ffffff;--wt-cardHover:#f7f9fb;--wt-border:#d8dee6;--wt-borderHover:#b9c4d2;--wt-text:#1c2128;--wt-text2:#57606a;--wt-text3:#8b949e;--wt-chip:rgba(27,31,36,.07);--wt-shadow:0 6px 20px rgba(31,41,55,.10)}',
'.dsh-wt_consoleHead{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:12px}',
'.dsh-wt_consoleTitle{font-size:14px;font-weight:700;line-height:20px;color:var(--wt-text)}',
@@ -390,7 +390,7 @@ export const css = xtermCss + '\n' + [
'.dsh-wt_consoleThemeBtn{flex:none;padding:3px 9px;border:none;border-radius:999px;background:transparent;color:var(--wt-text3);font:inherit;font-size:12px;line-height:18px;cursor:pointer;opacity:.85}',
'.dsh-wt_consoleThemeBtn:hover{opacity:1;background:var(--wt-chip)}',
'.dsh-wt_consoleThemeBtnOn{background:var(--wt-chip);color:var(--wt-text);opacity:1}',
'.dsh-wt_consoleGrid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px;align-content:start}',
'.dsh-wt_consoleGrid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px;align-content:start;max-width:720px;margin:0 auto}',
'.dsh-wt_consoleCard{position:relative;display:flex;flex-direction:column;gap:10px;aspect-ratio:1/1;min-height:150px;padding:16px;background:var(--wt-card);border:1px solid var(--wt-border);border-radius:14px;cursor:pointer;transition:transform .12s ease,border-color .12s ease,box-shadow .12s ease;overflow:hidden}',
'.dsh-wt_consoleCard:hover{transform:translateY(-2px);border-color:var(--wt-borderHover);box-shadow:var(--wt-shadow)}',
'.dsh-wt_consoleCardSelf{cursor:default;opacity:.97}',
@@ -411,13 +411,12 @@ export const css = xtermCss + '\n' + [
'.dsh-wt_consoleBadge{flex:none;padding:2px 9px;border-radius:999px;font-size:12px;font-weight:600;line-height:18px;color:var(--wt-text2);background:var(--wt-chip)}',
'.dsh-wt_consolePreview{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;font-size:12.5px;line-height:18px;color:var(--wt-text2);word-break:break-word}',
'.dsh-wt_consolePreviewNone{color:var(--wt-text3);font-style:italic;-webkit-line-clamp:1}',
// 三色光效:完成=绿光、待决=黄光(背景泛光,点击确认后熄灭);工作=蓝色描边光晕顺时针旋转
'.dsh-wt_consoleCard-glowDone{border-color:rgba(63,185,80,.45);box-shadow:0 0 22px rgba(63,185,80,.22)}',
'.dsh-wt_consoleCard-glowDone::after{content:"";position:absolute;inset:0;border-radius:inherit;background:radial-gradient(130% 130% at 50% 0%,rgba(63,185,80,.16),transparent 62%);pointer-events:none}',
'.dsh-wt_consoleCard-glowNeed{border-color:rgba(210,153,34,.5);box-shadow:0 0 22px rgba(210,153,34,.22)}',
'.dsh-wt_consoleCard-glowNeed::after{content:"";position:absolute;inset:0;border-radius:inherit;background:radial-gradient(130% 130% at 50% 0%,rgba(210,153,34,.17),transparent 62%);pointer-events:none}',
// 三色光效:整卡霓虹描边光晕(像侧栏双圆点的发光质感)——完成=绿、待决=黄;工作=蓝色描边顺时针旋转
'.dsh-wt_consoleCard-glowDone{border-color:rgba(63,185,80,.9);box-shadow:0 0 8px rgba(63,185,80,.5),0 0 26px rgba(63,185,80,.3),inset 0 0 16px rgba(63,185,80,.07)}',
'.dsh-wt_consoleCard-glowNeed{border-color:rgba(210,153,34,.95);box-shadow:0 0 8px rgba(210,153,34,.5),0 0 26px rgba(210,153,34,.3),inset 0 0 16px rgba(210,153,34,.08)}',
'@property --consoleAngle{syntax:"<angle>";initial-value:0deg;inherits:false}',
'.dsh-wt_consoleCard-busy::before{content:"";position:absolute;inset:-2px;border-radius:16px;padding:2px;background:conic-gradient(from var(--consoleAngle),transparent 0deg,rgba(79,142,247,0) 30deg,rgba(79,142,247,.95) 80deg,rgba(79,142,247,0) 130deg,transparent 200deg);-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);mask-composite:exclude;animation:consoleAngleSpin 1.8s linear infinite;pointer-events:none}',
'.dsh-wt_consoleCard-busy{box-shadow:0 0 14px rgba(79,142,247,.28)}',
'.dsh-wt_consoleCard-busy::before{content:"";position:absolute;inset:-2px;border-radius:16px;padding:2px;background:conic-gradient(from var(--consoleAngle),transparent 0deg,rgba(79,142,247,0) 20deg,#5aa0ff 80deg,rgba(79,142,247,0) 140deg,transparent 200deg);-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);mask-composite:exclude;animation:consoleAngleSpin 1.6s linear infinite;pointer-events:none}',
'@keyframes consoleAngleSpin{to{--consoleAngle:360deg}}',
'.dsh-wt_consoleJump{position:absolute;top:12px;right:12px;width:24px;height:24px;padding:0;border:none;border-radius:7px;background:transparent;color:var(--wt-text3);font-size:13px;line-height:1;cursor:pointer;opacity:0;transition:opacity .12s ease}',
'.dsh-wt_consoleCard:hover .dsh-wt_consoleJump{opacity:1}',
@@ -1202,3 +1202,21 @@
三阶段全流程与主题切换全 ✓;functional-diag 全 20 STEP ERROR_COUNT: 0。
- 备注:纯客户端改动,F5 即生效;face.history 为非公开接口(只读、失败静默回退内存路径),
AGENTS.md 已注明。
## 补记(控制室第二轮打磨:标签不可关 + 小卡片多留白 + 霓虹描边发光 + 命名纠正)
- 用户反馈:① 控制室标签上的 ✕ 关掉后回不去(不可逆)——要么没有标签栏,要么关不掉;
② 卡片太大把空间占满——要小一点、留白多一点;③ 发光理解错了:要的是「整个卡片」的
光(像侧栏绑定双圆点那种发光质感),不是圆点发光——做整卡光晕描边;④ 命名纠正:侧栏
最上面区块标题叫「工作台」(整个插件),第一个默认项目叫「控制室」(工作台的控制室)。
- 实现:① PaneBody 标签渲染改 map 回调:content.type==='console' 的标签 locked——不渲染
✕ 关闭按钮、禁拖拽(标签栏保留标题,保证可回);② 网格 max-width 720px 居中 + gap 16px
+ 面板 padding 22px(卡片实测 229px 宽,1:1);③ 光效重做:glowDone/glowNeed = 亮色
描边(rgba 0.9 边框)+ 双层外发光(8px 高亮 + 26px 泛光)+ 微弱内辉光(inset),删掉
原来过强的 radial 背景 tint;busy 旋转描边峰值改 #5aa0ff + 卡片蓝色外发光;④ locale
title 回「工作台」/Worktable,console.name 保持「控制室」/Console。
- 验证(probe-batch16 更新):sectionTitle=工作台 ✓、consoleName=控制室 ✓、
consoleTabCloseGone=true(控制室标签无 ✕)✓、cardWidth 229/1:1 ✓、glowDoneBorder
rgba(63,185,80,0.9) + 双层 box-shadow ✓、busy 旋转描边 #5aa0ff ✓;三阶段流程 + 主题
全 ✓;functional-diag 全 20 STEP ERROR_COUNT: 0。
- 备注:纯客户端改动,F5 即生效。
+12 -1
View File
@@ -51,6 +51,9 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
var first = document.querySelector('.dsh-wt_projects > *');
out.firstIsConsole = !!(first && first.classList && first.classList.contains('dsh-wt_consoleEntry'));
out.consoleName = first ? (first.querySelector('.dsh-wt_layoutName') ? first.querySelector('.dsh-wt_layoutName').textContent : null) : null;
var handle = document.querySelector('.dsh-wt_title');
out.sectionTitle = handle ? (handle.textContent || '').trim() : null;
out.sectionTitleRight = out.sectionTitle === '工作台';
out.consoleIcon = first ? (first.querySelector('.dsh-wt_layoutIcon') ? first.querySelector('.dsh-wt_layoutIcon').textContent : null) : null;
out.consoleOrder = first ? getComputedStyle(first).order : null;
out.consoleHasBindBtn = !!(first && first.querySelector('.dsh-wt_bindBtn'));
@@ -113,6 +116,12 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
}
out.bindPopClosed = !vis('.dsh-wt_consoleBindPop');
out.specId = st.active && st.spec ? st.spec.id : null;
// 控制室标签关不掉:标签栏存在但该标签无 ✕
var tabBar = vis('.dsh-wt_tabBar');
var ctab = tabBar && tabBar.querySelector('.dsh-wt_tab');
out.consoleTabExists = !!ctab;
out.consoleTabCloseGone = !!(ctab && !ctab.querySelector('.dsh-wt_tabClose'));
out.consoleTabTitle = ctab ? (ctab.textContent || '').trim() : null;
out.mainType = st.spec && st.spec.main[0] && st.spec.main[0].tabs && st.spec.main[0].tabs[0] ? st.spec.main[0].tabs[0].content.type : null;
var saved = JSON.parse(localStorage.getItem('dsh.worktable.projects.v1'));
out.savedConsoleBinding = saved.bindings['wt-console'];
@@ -136,6 +145,7 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
if (cardEl) {
var cr = cardEl.getBoundingClientRect();
out.cardAspect = Math.round((cr.width / Math.max(1, cr.height)) * 100) / 100;
out.cardWidth = Math.round(cr.width);
var statusEl = cardEl.querySelector('.dsh-wt_consoleStatus');
out.statusFont = statusEl ? getComputedStyle(statusEl).fontSize : null;
cardEl.classList.add('dsh-wt_consoleCard-busy');
@@ -147,7 +157,8 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
await sleep(300);
var gs = getComputedStyle(cardEl, '::after');
out.glowDoneBg = gs.backgroundImage ? gs.backgroundImage.slice(0, 60) : null;
out.glowDoneShadow = getComputedStyle(cardEl).boxShadow.slice(0, 60);
out.glowDoneShadow = getComputedStyle(cardEl).boxShadow.slice(0, 80);
out.glowDoneBorder = getComputedStyle(cardEl).borderColor;
cardEl.classList.remove('dsh-wt_consoleCard-glowDone');
cardEl.classList.add('dsh-wt_consoleCard-glowNeed');
await sleep(300);
+11 -6
View File
@@ -95,12 +95,17 @@ node --check lib/index.js
prefers-color-scheme 兜底;落成 .dsh-wt_console[data-wt-theme=dark|light] 作用域变量
--wt-*(宿主不发布 --dsw-alias-*,工作台全站一直靠回退色渲染——控制室自带主题作用域,
不受其影响)。
- 状态光效:工作=蓝色描边光晕顺时针绕卡旋转(.dsh-wt_consoleCard-busy ::before
conic-gradient + @property --consoleAngle + consoleAngleSpin);完成=绿光、待决=黄光
(-glowDone/-glowNeed:radial 泛光 + 外发光;glow 字段 = done/need 且本轮未 ack,
点卡片先 onAck 熄光再进入,与提醒 ack 生命周期一致)。
- 命名统一「控制室」:侧栏区块标题、默认项目卡名、面板标题均为 console.name/title 系列
locale(不再是「工作台」)。
- 状态光效(整卡霓虹描边,参考侧栏双圆点发光质感):工作=蓝色描边光晕顺时针绕卡旋转
(.dsh-wt_consoleCard-busy ::before conic-gradient + @property --consoleAngle +
consoleAngleSpin,峰值 #5aa0ff);完成=绿光、待决=黄光(-glowDone/-glowNeed:亮色描边
+ 双层外发光 + 微弱内辉光;glow 字段 = done/need 且本轮未 ack,点卡片先 onAck 熄光
再进入,与提醒 ack 生命周期一致)。
- 命名:侧栏区块标题 = 「工作台」(title locale,整个插件);默认项目卡名与面板标题 =
「控制室」(console.name/console.title locale,工作台的控制室)。
- 控制室标签不可关:PaneBody 对 content.type==='console' 的标签 locked(不渲染 ✕、
禁拖拽)——关掉会退化成窗格选择器,不可逆。
- 布局尺度:网格 max-width 720px 居中、gap 16px、面板 padding 22px;卡片 1:1(实测
~229px 宽),保持留白。
- 冷会话消息预览(方案 A):binding() 对冷会话不载入文本;预热走 face.history({maxMessages:2})
(运行期内建方法、非公开接口,只读无副作用)尾部扫 user/message 与 assistant/message 的
text 块 → previewCache;sweepPreviews 在打开控制室时 + 控制室开着且会话快照变化防抖 6s