dsh-mini-utility-dock 0.5.0 → 0.6.0

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/dist/launcher.js CHANGED
@@ -1,18 +1,20 @@
1
- // Family utility launcher shared by the browser halves of the DSH plugins.
1
+ // The shared launcher assembly: one icon plus the menu of panels it opens, for a
2
+ // plugin's browser half.
2
3
  //
3
4
  // This fragment has ONE source of truth: dsh-mini-utility-dock/dist/launcher.js.
4
5
  // DSH client artifacts are self-contained classic scripts, so the fragment is
5
6
  // embedded into lib/client.js at build time by
6
7
  // npm run launcher:sync (write it)
7
8
  // npm run launcher:check (fail on drift)
8
- // instead of being imported: a bare import would put a runtime dependency on the
9
- // dock into every plugin, and the whole point of the dock is that a plugin ships
9
+ // instead of being imported: a bare import would put a runtime dependency on
10
+ // this package into the bundle that embeds it, and an embedding plugin ships
10
11
  // standalone, with nothing else required.
11
12
  //
12
- // The launcher is one icon that opens a menu of family panels. It is contributed
13
+ // The launcher is one icon that opens a menu of utility panels. It is contributed
13
14
  // through slots, not through a page-local protocol: exactly one copy of this
14
- // assembly runs per page (the first plugin to load wins the window mutex and
15
- // declares the menu seat), and every plugin adds one row to that seat.
15
+ // assembly runs per page — the first to load wins the window mutex, registers the
16
+ // launcher, and declares the menu seat — while the other copies register nothing
17
+ // here and only wait to take the claim over once their owner is disposed.
16
18
  const UTILITY_ITEM_SLOT = 'createhelper.utility.item'
17
19
  const UTILITY_MUTEX_KEY = '__CREATEHELPER_DSH_UTILITY_LAUNCHER_V1__'
18
20
  const UTILITY_CSS_ID = 'createhelper-utility-launcher'
@@ -21,7 +23,7 @@ const UTILITY_MEASURE_TRIES = 120
21
23
  const UTILITY_ICON = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="7" height="7" rx="2"></rect><rect x="14" y="3" width="7" height="7" rx="2"></rect><rect x="3" y="14" width="7" height="7" rx="2"></rect><rect x="14" y="14" width="7" height="7" rx="2"></rect></svg>'
22
24
 
23
25
  // The launcher's own chrome, injected once by whichever copy wins the mutex.
24
- // The rows belong to other plugins, so the menu styles its own buttons by
26
+ // The rows are other plugins' components, so the menu styles its buttons by
25
27
  // position instead of asking every contributor for a class name.
26
28
  function ensureUtilityStyles() {
27
29
  if (typeof document === 'undefined') return
@@ -41,8 +43,9 @@ function ensureUtilityStyles() {
41
43
  document.head.appendChild(styleEl)
42
44
  }
43
45
 
44
- // The position rule the retired page dock used: right of the sidebar, 16px
45
- // in, and 80px when the shell has not laid the column out yet.
46
+ // The launcher tracks the shell's sidebar column rather than a fixed offset: its
47
+ // left edge sits 16px right of the sidebar's edge, and 80px while the shell has
48
+ // not laid that column out yet.
46
49
  const measureUtilityLeft = () => {
47
50
  if (typeof document === 'undefined') return UTILITY_FALLBACK_LEFT_PX
48
51
  const overlay = document.querySelector('[data-shell-overlay]')
@@ -105,7 +108,7 @@ const useUtilityMenu = () => {
105
108
  // The launcher and its menu. The menu is always rendered - a declared child
106
109
  // slot is not conditionally declared - and hidden when closed. Choosing a row
107
110
  // closes the menu through the click that bubbles out of it, because the rows
108
- // are other plugins' components.
111
+ // belong to other plugins, not to this block.
109
112
  function UtilityLauncher (props) {
110
113
  const left = useUtilityLeft()
111
114
  const open = useUtilityMenu()
@@ -155,9 +158,39 @@ function UtilityLauncher (props) {
155
158
  // The seat belongs to the shell, so it is reached through inject: registering
156
159
  // into it directly throws while that declaration is still pending, and a
157
160
  // launcher must never cost the caller the surfaces it registers afterwards.
161
+ //
162
+ // The claim lives on the page and is released with its owner. Any plugin that
163
+ // embeds this block may win the claim, so without the release a hot reload of
164
+ // the owner would dispose its registration while the claim stayed taken, and the
165
+ // launcher would stay missing until the page was reloaded. A released claim wakes
166
+ // the other copies on the page, which re-register immediately.
167
+ const utilityClaim = () => {
168
+ if (typeof window === 'undefined') return null
169
+ const existing = window[UTILITY_MUTEX_KEY]
170
+ if (existing !== null && typeof existing === 'object') return existing
171
+ const claim = { owner: null, waiters: new Set() }
172
+ window[UTILITY_MUTEX_KEY] = claim
173
+ return claim
174
+ }
158
175
  const registerUtilityLauncher = (scope) => {
159
- if (typeof window === 'undefined' || window[UTILITY_MUTEX_KEY] === true) return
160
- window[UTILITY_MUTEX_KEY] = true
176
+ const claim = utilityClaim()
177
+ if (claim === null) return
178
+ if (claim.owner !== null) {
179
+ claim.waiters.add(() => registerUtilityLauncher(scope))
180
+ return
181
+ }
182
+ claim.owner = scope
183
+ let released = false
184
+ const release = () => {
185
+ if (released) return
186
+ released = true
187
+ if (claim.owner === scope) claim.owner = null
188
+ if (window[UTILITY_MUTEX_KEY] !== claim) return
189
+ const waiters = [...claim.waiters]
190
+ claim.waiters.clear()
191
+ for (const wake of waiters) wake()
192
+ }
193
+ scope.on('dispose', release)
161
194
  scope.slots.inject('shell.overlay', () => {
162
195
  try {
163
196
  scope.slots.register({
@@ -167,9 +200,9 @@ const registerUtilityLauncher = (scope) => {
167
200
  children: { [UTILITY_ITEM_SLOT]: { kind: 'list', scope: 'root' } }
168
201
  }, UtilityLauncher)
169
202
  } catch (error) {
170
- window[UTILITY_MUTEX_KEY] = false
203
+ release()
171
204
  if (typeof console !== 'undefined' && typeof console.error === 'function') {
172
- console.error('[utility-launcher] could not register the family launcher', error)
205
+ console.error('[utility-launcher] could not register the launcher', error)
173
206
  }
174
207
  }
175
208
  })
package/dist/loopback.js CHANGED
@@ -1,28 +1,25 @@
1
- // Loopback predicates shared by the host halves of the DSH plugins.
1
+ // The loopback predicates: which Host names and which peer addresses count as
2
+ // loopback, for a plugin's host half that binds an API to the loopback interface.
2
3
  //
3
4
  // This fragment has ONE source of truth: dsh-mini-utility-dock/dist/loopback.js.
4
- // DSH plugin host halves are plain Node ESM that each package ships standalone,
5
- // so the fragment is embedded into `lib/shared.js` at build time by
5
+ // A host half is plain Node ESM that its package ships standalone, so the
6
+ // fragment is embedded into `lib/shared.js` at build time by
6
7
  // npm run loopback:sync (write it)
7
8
  // npm run loopback:check (fail on drift)
8
9
  // instead of being imported: a bare `import 'dsh-mini-utility-dock/...'` would
9
- // put a runtime dependency on the dock into every plugin, and the whole point of
10
- // the dock is that a plugin ships standalone, with nothing else required.
11
- //
12
- // Why it is shared at all: these predicates were copy-pasted per repo and
13
- // drifted twice. The first drift rejected IPv6 loopback everywhere; the second
14
- // is that consumers disagreed on which Host spellings count as loopback
15
- // (see the parity bin this package ships, which compares this block across repositories).
10
+ // put a runtime dependency on this package into the file that embeds it, and an
11
+ // embedding plugin ships standalone, with nothing else required.
16
12
  //
17
13
  // "Loopback" is decided in exactly one place — LOOPBACK_HOSTNAMES plus the
18
14
  // IPv4-mapped IPv6 form of each entry — and both the name and the address
19
- // predicate route through it, so the Host path and the peer path cannot drift
20
- // apart again.
15
+ // predicate route through it, so the Host path and the peer path cannot
16
+ // disagree. Every spelling this file accepts is a documented one; anything
17
+ // unrecognised fails closed.
21
18
  //
22
19
  // Kept a separate fragment from the host guard on purpose: the predicates are
23
20
  // stable facts about what an address is, while the guard is a policy about who
24
- // may call an API. `dist/guard.js` imports this module, so the guard depends on
25
- // this block and never the other way round.
21
+ // may call an API. The guard block reads the names this block declares, so it
22
+ // depends on this block and never the other way round.
26
23
 
27
24
  // Hostnames a request to a loopback-bound API may legitimately arrive with.
28
25
  // Exact spellings only: `api.localhost` and `127.0.0.1.evil.example` must stay
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-mini-utility-dock",
3
- "version": "0.5.0",
4
- "description": "The DSH plugin family's shared assets: source fragments (the loopback predicates and host request guard for host halves, the utility launcher for client halves), the embedding CLI, and the family diagnostics — cross-repo drift check and bilingual docs structure check.",
3
+ "version": "0.6.0",
4
+ "description": "The DSH plugin family's shared assets: source fragments (the loopback predicates, host request guard and host HTTP glue for host halves, the utility launcher for client halves), the embedding CLI, and the family diagnostics — cross-repo drift check and bilingual docs structure check.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "dsh-mini-utility-dock": "bin/dsh-mini-utility-dock.js",
@@ -29,6 +29,11 @@
29
29
  "type": "git",
30
30
  "url": "git+https://github.com/xswt442-cmd/dsh-mini-utility-dock.git"
31
31
  },
32
+ "homepage": "https://github.com/xswt442-cmd/dsh-mini-utility-dock#readme",
33
+ "bugs": {
34
+ "url": "https://github.com/xswt442-cmd/dsh-mini-utility-dock/issues"
35
+ },
36
+ "author": "xswt442-cmd",
32
37
  "keywords": [
33
38
  "dsh",
34
39
  "deepseek-harness",