fix(shared): de-collide css module style tags and lift settings card contrast

The aggregate client bundle keyed every injected stylesheet tag by
basename, so the eight same-named settings-card.module.css copies
suppressed each other and seven family packages rendered their settings
cards with UA defaults under every skin. Style tags now carry the full
repo-relative file id. The shared card chrome lifts 12-13px hint text
from label-tertiary to label-secondary (orca-link measures ~3.4:1 on
tertiary) and points the error roles at state-error-primary, which the
suite defines, instead of the nonexistent label-error token. sync-shared
gains dsh-perf for the card chrome pair only: its settings-form.ts still
runs the pre-0.1.2 per-field generation and must not be overwritten.
Live GUI verified on 127.0.0.1:3080 under the orca-link skin and the
default appearance.
This commit is contained in:
zhu1090093659
2026-09-02 13:06:53 +08:00
parent 4213d31f94
commit eb2e135686
19 changed files with 350 additions and 131 deletions
@@ -0,0 +1,88 @@
# Agent Note: CSS module style-tag collision in inlined client bundles
Status: implemented
## Problem
In the deployed web GUI every family settings card except the first-processed
one rendered as bare UA defaults: collapsed cards were fit-content inline
buttons instead of full-width disclosure rows, and the pet / doctor /
task-board / remote / describe-image / desktop-launcher fields had no styling
at all, under every appearance skin. Separately, hint and description text in
the shared card chrome could drop below readable contrast (orca-link's
`--dsw-alias-label-tertiary` #778399 on its cream surface measures ~3.4:1 at
12-13px), and the card's error styling referenced `--dsw-alias-label-error`,
a token no skin or host surface defines, so save-failure and invalid-input
signaling silently inherited the ink color.
Two root causes:
1. The shared client preset (`shared/tsdown.client.ts`) keyed each injected
stylesheet tag by `data-plugin-css = "<bundle id>/<basename>"`. The
aggregate build inlines eight packages that each ship their own
`settings-card.module.css`; all eight emitted the same tag id, the
idempotency guard let the first tag suppress the other seven, while every
package's class map carries a path-derived CSS-modules hash — so seven
class maps pointed at styles that were never injected.
2. `SETTINGS_CONSUMERS` in `scripts/sync-shared.mjs` did not include
`dsh-perf`, so its card-chrome copies drifted: the aggregate inlined the
fresh shared stylesheet for seven children but perf's stale copy kept
serving the old tertiary hint color from its own bundle.
## Decision
- The preset keys each style tag by the full repo-relative file id
(`<bundle id>/packages/<pkg>/src/client/<file>.module.css`), so
same-basename modules in different packages can no longer suppress each
other. `data-plugin` (the unload-cleanup key) is unchanged.
- The shared `settings-card.module.css` small-text roles (`.description`,
`.hint`, `.readOnly`) use `--dsw-alias-label-secondary` instead of
tertiary; the error roles (`.failed`, `.invalid`, `.inputInvalid` and its
focus ring) use `--dsw-alias-state-error-primary` with a `#b42318` fallback.
- `scripts/sync-shared.mjs` gains a `SETTINGS_CARD_ONLY_CONSUMERS` tier:
`dsh-perf` syncs the card chrome pair (`PluginSettingsCard.tsx` +
`settings-card.module.css`) like every other consumer, but
`settings-form.ts` deliberately stays a seven-consumer target because
dsh-perf still runs the pre-0.1.2 per-field form generation (its save path
judges each write per-field instead of the shared atomic mutate +
read-back). Overwriting it would be a behavioral change to perf's save
flow, not a style sync. `perf-settings-card.tsx` now renders its two
select hints through `css.hint` instead of inline `opacity: 0.66` styles.
## Alternatives considered
- Content-hash dedupe of identical stylesheets: rejected — CSS-modules
hashes derive from the file path, so identical copies still produce
different class maps and cannot share one tag.
- Aliasing all eight copies onto one canonical stylesheet inside the
aggregate build: rejected — the per-package committed copy is the sync
contract; remapping inside the preset would couple it to package layout.
- Leaving dsh-perf out of the sync manifest: that was the bug (the drift
this note fixes).
## Consequences
- Bundles that inline several packages now inject one style tag per module
file instead of per basename; the aggregate carries a handful of
near-duplicate 4KB stylesheets. Negligible, and it fails visibly (styled)
rather than silently.
- dsh-perf's settings save path still runs the older form generation and its
bundle keeps a stale hint shade until it rebuilds; with dsh-perf slated for
full deprecation, aligning its `settings-form.ts` is not planned — the
deprecation should remove the `SETTINGS_CARD_ONLY_CONSUMERS` tier.
- Square-corner skins (orca-link, xp) deliberately square switches, selects
and popups with `border-radius: 0 !important`; the settings controls stay
legible but squared there. Exempting `[role="switch"]` per skin is a
one-line patch if the look is judged broken — left to the skins' owner.
## Testing
`node --test scripts/sync-shared.test.mjs` 4/4 (copy counts 110 -> 112,
client trio 39 -> 41); `node scripts/aggregate.mjs --check` OK; repo-wide
`pnpm typecheck` and `pnpm i18n:check` green. Live GUI (127.0.0.1:3080):
under the orca-link skin and the default appearance all six Web 插件 cards
plus the pet card render full-width disclosure headers (`display: flex`,
522px), nine settings-card stylesheets inject (8 aggregate + perf
standalone), fields/badges/hints carry the shared chrome, the native-select
fallback under skins is 34px tall with token colors, and synced packages'
hints compute to label-secondary #343b47.
@@ -0,0 +1,71 @@
# Agent Note:内联 client bundle 里 CSS module 样式标签互踩
状态:implemented
## 问题
线上 web GUI 里,除第一个被处理的子包外,其余全部家族设置卡都以 UA 裸默认
样式渲染:折叠卡变成 fit-content 的行内按钮而非全宽折叠行,宠物 / Doctor /
任务板 / 远程访问 / 图像理解 / 桌面启动器的字段完全没有样式,且与皮肤无关。
另外,共享卡片 chrome 的提示与描述文字对比度可能跌破可读线(orca-link 的
`--dsw-alias-label-tertiary` #778399 落在奶油底上,12-13px 下约 3.4:1);
错误态样式引用了 `--dsw-alias-label-error`——任何皮肤与宿主表面都未定义该
token,保存失败与非法输入的报错信号静默退化为继承墨色。
两个根因:
1. 共享 client 构建预设(`shared/tsdown.client.ts`)用
`data-plugin-css = "<bundle id>/<basename>"` 作为注入样式标签的去重键。
聚合构建内联的八个包各带一份 `settings-card.module.css`,八份发射出同
一个 tag id,幂等守卫让第一个标签吞掉其余七个;而每个包的类名映射携带
按路径派生的 CSS-modules 哈希——七个类映射指向从未注入的样式表。
2. `scripts/sync-shared.mjs` 的 `SETTINGS_CONSUMERS` 不含 `dsh-perf`,其
卡片层拷贝随之漂移:聚合为七个子包内联了新鲜共享样式,而 perf 自己
bundle 里的陈旧拷贝继续吐旧的 tertiary 提示色。
## 决策
- 预设改用完整仓库相对文件 id 作为样式标签键
(`<bundle id>/packages/<pkg>/src/client/<file>.module.css`),不同包的
同名 module 不再互相吞并。`data-plugin`(卸载清理键)不变。
- 共享 `settings-card.module.css` 的小字角色(`.description`、`.hint`、
`.readOnly`)改用 `--dsw-alias-label-secondary`;错误角色(`.failed`、
`.invalid`、`.inputInvalid` 及其焦点环)改用
`--dsw-alias-state-error-primary` 并回退 `#b42318`。
- `scripts/sync-shared.mjs` 新增 `SETTINGS_CARD_ONLY_CONSUMERS` 层:
`dsh-perf` 与其他消费者一样同步卡片层拷贝(`PluginSettingsCard.tsx` +
`settings-card.module.css`),但 `settings-form.ts` 刻意保持七包目标——
dsh-perf 仍运行 0.1.2 之前的逐字段表单实现(保存路径按逐字段 judge,
而非共享的原子 mutate + 回读校验),覆盖它属于行为变更而非样式同步。
`perf-settings-card.tsx` 的两处 select 提示改走 `css.hint`,不再用内联
`opacity: 0.66`。
## 已考虑的替代方案
- 按内容哈希去重相同样式表:否决——CSS-modules 哈希由文件路径派生,相同
内容的拷贝仍产生不同类映射,无法共享标签。
- 聚合构建内把八份拷贝别名到单一规范样式表:否决——按包提交拷贝正是同步
契约;在预设里重映射会把它耦合到包目录布局。
- 把 dsh-perf 挡在同步名单之外:这就是本次修的 bug(漂移本身)。
## 后果
- 内联多包的 bundle 现在按 module 文件(而非 basename)注入样式标签;聚合
包多带几份近似重复的 4KB 样式表,代价可忽略,且失效模式从静默丢样式变
为可见的正确样式。
- dsh-perf 的设置保存路径仍是旧代表单实现,其 bundle 在重建前提示色依旧;
鉴于 dsh-perf 已计划完全弃用,不再计划对齐它的 `settings-form.ts`——弃
用时应一并移除 `SETTINGS_CARD_ONLY_CONSUMERS` 层。
- 方角皮肤(orca-link、xp)用 `border-radius: 0 !important` 刻意压平开关、
下拉与弹层;设置控件在这些皮肤下保持可读但为方角。若判定观感破损,按皮
肤豁免 `[role="switch"]` 只需一行 patch——留给皮肤负责人决定。
## 测试
`node --test scripts/sync-shared.test.mjs` 4/4(拷贝计数 110 -> 112、
client trio 39 -> 41);`node scripts/aggregate.mjs --check` OK;全仓
`pnpm typecheck` 与 `pnpm i18n:check` 通过。实测 GUI(127.0.0.1:3080):
orca-link 皮肤与默认外观下,Web 插件六张卡与宠物卡均为全宽折叠头
(`display: flex`、522px),九份 settings-card 样式表全部注入(聚合 8 +
perf 独立 1),字段/徽标/提示恢复共享 chrome,皮肤下原生 select 回退为
34px 高、token 配色,已同步包的提示计算色为 label-secondary #343b47。
@@ -72,7 +72,10 @@
}
.description {
color: var(--dsw-alias-label-tertiary);
/* Secondary, not tertiary: skins whose tertiary token is tuned for large
print leave 12-13px hint text below readable contrast (orca-link #778399
on cream measures ~3.4:1). */
color: var(--dsw-alias-label-secondary);
font-size: 13px;
line-height: 1.5;
}
@@ -106,7 +109,7 @@
}
.readOnly {
color: var(--dsw-alias-label-tertiary);
color: var(--dsw-alias-label-secondary);
margin: 12px 0 0;
font-size: 12px;
line-height: 1.5;
@@ -130,7 +133,10 @@
.failed {
min-width: 0;
color: var(--dsw-alias-label-error);
/* The suite's error alias is state-error-primary; a label-error token does
not exist, so the var() would resolve to nothing and the message would
inherit the ink color, losing the failure signal. */
color: var(--dsw-alias-state-error-primary, #b42318);
flex: 1;
margin: 0;
font-size: 12px;
@@ -278,7 +284,7 @@
}
.inputInvalid {
border: 1px solid var(--dsw-alias-label-error);
border: 1px solid var(--dsw-alias-state-error-primary, #b42318);
background: var(--dsw-alias-bg-layer-3);
height: 34px;
font: inherit;
@@ -290,9 +296,9 @@
}
.inputInvalid:focus-visible {
outline: 2px solid var(--dsw-alias-label-error);
outline: 2px solid var(--dsw-alias-state-error-primary, #b42318);
outline-offset: 1px;
border-color: var(--dsw-alias-label-error);
border-color: var(--dsw-alias-state-error-primary, #b42318);
}
.selectWrap {
@@ -384,14 +390,14 @@
}
.invalid {
color: var(--dsw-alias-label-error);
color: var(--dsw-alias-state-error-primary, #b42318);
margin: 0;
font-size: 12px;
line-height: 1.5;
}
.hint {
color: var(--dsw-alias-label-tertiary);
color: var(--dsw-alias-label-secondary);
margin: 0;
font-size: 12px;
line-height: 1.5;
@@ -72,7 +72,10 @@
}
.description {
color: var(--dsw-alias-label-tertiary);
/* Secondary, not tertiary: skins whose tertiary token is tuned for large
print leave 12-13px hint text below readable contrast (orca-link #778399
on cream measures ~3.4:1). */
color: var(--dsw-alias-label-secondary);
font-size: 13px;
line-height: 1.5;
}
@@ -106,7 +109,7 @@
}
.readOnly {
color: var(--dsw-alias-label-tertiary);
color: var(--dsw-alias-label-secondary);
margin: 12px 0 0;
font-size: 12px;
line-height: 1.5;
@@ -130,7 +133,10 @@
.failed {
min-width: 0;
color: var(--dsw-alias-label-error);
/* The suite's error alias is state-error-primary; a label-error token does
not exist, so the var() would resolve to nothing and the message would
inherit the ink color, losing the failure signal. */
color: var(--dsw-alias-state-error-primary, #b42318);
flex: 1;
margin: 0;
font-size: 12px;
@@ -278,7 +284,7 @@
}
.inputInvalid {
border: 1px solid var(--dsw-alias-label-error);
border: 1px solid var(--dsw-alias-state-error-primary, #b42318);
background: var(--dsw-alias-bg-layer-3);
height: 34px;
font: inherit;
@@ -290,9 +296,9 @@
}
.inputInvalid:focus-visible {
outline: 2px solid var(--dsw-alias-label-error);
outline: 2px solid var(--dsw-alias-state-error-primary, #b42318);
outline-offset: 1px;
border-color: var(--dsw-alias-label-error);
border-color: var(--dsw-alias-state-error-primary, #b42318);
}
.selectWrap {
@@ -384,14 +390,14 @@
}
.invalid {
color: var(--dsw-alias-label-error);
color: var(--dsw-alias-state-error-primary, #b42318);
margin: 0;
font-size: 12px;
line-height: 1.5;
}
.hint {
color: var(--dsw-alias-label-tertiary);
color: var(--dsw-alias-label-secondary);
margin: 0;
font-size: 12px;
line-height: 1.5;
File diff suppressed because one or more lines are too long
@@ -72,7 +72,10 @@
}
.description {
color: var(--dsw-alias-label-tertiary);
/* Secondary, not tertiary: skins whose tertiary token is tuned for large
print leave 12-13px hint text below readable contrast (orca-link #778399
on cream measures ~3.4:1). */
color: var(--dsw-alias-label-secondary);
font-size: 13px;
line-height: 1.5;
}
@@ -106,7 +109,7 @@
}
.readOnly {
color: var(--dsw-alias-label-tertiary);
color: var(--dsw-alias-label-secondary);
margin: 12px 0 0;
font-size: 12px;
line-height: 1.5;
@@ -130,7 +133,10 @@
.failed {
min-width: 0;
color: var(--dsw-alias-label-error);
/* The suite's error alias is state-error-primary; a label-error token does
not exist, so the var() would resolve to nothing and the message would
inherit the ink color, losing the failure signal. */
color: var(--dsw-alias-state-error-primary, #b42318);
flex: 1;
margin: 0;
font-size: 12px;
@@ -278,7 +284,7 @@
}
.inputInvalid {
border: 1px solid var(--dsw-alias-label-error);
border: 1px solid var(--dsw-alias-state-error-primary, #b42318);
background: var(--dsw-alias-bg-layer-3);
height: 34px;
font: inherit;
@@ -290,9 +296,9 @@
}
.inputInvalid:focus-visible {
outline: 2px solid var(--dsw-alias-label-error);
outline: 2px solid var(--dsw-alias-state-error-primary, #b42318);
outline-offset: 1px;
border-color: var(--dsw-alias-label-error);
border-color: var(--dsw-alias-state-error-primary, #b42318);
}
.selectWrap {
@@ -384,14 +390,14 @@
}
.invalid {
color: var(--dsw-alias-label-error);
color: var(--dsw-alias-state-error-primary, #b42318);
margin: 0;
font-size: 12px;
line-height: 1.5;
}
.hint {
color: var(--dsw-alias-label-tertiary);
color: var(--dsw-alias-label-secondary);
margin: 0;
font-size: 12px;
line-height: 1.5;
@@ -7,7 +7,6 @@ import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-cli
import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client'
import type { SnapshotStore } from '@deepseek-ai/dsh-client-store'
import { PluginSettingsCard, ValueField, BooleanField, SelectField } from './plugin-settings-card.tsx'
import { BetterSessionCard } from './better-session-card.tsx'
import { CardForm, booleanField, choiceField, numberField, type CardActions, type CardShell, type FieldState as CardFieldState } from './settings-form.ts'
/** The dsh-perf settings namespace shape (mirrors the host Config schema). */
@@ -172,10 +171,6 @@ export function PerfSettingsCard(props: PerfSettingsCardProps) {
onEdit={(text) => { props.edit('hudEnabled', text) }}
onReset={() => { props.resetField('hudEnabled') }}
/>
{/* Better Session 子节: 第三方外部集成(@morlay/better-session)的启用/迁移
管理面。better-session 本身是会话性能治理, 所以管理面嵌在本卡内部,
不再作为 Web 插件组的平级卡片。 */}
<BetterSessionCard t={t} />
</PluginSettingsCard>
)
}
@@ -72,7 +72,10 @@
}
.description {
color: var(--dsw-alias-label-tertiary);
/* Secondary, not tertiary: skins whose tertiary token is tuned for large
print leave 12-13px hint text below readable contrast (orca-link #778399
on cream measures ~3.4:1). */
color: var(--dsw-alias-label-secondary);
font-size: 13px;
line-height: 1.5;
}
@@ -106,7 +109,7 @@
}
.readOnly {
color: var(--dsw-alias-label-tertiary);
color: var(--dsw-alias-label-secondary);
margin: 12px 0 0;
font-size: 12px;
line-height: 1.5;
@@ -130,7 +133,10 @@
.failed {
min-width: 0;
color: var(--dsw-alias-label-error);
/* The suite's error alias is state-error-primary; a label-error token does
not exist, so the var() would resolve to nothing and the message would
inherit the ink color, losing the failure signal. */
color: var(--dsw-alias-state-error-primary, #b42318);
flex: 1;
margin: 0;
font-size: 12px;
@@ -278,7 +284,7 @@
}
.inputInvalid {
border: 1px solid var(--dsw-alias-label-error);
border: 1px solid var(--dsw-alias-state-error-primary, #b42318);
background: var(--dsw-alias-bg-layer-3);
height: 34px;
font: inherit;
@@ -290,9 +296,9 @@
}
.inputInvalid:focus-visible {
outline: 2px solid var(--dsw-alias-label-error);
outline: 2px solid var(--dsw-alias-state-error-primary, #b42318);
outline-offset: 1px;
border-color: var(--dsw-alias-label-error);
border-color: var(--dsw-alias-state-error-primary, #b42318);
}
.selectWrap {
@@ -384,14 +390,14 @@
}
.invalid {
color: var(--dsw-alias-label-error);
color: var(--dsw-alias-state-error-primary, #b42318);
margin: 0;
font-size: 12px;
line-height: 1.5;
}
.hint {
color: var(--dsw-alias-label-tertiary);
color: var(--dsw-alias-label-secondary);
margin: 0;
font-size: 12px;
line-height: 1.5;
@@ -72,7 +72,10 @@
}
.description {
color: var(--dsw-alias-label-tertiary);
/* Secondary, not tertiary: skins whose tertiary token is tuned for large
print leave 12-13px hint text below readable contrast (orca-link #778399
on cream measures ~3.4:1). */
color: var(--dsw-alias-label-secondary);
font-size: 13px;
line-height: 1.5;
}
@@ -106,7 +109,7 @@
}
.readOnly {
color: var(--dsw-alias-label-tertiary);
color: var(--dsw-alias-label-secondary);
margin: 12px 0 0;
font-size: 12px;
line-height: 1.5;
@@ -130,7 +133,10 @@
.failed {
min-width: 0;
color: var(--dsw-alias-label-error);
/* The suite's error alias is state-error-primary; a label-error token does
not exist, so the var() would resolve to nothing and the message would
inherit the ink color, losing the failure signal. */
color: var(--dsw-alias-state-error-primary, #b42318);
flex: 1;
margin: 0;
font-size: 12px;
@@ -278,7 +284,7 @@
}
.inputInvalid {
border: 1px solid var(--dsw-alias-label-error);
border: 1px solid var(--dsw-alias-state-error-primary, #b42318);
background: var(--dsw-alias-bg-layer-3);
height: 34px;
font: inherit;
@@ -290,9 +296,9 @@
}
.inputInvalid:focus-visible {
outline: 2px solid var(--dsw-alias-label-error);
outline: 2px solid var(--dsw-alias-state-error-primary, #b42318);
outline-offset: 1px;
border-color: var(--dsw-alias-label-error);
border-color: var(--dsw-alias-state-error-primary, #b42318);
}
.selectWrap {
@@ -384,14 +390,14 @@
}
.invalid {
color: var(--dsw-alias-label-error);
color: var(--dsw-alias-state-error-primary, #b42318);
margin: 0;
font-size: 12px;
line-height: 1.5;
}
.hint {
color: var(--dsw-alias-label-tertiary);
color: var(--dsw-alias-label-secondary);
margin: 0;
font-size: 12px;
line-height: 1.5;
@@ -72,7 +72,10 @@
}
.description {
color: var(--dsw-alias-label-tertiary);
/* Secondary, not tertiary: skins whose tertiary token is tuned for large
print leave 12-13px hint text below readable contrast (orca-link #778399
on cream measures ~3.4:1). */
color: var(--dsw-alias-label-secondary);
font-size: 13px;
line-height: 1.5;
}
@@ -106,7 +109,7 @@
}
.readOnly {
color: var(--dsw-alias-label-tertiary);
color: var(--dsw-alias-label-secondary);
margin: 12px 0 0;
font-size: 12px;
line-height: 1.5;
@@ -130,7 +133,10 @@
.failed {
min-width: 0;
color: var(--dsw-alias-label-error);
/* The suite's error alias is state-error-primary; a label-error token does
not exist, so the var() would resolve to nothing and the message would
inherit the ink color, losing the failure signal. */
color: var(--dsw-alias-state-error-primary, #b42318);
flex: 1;
margin: 0;
font-size: 12px;
@@ -278,7 +284,7 @@
}
.inputInvalid {
border: 1px solid var(--dsw-alias-label-error);
border: 1px solid var(--dsw-alias-state-error-primary, #b42318);
background: var(--dsw-alias-bg-layer-3);
height: 34px;
font: inherit;
@@ -290,9 +296,9 @@
}
.inputInvalid:focus-visible {
outline: 2px solid var(--dsw-alias-label-error);
outline: 2px solid var(--dsw-alias-state-error-primary, #b42318);
outline-offset: 1px;
border-color: var(--dsw-alias-label-error);
border-color: var(--dsw-alias-state-error-primary, #b42318);
}
.selectWrap {
@@ -384,14 +390,14 @@
}
.invalid {
color: var(--dsw-alias-label-error);
color: var(--dsw-alias-state-error-primary, #b42318);
margin: 0;
font-size: 12px;
line-height: 1.5;
}
.hint {
color: var(--dsw-alias-label-tertiary);
color: var(--dsw-alias-label-secondary);
margin: 0;
font-size: 12px;
line-height: 1.5;
@@ -72,7 +72,10 @@
}
.description {
color: var(--dsw-alias-label-tertiary);
/* Secondary, not tertiary: skins whose tertiary token is tuned for large
print leave 12-13px hint text below readable contrast (orca-link #778399
on cream measures ~3.4:1). */
color: var(--dsw-alias-label-secondary);
font-size: 13px;
line-height: 1.5;
}
@@ -106,7 +109,7 @@
}
.readOnly {
color: var(--dsw-alias-label-tertiary);
color: var(--dsw-alias-label-secondary);
margin: 12px 0 0;
font-size: 12px;
line-height: 1.5;
@@ -130,7 +133,10 @@
.failed {
min-width: 0;
color: var(--dsw-alias-label-error);
/* The suite's error alias is state-error-primary; a label-error token does
not exist, so the var() would resolve to nothing and the message would
inherit the ink color, losing the failure signal. */
color: var(--dsw-alias-state-error-primary, #b42318);
flex: 1;
margin: 0;
font-size: 12px;
@@ -278,7 +284,7 @@
}
.inputInvalid {
border: 1px solid var(--dsw-alias-label-error);
border: 1px solid var(--dsw-alias-state-error-primary, #b42318);
background: var(--dsw-alias-bg-layer-3);
height: 34px;
font: inherit;
@@ -290,9 +296,9 @@
}
.inputInvalid:focus-visible {
outline: 2px solid var(--dsw-alias-label-error);
outline: 2px solid var(--dsw-alias-state-error-primary, #b42318);
outline-offset: 1px;
border-color: var(--dsw-alias-label-error);
border-color: var(--dsw-alias-state-error-primary, #b42318);
}
.selectWrap {
@@ -384,14 +390,14 @@
}
.invalid {
color: var(--dsw-alias-label-error);
color: var(--dsw-alias-state-error-primary, #b42318);
margin: 0;
font-size: 12px;
line-height: 1.5;
}
.hint {
color: var(--dsw-alias-label-tertiary);
color: var(--dsw-alias-label-secondary);
margin: 0;
font-size: 12px;
line-height: 1.5;
@@ -72,7 +72,10 @@
}
.description {
color: var(--dsw-alias-label-tertiary);
/* Secondary, not tertiary: skins whose tertiary token is tuned for large
print leave 12-13px hint text below readable contrast (orca-link #778399
on cream measures ~3.4:1). */
color: var(--dsw-alias-label-secondary);
font-size: 13px;
line-height: 1.5;
}
@@ -106,7 +109,7 @@
}
.readOnly {
color: var(--dsw-alias-label-tertiary);
color: var(--dsw-alias-label-secondary);
margin: 12px 0 0;
font-size: 12px;
line-height: 1.5;
@@ -130,7 +133,10 @@
.failed {
min-width: 0;
color: var(--dsw-alias-label-error);
/* The suite's error alias is state-error-primary; a label-error token does
not exist, so the var() would resolve to nothing and the message would
inherit the ink color, losing the failure signal. */
color: var(--dsw-alias-state-error-primary, #b42318);
flex: 1;
margin: 0;
font-size: 12px;
@@ -278,7 +284,7 @@
}
.inputInvalid {
border: 1px solid var(--dsw-alias-label-error);
border: 1px solid var(--dsw-alias-state-error-primary, #b42318);
background: var(--dsw-alias-bg-layer-3);
height: 34px;
font: inherit;
@@ -290,9 +296,9 @@
}
.inputInvalid:focus-visible {
outline: 2px solid var(--dsw-alias-label-error);
outline: 2px solid var(--dsw-alias-state-error-primary, #b42318);
outline-offset: 1px;
border-color: var(--dsw-alias-label-error);
border-color: var(--dsw-alias-state-error-primary, #b42318);
}
.selectWrap {
@@ -384,14 +390,14 @@
}
.invalid {
color: var(--dsw-alias-label-error);
color: var(--dsw-alias-state-error-primary, #b42318);
margin: 0;
font-size: 12px;
line-height: 1.5;
}
.hint {
color: var(--dsw-alias-label-tertiary);
color: var(--dsw-alias-label-secondary);
margin: 0;
font-size: 12px;
line-height: 1.5;
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+14 -2
View File
@@ -28,6 +28,14 @@ export const REPO_ROOT = resolve(SCRIPT_DIR, '..')
*/
// Consumers of the settings card trio: one list, three derivations below.
const SETTINGS_CONSUMERS = ['dsh-pet', 'dsh-task-board', 'dsh-remote-web-ui', 'dsh-tool-describe-image', 'dsh-desktop-launcher', 'dsh-doctor', 'dsh-market']
// dsh-perf still runs the pre-0.1.2 per-field settings-form generation (its
// save path judges every write per-field instead of the shared atomic mutate
// + read-back), so the form source must not overwrite it. Its card chrome and
// stylesheet carry no local delta and sync like every other consumer; leaving
// them out is what let dsh-perf serve a stale stylesheet while the aggregate
// inlined the fresh one.
const SETTINGS_CARD_ONLY_CONSUMERS = ['dsh-perf']
const SETTINGS_CARD_CONSUMERS = [...SETTINGS_CONSUMERS, ...SETTINGS_CARD_ONLY_CONSUMERS]
const MANIFEST = [
{
@@ -38,12 +46,16 @@ const MANIFEST = [
{
file: 'PluginSettingsCard.tsx',
source: 'shared/client/settings/PluginSettingsCard.tsx',
targets: SETTINGS_CONSUMERS.map(pkg => `packages/${pkg}/src/client/PluginSettingsCard.tsx`),
targets: [
...SETTINGS_CONSUMERS.map(pkg => `packages/${pkg}/src/client/PluginSettingsCard.tsx`),
// dsh-perf imports the card under its pre-shared lowercase local name.
'packages/dsh-perf/src/client/plugin-settings-card.tsx',
],
},
{
file: 'settings-card.module.css',
source: 'shared/client/settings/settings-card.module.css',
targets: SETTINGS_CONSUMERS.map(pkg => `packages/${pkg}/src/client/settings-card.module.css`),
targets: SETTINGS_CARD_CONSUMERS.map(pkg => `packages/${pkg}/src/client/settings-card.module.css`),
},
{
file: 'poll-guard.ts',
+3 -2
View File
@@ -21,9 +21,10 @@ test('copies cover the settings trio for all consumers plus host and http helper
// Normalize separators: node:path join yields backslashes on Windows, and
// the copy-count buckets below match on forward slashes.
const entries = copyEntries().map(entry => ({ ...entry, target: entry.target.replaceAll('\\', '/') }))
assert.equal(entries.length, 110)
// 8 consumer packages for the card chrome pair, 7 for the form source.
assert.equal(entries.length, 112)
const clientTrio = entries.filter(entry => entry.target.includes('/src/client/'))
assert.equal(clientTrio.length, 39)
assert.equal(clientTrio.length, 41)
const hostCopies = entries.filter(entry => entry.target.includes('/src/host/')
|| entry.target.includes('/src/dsh-home.ts')
|| entry.target.includes('/src/mount-once.ts')
@@ -71,7 +71,10 @@
}
.description {
color: var(--dsw-alias-label-tertiary);
/* Secondary, not tertiary: skins whose tertiary token is tuned for large
print leave 12-13px hint text below readable contrast (orca-link #778399
on cream measures ~3.4:1). */
color: var(--dsw-alias-label-secondary);
font-size: 13px;
line-height: 1.5;
}
@@ -105,7 +108,7 @@
}
.readOnly {
color: var(--dsw-alias-label-tertiary);
color: var(--dsw-alias-label-secondary);
margin: 12px 0 0;
font-size: 12px;
line-height: 1.5;
@@ -129,7 +132,10 @@
.failed {
min-width: 0;
color: var(--dsw-alias-label-error);
/* The suite's error alias is state-error-primary; a label-error token does
not exist, so the var() would resolve to nothing and the message would
inherit the ink color, losing the failure signal. */
color: var(--dsw-alias-state-error-primary, #b42318);
flex: 1;
margin: 0;
font-size: 12px;
@@ -277,7 +283,7 @@
}
.inputInvalid {
border: 1px solid var(--dsw-alias-label-error);
border: 1px solid var(--dsw-alias-state-error-primary, #b42318);
background: var(--dsw-alias-bg-layer-3);
height: 34px;
font: inherit;
@@ -289,9 +295,9 @@
}
.inputInvalid:focus-visible {
outline: 2px solid var(--dsw-alias-label-error);
outline: 2px solid var(--dsw-alias-state-error-primary, #b42318);
outline-offset: 1px;
border-color: var(--dsw-alias-label-error);
border-color: var(--dsw-alias-state-error-primary, #b42318);
}
.selectWrap {
@@ -383,14 +389,14 @@
}
.invalid {
color: var(--dsw-alias-label-error);
color: var(--dsw-alias-state-error-primary, #b42318);
margin: 0;
font-size: 12px;
line-height: 1.5;
}
.hint {
color: var(--dsw-alias-label-tertiary);
color: var(--dsw-alias-label-secondary);
margin: 0;
font-size: 12px;
line-height: 1.5;
+8 -2
View File
@@ -15,7 +15,7 @@
import { readFile } from 'node:fs/promises'
import { existsSync, readFileSync } from 'node:fs'
import { createRequire } from 'node:module'
import { basename, dirname, isAbsolute, relative, resolve as resolvePath, sep } from 'node:path'
import { dirname, isAbsolute, relative, resolve as resolvePath, sep } from 'node:path'
import { fileURLToPath } from 'node:url'
import type { UserConfig } from 'tsdown'
import { transform } from 'lightningcss'
@@ -346,9 +346,15 @@ function clientConfig(id: string, entry: string, extraPlugins: readonly NonNulla
classMap[local] = exp.name
}
// One <style data-plugin> per module file; idempotent under re-evaluation.
// The tag id carries the full repo-relative file id, not the basename:
// the aggregate build inlines several packages whose module files share
// a basename (eight settings-card.module.css copies), and a basename-only
// id lets the first tag suppress the others while each package's class
// map carries a path-derived hash — leaving every later copy's classes
// with no stylesheet at all.
return [
`const css = ${JSON.stringify(code.toString())};`,
`const tagId = ${JSON.stringify(`${id}/${basename(fileId)}`)};`,
`const tagId = ${JSON.stringify(`${id}/${fileId}`)};`,
'if (typeof document !== \'undefined\' && document.querySelector(\'style[data-plugin-css=\' + JSON.stringify(tagId) + \']\') === null) {',
' const tag = document.createElement(\'style\');',
` tag.dataset.plugin = ${JSON.stringify(id)};`,