@ai-matrx/vault 0.1.56 → 0.1.57

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
@@ -432,7 +432,7 @@ function PasswordHealthView(props) {
432
432
  }
433
433
 
434
434
  // src/VaultApp.tsx
435
- import { useCallback as useCallback4, useEffect as useEffect23, useMemo as useMemo6, useRef as useRef19, useState as useState41, useSyncExternalStore as useSyncExternalStore5 } from "react";
435
+ import { useCallback as useCallback4, useEffect as useEffect23, useMemo as useMemo6, useRef as useRef21, useState as useState41, useSyncExternalStore as useSyncExternalStore5 } from "react";
436
436
  import { DropdownMenu as DropdownMenu5, DropdownMenuContent as DropdownMenuContent5, DropdownMenuItem as DropdownMenuItem5, DropdownMenuTrigger as DropdownMenuTrigger5, LookScope } from "@ai-matrx/design-system";
437
437
  import { Button as Button34, SearchField as SearchField4, Select as Select12 } from "@ai-matrx/design-system/controls";
438
438
  import { confirm as confirm14 } from "@ai-matrx/kit/confirm-opener";
@@ -1440,7 +1440,7 @@ function ItemIcon({ definitionKey, family, site, size = "md", inverted = false }
1440
1440
  }
1441
1441
 
1442
1442
  // src/authenticator/AddOtpPanel.tsx
1443
- import { useState as useState11 } from "react";
1443
+ import { useRef as useRef9, useState as useState11 } from "react";
1444
1444
  import { Button as Button8, Field as Field3, Select as Select2 } from "@ai-matrx/design-system/controls";
1445
1445
 
1446
1446
  // src/authenticator/CodeParts.tsx
@@ -1968,7 +1968,7 @@ function OtpSetupRow({ value, onChange, label = "Authenticator", placeholder = S
1968
1968
  }
1969
1969
 
1970
1970
  // src/authenticator/TotpRows.tsx
1971
- import { useState as useState10 } from "react";
1971
+ import { useRef as useRef8, useState as useState10 } from "react";
1972
1972
  import { Button as Button7, Select } from "@ai-matrx/design-system/controls";
1973
1973
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@ai-matrx/design-system";
1974
1974
  import { confirm as confirm2 } from "@ai-matrx/kit/confirm-opener";
@@ -2085,10 +2085,22 @@ function TotpRow({ port, item, onChanged }) {
2085
2085
  function TotpEditRow({ value, onChange, hasExisting, item, port, onEnrolled, onRecognized }) {
2086
2086
  const confirmed = useConfirmed();
2087
2087
  const [status, setStatus] = useState10(null);
2088
+ const [retryImage, setRetryImage] = useState10(null);
2089
+ const latestItem = useRef8(item);
2090
+ latestItem.current = item;
2088
2091
  const uploadImage = item && port ? async (file) => {
2092
+ setRetryImage(file);
2089
2093
  setStatus("Adding from image\u2026");
2090
2094
  try {
2091
- const entry = await confirmed(() => port.authenticator.enrolQrImage(item, file, file.name));
2095
+ const entry = await confirmed(async () => {
2096
+ const current = await port.get(item);
2097
+ const visible = latestItem.current;
2098
+ if (!visible || visible.id !== item.id || visible.authenticatorState !== "known" || current.authenticatorState !== "known" || !item.authenticator && (visible.authenticator || current.authenticator)) {
2099
+ throw new Error("Authenticator status unavailable. Retry after refreshing.");
2100
+ }
2101
+ return port.authenticator.enrolQrImage(current, file, file.name);
2102
+ });
2103
+ setRetryImage(null);
2092
2104
  setStatus(entry.issuer ? `Added \xB7 ${entry.issuer}` : "Added");
2093
2105
  onChange("");
2094
2106
  onEnrolled?.({ ...item, authenticator: { digits: entry.digits, period: entry.period, enabled: entry.enabled } });
@@ -2105,7 +2117,11 @@ function TotpEditRow({ value, onChange, hasExisting, item, port, onEnrolled, onR
2105
2117
  onChange(v);
2106
2118
  },
2107
2119
  placeholder: hasExisting ? "Paste a new key to replace" : SETUP_KEY_PLACEHOLDER,
2108
- status,
2120
+ status: status && retryImage && uploadImage ? /* @__PURE__ */ jsxs8(Fragment6, { children: [
2121
+ status,
2122
+ " ",
2123
+ /* @__PURE__ */ jsx9(Button7, { variant: "quiet", onClick: () => void uploadImage(retryImage), children: "Retry" })
2124
+ ] }) : status,
2109
2125
  ...uploadImage ? { onImage: (f) => void uploadImage(f) } : {},
2110
2126
  ...onRecognized ? { onRecognized } : {}
2111
2127
  }
@@ -2166,6 +2182,8 @@ function normalizeWebsite(raw) {
2166
2182
  }
2167
2183
  function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChanged, onAdded, onDone }) {
2168
2184
  const confirmed = useConfirmed();
2185
+ const itemsRef = useRef9(items);
2186
+ itemsRef.current = items;
2169
2187
  const candidates = items.filter((i) => i.authenticatorState === "known" && !i.authenticator && !i.deletion && (i.capabilities.canEdit || i.capabilities.canManage)).sort((a, b) => a.name.localeCompare(b.name, void 0, { sensitivity: "base" }));
2170
2188
  const [input, setInput] = useState11("");
2171
2189
  const [image, setImage] = useState11(null);
@@ -2194,17 +2212,25 @@ function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChang
2194
2212
  setBusy(true);
2195
2213
  setError(null);
2196
2214
  try {
2197
- let item = isNew ? created : items.find((i) => i.id === target) ?? null;
2215
+ let item = isNew ? created : itemsRef.current.find((i) => i.id === target) ?? null;
2216
+ let freshlyCreated = false;
2217
+ if (!isNew && (!item || item.authenticatorState !== "known" || item.authenticator)) throw new Error("Authenticator status unavailable. Retry after refreshing.");
2198
2218
  if (!item) {
2199
2219
  const def = LOGIN_KEYS.map((k) => definitions.find((d) => d.key === k)).find((d) => Boolean(d));
2200
2220
  if (!def) throw new Error("No login type is available.");
2201
2221
  const r = await port.create(def, null, { ...draftOf(null), name: name.trim(), loginUrls: websiteUrl ? [websiteUrl] : [] });
2202
2222
  item = r.item;
2223
+ freshlyCreated = true;
2203
2224
  setCreated(item);
2204
2225
  onChanged();
2205
2226
  }
2206
2227
  const it = item;
2207
- const entry = await confirmed(() => image ? port.authenticator.enrolQrImage(it, image, image.name) : port.authenticator.enrol(it, input));
2228
+ const entry = await confirmed(async () => {
2229
+ const latest = freshlyCreated ? it : await port.get(it);
2230
+ const listed = itemsRef.current.find((i) => i.id === it.id);
2231
+ if (!freshlyCreated && (latest.authenticatorState !== "known" || latest.authenticator || !isNew && !listed) || listed && (listed.authenticatorState !== "known" || listed.authenticator)) throw new Error("Authenticator status unavailable. Retry after refreshing.");
2232
+ return image ? port.authenticator.enrolQrImage(latest, image, image.name) : port.authenticator.enrol(latest, input);
2233
+ });
2208
2234
  let next = { ...it, authenticatorState: "known", authenticator: { digits: entry.digits, period: entry.period, enabled: entry.enabled } };
2209
2235
  const better = authenticatorName(it.name, { issuer: entry.issuer, account: entry.account });
2210
2236
  if (!isNew && better !== it.name) {
@@ -2291,7 +2317,11 @@ function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChang
2291
2317
  /* @__PURE__ */ jsx10(VaultRow, { label: "Website", hint: websiteBad ? /* @__PURE__ */ jsx10("span", { className: "text-destructive", children: "Not a web address" }) : void 0, children: /* @__PURE__ */ jsx10(Field3, { className: "w-full", type: "url", value: website, disabled: busy || created !== null, onChange: (e) => setWebsite(e.target.value), placeholder: "example.com", "aria-label": "Website", "aria-invalid": websiteBad || void 0 }) })
2292
2318
  ] }) : null
2293
2319
  ] }),
2294
- error ? /* @__PURE__ */ jsx10("p", { className: "px-1 text-sm text-destructive", role: "alert", children: error }) : null
2320
+ error ? /* @__PURE__ */ jsxs9("p", { className: "px-1 text-sm text-destructive", role: "alert", children: [
2321
+ error,
2322
+ " ",
2323
+ /* @__PURE__ */ jsx10(Button8, { variant: "quiet", onClick: onChanged, children: "Retry" })
2324
+ ] }) : null
2295
2325
  ]
2296
2326
  }
2297
2327
  );
@@ -2326,23 +2356,7 @@ function otpGroups(items, entries, query, hidden = /* @__PURE__ */ new Set()) {
2326
2356
  const rank = (k) => k === "me" ? 0 : k === "shared" ? 2 : 1;
2327
2357
  return [...groups.entries()].sort(([a, x], [b, y]) => rank(a) - rank(b) || x.label.localeCompare(y.label)).map(([key, g]) => ({ key, ...g }));
2328
2358
  }
2329
- function withAdded(items, added) {
2330
- if (added.size === 0) return items;
2331
- const seen = /* @__PURE__ */ new Set();
2332
- const out = items.map((i) => {
2333
- seen.add(i.id);
2334
- const mine = added.get(i.id);
2335
- return mine && !i.authenticator ? { ...i, authenticator: mine.authenticator, name: mine.name } : i;
2336
- });
2337
- for (const [id, a] of added) if (!seen.has(id)) out.push(a);
2338
- return out;
2339
- }
2340
- function AuthenticatorView({ port, host, items: listed, definitions, query, onOpen, onChanged, header }) {
2341
- const [added, setAdded] = useState12(/* @__PURE__ */ new Map());
2342
- const items = withAdded(listed, added);
2343
- useEffect10(() => {
2344
- if (added.size > 0 && [...added.keys()].every((id) => listed.some((i) => i.id === id && i.authenticator))) setAdded(/* @__PURE__ */ new Map());
2345
- }, [listed, added]);
2359
+ function AuthenticatorView({ port, host, items, definitions, query, onOpen, onChanged, header }) {
2346
2360
  const [incomplete, setIncomplete] = useState12(true);
2347
2361
  const [entries, setEntries] = useState12(/* @__PURE__ */ new Map());
2348
2362
  const [hidden, setHidden] = useState12(/* @__PURE__ */ new Set());
@@ -2366,7 +2380,7 @@ function AuthenticatorView({ port, host, items: listed, definitions, query, onOp
2366
2380
  return () => {
2367
2381
  live = false;
2368
2382
  };
2369
- }, [port, listed, added]);
2383
+ }, [port, items]);
2370
2384
  const groups = otpGroups(items, entries, query, hidden);
2371
2385
  const total = items.filter((i) => i.authenticator && !i.deletion && !hidden.has(i.id)).length;
2372
2386
  const familyOf2 = (key) => definitions.find((d) => d.key === key)?.family ?? "generic";
@@ -2379,7 +2393,7 @@ function AuthenticatorView({ port, host, items: listed, definitions, query, onOp
2379
2393
  ] }),
2380
2394
  total > 0 ? /* @__PURE__ */ jsx11(Button9, { variant: "outline", icon: /* @__PURE__ */ jsx11(PlusGlyph, {}), onClick: () => setAdding(true), children: "Add" }) : null
2381
2395
  ] }),
2382
- total > 0 && incomplete ? /* @__PURE__ */ jsxs10("div", { className: "px-4 text-sm text-muted-foreground", children: [
2396
+ total > 0 && (incomplete || items.some((i) => i.authenticatorState !== "known")) ? /* @__PURE__ */ jsxs10("div", { className: "px-4 text-sm text-muted-foreground", children: [
2383
2397
  "Some codes unavailable ",
2384
2398
  /* @__PURE__ */ jsx11(Button9, { variant: "quiet", onClick: onChanged, children: "Retry" })
2385
2399
  ] }) : null,
@@ -2419,7 +2433,6 @@ function AuthenticatorView({ port, host, items: listed, definitions, query, onOp
2419
2433
  definitions,
2420
2434
  onClose: () => setAdding(false),
2421
2435
  onChanged,
2422
- onAdded: (item) => setAdded((cur) => new Map(cur).set(item.id, item)),
2423
2436
  onDone: (id) => {
2424
2437
  setAdding(false);
2425
2438
  onOpen(id);
@@ -2537,7 +2550,7 @@ function OtpListRow({ port, item, family, entry, onOpen, onOpenInNewTab, onRenam
2537
2550
  }
2538
2551
 
2539
2552
  // src/fields/SettingsPanels.tsx
2540
- import { useRef as useRef8, useState as useState13 } from "react";
2553
+ import { useRef as useRef10, useState as useState13 } from "react";
2541
2554
  import { Badge as Badge3, Button as Button10, DisclosureHeader, Field as Field4, SegmentedControl, Select as Select3, Switch } from "@ai-matrx/design-system/controls";
2542
2555
  import { confirm as confirm3 } from "@ai-matrx/kit/confirm-opener";
2543
2556
  import { GlobeIcon } from "@ai-matrx/design-system/icons";
@@ -2670,7 +2683,7 @@ function FieldSettings({ item, port, field, onClose, onChanged }) {
2670
2683
  const { busy, error, setError, save } = useSaver((next) => onChanged(next));
2671
2684
  const [envDraft, setEnvDraft] = useState13(field.envKey ?? "");
2672
2685
  const [descDraft, setDescDraft] = useState13(field.description);
2673
- const envRef = useRef8(null);
2686
+ const envRef = useRef10(null);
2674
2687
  const canEdit = item.capabilities.canEdit;
2675
2688
  const envProblem = envKeyProblem(envDraft);
2676
2689
  const blocker = sandboxBlocker(envDraft, field.envKey);
@@ -2834,7 +2847,7 @@ function ItemSettingsPanel({ item, port, onClose, onChanged, onPanel }) {
2834
2847
  }
2835
2848
 
2836
2849
  // src/generator/PasswordGenerator.tsx
2837
- import { createContext as createContext3, useContext as useContext3, useEffect as useEffect11, useRef as useRef9, useState as useState14 } from "react";
2850
+ import { createContext as createContext3, useContext as useContext3, useEffect as useEffect11, useRef as useRef11, useState as useState14 } from "react";
2838
2851
  import { Popover, PopoverContent, PopoverTrigger, cn as cn6 } from "@ai-matrx/design-system";
2839
2852
  import { Button as Button11, NumberStepper, SegmentedControl as SegmentedControl2, Select as Select4, Switch as Switch2 } from "@ai-matrx/design-system/controls";
2840
2853
  import { CopyIcon as CopyIcon6 } from "@ai-matrx/design-system/icons";
@@ -2912,7 +2925,7 @@ function PasswordGenerator({ limits: source, onUse, useLabel = "Use" }) {
2912
2925
  const [revealed, setRevealed] = useState14(false);
2913
2926
  const [note, setNote] = useState14(null);
2914
2927
  const copyValue2 = useCopy();
2915
- const expiry = useRef9(null);
2928
+ const expiry = useRef11(null);
2916
2929
  const drop = () => {
2917
2930
  if (expiry.current) clearTimeout(expiry.current);
2918
2931
  expiry.current = null;
@@ -3138,11 +3151,11 @@ function organizationHints(orgs) {
3138
3151
  }
3139
3152
 
3140
3153
  // src/shown-until-move.ts
3141
- import { useEffect as useEffect12, useRef as useRef10, useState as useState15 } from "react";
3154
+ import { useEffect as useEffect12, useRef as useRef12, useState as useState15 } from "react";
3142
3155
  var keyOf = (loc) => `${String(loc.vault)}|${loc.view}|${loc.itemId ?? ""}|${loc.mode}|${loc.panel ?? ""}|${loc.kind ?? ""}`;
3143
3156
  function useShownUntilMove(loc) {
3144
3157
  const [shown, setShown] = useState15(null);
3145
- const home = useRef10(null);
3158
+ const home = useRef12(null);
3146
3159
  const key = keyOf(loc);
3147
3160
  useEffect12(() => {
3148
3161
  if (!shown) {
@@ -3200,7 +3213,7 @@ function useLocked(lock) {
3200
3213
 
3201
3214
  // src/io/BackupPanel.tsx
3202
3215
  import { formatFileSize } from "@ai-matrx/kit/format";
3203
- import { useEffect as useEffect13, useRef as useRef11, useState as useState17 } from "react";
3216
+ import { useEffect as useEffect13, useRef as useRef13, useState as useState17 } from "react";
3204
3217
  import { Button as Button13, Field as Field5, SegmentedControl as SegmentedControl3, Switch as Switch3 } from "@ai-matrx/design-system/controls";
3205
3218
  import { downloadFile } from "@ai-matrx/kit/download";
3206
3219
  import { confirm as confirm4 } from "@ai-matrx/kit/confirm-opener";
@@ -3357,8 +3370,8 @@ function Omissions({ omissions, accepted, onAccepted, verb }) {
3357
3370
  function BackupPanel({ port, host, items, onClose, onChanged }) {
3358
3371
  const minLength = passphraseMinimum(host.backupPassphraseMinLength);
3359
3372
  const confirmed = useConfirmed();
3360
- const fileInput = useRef11(null);
3361
- const abort = useRef11(null);
3373
+ const fileInput = useRef13(null);
3374
+ const abort = useRef13(null);
3362
3375
  const [mode, setMode] = useState17("backup");
3363
3376
  const [busy, setBusy] = useState17(false);
3364
3377
  const [error, setError] = useState17(null);
@@ -3710,7 +3723,7 @@ function ExportPanel({ port, items, onClose }) {
3710
3723
  }
3711
3724
 
3712
3725
  // src/io/ImportPanel.tsx
3713
- import { useEffect as useEffect14, useMemo as useMemo2, useRef as useRef12, useState as useState19 } from "react";
3726
+ import { useEffect as useEffect14, useMemo as useMemo2, useRef as useRef14, useState as useState19 } from "react";
3714
3727
  import { Button as Button15, Select as Select6, Switch as Switch4, Textarea as Textarea2 } from "@ai-matrx/design-system/controls";
3715
3728
 
3716
3729
  // src/io/csv-import.ts
@@ -4573,10 +4586,10 @@ var ME = "me";
4573
4586
  function ImportPanel({ port, host, items, definitions, organizations, onClose, onChanged, limits }) {
4574
4587
  const confirmed = useConfirmed();
4575
4588
  const [otpFor, setOtpFor] = useState19(null);
4576
- const fileInput = useRef12(null);
4577
- const abort = useRef12(null);
4578
- const stop = useRef12(false);
4579
- const keys = useRef12(/* @__PURE__ */ new Map());
4589
+ const fileInput = useRef14(null);
4590
+ const abort = useRef14(null);
4591
+ const stop = useRef14(false);
4592
+ const keys = useRef14(/* @__PURE__ */ new Map());
4580
4593
  const [source, setSource] = useState19("auto");
4581
4594
  const [envText, setEnvText] = useState19("");
4582
4595
  const [injectSandbox, setInjectSandbox] = useState19(false);
@@ -4877,14 +4890,14 @@ function Notices({ draft }) {
4877
4890
  }
4878
4891
 
4879
4892
  // src/ItemList.tsx
4880
- import { useEffect as useEffect17, useRef as useRef15, useState as useState28 } from "react";
4893
+ import { useEffect as useEffect17, useRef as useRef17, useState as useState28 } from "react";
4881
4894
  import { DropdownMenu as DropdownMenu4, DropdownMenuContent as DropdownMenuContent4, DropdownMenuItem as DropdownMenuItem4, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator as DropdownMenuSeparator4, DropdownMenuTrigger as DropdownMenuTrigger4 } from "@ai-matrx/design-system";
4882
4895
  import { cn as cn11 } from "@ai-matrx/design-system";
4883
4896
  import { Button as Button23, SearchField, Tile as Tile3 } from "@ai-matrx/design-system/controls";
4884
4897
  import { CopyIcon as CopyIcon8 } from "@ai-matrx/design-system/icons";
4885
4898
 
4886
4899
  // src/ItemPane.tsx
4887
- import { Fragment as Fragment17, useEffect as useEffect16, useMemo as useMemo3, useRef as useRef14, useState as useState27 } from "react";
4900
+ import { Fragment as Fragment17, useEffect as useEffect16, useMemo as useMemo3, useRef as useRef16, useState as useState27 } from "react";
4888
4901
  import { DropdownMenu as DropdownMenu3, DropdownMenuContent as DropdownMenuContent3, DropdownMenuItem as DropdownMenuItem3, DropdownMenuSeparator as DropdownMenuSeparator3, DropdownMenuTrigger as DropdownMenuTrigger3 } from "@ai-matrx/design-system";
4889
4902
  import { cn as cn10 } from "@ai-matrx/design-system";
4890
4903
  import { Badge as Badge7, Button as Button22, CONTROLS_CONTAINER_CLASS, OptionCombobox, Switch as Switch6 } from "@ai-matrx/design-system/controls";
@@ -4892,7 +4905,7 @@ import { confirm as confirm10 } from "@ai-matrx/kit/confirm-opener";
4892
4905
 
4893
4906
  // src/activity/FilesSection.tsx
4894
4907
  import { formatFileSize as formatFileSize2 } from "@ai-matrx/kit/format";
4895
- import { useRef as useRef13, useState as useState20 } from "react";
4908
+ import { useRef as useRef15, useState as useState20 } from "react";
4896
4909
  import { Badge as Badge5, Button as Button16, Field as Field6, SegmentedControl as SegmentedControl4 } from "@ai-matrx/design-system/controls";
4897
4910
  import { cn as cn7 } from "@ai-matrx/design-system";
4898
4911
  import { confirm as confirm6 } from "@ai-matrx/kit/confirm-opener";
@@ -4914,7 +4927,7 @@ function FilesSection({ item, port, editing, onChanged }) {
4914
4927
  const [error, setError] = useState20(null);
4915
4928
  const [pending, setPending] = useState20(null);
4916
4929
  const [dragging, setDragging] = useState20(false);
4917
- const picker = useRef13(null);
4930
+ const picker = useRef15(null);
4918
4931
  if (!canAdd && item.attachments.length === 0) return null;
4919
4932
  async function run(key, work, fallback) {
4920
4933
  setBusy(key);
@@ -4992,7 +5005,7 @@ function FilesSection({ item, port, editing, onChanged }) {
4992
5005
  }
4993
5006
  function EditableFile({ port, item: it, file: a, busy: b, run: go, onDownload, onTooBig }) {
4994
5007
  const [open, setOpen] = useState20(false);
4995
- const replacer = useRef13(null);
5008
+ const replacer = useRef15(null);
4996
5009
  async function remove() {
4997
5010
  const yes = await confirm6({ title: `Delete ${a.label}?`, description: "The encrypted file is removed from this item.", confirmLabel: "Delete", variant: "destructive" });
4998
5011
  if (yes) await go(`delete:${a.id}`, () => port.activity.deleteFile(it, a.id), "The file could not be deleted.");
@@ -5104,7 +5117,7 @@ function DraftFilesSection({ files, onFiles }) {
5104
5117
  const [pending, setPending] = useState20(null);
5105
5118
  const [error, setError] = useState20(null);
5106
5119
  const [dragging, setDragging] = useState20(false);
5107
- const picker = useRef13(null);
5120
+ const picker = useRef15(null);
5108
5121
  const choose = (file) => {
5109
5122
  if (!file) return;
5110
5123
  if (fileTooBig(file.size)) return setError(TOO_BIG);
@@ -6027,6 +6040,10 @@ function confirmDelete(item) {
6027
6040
  function ItemPane(props) {
6028
6041
  const { port, item, definition, mode, host, organizations, defaultOwner, seed, onBack, onMode, onSaved, onDeleted, onDirty, onCancelNew, onPanel, onChanged, favorite, notice, refreshKey } = props;
6029
6042
  const confirmed = useConfirmed();
6043
+ const latestItem = useRef16(item);
6044
+ latestItem.current = item;
6045
+ const [createdForRetry, setCreatedForRetry] = useState27(null);
6046
+ const enrolledForRetry = useRef16(null);
6030
6047
  const notify = useNotify();
6031
6048
  const editing = mode !== "view";
6032
6049
  const seedName = seed?.name ?? "";
@@ -6059,8 +6076,8 @@ function ItemPane(props) {
6059
6076
  const [savedNew, setSavedNew] = useState27(false);
6060
6077
  const dirty = editing && !savedNew && isDirty(base, draft);
6061
6078
  const declared = definition?.fields ?? [];
6062
- const prev = useRef14(null);
6063
- const paneRef = useRef14(null);
6079
+ const prev = useRef16(null);
6080
+ const paneRef = useRef16(null);
6064
6081
  const room = useRoomOf(paneRef);
6065
6082
  useEffect16(() => {
6066
6083
  const was = prev.current;
@@ -6082,7 +6099,7 @@ function ItemPane(props) {
6082
6099
  setShowRecovery(false);
6083
6100
  setSavedNew(false);
6084
6101
  }, [item?.id, mode]);
6085
- const onDirtyRef = useRef14(onDirty);
6102
+ const onDirtyRef = useRef16(onDirty);
6086
6103
  onDirtyRef.current = onDirty;
6087
6104
  useEffect16(() => onDirtyRef.current(dirty), [dirty]);
6088
6105
  const keepKey = draftKey(item?.id ?? null, definition?.key ?? item?.definitionKey ?? null);
@@ -6112,23 +6129,39 @@ function ItemPane(props) {
6112
6129
  setError(null);
6113
6130
  const out = { ...draft, values: Object.fromEntries(changedValues(base, draft)) };
6114
6131
  if (out.values[RECOVERY_CODES_KEY] !== void 0) out.values[RECOVERY_CODES_KEY] = parseRecoveryCodes(out.values[RECOVERY_CODES_KEY]).join("\n");
6115
- const withOtp = async (saved, notice2) => {
6132
+ const withOtp = async (saved, notice2, freshlyCreated = false) => {
6116
6133
  const setup = out.authenticator.trim();
6117
6134
  if (!setup) return [saved, notice2, false];
6118
6135
  try {
6119
- await confirmed(() => port.authenticator.enrol(saved, setup));
6136
+ if (enrolledForRetry.current?.itemId !== saved.id || enrolledForRetry.current.setup !== setup) await confirmed(async () => {
6137
+ const current = freshlyCreated ? saved : await port.get(saved);
6138
+ const visible = latestItem.current?.id === saved.id ? latestItem.current : null;
6139
+ const ownEnrollment = enrolledForRetry.current?.itemId === saved.id;
6140
+ if (!freshlyCreated && (current.authenticatorState !== "known" || !item?.authenticator && current.authenticator && !ownEnrollment || item && !visible) || visible && (visible.authenticatorState !== "known" || !item?.authenticator && visible.authenticator && !ownEnrollment)) throw new Error("Authenticator status unavailable. Retry after refreshing");
6141
+ await port.authenticator.enrol(current, setup);
6142
+ enrolledForRetry.current = { itemId: saved.id, setup };
6143
+ });
6120
6144
  return [await port.get(saved), notice2, true];
6121
6145
  } catch (e) {
6146
+ if (enrolledForRetry.current?.itemId === saved.id && enrolledForRetry.current.setup === setup) return [saved, `Saved and authenticator set up, but refresh failed: ${e instanceof Error ? e.message : "Retry"}.`, false];
6122
6147
  const why = wasCancelled(e) ? "it was not confirmed" : e instanceof Error ? e.message : "it was refused";
6123
6148
  return [saved, `Saved, but the authenticator was not set up: ${why}. Add it with Edit.`, false];
6124
6149
  }
6125
6150
  };
6126
6151
  try {
6127
- if (item) {
6128
- const r = await port.save(item, definition, out);
6152
+ if (item || createdForRetry) {
6153
+ const r = await port.save(item ?? createdForRetry, definition, out);
6129
6154
  const [next, n, enrolled] = await withOtp(r.item, r.notice);
6130
- dropKeptDraft(keepKey);
6131
- onSaved(next, false, n, enrolled);
6155
+ if (out.authenticator.trim() && !enrolled) {
6156
+ setError(n);
6157
+ onChanged();
6158
+ return;
6159
+ }
6160
+ if (createdForRetry) await finishNew(next, n, enrolled);
6161
+ else {
6162
+ dropKeptDraft(keepKey);
6163
+ onSaved(next, false, n, enrolled);
6164
+ }
6132
6165
  } else if (definition && owner === "person") {
6133
6166
  const secretKey = declared.find((f) => f.storage === "encrypted" && f.kind === "secret")?.key ?? null;
6134
6167
  const r = await port.sharing.assign({ recipientEmail: recipient.trim(), definition, draft: out, generateFieldKey: generatePrivately ? secretKey : null });
@@ -6138,12 +6171,14 @@ function ItemPane(props) {
6138
6171
  setSent(r);
6139
6172
  } else if (definition) {
6140
6173
  const r = await port.create(definition, owner === "me" ? null : owner, out);
6141
- const [withCode, n0, enrolled] = await withOtp(r.item, r.notice);
6142
- const [next, n] = await storeQueuedFiles(withCode, n0);
6143
- dropKeptDraft(keepKey);
6144
- setSavedNew(true);
6145
- onDirty(false);
6146
- onSaved(next, true, n, enrolled);
6174
+ setCreatedForRetry(r.item);
6175
+ const [withCode, n0, enrolled] = await withOtp(r.item, r.notice, true);
6176
+ if (out.authenticator.trim() && !enrolled) {
6177
+ setError(n0);
6178
+ onChanged();
6179
+ return;
6180
+ }
6181
+ await finishNew(withCode, n0, enrolled);
6147
6182
  }
6148
6183
  } catch (e) {
6149
6184
  setError(e instanceof Error ? e.message : "Saving failed. Nothing was changed.");
@@ -6151,6 +6186,13 @@ function ItemPane(props) {
6151
6186
  setBusy(false);
6152
6187
  }
6153
6188
  }
6189
+ async function finishNew(saved, notice2, enrolled) {
6190
+ const [next, finalNotice] = await storeQueuedFiles(saved, notice2);
6191
+ dropKeptDraft(keepKey);
6192
+ setSavedNew(true);
6193
+ onDirty(false);
6194
+ onSaved(next, true, finalNotice, enrolled);
6195
+ }
6154
6196
  async function storeQueuedFiles(saved, notice2) {
6155
6197
  let next = saved;
6156
6198
  const failed = [];
@@ -6332,7 +6374,11 @@ function ItemPane(props) {
6332
6374
  const filesProps = item ? { item, port, host, definition, organizations, arg: null, onClose: () => void 0, onChanged, onPanel } : null;
6333
6375
  return /* @__PURE__ */ jsx25(RoomProvider, { value: room, children: /* @__PURE__ */ jsxs23("article", { ref: paneRef, "aria-label": item?.name ?? "New item", className: cn10(CONTROLS_CONTAINER_CLASS, "flex h-full min-h-0 flex-col bg-background"), children: [
6334
6376
  header,
6335
- error ? /* @__PURE__ */ jsx25("div", { role: "alert", className: "border-b border-border bg-muted px-5 py-2 text-sm text-destructive", children: error }) : null,
6377
+ error ? /* @__PURE__ */ jsxs23("div", { role: "alert", className: "border-b border-border bg-muted px-5 py-2 text-sm text-destructive", children: [
6378
+ error,
6379
+ " ",
6380
+ error.includes("Retry after refreshing") ? /* @__PURE__ */ jsx25(Button22, { variant: "quiet", onClick: () => onChanged(), children: "Retry" }) : null
6381
+ ] }) : null,
6336
6382
  notice && !editing ? /* @__PURE__ */ jsx25("div", { role: "status", className: "border-b border-border bg-muted px-5 py-2 text-sm text-warning", children: notice }) : null,
6337
6383
  item && !editing && (itemOff || offFields.length > 0) ? /* @__PURE__ */ jsxs23("div", { role: "status", className: "flex items-center gap-3 border-b border-border bg-muted px-5 py-2 text-sm", children: [
6338
6384
  /* @__PURE__ */ jsx25("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` }),
@@ -6436,10 +6482,11 @@ function ItemPane(props) {
6436
6482
  i
6437
6483
  )),
6438
6484
  /* @__PURE__ */ jsx25(AddRow, { label: draft.loginUrls.length === 0 ? "Website" : "", onAdd: () => patch({ loginUrls: [...draft.loginUrls, ""] }), children: draft.loginUrls.length === 0 ? "Add website" : "Add another website" }),
6439
- !item || item.authenticatorState === "known" ? /* @__PURE__ */ jsx25(TotpEditRow, { value: draft.authenticator, onChange: (v) => patch({ authenticator: v }), hasExisting: Boolean(item?.authenticator || seedField), item, port, onEnrolled: onChanged }) : /* @__PURE__ */ jsxs23(VaultRow, { label: "Authenticator", children: [
6485
+ /* @__PURE__ */ jsx25("div", { hidden: Boolean(item && item.authenticatorState !== "known"), children: /* @__PURE__ */ jsx25(TotpEditRow, { value: draft.authenticator, onChange: (v) => patch({ authenticator: v }), hasExisting: Boolean(item?.authenticator || seedField), item, port, onEnrolled: onChanged }) }),
6486
+ item && item.authenticatorState !== "known" ? /* @__PURE__ */ jsxs23(VaultRow, { label: "Authenticator", children: [
6440
6487
  /* @__PURE__ */ jsx25("span", { className: "text-muted-foreground", children: item.authenticatorState === "pending" ? "Loading" : "Unavailable" }),
6441
6488
  item.authenticatorState === "unavailable" ? /* @__PURE__ */ jsx25(Button22, { variant: "quiet", onClick: () => onChanged?.(), children: "Retry" }) : null
6442
- ] }),
6489
+ ] }) : null,
6443
6490
  item && isLogin && !item.authenticator ? /* @__PURE__ */ jsx25(TwoFactorMethodRow, { port, item, onChanged }) : null,
6444
6491
  !item && isLogin && draft.authenticator.trim() === "" ? /* @__PURE__ */ jsx25(DraftTwoFactorRow, { custom: draft.custom, onCustom: (custom) => patch({ custom }) }) : null,
6445
6492
  recoveryEditing ? /* @__PURE__ */ jsx25(
@@ -6598,7 +6645,7 @@ function ItemList({ title, items, definitions, port, selectedId, onSelect, query
6598
6645
  const pinned = matchingIds ? items.filter((i) => matchingIds.has(i.id)) : [];
6599
6646
  const rest = matchingIds ? items.filter((i) => !matchingIds.has(i.id)) : items;
6600
6647
  const ordered = [...pinned, ...rest];
6601
- const listRef = useRef15(null);
6648
+ const listRef = useRef17(null);
6602
6649
  useEffect17(() => {
6603
6650
  if (selectedId) listRef.current?.querySelector(`[data-item="${selectedId}"]`)?.scrollIntoView({ block: "nearest" });
6604
6651
  }, [selectedId, items.length]);
@@ -6702,7 +6749,7 @@ function ListRow({ item, family, port, on, onSelect, withOwner, ownContextMenu,
6702
6749
  ...manageEntries(item, onAction),
6703
6750
  ...hostEntries.map((h) => ({ ...h, kind: h.kind ?? "host" }))
6704
6751
  ];
6705
- const entriesRef = useRef15(entries);
6752
+ const entriesRef = useRef17(entries);
6706
6753
  entriesRef.current = entries;
6707
6754
  const glyphOf = (e) => e.id === "copy:username" ? /* @__PURE__ */ jsx26(UserGlyph, { className: "size-4" }) : e.id === "copy:password" ? /* @__PURE__ */ jsx26(KeyGlyph, { className: "size-4" }) : e.id === "copy:code" ? /* @__PURE__ */ jsx26(ClockGlyph, { className: "size-4" }) : e.kind === "favorite" ? /* @__PURE__ */ jsx26(StarGlyph, { className: cn11("size-4", favorite?.on && "fill-current text-warning") }) : e.kind === "open" ? /* @__PURE__ */ jsx26(ChevronRightGlyph, { className: "size-4" }) : e.kind === "host" || e.kind === "manage" ? null : /* @__PURE__ */ jsx26(CopyIcon8, { className: "size-4" });
6708
6755
  const menuItem = (e) => /* @__PURE__ */ jsxs24(DropdownMenuItem4, { disabled: e.disabled ?? false, onSelect: e.onSelect, children: [
@@ -7109,7 +7156,7 @@ function NewItemFlow({ definitions, url, onUrl, onChoose, onCancel }) {
7109
7156
  }
7110
7157
 
7111
7158
  // src/sharing/SecureLinkPanel.tsx
7112
- import { useEffect as useEffect19, useRef as useRef16, useState as useState32 } from "react";
7159
+ import { useEffect as useEffect19, useRef as useRef18, useState as useState32 } from "react";
7113
7160
  import { confirm as confirm11 } from "@ai-matrx/kit/confirm-opener";
7114
7161
  import { Checkbox as Checkbox3 } from "@ai-matrx/design-system";
7115
7162
  import { Badge as Badge8, Button as Button27, Field as Field11, RegionSkeleton as RegionSkeleton2, SegmentedControl as SegmentedControl6, Textarea as Textarea4 } from "@ai-matrx/design-system/controls";
@@ -7234,7 +7281,7 @@ function SecureLinkPanel({ item, port, onClose }) {
7234
7281
  };
7235
7282
  }, [sharing, item, nonce]);
7236
7283
  const [phoneTyped, setPhoneTyped] = useState32(false);
7237
- const prefilled = useRef16(false);
7284
+ const prefilled = useRef18(false);
7238
7285
  useEffect19(() => {
7239
7286
  const lookup = sharing.phoneOnFile;
7240
7287
  if (!lookup || phoneTyped) return;
@@ -7711,19 +7758,19 @@ async function loadFullPreview(bridge, operationId, current) {
7711
7758
  }
7712
7759
 
7713
7760
  // src/native/use-native-import.ts
7714
- import { useEffect as useEffect21, useRef as useRef17, useState as useState34 } from "react";
7761
+ import { useEffect as useEffect21, useRef as useRef19, useState as useState34 } from "react";
7715
7762
  var fail = (e, fallback) => e instanceof Error && e.message ? e.message : fallback;
7716
7763
  var RESTART = "Your account changed. The import was stopped; start it again.";
7717
7764
  function useNativeImport(bridge, stillAllowed, onImported) {
7718
7765
  const [screen, setScreen] = useState34({ kind: "idle", message: null });
7719
7766
  const [busy, setBusy] = useState34(false);
7720
- const generation = useRef17(0);
7721
- const mounted = useRef17(false);
7722
- const op = useRef17(null);
7723
- const last = useRef17(null);
7724
- const timer2 = useRef17(null);
7725
- const polls = useRef17(0);
7726
- const refreshRef = useRef17(async () => void 0);
7767
+ const generation = useRef19(0);
7768
+ const mounted = useRef19(false);
7769
+ const op = useRef19(null);
7770
+ const last = useRef19(null);
7771
+ const timer2 = useRef19(null);
7772
+ const polls = useRef19(0);
7773
+ const refreshRef = useRef19(async () => void 0);
7727
7774
  const current = (g) => mounted.current && g === generation.current;
7728
7775
  const stopPolling = () => {
7729
7776
  if (timer2.current !== null) clearTimeout(timer2.current);
@@ -7892,7 +7939,7 @@ function useNativeImport(bridge, stillAllowed, onImported) {
7892
7939
  setBusy(false);
7893
7940
  setScreen({ kind: "idle", message: null });
7894
7941
  };
7895
- const invalidateRef = useRef17(invalidate);
7942
+ const invalidateRef = useRef19(invalidate);
7896
7943
  invalidateRef.current = invalidate;
7897
7944
  useEffect21(() => {
7898
7945
  mounted.current = true;
@@ -8064,13 +8111,13 @@ function CaptureCard({ capture, onChanged }) {
8064
8111
  }
8065
8112
 
8066
8113
  // src/extension/FillSetupCard.tsx
8067
- import { useRef as useRef18, useState as useState37, useSyncExternalStore as useSyncExternalStore4 } from "react";
8114
+ import { useRef as useRef20, useState as useState37, useSyncExternalStore as useSyncExternalStore4 } from "react";
8068
8115
  import { Button as Button31, Field as Field12 } from "@ai-matrx/design-system/controls";
8069
8116
  import { jsx as jsx34, jsxs as jsxs32 } from "react/jsx-runtime";
8070
8117
  function FillSetupCard({ fillSetup }) {
8071
8118
  const notify = useNotify();
8072
8119
  const state = useSyncExternalStore4(fillSetup.subscribe, fillSetup.get, fillSetup.get);
8073
- const box = useRef18(null);
8120
+ const box = useRef20(null);
8074
8121
  const [busy, setBusy] = useState37(false);
8075
8122
  const [error, setError] = useState37(null);
8076
8123
  if (state.status !== "off") return null;
@@ -8610,7 +8657,7 @@ function LockedScreen({ lock }) {
8610
8657
  ] });
8611
8658
  }
8612
8659
  function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocationChange, onInventory }) {
8613
- const rootRef = useRef19(null);
8660
+ const rootRef = useRef21(null);
8614
8661
  const width = useElementWidth(rootRef);
8615
8662
  const mode = layout !== "auto" ? layout : width >= FULL_AT ? "full" : width >= SPLIT_AT ? "split" : "stack";
8616
8663
  const notify = useNotify();
@@ -8622,7 +8669,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8622
8669
  const [error, setError] = useState41(null);
8623
8670
  const [inner, setInner] = useState41(DEFAULT_LOCATION);
8624
8671
  const loc = location ?? inner;
8625
- const dirty = useRef19(false);
8672
+ const dirty = useRef21(false);
8626
8673
  const [notice, setNotice] = useState41(null);
8627
8674
  const [firstCode, setFirstCode] = useShownUntilMove(loc);
8628
8675
  const move = useCallback4(
@@ -8651,7 +8698,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8651
8698
  },
8652
8699
  [loc, move]
8653
8700
  );
8654
- const keyState = useRef19({ loc, go });
8701
+ const keyState = useRef21({ loc, go });
8655
8702
  keyState.current = { loc, go };
8656
8703
  useEffect23(() => {
8657
8704
  const onKey = (e) => {
@@ -8675,7 +8722,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8675
8722
  document.addEventListener("keydown", onKey);
8676
8723
  return () => document.removeEventListener("keydown", onKey);
8677
8724
  }, []);
8678
- const keepSearchFocus = useRef19(false);
8725
+ const keepSearchFocus = useRef21(false);
8679
8726
  useEffect23(() => {
8680
8727
  if (!keepSearchFocus.current) return;
8681
8728
  keepSearchFocus.current = false;
@@ -8685,13 +8732,14 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8685
8732
  search.setSelectionRange(search.value.length, search.value.length);
8686
8733
  }, [loc.q]);
8687
8734
  const hasPrompts = useHostPrompts(host.capture, host.fillSetup);
8688
- const knobsRef = useRef19(host.knobs);
8735
+ const knobsRef = useRef21(host.knobs);
8689
8736
  knobsRef.current = host.knobs;
8690
8737
  const [signedOut, setSignedOut] = useState41(false);
8691
8738
  const [refreshError, setRefreshError] = useState41(null);
8692
8739
  const [refreshing, setRefreshing] = useState41(false);
8693
- const loadedAt = useRef19(0);
8694
- const inFlight = useRef19(false);
8740
+ const loadedAt = useRef21(0);
8741
+ const inFlight = useRef21(false);
8742
+ const loadGeneration = useRef21(0);
8695
8743
  const apply = useCallback4(
8696
8744
  (s) => {
8697
8745
  loadedAt.current = Date.now();
@@ -8708,11 +8756,15 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8708
8756
  const refresh = useCallback4(() => {
8709
8757
  if (inFlight.current) return;
8710
8758
  inFlight.current = true;
8759
+ const generation = ++loadGeneration.current;
8711
8760
  setRefreshing(true);
8712
8761
  port.load().then((s) => {
8762
+ if (generation !== loadGeneration.current) return;
8713
8763
  setRefreshError(null);
8714
8764
  apply(s);
8715
- }).catch((e) => setRefreshError(plainLoadError(e instanceof Error && e.message ? e.message : "The vault could not be read."))).finally(() => {
8765
+ }).catch((e) => {
8766
+ if (generation === loadGeneration.current) setRefreshError(plainLoadError(e instanceof Error && e.message ? e.message : "The vault could not be read."));
8767
+ }).finally(() => {
8716
8768
  inFlight.current = false;
8717
8769
  setRefreshing(false);
8718
8770
  });
@@ -8720,18 +8772,20 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8720
8772
  port.lists.recents?.list().then(setRecents, () => void 0);
8721
8773
  }, [port, apply]);
8722
8774
  const load = useCallback4(() => {
8775
+ const generation = ++loadGeneration.current;
8723
8776
  setError(null);
8724
8777
  setRefreshError(null);
8725
8778
  let finished = false;
8726
8779
  port.load((partial) => {
8727
- if (finished) return;
8780
+ if (finished || generation !== loadGeneration.current) return;
8728
8781
  setSnap(partial);
8729
8782
  setItems(partial.items);
8730
8783
  setDeleted(partial.deleted);
8731
8784
  }).then((s) => {
8732
8785
  finished = true;
8733
- apply(s);
8786
+ if (generation === loadGeneration.current) apply(s);
8734
8787
  }).catch((e) => {
8788
+ if (generation !== loadGeneration.current) return;
8735
8789
  setSignedOut(e?.status === 401);
8736
8790
  setError(e instanceof Error ? e.message : "The vault could not be loaded.");
8737
8791
  });
@@ -8740,7 +8794,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8740
8794
  port.lists.recents?.list().then(setRecents, () => void 0);
8741
8795
  }, [port, apply]);
8742
8796
  useEffect23(load, [load]);
8743
- const quietRef = useRef19({ loc, ready: false });
8797
+ const quietRef = useRef21({ loc, ready: false });
8744
8798
  quietRef.current = { loc, ready: snap !== null && !error };
8745
8799
  const [knobSeconds, setKnobSeconds] = useState41(null);
8746
8800
  useEffect23(() => {
@@ -8776,7 +8830,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8776
8830
  if (timer2) clearInterval(timer2);
8777
8831
  };
8778
8832
  }, [refresh, everySeconds]);
8779
- const inventoryRef = useRef19(onInventory);
8833
+ const inventoryRef = useRef21(onInventory);
8780
8834
  inventoryRef.current = onInventory;
8781
8835
  useEffect23(() => {
8782
8836
  if (snap) inventoryRef.current?.({ items, definitions: snap.definitions });
@@ -9940,7 +9994,7 @@ function itemOf(w, owner, defs, auth, state = "known", unavailable = [], invento
9940
9994
  fields: (w.fields ?? []).filter((f) => f.execution_purpose !== "passkey_private").map((f) => fieldOf(f, def)),
9941
9995
  custom: (w.non_secret_fields ?? []).map((c) => ({ key: c["key"] ?? "", label: c["label"] ?? humanize(c["key"] ?? ""), value: c["value"] ?? "" })),
9942
9996
  authenticator: a ? { digits: a.digits, period: a.period, enabled: a.enabled } : null,
9943
- authenticatorState: a ? "known" : state === "pending" ? "pending" : state === "unavailable" || scopeUnavailable(inventoryOwner, unavailable) ? "unavailable" : "known",
9997
+ authenticatorState: a ? "known" : state === "pending" ? "pending" : state === "unavailable" || scopeUnavailable(inventoryOwner, unavailable) || (w.fields ?? []).some((f) => f.field_key === OTP_SEED_KEY && f.is_active !== false) ? "unavailable" : "known",
9944
9998
  capabilities: { canUse: cap.can_use ?? false, canEdit: cap.can_edit ?? false, canReveal: cap.can_reveal ?? false, canManage: cap.can_manage ?? false },
9945
9999
  attachments: (w.attachments ?? []).map((a2) => ({ id: a2.id, label: a2.label, description: a2.description ?? "", fileName: a2.file_name, mediaType: a2.media_type ?? "application/octet-stream", sizeBytes: a2.size_bytes, handling: handlingOf(a2.handling), createdAt: a2.created_at, updatedAt: a2.updated_at })),
9946
10000
  uriMatchMode: MATCH_MODES2.find((m) => m === w.uri_match_mode) ?? "host",
@@ -9981,16 +10035,12 @@ function createApiVaultPort(hostApi, options = {}) {
9981
10035
  let authUnavailable = [];
9982
10036
  let inventoryOwners = /* @__PURE__ */ new Map();
9983
10037
  let orgs = [];
10038
+ let loadGeneration = 0;
10039
+ let authMutation = 0;
9984
10040
  const orgOf = (id, userId, mine) => {
9985
10041
  if (id) return { kind: "organization", label: orgs.find((o) => o.id === id)?.name ?? "Organization", organizationId: id };
9986
10042
  return mine || userId ? { kind: "me", label: "Personal", organizationId: null } : { kind: "shared", label: "Shared with me", organizationId: null };
9987
10043
  };
9988
- const ownerInOrgList = (w, via, meId) => {
9989
- if (w.organization_id) return orgOf(w.organization_id, null, false);
9990
- if (w.user_id && meId && w.user_id === meId) return { kind: "me", label: "Personal", organizationId: null };
9991
- if (via) return { kind: "organization", label: via.name, organizationId: via.id, memberShared: { userId: w.user_id ?? null } };
9992
- return { kind: "shared", label: "Shared with me", organizationId: null };
9993
- };
9994
10044
  async function refetch(id, owner) {
9995
10045
  const w = await account9.getItem(api, { path: { item_id: id } });
9996
10046
  return itemOf(w, owner, defs, auth, authState, authUnavailable, inventoryOwners.get(id) ?? owner);
@@ -10003,6 +10053,7 @@ function createApiVaultPort(hostApi, options = {}) {
10003
10053
  definition: (key) => defs.get(key),
10004
10054
  organizations: () => orgs,
10005
10055
  setAuthenticator: (itemId, entry) => {
10056
+ authMutation++;
10006
10057
  const next = new Map(auth);
10007
10058
  if (entry) next.set(itemId, entry);
10008
10059
  else next.delete(itemId);
@@ -10020,134 +10071,155 @@ function createApiVaultPort(hostApi, options = {}) {
10020
10071
  fields: createFieldsPort(ctx),
10021
10072
  lists: createListsPort(ctx, options.userState),
10022
10073
  async load(onProgress) {
10023
- authState = "pending";
10024
- auth = /* @__PURE__ */ new Map();
10025
- authUnavailable = [];
10026
- inventoryOwners = /* @__PURE__ */ new Map();
10027
- const catalogP = retrying(() => apps2.getAppCatalogKind(api, { path: { app: "matrx", kind: "credential_definition" } }));
10028
- const authP = retrying(() => apps2.listAuthenticators(api));
10029
- const userP = retrying(() => account9.listItems(api, { query: { principal_type: "user" } }));
10030
- const sharedP = retrying(() => account9.sharedWithMe(api));
10031
- const quiet = (p) => void p.catch(() => void 0);
10032
- [catalogP, authP, userP, sharedP].forEach(quiet);
10033
- const orgItemsP = retrying(() => account9.organizationItems(api));
10034
- quiet(orgItemsP);
10035
- const raw = {
10036
- orgLists: [],
10037
- orgsKnown: options.organizations !== void 0,
10038
- meId: null
10039
- };
10040
- const unavailable = [];
10041
- let done = false;
10042
- const build = () => {
10043
- const catalog2 = raw.catalog ?? [];
10044
- defs = new Map(parseDefinitions(catalog2.filter((e) => e.is_active).map((e) => ({ key: e.key, payload: e.payload, sort_order: e.sort_order }))).map((d) => [d.key, d]));
10045
- const mineItems = raw.mine?.items ?? [];
10046
- const meId = raw.meId ?? mineItems.find((w) => w.user_id)?.user_id ?? null;
10047
- const seen = /* @__PURE__ */ new Map();
10048
- const deleted = /* @__PURE__ */ new Map();
10049
- const take = (w, owner, inventoryOwner = owner) => {
10050
- if (seen.has(w.id) || deleted.has(w.id) || isHiddenItem(w)) return;
10051
- inventoryOwners.set(w.id, inventoryOwner);
10052
- const item = itemOf(w, owner, defs, auth, authState, authUnavailable, inventoryOwner);
10053
- (isDeleted(w) ? deleted : seen).set(w.id, item);
10074
+ const run = async (restarts) => {
10075
+ const generation = ++loadGeneration;
10076
+ const mutationAtStart = authMutation;
10077
+ let localAuth = /* @__PURE__ */ new Map();
10078
+ let localState = "pending";
10079
+ let localUnavailable = [];
10080
+ let localDefs = /* @__PURE__ */ new Map();
10081
+ let localOrgs = [];
10082
+ const localOwners = /* @__PURE__ */ new Map();
10083
+ const catalogP = retrying(() => apps2.getAppCatalogKind(api, { path: { app: "matrx", kind: "credential_definition" } }));
10084
+ const authP = retrying(() => apps2.listAuthenticators(api));
10085
+ const userP = retrying(() => account9.listItems(api, { query: { principal_type: "user" } }));
10086
+ const sharedP = retrying(() => account9.sharedWithMe(api));
10087
+ const quiet = (p) => void p.catch(() => void 0);
10088
+ [catalogP, authP, userP, sharedP].forEach(quiet);
10089
+ const orgItemsP = retrying(() => account9.organizationItems(api));
10090
+ quiet(orgItemsP);
10091
+ const raw = {
10092
+ orgLists: [],
10093
+ orgsKnown: options.organizations !== void 0,
10094
+ meId: null
10054
10095
  };
10055
- for (const w of mineItems) take(w, orgOf(w.organization_id, null, true));
10056
- raw.orgLists.forEach((l, i) => {
10057
- const via = orgs[i];
10058
- for (const w of l?.items ?? []) take(w, ownerInOrgList(w, via, meId));
10059
- });
10060
- for (const w of raw.shared?.items ?? []) take(w, w.organization_id ? orgOf(w.organization_id, null, false) : { kind: "shared", label: "Shared with me", organizationId: null }, { kind: "shared", label: "Shared with me", organizationId: null });
10061
- const pending = done ? [] : [...raw.orgsKnown ? orgs.filter((_, i) => raw.orgLists[i] === void 0).map((o) => o.name) : ["your organizations"], ...raw.shared ? [] : ["Shared with me"]];
10062
- return {
10063
- unavailable: [...unavailable],
10064
- ...pending.length > 0 ? { pending } : {},
10065
- items: [...seen.values()],
10066
- deleted: [...deleted.values()],
10067
- organizations: orgs,
10068
- definitions: [...defs.values()].sort((a, b) => a.sortOrder - b.sortOrder || a.label.localeCompare(b.label))
10096
+ const unavailable = [];
10097
+ let done = false;
10098
+ const build = () => {
10099
+ const catalog2 = raw.catalog ?? [];
10100
+ localDefs = new Map(parseDefinitions(catalog2.filter((e) => e.is_active).map((e) => ({ key: e.key, payload: e.payload, sort_order: e.sort_order }))).map((d) => [d.key, d]));
10101
+ const mineItems = raw.mine?.items ?? [];
10102
+ const meId = raw.meId ?? mineItems.find((w) => w.user_id)?.user_id ?? null;
10103
+ const seen = /* @__PURE__ */ new Map();
10104
+ const deleted = /* @__PURE__ */ new Map();
10105
+ const localOrgOf = (id, mine2) => id ? { kind: "organization", label: localOrgs.find((o) => o.id === id)?.name ?? "Organization", organizationId: id } : mine2 ? { kind: "me", label: "Personal", organizationId: null } : { kind: "shared", label: "Shared with me", organizationId: null };
10106
+ const take = (w, owner, inventoryOwner = owner) => {
10107
+ if (seen.has(w.id) || deleted.has(w.id) || isHiddenItem(w)) return;
10108
+ localOwners.set(w.id, inventoryOwner);
10109
+ const item = itemOf(w, owner, localDefs, localAuth, localState, localUnavailable, inventoryOwner);
10110
+ (isDeleted(w) ? deleted : seen).set(w.id, item);
10111
+ };
10112
+ for (const w of mineItems) take(w, localOrgOf(w.organization_id, true));
10113
+ raw.orgLists.forEach((l, i) => {
10114
+ const via = localOrgs[i];
10115
+ for (const w of l?.items ?? []) take(w, w.organization_id ? localOrgOf(w.organization_id, false) : w.user_id && meId && w.user_id === meId ? localOrgOf(null, true) : via ? { kind: "organization", label: via.name, organizationId: via.id, memberShared: { userId: w.user_id ?? null } } : localOrgOf(null, false));
10116
+ });
10117
+ for (const w of raw.shared?.items ?? []) take(w, w.organization_id ? localOrgOf(w.organization_id, false) : { kind: "shared", label: "Shared with me", organizationId: null }, { kind: "shared", label: "Shared with me", organizationId: null });
10118
+ const pending = done ? [] : [...raw.orgsKnown ? localOrgs.filter((_, i) => raw.orgLists[i] === void 0).map((o) => o.name) : ["your organizations"], ...raw.shared ? [] : ["Shared with me"]];
10119
+ return {
10120
+ unavailable: [...unavailable],
10121
+ ...pending.length > 0 ? { pending } : {},
10122
+ items: [...seen.values()],
10123
+ deleted: [...deleted.values()],
10124
+ organizations: localOrgs,
10125
+ definitions: [...localDefs.values()].sort((a, b) => a.sortOrder - b.sortOrder || a.label.localeCompare(b.label))
10126
+ };
10069
10127
  };
10070
- };
10071
- const progress = () => {
10072
- if (!done && onProgress && raw.catalog && raw.mine) onProgress(build());
10073
- };
10074
- const orgReportP = options.organizations ? null : retrying(() => platform.listMyOrganizations(api));
10075
- if (orgReportP) quiet(orgReportP);
10076
- const orgListsReady = (async () => {
10077
- const report = orgReportP ? await orgReportP : null;
10078
- raw.meId = report?.user_id ?? null;
10079
- orgs = [...new Map((options.organizations ?? (report?.organizations ?? []).filter((o) => !o.archived_at).map((o) => ({ id: o.id, name: o.name }))).map((o) => [o.id, o])).values()];
10080
- raw.orgLists = orgs.map(() => void 0);
10081
- raw.orgsKnown = true;
10082
- progress();
10083
- const one = await orgItemsP.then(
10084
- (r) => r,
10085
- (e) => {
10086
- if (e?.status === 404) {
10087
- hostProblem("GET /vault/organization-items answered 404; reading each organization vault separately", e);
10088
- return null;
10128
+ const progress = () => {
10129
+ if (!done && generation === loadGeneration && mutationAtStart === authMutation && onProgress && raw.catalog && raw.mine) onProgress(build());
10130
+ };
10131
+ const orgReportP = options.organizations ? null : retrying(() => platform.listMyOrganizations(api));
10132
+ if (orgReportP) quiet(orgReportP);
10133
+ const orgListsReady = (async () => {
10134
+ const report = orgReportP ? await orgReportP : null;
10135
+ raw.meId = report?.user_id ?? null;
10136
+ localOrgs = [...new Map((options.organizations ?? (report?.organizations ?? []).filter((o) => !o.archived_at).map((o) => ({ id: o.id, name: o.name }))).map((o) => [o.id, o])).values()];
10137
+ raw.orgLists = localOrgs.map(() => void 0);
10138
+ raw.orgsKnown = true;
10139
+ progress();
10140
+ const one = await orgItemsP.then(
10141
+ (r) => r,
10142
+ (e) => {
10143
+ if (e?.status === 404) {
10144
+ hostProblem("GET /vault/organization-items answered 404; reading each organization vault separately", e);
10145
+ return null;
10146
+ }
10147
+ return "failed";
10089
10148
  }
10090
- return "failed";
10149
+ );
10150
+ if (one) {
10151
+ const byOrg = one === "failed" ? /* @__PURE__ */ new Map() : new Map(one.views.map((v) => [v.organization_id, v.items]));
10152
+ const failed = one === "failed" ? new Set(localOrgs.map((o) => o.id)) : new Set(one.unavailable);
10153
+ const settled = localOrgs.map((o, i) => {
10154
+ const items = byOrg.get(o.id);
10155
+ if (failed.has(o.id) || items === void 0) return Promise.reject(new Error(`${o.name} is unavailable`));
10156
+ raw.orgLists[i] = { items, count: items.length };
10157
+ return Promise.resolve(raw.orgLists[i]);
10158
+ });
10159
+ settled.forEach(quiet);
10160
+ progress();
10161
+ return settled;
10091
10162
  }
10092
- );
10093
- if (one) {
10094
- const byOrg = one === "failed" ? /* @__PURE__ */ new Map() : new Map(one.views.map((v) => [v.organization_id, v.items]));
10095
- const failed = one === "failed" ? new Set(orgs.map((o) => o.id)) : new Set(one.unavailable);
10096
- const settled = orgs.map((o, i) => {
10097
- const items = byOrg.get(o.id);
10098
- if (failed.has(o.id) || items === void 0) return Promise.reject(new Error(`${o.name} is unavailable`));
10099
- raw.orgLists[i] = { items, count: items.length };
10100
- return Promise.resolve(raw.orgLists[i]);
10163
+ return localOrgs.map((o, i) => {
10164
+ const p = retrying(() => account9.listItems(api, { query: { principal_type: "organization", organization_id: o.id } }, { organizationId: o.id }));
10165
+ const arrived = p.then((l) => {
10166
+ raw.orgLists[i] = l;
10167
+ progress();
10168
+ return l;
10169
+ });
10170
+ quiet(arrived);
10171
+ return arrived;
10101
10172
  });
10102
- settled.forEach(quiet);
10173
+ })();
10174
+ quiet(orgListsReady);
10175
+ const authDone = authP.then((a) => {
10176
+ localUnavailable = a.unavailable_scopes ?? [];
10177
+ const fresh = new Map((a.entries ?? []).map((e) => [e.credential_item_id, e]));
10178
+ localAuth = fresh;
10179
+ localState = "known";
10180
+ if (localUnavailable.length) unavailable.push("One-time codes");
10103
10181
  progress();
10104
- return settled;
10105
- }
10106
- return orgs.map((o, i) => {
10107
- const p = retrying(() => account9.listItems(api, { query: { principal_type: "organization", organization_id: o.id } }, { organizationId: o.id }));
10108
- const arrived = p.then((l) => {
10109
- raw.orgLists[i] = l;
10110
- progress();
10111
- return l;
10112
- });
10113
- quiet(arrived);
10114
- return arrived;
10182
+ }, () => {
10183
+ localState = "unavailable";
10184
+ unavailable.push("One-time codes");
10115
10185
  });
10116
- })();
10117
- quiet(orgListsReady);
10118
- const authDone = authP.then((a) => {
10119
- authUnavailable = a.unavailable_scopes ?? [];
10120
- const fresh = new Map((a.entries ?? []).map((e) => [e.credential_item_id, e]));
10121
- auth = fresh;
10122
- authState = "known";
10123
- if (authUnavailable.length) unavailable.push("One-time codes");
10124
- progress();
10125
- }, () => {
10126
- authState = "unavailable";
10127
- unavailable.push("One-time codes");
10128
- });
10129
- const sharedDone = sharedP.then((sh) => {
10130
- raw.shared = sh;
10186
+ const sharedDone = sharedP.then((sh) => {
10187
+ raw.shared = sh;
10188
+ progress();
10189
+ }, () => {
10190
+ raw.shared = { items: [], count: 0 };
10191
+ unavailable.push("Shared with me");
10192
+ });
10193
+ const [catalog, mine] = await Promise.all([catalogP, userP]);
10194
+ raw.catalog = catalog;
10195
+ raw.mine = mine;
10131
10196
  progress();
10132
- }, () => {
10133
- raw.shared = { items: [], count: 0 };
10134
- unavailable.push("Shared with me");
10135
- });
10136
- const [catalog, mine] = await Promise.all([catalogP, userP]);
10137
- raw.catalog = catalog;
10138
- raw.mine = mine;
10139
- progress();
10140
- const orgPs = await orgListsReady;
10141
- const orgSettled = await Promise.allSettled(orgPs);
10142
- orgSettled.forEach((r, i) => {
10143
- if (r.status === "rejected") {
10144
- raw.orgLists[i] = { items: [], count: 0 };
10145
- unavailable.push(orgs[i]?.name ?? "An organization");
10197
+ const orgPs = await orgListsReady;
10198
+ const orgSettled = await Promise.allSettled(orgPs);
10199
+ orgSettled.forEach((r, i) => {
10200
+ if (r.status === "rejected") {
10201
+ raw.orgLists[i] = { items: [], count: 0 };
10202
+ unavailable.push(localOrgs[i]?.name ?? "An organization");
10203
+ }
10204
+ });
10205
+ await Promise.all([authDone, sharedDone]);
10206
+ done = true;
10207
+ if (generation === loadGeneration && mutationAtStart !== authMutation) {
10208
+ if (restarts >= 2) throw new Error("Authenticator changed while loading. Retry");
10209
+ return run(restarts + 1);
10146
10210
  }
10147
- });
10148
- await Promise.all([authDone, sharedDone]);
10149
- done = true;
10150
- return build();
10211
+ const snapshot = build();
10212
+ if (generation === loadGeneration) {
10213
+ defs = localDefs;
10214
+ auth = localAuth;
10215
+ authState = localState;
10216
+ authUnavailable = localUnavailable;
10217
+ inventoryOwners = localOwners;
10218
+ orgs = localOrgs;
10219
+ }
10220
+ return snapshot;
10221
+ };
10222
+ return run(0);
10151
10223
  },
10152
10224
  async reveal(item, fieldKey2) {
10153
10225
  const field = item.fields.find((f) => f.key === fieldKey2);