dsh-background-by-model 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/README.zh.md +6 -0
- package/lib/client.js +2307 -2232
- package/lib/client.js.map +1 -1
- package/lib/{host-scan-BDh0upIQ.js → host-scan-WjK2VGBe.js} +22 -0
- package/lib/host-scan.js +1 -1
- package/lib/index.js +1 -1
- package/lib/testing.js +326 -275
- package/package.json +1 -1
- package/test/host-check.test.mjs +109 -20
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-background-by-model",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "Appearance plugin for DeepSeek Harness: an ordered list of model rules, each carrying its own wallpaper, theme color, layout mode, opacity and blur 鈥?the background follows the model you switch to.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
package/test/host-check.test.mjs
CHANGED
|
@@ -31,7 +31,7 @@ const here = dirname(fileURLToPath(import.meta.url))
|
|
|
31
31
|
const pluginDir = resolve(here, '..')
|
|
32
32
|
const testingPath = join(pluginDir, 'lib', 'testing.js')
|
|
33
33
|
|
|
34
|
-
const { HOST_CONTRACTS, DSH_FLOOR, probeContracts, verdictOf, meetsFloor, UI_CSS } =
|
|
34
|
+
const { HOST_CONTRACTS, DSH_FLOOR, probeContracts, verdictOf, meetsFloor, UI_CSS, OWN_SHEET_ATTR, OWN_SHEET_DATASET } =
|
|
35
35
|
await import(pathToFileURL(testingPath).href)
|
|
36
36
|
|
|
37
37
|
const { readFile, readdir } = await import('node:fs/promises')
|
|
@@ -90,10 +90,14 @@ function makeDom(host) {
|
|
|
90
90
|
}
|
|
91
91
|
return {
|
|
92
92
|
querySelector: sel => (matches(sel) ? { style: {} } : null),
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
|
|
93
|
+
// Where the host publishes its palette: `body` in the real client, and the
|
|
94
|
+
// fixture models exactly that. Reading the document element instead is what
|
|
95
|
+
// made seven healthy tokens report as empty.
|
|
96
|
+
tokenRoot: () => ({ style: {} }),
|
|
97
|
+
// `on` is null for an unscoped token check (the probe reads it off the token
|
|
98
|
+
// root) and the resolved element otherwise. The fixture answers from its own
|
|
99
|
+
// table either way — whether a value is readable is the host's own business,
|
|
100
|
+
// and that is exactly what the table stands in for.
|
|
97
101
|
computedValue: (token, _on) => (host.get(`computed:${token}`) ?? ''),
|
|
98
102
|
styleSheets: () => [],
|
|
99
103
|
}
|
|
@@ -127,21 +131,55 @@ const PRESENT_TOKENS = [
|
|
|
127
131
|
]
|
|
128
132
|
|
|
129
133
|
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
134
|
+
* The literals the HOST's stylesheets declare. The two halves of a token
|
|
135
|
+
* contract ask different questions — `rule` = "does the host still declare this
|
|
136
|
+
* name", `computed` = "does it resolve to a value" — so both are listed.
|
|
132
137
|
*/
|
|
133
138
|
const HOST_RULES = [
|
|
134
139
|
'--dsw-menu-surface-fill', '--dsw-specific-menu', '--dsw-specific-sidebar-fill',
|
|
135
140
|
'--dsw-specific-input-major', '--dsw-alias-bg-base', '--dsw-alias-bg-layer-1',
|
|
136
|
-
'
|
|
141
|
+
'--dsw-alias-label-caption',
|
|
142
|
+
'.md-table-wide', '[data-dockkit-content]', '[data-dockkit-empty]',
|
|
137
143
|
'[data-dockkit-float]', '[data-sidebar-right-open]', 'data-ds-dark-theme',
|
|
138
144
|
]
|
|
139
145
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
146
|
+
/** The one literal only THIS plugin's stylesheet declares (`.dab-card`, `side: 'own'`). */
|
|
147
|
+
const OWN_RULES = ['.dab-card']
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* A stylesheet the probe may read.
|
|
151
|
+
*
|
|
152
|
+
* `own: true` writes this plugin's marker — the ONLY thing that tells our sheets
|
|
153
|
+
* apart from the host's, since the host's own CSS modules set `data-plugin` on
|
|
154
|
+
* theirs, so a probe keying off that skips the entire host theme.
|
|
155
|
+
*
|
|
156
|
+
* The fixture exposes the attribute the way a REAL element does: `getAttribute`
|
|
157
|
+
* answers the dashed name, and `dataset` only the camel-cased key. Modelling this
|
|
158
|
+
* wrong is how a probe that reads `dataset['data-dab-side']` passes here and
|
|
159
|
+
* silently fails in a browser.
|
|
160
|
+
*/
|
|
161
|
+
function sheet(literals, own = false) {
|
|
162
|
+
const attrs = {}
|
|
163
|
+
const dataset = {}
|
|
164
|
+
if (own) {
|
|
165
|
+
attrs[OWN_SHEET_ATTR] = 'own'
|
|
166
|
+
dataset[OWN_SHEET_DATASET] = 'own'
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
ownerNode: {
|
|
170
|
+
dataset,
|
|
171
|
+
getAttribute: name => (name in attrs ? attrs[name] : null),
|
|
172
|
+
setAttribute: (name, value) => { attrs[name] = value },
|
|
173
|
+
},
|
|
143
174
|
cssRules: literals.map(l => ({ cssText: `.x{color:red}/* ${l} */` })),
|
|
144
|
-
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** The host's sheets plus our own, minus whatever a test removes. */
|
|
179
|
+
function hostStyles(literals = HOST_RULES, ownLiterals = OWN_RULES) {
|
|
180
|
+
const sheets = [sheet(literals)]
|
|
181
|
+
if (ownLiterals.length > 0) sheets.push(sheet(ownLiterals, true))
|
|
182
|
+
return sheets
|
|
145
183
|
}
|
|
146
184
|
|
|
147
185
|
/**
|
|
@@ -192,6 +230,36 @@ test('a renamed menu token fails exactly that contract, and names the symptom',
|
|
|
192
230
|
assert.match(row.symptom, /solid band|white band|sticky/i)
|
|
193
231
|
})
|
|
194
232
|
|
|
233
|
+
test('a renamed token the host still aliases is caught by the rule half', () => {
|
|
234
|
+
// The 0.1.7 menu split in reverse: the host keeps the OLD name working (aliased)
|
|
235
|
+
// but stops declaring the new one. `computed` still answers — which is exactly
|
|
236
|
+
// how that bug stayed silent — so the `rule` half is the only thing that can
|
|
237
|
+
// tell "renamed" from "still there".
|
|
238
|
+
const env = hostEnv({
|
|
239
|
+
ctx: healthyCtx(),
|
|
240
|
+
rules: HOST_RULES.filter(l => l !== '--dsw-menu-surface-fill'),
|
|
241
|
+
})
|
|
242
|
+
const results = probeContracts(env, 'en')
|
|
243
|
+
const row = results.find(r => r.id === 'token.menuSurface')
|
|
244
|
+
assert.equal(row.status, 'fail')
|
|
245
|
+
// The computed half would have passed: the token still resolves a value.
|
|
246
|
+
assert.match(row.detail, /host stylesheet includes/)
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
test('a host that stops publishing the palette fails the computed half', () => {
|
|
250
|
+
// The other failure shape: the name is still declared somewhere, but body no
|
|
251
|
+
// longer carries a value — the case that made 7 healthy tokens look empty when
|
|
252
|
+
// the probe read the wrong element.
|
|
253
|
+
const env = hostEnv({
|
|
254
|
+
ctx: healthyCtx(),
|
|
255
|
+
tokens: PRESENT_TOKENS.filter(t => t !== '--dsw-alias-bg-base'),
|
|
256
|
+
})
|
|
257
|
+
const results = probeContracts(env, 'en')
|
|
258
|
+
const row = results.find(r => r.id === 'token.bgBase')
|
|
259
|
+
assert.equal(row.status, 'fail')
|
|
260
|
+
assert.match(row.reason, /--dsw-alias-bg-base/)
|
|
261
|
+
})
|
|
262
|
+
|
|
195
263
|
test('a dropped session service fails the model hop', () => {
|
|
196
264
|
const env = hostEnv({ ctx: healthyCtx({ sessions: undefined }) })
|
|
197
265
|
const results = probeContracts(env, 'en')
|
|
@@ -200,19 +268,40 @@ test('a dropped session service fails the model hop', () => {
|
|
|
200
268
|
})
|
|
201
269
|
|
|
202
270
|
test('the plugin\'s own token stylesheet cannot make a host check pass', () => {
|
|
271
|
+
// The situation that produced the 0.1.7 white band: the plugin re-emits the
|
|
272
|
+
// menu fill itself, so its own sheet declares the token. That re-emission must
|
|
273
|
+
// never be accepted as evidence about the HOST — otherwise the check is green
|
|
274
|
+
// by construction on exactly the host where the token was renamed away.
|
|
203
275
|
const env = hostEnv({
|
|
204
276
|
ctx: healthyCtx(),
|
|
205
|
-
// The
|
|
206
|
-
//
|
|
277
|
+
// The host sheet is readable but has moved on: it no longer declares the
|
|
278
|
+
// menu fill under this name.
|
|
207
279
|
rules: [],
|
|
208
280
|
})
|
|
209
|
-
env.styleSheets = () => [
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
281
|
+
env.styleSheets = () => [
|
|
282
|
+
sheet([]), // the host's own, readable, declaring nothing
|
|
283
|
+
sheet(['--dsw-menu-surface-fill', '--dsw-specific-menu', '.dab-card'], true),
|
|
284
|
+
]
|
|
285
|
+
const results = probeContracts(env, 'en')
|
|
286
|
+
const byId = id => results.find(r => r.id === id)
|
|
287
|
+
assert.equal(byId('token.menuSurface').status, 'fail')
|
|
288
|
+
assert.match(byId('token.menuSurface').reason, /no host stylesheet declares/)
|
|
289
|
+
assert.equal(byId('token.menuAlias').status, 'fail')
|
|
290
|
+
// …while the contract whose other half IS our own sheet still passes: this is
|
|
291
|
+
// what `side: 'own'` is for, and why the two sides must never be merged.
|
|
292
|
+
assert.equal(byId('settings.card').status, 'pass')
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
test('a host sheet carrying data-plugin is not mistaken for ours', () => {
|
|
296
|
+
// Regression for the probe bug that reported 7 healthy tokens as renamed: the
|
|
297
|
+
// host's CSS modules ALSO set `data-plugin` on their <style> elements, so a
|
|
298
|
+
// probe that skips "sheets with data-plugin" skips the host's whole theme.
|
|
299
|
+
const hostSheet = sheet(['--dsw-alias-bg-base'])
|
|
300
|
+
hostSheet.ownerNode.dataset.plugin = '@deepseek-ai/dsh-client-ui-theme'
|
|
301
|
+
const env = hostEnv({ ctx: healthyCtx(), rules: [] })
|
|
302
|
+
env.styleSheets = () => [hostSheet]
|
|
213
303
|
const results = probeContracts(env, 'en')
|
|
214
|
-
assert.equal(results.find(r => r.id === 'token.
|
|
215
|
-
assert.equal(results.find(r => r.id === 'token.menuAlias').status, 'fail')
|
|
304
|
+
assert.equal(results.find(r => r.id === 'token.bgBase').status, 'pass')
|
|
216
305
|
})
|
|
217
306
|
|
|
218
307
|
test('a surface that is merely off screen is n/a, not a failure', () => {
|