@ai-matrx/vault 0.1.27 → 0.1.28

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.28
4
+
5
+ - **A website typed as a bare domain always saves.** Every bare domain is sent as `https://…` on create, edit, assign and the item settings call, whether or not the server could reach it (an intranet, a site not live yet) (`normalizeLoginUrls`); the Website box shows the `https://` form after leaving it, and "Not a public website. You can still save it." is a warning, never a block. Guard: `src/review-0-1-28.test.tsx` § 1. Consumer action: none.
6
+ - **Every vault refusal shows the server's reason.** `createApiVaultPort` wraps the host's `Api` once (`withServerReasons`): a 4xx whose sentence is generic ("Invalid request. Please check your input and try again.") is re-worded, on the same error object, with the server's own reason ("Login URL must start with https:// or http://…"), so every action inherits it. Guard: § 1. Consumer action: none.
7
+ - **A restored item turns back on in one press.** Restore still comes back as the server's quarantine (item, fields, codes, browser fill and sandbox use off; shares never recreated). The restore result now offers **Turn these back on**: it turns on the item, its fields, its one-time codes and browser fill (when it has a website), then says what it turned on; sharing and sandbox use stay the person's choice (`RestorePanel turnOn`, `turnBackOn`, `turnedOnSentence`; the item's own Turn on uses the same `turnBackOn`). Not offered when the item was already off before it was deleted. Guard: § 2. Consumer action: none.
8
+ - **The first save of a new item leaves the form clean.** Once create resolves the pane reports clean and Save is off, even while a host's address has not moved to the saved item yet (it used to keep "Unsaved" up and ask "Discard your changes?"); `onDirty` is said when it changes, never re-said for a new callback. Guard: § 3. Consumer action: none.
9
+ - Guard for every row copy ending in "<label> copied" or a failure line (refused clipboard, unreadable value): § 4. Consumer action: none.
10
+
3
11
  ## 0.1.27
4
12
 
5
13
  - **`emptyReadAlarm`, `holdingsDoorIsAbsent`, `HOLDINGS_DOOR_MIGRATION` and the `HoldingRow`/`Holdings` types are exported from the root** so the web app can drop its whole-file copy (`features/secrets/empty-read.ts`). Consumer action: none.
package/dist/index.cjs CHANGED
@@ -2663,9 +2663,12 @@ function normalizeLoginUrl(input) {
2663
2663
  return null;
2664
2664
  }
2665
2665
  }
2666
+ function normalizeLoginUrls(inputs) {
2667
+ return inputs.map((u) => u.trim()).filter(Boolean).map((u) => normalizeLoginUrl(u) ?? u);
2668
+ }
2666
2669
  function describeDestination(r) {
2667
2670
  if (r.state === "reachable") return { url: r.normalizedUrl, state: r.state, tone: "success", message: "Website confirmed" };
2668
- if (r.state === "invalid") return { url: r.normalizedUrl, state: r.state, tone: "destructive", message: "Not a public website" };
2671
+ if (r.state === "invalid") return { url: r.normalizedUrl, state: r.state, tone: "warning", message: "Not a public website. You can still save it." };
2669
2672
  if (r.state === "not_found") return { url: r.normalizedUrl, state: r.state, tone: "warning", message: "Site answered, but this page was not found" };
2670
2673
  return { url: r.normalizedUrl, state: r.state, tone: "warning", message: "Couldn\u2019t confirm it. You can still save it." };
2671
2674
  }
@@ -5368,6 +5371,40 @@ function AssignResult({ sent, onAnother, onDone }) {
5368
5371
  ] });
5369
5372
  }
5370
5373
 
5374
+ // src/lists/turn-back-on.ts
5375
+ async function turnBackOn(port, item, fields, options = {}) {
5376
+ const run = options.run ?? ((op) => op());
5377
+ const done = [];
5378
+ if (item.turnedOff) {
5379
+ await run(() => port.fields.updateItem(item, { active: true }));
5380
+ done.push("the item");
5381
+ }
5382
+ let n = 0;
5383
+ for (const f of fields) {
5384
+ if (f.active) continue;
5385
+ await run(() => port.fields.updateField(item, f.id, { active: true }));
5386
+ n += 1;
5387
+ }
5388
+ if (n) done.push(n === 1 ? "1 field" : `${n} fields`);
5389
+ if (fields.some((f) => f.key === OTP_SEED_KEY)) {
5390
+ const codes = await port.authenticator.get(item);
5391
+ if (codes && !codes.enabled) {
5392
+ await run(() => port.authenticator.setEnabled(item, true));
5393
+ done.push("one-time codes");
5394
+ }
5395
+ }
5396
+ if (options.browserFill && !item.browserFillEnabled && item.loginUrls.length > 0) {
5397
+ await run(() => port.fields.updateItem(item, { browserFillEnabled: true }));
5398
+ done.push("browser fill");
5399
+ }
5400
+ return done;
5401
+ }
5402
+ function turnedOnSentence(done) {
5403
+ if (done.length === 0) return "Nothing here was off.";
5404
+ const list = done.length === 1 ? done[0] : `${done.slice(0, -1).join(", ")} and ${done[done.length - 1]}`;
5405
+ return `Turned on ${list}.`;
5406
+ }
5407
+
5371
5408
  // src/ItemPane.tsx
5372
5409
  var import_icons11 = require("@ai-matrx/design-system/icons");
5373
5410
  var import_drafts = require("@ai-matrx/kit/drafts");
@@ -5981,7 +6018,8 @@ function ItemPane(props) {
5981
6018
  const [showBrowser, setShowBrowser] = (0, import_react28.useState)(false);
5982
6019
  const [error, setError] = (0, import_react28.useState)(null);
5983
6020
  const [destinations, setDestinations] = (0, import_react28.useState)({});
5984
- const dirty = editing && isDirty(base, draft);
6021
+ const [savedNew, setSavedNew] = (0, import_react28.useState)(false);
6022
+ const dirty = editing && !savedNew && isDirty(base, draft);
5985
6023
  const declared = definition?.fields ?? [];
5986
6024
  const prev = (0, import_react28.useRef)(null);
5987
6025
  const paneRef = (0, import_react28.useRef)(null);
@@ -6004,8 +6042,11 @@ function ItemPane(props) {
6004
6042
  setShowTags(false);
6005
6043
  setShowDescription(false);
6006
6044
  setShowRecovery(false);
6045
+ setSavedNew(false);
6007
6046
  }, [item?.id, mode]);
6008
- (0, import_react28.useEffect)(() => onDirty(dirty), [dirty, onDirty]);
6047
+ const onDirtyRef = (0, import_react28.useRef)(onDirty);
6048
+ onDirtyRef.current = onDirty;
6049
+ (0, import_react28.useEffect)(() => onDirtyRef.current(dirty), [dirty]);
6009
6050
  const keepKey = draftKey(item?.id ?? null, definition?.key ?? item?.definitionKey ?? null);
6010
6051
  (0, import_react28.useEffect)(() => {
6011
6052
  if (editing && dirty) (0, import_drafts.keepDraft)(keepKey, keptFieldsOf(draft), { basedOn: item?.updatedAt ?? "new" });
@@ -6062,6 +6103,8 @@ function ItemPane(props) {
6062
6103
  const [withCode, n0, enrolled] = await withOtp(r.item, r.notice);
6063
6104
  const [next, n] = await storeQueuedFiles(withCode, n0);
6064
6105
  (0, import_drafts.dropKeptDraft)(keepKey);
6106
+ setSavedNew(true);
6107
+ onDirty(false);
6065
6108
  onSaved(next, true, n, enrolled);
6066
6109
  }
6067
6110
  } catch (e) {
@@ -6124,12 +6167,7 @@ function ItemPane(props) {
6124
6167
  if (!item) return;
6125
6168
  setError(null);
6126
6169
  try {
6127
- if (item.turnedOff) await confirmed(() => port.fields.updateItem(item, { active: true }));
6128
- for (const f of fields) await confirmed(() => port.fields.updateField(item, f.id, { active: true }));
6129
- if (fields.some((f) => f.key === OTP_SEED_KEY)) {
6130
- const codes = await port.authenticator.get(item);
6131
- if (codes && !codes.enabled) await confirmed(() => port.authenticator.setEnabled(item, true));
6132
- }
6170
+ await turnBackOn(port, item, fields, { run: confirmed });
6133
6171
  onChanged();
6134
6172
  } catch (e) {
6135
6173
  if (!wasCancelled(e)) setError(e instanceof Error ? e.message : "It could not be turned on.");
@@ -6184,7 +6222,7 @@ function ItemPane(props) {
6184
6222
  "Unsaved"
6185
6223
  ] }) : null,
6186
6224
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_controls22.Button, { variant: "quiet", disabled: busy, onClick: () => mode === "new" ? onCancelNew() : onMode("view"), children: dirty ? "Discard" : "Cancel" }),
6187
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_controls22.Button, { variant: "primary", disabled: busy || sent !== null || draft.name.trim() === "" || mode === "edit" && !dirty || mode === "new" && owner === "person" && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(recipient.trim()), onClick: () => void save(), children: mode === "new" && owner === "person" ? "Send" : "Save" })
6225
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_controls22.Button, { variant: "primary", disabled: busy || savedNew || sent !== null || draft.name.trim() === "" || mode === "edit" && !dirty || mode === "new" && owner === "person" && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(recipient.trim()), onClick: () => void save(), children: mode === "new" && owner === "person" ? "Send" : "Save" })
6188
6226
  ] }) : item ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex shrink-0 items-center gap-1", children: [
6189
6227
  host.page && canUseOnSite(item, host.page.url) ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(UseOnSiteButton, { port, item, pageUrl: host.page.url, onChanged: (next) => onChanged(next) }) : null,
6190
6228
  host.fill && item.capabilities.canUse ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
@@ -6352,7 +6390,8 @@ function ItemPane(props) {
6352
6390
  else delete next[i];
6353
6391
  return next;
6354
6392
  });
6355
- if (r?.url && r.url !== u) setDraft((d) => ({ ...d, loginUrls: d.loginUrls.map((x, j) => j === i && x === u ? r.url : x) }));
6393
+ const stored = r?.url || normalizeLoginUrl(u);
6394
+ if (stored && stored !== u) setDraft((d) => ({ ...d, loginUrls: d.loginUrls.map((x, j) => j === i && x === u ? stored : x) }));
6356
6395
  }),
6357
6396
  onRemove: () => patch({ loginUrls: draft.loginUrls.filter((_, j) => j !== i) }),
6358
6397
  removeLabel: "Remove website"
@@ -6764,8 +6803,16 @@ var import_react30 = require("react");
6764
6803
  var import_controls24 = require("@ai-matrx/design-system/controls");
6765
6804
  var import_jsx_runtime29 = require("react/jsx-runtime");
6766
6805
  var count = (n, one) => `${n} ${one}${n === 1 ? "" : "s"}`;
6767
- function RestorePanel({ lists, item, restore, onClose }) {
6806
+ function RestorePanel({
6807
+ lists,
6808
+ item,
6809
+ restore,
6810
+ turnOn,
6811
+ onClose
6812
+ }) {
6768
6813
  const [preview, setPreview] = (0, import_react30.useState)(null);
6814
+ const [restored, setRestored] = (0, import_react30.useState)(null);
6815
+ const [turnedOn, setTurnedOn] = (0, import_react30.useState)(null);
6769
6816
  const [error, setError] = (0, import_react30.useState)(null);
6770
6817
  const [busy, setBusy] = (0, import_react30.useState)(false);
6771
6818
  const [loading, setLoading] = (0, import_react30.useState)(Boolean(lists.preview));
@@ -6794,13 +6841,54 @@ function RestorePanel({ lists, item, restore, onClose }) {
6794
6841
  setError(null);
6795
6842
  try {
6796
6843
  const result = await restore(preview?.supported ? preview.deletionId : void 0);
6797
- if (result) onClose();
6844
+ if (!result) return;
6845
+ if (turnOn && !result.alreadyRestored && preview?.supported && !preview.priorWasDisabled) setRestored(result);
6846
+ else onClose();
6798
6847
  } catch (e) {
6799
6848
  setError(e instanceof Error && e.message ? e.message : "Restore did not complete. Try again.");
6800
6849
  } finally {
6801
6850
  setBusy(false);
6802
6851
  }
6803
6852
  };
6853
+ const runTurnOn = async () => {
6854
+ if (!restored || !turnOn) return;
6855
+ setBusy(true);
6856
+ setError(null);
6857
+ try {
6858
+ const said = await turnOn(restored.item);
6859
+ if (said !== null) setTurnedOn(said);
6860
+ } catch (e) {
6861
+ setError(e instanceof Error && e.message ? e.message : "It could not be turned on. Turn it on from the item.");
6862
+ } finally {
6863
+ setBusy(false);
6864
+ }
6865
+ };
6866
+ if (restored) {
6867
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
6868
+ Panel,
6869
+ {
6870
+ title: `Restored "${item.name}"`,
6871
+ size: "md",
6872
+ onClose,
6873
+ footer: turnedOn ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_controls24.Button, { variant: "primary", onClick: onClose, children: "Done" }) : /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_jsx_runtime29.Fragment, { children: [
6874
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_controls24.Button, { variant: "quiet", onClick: onClose, children: "Leave off" }),
6875
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_controls24.Button, { variant: "primary", icon: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(RestoreGlyph, {}), disabled: busy, onClick: () => void runTurnOn(), children: busy ? "Turning on\u2026" : "Turn these back on" })
6876
+ ] }),
6877
+ children: [
6878
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(VaultSection, { title: turnedOn ? "Turned on" : "Back, turned off", children: [
6879
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(VaultRow, { label: "Came back", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("span", { className: "text-foreground", children: [
6880
+ count(restored.restoredFields, "field"),
6881
+ ", ",
6882
+ count(restored.restoredAttachments, "file")
6883
+ ] }) }),
6884
+ turnedOn ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(VaultRow, { label: "Now on", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { role: "status", className: "text-foreground", children: turnedOn }) }) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(VaultRow, { label: "Turns on", hint: "One press, nothing else changes", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "text-muted-foreground", children: "The item, its fields, codes and browser fill" }) }),
6885
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(VaultRow, { label: "Stays off", hint: "Choose these again yourself", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "text-muted-foreground", children: "Sharing, sandbox use" }) })
6886
+ ] }),
6887
+ error ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { role: "alert", className: "px-1 text-sm text-destructive", children: error }) : null
6888
+ ]
6889
+ }
6890
+ );
6891
+ }
6804
6892
  return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
6805
6893
  Panel,
6806
6894
  {
@@ -6841,7 +6929,7 @@ function TrashView({ port, deleted, query, header, onChanged }) {
6841
6929
  setMessage(null);
6842
6930
  try {
6843
6931
  const r = await confirmed(() => port.lists.restore(item, deletionId));
6844
- setMessage({ tone: "ok", text: r.alreadyRestored ? `"${item.name}" was already restored.` : `Restored "${item.name}" with ${r.restoredFields} fields and ${r.restoredAttachments} files. Turn its fields on from the item.` });
6932
+ setMessage({ tone: "ok", text: r.alreadyRestored ? `"${item.name}" was already restored.` : `Restored "${item.name}" with ${r.restoredFields} fields and ${r.restoredAttachments} files, turned off.` });
6845
6933
  onChanged();
6846
6934
  return r;
6847
6935
  } catch (e) {
@@ -6851,6 +6939,19 @@ function TrashView({ port, deleted, query, header, onChanged }) {
6851
6939
  setBusyId(null);
6852
6940
  }
6853
6941
  };
6942
+ const turnOn = async (item) => {
6943
+ try {
6944
+ const done = await turnBackOn(port, item, item.fields, { browserFill: true, run: confirmed });
6945
+ const said = turnedOnSentence(done);
6946
+ setMessage({ tone: "ok", text: `"${item.name}": ${said}` });
6947
+ onChanged();
6948
+ return said;
6949
+ } catch (e) {
6950
+ onChanged();
6951
+ if (wasCancelled(e)) return null;
6952
+ throw e;
6953
+ }
6954
+ };
6854
6955
  return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto p-3", children: [
6855
6956
  header,
6856
6957
  /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(VaultSection, { title: "Trash", children: shown.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Empty, { children: deleted.length === 0 ? "The trash is empty." : "Nothing matches." }) : shown.map((item) => (
@@ -6864,7 +6965,7 @@ function TrashView({ port, deleted, query, header, onChanged }) {
6864
6965
  /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_controls25.Button, { variant: "outline", icon: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(RestoreGlyph, {}), disabled: busyId !== null || !item.deletion, onClick: () => setRestoring(item), children: "Restore" })
6865
6966
  ] }, item.id)
6866
6967
  )) }),
6867
- restoring ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(RestorePanel, { lists: port.lists, item: restoring, restore: (deletionId) => restore(restoring, deletionId), onClose: () => setRestoring(null) }) : null,
6968
+ restoring ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(RestorePanel, { lists: port.lists, item: restoring, restore: (deletionId) => restore(restoring, deletionId), turnOn, onClose: () => setRestoring(null) }) : null,
6868
6969
  message2 ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("p", { role: "status", className: message2.tone === "error" ? "px-1 text-sm text-destructive" : "px-1 text-sm text-muted-foreground", children: message2.text }) : null
6869
6970
  ] });
6870
6971
  }
@@ -9129,7 +9230,7 @@ function createFieldsPort(ctx) {
9129
9230
  const body = { initiation: "user" };
9130
9231
  if (patch.uriMatchMode !== void 0) body.uri_match_mode = patch.uriMatchMode;
9131
9232
  if (patch.browserFillEnabled !== void 0) body.browser_fill_enabled = patch.browserFillEnabled;
9132
- if (patch.loginUrls !== void 0) body.login_urls = patch.loginUrls;
9233
+ if (patch.loginUrls !== void 0) body.login_urls = normalizeLoginUrls(patch.loginUrls);
9133
9234
  if (patch.active !== void 0) body.status = patch.active ? "active" : "disabled";
9134
9235
  await account3.updateItem(api, { path: { item_id: item.id }, body }, ctx.optsFor(item));
9135
9236
  return reread(item);
@@ -9141,6 +9242,33 @@ function createFieldsPort(ctx) {
9141
9242
  };
9142
9243
  }
9143
9244
 
9245
+ // src/server-reasons.ts
9246
+ var GENERIC = [/please check your input/i, /^\s*request failed\s*\(\d+\)\s*$/i, /^\s*bad request\.?\s*$/i, /^\s*HTTP \d+\s*$/i];
9247
+ var isGeneric = (s) => GENERIC.some((re) => re.test(s));
9248
+ function cleanReason(detail) {
9249
+ const s = detail.replace(/^\s*validation error:\s*/i, "").replace(/^\s*value error,\s*/i, "").trim();
9250
+ return s ? s.charAt(0).toUpperCase() + s.slice(1) : s;
9251
+ }
9252
+ function serverReason(e) {
9253
+ if (!e || typeof e !== "object") return null;
9254
+ const { status, detail, message: message2 } = e;
9255
+ if (typeof status !== "number" || status < 400 || status >= 500) return null;
9256
+ if (typeof detail !== "string" || !detail.trim() || isGeneric(detail)) return null;
9257
+ const shown = typeof message2 === "string" ? message2 : "";
9258
+ if (shown && !isGeneric(shown)) return null;
9259
+ return cleanReason(detail);
9260
+ }
9261
+ function withReason(e) {
9262
+ const reason = serverReason(e);
9263
+ if (reason) Object.assign(e, { message: reason, userMessage: reason });
9264
+ return e;
9265
+ }
9266
+ function withServerReasons(api) {
9267
+ const wrapped = Object.create(api);
9268
+ wrapped.call = (...args) => api.call(...args).catch((e) => Promise.reject(withReason(e)));
9269
+ return wrapped;
9270
+ }
9271
+
9144
9272
  // src/io/api.ts
9145
9273
  var account4 = __toESM(require("@ai-matrx/api/account"), 1);
9146
9274
  var orgOpts = (organizationId) => organizationId ? { organizationId } : void 0;
@@ -9413,7 +9541,7 @@ function createSharingPort(ctx, options = {}) {
9413
9541
  definition_key: definition.key,
9414
9542
  provider_key: definition.providerKey,
9415
9543
  tags: draft.tags,
9416
- login_urls: draft.loginUrls,
9544
+ login_urls: normalizeLoginUrls(draft.loginUrls),
9417
9545
  notes: draft.notes || null,
9418
9546
  generate_field_key: generateFieldKey,
9419
9547
  initiation: "user",
@@ -9610,7 +9738,8 @@ async function retrying(run, tries = 3) {
9610
9738
  }
9611
9739
  }
9612
9740
  }
9613
- function createApiVaultPort(api, options = {}) {
9741
+ function createApiVaultPort(hostApi, options = {}) {
9742
+ const api = withServerReasons(hostApi);
9614
9743
  let defs = /* @__PURE__ */ new Map();
9615
9744
  let auth = /* @__PURE__ */ new Map();
9616
9745
  let orgs = [];
@@ -9765,6 +9894,7 @@ function createApiVaultPort(api, options = {}) {
9765
9894
  const declared = definition.fields;
9766
9895
  const encrypted = declared.filter((f) => f.storage === "encrypted" && (draft.values[f.key] ?? "") !== "");
9767
9896
  const meta = declared.filter((f) => f.storage === "metadata" && (draft.values[f.key] ?? "") !== "");
9897
+ const loginUrls = normalizeLoginUrls(draft.loginUrls);
9768
9898
  const w = await account9.createItem(api, {
9769
9899
  body: {
9770
9900
  display_name: draft.name.trim(),
@@ -9772,10 +9902,10 @@ function createApiVaultPort(api, options = {}) {
9772
9902
  definition_key: definition.key,
9773
9903
  provider_key: definition.providerKey,
9774
9904
  tags: draft.tags,
9775
- login_urls: draft.loginUrls,
9905
+ login_urls: loginUrls,
9776
9906
  // A login a person saves with its website is offered on that website (the server's own
9777
9907
  // default is off, which made "Save a login" on a page answer "No saved login" there).
9778
- ...draft.browser ? { uri_match_mode: draft.browser.uriMatchMode, browser_fill_enabled: draft.browser.browserFillEnabled } : draft.loginUrls.length > 0 ? { browser_fill_enabled: true } : {},
9908
+ ...draft.browser ? { uri_match_mode: draft.browser.uriMatchMode, browser_fill_enabled: draft.browser.browserFillEnabled } : loginUrls.length > 0 ? { browser_fill_enabled: true } : {},
9779
9909
  notes: draft.notes || null,
9780
9910
  principal: organizationId ? { type: "organization", organization_id: organizationId } : { type: "user" },
9781
9911
  initiation: "user",
@@ -9802,7 +9932,7 @@ function createApiVaultPort(api, options = {}) {
9802
9932
  else body.description = draft.description.trim();
9803
9933
  }
9804
9934
  if (JSON.stringify(draft.tags) !== JSON.stringify(item.tags)) body.tags = draft.tags;
9805
- if (JSON.stringify(draft.loginUrls) !== JSON.stringify(item.loginUrls)) body.login_urls = draft.loginUrls;
9935
+ if (JSON.stringify(draft.loginUrls) !== JSON.stringify(item.loginUrls)) body.login_urls = normalizeLoginUrls(draft.loginUrls);
9806
9936
  if (draft.notes !== item.notes) {
9807
9937
  if (draft.notes === "") body.clear_notes = true;
9808
9938
  else body.notes = draft.notes;