feat: 皮肤兼容(令牌驱动)+ CSS 修复(#106 #105 #90 #60,附带 #52 #57 #92) (#110)

令牌驱动方案(主目标:与 dsh-web-ui 皮肤中心兼容):
- 面板表面改用通用卡片令牌 --dsw-alias-bg-layer-1,不再消费宿主左导航专属
  --dsw-specific-sidebar-fill——dsh-web-ui 10 款皮肤全部覆盖 --dsw-alias-*
  层,换肤后侧边栏自动跟随,零每皮肤适配(#60/#105/#106)。
- effectiveTokenValue 增加 alpha 阈值(新增 colorAlpha 解析 computed 颜色):
  transparent 与 alpha<0.9 的半透明玻璃值(dsh-web-ui 皮肤 bg-base 为
  rgba 0.16–0.7)一律回退不透明底色,终端/编辑器文字永不叠在皮肤背景画上
  (#90);≥0.9 的近不透明值放行,皮肤仍可控终端表面。

独立修复(保留):
- z-index 降到 DSH 浮层栈(100+)之下:面板 40 / 按钮簇 45 / 角手柄面板内
  局部 2,Cordis 弹出框不再被底部工作台遮挡(#52)。
- 角手柄移入右面板、CSS 相对定位(bottom: calc(var(--dsh-sidebar-height)
  + 6px)),删除 JS 内联 viewport 坐标。
- 刷新按钮统一 14px(#57 前半)。
- 拖拽逐帧回归 e2e(#92):真实挂载下 rAF 采样(拖条以 cursor: col-resize
  语义定位),断言拖拽期间 data-dsh-sidebar-dragging 存在、#root transition
  为 none、会话列与面板边单调 1:1 跟随(实测通过)。

按 KISS 回退首版契约:类名 kebab-case 改名与 data-bs-* 语义钩子全部移除
(令牌驱动下不需要,改名引入无谓 breaking),skin-hooks 测试删除;
mount e2e 改为断言 --dsh-sidebar-width 布局变量随面板挂载生效。

文档:AGENTS.md §8 重写为「皮肤兼容(令牌驱动)」;设计文档记录决策与
回退。README 与版本号不在本批更新(发布流程统一处理)。

测试:tests/theme.spec.ts(colorAlpha + effectiveTokenValue 阈值);
typecheck/vitest(511)/build/consumer-types/test:mount 全绿。
This commit is contained in:
Atlas Zeng
2026-08-16 16:37:11 +08:00
committed by GitHub
parent fd9544f16b
commit 3c196fffc3
12 changed files with 569 additions and 70 deletions
+42 -13
View File
@@ -10,16 +10,29 @@
/* ── Shell ─────────────────────────────────────────────────────────────── */
/* Skinning contract (token-driven): every surface here consumes DSH's
generic tokens (`--dsw-alias-*`), NEVER `--dsw-specific-sidebar-fill` —
skin systems override that token to restyle the APP's own left-nav column
(dsh-web-ui skins make it translucent glass or theme colors, Aqua makes it
transparent), and a panel consuming it would lose its fill or clash with
the label tokens. The panels use the generic card surface
`--dsw-alias-bg-layer-1`, which every dsh-web-ui skin overrides — so
switching skins in the dsh-web-ui skin center re-skins the sidebar panels
automatically (see AGENTS.md §8). */
/* The persistent expand/collapse cluster at the top-right corner: two rail
controls side by side (bottom panel's glyph LEFT of the right panel's).
Always pinned to the viewport corner — inside the right panel's top-right
while it is open, sitting flush in the 34px tab strip (28px buttons at
top: 3) whose right end it really squeezes. */
top: 3) whose right end it really squeezes. z-index 45: above the panels
(40) but below the app's overlay stack (menus/tooltips/modals sit at
100+), so a cordis approval popup is never hidden behind the workbench
(issue #52). */
.toggleCluster {
position: fixed;
top: 3px;
right: 10px;
z-index: 55;
z-index: 45;
display: flex;
flex-direction: row;
gap: 4px;
@@ -70,12 +83,18 @@
/* Full height: the bottom panel squeezes only the center column, so the
right panel never gives up any vertical position. */
bottom: 0;
z-index: 50;
/* z-index 40: above the app's in-flow content, below the app's overlay
stack (100+), so menus/tooltips/modals always win over the panels
(issue #52). The toggle cluster (45) sits above it; the corner handle
is a child and stacks within the panel. */
z-index: 40;
display: flex;
flex-direction: column;
/* The native sidebar surface: bluish-50 over white / bluish-900 over dark,
exactly like the app's own sidebar column (SidebarRoot .root). */
background: var(--dsw-specific-sidebar-fill);
/* The generic card surface (see the root contract above). Never
`--dsw-specific-sidebar-fill` — skin plugins restyle that token for the
app's own left nav and a panel consuming it loses its fill or clashes
with labels (#60/#105/#106). */
background: var(--dsw-alias-bg-layer-1);
border-left: 1px solid var(--dsw-alias-border-l2);
/* Expand/collapse slides the panel; fullscreen animates the width. */
transition:
@@ -101,6 +120,8 @@
.panelResize {
position: absolute;
/* Centered on the panel edge (an 8px hit strip straddling the border, the
same pattern as the host's own frame handles). */
left: -4px;
top: 0;
bottom: 0;
@@ -127,14 +148,16 @@
column only (the Sidebar shell writes left/right inline: from the app's
own left sidebar to the right panel's left edge) — the sidebars never
give up any position. Same surface as the right panel, with a top
hairline instead of the left one. */
hairline instead of the left one. z-index 40 mirrors the right panel
(issue #52: below the app's overlay stack so popups win over the
workbench). */
.bottomPanel {
position: fixed;
bottom: 0;
z-index: 50;
z-index: 40;
display: flex;
flex-direction: column;
background: var(--dsw-specific-sidebar-fill);
background: var(--dsw-alias-bg-layer-1);
border-top: 1px solid var(--dsw-alias-border-l2);
/* Expand/collapse slides the panel; height drags animate the height. */
transition:
@@ -159,7 +182,7 @@
}
/* The bottom panel's height drag: the top edge strip (mirror of the right
panel's width strip). */
panel's width strip), centered on the border like the width strip. */
.bottomResize {
position: absolute;
left: 0;
@@ -235,12 +258,18 @@
/* The shared corner (both panels open): the intersection of the right
panel's left edge and the bottom panel's top edge. Horizontal drags
resize the width, vertical drags the height — the two panels drag
against each other. */
against each other. Rendered INSIDE the right panel and positioned
relative to it (left edge + the bottom panel's height from the layout
variable), so no JS-written viewport coordinates are involved. The
z-index is panel-local (above the panel's content; the panel itself sits
at 40, below the app's overlay stack). */
.cornerHandle {
position: fixed;
position: absolute;
left: -6px;
bottom: calc(var(--dsh-sidebar-height, 0px) + 6px);
width: 12px;
height: 12px;
z-index: 52;
z-index: 2;
cursor: nwse-resize;
touch-action: none;
}