Repository navigation
Releases: 969246694/dsh-transparent
Release list
dsh-transparent 1.4.0 — settings panel renders
dsh-transparent 1.4.0 — the settings panel actually renders
What was wrong
1.3.3 registered a Config schema and nothing appeared. The schema was
accepted — the host reports status: "schema" — but a schema renders nothing
by itself.
The Plugins page draws a bundle's configuration only for a bundle that
registered one, and registration is a browser-half job:
ctx.inject(['slots'], (child) => child.slots.register(
{ name: 'plugins.bundle.config', key: 'dsh-transparent' }, Panel))The page then renders that section between the bundle's description and its
rows — where you pointed, and where a reader looks for it.
How it reads and writes
Values come from ctx.remote.settings, the host's own configuration API:
describe()→ every namespace with its live value and revisionmutate(ns, ops, revision)→ path-addressed edits
The revision read is passed back, so the write is a compare-and-set — a
concurrent edit is reported rather than silently overwritten.
Why the slot service is optional
It is requested with ctx.inject([...]), not declared in the plugin's inject
list. A hard dependency would stop the whole plugin from applying if the
service were ever absent — and applying is what makes the window translucent.
The panel is optional; the window is not. Every failure path leaves the
plugin running.
Three attempts
| problem | |
|---|---|
| 1.3.0 | hand-written schema missing ~standard → plugin failed to start |
| 1.3.2 | schema could not resolve → no form |
| 1.4.0 | — |
The first two were about making the host half describe the options. Neither
was about the half that draws them.
Restart the client to load it.
dsh-transparent 1.3.3 — settings schema resolves
dsh-transparent 1.3.3 — the settings schema now actually resolves
1.3.2 guessed that the application's module-resolution rewrite might let a
profile plugin import @deepseek-ai/schemastery. The restart answered it:
--- activated v1.3.2 (options: 0)
The rewrite covers a plugin's entry specifier — that is how bundled plugins
load by package name — but it does not extend to the imports inside a profile
plugin. The guarded import did its job: the plugin ran, and simply had no
settings form.
The fix
schemastery and its one runtime dependency are now vendored under
lib/vendor/:
| file | from | version | licence |
|---|---|---|---|
schemastery.mjs |
@deepseek-ai/schemastery |
3.18.4 | MIT |
cosmokit.mjs |
@deepseek-ai/cosmokit |
1.8.5 | MIT |
One import path rewritten. Both licences copied alongside, and NOTICE.md records
the provenance, the versions and the exact edit. The plugin stays
dependency-free — the same reason the stylesheet and the sprites ship inline.
Package size 55.8 kB, 17 files.
Verified locally, not assumed
Config exists true
Config['~standard'].validate function <- its absence broke 1.3.0
volatile fields resolve to { get() } <- why readValue exists
defaults applied alpha 215
verify.mjs asserts all four, plus that the vendored files are actually present
in the package — so a missing lib/vendor/ fails in CI instead of at load.
34 checks, all passing.
After updating
Restart the client. The settings UI should then show four controls: alpha,
darkTheme, updateCheck, updateSource.
If it does not, the log line says which case you are in:
--- activated v1.3.3 (options: 4) settings form available
--- activated v1.3.3 (options: 0) plugin fine, no form
dsh-transparent 1.3.2 — fixes the startup failure in 1.3.0
dsh-transparent 1.3.2 — fixes the startup failure in 1.3.0
If you installed 1.3.0 or 1.3.1, the plugin would not start. The application
reported it as 启动失败. Sorry — that was my fault, and the cause is specific.
What went wrong
1.3.0 declared a hand-written Config schema. The settings service reads such a
schema for three things — meta.volatile, ype/dict, and oJSON() — and
every one of them was verified against a genuine schemastery schema. All passed.
Loading uses one more thing that reading the settings service could never
reveal:
\\js
function resolveConfig(runtime, config) {
if (!runtime.Config) return config
const result = runtime.Config['~standard'].validate(config) // Standard Schema
...
}
\\
Config is validated through the Standard Schema interface. Schemastery
provides it; a hand-written object does not. Missing it throws during load and
takes the whole plugin down.
I verified the consumer I happened to be reading, and not the loader.
The fix
Stop imitating the schema, use the real one:
- \Config\ is now \z.object({...})\ from @deepseek-ai/schemastery.
- The import is guarded. It is the only part of the host half that depends on
how the application resolves modules for a profile plugin. If it ever stops
resolving, the plugin still runs and simply has no settings form — a
degradation instead of an outage.
If you are on 1.3.0 / 1.3.1
Update to 1.3.2 and restart the client. Re-enabling the plugin is not enough:
the host half is imported once at startup and toggling reuses the cached module.
Verification
\�erify.mjs\ now checks for ~standard.validate\ on any exported Config —
that is the difference between a working plugin and a broken one — and checks
that the import stays guarded. In plain Node the import does not resolve, so the
check reports the degradation rather than failing on it, which also proves the
fallback path works.
dsh-transparent 1.3.1
dsh-transparent 1.3.1
Every activation now says which build is running
\
--- activated v1.3.1 (options: 4)
\\
Written to %TEMP%\dsh-transparent.log\ on every activation, together with the
existing lines. The option count is not decoration — it is what differs between
a build with no Config schema and one with it, which was previously invisible
from outside.
Why this exists: two questions that could not be answered
Is the running plugin the code on disk? Does the settings UI need a restart?
Testing settled both. The host half is imported once at startup, and
re-enabling the plugin re-runs �pply() from the cached module — ESM modules
are cached by URL, so toggling off and on does not pick up an edit. Verified
by adding a line only the new code prints, clearing the log, toggling the plugin,
and watching the two old lines appear while the new one did not.
What this means in practice
| changed | takes effect |
|---|---|
| \lib/client.js\ — the interface, glass, refraction | refresh the page (\Ctrl+Shift+R) |
| \lib/index.js\ — window alpha, theme, update check, config schema | restart the client |
A page refresh only re-fetches the browser half through the /plugins\ route.
The host half runs in a different process and the refresh cannot reach it — which
is why the settings UI added in 1.3.0 needs a restart.
dsh-transparent 1.3.0
dsh-transparent 1.3.0
The options are now in the settings UI
alpha, darkTheme, updateCheck and updateSource appear in the
application's own settings UI with real controls, instead of only being editable
by hand in YAML.
Why the schema is hand-written
Declaring a Config schema normally means z.object({...}) from
@deepseek-ai/schemastery. A plugin installed under
<profile>/node_modules cannot import that package — it ships inside the
application bundle. Measured, not assumed:
import.meta.resolve('@deepseek-ai/schemastery') -> ERR_MODULE_NOT_FOUND
So the schema is a plain object shaped the way the settings service reads one.
That shape came from the service's own source, not from guesswork: it reads
exactly three things, and it is duck-typed throughout — no instanceof.
| read | what it needs |
|---|---|
isVolatilePath |
schema.meta.volatile, schema.dict[key] |
projectForm |
schema.type, schema.dict |
plainSchema |
new z(schema.toJSON()) |
Every one of those was replicated against a genuine z.object(...volatile())
schema taken out of the application bundle, and produces identical results.
Two mistakes that testing caught
dictholds objects,refshold uids. The live schema'sdictmust
contain child schema objects, becauseisVolatilePathwalks into them;
only the serialisedrefsmap uses uid strings. A first draft used uid strings
in both, which would have made every option read as non-volatile — an empty
form, and nothing logged anywhere.- Every node needs
toJSON(), not just the root:volatileFormcalls
plainSchemaon each volatile node individually.
And one that would have broken the plugin outright
Marking a field volatile changes how it is delivered. A volatile field
arrives at apply() as an accessor object with a get() — not as the value:
config.alpha // { get() } <- volatile
config.alpha.get() // 200
config.plain // 200 <- ordinaryReading cfg.alpha directly would have silently reverted every option to its
default, with no error attached to it. All four options now go through an
unwrapping helper that accepts either shape — so the plugin also keeps working
if a future runtime stops resolving the schema at all.
Verification
verify.mjs grew a section for this: 33 checks, all passing. It tests the
schema against the service's own logic and does not import schemastery,
which is not installable from a plugin and would make CI fail for the wrong
reason.
dsh-transparent 1.2.1
dsh-transparent 1.2.1
autoUpdate is gone
1.2.0 added both an update check and the ability to fetch the update. The second
half was removed, because it rewrote your profile to buy nothing: a new version
only starts running after a restart either way, and a failure part way through
leaves the plugin removed.
Updating is already one step you have — reinstall through the plugin manager, or
install_bundle dsh-transparent, which re-runs pnpm and takes the newest version
in range.
What stayed, and why
The plugin manager has no update affordance and no version check — there is
no outdated, upgrade or checkForUpdate anywhere in it. So nothing in the
application will ever tell you a newer version exists. Without the check you stay
on whatever you installed, which is a real gap rather than a hypothetical one.
It is one small request against the registry your profile is configured for
(including the China mirror), its failure is silent, and it now says plainly what
to do:
[transparent] v1.2.1 is available (running 1.2.0) — reinstall the plugin to update
Config
config:
alpha: 215 # whole-window opacity
darkTheme: true # pin the interface to the dark theme
updateCheck: true # say so when a newer version exists
updateSource: "" # override where the version is read fromIf you are on 1.2.0
Nothing breaks. autoUpdate is simply ignored from 1.2.1 on, and it defaulted to
off anyway.
Still true
The liquid-glass composer is implemented and its refraction is real, but it stays
barely visible: backdrop-filter only sees content inside the page, and nothing
scrolls behind the composer. See the README.
dsh-transparent 1.2.0
Superseded by 1.2.1. The
autoUpdateoption described below was removed: it rewrote the profile to buy nothing, since a restart was needed either way. Nothing else changed.
dsh-transparent 1.2.0
New: built-in update check
The plugin manager has no update action — an installed bundle cannot be
re-installed over itself through the GUI, and there is no outdated, upgrade
or checkForUpdate code path anywhere in it. So the host half now checks for
itself, through the pluginManager service the manager already exposes:
[transparent] v1.2.0 is available (running 1.1.0) — set autoUpdate: true to fetch it automatically
registries()is read first, so the check goes to whichever registry your
profile is configured for — including the China mirror, rather than being sent
to a host you cannot reach.installBundle()is the update itself, used only whenautoUpdateis on.- The check costs one small request. A failure is silent: an offline machine, a
private registry or a blocked route is not worth a warning.
autoUpdate is off by default, on purpose
Turning it on makes the plugin rewrite your profile. A failure part way
through leaves the plugin removed. That risk belongs to the person who chose
it, not to the default.
Either way, a restart is what actually runs the new version.
A working copy is never "updated"
The check is skipped unless the package sits under node_modules. The obvious
test — "is my own directory a symlink?" — does not work, because Node resolves
symlinks and a linked install reports the real path of the checkout. The path
itself is the reliable signal. Without this, running from a checkout could
replace your source tree with a published tarball.
Config
config:
alpha: 215 # whole-window opacity
darkTheme: true # pin the interface to the dark theme
updateCheck: true # say so when a newer version exists
autoUpdate: false # fetch it instead of just saying so
updateSource: "" # override where the version is read fromAlso fixed
A helper that exits non-zero carries no reason, so the failure line read
FAIL undefined and threw away the exit code and stderr — the only diagnostics
that existed. Found while testing this release.
Still true
The liquid-glass composer is implemented and its refraction is real, but it stays
barely visible: backdrop-filter only sees content inside the page, and nothing
scrolls behind the composer. See the README.
dsh-transparent 1.1.0
dsh-transparent 1.1.0
New: the interface is pinned to the dark theme
Translucency over a light interface reads as grey rather than glass, so the
plugin now sets the theme to dark on activation.
It goes through the host settings service — writing ui-theme.preference —
rather than setting an attribute on <body>. The theme is a durable user
preference the host owns; an attribute would be re-imposed by the next render,
and the change would not survive a restart or appear in the settings UI.
Two details worth knowing:
- It reads before writing. If the preference is already dark nothing is
written, so startup does not rewrite the settings document every time. - The write is a compare-and-set. The revision read from
describe()is
passed back toupdate(), so a change you made in the settings UI in the
meantime is not silently overwritten.
Turn it off with darkTheme: false. Uninstalling does not restore the previous
preference — if you want your own choice respected, set that flag.
Config
config:
alpha: 215 # whole-window opacity
darkTheme: true # pin the interface to the dark themeStill true from 1.0.x
The liquid-glass composer is implemented and its refraction is real, but it stays
barely visible: backdrop-filter only sees content inside the page, and nothing
scrolls behind the composer. See the README.
dsh-transparent 1.0.1
Docs-only patch. The npm page shows the README from inside the tarball, and
1.0.0 shipped before the install section described the mirror route.
Install from npm (no GitHub access needed) — set the install source to
中国大陆镜像源 (registry.npmmirror.com) and use the package name:
dsh-transparent
Everything else is unchanged from 1.0.0. See that release for the full notes,
including the honest limitations: a GitHub URL is fetched by git and bypasses
npm, so the mirror only helps the npm route.
dsh-transparent 1.0.0
dsh-transparent 1.0.0
Unofficial plugin for the DeepSeek Harness desktop client. Windows only.
What it does
| Effect | Status |
|---|---|
| Window becomes translucent, wallpaper shows through | Works |
| The app's opaque background is cleared | Works |
| The composer refracts its backdrop instead of just blurring it | Implemented, but barely visible in practice |
Please read before installing: what you will actually see is a translucent
window. The liquid-glass implementation is real — it generates a normal map of
the composer's rounded rectangle at runtime and feeds it to an SVG
feDisplacementMap through backdrop-filter: url() — but refraction needs
content passing behind it, and nothing scrolls behind the composer because the
message list ends above it. Details in the README.
Install
plugin_manager install_bundle https://github.com/969246694/dsh-transparent
Pin this release instead of tracking main:
plugin_manager install_bundle https://github.com/969246694/dsh-transparent#v1.0.0
Then restart the client. lib/client.js is committed, so no build step is needed
after installing.
Configuration
cordis.patch.yml:
config:
alpha: 215 # 255 = opaque and crisp, 215 = default, 150 = wallpaper obviousDesign tokens live in dsh-transparent.css — edit, then run node build.mjs;
the browser half hot-reloads.
Architecture, in one paragraph
Styles are applied with CSS selectors that match by structure, not by JavaScript
marking elements. Marking cannot avoid flicker: the app replaces its own elements
when it re-renders, the replacement carries no marker, the rule stops applying,
and the plugin re-applies — and that gap is visible. So the browser half does
exactly two things, both once: resolve how deeply the composer sits above the
editable element and append one :has() rule for that depth, and build the lens
map. There is no MutationObserver, no scheduler and no re-marking. If the
composer is replaced a thousand times, the browser matches it a thousand times
and none of the plugin's code runs.
Quality gates
- Zero dependencies.
build.mjsandverify.mjsuse only Node's standard library. verify.mjscovers the client-half contract, packaging, the throwing-global
traps, YAML validity, ASCII purity of the PowerShell helper, and build drift.- CI runs both jobs on Windows; both are green.
中文
面向 DSH 桌面客户端的非官方插件,仅支持 Windows。
| 效果 | 状态 |
|---|---|
| 窗口变半透明,桌面壁纸透出来 | 有效 |
| 应用的不透明背景被清掉 | 有效 |
| 输入框折射背景,而不只是模糊 | 已实现,但实际几乎看不出 |
安装前请注意:你实际能看到的效果就是「窗口变半透明」。液态玻璃的实现是真实的——运行时生成输入框圆角矩形的法线贴图,通过 backdrop-filter: url() 交给 SVG feDisplacementMap——但折射需要有内容从后面经过,而消息列表的底边在输入框上方,没有任何东西会滚到它后面。详见 README。
安装:
plugin_manager install_bundle https://github.com/969246694/dsh-transparent#v1.0.0
已知限制:壁纸本身无法被折射(backdrop-filter 只看得见页面内部);聊天文字不会经过输入框后面(要改变这一点的三次布局尝试全部回退了)。
详见 README.zh-CN.md。