dsh-unlag 1.9.85 → 1.9.87

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 CHANGED
@@ -72,6 +72,7 @@ dsh plugin install dsh-unlag
72
72
  ## 排错
73
73
 
74
74
  - 日志:`~/.dsh/unlag-reload.log`,换代码/跳过/被拦各一行,含原因。
75
+ - 演练:`npm run live-smoke` 不按按钮,只按当前启动清单把下一个文档要加载的字节在沙箱里跑一遍,看每个插件是否都注册(`rehearsal.ok`)——它证明的是「按下去之后新页面能起来」,不换任何代码。
75
76
  - 状态:`GET /dsh-unlag/state` → `{"version":"<当前版本>","code":"<index.js 写入时间>","plugins":true,"page":true,"shim":{...}}`。`code` 用来分清「版本号没刷」和「旧代码真的还在跑」。
76
77
  - 重建后的日志带起页面代次:`interface rebuilt; the page runs the plugins as served now (page <rev>)`,据此判断新文档加载的是新脚本还是旧脚本。
77
78
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-unlag",
3
- "version": "1.9.85",
3
+ "version": "1.9.87",
4
4
  "type": "module",
5
5
  "description": "A press applies plugin edits right away and brings a laggy interface back; automatic reloading stays off unless you turn it on, so the press is the only trigger, and nothing restarts.",
6
6
  "license": "MIT",
@@ -89,6 +89,87 @@ contract.push(['reload without header', await status('/dsh-unlag/reload', { meth
89
89
  contract.push(['verify without header', await status('/dsh-unlag/verify', { method: 'POST' }), 403])
90
90
  const contractHeld = contract.every(([, got, want]) => got === want)
91
91
 
92
+ // Rehearsal, not a press: run the bytes the next document would load for the startup list the
93
+ // desktop shell froze, with the same loader surface a document gives them, and see whether every
94
+ // plugin in the list registers. Nothing is swapped here; when this passes, the press's own page
95
+ // load is the only unproven step.
96
+ async function rehearseBoot() {
97
+ const stamped = (url) => {
98
+ const path = String(url).startsWith('/') ? String(url) : `/${String(url)}`
99
+ return `${HOST}${path}`
100
+ }
101
+ const registered = new Set()
102
+ const load = (row) => {
103
+ if (row !== null && typeof row === 'object' && typeof row.id === 'string') registered.add(row.id)
104
+ }
105
+ const noop = () => {}
106
+ const element = () => ({ style: {}, setAttribute: noop, append() {}, appendChild() {}, remove() {}, addEventListener: noop, removeEventListener: noop, classList: { add: noop, remove: noop }, querySelector: () => null })
107
+ const rehearsalWindow = {
108
+ __ModuleLoader__: { load, ensure: () => ({}) },
109
+ document: { head: element(), body: element(), createElement: element, querySelector: () => null, querySelectorAll: () => [], getElementById: () => null, addEventListener: noop, removeEventListener: noop },
110
+ location: { href: `${HOST}/`, origin: HOST, pathname: '/', search: '', hash: '', replace: noop, assign: noop, reload: noop },
111
+ navigator: { language: 'en-US', userAgent: 'Mozilla/5.0 (Macintosh) Electron/44.7.0', platform: 'MacIntel' },
112
+ sessionStorage: { getItem: () => null, setItem: noop, removeItem: noop },
113
+ localStorage: { getItem: () => null, setItem: noop, removeItem: noop },
114
+ matchMedia: () => ({ matches: false, addEventListener: noop, removeEventListener: noop }),
115
+ requestAnimationFrame: (fn) => { try { fn(0) } catch {} return 0 },
116
+ cancelAnimationFrame: noop,
117
+ MutationObserver: class { observe() {} disconnect() {} takeRecords() { return [] } },
118
+ IntersectionObserver: class { observe() {} disconnect() {} unobserve() {} },
119
+ ResizeObserver: class { observe() {} disconnect() {} unobserve() {} },
120
+ addEventListener: noop,
121
+ removeEventListener: noop,
122
+ dispatchEvent: () => true,
123
+ fetch: async (url, init) => fetch(stamped(url), init),
124
+ URL,
125
+ Response,
126
+ Request,
127
+ Headers,
128
+ AbortController,
129
+ Blob,
130
+ TextEncoder,
131
+ TextDecoder,
132
+ URLSearchParams,
133
+ console,
134
+ setTimeout,
135
+ clearTimeout,
136
+ queueMicrotask,
137
+ performance,
138
+ Event,
139
+ CustomEvent,
140
+ JSON,
141
+ Math,
142
+ Date,
143
+ Promise,
144
+ RegExp,
145
+ Error,
146
+ Buffer,
147
+ }
148
+ rehearsalWindow.window = rehearsalWindow
149
+ rehearsalWindow.globalThis = rehearsalWindow
150
+ rehearsalWindow.self = rehearsalWindow
151
+ rehearsalWindow.document.defaultView = rehearsalWindow
152
+ const ran = []
153
+ for (const batch of graph.batches) {
154
+ const path = String(batch.url).startsWith('/') ? String(batch.url) : `/${String(batch.url)}`
155
+ const response = await fetch(`${HOST}${path}`, { headers: { 'x-dsh-unlag': '1' } })
156
+ const body = await response.text()
157
+ ran.push({ url: path, status: response.status, bytes: body.length })
158
+ if (!response.ok) continue
159
+ try {
160
+ runInNewContext(body, rehearsalWindow, { timeout: 5000 })
161
+ } catch (error) {
162
+ ran[ran.length - 1].threw = String(error?.message ?? error)
163
+ }
164
+ }
165
+ const wanted = graph.entries.map((entry) => (typeof entry === 'string' ? entry : entry?.id)).filter((id) => typeof id === 'string')
166
+ const missing = wanted.filter((id) => !registered.has(id))
167
+ return { registered: registered.size, wanted: wanted.length, missing, ran }
168
+ }
169
+
170
+ const rehearsal = await rehearseBoot().catch((error) => ({ threw: String(error?.message ?? error) }));
171
+ const rehearsalOk = rehearsal.threw === undefined && rehearsal.missing !== undefined && rehearsal.missing.length === 0 && rehearsal.ran.every((row) => row.threw === undefined)
172
+
92
173
  const running = { version: stated.version, code: stated.code, shim: stated.shim }
93
174
  const verdict = await probe.startupLooksServable()
94
175
  console.log(JSON.stringify({
@@ -99,7 +180,8 @@ console.log(JSON.stringify({
99
180
  rows: rows.length,
100
181
  verdict,
101
182
  holdReason: verdict ? null : probe.startupHoldReason(),
183
+ rehearsal: { ok: rehearsalOk, ...rehearsal },
102
184
  asked: requests,
103
185
  contract: Object.fromEntries(contract.map(([label, got, want]) => [label, `${got}${got === want ? '' : ` (wanted ${want})`}`])),
104
186
  }, null, 2))
105
- process.exitCode = verdict === true && contractHeld ? 0 : 1
187
+ process.exitCode = verdict === true && contractHeld && rehearsalOk ? 0 : 1