@quanta-intellect/vessel-browser 0.1.60 → 0.1.61

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/README.md CHANGED
@@ -93,6 +93,8 @@ Today, Vessel provides the browser shell, page visibility, and supervisory surfa
93
93
  - **Chat Assistant** — built-in conversational AI in the sidebar Chat tab; supports Anthropic, OpenAI, Ollama, llama.cpp, Mistral, xAI, Google Gemini, OpenRouter, and any OpenAI-compatible endpoint; reads the current page automatically; has full access to the same browser tools as external agents; multi-turn session history; configure provider, model, and API key in Settings
94
94
  - **Automation Kits** (Premium) — parameterized workflow templates in the sidebar Automate tab; fill in a short form and the built-in agent executes the workflow autonomously; bundled kits include Research & Collect (multi-source research with bookmark saving) and Price Scout (cross-retailer price comparison); designed for a future kit marketplace
95
95
  - **Dev Tools Panel** (`F12`) — inspect console output, network requests, and MCP/agent activity in a resizable panel at the bottom of the window; export logs by category and date range as JSON
96
+ - **Agent-Meaningful Bookmarks** — bookmarks carry structured context the agent can read and act on: `intent` (what the page is for), `expectedContent` (what to expect on the page), `keyFields` (important form fields), `agentHints` (arbitrary directives), and a stored `pageSchema`; all fields are searchable
97
+ - **Page Schema Inference** — Vessel automatically infers a typed schema for every page: `pageType` (article, product, form, search, checkout, login, dashboard), `primaryEntity` (structured fields for products and articles), `formFields` (with names, types, labels, selectors), and `actionButtons` (with inferred intents: submit, addToCart, login, etc.); schema is attached to every content extraction result
96
98
  - **Bookmarks for Agents** — save pages into folders, attach one-line folder summaries, and search bookmarks over MCP instead of dumping the entire library
97
99
  - **Named Session Persistence** — save cookies, localStorage, and current tab layout under a reusable name, then reload it after a restart
98
100
  - **Page Highlights** — agents can visually highlight text or elements on any page with labeled, color-coded markers that persist across navigation; highlight count and navigation controls appear in the sidebar; cleared explicitly or via tool call
package/out/main/index.js CHANGED
@@ -3903,10 +3903,8 @@ function startBackgroundRevalidation() {
3903
3903
  }, REVALIDATION_INTERVAL_MS);
3904
3904
  }
3905
3905
  function stopBackgroundRevalidation() {
3906
- if (revalidationTimer) {
3907
- clearInterval(revalidationTimer);
3908
- revalidationTimer = null;
3909
- }
3906
+ clearInterval(revalidationTimer);
3907
+ revalidationTimer = null;
3910
3908
  }
3911
3909
  function isPremiumActiveState(state2) {
3912
3910
  return state2.status === "active" || state2.status === "trialing";
@@ -4011,8 +4009,8 @@ function stopTelemetry() {
4011
4009
  }
4012
4010
  if (flushTimer) {
4013
4011
  clearInterval(flushTimer);
4014
- flushTimer = null;
4015
4012
  }
4013
+ flushTimer = null;
4016
4014
  void flush();
4017
4015
  }
4018
4016
  async function flush() {
@@ -4113,6 +4111,226 @@ function selectorHelpersJS(attributes = DEFAULT_SELECTOR_ATTRIBUTES) {
4113
4111
  "}"
4114
4112
  ].join("\n");
4115
4113
  }
4114
+ function normalizeString(value) {
4115
+ return (value ?? "").trim().toLowerCase();
4116
+ }
4117
+ function mapInputType(el) {
4118
+ const inputType = el.inputType ?? el.type ?? "text";
4119
+ switch (inputType.toLowerCase()) {
4120
+ case "email":
4121
+ return "email";
4122
+ case "password":
4123
+ return "password";
4124
+ case "number":
4125
+ case "range":
4126
+ return "number";
4127
+ case "select-one":
4128
+ case "select":
4129
+ return "select";
4130
+ case "checkbox":
4131
+ case "radio":
4132
+ return "checkbox";
4133
+ case "date":
4134
+ case "datetime-local":
4135
+ case "time":
4136
+ case "month":
4137
+ case "week":
4138
+ return "date";
4139
+ case "file":
4140
+ return "file";
4141
+ default:
4142
+ return "text";
4143
+ }
4144
+ }
4145
+ function mapFormFields(forms, interactiveElements) {
4146
+ const fields = [];
4147
+ const formFieldSelectors = /* @__PURE__ */ new Set();
4148
+ for (const form of forms) {
4149
+ for (const el of form.fields ?? []) {
4150
+ formFieldSelectors.add(el.selector || el.name || el.label || String(el.index));
4151
+ }
4152
+ }
4153
+ for (const el of interactiveElements) {
4154
+ const key = el.selector || el.name || el.label || String(el.index);
4155
+ if (formFieldSelectors.has(key)) {
4156
+ fields.push({
4157
+ name: el.name || el.label || key,
4158
+ type: mapInputType(el),
4159
+ label: el.label,
4160
+ required: el.required,
4161
+ selector: el.selector || ""
4162
+ });
4163
+ }
4164
+ }
4165
+ return fields;
4166
+ }
4167
+ function mapActionButtons(interactiveElements) {
4168
+ const buttons = [];
4169
+ const seen = /* @__PURE__ */ new Set();
4170
+ for (const el of interactiveElements) {
4171
+ if (el.type !== "button" && el.type !== "submit" && el.type !== "reset") continue;
4172
+ const label = (el.label || el.textContent || "").trim();
4173
+ if (!label || seen.has(label)) continue;
4174
+ seen.add(label);
4175
+ let intent;
4176
+ const normalized = normalizeString(label);
4177
+ if (/\b(add to cart|buy now|add to bag|add to basket|shop now)\b/.test(normalized)) {
4178
+ intent = "addToCart";
4179
+ } else if (/\b(login|sign in|log in|signin|log-in)\b/.test(normalized)) {
4180
+ intent = "login";
4181
+ } else if (/\b(submit|send|continue|next|proceed|register|create account|sign up)\b/.test(normalized)) {
4182
+ intent = "submit";
4183
+ } else if (/\b(cancel|back|return|go back|close)\b/.test(normalized)) {
4184
+ intent = "cancel";
4185
+ } else if (/\b(download|export|save as)\b/.test(normalized)) {
4186
+ intent = "download";
4187
+ } else if (/\b(search|find|go|submit search)\b/.test(normalized)) {
4188
+ intent = "search";
4189
+ } else if (el.href || el.url) {
4190
+ intent = "navigate";
4191
+ }
4192
+ if (el.selector || el.name || el.label) {
4193
+ buttons.push({
4194
+ label,
4195
+ selector: el.selector || el.name || el.label,
4196
+ intent
4197
+ });
4198
+ }
4199
+ }
4200
+ return buttons;
4201
+ }
4202
+ function extractPrimaryEntity(pageType, structuredData, metaTags) {
4203
+ if (pageType === "product") {
4204
+ const product = structuredData?.find(
4205
+ (e) => e.types.some((t) => /^product$/i.test(t))
4206
+ );
4207
+ if (product) {
4208
+ const attrs = product.attributes ?? {};
4209
+ return {
4210
+ type: "Product",
4211
+ nameField: typeof attrs.name === "string" ? attrs.name : void 0,
4212
+ priceField: typeof attrs.price === "string" ? attrs.price : typeof attrs.offers === "object" && attrs.offers !== null ? String(attrs.offers["price"] ?? "") : void 0,
4213
+ imageField: typeof attrs.image === "string" ? attrs.image : Array.isArray(attrs.image) ? String(attrs.image[0]) : void 0,
4214
+ descriptionField: typeof attrs.description === "string" ? attrs.description : void 0,
4215
+ reviewsField: typeof attrs.reviews === "string" ? attrs.reviews : void 0,
4216
+ ratingField: typeof attrs.rating === "string" ? attrs.rating : void 0,
4217
+ addToCartField: void 0
4218
+ };
4219
+ }
4220
+ }
4221
+ if (pageType === "article") {
4222
+ const article = structuredData?.find(
4223
+ (e) => e.types.some(
4224
+ (t) => /^(article|newsarticle|blogposting|webpage)$/i.test(t)
4225
+ )
4226
+ );
4227
+ if (article) {
4228
+ const attrs = article.attributes ?? {};
4229
+ return {
4230
+ type: article.types[0] ?? "Article",
4231
+ nameField: typeof attrs.headline === "string" ? attrs.headline : typeof attrs.name === "string" ? attrs.name : void 0,
4232
+ descriptionField: typeof attrs.articleBody === "string" ? attrs.articleBody : typeof attrs.description === "string" ? attrs.description : void 0
4233
+ };
4234
+ }
4235
+ }
4236
+ return void 0;
4237
+ }
4238
+ function inferPageSchema(page) {
4239
+ let pageType = "unknown";
4240
+ let confidence = 0.5;
4241
+ const structuredData = page.structuredData;
4242
+ const metaTags = page.metaTags;
4243
+ const url = page.url ?? "";
4244
+ const forms = page.forms ?? [];
4245
+ const interactiveElements = page.interactiveElements ?? [];
4246
+ const urlLower = normalizeString(url);
4247
+ const jsonLdTypes = [];
4248
+ for (const entity of structuredData ?? []) {
4249
+ jsonLdTypes.push(...entity.types);
4250
+ }
4251
+ const hasProduct = jsonLdTypes.some((t) => /^product$/i.test(t));
4252
+ const hasArticle = jsonLdTypes.some(
4253
+ (t) => /^(article|newsarticle|blogposting)$/i.test(t)
4254
+ );
4255
+ const hasEvent = jsonLdTypes.some((t) => /^event$/i.test(t));
4256
+ const hasSearchResults = jsonLdTypes.some((t) => /^searchresultspage$/i.test(t));
4257
+ if (hasProduct) {
4258
+ pageType = "product";
4259
+ confidence += 0.2;
4260
+ } else if (hasArticle) {
4261
+ pageType = "article";
4262
+ confidence += 0.2;
4263
+ } else if (hasEvent) {
4264
+ pageType = "form";
4265
+ confidence += 0.15;
4266
+ } else if (hasSearchResults) {
4267
+ pageType = "search";
4268
+ confidence += 0.2;
4269
+ }
4270
+ const ogType = metaTags?.["og:type"];
4271
+ if (ogType) {
4272
+ const normalized = normalizeString(ogType);
4273
+ if (/^product$/i.test(normalized) && pageType !== "product") {
4274
+ pageType = "product";
4275
+ confidence += 0.15;
4276
+ } else if (/^article|blog|news/i.test(normalized) && pageType !== "article") {
4277
+ pageType = "article";
4278
+ confidence += 0.15;
4279
+ }
4280
+ }
4281
+ if (/\/checkout|\/cart|\/payment|\/billing/i.test(urlLower) && pageType === "unknown") {
4282
+ pageType = "checkout";
4283
+ confidence += 0.1;
4284
+ } else if (/\/login|\/signin|\/auth/i.test(urlLower) && pageType === "unknown") {
4285
+ pageType = "login";
4286
+ confidence += 0.1;
4287
+ } else if (/\/dashboard|\/account|\/profile/i.test(urlLower) && pageType === "unknown") {
4288
+ pageType = "dashboard";
4289
+ confidence += 0.1;
4290
+ } else if (/\/search/i.test(urlLower) && pageType === "unknown") {
4291
+ pageType = "search";
4292
+ confidence += 0.1;
4293
+ }
4294
+ const hasFormWithSubmit = forms.some(
4295
+ (f) => f.fields.some(
4296
+ (el) => el.type === "submit" || normalizeString(el.inputType) === "submit" || normalizeString(el.name) === "submit"
4297
+ )
4298
+ );
4299
+ const hasPriceSelectors = interactiveElements.some(
4300
+ (el) => el.selector?.includes("price") || normalizeString(el.label).includes("price") || normalizeString(el.name).includes("price") || el.selector?.includes("cost") || el.selector?.includes("amount")
4301
+ );
4302
+ if (pageType === "unknown") {
4303
+ if (hasFormWithSubmit && forms.length > 0) {
4304
+ pageType = "form";
4305
+ confidence += 0.1;
4306
+ } else if (hasPriceSelectors) {
4307
+ pageType = "product";
4308
+ confidence += 0.1;
4309
+ }
4310
+ }
4311
+ if ((pageType === "checkout" || pageType === "unknown") && forms.length > 0) {
4312
+ if (hasFormWithSubmit) {
4313
+ if (pageType === "unknown") {
4314
+ pageType = "form";
4315
+ }
4316
+ confidence += 0.15;
4317
+ }
4318
+ }
4319
+ if (pageType === "unknown") {
4320
+ confidence = 0.5;
4321
+ }
4322
+ confidence = Math.min(0.95, confidence);
4323
+ const primaryEntity = extractPrimaryEntity(pageType, structuredData);
4324
+ const formFields = pageType === "form" || pageType === "checkout" || pageType === "login" ? mapFormFields(forms, interactiveElements) : void 0;
4325
+ const actionButtons = mapActionButtons(interactiveElements);
4326
+ return {
4327
+ pageType,
4328
+ primaryEntity,
4329
+ formFields,
4330
+ actionButtons,
4331
+ confidence
4332
+ };
4333
+ }
4116
4334
  const EMPTY_PAGE_CONTENT = {
4117
4335
  title: "",
4118
4336
  content: "",
@@ -4826,156 +5044,6 @@ const DIRECT_EXTRACTION_SCRIPT = String.raw`
4826
5044
  };
4827
5045
  })()
4828
5046
  `;
4829
- const SAFE_EXTRACTION_SCRIPT = String.raw`
4830
- (function() {
4831
- function getCleanBodyText() {
4832
- var removed = [];
4833
- document
4834
- .querySelectorAll('.__vessel-highlight-label[data-vessel-highlight]')
4835
- .forEach(function(label) {
4836
- var parent = label.parentNode;
4837
- if (!parent) return;
4838
- removed.push({ label: label, parent: parent, nextSibling: label.nextSibling });
4839
- parent.removeChild(label);
4840
- });
4841
- try {
4842
- return document.body?.innerText || document.documentElement?.innerText || "";
4843
- } finally {
4844
- for (var i = removed.length - 1; i >= 0; i--) {
4845
- var entry = removed[i];
4846
- entry.parent.insertBefore(entry.label, entry.nextSibling);
4847
- }
4848
- }
4849
- }
4850
-
4851
- function text(value) {
4852
- const trimmed = value == null ? "" : String(value).trim();
4853
- return trimmed || undefined;
4854
- }
4855
-
4856
- function labelFor(el) {
4857
- const aria = text(el.getAttribute && el.getAttribute("aria-label"));
4858
- if (aria) return aria;
4859
- const placeholder = text(el.getAttribute && el.getAttribute("placeholder"));
4860
- if (placeholder) return placeholder;
4861
- if (el.id) {
4862
- const directLabel = document.querySelector('label[for="' + String(el.id).replace(/["\\]/g, "\\$&") + '"]');
4863
- const labelText = text(directLabel && directLabel.textContent);
4864
- if (labelText) return labelText;
4865
- }
4866
- return text(el.textContent);
4867
- }
4868
-
4869
- let indexCounter = 0;
4870
- function nextIndex() {
4871
- indexCounter += 1;
4872
- return indexCounter;
4873
- }
4874
-
4875
- const headings = Array.from(document.querySelectorAll("h1, h2, h3, h4, h5, h6"))
4876
- .map((el) => {
4877
- const headingText = text(el.textContent);
4878
- if (!headingText) return null;
4879
- return { level: Number.parseInt(el.tagName[1], 10), text: headingText };
4880
- })
4881
- .filter(Boolean);
4882
-
4883
- const navigation = Array.from(document.querySelectorAll("nav a[href], [role='navigation'] a[href], header nav a[href]"))
4884
- .map((el) => {
4885
- const href = text(el.href || el.getAttribute("href"));
4886
- const linkText = text(el.textContent);
4887
- if (!href || href.startsWith("#") || !linkText) return null;
4888
- return {
4889
- type: "link",
4890
- text: linkText.slice(0, 100),
4891
- href: href.slice(0, 500),
4892
- context: "nav",
4893
- index: nextIndex(),
4894
- visible: true,
4895
- disabled: false,
4896
- };
4897
- })
4898
- .filter(Boolean);
4899
-
4900
- const interactiveElements = [];
4901
- Array.from(document.querySelectorAll("button, [role='button'], input[type='submit'], input[type='button']"))
4902
- .forEach((el) => {
4903
- interactiveElements.push({
4904
- type: "button",
4905
- text: text(el.textContent || el.value || el.getAttribute("aria-label") || "Button")?.slice(0, 100),
4906
- index: nextIndex(),
4907
- visible: true,
4908
- disabled: !!(el.hasAttribute && (el.hasAttribute("disabled") || el.getAttribute("aria-disabled") === "true")),
4909
- });
4910
- });
4911
-
4912
- Array.from(document.querySelectorAll("a[href]")).forEach((el) => {
4913
- const href = text(el.href || el.getAttribute("href"));
4914
- const linkText = text(el.textContent);
4915
- if (!href || href.startsWith("#") || !linkText) return;
4916
- interactiveElements.push({
4917
- type: "link",
4918
- text: linkText.slice(0, 100),
4919
- href: href.slice(0, 500),
4920
- index: nextIndex(),
4921
- visible: true,
4922
- disabled: false,
4923
- });
4924
- });
4925
-
4926
- Array.from(document.querySelectorAll("input:not([type='hidden']):not([type='submit']):not([type='button']), select, textarea"))
4927
- .forEach((el) => {
4928
- const tag = el.tagName.toLowerCase();
4929
- var elType = (el.type || "").toLowerCase();
4930
- interactiveElements.push({
4931
- type: tag === "select" ? "select" : tag === "textarea" ? "textarea" : "input",
4932
- label: labelFor(el)?.slice(0, 100),
4933
- inputType: text(el.getAttribute && el.getAttribute("type")),
4934
- placeholder: text(el.getAttribute && el.getAttribute("placeholder")),
4935
- value: shouldExposeFieldValue(el) ? text(el.value) : undefined,
4936
- options: tag === "select"
4937
- ? Array.from(el.options || []).map(function(option) { return { label: text(option.textContent || option.value) || option.value, value: option.value }; }).filter(function(o) { return o.label || o.value; }).slice(0, 25)
4938
- : undefined,
4939
- required: !!(el.hasAttribute && el.hasAttribute("required")) || undefined,
4940
- index: nextIndex(),
4941
- visible: true,
4942
- disabled: !!(el.hasAttribute && (el.hasAttribute("disabled") || el.getAttribute("aria-disabled") === "true")),
4943
- name: el.name || undefined,
4944
- autocomplete: text(el.getAttribute && el.getAttribute("autocomplete")),
4945
- checked: (elType === "checkbox" || elType === "radio") ? !!el.checked : undefined,
4946
- });
4947
- });
4948
-
4949
- const forms = Array.from(document.querySelectorAll("form")).map((form) => ({
4950
- id: text(form.id),
4951
- action: text(form.getAttribute("action")),
4952
- method: text(form.getAttribute("method")),
4953
- fields: [],
4954
- }));
4955
-
4956
- return {
4957
- title: document.title || "",
4958
- content: getCleanBodyText(),
4959
- htmlContent: "",
4960
- byline: "",
4961
- excerpt: "",
4962
- url: window.location.href || "",
4963
- headings,
4964
- navigation,
4965
- interactiveElements,
4966
- forms,
4967
- viewport: {
4968
- width: window.innerWidth || document.documentElement?.clientWidth || 0,
4969
- height: window.innerHeight || document.documentElement?.clientHeight || 0,
4970
- scrollX: 0,
4971
- scrollY: 0,
4972
- },
4973
- overlays: [],
4974
- dormantOverlays: [],
4975
- landmarks: [],
4976
- };
4977
- })()
4978
- `;
4979
5047
  function delay(ms) {
4980
5048
  return new Promise((resolve) => setTimeout(resolve, ms));
4981
5049
  }
@@ -5012,7 +5080,7 @@ async function executeScript(webContents, script) {
5012
5080
  } catch {
5013
5081
  return null;
5014
5082
  } finally {
5015
- if (typeof timer !== "undefined" && timer) {
5083
+ if (timer) {
5016
5084
  clearTimeout(timer);
5017
5085
  }
5018
5086
  }
@@ -5087,10 +5155,17 @@ function mergePageContent(candidates, webContents) {
5087
5155
  headings: mergedBase.headings,
5088
5156
  metaTags: mergedBase.metaTags
5089
5157
  });
5158
+ const pageSchema = inferPageSchema({
5159
+ ...mergedBase,
5160
+ structuredData: normalizedStructuredData,
5161
+ title: mergedBase.title || webContents.getTitle() || "",
5162
+ url: mergedBase.url || webContents.getURL() || ""
5163
+ });
5090
5164
  return {
5091
5165
  ...mergedBase,
5092
5166
  structuredData: normalizedStructuredData,
5093
5167
  pageIssues,
5168
+ pageSchema,
5094
5169
  title: mergedBase.title || webContents.getTitle() || "",
5095
5170
  url: mergedBase.url || webContents.getURL() || ""
5096
5171
  };
@@ -5116,13 +5191,12 @@ async function estimateExtractionTimeout(webContents) {
5116
5191
  }
5117
5192
  async function extractContentInner(webContents) {
5118
5193
  await waitForDomReady(webContents);
5119
- const [preloadResult, directResult, safeResult] = await Promise.all([
5194
+ const [preloadResult, directResult] = await Promise.all([
5120
5195
  executeScript(webContents, PRELOAD_EXTRACTION_SCRIPT),
5121
- executeScript(webContents, DIRECT_EXTRACTION_SCRIPT),
5122
- executeScript(webContents, SAFE_EXTRACTION_SCRIPT)
5196
+ executeScript(webContents, DIRECT_EXTRACTION_SCRIPT)
5123
5197
  ]);
5124
5198
  return mergePageContent(
5125
- [preloadResult, directResult, safeResult],
5199
+ [preloadResult, directResult],
5126
5200
  webContents
5127
5201
  );
5128
5202
  }
@@ -5179,7 +5253,8 @@ function normalizePageContent(value) {
5179
5253
  rdfa: Array.isArray(page.rdfa) ? page.rdfa : [],
5180
5254
  metaTags: page.metaTags && typeof page.metaTags === "object" && !Array.isArray(page.metaTags) ? page.metaTags : {},
5181
5255
  structuredData: Array.isArray(page.structuredData) ? page.structuredData : [],
5182
- pageIssues: Array.isArray(page.pageIssues) ? page.pageIssues : []
5256
+ pageIssues: Array.isArray(page.pageIssues) ? page.pageIssues : [],
5257
+ pageSchema: page.pageSchema
5183
5258
  };
5184
5259
  }
5185
5260
  const latestPageDiffs = /* @__PURE__ */ new Map();
@@ -5189,17 +5264,19 @@ const pendingPageSnapshotDueAt = /* @__PURE__ */ new Map();
5189
5264
  const lastMutationSnapshotAt = /* @__PURE__ */ new Map();
5190
5265
  const lastMutationActivityAt = /* @__PURE__ */ new Map();
5191
5266
  const destroyListenerAttached = /* @__PURE__ */ new WeakSet();
5267
+ function cleanupTimersForWcId(wcId) {
5268
+ const timer = pendingPageSnapshotTimers.get(wcId);
5269
+ if (timer) clearTimeout(timer);
5270
+ pendingPageSnapshotTimers.delete(wcId);
5271
+ pendingPageSnapshotDueAt.delete(wcId);
5272
+ lastMutationSnapshotAt.delete(wcId);
5273
+ lastMutationActivityAt.delete(wcId);
5274
+ }
5192
5275
  function attachDestroyCleanup(wc) {
5193
5276
  if (destroyListenerAttached.has(wc)) return;
5194
5277
  destroyListenerAttached.add(wc);
5195
5278
  wc.once("destroyed", () => {
5196
- const wcId = wc.id;
5197
- const timer = pendingPageSnapshotTimers.get(wcId);
5198
- if (timer) clearTimeout(timer);
5199
- pendingPageSnapshotTimers.delete(wcId);
5200
- pendingPageSnapshotDueAt.delete(wcId);
5201
- lastMutationSnapshotAt.delete(wcId);
5202
- lastMutationActivityAt.delete(wcId);
5279
+ cleanupTimersForWcId(wc.id);
5203
5280
  });
5204
5281
  }
5205
5282
  const MIN_MUTATION_CAPTURE_INTERVAL_MS = 5e3;
@@ -5271,8 +5348,7 @@ function scheduleTimerAt(wc, sendToRendererViews, dueAt) {
5271
5348
  const existing = pendingPageSnapshotTimers.get(wcId);
5272
5349
  if (existing) clearTimeout(existing);
5273
5350
  const timer = setTimeout(() => {
5274
- pendingPageSnapshotTimers.delete(wcId);
5275
- pendingPageSnapshotDueAt.delete(wcId);
5351
+ cleanupTimersForWcId(wcId);
5276
5352
  if (wc.isDestroyed()) return;
5277
5353
  lastMutationSnapshotAt.set(wcId, Date.now());
5278
5354
  void capturePageSnapshot(wc.getURL(), wc, sendToRendererViews);
@@ -5596,9 +5672,9 @@ function resizeSidebarViews(state2) {
5596
5672
  const contentWidth = width - sidebarWidth;
5597
5673
  sidebarView.setBounds({
5598
5674
  x: width - sidebarWidth - resizeHandleOverlap,
5599
- y: 0,
5675
+ y: chromeHeight,
5600
5676
  width: sidebarWidth + resizeHandleOverlap,
5601
- height
5677
+ height: height - chromeHeight
5602
5678
  });
5603
5679
  if (uiState.devtoolsPanelOpen) {
5604
5680
  devtoolsPanelView.setBounds({
@@ -9761,7 +9837,17 @@ const TOOL_DEFINITIONS = [
9761
9837
  folderSummary: zod.z.string().optional().describe("Optional summary used if a new folder is created"),
9762
9838
  createFolderIfMissing: zod.z.boolean().optional().describe("Create folderName automatically when it does not exist"),
9763
9839
  note: zod.z.string().optional().describe("Optional note about why the page was saved"),
9764
- onDuplicate: zod.z.enum(["ask", "update", "duplicate"]).optional().describe("How to handle duplicate URLs in the same folder")
9840
+ onDuplicate: zod.z.enum(["ask", "update", "duplicate"]).optional().describe("How to handle duplicate URLs in the same folder"),
9841
+ intent: zod.z.string().optional().describe(
9842
+ "Human-readable description of what this bookmark is for (e.g. 'expense reporting')"
9843
+ ),
9844
+ expectedContent: zod.z.string().optional().describe(
9845
+ "Brief description of the content the agent should expect to find here"
9846
+ ),
9847
+ keyFields: zod.z.array(zod.z.string()).optional().describe(
9848
+ "Important form field names for this page (e.g. ['receipt_id', 'date', 'amount'])"
9849
+ ),
9850
+ agentHints: zod.z.record(zod.z.string(), zod.z.string()).optional().describe("Arbitrary key-value hints for the agent")
9765
9851
  },
9766
9852
  tier: 1
9767
9853
  },
@@ -9780,7 +9866,11 @@ const TOOL_DEFINITIONS = [
9780
9866
  folderSummary: zod.z.string().optional().describe("Optional summary for new folder"),
9781
9867
  createFolderIfMissing: zod.z.boolean().optional().describe("Create folderName automatically when it does not exist"),
9782
9868
  note: zod.z.string().optional().describe("Optional note"),
9783
- archive: zod.z.boolean().optional().describe('If true, organize into the default "Archive" folder')
9869
+ archive: zod.z.boolean().optional().describe('If true, organize into the default "Archive" folder'),
9870
+ intent: zod.z.string().optional().describe("Human-readable description of what this bookmark is for"),
9871
+ expectedContent: zod.z.string().optional().describe("Brief description of content the agent should expect"),
9872
+ keyFields: zod.z.array(zod.z.string()).optional().describe("Important form field names for this page"),
9873
+ agentHints: zod.z.record(zod.z.string(), zod.z.string()).optional().describe("Arbitrary key-value hints for the agent")
9784
9874
  },
9785
9875
  tier: 2
9786
9876
  },
@@ -10352,7 +10442,9 @@ const FIELD_WEIGHTS = {
10352
10442
  note: 5,
10353
10443
  folder: 3,
10354
10444
  folderSummary: 2,
10355
- url: 1
10445
+ url: 1,
10446
+ intent: 4,
10447
+ expectedContent: 3
10356
10448
  };
10357
10449
  function normalizeBookmarkSearchText(value) {
10358
10450
  let normalized = value.toLowerCase();
@@ -10383,7 +10475,9 @@ function getBookmarkSearchMatch(args) {
10383
10475
  url: args.url,
10384
10476
  note: args.note,
10385
10477
  folder: args.folder,
10386
- folderSummary: args.folderSummary
10478
+ folderSummary: args.folderSummary,
10479
+ intent: args.intent,
10480
+ expectedContent: args.expectedContent
10387
10481
  };
10388
10482
  for (const field of Object.keys(values)) {
10389
10483
  if (!bookmarkFieldMatchesQuery(values[field], normalizedQuery, tokens)) {
@@ -10523,7 +10617,9 @@ function searchBookmarks(query) {
10523
10617
  url: bookmark.url,
10524
10618
  note: bookmark.note,
10525
10619
  folder: folder?.name,
10526
- folderSummary: folder?.summary
10620
+ folderSummary: folder?.summary,
10621
+ intent: bookmark.intent,
10622
+ expectedContent: bookmark.expectedContent
10527
10623
  });
10528
10624
  return {
10529
10625
  bookmark,
@@ -10603,6 +10699,9 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
10603
10699
  if (note !== void 0) {
10604
10700
  bookmark2.note = note.trim() || void 0;
10605
10701
  }
10702
+ if (options?.extra) {
10703
+ Object.assign(bookmark2, options.extra);
10704
+ }
10606
10705
  bookmark2.savedAt = (/* @__PURE__ */ new Date()).toISOString();
10607
10706
  save();
10608
10707
  emit();
@@ -10618,7 +10717,8 @@ function saveBookmarkWithPolicy(url, title, folderId, note, options) {
10618
10717
  title: normalizedTitle,
10619
10718
  note: note?.trim() || void 0,
10620
10719
  folderId: targetId,
10621
- savedAt: (/* @__PURE__ */ new Date()).toISOString()
10720
+ savedAt: (/* @__PURE__ */ new Date()).toISOString(),
10721
+ ...options?.extra
10622
10722
  };
10623
10723
  state$1.bookmarks.push(bookmark);
10624
10724
  save();
@@ -10654,6 +10754,21 @@ function updateBookmark(id, updates) {
10654
10754
  if (typeof updates.folderId === "string") {
10655
10755
  bookmark.folderId = updates.folderId && updates.folderId !== UNSORTED_ID ? state$1.folders.find((item) => item.id === updates.folderId)?.id ?? UNSORTED_ID : UNSORTED_ID;
10656
10756
  }
10757
+ if (typeof updates.intent === "string") {
10758
+ bookmark.intent = updates.intent.trim() || void 0;
10759
+ }
10760
+ if (typeof updates.expectedContent === "string") {
10761
+ bookmark.expectedContent = updates.expectedContent.trim() || void 0;
10762
+ }
10763
+ if (updates.keyFields !== void 0) {
10764
+ bookmark.keyFields = updates.keyFields;
10765
+ }
10766
+ if (updates.pageSchema !== void 0) {
10767
+ bookmark.pageSchema = updates.pageSchema;
10768
+ }
10769
+ if (updates.agentHints !== void 0) {
10770
+ bookmark.agentHints = updates.agentHints;
10771
+ }
10657
10772
  save();
10658
10773
  emit();
10659
10774
  return { ...bookmark };
@@ -15558,7 +15673,17 @@ ${truncated}`;
15558
15673
  source.title,
15559
15674
  target.folderId,
15560
15675
  note,
15561
- { onDuplicate }
15676
+ {
15677
+ onDuplicate,
15678
+ extra: {
15679
+ intent: typeof args.intent === "string" && args.intent.trim() ? args.intent.trim() : void 0,
15680
+ expectedContent: typeof args.expectedContent === "string" && args.expectedContent.trim() ? args.expectedContent.trim() : void 0,
15681
+ keyFields: Array.isArray(args.keyFields) ? args.keyFields.filter(
15682
+ (f) => typeof f === "string"
15683
+ ) : void 0,
15684
+ agentHints: args.agentHints && typeof args.agentHints === "object" ? args.agentHints : void 0
15685
+ }
15686
+ }
15562
15687
  );
15563
15688
  if (result2.status === "conflict" && result2.existing) {
15564
15689
  return composeFolderAwareResponse$1(
@@ -15597,7 +15722,13 @@ ${truncated}`;
15597
15722
  const updated = updateBookmark(existing.id, {
15598
15723
  folderId: target.folderId,
15599
15724
  title: typeof args.title === "string" && args.title.trim() ? args.title.trim() : void 0,
15600
- note
15725
+ note,
15726
+ intent: typeof args.intent === "string" && args.intent.trim() ? args.intent.trim() : void 0,
15727
+ expectedContent: typeof args.expectedContent === "string" && args.expectedContent.trim() ? args.expectedContent.trim() : void 0,
15728
+ keyFields: Array.isArray(args.keyFields) ? args.keyFields.filter(
15729
+ (f) => typeof f === "string"
15730
+ ) : void 0,
15731
+ agentHints: args.agentHints && typeof args.agentHints === "object" ? args.agentHints : void 0
15601
15732
  });
15602
15733
  if (!updated) {
15603
15734
  return `Bookmark ${existing.id} not found`;
@@ -15608,12 +15739,25 @@ ${truncated}`;
15608
15739
  );
15609
15740
  }
15610
15741
  if ("error" in source) return `Error: ${source.error}`;
15611
- const bookmark = saveBookmark(
15742
+ const result2 = saveBookmarkWithPolicy(
15612
15743
  source.url,
15613
15744
  source.title,
15614
15745
  target.folderId,
15615
- note
15746
+ note,
15747
+ {
15748
+ onDuplicate: "update",
15749
+ extra: {
15750
+ intent: typeof args.intent === "string" && args.intent.trim() ? args.intent.trim() : void 0,
15751
+ expectedContent: typeof args.expectedContent === "string" && args.expectedContent.trim() ? args.expectedContent.trim() : void 0,
15752
+ keyFields: Array.isArray(args.keyFields) ? args.keyFields.filter(
15753
+ (f) => typeof f === "string"
15754
+ ) : void 0,
15755
+ agentHints: args.agentHints && typeof args.agentHints === "object" ? args.agentHints : void 0
15756
+ }
15757
+ }
15616
15758
  );
15759
+ const bookmark = result2.bookmark;
15760
+ if (!bookmark) return "Error: Bookmark save failed";
15617
15761
  return composeFolderAwareResponse$1(
15618
15762
  `Saved and organized "${bookmark.title}" (${bookmark.url}) into "${describeFolder$1(bookmark.folderId)}" (id=${bookmark.id})`,
15619
15763
  target.createdFolder
@@ -22798,7 +22942,7 @@ function registerWindowControlHandlers(mainWindow) {
22798
22942
  });
22799
22943
  }
22800
22944
  let activeChatProvider = null;
22801
- const VALID_APPROVAL_MODES = /* @__PURE__ */ new Set(["auto", "confirm-dangerous", "manual"]);
22945
+ const VALID_APPROVAL_MODES = ["auto", "confirm-dangerous", "manual"];
22802
22946
  function registerIpcHandlers(windowState, runtime2) {
22803
22947
  const { tabManager, chromeView, sidebarView, devtoolsPanelView, mainWindow } = windowState;
22804
22948
  let sidebarResizeRecoveryTimer = null;
@@ -22807,10 +22951,9 @@ function registerIpcHandlers(windowState, runtime2) {
22807
22951
  let pendingRuntimeState = null;
22808
22952
  const premiumApiOrigin = process.env.VESSEL_PREMIUM_API ? new URL(process.env.VESSEL_PREMIUM_API).origin : "https://vesselpremium.quantaintellect.com";
22809
22953
  const clearSidebarResizeRecoveryTimer = () => {
22810
- if (sidebarResizeRecoveryTimer) {
22811
- clearTimeout(sidebarResizeRecoveryTimer);
22812
- sidebarResizeRecoveryTimer = null;
22813
- }
22954
+ if (!sidebarResizeRecoveryTimer) return;
22955
+ clearTimeout(sidebarResizeRecoveryTimer);
22956
+ sidebarResizeRecoveryTimer = null;
22814
22957
  };
22815
22958
  const restoreSidebarLayoutAfterResize = () => {
22816
22959
  clearSidebarResizeRecoveryTimer();
@@ -23085,7 +23228,15 @@ function registerIpcHandlers(windowState, runtime2) {
23085
23228
  sidebarResizeActive = true;
23086
23229
  clearSidebarResizeRecoveryTimer();
23087
23230
  const [width, height] = windowState.mainWindow.getContentSize();
23088
- windowState.sidebarView.setBounds({ x: 0, y: 0, width, height });
23231
+ const chromeHeight = windowState.uiState.focusMode ? 0 : 110;
23232
+ const sidebarWidth = windowState.uiState.sidebarWidth;
23233
+ const resizeHandleOverlap = 6;
23234
+ windowState.sidebarView.setBounds({
23235
+ x: width - sidebarWidth - resizeHandleOverlap,
23236
+ y: chromeHeight,
23237
+ width: sidebarWidth + resizeHandleOverlap,
23238
+ height: height - chromeHeight
23239
+ });
23089
23240
  scheduleSidebarResizeRecovery();
23090
23241
  });
23091
23242
  electron.ipcMain.handle(Channels.SIDEBAR_RESIZE, (_, width) => {
@@ -23093,7 +23244,6 @@ function registerIpcHandlers(windowState, runtime2) {
23093
23244
  const clamped = Math.max(240, Math.min(800, Math.round(width)));
23094
23245
  windowState.uiState.sidebarWidth = clamped;
23095
23246
  resizeSidebarViews(windowState);
23096
- scheduleSidebarResizeRecovery();
23097
23247
  return clamped;
23098
23248
  });
23099
23249
  electron.ipcMain.handle(Channels.SIDEBAR_RESIZE_COMMIT, () => {
@@ -23152,7 +23302,7 @@ function registerIpcHandlers(windowState, runtime2) {
23152
23302
  Channels.AGENT_SET_APPROVAL_MODE,
23153
23303
  (_, mode) => {
23154
23304
  assertString(mode, "mode");
23155
- if (!VALID_APPROVAL_MODES.has(mode)) {
23305
+ if (!VALID_APPROVAL_MODES.includes(mode)) {
23156
23306
  throw new Error(`Invalid approval mode: ${mode}`);
23157
23307
  }
23158
23308
  trackApprovalModeChanged(mode);
@@ -23192,9 +23342,16 @@ function registerIpcHandlers(windowState, runtime2) {
23192
23342
  );
23193
23343
  electron.ipcMain.handle(
23194
23344
  Channels.BOOKMARK_SAVE,
23195
- (_, url, title, folderId, note) => {
23345
+ (_, url, title, folderId, note, intent, expectedContent, keyFields, agentHints) => {
23196
23346
  trackBookmarkAction("save");
23197
- return saveBookmark(url, title, folderId, note);
23347
+ return saveBookmarkWithPolicy(url, title, folderId, note, {
23348
+ extra: {
23349
+ intent: intent?.trim() || void 0,
23350
+ expectedContent: expectedContent?.trim() || void 0,
23351
+ keyFields,
23352
+ agentHints
23353
+ }
23354
+ });
23198
23355
  }
23199
23356
  );
23200
23357
  electron.ipcMain.handle(Channels.BOOKMARK_REMOVE, (_, id) => {
@@ -23308,6 +23465,7 @@ function registerIpcHandlers(windowState, runtime2) {
23308
23465
  }
23309
23466
  }
23310
23467
  findWiredWcId = wc.id;
23468
+ if (wc.isDestroyed()) return;
23311
23469
  const listener = (_event, result) => {
23312
23470
  if (!chromeView.webContents.isDestroyed()) {
23313
23471
  chromeView.webContents.send(Channels.FIND_IN_PAGE_RESULT, result);
@@ -23315,8 +23473,9 @@ function registerIpcHandlers(windowState, runtime2) {
23315
23473
  };
23316
23474
  findResultListener = listener;
23317
23475
  wc.on("found-in-page", listener);
23476
+ const capturedWcId = wc.id;
23318
23477
  wc.once("destroyed", () => {
23319
- if (findWiredWcId === wc.id) {
23478
+ if (findWiredWcId === capturedWcId) {
23320
23479
  findWiredWcId = null;
23321
23480
  findResultListener = null;
23322
23481
  }
@@ -23424,9 +23583,20 @@ function registerIpcHandlers(windowState, runtime2) {
23424
23583
  sendToRendererViews(Channels.PREMIUM_UPDATE, state2);
23425
23584
  return state2;
23426
23585
  });
23586
+ const PREMIUM_TRACKABLE_STEPS = [
23587
+ "chat_banner_viewed",
23588
+ "chat_banner_clicked",
23589
+ "settings_banner_viewed",
23590
+ "settings_banner_clicked",
23591
+ "welcome_banner_clicked",
23592
+ "premium_gate_seen",
23593
+ "premium_gate_clicked",
23594
+ "iteration_limit_seen",
23595
+ "iteration_limit_clicked"
23596
+ ];
23427
23597
  electron.ipcMain.handle(Channels.PREMIUM_TRACK_CONTEXT, (_, step) => {
23428
23598
  assertString(step, "step");
23429
- if (step === "chat_banner_viewed" || step === "chat_banner_clicked" || step === "settings_banner_viewed" || step === "settings_banner_clicked" || step === "welcome_banner_clicked" || step === "premium_gate_seen" || step === "premium_gate_clicked" || step === "iteration_limit_seen" || step === "iteration_limit_clicked") {
23599
+ if (PREMIUM_TRACKABLE_STEPS.includes(step)) {
23430
23600
  trackPremiumFunnel(step);
23431
23601
  }
23432
23602
  });
@@ -259,7 +259,17 @@ const api = {
259
259
  },
260
260
  bookmarks: {
261
261
  get: () => electron.ipcRenderer.invoke(Channels.BOOKMARKS_GET),
262
- saveBookmark: (url, title, folderId, note) => electron.ipcRenderer.invoke(Channels.BOOKMARK_SAVE, url, title, folderId, note),
262
+ saveBookmark: (url, title, folderId, note, intent, expectedContent, keyFields, agentHints) => electron.ipcRenderer.invoke(
263
+ Channels.BOOKMARK_SAVE,
264
+ url,
265
+ title,
266
+ folderId,
267
+ note,
268
+ intent,
269
+ expectedContent,
270
+ keyFields,
271
+ agentHints
272
+ ),
263
273
  removeBookmark: (id) => electron.ipcRenderer.invoke(Channels.BOOKMARK_REMOVE, id),
264
274
  createFolder: (name) => electron.ipcRenderer.invoke(Channels.FOLDER_CREATE, name),
265
275
  createFolderWithSummary: (name, summary) => electron.ipcRenderer.invoke(Channels.FOLDER_CREATE, name, summary),
@@ -1491,32 +1491,34 @@ const [activeTabId, setActiveTabId] = createSignal("");
1491
1491
  let initialized$4 = false;
1492
1492
  let initPromise$3 = null;
1493
1493
  let unsubscribeStateUpdate = null;
1494
+ async function doInit() {
1495
+ try {
1496
+ if (unsubscribeStateUpdate) {
1497
+ unsubscribeStateUpdate();
1498
+ unsubscribeStateUpdate = null;
1499
+ }
1500
+ unsubscribeStateUpdate = window.vessel.tabs.onStateUpdate(
1501
+ (newTabs, newActiveId) => {
1502
+ setTabs(newTabs);
1503
+ setActiveTabId(newActiveId);
1504
+ }
1505
+ );
1506
+ const initialState = await window.vessel.tabs.getState();
1507
+ setTabs(initialState.tabs);
1508
+ setActiveTabId(initialState.activeId);
1509
+ } catch (error) {
1510
+ initialized$4 = false;
1511
+ console.error("Failed to initialize tabs store", error);
1512
+ throw error;
1513
+ }
1514
+ }
1494
1515
  function init$4() {
1495
1516
  if (initPromise$3) return initPromise$3;
1496
1517
  if (initialized$4) return;
1497
1518
  initialized$4 = true;
1498
- initPromise$3 = (async () => {
1499
- try {
1500
- if (unsubscribeStateUpdate) {
1501
- unsubscribeStateUpdate();
1502
- unsubscribeStateUpdate = null;
1503
- }
1504
- unsubscribeStateUpdate = window.vessel.tabs.onStateUpdate(
1505
- (newTabs, newActiveId) => {
1506
- setTabs(newTabs);
1507
- setActiveTabId(newActiveId);
1508
- }
1509
- );
1510
- const initialState = await window.vessel.tabs.getState();
1511
- setTabs(initialState.tabs);
1512
- setActiveTabId(initialState.activeId);
1513
- } catch (error) {
1514
- initialized$4 = false;
1515
- console.error("Failed to initialize tabs store", error);
1516
- } finally {
1517
- initPromise$3 = null;
1518
- }
1519
- })();
1519
+ initPromise$3 = doInit().finally(() => {
1520
+ initPromise$3 = null;
1521
+ });
1520
1522
  return initPromise$3;
1521
1523
  }
1522
1524
  const patchTab = (id, patch) => {
@@ -1867,29 +1869,8 @@ const [focusMode, setFocusMode] = createSignal(false);
1867
1869
  const [commandBarOpen, setCommandBarOpen] = createSignal(false);
1868
1870
  const [settingsOpen, setSettingsOpen] = createSignal(false);
1869
1871
  const [devtoolsPanelOpen, setDevtoolsPanelOpen] = createSignal(false);
1870
- let pendingWidth = null;
1871
- let resizeInFlight = null;
1872
- async function flushResize() {
1873
- if (resizeInFlight) {
1874
- await resizeInFlight;
1875
- if (pendingWidth !== null) {
1876
- return flushResize();
1877
- }
1878
- return;
1879
- }
1880
- resizeInFlight = (async () => {
1881
- while (pendingWidth !== null) {
1882
- const nextWidth = pendingWidth;
1883
- pendingWidth = null;
1884
- await window.vessel.ui.resizeSidebar(nextWidth);
1885
- }
1886
- })();
1887
- try {
1888
- await resizeInFlight;
1889
- } finally {
1890
- resizeInFlight = null;
1891
- }
1892
- }
1872
+ let lastIpcTime = 0;
1873
+ const IPC_THROTTLE_MS = 8;
1893
1874
  function useUI() {
1894
1875
  return {
1895
1876
  sidebarOpen,
@@ -1909,12 +1890,13 @@ function useUI() {
1909
1890
  Math.min(MAX_SIDEBAR, Math.round(width))
1910
1891
  );
1911
1892
  setSidebarWidth(clamped);
1912
- pendingWidth = clamped;
1913
- void flushResize();
1893
+ const now2 = performance.now();
1894
+ if (now2 - lastIpcTime >= IPC_THROTTLE_MS) {
1895
+ lastIpcTime = now2;
1896
+ void window.vessel.ui.resizeSidebar(clamped);
1897
+ }
1914
1898
  },
1915
1899
  commitResize: async () => {
1916
- pendingWidth = sidebarWidth();
1917
- await flushResize();
1918
1900
  await window.vessel.ui.commitSidebarResize();
1919
1901
  },
1920
1902
  toggleFocusMode: async () => {
@@ -3533,7 +3515,16 @@ function useBookmarks() {
3533
3515
  void init();
3534
3516
  return {
3535
3517
  bookmarksState,
3536
- saveBookmark: (url, title, folderId, note) => window.vessel.bookmarks.saveBookmark(url, title, folderId, note),
3518
+ saveBookmark: (url, title, folderId, note, intent, expectedContent, keyFields, agentHints) => window.vessel.bookmarks.saveBookmark(
3519
+ url,
3520
+ title,
3521
+ folderId,
3522
+ note,
3523
+ intent,
3524
+ expectedContent,
3525
+ keyFields,
3526
+ agentHints
3527
+ ),
3537
3528
  removeBookmark: (id) => window.vessel.bookmarks.removeBookmark(id),
3538
3529
  createFolder: (name) => window.vessel.bookmarks.createFolder(name),
3539
3530
  createFolderWithSummary: (name, summary) => window.vessel.bookmarks.createFolderWithSummary(name, summary),
@@ -4981,7 +4972,9 @@ const FIELD_WEIGHTS = {
4981
4972
  note: 5,
4982
4973
  folder: 3,
4983
4974
  folderSummary: 2,
4984
- url: 1
4975
+ url: 1,
4976
+ intent: 4,
4977
+ expectedContent: 3
4985
4978
  };
4986
4979
  function normalizeBookmarkSearchText(value) {
4987
4980
  let normalized = value.toLowerCase();
@@ -5012,7 +5005,9 @@ function getBookmarkSearchMatch(args) {
5012
5005
  url: args.url,
5013
5006
  note: args.note,
5014
5007
  folder: args.folder,
5015
- folderSummary: args.folderSummary
5008
+ folderSummary: args.folderSummary,
5009
+ intent: args.intent,
5010
+ expectedContent: args.expectedContent
5016
5011
  };
5017
5012
  for (const field of Object.keys(values)) {
5018
5013
  if (!bookmarkFieldMatchesQuery(values[field], normalizedQuery, tokens)) {
@@ -6671,6 +6666,18 @@ ${contextBlock}` : contextBlock);
6671
6666
  const startX = e.screenX;
6672
6667
  const startWidth = sidebarWidth2();
6673
6668
  let finished = false;
6669
+ const state = {
6670
+ currentX: startX,
6671
+ rafId: null
6672
+ };
6673
+ const flushResizeUpdate = () => {
6674
+ state.rafId = null;
6675
+ if (finished) return;
6676
+ const totalDelta = startX - state.currentX;
6677
+ const targetWidth = startWidth + totalDelta;
6678
+ const newWidth = Math.max(240, Math.min(800, Math.round(targetWidth)));
6679
+ resizeSidebar(newWidth);
6680
+ };
6674
6681
  const clearPointerTracking = () => {
6675
6682
  window.removeEventListener("pointermove", onPointerMove);
6676
6683
  window.removeEventListener("pointerup", onPointerUp);
@@ -6681,14 +6688,25 @@ ${contextBlock}` : contextBlock);
6681
6688
  if (target.hasPointerCapture?.(e.pointerId)) {
6682
6689
  target.releasePointerCapture(e.pointerId);
6683
6690
  }
6691
+ if (state.rafId !== null) {
6692
+ cancelAnimationFrame(state.rafId);
6693
+ state.rafId = null;
6694
+ }
6684
6695
  };
6685
6696
  const onPointerMove = (ev) => {
6686
- const delta = startX - ev.screenX;
6687
- resizeSidebar(startWidth + delta);
6697
+ state.currentX = ev.screenX;
6698
+ if (state.rafId === null) {
6699
+ state.rafId = requestAnimationFrame(flushResizeUpdate);
6700
+ }
6688
6701
  };
6689
6702
  const finishResize = () => {
6690
6703
  if (finished) return;
6691
6704
  finished = true;
6705
+ if (state.rafId !== null) {
6706
+ cancelAnimationFrame(state.rafId);
6707
+ state.rafId = null;
6708
+ }
6709
+ flushResizeUpdate();
6692
6710
  setIsDragging(false);
6693
6711
  clearPointerTracking();
6694
6712
  document.body.style.cursor = "";
@@ -10166,17 +10184,19 @@ const App = () => {
10166
10184
  });
10167
10185
  }
10168
10186
  };
10169
- const applyTheme = async () => {
10170
- const s = await window.vessel.settings.get();
10171
- const theme = s.theme ?? "dark";
10187
+ const applyTheme = (theme) => {
10172
10188
  document.documentElement.setAttribute("data-theme", theme);
10173
10189
  try {
10174
10190
  localStorage.setItem("vessel:theme", theme);
10175
10191
  } catch {
10176
10192
  }
10177
10193
  };
10194
+ const loadAndApplyTheme = async () => {
10195
+ const s = await window.vessel.settings.get();
10196
+ applyTheme(s.theme ?? "dark");
10197
+ };
10178
10198
  onMount(() => {
10179
- void applyTheme();
10199
+ void loadAndApplyTheme();
10180
10200
  window.vessel.ui.rendererReady(view);
10181
10201
  if (view !== "chrome") return;
10182
10202
  const cleanupKeys = setupKeybindings({
@@ -10196,8 +10216,8 @@ const App = () => {
10196
10216
  toggleKeyboardHelp: () => setKeyboardHelpOpen((v) => !v)
10197
10217
  });
10198
10218
  const cleanupCapture = window.vessel.highlights.onCaptureResult(showHighlightResult);
10199
- const cleanupSettings = window.vessel.settings.onUpdate(() => {
10200
- void applyTheme();
10219
+ const cleanupSettings = window.vessel.settings.onUpdate((settings) => {
10220
+ applyTheme(settings.theme ?? "dark");
10201
10221
  });
10202
10222
  onCleanup(() => {
10203
10223
  cleanupKeys();
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; font-src 'self' data:;" />
7
7
  <title>Vessel</title>
8
- <script type="module" crossorigin src="./assets/index-NKk_lpQh.js"></script>
8
+ <script type="module" crossorigin src="./assets/index-Di64dPm5.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="./assets/index-CBe7EN_l.css">
10
10
  </head>
11
11
  <body>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@quanta-intellect/vessel-browser",
3
3
  "mcpName": "io.github.unmodeled-tyler/vessel-browser",
4
- "version": "0.1.60",
4
+ "version": "0.1.61",
5
5
  "description": "AI-native web browser runtime for autonomous agents with human supervision",
6
6
  "main": "./out/main/index.js",
7
7
  "bin": {