@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/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.19
4
+
5
+ UNVERIFIED: this release has no Unreleased entry and the source comparison could not be made (the caller supplied no source comparison). Whether source changed is unknown; consumer action is unknown.
6
+ Check the package's gitHead on npm against this version's gitHead: `git diff <previous gitHead> <this gitHead> -- apps/shared/vault/src`.
7
+
3
8
  ## 0.1.18
4
9
 
5
10
  - The Authenticator list keeps the code and its countdown ring apart at every width (the code column never squeezes), the list header lines up with the list on wide screens, and the Add authenticator panel's camera button is icon-only so the setup-key box has room on a phone. Consumer action: none.
package/dist/index.cjs CHANGED
@@ -929,6 +929,7 @@ function locationFromParams(params, itemId = params.get("item")) {
929
929
  mode: mode === "edit" || mode === "new" ? mode : "view",
930
930
  kind: params.get("kind"),
931
931
  url: params.get("url") ?? "",
932
+ ...params.get("name") ? { name: params.get("name") ?? "" } : {},
932
933
  panel: params.get("panel")
933
934
  };
934
935
  }
@@ -941,6 +942,7 @@ function locationToParams(loc) {
941
942
  if (loc.mode !== "view") q.set("mode", loc.mode);
942
943
  if (loc.kind) q.set("kind", loc.kind);
943
944
  if (loc.url) q.set("url", loc.url);
945
+ if (loc.name) q.set("name", loc.name);
944
946
  if (loc.panel) q.set("panel", loc.panel);
945
947
  return q;
946
948
  }
@@ -1981,10 +1983,16 @@ function QrScanPanel({ open, onResult, onClose }) {
1981
1983
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-center text-xs text-muted-foreground", children: "Point at the site's QR code" })
1982
1984
  ] });
1983
1985
  }
1984
- function OtpSetupRow({ value, onChange, label = "Authenticator", placeholder = "Paste setup key or otpauth link", disabled = false, autoFocus = false, onImage, onRecognized, status }) {
1986
+ function OtpSetupRow({ value, onChange, label = "Authenticator", placeholder = "Paste setup key or otpauth link", disabled = false, autoFocus = false, onImage, onRecognized, status, autoStartCamera = false }) {
1985
1987
  const file = (0, import_react10.useRef)(null);
1986
1988
  const canScan = useCanScan();
1987
1989
  const [scanning, setScanning] = (0, import_react10.useState)(false);
1990
+ const autoStarted = (0, import_react10.useRef)(false);
1991
+ (0, import_react10.useEffect)(() => {
1992
+ if (!autoStartCamera || !canScan || disabled || autoStarted.current || value) return;
1993
+ autoStarted.current = true;
1994
+ setScanning(true);
1995
+ }, [autoStartCamera, canScan, disabled, value]);
1988
1996
  const [reading, setReading] = (0, import_react10.useState)(false);
1989
1997
  const [imageError, setImageError] = (0, import_react10.useState)(null);
1990
1998
  const [dragging, setDragging] = (0, import_react10.useState)(false);
@@ -2369,6 +2377,7 @@ function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChang
2369
2377
  label: "Setup key",
2370
2378
  value: input,
2371
2379
  autoFocus: true,
2380
+ autoStartCamera: true,
2372
2381
  disabled: busy,
2373
2382
  onChange: (v) => {
2374
2383
  setImage(null);
@@ -3918,7 +3927,7 @@ var MAX_FAILURES_IN_A_ROW = 5;
3918
3927
  var failureText = (cause) => cause instanceof Error && cause.message ? cause.message : "The server refused this one.";
3919
3928
  async function runImport(input) {
3920
3929
  const { io, organizationId, lines, keys, confirmed } = input;
3921
- const result = { created: [], failed: [], withOtp: 0, stoppedEarly: false, untried: [] };
3930
+ const result = { created: [], failed: [], withOtp: 0, needOtp: [], stoppedEarly: false, untried: [] };
3922
3931
  let streak = 0;
3923
3932
  for (let i = 0; i < lines.length; i += 1) {
3924
3933
  const line = lines[i];
@@ -3937,7 +3946,10 @@ async function runImport(input) {
3937
3946
  try {
3938
3947
  const made = await confirmed(() => io.createImported(command.body, organizationId, key));
3939
3948
  result.created.push(made);
3940
- if (command.hasOtp) result.withOtp += 1;
3949
+ if (command.hasOtp) {
3950
+ result.withOtp += 1;
3951
+ result.needOtp.push(made);
3952
+ }
3941
3953
  streak = 0;
3942
3954
  } catch (cause) {
3943
3955
  const reason = failureText(cause);
@@ -4537,8 +4549,9 @@ var STATE_BADGE = {
4537
4549
  unsupported: { text: "Not supported", tone: "neutral" }
4538
4550
  };
4539
4551
  var ME = "me";
4540
- function ImportPanel({ port, host, items, organizations, onClose, onChanged, limits }) {
4552
+ function ImportPanel({ port, host, items, definitions, organizations, onClose, onChanged, limits }) {
4541
4553
  const confirmed = useConfirmed();
4554
+ const [otpFor, setOtpFor] = (0, import_react20.useState)(null);
4542
4555
  const fileInput = (0, import_react20.useRef)(null);
4543
4556
  const abort = (0, import_react20.useRef)(null);
4544
4557
  const stop = (0, import_react20.useRef)(false);
@@ -4646,6 +4659,7 @@ function ImportPanel({ port, host, items, organizations, onClose, onChanged, lim
4646
4659
  failed: result.failed,
4647
4660
  skipped,
4648
4661
  withOtp: (previous?.withOtp ?? 0) + result.withOtp,
4662
+ needOtp: [...previous?.needOtp ?? [], ...result.needOtp],
4649
4663
  untried: result.untried
4650
4664
  };
4651
4665
  setDone(merged);
@@ -4671,7 +4685,7 @@ function ImportPanel({ port, host, items, organizations, onClose, onChanged, lim
4671
4685
  setRunning({ done: 0, total: chosen });
4672
4686
  try {
4673
4687
  const made = await confirmed(() => port.io.importEnv(text, organizationId, injectSandbox));
4674
- setDone({ created: made, failed: [], skipped: draft.entries.length - chosen, withOtp: 0, untried: [] });
4688
+ setDone({ created: made, failed: [], skipped: draft.entries.length - chosen, withOtp: 0, needOtp: [], untried: [] });
4675
4689
  if (made.length > 0) onChanged();
4676
4690
  } catch (cause) {
4677
4691
  setError(cause instanceof Error ? cause.message : "The import could not finish.");
@@ -4786,9 +4800,32 @@ function ImportPanel({ port, host, items, organizations, onClose, onChanged, lim
4786
4800
  plural(done.withOtp, "login"),
4787
4801
  " still need the one-time password added."
4788
4802
  ] }) }) : null,
4803
+ done.needOtp.map((made) => {
4804
+ const now = items.find((i) => i.id === made.id);
4805
+ if (now?.authenticator) return null;
4806
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(VaultRow, { label: "Authenticator", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("button", { type: "button", className: "truncate text-left text-primary hover:underline", onClick: () => setOtpFor(made.id), children: [
4807
+ "Set up authenticator for ",
4808
+ made.name
4809
+ ] }) }, `otp:${made.id}`);
4810
+ }),
4789
4811
  done.failed.map((f) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(VaultRow, { label: f.title, children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "block truncate text-destructive", title: f.reason, children: f.reason }) }, f.key))
4790
4812
  ] }) : null,
4791
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ErrorLine, { message: error })
4813
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ErrorLine, { message: error }),
4814
+ otpFor ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
4815
+ AddOtpPanel,
4816
+ {
4817
+ port,
4818
+ items,
4819
+ definitions,
4820
+ itemId: otpFor,
4821
+ onClose: () => setOtpFor(null),
4822
+ onChanged,
4823
+ onDone: () => {
4824
+ setOtpFor(null);
4825
+ onChanged();
4826
+ }
4827
+ }
4828
+ ) : null
4792
4829
  ] });
4793
4830
  }
4794
4831
  function toggle(set, key, on) {
@@ -6410,6 +6447,13 @@ function ItemPane(props) {
6410
6447
 
6411
6448
  // src/ItemList.tsx
6412
6449
  var import_jsx_runtime28 = require("react/jsx-runtime");
6450
+ var RECOVERY_KEYS = /^recovery_codes?$/;
6451
+ function otherCopyFields(item, user, secret) {
6452
+ if (!item.capabilities.canUse) return [];
6453
+ return item.fields.filter(
6454
+ (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)
6455
+ );
6456
+ }
6413
6457
  var SORTS2 = [
6414
6458
  { value: "name", label: "Name A\u2013Z" },
6415
6459
  { value: "name-desc", label: "Name Z\u2013A" },
@@ -6422,7 +6466,7 @@ function subtitleOf(item, withOwner) {
6422
6466
  const what = loginSiteOf(item) || item.definitionLabel;
6423
6467
  return withOwner ? `${what} \xB7 ${item.owner.label}` : what;
6424
6468
  }
6425
- function ItemList({ title, items, definitions, port, selectedId, onSelect, query, onQuery, sort, onSort, onNew, matchingIds, header, showOwner, canSortViewed = false }) {
6469
+ function ItemList({ title, items, definitions, port, selectedId, onSelect, query, onQuery, sort, onSort, onNew, matchingIds, header, showOwner, canSortViewed = false, onRefresh, refreshing = false, rowContextMenu = "package", rowMenuItems }) {
6426
6470
  const sorts = SORTS2.filter((s) => canSortViewed || s.value !== "viewed");
6427
6471
  const pinned = matchingIds ? items.filter((i) => matchingIds.has(i.id)) : [];
6428
6472
  const rest = matchingIds ? items.filter((i) => !matchingIds.has(i.id)) : items;
@@ -6443,7 +6487,7 @@ function ItemList({ title, items, definitions, port, selectedId, onSelect, query
6443
6487
  }
6444
6488
  };
6445
6489
  const mixedOwners = showOwner ?? new Set(items.map((i) => i.owner.organizationId ?? i.owner.kind)).size > 1;
6446
- const row = (i) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ListRow, { item: i, family: familyOf2(i.definitionKey), port, on: i.id === selectedId, onSelect, withOwner: mixedOwners }, i.id);
6490
+ const row = (i) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ListRow, { item: i, family: familyOf2(i.definitionKey), port, on: i.id === selectedId, onSelect, withOwner: mixedOwners, ownContextMenu: rowContextMenu === "package", extra: rowMenuItems }, i.id);
6447
6491
  const sortLabel = sorts.find((s) => s.value === sort)?.label ?? "Name A\u2013Z";
6448
6492
  return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("section", { "aria-label": title, className: "flex h-full min-h-0 flex-col border-r border-border bg-background", children: [
6449
6493
  /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex flex-col gap-2 px-3 pb-2 pt-3", children: [
@@ -6458,9 +6502,12 @@ function ItemList({ title, items, definitions, port, selectedId, onSelect, query
6458
6502
  " ",
6459
6503
  items.length === 1 ? "item" : "items"
6460
6504
  ] }),
6461
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_design_system19.DropdownMenu, { children: [
6462
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_design_system19.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_controls23.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(SortGlyph, {}), "aria-label": `Sort: ${sortLabel}`, children: sortLabel }) }),
6463
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_design_system19.DropdownMenuContent, { align: "end", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_design_system19.DropdownMenuRadioGroup, { value: sort, onValueChange: (v) => onSort(sorts.find((s) => s.value === v)?.value ?? "name"), children: sorts.map((s) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_design_system19.DropdownMenuRadioItem, { value: s.value, children: s.label }, s.value)) }) })
6505
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: "flex shrink-0 items-center gap-0.5", children: [
6506
+ onRefresh ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_controls23.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(RefreshGlyph, { className: refreshing ? "animate-spin" : "" }), "aria-label": "Refresh", title: "Refresh the list", disabled: refreshing, onClick: onRefresh }) : null,
6507
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_design_system19.DropdownMenu, { children: [
6508
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_design_system19.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_controls23.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(SortGlyph, {}), "aria-label": `Sort: ${sortLabel}`, children: sortLabel }) }),
6509
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_design_system19.DropdownMenuContent, { align: "end", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_design_system19.DropdownMenuRadioGroup, { value: sort, onValueChange: (v) => onSort(sorts.find((s) => s.value === v)?.value ?? "name"), children: sorts.map((s) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_design_system19.DropdownMenuRadioItem, { value: s.value, children: s.label }, s.value)) }) })
6510
+ ] })
6464
6511
  ] })
6465
6512
  ] })
6466
6513
  ] }),
@@ -6475,7 +6522,7 @@ function ItemList({ title, items, definitions, port, selectedId, onSelect, query
6475
6522
  ] }) })
6476
6523
  ] });
6477
6524
  }
6478
- function ListRow({ item, family, port, on, onSelect, withOwner }) {
6525
+ function ListRow({ item, family, port, on, onSelect, withOwner, ownContextMenu, extra }) {
6479
6526
  const confirmed = useConfirmed();
6480
6527
  const copy = useCopy();
6481
6528
  const notify = useNotify();
@@ -6500,67 +6547,93 @@ function ListRow({ item, family, port, on, onSelect, withOwner }) {
6500
6547
  const copyPassword = () => secret && void run("Password", () => port.reveal(item, secret.key), true);
6501
6548
  const copyCode = () => code && void run("One-time code", async () => (await port.authenticator.code(item)).code, true);
6502
6549
  const copyName = () => void copy(item.name, "Name", { secret: false });
6503
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("li", { className: "group relative", "data-item": item.id, children: [
6504
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
6505
- "button",
6506
- {
6507
- type: "button",
6508
- "aria-current": on ? "true" : void 0,
6509
- "data-surface": on ? "primary" : void 0,
6510
- onClick: () => onSelect(item.id),
6511
- className: (0, import_design_system20.cn)("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"),
6512
- children: [
6513
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ItemIcon, { definitionKey: item.definitionKey, family, site: loginSiteOf(item), inverted: on }),
6514
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: "flex min-w-0 flex-1 flex-col leading-tight", children: [
6515
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "truncate text-sm font-medium", children: item.name }),
6516
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "truncate text-xs text-muted-foreground", children: subtitleOf(item, withOwner) })
6517
- ] })
6518
- ]
6519
- }
6520
- ),
6521
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("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: [
6522
- user || secret ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: (0, import_design_system20.cn)("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: [
6523
- user ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_controls23.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(UserGlyph, {}), "aria-label": `Copy ${user.label}`, disabled: busy, onClick: copyUser }) : null,
6524
- secret ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_controls23.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(KeyGlyph, {}), "aria-label": "Copy password", disabled: busy, onClick: copyPassword }) : null
6525
- ] }) : null,
6526
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_design_system19.DropdownMenu, { open: menuOpen, onOpenChange: setMenuOpen, children: [
6527
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_design_system19.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
6528
- import_controls23.Button,
6550
+ const others = otherCopyFields(item, user, secret);
6551
+ const custom = canUse ? item.custom.filter((c) => c.value) : [];
6552
+ const hostEntries = extra?.(item) ?? [];
6553
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
6554
+ "li",
6555
+ {
6556
+ className: "group relative",
6557
+ "data-item": item.id,
6558
+ onContextMenu: ownContextMenu ? (e) => {
6559
+ e.preventDefault();
6560
+ setMenuOpen(true);
6561
+ } : void 0,
6562
+ children: [
6563
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
6564
+ "button",
6529
6565
  {
6530
- variant: "quiet",
6531
- icon: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(MoreGlyph, {}),
6532
- "aria-label": `Actions for ${item.name}`,
6533
- disabled: busy,
6534
- className: (0, import_design_system20.cn)("[@media(hover:hover)]:invisible [@media(hover:hover)]:group-hover:visible [@media(hover:hover)]:group-focus-within:visible", menuOpen && "[@media(hover:hover)]:visible")
6566
+ type: "button",
6567
+ "aria-current": on ? "true" : void 0,
6568
+ "data-surface": on ? "primary" : void 0,
6569
+ onClick: () => onSelect(item.id),
6570
+ className: (0, import_design_system20.cn)("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"),
6571
+ children: [
6572
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ItemIcon, { definitionKey: item.definitionKey, family, site: loginSiteOf(item), inverted: on }),
6573
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: "flex min-w-0 flex-1 flex-col leading-tight", children: [
6574
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "truncate text-sm font-medium", children: item.name }),
6575
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "truncate text-xs text-muted-foreground", children: subtitleOf(item, withOwner) })
6576
+ ] })
6577
+ ]
6535
6578
  }
6536
- ) }),
6537
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_design_system19.DropdownMenuContent, { align: "end", children: [
6538
- user ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_design_system19.DropdownMenuItem, { onSelect: copyUser, children: [
6539
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(UserGlyph, { className: "size-4" }),
6540
- "Copy ",
6541
- user.label.toLowerCase()
6542
- ] }) : null,
6543
- secret ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_design_system19.DropdownMenuItem, { onSelect: copyPassword, children: [
6544
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(KeyGlyph, { className: "size-4" }),
6545
- "Copy password"
6546
- ] }) : null,
6547
- code ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_design_system19.DropdownMenuItem, { onSelect: copyCode, children: [
6548
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ClockGlyph, { className: "size-4" }),
6549
- "Copy one-time code"
6579
+ ),
6580
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("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: [
6581
+ user || secret ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: (0, import_design_system20.cn)("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: [
6582
+ user ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_controls23.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(UserGlyph, {}), "aria-label": `Copy ${user.label}`, disabled: busy, onClick: copyUser }) : null,
6583
+ secret ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_controls23.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(KeyGlyph, {}), "aria-label": "Copy password", disabled: busy, onClick: copyPassword }) : null
6550
6584
  ] }) : null,
6551
- user || secret || code ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_design_system19.DropdownMenuSeparator, {}) : null,
6552
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_design_system19.DropdownMenuItem, { onSelect: () => onSelect(item.id), children: [
6553
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ChevronRightGlyph, { className: "size-4" }),
6554
- "Open"
6555
- ] }),
6556
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_design_system19.DropdownMenuItem, { onSelect: copyName, children: [
6557
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_icons12.CopyIcon, { className: "size-4" }),
6558
- "Copy name"
6585
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_design_system19.DropdownMenu, { open: menuOpen, onOpenChange: setMenuOpen, children: [
6586
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_design_system19.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
6587
+ import_controls23.Button,
6588
+ {
6589
+ variant: "quiet",
6590
+ icon: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(MoreGlyph, {}),
6591
+ "aria-label": `Actions for ${item.name}`,
6592
+ disabled: busy,
6593
+ className: (0, import_design_system20.cn)("[@media(hover:hover)]:invisible [@media(hover:hover)]:group-hover:visible [@media(hover:hover)]:group-focus-within:visible", menuOpen && "[@media(hover:hover)]:visible")
6594
+ }
6595
+ ) }),
6596
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_design_system19.DropdownMenuContent, { align: "end", children: [
6597
+ user ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_design_system19.DropdownMenuItem, { onSelect: copyUser, children: [
6598
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(UserGlyph, { className: "size-4" }),
6599
+ "Copy ",
6600
+ user.label.toLowerCase()
6601
+ ] }) : null,
6602
+ secret ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_design_system19.DropdownMenuItem, { onSelect: copyPassword, children: [
6603
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(KeyGlyph, { className: "size-4" }),
6604
+ "Copy password"
6605
+ ] }) : null,
6606
+ code ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_design_system19.DropdownMenuItem, { onSelect: copyCode, children: [
6607
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ClockGlyph, { className: "size-4" }),
6608
+ "Copy one-time code"
6609
+ ] }) : null,
6610
+ others.map((f) => /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_design_system19.DropdownMenuItem, { onSelect: () => void run(f.label, () => port.reveal(item, f.key), f.handling !== "visible"), children: [
6611
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_icons12.CopyIcon, { className: "size-4" }),
6612
+ "Copy ",
6613
+ f.label.toLowerCase()
6614
+ ] }, f.key)),
6615
+ custom.map((c) => /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_design_system19.DropdownMenuItem, { onSelect: () => void copy(c.value, c.label, { secret: false }), children: [
6616
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_icons12.CopyIcon, { className: "size-4" }),
6617
+ "Copy ",
6618
+ c.label.toLowerCase()
6619
+ ] }, `custom:${c.key}`)),
6620
+ user || secret || code || others.length > 0 || custom.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_design_system19.DropdownMenuSeparator, {}) : null,
6621
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_design_system19.DropdownMenuItem, { onSelect: () => onSelect(item.id), children: [
6622
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ChevronRightGlyph, { className: "size-4" }),
6623
+ "Open"
6624
+ ] }),
6625
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_design_system19.DropdownMenuItem, { onSelect: copyName, children: [
6626
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_icons12.CopyIcon, { className: "size-4" }),
6627
+ "Copy name"
6628
+ ] }),
6629
+ hostEntries.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_design_system19.DropdownMenuSeparator, {}) : null,
6630
+ hostEntries.map((h) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_design_system19.DropdownMenuItem, { onSelect: h.onSelect, children: h.label }, h.label))
6631
+ ] })
6559
6632
  ] })
6560
6633
  ] })
6561
- ] })
6562
- ] })
6563
- ] });
6634
+ ]
6635
+ }
6636
+ );
6564
6637
  }
6565
6638
 
6566
6639
  // src/lists/TrashView.tsx
@@ -8187,6 +8260,7 @@ var import_jsx_runtime40 = require("react/jsx-runtime");
8187
8260
  var FULL_AT = 1120;
8188
8261
  var SPLIT_AT = 640;
8189
8262
  var NO_HOST = {};
8263
+ var FOCUS_GAP_MS = 2e3;
8190
8264
  var ITEM_PANELS = {
8191
8265
  share: SharePanel,
8192
8266
  move: MovePanel,
@@ -8315,9 +8389,13 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8315
8389
  const knobsRef = (0, import_react42.useRef)(host.knobs);
8316
8390
  knobsRef.current = host.knobs;
8317
8391
  const [signedOut, setSignedOut] = (0, import_react42.useState)(false);
8318
- const load = (0, import_react42.useCallback)(() => {
8319
- setError(null);
8320
- port.load().then((s) => {
8392
+ const [refreshError, setRefreshError] = (0, import_react42.useState)(null);
8393
+ const [refreshing, setRefreshing] = (0, import_react42.useState)(false);
8394
+ const loadedAt = (0, import_react42.useRef)(0);
8395
+ const inFlight = (0, import_react42.useRef)(false);
8396
+ const apply = (0, import_react42.useCallback)(
8397
+ (s) => {
8398
+ loadedAt.current = Date.now();
8321
8399
  setSnap(s);
8322
8400
  setItems(s.items);
8323
8401
  setDeleted(s.deleted);
@@ -8325,15 +8403,71 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8325
8403
  (rows) => setDeleted([...s.deleted, ...rows.filter((r) => !s.deleted.some((d) => d.id === r.id))]),
8326
8404
  () => void 0
8327
8405
  );
8328
- }).catch((e) => {
8406
+ },
8407
+ [port]
8408
+ );
8409
+ const refresh = (0, import_react42.useCallback)(() => {
8410
+ if (inFlight.current) return;
8411
+ inFlight.current = true;
8412
+ setRefreshing(true);
8413
+ port.load().then((s) => {
8414
+ setRefreshError(null);
8415
+ apply(s);
8416
+ }).catch((e) => setRefreshError(plainLoadError(e instanceof Error && e.message ? e.message : "The vault could not be read."))).finally(() => {
8417
+ inFlight.current = false;
8418
+ setRefreshing(false);
8419
+ });
8420
+ port.lists.favorites?.list().then((ids) => setFavorites(new Set(ids)), () => void 0);
8421
+ port.lists.recents?.list().then(setRecents, () => void 0);
8422
+ }, [port, apply]);
8423
+ const load = (0, import_react42.useCallback)(() => {
8424
+ setError(null);
8425
+ setRefreshError(null);
8426
+ port.load().then(apply).catch((e) => {
8329
8427
  setSignedOut(e?.status === 401);
8330
8428
  setError(e instanceof Error ? e.message : "The vault could not be loaded.");
8331
8429
  });
8332
8430
  port.lists.favorites?.list().then((ids) => setFavorites(new Set(ids)), () => void 0);
8333
8431
  knobsRef.current?.().catch(() => void 0);
8334
8432
  port.lists.recents?.list().then(setRecents, () => void 0);
8335
- }, [port]);
8433
+ }, [port, apply]);
8336
8434
  (0, import_react42.useEffect)(load, [load]);
8435
+ const quietRef = (0, import_react42.useRef)({ loc, ready: false });
8436
+ quietRef.current = { loc, ready: snap !== null && !error };
8437
+ const [knobSeconds, setKnobSeconds] = (0, import_react42.useState)(null);
8438
+ (0, import_react42.useEffect)(() => {
8439
+ if (host.refreshSeconds !== void 0 || !knobsRef.current) return;
8440
+ let live = true;
8441
+ knobsRef.current().then(
8442
+ (r) => {
8443
+ const v = r["vault.list.refresh_seconds"];
8444
+ if (live && typeof v === "number" && v > 0) setKnobSeconds(v);
8445
+ },
8446
+ () => void 0
8447
+ );
8448
+ return () => {
8449
+ live = false;
8450
+ };
8451
+ }, [host.refreshSeconds]);
8452
+ const everySeconds = host.refreshSeconds ?? knobSeconds;
8453
+ (0, import_react42.useEffect)(() => {
8454
+ const quietly = (minGapMs) => {
8455
+ const { loc: here, ready } = quietRef.current;
8456
+ if (!ready || document.visibilityState === "hidden" || dirty.current || here.mode !== "view") return;
8457
+ if (Date.now() - loadedAt.current < minGapMs) return;
8458
+ refresh();
8459
+ };
8460
+ const onBack = () => quietly(FOCUS_GAP_MS);
8461
+ const onVisible = () => document.visibilityState === "visible" && onBack();
8462
+ window.addEventListener("focus", onBack);
8463
+ document.addEventListener("visibilitychange", onVisible);
8464
+ const timer2 = everySeconds && everySeconds > 0 ? setInterval(() => quietly(0), everySeconds * 1e3) : null;
8465
+ return () => {
8466
+ window.removeEventListener("focus", onBack);
8467
+ document.removeEventListener("visibilitychange", onVisible);
8468
+ if (timer2) clearInterval(timer2);
8469
+ };
8470
+ }, [refresh, everySeconds]);
8337
8471
  const inventoryRef = (0, import_react42.useRef)(onInventory);
8338
8472
  inventoryRef.current = onInventory;
8339
8473
  (0, import_react42.useEffect)(() => {
@@ -8499,6 +8633,10 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8499
8633
  onSort: (sort) => go({ sort }, "replace"),
8500
8634
  canSortViewed: port.lists.recents !== void 0,
8501
8635
  onNew: () => go({ itemId: null, mode: "new", kind: null, panel: null }),
8636
+ onRefresh: refresh,
8637
+ refreshing,
8638
+ rowContextMenu: host.rowContextMenu ?? "package",
8639
+ ...host.rowMenuItems ? { rowMenuItems: host.rowMenuItems } : {},
8502
8640
  ...matchingIds ? { matchingIds } : {},
8503
8641
  ...compactHeader ? { header: compactHeader } : {}
8504
8642
  }
@@ -8523,14 +8661,14 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8523
8661
  host,
8524
8662
  organizations: orgs,
8525
8663
  defaultOwner: ownerDefault,
8526
- seed: { name: guess?.name ?? "", url: guess?.url ?? loc.url.trim() },
8664
+ seed: { name: loc.name?.trim() || guess?.name || "", url: guess?.url ?? loc.url.trim() },
8527
8665
  onBack: () => go({ kind: null }),
8528
8666
  onMode: () => void 0,
8529
8667
  onSaved: (item, _wasNew, n, enrolled) => {
8530
8668
  replaceItem(item);
8531
8669
  dirty.current = false;
8532
8670
  if (enrolled) setFirstCode(item);
8533
- move({ ...loc, itemId: item.id, mode: "view", kind: null, url: "", panel: null }, "replace");
8671
+ move({ ...loc, itemId: item.id, mode: "view", kind: null, url: "", name: "", panel: null }, "replace");
8534
8672
  setNotice(n);
8535
8673
  notify("Saved");
8536
8674
  },
@@ -8538,7 +8676,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8538
8676
  onDirty: (d) => {
8539
8677
  dirty.current = d;
8540
8678
  },
8541
- onCancelNew: () => go({ mode: "view", kind: null, url: "", itemId: null }),
8679
+ onCancelNew: () => go({ mode: "view", kind: null, url: "", name: "", itemId: null }),
8542
8680
  onPanel: () => void 0,
8543
8681
  onChanged: () => void 0
8544
8682
  },
@@ -8629,13 +8767,17 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8629
8767
  const grid = mode === "full" ? "grid-cols-[15rem_21rem_minmax(0,1fr)]" : mode === "split" ? "grid-cols-[19rem_minmax(0,1fr)]" : "grid-cols-1";
8630
8768
  const shown = panesShown(mode, specialView !== null, showingDetail);
8631
8769
  return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { ref: rootRef, className: "flex h-full min-h-0 w-full flex-col bg-background text-foreground", children: [
8770
+ refreshError ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { role: "status", className: "flex items-center gap-3 border-b border-border bg-muted px-4 py-2 text-sm", children: [
8771
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "min-w-0 flex-1 truncate text-warning", title: refreshError, children: "Couldn\u2019t refresh. Showing what was loaded." }),
8772
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("button", { type: "button", className: "shrink-0 text-primary hover:underline", onClick: refresh, children: "Try again" })
8773
+ ] }) : null,
8632
8774
  snap.unavailable.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { role: "status", className: "flex items-center gap-3 border-b border-border bg-muted px-4 py-2 text-sm", children: [
8633
8775
  /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "min-w-0 flex-1 truncate text-warning", children: [
8634
8776
  "Couldn\u2019t load ",
8635
8777
  snap.unavailable.join(", "),
8636
8778
  ". Showing the rest."
8637
8779
  ] }),
8638
- /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("button", { type: "button", className: "shrink-0 text-primary hover:underline", onClick: load, children: "Try again" })
8780
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("button", { type: "button", className: "shrink-0 text-primary hover:underline", onClick: refresh, children: "Try again" })
8639
8781
  ] }) : null,
8640
8782
  /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: `grid min-h-0 w-full flex-1 ${grid} overflow-hidden`, children: [
8641
8783
  mode === "full" ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(