@quanta-intellect/vessel-browser 0.1.11 → 0.1.13

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.
@@ -2128,33 +2128,76 @@ const elementSelectors = {};
2128
2128
  let indexedElements = /* @__PURE__ */ new WeakMap();
2129
2129
  const indexedElementRefs = {};
2130
2130
  let activeOverlays = [];
2131
- function deepQuerySelectorAll(selector, root = document) {
2132
- const results = [];
2133
- root.querySelectorAll(selector).forEach((el) => results.push(el));
2134
- const MAX_SHADOW_HOSTS = 50;
2135
- const MAX_DEPTH = 3;
2131
+ const MAX_SHADOW_HOSTS = 150;
2132
+ const MAX_SHADOW_DEPTH = 5;
2133
+ const MAX_WALK_ELEMENTS = 1e4;
2134
+ function collectShadowRoots(root) {
2136
2135
  const shadowRoots = [];
2137
- const findShadowRoots = (node, depth) => {
2138
- if (depth > MAX_DEPTH || shadowRoots.length >= MAX_SHADOW_HOSTS) return;
2139
- const children = node.children;
2140
- for (let i = 0; i < children.length && shadowRoots.length < MAX_SHADOW_HOSTS; i++) {
2141
- const el = children[i];
2136
+ let walked = 0;
2137
+ const walk = (node, depth) => {
2138
+ if (depth > MAX_SHADOW_DEPTH || shadowRoots.length >= MAX_SHADOW_HOSTS) return;
2139
+ const tw = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT);
2140
+ let el = tw.nextNode();
2141
+ while (el && walked < MAX_WALK_ELEMENTS && shadowRoots.length < MAX_SHADOW_HOSTS) {
2142
+ walked++;
2142
2143
  if (el.shadowRoot) {
2143
2144
  shadowRoots.push(el.shadowRoot);
2144
- findShadowRoots(el.shadowRoot, depth + 1);
2145
- }
2146
- if (el.children.length > 0 && el.children.length < 200) {
2147
- findShadowRoots(el, depth);
2145
+ walk(el.shadowRoot, depth + 1);
2148
2146
  }
2147
+ el = tw.nextNode();
2149
2148
  }
2150
2149
  };
2151
- findShadowRoots(root, 0);
2152
- for (const sr of shadowRoots) {
2150
+ walk(root, 0);
2151
+ return shadowRoots;
2152
+ }
2153
+ function deepQuerySelectorAll(selector, root = document) {
2154
+ const results = [];
2155
+ root.querySelectorAll(selector).forEach((el) => results.push(el));
2156
+ for (const sr of collectShadowRoots(root)) {
2153
2157
  sr.querySelectorAll(selector).forEach((el) => results.push(el));
2154
2158
  }
2155
2159
  return results;
2156
2160
  }
2161
+ function isInShadowDom(el) {
2162
+ return el.getRootNode() instanceof ShadowRoot;
2163
+ }
2164
+ function generateShadowPiercingSelector(el) {
2165
+ const segments = [];
2166
+ let current = el;
2167
+ while (current) {
2168
+ const rootNode = current.getRootNode();
2169
+ const innerSel = generateStableSelector(current);
2170
+ if (rootNode instanceof ShadowRoot) {
2171
+ segments.unshift(innerSel);
2172
+ current = rootNode.host;
2173
+ } else {
2174
+ segments.unshift(innerSel);
2175
+ break;
2176
+ }
2177
+ }
2178
+ if (segments.length <= 1) return null;
2179
+ return segments.join(" >>> ");
2180
+ }
2181
+ function resolveShadowSelector(selectorPath) {
2182
+ const segments = selectorPath.split(" >>> ").map((s) => s.trim());
2183
+ let scope = document;
2184
+ for (let i = 0; i < segments.length; i++) {
2185
+ const el = scope.querySelector(segments[i]);
2186
+ if (!el) return null;
2187
+ if (i < segments.length - 1) {
2188
+ if (!el.shadowRoot) return null;
2189
+ scope = el.shadowRoot;
2190
+ } else {
2191
+ return el;
2192
+ }
2193
+ }
2194
+ return null;
2195
+ }
2157
2196
  function generateSelector(el) {
2197
+ if (isInShadowDom(el)) {
2198
+ const shadowPath = generateShadowPiercingSelector(el);
2199
+ if (shadowPath) return shadowPath;
2200
+ }
2158
2201
  return generateStableSelector(el);
2159
2202
  }
2160
2203
  function assignIndex(el) {
@@ -2672,7 +2715,7 @@ function extractLandmarks() {
2672
2715
  "dialog, [role='dialog'], [role='alertdialog']"
2673
2716
  ];
2674
2717
  selectors.forEach((selector) => {
2675
- document.querySelectorAll(selector).forEach((el) => {
2718
+ deepQuerySelectorAll(selector).forEach((el) => {
2676
2719
  const tag = el.tagName.toLowerCase();
2677
2720
  const role = el.getAttribute("role") || (tag === "header" ? "banner" : tag === "nav" ? "navigation" : tag === "main" ? "main" : tag === "aside" ? "complementary" : tag === "footer" ? "contentinfo" : tag === "article" ? "article" : tag === "section" ? "region" : tag === "dialog" ? "dialog" : "generic");
2678
2721
  landmarks.push({
@@ -2896,6 +2939,7 @@ electron.contextBridge.exposeInMainWorld("__vessel", {
2896
2939
  extractContent: vesselExtractContent,
2897
2940
  getElementSelector: resolveElementSelector,
2898
2941
  interactByIndex,
2942
+ resolveShadowSelector,
2899
2943
  notifyHighlightSelection: (text) => {
2900
2944
  if (typeof text === "string" && text.trim()) {
2901
2945
  electron.ipcRenderer.send("vessel:highlight-selection", text.trim());
@@ -35,6 +35,7 @@ const Channels = {
35
35
  SIDEBAR_RESIZE: "ui:sidebar-resize",
36
36
  SIDEBAR_RESIZE_START: "ui:sidebar-resize-start",
37
37
  SIDEBAR_RESIZE_COMMIT: "ui:sidebar-resize-commit",
38
+ SIDEBAR_CONTEXT_MENU: "ui:sidebar-context-menu",
38
39
  FOCUS_MODE_TOGGLE: "ui:focus-mode-toggle",
39
40
  SETTINGS_VISIBILITY: "ui:settings-visibility",
40
41
  // Settings
@@ -47,12 +48,18 @@ const Channels = {
47
48
  BOOKMARKS_UPDATE: "bookmarks:update",
48
49
  BOOKMARK_SAVE: "bookmarks:save",
49
50
  BOOKMARK_REMOVE: "bookmarks:remove",
51
+ BOOKMARK_ADD_CONTEXT_TO_CHAT: "bookmarks:add-context-to-chat",
50
52
  FOLDER_CREATE: "bookmarks:folder-create",
51
53
  FOLDER_REMOVE: "bookmarks:folder-remove",
52
54
  FOLDER_RENAME: "bookmarks:folder-rename",
53
55
  // Highlights
54
56
  HIGHLIGHT_CAPTURE: "highlights:capture",
55
57
  HIGHLIGHT_CAPTURE_RESULT: "highlights:capture-result",
58
+ HIGHLIGHT_NAV_COUNT: "highlights:nav-count",
59
+ HIGHLIGHT_NAV_SCROLL: "highlights:nav-scroll",
60
+ HIGHLIGHT_NAV_REMOVE: "highlights:nav-remove",
61
+ HIGHLIGHT_NAV_CLEAR: "highlights:nav-clear",
62
+ SIDEBAR_HIGHLIGHT_ACTION: "highlights:sidebar-action",
56
63
  // DevTools panel
57
64
  DEVTOOLS_PANEL_TOGGLE: "devtools-panel:toggle",
58
65
  DEVTOOLS_PANEL_STATE: "devtools-panel:state",
@@ -120,6 +127,15 @@ const api = {
120
127
  const handler = (_, result) => cb(result);
121
128
  electron.ipcRenderer.on(Channels.HIGHLIGHT_CAPTURE_RESULT, handler);
122
129
  return () => electron.ipcRenderer.removeListener(Channels.HIGHLIGHT_CAPTURE_RESULT, handler);
130
+ },
131
+ getCount: () => electron.ipcRenderer.invoke(Channels.HIGHLIGHT_NAV_COUNT),
132
+ scrollTo: (index) => electron.ipcRenderer.invoke(Channels.HIGHLIGHT_NAV_SCROLL, index),
133
+ remove: (index) => electron.ipcRenderer.invoke(Channels.HIGHLIGHT_NAV_REMOVE, index),
134
+ clearAll: () => electron.ipcRenderer.invoke(Channels.HIGHLIGHT_NAV_CLEAR),
135
+ onSidebarAction: (cb) => {
136
+ const handler = (_, action) => cb(action);
137
+ electron.ipcRenderer.on(Channels.SIDEBAR_HIGHLIGHT_ACTION, handler);
138
+ return () => electron.ipcRenderer.removeListener(Channels.SIDEBAR_HIGHLIGHT_ACTION, handler);
123
139
  }
124
140
  },
125
141
  ui: {
@@ -127,6 +143,11 @@ const api = {
127
143
  startSidebarResize: () => electron.ipcRenderer.invoke(Channels.SIDEBAR_RESIZE_START),
128
144
  resizeSidebar: (width) => electron.ipcRenderer.invoke(Channels.SIDEBAR_RESIZE, width),
129
145
  commitSidebarResize: () => electron.ipcRenderer.invoke(Channels.SIDEBAR_RESIZE_COMMIT),
146
+ onSidebarContextMenu: (cb) => {
147
+ const handler = (_, position) => cb(position);
148
+ electron.ipcRenderer.on(Channels.SIDEBAR_CONTEXT_MENU, handler);
149
+ return () => electron.ipcRenderer.removeListener(Channels.SIDEBAR_CONTEXT_MENU, handler);
150
+ },
130
151
  toggleFocusMode: () => electron.ipcRenderer.invoke(Channels.FOCUS_MODE_TOGGLE),
131
152
  setSettingsVisibility: (open) => electron.ipcRenderer.invoke(Channels.SETTINGS_VISIBILITY, open)
132
153
  },
@@ -148,6 +169,14 @@ const api = {
148
169
  createFolderWithSummary: (name, summary) => electron.ipcRenderer.invoke(Channels.FOLDER_CREATE, name, summary),
149
170
  removeFolder: (id) => electron.ipcRenderer.invoke(Channels.FOLDER_REMOVE, id),
150
171
  renameFolder: (id, newName, summary) => electron.ipcRenderer.invoke(Channels.FOLDER_RENAME, id, newName, summary),
172
+ onAddContextToChat: (cb) => {
173
+ const handler = (_, bookmarkId) => cb(bookmarkId);
174
+ electron.ipcRenderer.on(Channels.BOOKMARK_ADD_CONTEXT_TO_CHAT, handler);
175
+ return () => electron.ipcRenderer.removeListener(
176
+ Channels.BOOKMARK_ADD_CONTEXT_TO_CHAT,
177
+ handler
178
+ );
179
+ },
151
180
  onUpdate: (cb) => {
152
181
  const handler = (_, state) => cb(state);
153
182
  electron.ipcRenderer.on(Channels.BOOKMARKS_UPDATE, handler);
@@ -2533,6 +2533,101 @@
2533
2533
  border-color: var(--border-visible);
2534
2534
  }
2535
2535
 
2536
+ /* ═══════════════════════════════════════
2537
+ Highlight navigation bar
2538
+ ═══════════════════════════════════════ */
2539
+
2540
+ .highlight-nav {
2541
+ display: flex;
2542
+ align-items: center;
2543
+ justify-content: flex-end;
2544
+ gap: 2px;
2545
+ padding: 4px 14px;
2546
+ flex-shrink: 0;
2547
+ }
2548
+
2549
+ .highlight-nav-btn {
2550
+ display: inline-flex;
2551
+ align-items: center;
2552
+ justify-content: center;
2553
+ width: 24px;
2554
+ height: 24px;
2555
+ padding: 0;
2556
+ color: var(--text-muted);
2557
+ background: transparent;
2558
+ border: 1px solid transparent;
2559
+ border-radius: var(--radius-sm);
2560
+ cursor: pointer;
2561
+ transition: color var(--duration-fast) var(--ease-in-out),
2562
+ background var(--duration-fast) var(--ease-in-out);
2563
+ }
2564
+
2565
+ .highlight-nav-btn:hover:not(:disabled) {
2566
+ color: var(--text-primary);
2567
+ background: var(--bg-tertiary);
2568
+ }
2569
+
2570
+ .highlight-nav-btn:disabled {
2571
+ opacity: 0.3;
2572
+ cursor: default;
2573
+ }
2574
+
2575
+ .highlight-nav-label {
2576
+ display: inline-flex;
2577
+ align-items: center;
2578
+ gap: 5px;
2579
+ padding: 3px 10px;
2580
+ font-size: 11px;
2581
+ font-weight: 500;
2582
+ color: rgba(196, 160, 90, 0.85);
2583
+ background: rgba(196, 160, 90, 0.08);
2584
+ border: 1px solid rgba(196, 160, 90, 0.2);
2585
+ border-radius: var(--radius-md);
2586
+ cursor: pointer;
2587
+ transition: background var(--duration-fast) var(--ease-in-out),
2588
+ border-color var(--duration-fast) var(--ease-in-out);
2589
+ white-space: nowrap;
2590
+ }
2591
+
2592
+ .highlight-nav-label:hover {
2593
+ background: rgba(196, 160, 90, 0.14);
2594
+ border-color: rgba(196, 160, 90, 0.35);
2595
+ }
2596
+
2597
+ .hl-context-menu {
2598
+ position: fixed;
2599
+ z-index: 9999;
2600
+ min-width: 160px;
2601
+ padding: 4px 0;
2602
+ background: var(--bg-secondary);
2603
+ border: 1px solid var(--border-visible);
2604
+ border-radius: var(--radius-md);
2605
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
2606
+ }
2607
+
2608
+ .hl-context-item {
2609
+ display: block;
2610
+ width: 100%;
2611
+ padding: 6px 12px;
2612
+ font-size: 11.5px;
2613
+ color: var(--text-secondary);
2614
+ background: transparent;
2615
+ border: none;
2616
+ cursor: pointer;
2617
+ text-align: left;
2618
+ transition: background var(--duration-fast) var(--ease-in-out);
2619
+ }
2620
+
2621
+ .hl-context-item:hover {
2622
+ background: rgba(255, 255, 255, 0.06);
2623
+ color: var(--text-primary);
2624
+ }
2625
+
2626
+ .hl-context-danger:hover {
2627
+ color: #e06060;
2628
+ background: rgba(224, 96, 96, 0.08);
2629
+ }
2630
+
2536
2631
  .sidebar-input-area {
2537
2632
  display: flex;
2538
2633
  gap: 6px;