dsh-structured 0.9.6 → 0.9.9
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/CHANGELOG.md +19 -0
- package/README.md +274 -273
- package/docs/0.9-FINAL-RELEASE.md +22 -0
- package/docs/1.0-BASELINE.md +38 -0
- package/docs/1.0-GATE.md +39 -0
- package/docs/1.0-PERSONA-SPEC.md +59 -0
- package/lib/client.js +4 -4
- package/lib/index.js +518 -517
- package/package.json +102 -99
- package/scripts/theme-contrast.mjs +2 -2
- package/scripts/theme-hue-distance.mjs +55 -0
- package/src/client.js +2 -2
- package/src/index.js +516 -515
- package/test/client-cards.test.mjs +145 -140
- package/test/fixtures/persona-gate/no-persona.md +8 -0
- package/test/fixtures/persona-gate/persona-append.md +9 -0
- package/test/fixtures/persona-gate/style-override.md +8 -0
- package/test/structured.test.mjs +8 -0
- package/test/theme.test.mjs +8 -1
|
@@ -1,140 +1,145 @@
|
|
|
1
|
-
import test from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import { readFileSync } from 'node:fs'
|
|
4
|
-
import vm from 'node:vm'
|
|
5
|
-
import { parseHTML } from 'linkedom'
|
|
6
|
-
|
|
7
|
-
const clientSource = readFileSync('src/client.js', 'utf8')
|
|
8
|
-
|
|
9
|
-
function render(html) {
|
|
10
|
-
const { window, document } = parseHTML(`<!doctype html><html><head></head><body>${html}</body></html>`)
|
|
11
|
-
let definition
|
|
12
|
-
const sandbox = {
|
|
13
|
-
window: {
|
|
14
|
-
__ModuleLoader__: {
|
|
15
|
-
load(value) {
|
|
16
|
-
definition = value
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
document,
|
|
21
|
-
MutationObserver: class {
|
|
22
|
-
observe() {}
|
|
23
|
-
disconnect() {}
|
|
24
|
-
},
|
|
25
|
-
requestAnimationFrame(fn) {
|
|
26
|
-
fn()
|
|
27
|
-
return 1
|
|
28
|
-
},
|
|
29
|
-
cancelAnimationFrame() {},
|
|
30
|
-
setTimeout,
|
|
31
|
-
clearTimeout,
|
|
32
|
-
console,
|
|
33
|
-
}
|
|
34
|
-
sandbox.globalThis = sandbox
|
|
35
|
-
vm.runInNewContext(clientSource, sandbox)
|
|
36
|
-
assert.ok(definition, 'client module definition captured')
|
|
37
|
-
const plugin = definition.factory()
|
|
38
|
-
plugin.apply({
|
|
39
|
-
inject(_services, callback) {
|
|
40
|
-
callback({ configForms: { get: () => null } })
|
|
41
|
-
},
|
|
42
|
-
effect() {},
|
|
43
|
-
})
|
|
44
|
-
return document
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function block(document, id) {
|
|
48
|
-
const element = document.querySelector(`#${id}`)
|
|
49
|
-
assert.ok(element, `missing #${id}`)
|
|
50
|
-
return element
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function assertPlain(document, id) {
|
|
54
|
-
const element = block(document, id)
|
|
55
|
-
assert.equal(element.getAttribute('data-dsh-structured-card'), null, `${id} card`)
|
|
56
|
-
assert.equal(element.getAttribute('data-dsh-structured-card-type'), null, `${id} type`)
|
|
57
|
-
assert.equal(element.getAttribute('data-dsh-structured-tone'), null, `${id} tone`)
|
|
58
|
-
assert.equal(element.getAttribute('data-dsh-structured-lead'), null, `${id} lead`)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function assertCard(document, id, type, tone) {
|
|
62
|
-
const element = block(document, id)
|
|
63
|
-
assert.equal(element.getAttribute('data-dsh-structured-card'), '1', `${id} card`)
|
|
64
|
-
assert.equal(element.getAttribute('data-dsh-structured-card-type'), type, `${id} type`)
|
|
65
|
-
assert.equal(element.getAttribute('data-dsh-structured-tone'), tone, `${id} tone`)
|
|
66
|
-
assert.equal(element.getAttribute('data-dsh-structured-lead'), null, `${id} lead`)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
test('
|
|
70
|
-
const document = render(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
<div class="
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
<blockquote id="
|
|
90
|
-
<blockquote id="
|
|
91
|
-
<blockquote id="
|
|
92
|
-
<blockquote id="
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
<div data-genui="1">
|
|
99
|
-
<blockquote id="
|
|
100
|
-
</div>
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
assertPlain(document, '
|
|
112
|
-
assertPlain(document, '
|
|
113
|
-
assertPlain(document, '
|
|
114
|
-
assertPlain(document, '
|
|
115
|
-
assertPlain(document, '
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
assertCard(document, '
|
|
124
|
-
assertCard(document, '
|
|
125
|
-
assertCard(document, '
|
|
126
|
-
assertCard(document, '
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
1
|
+
import test from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { readFileSync } from 'node:fs'
|
|
4
|
+
import vm from 'node:vm'
|
|
5
|
+
import { parseHTML } from 'linkedom'
|
|
6
|
+
|
|
7
|
+
const clientSource = readFileSync('src/client.js', 'utf8')
|
|
8
|
+
|
|
9
|
+
function render(html) {
|
|
10
|
+
const { window, document } = parseHTML(`<!doctype html><html><head></head><body>${html}</body></html>`)
|
|
11
|
+
let definition
|
|
12
|
+
const sandbox = {
|
|
13
|
+
window: {
|
|
14
|
+
__ModuleLoader__: {
|
|
15
|
+
load(value) {
|
|
16
|
+
definition = value
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
document,
|
|
21
|
+
MutationObserver: class {
|
|
22
|
+
observe() {}
|
|
23
|
+
disconnect() {}
|
|
24
|
+
},
|
|
25
|
+
requestAnimationFrame(fn) {
|
|
26
|
+
fn()
|
|
27
|
+
return 1
|
|
28
|
+
},
|
|
29
|
+
cancelAnimationFrame() {},
|
|
30
|
+
setTimeout,
|
|
31
|
+
clearTimeout,
|
|
32
|
+
console,
|
|
33
|
+
}
|
|
34
|
+
sandbox.globalThis = sandbox
|
|
35
|
+
vm.runInNewContext(clientSource, sandbox)
|
|
36
|
+
assert.ok(definition, 'client module definition captured')
|
|
37
|
+
const plugin = definition.factory()
|
|
38
|
+
plugin.apply({
|
|
39
|
+
inject(_services, callback) {
|
|
40
|
+
callback({ configForms: { get: () => null } })
|
|
41
|
+
},
|
|
42
|
+
effect() {},
|
|
43
|
+
})
|
|
44
|
+
return document
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function block(document, id) {
|
|
48
|
+
const element = document.querySelector(`#${id}`)
|
|
49
|
+
assert.ok(element, `missing #${id}`)
|
|
50
|
+
return element
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function assertPlain(document, id) {
|
|
54
|
+
const element = block(document, id)
|
|
55
|
+
assert.equal(element.getAttribute('data-dsh-structured-card'), null, `${id} card`)
|
|
56
|
+
assert.equal(element.getAttribute('data-dsh-structured-card-type'), null, `${id} type`)
|
|
57
|
+
assert.equal(element.getAttribute('data-dsh-structured-tone'), null, `${id} tone`)
|
|
58
|
+
assert.equal(element.getAttribute('data-dsh-structured-lead'), null, `${id} lead`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function assertCard(document, id, type, tone) {
|
|
62
|
+
const element = block(document, id)
|
|
63
|
+
assert.equal(element.getAttribute('data-dsh-structured-card'), '1', `${id} card`)
|
|
64
|
+
assert.equal(element.getAttribute('data-dsh-structured-card-type'), type, `${id} type`)
|
|
65
|
+
assert.equal(element.getAttribute('data-dsh-structured-tone'), tone, `${id} tone`)
|
|
66
|
+
assert.equal(element.getAttribute('data-dsh-structured-lead'), null, `${id} lead`)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
test('0.9.7 client 不改写消息里的 $...$', () => {
|
|
70
|
+
const document = render('<p id="dollar">成本 $5,公式 $x^2$</p>')
|
|
71
|
+
assert.equal(document.getElementById('dollar').textContent, '成本 $5,公式 $x^2$')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('严格卡片:复合标签、普通引用不升级,完整标签才成卡', () => {
|
|
75
|
+
const document = render(`
|
|
76
|
+
<div class="_markdown_q2"><blockquote id="q2"><p><strong>风险提示</strong><br>影响:宿主升级后可能失效。</p></blockquote></div>
|
|
77
|
+
<div class="_markdown_test">
|
|
78
|
+
<blockquote id="q1"><p>风险提示:宿主升级后可能失效。</p></blockquote>
|
|
79
|
+
<blockquote id="q3"><p>结论:这是用户引用的原文。</p></blockquote>
|
|
80
|
+
<blockquote id="q4"><p>这是普通引用,没有加粗标题。</p></blockquote>
|
|
81
|
+
<blockquote id="q9"><p><strong>判断依据</strong><br>不应成卡。</p></blockquote>
|
|
82
|
+
</div>
|
|
83
|
+
<div class="_markdown_q10"><blockquote id="q10"><p>前缀 <strong>标题</strong></p></blockquote></div>
|
|
84
|
+
<div class="_markdown_q11"><blockquote id="q11"><p> <strong>标题</strong></p></blockquote></div>
|
|
85
|
+
<div class="_markdown_q18"><blockquote id="q18"><p>前缀 <strong>标题</strong>尾正文</p></blockquote></div>
|
|
86
|
+
<div class="_markdown_q16"><blockquote id="q16"><p><strong>结论性说明</strong><br>不应成卡。</p></blockquote></div>
|
|
87
|
+
<div class="_markdown_q17"><blockquote id="q17"><p><strong>指标数据</strong><br>不应成卡。</p></blockquote></div>
|
|
88
|
+
<div class="_markdown_test2">
|
|
89
|
+
<blockquote id="q5"><p><strong>Phase 1 完成</strong><br>28/28 通过。</p></blockquote>
|
|
90
|
+
<blockquote id="q5b"><p><strong>Phase 2 完成</strong><br>不应升级为 statement。</p></blockquote>
|
|
91
|
+
<blockquote id="q6"><p><strong>风险</strong><br>影响:宿主升级后可能失效。</p></blockquote>
|
|
92
|
+
<blockquote id="q7"><p><strong>下一步</strong><br>1. 安装测试 profile。</p></blockquote>
|
|
93
|
+
<blockquote id="q8"><p><strong>验收</strong><br>✅ 3/3</p></blockquote>
|
|
94
|
+
<blockquote id="q12"><p><strong>结论</strong><br>结论卡。</p></blockquote>
|
|
95
|
+
<blockquote id="q13"><p><strong>决策</strong><br>决策卡。</p></blockquote>
|
|
96
|
+
<blockquote id="q14"><p><strong>指标</strong><br>指标卡。</p></blockquote>
|
|
97
|
+
<blockquote id="q15"><p><strong>重点</strong><br>重点卡。</p></blockquote>
|
|
98
|
+
<div data-genui="1">
|
|
99
|
+
<blockquote id="gen"><p><strong>风险</strong><br>GenUI 内不处理。</p></blockquote>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
<div class="_markdown_test5">
|
|
103
|
+
<div data-genui="1">
|
|
104
|
+
<blockquote id="genfirst"><p><strong>风险</strong><br>GenUI 内不处理,也不应阻断真实首块。</p></blockquote>
|
|
105
|
+
</div>
|
|
106
|
+
<blockquote id="real"><p><strong>Phase 9 完成</strong><br>真实首块仍应升级 statement。</p></blockquote>
|
|
107
|
+
</div>
|
|
108
|
+
`)
|
|
109
|
+
|
|
110
|
+
// 反例:前缀/复合词、普通引用、strong 前有正文或空白
|
|
111
|
+
assertPlain(document, 'q1')
|
|
112
|
+
assertPlain(document, 'q2')
|
|
113
|
+
assertPlain(document, 'q3')
|
|
114
|
+
assertPlain(document, 'q4')
|
|
115
|
+
assertPlain(document, 'q9')
|
|
116
|
+
assertPlain(document, 'q10')
|
|
117
|
+
assertPlain(document, 'q11')
|
|
118
|
+
assertPlain(document, 'q18')
|
|
119
|
+
assertPlain(document, 'q16')
|
|
120
|
+
assertPlain(document, 'q17')
|
|
121
|
+
|
|
122
|
+
// 正例:全部卡型 + 仅第一块可升级 + GenUI 前置不阻断真实首块
|
|
123
|
+
assertCard(document, 'q5', 'statement', 'info')
|
|
124
|
+
assertCard(document, 'q6', 'risk', 'warn')
|
|
125
|
+
assertCard(document, 'q7', 'next', 'info')
|
|
126
|
+
assertCard(document, 'q8', 'accept', 'success')
|
|
127
|
+
assertCard(document, 'q12', 'conclusion', 'info')
|
|
128
|
+
assertCard(document, 'q13', 'decision', 'info')
|
|
129
|
+
assertCard(document, 'q14', 'metric', 'info')
|
|
130
|
+
assertCard(document, 'q15', 'key', 'info')
|
|
131
|
+
assertCard(document, 'real', 'statement', 'info')
|
|
132
|
+
assertPlain(document, 'q5b')
|
|
133
|
+
|
|
134
|
+
// GenUI 区域跳过
|
|
135
|
+
assertPlain(document, 'gen')
|
|
136
|
+
assertPlain(document, 'genfirst')
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
test('严格卡片:不再有普通首引用 lead 与文本前缀 tone 逻辑', () => {
|
|
140
|
+
assert.doesNotMatch(clientSource, /isFirstBlockquote/)
|
|
141
|
+
assert.doesNotMatch(clientSource, /toneFor/)
|
|
142
|
+
assert.doesNotMatch(clientSource, /setAttribute\(LEAD_ATTR,\s*'lead'\)/)
|
|
143
|
+
assert.match(clientSource, /CARD_TYPES = new Map/)
|
|
144
|
+
assert.match(clientSource, /isStatementCard/)
|
|
145
|
+
})
|
package/test/structured.test.mjs
CHANGED
|
@@ -28,6 +28,14 @@ import {
|
|
|
28
28
|
} from '../src/index.js'
|
|
29
29
|
import { classify, firstScreenStructured } from '../scripts/measure-prose.mjs'
|
|
30
30
|
|
|
31
|
+
test('0.9.7 公式规避规则:金额用转义/反引号,不写裸 $...$', () => {
|
|
32
|
+
assert.match(BUILTIN_STYLE_TEXT, /金额\/美元符号/)
|
|
33
|
+
assert.match(BUILTIN_STYLE_TEXT, /\\\$/)
|
|
34
|
+
assert.match(BUILTIN_STYLE_TEXT, /反引号/)
|
|
35
|
+
assert.match(BUILTIN_STYLE_TEXT, /不要写裸 `\$\.\.\.\$`/)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
|
|
31
39
|
function isolate() {
|
|
32
40
|
const home = mkdtempSync(join(tmpdir(), 'dsh-structured-test-'))
|
|
33
41
|
const saved = { dir: process.env.DSH_PROFILE_DIR, profile: process.env.DSH_PROFILE }
|
package/test/theme.test.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { readFileSync } from 'node:fs'
|
|
|
3
3
|
import test from 'node:test'
|
|
4
4
|
import { Config, statusText, THEMES } from '../src/index.js'
|
|
5
5
|
import { checkThemeContrast, THEME_TOKENS } from '../scripts/theme-contrast.mjs'
|
|
6
|
+
import { checkHueSeparation } from '../scripts/theme-hue-distance.mjs'
|
|
6
7
|
|
|
7
8
|
const value = (field) => (field && typeof field.get === 'function' ? field.get() : field)
|
|
8
9
|
|
|
@@ -14,7 +15,13 @@ test('0.9.0 Config:theme 默认 deepseek,三值可选', () => {
|
|
|
14
15
|
assert.equal(value(Config({ defaultEnabled: false }).defaultEnabled), false)
|
|
15
16
|
})
|
|
16
17
|
|
|
17
|
-
test('0.9.
|
|
18
|
+
test('0.9.8 accent/status hue 分离:12 项通过', () => {
|
|
19
|
+
const { results, failures } = checkHueSeparation()
|
|
20
|
+
assert.equal(failures.length, 0, JSON.stringify(failures, null, 2))
|
|
21
|
+
assert.equal(results.length, 12)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('0.9.8 Config:六个字段的默认值与显式值', () => {
|
|
18
25
|
const defaults = Config({})
|
|
19
26
|
assert.equal(value(defaults.theme), 'deepseek')
|
|
20
27
|
assert.equal(value(defaults.visual), true)
|