dsh-vision-router 3.0.1 → 3.0.3
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 +8 -8
- package/README.zh.md +6 -6
- package/docs/architecture/ci-risk-posture.md +45 -0
- package/docs/architecture/dsh-021-alpha1-adaptation.md +46 -0
- package/docs/releases/v3.0.2.md +105 -0
- package/docs/releases/v3.0.3.md +45 -0
- package/index.js +13 -3
- package/lib/adapter-ownership.js +51 -0
- package/lib/client-presentation-boundary-main.js +123 -50
- package/lib/client-presentation-boundary.js +36 -46
- package/lib/client.js +10 -4
- package/lib/core-primitives.js +21 -0
- package/lib/core-vision-surface.js +3 -1
- package/lib/desktop-screenshot-tool.js +11 -9
- package/lib/diagnostic-redaction.js +67 -12
- package/lib/doctor-cli-p0.js +36 -5
- package/lib/dsh-support-window.js +4 -1
- package/lib/file-logger.js +9 -9
- package/lib/image-offload-compat.js +10 -3
- package/lib/local-vision-stabilizer.js +8 -6
- package/lib/native-image-coexistence.js +11 -1
- package/lib/proxy-routing.js +39 -5
- package/lib/remote-settings-risk-client-boundary.js +6 -1
- package/lib/session-vision-mode-authority.js +2 -7
- package/lib/session-vision-policy.js +9 -4
- package/lib/settings-number-contract.js +3 -1
- package/lib/vision-background-stop-store-core.js +14 -2
- package/lib/vision-resilience.js +31 -2
- package/lib/vision-tool-runtime-boundary.js +2 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -42,15 +42,15 @@
|
|
|
42
42
|
> **Data leaves your machine by default.** When a cloud vision model is used, Vision Router sends the image (or a derived crop), the vision prompt and related request metadata to that provider. A fresh install has an anonymous OVHcloud vision fallback enabled, so "free" and "no key" do **not** mean offline. Do not use the cloud chain for confidential, regulated or classified material. See [Data flow and strict local-only use](#data-flow-and-strict-local-only-use).
|
|
43
43
|
|
|
44
44
|
> [!WARNING]
|
|
45
|
-
> 📌 **Announcement (v3.0.
|
|
45
|
+
> 📌 **Announcement (v3.0.3)**
|
|
46
46
|
>
|
|
47
|
-
> **v3.0.
|
|
48
|
-
>
|
|
49
|
-
>
|
|
50
|
-
>
|
|
51
|
-
>
|
|
52
|
-
>
|
|
53
|
-
>
|
|
47
|
+
> **v3.0.3: presentation and proxy-boundary corrections.** Restored history and empty
|
|
48
|
+
> `vision_present` results no longer steal the reader’s scroll position, while fresh presented
|
|
49
|
+
> images still focus correctly on both the newer phase-based Host contract and the supported DSH
|
|
50
|
+
> 0.1.5 lifecycle. Cookie-domain-style `.example.com` entries now follow the same proxy-host
|
|
51
|
+
> policy as `*.example.com`, and the exact DSH 0.1.7 browser smoke again follows both
|
|
52
|
+
> presentation-boundary owners. CI action pins were also refreshed; runtime dependencies and
|
|
53
|
+
> supported Hosts are unchanged. [What’s new →](docs/releases/v3.0.3.md)
|
|
54
54
|
|
|
55
55
|
<p align="center">
|
|
56
56
|
<img src="assets/vision-demo.gif" width="640" alt="Demo: paste an image, the agent locates the send button with vision_ground / vision_crop / vision_pixel_diff and answers with coordinates" />
|
package/README.zh.md
CHANGED
|
@@ -42,13 +42,13 @@
|
|
|
42
42
|
> **默认配置会让数据出网。** 使用云端视觉模型时,Vision Router 会将图片(或由它裁剪出的局部)、识图提示词及相关请求元数据发送给对应供应商。新安装默认开启 OVHcloud 匿名视觉兜底,因此“免费”和“免 Key”不等于离线。涉密、涉案、内部或其他受限材料请勿使用云端链路。详见[数据流向与严格纯本地配置](#数据流向与严格纯本地配置)。
|
|
43
43
|
|
|
44
44
|
> [!WARNING]
|
|
45
|
-
> 📌 **公告(v3.0.
|
|
45
|
+
> 📌 **公告(v3.0.3)**
|
|
46
46
|
>
|
|
47
|
-
> **v3.0.
|
|
48
|
-
>
|
|
49
|
-
>
|
|
50
|
-
>
|
|
51
|
-
>
|
|
47
|
+
> **v3.0.3:修正图片呈现与代理边界。** 恢复历史会话和空的 `vision_present` 结果不再抢走滚动位置,
|
|
48
|
+
> 新生成的图片仍会在新版 phase 生命周期与受支持的 DSH 0.1.5 旧生命周期上正确聚焦;
|
|
49
|
+
> cookie-domain 风格的 `.example.com` 现在与 `*.example.com` 遵守同一套代理主机规则;
|
|
50
|
+
> DSH 0.1.7 精确浏览器 smoke 也重新跟随两个 presentation-boundary owner。CI Action 固定版本同步更新,
|
|
51
|
+
> 运行时依赖和 Host 支持范围均不变。[查看完整更新 →](docs/releases/v3.0.3.md)
|
|
52
52
|
|
|
53
53
|
<p align="center">
|
|
54
54
|
<img src="assets/vision-demo.gif" width="640" alt="演示:粘贴图片,Agent 用 vision_ground / vision_crop / vision_pixel_diff 定位发送按钮并给出坐标" />
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# CI risk posture
|
|
2
|
+
|
|
3
|
+
This file records security-posture decisions that are deliberate rather than pending, so a
|
|
4
|
+
later reader does not re-litigate them or mistake them for oversights.
|
|
5
|
+
|
|
6
|
+
## Resolved: Scorecard `TokenPermissionsID` on workflows that publish or open pull requests
|
|
7
|
+
|
|
8
|
+
Three alerts in this class were open on 2026-10-04 and are now `fixed` (2026-10-04T14:17:06Z):
|
|
9
|
+
|
|
10
|
+
| Alert | Location | Grant then | Outcome |
|
|
11
|
+
| --- | --- | --- | --- |
|
|
12
|
+
| `#127` | `.github/workflows/dsh-alpha-pin-refresh.yml` | workflow-level `contents: write` + `pull-requests: write` | fixed by moving both grants onto the job that pushes the pin branch and opens its pull request |
|
|
13
|
+
| `#125` | `.github/workflows/release.yml` | job-level `contents: write` (materialize the release tag) | fixed on re-analysis |
|
|
14
|
+
| `#126` | `.github/workflows/release.yml` | job-level `contents: write` (publish the GitHub Release) | fixed on re-analysis |
|
|
15
|
+
|
|
16
|
+
Scorecard's rule asks for top-level permissions to be `read-all` or `contents: read`, and
|
|
17
|
+
job-level least-privilege grants are accepted. Every workflow here now has that shape:
|
|
18
|
+
`release.yml` declares top-level `permissions: {}`, the alpha gate and the pin refresh declare
|
|
19
|
+
`contents: read`, and each write grant lives on the single job that needs it.
|
|
20
|
+
|
|
21
|
+
A previous revision of this file claimed the opposite — that the rule reports the grants
|
|
22
|
+
themselves, so moving one between levels could not clear the alert. That claim was wrong. It
|
|
23
|
+
generalised from the presence of alerts on `release.yml`'s job-level grants instead of measuring
|
|
24
|
+
the level change itself; moving the pin refresh's grants to job level cleared its alert, and the
|
|
25
|
+
two `release.yml` alerts cleared on re-analysis. The measurement is recorded above so the next
|
|
26
|
+
reader does not repeat the inference.
|
|
27
|
+
|
|
28
|
+
What remains true regardless of the score:
|
|
29
|
+
|
|
30
|
+
- the pin refresh never checks out or executes upstream code: it reads npm and GitHub metadata
|
|
31
|
+
and rewrites one line, so the write token is never exposed to third-party code;
|
|
32
|
+
- it has no access to secrets beyond the default token, and its pull request is opened with
|
|
33
|
+
`GITHUB_TOKEN`, which by design starts no workflow runs;
|
|
34
|
+
- the gate that *does* execute upstream code keeps top-level `contents: read`, pins the upstream
|
|
35
|
+
commit literally, and neither writes nor restores any shared cache;
|
|
36
|
+
- the platform-side control (a protected environment with required reviewers on the publishing
|
|
37
|
+
jobs) is available but deliberately not adopted: it would break unattended pin discovery, and
|
|
38
|
+
every write path here is already a reviewed pull request or a dispatched release.
|
|
39
|
+
|
|
40
|
+
## How to revisit
|
|
41
|
+
|
|
42
|
+
If the write tokens ever need to shrink, the order is: attach the publishing jobs to a protected
|
|
43
|
+
environment, then drop `pull-requests: write` from the pin refresh by opening the bump as an
|
|
44
|
+
issue instead of a pull request. Both trade automation for score, which is why they are not the
|
|
45
|
+
default.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# DSH 0.2.1-alpha.1 compatibility audit
|
|
2
|
+
|
|
3
|
+
## Release identity and channel
|
|
4
|
+
|
|
5
|
+
- Upstream tag: `dsh-v0.2.1-alpha.1`
|
|
6
|
+
- Upstream commit: `5badb15009ae1756c3afe0ae0cef1faafc290ccc`
|
|
7
|
+
- npm publication: `2026-10-03T04:53:22.343Z`
|
|
8
|
+
- GitHub release: `2026-10-03T06:42:00Z`
|
|
9
|
+
- Channel: `alpha` only. At publication time both `latest` and `next` remain
|
|
10
|
+
`0.2.0-rc.2`.
|
|
11
|
+
|
|
12
|
+
This release is inside the already peer-admitted
|
|
13
|
+
`>=0.2.0-rc.2 <0.3.0-0` train. It does not move the exact supported evidence
|
|
14
|
+
boundary away from `0.2.0-rc.2`, and it does not raise the public DSH 0.1.x
|
|
15
|
+
minimum.
|
|
16
|
+
|
|
17
|
+
## Compatibility review from 0.2.0-rc.2
|
|
18
|
+
|
|
19
|
+
| Surface | Upstream change | DVR conclusion |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| Host / Desktop | Desktop defaults to a system-assigned port. Boot/profile resolution and plugin package metadata were reworked. | Keep capability-based startup and authenticated Desktop tests. No version branch is needed. |
|
|
22
|
+
| Session | Session list generation yields between work slices and honors cancellation. Core Session event signatures and the durable v4 admission surface remain compatible. | The existing post-commit `session/event(session, event)` observer and v4 admission contracts stay valid. |
|
|
23
|
+
| Settings | No breaking Settings controller API change. Client Settings and Agent preset composition changed, and a Host may expose a different reachable Settings seam. | DVR already derives the seam from the installed Host and verifies mount/save/reload/readback in a real browser. |
|
|
24
|
+
| Bundle / HMR | Web adds Schedule rows and `publicUrl`; HMR now reloads package entry/dependency maps and removes stale mappings. | Re-run real bundle recomposition and plugin lifecycle tests; do not pin private HMR helper names. |
|
|
25
|
+
| Web / client | Draft preservation, plugin creation, inspector/devtools, tool preparation, layout, and document frontmatter rendering changed. | DVR's client injection remains capability/slot based. Real Chromium cold Vision, mixed attachment, and Settings tests cover the integration points. |
|
|
26
|
+
| Model / LLM | The LLM base adapter exposes the synchronous `imageRequestPricing()` default used by the Host. | DVR's adapter registration boundary already supplies the inherited undefined default to duck-typed adapters. |
|
|
27
|
+
| Attachment | Published attachment API shapes remain compatible. The alpha line retains the extended image and normalization limits. | DVR's root repair reconciles stale whole-row Cordis configs to the full Host policy and exercises the Host-owned Sharp pipeline. |
|
|
28
|
+
| Service injection | Public `modelCatalog`, credential update, connection rejection, WebServer route, and Cordis lifecycle seams remain present. | Structured injection and authenticated Desktop RPC stay under exact-source and real-Host tests. |
|
|
29
|
+
|
|
30
|
+
## Validation gate
|
|
31
|
+
|
|
32
|
+
`.github/workflows/dsh-alpha-settings-host-gate.yml` pins the immutable commit
|
|
33
|
+
above and runs:
|
|
34
|
+
|
|
35
|
+
- exact-source and Host-owned Sharp contracts on Ubuntu, macOS, and Windows;
|
|
36
|
+
- public 0.2.x peer admission and Host proxy ownership without changing the
|
|
37
|
+
minimum support window;
|
|
38
|
+
- real macOS Desktop renderer lifecycle;
|
|
39
|
+
- Windows Desktop authentication on Node 22 and 24, plus Node 24 renderer and
|
|
40
|
+
multi-plugin ordering adversaries;
|
|
41
|
+
- real DSH Web + Chromium cold Vision, mixed attachments, Settings
|
|
42
|
+
mount/save/reload/readback, and bundle recomposition.
|
|
43
|
+
|
|
44
|
+
The floating npm alpha canaries remain surveillance. Advancement of the
|
|
45
|
+
ordinary development pin or exact supported evidence boundary is a separate,
|
|
46
|
+
reviewed decision. This audit does not authorize a DVR package release.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# v3.0.2
|
|
2
|
+
|
|
3
|
+
DSH Vision Router 3.0.2 is a hardening release on the 3.0 line. It ships the fixes from a
|
|
4
|
+
full-repository adversarial review plus the follow-up pass over that work, the composer Vision
|
|
5
|
+
control's responsive-design fix (issue #654) and the presented-image delivery fix, together with
|
|
6
|
+
the one consumer-visible transport fix merged after v3.0.1 was tagged. There are no new peers, no
|
|
7
|
+
settings migration, no Session format change and no public API change.
|
|
8
|
+
|
|
9
|
+
## Highlights
|
|
10
|
+
|
|
11
|
+
- **The composer Vision chip follows the shipped chip and adapts to narrow rows (#654).** It used
|
|
12
|
+
to draw its own 1px border, 12px type and a brand-coloured outline/fill while selected, and it
|
|
13
|
+
kept its label at every width, so on a narrow composer it took more room and read heavier than
|
|
14
|
+
its shipped neighbours. Its size, radius, hover, focus ring and selected state now come from the
|
|
15
|
+
shipped chip tokens (the same ones `ui-plan`'s PlanChip and the access-mode trigger use) and it
|
|
16
|
+
follows the composer row's size container: below the same 460px cut that chip uses, only the
|
|
17
|
+
14px glyph remains. Measured in one window: 72px → 30px, label hidden, glyph kept.
|
|
18
|
+
- **The collapsed chip keeps the Vision eye; the check appears only while the label is there.** A
|
|
19
|
+
bare check says nothing about what the chip does, and on a narrow row it is the only thing left
|
|
20
|
+
on screen. The 14px slot now carries both glyphs and the same container query that collapses the
|
|
21
|
+
label swaps them: the enabled chip shows the check at `@container (min-width: 460.01px)`, and the
|
|
22
|
+
eye stays below that cut. Both states use the same fixed 14px slot, so the chip width never
|
|
23
|
+
changes with the state. The guide copy no longer teaches the check as the on-signal.
|
|
24
|
+
- **A presented image lands in the message body, not only in a tool card.** The card shows a 240px
|
|
25
|
+
thumbnail and every later message pushes it up (measured on a real host: the same image drifted
|
|
26
|
+
from inside the viewport to 1692px above it within minutes). `vision_present` now also returns a
|
|
27
|
+
paste-ready `replyImage` that the visible reply carries, and the host renders an absolute image
|
|
28
|
+
path inside a message at full size through its own `api/file` route (measured in the DOM at
|
|
29
|
+
640×200 for the same image the card showed as a thumbnail). The tool card stays, and it now
|
|
30
|
+
centres itself when it mounts below the viewport — never when it is already visible, and never
|
|
31
|
+
for history above the reader.
|
|
32
|
+
- **Instant local describe keeps the Router transport (#647, issue #646).** The instant local
|
|
33
|
+
describe path now hands the Router-owned `providerTransport` to its adapter call, so a configured
|
|
34
|
+
`proxy` / `proxyHosts` covers it exactly like every other provider leg.
|
|
35
|
+
- **Credential redaction is one shared list (#653, #655).** The log sink and the benchmark-failure
|
|
36
|
+
diagnostic used to carry partial copies of the credential shapes, and each leaked what the other
|
|
37
|
+
covered. One shared list now covers `Authorization` with any scheme (including
|
|
38
|
+
`Proxy-Authorization`), `Cookie` / `Set-Cookie`, URL userinfo, `Bearer`, vendor keys (`sk-`,
|
|
39
|
+
`ghp_` / `github_pat_`, `AKIA` / `ASIA`, `xox…`, `AIza…`), JWTs, PEM blocks and `key=value`
|
|
40
|
+
assignments (including `#token=` fragments). A value that contains `;` is redacted whole, and a
|
|
41
|
+
PEM block truncated before its END marker is redacted to the end of the text.
|
|
42
|
+
- **Adapter ownership no longer accepts a primitive marker (#653).** Three modules used to read the
|
|
43
|
+
shared ownership symbol on their own, and two of them treated "the marker is not undefined" as
|
|
44
|
+
ownership. Ownership now means "an object marker is present", so a foreign adapter that writes
|
|
45
|
+
`'anything'`, `false`, `null` or `0` onto the symbol is no longer mistaken for the Router's own
|
|
46
|
+
adapter; delegation, Session Vision authority and the tool boundary all read the same rule.
|
|
47
|
+
- **Proxy host admission normalizes what it compares (#653, #655).** A bracketed IPv6 literal and
|
|
48
|
+
its bare spelling canonicalize to one host, and a `*.host` entry now matches the host it names
|
|
49
|
+
plus its subdomains instead of silently matching nothing.
|
|
50
|
+
- **The background stop cache can neither go stale nor vanish (#653, #655).** A load that drops a
|
|
51
|
+
record (expired, malformed or superseded) rewrites the file instead of leaving a stale row
|
|
52
|
+
behind, and a failed cleanup write no longer discards the records that were read.
|
|
53
|
+
- **Failure classification and numeric settings resolve own keys only (#653).** Error codes and
|
|
54
|
+
settings field names no longer resolve inherited values such as `constructor` or `__proto__`, and
|
|
55
|
+
a hostile `message` / `toString` / `code` / `status` accessor can no longer make the classifier
|
|
56
|
+
throw.
|
|
57
|
+
- **The retained-image walker is depth-bounded (#653).** It now shares the bound `convertBlocks`
|
|
58
|
+
already applies to the same block tree, so a pathological message tree returns `false` instead of
|
|
59
|
+
overflowing the stack.
|
|
60
|
+
- **Same-class hardening from the same review (#653).** Session Vision policy rows with
|
|
61
|
+
prototype-carried fields are treated as malformed; an explicit `null` options bag means "no
|
|
62
|
+
options" at four entry points instead of throwing from a parameter destructure; host capability
|
|
63
|
+
evidence is tri-state, so only an explicit `true` proves the support floor; the doctor host probe
|
|
64
|
+
is bounded even when the fetch ignores `AbortSignal`, and the error text it stores is redacted.
|
|
65
|
+
|
|
66
|
+
## Host support window
|
|
67
|
+
|
|
68
|
+
Unchanged from 3.0.1:
|
|
69
|
+
|
|
70
|
+
- DSH 0.1.x: `>=0.1.5-rc.1 <0.2.0-0`
|
|
71
|
+
- DSH 0.2.x: `>=0.2.0-rc.2 <0.3.0-0`
|
|
72
|
+
- ordinary development Host: exact `0.2.0-rc.2`
|
|
73
|
+
|
|
74
|
+
The 0.2.1 alpha line stays inside the admitted 0.2.x range and is now covered by a dedicated
|
|
75
|
+
adaptation note (`docs/architecture/dsh-021-alpha1-adaptation.md`) in addition to the scheduled
|
|
76
|
+
drift canaries.
|
|
77
|
+
|
|
78
|
+
## Validation
|
|
79
|
+
|
|
80
|
+
- every finding carries a failing-first reproduction that fails on the pre-fix commit and passes on
|
|
81
|
+
this release commit, and the four residual items found by the follow-up pass were reproduced
|
|
82
|
+
against the merged code before they were fixed;
|
|
83
|
+
- the full gate battery on the exact release commit: install, build, `build:check`, source-ownership
|
|
84
|
+
lint, source→artifact evidence, reproducible clean build, typecheck, the test suite, client-source
|
|
85
|
+
synchronization, the Host support-window contract and the packed public-API contract;
|
|
86
|
+
- a real-Host pass on this exact build against an installed DSH checkout: the Host proxy egress
|
|
87
|
+
contract, the settings lifecycle in a real browser (mount, save, reload and readback), the Desktop
|
|
88
|
+
Host injection contract (structured injections, token exchange, authenticated index and DVR RPC)
|
|
89
|
+
and the macOS Desktop renderer lifecycle with a functional vision turn;
|
|
90
|
+
- counter-probes independent of the test suite: the eight review points go from 7/8 to 8/8 and the
|
|
91
|
+
pre/post differential from 2/4 to 4/4, with the host-admission differential showing no widening
|
|
92
|
+
for non-wildcard lists;
|
|
93
|
+
- the real-Host matrix on the release pull request (Windows Node 22/24 Desktop, macOS renderer,
|
|
94
|
+
Linux real Host + Chromium), the adversarial compatibility/fuzz gates and the resource-stress
|
|
95
|
+
gates;
|
|
96
|
+
- a write-scoped CI pass: the accepted risk posture is recorded in
|
|
97
|
+
`docs/architecture/ci-risk-posture.md` and each workflow grant is narrowed to the job that needs
|
|
98
|
+
it.
|
|
99
|
+
|
|
100
|
+
## Upgrade
|
|
101
|
+
|
|
102
|
+
Drop-in replacement for 3.0.1: no settings migration, no Session format change and no new peer
|
|
103
|
+
dependency. Upgrading `dsh plugin --profile web add github:ysr666/dsh-vision-router` (or the pinned
|
|
104
|
+
`dsh-vision-router@3.0.2`) keeps existing Vision Router settings and Session data readable as
|
|
105
|
+
before.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# v3.0.3
|
|
2
|
+
|
|
3
|
+
DSH Vision Router 3.0.3 is a corrective patch on the 3.0 line. It closes the three residuals
|
|
4
|
+
reported after 3.0.2 and carries two CI-action maintenance updates. There is no runtime dependency,
|
|
5
|
+
settings migration, Session-format change, public-API change or Host-support-window change.
|
|
6
|
+
|
|
7
|
+
## Highlights
|
|
8
|
+
|
|
9
|
+
- **Presented-image focus only reacts to fresh image results (#666).** Restoring a long Session no
|
|
10
|
+
longer jumps to an old image below the viewport, and an empty `vision_present` result cannot
|
|
11
|
+
steer the reader to another card. The helper receives the exact card node instead of querying
|
|
12
|
+
the document for the last presented card.
|
|
13
|
+
- **The focus rule works across the supported Host window.** DSH 0.1.7+ exposes an explicit
|
|
14
|
+
`phase`; the supported DSH 0.1.5 train does not. The client normalizes both contracts by using
|
|
15
|
+
`phase` when present and falling back to the settled `block.kind === 'tool-result'`
|
|
16
|
+
discriminant otherwise. Restored results initialize as already settled, while a live call still
|
|
17
|
+
produces the non-result → result edge.
|
|
18
|
+
- **Leading-dot proxy hosts are no longer inert (#665).** `.example.com` now canonicalizes to the
|
|
19
|
+
same apex-and-subdomain policy as `*.example.com`. Dot-boundary matching remains conservative,
|
|
20
|
+
so lookalikes such as `evilexample.com` stay outside.
|
|
21
|
+
- **The exact DSH 0.1.7 browser smoke follows both presentation owners again (#664).** Changes to
|
|
22
|
+
either presentation-boundary carrier now trigger the exact-Host browser regression layer.
|
|
23
|
+
- **CI action maintenance (#662, #663).** `actions/download-artifact` is pinned to v8.0.1, and the
|
|
24
|
+
HOL scanner Action is pinned to v1.2.763 (plugin-scanner 3.24.2). The scanner version annotation
|
|
25
|
+
in the workflow is corrected to match the actual immutable SHA.
|
|
26
|
+
|
|
27
|
+
## Compatibility
|
|
28
|
+
|
|
29
|
+
Unchanged from 3.0.2:
|
|
30
|
+
|
|
31
|
+
- DSH 0.1.x: `>=0.1.5-rc.1 <0.2.0-0`
|
|
32
|
+
- DSH 0.2.x: `>=0.2.0-rc.2 <0.3.0-0`
|
|
33
|
+
- Node.js: `^22.19.0 || >=24.0.0`
|
|
34
|
+
|
|
35
|
+
## Validation
|
|
36
|
+
|
|
37
|
+
The fixes carry direct regression coverage for restored/empty presentation results, the legacy
|
|
38
|
+
no-`phase` Host lifecycle, leading-dot proxy admission/non-admission, and the DSH 0.1.7 workflow
|
|
39
|
+
ownership paths. The release workflow re-runs the full test, build, reproducibility, type, packed
|
|
40
|
+
API and Host-support gates on the exact release SHA before it creates the immutable tag or
|
|
41
|
+
publishes to npm.
|
|
42
|
+
|
|
43
|
+
## Upgrade
|
|
44
|
+
|
|
45
|
+
Drop-in replacement for 3.0.2. No configuration or data migration is required.
|
package/index.js
CHANGED
|
@@ -2442,6 +2442,7 @@ export function apply(ctx, config = {}, runtime = {}) {
|
|
|
2442
2442
|
memory: sessionImageMemory,
|
|
2443
2443
|
timeoutMs: timeoutMs(),
|
|
2444
2444
|
downscaleMaxPixels: instantLocalMaxPixels(),
|
|
2445
|
+
providerTransport,
|
|
2445
2446
|
})
|
|
2446
2447
|
if (instantMap.size > 0) {
|
|
2447
2448
|
ctx.logger?.info(
|
|
@@ -3859,7 +3860,10 @@ ctx.logger?.info(
|
|
|
3859
3860
|
'Present a generated local image directly to the user with the host-native image preview. ' +
|
|
3860
3861
|
'MANDATORY PRESENTATION RULE: when you generate, edit, screenshot, or export an image and want the user to see it, ' +
|
|
3861
3862
|
'you MUST call vision_present. read_image is only for model-side inspection; NEVER use read_image to present or send ' +
|
|
3862
|
-
'an image to the user.
|
|
3863
|
+
'an image to the user. ALWAYS paste the returned replyImage into your visible reply too: the host renders an ' +
|
|
3864
|
+
'absolute image path in a message as a full-size image where the reader is, while the tool card only shows a ' +
|
|
3865
|
+
'thumbnail that later messages push away. The image is retained in the session UI but sanitized out of later ' +
|
|
3866
|
+
'text-only model requests.',
|
|
3863
3867
|
parameters: {
|
|
3864
3868
|
type: 'object',
|
|
3865
3869
|
properties: {
|
|
@@ -4770,9 +4774,15 @@ ctx.logger?.info(
|
|
|
4770
4774
|
'2. 还原验证循环(本插件招牌流程):参考图 → 实现 → `vision_html_screenshot` 截图 → `vision_pixel_diff` 度量差异 → 修复 → 再截图,迭代到差异收敛(0% 是常见终点);长页面用 `fullPage: true` 一次截整页并拿到 `pageHeight`;\n' +
|
|
4771
4775
|
'3. 其余按需取用:配色用 `vision_colors`,文字用 `vision_ocr`,图标矢量化用 `vision_trace`,纯色背景抠图用 `vision_extract_foreground`,本地 HTML 截图用 `vision_html_screenshot`(长页面加 `fullPage: true` 截整页);\n' +
|
|
4772
4776
|
'4. 展示规则(必须遵守):当你生成、编辑、截图或导出一张图片,并希望用户看到它时,必须调用 `vision_present`。' +
|
|
4773
|
-
'`read_image` 仅用于你自己读取或检查图片内容,绝不能把 `read_image`
|
|
4777
|
+
'`read_image` 仅用于你自己读取或检查图片内容,绝不能把 `read_image` 当成向用户展示或发送图片的方法。' +
|
|
4778
|
+
'调用后必须把返回的 `replyImage` 原样放进你的可见回复:宿主会把消息里的绝对图片路径渲染成完整尺寸的图,' +
|
|
4779
|
+
'而工具卡片只显示缩略图、且会被后续消息顶出视野。\n' +
|
|
4780
|
+
|
|
4774
4781
|
' MANDATORY PRESENTATION RULE: when you generate, edit, screenshot, or export an image and want the user to see it, ' +
|
|
4775
|
-
'you MUST call `vision_present`. `read_image` is only for your own model-side inspection; NEVER use `read_image` to present or send an image to the user
|
|
4782
|
+
'you MUST call `vision_present`. `read_image` is only for your own model-side inspection; NEVER use `read_image` to present or send an image to the user. ' +
|
|
4783
|
+
'Also paste the returned `replyImage` into your visible reply: the host renders an absolute image path in a ' +
|
|
4784
|
+
'message at full size, while the tool card only shows a thumbnail that later messages push out of view.\n' +
|
|
4785
|
+
|
|
4776
4786
|
'5. 所有坐标都是原图像素(x1/y1/x2/y2);上传的图片可以直接用其附件 ID(如 `sha256:…`)作为各工具的 image 参数,无需先找磁盘路径。' +
|
|
4777
4787
|
'All coordinates are original pixels (x1/y1/x2/y2); uploaded images can be referenced directly by their attachment id (e.g. `sha256:…`) as the image argument. 产物写入工作区 `' +
|
|
4778
4788
|
`${artifactsRel}` +
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single owner of the Vision Router adapter ownership marker.
|
|
3
|
+
*
|
|
4
|
+
* Three modules used to interpret the same process-wide symbol on their own, and
|
|
5
|
+
* two of them treated "the marker is not undefined" as ownership — so a foreign
|
|
6
|
+
* adapter that wrote `'anything'`, `false`, `null` or `0` onto the shared symbol
|
|
7
|
+
* was classified as Vision Router's own adapter. Ownership now means "a marker
|
|
8
|
+
* *object* is present"; the per-function notes below record what that does and
|
|
9
|
+
* does not prove.
|
|
10
|
+
*
|
|
11
|
+
* The symbol stays `Symbol.for(...)` so separately loaded copies of this package
|
|
12
|
+
* still recognize each other's adapters; the rule about its *value* lives here
|
|
13
|
+
* once.
|
|
14
|
+
*/
|
|
15
|
+
export const VISION_ROUTER_ADAPTER_OWNER = Symbol.for('dsh-vision-router.adapter-owner');
|
|
16
|
+
/** Read the marker without letting a hostile proxy/getter escape. */
|
|
17
|
+
export function readVisionRouterAdapterOwner(adapter) {
|
|
18
|
+
if (adapter === null || (typeof adapter !== 'object' && typeof adapter !== 'function')) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
return adapter[VISION_ROUTER_ADAPTER_OWNER];
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* True when the adapter carries an owner marker object.
|
|
30
|
+
*
|
|
31
|
+
* The marker's value has always been an object — the registration chain mints
|
|
32
|
+
* `Object.freeze({})`, and composition/tests mark adapters with plain records
|
|
33
|
+
* such as `{ route: 'deepseek-vision' }`. Requiring "an object" therefore keeps
|
|
34
|
+
* every genuine owner (including ones marked by another loaded copy) and rejects
|
|
35
|
+
* the values a foreign adapter can write by accident or in one line: `'anything'`,
|
|
36
|
+
* `false`, `null`, `0` and any other primitive.
|
|
37
|
+
*
|
|
38
|
+
* A first version of this fix also demanded `Object.isFrozen(owner)`. The
|
|
39
|
+
* repository's own issue-276/289/374/512 suites mark adapters with plain objects,
|
|
40
|
+
* so that extra requirement classified genuine owners as foreign and broke
|
|
41
|
+
* delegation; it was reverted here instead of being worked around in the tests.
|
|
42
|
+
*
|
|
43
|
+
* Residual risk, stated rather than hidden: in-process code able to run arbitrary
|
|
44
|
+
* JavaScript can still assign `{}` and be treated as an owner. Removing that needs
|
|
45
|
+
* one shared authority instead of a process-wide symbol, which is a larger design
|
|
46
|
+
* change than this fix.
|
|
47
|
+
*/
|
|
48
|
+
export function isVisionRouterOwnedAdapter(adapter) {
|
|
49
|
+
const owner = readVisionRouterAdapterOwner(adapter);
|
|
50
|
+
return typeof owner === 'object' && owner !== null;
|
|
51
|
+
}
|
|
@@ -432,21 +432,21 @@ export const CLIENT_PRESENTATION_PRELUDE = String.raw`(function(){
|
|
|
432
432
|
if (next.zh && typeof next.zh === 'object') {
|
|
433
433
|
next.zh = Object.assign({}, next.zh, {
|
|
434
434
|
quickStartTitle: '聊天模型 + 识图模式',
|
|
435
|
-
quickStartBody: '
|
|
435
|
+
quickStartBody: '先选择你平时使用的聊天模型。需要看图时,点击输入框旁的「识图」;它变成选中态(高亮底色)表示已开启。开启后会持续生效;在同一模型组内切换到另一个支持识图模式的模型也会保持开启,直到你主动关闭或切到没有对应识图模式的普通模型。',
|
|
436
436
|
onboardingStep1Title: '1 · 选择聊天模型并开启识图',
|
|
437
|
-
onboardingStep1Body: '
|
|
437
|
+
onboardingStep1Body: '先在聊天页右下角选择你平时使用的模型。需要看图时,点击模型选择器左侧的「识图」;它变成选中态(高亮底色)表示已开启,不需要时再主动关闭。',
|
|
438
438
|
guideStep1Title: '第 1 步 · 选择聊天模型并认识「识图」',
|
|
439
|
-
guideStep1Body: '
|
|
439
|
+
guideStep1Body: '高亮的是聊天模型选择器;它左侧就是「识图」按钮。先选择你平时使用的聊天模型;需要看图时点击「识图」,它变成选中态(高亮底色)表示已开启。开启后会持续生效;在同一模型组内切换到另一个支持识图模式的模型也会保持开启,直到你主动关闭或切到没有对应识图模式的普通模型。选好后点击「下一步」。'
|
|
440
440
|
});
|
|
441
441
|
}
|
|
442
442
|
if (next.en && typeof next.en === 'object') {
|
|
443
443
|
next.en = Object.assign({}, next.en, {
|
|
444
444
|
quickStartTitle: 'Chat model + Vision mode',
|
|
445
|
-
quickStartBody: 'Choose the chat model you normally use first. When you need image understanding, click “Vision” beside the composer;
|
|
445
|
+
quickStartBody: 'Choose the chat model you normally use first. When you need image understanding, click “Vision” beside the composer; the chip lights up in its selected state to mean it is on. It stays on when you switch to another Vision-enabled model in the same model group, until you turn it off or choose a normal model with no matching Vision route.',
|
|
446
446
|
onboardingStep1Title: '1 · Choose your chat model and enable Vision',
|
|
447
|
-
onboardingStep1Body: 'Choose the model you normally use from the lower-right chat selector. When you need image understanding, click “Vision” immediately to the left of the model selector;
|
|
447
|
+
onboardingStep1Body: 'Choose the model you normally use from the lower-right chat selector. When you need image understanding, click “Vision” immediately to the left of the model selector; the chip lights up in its selected state to mean it is on. Turn it off again when you no longer need it.',
|
|
448
448
|
guideStep1Title: 'Step 1 · Choose your chat model and find “Vision”',
|
|
449
|
-
guideStep1Body: 'The highlighted control is the chat model selector; the “Vision” button is immediately to its left. Choose your normal chat model first, then click “Vision” when you need image understanding.
|
|
449
|
+
guideStep1Body: 'The highlighted control is the chat model selector; the “Vision” button is immediately to its left. Choose your normal chat model first, then click “Vision” when you need image understanding. The chip lights up in its selected state to mean it is on. It stays on when you switch to another Vision-enabled model in the same model group, until you turn it off or choose a normal model with no matching Vision route. Click “Next” when done.'
|
|
450
450
|
});
|
|
451
451
|
}
|
|
452
452
|
return next;
|
|
@@ -948,6 +948,99 @@ export const CLIENT_PRESENTATION_PRELUDE = String.raw`(function(){
|
|
|
948
948
|
}, 'vision-router: clipboard image paste normalization');
|
|
949
949
|
}
|
|
950
950
|
|
|
951
|
+
// The composer chip chrome follows the shipped mode chips (ui-plan's PlanChip and the
|
|
952
|
+
// access-mode trigger): 28px tall, token radius, no border, token hover fill, the shipped
|
|
953
|
+
// focus ring, and the shipped "selected chip" treatment while Vision is on. The composer row
|
|
954
|
+
// is a size container (InputBar .row), so the label collapses to the 14px glyph below the
|
|
955
|
+
// shipped 460px cut — the same cut the access-mode trigger uses — instead of squeezing the
|
|
956
|
+
// model trigger on narrow windows.
|
|
957
|
+
var VISION_TOGGLE_CSS = '' +
|
|
958
|
+
'.vr-vision-toggle{display:inline-flex;align-items:center;gap:4px;min-width:0;height:28px;' +
|
|
959
|
+
'padding:0 8px;border:none;border-radius:var(--dsw-radius-sm);background:transparent;' +
|
|
960
|
+
'color:var(--dsw-alias-label-secondary);font:inherit;font-size:13px;font-weight:500;' +
|
|
961
|
+
'line-height:20px;cursor:pointer;white-space:nowrap}' +
|
|
962
|
+
'.vr-vision-toggle:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}' +
|
|
963
|
+
'.vr-vision-toggle:focus-visible{outline:var(--dsw-focus-ring-width) solid ' +
|
|
964
|
+
'var(--dsw-focus-ring-color,var(--dsw-alias-brand-primary));outline-offset:2px}' +
|
|
965
|
+
'.vr-vision-toggle:disabled{cursor:default}' +
|
|
966
|
+
// Dim only the states that read as "cannot be used right now". A disabled chip that is
|
|
967
|
+
// merely waiting for this toggle's own selection keeps full opacity, so the composer does
|
|
968
|
+
// not flash a greyed control while the model swap it owns is still settling.
|
|
969
|
+
'.vr-vision-toggle[data-dimmed="true"]{opacity:.45}' +
|
|
970
|
+
'.vr-vision-toggle[data-active="true"]{color:var(--dsw-alias-label-primary);' +
|
|
971
|
+
'background:var(--dsw-alias-button-ghost-active-fill);' +
|
|
972
|
+
'box-shadow:inset 0 0 0 1px var(--dsw-alias-button-ghost-active-border)}' +
|
|
973
|
+
'.vr-vision-toggle[data-active="true"]:hover:not(:disabled){' +
|
|
974
|
+
'background:var(--dsw-alias-button-ghost-active-hover)}' +
|
|
975
|
+
'.vr-vision-toggle-glyph{display:inline-flex;align-items:center;justify-content:center;' +
|
|
976
|
+
'flex:0 0 auto;width:14px;height:14px;font-size:13px;line-height:1;color:currentColor}' +
|
|
977
|
+
'.vr-vision-toggle-glyph-part{display:inline-flex;align-items:center;justify-content:center;' +
|
|
978
|
+
'width:14px;height:14px}' +
|
|
979
|
+
// The check carries the state only while the label is there to explain it. Once the label
|
|
980
|
+
// collapses (the shipped 460px cut below) a bare check means nothing, so the Vision eye —
|
|
981
|
+
// the only thing that still says what the chip does — stays, and the chip's selected chrome
|
|
982
|
+
// marks on/off on its own.
|
|
983
|
+
'.vr-vision-toggle-glyph-check{display:none}' +
|
|
984
|
+
'@container (min-width:460.01px){' +
|
|
985
|
+
'.vr-vision-toggle[data-active="true"] .vr-vision-toggle-glyph-eye{display:none}' +
|
|
986
|
+
'.vr-vision-toggle[data-active="true"] .vr-vision-toggle-glyph-check{display:inline-flex}}' +
|
|
987
|
+
'@container (max-width:460px){.vr-vision-toggle-label{display:none}}';
|
|
988
|
+
|
|
989
|
+
function installVisionToggleStyles() {
|
|
990
|
+
if (typeof document === 'undefined' || !document.head) return undefined;
|
|
991
|
+
var tag = document.createElement('style');
|
|
992
|
+
tag.dataset.plugin = 'dsh-vision-router';
|
|
993
|
+
tag.dataset.pluginCss = 'dsh-vision-router/mode-toggle';
|
|
994
|
+
tag.textContent = VISION_TOGGLE_CSS;
|
|
995
|
+
document.head.appendChild(tag);
|
|
996
|
+
return function(){ tag.remove(); };
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// A fresh presented image below the reader is the same as not being shown. Centre only that
|
|
1000
|
+
// card; visible cards and history on either side of a restored viewport stay untouched.
|
|
1001
|
+
function focusPresentedCard(node, viewportHeight) {
|
|
1002
|
+
if (!node || typeof node.getBoundingClientRect !== 'function') return false;
|
|
1003
|
+
var rect = node.getBoundingClientRect();
|
|
1004
|
+
if (rect.top <= viewportHeight) return false;
|
|
1005
|
+
var scroller = node.parentElement;
|
|
1006
|
+
var hops = 0;
|
|
1007
|
+
while (scroller && hops < 20) {
|
|
1008
|
+
var style = typeof getComputedStyle === 'function' ? getComputedStyle(scroller) : undefined;
|
|
1009
|
+
var scrollable = /(auto|scroll)/.test(String(style && style.overflowY)) &&
|
|
1010
|
+
scroller.scrollHeight > scroller.clientHeight + 4;
|
|
1011
|
+
if (scrollable) {
|
|
1012
|
+
var box = scroller.getBoundingClientRect();
|
|
1013
|
+
scroller.scrollTop += (rect.top - box.top) - (box.height / 2 - rect.height / 2);
|
|
1014
|
+
return true;
|
|
1015
|
+
}
|
|
1016
|
+
scroller = scroller.parentElement;
|
|
1017
|
+
hops += 1;
|
|
1018
|
+
}
|
|
1019
|
+
return false;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// New Hosts expose phase; supported DSH 0.1.5 instead marks settled blocks as
|
|
1023
|
+
// 'tool-result'. Normalize the [phase, block kind] tuple without raising the Host floor.
|
|
1024
|
+
function isPresentedResultState(state) {
|
|
1025
|
+
var phase = state && state[0];
|
|
1026
|
+
return phase !== undefined ? phase === 'result' : !!state && state[1] === 'tool-result';
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// Restored history starts settled on both sides of the comparison. Only a live transition into
|
|
1030
|
+
// an image-bearing result is fresh presentation.
|
|
1031
|
+
function shouldFocusPresentedCard(previousState, state, imageCount) {
|
|
1032
|
+
return imageCount > 0 &&
|
|
1033
|
+
isPresentedResultState(state) &&
|
|
1034
|
+
!isPresentedResultState(previousState);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
try {
|
|
1038
|
+
globalThis.__dvrPresentFocus = function (node, previousState, state, imageCount) {
|
|
1039
|
+
return shouldFocusPresentedCard(previousState, state, imageCount) &&
|
|
1040
|
+
focusPresentedCard(node, globalThis.innerHeight || 0);
|
|
1041
|
+
};
|
|
1042
|
+
} catch (_) {}
|
|
1043
|
+
|
|
951
1044
|
function installVisionModeToggle(ctx, React, primitives) {
|
|
952
1045
|
if (!ctx || typeof ctx.inject !== 'function' || !React) return;
|
|
953
1046
|
var zh = {
|
|
@@ -974,6 +1067,9 @@ export const CLIENT_PRESENTATION_PRELUDE = String.raw`(function(){
|
|
|
974
1067
|
try {
|
|
975
1068
|
ctx.effect(function(){ return ctx.locale.register(VISION_MODE_NS, { zh: zh, en: en }); }, 'vision-router: mode toggle locale');
|
|
976
1069
|
} catch (_) {}
|
|
1070
|
+
try {
|
|
1071
|
+
ctx.effect(function(){ return installVisionToggleStyles(); }, 'vision-router: mode toggle styles');
|
|
1072
|
+
} catch (_) {}
|
|
977
1073
|
|
|
978
1074
|
function FallbackToast(props) {
|
|
979
1075
|
React.useEffect(function(){
|
|
@@ -1004,9 +1100,13 @@ export const CLIENT_PRESENTATION_PRELUDE = String.raw`(function(){
|
|
|
1004
1100
|
}
|
|
1005
1101
|
|
|
1006
1102
|
var ToastComponent = primitives && typeof primitives.Toast === 'function' ? primitives.Toast : FallbackToast;
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1103
|
+
// The product icon set names weights, not sizes (IconWarningOutlineRegular); the old
|
|
1104
|
+
// "…16" spelling never existed, so the toast silently fell back to a text glyph.
|
|
1105
|
+
var WarningIcon = primitives && typeof primitives.IconWarningOutlineRegular === 'function'
|
|
1106
|
+
? primitives.IconWarningOutlineRegular
|
|
1107
|
+
: primitives && typeof primitives.IconWarningOutline16 === 'function'
|
|
1108
|
+
? primitives.IconWarningOutline16
|
|
1109
|
+
: undefined;
|
|
1010
1110
|
|
|
1011
1111
|
var settings = bindVisionModeSettings(ctx);
|
|
1012
1112
|
var unavailableSettingsState = { value: undefined };
|
|
@@ -1106,29 +1206,6 @@ export const CLIENT_PRESENTATION_PRELUDE = String.raw`(function(){
|
|
|
1106
1206
|
: livePair.mode === 'unavailable'
|
|
1107
1207
|
? t('unavailable')
|
|
1108
1208
|
: active ? t('disable') : t('enable');
|
|
1109
|
-
var style = {
|
|
1110
|
-
appearance: 'none',
|
|
1111
|
-
minHeight: 28,
|
|
1112
|
-
display: 'inline-flex',
|
|
1113
|
-
alignItems: 'center',
|
|
1114
|
-
gap: 5,
|
|
1115
|
-
padding: '4px 8px',
|
|
1116
|
-
borderRadius: 8,
|
|
1117
|
-
border: '1px solid ' + (active ? 'var(--dsw-alias-brand-primary)' : 'var(--dsw-alias-border-l2)'),
|
|
1118
|
-
background: active
|
|
1119
|
-
? 'color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, transparent)'
|
|
1120
|
-
: 'transparent',
|
|
1121
|
-
color: active ? 'var(--dsw-alias-brand-primary)' : 'var(--dsw-alias-label-secondary)',
|
|
1122
|
-
boxShadow: active ? 'inset 0 0 0 1px var(--dsw-alias-brand-primary)' : 'none',
|
|
1123
|
-
font: 'inherit',
|
|
1124
|
-
fontSize: 12,
|
|
1125
|
-
lineHeight: 1.4,
|
|
1126
|
-
fontWeight: active ? 650 : 500,
|
|
1127
|
-
cursor: disabled ? 'default' : 'pointer',
|
|
1128
|
-
opacity: disabled && !ownSettling && (livePair.mode === 'unavailable' || loading) ? 0.45 : 1,
|
|
1129
|
-
whiteSpace: 'nowrap'
|
|
1130
|
-
};
|
|
1131
|
-
|
|
1132
1209
|
function announceRejectedSelection() {
|
|
1133
1210
|
var latest;
|
|
1134
1211
|
try {
|
|
@@ -1155,7 +1232,9 @@ export const CLIENT_PRESENTATION_PRELUDE = String.raw`(function(){
|
|
|
1155
1232
|
'aria-busy': busy || loading,
|
|
1156
1233
|
title: title,
|
|
1157
1234
|
disabled: disabled,
|
|
1158
|
-
|
|
1235
|
+
className: 'vr-vision-toggle',
|
|
1236
|
+
'data-active': active ? 'true' : 'false',
|
|
1237
|
+
'data-dimmed': disabled && !ownSettling && (livePair.mode === 'unavailable' || loading) ? 'true' : 'false',
|
|
1159
1238
|
onClick: function() {
|
|
1160
1239
|
if (disabled || !pair.target || typeof props.select !== 'function') return;
|
|
1161
1240
|
var target = pair.target;
|
|
@@ -1179,25 +1258,19 @@ export const CLIENT_PRESENTATION_PRELUDE = String.raw`(function(){
|
|
|
1179
1258
|
});
|
|
1180
1259
|
}
|
|
1181
1260
|
},
|
|
1182
|
-
// One fixed 14px leading slot carries
|
|
1183
|
-
//
|
|
1184
|
-
//
|
|
1185
|
-
//
|
|
1186
|
-
//
|
|
1261
|
+
// One fixed 14px leading slot carries both glyphs: the Vision eye and the check that
|
|
1262
|
+
// marks the enabled state. The stylesheet shows the check only while the label is
|
|
1263
|
+
// present (the same 460px container cut that collapses the label), because a bare check
|
|
1264
|
+
// tells a reader nothing — once the label is gone the eye, which still says what the
|
|
1265
|
+
// chip does, stays. The slot is a fixed 14px box either way, so the chip width never
|
|
1266
|
+
// changes with the state.
|
|
1187
1267
|
React.createElement('span', {
|
|
1188
1268
|
'aria-hidden': 'true',
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
width: 14,
|
|
1195
|
-
height: 14,
|
|
1196
|
-
fontSize: 13,
|
|
1197
|
-
lineHeight: 1
|
|
1198
|
-
}
|
|
1199
|
-
}, active ? '✓' : '👁'),
|
|
1200
|
-
React.createElement('span', null, t('label'))
|
|
1269
|
+
className: 'vr-vision-toggle-glyph'
|
|
1270
|
+
},
|
|
1271
|
+
React.createElement('span', { className: 'vr-vision-toggle-glyph-part vr-vision-toggle-glyph-eye' }, '👁'),
|
|
1272
|
+
React.createElement('span', { className: 'vr-vision-toggle-glyph-part vr-vision-toggle-glyph-check' }, '✓')),
|
|
1273
|
+
React.createElement('span', { className: 'vr-vision-toggle-label' }, t('label'))
|
|
1201
1274
|
);
|
|
1202
1275
|
|
|
1203
1276
|
var toastNode = toast
|