@symbo.ls/connect 3.2.7

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.
Files changed (77) hide show
  1. package/build.js +205 -0
  2. package/dist/assets/1024x1024.png +0 -0
  3. package/dist/assets/128x128.png +0 -0
  4. package/dist/assets/144x144.png +0 -0
  5. package/dist/assets/192x192.png +0 -0
  6. package/dist/assets/48x48.png +0 -0
  7. package/dist/assets/512x512.png +0 -0
  8. package/dist/assets/72x72.png +0 -0
  9. package/dist/assets/96x96.png +0 -0
  10. package/dist/assets/active_cursor.png +0 -0
  11. package/dist/assets/favicon.svg +6 -0
  12. package/dist/assets/old/144x144.png +0 -0
  13. package/dist/assets/old/192x192.png +0 -0
  14. package/dist/assets/old/48x48.png +0 -0
  15. package/dist/assets/old/48x48_faint.png +0 -0
  16. package/dist/assets/old/512x512.png +0 -0
  17. package/dist/assets/old/72x72.png +0 -0
  18. package/dist/assets/old/96x96.png +0 -0
  19. package/dist/auth.js +373 -0
  20. package/dist/content.css +46 -0
  21. package/dist/content.js +1171 -0
  22. package/dist/content.js.map +7 -0
  23. package/dist/devtools.html +7 -0
  24. package/dist/devtools.js +5 -0
  25. package/dist/manifest.json +87 -0
  26. package/dist/page-agent.js +727 -0
  27. package/dist/panel.css +2239 -0
  28. package/dist/panel.html +235 -0
  29. package/dist/panel.js +4973 -0
  30. package/dist/picker.html +111 -0
  31. package/dist/picker.js +300 -0
  32. package/dist/service_worker.js +219 -0
  33. package/dist/service_worker.js.map +7 -0
  34. package/dist/settings.css +128 -0
  35. package/dist/settings.html +26 -0
  36. package/dist/settings_ui.js +57 -0
  37. package/dist/settings_ui.js.map +7 -0
  38. package/package.json +20 -0
  39. package/src/content.js +104 -0
  40. package/src/grabber/clean.js +605 -0
  41. package/src/grabber/computed.js +78 -0
  42. package/src/grabber/parse.js +268 -0
  43. package/src/grabber/stylesheets.js +117 -0
  44. package/src/grabber/utils.js +238 -0
  45. package/src/service_worker.js +246 -0
  46. package/src/settings/settings_ui.js +52 -0
  47. package/src/settings/settings_utils.js +70 -0
  48. package/static/assets/1024x1024.png +0 -0
  49. package/static/assets/128x128.png +0 -0
  50. package/static/assets/144x144.png +0 -0
  51. package/static/assets/192x192.png +0 -0
  52. package/static/assets/48x48.png +0 -0
  53. package/static/assets/512x512.png +0 -0
  54. package/static/assets/72x72.png +0 -0
  55. package/static/assets/96x96.png +0 -0
  56. package/static/assets/active_cursor.png +0 -0
  57. package/static/assets/favicon.svg +6 -0
  58. package/static/assets/old/144x144.png +0 -0
  59. package/static/assets/old/192x192.png +0 -0
  60. package/static/assets/old/48x48.png +0 -0
  61. package/static/assets/old/48x48_faint.png +0 -0
  62. package/static/assets/old/512x512.png +0 -0
  63. package/static/assets/old/72x72.png +0 -0
  64. package/static/assets/old/96x96.png +0 -0
  65. package/static/auth.js +373 -0
  66. package/static/content.css +46 -0
  67. package/static/devtools.html +7 -0
  68. package/static/devtools.js +5 -0
  69. package/static/manifest.json +56 -0
  70. package/static/page-agent.js +727 -0
  71. package/static/panel.css +2239 -0
  72. package/static/panel.html +235 -0
  73. package/static/panel.js +4973 -0
  74. package/static/picker.html +111 -0
  75. package/static/picker.js +300 -0
  76. package/static/settings.css +128 -0
  77. package/static/settings.html +26 -0
@@ -0,0 +1,727 @@
1
+ // Page-agent: runs in page context, provides DOMQL inspection utilities
2
+ // Accessed from DevTools panel via chrome.devtools.inspectedWindow.eval()
3
+
4
+ ;(function () {
5
+ 'use strict'
6
+
7
+ const REGISTRY_KEYS = new Set([
8
+ 'attr', 'style', 'text', 'html', 'data', 'classlist', 'state', 'scope',
9
+ 'extend', 'extends', 'children', 'childExtend', 'childExtends', 'childExtendRecursive',
10
+ 'props', 'if', 'define', '__name', '__ref', '__hash', '__text',
11
+ 'key', 'tag', 'query', 'parent', 'node', 'variables', 'on', 'component', 'context',
12
+ 'update', 'set', 'reset', 'remove', 'setProps', 'lookup', 'lookdown',
13
+ 'lookdownAll', 'nextElement', 'previousElement', 'updateContent', 'removeContent',
14
+ 'getRootState', 'getRootData', 'getRoot', 'getContext', 'setNodeStyles',
15
+ 'call', 'parse', 'keys', 'verbose', 'getRef', 'getPath'
16
+ ])
17
+
18
+ const METHOD_NAMES = [
19
+ 'update', 'set', 'reset', 'remove', 'setProps',
20
+ 'lookup', 'lookdown', 'lookdownAll',
21
+ 'updateContent', 'removeContent',
22
+ 'getRootState', 'getRootData', 'getRoot', 'getContext',
23
+ 'setNodeStyles', 'call', 'parse', 'keys', 'verbose',
24
+ 'nextElement', 'previousElement', 'getRef', 'getPath'
25
+ ]
26
+
27
+ const STATE_METHOD_NAMES = [
28
+ 'update', 'set', 'reset', 'replace', 'quietUpdate',
29
+ 'toggle', 'remove', 'add', 'apply', 'setByPath',
30
+ 'parse', 'clean', 'destroy'
31
+ ]
32
+
33
+ // Get DOMQL element ref from a DOM node
34
+ function getRef (node) {
35
+ if (!node) return null
36
+ if (node.ref) return node.ref
37
+ // Walk up to find nearest DOMQL element
38
+ let current = node
39
+ while (current) {
40
+ if (current.ref) return current.ref
41
+ current = current.parentElement
42
+ }
43
+ return null
44
+ }
45
+
46
+ // Serialize a value safely (handle circular refs, functions, DOM nodes)
47
+ function serialize (val, depth, maxDepth, seen) {
48
+ if (depth > maxDepth) return { __type: 'truncated', value: '...' }
49
+ if (val === null) return null
50
+ if (val === undefined) return { __type: 'undefined' }
51
+
52
+ const type = typeof val
53
+ if (type === 'string' || type === 'number' || type === 'boolean') return val
54
+ if (type === 'function') return { __type: 'function', name: val.name || 'anonymous' }
55
+ if (val instanceof HTMLElement) return { __type: 'node', tag: val.tagName.toLowerCase(), key: val.getAttribute('key') }
56
+
57
+ if (type === 'object') {
58
+ if (seen.has(val)) return { __type: 'circular' }
59
+ seen.add(val)
60
+
61
+ if (Array.isArray(val)) {
62
+ return val.slice(0, 100).map(item => serialize(item, depth + 1, maxDepth, seen))
63
+ }
64
+
65
+ const result = {}
66
+ const keys = Object.keys(val)
67
+ for (const k of keys.slice(0, 100)) {
68
+ if (k === 'node' || k === 'parent' || k === 'context' || k === '__element') continue
69
+ try {
70
+ result[k] = serialize(val[k], depth + 1, maxDepth, seen)
71
+ } catch (e) {
72
+ result[k] = { __type: 'error', message: e.message }
73
+ }
74
+ }
75
+ if (keys.length > 100) result.__truncated = keys.length + ' keys total'
76
+ return result
77
+ }
78
+
79
+ return String(val)
80
+ }
81
+
82
+ // Trace which component each prop comes from
83
+ function getPropsOrigin (el) {
84
+ const origins = {}
85
+
86
+ function traceExtend (def, depth) {
87
+ if (!def || typeof def !== 'object' || depth > 10) return
88
+ const name = def.__name || def.key || null
89
+
90
+ if (def.props && typeof def.props === 'object') {
91
+ for (const k of Object.keys(def.props)) {
92
+ if (typeof def.props[k] === 'function' || k.startsWith('__')) continue
93
+ if (!origins[k]) origins[k] = name
94
+ }
95
+ }
96
+
97
+ // Check text
98
+ if (def.text !== undefined && !origins.text) {
99
+ origins.text = name
100
+ }
101
+
102
+ // Recurse into extend chain
103
+ if (def.extend) traceExtend(def.extend, depth + 1)
104
+ if (Array.isArray(def.extends)) {
105
+ for (const ext of def.extends) traceExtend(ext, depth + 1)
106
+ }
107
+ }
108
+
109
+ // Walk from the element itself
110
+ if (el.__ref) {
111
+ // The element's own component name
112
+ const selfName = el.__ref.__name || el.component || el.key
113
+
114
+ // Check if the element defines its own props
115
+ if (el.__ref.__extend) traceExtend(el.__ref.__extend, 0)
116
+ if (el.__ref.__extends && Array.isArray(el.__ref.__extends)) {
117
+ for (const ext of el.__ref.__extends) traceExtend(ext, 0)
118
+ }
119
+
120
+ // Mark direct props (set on this instance) as "self"
121
+ if (el.props && typeof el.props === 'object') {
122
+ for (const k of Object.keys(el.props)) {
123
+ if (typeof el.props[k] === 'function' || k.startsWith('__')) continue
124
+ if (!origins[k]) origins[k] = selfName || 'self'
125
+ }
126
+ }
127
+ }
128
+
129
+ return origins
130
+ }
131
+
132
+ // Get serialized info about a DOMQL element
133
+ function getElementInfo (el) {
134
+ if (!el) return null
135
+
136
+ const info = {
137
+ key: el.key || null,
138
+ tag: el.tag || (el.node && el.node.tagName ? el.node.tagName.toLowerCase() : null),
139
+ path: el.__ref && el.__ref.path ? el.__ref.path : null
140
+ }
141
+
142
+ // State
143
+ if (el.state && typeof el.state === 'object') {
144
+ info.state = {}
145
+ for (const k of Object.keys(el.state)) {
146
+ if (STATE_METHOD_NAMES.includes(k) || k.startsWith('__') || k === 'parent' || k === 'root') continue
147
+ try {
148
+ info.state[k] = serialize(el.state[k], 0, 3, new WeakSet())
149
+ } catch (e) {
150
+ info.state[k] = { __type: 'error', message: e.message }
151
+ }
152
+ }
153
+ }
154
+
155
+ // Props — collect el.props if it exists as an object
156
+ info.props = {}
157
+
158
+ // Include text as a prop shortcut — el.text is a top-level DOMQL property
159
+ // It can be a string, number, or function (getter). Always try to resolve it.
160
+ var textVal = undefined
161
+ if (el.text !== undefined && el.text !== null) {
162
+ if (typeof el.text === 'function') {
163
+ try { textVal = el.text.call(el, el, el.state, el.context) } catch (e) { /* skip */ }
164
+ } else if (typeof el.text !== 'object') {
165
+ textVal = el.text
166
+ }
167
+ }
168
+ if ((textVal === undefined || textVal === null) && el.props && el.props.text != null && typeof el.props.text !== 'function') {
169
+ textVal = el.props.text
170
+ }
171
+ if ((textVal === undefined || textVal === null) && el.node) {
172
+ // Fallback: read direct text nodes from DOM
173
+ var directText = ''
174
+ for (var ci = 0; ci < el.node.childNodes.length; ci++) {
175
+ if (el.node.childNodes[ci].nodeType === 3) directText += el.node.childNodes[ci].textContent
176
+ }
177
+ if (directText.trim()) textVal = directText.trim()
178
+ }
179
+ if (textVal !== undefined && textVal !== null) {
180
+ info.props.text = serialize(textVal, 0, 3, new WeakSet())
181
+ }
182
+
183
+ if (el.props && typeof el.props === 'object') {
184
+ for (const k of Object.keys(el.props)) {
185
+ if (k === 'text' || k === 'parent' || k === 'root' || k === 'update' || k === 'set' || k === 'reset' || k.startsWith('__')) continue
186
+ if (typeof el.props[k] === 'function') continue
187
+ try {
188
+ info.props[k] = serialize(el.props[k], 0, 3, new WeakSet())
189
+ } catch (e) {
190
+ info.props[k] = { __type: 'error', message: e.message }
191
+ }
192
+ }
193
+ }
194
+
195
+ // Prop origin mapping — trace which component defines each prop
196
+ info.propsOrigin = getPropsOrigin(el)
197
+
198
+ // Children (DOMQL child elements)
199
+ info.children = []
200
+ const childKeys = (el.__ref && el.__ref.__children) || []
201
+ for (const ck of childKeys) {
202
+ if (el[ck] && el[ck].node) {
203
+ info.children.push({
204
+ key: ck,
205
+ tag: el[ck].tag || (el[ck].node ? el[ck].node.tagName.toLowerCase() : null),
206
+ hasChildren: !!(el[ck].__ref && el[ck].__ref.__children && el[ck].__ref.__children.length)
207
+ })
208
+ }
209
+ }
210
+
211
+ // Also find children by walking own keys for DOMQL elements
212
+ for (const k of Object.keys(el)) {
213
+ if (REGISTRY_KEYS.has(k) || k.startsWith('__') || childKeys.includes(k)) continue
214
+ const val = el[k]
215
+ if (val && typeof val === 'object' && val.node instanceof HTMLElement && val.key) {
216
+ info.children.push({
217
+ key: k,
218
+ tag: val.tag || (val.node ? val.node.tagName.toLowerCase() : null),
219
+ hasChildren: !!(val.__ref && val.__ref.__children && val.__ref.__children.length)
220
+ })
221
+ }
222
+ }
223
+
224
+ // Original (definition) props — from __ref before function resolution
225
+ info.originalProps = {}
226
+ info.functionProps = {} // props whose original value is a function
227
+
228
+ // Gather original definition values from __ref and extend chain
229
+ function collectOriginal (def, depth) {
230
+ if (!def || typeof def !== 'object' || depth > 10) return
231
+ // Props from definition
232
+ if (def.props && typeof def.props === 'object') {
233
+ for (var pk of Object.keys(def.props)) {
234
+ if (pk.startsWith('__')) continue
235
+ if (!(pk in info.originalProps)) {
236
+ if (typeof def.props[pk] === 'function') {
237
+ info.functionProps[pk] = { name: def.props[pk].name || 'anonymous' }
238
+ // Still store the computed value
239
+ if (el.props && el.props[pk] !== undefined && typeof el.props[pk] !== 'function') {
240
+ info.originalProps[pk] = serialize(el.props[pk], 0, 3, new WeakSet())
241
+ }
242
+ } else {
243
+ info.originalProps[pk] = serialize(def.props[pk], 0, 3, new WeakSet())
244
+ }
245
+ }
246
+ }
247
+ }
248
+ // Text from definition
249
+ if (def.text !== undefined && !('text' in info.originalProps)) {
250
+ if (typeof def.text === 'function') {
251
+ info.functionProps.text = { name: def.text.name || 'anonymous' }
252
+ } else {
253
+ info.originalProps.text = serialize(def.text, 0, 3, new WeakSet())
254
+ }
255
+ }
256
+ // Top-level DOMQL keys that are also "props" in the broad sense
257
+ var domqlKeys = ['tag', 'theme', 'flow', 'wrap', 'display', 'position', 'cursor', 'opacity', 'overflow']
258
+ for (var dk of domqlKeys) {
259
+ if (def[dk] !== undefined && !(dk in info.originalProps)) {
260
+ if (typeof def[dk] === 'function') {
261
+ info.functionProps[dk] = { name: def[dk].name || 'anonymous' }
262
+ } else {
263
+ info.originalProps[dk] = serialize(def[dk], 0, 3, new WeakSet())
264
+ }
265
+ }
266
+ }
267
+ // Walk extends
268
+ if (def.extend) collectOriginal(def.extend, depth + 1)
269
+ if (Array.isArray(def.extends)) {
270
+ for (var ext of def.extends) collectOriginal(ext, depth + 1)
271
+ }
272
+ }
273
+
274
+ // Start from the element's own __ref
275
+ if (el.__ref) {
276
+ collectOriginal(el.__ref, 0)
277
+ if (el.__ref.__extend) collectOriginal(el.__ref.__extend, 0)
278
+ if (el.__ref.__extends && Array.isArray(el.__ref.__extends)) {
279
+ for (var re of el.__ref.__extends) collectOriginal(re, 0)
280
+ }
281
+ }
282
+
283
+ // Also check top-level element properties for functions
284
+ var topFuncKeys = ['text', 'tag', 'if', 'data']
285
+ for (var tfk of topFuncKeys) {
286
+ if (typeof el[tfk] === 'function' && !(tfk in info.functionProps)) {
287
+ info.functionProps[tfk] = { name: el[tfk].name || 'anonymous' }
288
+ }
289
+ }
290
+
291
+ // Methods available
292
+ info.methods = METHOD_NAMES.filter(m => typeof el[m] === 'function')
293
+
294
+ // State methods
295
+ if (el.state && typeof el.state === 'object') {
296
+ info.stateMethods = STATE_METHOD_NAMES.filter(m => typeof el.state[m] === 'function')
297
+ }
298
+
299
+ // __ref info
300
+ if (el.__ref) {
301
+ info.ref = {}
302
+ for (const k of Object.keys(el.__ref)) {
303
+ if (k === 'root' || k === '__element') continue
304
+ try {
305
+ info.ref[k] = serialize(el.__ref[k], 0, 2, new WeakSet())
306
+ } catch (e) {
307
+ info.ref[k] = { __type: 'error', message: e.message }
308
+ }
309
+ }
310
+ }
311
+
312
+ return info
313
+ }
314
+
315
+ // Build a tree of DOMQL elements starting from root
316
+ function buildTree (el, depth, maxDepth) {
317
+ if (!el || depth > maxDepth) return null
318
+
319
+ const node = {
320
+ key: el.key || '(unknown)',
321
+ tag: el.tag || (el.node && el.node.tagName ? el.node.tagName.toLowerCase() : null),
322
+ children: []
323
+ }
324
+
325
+ // Collect children
326
+ const childKeys = (el.__ref && el.__ref.__children) || []
327
+ const seen = new Set(childKeys)
328
+
329
+ for (const ck of childKeys) {
330
+ if (ck === '__text' || ck.startsWith('__')) continue
331
+ if (el[ck] && el[ck].node) {
332
+ const child = buildTree(el[ck], depth + 1, maxDepth)
333
+ if (child) node.children.push(child)
334
+ }
335
+ }
336
+
337
+ // Also check own keys for child elements not in __children
338
+ for (const k of Object.keys(el)) {
339
+ if (REGISTRY_KEYS.has(k) || k.startsWith('__') || seen.has(k)) continue
340
+ const val = el[k]
341
+ if (val && typeof val === 'object' && val.node instanceof HTMLElement && val.key) {
342
+ seen.add(k)
343
+ const child = buildTree(val, depth + 1, maxDepth)
344
+ if (child) node.children.push(child)
345
+ }
346
+ }
347
+
348
+ return node
349
+ }
350
+
351
+ // Find the root DOMQL element
352
+ function findRoot () {
353
+ // Check document.body.ref first
354
+ if (document.body && document.body.ref) return document.body.ref
355
+ // Check common root selectors
356
+ const candidates = document.querySelectorAll('[key]')
357
+ for (const node of candidates) {
358
+ if (node.ref) {
359
+ // Walk up to find the topmost ref
360
+ let root = node.ref
361
+ while (root.parent && root.parent.node && root.parent.key) {
362
+ root = root.parent
363
+ }
364
+ return root
365
+ }
366
+ }
367
+ return null
368
+ }
369
+
370
+ // Build a key path from a DOMQL element back to the root
371
+ function getPathToRoot (el) {
372
+ const parts = []
373
+ let current = el
374
+ while (current) {
375
+ if (current.key) parts.unshift(current.key)
376
+ if (!current.parent || !current.parent.key) break
377
+ current = current.parent
378
+ }
379
+ return parts.join('.')
380
+ }
381
+
382
+ // Highlight overlay management
383
+ let _highlightedNode = null
384
+ let _highlightColor = null
385
+ let _scrollRaf = null
386
+
387
+ function _updateOverlayPosition () {
388
+ if (!_highlightedNode) return
389
+ const overlay = document.getElementById('__domql-highlight__')
390
+ if (!overlay || overlay.style.display === 'none') return
391
+ const rect = _highlightedNode.getBoundingClientRect()
392
+ overlay.style.top = rect.top + 'px'
393
+ overlay.style.left = rect.left + 'px'
394
+ overlay.style.width = rect.width + 'px'
395
+ overlay.style.height = rect.height + 'px'
396
+ }
397
+
398
+ function _onScroll () {
399
+ if (_scrollRaf) return
400
+ _scrollRaf = requestAnimationFrame(() => {
401
+ _scrollRaf = null
402
+ _updateOverlayPosition()
403
+ })
404
+ }
405
+
406
+ function showHighlight (node, color) {
407
+ let overlay = document.getElementById('__domql-highlight__')
408
+ if (!overlay) {
409
+ overlay = document.createElement('div')
410
+ overlay.id = '__domql-highlight__'
411
+ overlay.style.cssText = `
412
+ position: fixed;
413
+ pointer-events: none;
414
+ z-index: 999999;
415
+ transition: top 0.08s, left 0.08s, width 0.08s, height 0.08s;
416
+ `
417
+ document.body.appendChild(overlay)
418
+ }
419
+
420
+ _highlightedNode = node
421
+ _highlightColor = color || 'rgba(66, 133, 244, 0.6)'
422
+
423
+ const rect = node.getBoundingClientRect()
424
+ overlay.style.top = rect.top + 'px'
425
+ overlay.style.left = rect.left + 'px'
426
+ overlay.style.width = rect.width + 'px'
427
+ overlay.style.height = rect.height + 'px'
428
+ overlay.style.background = _highlightColor.replace('0.6', '0.12')
429
+ overlay.style.border = '2px solid ' + _highlightColor
430
+ overlay.style.display = 'block'
431
+
432
+ window.removeEventListener('scroll', _onScroll, true)
433
+ window.addEventListener('scroll', _onScroll, true)
434
+ }
435
+
436
+ function hideHighlight () {
437
+ const overlay = document.getElementById('__domql-highlight__')
438
+ if (overlay) overlay.style.display = 'none'
439
+ _highlightedNode = null
440
+ window.removeEventListener('scroll', _onScroll, true)
441
+ }
442
+
443
+ // --- Picker mode ---
444
+ let pickerActive = false
445
+ let pickerHoverHandler = null
446
+ let pickerClickHandler = null
447
+ let pickerKeyHandler = null
448
+
449
+ function startPicker () {
450
+ if (pickerActive) return
451
+ pickerActive = true
452
+
453
+ // Show a tooltip with the DOMQL key
454
+ let tooltip = document.getElementById('__domql-picker-tooltip__')
455
+ if (!tooltip) {
456
+ tooltip = document.createElement('div')
457
+ tooltip.id = '__domql-picker-tooltip__'
458
+ tooltip.style.cssText = `
459
+ position: fixed;
460
+ background: #1e1e1e;
461
+ color: #9cdcfe;
462
+ font-family: 'SF Mono', Menlo, Monaco, monospace;
463
+ font-size: 12px;
464
+ padding: 4px 8px;
465
+ border-radius: 4px;
466
+ border: 1px solid #4285f4;
467
+ pointer-events: none;
468
+ z-index: 1000000;
469
+ display: none;
470
+ white-space: nowrap;
471
+ box-shadow: 0 2px 8px rgba(0,0,0,0.4);
472
+ `
473
+ document.body.appendChild(tooltip)
474
+ }
475
+
476
+ document.body.style.cursor = 'crosshair'
477
+
478
+ pickerHoverHandler = (e) => {
479
+ const el = getRef(e.target)
480
+ if (el && el.node) {
481
+ showHighlight(el.node, 'rgba(66, 133, 244, 0.6)')
482
+ const path = getPathToRoot(el)
483
+ tooltip.textContent = (el.key || '?') + (el.tag ? ' <' + el.tag + '>' : '') + ' \u2014 ' + path
484
+ tooltip.style.display = 'block'
485
+ // Position tooltip near cursor
486
+ const tx = Math.min(e.clientX + 12, window.innerWidth - tooltip.offsetWidth - 8)
487
+ const ty = Math.min(e.clientY + 16, window.innerHeight - tooltip.offsetHeight - 8)
488
+ tooltip.style.left = tx + 'px'
489
+ tooltip.style.top = ty + 'px'
490
+ } else {
491
+ hideHighlight()
492
+ tooltip.style.display = 'none'
493
+ }
494
+ }
495
+
496
+ pickerClickHandler = (e) => {
497
+ e.preventDefault()
498
+ e.stopPropagation()
499
+ e.stopImmediatePropagation()
500
+
501
+ const el = getRef(e.target)
502
+ if (el) {
503
+ const path = getPathToRoot(el)
504
+ const info = getElementInfo(el)
505
+ // Store the pick result for the panel to read
506
+ window.__DOMQL_INSPECTOR__._lastPick = { path, info }
507
+ }
508
+
509
+ stopPicker()
510
+ }
511
+
512
+ pickerKeyHandler = (e) => {
513
+ if (e.key === 'Escape') {
514
+ e.preventDefault()
515
+ stopPicker()
516
+ }
517
+ }
518
+
519
+ document.addEventListener('mousemove', pickerHoverHandler, true)
520
+ document.addEventListener('click', pickerClickHandler, true)
521
+ document.addEventListener('keydown', pickerKeyHandler, true)
522
+ }
523
+
524
+ function stopPicker () {
525
+ pickerActive = false
526
+ document.body.style.cursor = ''
527
+ hideHighlight()
528
+
529
+ const tooltip = document.getElementById('__domql-picker-tooltip__')
530
+ if (tooltip) tooltip.style.display = 'none'
531
+
532
+ if (pickerHoverHandler) {
533
+ document.removeEventListener('mousemove', pickerHoverHandler, true)
534
+ pickerHoverHandler = null
535
+ }
536
+ if (pickerClickHandler) {
537
+ document.removeEventListener('click', pickerClickHandler, true)
538
+ pickerClickHandler = null
539
+ }
540
+ if (pickerKeyHandler) {
541
+ document.removeEventListener('keydown', pickerKeyHandler, true)
542
+ pickerKeyHandler = null
543
+ }
544
+ }
545
+
546
+ // Expose global API for the DevTools panel
547
+ window.__DOMQL_INSPECTOR__ = {
548
+ getRef,
549
+ getElementInfo,
550
+ buildTree,
551
+ findRoot,
552
+ serialize,
553
+ getPathToRoot,
554
+ METHOD_NAMES,
555
+ STATE_METHOD_NAMES,
556
+ _lastPick: null,
557
+
558
+ // Get tree from root
559
+ getTree (maxDepth) {
560
+ const root = findRoot()
561
+ if (!root) return null
562
+ return buildTree(root, 0, maxDepth || 8)
563
+ },
564
+
565
+ // Inspect a DOM node and return info + path
566
+ inspectNode (node) {
567
+ const el = getRef(node)
568
+ if (!el) return null
569
+ const info = getElementInfo(el)
570
+ info._path = getPathToRoot(el)
571
+ return info
572
+ },
573
+
574
+ // Navigate to child by key path (e.g., "App.Header.Nav.Link")
575
+ // First part is the root element's own key, remaining parts are children
576
+ navigatePath (path) {
577
+ const root = findRoot()
578
+ if (!root) return null
579
+ const parts = path.split('.')
580
+ let current = root
581
+ // If first part matches root's key, skip it (it refers to root itself)
582
+ var start = 0
583
+ if (parts[0] === root.key) start = 1
584
+ for (var i = start; i < parts.length; i++) {
585
+ if (!current[parts[i]]) return null
586
+ current = current[parts[i]]
587
+ }
588
+ return getElementInfo(current)
589
+ },
590
+
591
+ // Get element ref by path (returns the actual element, not serialized)
592
+ getElementByPath (path) {
593
+ const root = findRoot()
594
+ if (!root) return null
595
+ const parts = path.split('.')
596
+ let current = root
597
+ var start = 0
598
+ if (parts[0] === root.key) start = 1
599
+ for (var i = start; i < parts.length; i++) {
600
+ if (!current[parts[i]]) return null
601
+ current = current[parts[i]]
602
+ }
603
+ return current
604
+ },
605
+
606
+ // Update state value
607
+ updateState (path, key, value) {
608
+ const el = this.getElementByPath(path)
609
+ if (!el || !el.state) return { error: 'Element or state not found' }
610
+ try {
611
+ el.state.update({ [key]: value })
612
+ return { success: true }
613
+ } catch (e) {
614
+ return { error: e.message }
615
+ }
616
+ },
617
+
618
+ // Update prop value — uses setProps to update and re-render
619
+ // text is a top-level DOMQL property, not inside props
620
+ updateProp (path, key, value) {
621
+ const el = this.getElementByPath(path)
622
+ if (!el) return { error: 'Element not found' }
623
+ try {
624
+ if (key === 'text') {
625
+ el.update({ text: value })
626
+ } else if (typeof el.setProps === 'function') {
627
+ el.setProps({ [key]: value })
628
+ } else {
629
+ el.update({ props: { [key]: value } })
630
+ }
631
+ return { success: true }
632
+ } catch (e) {
633
+ return { error: e.message }
634
+ }
635
+ },
636
+
637
+ // Call a method on element
638
+ callMethod (path, method, args) {
639
+ const el = this.getElementByPath(path)
640
+ if (!el) return { error: 'Element not found' }
641
+ if (typeof el[method] !== 'function') return { error: 'Method not found: ' + method }
642
+ try {
643
+ const result = el[method].apply(el, args || [])
644
+ return { success: true, result: serialize(result, 0, 3, new WeakSet()) }
645
+ } catch (e) {
646
+ return { error: e.message }
647
+ }
648
+ },
649
+
650
+ // Call a state method
651
+ callStateMethod (path, method, args) {
652
+ const el = this.getElementByPath(path)
653
+ if (!el || !el.state) return { error: 'Element or state not found' }
654
+ if (typeof el.state[method] !== 'function') return { error: 'State method not found: ' + method }
655
+ try {
656
+ const result = el.state[method].apply(el.state, args || [])
657
+ return { success: true, result: serialize(result, 0, 3, new WeakSet()) }
658
+ } catch (e) {
659
+ return { error: e.message }
660
+ }
661
+ },
662
+
663
+ // Highlight element by path
664
+ highlight (path) {
665
+ const el = this.getElementByPath(path)
666
+ if (!el || !el.node) return
667
+ showHighlight(el.node)
668
+ },
669
+
670
+ // Remove highlight
671
+ removeHighlight () {
672
+ hideHighlight()
673
+ },
674
+
675
+ // Picker mode
676
+ startPicker () {
677
+ startPicker()
678
+ },
679
+
680
+ stopPicker () {
681
+ stopPicker()
682
+ },
683
+
684
+ isPickerActive () {
685
+ return pickerActive
686
+ },
687
+
688
+ // Get design system from any element's context
689
+ getDesignSystem (el) {
690
+ const target = el || findRoot()
691
+ if (!target) return null
692
+ const ds = (target.context && target.context.designSystem) || target.designSystem
693
+ if (!ds || typeof ds !== 'object') return null
694
+ // Custom serializer that handles DS-specific structures
695
+ const seen = new WeakSet()
696
+ function ser (v, d) {
697
+ if (d > 5) return { __type: 'truncated' }
698
+ if (v === null) return null
699
+ if (v === undefined) return { __type: 'undefined' }
700
+ if (typeof v === 'function') return { __type: 'function', name: v.name || '' }
701
+ if (typeof v !== 'object') return v
702
+ if (v instanceof HTMLElement || v instanceof Node) return { __type: 'node' }
703
+ if (seen.has(v)) return { __type: 'circular' }
704
+ seen.add(v)
705
+ if (Array.isArray(v)) return v.slice(0, 50).map(function (x) { return ser(x, d + 1) })
706
+ var o = {}
707
+ var keys = Object.keys(v)
708
+ for (var i = 0; i < keys.length && i < 200; i++) {
709
+ var k = keys[i]
710
+ if (k === 'parent' || k === 'node' || k === '__element' || k === 'context') continue
711
+ try { o[k] = ser(v[k], d + 1) } catch (e) { /* skip */ }
712
+ }
713
+ return o
714
+ }
715
+ return ser(ds, 0)
716
+ },
717
+
718
+ // Read and clear last pick result
719
+ consumePick () {
720
+ const result = this._lastPick
721
+ this._lastPick = null
722
+ return result
723
+ }
724
+ }
725
+
726
+ console.log('[Symbols Connect] Page agent loaded')
727
+ })()