dsh-site-bridge 0.5.3 → 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.
@@ -2,7 +2,7 @@
2
2
  "manifest_version": 3,
3
3
  "default_locale": "en",
4
4
  "name": "__MSG_appName__",
5
- "version": "0.5.3",
5
+ "version": "0.6.0",
6
6
  "description": "__MSG_appDesc__",
7
7
  "minimum_chrome_version": "116",
8
8
  "permissions": [
@@ -94,6 +94,23 @@
94
94
  button.link:hover:not(:disabled) { color: #7e98ff; background: none; }
95
95
 
96
96
  .target-text { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 2px; }
97
+ .tabs-list {
98
+ display: flex; flex-direction: column; gap: 6px; max-height: 150px; overflow-y: auto;
99
+ margin-top: 8px; scrollbar-width: thin; scrollbar-color: #3a4150 transparent;
100
+ }
101
+ .tabs-list::-webkit-scrollbar { width: 6px; }
102
+ .tabs-list::-webkit-scrollbar-thumb { background: #3a4150; border-radius: 3px; }
103
+ .tabs-list::-webkit-scrollbar-track { background: transparent; }
104
+ .tab-item {
105
+ display: flex; align-items: center; justify-content: space-between; gap: 8px;
106
+ padding: 6px 8px; background: #15181e; border: 1px solid var(--line);
107
+ border-radius: 7px; cursor: pointer; min-height: 44px;
108
+ }
109
+ .tab-item:hover { border-color: #3a4150; }
110
+ .tab-item.active { border-color: var(--accent); background: #1a1f2c; }
111
+ .tab-item-text { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 2px; }
112
+ .tab-item-title { font-size: 12px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
113
+ .tab-item-url { color: var(--muted); font-size: 10.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
97
114
  .capture-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--line); }
98
115
  .capture-row button { padding: 5px 9px; font-size: 11.5px; }
99
116
  select {
@@ -148,12 +165,9 @@
148
165
 
149
166
  <div class="target empty" id="target-block">
150
167
  <div class="target-text">
151
- <div class="target-title" id="target-title" data-i18n="targetNoneTitle">Вкладка не подключена</div>
152
- <div class="target-url" id="target-url" data-i18n="targetNoneUrl">Откройте нужную страницу и подключите её</div>
168
+ <div class="target-title" data-i18n="targetNoneTitle">Вкладка не подключена</div>
169
+ <div class="target-url" data-i18n="targetNoneUrl">Откройте нужную страницу и подключите её</div>
153
170
  </div>
154
- <button class="icon" id="release" hidden data-i18n-title="disconnectTab" title="Отключить вкладку">
155
- <svg width="12" height="12" viewBox="0 0 12 12" aria-hidden="true"><path d="M2 2 L10 10 M10 2 L2 10" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
156
- </button>
157
171
  </div>
158
172
 
159
173
  <button class="primary" id="claim" data-i18n="connectTab">Подключить эту вкладку</button>
@@ -161,10 +175,7 @@
161
175
 
162
176
  <button class="primary" id="claim-more" hidden data-i18n="attachAnother">Подключить ещё вкладку</button>
163
177
 
164
- <div class="capture-row" id="tabs-row" hidden>
165
- <span class="muted" data-i18n="tabsLabel">Вкладки</span>
166
- <select id="tabs-select"></select>
167
- </div>
178
+ <div class="tabs-list" id="tabs-list" hidden></div>
168
179
 
169
180
  <div class="capture-row">
170
181
  <span class="muted" id="captured">0</span>
@@ -59,7 +59,6 @@ function render(status) {
59
59
  const dot = $('dot')
60
60
  const text = $('connection-text')
61
61
  const claim = $('claim')
62
- const release = $('release')
63
62
  const capture = $('capture')
64
63
  const target = $('target-block')
65
64
 
@@ -93,35 +92,48 @@ function render(status) {
93
92
  text.textContent = t('statusNotConfigured')
94
93
  }
95
94
 
96
- target.classList.toggle('empty', !hasTarget)
97
- $('target-title').textContent = hasTarget ? status.target.title || status.target.url : t('targetNoneTitle')
98
- $('target-url').textContent = hasTarget ? String(status.target.url || '') : t('targetNoneUrl')
99
-
100
- // Several attached tabs: a compact switcher, nothing else changes.
95
+ // One block per attached tab: title, address and its own detach cross. The
96
+ // list scrolls after three blocks so the popup keeps its height.
101
97
  const targets = Array.isArray(status.targets) ? status.targets : []
102
- const tabsRow = $('tabs-row')
103
- const tabsSelect = $('tabs-select')
104
- const selfAttached = selfTabId != null && targets.some((item) => item.id === selfTabId)
105
- $('claim-more').hidden = !hasTarget || selfAttached
106
- if (targets.length > 1) {
107
- tabsRow.hidden = false
108
- tabsSelect.textContent = ''
109
- for (const item of targets) {
110
- const option = document.createElement('option')
111
- option.value = String(item.id)
112
- option.textContent = item.title || item.url || `#${item.id}`
113
- option.selected = item.id === status.activeTabId
114
- tabsSelect.appendChild(option)
115
- }
116
- } else {
117
- tabsRow.hidden = true
98
+ const activeId = status.activeTabId ?? status.target?.id ?? null
99
+ target.classList.toggle('empty', targets.length === 0)
100
+ target.hidden = targets.length > 0
101
+
102
+ const list = $('tabs-list')
103
+ list.hidden = targets.length === 0
104
+ list.textContent = ''
105
+ for (const item of targets) {
106
+ const row = document.createElement('div')
107
+ row.className = item.id === activeId ? 'tab-item active' : 'tab-item'
108
+ row.dataset.id = String(item.id)
109
+ row.title = item.url || ''
110
+
111
+ const body = document.createElement('div')
112
+ body.className = 'tab-item-text'
113
+ const title = document.createElement('div')
114
+ title.className = 'tab-item-title'
115
+ title.textContent = item.title || item.url || `#${item.id}`
116
+ const address = document.createElement('div')
117
+ address.className = 'tab-item-url'
118
+ address.textContent = item.url || ''
119
+ body.append(title, address)
120
+
121
+ const remove = document.createElement('button')
122
+ remove.className = 'icon'
123
+ remove.dataset.remove = String(item.id)
124
+ remove.title = t('disconnectTab')
125
+ remove.innerHTML = '<svg width="12" height="12" viewBox="0 0 12 12" aria-hidden="true"><path d="M2 2 L10 10 M10 2 L2 10" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>'
126
+
127
+ row.append(body, remove)
128
+ list.append(row)
118
129
  }
119
130
 
120
131
  // Nothing to claim once a tab is bound: the action would be a no-op.
121
- claim.hidden = hasTarget
122
- claim.disabled = !connected || hasTarget
123
- claim.textContent = hasTarget ? t('tabConnected') : t('connectTab')
124
- release.hidden = !hasTarget
132
+ const selfAttached = selfTabId != null && targets.some((item) => item.id === selfTabId)
133
+ claim.hidden = targets.length > 0
134
+ claim.disabled = !connected || targets.length > 0
135
+ claim.textContent = t('connectTab')
136
+ $('claim-more').hidden = targets.length === 0 || selfAttached
125
137
 
126
138
  const pick = $('pick')
127
139
  pick.classList.toggle('active', status.picking === true)
@@ -273,8 +285,17 @@ $('capture').addEventListener('click', async () => {
273
285
  await refresh()
274
286
  })
275
287
 
276
- $('release').addEventListener('click', async () => {
277
- await send({ type: 'release' })
288
+ $('tabs-list').addEventListener('click', async (event) => {
289
+ const remove = event.target.closest('[data-remove]')
290
+ if (remove) {
291
+ event.stopPropagation()
292
+ await send({ type: 'release', tabId: Number(remove.dataset.remove) })
293
+ await refresh()
294
+ return
295
+ }
296
+ const row = event.target.closest('.tab-item')
297
+ if (!row || row.classList.contains('active')) return
298
+ await send({ type: 'use', tabId: Number(row.dataset.id) })
278
299
  await refresh()
279
300
  })
280
301
 
@@ -296,11 +317,6 @@ $('pick').addEventListener('click', async () => {
296
317
 
297
318
  $('claim-more').addEventListener('click', attachCurrentTab)
298
319
 
299
- $('tabs-select').addEventListener('change', async (event) => {
300
- const tabId = Number(event.target.value)
301
- if (!Number.isFinite(tabId)) return
302
- await send({ type: 'use', tabId })
303
- await refresh()
304
- })
320
+
305
321
 
306
322
  void init()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-site-bridge",
3
- "version": "0.5.3",
3
+ "version": "0.6.0",
4
4
  "description": "Chrome/Edge MV3 bridge for DeepSeek Harness: capture page markup (DOM, styles, selectors) and BFF traffic (xhr/fetch with headers and bodies) into a workspace snapshot bundle, drive the page with trusted CDP input, and verify injected scripts. Authorization never leaves the browser and is never written to disk.",
5
5
  "icon": "./icon.svg",
6
6
  "type": "module",
@@ -21,6 +21,17 @@ const chromePath = process.env.CHROME_PATH ?? 'C:\\Program Files\\Google\\Chrome
21
21
  const sleep = (ms) => new Promise((done) => setTimeout(done, ms))
22
22
 
23
23
  const statuses = {
24
+ many: {
25
+ ok: true, configured: true, connected: true, port: 47321, recording: true, captured: 7, selfTabId: 9,
26
+ activeTabId: 1,
27
+ target: { id: 1, title: 'Example Store — Products', url: 'https://shop.example/products' },
28
+ targets: [
29
+ { id: 1, title: 'Example Store — Products', url: 'https://shop.example/products', recording: true, captured: 7 },
30
+ { id: 2, title: 'Example Store — Cart', url: 'https://shop.example/cart', recording: false, captured: 0 },
31
+ { id: 3, title: 'Example Store — Checkout', url: 'https://shop.example/checkout', recording: false, captured: 0 },
32
+ { id: 4, title: 'Example Store — Orders', url: 'https://shop.example/orders', recording: false, captured: 0 }
33
+ ]
34
+ },
24
35
  'connected-one': {
25
36
  ok: true, configured: true, connected: true, port: 47321, recording: false, captured: 0, selfTabId: 1,
26
37
  activeTabId: 1,