dsh-session-superagent 0.8.15 → 0.8.16
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 +41 -0
- package/README.md +1 -1
- package/client.js +3 -2
- package/package.json +5 -3
- package/scripts/render-preview.mjs +213 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
格式参照 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
|
4
4
|
|
|
5
|
+
## [0.8.16] - 2026-11-02 —— 第一次在真浏览器里看面板,抓到一个对比度问题
|
|
6
|
+
|
|
7
|
+
宿主的 web 界面有鉴权(`dsh web authentication required`),ego 浏览器过不去,所以搭了个
|
|
8
|
+
**静态预览**:`scripts/render-preview.mjs` 用极简假 React 把真实 `client.js` 的组件渲染成 DOM,
|
|
9
|
+
内联移植来的官方 CSS,六个场景(总览/判定/触发/设置/术语表/两种 Toast)一起看。
|
|
10
|
+
|
|
11
|
+
### 修复(真浏览器里发现的)
|
|
12
|
+
- **错误 Toast 白字看不清**:0.8.12 给错误态加了「浅红底」,而官方 toast 是深底白字——
|
|
13
|
+
白字压在浅粉上对比度极低。现在保留官方深底,改用**左侧红条**标记错误。
|
|
14
|
+
|
|
15
|
+
### 验证(在 Chromium 里读 computed style,不是猜)
|
|
16
|
+
```
|
|
17
|
+
模态框 position:fixed; inset:0,宽度 380 居中 ← 「弹层掉到页面底部」的 bug 确认已修
|
|
18
|
+
Toast position:fixed; top:40px; z-index:1100; 299×46 深底白字
|
|
19
|
+
错误态 背景 #24292f / 白字 / 左边框 #f85149
|
|
20
|
+
标签 success 绿底绿字、warning 琥珀底琥珀字(tone 与 CSS 对得上)
|
|
21
|
+
列表行 高 102–122px(三线结构:标题 / meta / 操作)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
顺带搞清了 `dt-tag` 的颜色依赖 `--dsw-alias-state-{success,warn,error,info}-primary` 这几个 token
|
|
25
|
+
(之前预览里缺,标签看着是裸文字——这和审核第二轮发现的「状态色全丢」是同一个根因)。
|
|
26
|
+
|
|
27
|
+
### 工具
|
|
28
|
+
- `pnpm run preview` 生成 `preview-panel.html`(已加进 .gitignore,不进仓库/不发包)。
|
|
29
|
+
|
|
30
|
+
### 测试
|
|
31
|
+
- 合计 **81 例**(本次是样式与渲染层修复,靠真浏览器核对)。
|
|
32
|
+
|
|
5
33
|
## [0.8.15] - 2026-11-02 —— sync-profile 支持修复断链(事故 1 的第二种形态)
|
|
6
34
|
|
|
7
35
|
### 修什么
|
|
@@ -24,6 +52,19 @@ pnpm run sync:profile:check # 只检查:有缺失/断链/陈旧就 exit 1
|
|
|
24
52
|
- 新增用例:`sync-profile` 的 `TARGETS` 必须覆盖 `lib/` 下所有 js 文件 ——
|
|
25
53
|
防止「新增 lib 文件忘了登记」再次导致插件不挂载。
|
|
26
54
|
|
|
55
|
+
### 发布
|
|
56
|
+
- 已发 `dsh-session-superagent@0.8.15`(registry `latest = 0.8.15`)。
|
|
57
|
+
- tarball 直装验证:`version 0.8.15`、**routes 6**、`client.js` 语法通过、包内 `scripts/` 三个脚本齐全。
|
|
58
|
+
|
|
59
|
+
### 发布前快照(每次发版都跑这一套)
|
|
60
|
+
```
|
|
61
|
+
node --test 81/81
|
|
62
|
+
node --check client.js lib/*.js scripts/*.mjs 全过
|
|
63
|
+
sync-profile --check 缺失 0 / 断链 0 / 陈旧 0,相对 import 全可解析
|
|
64
|
+
smoke-observe 两场景 12 项 OK
|
|
65
|
+
npm pack --dry-run 13 文件 / 75.4 kB
|
|
66
|
+
```
|
|
67
|
+
|
|
27
68
|
### 测试
|
|
28
69
|
- 合计 **81 例**。
|
|
29
70
|
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# dsh-session-superagent
|
|
2
2
|
|
|
3
3
|
[](LICENSE)
|
|
4
|
-
[](https://github.com/MYCF711/dsh-session-superagent/releases)
|
|
5
5
|
[](https://www.npmjs.com/package/dsh-session-superagent)
|
|
6
6
|
[](https://github.com/topics/dsh-plugin)
|
|
7
7
|
|
package/client.js
CHANGED
|
@@ -14,7 +14,7 @@ window.__ModuleLoader__.load({
|
|
|
14
14
|
const { useCallback, useEffect, useRef, useState } = React
|
|
15
15
|
|
|
16
16
|
// 客户端版本探针:每次模块加载写一次,用来确认浏览器里跑的是哪一版
|
|
17
|
-
const CLIENT_VERSION = '0.8.
|
|
17
|
+
const CLIENT_VERSION = '0.8.16'
|
|
18
18
|
const BASE = '/session-superagent'
|
|
19
19
|
const ENDPOINT = BASE + '/state'
|
|
20
20
|
const POLL_MS = 5000
|
|
@@ -614,8 +614,9 @@ window.__ModuleLoader__.load({
|
|
|
614
614
|
className: cx('dt-toast', item.tone === 'error' && 'dt-danger'),
|
|
615
615
|
role: 'status',
|
|
616
616
|
'aria-live': 'polite',
|
|
617
|
+
// 官方 toast 是深底白字;错误态不要改成浅红底(白字会看不清),改用左侧红条标记
|
|
617
618
|
style: item.tone === 'error'
|
|
618
|
-
? { '--dt-toast-hold': '3600ms',
|
|
619
|
+
? { '--dt-toast-hold': '3600ms', borderLeft: '3px solid #f85149' }
|
|
619
620
|
: { '--dt-toast-hold': '3600ms' },
|
|
620
621
|
}, item.text)
|
|
621
622
|
return [view, notify]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-session-superagent",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.16",
|
|
4
4
|
"description": "Session-level superagent for DeepSeek Harness: watch a GitHub repository and wake a session when something needs doing. Credentials never reach the model.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"client.js",
|
|
21
21
|
"scripts/preview-orbit.mjs",
|
|
22
22
|
"scripts/port-official-ui.mjs",
|
|
23
|
-
"scripts/sync-profile.mjs"
|
|
23
|
+
"scripts/sync-profile.mjs",
|
|
24
|
+
"scripts/render-preview.mjs"
|
|
24
25
|
],
|
|
25
26
|
"engines": {
|
|
26
27
|
"node": ">=22.19.0",
|
|
@@ -33,7 +34,8 @@
|
|
|
33
34
|
"sync:profile": "node scripts/sync-profile.mjs",
|
|
34
35
|
"smoke:observe": "node scripts/smoke-observe.mjs",
|
|
35
36
|
"sync:profile:check": "node scripts/sync-profile.mjs --check",
|
|
36
|
-
"sync:profile:repair": "node scripts/sync-profile.mjs --repair"
|
|
37
|
+
"sync:profile:repair": "node scripts/sync-profile.mjs --repair",
|
|
38
|
+
"preview": "node scripts/render-preview.mjs"
|
|
37
39
|
},
|
|
38
40
|
"keywords": [
|
|
39
41
|
"dsh",
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// 把「主理鱼」面板渲染成静态 HTML,用于在真浏览器里看布局(不需要宿主、不需要 React)。
|
|
2
|
+
//
|
|
3
|
+
// 为什么:client.js 是给宿主用的模块(`window.__ModuleLoader__.load` + `require('react')`),
|
|
4
|
+
// 组件树用 h() 构造。这里提供一个极简的假 React,把组件渲染成虚拟节点树,
|
|
5
|
+
// 再序列化成 HTML,并内联移植来的官方 CSS —— 于是可以在浏览器里真实地看布局。
|
|
6
|
+
//
|
|
7
|
+
// 用法: node scripts/render-preview.mjs [输出文件]
|
|
8
|
+
// node scripts/render-preview.mjs preview-panel.html
|
|
9
|
+
import { readFileSync, writeFileSync } from 'node:fs'
|
|
10
|
+
import { dirname, join } from 'node:path'
|
|
11
|
+
import { fileURLToPath } from 'node:url'
|
|
12
|
+
import vm from 'node:vm'
|
|
13
|
+
|
|
14
|
+
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
15
|
+
const OUT = process.argv[2] || join(ROOT, 'preview-panel.html')
|
|
16
|
+
|
|
17
|
+
// ── 假 React:只需要 createElement/useState/useEffect/useCallback/useRef/memo/Fragment ──
|
|
18
|
+
let hookQueue = []
|
|
19
|
+
let hookIndex = 0
|
|
20
|
+
const React = {
|
|
21
|
+
Fragment: Symbol('Fragment'),
|
|
22
|
+
createElement(type, props, ...children) {
|
|
23
|
+
return { type, props: { ...(props || {}), children: children.flat(Infinity).filter((c) => c !== null && c !== undefined && c !== false) } }
|
|
24
|
+
},
|
|
25
|
+
memo: (fn) => fn,
|
|
26
|
+
useState(init) {
|
|
27
|
+
const value = hookIndex < hookQueue.length ? hookQueue[hookIndex] : (typeof init === 'function' ? init() : init)
|
|
28
|
+
hookIndex += 1
|
|
29
|
+
return [value, () => {}]
|
|
30
|
+
},
|
|
31
|
+
useEffect() {},
|
|
32
|
+
useCallback: (fn) => fn,
|
|
33
|
+
useRef: (value) => ({ current: value }),
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 假宿主:只收集注册,不真的挂任何东西
|
|
37
|
+
const registrations = []
|
|
38
|
+
const sandbox = {
|
|
39
|
+
window: {
|
|
40
|
+
__ModuleLoader__: { load: (mod) => registrations.push(mod) },
|
|
41
|
+
innerWidth: 1280, innerHeight: 900,
|
|
42
|
+
addEventListener() {}, removeEventListener() {},
|
|
43
|
+
},
|
|
44
|
+
document: {
|
|
45
|
+
getElementById: () => null,
|
|
46
|
+
createElement: () => ({ id: '', textContent: '' }),
|
|
47
|
+
head: { appendChild() {} },
|
|
48
|
+
addEventListener() {}, removeEventListener() {},
|
|
49
|
+
},
|
|
50
|
+
fetch: () => Promise.resolve({ ok: true, status: 200, text: () => Promise.resolve('{}') }),
|
|
51
|
+
setInterval: () => 0, clearInterval: () => {}, setTimeout: () => 0, clearTimeout: () => {},
|
|
52
|
+
console,
|
|
53
|
+
}
|
|
54
|
+
vm.createContext(sandbox)
|
|
55
|
+
vm.runInContext(readFileSync(join(ROOT, 'client.js'), 'utf8'), sandbox)
|
|
56
|
+
|
|
57
|
+
const mod = registrations[0]
|
|
58
|
+
if (!mod) throw new Error('client.js 没有注册任何模块')
|
|
59
|
+
const slots = new Map()
|
|
60
|
+
const ctx = {
|
|
61
|
+
slots: {
|
|
62
|
+
inject: (_name, fn) => fn(),
|
|
63
|
+
register: (opts, Component) => { slots.set(opts.id, Component); return () => {} },
|
|
64
|
+
},
|
|
65
|
+
effect: (fn) => fn(),
|
|
66
|
+
}
|
|
67
|
+
mod.factory((name) => {
|
|
68
|
+
if (name === 'react') return React
|
|
69
|
+
throw new Error('unexpected require: ' + name)
|
|
70
|
+
}).apply(ctx)
|
|
71
|
+
|
|
72
|
+
const Desk = slots.get('session-superagent-desk')
|
|
73
|
+
if (!Desk) throw new Error('没有注册 session-superagent-desk,已注册:' + [...slots.keys()].join(','))
|
|
74
|
+
|
|
75
|
+
// ── 样本快照:覆盖各种状态(正常/暂停/异常/仅观察/隔离/退避/过滤记录/历史)──
|
|
76
|
+
const now = Date.now()
|
|
77
|
+
const iso = (ms) => new Date(now - ms).toISOString()
|
|
78
|
+
const snapshot = {
|
|
79
|
+
loading: false, error: null,
|
|
80
|
+
body: {
|
|
81
|
+
sessionTitle: 'README 收尾与发布',
|
|
82
|
+
sessionGoal: null,
|
|
83
|
+
gh: { ok: true, version: 'gh version 2.101.0' },
|
|
84
|
+
credentials: { token: 'configured', sshKeyPath: 'none' },
|
|
85
|
+
watchers: [
|
|
86
|
+
{
|
|
87
|
+
id: 'MYCF711/dsh-session-superagent#s1', repo: 'MYCF711/dsh-session-superagent', sessionId: 's1',
|
|
88
|
+
intervalSec: 60, enabled: true, observe: false, title: 'README 收尾与发布', note: '把 0.8.x 的文档补齐',
|
|
89
|
+
lastPoll: iso(15000), lastWake: iso(300000), lastWakeCount: 3, lastAbsorbed: 1,
|
|
90
|
+
filters: { labels: ['ai-fix'], ignoreLabels: [], skipBots: true, skipDrafts: true, skipClosed: true, onlyAssigned: false, assignee: null },
|
|
91
|
+
activeHours: '09:00-23:00', isolated: true, workdir: 'E:/temp/iso', lastWorktree: 'E:/temp/iso/.dsh-zhuli/a-b',
|
|
92
|
+
history: [
|
|
93
|
+
{ at: iso(300000), numbers: [12], ok: true, reason: null },
|
|
94
|
+
{ at: iso(600000), numbers: [11], ok: false, reason: 'observe' },
|
|
95
|
+
],
|
|
96
|
+
filteredLog: [{ number: 7, title: '机器人提的依赖升级', reason: '机器人', at: iso(120000) }],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: 'MYCF711/dsh-GOkifu-compact#s2', repo: 'MYCF711/dsh-GOkifu-compact', sessionId: 's2',
|
|
100
|
+
intervalSec: 120, enabled: false, observe: true, title: '联调会话',
|
|
101
|
+
lastPoll: iso(900000), lastDeferred: '仅观察:本轮本会唤醒 2 条(#4 #3)',
|
|
102
|
+
filters: { labels: [], ignoreLabels: [], skipBots: true, skipDrafts: true, skipClosed: true, onlyAssigned: false, assignee: null },
|
|
103
|
+
history: [], filteredLog: [],
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: 'a/b#s3', repo: 'someone/broken-repo', sessionId: 's3', intervalSec: 30, enabled: true,
|
|
107
|
+
title: '出错示例', lastPoll: iso(5000), lastError: 'gh: Not Found (HTTP 404)(仓库不存在或没权限:检查 owner/name)',
|
|
108
|
+
consecutiveFailures: 2, nextAt: new Date(now + 180000).toISOString(),
|
|
109
|
+
filters: { labels: [], ignoreLabels: [], skipBots: true, skipDrafts: true, skipClosed: true, onlyAssigned: false, assignee: null },
|
|
110
|
+
history: [], filteredLog: [],
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ── 渲染:把元素树序列化成 HTML ───────────────────────────────────────────────
|
|
117
|
+
function escapeHtml(text) {
|
|
118
|
+
return String(text).replace(/[&<>]/g, (c) => ({ '&': '&', '<': '<', '>': '>' }[c]))
|
|
119
|
+
}
|
|
120
|
+
function styleToCss(style) {
|
|
121
|
+
if (!style) return ''
|
|
122
|
+
return Object.entries(style).map(([k, v]) => {
|
|
123
|
+
const prop = k.startsWith('--') ? k : k.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase())
|
|
124
|
+
return prop + ':' + v
|
|
125
|
+
}).join(';')
|
|
126
|
+
}
|
|
127
|
+
function attr(name, value) {
|
|
128
|
+
if (value === undefined || value === null || value === false || value === true) {
|
|
129
|
+
return value === true ? ' ' + name : ''
|
|
130
|
+
}
|
|
131
|
+
return ' ' + name + '="' + escapeHtml(value) + '"'
|
|
132
|
+
}
|
|
133
|
+
function render(node) {
|
|
134
|
+
if (node === null || node === undefined || node === false) return ''
|
|
135
|
+
if (typeof node === 'string' || typeof node === 'number') return escapeHtml(node)
|
|
136
|
+
if (Array.isArray(node)) return node.map(render).join('')
|
|
137
|
+
if (typeof node !== 'object') return ''
|
|
138
|
+
const { type, props = {} } = node
|
|
139
|
+
if (typeof type === 'function') return render(type(props))
|
|
140
|
+
if (type === React.Fragment) return (props.children || []).map(render).join('')
|
|
141
|
+
const voidTags = new Set(['input', 'img', 'br', 'hr', 'meta', 'link'])
|
|
142
|
+
const tag = String(type)
|
|
143
|
+
const parts = []
|
|
144
|
+
if (props.className) parts.push(attr('class', props.className))
|
|
145
|
+
if (props.style) parts.push(attr('style', styleToCss(props.style)))
|
|
146
|
+
for (const key of ['data-tone', 'data-state', 'data-open', 'role', 'title', 'aria-label', 'aria-selected', 'aria-pressed', 'aria-modal', 'type', 'placeholder', 'value', 'disabled']) {
|
|
147
|
+
if (props[key] !== undefined && props[key] !== false) parts.push(attr(key, props[key]))
|
|
148
|
+
}
|
|
149
|
+
if (keyIsVoid(tag)) return '<' + tag + parts.join('') + '>'
|
|
150
|
+
return '<' + tag + parts.join('') + '>' + (props.children || []).map(render).join('') + '</' + tag + '>'
|
|
151
|
+
}
|
|
152
|
+
function keyIsVoid(tag) {
|
|
153
|
+
return ['input', 'img', 'br', 'hr', 'meta', 'link'].includes(tag)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 取移植来的 CSS
|
|
157
|
+
const clientSrc = readFileSync(join(ROOT, 'client.js'), 'utf8')
|
|
158
|
+
const marker = 'const PORTED_CSS = "'
|
|
159
|
+
const head = clientSrc.indexOf(marker) + marker.length
|
|
160
|
+
let i = head
|
|
161
|
+
while (i < clientSrc.length && clientSrc[i] !== '"') i += clientSrc[i] === '\\' ? 2 : 1
|
|
162
|
+
const PORTED_CSS = JSON.parse('"' + clientSrc.slice(head, i) + '"')
|
|
163
|
+
|
|
164
|
+
const scenarios = [
|
|
165
|
+
{ name: '总览', hooks: [snapshot, 'overview', null, { repo: '', intervalSec: '60', goal: '', labels: '', onlyAssigned: false, activeHours: '', isolated: false, workdir: '' }, '', '', false, null, null, {}, '', false] },
|
|
166
|
+
{ name: '判定', hooks: [snapshot, 'judge', null, { repo: '', intervalSec: '60', goal: '', labels: '', onlyAssigned: false, activeHours: '', isolated: false, workdir: '' }, '', '', false, null, null, {}, '', false] },
|
|
167
|
+
{ name: '触发', hooks: [snapshot, 'trigger', null, { repo: '', intervalSec: '60', goal: '', labels: '', onlyAssigned: false, activeHours: '', isolated: false, workdir: '' }, '', '', false, null, null, {}, '', false] },
|
|
168
|
+
{ name: '设置', hooks: [snapshot, 'settings', null, { repo: '', intervalSec: '60', goal: '', labels: '', onlyAssigned: false, activeHours: '', isolated: false, workdir: '' }, '', '', false, null, null, {}, '', false] },
|
|
169
|
+
{ name: 'Toast(保存成功)', hooks: [snapshot, 'overview', null, { repo: '', intervalSec: '60', goal: '', labels: '', onlyAssigned: false, activeHours: '', isolated: false, workdir: '' }, '', '', false, { id: 't1', text: '已保存:MYCF711/dsh-session-superagent', tone: 'info' }, null, {}, '', false] },
|
|
170
|
+
{ name: 'Toast(错误)', hooks: [snapshot, 'overview', null, { repo: '', intervalSec: '60', goal: '', labels: '', onlyAssigned: false, activeHours: '', isolated: false, workdir: '' }, '', '', false, { id: 't2', text: '保存失败:gh: Not Found (HTTP 404)', tone: 'error' }, null, {}, '', false] },
|
|
171
|
+
{ name: '术语表', hooks: [snapshot, 'overview', null, { repo: '', intervalSec: '60', goal: '', labels: '', onlyAssigned: false, activeHours: '', isolated: false, workdir: '' }, '', '', false, null, null, {}, '', true] },
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
const sections = scenarios.map((scenario) => {
|
|
175
|
+
hookQueue = scenario.hooks
|
|
176
|
+
hookIndex = 0
|
|
177
|
+
const tree = Desk({ sessionId: 'preview-session' })
|
|
178
|
+
return '<h2 class="preview-title">' + scenario.name + '</h2>\n<div class="preview-frame">' + render(tree) + '</div>'
|
|
179
|
+
}).join('\n')
|
|
180
|
+
|
|
181
|
+
// 极简 token 兜底:宿主里这些由 --dsw-alias-* 提供,预览里给一组浅色近似值
|
|
182
|
+
const tokens = `:root{
|
|
183
|
+
--dsw-alias-label-primary:#1f2328; --dsw-alias-label-secondary:#59636e; --dsw-alias-label-tertiary:#818b98;
|
|
184
|
+
--dsw-alias-bg-layer-1:#ffffff; --dsw-alias-bg-layer-2:#f6f8fa; --dsw-alias-bg-layer-3:#eef1f4;
|
|
185
|
+
--dsw-alias-border-default:#d8dee4; --dsw-alias-border-muted:#e6eaef;
|
|
186
|
+
--dsw-alias-accent-primary:#0969da; --dsw-alias-success-500:#1a7f37; --dsw-alias-warning-500:#9a6700;
|
|
187
|
+
--dsw-alias-error-500:#cf222e; --dsw-static-neutral-bluish-00:#ffffff;
|
|
188
|
+
--dsw-shadow-lv3:0 8px 24px rgba(31,35,40,.12);
|
|
189
|
+
/* tag / 状态点真正引用的 token(官方 CSS 用 --dsw-alias-state-*) */
|
|
190
|
+
--dsw-alias-state-success-primary:#1a7f37; --dsw-alias-state-warn-primary:#9a6700;
|
|
191
|
+
--dsw-alias-state-error-primary:#cf222e; --dsw-alias-state-info-primary:#0969da;
|
|
192
|
+
--dsw-alias-toast-bg:#24292f;
|
|
193
|
+
}`
|
|
194
|
+
|
|
195
|
+
const html = `<!doctype html>
|
|
196
|
+
<meta charset="utf-8">
|
|
197
|
+
<title>主理鱼面板预览</title>
|
|
198
|
+
<style>
|
|
199
|
+
${tokens}
|
|
200
|
+
body{font:14px/1.45 -apple-system,"Segoe UI",system-ui,sans-serif;background:#eef1f4;margin:0;padding:24px;color:#1f2328}
|
|
201
|
+
.preview-title{margin:28px 0 8px;font-size:15px;color:#57606a}
|
|
202
|
+
.preview-frame{width:900px;background:#fff;border:1px solid #d8dee4;border-radius:12px;min-height:120px;max-height:820px;overflow:auto}
|
|
203
|
+
.dt-page{height:auto!important;max-height:none!important}
|
|
204
|
+
${PORTED_CSS}
|
|
205
|
+
</style>
|
|
206
|
+
<div style="font:13px/1.5 system-ui;color:#57606a">
|
|
207
|
+
主理鱼面板静态预览 —— 真实 client.js 渲染出的 DOM + 移植的官方 CSS(宽度 900px,token 为浅色近似值)
|
|
208
|
+
</div>
|
|
209
|
+
${sections}
|
|
210
|
+
`
|
|
211
|
+
writeFileSync(OUT, html)
|
|
212
|
+
console.log('已写出:' + OUT)
|
|
213
|
+
console.log('字节:' + html.length)
|