dsh-structured 0.8.2 → 0.9.1

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/lib/client.js CHANGED
@@ -1,510 +1,590 @@
1
1
  // Generated from src/client.js by scripts/build.mjs — edit src/, then run `npm run build`.
2
2
  // dsh-structured client module: renders "结论:" blockquotes as cards.
3
- /**
4
- * dsh-structured client
5
- *
6
- * 两层,都是纯 DOM 增强,不改消息内容、不替换 app root:
7
- * 1) 对话 markdown 视觉层:标题字号/层级、表格、代码块、引用块、列表、链接的排版与配色。
8
- * 2) 语义装饰:结论卡片、风险/验收等 callout 色调、标题关键词图标、代码块语言色。
9
- *
10
- * 只给宿主已有节点加 class/attribute,不搬 React 节点;markdown 重渲染后由 MutationObserver
11
- * 重新扫描。主题色全部走 --dsw-alias-*,自动跟随明暗主题。
12
- */
13
- window.__ModuleLoader__.load({
14
- id: 'dsh-structured',
15
- factory() {
16
- const CARD_ATTR = 'data-dsh-structured-card'
17
- const CARD_TYPE_ATTR = 'data-dsh-structured-card-type'
18
- const ICON_ATTR = 'data-dsh-structured-icon'
19
- const TONE_ATTR = 'data-dsh-structured-tone'
20
- const LANG_ATTR = 'data-dsh-structured-lang'
21
- const STATUS_ATTR = 'data-dsh-structured-status'
22
- const STATUS_TABLE_ATTR = 'data-dsh-structured-status-table'
23
- const LEAD_ATTR = 'data-dsh-structured-lead'
24
- const ROW_ATTR = 'data-dsh-structured-row'
25
- const ROOT_ATTR = 'data-dsh-structured'
26
- const STYLE_ID = 'dsh-structured-skin-style'
27
- const LEGACY_STYLE_ID = 'dsh-structured-card-style'
28
-
29
- const CARD_TYPES = new Map([
30
- ['结论', 'conclusion'], ['总结', 'conclusion'], ['答案', 'conclusion'],
31
- ['判断', 'decision'], ['决策', 'decision'],
32
- ['指标', 'metric'], ['数据', 'metric'],
33
- ['风险', 'risk'], ['注意', 'risk'], ['警告', 'risk'],
34
- ['下一步', 'next'], ['行动', 'next'], ['待办', 'next'],
35
- ['验收', 'accept'], ['通过', 'accept'], ['完成', 'accept'],
36
- ['重点', 'key'], ['关键', 'key'],
37
- ])
38
- const ICONS = [
39
- [/结论|总结|答案|判断/, '◆'],
40
- [/方案|计划|步骤|流程/, '▸'],
41
- [/风险|注意|警告|坑/, '⚠'],
42
- [/验收|检查|核对/, '✓'],
43
- [/对比|选型|取舍/, '⇄'],
44
- [/下一步|行动|待办/, '→'],
45
- [/背景|原因|根因/, '◎'],
46
- [/指标|数据|结果/, '▦'],
47
- ]
48
-
49
- const CSS = `
50
- [class*="_markdown_"]{
51
- --ds-accent:#2563eb;
52
- --ds-text:var(--dsw-alias-label-primary,#111827);
53
- --ds-muted:var(--dsw-alias-label-secondary,#6b7280);
54
- --ds-border:var(--dsw-alias-border-l2,rgba(127,127,127,.22));
55
- --ds-surface:var(--dsw-alias-bg-layer-1,#fff);
56
- --ds-surface-2:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.06));
57
- font-size:15px;line-height:1.72;letter-spacing:.01em;
58
- }
59
- [data-theme="dark"] [class*="_markdown_"]{--ds-accent:#60a5fa}
60
- [class*="_markdown_"] p{margin:.72em 0}
61
- [class*="_markdown_"] h1,[class*="_markdown_"] h2,[class*="_markdown_"] h3,
62
- [class*="_markdown_"] h4,[class*="_markdown_"] h5,[class*="_markdown_"] h6{
63
- color:var(--ds-text);line-height:1.32;letter-spacing:-.01em;
64
- }
65
- [class*="_markdown_"] h1{font-size:26px;font-weight:700;margin:32px 0 12px;padding-bottom:8px;border-bottom:1px solid var(--ds-border)}
66
- [class*="_markdown_"] h2{font-size:21px;font-weight:700;margin:28px 0 10px;padding-left:12px;border-left:3px solid var(--ds-accent)}
67
- [class*="_markdown_"] h3{font-size:17px;font-weight:650;margin:22px 0 8px}
68
- [class*="_markdown_"] h4{font-size:15px;font-weight:650;margin:16px 0 6px}
69
- [class*="_markdown_"] h5,[class*="_markdown_"] h6{font-size:13px;font-weight:700;margin:14px 0 6px;color:var(--ds-muted);text-transform:uppercase;letter-spacing:.06em}
70
- [class*="_markdown_"] h2[${ICON_ATTR}]::before,
71
- [class*="_markdown_"] h3[${ICON_ATTR}]::before{
72
- content:attr(${ICON_ATTR});
73
- display:inline-flex;align-items:center;justify-content:center;
74
- width:22px;height:22px;margin-right:8px;border-radius:6px;
75
- background:color-mix(in srgb,var(--ds-accent) 12%,transparent);
76
- color:var(--ds-accent);font-size:12px;font-weight:700;vertical-align:2px;
77
- }
78
- [class*="_markdown_"] strong{font-weight:700;color:var(--ds-text)}
79
- [class*="_markdown_"] a{color:var(--ds-accent);text-decoration:none;border-bottom:1px solid color-mix(in srgb,var(--ds-accent) 38%,transparent)}
80
- [class*="_markdown_"] a:hover{border-bottom-color:var(--ds-accent)}
81
- [class*="_markdown_"] ul,[class*="_markdown_"] ol{padding-left:1.5em;margin:.6em 0}
82
- [class*="_markdown_"] li{margin:.28em 0}
83
- [class*="_markdown_"] li::marker{color:var(--ds-accent)}
84
- [class*="_markdown_"] hr{height:1px;border:0;margin:28px 0;background:linear-gradient(90deg,transparent,var(--ds-border),transparent)}
85
- [class*="_markdown_"] table{
86
- width:100%;margin:18px 0;border-collapse:separate;border-spacing:0;
87
- border:1px solid var(--ds-border);border-radius:12px;overflow:hidden;font-size:13.5px;
88
- box-shadow:0 1px 2px rgba(0,0,0,.03);
89
- }
90
- [class*="_markdown_"] thead th{
91
- background:var(--ds-surface-2);
92
- font-size:11px!important;letter-spacing:.07em!important;color:var(--ds-muted);
93
- font-weight:700!important;text-align:left;padding:12px 18px!important;
94
- border-bottom:1px solid var(--ds-border);
95
- }
96
- [class*="_markdown_"] tbody td{
97
- padding:13px 18px!important;line-height:1.55;vertical-align:middle;border-left:0;border-right:0;
98
- border-bottom:1px solid color-mix(in srgb,var(--ds-border) 55%,transparent);
99
- word-break:break-word;
100
- }
101
- [class*="_markdown_"] tbody tr:nth-child(even){background:color-mix(in srgb,var(--ds-surface-2) 45%,transparent)}
102
- [class*="_markdown_"] tbody tr:hover{background:color-mix(in srgb,var(--ds-accent) 5%,transparent)}
103
- [class*="_markdown_"] tbody tr:last-child td{border-bottom:0}
104
- [class*="_markdown_"] table:has(thead th:nth-child(2):last-child) th:first-child,
105
- [class*="_markdown_"] table:has(thead th:nth-child(2):last-child) td:first-child{width:30%}
106
- [class*="_markdown_"] table:not([${STATUS_TABLE_ATTR}]):has(thead th:nth-child(3):last-child) th:first-child,
107
- [class*="_markdown_"] table:not([${STATUS_TABLE_ATTR}]):has(thead th:nth-child(3):last-child) td:first-child{width:24%}
108
- [class*="_markdown_"] table:not([${STATUS_TABLE_ATTR}]):has(thead th:nth-child(3):last-child) th:nth-child(2),
109
- [class*="_markdown_"] table:not([${STATUS_TABLE_ATTR}]):has(thead th:nth-child(3):last-child) td:nth-child(2){width:22%}
110
- [class*="_markdown_"] table[${STATUS_TABLE_ATTR}] th:first-child,
111
- [class*="_markdown_"] table[${STATUS_TABLE_ATTR}] td:first-child{
112
- width:68px;text-align:center;padding:7px 8px!important;font-size:11px;font-weight:700;
113
- white-space:nowrap;vertical-align:middle;border-radius:7px;
114
- }
115
- [class*="_markdown_"] table[${STATUS_TABLE_ATTR}] tbody tr[${ROW_ATTR}="success"] td:first-child{background:color-mix(in srgb,var(--dsw-alias-state-success-primary,#16a34a) 12%,transparent);color:var(--dsw-alias-state-success-primary,#16a34a)}
116
- [class*="_markdown_"] table[${STATUS_TABLE_ATTR}] tbody tr[${ROW_ATTR}="warn"] td:first-child{background:color-mix(in srgb,var(--dsw-alias-state-warn-primary,#d97706) 12%,transparent);color:var(--dsw-alias-state-warn-primary,#d97706)}
117
- [class*="_markdown_"] table[${STATUS_TABLE_ATTR}] tbody tr[${ROW_ATTR}="error"] td:first-child{background:color-mix(in srgb,var(--dsw-alias-state-error-primary,#dc2626) 12%,transparent);color:var(--dsw-alias-state-error-primary,#dc2626)}
118
- [class*="_markdown_"] p[${LEAD_ATTR}="table"]{
119
- border-left:2px solid var(--ds-accent);padding-left:10px;margin:10px 0 8px;
120
- font-size:14px;color:var(--ds-text);
121
- }
122
- [class*="_markdown_"] p[${LEAD_ATTR}="table"] strong{color:var(--ds-accent)}
123
- [class*="_markdown_"] h2+table,[class*="_markdown_"] h2+p,[class*="_markdown_"] h3+table,[class*="_markdown_"] h3+p{margin-top:8px}
124
- [class*="_markdown_"] :not(pre)>code{
125
- background:var(--ds-surface-2);border:1px solid var(--ds-border);border-radius:6px;
126
- padding:.12em .38em;font-size:.9em;color:var(--ds-accent);overflow-wrap:anywhere;
127
- }
128
- [class*="_markdown_"] .md-code-block{
129
- margin:16px 0;border:1px solid var(--ds-border);border-radius:12px;overflow:hidden;
130
- background:var(--ds-surface-2);box-shadow:0 1px 2px rgba(0,0,0,.04);
131
- }
132
- [class*="_markdown_"] .md-code-block > [class*="_bannerWrap"]{
133
- margin:0;padding:0;border:0;background:none;
134
- }
135
- [class*="_markdown_"] .md-code-block [data-code-block-banner]{
136
- display:flex;align-items:center;justify-content:space-between;gap:8px;margin:0;border-radius:0;
137
- padding:6px 12px;font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
138
- color:var(--ds-muted);border-bottom:1px solid var(--ds-border);
139
- background:linear-gradient(90deg,color-mix(in srgb,var(--ds-code-accent,var(--ds-accent)) 14%,transparent),transparent 72%);
140
- }
141
- [class*="_markdown_"] .md-code-block [data-code-block-banner] > [class*="_heading"]{
142
- flex:1;min-width:0;justify-content:flex-start;text-align:left;
143
- }
144
- [class*="_markdown_"] .md-code-block [data-code-block-banner] > [class*="_actions"]{
145
- margin-left:auto;flex:none;
146
- }
147
- [class*="_markdown_"] .md-code-block > [data-code-block-content]{padding:0}
148
- [class*="_markdown_"] .md-code-block [data-code-block-banner]::before{
149
- content:"";width:8px;height:8px;border-radius:50%;flex:none;
150
- background:var(--ds-code-accent,var(--ds-accent));
151
- box-shadow:0 0 0 3px color-mix(in srgb,var(--ds-code-accent,var(--ds-accent)) 18%,transparent);
152
- }
153
- [class*="_markdown_"] .md-code-block pre{margin:0;padding:14px 16px;font-size:13px;line-height:1.65;overflow:auto}
154
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="js"]{--ds-code-accent:#eab308}
155
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="ts"],
156
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="tsx"]{--ds-code-accent:#3b82f6}
157
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="py"],
158
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="python"]{--ds-code-accent:#22c55e}
159
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="bash"],
160
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="sh"],
161
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="powershell"]{--ds-code-accent:#14b8a6}
162
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="json"]{--ds-code-accent:#a855f7}
163
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="html"],
164
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="css"]{--ds-code-accent:#f97316}
165
- [class*="_markdown_"] .md-code-block[data-dsh-structured-lang="sql"]{--ds-code-accent:#06b6d4}
166
- [class*="_markdown_"] blockquote{
167
- margin:14px 0;padding:10px 14px;border-left:3px solid var(--ds-accent);
168
- background:color-mix(in srgb,var(--ds-accent) 6%,transparent);border-radius:0 10px 10px 0;
169
- }
170
- [class*="_markdown_"] blockquote[${TONE_ATTR}="warn"]{--ds-accent:var(--dsw-alias-state-warn-primary,#d97706)}
171
- [class*="_markdown_"] blockquote[${TONE_ATTR}="success"]{--ds-accent:var(--dsw-alias-state-success-primary,#16a34a)}
172
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]{
173
- border:1px solid color-mix(in srgb,var(--ds-accent) 16%,var(--ds-border));
174
- border-left:3px solid var(--ds-accent);
175
- border-radius:12px;padding:14px 16px 15px;
176
- background:linear-gradient(135deg,color-mix(in srgb,var(--ds-accent) 6%,var(--ds-surface)),var(--ds-surface) 60%);
177
- box-shadow:0 1px 2px rgba(0,0,0,.04),0 12px 32px -24px color-mix(in srgb,var(--ds-accent) 32%,transparent);
178
- }
179
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:first-child>strong:first-child{
180
- display:flex;align-items:center;gap:9px;position:relative;
181
- font-size:13px;line-height:1.1;letter-spacing:.06em;text-transform:uppercase;
182
- color:var(--ds-accent);margin-bottom:8px;
183
- }
184
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:first-child>strong:first-child::before{
185
- content:"";display:inline-flex;flex:none;
186
- width:26px;height:26px;border-radius:7px;
187
- background:color-mix(in srgb,var(--ds-accent) 12%,transparent);
188
- }
189
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:first-child>strong:first-child::after{
190
- content:"";position:absolute;left:5.5px;top:5.5px;width:15px;height:15px;
191
- background-color:var(--ds-accent);
192
- -webkit-mask:var(--ds-card-icon) center/contain no-repeat;
193
- mask:var(--ds-card-icon) center/contain no-repeat;
194
- }
195
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:nth-child(2)>strong:first-child{
196
- display:block;font-size:16px;font-weight:700;line-height:1.45;
197
- letter-spacing:-.01em;color:var(--ds-text);margin-bottom:4px;
198
- }
199
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p{font-size:15.5px;line-height:1.7;font-weight:450;color:var(--ds-text)}
200
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p strong{font-weight:700;color:var(--ds-text)}
201
- [class*="_markdown_"] blockquote[${TONE_ATTR}]>p:first-child>strong:first-child{color:var(--ds-accent)}
202
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="conclusion"]{
203
- --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 3l9 9-9 9-9-9 9-9z' fill='none' stroke='black' stroke-width='1.8' stroke-linejoin='round'/%3E%3C/svg%3E");
204
- }
205
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="decision"]{
206
- --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 7h6l4 10h6M4 17h6l4-10h6' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
207
- }
208
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="metric"]{
209
- --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 20V10M12 20V4M19 20v-7' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E");
210
- }
211
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="risk"]{
212
- --ds-accent:var(--dsw-alias-state-warn-primary,#d97706);
213
- --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 4l9 16H3L12 4z' fill='none' stroke='black' stroke-width='1.8' stroke-linejoin='round'/%3E%3Cpath d='M12 10v4M12 17v.5' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E");
214
- }
215
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="next"]{
216
- --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 12h14M13 6l6 6-6 6' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
217
- }
218
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="accept"]{
219
- --ds-accent:var(--dsw-alias-state-success-primary,#16a34a);
220
- --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 12.5l5 5L20 6.5' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
221
- }
222
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="key"]{
223
- --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 3l2.6 6.2 6.7.5-5.1 4.4 1.6 6.5L12 17.3 6.2 20.6l1.6-6.5L2.7 9.7l6.7-.5L12 3z' fill='none' stroke='black' stroke-width='1.6' stroke-linejoin='round'/%3E%3C/svg%3E");
224
- }
225
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="statement"]{
226
- --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 3l9 9-9 9-9-9 9-9z' fill='none' stroke='black' stroke-width='1.8' stroke-linejoin='round'/%3E%3C/svg%3E");
227
- }
228
- [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="statement"]>p:first-child>strong:first-child{
229
- font-size:16px;line-height:1.45;letter-spacing:-.01em;text-transform:none;
230
- color:var(--ds-text);margin-bottom:4px;
231
- }
232
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:first-child{margin-top:0}
233
- [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:last-child{margin-bottom:0}
234
- [class*="_markdown_"] p[${STATUS_ATTR}],[class*="_markdown_"] li[${STATUS_ATTR}]{
235
- border-left:3px solid var(--ds-accent);background:color-mix(in srgb,var(--ds-accent) 7%,transparent);
236
- border-radius:0 6px 6px 0;padding:4px 10px;margin:.4em 0;
237
- }
238
- [class*="_markdown_"] p[${STATUS_ATTR}="success"],[class*="_markdown_"] li[${STATUS_ATTR}="success"]{--ds-accent:var(--dsw-alias-state-success-primary,#16a34a)}
239
- [class*="_markdown_"] p[${STATUS_ATTR}="warn"],[class*="_markdown_"] li[${STATUS_ATTR}="warn"]{--ds-accent:var(--dsw-alias-state-warn-primary,#d97706)}
240
- [class*="_markdown_"] p[${STATUS_ATTR}="error"],[class*="_markdown_"] li[${STATUS_ATTR}="error"]{--ds-accent:var(--dsw-alias-state-error-primary,#dc2626)}
241
- @media (max-width:720px){
242
- [class*="_markdown_"]{font-size:14.5px}
243
- [class*="_markdown_"] h1{font-size:22px}
244
- [class*="_markdown_"] h2{font-size:18px}
245
- [class*="_markdown_"] table{font-size:13px}
246
- [class*="_markdown_"] thead th,[class*="_markdown_"] tbody td{padding:7px 9px}
247
- }
248
- @media (prefers-reduced-motion:reduce){
249
- [class*="_markdown_"] *{transition:none!important;animation:none!important}
250
- }
251
- `
252
-
253
- function iconFor(text) {
254
- for (const [pattern, icon] of ICONS) if (pattern.test(text)) return icon
255
- return null
256
- }
257
-
258
- function firstParagraph(block) {
259
- return [...(block.children ?? [])].find((element) => element.tagName === 'P') ?? null
260
- }
261
-
262
- function labelOf(block) {
263
- const first = firstParagraph(block)
264
- const strong = first?.firstElementChild
265
- if (!strong || strong.tagName !== 'STRONG') return null
266
- return (strong.textContent ?? '').replace(/[::]\s*$/u, '').trim()
267
- }
268
-
269
- function cardTypeFor(block) {
270
- const label = labelOf(block)
271
- return label === null ? null : (CARD_TYPES.get(label) ?? null)
272
- }
273
-
274
- function isLabelLike(text) {
275
- return [...CARD_TYPES.keys()].some((label) => text.startsWith(label) && text !== label)
276
- }
277
-
278
- function inGenui(element) {
279
- try {
280
- return Boolean(element.closest?.('[data-genui], [class*="genui-root"]'))
281
- } catch {
282
- return false
283
- }
284
- }
285
-
286
- function statusFor(text) {
287
- if (/^✅/.test(text)) return 'success'
288
- if (/^⚠/.test(text)) return 'warn'
289
- if (/^❌/.test(text)) return 'error'
290
- return null
291
- }
292
-
293
- function isStatementCard(block) {
294
- const markdown = block.closest?.('[class*="_markdown_"]')
295
- if (!markdown) return false
296
- const first = [...markdown.querySelectorAll('blockquote')].find((element) => !inGenui(element))
297
- if (first !== block) return false
298
- const paragraphs = [...block.children].filter((element) => element.tagName === 'P')
299
- const firstParagraph = paragraphs[0]
300
- const strong = firstParagraph?.firstElementChild
301
- if (!strong || strong.tagName !== 'STRONG') return false
302
- let previous = strong.previousSibling
303
- while (previous) {
304
- if (previous.nodeType === 1) return false
305
- if (previous.nodeType === 3 && (previous.textContent ?? '').trim() !== '') return false
306
- previous = previous.previousSibling
307
- }
308
- const title = (strong.textContent ?? '').trim()
309
- if (title.length === 0 || isLabelLike(title)) return false
310
- let rest = ''
311
- for (let node = strong.nextSibling; node; node = node.nextSibling) rest += node.textContent ?? ''
312
- const secondBody = paragraphs.slice(1).some((element) => (element.textContent ?? '').trim().length > 0)
313
- return rest.trim().length > 0 || secondBody
314
- }
315
-
316
- function decorate(root) {
317
- for (const heading of root.querySelectorAll('[class*="_markdown_"] h2, [class*="_markdown_"] h3')) {
318
- if (inGenui(heading)) continue
319
- const icon = iconFor((heading.textContent ?? '').trim())
320
- if (icon === null) heading.removeAttribute(ICON_ATTR)
321
- else heading.setAttribute(ICON_ATTR, icon)
322
- }
323
-
324
- for (const block of root.querySelectorAll('blockquote')) {
325
- if (inGenui(block)) continue
326
- let type = cardTypeFor(block)
327
- if (type === null && isStatementCard(block)) type = 'statement'
328
- if (type === null) {
329
- block.removeAttribute(CARD_ATTR)
330
- block.removeAttribute(CARD_TYPE_ATTR)
331
- block.removeAttribute(LEAD_ATTR)
332
- block.removeAttribute(TONE_ATTR)
333
- continue
334
- }
335
- block.setAttribute(CARD_ATTR, '1')
336
- block.setAttribute(CARD_TYPE_ATTR, type)
337
- block.removeAttribute(LEAD_ATTR)
338
- block.setAttribute(TONE_ATTR, type === 'risk' ? 'warn' : type === 'accept' ? 'success' : 'info')
339
- }
340
-
341
- for (const element of root.querySelectorAll('[class*="_markdown_"] p, [class*="_markdown_"] li')) {
342
- if (inGenui(element) || element.closest('blockquote, table')) {
343
- element.removeAttribute(STATUS_ATTR)
344
- continue
345
- }
346
- const status = statusFor((element.textContent ?? '').trimStart())
347
- if (status === null) element.removeAttribute(STATUS_ATTR)
348
- else element.setAttribute(STATUS_ATTR, status)
349
- }
350
-
351
- for (const row of root.querySelectorAll('[class*="_markdown_"] table tr')) {
352
- if (inGenui(row) || row.closest('thead')) {
353
- row.removeAttribute(ROW_ATTR)
354
- continue
355
- }
356
- const status = statusFor((row.textContent ?? '').trimStart())
357
- if (status === null) row.removeAttribute(ROW_ATTR)
358
- else row.setAttribute(ROW_ATTR, status)
359
- }
360
-
361
- for (const table of root.querySelectorAll('[class*="_markdown_"] table')) {
362
- if (inGenui(table)) continue
363
- const rows = [...table.querySelectorAll('tbody tr')]
364
- const statusTable =
365
- rows.length > 0 &&
366
- rows.every((row) => statusFor((row.cells?.[0]?.textContent ?? '').trimStart()) !== null)
367
- if (statusTable) table.setAttribute(STATUS_TABLE_ATTR, '1')
368
- else table.removeAttribute(STATUS_TABLE_ATTR)
369
- }
370
-
371
- for (const paragraph of root.querySelectorAll('[class*="_markdown_"] p')) {
372
- if (inGenui(paragraph) || paragraph.closest('blockquote, table')) {
373
- paragraph.removeAttribute(LEAD_ATTR)
374
- continue
375
- }
376
- if (/^表格结论/.test((paragraph.textContent ?? '').trimStart())) paragraph.setAttribute(LEAD_ATTR, 'table')
377
- else paragraph.removeAttribute(LEAD_ATTR)
378
- }
379
-
380
- for (const code of root.querySelectorAll('[class*="_markdown_"] .md-code-block')) {
381
- if (inGenui(code)) continue
382
- const banner = code.querySelector('[data-code-block-banner]')
383
- const raw = (banner?.textContent ?? '').trim().split(/\s+/)[0] ?? ''
384
- const lang = raw.toLowerCase().replace(/[^a-z0-9+#]/g, '')
385
- if (lang.length === 0) code.removeAttribute(LANG_ATTR)
386
- else code.setAttribute(LANG_ATTR, lang)
387
- }
388
- }
389
-
390
- let active = false
391
- let disposeLayer = null
392
-
393
- const enable = () => {
394
- if (active) return
395
- if (typeof document === 'undefined' || typeof MutationObserver === 'undefined') return
396
- if (document.body === null) return
397
- active = true
398
-
399
- document.getElementById(LEGACY_STYLE_ID)?.remove()
400
- document.getElementById(STYLE_ID)?.remove()
401
- const style = document.createElement('style')
402
- style.id = STYLE_ID
403
- style.textContent = CSS
404
- document.head.appendChild(style)
405
- document.documentElement.setAttribute(ROOT_ATTR, 'active')
406
- try {
407
- console.info('[dsh-structured] visual layer active')
408
- } catch {
409
- // console 不可用时忽略。
410
- }
411
-
412
- let disposed = false
413
- let scheduled = false
414
- let pending = null
415
- const run = () => {
416
- if (disposed) return
417
- scheduled = false
418
- pending = null
419
- decorate(document)
420
- }
421
- const schedule = () => {
422
- if (disposed || scheduled) return
423
- scheduled = true
424
- if (typeof requestAnimationFrame === 'function') pending = requestAnimationFrame(run)
425
- else pending = setTimeout(run, 16)
426
- }
427
- const observer = new MutationObserver(schedule)
428
- observer.observe(document.body, { childList: true, subtree: true, characterData: true })
429
- run()
430
-
431
- disposeLayer = () => {
432
- if (disposed) return
433
- disposed = true
434
- active = false
435
- disposeLayer = null
436
- observer.disconnect()
437
- if (pending !== null) {
438
- if (typeof cancelAnimationFrame === 'function') cancelAnimationFrame(pending)
439
- else clearTimeout(pending)
440
- }
441
- style.remove()
442
- document.documentElement.removeAttribute(ROOT_ATTR)
443
- for (const attr of [CARD_ATTR, CARD_TYPE_ATTR, ICON_ATTR, TONE_ATTR, LANG_ATTR, STATUS_ATTR, STATUS_TABLE_ATTR, LEAD_ATTR, ROW_ATTR]) {
444
- for (const element of document.querySelectorAll('[' + attr + ']')) element.removeAttribute(attr)
445
- }
446
- try {
447
- console.info('[dsh-structured] visual layer disposed')
448
- } catch {
449
- // console 不可用时忽略。
450
- }
451
- }
452
- }
453
-
454
- const disable = () => {
455
- const layer = disposeLayer
456
- disposeLayer = null
457
- if (typeof layer === 'function') layer()
458
- if (typeof document === 'undefined') return
459
- document.getElementById(STYLE_ID)?.remove()
460
- document.documentElement.removeAttribute(ROOT_ATTR)
461
- for (const attr of [CARD_ATTR, CARD_TYPE_ATTR, ICON_ATTR, TONE_ATTR, LANG_ATTR, STATUS_ATTR, STATUS_TABLE_ATTR, LEAD_ATTR, ROW_ATTR]) {
462
- for (const element of document.querySelectorAll('[' + attr + ']')) element.removeAttribute(attr)
463
- }
464
- }
465
-
466
- const applyVisual = (on) => {
467
- if (on) enable()
468
- else disable()
469
- }
470
-
471
- return {
472
- inject: [],
473
- apply(ctx) {
474
- // 默认开启,保持 0.8.1 的现有观感;随后由插件设置里的 visual 覆盖。
475
- enable()
476
- try {
477
- ctx?.inject?.(['configForms'], (child) => {
478
- try {
479
- const form = child.configForms?.get('dsh-structured')
480
- if (!form || typeof form.getSnapshot !== 'function') return
481
- const read = () => {
482
- try {
483
- const raw = form.getSnapshot()?.value?.visual
484
- const visual = raw && typeof raw.get === 'function' ? raw.get() : raw
485
- applyVisual(visual !== false)
486
- } catch {
487
- // 读取失败时保持当前状态,不打断回复。
488
- }
489
- }
490
- read()
491
- if (typeof form.subscribe === 'function') {
492
- child.effect?.(() => form.subscribe(read), 'dsh-structured: visual preference')
493
- }
494
- } catch {
495
- // configForms 不可用时保持默认开启。
496
- }
497
- })
498
- } catch {
499
- // 无 ctx.inject 的宿主保持默认开启。
500
- }
501
- try {
502
- ctx?.effect?.(() => disable, 'dsh-structured: visual layer')
503
- } catch {
504
- // 没有 effect 释放器时,视觉层保持到页面卸载。
505
- }
506
- return disable
507
- },
508
- }
509
- },
510
- })
3
+ /**
4
+ * dsh-structured client
5
+ *
6
+ * 两层,都是纯 DOM 增强,不改消息内容、不替换 app root:
7
+ * 1) 对话 markdown 视觉层:标题字号/层级、表格、代码块、引用块、列表、链接的排版与配色。
8
+ * 2) 语义装饰:结论卡片、风险/验收等 callout 色调、标题关键词图标、代码块语言色。
9
+ *
10
+ * 只给宿主已有节点加 class/attribute,不搬 React 节点;markdown 重渲染后由 MutationObserver
11
+ * 重新扫描。主题色全部走 --dsw-alias-*,自动跟随明暗主题。
12
+ */
13
+ window.__ModuleLoader__.load({
14
+ id: 'dsh-structured',
15
+ factory() {
16
+ const CARD_ATTR = 'data-dsh-structured-card'
17
+ const CARD_TYPE_ATTR = 'data-dsh-structured-card-type'
18
+ const ICON_ATTR = 'data-dsh-structured-icon'
19
+ const TONE_ATTR = 'data-dsh-structured-tone'
20
+ const LANG_ATTR = 'data-dsh-structured-lang'
21
+ const CODE_ATTR = 'data-dsh-structured-code'
22
+ const SKIP_ATTR = 'data-dsh-structured-skip'
23
+ const STATUS_ATTR = 'data-dsh-structured-status'
24
+ const STATUS_TABLE_ATTR = 'data-dsh-structured-status-table'
25
+ const LEAD_ATTR = 'data-dsh-structured-lead'
26
+ const ROW_ATTR = 'data-dsh-structured-row'
27
+ const ROOT_ATTR = 'data-dsh-structured'
28
+ const THEME_ATTR = 'data-dsh-structured-theme'
29
+ const THEMES = ['deepseek', 'codex', 'anthropic']
30
+ const STYLE_ID = 'dsh-structured-skin-style'
31
+ const LEGACY_STYLE_ID = 'dsh-structured-card-style'
32
+
33
+ const CARD_TYPES = new Map([
34
+ ['结论', 'conclusion'], ['总结', 'conclusion'], ['答案', 'conclusion'],
35
+ ['判断', 'decision'], ['决策', 'decision'],
36
+ ['指标', 'metric'], ['数据', 'metric'],
37
+ ['风险', 'risk'], ['注意', 'risk'], ['警告', 'risk'],
38
+ ['下一步', 'next'], ['行动', 'next'], ['待办', 'next'],
39
+ ['验收', 'accept'], ['通过', 'accept'], ['完成', 'accept'],
40
+ ['重点', 'key'], ['关键', 'key'],
41
+ ])
42
+ const ICONS = [
43
+ [/结论|总结|答案|判断/, '◆'],
44
+ [/方案|计划|步骤|流程/, '▸'],
45
+ [/风险|注意|警告|坑/, '⚠'],
46
+ [/验收|检查|核对/, '✓'],
47
+ [/对比|选型|取舍/, '⇄'],
48
+ [/下一步|行动|待办/, '→'],
49
+ [/背景|原因|根因/, '◎'],
50
+ [/指标|数据|结果/, '▦'],
51
+ ]
52
+
53
+ const CSS = `
54
+ [class*="_markdown_"]{
55
+ --ds-surface:#FFFFFF;--ds-surface-2:#F4F7FB;--ds-text:#0F172A;--ds-muted:#5B6B82;
56
+ --ds-border:#E2E8F0;--ds-accent:#2563EB;--ds-accent-text:#2563EB;--ds-success:#166534;--ds-warn:#92400E;--ds-error:#991B1B;
57
+ --ds-code-bg:#F6F8FA;--ds-code-text:#0F172A;
58
+ font-size:15px;line-height:1.72;letter-spacing:.01em;
59
+ }
60
+ body[data-ds-dark-theme] [class*="_markdown_"]{
61
+ --ds-surface:#111827;--ds-surface-2:#1F2937;--ds-text:#E5E7EB;--ds-muted:#9CA3AF;
62
+ --ds-border:#374151;--ds-accent:#60A5FA;--ds-accent-text:#60A5FA;--ds-success:#4ADE80;--ds-warn:#FBBF24;--ds-error:#F87171;
63
+ --ds-code-bg:#0B1220;--ds-code-text:#E5E7EB;
64
+ }
65
+ body[data-dsh-structured-theme="codex"] [class*="_markdown_"]{
66
+ --ds-surface:#FFFFFF;--ds-surface-2:#F0F7F4;--ds-text:#0D1117;--ds-muted:#4F625A;
67
+ --ds-border:#CFE3DB;--ds-accent:#10A37F;--ds-accent-text:#0B6B4F;--ds-success:#116329;--ds-warn:#7D4E00;--ds-error:#A40E26;
68
+ --ds-code-bg:#F0F7F4;--ds-code-text:#0D1117;
69
+ }
70
+ body[data-ds-dark-theme][data-dsh-structured-theme="codex"] [class*="_markdown_"]{
71
+ --ds-surface:#0B1512;--ds-surface-2:#12211B;--ds-text:#E6F2EC;--ds-muted:#9DB2A8;
72
+ --ds-border:#233A31;--ds-accent:#34D399;--ds-accent-text:#6EE7B7;--ds-success:#3FB950;--ds-warn:#D29922;--ds-error:#F85149;
73
+ --ds-code-bg:#07100D;--ds-code-text:#E6F2EC;
74
+ }
75
+ body[data-dsh-structured-theme="anthropic"] [class*="_markdown_"]{
76
+ --ds-surface:#FAF9F5;--ds-surface-2:#F5F1E8;--ds-text:#141413;--ds-muted:#6B6359;
77
+ --ds-border:#E5E0D5;--ds-accent:#CC785C;--ds-accent-text:#A14A2E;--ds-success:#266B4D;--ds-warn:#92400E;--ds-error:#8C1D18;
78
+ --ds-code-bg:#F5F1E8;--ds-code-text:#141413;
79
+ }
80
+ body[data-ds-dark-theme][data-dsh-structured-theme="anthropic"] [class*="_markdown_"]{
81
+ --ds-surface:#1F1E1D;--ds-surface-2:#2A2825;--ds-text:#F5F1E8;--ds-muted:#A8A29A;
82
+ --ds-border:#3A3733;--ds-accent:#E8A087;--ds-accent-text:#E8A087;--ds-success:#6FBF9A;--ds-warn:#E0A458;--ds-error:#F28B82;
83
+ --ds-code-bg:#171614;--ds-code-text:#F5F1E8;
84
+ }
85
+ [class*="_markdown_"] p{margin:.72em 0}
86
+ [class*="_markdown_"] h1,[class*="_markdown_"] h2,[class*="_markdown_"] h3,
87
+ [class*="_markdown_"] h4,[class*="_markdown_"] h5,[class*="_markdown_"] h6{
88
+ color:var(--ds-text);line-height:1.32;letter-spacing:-.01em;
89
+ }
90
+ [class*="_markdown_"] h1{font-size:26px;font-weight:700;margin:32px 0 12px;padding-bottom:8px;border-bottom:1px solid var(--ds-border)}
91
+ [class*="_markdown_"] h2{font-size:21px;font-weight:700;margin:28px 0 10px;padding-left:12px;border-left:3px solid var(--ds-accent)}
92
+ [class*="_markdown_"] h3{font-size:17px;font-weight:650;margin:22px 0 8px}
93
+ [class*="_markdown_"] h4{font-size:15px;font-weight:650;margin:16px 0 6px}
94
+ [class*="_markdown_"] h5,[class*="_markdown_"] h6{font-size:13px;font-weight:700;margin:14px 0 6px;color:var(--ds-muted);text-transform:uppercase;letter-spacing:.06em}
95
+ [class*="_markdown_"] h2[${ICON_ATTR}]::before,
96
+ [class*="_markdown_"] h3[${ICON_ATTR}]::before{
97
+ content:attr(${ICON_ATTR});
98
+ display:inline-flex;align-items:center;justify-content:center;
99
+ width:22px;height:22px;margin-right:8px;border-radius:6px;
100
+ background:color-mix(in srgb,var(--ds-accent) 12%,transparent);
101
+ color:var(--ds-accent-text);font-size:12px;font-weight:700;vertical-align:2px;
102
+ }
103
+ [class*="_markdown_"] strong{font-weight:700;color:var(--ds-text)}
104
+ [class*="_markdown_"] a{color:var(--ds-accent-text);text-decoration:none;border-bottom:1px solid color-mix(in srgb,var(--ds-accent) 38%,transparent)}
105
+ [class*="_markdown_"] a:hover{border-bottom-color:var(--ds-accent)}
106
+ [class*="_markdown_"] ul,[class*="_markdown_"] ol{padding-left:1.5em;margin:.6em 0}
107
+ [class*="_markdown_"] li{margin:.28em 0}
108
+ [class*="_markdown_"] li::marker{color:var(--ds-accent-text)}
109
+ [class*="_markdown_"] hr{height:1px;border:0;margin:28px 0;background:linear-gradient(90deg,transparent,var(--ds-border),transparent)}
110
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]){
111
+ width:100%;margin:18px 0;border-collapse:separate;border-spacing:0;
112
+ border:1px solid var(--ds-border);border-radius:12px;overflow:hidden;font-size:13.5px;
113
+ box-shadow:0 1px 2px rgba(0,0,0,.03);
114
+ }
115
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]) thead th{
116
+ background:var(--ds-surface-2);
117
+ font-size:11px!important;letter-spacing:.07em!important;color:var(--ds-muted);
118
+ font-weight:700!important;text-align:left;padding:12px 18px!important;
119
+ border-bottom:1px solid var(--ds-border);
120
+ }
121
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]) tbody td{
122
+ padding:13px 18px!important;line-height:1.55;vertical-align:middle;border-left:0;border-right:0;
123
+ border-bottom:1px solid color-mix(in srgb,var(--ds-border) 55%,transparent);
124
+ word-break:break-word;
125
+ }
126
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]) tbody tr:nth-child(even){background:color-mix(in srgb,var(--ds-surface-2) 45%,transparent)}
127
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]) tbody tr:hover{background:color-mix(in srgb,var(--ds-accent) 5%,transparent)}
128
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]) tbody tr:last-child td{border-bottom:0}
129
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]):has(thead th:nth-child(2):last-child) th:first-child,
130
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]):has(thead th:nth-child(2):last-child) td:first-child{width:30%}
131
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]):not([${STATUS_TABLE_ATTR}]):has(thead th:nth-child(3):last-child) th:first-child,
132
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]):not([${STATUS_TABLE_ATTR}]):has(thead th:nth-child(3):last-child) td:first-child{width:24%}
133
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]):not([${STATUS_TABLE_ATTR}]):has(thead th:nth-child(3):last-child) th:nth-child(2),
134
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]):not([${STATUS_TABLE_ATTR}]):has(thead th:nth-child(3):last-child) td:nth-child(2){width:22%}
135
+ [class*="_markdown_"] table:not([${SKIP_ATTR}])[${STATUS_TABLE_ATTR}] th:first-child,
136
+ [class*="_markdown_"] table:not([${SKIP_ATTR}])[${STATUS_TABLE_ATTR}] td:first-child{
137
+ width:68px;text-align:center;padding:7px 8px!important;font-size:11px;font-weight:700;
138
+ white-space:nowrap;vertical-align:middle;border-radius:7px;
139
+ }
140
+ [class*="_markdown_"] table:not([${SKIP_ATTR}])[${STATUS_TABLE_ATTR}] tbody tr[${ROW_ATTR}="success"] td:first-child{background:color-mix(in srgb,var(--ds-success) 12%,transparent);color:var(--ds-success)}
141
+ [class*="_markdown_"] table:not([${SKIP_ATTR}])[${STATUS_TABLE_ATTR}] tbody tr[${ROW_ATTR}="warn"] td:first-child{background:color-mix(in srgb,var(--ds-warn) 12%,transparent);color:var(--ds-warn)}
142
+ [class*="_markdown_"] table:not([${SKIP_ATTR}])[${STATUS_TABLE_ATTR}] tbody tr[${ROW_ATTR}="error"] td:first-child{background:color-mix(in srgb,var(--ds-error) 12%,transparent);color:var(--ds-error)}
143
+ [class*="_markdown_"] p[${LEAD_ATTR}="table"]{
144
+ border-left:2px solid var(--ds-accent);padding-left:10px;margin:10px 0 8px;
145
+ font-size:14px;color:var(--ds-text);
146
+ }
147
+ [class*="_markdown_"] p[${LEAD_ATTR}="table"] strong{color:var(--ds-accent-text)}
148
+ [class*="_markdown_"] h2+table,[class*="_markdown_"] h2+p,[class*="_markdown_"] h3+table,[class*="_markdown_"] h3+p{margin-top:8px}
149
+ [class*="_markdown_"] :not(pre)>code{
150
+ background:var(--ds-surface-2);border:1px solid var(--ds-border);border-radius:6px;
151
+ padding:.12em .38em;font-size:.9em;color:var(--ds-accent-text);overflow-wrap:anywhere;
152
+ }
153
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"]{
154
+ margin:16px 0;border:1px solid var(--ds-border);border-radius:12px;overflow:hidden;
155
+ background:var(--ds-code-bg);box-shadow:0 1px 2px rgba(0,0,0,.04);
156
+ }
157
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"] > [class*="_bannerWrap"]{
158
+ margin:0;padding:0;border:0;background:none;
159
+ }
160
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"] [data-code-block-banner]{
161
+ display:flex;align-items:center;justify-content:space-between;gap:8px;margin:0;border-radius:0;
162
+ padding:6px 12px;font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
163
+ color:var(--ds-muted);border-bottom:1px solid var(--ds-border);
164
+ background:linear-gradient(90deg,color-mix(in srgb,var(--ds-code-accent,var(--ds-accent)) 14%,transparent),transparent 72%);
165
+ }
166
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"] [data-code-block-banner] > [class*="_heading"]{
167
+ flex:1;min-width:0;justify-content:flex-start;text-align:left;
168
+ }
169
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"] [data-code-block-banner] > [class*="_actions"]{
170
+ margin-left:auto;flex:none;
171
+ }
172
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"] > [data-code-block-content]{padding:0}
173
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"] [data-code-block-banner]::before{
174
+ content:"";width:8px;height:8px;border-radius:50%;flex:none;
175
+ background:var(--ds-code-accent,var(--ds-accent));
176
+ box-shadow:0 0 0 3px color-mix(in srgb,var(--ds-code-accent,var(--ds-accent)) 18%,transparent);
177
+ }
178
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"] pre{margin:0;padding:14px 16px;font-size:13px;line-height:1.65;overflow:auto;color:var(--ds-code-text)}
179
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="js"]{--ds-code-accent:#eab308}
180
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="ts"],
181
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="tsx"]{--ds-code-accent:#3b82f6}
182
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="py"],
183
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="python"]{--ds-code-accent:#22c55e}
184
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="bash"],
185
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="sh"],
186
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="powershell"]{--ds-code-accent:#14b8a6}
187
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="json"]{--ds-code-accent:#a855f7}
188
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="html"],
189
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="css"]{--ds-code-accent:#f97316}
190
+ [class*="_markdown_"] .md-code-block[${CODE_ATTR}="1"][data-dsh-structured-lang="sql"]{--ds-code-accent:#06b6d4}
191
+ [class*="_markdown_"] blockquote{
192
+ margin:14px 0;padding:10px 14px;border-left:3px solid var(--ds-accent);
193
+ background:color-mix(in srgb,var(--ds-accent) 6%,transparent);border-radius:0 10px 10px 0;
194
+ }
195
+ [class*="_markdown_"] blockquote[${TONE_ATTR}="warn"]{--ds-accent:var(--ds-warn);--ds-accent-text:var(--ds-warn)}
196
+ [class*="_markdown_"] blockquote[${TONE_ATTR}="success"]{--ds-accent:var(--ds-success);--ds-accent-text:var(--ds-success)}
197
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]{
198
+ border:1px solid color-mix(in srgb,var(--ds-accent) 16%,var(--ds-border));
199
+ border-left:3px solid var(--ds-accent);
200
+ border-radius:12px;padding:14px 16px 15px;
201
+ background:linear-gradient(135deg,color-mix(in srgb,var(--ds-accent) 6%,var(--ds-surface)),var(--ds-surface) 60%);
202
+ box-shadow:0 1px 2px rgba(0,0,0,.04),0 12px 32px -24px color-mix(in srgb,var(--ds-accent) 32%,transparent);
203
+ }
204
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:first-child>strong:first-child{
205
+ display:flex;align-items:center;gap:9px;position:relative;
206
+ font-size:13px;line-height:1.1;letter-spacing:.06em;text-transform:uppercase;
207
+ color:var(--ds-accent-text);margin-bottom:8px;
208
+ }
209
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:first-child>strong:first-child::before{
210
+ content:"";display:inline-flex;flex:none;
211
+ width:26px;height:26px;border-radius:7px;
212
+ background:color-mix(in srgb,var(--ds-accent) 12%,transparent);
213
+ }
214
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:first-child>strong:first-child::after{
215
+ content:"";position:absolute;left:5.5px;top:5.5px;width:15px;height:15px;
216
+ background-color:var(--ds-accent);
217
+ -webkit-mask:var(--ds-card-icon) center/contain no-repeat;
218
+ mask:var(--ds-card-icon) center/contain no-repeat;
219
+ }
220
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:nth-child(2)>strong:first-child{
221
+ display:block;font-size:16px;font-weight:700;line-height:1.45;
222
+ letter-spacing:-.01em;color:var(--ds-text);margin-bottom:4px;
223
+ }
224
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p{font-size:15.5px;line-height:1.7;font-weight:450;color:var(--ds-text)}
225
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p strong{font-weight:700;color:var(--ds-text)}
226
+ [class*="_markdown_"] blockquote[${TONE_ATTR}]>p:first-child>strong:first-child{color:var(--ds-accent-text)}
227
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="conclusion"]{
228
+ --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 3l9 9-9 9-9-9 9-9z' fill='none' stroke='black' stroke-width='1.8' stroke-linejoin='round'/%3E%3C/svg%3E");
229
+ }
230
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="decision"]{
231
+ --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 7h6l4 10h6M4 17h6l4-10h6' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
232
+ }
233
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="metric"]{
234
+ --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 20V10M12 20V4M19 20v-7' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E");
235
+ }
236
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="risk"]{
237
+ --ds-accent:var(--ds-warn);--ds-accent-text:var(--ds-warn);
238
+ --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 4l9 16H3L12 4z' fill='none' stroke='black' stroke-width='1.8' stroke-linejoin='round'/%3E%3Cpath d='M12 10v4M12 17v.5' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E");
239
+ }
240
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="next"]{
241
+ --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 12h14M13 6l6 6-6 6' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
242
+ }
243
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="accept"]{
244
+ --ds-accent:var(--ds-success);--ds-accent-text:var(--ds-success);
245
+ --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 12.5l5 5L20 6.5' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
246
+ }
247
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="key"]{
248
+ --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 3l2.6 6.2 6.7.5-5.1 4.4 1.6 6.5L12 17.3 6.2 20.6l1.6-6.5L2.7 9.7l6.7-.5L12 3z' fill='none' stroke='black' stroke-width='1.6' stroke-linejoin='round'/%3E%3C/svg%3E");
249
+ }
250
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="statement"]{
251
+ --ds-card-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 3l9 9-9 9-9-9 9-9z' fill='none' stroke='black' stroke-width='1.8' stroke-linejoin='round'/%3E%3C/svg%3E");
252
+ }
253
+ [class*="_markdown_"] blockquote[${CARD_TYPE_ATTR}="statement"]>p:first-child>strong:first-child{
254
+ font-size:16px;line-height:1.45;letter-spacing:-.01em;text-transform:none;
255
+ color:var(--ds-text);margin-bottom:4px;
256
+ }
257
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:first-child{margin-top:0}
258
+ [class*="_markdown_"] blockquote[${CARD_ATTR}="1"]>p:last-child{margin-bottom:0}
259
+ [class*="_markdown_"] p[${STATUS_ATTR}],[class*="_markdown_"] li[${STATUS_ATTR}]{
260
+ border-left:3px solid var(--ds-accent);background:color-mix(in srgb,var(--ds-accent) 7%,transparent);
261
+ border-radius:0 6px 6px 0;padding:4px 10px;margin:.4em 0;
262
+ }
263
+ [class*="_markdown_"] p[${STATUS_ATTR}="success"],[class*="_markdown_"] li[${STATUS_ATTR}="success"]{--ds-accent:var(--ds-success)}
264
+ [class*="_markdown_"] p[${STATUS_ATTR}="warn"],[class*="_markdown_"] li[${STATUS_ATTR}="warn"]{--ds-accent:var(--ds-warn)}
265
+ [class*="_markdown_"] p[${STATUS_ATTR}="error"],[class*="_markdown_"] li[${STATUS_ATTR}="error"]{--ds-accent:var(--ds-error)}
266
+ @media (max-width:720px){
267
+ [class*="_markdown_"]{font-size:14.5px}
268
+ [class*="_markdown_"] h1{font-size:22px}
269
+ [class*="_markdown_"] h2{font-size:18px}
270
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]){font-size:13px}
271
+ [class*="_markdown_"] table:not([${SKIP_ATTR}]) thead th,[class*="_markdown_"] table:not([${SKIP_ATTR}]) tbody td{padding:7px 9px}
272
+ }
273
+ @media (prefers-reduced-motion:reduce){
274
+ [class*="_markdown_"] *{transition:none!important;animation:none!important}
275
+ }
276
+ `
277
+
278
+ function iconFor(text) {
279
+ for (const [pattern, icon] of ICONS) if (pattern.test(text)) return icon
280
+ return null
281
+ }
282
+
283
+ function firstParagraph(block) {
284
+ return [...(block.children ?? [])].find((element) => element.tagName === 'P') ?? null
285
+ }
286
+
287
+ function labelOf(block) {
288
+ const first = firstParagraph(block)
289
+ const strong = first?.firstElementChild
290
+ if (!strong || strong.tagName !== 'STRONG') return null
291
+ return (strong.textContent ?? '').replace(/[::]\s*$/u, '').trim()
292
+ }
293
+
294
+ function cardTypeFor(block) {
295
+ const label = labelOf(block)
296
+ return label === null ? null : (CARD_TYPES.get(label) ?? null)
297
+ }
298
+
299
+ function isLabelLike(text) {
300
+ return [...CARD_TYPES.keys()].some((label) => text.startsWith(label) && text !== label)
301
+ }
302
+
303
+ function inGenui(element) {
304
+ try {
305
+ return Boolean(element.closest?.('[data-genui], [class*="genui-root"]'))
306
+ } catch {
307
+ return false
308
+ }
309
+ }
310
+
311
+ function inDiff(element) {
312
+ try {
313
+ return Boolean(element.closest?.('[data-diff], .md-diff, [class*="_diff_"], [class*="diff-view"], [class*="diff-block"], [data-testid*="diff"]'))
314
+ } catch {
315
+ return false
316
+ }
317
+ }
318
+
319
+ function bannerLanguage(code) {
320
+ const banner = code.querySelector?.('[data-code-block-banner]')
321
+ const heading = banner?.querySelector?.('[class*="_heading"]')
322
+ const raw = ((heading?.textContent ?? banner?.textContent) ?? '').trim().split(/\s+/)[0] ?? ''
323
+ return raw.toLowerCase().replace(/[^a-z0-9+#]/g, '')
324
+ }
325
+
326
+ function isDiffCode(code) {
327
+ const lang = bannerLanguage(code)
328
+ return lang === 'diff' || lang === 'patch'
329
+ }
330
+
331
+ function statusFor(text) {
332
+ if (/^✅/.test(text)) return 'success'
333
+ if (/^⚠/.test(text)) return 'warn'
334
+ if (/^❌/.test(text)) return 'error'
335
+ return null
336
+ }
337
+
338
+ function isStatementCard(block) {
339
+ if (inDiff(block)) return false
340
+ const markdown = block.closest?.('[class*="_markdown_"]')
341
+ if (!markdown) return false
342
+ const first = [...markdown.querySelectorAll('blockquote')].find((element) => !inGenui(element) && !inDiff(element))
343
+ if (first !== block) return false
344
+ const paragraphs = [...block.children].filter((element) => element.tagName === 'P')
345
+ const firstParagraph = paragraphs[0]
346
+ const strong = firstParagraph?.firstElementChild
347
+ if (!strong || strong.tagName !== 'STRONG') return false
348
+ let previous = strong.previousSibling
349
+ while (previous) {
350
+ if (previous.nodeType === 1) return false
351
+ if (previous.nodeType === 3 && (previous.textContent ?? '').trim() !== '') return false
352
+ previous = previous.previousSibling
353
+ }
354
+ const title = (strong.textContent ?? '').trim()
355
+ if (title.length === 0 || isLabelLike(title)) return false
356
+ let rest = ''
357
+ for (let node = strong.nextSibling; node; node = node.nextSibling) rest += node.textContent ?? ''
358
+ const secondBody = paragraphs.slice(1).some((element) => (element.textContent ?? '').trim().length > 0)
359
+ return rest.trim().length > 0 || secondBody
360
+ }
361
+
362
+ function decorate(root) {
363
+ for (const heading of root.querySelectorAll('[class*="_markdown_"] h2, [class*="_markdown_"] h3')) {
364
+ if (inGenui(heading) || inDiff(heading)) continue
365
+ const icon = iconFor((heading.textContent ?? '').trim())
366
+ if (icon === null) heading.removeAttribute(ICON_ATTR)
367
+ else heading.setAttribute(ICON_ATTR, icon)
368
+ }
369
+
370
+ for (const block of root.querySelectorAll('blockquote')) {
371
+ if (inGenui(block) || inDiff(block)) continue
372
+ let type = cardTypeFor(block)
373
+ if (type === null && isStatementCard(block)) type = 'statement'
374
+ if (type === null) {
375
+ block.removeAttribute(CARD_ATTR)
376
+ block.removeAttribute(CARD_TYPE_ATTR)
377
+ block.removeAttribute(LEAD_ATTR)
378
+ block.removeAttribute(TONE_ATTR)
379
+ continue
380
+ }
381
+ block.setAttribute(CARD_ATTR, '1')
382
+ block.setAttribute(CARD_TYPE_ATTR, type)
383
+ block.removeAttribute(LEAD_ATTR)
384
+ block.setAttribute(TONE_ATTR, type === 'risk' ? 'warn' : type === 'accept' ? 'success' : 'info')
385
+ }
386
+
387
+ for (const element of root.querySelectorAll('[class*="_markdown_"] p, [class*="_markdown_"] li')) {
388
+ if (inGenui(element) || inDiff(element) || element.closest('blockquote, table')) {
389
+ element.removeAttribute(STATUS_ATTR)
390
+ continue
391
+ }
392
+ const status = statusFor((element.textContent ?? '').trimStart())
393
+ if (status === null) element.removeAttribute(STATUS_ATTR)
394
+ else element.setAttribute(STATUS_ATTR, status)
395
+ }
396
+
397
+ for (const row of root.querySelectorAll('[class*="_markdown_"] table tr')) {
398
+ if (inGenui(row) || inDiff(row) || row.closest('thead') || row.closest(`[${SKIP_ATTR}="table"]`)) {
399
+ row.removeAttribute(ROW_ATTR)
400
+ continue
401
+ }
402
+ const status = statusFor((row.textContent ?? '').trimStart())
403
+ if (status === null) row.removeAttribute(ROW_ATTR)
404
+ else row.setAttribute(ROW_ATTR, status)
405
+ }
406
+
407
+ for (const table of root.querySelectorAll('[class*="_markdown_"] table')) {
408
+ if (inGenui(table)) {
409
+ table.removeAttribute(SKIP_ATTR)
410
+ table.removeAttribute(STATUS_TABLE_ATTR)
411
+ continue
412
+ }
413
+ if (inDiff(table)) {
414
+ table.setAttribute(SKIP_ATTR, 'table')
415
+ table.removeAttribute(STATUS_TABLE_ATTR)
416
+ continue
417
+ }
418
+ table.removeAttribute(SKIP_ATTR)
419
+ const rows = [...table.querySelectorAll('tbody tr')]
420
+ const statusTable =
421
+ rows.length > 0 &&
422
+ rows.every((row) => statusFor((row.cells?.[0]?.textContent ?? '').trimStart()) !== null)
423
+ if (statusTable) table.setAttribute(STATUS_TABLE_ATTR, '1')
424
+ else table.removeAttribute(STATUS_TABLE_ATTR)
425
+ }
426
+
427
+ for (const paragraph of root.querySelectorAll('[class*="_markdown_"] p')) {
428
+ if (inGenui(paragraph) || inDiff(paragraph) || paragraph.closest('blockquote, table')) {
429
+ paragraph.removeAttribute(LEAD_ATTR)
430
+ continue
431
+ }
432
+ if (/^表格结论/.test((paragraph.textContent ?? '').trimStart())) paragraph.setAttribute(LEAD_ATTR, 'table')
433
+ else paragraph.removeAttribute(LEAD_ATTR)
434
+ }
435
+
436
+ for (const code of root.querySelectorAll('[class*="_markdown_"] .md-code-block')) {
437
+ if (inGenui(code)) {
438
+ code.removeAttribute(LANG_ATTR)
439
+ code.removeAttribute(CODE_ATTR)
440
+ code.removeAttribute(SKIP_ATTR)
441
+ continue
442
+ }
443
+ if (inDiff(code) || isDiffCode(code)) {
444
+ code.removeAttribute(LANG_ATTR)
445
+ code.removeAttribute(CODE_ATTR)
446
+ code.setAttribute(SKIP_ATTR, 'code')
447
+ continue
448
+ }
449
+ code.removeAttribute(SKIP_ATTR)
450
+ code.setAttribute(CODE_ATTR, '1')
451
+ const lang = bannerLanguage(code)
452
+ if (lang.length === 0) code.removeAttribute(LANG_ATTR)
453
+ else code.setAttribute(LANG_ATTR, lang)
454
+ }
455
+ }
456
+
457
+ let active = false
458
+ let disposeLayer = null
459
+ let currentTheme = 'deepseek'
460
+
461
+ const applyTheme = (theme) => {
462
+ const safe = THEMES.includes(theme) ? theme : 'deepseek'
463
+ currentTheme = safe
464
+ if (typeof document === 'undefined') return
465
+ document.body?.setAttribute(THEME_ATTR, safe)
466
+ }
467
+
468
+ const enable = () => {
469
+ if (active) return
470
+ if (typeof document === 'undefined' || typeof MutationObserver === 'undefined') return
471
+ if (document.body === null) return
472
+ active = true
473
+
474
+ document.getElementById(LEGACY_STYLE_ID)?.remove()
475
+ document.getElementById(STYLE_ID)?.remove()
476
+ const style = document.createElement('style')
477
+ style.id = STYLE_ID
478
+ style.textContent = CSS
479
+ document.head.appendChild(style)
480
+ document.documentElement.setAttribute(ROOT_ATTR, 'active')
481
+ document.body.setAttribute(THEME_ATTR, currentTheme)
482
+ try {
483
+ console.info('[dsh-structured] visual layer active')
484
+ } catch {
485
+ // console 不可用时忽略。
486
+ }
487
+
488
+ let disposed = false
489
+ let scheduled = false
490
+ let pending = null
491
+ const run = () => {
492
+ if (disposed) return
493
+ scheduled = false
494
+ pending = null
495
+ decorate(document)
496
+ }
497
+ const schedule = () => {
498
+ if (disposed || scheduled) return
499
+ scheduled = true
500
+ if (typeof requestAnimationFrame === 'function') pending = requestAnimationFrame(run)
501
+ else pending = setTimeout(run, 16)
502
+ }
503
+ const observer = new MutationObserver(schedule)
504
+ observer.observe(document.body, { childList: true, subtree: true, characterData: true })
505
+ run()
506
+
507
+ disposeLayer = () => {
508
+ if (disposed) return
509
+ disposed = true
510
+ active = false
511
+ disposeLayer = null
512
+ observer.disconnect()
513
+ if (pending !== null) {
514
+ if (typeof cancelAnimationFrame === 'function') cancelAnimationFrame(pending)
515
+ else clearTimeout(pending)
516
+ }
517
+ style.remove()
518
+ document.documentElement.removeAttribute(ROOT_ATTR)
519
+ document.body?.removeAttribute(THEME_ATTR)
520
+ for (const attr of [CARD_ATTR, CARD_TYPE_ATTR, ICON_ATTR, TONE_ATTR, LANG_ATTR, CODE_ATTR, SKIP_ATTR, STATUS_ATTR, STATUS_TABLE_ATTR, LEAD_ATTR, ROW_ATTR]) {
521
+ for (const element of document.querySelectorAll('[' + attr + ']')) element.removeAttribute(attr)
522
+ }
523
+ try {
524
+ console.info('[dsh-structured] visual layer disposed')
525
+ } catch {
526
+ // console 不可用时忽略。
527
+ }
528
+ }
529
+ }
530
+
531
+ const disable = () => {
532
+ const layer = disposeLayer
533
+ disposeLayer = null
534
+ if (typeof layer === 'function') layer()
535
+ if (typeof document === 'undefined') return
536
+ document.getElementById(STYLE_ID)?.remove()
537
+ document.documentElement.removeAttribute(ROOT_ATTR)
538
+ document.body?.removeAttribute(THEME_ATTR)
539
+ for (const attr of [CARD_ATTR, CARD_TYPE_ATTR, ICON_ATTR, TONE_ATTR, LANG_ATTR, CODE_ATTR, SKIP_ATTR, STATUS_ATTR, STATUS_TABLE_ATTR, LEAD_ATTR, ROW_ATTR]) {
540
+ for (const element of document.querySelectorAll('[' + attr + ']')) element.removeAttribute(attr)
541
+ }
542
+ }
543
+
544
+ const applyVisual = (on) => {
545
+ if (on) enable()
546
+ else disable()
547
+ }
548
+
549
+ return {
550
+ inject: [],
551
+ __test: { inDiff, isDiffCode, decorate },
552
+ apply(ctx) {
553
+ // 默认主题/视觉先启用;随后由 profile Config 的 theme/visual 覆盖。
554
+ enable()
555
+ try {
556
+ ctx?.inject?.(['configForms'], (child) => {
557
+ try {
558
+ const form = child.configForms?.get('dsh-structured')
559
+ if (!form || typeof form.getSnapshot !== 'function') return
560
+ const readValue = (value) => (value && typeof value.get === 'function' ? value.get() : value)
561
+ const read = () => {
562
+ try {
563
+ const value = form.getSnapshot()?.value ?? {}
564
+ applyTheme(readValue(value.theme))
565
+ applyVisual(readValue(value.visual) !== false)
566
+ } catch {
567
+ // 读取失败时保持当前状态,不打断回复。
568
+ }
569
+ }
570
+ read()
571
+ if (typeof form.subscribe === 'function') {
572
+ child.effect?.(() => form.subscribe(read), 'dsh-structured: theme/visual preferences')
573
+ }
574
+ } catch {
575
+ // configForms 不可用时保持默认开启。
576
+ }
577
+ })
578
+ } catch {
579
+ // 无 ctx.inject 的宿主保持默认开启。
580
+ }
581
+ try {
582
+ ctx?.effect?.(() => disable, 'dsh-structured: visual layer')
583
+ } catch {
584
+ // 没有 effect 释放器时,视觉层保持到页面卸载。
585
+ }
586
+ return disable
587
+ },
588
+ }
589
+ },
590
+ })