dsh-vision-router 1.7.4 → 1.7.5
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/docs/doctor.md +120 -27
- package/entry.js +29 -2
- package/lib/client-presentation-boundary.js +72 -0
- package/lib/doctor-cli.js +162 -69
- package/lib/doctor-runtime.js +385 -0
- package/lib/doctor.js +394 -33
- package/lib/legacy-session-repair.js +204 -49
- package/lib/native-image-coexistence.js +185 -0
- package/lib/pi-ai-bridge-wire-compat.js +258 -0
- package/lib/vision-backend-smoke-test-client.js +228 -0
- package/lib/vision-backend-smoke-test.js +449 -0
- package/lib/web-capability-boundary.js +7 -0
- package/package.json +9 -19
package/docs/doctor.md
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# Vision Router doctor / repair
|
|
2
2
|
|
|
3
|
-
Vision Router ships a
|
|
3
|
+
Vision Router ships a standalone diagnostic CLI. It can inspect a broken DSH profile even when DSH cannot boot, and it can optionally probe a running Web instance without executing any Vision Router action.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The commands have a deliberately conservative split:
|
|
6
|
+
|
|
7
|
+
- `doctor` is read-only by default;
|
|
8
|
+
- `repair` changes only the two known profile-level faults described below;
|
|
9
|
+
- `repair-sessions` changes only exact, known Vision Router session-corruption signatures and always makes a byte-for-byte backup first.
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
## Normal installation stays unchanged
|
|
8
12
|
|
|
9
13
|
```sh
|
|
10
14
|
npx @deepseek-ai/dsh plugin --profile web add dsh-vision-router
|
|
@@ -20,19 +24,90 @@ pnpm dsh web
|
|
|
20
24
|
|
|
21
25
|
The doctor is a recovery/diagnostic tool, not a replacement installer.
|
|
22
26
|
|
|
23
|
-
##
|
|
27
|
+
## Doctor v2
|
|
28
|
+
|
|
29
|
+
Run the normal health check:
|
|
24
30
|
|
|
25
31
|
```sh
|
|
26
32
|
npx dsh-vision-router doctor
|
|
27
33
|
```
|
|
28
34
|
|
|
29
|
-
|
|
35
|
+
Or inspect one profile explicitly:
|
|
30
36
|
|
|
31
37
|
```sh
|
|
32
38
|
npx dsh-vision-router doctor --profile web
|
|
33
39
|
```
|
|
34
40
|
|
|
35
|
-
The
|
|
41
|
+
The doctor now separates **"the profile JSON parses"** from **"Vision Router is actually healthy"**. A requested profile is unhealthy when the package is missing, declared but not installed, installed but not mounted, registered twice, or otherwise cannot form one valid activation path.
|
|
42
|
+
|
|
43
|
+
It recognizes both supported profile shapes:
|
|
44
|
+
|
|
45
|
+
- **bundle mode** — `dsh-vision-router` is declared in `dependencies` and registered once in `dsh.profile.bundles`;
|
|
46
|
+
- **manual mode** — the dependency is declared and the profile's `cordis.patch.yml` contains one Vision Router row.
|
|
47
|
+
|
|
48
|
+
It reports a hard failure for a bundle+manual double registration, duplicate bundle/manual rows, a missing installed package, or an installed/declared package with no activation row. Profiles that do not use Vision Router are shown as unrelated when scanning all profiles; they do not make another healthy Vision Router profile fail.
|
|
49
|
+
|
|
50
|
+
The offline profile check also keeps the existing diagnostics for:
|
|
51
|
+
|
|
52
|
+
- a leading UTF-8 BOM or otherwise invalid profile JSON;
|
|
53
|
+
- stale version-pinned `minimumReleaseAgeExclude` entries;
|
|
54
|
+
- coexisting vision plugins;
|
|
55
|
+
- legacy profile patches that statically disable `llm-deepseek`;
|
|
56
|
+
- recent structured `settings save failed field=... operation=... reason=...` records from the Vision Router log. Raw log lines, API keys and arbitrary values are not copied into the report.
|
|
57
|
+
|
|
58
|
+
### Running-DSH route probe
|
|
59
|
+
|
|
60
|
+
When DSH Web is reachable, doctor checks that the Vision Router exact routes are really registered. This catches the class of regressions where the plugin partly loads but routes such as `update-check` or `model-capabilities` silently disappear and the SPA fallback returns HTML instead.
|
|
61
|
+
|
|
62
|
+
The probe sends an intentionally unsupported **DELETE** request to each known route. Every current Vision Router handler rejects that method with `405 Method Not Allowed` and its exact `Allow` contract **before** executing GET/POST behavior. Doctor verifies that exact method contract, so it does **not** run an update check, model discovery, connection test, settings mutation, self-update or log-opening action. Using the expected `Allow` set also prevents a generic SPA/unknown-route response from being mistaken for a registered Vision Router route.
|
|
63
|
+
|
|
64
|
+
Default target:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
http://127.0.0.1:3080
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Override it with:
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
npx dsh-vision-router doctor --runtime-url http://127.0.0.1:4000
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
or `DSH_WEB_URL`. To perform offline checks only:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
npx dsh-vision-router doctor --no-runtime
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
An unreachable DSH process is advisory rather than a doctor failure; offline checks still complete. When `--profile` is used against a reachable runtime, doctor does not attribute green route health to that profile unless the runtime exposes a verified Vision Router profile identity. If ownership cannot be proven, the human report shows `? runtime profile ownership unknown` instead of a false green binding.
|
|
83
|
+
|
|
84
|
+
### Local capability diagnostics
|
|
85
|
+
|
|
86
|
+
Doctor reports the local platform, Node version, and whether it can find:
|
|
87
|
+
|
|
88
|
+
- Tesseract (PATH plus the common Windows install location);
|
|
89
|
+
- Chromium / Chrome / Edge (PATH plus common Windows and macOS locations);
|
|
90
|
+
- `sharp` and selected DSH host package versions when they are present inside the inspected profile.
|
|
91
|
+
|
|
92
|
+
Tesseract, Chromium and profile-local Sharp are advisory because the corresponding optional tool may be unused or supplied by the host through another resolution path.
|
|
93
|
+
|
|
94
|
+
### Scan historical sessions
|
|
95
|
+
|
|
96
|
+
Session scanning is opt-in because a large session store can take longer to read/decompress:
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
npx dsh-vision-router doctor --sessions
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
This is read-only. It looks only for the exact known Vision Router corruption signatures supported by `repair-sessions`.
|
|
103
|
+
|
|
104
|
+
### Shareable JSON report
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
npx dsh-vision-router doctor --profile web --sessions --json
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The JSON report is schema-versioned and includes the running Doctor/Vision Router version. It is intentionally minimized for issue reports: it does not contain the raw profile manifest, raw log lines, API keys, raw dependency specs, local dependency paths, URL credentials/query strings/fragments, session ids, or raw runtime error text. It keeps only structured diagnostic facts such as install mode, installed version, route status, failure counts, optional capability versions and known session-repair kinds.
|
|
36
111
|
|
|
37
112
|
## Repair the UTF-8 BOM startup failure
|
|
38
113
|
|
|
@@ -51,33 +126,47 @@ npx dsh-vision-router repair --profile web
|
|
|
51
126
|
|
|
52
127
|
`repair` removes only the three-byte UTF-8 BOM prefix (`EF BB BF`) when it is present, then validates the remaining JSON. It does not reformat, regenerate, or otherwise rewrite the profile contents. If JSON is still invalid for another reason, the command reports that and stops rather than guessing a repair.
|
|
53
128
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
pnpm v11 defaults `minimumReleaseAge` to 1440 minutes: a version published less than 24 hours ago is not resolved, so `dsh plugin update` silently keeps the previous version and prints `downloaded 0 / added 0`. An exemption entry that pins a version — `dsh-vision-router@1.2.0` — only exempts that one version and goes stale on the next release, which is why "a new release is out but the update does nothing" keeps recurring.
|
|
129
|
+
`doctor --fix` remains accepted for backward compatibility, but new troubleshooting instructions should prefer the explicit `repair` command so that ordinary doctor runs stay obviously read-only.
|
|
57
130
|
|
|
58
|
-
|
|
131
|
+
## Repair a stale release-age exemption (the "update does nothing" gate)
|
|
59
132
|
|
|
60
|
-
|
|
61
|
-
✗ web — … — release-age exemption version-pinned (dsh-vision-router@1.2.0) — releases younger than 24h will not be picked up
|
|
62
|
-
```
|
|
133
|
+
pnpm v11 defaults `minimumReleaseAge` to 1440 minutes. A version-pinned exemption such as `dsh-vision-router@1.2.0` exempts only that version and goes stale on the next release.
|
|
63
134
|
|
|
64
|
-
Run:
|
|
135
|
+
Doctor flags version-pinned entries for `dsh-vision-router` and `@deepseek-ai/*`. Run:
|
|
65
136
|
|
|
66
137
|
```sh
|
|
67
138
|
npx dsh-vision-router repair --profile web
|
|
68
139
|
```
|
|
69
140
|
|
|
70
|
-
to rewrite
|
|
141
|
+
to rewrite only those stale targets to bare names (`dsh-vision-router`, `@deepseek-ai/*`). Unrelated entries and the rest of the workspace file are left untouched.
|
|
142
|
+
|
|
143
|
+
## Repair conversations that only break after restarting DSH
|
|
144
|
+
|
|
145
|
+
Two historical Vision Router defects can leave already-persisted sessions unable to cold-resume even though current builds no longer create the bad events.
|
|
71
146
|
|
|
72
|
-
|
|
147
|
+
### Legacy missing message id
|
|
73
148
|
|
|
74
|
-
A very early
|
|
149
|
+
A very early build persisted the automatic vision-tool mount reminder as a `user/message` without an `id`. DSH can later reject it with an error containing:
|
|
75
150
|
|
|
76
151
|
```text
|
|
77
152
|
lacks an identified message
|
|
78
153
|
```
|
|
79
154
|
|
|
80
|
-
|
|
155
|
+
### Duplicate structured guard-stop id
|
|
156
|
+
|
|
157
|
+
Older structured-flow builds could persist the same exact guard message more than once in one turn, for example:
|
|
158
|
+
|
|
159
|
+
```text
|
|
160
|
+
vision-router-structured-guard-stop-1
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
DSH then sees more than one `input-message...` start when the conversation is reloaded and can fail with an error containing:
|
|
164
|
+
|
|
165
|
+
```text
|
|
166
|
+
received more than one start Match
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
To repair either known case, **stop DSH first**, then run:
|
|
81
170
|
|
|
82
171
|
```sh
|
|
83
172
|
npx dsh-vision-router repair-sessions
|
|
@@ -85,12 +174,16 @@ npx dsh-vision-router repair-sessions
|
|
|
85
174
|
|
|
86
175
|
The repair is intentionally narrow and fail-closed:
|
|
87
176
|
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
-
|
|
91
|
-
-
|
|
92
|
-
-
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
|
|
96
|
-
|
|
177
|
+
- the old missing-id repair still matches only the exact historical Vision Router auto-mount reminder;
|
|
178
|
+
- duplicate guard repair recognizes only Vision Router `user/message` events with the exact historical guard id shape, plugin source and one of the exact known budget/depth exhaustion texts;
|
|
179
|
+
- the first legitimate guard is preserved; later exact duplicates keep their original event and `seq`, but receive deterministic unique recovery ids so the durable event stream is not shortened;
|
|
180
|
+
- if the same guard id appears with a different/near-miss shape or text, automatic repair aborts instead of deleting data;
|
|
181
|
+
- raw `session.jsonl` and DSH's checksummed `session.jsonl.zstd` are supported;
|
|
182
|
+
- duplicate detection is preserved across separate Zstandard frames, and packed chunk rows are understood when validating the contiguous durable `seq` stream;
|
|
183
|
+
- unaffected Zstandard frames remain byte-for-byte identical;
|
|
184
|
+
- mutation refuses torn/incomplete logs so DSH can perform its own crash recovery first; read-only `doctor --sessions` treats only an incomplete live tail as advisory while committed structural corruption remains a failure;
|
|
185
|
+
- source file identity is rechecked immediately before replacement so a live writer aborts the operation;
|
|
186
|
+
- every changed log receives a byte-for-byte backup next to the original before replacement;
|
|
187
|
+
- the repaired file is re-read and verified before success is reported.
|
|
188
|
+
|
|
189
|
+
After repair, restart DSH and reopen the affected conversation. Running `repair-sessions` again is idempotent.
|
package/entry.js
CHANGED
|
@@ -12,9 +12,13 @@ import { installVisionRouterFileLogging } from './lib/file-logger.js'
|
|
|
12
12
|
import { contextWithDelegatedReplay } from './lib/replay-delegation.js'
|
|
13
13
|
import { contextWithReplayEnvelopeV2Compat } from './lib/replay-envelope-v2-compat.js'
|
|
14
14
|
import { contextWithVisionExecutionPolicy } from './lib/vision-execution-policy.js'
|
|
15
|
+
import { contextWithNativeImageCoexistence } from './lib/native-image-coexistence.js'
|
|
16
|
+
import { installPiAiBridgeWireCompat } from './lib/pi-ai-bridge-wire-compat.js'
|
|
15
17
|
import { installLiveModelDiscovery } from './lib/live-model-discovery.js'
|
|
16
18
|
import { installVisionModelRegistry } from './lib/vision-model-registry.js'
|
|
17
19
|
import { installLiveModelClientPrelude } from './lib/live-model-client-prelude.js'
|
|
20
|
+
import { installExactVisionTestClient } from './lib/vision-backend-smoke-test-client.js'
|
|
21
|
+
import { installVisionBackendSmokeTest } from './lib/vision-backend-smoke-test.js'
|
|
18
22
|
import { installClientPresentationBoundary } from './lib/client-presentation-boundary.js'
|
|
19
23
|
import { installAdversarialHardening } from './lib/adversarial-hardening.js'
|
|
20
24
|
import { installOllamaColdStartGuard } from './lib/ollama-cold-start.js'
|
|
@@ -201,11 +205,17 @@ export function apply(ctx, config = {}) {
|
|
|
201
205
|
// The secure screenshot renderer owns its exact FsTarget and active browser
|
|
202
206
|
// cancellation directly, so it does not depend on this placement.
|
|
203
207
|
const toolRuntimeCtx = installVisionToolRuntimeBoundary(attachmentCompatCtx)
|
|
208
|
+
// DSH 0.1.1 publishes an exact native image-capable DeepSeek model. Do not
|
|
209
|
+
// put it ahead of Vision Router's own configured chain: only when the user
|
|
210
|
+
// has explicitly selected any Host-native image route, preserve raw pixels
|
|
211
|
+
// and skip the hidden instant-local caption pass for that turn. The override
|
|
212
|
+
// is AsyncLocalStorage-scoped and never mutates settings or provider order.
|
|
213
|
+
const nativeImageCompat = contextWithNativeImageCoexistence(toolRuntimeCtx, runtimeConfig)
|
|
204
214
|
// Final structured-flow guard sits closest to core.apply so it sees the
|
|
205
215
|
// actual tool registrations and pre-step listener. It makes bootstrap
|
|
206
216
|
// one-shot, enforces fast/standard/deep/custom quotas, tracks mixed branches,
|
|
207
217
|
// rejects empty/non-evidence results, and applies one shared visual deadline.
|
|
208
|
-
const structuredCtx = installStructuredFlowHardening(
|
|
218
|
+
const structuredCtx = installStructuredFlowHardening(nativeImageCompat.ctx, nativeImageCompat.config)
|
|
209
219
|
// Newer DSH releases publish llm/adapters-updated synchronously from inside
|
|
210
220
|
// registerAdapter(). Coalesce only Vision Router's listener: nested events
|
|
211
221
|
// mark the topology dirty and the outer pass reruns to a fixed point, so we
|
|
@@ -240,6 +250,15 @@ export function apply(ctx, config = {}) {
|
|
|
240
250
|
// ordinary chat model picker). The existing classic client bundle stays the
|
|
241
251
|
// DSH module-system artifact, including HMR/source-map behavior.
|
|
242
252
|
installLiveModelClientPrelude(reconciledCtx)
|
|
253
|
+
// #266: 1.7.x gets one exact, no-fallback image smoke test per visible row.
|
|
254
|
+
// Keep it out of the controlled React form so the v2 capability-benchmark
|
|
255
|
+
// client can take ownership later without forking the stable settings UI.
|
|
256
|
+
installExactVisionTestClient(reconciledCtx)
|
|
257
|
+
// DSH 0.1.1 adds per-route/model pi-ai wire compatibility. The legacy
|
|
258
|
+
// direct image bridge is non-streaming and predates that surface, so preserve
|
|
259
|
+
// maxTokensField + route headers at its final fetch boundary. Ordinary DSH
|
|
260
|
+
// streams and unrelated Vision Router HTTP providers remain byte-identical.
|
|
261
|
+
installPiAiBridgeWireCompat(reconciledCtx, logging.logger)
|
|
243
262
|
// Direct compatibility bridging is allowed only after DSH/pi-ai's exact
|
|
244
263
|
// pre-wire image-capability admission rejection, or a local UNKNOWN_MODEL
|
|
245
264
|
// backed by exact private-registry evidence. Record the same provenance in
|
|
@@ -251,6 +270,14 @@ export function apply(ctx, config = {}) {
|
|
|
251
270
|
evidenceSource: (provider, model) => liveDiscovery.evidenceSource?.(provider, model),
|
|
252
271
|
logger: logging.logger,
|
|
253
272
|
})
|
|
273
|
+
// The smoke-test route sends only a built-in probe image to the exact selected
|
|
274
|
+
// backend. It never walks the configured fallback chain, so a healthy OVH
|
|
275
|
+
// fallback can no longer make a broken custom model look healthy. Its narrow
|
|
276
|
+
// compatibility bridge uses the same live-discovery evidence gate as runtime.
|
|
277
|
+
installVisionBackendSmokeTest(executionCtx, runtimeConfig, core, {
|
|
278
|
+
logger: logging.logger,
|
|
279
|
+
isBridgeEvidence: (provider, model) => liveDiscovery.hasModel(provider, model),
|
|
280
|
+
})
|
|
254
281
|
// index.js historically passes image bytes as `options.input` to the async
|
|
255
282
|
// execFile API. That option is not fed into child stdin, so Tesseract waits
|
|
256
283
|
// for data until the OCR slice expires. Materialize only this exact
|
|
@@ -277,7 +304,7 @@ export function apply(ctx, config = {}) {
|
|
|
277
304
|
/* diagnostics must never break apply */
|
|
278
305
|
}
|
|
279
306
|
try {
|
|
280
|
-
const result = core.apply(executionCtx,
|
|
307
|
+
const result = core.apply(executionCtx, nativeImageCompat.config)
|
|
281
308
|
// On newer Hosts the Settings -> Models surface is backed by the
|
|
282
309
|
// configurable-provider directory, not by the live adapter registry alone.
|
|
283
310
|
// Publish the main DeepSeek + 自动识图 route as a derived alias of official
|
|
@@ -26,6 +26,77 @@ export const CLIENT_PRESENTATION_PRELUDE = String.raw`(function(){
|
|
|
26
26
|
var TARGET = 'dsh-vision-router';
|
|
27
27
|
var LEGACY_ATTACHMENT_VALUE = '@deepseek-ai/dsh-client-ui-attachment';
|
|
28
28
|
|
|
29
|
+
// #271 pre-release regression fence: #210 made the walkthrough runtime lazy,
|
|
30
|
+
// but its document.body MutationObserver still invalidates the 250ms target
|
|
31
|
+
// cache for every class mutation. DSH toggles transient scroll/shadow classes
|
|
32
|
+
// while the settings modal moves, turning an otherwise cheap rAF scroll frame
|
|
33
|
+
// back into several querySelectorAll passes + forced layout. Keep the observer
|
|
34
|
+
// fully authoritative for child-list and aria mutations, but suppress class-
|
|
35
|
+
// only batches for the one exact Vision Router guide observer. Normal scroll
|
|
36
|
+
// frames will still refresh the cached geometry at the existing 250ms bound.
|
|
37
|
+
function installGuideMutationFence() {
|
|
38
|
+
var NativeObserver = window.MutationObserver;
|
|
39
|
+
var doc = window.document;
|
|
40
|
+
if (
|
|
41
|
+
typeof NativeObserver !== 'function' ||
|
|
42
|
+
NativeObserver.__visionRouterGuideMutationFence ||
|
|
43
|
+
!doc || !doc.body ||
|
|
44
|
+
typeof Proxy !== 'function'
|
|
45
|
+
) return;
|
|
46
|
+
|
|
47
|
+
function isGuideObservation(callback, target, options) {
|
|
48
|
+
if (!callback || callback.name !== 'resolveSync') return false;
|
|
49
|
+
if (
|
|
50
|
+
target !== doc.body || !options ||
|
|
51
|
+
options.childList !== true || options.subtree !== true || options.attributes !== true
|
|
52
|
+
) return false;
|
|
53
|
+
var filter = Array.isArray(options.attributeFilter) ? options.attributeFilter.slice().sort() : [];
|
|
54
|
+
return filter.length === 3 &&
|
|
55
|
+
filter[0] === 'aria-expanded' && filter[1] === 'aria-hidden' && filter[2] === 'class';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function withoutTransientClassMutations(records) {
|
|
59
|
+
if (!records || typeof records.filter !== 'function') return records;
|
|
60
|
+
return records.filter(function(record){
|
|
61
|
+
return !(record && record.type === 'attributes' && record.attributeName === 'class');
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
var WrappedObserver = new Proxy(NativeObserver, {
|
|
66
|
+
construct: function(Target, args) {
|
|
67
|
+
var callback = args && args[0];
|
|
68
|
+
var guideObservation = false;
|
|
69
|
+
var wrappedObserver;
|
|
70
|
+
var nativeObserver = new Target(function(records) {
|
|
71
|
+
var next = guideObservation ? withoutTransientClassMutations(records) : records;
|
|
72
|
+
if (!next || next.length === 0) return;
|
|
73
|
+
return callback(next, wrappedObserver || nativeObserver);
|
|
74
|
+
});
|
|
75
|
+
wrappedObserver = new Proxy(nativeObserver, {
|
|
76
|
+
get: function(target, property) {
|
|
77
|
+
if (property === 'observe') {
|
|
78
|
+
return function(node, options) {
|
|
79
|
+
guideObservation = isGuideObservation(callback, node, options);
|
|
80
|
+
return target.observe(node, options);
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (property === 'takeRecords') {
|
|
84
|
+
return function() {
|
|
85
|
+
var records = target.takeRecords();
|
|
86
|
+
return guideObservation ? withoutTransientClassMutations(records) : records;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
var value = Reflect.get(target, property, target);
|
|
90
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
return wrappedObserver;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
Object.defineProperty(WrappedObserver, '__visionRouterGuideMutationFence', { value: true });
|
|
97
|
+
window.MutationObserver = WrappedObserver;
|
|
98
|
+
}
|
|
99
|
+
|
|
29
100
|
function createPresentation(React) {
|
|
30
101
|
function PresentedImage(props) {
|
|
31
102
|
var attachment = props.attachment;
|
|
@@ -221,6 +292,7 @@ export const CLIENT_PRESENTATION_PRELUDE = String.raw`(function(){
|
|
|
221
292
|
}
|
|
222
293
|
|
|
223
294
|
function install() {
|
|
295
|
+
installGuideMutationFence();
|
|
224
296
|
if (window.__ModuleLoader__) {
|
|
225
297
|
patchLoader(window.__ModuleLoader__);
|
|
226
298
|
return;
|