@ai-matrx/vault 0.1.55 → 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,7 +2182,9 @@ function normalizeWebsite(raw) {
2166
2182
  }
2167
2183
  function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChanged, onAdded, onDone }) {
2168
2184
  const confirmed = useConfirmed();
2169
- const candidates = items.filter((i) => !i.authenticator && !i.deletion && (i.capabilities.canEdit || i.capabilities.canManage)).sort((a, b) => a.name.localeCompare(b.name, void 0, { sensitivity: "base" }));
2185
+ const itemsRef = useRef9(items);
2186
+ itemsRef.current = items;
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);
2172
2190
  const [target, setTarget] = useState11(itemId && candidates.some((c) => c.id === itemId) ? itemId : NEW);
@@ -2194,18 +2212,26 @@ 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));
2208
- let next = { ...it, authenticator: { digits: entry.digits, period: entry.period, enabled: entry.enabled } };
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
+ });
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) {
2211
2237
  try {
@@ -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,8 @@ 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 }) {
2360
+ const [incomplete, setIncomplete] = useState12(true);
2346
2361
  const [entries, setEntries] = useState12(/* @__PURE__ */ new Map());
2347
2362
  const [hidden, setHidden] = useState12(/* @__PURE__ */ new Set());
2348
2363
  const [adding, setAdding] = useState12(false);
@@ -2352,13 +2367,20 @@ function AuthenticatorView({ port, host, items: listed, definitions, query, onOp
2352
2367
  useEffect10(() => {
2353
2368
  let live = true;
2354
2369
  port.authenticator.list().then(
2355
- (list) => live && setEntries(new Map(list.map((e) => [e.itemId, e]))),
2356
- (e) => console.error("One-time password accounts could not be read", e)
2370
+ (list) => {
2371
+ if (live) {
2372
+ setEntries(new Map(list.entries.map((e) => [e.itemId, e])));
2373
+ setIncomplete(list.unavailableScopes.length > 0);
2374
+ }
2375
+ },
2376
+ () => {
2377
+ if (live) setIncomplete(true);
2378
+ }
2357
2379
  );
2358
2380
  return () => {
2359
2381
  live = false;
2360
2382
  };
2361
- }, [port, listed, added]);
2383
+ }, [port, items]);
2362
2384
  const groups = otpGroups(items, entries, query, hidden);
2363
2385
  const total = items.filter((i) => i.authenticator && !i.deletion && !hidden.has(i.id)).length;
2364
2386
  const familyOf2 = (key) => definitions.find((d) => d.key === key)?.family ?? "generic";
@@ -2371,7 +2393,14 @@ function AuthenticatorView({ port, host, items: listed, definitions, query, onOp
2371
2393
  ] }),
2372
2394
  total > 0 ? /* @__PURE__ */ jsx11(Button9, { variant: "outline", icon: /* @__PURE__ */ jsx11(PlusGlyph, {}), onClick: () => setAdding(true), children: "Add" }) : null
2373
2395
  ] }),
2374
- total === 0 ? /* @__PURE__ */ jsxs10("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 px-6 pb-12 text-center", children: [
2396
+ total > 0 && (incomplete || items.some((i) => i.authenticatorState !== "known")) ? /* @__PURE__ */ jsxs10("div", { className: "px-4 text-sm text-muted-foreground", children: [
2397
+ "Some codes unavailable ",
2398
+ /* @__PURE__ */ jsx11(Button9, { variant: "quiet", onClick: onChanged, children: "Retry" })
2399
+ ] }) : null,
2400
+ total === 0 && (incomplete || items.some((i) => i.authenticatorState !== "known")) ? /* @__PURE__ */ jsxs10("div", { className: "px-4 py-10 text-center text-sm text-muted-foreground", children: [
2401
+ "Codes unavailable ",
2402
+ /* @__PURE__ */ jsx11(Button9, { variant: "quiet", onClick: onChanged, children: "Retry" })
2403
+ ] }) : total === 0 ? /* @__PURE__ */ jsxs10("div", { className: "flex flex-1 flex-col items-center justify-center gap-3 px-6 pb-12 text-center", children: [
2375
2404
  /* @__PURE__ */ jsx11("span", { className: "flex size-12 items-center justify-center rounded-full bg-muted text-muted-foreground", children: /* @__PURE__ */ jsx11(ShieldGlyph, { className: "size-6" }) }),
2376
2405
  /* @__PURE__ */ jsx11("p", { className: "text-sm font-medium text-foreground", children: "No authenticator codes yet" }),
2377
2406
  /* @__PURE__ */ jsx11(Button9, { variant: "primary", icon: /* @__PURE__ */ jsx11(PlusGlyph, {}), onClick: () => setAdding(true), children: "Add authenticator" })
@@ -2404,7 +2433,6 @@ function AuthenticatorView({ port, host, items: listed, definitions, query, onOp
2404
2433
  definitions,
2405
2434
  onClose: () => setAdding(false),
2406
2435
  onChanged,
2407
- onAdded: (item) => setAdded((cur) => new Map(cur).set(item.id, item)),
2408
2436
  onDone: (id) => {
2409
2437
  setAdding(false);
2410
2438
  onOpen(id);
@@ -2522,7 +2550,7 @@ function OtpListRow({ port, item, family, entry, onOpen, onOpenInNewTab, onRenam
2522
2550
  }
2523
2551
 
2524
2552
  // src/fields/SettingsPanels.tsx
2525
- import { useRef as useRef8, useState as useState13 } from "react";
2553
+ import { useRef as useRef10, useState as useState13 } from "react";
2526
2554
  import { Badge as Badge3, Button as Button10, DisclosureHeader, Field as Field4, SegmentedControl, Select as Select3, Switch } from "@ai-matrx/design-system/controls";
2527
2555
  import { confirm as confirm3 } from "@ai-matrx/kit/confirm-opener";
2528
2556
  import { GlobeIcon } from "@ai-matrx/design-system/icons";
@@ -2655,7 +2683,7 @@ function FieldSettings({ item, port, field, onClose, onChanged }) {
2655
2683
  const { busy, error, setError, save } = useSaver((next) => onChanged(next));
2656
2684
  const [envDraft, setEnvDraft] = useState13(field.envKey ?? "");
2657
2685
  const [descDraft, setDescDraft] = useState13(field.description);
2658
- const envRef = useRef8(null);
2686
+ const envRef = useRef10(null);
2659
2687
  const canEdit = item.capabilities.canEdit;
2660
2688
  const envProblem = envKeyProblem(envDraft);
2661
2689
  const blocker = sandboxBlocker(envDraft, field.envKey);
@@ -2819,7 +2847,7 @@ function ItemSettingsPanel({ item, port, onClose, onChanged, onPanel }) {
2819
2847
  }
2820
2848
 
2821
2849
  // src/generator/PasswordGenerator.tsx
2822
- 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";
2823
2851
  import { Popover, PopoverContent, PopoverTrigger, cn as cn6 } from "@ai-matrx/design-system";
2824
2852
  import { Button as Button11, NumberStepper, SegmentedControl as SegmentedControl2, Select as Select4, Switch as Switch2 } from "@ai-matrx/design-system/controls";
2825
2853
  import { CopyIcon as CopyIcon6 } from "@ai-matrx/design-system/icons";
@@ -2897,7 +2925,7 @@ function PasswordGenerator({ limits: source, onUse, useLabel = "Use" }) {
2897
2925
  const [revealed, setRevealed] = useState14(false);
2898
2926
  const [note, setNote] = useState14(null);
2899
2927
  const copyValue2 = useCopy();
2900
- const expiry = useRef9(null);
2928
+ const expiry = useRef11(null);
2901
2929
  const drop = () => {
2902
2930
  if (expiry.current) clearTimeout(expiry.current);
2903
2931
  expiry.current = null;
@@ -3123,11 +3151,11 @@ function organizationHints(orgs) {
3123
3151
  }
3124
3152
 
3125
3153
  // src/shown-until-move.ts
3126
- 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";
3127
3155
  var keyOf = (loc) => `${String(loc.vault)}|${loc.view}|${loc.itemId ?? ""}|${loc.mode}|${loc.panel ?? ""}|${loc.kind ?? ""}`;
3128
3156
  function useShownUntilMove(loc) {
3129
3157
  const [shown, setShown] = useState15(null);
3130
- const home = useRef10(null);
3158
+ const home = useRef12(null);
3131
3159
  const key = keyOf(loc);
3132
3160
  useEffect12(() => {
3133
3161
  if (!shown) {
@@ -3185,7 +3213,7 @@ function useLocked(lock) {
3185
3213
 
3186
3214
  // src/io/BackupPanel.tsx
3187
3215
  import { formatFileSize } from "@ai-matrx/kit/format";
3188
- 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";
3189
3217
  import { Button as Button13, Field as Field5, SegmentedControl as SegmentedControl3, Switch as Switch3 } from "@ai-matrx/design-system/controls";
3190
3218
  import { downloadFile } from "@ai-matrx/kit/download";
3191
3219
  import { confirm as confirm4 } from "@ai-matrx/kit/confirm-opener";
@@ -3342,8 +3370,8 @@ function Omissions({ omissions, accepted, onAccepted, verb }) {
3342
3370
  function BackupPanel({ port, host, items, onClose, onChanged }) {
3343
3371
  const minLength = passphraseMinimum(host.backupPassphraseMinLength);
3344
3372
  const confirmed = useConfirmed();
3345
- const fileInput = useRef11(null);
3346
- const abort = useRef11(null);
3373
+ const fileInput = useRef13(null);
3374
+ const abort = useRef13(null);
3347
3375
  const [mode, setMode] = useState17("backup");
3348
3376
  const [busy, setBusy] = useState17(false);
3349
3377
  const [error, setError] = useState17(null);
@@ -3695,7 +3723,7 @@ function ExportPanel({ port, items, onClose }) {
3695
3723
  }
3696
3724
 
3697
3725
  // src/io/ImportPanel.tsx
3698
- 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";
3699
3727
  import { Button as Button15, Select as Select6, Switch as Switch4, Textarea as Textarea2 } from "@ai-matrx/design-system/controls";
3700
3728
 
3701
3729
  // src/io/csv-import.ts
@@ -4558,10 +4586,10 @@ var ME = "me";
4558
4586
  function ImportPanel({ port, host, items, definitions, organizations, onClose, onChanged, limits }) {
4559
4587
  const confirmed = useConfirmed();
4560
4588
  const [otpFor, setOtpFor] = useState19(null);
4561
- const fileInput = useRef12(null);
4562
- const abort = useRef12(null);
4563
- const stop = useRef12(false);
4564
- 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());
4565
4593
  const [source, setSource] = useState19("auto");
4566
4594
  const [envText, setEnvText] = useState19("");
4567
4595
  const [injectSandbox, setInjectSandbox] = useState19(false);
@@ -4862,14 +4890,14 @@ function Notices({ draft }) {
4862
4890
  }
4863
4891
 
4864
4892
  // src/ItemList.tsx
4865
- 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";
4866
4894
  import { DropdownMenu as DropdownMenu4, DropdownMenuContent as DropdownMenuContent4, DropdownMenuItem as DropdownMenuItem4, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator as DropdownMenuSeparator4, DropdownMenuTrigger as DropdownMenuTrigger4 } from "@ai-matrx/design-system";
4867
4895
  import { cn as cn11 } from "@ai-matrx/design-system";
4868
4896
  import { Button as Button23, SearchField, Tile as Tile3 } from "@ai-matrx/design-system/controls";
4869
4897
  import { CopyIcon as CopyIcon8 } from "@ai-matrx/design-system/icons";
4870
4898
 
4871
4899
  // src/ItemPane.tsx
4872
- 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";
4873
4901
  import { DropdownMenu as DropdownMenu3, DropdownMenuContent as DropdownMenuContent3, DropdownMenuItem as DropdownMenuItem3, DropdownMenuSeparator as DropdownMenuSeparator3, DropdownMenuTrigger as DropdownMenuTrigger3 } from "@ai-matrx/design-system";
4874
4902
  import { cn as cn10 } from "@ai-matrx/design-system";
4875
4903
  import { Badge as Badge7, Button as Button22, CONTROLS_CONTAINER_CLASS, OptionCombobox, Switch as Switch6 } from "@ai-matrx/design-system/controls";
@@ -4877,7 +4905,7 @@ import { confirm as confirm10 } from "@ai-matrx/kit/confirm-opener";
4877
4905
 
4878
4906
  // src/activity/FilesSection.tsx
4879
4907
  import { formatFileSize as formatFileSize2 } from "@ai-matrx/kit/format";
4880
- import { useRef as useRef13, useState as useState20 } from "react";
4908
+ import { useRef as useRef15, useState as useState20 } from "react";
4881
4909
  import { Badge as Badge5, Button as Button16, Field as Field6, SegmentedControl as SegmentedControl4 } from "@ai-matrx/design-system/controls";
4882
4910
  import { cn as cn7 } from "@ai-matrx/design-system";
4883
4911
  import { confirm as confirm6 } from "@ai-matrx/kit/confirm-opener";
@@ -4899,7 +4927,7 @@ function FilesSection({ item, port, editing, onChanged }) {
4899
4927
  const [error, setError] = useState20(null);
4900
4928
  const [pending, setPending] = useState20(null);
4901
4929
  const [dragging, setDragging] = useState20(false);
4902
- const picker = useRef13(null);
4930
+ const picker = useRef15(null);
4903
4931
  if (!canAdd && item.attachments.length === 0) return null;
4904
4932
  async function run(key, work, fallback) {
4905
4933
  setBusy(key);
@@ -4977,7 +5005,7 @@ function FilesSection({ item, port, editing, onChanged }) {
4977
5005
  }
4978
5006
  function EditableFile({ port, item: it, file: a, busy: b, run: go, onDownload, onTooBig }) {
4979
5007
  const [open, setOpen] = useState20(false);
4980
- const replacer = useRef13(null);
5008
+ const replacer = useRef15(null);
4981
5009
  async function remove() {
4982
5010
  const yes = await confirm6({ title: `Delete ${a.label}?`, description: "The encrypted file is removed from this item.", confirmLabel: "Delete", variant: "destructive" });
4983
5011
  if (yes) await go(`delete:${a.id}`, () => port.activity.deleteFile(it, a.id), "The file could not be deleted.");
@@ -5089,7 +5117,7 @@ function DraftFilesSection({ files, onFiles }) {
5089
5117
  const [pending, setPending] = useState20(null);
5090
5118
  const [error, setError] = useState20(null);
5091
5119
  const [dragging, setDragging] = useState20(false);
5092
- const picker = useRef13(null);
5120
+ const picker = useRef15(null);
5093
5121
  const choose = (file) => {
5094
5122
  if (!file) return;
5095
5123
  if (fileTooBig(file.size)) return setError(TOO_BIG);
@@ -6012,6 +6040,10 @@ function confirmDelete(item) {
6012
6040
  function ItemPane(props) {
6013
6041
  const { port, item, definition, mode, host, organizations, defaultOwner, seed, onBack, onMode, onSaved, onDeleted, onDirty, onCancelNew, onPanel, onChanged, favorite, notice, refreshKey } = props;
6014
6042
  const confirmed = useConfirmed();
6043
+ const latestItem = useRef16(item);
6044
+ latestItem.current = item;
6045
+ const [createdForRetry, setCreatedForRetry] = useState27(null);
6046
+ const enrolledForRetry = useRef16(null);
6015
6047
  const notify = useNotify();
6016
6048
  const editing = mode !== "view";
6017
6049
  const seedName = seed?.name ?? "";
@@ -6044,8 +6076,8 @@ function ItemPane(props) {
6044
6076
  const [savedNew, setSavedNew] = useState27(false);
6045
6077
  const dirty = editing && !savedNew && isDirty(base, draft);
6046
6078
  const declared = definition?.fields ?? [];
6047
- const prev = useRef14(null);
6048
- const paneRef = useRef14(null);
6079
+ const prev = useRef16(null);
6080
+ const paneRef = useRef16(null);
6049
6081
  const room = useRoomOf(paneRef);
6050
6082
  useEffect16(() => {
6051
6083
  const was = prev.current;
@@ -6067,7 +6099,7 @@ function ItemPane(props) {
6067
6099
  setShowRecovery(false);
6068
6100
  setSavedNew(false);
6069
6101
  }, [item?.id, mode]);
6070
- const onDirtyRef = useRef14(onDirty);
6102
+ const onDirtyRef = useRef16(onDirty);
6071
6103
  onDirtyRef.current = onDirty;
6072
6104
  useEffect16(() => onDirtyRef.current(dirty), [dirty]);
6073
6105
  const keepKey = draftKey(item?.id ?? null, definition?.key ?? item?.definitionKey ?? null);
@@ -6097,23 +6129,39 @@ function ItemPane(props) {
6097
6129
  setError(null);
6098
6130
  const out = { ...draft, values: Object.fromEntries(changedValues(base, draft)) };
6099
6131
  if (out.values[RECOVERY_CODES_KEY] !== void 0) out.values[RECOVERY_CODES_KEY] = parseRecoveryCodes(out.values[RECOVERY_CODES_KEY]).join("\n");
6100
- const withOtp = async (saved, notice2) => {
6132
+ const withOtp = async (saved, notice2, freshlyCreated = false) => {
6101
6133
  const setup = out.authenticator.trim();
6102
6134
  if (!setup) return [saved, notice2, false];
6103
6135
  try {
6104
- 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
+ });
6105
6144
  return [await port.get(saved), notice2, true];
6106
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];
6107
6147
  const why = wasCancelled(e) ? "it was not confirmed" : e instanceof Error ? e.message : "it was refused";
6108
6148
  return [saved, `Saved, but the authenticator was not set up: ${why}. Add it with Edit.`, false];
6109
6149
  }
6110
6150
  };
6111
6151
  try {
6112
- if (item) {
6113
- const r = await port.save(item, definition, out);
6152
+ if (item || createdForRetry) {
6153
+ const r = await port.save(item ?? createdForRetry, definition, out);
6114
6154
  const [next, n, enrolled] = await withOtp(r.item, r.notice);
6115
- dropKeptDraft(keepKey);
6116
- 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
+ }
6117
6165
  } else if (definition && owner === "person") {
6118
6166
  const secretKey = declared.find((f) => f.storage === "encrypted" && f.kind === "secret")?.key ?? null;
6119
6167
  const r = await port.sharing.assign({ recipientEmail: recipient.trim(), definition, draft: out, generateFieldKey: generatePrivately ? secretKey : null });
@@ -6123,12 +6171,14 @@ function ItemPane(props) {
6123
6171
  setSent(r);
6124
6172
  } else if (definition) {
6125
6173
  const r = await port.create(definition, owner === "me" ? null : owner, out);
6126
- const [withCode, n0, enrolled] = await withOtp(r.item, r.notice);
6127
- const [next, n] = await storeQueuedFiles(withCode, n0);
6128
- dropKeptDraft(keepKey);
6129
- setSavedNew(true);
6130
- onDirty(false);
6131
- 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);
6132
6182
  }
6133
6183
  } catch (e) {
6134
6184
  setError(e instanceof Error ? e.message : "Saving failed. Nothing was changed.");
@@ -6136,6 +6186,13 @@ function ItemPane(props) {
6136
6186
  setBusy(false);
6137
6187
  }
6138
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
+ }
6139
6196
  async function storeQueuedFiles(saved, notice2) {
6140
6197
  let next = saved;
6141
6198
  const failed = [];
@@ -6317,7 +6374,11 @@ function ItemPane(props) {
6317
6374
  const filesProps = item ? { item, port, host, definition, organizations, arg: null, onClose: () => void 0, onChanged, onPanel } : null;
6318
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: [
6319
6376
  header,
6320
- 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,
6321
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,
6322
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: [
6323
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` }),
@@ -6421,7 +6482,11 @@ function ItemPane(props) {
6421
6482
  i
6422
6483
  )),
6423
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" }),
6424
- /* @__PURE__ */ jsx25(TotpEditRow, { value: draft.authenticator, onChange: (v) => patch({ authenticator: v }), hasExisting: Boolean(item?.authenticator || seedField), item, port, onEnrolled: onChanged }),
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: [
6487
+ /* @__PURE__ */ jsx25("span", { className: "text-muted-foreground", children: item.authenticatorState === "pending" ? "Loading" : "Unavailable" }),
6488
+ item.authenticatorState === "unavailable" ? /* @__PURE__ */ jsx25(Button22, { variant: "quiet", onClick: () => onChanged?.(), children: "Retry" }) : null
6489
+ ] }) : null,
6425
6490
  item && isLogin && !item.authenticator ? /* @__PURE__ */ jsx25(TwoFactorMethodRow, { port, item, onChanged }) : null,
6426
6491
  !item && isLogin && draft.authenticator.trim() === "" ? /* @__PURE__ */ jsx25(DraftTwoFactorRow, { custom: draft.custom, onCustom: (custom) => patch({ custom }) }) : null,
6427
6492
  recoveryEditing ? /* @__PURE__ */ jsx25(
@@ -6500,7 +6565,10 @@ function ItemPane(props) {
6500
6565
  }),
6501
6566
  extraFields.map((f) => isRecoveryCodesField(f) ? /* @__PURE__ */ jsx25(RecoveryCodesRow, { item, field: f, port, onChanged }, f.id) : /* @__PURE__ */ jsx25(SecretRow, { port, item, field: f, onTurnOn: turnOnOne(f), extra: fieldSettings(f) }, f.id)),
6502
6567
  item.loginUrls.length > 0 ? item.loginUrls.map((u, i) => /* @__PURE__ */ jsx25(PlainRow, { label: i === 0 ? "Website" : `Website ${i + 1}`, value: u, openUrl: host.openUrl }, `${u}-${i}`)) : /* @__PURE__ */ jsx25(AddRow, { label: "Website", onAdd: canEdit ? () => onMode("edit") : null, children: "Add website" }),
6503
- item.authenticator ? /* @__PURE__ */ jsx25(TotpRow, { port, item, onChanged }) : seedField && !seedField.active ? (
6568
+ item.authenticator ? /* @__PURE__ */ jsx25(TotpRow, { port, item, onChanged }) : item.authenticatorState !== "known" ? /* @__PURE__ */ jsxs23(VaultRow, { label: "Authenticator", children: [
6569
+ /* @__PURE__ */ jsx25("span", { className: "text-muted-foreground", children: item.authenticatorState === "pending" ? "Loading" : "Unavailable" }),
6570
+ item.authenticatorState === "unavailable" ? /* @__PURE__ */ jsx25(Button22, { variant: "quiet", onClick: () => onChanged?.(), children: "Retry" }) : null
6571
+ ] }) : seedField && !seedField.active ? (
6504
6572
  // The secret is still there, only off: turning it on brings the codes back. Never "Set up",
6505
6573
  // which would invite replacing a working two-factor secret.
6506
6574
  /* @__PURE__ */ jsx25(VaultRow, { label: "Authenticator", actions: canTurnOn ? /* @__PURE__ */ jsx25(TurnOnButton, { label: "Authenticator", onTurnOn: () => turnOn([seedField]) }) : null, children: /* @__PURE__ */ jsx25("span", { className: "text-muted-foreground", children: "Turned off" }) })
@@ -6577,7 +6645,7 @@ function ItemList({ title, items, definitions, port, selectedId, onSelect, query
6577
6645
  const pinned = matchingIds ? items.filter((i) => matchingIds.has(i.id)) : [];
6578
6646
  const rest = matchingIds ? items.filter((i) => !matchingIds.has(i.id)) : items;
6579
6647
  const ordered = [...pinned, ...rest];
6580
- const listRef = useRef15(null);
6648
+ const listRef = useRef17(null);
6581
6649
  useEffect17(() => {
6582
6650
  if (selectedId) listRef.current?.querySelector(`[data-item="${selectedId}"]`)?.scrollIntoView({ block: "nearest" });
6583
6651
  }, [selectedId, items.length]);
@@ -6681,7 +6749,7 @@ function ListRow({ item, family, port, on, onSelect, withOwner, ownContextMenu,
6681
6749
  ...manageEntries(item, onAction),
6682
6750
  ...hostEntries.map((h) => ({ ...h, kind: h.kind ?? "host" }))
6683
6751
  ];
6684
- const entriesRef = useRef15(entries);
6752
+ const entriesRef = useRef17(entries);
6685
6753
  entriesRef.current = entries;
6686
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" });
6687
6755
  const menuItem = (e) => /* @__PURE__ */ jsxs24(DropdownMenuItem4, { disabled: e.disabled ?? false, onSelect: e.onSelect, children: [
@@ -7088,7 +7156,7 @@ function NewItemFlow({ definitions, url, onUrl, onChoose, onCancel }) {
7088
7156
  }
7089
7157
 
7090
7158
  // src/sharing/SecureLinkPanel.tsx
7091
- 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";
7092
7160
  import { confirm as confirm11 } from "@ai-matrx/kit/confirm-opener";
7093
7161
  import { Checkbox as Checkbox3 } from "@ai-matrx/design-system";
7094
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";
@@ -7213,7 +7281,7 @@ function SecureLinkPanel({ item, port, onClose }) {
7213
7281
  };
7214
7282
  }, [sharing, item, nonce]);
7215
7283
  const [phoneTyped, setPhoneTyped] = useState32(false);
7216
- const prefilled = useRef16(false);
7284
+ const prefilled = useRef18(false);
7217
7285
  useEffect19(() => {
7218
7286
  const lookup = sharing.phoneOnFile;
7219
7287
  if (!lookup || phoneTyped) return;
@@ -7690,19 +7758,19 @@ async function loadFullPreview(bridge, operationId, current) {
7690
7758
  }
7691
7759
 
7692
7760
  // src/native/use-native-import.ts
7693
- 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";
7694
7762
  var fail = (e, fallback) => e instanceof Error && e.message ? e.message : fallback;
7695
7763
  var RESTART = "Your account changed. The import was stopped; start it again.";
7696
7764
  function useNativeImport(bridge, stillAllowed, onImported) {
7697
7765
  const [screen, setScreen] = useState34({ kind: "idle", message: null });
7698
7766
  const [busy, setBusy] = useState34(false);
7699
- const generation = useRef17(0);
7700
- const mounted = useRef17(false);
7701
- const op = useRef17(null);
7702
- const last = useRef17(null);
7703
- const timer2 = useRef17(null);
7704
- const polls = useRef17(0);
7705
- 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);
7706
7774
  const current = (g) => mounted.current && g === generation.current;
7707
7775
  const stopPolling = () => {
7708
7776
  if (timer2.current !== null) clearTimeout(timer2.current);
@@ -7871,7 +7939,7 @@ function useNativeImport(bridge, stillAllowed, onImported) {
7871
7939
  setBusy(false);
7872
7940
  setScreen({ kind: "idle", message: null });
7873
7941
  };
7874
- const invalidateRef = useRef17(invalidate);
7942
+ const invalidateRef = useRef19(invalidate);
7875
7943
  invalidateRef.current = invalidate;
7876
7944
  useEffect21(() => {
7877
7945
  mounted.current = true;
@@ -8043,13 +8111,13 @@ function CaptureCard({ capture, onChanged }) {
8043
8111
  }
8044
8112
 
8045
8113
  // src/extension/FillSetupCard.tsx
8046
- 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";
8047
8115
  import { Button as Button31, Field as Field12 } from "@ai-matrx/design-system/controls";
8048
8116
  import { jsx as jsx34, jsxs as jsxs32 } from "react/jsx-runtime";
8049
8117
  function FillSetupCard({ fillSetup }) {
8050
8118
  const notify = useNotify();
8051
8119
  const state = useSyncExternalStore4(fillSetup.subscribe, fillSetup.get, fillSetup.get);
8052
- const box = useRef18(null);
8120
+ const box = useRef20(null);
8053
8121
  const [busy, setBusy] = useState37(false);
8054
8122
  const [error, setError] = useState37(null);
8055
8123
  if (state.status !== "off") return null;
@@ -8589,7 +8657,7 @@ function LockedScreen({ lock }) {
8589
8657
  ] });
8590
8658
  }
8591
8659
  function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocationChange, onInventory }) {
8592
- const rootRef = useRef19(null);
8660
+ const rootRef = useRef21(null);
8593
8661
  const width = useElementWidth(rootRef);
8594
8662
  const mode = layout !== "auto" ? layout : width >= FULL_AT ? "full" : width >= SPLIT_AT ? "split" : "stack";
8595
8663
  const notify = useNotify();
@@ -8601,7 +8669,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8601
8669
  const [error, setError] = useState41(null);
8602
8670
  const [inner, setInner] = useState41(DEFAULT_LOCATION);
8603
8671
  const loc = location ?? inner;
8604
- const dirty = useRef19(false);
8672
+ const dirty = useRef21(false);
8605
8673
  const [notice, setNotice] = useState41(null);
8606
8674
  const [firstCode, setFirstCode] = useShownUntilMove(loc);
8607
8675
  const move = useCallback4(
@@ -8630,7 +8698,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8630
8698
  },
8631
8699
  [loc, move]
8632
8700
  );
8633
- const keyState = useRef19({ loc, go });
8701
+ const keyState = useRef21({ loc, go });
8634
8702
  keyState.current = { loc, go };
8635
8703
  useEffect23(() => {
8636
8704
  const onKey = (e) => {
@@ -8654,7 +8722,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8654
8722
  document.addEventListener("keydown", onKey);
8655
8723
  return () => document.removeEventListener("keydown", onKey);
8656
8724
  }, []);
8657
- const keepSearchFocus = useRef19(false);
8725
+ const keepSearchFocus = useRef21(false);
8658
8726
  useEffect23(() => {
8659
8727
  if (!keepSearchFocus.current) return;
8660
8728
  keepSearchFocus.current = false;
@@ -8664,13 +8732,14 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8664
8732
  search.setSelectionRange(search.value.length, search.value.length);
8665
8733
  }, [loc.q]);
8666
8734
  const hasPrompts = useHostPrompts(host.capture, host.fillSetup);
8667
- const knobsRef = useRef19(host.knobs);
8735
+ const knobsRef = useRef21(host.knobs);
8668
8736
  knobsRef.current = host.knobs;
8669
8737
  const [signedOut, setSignedOut] = useState41(false);
8670
8738
  const [refreshError, setRefreshError] = useState41(null);
8671
8739
  const [refreshing, setRefreshing] = useState41(false);
8672
- const loadedAt = useRef19(0);
8673
- const inFlight = useRef19(false);
8740
+ const loadedAt = useRef21(0);
8741
+ const inFlight = useRef21(false);
8742
+ const loadGeneration = useRef21(0);
8674
8743
  const apply = useCallback4(
8675
8744
  (s) => {
8676
8745
  loadedAt.current = Date.now();
@@ -8687,11 +8756,15 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8687
8756
  const refresh = useCallback4(() => {
8688
8757
  if (inFlight.current) return;
8689
8758
  inFlight.current = true;
8759
+ const generation = ++loadGeneration.current;
8690
8760
  setRefreshing(true);
8691
8761
  port.load().then((s) => {
8762
+ if (generation !== loadGeneration.current) return;
8692
8763
  setRefreshError(null);
8693
8764
  apply(s);
8694
- }).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(() => {
8695
8768
  inFlight.current = false;
8696
8769
  setRefreshing(false);
8697
8770
  });
@@ -8699,18 +8772,20 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8699
8772
  port.lists.recents?.list().then(setRecents, () => void 0);
8700
8773
  }, [port, apply]);
8701
8774
  const load = useCallback4(() => {
8775
+ const generation = ++loadGeneration.current;
8702
8776
  setError(null);
8703
8777
  setRefreshError(null);
8704
8778
  let finished = false;
8705
8779
  port.load((partial) => {
8706
- if (finished) return;
8780
+ if (finished || generation !== loadGeneration.current) return;
8707
8781
  setSnap(partial);
8708
8782
  setItems(partial.items);
8709
8783
  setDeleted(partial.deleted);
8710
8784
  }).then((s) => {
8711
8785
  finished = true;
8712
- apply(s);
8786
+ if (generation === loadGeneration.current) apply(s);
8713
8787
  }).catch((e) => {
8788
+ if (generation !== loadGeneration.current) return;
8714
8789
  setSignedOut(e?.status === 401);
8715
8790
  setError(e instanceof Error ? e.message : "The vault could not be loaded.");
8716
8791
  });
@@ -8719,7 +8794,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8719
8794
  port.lists.recents?.list().then(setRecents, () => void 0);
8720
8795
  }, [port, apply]);
8721
8796
  useEffect23(load, [load]);
8722
- const quietRef = useRef19({ loc, ready: false });
8797
+ const quietRef = useRef21({ loc, ready: false });
8723
8798
  quietRef.current = { loc, ready: snap !== null && !error };
8724
8799
  const [knobSeconds, setKnobSeconds] = useState41(null);
8725
8800
  useEffect23(() => {
@@ -8755,7 +8830,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
8755
8830
  if (timer2) clearInterval(timer2);
8756
8831
  };
8757
8832
  }, [refresh, everySeconds]);
8758
- const inventoryRef = useRef19(onInventory);
8833
+ const inventoryRef = useRef21(onInventory);
8759
8834
  inventoryRef.current = onInventory;
8760
8835
  useEffect23(() => {
8761
8836
  if (snap) inventoryRef.current?.({ items, definitions: snap.definitions });
@@ -9329,7 +9404,7 @@ function createAuthenticatorPort(ctx) {
9329
9404
  return {
9330
9405
  async list() {
9331
9406
  const r = await apps.listAuthenticators(api);
9332
- return (r.entries ?? []).map(entryOf);
9407
+ return { entries: (r.entries ?? []).map(entryOf), unavailableScopes: (r.unavailable_scopes ?? []).map((s) => ({ type: s.type, organizationId: s.organization_id ?? null })) };
9333
9408
  },
9334
9409
  async get(item) {
9335
9410
  try {
@@ -9543,6 +9618,7 @@ function trashItem(r, ctx) {
9543
9618
  fields: [],
9544
9619
  custom: [],
9545
9620
  authenticator: null,
9621
+ authenticatorState: "known",
9546
9622
  capabilities: { canUse: false, canEdit: false, canReveal: false, canManage: true },
9547
9623
  attachments: [],
9548
9624
  uriMatchMode: "host",
@@ -9899,7 +9975,8 @@ function fieldOf(w, def) {
9899
9975
  active: w.is_active ?? true
9900
9976
  };
9901
9977
  }
9902
- function itemOf(w, owner, defs, auth) {
9978
+ var scopeUnavailable = (owner, scopes) => scopes.some((s) => s.type === (owner.kind === "me" ? "user" : owner.kind === "organization" ? "organization" : "shared") && (s.type !== "organization" || !s.organization_id || s.organization_id === owner.organizationId));
9979
+ function itemOf(w, owner, defs, auth, state = "known", unavailable = [], inventoryOwner = owner) {
9903
9980
  const def = defs.get(w.definition_key ?? "");
9904
9981
  const a = auth.get(w.id);
9905
9982
  const cap = w.capabilities ?? {};
@@ -9917,6 +9994,7 @@ function itemOf(w, owner, defs, auth) {
9917
9994
  fields: (w.fields ?? []).filter((f) => f.execution_purpose !== "passkey_private").map((f) => fieldOf(f, def)),
9918
9995
  custom: (w.non_secret_fields ?? []).map((c) => ({ key: c["key"] ?? "", label: c["label"] ?? humanize(c["key"] ?? ""), value: c["value"] ?? "" })),
9919
9996
  authenticator: a ? { digits: a.digits, period: a.period, enabled: a.enabled } : null,
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",
9920
9998
  capabilities: { canUse: cap.can_use ?? false, canEdit: cap.can_edit ?? false, canReveal: cap.can_reveal ?? false, canManage: cap.can_manage ?? false },
9921
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 })),
9922
10000
  uriMatchMode: MATCH_MODES2.find((m) => m === w.uri_match_mode) ?? "host",
@@ -9953,20 +10031,19 @@ function createApiVaultPort(hostApi, options = {}) {
9953
10031
  };
9954
10032
  let defs = /* @__PURE__ */ new Map();
9955
10033
  let auth = /* @__PURE__ */ new Map();
10034
+ let authState = "pending";
10035
+ let authUnavailable = [];
10036
+ let inventoryOwners = /* @__PURE__ */ new Map();
9956
10037
  let orgs = [];
10038
+ let loadGeneration = 0;
10039
+ let authMutation = 0;
9957
10040
  const orgOf = (id, userId, mine) => {
9958
10041
  if (id) return { kind: "organization", label: orgs.find((o) => o.id === id)?.name ?? "Organization", organizationId: id };
9959
10042
  return mine || userId ? { kind: "me", label: "Personal", organizationId: null } : { kind: "shared", label: "Shared with me", organizationId: null };
9960
10043
  };
9961
- const ownerInOrgList = (w, via, meId) => {
9962
- if (w.organization_id) return orgOf(w.organization_id, null, false);
9963
- if (w.user_id && meId && w.user_id === meId) return { kind: "me", label: "Personal", organizationId: null };
9964
- if (via) return { kind: "organization", label: via.name, organizationId: via.id, memberShared: { userId: w.user_id ?? null } };
9965
- return { kind: "shared", label: "Shared with me", organizationId: null };
9966
- };
9967
10044
  async function refetch(id, owner) {
9968
10045
  const w = await account9.getItem(api, { path: { item_id: id } });
9969
- return itemOf(w, owner, defs, auth);
10046
+ return itemOf(w, owner, defs, auth, authState, authUnavailable, inventoryOwners.get(id) ?? owner);
9970
10047
  }
9971
10048
  const visibleCache = /* @__PURE__ */ new Map();
9972
10049
  const ctx = {
@@ -9976,6 +10053,7 @@ function createApiVaultPort(hostApi, options = {}) {
9976
10053
  definition: (key) => defs.get(key),
9977
10054
  organizations: () => orgs,
9978
10055
  setAuthenticator: (itemId, entry) => {
10056
+ authMutation++;
9979
10057
  const next = new Map(auth);
9980
10058
  if (entry) next.set(itemId, entry);
9981
10059
  else next.delete(itemId);
@@ -9993,124 +10071,155 @@ function createApiVaultPort(hostApi, options = {}) {
9993
10071
  fields: createFieldsPort(ctx),
9994
10072
  lists: createListsPort(ctx, options.userState),
9995
10073
  async load(onProgress) {
9996
- const catalogP = retrying(() => apps2.getAppCatalogKind(api, { path: { app: "matrx", kind: "credential_definition" } }));
9997
- const authP = retrying(() => apps2.listAuthenticators(api));
9998
- const userP = retrying(() => account9.listItems(api, { query: { principal_type: "user" } }));
9999
- const sharedP = retrying(() => account9.sharedWithMe(api));
10000
- const quiet = (p) => void p.catch(() => void 0);
10001
- [catalogP, authP, userP, sharedP].forEach(quiet);
10002
- const orgItemsP = retrying(() => account9.organizationItems(api));
10003
- quiet(orgItemsP);
10004
- const raw = {
10005
- orgLists: [],
10006
- orgsKnown: options.organizations !== void 0,
10007
- meId: null
10008
- };
10009
- const unavailable = [];
10010
- let done = false;
10011
- const build = () => {
10012
- const catalog2 = raw.catalog ?? [];
10013
- 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]));
10014
- const mineItems = raw.mine?.items ?? [];
10015
- const meId = raw.meId ?? mineItems.find((w) => w.user_id)?.user_id ?? null;
10016
- const seen = /* @__PURE__ */ new Map();
10017
- const deleted = /* @__PURE__ */ new Map();
10018
- const take = (w, owner) => {
10019
- if (seen.has(w.id) || deleted.has(w.id) || isHiddenItem(w)) return;
10020
- const item = itemOf(w, owner, defs, auth);
10021
- (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
10022
10095
  };
10023
- for (const w of mineItems) take(w, orgOf(w.organization_id, null, true));
10024
- raw.orgLists.forEach((l, i) => {
10025
- const via = orgs[i];
10026
- for (const w of l?.items ?? []) take(w, ownerInOrgList(w, via, meId));
10027
- });
10028
- 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 });
10029
- const pending = done ? [] : [...raw.orgsKnown ? orgs.filter((_, i) => raw.orgLists[i] === void 0).map((o) => o.name) : ["your organizations"], ...raw.shared ? [] : ["Shared with me"]];
10030
- return {
10031
- unavailable: [...unavailable],
10032
- ...pending.length > 0 ? { pending } : {},
10033
- items: [...seen.values()],
10034
- deleted: [...deleted.values()],
10035
- organizations: orgs,
10036
- 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
+ };
10037
10127
  };
10038
- };
10039
- const progress = () => {
10040
- if (!done && onProgress && raw.catalog && raw.mine) onProgress(build());
10041
- };
10042
- const orgReportP = options.organizations ? null : retrying(() => platform.listMyOrganizations(api));
10043
- if (orgReportP) quiet(orgReportP);
10044
- const orgListsReady = (async () => {
10045
- const report = orgReportP ? await orgReportP : null;
10046
- raw.meId = report?.user_id ?? null;
10047
- 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()];
10048
- raw.orgLists = orgs.map(() => void 0);
10049
- raw.orgsKnown = true;
10050
- progress();
10051
- const one = await orgItemsP.then(
10052
- (r) => r,
10053
- (e) => {
10054
- if (e?.status === 404) {
10055
- hostProblem("GET /vault/organization-items answered 404; reading each organization vault separately", e);
10056
- 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";
10057
10148
  }
10058
- 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;
10059
10162
  }
10060
- );
10061
- if (one) {
10062
- const byOrg = one === "failed" ? /* @__PURE__ */ new Map() : new Map(one.views.map((v) => [v.organization_id, v.items]));
10063
- const failed = one === "failed" ? new Set(orgs.map((o) => o.id)) : new Set(one.unavailable);
10064
- const settled = orgs.map((o, i) => {
10065
- const items = byOrg.get(o.id);
10066
- if (failed.has(o.id) || items === void 0) return Promise.reject(new Error(`${o.name} is unavailable`));
10067
- raw.orgLists[i] = { items, count: items.length };
10068
- 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;
10069
10172
  });
10070
- 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");
10071
10181
  progress();
10072
- return settled;
10073
- }
10074
- return orgs.map((o, i) => {
10075
- const p = retrying(() => account9.listItems(api, { query: { principal_type: "organization", organization_id: o.id } }, { organizationId: o.id }));
10076
- const arrived = p.then((l) => {
10077
- raw.orgLists[i] = l;
10078
- progress();
10079
- return l;
10080
- });
10081
- quiet(arrived);
10082
- return arrived;
10182
+ }, () => {
10183
+ localState = "unavailable";
10184
+ unavailable.push("One-time codes");
10083
10185
  });
10084
- })();
10085
- quiet(orgListsReady);
10086
- const authDone = authP.then((a) => {
10087
- auth = new Map((a.entries ?? []).map((e) => [e.credential_item_id, e]));
10088
- progress();
10089
- }, () => {
10090
- unavailable.push("One-time codes");
10091
- });
10092
- const sharedDone = sharedP.then((sh) => {
10093
- 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;
10094
10196
  progress();
10095
- }, () => {
10096
- raw.shared = { items: [], count: 0 };
10097
- unavailable.push("Shared with me");
10098
- });
10099
- const [catalog, mine] = await Promise.all([catalogP, userP]);
10100
- raw.catalog = catalog;
10101
- raw.mine = mine;
10102
- progress();
10103
- const orgPs = await orgListsReady;
10104
- const orgSettled = await Promise.allSettled(orgPs);
10105
- orgSettled.forEach((r, i) => {
10106
- if (r.status === "rejected") {
10107
- raw.orgLists[i] = { items: [], count: 0 };
10108
- 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);
10109
10210
  }
10110
- });
10111
- await Promise.all([authDone, sharedDone]);
10112
- done = true;
10113
- 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);
10114
10223
  },
10115
10224
  async reveal(item, fieldKey2) {
10116
10225
  const field = item.fields.find((f) => f.key === fieldKey2);