@ai-matrx/vault 0.1.18 → 0.1.20

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/dist/index.js CHANGED
@@ -671,7 +671,7 @@ var DAY_FORMAT = { year: "numeric", month: "short", day: "numeric" };
671
671
  import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
672
672
  var MASK = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
673
673
  function SecretText({ value, multiline }) {
674
- if (multiline || value.length > 200) return /* @__PURE__ */ jsx5("span", { className: "whitespace-pre-wrap break-all font-mono text-sm", children: value });
674
+ if (multiline || value.length > 200 || value.includes("\n")) return /* @__PURE__ */ jsx5("span", { className: "whitespace-pre-wrap break-all font-mono text-sm", children: value });
675
675
  return /* @__PURE__ */ jsx5("span", { className: "break-all font-mono text-sm tracking-wide", children: [...value].map((ch, i) => /* @__PURE__ */ jsx5("span", { className: /[0-9]/.test(ch) ? "text-primary" : /[^A-Za-z0-9]/.test(ch) ? "text-warning" : void 0, children: ch }, i)) });
676
676
  }
677
677
  var quietError = (e, fallback) => wasCancelled(e) ? null : e instanceof Error ? e.message : fallback;
@@ -807,18 +807,19 @@ function EditRow({ label, hint: hint2, value, onChange, placeholder, secret = fa
807
807
  }
808
808
  );
809
809
  }
810
- function CustomEditRow({ label, value, onLabel, onValue, secret, onRemove, extra, onPasteValue }) {
810
+ function CustomEditRow({ label, value, onLabel, onValue, secret, onRemove, extra, onPasteValue, multiline = false }) {
811
811
  const [shown, setShown] = useState5(false);
812
812
  return /* @__PURE__ */ jsx5(
813
813
  VaultRow,
814
814
  {
815
815
  label: /* @__PURE__ */ jsx5(Field2, { className: "w-full", value: label, onChange: (e) => onLabel(e.target.value), placeholder: "Name", "aria-label": "Field name" }),
816
+ top: multiline,
816
817
  actions: /* @__PURE__ */ jsxs5(Fragment2, { children: [
817
- secret && value ? /* @__PURE__ */ jsx5(Button2, { variant: "quiet", icon: shown ? /* @__PURE__ */ jsx5(EyeOffGlyph, {}) : /* @__PURE__ */ jsx5(EyeGlyph, {}), "aria-label": shown ? "Hide" : "Show", onClick: () => setShown((x) => !x) }) : null,
818
+ secret && value && !multiline ? /* @__PURE__ */ jsx5(Button2, { variant: "quiet", icon: shown ? /* @__PURE__ */ jsx5(EyeOffGlyph, {}) : /* @__PURE__ */ jsx5(EyeGlyph, {}), "aria-label": shown ? "Hide" : "Show", onClick: () => setShown((x) => !x) }) : null,
818
819
  extra,
819
820
  /* @__PURE__ */ jsx5(Button2, { variant: "quiet", removes: true, icon: /* @__PURE__ */ jsx5(XGlyph, {}), "aria-label": "Remove field", onClick: onRemove })
820
821
  ] }),
821
- children: /* @__PURE__ */ jsx5(
822
+ children: multiline ? /* @__PURE__ */ jsx5(Textarea, { className: "w-full", value, onChange: (e) => onValue(e.target.value), rows: 4, placeholder: secret ? "Encrypted, hidden until shown" : "Value", autoComplete: "off", spellCheck: !secret, "aria-label": label ? `${label} value` : "Field value" }) : /* @__PURE__ */ jsx5(
822
823
  Field2,
823
824
  {
824
825
  className: "w-full",
@@ -860,6 +861,7 @@ function locationFromParams(params, itemId = params.get("item")) {
860
861
  mode: mode === "edit" || mode === "new" ? mode : "view",
861
862
  kind: params.get("kind"),
862
863
  url: params.get("url") ?? "",
864
+ ...params.get("name") ? { name: params.get("name") ?? "" } : {},
863
865
  panel: params.get("panel")
864
866
  };
865
867
  }
@@ -872,6 +874,7 @@ function locationToParams(loc) {
872
874
  if (loc.mode !== "view") q.set("mode", loc.mode);
873
875
  if (loc.kind) q.set("kind", loc.kind);
874
876
  if (loc.url) q.set("url", loc.url);
877
+ if (loc.name) q.set("name", loc.name);
875
878
  if (loc.panel) q.set("panel", loc.panel);
876
879
  return q;
877
880
  }
@@ -935,6 +938,7 @@ function sortItems(items, sort, recents) {
935
938
  if (sort === "updated") return out.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
936
939
  return out.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
937
940
  }
941
+ var SECURE_NOTE_KEY = "secure_notes";
938
942
  function draftOf(item) {
939
943
  return {
940
944
  name: item?.name ?? "",
@@ -985,7 +989,9 @@ function rebaseDraft(oldBase, newBase, draft) {
985
989
  secrets: draft.secrets,
986
990
  custom,
987
991
  removed: draft.removed,
988
- authenticator: draft.authenticator
992
+ authenticator: draft.authenticator,
993
+ ...draft.browser ? { browser: draft.browser } : {},
994
+ ...draft.fieldSettings ? { fieldSettings: draft.fieldSettings } : {}
989
995
  };
990
996
  }
991
997
  function panesShown(mode, specialView, showingDetail) {
@@ -1013,7 +1019,8 @@ var FAMILY_LABELS = {
1013
1019
  signing_files: "Signing & files"
1014
1020
  };
1015
1021
  var COMMON_DEFINITION_KEYS = ["website_login", "username_password", "api_key", "env_value", "database_login", "custom"];
1016
- var MULTILINE = /(json|pem|private_key|certificate|cert|bundle|document|recovery_codes)/;
1022
+ var MULTILINE = /(json|pem|private_key|certificate|cert|bundle|document|recovery_codes|(^|_)notes?($|_))/;
1023
+ var isMultilineKey = (key) => MULTILINE.test(key);
1017
1024
  var SECRET = /(password|secret|token|api_key|private|passphrase|credential|seed|key$|_key)/;
1018
1025
  var URLISH = /(url|uri|endpoint|webhook|hook)/;
1019
1026
  function humanize(key) {
@@ -1912,10 +1919,16 @@ function QrScanPanel({ open, onResult, onClose }) {
1912
1919
  /* @__PURE__ */ jsx10("p", { className: "text-center text-xs text-muted-foreground", children: "Point at the site's QR code" })
1913
1920
  ] });
1914
1921
  }
1915
- function OtpSetupRow({ value, onChange, label = "Authenticator", placeholder = "Paste setup key or otpauth link", disabled = false, autoFocus = false, onImage, onRecognized, status }) {
1922
+ function OtpSetupRow({ value, onChange, label = "Authenticator", placeholder = "Paste setup key or otpauth link", disabled = false, autoFocus = false, onImage, onRecognized, status, autoStartCamera = false }) {
1916
1923
  const file = useRef8(null);
1917
1924
  const canScan = useCanScan();
1918
1925
  const [scanning, setScanning] = useState10(false);
1926
+ const autoStarted = useRef8(false);
1927
+ useEffect10(() => {
1928
+ if (!autoStartCamera || !canScan || disabled || autoStarted.current || value) return;
1929
+ autoStarted.current = true;
1930
+ setScanning(true);
1931
+ }, [autoStartCamera, canScan, disabled, value]);
1919
1932
  const [reading, setReading] = useState10(false);
1920
1933
  const [imageError, setImageError] = useState10(null);
1921
1934
  const [dragging, setDragging] = useState10(false);
@@ -2221,7 +2234,7 @@ function normalizeWebsite(raw) {
2221
2234
  return null;
2222
2235
  }
2223
2236
  }
2224
- function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChanged, onDone }) {
2237
+ function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChanged, onAdded, onDone }) {
2225
2238
  const confirmed = useConfirmed();
2226
2239
  const candidates = items.filter((i) => !i.authenticator && !i.deletion && (i.capabilities.canEdit || i.capabilities.canManage)).sort((a, b) => a.name.localeCompare(b.name, void 0, { sensitivity: "base" }));
2227
2240
  const [input, setInput] = useState12("");
@@ -2274,6 +2287,7 @@ function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChang
2274
2287
  setInput("");
2275
2288
  setImage(null);
2276
2289
  setSaved(next);
2290
+ onAdded?.(next);
2277
2291
  onChanged();
2278
2292
  } catch (e) {
2279
2293
  setError(wasCancelled(e) ? "Not confirmed, so nothing was added." : otpActionError(e, "add it"));
@@ -2300,6 +2314,7 @@ function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChang
2300
2314
  label: "Setup key",
2301
2315
  value: input,
2302
2316
  autoFocus: true,
2317
+ autoStartCamera: true,
2303
2318
  disabled: busy,
2304
2319
  onChange: (v) => {
2305
2320
  setImage(null);
@@ -2381,7 +2396,23 @@ function otpGroups(items, entries, query, hidden = /* @__PURE__ */ new Set()) {
2381
2396
  const rank = (k) => k === "me" ? 0 : k === "shared" ? 2 : 1;
2382
2397
  return [...groups.entries()].sort(([a, x], [b, y]) => rank(a) - rank(b) || x.label.localeCompare(y.label)).map(([key, g]) => ({ key, ...g }));
2383
2398
  }
2384
- function AuthenticatorView({ port, host, items, definitions, query, onOpen, onChanged, header }) {
2399
+ function withAdded(items, added) {
2400
+ if (added.size === 0) return items;
2401
+ const seen = /* @__PURE__ */ new Set();
2402
+ const out = items.map((i) => {
2403
+ seen.add(i.id);
2404
+ const mine = added.get(i.id);
2405
+ return mine && !i.authenticator ? { ...i, authenticator: mine.authenticator, name: mine.name } : i;
2406
+ });
2407
+ for (const [id, a] of added) if (!seen.has(id)) out.push(a);
2408
+ return out;
2409
+ }
2410
+ function AuthenticatorView({ port, host, items: listed, definitions, query, onOpen, onChanged, header }) {
2411
+ const [added, setAdded] = useState13(/* @__PURE__ */ new Map());
2412
+ const items = withAdded(listed, added);
2413
+ useEffect11(() => {
2414
+ if (added.size > 0 && [...added.keys()].every((id) => listed.some((i) => i.id === id && i.authenticator))) setAdded(/* @__PURE__ */ new Map());
2415
+ }, [listed, added]);
2385
2416
  const [entries, setEntries] = useState13(/* @__PURE__ */ new Map());
2386
2417
  const [hidden, setHidden] = useState13(/* @__PURE__ */ new Set());
2387
2418
  const [adding, setAdding] = useState13(false);
@@ -2397,7 +2428,7 @@ function AuthenticatorView({ port, host, items, definitions, query, onOpen, onCh
2397
2428
  return () => {
2398
2429
  live = false;
2399
2430
  };
2400
- }, [port, items]);
2431
+ }, [port, listed, added]);
2401
2432
  const groups = otpGroups(items, entries, query, hidden);
2402
2433
  const total = items.filter((i) => i.authenticator && !i.deletion && !hidden.has(i.id)).length;
2403
2434
  const familyOf2 = (key) => definitions.find((d) => d.key === key)?.family ?? "generic";
@@ -2443,6 +2474,7 @@ function AuthenticatorView({ port, host, items, definitions, query, onOpen, onCh
2443
2474
  definitions,
2444
2475
  onClose: () => setAdding(false),
2445
2476
  onChanged,
2477
+ onAdded: (item) => setAdded((cur) => new Map(cur).set(item.id, item)),
2446
2478
  onDone: (id) => {
2447
2479
  setAdding(false);
2448
2480
  onOpen(id);
@@ -3852,7 +3884,7 @@ var MAX_FAILURES_IN_A_ROW = 5;
3852
3884
  var failureText = (cause) => cause instanceof Error && cause.message ? cause.message : "The server refused this one.";
3853
3885
  async function runImport(input) {
3854
3886
  const { io, organizationId, lines, keys, confirmed } = input;
3855
- const result = { created: [], failed: [], withOtp: 0, stoppedEarly: false, untried: [] };
3887
+ const result = { created: [], failed: [], withOtp: 0, needOtp: [], stoppedEarly: false, untried: [] };
3856
3888
  let streak = 0;
3857
3889
  for (let i = 0; i < lines.length; i += 1) {
3858
3890
  const line = lines[i];
@@ -3871,7 +3903,10 @@ async function runImport(input) {
3871
3903
  try {
3872
3904
  const made = await confirmed(() => io.createImported(command.body, organizationId, key));
3873
3905
  result.created.push(made);
3874
- if (command.hasOtp) result.withOtp += 1;
3906
+ if (command.hasOtp) {
3907
+ result.withOtp += 1;
3908
+ result.needOtp.push(made);
3909
+ }
3875
3910
  streak = 0;
3876
3911
  } catch (cause) {
3877
3912
  const reason = failureText(cause);
@@ -4471,8 +4506,9 @@ var STATE_BADGE = {
4471
4506
  unsupported: { text: "Not supported", tone: "neutral" }
4472
4507
  };
4473
4508
  var ME = "me";
4474
- function ImportPanel({ port, host, items, organizations, onClose, onChanged, limits }) {
4509
+ function ImportPanel({ port, host, items, definitions, organizations, onClose, onChanged, limits }) {
4475
4510
  const confirmed = useConfirmed();
4511
+ const [otpFor, setOtpFor] = useState19(null);
4476
4512
  const fileInput = useRef12(null);
4477
4513
  const abort = useRef12(null);
4478
4514
  const stop = useRef12(false);
@@ -4580,6 +4616,7 @@ function ImportPanel({ port, host, items, organizations, onClose, onChanged, lim
4580
4616
  failed: result.failed,
4581
4617
  skipped,
4582
4618
  withOtp: (previous?.withOtp ?? 0) + result.withOtp,
4619
+ needOtp: [...previous?.needOtp ?? [], ...result.needOtp],
4583
4620
  untried: result.untried
4584
4621
  };
4585
4622
  setDone(merged);
@@ -4605,7 +4642,7 @@ function ImportPanel({ port, host, items, organizations, onClose, onChanged, lim
4605
4642
  setRunning({ done: 0, total: chosen });
4606
4643
  try {
4607
4644
  const made = await confirmed(() => port.io.importEnv(text, organizationId, injectSandbox));
4608
- setDone({ created: made, failed: [], skipped: draft.entries.length - chosen, withOtp: 0, untried: [] });
4645
+ setDone({ created: made, failed: [], skipped: draft.entries.length - chosen, withOtp: 0, needOtp: [], untried: [] });
4609
4646
  if (made.length > 0) onChanged();
4610
4647
  } catch (cause) {
4611
4648
  setError(cause instanceof Error ? cause.message : "The import could not finish.");
@@ -4720,9 +4757,32 @@ function ImportPanel({ port, host, items, organizations, onClose, onChanged, lim
4720
4757
  plural(done.withOtp, "login"),
4721
4758
  " still need the one-time password added."
4722
4759
  ] }) }) : null,
4760
+ done.needOtp.map((made) => {
4761
+ const now = items.find((i) => i.id === made.id);
4762
+ if (now?.authenticator) return null;
4763
+ return /* @__PURE__ */ jsx19(VaultRow, { label: "Authenticator", children: /* @__PURE__ */ jsxs18("button", { type: "button", className: "truncate text-left text-primary hover:underline", onClick: () => setOtpFor(made.id), children: [
4764
+ "Set up authenticator for ",
4765
+ made.name
4766
+ ] }) }, `otp:${made.id}`);
4767
+ }),
4723
4768
  done.failed.map((f) => /* @__PURE__ */ jsx19(VaultRow, { label: f.title, children: /* @__PURE__ */ jsx19("span", { className: "block truncate text-destructive", title: f.reason, children: f.reason }) }, f.key))
4724
4769
  ] }) : null,
4725
- /* @__PURE__ */ jsx19(ErrorLine, { message: error })
4770
+ /* @__PURE__ */ jsx19(ErrorLine, { message: error }),
4771
+ otpFor ? /* @__PURE__ */ jsx19(
4772
+ AddOtpPanel,
4773
+ {
4774
+ port,
4775
+ items,
4776
+ definitions,
4777
+ itemId: otpFor,
4778
+ onClose: () => setOtpFor(null),
4779
+ onChanged,
4780
+ onDone: () => {
4781
+ setOtpFor(null);
4782
+ onChanged();
4783
+ }
4784
+ }
4785
+ ) : null
4726
4786
  ] });
4727
4787
  }
4728
4788
  function toggle(set, key, on) {
@@ -4760,7 +4820,7 @@ import { Button as Button23, SearchField } from "@ai-matrx/design-system/control
4760
4820
  import { CopyIcon as CopyIcon7 } from "@ai-matrx/design-system/icons";
4761
4821
 
4762
4822
  // src/ItemPane.tsx
4763
- import { useEffect as useEffect16, useMemo as useMemo3, useRef as useRef14, useState as useState27 } from "react";
4823
+ import { Fragment as Fragment17, useEffect as useEffect16, useMemo as useMemo3, useRef as useRef14, useState as useState27 } from "react";
4764
4824
  import { DropdownMenu as DropdownMenu3, DropdownMenuContent as DropdownMenuContent3, DropdownMenuItem as DropdownMenuItem3, DropdownMenuSeparator as DropdownMenuSeparator3, DropdownMenuTrigger as DropdownMenuTrigger3 } from "@ai-matrx/design-system";
4765
4825
  import { cn as cn12 } from "@ai-matrx/design-system";
4766
4826
  import { Badge as Badge7, Button as Button22, CONTROLS_CONTAINER_CLASS, OptionCombobox, Switch as Switch6 } from "@ai-matrx/design-system/controls";
@@ -5142,14 +5202,12 @@ import { confirm as confirm7 } from "@ai-matrx/kit/confirm-opener";
5142
5202
  import { Fragment as Fragment15, jsx as jsx22, jsxs as jsxs21 } from "react/jsx-runtime";
5143
5203
  var HANDLINGS = ["visible", "revealable", "sealed"];
5144
5204
  var hint = (text, tone = "muted") => /* @__PURE__ */ jsx22("span", { className: tone === "destructive" ? "text-destructive" : void 0, title: text, children: text });
5145
- var hasSecretSettings = (s) => Boolean(s.envKey?.trim() || s.injectIntoSandbox || s.handling && s.handling !== "revealable");
5146
- function SecretBuilderRow({ field, onChange, onRemove }) {
5147
- const [open, setOpen] = useState22(hasSecretSettings(field));
5148
- const env = field.envKey ?? "";
5205
+ var saysMore = (s, defaultHandling) => Boolean(s.envKey?.trim() || s.injectIntoSandbox || s.handling && s.handling !== defaultHandling);
5206
+ function FieldSettingsRows({ name, value, defaultHandling = "revealable", onChange }) {
5207
+ const env = value.envKey ?? "";
5149
5208
  const envProblem = envKeyProblem(env);
5150
5209
  const envOk = env.trim() !== "" && !envProblem;
5151
- const handling = field.handling ?? "revealable";
5152
- const name = field.label.trim() || "Secret";
5210
+ const handling = value.handling ?? defaultHandling;
5153
5211
  const chooseHandling = async (next) => {
5154
5212
  if (next === handling) return;
5155
5213
  if (next === "sealed") {
@@ -5163,6 +5221,38 @@ function SecretBuilderRow({ field, onChange, onRemove }) {
5163
5221
  }
5164
5222
  onChange({ handling: next });
5165
5223
  };
5224
+ return /* @__PURE__ */ jsxs21(Fragment15, { children: [
5225
+ /* @__PURE__ */ jsx22(VaultRow, { label: "Env key", hint: envProblem ? hint(envProblem, "destructive") : hint("The name sandboxes and workflows use"), children: /* @__PURE__ */ jsx22(
5226
+ Field8,
5227
+ {
5228
+ className: "w-full font-mono",
5229
+ "aria-label": `${name} env key`,
5230
+ "aria-invalid": Boolean(envProblem),
5231
+ placeholder: "e.g. GITHUB_TOKEN",
5232
+ autoComplete: "off",
5233
+ spellCheck: false,
5234
+ value: env,
5235
+ onChange: (e) => onChange({ envKey: e.target.value, ...e.target.value.trim() ? {} : { injectIntoSandbox: false } })
5236
+ }
5237
+ ) }),
5238
+ /* @__PURE__ */ jsx22(VaultRow, { label: "Who can see it", hint: hint(HANDLING_WORDS[handling].hint), children: /* @__PURE__ */ jsx22(
5239
+ SegmentedControl5,
5240
+ {
5241
+ "aria-label": `Who can see ${name}`,
5242
+ value: handling,
5243
+ onValueChange: (v) => void chooseHandling(v),
5244
+ data: HANDLINGS.map((h) => ({ value: h, label: HANDLING_WORDS[h].label, title: HANDLING_WORDS[h].hint }))
5245
+ }
5246
+ ) }),
5247
+ /* @__PURE__ */ jsx22(VaultRow, { label: "Use in sandbox", hint: envOk ? hint("Sandboxes get it as an env variable") : hint("Needs an env key"), children: /* @__PURE__ */ jsx22(Switch5, { "aria-label": `Use ${name} in sandbox`, checked: envOk && value.injectIntoSandbox === true, disabled: !envOk, onCheckedChange: (v) => onChange({ injectIntoSandbox: v }) }) })
5248
+ ] });
5249
+ }
5250
+ var hasSecretSettings = (s) => saysMore(s, "revealable");
5251
+ var hasDeclaredSettings = (s, defaultHandling) => Boolean(s && saysMore(s, defaultHandling));
5252
+ function SecretBuilderRow({ field, onChange, onRemove }) {
5253
+ const [open, setOpen] = useState22(hasSecretSettings(field));
5254
+ const handling = field.handling ?? "revealable";
5255
+ const name = field.label.trim() || "Secret";
5166
5256
  return /* @__PURE__ */ jsxs21(Fragment15, { children: [
5167
5257
  /* @__PURE__ */ jsx22(
5168
5258
  CustomEditRow,
@@ -5170,47 +5260,26 @@ function SecretBuilderRow({ field, onChange, onRemove }) {
5170
5260
  label: field.label,
5171
5261
  value: field.value,
5172
5262
  secret: true,
5263
+ multiline: field.multiline === true,
5173
5264
  onLabel: (v) => onChange({ label: v }),
5174
5265
  onValue: (v) => onChange({ value: v }),
5175
5266
  onRemove,
5176
- onPasteValue: (text) => {
5177
- const a = parseEnvAssignment(text);
5178
- if (!a) return false;
5179
- onChange({ envKey: a.key, value: a.value, ...field.label.trim() ? {} : { label: labelFromEnvKey(a.key) } });
5180
- setOpen(true);
5181
- return true;
5267
+ ...field.multiline ? {} : {
5268
+ onPasteValue: (text) => {
5269
+ const a = parseEnvAssignment(text);
5270
+ if (!a) return false;
5271
+ onChange({ envKey: a.key, value: a.value, ...field.label.trim() ? {} : { label: labelFromEnvKey(a.key) } });
5272
+ setOpen(true);
5273
+ return true;
5274
+ }
5182
5275
  },
5183
5276
  extra: /* @__PURE__ */ jsxs21(Fragment15, { children: [
5184
- handling !== "sealed" ? /* @__PURE__ */ jsx22(GenerateButton, { onUse: (v) => onChange({ value: v }) }) : null,
5277
+ handling !== "sealed" && !field.multiline ? /* @__PURE__ */ jsx22(GenerateButton, { onUse: (v) => onChange({ value: v }) }) : null,
5185
5278
  /* @__PURE__ */ jsx22(Button18, { variant: "quiet", icon: /* @__PURE__ */ jsx22(SlidersGlyph, {}), "aria-label": `${name} settings`, pressed: open, onClick: () => setOpen((o) => !o) })
5186
5279
  ] })
5187
5280
  }
5188
5281
  ),
5189
- open ? /* @__PURE__ */ jsxs21(Fragment15, { children: [
5190
- /* @__PURE__ */ jsx22(VaultRow, { label: "Env key", hint: envProblem ? hint(envProblem, "destructive") : hint("The name sandboxes and workflows use"), children: /* @__PURE__ */ jsx22(
5191
- Field8,
5192
- {
5193
- className: "w-full font-mono",
5194
- "aria-label": `${name} env key`,
5195
- "aria-invalid": Boolean(envProblem),
5196
- placeholder: "e.g. GITHUB_TOKEN",
5197
- autoComplete: "off",
5198
- spellCheck: false,
5199
- value: env,
5200
- onChange: (e) => onChange({ envKey: e.target.value, ...e.target.value.trim() ? {} : { injectIntoSandbox: false } })
5201
- }
5202
- ) }),
5203
- /* @__PURE__ */ jsx22(VaultRow, { label: "Who can see it", hint: hint(HANDLING_WORDS[handling].hint), children: /* @__PURE__ */ jsx22(
5204
- SegmentedControl5,
5205
- {
5206
- "aria-label": `Who can see ${name}`,
5207
- value: handling,
5208
- onValueChange: (v) => void chooseHandling(v),
5209
- data: HANDLINGS.map((h) => ({ value: h, label: HANDLING_WORDS[h].label, title: HANDLING_WORDS[h].hint }))
5210
- }
5211
- ) }),
5212
- /* @__PURE__ */ jsx22(VaultRow, { label: "Use in sandbox", hint: envOk ? hint("Sandboxes get it as an env variable") : hint("Needs an env key"), children: /* @__PURE__ */ jsx22(Switch5, { "aria-label": `Use ${name} in sandbox`, checked: envOk && field.injectIntoSandbox === true, disabled: !envOk, onCheckedChange: (v) => onChange({ injectIntoSandbox: v }) }) })
5213
- ] }) : null
5282
+ open ? /* @__PURE__ */ jsx22(FieldSettingsRows, { name, value: field, onChange }) : null
5214
5283
  ] });
5215
5284
  }
5216
5285
  function withMfaMethod(custom, v) {
@@ -5839,7 +5908,9 @@ function SharingSummary({ item, port, className }) {
5839
5908
 
5840
5909
  // src/ItemPane.tsx
5841
5910
  import { formatAbsoluteDate as formatAbsoluteDate3 } from "@ai-matrx/kit/format";
5842
- import { Fragment as Fragment17, jsx as jsx27, jsxs as jsxs25 } from "react/jsx-runtime";
5911
+ import { Fragment as Fragment18, jsx as jsx27, jsxs as jsxs25 } from "react/jsx-runtime";
5912
+ var ENV_VALUE_KEY = "env_value";
5913
+ var secureNoteDraft = () => ({ key: SECURE_NOTE_KEY, label: "Secure note", value: "", multiline: true, description: "Encrypted notes" });
5843
5914
  var USERNAME_KEYS = /^(username|user_name|email|login|username_or_email|account_email)$/;
5844
5915
  function ItemPane(props) {
5845
5916
  const { port, item, definition, mode, host, organizations, defaultOwner, seed, onBack, onMode, onSaved, onDeleted, onDirty, onCancelNew, onPanel, onChanged, favorite, notice, refreshKey } = props;
@@ -6030,6 +6101,16 @@ function ItemPane(props) {
6030
6101
  const fieldSettings = (f) => canEdit ? /* @__PURE__ */ jsx27(Button22, { variant: "quiet", icon: /* @__PURE__ */ jsx27(SlidersGlyph, {}), "aria-label": `${f.label} settings`, title: `${f.label} settings`, onClick: () => onPanel(`field:${f.id}`) }) : null;
6031
6102
  const turnOnOne = canTurnOn ? (f) => () => turnOn([f]) : () => void 0;
6032
6103
  const declaredKeys = new Set(declared.map((f) => f.key));
6104
+ const hasSecureNote = declaredKeys.has(SECURE_NOTE_KEY) || Boolean(item?.fields.some((f) => f.key === SECURE_NOTE_KEY)) || draft.secrets.some((x) => x.key === SECURE_NOTE_KEY);
6105
+ const [settingsToggled, setSettingsToggled] = useState27(/* @__PURE__ */ new Set());
6106
+ const settingsOpen = (f) => settingsToggled.has(f.key) !== (definition?.key === ENV_VALUE_KEY || hasDeclaredSettings(draft.fieldSettings?.[f.key], f.handling));
6107
+ const toggleSettings = (key) => setSettingsToggled((cur) => {
6108
+ const next = new Set(cur);
6109
+ if (next.has(key)) next.delete(key);
6110
+ else next.add(key);
6111
+ return next;
6112
+ });
6113
+ const patchFieldSettings = (key, p) => setDraft((d) => ({ ...d, fieldSettings: { ...d.fieldSettings, [key]: { ...d.fieldSettings?.[key], ...p } } }));
6033
6114
  const extraFields = (item?.fields ?? []).filter((f) => !declaredKeys.has(f.key) && !draft.removed.includes(f.id) && !isAuthenticatorStorage(f.key));
6034
6115
  const metaValue = (key) => draft.values[key] ?? item?.custom.find((c) => c.key === key)?.value ?? "";
6035
6116
  const extraCustom = draft.custom.filter((c) => !declaredKeys.has(c.key) && c.key !== MFA_METHOD_KEY && !isAuthenticatorStorage(c.key));
@@ -6112,7 +6193,7 @@ function ItemPane(props) {
6112
6193
  /* @__PURE__ */ jsx27(SettingsIcon, { className: "size-4" }),
6113
6194
  "Settings"
6114
6195
  ] }) : null,
6115
- canManage ? /* @__PURE__ */ jsxs25(Fragment17, { children: [
6196
+ canManage ? /* @__PURE__ */ jsxs25(Fragment18, { children: [
6116
6197
  /* @__PURE__ */ jsx27(DropdownMenuSeparator3, {}),
6117
6198
  /* @__PURE__ */ jsxs25(DropdownMenuItem3, { className: "text-destructive", disabled: busy, onSelect: () => void remove(), children: [
6118
6199
  /* @__PURE__ */ jsx27(TrashGlyph, { className: "size-4" }),
@@ -6132,7 +6213,7 @@ function ItemPane(props) {
6132
6213
  /* @__PURE__ */ jsx27("span", { className: "min-w-0 flex-1 truncate text-warning", children: offFields.length === 0 ? "This item is turned off" : offFields.length === 1 ? "1 field is turned off" : `${offFields.length} fields are turned off` }),
6133
6214
  canTurnOn ? /* @__PURE__ */ jsx27(TurnOnButton, { label: "all fields", onTurnOn: () => turnOn(offFields) }) : null
6134
6215
  ] }) : null,
6135
- /* @__PURE__ */ jsx27("div", { className: "min-h-0 flex-1 overflow-y-auto", children: sent ? /* @__PURE__ */ jsx27(AssignResult, { sent, onAnother: createAnother, onDone: onCancelNew }) : /* @__PURE__ */ jsx27("div", { className: "mx-auto flex w-full max-w-3xl flex-col gap-5 px-3 py-4 sm:px-6 sm:py-6", children: editing ? /* @__PURE__ */ jsxs25(Fragment17, { children: [
6216
+ /* @__PURE__ */ jsx27("div", { className: "min-h-0 flex-1 overflow-y-auto", children: sent ? /* @__PURE__ */ jsx27(AssignResult, { sent, onAnother: createAnother, onDone: onCancelNew }) : /* @__PURE__ */ jsx27("div", { className: "mx-auto flex w-full max-w-3xl flex-col gap-5 px-3 py-4 sm:px-6 sm:py-6", children: editing ? /* @__PURE__ */ jsxs25(Fragment18, { children: [
6136
6217
  /* @__PURE__ */ jsxs25(VaultSection, { children: [
6137
6218
  mode === "new" ? /* @__PURE__ */ jsx27(VaultRow, { label: "Save to", children: /* @__PURE__ */ jsx27(
6138
6219
  OptionCombobox,
@@ -6153,7 +6234,7 @@ function ItemPane(props) {
6153
6234
  }
6154
6235
  ) }) : null,
6155
6236
  /* @__PURE__ */ jsx27(EditRow, { label: "Name", value: draft.name, onChange: (v) => patch({ name: v }), placeholder: "Name", autoFocus: mode === "new" }),
6156
- mode === "new" && owner === "person" ? /* @__PURE__ */ jsxs25(Fragment17, { children: [
6237
+ mode === "new" && owner === "person" ? /* @__PURE__ */ jsxs25(Fragment18, { children: [
6157
6238
  /* @__PURE__ */ jsx27(EditRow, { label: "Their email", value: recipient, placeholder: "name@company.com", inputMode: "email", onChange: setRecipient }),
6158
6239
  declared.some((f) => f.storage === "encrypted" && f.kind === "secret") ? /* @__PURE__ */ jsx27(VaultRow, { label: "Password", hint: "You never see it", children: /* @__PURE__ */ jsx27(Switch6, { checked: generatePrivately, onCheckedChange: setGeneratePrivately, "aria-label": "Generate the password privately" }) }) : null
6159
6240
  ] }) : null,
@@ -6162,24 +6243,29 @@ function ItemPane(props) {
6162
6243
  const secretKind = f.storage === "encrypted" && f.kind === "secret";
6163
6244
  const value = f.storage === "metadata" ? metaValue(f.key) : draft.values[f.key] ?? "";
6164
6245
  const hidden = existing && existing.handling !== "visible" && !(f.key in loaded);
6165
- return /* @__PURE__ */ jsx27(
6166
- EditRow,
6167
- {
6168
- label: f.label,
6169
- value,
6170
- placeholder: hidden ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : f.required ? "Required" : isRecoveryCodesField(f) ? "Backup codes, one per line" : "",
6171
- secret: secretKind,
6172
- multiline: f.kind === "multiline",
6173
- inputMode: f.kind === "url" ? "url" : USERNAME_KEYS.test(f.key) ? "email" : "text",
6174
- onChange: (v) => setValue(f.key, v),
6175
- actions: /* @__PURE__ */ jsxs25(Fragment17, { children: [
6176
- hidden && existing?.handling === "revealable" && item?.capabilities.canReveal ? /* @__PURE__ */ jsx27(Button22, { variant: "quiet", icon: /* @__PURE__ */ jsx27(EyeGlyph, {}), "aria-label": `Show current ${f.label}`, onClick: () => void loadForEdit(f.key) }) : null,
6177
- secretKind ? /* @__PURE__ */ jsx27(GenerateButton, { onUse: (v) => setValue(f.key, v) }) : null,
6178
- existing ? /* @__PURE__ */ jsx27(Button22, { variant: "quiet", icon: /* @__PURE__ */ jsx27(SettingsIcon, {}), "aria-label": `${f.label} settings`, onClick: () => onPanel(`field:${existing.id}`) }) : null
6179
- ] })
6180
- },
6181
- f.key
6182
- );
6246
+ const newSecret = mode === "new" && f.storage === "encrypted" && owner !== "person";
6247
+ const open = newSecret && settingsOpen(f);
6248
+ return /* @__PURE__ */ jsxs25(Fragment17, { children: [
6249
+ /* @__PURE__ */ jsx27(
6250
+ EditRow,
6251
+ {
6252
+ label: f.label,
6253
+ value,
6254
+ placeholder: hidden ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : f.required ? "Required" : isRecoveryCodesField(f) ? "Backup codes, one per line" : "",
6255
+ secret: secretKind,
6256
+ multiline: f.kind === "multiline" || value.includes("\n"),
6257
+ inputMode: f.kind === "url" ? "url" : USERNAME_KEYS.test(f.key) ? "email" : "text",
6258
+ onChange: (v) => setValue(f.key, v),
6259
+ actions: /* @__PURE__ */ jsxs25(Fragment18, { children: [
6260
+ hidden && existing?.handling === "revealable" && item?.capabilities.canReveal ? /* @__PURE__ */ jsx27(Button22, { variant: "quiet", icon: /* @__PURE__ */ jsx27(EyeGlyph, {}), "aria-label": `Show current ${f.label}`, onClick: () => void loadForEdit(f.key) }) : null,
6261
+ secretKind ? /* @__PURE__ */ jsx27(GenerateButton, { onUse: (v) => setValue(f.key, v) }) : null,
6262
+ existing ? /* @__PURE__ */ jsx27(Button22, { variant: "quiet", icon: /* @__PURE__ */ jsx27(SettingsIcon, {}), "aria-label": `${f.label} settings`, onClick: () => onPanel(`field:${existing.id}`) }) : null,
6263
+ newSecret ? /* @__PURE__ */ jsx27(Button22, { variant: "quiet", icon: /* @__PURE__ */ jsx27(SlidersGlyph, {}), "aria-label": `${f.label} settings`, pressed: open, onClick: () => toggleSettings(f.key) }) : null
6264
+ ] })
6265
+ }
6266
+ ),
6267
+ open ? /* @__PURE__ */ jsx27(FieldSettingsRows, { name: f.label, value: draft.fieldSettings?.[f.key] ?? {}, defaultHandling: f.handling, onChange: (p) => patchFieldSettings(f.key, p) }) : null
6268
+ ] }, f.key);
6183
6269
  }),
6184
6270
  extraFields.map((f) => /* @__PURE__ */ jsx27(
6185
6271
  EditRow,
@@ -6188,11 +6274,11 @@ function ItemPane(props) {
6188
6274
  value: draft.values[f.key] ?? "",
6189
6275
  placeholder: f.handling === "visible" || f.key in loaded ? "" : "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
6190
6276
  secret: f.handling !== "visible" && !isRecoveryCodesField(f),
6191
- multiline: isRecoveryCodesField(f),
6277
+ multiline: f.kind === "multiline" || isRecoveryCodesField(f) || (draft.values[f.key] ?? "").includes("\n"),
6192
6278
  onChange: (v) => setValue(f.key, v),
6193
6279
  onRemove: () => patch({ removed: [...draft.removed, f.id] }),
6194
6280
  removeLabel: `Delete ${f.label}`,
6195
- actions: /* @__PURE__ */ jsxs25(Fragment17, { children: [
6281
+ actions: /* @__PURE__ */ jsxs25(Fragment18, { children: [
6196
6282
  f.handling === "revealable" && !(f.key in loaded) && item?.capabilities.canReveal ? /* @__PURE__ */ jsx27(Button22, { variant: "quiet", icon: /* @__PURE__ */ jsx27(EyeGlyph, {}), "aria-label": `Show current ${f.label}`, onClick: () => void loadForEdit(f.key) }) : null,
6197
6283
  /* @__PURE__ */ jsx27(Button22, { variant: "quiet", icon: /* @__PURE__ */ jsx27(SettingsIcon, {}), "aria-label": `${f.label} settings`, onClick: () => onPanel(`field:${f.id}`) })
6198
6284
  ] })
@@ -6257,6 +6343,7 @@ function ItemPane(props) {
6257
6343
  /* @__PURE__ */ jsx27(DropdownMenuTrigger3, { asChild: true, children: /* @__PURE__ */ jsx27(Button22, { variant: "quiet", icon: /* @__PURE__ */ jsx27(PlusGlyph, {}), children: "Add" }) }),
6258
6344
  /* @__PURE__ */ jsxs25(DropdownMenuContent3, { align: "end", children: [
6259
6345
  /* @__PURE__ */ jsx27(DropdownMenuItem3, { onSelect: () => patch({ secrets: [...draft.secrets, { key: "", label: "", value: "" }] }), children: "Secret field" }),
6346
+ !hasSecureNote ? /* @__PURE__ */ jsx27(DropdownMenuItem3, { onSelect: () => patch({ secrets: [...draft.secrets, secureNoteDraft()] }), children: "Secure note" }) : null,
6260
6347
  /* @__PURE__ */ jsx27(DropdownMenuItem3, { onSelect: () => patch({ custom: [...draft.custom, { key: `custom_${draft.custom.length + 1}_${Date.now() % 1e5}`, label: "", value: "" }] }), children: "Text field" }),
6261
6348
  !tagsVisible ? /* @__PURE__ */ jsx27(DropdownMenuItem3, { onSelect: () => setShowTags(true), children: "Tags" }) : null,
6262
6349
  !descriptionVisible ? /* @__PURE__ */ jsx27(DropdownMenuItem3, { onSelect: () => setShowDescription(true), children: "Description" }) : null,
@@ -6289,7 +6376,7 @@ function ItemPane(props) {
6289
6376
  }
6290
6377
  ),
6291
6378
  filesProps ? /* @__PURE__ */ jsx27(FilesSection, { ...filesProps, editing: true }) : mode === "new" && owner !== "person" ? /* @__PURE__ */ jsx27(DraftFilesSection, { files: queuedFiles, onFiles: setQueuedFiles }) : null
6292
- ] }) : item ? /* @__PURE__ */ jsxs25(Fragment17, { children: [
6379
+ ] }) : item ? /* @__PURE__ */ jsxs25(Fragment18, { children: [
6293
6380
  /* @__PURE__ */ jsxs25(VaultSection, { children: [
6294
6381
  declared.map((f) => {
6295
6382
  if (f.storage === "metadata") {
@@ -6344,6 +6431,13 @@ function ItemPane(props) {
6344
6431
 
6345
6432
  // src/ItemList.tsx
6346
6433
  import { jsx as jsx28, jsxs as jsxs26 } from "react/jsx-runtime";
6434
+ var RECOVERY_KEYS = /^recovery_codes?$/;
6435
+ function otherCopyFields(item, user, secret) {
6436
+ if (!item.capabilities.canUse) return [];
6437
+ return item.fields.filter(
6438
+ (f) => f !== user && f !== secret && f.active !== false && f.handling !== "sealed" && (f.handling === "visible" || item.capabilities.canReveal) && !isAuthenticatorStorage(f.key) && !RECOVERY_KEYS.test(f.key)
6439
+ );
6440
+ }
6347
6441
  var SORTS2 = [
6348
6442
  { value: "name", label: "Name A\u2013Z" },
6349
6443
  { value: "name-desc", label: "Name Z\u2013A" },
@@ -6356,7 +6450,7 @@ function subtitleOf(item, withOwner) {
6356
6450
  const what = loginSiteOf(item) || item.definitionLabel;
6357
6451
  return withOwner ? `${what} \xB7 ${item.owner.label}` : what;
6358
6452
  }
6359
- function ItemList({ title, items, definitions, port, selectedId, onSelect, query, onQuery, sort, onSort, onNew, matchingIds, header, showOwner, canSortViewed = false }) {
6453
+ function ItemList({ title, items, definitions, port, selectedId, onSelect, query, onQuery, sort, onSort, onNew, matchingIds, header, showOwner, canSortViewed = false, onRefresh, refreshing = false, rowContextMenu = "package", rowMenuItems }) {
6360
6454
  const sorts = SORTS2.filter((s) => canSortViewed || s.value !== "viewed");
6361
6455
  const pinned = matchingIds ? items.filter((i) => matchingIds.has(i.id)) : [];
6362
6456
  const rest = matchingIds ? items.filter((i) => !matchingIds.has(i.id)) : items;
@@ -6377,7 +6471,7 @@ function ItemList({ title, items, definitions, port, selectedId, onSelect, query
6377
6471
  }
6378
6472
  };
6379
6473
  const mixedOwners = showOwner ?? new Set(items.map((i) => i.owner.organizationId ?? i.owner.kind)).size > 1;
6380
- const row = (i) => /* @__PURE__ */ jsx28(ListRow, { item: i, family: familyOf2(i.definitionKey), port, on: i.id === selectedId, onSelect, withOwner: mixedOwners }, i.id);
6474
+ const row = (i) => /* @__PURE__ */ jsx28(ListRow, { item: i, family: familyOf2(i.definitionKey), port, on: i.id === selectedId, onSelect, withOwner: mixedOwners, ownContextMenu: rowContextMenu === "package", extra: rowMenuItems }, i.id);
6381
6475
  const sortLabel = sorts.find((s) => s.value === sort)?.label ?? "Name A\u2013Z";
6382
6476
  return /* @__PURE__ */ jsxs26("section", { "aria-label": title, className: "flex h-full min-h-0 flex-col border-r border-border bg-background", children: [
6383
6477
  /* @__PURE__ */ jsxs26("div", { className: "flex flex-col gap-2 px-3 pb-2 pt-3", children: [
@@ -6392,9 +6486,12 @@ function ItemList({ title, items, definitions, port, selectedId, onSelect, query
6392
6486
  " ",
6393
6487
  items.length === 1 ? "item" : "items"
6394
6488
  ] }),
6395
- /* @__PURE__ */ jsxs26(DropdownMenu4, { children: [
6396
- /* @__PURE__ */ jsx28(DropdownMenuTrigger4, { asChild: true, children: /* @__PURE__ */ jsx28(Button23, { variant: "quiet", icon: /* @__PURE__ */ jsx28(SortGlyph, {}), "aria-label": `Sort: ${sortLabel}`, children: sortLabel }) }),
6397
- /* @__PURE__ */ jsx28(DropdownMenuContent4, { align: "end", children: /* @__PURE__ */ jsx28(DropdownMenuRadioGroup, { value: sort, onValueChange: (v) => onSort(sorts.find((s) => s.value === v)?.value ?? "name"), children: sorts.map((s) => /* @__PURE__ */ jsx28(DropdownMenuRadioItem, { value: s.value, children: s.label }, s.value)) }) })
6489
+ /* @__PURE__ */ jsxs26("span", { className: "flex shrink-0 items-center gap-0.5", children: [
6490
+ onRefresh ? /* @__PURE__ */ jsx28(Button23, { variant: "quiet", icon: /* @__PURE__ */ jsx28(RefreshGlyph, { className: refreshing ? "animate-spin" : "" }), "aria-label": "Refresh", title: "Refresh the list", disabled: refreshing, onClick: onRefresh }) : null,
6491
+ /* @__PURE__ */ jsxs26(DropdownMenu4, { children: [
6492
+ /* @__PURE__ */ jsx28(DropdownMenuTrigger4, { asChild: true, children: /* @__PURE__ */ jsx28(Button23, { variant: "quiet", icon: /* @__PURE__ */ jsx28(SortGlyph, {}), "aria-label": `Sort: ${sortLabel}`, children: sortLabel }) }),
6493
+ /* @__PURE__ */ jsx28(DropdownMenuContent4, { align: "end", children: /* @__PURE__ */ jsx28(DropdownMenuRadioGroup, { value: sort, onValueChange: (v) => onSort(sorts.find((s) => s.value === v)?.value ?? "name"), children: sorts.map((s) => /* @__PURE__ */ jsx28(DropdownMenuRadioItem, { value: s.value, children: s.label }, s.value)) }) })
6494
+ ] })
6398
6495
  ] })
6399
6496
  ] })
6400
6497
  ] }),
@@ -6409,7 +6506,7 @@ function ItemList({ title, items, definitions, port, selectedId, onSelect, query
6409
6506
  ] }) })
6410
6507
  ] });
6411
6508
  }
6412
- function ListRow({ item, family, port, on, onSelect, withOwner }) {
6509
+ function ListRow({ item, family, port, on, onSelect, withOwner, ownContextMenu, extra }) {
6413
6510
  const confirmed = useConfirmed();
6414
6511
  const copy = useCopy();
6415
6512
  const notify = useNotify();
@@ -6434,67 +6531,93 @@ function ListRow({ item, family, port, on, onSelect, withOwner }) {
6434
6531
  const copyPassword = () => secret && void run("Password", () => port.reveal(item, secret.key), true);
6435
6532
  const copyCode = () => code && void run("One-time code", async () => (await port.authenticator.code(item)).code, true);
6436
6533
  const copyName = () => void copy(item.name, "Name", { secret: false });
6437
- return /* @__PURE__ */ jsxs26("li", { className: "group relative", "data-item": item.id, children: [
6438
- /* @__PURE__ */ jsxs26(
6439
- "button",
6440
- {
6441
- type: "button",
6442
- "aria-current": on ? "true" : void 0,
6443
- "data-surface": on ? "primary" : void 0,
6444
- onClick: () => onSelect(item.id),
6445
- className: cn13("flex h-12 w-full items-center gap-3 rounded-lg px-2 pr-10 text-left", on ? "bg-primary text-primary-foreground" : "hover:bg-accent"),
6446
- children: [
6447
- /* @__PURE__ */ jsx28(ItemIcon, { definitionKey: item.definitionKey, family, site: loginSiteOf(item), inverted: on }),
6448
- /* @__PURE__ */ jsxs26("span", { className: "flex min-w-0 flex-1 flex-col leading-tight", children: [
6449
- /* @__PURE__ */ jsx28("span", { className: "truncate text-sm font-medium", children: item.name }),
6450
- /* @__PURE__ */ jsx28("span", { className: "truncate text-xs text-muted-foreground", children: subtitleOf(item, withOwner) })
6451
- ] })
6452
- ]
6453
- }
6454
- ),
6455
- /* @__PURE__ */ jsxs26("div", { className: "absolute right-1.5 top-1/2 flex -translate-y-1/2 items-center gap-0.5", "data-surface": on ? "primary" : void 0, children: [
6456
- user || secret ? /* @__PURE__ */ jsxs26("div", { className: cn13("hidden items-center gap-0.5 rounded-md pl-2 [@media(hover:hover)]:group-hover:flex [@media(hover:hover)]:group-focus-within:flex", on ? "bg-primary" : "bg-accent"), "data-row-quick": "", children: [
6457
- user ? /* @__PURE__ */ jsx28(Button23, { variant: "quiet", icon: /* @__PURE__ */ jsx28(UserGlyph, {}), "aria-label": `Copy ${user.label}`, disabled: busy, onClick: copyUser }) : null,
6458
- secret ? /* @__PURE__ */ jsx28(Button23, { variant: "quiet", icon: /* @__PURE__ */ jsx28(KeyGlyph, {}), "aria-label": "Copy password", disabled: busy, onClick: copyPassword }) : null
6459
- ] }) : null,
6460
- /* @__PURE__ */ jsxs26(DropdownMenu4, { open: menuOpen, onOpenChange: setMenuOpen, children: [
6461
- /* @__PURE__ */ jsx28(DropdownMenuTrigger4, { asChild: true, children: /* @__PURE__ */ jsx28(
6462
- Button23,
6534
+ const others = otherCopyFields(item, user, secret);
6535
+ const custom = canUse ? item.custom.filter((c) => c.value) : [];
6536
+ const hostEntries = extra?.(item) ?? [];
6537
+ return /* @__PURE__ */ jsxs26(
6538
+ "li",
6539
+ {
6540
+ className: "group relative",
6541
+ "data-item": item.id,
6542
+ onContextMenu: ownContextMenu ? (e) => {
6543
+ e.preventDefault();
6544
+ setMenuOpen(true);
6545
+ } : void 0,
6546
+ children: [
6547
+ /* @__PURE__ */ jsxs26(
6548
+ "button",
6463
6549
  {
6464
- variant: "quiet",
6465
- icon: /* @__PURE__ */ jsx28(MoreGlyph, {}),
6466
- "aria-label": `Actions for ${item.name}`,
6467
- disabled: busy,
6468
- className: cn13("[@media(hover:hover)]:invisible [@media(hover:hover)]:group-hover:visible [@media(hover:hover)]:group-focus-within:visible", menuOpen && "[@media(hover:hover)]:visible")
6550
+ type: "button",
6551
+ "aria-current": on ? "true" : void 0,
6552
+ "data-surface": on ? "primary" : void 0,
6553
+ onClick: () => onSelect(item.id),
6554
+ className: cn13("flex h-12 w-full items-center gap-3 rounded-lg px-2 pr-10 text-left", on ? "bg-primary text-primary-foreground" : "hover:bg-accent"),
6555
+ children: [
6556
+ /* @__PURE__ */ jsx28(ItemIcon, { definitionKey: item.definitionKey, family, site: loginSiteOf(item), inverted: on }),
6557
+ /* @__PURE__ */ jsxs26("span", { className: "flex min-w-0 flex-1 flex-col leading-tight", children: [
6558
+ /* @__PURE__ */ jsx28("span", { className: "truncate text-sm font-medium", children: item.name }),
6559
+ /* @__PURE__ */ jsx28("span", { className: "truncate text-xs text-muted-foreground", children: subtitleOf(item, withOwner) })
6560
+ ] })
6561
+ ]
6469
6562
  }
6470
- ) }),
6471
- /* @__PURE__ */ jsxs26(DropdownMenuContent4, { align: "end", children: [
6472
- user ? /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: copyUser, children: [
6473
- /* @__PURE__ */ jsx28(UserGlyph, { className: "size-4" }),
6474
- "Copy ",
6475
- user.label.toLowerCase()
6476
- ] }) : null,
6477
- secret ? /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: copyPassword, children: [
6478
- /* @__PURE__ */ jsx28(KeyGlyph, { className: "size-4" }),
6479
- "Copy password"
6480
- ] }) : null,
6481
- code ? /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: copyCode, children: [
6482
- /* @__PURE__ */ jsx28(ClockGlyph, { className: "size-4" }),
6483
- "Copy one-time code"
6563
+ ),
6564
+ /* @__PURE__ */ jsxs26("div", { className: "absolute right-1.5 top-1/2 flex -translate-y-1/2 items-center gap-0.5", "data-surface": on ? "primary" : void 0, children: [
6565
+ user || secret ? /* @__PURE__ */ jsxs26("div", { className: cn13("hidden items-center gap-0.5 rounded-md pl-2 [@media(hover:hover)]:group-hover:flex [@media(hover:hover)]:group-focus-within:flex", on ? "bg-primary" : "bg-accent"), "data-row-quick": "", children: [
6566
+ user ? /* @__PURE__ */ jsx28(Button23, { variant: "quiet", icon: /* @__PURE__ */ jsx28(UserGlyph, {}), "aria-label": `Copy ${user.label}`, disabled: busy, onClick: copyUser }) : null,
6567
+ secret ? /* @__PURE__ */ jsx28(Button23, { variant: "quiet", icon: /* @__PURE__ */ jsx28(KeyGlyph, {}), "aria-label": "Copy password", disabled: busy, onClick: copyPassword }) : null
6484
6568
  ] }) : null,
6485
- user || secret || code ? /* @__PURE__ */ jsx28(DropdownMenuSeparator4, {}) : null,
6486
- /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: () => onSelect(item.id), children: [
6487
- /* @__PURE__ */ jsx28(ChevronRightGlyph, { className: "size-4" }),
6488
- "Open"
6489
- ] }),
6490
- /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: copyName, children: [
6491
- /* @__PURE__ */ jsx28(CopyIcon7, { className: "size-4" }),
6492
- "Copy name"
6569
+ /* @__PURE__ */ jsxs26(DropdownMenu4, { open: menuOpen, onOpenChange: setMenuOpen, children: [
6570
+ /* @__PURE__ */ jsx28(DropdownMenuTrigger4, { asChild: true, children: /* @__PURE__ */ jsx28(
6571
+ Button23,
6572
+ {
6573
+ variant: "quiet",
6574
+ icon: /* @__PURE__ */ jsx28(MoreGlyph, {}),
6575
+ "aria-label": `Actions for ${item.name}`,
6576
+ disabled: busy,
6577
+ className: cn13("[@media(hover:hover)]:invisible [@media(hover:hover)]:group-hover:visible [@media(hover:hover)]:group-focus-within:visible", menuOpen && "[@media(hover:hover)]:visible")
6578
+ }
6579
+ ) }),
6580
+ /* @__PURE__ */ jsxs26(DropdownMenuContent4, { align: "end", children: [
6581
+ user ? /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: copyUser, children: [
6582
+ /* @__PURE__ */ jsx28(UserGlyph, { className: "size-4" }),
6583
+ "Copy ",
6584
+ user.label.toLowerCase()
6585
+ ] }) : null,
6586
+ secret ? /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: copyPassword, children: [
6587
+ /* @__PURE__ */ jsx28(KeyGlyph, { className: "size-4" }),
6588
+ "Copy password"
6589
+ ] }) : null,
6590
+ code ? /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: copyCode, children: [
6591
+ /* @__PURE__ */ jsx28(ClockGlyph, { className: "size-4" }),
6592
+ "Copy one-time code"
6593
+ ] }) : null,
6594
+ others.map((f) => /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: () => void run(f.label, () => port.reveal(item, f.key), f.handling !== "visible"), children: [
6595
+ /* @__PURE__ */ jsx28(CopyIcon7, { className: "size-4" }),
6596
+ "Copy ",
6597
+ f.label.toLowerCase()
6598
+ ] }, f.key)),
6599
+ custom.map((c) => /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: () => void copy(c.value, c.label, { secret: false }), children: [
6600
+ /* @__PURE__ */ jsx28(CopyIcon7, { className: "size-4" }),
6601
+ "Copy ",
6602
+ c.label.toLowerCase()
6603
+ ] }, `custom:${c.key}`)),
6604
+ user || secret || code || others.length > 0 || custom.length > 0 ? /* @__PURE__ */ jsx28(DropdownMenuSeparator4, {}) : null,
6605
+ /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: () => onSelect(item.id), children: [
6606
+ /* @__PURE__ */ jsx28(ChevronRightGlyph, { className: "size-4" }),
6607
+ "Open"
6608
+ ] }),
6609
+ /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: copyName, children: [
6610
+ /* @__PURE__ */ jsx28(CopyIcon7, { className: "size-4" }),
6611
+ "Copy name"
6612
+ ] }),
6613
+ hostEntries.length > 0 ? /* @__PURE__ */ jsx28(DropdownMenuSeparator4, {}) : null,
6614
+ hostEntries.map((h) => /* @__PURE__ */ jsx28(DropdownMenuItem4, { onSelect: h.onSelect, children: h.label }, h.label))
6615
+ ] })
6493
6616
  ] })
6494
6617
  ] })
6495
- ] })
6496
- ] })
6497
- ] });
6618
+ ]
6619
+ }
6620
+ );
6498
6621
  }
6499
6622
 
6500
6623
  // src/lists/TrashView.tsx
@@ -6574,7 +6697,7 @@ function refusalText(reason) {
6574
6697
  // src/lists/RestorePanel.tsx
6575
6698
  import { useEffect as useEffect18, useState as useState29 } from "react";
6576
6699
  import { Button as Button24 } from "@ai-matrx/design-system/controls";
6577
- import { Fragment as Fragment18, jsx as jsx29, jsxs as jsxs27 } from "react/jsx-runtime";
6700
+ import { Fragment as Fragment19, jsx as jsx29, jsxs as jsxs27 } from "react/jsx-runtime";
6578
6701
  var count = (n, one) => `${n} ${one}${n === 1 ? "" : "s"}`;
6579
6702
  function RestorePanel({ lists, item, restore, onClose }) {
6580
6703
  const [preview, setPreview] = useState29(null);
@@ -6619,7 +6742,7 @@ function RestorePanel({ lists, item, restore, onClose }) {
6619
6742
  title: `Restore "${item.name}"`,
6620
6743
  size: "md",
6621
6744
  onClose,
6622
- footer: /* @__PURE__ */ jsxs27(Fragment18, { children: [
6745
+ footer: /* @__PURE__ */ jsxs27(Fragment19, { children: [
6623
6746
  /* @__PURE__ */ jsx29(Button24, { variant: "quiet", onClick: onClose, children: refused ? "Close" : "Cancel" }),
6624
6747
  refused ? null : /* @__PURE__ */ jsx29(Button24, { variant: "primary", icon: /* @__PURE__ */ jsx29(RestoreGlyph, {}), disabled: busy || loading || lists.preview !== void 0 && preview === null, onClick: () => void run(), children: busy ? "Restoring\u2026" : "Restore" })
6625
6748
  ] }),
@@ -7117,7 +7240,7 @@ function addPasskeyFailure(message2) {
7117
7240
  var isRevoked = (d) => d.revokedAt !== null;
7118
7241
 
7119
7242
  // src/devices/DevicesPanel.tsx
7120
- import { Fragment as Fragment19, jsx as jsx33, jsxs as jsxs31 } from "react/jsx-runtime";
7243
+ import { Fragment as Fragment20, jsx as jsx33, jsxs as jsxs31 } from "react/jsx-runtime";
7121
7244
  var messageOf3 = (e, fallback) => e instanceof Error && e.message ? e.message : fallback;
7122
7245
  function ErrorLine2({ text }) {
7123
7246
  return text ? /* @__PURE__ */ jsx33("p", { role: "alert", className: "px-1 text-sm text-destructive", children: text }) : null;
@@ -7248,7 +7371,7 @@ function ApprovePanel({ port, host, onClose, approvalKey, label, kind = "browser
7248
7371
  };
7249
7372
  const canApprove = Boolean(key && passkeys && here && methods?.passkey && !approvedUntil);
7250
7373
  const title = isMac ? "Approve this Mac" : name ? `Approve ${name}` : "Approve this browser";
7251
- const footer = approvedUntil ? /* @__PURE__ */ jsx33(Button28, { variant: "primary", onClick: onClose, children: "Done" }) : /* @__PURE__ */ jsxs31(Fragment19, { children: [
7374
+ const footer = approvedUntil ? /* @__PURE__ */ jsx33(Button28, { variant: "primary", onClick: onClose, children: "Done" }) : /* @__PURE__ */ jsxs31(Fragment20, { children: [
7252
7375
  /* @__PURE__ */ jsx33(Button28, { variant: "quiet", disabled: busy !== null, onClick: refuse, children: key ? "Refuse" : "Close" }),
7253
7376
  canApprove ? /* @__PURE__ */ jsx33(Button28, { variant: "primary", icon: /* @__PURE__ */ jsx33(FingerprintGlyph, {}), disabled: busy !== null, onClick: () => void approve(), children: busy === "approve" ? "Approving\u2026" : "Approve with passkey" }) : null
7254
7377
  ] });
@@ -7256,7 +7379,7 @@ function ApprovePanel({ port, host, onClose, approvalKey, label, kind = "browser
7256
7379
  "This link names no key. Start again from ",
7257
7380
  where,
7258
7381
  "."
7259
- ] }) }) : /* @__PURE__ */ jsxs31(Fragment19, { children: [
7382
+ ] }) }) : /* @__PURE__ */ jsxs31(Fragment20, { children: [
7260
7383
  /* @__PURE__ */ jsxs31(VaultSection, { title: "What is asking", children: [
7261
7384
  /* @__PURE__ */ jsx33(VaultRow, { label: "Asking", hint: "Approve only if you just asked", children: asker }),
7262
7385
  name ? /* @__PURE__ */ jsx33(VaultRow, { label: "Name", children: /* @__PURE__ */ jsx33("span", { className: "block truncate", children: name }) }) : null,
@@ -7556,7 +7679,7 @@ function useNativeImport(bridge, stillAllowed, onImported) {
7556
7679
  }
7557
7680
 
7558
7681
  // src/native/NativeImportPanel.tsx
7559
- import { Fragment as Fragment20, jsx as jsx34, jsxs as jsxs32 } from "react/jsx-runtime";
7682
+ import { Fragment as Fragment21, jsx as jsx34, jsxs as jsxs32 } from "react/jsx-runtime";
7560
7683
  var TONE = {
7561
7684
  eligible: "success",
7562
7685
  committed: "success",
@@ -7600,16 +7723,16 @@ function Importer({ bridge, readLimits, activeOrganizationId, organizations, onC
7600
7723
  const yes = await confirm13({ title: `Add ${plural2(ready, "passkey")} to ${orgName}?`, description: "Passkeys already there are skipped.", confirmLabel: "Import" });
7601
7724
  if (yes) await flow.confirm();
7602
7725
  };
7603
- const footer = preview ? /* @__PURE__ */ jsxs32(Fragment20, { children: [
7726
+ const footer = preview ? /* @__PURE__ */ jsxs32(Fragment21, { children: [
7604
7727
  /* @__PURE__ */ jsx34(Button29, { variant: "quiet", disabled: busy, onClick: () => void flow.cancel(), children: "Cancel" }),
7605
7728
  /* @__PURE__ */ jsxs32(Button29, { variant: "primary", icon: /* @__PURE__ */ jsx34(ImportGlyph, { className: "size-4" }), disabled: busy || ready === 0, onClick: () => void importNow(), children: [
7606
7729
  "Import ",
7607
7730
  ready
7608
7731
  ] })
7609
- ] }) : status ? active ? /* @__PURE__ */ jsxs32(Fragment20, { children: [
7732
+ ] }) : status ? active ? /* @__PURE__ */ jsxs32(Fragment21, { children: [
7610
7733
  /* @__PURE__ */ jsx34(Button29, { variant: "outline", disabled: busy, onClick: () => void flow.refresh(), children: "Refresh" }),
7611
7734
  /* @__PURE__ */ jsx34(Button29, { variant: "outline", disabled: busy, onClick: () => void flow.cancel(), children: "Cancel import" })
7612
- ] }) : /* @__PURE__ */ jsxs32(Fragment20, { children: [
7735
+ ] }) : /* @__PURE__ */ jsxs32(Fragment21, { children: [
7613
7736
  /* @__PURE__ */ jsx34(Button29, { variant: "quiet", onClick: flow.reset, children: "New file" }),
7614
7737
  /* @__PURE__ */ jsx34(Button29, { variant: "primary", onClick: onClose, children: "Done" })
7615
7738
  ] }) : /* @__PURE__ */ jsx34(Button29, { variant: "quiet", onClick: onClose, children: "Close" });
@@ -7648,7 +7771,7 @@ import { GlobeIcon as GlobeIcon4 } from "@ai-matrx/design-system/icons";
7648
7771
  // src/extension/CaptureCard.tsx
7649
7772
  import { useState as useState36, useSyncExternalStore as useSyncExternalStore3 } from "react";
7650
7773
  import { Button as Button30, SearchField as SearchField3 } from "@ai-matrx/design-system/controls";
7651
- import { Fragment as Fragment21, jsx as jsx35, jsxs as jsxs33 } from "react/jsx-runtime";
7774
+ import { Fragment as Fragment22, jsx as jsx35, jsxs as jsxs33 } from "react/jsx-runtime";
7652
7775
  var SEARCH_FROM = 4;
7653
7776
  function CaptureCard({ capture, onChanged }) {
7654
7777
  const prompt = useSyncExternalStore3(capture.subscribe, capture.get, capture.get);
@@ -7681,7 +7804,7 @@ function CaptureCard({ capture, onChanged }) {
7681
7804
  ] }),
7682
7805
  /* @__PURE__ */ jsx35(Button30, { variant: "quiet", icon: /* @__PURE__ */ jsx35(XGlyph, {}), "aria-label": "Not now", disabled: busy, onClick: () => void decide({ action: "dismiss" }) })
7683
7806
  ] }),
7684
- prompt.unavailable ? /* @__PURE__ */ jsx35("p", { className: "text-xs text-muted-foreground", children: "Save it by hand with Save a login." }) : /* @__PURE__ */ jsxs33(Fragment21, { children: [
7807
+ prompt.unavailable ? /* @__PURE__ */ jsx35("p", { className: "text-xs text-muted-foreground", children: "Save it by hand with Save a login." }) : /* @__PURE__ */ jsxs33(Fragment22, { children: [
7685
7808
  existing.length > 0 ? /* @__PURE__ */ jsxs33("div", { className: "flex flex-col gap-1", children: [
7686
7809
  existing.length >= SEARCH_FROM ? /* @__PURE__ */ jsx35(SearchField3, { value: q, onChange: (e) => setQ(e.target.value), placeholder: "Search logins", "aria-label": "Search logins to update" }) : null,
7687
7810
  /* @__PURE__ */ jsxs33("ul", { className: "flex max-h-36 flex-col gap-px overflow-y-auto", children: [
@@ -7787,7 +7910,7 @@ import { useState as useState38 } from "react";
7787
7910
  import { Popover as Popover2, PopoverContent as PopoverContent2, PopoverTrigger as PopoverTrigger2 } from "@ai-matrx/design-system";
7788
7911
  import { Button as Button32 } from "@ai-matrx/design-system/controls";
7789
7912
  import { CopyIcon as CopyIcon8 } from "@ai-matrx/design-system/icons";
7790
- import { Fragment as Fragment22, jsx as jsx37, jsxs as jsxs35 } from "react/jsx-runtime";
7913
+ import { Fragment as Fragment23, jsx as jsx37, jsxs as jsxs35 } from "react/jsx-runtime";
7791
7914
  var HOLD_MS = 6e4;
7792
7915
  function PageGenerator({ fields }) {
7793
7916
  const limits = useGeneratorLimitsSource();
@@ -7838,12 +7961,12 @@ function PageGenerator({ fields }) {
7838
7961
  }
7839
7962
  };
7840
7963
  const value = held.value;
7841
- return /* @__PURE__ */ jsxs35(Fragment22, { children: [
7964
+ return /* @__PURE__ */ jsxs35(Fragment23, { children: [
7842
7965
  /* @__PURE__ */ jsx37(
7843
7966
  VaultRow,
7844
7967
  {
7845
7968
  label: "New password",
7846
- actions: /* @__PURE__ */ jsxs35(Fragment22, { children: [
7969
+ actions: /* @__PURE__ */ jsxs35(Fragment23, { children: [
7847
7970
  value ? /* @__PURE__ */ jsx37(Button32, { variant: "quiet", icon: /* @__PURE__ */ jsx37(CopyIcon8, {}), "aria-label": "Copy password", onClick: () => void copy(value, "Password") }) : null,
7848
7971
  /* @__PURE__ */ jsxs35(Popover2, { open, onOpenChange: setOpen, children: [
7849
7972
  /* @__PURE__ */ jsx37(PopoverTrigger2, { asChild: true, children: /* @__PURE__ */ jsx37(Button32, { variant: "outline", icon: /* @__PURE__ */ jsx37(KeyGlyph, {}), disabled: busy, children: "Generate" }) }),
@@ -8121,6 +8244,7 @@ import { jsx as jsx40, jsxs as jsxs38 } from "react/jsx-runtime";
8121
8244
  var FULL_AT = 1120;
8122
8245
  var SPLIT_AT = 640;
8123
8246
  var NO_HOST = {};
8247
+ var FOCUS_GAP_MS = 2e3;
8124
8248
  var ITEM_PANELS = {
8125
8249
  share: SharePanel,
8126
8250
  move: MovePanel,
@@ -8249,9 +8373,13 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8249
8373
  const knobsRef = useRef19(host.knobs);
8250
8374
  knobsRef.current = host.knobs;
8251
8375
  const [signedOut, setSignedOut] = useState41(false);
8252
- const load = useCallback5(() => {
8253
- setError(null);
8254
- port.load().then((s) => {
8376
+ const [refreshError, setRefreshError] = useState41(null);
8377
+ const [refreshing, setRefreshing] = useState41(false);
8378
+ const loadedAt = useRef19(0);
8379
+ const inFlight = useRef19(false);
8380
+ const apply = useCallback5(
8381
+ (s) => {
8382
+ loadedAt.current = Date.now();
8255
8383
  setSnap(s);
8256
8384
  setItems(s.items);
8257
8385
  setDeleted(s.deleted);
@@ -8259,15 +8387,71 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8259
8387
  (rows) => setDeleted([...s.deleted, ...rows.filter((r) => !s.deleted.some((d) => d.id === r.id))]),
8260
8388
  () => void 0
8261
8389
  );
8262
- }).catch((e) => {
8390
+ },
8391
+ [port]
8392
+ );
8393
+ const refresh = useCallback5(() => {
8394
+ if (inFlight.current) return;
8395
+ inFlight.current = true;
8396
+ setRefreshing(true);
8397
+ port.load().then((s) => {
8398
+ setRefreshError(null);
8399
+ apply(s);
8400
+ }).catch((e) => setRefreshError(plainLoadError(e instanceof Error && e.message ? e.message : "The vault could not be read."))).finally(() => {
8401
+ inFlight.current = false;
8402
+ setRefreshing(false);
8403
+ });
8404
+ port.lists.favorites?.list().then((ids) => setFavorites(new Set(ids)), () => void 0);
8405
+ port.lists.recents?.list().then(setRecents, () => void 0);
8406
+ }, [port, apply]);
8407
+ const load = useCallback5(() => {
8408
+ setError(null);
8409
+ setRefreshError(null);
8410
+ port.load().then(apply).catch((e) => {
8263
8411
  setSignedOut(e?.status === 401);
8264
8412
  setError(e instanceof Error ? e.message : "The vault could not be loaded.");
8265
8413
  });
8266
8414
  port.lists.favorites?.list().then((ids) => setFavorites(new Set(ids)), () => void 0);
8267
8415
  knobsRef.current?.().catch(() => void 0);
8268
8416
  port.lists.recents?.list().then(setRecents, () => void 0);
8269
- }, [port]);
8417
+ }, [port, apply]);
8270
8418
  useEffect23(load, [load]);
8419
+ const quietRef = useRef19({ loc, ready: false });
8420
+ quietRef.current = { loc, ready: snap !== null && !error };
8421
+ const [knobSeconds, setKnobSeconds] = useState41(null);
8422
+ useEffect23(() => {
8423
+ if (host.refreshSeconds !== void 0 || !knobsRef.current) return;
8424
+ let live = true;
8425
+ knobsRef.current().then(
8426
+ (r) => {
8427
+ const v = r["vault.list.refresh_seconds"];
8428
+ if (live && typeof v === "number" && v > 0) setKnobSeconds(v);
8429
+ },
8430
+ () => void 0
8431
+ );
8432
+ return () => {
8433
+ live = false;
8434
+ };
8435
+ }, [host.refreshSeconds]);
8436
+ const everySeconds = host.refreshSeconds ?? knobSeconds;
8437
+ useEffect23(() => {
8438
+ const quietly = (minGapMs) => {
8439
+ const { loc: here, ready } = quietRef.current;
8440
+ if (!ready || document.visibilityState === "hidden" || dirty.current || here.mode !== "view") return;
8441
+ if (Date.now() - loadedAt.current < minGapMs) return;
8442
+ refresh();
8443
+ };
8444
+ const onBack = () => quietly(FOCUS_GAP_MS);
8445
+ const onVisible = () => document.visibilityState === "visible" && onBack();
8446
+ window.addEventListener("focus", onBack);
8447
+ document.addEventListener("visibilitychange", onVisible);
8448
+ const timer2 = everySeconds && everySeconds > 0 ? setInterval(() => quietly(0), everySeconds * 1e3) : null;
8449
+ return () => {
8450
+ window.removeEventListener("focus", onBack);
8451
+ document.removeEventListener("visibilitychange", onVisible);
8452
+ if (timer2) clearInterval(timer2);
8453
+ };
8454
+ }, [refresh, everySeconds]);
8271
8455
  const inventoryRef = useRef19(onInventory);
8272
8456
  inventoryRef.current = onInventory;
8273
8457
  useEffect23(() => {
@@ -8433,6 +8617,10 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8433
8617
  onSort: (sort) => go({ sort }, "replace"),
8434
8618
  canSortViewed: port.lists.recents !== void 0,
8435
8619
  onNew: () => go({ itemId: null, mode: "new", kind: null, panel: null }),
8620
+ onRefresh: refresh,
8621
+ refreshing,
8622
+ rowContextMenu: host.rowContextMenu ?? "package",
8623
+ ...host.rowMenuItems ? { rowMenuItems: host.rowMenuItems } : {},
8436
8624
  ...matchingIds ? { matchingIds } : {},
8437
8625
  ...compactHeader ? { header: compactHeader } : {}
8438
8626
  }
@@ -8457,14 +8645,14 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8457
8645
  host,
8458
8646
  organizations: orgs,
8459
8647
  defaultOwner: ownerDefault,
8460
- seed: { name: guess?.name ?? "", url: guess?.url ?? loc.url.trim() },
8648
+ seed: { name: loc.name?.trim() || guess?.name || "", url: guess?.url ?? loc.url.trim() },
8461
8649
  onBack: () => go({ kind: null }),
8462
8650
  onMode: () => void 0,
8463
8651
  onSaved: (item, _wasNew, n, enrolled) => {
8464
8652
  replaceItem(item);
8465
8653
  dirty.current = false;
8466
8654
  if (enrolled) setFirstCode(item);
8467
- move({ ...loc, itemId: item.id, mode: "view", kind: null, url: "", panel: null }, "replace");
8655
+ move({ ...loc, itemId: item.id, mode: "view", kind: null, url: "", name: "", panel: null }, "replace");
8468
8656
  setNotice(n);
8469
8657
  notify("Saved");
8470
8658
  },
@@ -8472,7 +8660,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8472
8660
  onDirty: (d) => {
8473
8661
  dirty.current = d;
8474
8662
  },
8475
- onCancelNew: () => go({ mode: "view", kind: null, url: "", itemId: null }),
8663
+ onCancelNew: () => go({ mode: "view", kind: null, url: "", name: "", itemId: null }),
8476
8664
  onPanel: () => void 0,
8477
8665
  onChanged: () => void 0
8478
8666
  },
@@ -8563,13 +8751,17 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8563
8751
  const grid = mode === "full" ? "grid-cols-[15rem_21rem_minmax(0,1fr)]" : mode === "split" ? "grid-cols-[19rem_minmax(0,1fr)]" : "grid-cols-1";
8564
8752
  const shown = panesShown(mode, specialView !== null, showingDetail);
8565
8753
  return /* @__PURE__ */ jsxs38("div", { ref: rootRef, className: "flex h-full min-h-0 w-full flex-col bg-background text-foreground", children: [
8754
+ refreshError ? /* @__PURE__ */ jsxs38("div", { role: "status", className: "flex items-center gap-3 border-b border-border bg-muted px-4 py-2 text-sm", children: [
8755
+ /* @__PURE__ */ jsx40("span", { className: "min-w-0 flex-1 truncate text-warning", title: refreshError, children: "Couldn\u2019t refresh. Showing what was loaded." }),
8756
+ /* @__PURE__ */ jsx40("button", { type: "button", className: "shrink-0 text-primary hover:underline", onClick: refresh, children: "Try again" })
8757
+ ] }) : null,
8566
8758
  snap.unavailable.length > 0 ? /* @__PURE__ */ jsxs38("div", { role: "status", className: "flex items-center gap-3 border-b border-border bg-muted px-4 py-2 text-sm", children: [
8567
8759
  /* @__PURE__ */ jsxs38("span", { className: "min-w-0 flex-1 truncate text-warning", children: [
8568
8760
  "Couldn\u2019t load ",
8569
8761
  snap.unavailable.join(", "),
8570
8762
  ". Showing the rest."
8571
8763
  ] }),
8572
- /* @__PURE__ */ jsx40("button", { type: "button", className: "shrink-0 text-primary hover:underline", onClick: load, children: "Try again" })
8764
+ /* @__PURE__ */ jsx40("button", { type: "button", className: "shrink-0 text-primary hover:underline", onClick: refresh, children: "Try again" })
8573
8765
  ] }) : null,
8574
8766
  /* @__PURE__ */ jsxs38("div", { className: `grid min-h-0 w-full flex-1 ${grid} overflow-hidden`, children: [
8575
8767
  mode === "full" ? /* @__PURE__ */ jsx40(
@@ -8920,6 +9112,28 @@ function trashItem(r, ctx) {
8920
9112
  createdAt: ""
8921
9113
  };
8922
9114
  }
9115
+ async function withTypes(items, door, ctx) {
9116
+ if (!door.schema || items.length === 0) return items;
9117
+ const keys = /* @__PURE__ */ new Map();
9118
+ const CHUNK = 100;
9119
+ try {
9120
+ for (let i = 0; i < items.length; i += CHUNK) {
9121
+ const ids = items.slice(i, i + CHUNK).map((x) => x.id);
9122
+ const { data, error } = await door.schema("users").from("credential_items").select("id,definition_key").in("id", ids);
9123
+ if (error) throw new Error(error.message || "credential_items read failed");
9124
+ for (const r of Array.isArray(data) ? data : []) {
9125
+ if (typeof r.id === "string" && typeof r.definition_key === "string" && r.definition_key) keys.set(r.id, r.definition_key);
9126
+ }
9127
+ }
9128
+ } catch (e) {
9129
+ console.warn("[vault] Trash item types could not be read; rows say Credential", e);
9130
+ return items;
9131
+ }
9132
+ return items.map((it) => {
9133
+ const key = keys.get(it.id);
9134
+ return key ? { ...it, definitionKey: key, definitionLabel: ctx.definition(key)?.label ?? humanize(key) } : it;
9135
+ });
9136
+ }
8923
9137
  var previewOf = async (door, id) => parseRecoveryPreview(await call(door, "vault_recovery_preview", { p_id: id }));
8924
9138
  function createListsPort(ctx, door) {
8925
9139
  return {
@@ -8952,7 +9166,8 @@ function createListsPort(ctx, door) {
8952
9166
  if (rows.length < PAGE2) break;
8953
9167
  }
8954
9168
  const items = all.map((r) => trashItem(r, ctx)).filter((i) => i !== null);
8955
- return [...new Map(items.map((i) => [i.id, i])).values()];
9169
+ const unique = [...new Map(items.map((i) => [i.id, i])).values()];
9170
+ return withTypes(unique, door, ctx);
8956
9171
  }
8957
9172
  },
8958
9173
  favorites: {
@@ -8975,16 +9190,25 @@ function createListsPort(ctx, door) {
8975
9190
  import * as account6 from "@ai-matrx/api/account";
8976
9191
 
8977
9192
  // src/secret-field-in.ts
8978
- function secretFieldIn(s) {
9193
+ function settingsIn(s) {
8979
9194
  const envKey = s.envKey?.trim() || null;
8980
9195
  return {
8981
- field_key: fieldKeyOf(s.label),
8982
- value: s.value,
8983
- handling: s.handling ?? "revealable",
8984
9196
  ...envKey ? { env_key: envKey } : {},
8985
9197
  ...envKey && s.injectIntoSandbox ? { inject_into_sandbox: true } : {}
8986
9198
  };
8987
9199
  }
9200
+ function secretFieldIn(s) {
9201
+ return {
9202
+ field_key: s.key.trim() || fieldKeyOf(s.label),
9203
+ value: s.value,
9204
+ handling: s.handling ?? "revealable",
9205
+ ...s.description ? { description: s.description } : {},
9206
+ ...settingsIn(s)
9207
+ };
9208
+ }
9209
+ function declaredFieldIn(f, value, settings) {
9210
+ return { field_key: f.key, value, handling: settings?.handling ?? f.handling, ...settingsIn(settings ?? {}) };
9211
+ }
8988
9212
 
8989
9213
  // src/sharing/api.ts
8990
9214
  function grantOf(w) {
@@ -9085,7 +9309,7 @@ function createSharingPort(ctx, options = {}) {
9085
9309
  initiation: "user",
9086
9310
  non_secret_fields: [...meta.map((f) => ({ key: f.key, label: f.label, value: draft.values[f.key] ?? "" })), ...draft.custom.filter((c) => c.label.trim() !== "").map((c) => ({ key: c.key || fieldKeyOf(c.label), label: c.label, value: c.value }))],
9087
9311
  fields: [
9088
- ...encrypted.map((f) => ({ field_key: f.key, value: draft.values[f.key] ?? "", handling: f.handling })),
9312
+ ...encrypted.map((f) => declaredFieldIn(f, draft.values[f.key] ?? "", draft.fieldSettings?.[f.key])),
9089
9313
  ...draft.secrets.filter((s) => s.label.trim() !== "" && s.value !== "").map(secretFieldIn)
9090
9314
  ]
9091
9315
  }
@@ -9214,7 +9438,7 @@ async function readNativeImportLimits(api, organizationId) {
9214
9438
  var recoveryOf = (w) => w.lifecycle?.vault_recovery;
9215
9439
  var isDeleted = (w) => recoveryOf(w)?.state === "deleted";
9216
9440
  var MATCH_MODES2 = ["host", "domain", "exact", "never"];
9217
- var FIELD_WORDS = { totp_seed: "One-time password key", authenticator_last_time_step: "Last code used" };
9441
+ var FIELD_WORDS = { secure_notes: "Secure note", totp_seed: "One-time password key", authenticator_last_time_step: "Last code used" };
9218
9442
  var handlingOf = (v) => v === "visible" || v === "sealed" ? v : "revealable";
9219
9443
  function fieldOf(w, def) {
9220
9444
  const declared = def?.fields.find((f) => f.key === w.field_key);
@@ -9223,7 +9447,8 @@ function fieldOf(w, def) {
9223
9447
  id: w.id,
9224
9448
  key: w.field_key,
9225
9449
  label: declared?.label ?? FIELD_WORDS[w.field_key] ?? humanize(w.field_key),
9226
- kind: declared?.kind ?? (handling === "visible" ? "text" : "secret"),
9450
+ // A field no type declares still edits as several lines when its key says so (an old "Secure note").
9451
+ kind: declared?.kind ?? (isMultilineKey(w.field_key) ? "multiline" : handling === "visible" ? "text" : "secret"),
9227
9452
  handling,
9228
9453
  editable: w.editable ?? true,
9229
9454
  envKey: w.env_key ?? null,
@@ -9378,7 +9603,7 @@ function createApiVaultPort(api, options = {}) {
9378
9603
  initiation: "user",
9379
9604
  non_secret_fields: [...meta.map((f) => ({ key: f.key, label: f.label, value: draft.values[f.key] ?? "" })), ...draft.custom.filter((c) => c.label.trim() !== "").map((c) => ({ key: c.key || fieldKeyOf(c.label), label: c.label, value: c.value }))],
9380
9605
  fields: [
9381
- ...encrypted.map((f) => ({ field_key: f.key, value: draft.values[f.key] ?? "", handling: f.handling })),
9606
+ ...encrypted.map((f) => declaredFieldIn(f, draft.values[f.key] ?? "", draft.fieldSettings?.[f.key])),
9382
9607
  ...draft.secrets.filter((s) => s.label.trim() !== "" && s.value !== "").map(secretFieldIn),
9383
9608
  // Backup codes on a type that does not declare them (a website login): their own encrypted field.
9384
9609
  ...!declared.some((f) => f.key === RECOVERY_CODES_KEY) && (draft.values[RECOVERY_CODES_KEY] ?? "").trim() !== "" ? [{ field_key: RECOVERY_CODES_KEY, value: parseRecoveryCodes(draft.values[RECOVERY_CODES_KEY] ?? "").join("\n"), handling: "revealable", description: "One-time account recovery codes" }] : []