@ai-matrx/vault 0.1.18 → 0.1.19

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.d.cts CHANGED
@@ -116,6 +116,8 @@ interface VaultLocation {
116
116
  /** New-item flow: the chosen definition, and the URL pasted at the start (kept across steps). */
117
117
  kind: string | null;
118
118
  url: string;
119
+ /** New-item flow: a name the opener chose ("Instagram login"); beats the one guessed from the address. */
120
+ name?: string;
119
121
  /** A panel open over the workspace: `share`, `history`, `activity`, `files`, `move`, `import`, … */
120
122
  panel: string | null;
121
123
  }
@@ -1202,6 +1204,23 @@ interface VaultHostCapabilities {
1202
1204
  clipboard?: VaultClipboard;
1203
1205
  /** How long a shown value stays shown, in seconds (an organization knob the host reads); default 20. */
1204
1206
  revealSeconds?: number;
1207
+ /**
1208
+ * How often the list re-reads itself while the vault is on screen, in seconds (the desktop's
1209
+ * refresh knob). Without it the organization's `vault.list.refresh_seconds` knob decides; with
1210
+ * neither, the list still re-reads whenever the window or panel comes back into focus.
1211
+ */
1212
+ refreshSeconds?: number;
1213
+ /**
1214
+ * Who answers a right-click on a list row. `package` (default): the row's own menu (copy any
1215
+ * field, open, copy name, plus `rowMenuItems`). `host`: the host wraps the vault in its own
1216
+ * right-click menu (the website's agents menu) and the row leaves the event alone.
1217
+ */
1218
+ rowContextMenu?: "package" | "host";
1219
+ /** Extra entries a host adds to every row's menu (the More button and the right-click menu). */
1220
+ rowMenuItems?: (item: VaultItem) => readonly {
1221
+ label: string;
1222
+ onSelect: () => void;
1223
+ }[];
1205
1224
  nativeProvider?: {
1206
1225
  status: "ready" | "needs_setup" | "unavailable";
1207
1226
  openSetup: () => void;
package/dist/index.d.ts CHANGED
@@ -116,6 +116,8 @@ interface VaultLocation {
116
116
  /** New-item flow: the chosen definition, and the URL pasted at the start (kept across steps). */
117
117
  kind: string | null;
118
118
  url: string;
119
+ /** New-item flow: a name the opener chose ("Instagram login"); beats the one guessed from the address. */
120
+ name?: string;
119
121
  /** A panel open over the workspace: `share`, `history`, `activity`, `files`, `move`, `import`, … */
120
122
  panel: string | null;
121
123
  }
@@ -1202,6 +1204,23 @@ interface VaultHostCapabilities {
1202
1204
  clipboard?: VaultClipboard;
1203
1205
  /** How long a shown value stays shown, in seconds (an organization knob the host reads); default 20. */
1204
1206
  revealSeconds?: number;
1207
+ /**
1208
+ * How often the list re-reads itself while the vault is on screen, in seconds (the desktop's
1209
+ * refresh knob). Without it the organization's `vault.list.refresh_seconds` knob decides; with
1210
+ * neither, the list still re-reads whenever the window or panel comes back into focus.
1211
+ */
1212
+ refreshSeconds?: number;
1213
+ /**
1214
+ * Who answers a right-click on a list row. `package` (default): the row's own menu (copy any
1215
+ * field, open, copy name, plus `rowMenuItems`). `host`: the host wraps the vault in its own
1216
+ * right-click menu (the website's agents menu) and the row leaves the event alone.
1217
+ */
1218
+ rowContextMenu?: "package" | "host";
1219
+ /** Extra entries a host adds to every row's menu (the More button and the right-click menu). */
1220
+ rowMenuItems?: (item: VaultItem) => readonly {
1221
+ label: string;
1222
+ onSelect: () => void;
1223
+ }[];
1205
1224
  nativeProvider?: {
1206
1225
  status: "ready" | "needs_setup" | "unavailable";
1207
1226
  openSetup: () => void;
package/dist/index.js CHANGED
@@ -860,6 +860,7 @@ function locationFromParams(params, itemId = params.get("item")) {
860
860
  mode: mode === "edit" || mode === "new" ? mode : "view",
861
861
  kind: params.get("kind"),
862
862
  url: params.get("url") ?? "",
863
+ ...params.get("name") ? { name: params.get("name") ?? "" } : {},
863
864
  panel: params.get("panel")
864
865
  };
865
866
  }
@@ -872,6 +873,7 @@ function locationToParams(loc) {
872
873
  if (loc.mode !== "view") q.set("mode", loc.mode);
873
874
  if (loc.kind) q.set("kind", loc.kind);
874
875
  if (loc.url) q.set("url", loc.url);
876
+ if (loc.name) q.set("name", loc.name);
875
877
  if (loc.panel) q.set("panel", loc.panel);
876
878
  return q;
877
879
  }
@@ -1912,10 +1914,16 @@ function QrScanPanel({ open, onResult, onClose }) {
1912
1914
  /* @__PURE__ */ jsx10("p", { className: "text-center text-xs text-muted-foreground", children: "Point at the site's QR code" })
1913
1915
  ] });
1914
1916
  }
1915
- function OtpSetupRow({ value, onChange, label = "Authenticator", placeholder = "Paste setup key or otpauth link", disabled = false, autoFocus = false, onImage, onRecognized, status }) {
1917
+ function OtpSetupRow({ value, onChange, label = "Authenticator", placeholder = "Paste setup key or otpauth link", disabled = false, autoFocus = false, onImage, onRecognized, status, autoStartCamera = false }) {
1916
1918
  const file = useRef8(null);
1917
1919
  const canScan = useCanScan();
1918
1920
  const [scanning, setScanning] = useState10(false);
1921
+ const autoStarted = useRef8(false);
1922
+ useEffect10(() => {
1923
+ if (!autoStartCamera || !canScan || disabled || autoStarted.current || value) return;
1924
+ autoStarted.current = true;
1925
+ setScanning(true);
1926
+ }, [autoStartCamera, canScan, disabled, value]);
1919
1927
  const [reading, setReading] = useState10(false);
1920
1928
  const [imageError, setImageError] = useState10(null);
1921
1929
  const [dragging, setDragging] = useState10(false);
@@ -2300,6 +2308,7 @@ function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChang
2300
2308
  label: "Setup key",
2301
2309
  value: input,
2302
2310
  autoFocus: true,
2311
+ autoStartCamera: true,
2303
2312
  disabled: busy,
2304
2313
  onChange: (v) => {
2305
2314
  setImage(null);
@@ -3852,7 +3861,7 @@ var MAX_FAILURES_IN_A_ROW = 5;
3852
3861
  var failureText = (cause) => cause instanceof Error && cause.message ? cause.message : "The server refused this one.";
3853
3862
  async function runImport(input) {
3854
3863
  const { io, organizationId, lines, keys, confirmed } = input;
3855
- const result = { created: [], failed: [], withOtp: 0, stoppedEarly: false, untried: [] };
3864
+ const result = { created: [], failed: [], withOtp: 0, needOtp: [], stoppedEarly: false, untried: [] };
3856
3865
  let streak = 0;
3857
3866
  for (let i = 0; i < lines.length; i += 1) {
3858
3867
  const line = lines[i];
@@ -3871,7 +3880,10 @@ async function runImport(input) {
3871
3880
  try {
3872
3881
  const made = await confirmed(() => io.createImported(command.body, organizationId, key));
3873
3882
  result.created.push(made);
3874
- if (command.hasOtp) result.withOtp += 1;
3883
+ if (command.hasOtp) {
3884
+ result.withOtp += 1;
3885
+ result.needOtp.push(made);
3886
+ }
3875
3887
  streak = 0;
3876
3888
  } catch (cause) {
3877
3889
  const reason = failureText(cause);
@@ -4471,8 +4483,9 @@ var STATE_BADGE = {
4471
4483
  unsupported: { text: "Not supported", tone: "neutral" }
4472
4484
  };
4473
4485
  var ME = "me";
4474
- function ImportPanel({ port, host, items, organizations, onClose, onChanged, limits }) {
4486
+ function ImportPanel({ port, host, items, definitions, organizations, onClose, onChanged, limits }) {
4475
4487
  const confirmed = useConfirmed();
4488
+ const [otpFor, setOtpFor] = useState19(null);
4476
4489
  const fileInput = useRef12(null);
4477
4490
  const abort = useRef12(null);
4478
4491
  const stop = useRef12(false);
@@ -4580,6 +4593,7 @@ function ImportPanel({ port, host, items, organizations, onClose, onChanged, lim
4580
4593
  failed: result.failed,
4581
4594
  skipped,
4582
4595
  withOtp: (previous?.withOtp ?? 0) + result.withOtp,
4596
+ needOtp: [...previous?.needOtp ?? [], ...result.needOtp],
4583
4597
  untried: result.untried
4584
4598
  };
4585
4599
  setDone(merged);
@@ -4605,7 +4619,7 @@ function ImportPanel({ port, host, items, organizations, onClose, onChanged, lim
4605
4619
  setRunning({ done: 0, total: chosen });
4606
4620
  try {
4607
4621
  const made = await confirmed(() => port.io.importEnv(text, organizationId, injectSandbox));
4608
- setDone({ created: made, failed: [], skipped: draft.entries.length - chosen, withOtp: 0, untried: [] });
4622
+ setDone({ created: made, failed: [], skipped: draft.entries.length - chosen, withOtp: 0, needOtp: [], untried: [] });
4609
4623
  if (made.length > 0) onChanged();
4610
4624
  } catch (cause) {
4611
4625
  setError(cause instanceof Error ? cause.message : "The import could not finish.");
@@ -4720,9 +4734,32 @@ function ImportPanel({ port, host, items, organizations, onClose, onChanged, lim
4720
4734
  plural(done.withOtp, "login"),
4721
4735
  " still need the one-time password added."
4722
4736
  ] }) }) : null,
4737
+ done.needOtp.map((made) => {
4738
+ const now = items.find((i) => i.id === made.id);
4739
+ if (now?.authenticator) return null;
4740
+ 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: [
4741
+ "Set up authenticator for ",
4742
+ made.name
4743
+ ] }) }, `otp:${made.id}`);
4744
+ }),
4723
4745
  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
4746
  ] }) : null,
4725
- /* @__PURE__ */ jsx19(ErrorLine, { message: error })
4747
+ /* @__PURE__ */ jsx19(ErrorLine, { message: error }),
4748
+ otpFor ? /* @__PURE__ */ jsx19(
4749
+ AddOtpPanel,
4750
+ {
4751
+ port,
4752
+ items,
4753
+ definitions,
4754
+ itemId: otpFor,
4755
+ onClose: () => setOtpFor(null),
4756
+ onChanged,
4757
+ onDone: () => {
4758
+ setOtpFor(null);
4759
+ onChanged();
4760
+ }
4761
+ }
4762
+ ) : null
4726
4763
  ] });
4727
4764
  }
4728
4765
  function toggle(set, key, on) {
@@ -6344,6 +6381,13 @@ function ItemPane(props) {
6344
6381
 
6345
6382
  // src/ItemList.tsx
6346
6383
  import { jsx as jsx28, jsxs as jsxs26 } from "react/jsx-runtime";
6384
+ var RECOVERY_KEYS = /^recovery_codes?$/;
6385
+ function otherCopyFields(item, user, secret) {
6386
+ if (!item.capabilities.canUse) return [];
6387
+ return item.fields.filter(
6388
+ (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)
6389
+ );
6390
+ }
6347
6391
  var SORTS2 = [
6348
6392
  { value: "name", label: "Name A\u2013Z" },
6349
6393
  { value: "name-desc", label: "Name Z\u2013A" },
@@ -6356,7 +6400,7 @@ function subtitleOf(item, withOwner) {
6356
6400
  const what = loginSiteOf(item) || item.definitionLabel;
6357
6401
  return withOwner ? `${what} \xB7 ${item.owner.label}` : what;
6358
6402
  }
6359
- function ItemList({ title, items, definitions, port, selectedId, onSelect, query, onQuery, sort, onSort, onNew, matchingIds, header, showOwner, canSortViewed = false }) {
6403
+ 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
6404
  const sorts = SORTS2.filter((s) => canSortViewed || s.value !== "viewed");
6361
6405
  const pinned = matchingIds ? items.filter((i) => matchingIds.has(i.id)) : [];
6362
6406
  const rest = matchingIds ? items.filter((i) => !matchingIds.has(i.id)) : items;
@@ -6377,7 +6421,7 @@ function ItemList({ title, items, definitions, port, selectedId, onSelect, query
6377
6421
  }
6378
6422
  };
6379
6423
  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);
6424
+ 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
6425
  const sortLabel = sorts.find((s) => s.value === sort)?.label ?? "Name A\u2013Z";
6382
6426
  return /* @__PURE__ */ jsxs26("section", { "aria-label": title, className: "flex h-full min-h-0 flex-col border-r border-border bg-background", children: [
6383
6427
  /* @__PURE__ */ jsxs26("div", { className: "flex flex-col gap-2 px-3 pb-2 pt-3", children: [
@@ -6392,9 +6436,12 @@ function ItemList({ title, items, definitions, port, selectedId, onSelect, query
6392
6436
  " ",
6393
6437
  items.length === 1 ? "item" : "items"
6394
6438
  ] }),
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)) }) })
6439
+ /* @__PURE__ */ jsxs26("span", { className: "flex shrink-0 items-center gap-0.5", children: [
6440
+ 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,
6441
+ /* @__PURE__ */ jsxs26(DropdownMenu4, { children: [
6442
+ /* @__PURE__ */ jsx28(DropdownMenuTrigger4, { asChild: true, children: /* @__PURE__ */ jsx28(Button23, { variant: "quiet", icon: /* @__PURE__ */ jsx28(SortGlyph, {}), "aria-label": `Sort: ${sortLabel}`, children: sortLabel }) }),
6443
+ /* @__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)) }) })
6444
+ ] })
6398
6445
  ] })
6399
6446
  ] })
6400
6447
  ] }),
@@ -6409,7 +6456,7 @@ function ItemList({ title, items, definitions, port, selectedId, onSelect, query
6409
6456
  ] }) })
6410
6457
  ] });
6411
6458
  }
6412
- function ListRow({ item, family, port, on, onSelect, withOwner }) {
6459
+ function ListRow({ item, family, port, on, onSelect, withOwner, ownContextMenu, extra }) {
6413
6460
  const confirmed = useConfirmed();
6414
6461
  const copy = useCopy();
6415
6462
  const notify = useNotify();
@@ -6434,67 +6481,93 @@ function ListRow({ item, family, port, on, onSelect, withOwner }) {
6434
6481
  const copyPassword = () => secret && void run("Password", () => port.reveal(item, secret.key), true);
6435
6482
  const copyCode = () => code && void run("One-time code", async () => (await port.authenticator.code(item)).code, true);
6436
6483
  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,
6484
+ const others = otherCopyFields(item, user, secret);
6485
+ const custom = canUse ? item.custom.filter((c) => c.value) : [];
6486
+ const hostEntries = extra?.(item) ?? [];
6487
+ return /* @__PURE__ */ jsxs26(
6488
+ "li",
6489
+ {
6490
+ className: "group relative",
6491
+ "data-item": item.id,
6492
+ onContextMenu: ownContextMenu ? (e) => {
6493
+ e.preventDefault();
6494
+ setMenuOpen(true);
6495
+ } : void 0,
6496
+ children: [
6497
+ /* @__PURE__ */ jsxs26(
6498
+ "button",
6463
6499
  {
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")
6500
+ type: "button",
6501
+ "aria-current": on ? "true" : void 0,
6502
+ "data-surface": on ? "primary" : void 0,
6503
+ onClick: () => onSelect(item.id),
6504
+ 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"),
6505
+ children: [
6506
+ /* @__PURE__ */ jsx28(ItemIcon, { definitionKey: item.definitionKey, family, site: loginSiteOf(item), inverted: on }),
6507
+ /* @__PURE__ */ jsxs26("span", { className: "flex min-w-0 flex-1 flex-col leading-tight", children: [
6508
+ /* @__PURE__ */ jsx28("span", { className: "truncate text-sm font-medium", children: item.name }),
6509
+ /* @__PURE__ */ jsx28("span", { className: "truncate text-xs text-muted-foreground", children: subtitleOf(item, withOwner) })
6510
+ ] })
6511
+ ]
6469
6512
  }
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"
6513
+ ),
6514
+ /* @__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: [
6515
+ 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: [
6516
+ user ? /* @__PURE__ */ jsx28(Button23, { variant: "quiet", icon: /* @__PURE__ */ jsx28(UserGlyph, {}), "aria-label": `Copy ${user.label}`, disabled: busy, onClick: copyUser }) : null,
6517
+ secret ? /* @__PURE__ */ jsx28(Button23, { variant: "quiet", icon: /* @__PURE__ */ jsx28(KeyGlyph, {}), "aria-label": "Copy password", disabled: busy, onClick: copyPassword }) : null
6484
6518
  ] }) : 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"
6519
+ /* @__PURE__ */ jsxs26(DropdownMenu4, { open: menuOpen, onOpenChange: setMenuOpen, children: [
6520
+ /* @__PURE__ */ jsx28(DropdownMenuTrigger4, { asChild: true, children: /* @__PURE__ */ jsx28(
6521
+ Button23,
6522
+ {
6523
+ variant: "quiet",
6524
+ icon: /* @__PURE__ */ jsx28(MoreGlyph, {}),
6525
+ "aria-label": `Actions for ${item.name}`,
6526
+ disabled: busy,
6527
+ className: cn13("[@media(hover:hover)]:invisible [@media(hover:hover)]:group-hover:visible [@media(hover:hover)]:group-focus-within:visible", menuOpen && "[@media(hover:hover)]:visible")
6528
+ }
6529
+ ) }),
6530
+ /* @__PURE__ */ jsxs26(DropdownMenuContent4, { align: "end", children: [
6531
+ user ? /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: copyUser, children: [
6532
+ /* @__PURE__ */ jsx28(UserGlyph, { className: "size-4" }),
6533
+ "Copy ",
6534
+ user.label.toLowerCase()
6535
+ ] }) : null,
6536
+ secret ? /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: copyPassword, children: [
6537
+ /* @__PURE__ */ jsx28(KeyGlyph, { className: "size-4" }),
6538
+ "Copy password"
6539
+ ] }) : null,
6540
+ code ? /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: copyCode, children: [
6541
+ /* @__PURE__ */ jsx28(ClockGlyph, { className: "size-4" }),
6542
+ "Copy one-time code"
6543
+ ] }) : null,
6544
+ others.map((f) => /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: () => void run(f.label, () => port.reveal(item, f.key), f.handling !== "visible"), children: [
6545
+ /* @__PURE__ */ jsx28(CopyIcon7, { className: "size-4" }),
6546
+ "Copy ",
6547
+ f.label.toLowerCase()
6548
+ ] }, f.key)),
6549
+ custom.map((c) => /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: () => void copy(c.value, c.label, { secret: false }), children: [
6550
+ /* @__PURE__ */ jsx28(CopyIcon7, { className: "size-4" }),
6551
+ "Copy ",
6552
+ c.label.toLowerCase()
6553
+ ] }, `custom:${c.key}`)),
6554
+ user || secret || code || others.length > 0 || custom.length > 0 ? /* @__PURE__ */ jsx28(DropdownMenuSeparator4, {}) : null,
6555
+ /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: () => onSelect(item.id), children: [
6556
+ /* @__PURE__ */ jsx28(ChevronRightGlyph, { className: "size-4" }),
6557
+ "Open"
6558
+ ] }),
6559
+ /* @__PURE__ */ jsxs26(DropdownMenuItem4, { onSelect: copyName, children: [
6560
+ /* @__PURE__ */ jsx28(CopyIcon7, { className: "size-4" }),
6561
+ "Copy name"
6562
+ ] }),
6563
+ hostEntries.length > 0 ? /* @__PURE__ */ jsx28(DropdownMenuSeparator4, {}) : null,
6564
+ hostEntries.map((h) => /* @__PURE__ */ jsx28(DropdownMenuItem4, { onSelect: h.onSelect, children: h.label }, h.label))
6565
+ ] })
6493
6566
  ] })
6494
6567
  ] })
6495
- ] })
6496
- ] })
6497
- ] });
6568
+ ]
6569
+ }
6570
+ );
6498
6571
  }
6499
6572
 
6500
6573
  // src/lists/TrashView.tsx
@@ -8121,6 +8194,7 @@ import { jsx as jsx40, jsxs as jsxs38 } from "react/jsx-runtime";
8121
8194
  var FULL_AT = 1120;
8122
8195
  var SPLIT_AT = 640;
8123
8196
  var NO_HOST = {};
8197
+ var FOCUS_GAP_MS = 2e3;
8124
8198
  var ITEM_PANELS = {
8125
8199
  share: SharePanel,
8126
8200
  move: MovePanel,
@@ -8249,9 +8323,13 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8249
8323
  const knobsRef = useRef19(host.knobs);
8250
8324
  knobsRef.current = host.knobs;
8251
8325
  const [signedOut, setSignedOut] = useState41(false);
8252
- const load = useCallback5(() => {
8253
- setError(null);
8254
- port.load().then((s) => {
8326
+ const [refreshError, setRefreshError] = useState41(null);
8327
+ const [refreshing, setRefreshing] = useState41(false);
8328
+ const loadedAt = useRef19(0);
8329
+ const inFlight = useRef19(false);
8330
+ const apply = useCallback5(
8331
+ (s) => {
8332
+ loadedAt.current = Date.now();
8255
8333
  setSnap(s);
8256
8334
  setItems(s.items);
8257
8335
  setDeleted(s.deleted);
@@ -8259,15 +8337,71 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8259
8337
  (rows) => setDeleted([...s.deleted, ...rows.filter((r) => !s.deleted.some((d) => d.id === r.id))]),
8260
8338
  () => void 0
8261
8339
  );
8262
- }).catch((e) => {
8340
+ },
8341
+ [port]
8342
+ );
8343
+ const refresh = useCallback5(() => {
8344
+ if (inFlight.current) return;
8345
+ inFlight.current = true;
8346
+ setRefreshing(true);
8347
+ port.load().then((s) => {
8348
+ setRefreshError(null);
8349
+ apply(s);
8350
+ }).catch((e) => setRefreshError(plainLoadError(e instanceof Error && e.message ? e.message : "The vault could not be read."))).finally(() => {
8351
+ inFlight.current = false;
8352
+ setRefreshing(false);
8353
+ });
8354
+ port.lists.favorites?.list().then((ids) => setFavorites(new Set(ids)), () => void 0);
8355
+ port.lists.recents?.list().then(setRecents, () => void 0);
8356
+ }, [port, apply]);
8357
+ const load = useCallback5(() => {
8358
+ setError(null);
8359
+ setRefreshError(null);
8360
+ port.load().then(apply).catch((e) => {
8263
8361
  setSignedOut(e?.status === 401);
8264
8362
  setError(e instanceof Error ? e.message : "The vault could not be loaded.");
8265
8363
  });
8266
8364
  port.lists.favorites?.list().then((ids) => setFavorites(new Set(ids)), () => void 0);
8267
8365
  knobsRef.current?.().catch(() => void 0);
8268
8366
  port.lists.recents?.list().then(setRecents, () => void 0);
8269
- }, [port]);
8367
+ }, [port, apply]);
8270
8368
  useEffect23(load, [load]);
8369
+ const quietRef = useRef19({ loc, ready: false });
8370
+ quietRef.current = { loc, ready: snap !== null && !error };
8371
+ const [knobSeconds, setKnobSeconds] = useState41(null);
8372
+ useEffect23(() => {
8373
+ if (host.refreshSeconds !== void 0 || !knobsRef.current) return;
8374
+ let live = true;
8375
+ knobsRef.current().then(
8376
+ (r) => {
8377
+ const v = r["vault.list.refresh_seconds"];
8378
+ if (live && typeof v === "number" && v > 0) setKnobSeconds(v);
8379
+ },
8380
+ () => void 0
8381
+ );
8382
+ return () => {
8383
+ live = false;
8384
+ };
8385
+ }, [host.refreshSeconds]);
8386
+ const everySeconds = host.refreshSeconds ?? knobSeconds;
8387
+ useEffect23(() => {
8388
+ const quietly = (minGapMs) => {
8389
+ const { loc: here, ready } = quietRef.current;
8390
+ if (!ready || document.visibilityState === "hidden" || dirty.current || here.mode !== "view") return;
8391
+ if (Date.now() - loadedAt.current < minGapMs) return;
8392
+ refresh();
8393
+ };
8394
+ const onBack = () => quietly(FOCUS_GAP_MS);
8395
+ const onVisible = () => document.visibilityState === "visible" && onBack();
8396
+ window.addEventListener("focus", onBack);
8397
+ document.addEventListener("visibilitychange", onVisible);
8398
+ const timer2 = everySeconds && everySeconds > 0 ? setInterval(() => quietly(0), everySeconds * 1e3) : null;
8399
+ return () => {
8400
+ window.removeEventListener("focus", onBack);
8401
+ document.removeEventListener("visibilitychange", onVisible);
8402
+ if (timer2) clearInterval(timer2);
8403
+ };
8404
+ }, [refresh, everySeconds]);
8271
8405
  const inventoryRef = useRef19(onInventory);
8272
8406
  inventoryRef.current = onInventory;
8273
8407
  useEffect23(() => {
@@ -8433,6 +8567,10 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8433
8567
  onSort: (sort) => go({ sort }, "replace"),
8434
8568
  canSortViewed: port.lists.recents !== void 0,
8435
8569
  onNew: () => go({ itemId: null, mode: "new", kind: null, panel: null }),
8570
+ onRefresh: refresh,
8571
+ refreshing,
8572
+ rowContextMenu: host.rowContextMenu ?? "package",
8573
+ ...host.rowMenuItems ? { rowMenuItems: host.rowMenuItems } : {},
8436
8574
  ...matchingIds ? { matchingIds } : {},
8437
8575
  ...compactHeader ? { header: compactHeader } : {}
8438
8576
  }
@@ -8457,14 +8595,14 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8457
8595
  host,
8458
8596
  organizations: orgs,
8459
8597
  defaultOwner: ownerDefault,
8460
- seed: { name: guess?.name ?? "", url: guess?.url ?? loc.url.trim() },
8598
+ seed: { name: loc.name?.trim() || guess?.name || "", url: guess?.url ?? loc.url.trim() },
8461
8599
  onBack: () => go({ kind: null }),
8462
8600
  onMode: () => void 0,
8463
8601
  onSaved: (item, _wasNew, n, enrolled) => {
8464
8602
  replaceItem(item);
8465
8603
  dirty.current = false;
8466
8604
  if (enrolled) setFirstCode(item);
8467
- move({ ...loc, itemId: item.id, mode: "view", kind: null, url: "", panel: null }, "replace");
8605
+ move({ ...loc, itemId: item.id, mode: "view", kind: null, url: "", name: "", panel: null }, "replace");
8468
8606
  setNotice(n);
8469
8607
  notify("Saved");
8470
8608
  },
@@ -8472,7 +8610,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8472
8610
  onDirty: (d) => {
8473
8611
  dirty.current = d;
8474
8612
  },
8475
- onCancelNew: () => go({ mode: "view", kind: null, url: "", itemId: null }),
8613
+ onCancelNew: () => go({ mode: "view", kind: null, url: "", name: "", itemId: null }),
8476
8614
  onPanel: () => void 0,
8477
8615
  onChanged: () => void 0
8478
8616
  },
@@ -8563,13 +8701,17 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8563
8701
  const grid = mode === "full" ? "grid-cols-[15rem_21rem_minmax(0,1fr)]" : mode === "split" ? "grid-cols-[19rem_minmax(0,1fr)]" : "grid-cols-1";
8564
8702
  const shown = panesShown(mode, specialView !== null, showingDetail);
8565
8703
  return /* @__PURE__ */ jsxs38("div", { ref: rootRef, className: "flex h-full min-h-0 w-full flex-col bg-background text-foreground", children: [
8704
+ 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: [
8705
+ /* @__PURE__ */ jsx40("span", { className: "min-w-0 flex-1 truncate text-warning", title: refreshError, children: "Couldn\u2019t refresh. Showing what was loaded." }),
8706
+ /* @__PURE__ */ jsx40("button", { type: "button", className: "shrink-0 text-primary hover:underline", onClick: refresh, children: "Try again" })
8707
+ ] }) : null,
8566
8708
  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
8709
  /* @__PURE__ */ jsxs38("span", { className: "min-w-0 flex-1 truncate text-warning", children: [
8568
8710
  "Couldn\u2019t load ",
8569
8711
  snap.unavailable.join(", "),
8570
8712
  ". Showing the rest."
8571
8713
  ] }),
8572
- /* @__PURE__ */ jsx40("button", { type: "button", className: "shrink-0 text-primary hover:underline", onClick: load, children: "Try again" })
8714
+ /* @__PURE__ */ jsx40("button", { type: "button", className: "shrink-0 text-primary hover:underline", onClick: refresh, children: "Try again" })
8573
8715
  ] }) : null,
8574
8716
  /* @__PURE__ */ jsxs38("div", { className: `grid min-h-0 w-full flex-1 ${grid} overflow-hidden`, children: [
8575
8717
  mode === "full" ? /* @__PURE__ */ jsx40(