@ai-matrx/vault 0.1.19 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,9 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.20
4
+
5
+ - **A multi-line secret edits as several lines.** A field whose type says multi-line, any field under a notes key (an old **Secure note**, `secure_notes`) and any value that already holds a line break now edit in a multi-line box — a one-line box dropped the line breaks on save. Shown values keep their lines too. Guard: `src/vault-gaps-2026-10-19.test.tsx`. Consumer action: none.
6
+ - **Secure note is back** in the item form's Add menu: an encrypted multi-line field stored as `secure_notes`, hidden until shown (`secureNoteDraft`, `NewSecretField.multiline`/`key`/`description`). Consumer action: none.
7
+ - **A new item's declared secrets carry env key, who can see it and sandbox use to the create call** (`ItemDraft.fieldSettings`, `declaredFieldIn`, `FieldSettingsRows`): behind each secret's sliders, open from the start on an Environment value. Consumer action: none.
8
+ - **Trash rows say each item's type** ("Website login"), read from the item through the state door's `schema("users")` (`VaultUserStateDoor.schema`, `withTypes`), instead of "Credential". Consumer action: none — every host already passes its supabase client as `userState`.
9
+ - **A code added in the Authenticator shows in its list at once**, before the vault's re-read answers (or when an older re-read answers last) (`AddOtpPanel onAdded`, `withAdded`). Consumer action: none.
10
+ - **The list stays current.** A **Refresh** button beside the sort re-reads the list; the list also re-reads itself when the window or panel comes back into focus, and every `host.refreshSeconds` (else the organization's `vault.list.refresh_seconds` knob) while shown — never while hidden, never over an edit or a new item, never two reads at once. A refresh that fails keeps the loaded list and says "Couldn’t refresh. Showing what was loaded." with Try again; the partial-read line's Try again no longer drops to the "couldn't open" screen. Guard: `src/parity-gaps.test.tsx`. **Consumer action:** desktop: pass `refreshSeconds` from its refresh knob. Web app and extension: none (focus refresh is on by itself).
11
+ - **The row menu copies any field** — API key, token, account number, any plain custom field — besides username, password and code (never a sealed field, the authenticator's key or recovery codes). **Right-click on a row opens that menu** in every host, with a host's extra entries (`host.rowMenuItems`). **Consumer action:** web app: pass `rowContextMenu: "host"` so its own v3 right-click menu (agents, Copy, Export) keeps answering on rows. Extension and desktop: none.
12
+ - **After an import**, one **Set up authenticator for <name>** link per imported login that came with a one-time password; it opens the setup for that login over the result (`ImportRunResult.needOtp`). Consumer action: none.
13
+ - **The authenticator setup screen starts the camera by itself** where this browser can scan (`OtpSetupRow autoStartCamera`, used by `AddOtpPanel`; a setup row inside an edit never does). Consumer action: none.
14
+ - **An opener can name the new item**: `VaultLocation.name` (`?name=`) prefills the new item's name over the one guessed from the address. Consumer action: none.
15
+
3
16
  ## 0.1.19
4
17
 
5
- UNVERIFIED: this release has no Unreleased entry and the source comparison could not be made (the caller supplied no source comparison). Whether source changed is unknown; consumer action is unknown.
6
- Check the package's gitHead on npm against this version's gitHead: `git diff <previous gitHead> <this gitHead> -- apps/shared/vault/src`.
18
+ SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
19
+ so they may include breaking API changes. Consumer action is UNKNOWN: read the diff before upgrading.
20
+ Diff: `git diff ff560b1be4 05488c8d62 -- apps/shared/vault/src`
21
+
22
+ Changed shipped source files (8):
23
+ - `apps/shared/vault/src/ItemList.tsx`
24
+ - `apps/shared/vault/src/VaultApp.tsx`
25
+ - `apps/shared/vault/src/authenticator/AddOtpPanel.tsx`
26
+ - `apps/shared/vault/src/authenticator/Setup.tsx`
27
+ - `apps/shared/vault/src/io/ImportPanel.tsx`
28
+ - `apps/shared/vault/src/io/import-run.ts`
29
+ - `apps/shared/vault/src/model.ts`
30
+ - `apps/shared/vault/src/port.ts`
7
31
 
8
32
  ## 0.1.18
9
33
 
package/dist/index.cjs CHANGED
@@ -740,7 +740,7 @@ var DAY_FORMAT = { year: "numeric", month: "short", day: "numeric" };
740
740
  var import_jsx_runtime5 = require("react/jsx-runtime");
741
741
  var MASK = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
742
742
  function SecretText({ value, multiline }) {
743
- if (multiline || value.length > 200) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "whitespace-pre-wrap break-all font-mono text-sm", children: value });
743
+ if (multiline || value.length > 200 || value.includes("\n")) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "whitespace-pre-wrap break-all font-mono text-sm", children: value });
744
744
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "break-all font-mono text-sm tracking-wide", children: [...value].map((ch, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: /[0-9]/.test(ch) ? "text-primary" : /[^A-Za-z0-9]/.test(ch) ? "text-warning" : void 0, children: ch }, i)) });
745
745
  }
746
746
  var quietError = (e, fallback) => wasCancelled(e) ? null : e instanceof Error ? e.message : fallback;
@@ -876,18 +876,19 @@ function EditRow({ label, hint: hint2, value, onChange, placeholder, secret = fa
876
876
  }
877
877
  );
878
878
  }
879
- function CustomEditRow({ label, value, onLabel, onValue, secret, onRemove, extra, onPasteValue }) {
879
+ function CustomEditRow({ label, value, onLabel, onValue, secret, onRemove, extra, onPasteValue, multiline = false }) {
880
880
  const [shown, setShown] = (0, import_react5.useState)(false);
881
881
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
882
882
  VaultRow,
883
883
  {
884
884
  label: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls2.Field, { className: "w-full", value: label, onChange: (e) => onLabel(e.target.value), placeholder: "Name", "aria-label": "Field name" }),
885
+ top: multiline,
885
886
  actions: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
886
- secret && value ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls2.Button, { variant: "quiet", icon: shown ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(EyeOffGlyph, {}) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(EyeGlyph, {}), "aria-label": shown ? "Hide" : "Show", onClick: () => setShown((x) => !x) }) : null,
887
+ secret && value && !multiline ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls2.Button, { variant: "quiet", icon: shown ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(EyeOffGlyph, {}) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(EyeGlyph, {}), "aria-label": shown ? "Hide" : "Show", onClick: () => setShown((x) => !x) }) : null,
887
888
  extra,
888
889
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls2.Button, { variant: "quiet", removes: true, icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(XGlyph, {}), "aria-label": "Remove field", onClick: onRemove })
889
890
  ] }),
890
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
891
+ children: multiline ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls2.Textarea, { className: "w-full", value, onChange: (e) => onValue(e.target.value), rows: 4, placeholder: secret ? "Encrypted, hidden until shown" : "Value", autoComplete: "off", spellCheck: !secret, "aria-label": label ? `${label} value` : "Field value" }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
891
892
  import_controls2.Field,
892
893
  {
893
894
  className: "w-full",
@@ -1006,6 +1007,7 @@ function sortItems(items, sort, recents) {
1006
1007
  if (sort === "updated") return out.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
1007
1008
  return out.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
1008
1009
  }
1010
+ var SECURE_NOTE_KEY = "secure_notes";
1009
1011
  function draftOf(item) {
1010
1012
  return {
1011
1013
  name: item?.name ?? "",
@@ -1056,7 +1058,9 @@ function rebaseDraft(oldBase, newBase, draft) {
1056
1058
  secrets: draft.secrets,
1057
1059
  custom,
1058
1060
  removed: draft.removed,
1059
- authenticator: draft.authenticator
1061
+ authenticator: draft.authenticator,
1062
+ ...draft.browser ? { browser: draft.browser } : {},
1063
+ ...draft.fieldSettings ? { fieldSettings: draft.fieldSettings } : {}
1060
1064
  };
1061
1065
  }
1062
1066
  function panesShown(mode, specialView, showingDetail) {
@@ -1084,7 +1088,8 @@ var FAMILY_LABELS = {
1084
1088
  signing_files: "Signing & files"
1085
1089
  };
1086
1090
  var COMMON_DEFINITION_KEYS = ["website_login", "username_password", "api_key", "env_value", "database_login", "custom"];
1087
- var MULTILINE = /(json|pem|private_key|certificate|cert|bundle|document|recovery_codes)/;
1091
+ var MULTILINE = /(json|pem|private_key|certificate|cert|bundle|document|recovery_codes|(^|_)notes?($|_))/;
1092
+ var isMultilineKey = (key) => MULTILINE.test(key);
1088
1093
  var SECRET = /(password|secret|token|api_key|private|passphrase|credential|seed|key$|_key)/;
1089
1094
  var URLISH = /(url|uri|endpoint|webhook|hook)/;
1090
1095
  function humanize(key) {
@@ -2298,7 +2303,7 @@ function normalizeWebsite(raw) {
2298
2303
  return null;
2299
2304
  }
2300
2305
  }
2301
- function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChanged, onDone }) {
2306
+ function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChanged, onAdded, onDone }) {
2302
2307
  const confirmed = useConfirmed();
2303
2308
  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" }));
2304
2309
  const [input, setInput] = (0, import_react12.useState)("");
@@ -2351,6 +2356,7 @@ function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChang
2351
2356
  setInput("");
2352
2357
  setImage(null);
2353
2358
  setSaved(next);
2359
+ onAdded?.(next);
2354
2360
  onChanged();
2355
2361
  } catch (e) {
2356
2362
  setError(wasCancelled(e) ? "Not confirmed, so nothing was added." : otpActionError(e, "add it"));
@@ -2459,7 +2465,23 @@ function otpGroups(items, entries, query, hidden = /* @__PURE__ */ new Set()) {
2459
2465
  const rank = (k) => k === "me" ? 0 : k === "shared" ? 2 : 1;
2460
2466
  return [...groups.entries()].sort(([a, x], [b, y]) => rank(a) - rank(b) || x.label.localeCompare(y.label)).map(([key, g]) => ({ key, ...g }));
2461
2467
  }
2462
- function AuthenticatorView({ port, host, items, definitions, query, onOpen, onChanged, header }) {
2468
+ function withAdded(items, added) {
2469
+ if (added.size === 0) return items;
2470
+ const seen = /* @__PURE__ */ new Set();
2471
+ const out = items.map((i) => {
2472
+ seen.add(i.id);
2473
+ const mine = added.get(i.id);
2474
+ return mine && !i.authenticator ? { ...i, authenticator: mine.authenticator, name: mine.name } : i;
2475
+ });
2476
+ for (const [id, a] of added) if (!seen.has(id)) out.push(a);
2477
+ return out;
2478
+ }
2479
+ function AuthenticatorView({ port, host, items: listed, definitions, query, onOpen, onChanged, header }) {
2480
+ const [added, setAdded] = (0, import_react13.useState)(/* @__PURE__ */ new Map());
2481
+ const items = withAdded(listed, added);
2482
+ (0, import_react13.useEffect)(() => {
2483
+ if (added.size > 0 && [...added.keys()].every((id) => listed.some((i) => i.id === id && i.authenticator))) setAdded(/* @__PURE__ */ new Map());
2484
+ }, [listed, added]);
2463
2485
  const [entries, setEntries] = (0, import_react13.useState)(/* @__PURE__ */ new Map());
2464
2486
  const [hidden, setHidden] = (0, import_react13.useState)(/* @__PURE__ */ new Set());
2465
2487
  const [adding, setAdding] = (0, import_react13.useState)(false);
@@ -2475,7 +2497,7 @@ function AuthenticatorView({ port, host, items, definitions, query, onOpen, onCh
2475
2497
  return () => {
2476
2498
  live = false;
2477
2499
  };
2478
- }, [port, items]);
2500
+ }, [port, listed, added]);
2479
2501
  const groups = otpGroups(items, entries, query, hidden);
2480
2502
  const total = items.filter((i) => i.authenticator && !i.deletion && !hidden.has(i.id)).length;
2481
2503
  const familyOf2 = (key) => definitions.find((d) => d.key === key)?.family ?? "generic";
@@ -2521,6 +2543,7 @@ function AuthenticatorView({ port, host, items, definitions, query, onOpen, onCh
2521
2543
  definitions,
2522
2544
  onClose: () => setAdding(false),
2523
2545
  onChanged,
2546
+ onAdded: (item) => setAdded((cur) => new Map(cur).set(item.id, item)),
2524
2547
  onDone: (id) => {
2525
2548
  setAdding(false);
2526
2549
  onOpen(id);
@@ -5245,14 +5268,12 @@ var import_confirm_opener7 = require("@ai-matrx/kit/confirm-opener");
5245
5268
  var import_jsx_runtime22 = require("react/jsx-runtime");
5246
5269
  var HANDLINGS = ["visible", "revealable", "sealed"];
5247
5270
  var hint = (text, tone = "muted") => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: tone === "destructive" ? "text-destructive" : void 0, title: text, children: text });
5248
- var hasSecretSettings = (s) => Boolean(s.envKey?.trim() || s.injectIntoSandbox || s.handling && s.handling !== "revealable");
5249
- function SecretBuilderRow({ field, onChange, onRemove }) {
5250
- const [open, setOpen] = (0, import_react23.useState)(hasSecretSettings(field));
5251
- const env = field.envKey ?? "";
5271
+ var saysMore = (s, defaultHandling) => Boolean(s.envKey?.trim() || s.injectIntoSandbox || s.handling && s.handling !== defaultHandling);
5272
+ function FieldSettingsRows({ name, value, defaultHandling = "revealable", onChange }) {
5273
+ const env = value.envKey ?? "";
5252
5274
  const envProblem = envKeyProblem(env);
5253
5275
  const envOk = env.trim() !== "" && !envProblem;
5254
- const handling = field.handling ?? "revealable";
5255
- const name = field.label.trim() || "Secret";
5276
+ const handling = value.handling ?? defaultHandling;
5256
5277
  const chooseHandling = async (next) => {
5257
5278
  if (next === handling) return;
5258
5279
  if (next === "sealed") {
@@ -5266,6 +5287,38 @@ function SecretBuilderRow({ field, onChange, onRemove }) {
5266
5287
  }
5267
5288
  onChange({ handling: next });
5268
5289
  };
5290
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
5291
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(VaultRow, { label: "Env key", hint: envProblem ? hint(envProblem, "destructive") : hint("The name sandboxes and workflows use"), children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5292
+ import_controls18.Field,
5293
+ {
5294
+ className: "w-full font-mono",
5295
+ "aria-label": `${name} env key`,
5296
+ "aria-invalid": Boolean(envProblem),
5297
+ placeholder: "e.g. GITHUB_TOKEN",
5298
+ autoComplete: "off",
5299
+ spellCheck: false,
5300
+ value: env,
5301
+ onChange: (e) => onChange({ envKey: e.target.value, ...e.target.value.trim() ? {} : { injectIntoSandbox: false } })
5302
+ }
5303
+ ) }),
5304
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(VaultRow, { label: "Who can see it", hint: hint(HANDLING_WORDS[handling].hint), children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5305
+ import_controls18.SegmentedControl,
5306
+ {
5307
+ "aria-label": `Who can see ${name}`,
5308
+ value: handling,
5309
+ onValueChange: (v) => void chooseHandling(v),
5310
+ data: HANDLINGS.map((h) => ({ value: h, label: HANDLING_WORDS[h].label, title: HANDLING_WORDS[h].hint }))
5311
+ }
5312
+ ) }),
5313
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(VaultRow, { label: "Use in sandbox", hint: envOk ? hint("Sandboxes get it as an env variable") : hint("Needs an env key"), children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_controls18.Switch, { "aria-label": `Use ${name} in sandbox`, checked: envOk && value.injectIntoSandbox === true, disabled: !envOk, onCheckedChange: (v) => onChange({ injectIntoSandbox: v }) }) })
5314
+ ] });
5315
+ }
5316
+ var hasSecretSettings = (s) => saysMore(s, "revealable");
5317
+ var hasDeclaredSettings = (s, defaultHandling) => Boolean(s && saysMore(s, defaultHandling));
5318
+ function SecretBuilderRow({ field, onChange, onRemove }) {
5319
+ const [open, setOpen] = (0, import_react23.useState)(hasSecretSettings(field));
5320
+ const handling = field.handling ?? "revealable";
5321
+ const name = field.label.trim() || "Secret";
5269
5322
  return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
5270
5323
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5271
5324
  CustomEditRow,
@@ -5273,47 +5326,26 @@ function SecretBuilderRow({ field, onChange, onRemove }) {
5273
5326
  label: field.label,
5274
5327
  value: field.value,
5275
5328
  secret: true,
5329
+ multiline: field.multiline === true,
5276
5330
  onLabel: (v) => onChange({ label: v }),
5277
5331
  onValue: (v) => onChange({ value: v }),
5278
5332
  onRemove,
5279
- onPasteValue: (text) => {
5280
- const a = parseEnvAssignment(text);
5281
- if (!a) return false;
5282
- onChange({ envKey: a.key, value: a.value, ...field.label.trim() ? {} : { label: labelFromEnvKey(a.key) } });
5283
- setOpen(true);
5284
- return true;
5333
+ ...field.multiline ? {} : {
5334
+ onPasteValue: (text) => {
5335
+ const a = parseEnvAssignment(text);
5336
+ if (!a) return false;
5337
+ onChange({ envKey: a.key, value: a.value, ...field.label.trim() ? {} : { label: labelFromEnvKey(a.key) } });
5338
+ setOpen(true);
5339
+ return true;
5340
+ }
5285
5341
  },
5286
5342
  extra: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
5287
- handling !== "sealed" ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(GenerateButton, { onUse: (v) => onChange({ value: v }) }) : null,
5343
+ handling !== "sealed" && !field.multiline ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(GenerateButton, { onUse: (v) => onChange({ value: v }) }) : null,
5288
5344
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_controls18.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(SlidersGlyph, {}), "aria-label": `${name} settings`, pressed: open, onClick: () => setOpen((o) => !o) })
5289
5345
  ] })
5290
5346
  }
5291
5347
  ),
5292
- open ? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
5293
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(VaultRow, { label: "Env key", hint: envProblem ? hint(envProblem, "destructive") : hint("The name sandboxes and workflows use"), children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5294
- import_controls18.Field,
5295
- {
5296
- className: "w-full font-mono",
5297
- "aria-label": `${name} env key`,
5298
- "aria-invalid": Boolean(envProblem),
5299
- placeholder: "e.g. GITHUB_TOKEN",
5300
- autoComplete: "off",
5301
- spellCheck: false,
5302
- value: env,
5303
- onChange: (e) => onChange({ envKey: e.target.value, ...e.target.value.trim() ? {} : { injectIntoSandbox: false } })
5304
- }
5305
- ) }),
5306
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(VaultRow, { label: "Who can see it", hint: hint(HANDLING_WORDS[handling].hint), children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
5307
- import_controls18.SegmentedControl,
5308
- {
5309
- "aria-label": `Who can see ${name}`,
5310
- value: handling,
5311
- onValueChange: (v) => void chooseHandling(v),
5312
- data: HANDLINGS.map((h) => ({ value: h, label: HANDLING_WORDS[h].label, title: HANDLING_WORDS[h].hint }))
5313
- }
5314
- ) }),
5315
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(VaultRow, { label: "Use in sandbox", hint: envOk ? hint("Sandboxes get it as an env variable") : hint("Needs an env key"), children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_controls18.Switch, { "aria-label": `Use ${name} in sandbox`, checked: envOk && field.injectIntoSandbox === true, disabled: !envOk, onCheckedChange: (v) => onChange({ injectIntoSandbox: v }) }) })
5316
- ] }) : null
5348
+ open ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(FieldSettingsRows, { name, value: field, onChange }) : null
5317
5349
  ] });
5318
5350
  }
5319
5351
  function withMfaMethod(custom, v) {
@@ -5943,6 +5975,8 @@ function SharingSummary({ item, port, className }) {
5943
5975
  // src/ItemPane.tsx
5944
5976
  var import_format8 = require("@ai-matrx/kit/format");
5945
5977
  var import_jsx_runtime27 = require("react/jsx-runtime");
5978
+ var ENV_VALUE_KEY = "env_value";
5979
+ var secureNoteDraft = () => ({ key: SECURE_NOTE_KEY, label: "Secure note", value: "", multiline: true, description: "Encrypted notes" });
5946
5980
  var USERNAME_KEYS = /^(username|user_name|email|login|username_or_email|account_email)$/;
5947
5981
  function ItemPane(props) {
5948
5982
  const { port, item, definition, mode, host, organizations, defaultOwner, seed, onBack, onMode, onSaved, onDeleted, onDirty, onCancelNew, onPanel, onChanged, favorite, notice, refreshKey } = props;
@@ -6133,6 +6167,16 @@ function ItemPane(props) {
6133
6167
  const fieldSettings = (f) => canEdit ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_controls22.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(SlidersGlyph, {}), "aria-label": `${f.label} settings`, title: `${f.label} settings`, onClick: () => onPanel(`field:${f.id}`) }) : null;
6134
6168
  const turnOnOne = canTurnOn ? (f) => () => turnOn([f]) : () => void 0;
6135
6169
  const declaredKeys = new Set(declared.map((f) => f.key));
6170
+ const hasSecureNote = declaredKeys.has(SECURE_NOTE_KEY) || Boolean(item?.fields.some((f) => f.key === SECURE_NOTE_KEY)) || draft.secrets.some((x) => x.key === SECURE_NOTE_KEY);
6171
+ const [settingsToggled, setSettingsToggled] = (0, import_react28.useState)(/* @__PURE__ */ new Set());
6172
+ const settingsOpen = (f) => settingsToggled.has(f.key) !== (definition?.key === ENV_VALUE_KEY || hasDeclaredSettings(draft.fieldSettings?.[f.key], f.handling));
6173
+ const toggleSettings = (key) => setSettingsToggled((cur) => {
6174
+ const next = new Set(cur);
6175
+ if (next.has(key)) next.delete(key);
6176
+ else next.add(key);
6177
+ return next;
6178
+ });
6179
+ const patchFieldSettings = (key, p) => setDraft((d) => ({ ...d, fieldSettings: { ...d.fieldSettings, [key]: { ...d.fieldSettings?.[key], ...p } } }));
6136
6180
  const extraFields = (item?.fields ?? []).filter((f) => !declaredKeys.has(f.key) && !draft.removed.includes(f.id) && !isAuthenticatorStorage(f.key));
6137
6181
  const metaValue = (key) => draft.values[key] ?? item?.custom.find((c) => c.key === key)?.value ?? "";
6138
6182
  const extraCustom = draft.custom.filter((c) => !declaredKeys.has(c.key) && c.key !== MFA_METHOD_KEY && !isAuthenticatorStorage(c.key));
@@ -6265,24 +6309,29 @@ function ItemPane(props) {
6265
6309
  const secretKind = f.storage === "encrypted" && f.kind === "secret";
6266
6310
  const value = f.storage === "metadata" ? metaValue(f.key) : draft.values[f.key] ?? "";
6267
6311
  const hidden = existing && existing.handling !== "visible" && !(f.key in loaded);
6268
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
6269
- EditRow,
6270
- {
6271
- label: f.label,
6272
- value,
6273
- placeholder: hidden ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : f.required ? "Required" : isRecoveryCodesField(f) ? "Backup codes, one per line" : "",
6274
- secret: secretKind,
6275
- multiline: f.kind === "multiline",
6276
- inputMode: f.kind === "url" ? "url" : USERNAME_KEYS.test(f.key) ? "email" : "text",
6277
- onChange: (v) => setValue(f.key, v),
6278
- actions: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
6279
- hidden && existing?.handling === "revealable" && item?.capabilities.canReveal ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_controls22.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(EyeGlyph, {}), "aria-label": `Show current ${f.label}`, onClick: () => void loadForEdit(f.key) }) : null,
6280
- secretKind ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(GenerateButton, { onUse: (v) => setValue(f.key, v) }) : null,
6281
- existing ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_controls22.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_icons11.SettingsIcon, {}), "aria-label": `${f.label} settings`, onClick: () => onPanel(`field:${existing.id}`) }) : null
6282
- ] })
6283
- },
6284
- f.key
6285
- );
6312
+ const newSecret = mode === "new" && f.storage === "encrypted" && owner !== "person";
6313
+ const open = newSecret && settingsOpen(f);
6314
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_react28.Fragment, { children: [
6315
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
6316
+ EditRow,
6317
+ {
6318
+ label: f.label,
6319
+ value,
6320
+ placeholder: hidden ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : f.required ? "Required" : isRecoveryCodesField(f) ? "Backup codes, one per line" : "",
6321
+ secret: secretKind,
6322
+ multiline: f.kind === "multiline" || value.includes("\n"),
6323
+ inputMode: f.kind === "url" ? "url" : USERNAME_KEYS.test(f.key) ? "email" : "text",
6324
+ onChange: (v) => setValue(f.key, v),
6325
+ actions: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
6326
+ hidden && existing?.handling === "revealable" && item?.capabilities.canReveal ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_controls22.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(EyeGlyph, {}), "aria-label": `Show current ${f.label}`, onClick: () => void loadForEdit(f.key) }) : null,
6327
+ secretKind ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(GenerateButton, { onUse: (v) => setValue(f.key, v) }) : null,
6328
+ existing ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_controls22.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_icons11.SettingsIcon, {}), "aria-label": `${f.label} settings`, onClick: () => onPanel(`field:${existing.id}`) }) : null,
6329
+ newSecret ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_controls22.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(SlidersGlyph, {}), "aria-label": `${f.label} settings`, pressed: open, onClick: () => toggleSettings(f.key) }) : null
6330
+ ] })
6331
+ }
6332
+ ),
6333
+ open ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(FieldSettingsRows, { name: f.label, value: draft.fieldSettings?.[f.key] ?? {}, defaultHandling: f.handling, onChange: (p) => patchFieldSettings(f.key, p) }) : null
6334
+ ] }, f.key);
6286
6335
  }),
6287
6336
  extraFields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
6288
6337
  EditRow,
@@ -6291,7 +6340,7 @@ function ItemPane(props) {
6291
6340
  value: draft.values[f.key] ?? "",
6292
6341
  placeholder: f.handling === "visible" || f.key in loaded ? "" : "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
6293
6342
  secret: f.handling !== "visible" && !isRecoveryCodesField(f),
6294
- multiline: isRecoveryCodesField(f),
6343
+ multiline: f.kind === "multiline" || isRecoveryCodesField(f) || (draft.values[f.key] ?? "").includes("\n"),
6295
6344
  onChange: (v) => setValue(f.key, v),
6296
6345
  onRemove: () => patch({ removed: [...draft.removed, f.id] }),
6297
6346
  removeLabel: `Delete ${f.label}`,
@@ -6360,6 +6409,7 @@ function ItemPane(props) {
6360
6409
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_design_system17.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_controls22.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(PlusGlyph, {}), children: "Add" }) }),
6361
6410
  /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_design_system17.DropdownMenuContent, { align: "end", children: [
6362
6411
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_design_system17.DropdownMenuItem, { onSelect: () => patch({ secrets: [...draft.secrets, { key: "", label: "", value: "" }] }), children: "Secret field" }),
6412
+ !hasSecureNote ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_design_system17.DropdownMenuItem, { onSelect: () => patch({ secrets: [...draft.secrets, secureNoteDraft()] }), children: "Secure note" }) : null,
6363
6413
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_design_system17.DropdownMenuItem, { onSelect: () => patch({ custom: [...draft.custom, { key: `custom_${draft.custom.length + 1}_${Date.now() % 1e5}`, label: "", value: "" }] }), children: "Text field" }),
6364
6414
  !tagsVisible ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_design_system17.DropdownMenuItem, { onSelect: () => setShowTags(true), children: "Tags" }) : null,
6365
6415
  !descriptionVisible ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_design_system17.DropdownMenuItem, { onSelect: () => setShowDescription(true), children: "Description" }) : null,
@@ -9128,6 +9178,28 @@ function trashItem(r, ctx) {
9128
9178
  createdAt: ""
9129
9179
  };
9130
9180
  }
9181
+ async function withTypes(items, door, ctx) {
9182
+ if (!door.schema || items.length === 0) return items;
9183
+ const keys = /* @__PURE__ */ new Map();
9184
+ const CHUNK = 100;
9185
+ try {
9186
+ for (let i = 0; i < items.length; i += CHUNK) {
9187
+ const ids = items.slice(i, i + CHUNK).map((x) => x.id);
9188
+ const { data, error } = await door.schema("users").from("credential_items").select("id,definition_key").in("id", ids);
9189
+ if (error) throw new Error(error.message || "credential_items read failed");
9190
+ for (const r of Array.isArray(data) ? data : []) {
9191
+ if (typeof r.id === "string" && typeof r.definition_key === "string" && r.definition_key) keys.set(r.id, r.definition_key);
9192
+ }
9193
+ }
9194
+ } catch (e) {
9195
+ console.warn("[vault] Trash item types could not be read; rows say Credential", e);
9196
+ return items;
9197
+ }
9198
+ return items.map((it) => {
9199
+ const key = keys.get(it.id);
9200
+ return key ? { ...it, definitionKey: key, definitionLabel: ctx.definition(key)?.label ?? humanize(key) } : it;
9201
+ });
9202
+ }
9131
9203
  var previewOf = async (door, id) => parseRecoveryPreview(await call(door, "vault_recovery_preview", { p_id: id }));
9132
9204
  function createListsPort(ctx, door) {
9133
9205
  return {
@@ -9160,7 +9232,8 @@ function createListsPort(ctx, door) {
9160
9232
  if (rows.length < PAGE2) break;
9161
9233
  }
9162
9234
  const items = all.map((r) => trashItem(r, ctx)).filter((i) => i !== null);
9163
- return [...new Map(items.map((i) => [i.id, i])).values()];
9235
+ const unique = [...new Map(items.map((i) => [i.id, i])).values()];
9236
+ return withTypes(unique, door, ctx);
9164
9237
  }
9165
9238
  },
9166
9239
  favorites: {
@@ -9183,16 +9256,25 @@ function createListsPort(ctx, door) {
9183
9256
  var account6 = __toESM(require("@ai-matrx/api/account"), 1);
9184
9257
 
9185
9258
  // src/secret-field-in.ts
9186
- function secretFieldIn(s) {
9259
+ function settingsIn(s) {
9187
9260
  const envKey = s.envKey?.trim() || null;
9188
9261
  return {
9189
- field_key: fieldKeyOf(s.label),
9190
- value: s.value,
9191
- handling: s.handling ?? "revealable",
9192
9262
  ...envKey ? { env_key: envKey } : {},
9193
9263
  ...envKey && s.injectIntoSandbox ? { inject_into_sandbox: true } : {}
9194
9264
  };
9195
9265
  }
9266
+ function secretFieldIn(s) {
9267
+ return {
9268
+ field_key: s.key.trim() || fieldKeyOf(s.label),
9269
+ value: s.value,
9270
+ handling: s.handling ?? "revealable",
9271
+ ...s.description ? { description: s.description } : {},
9272
+ ...settingsIn(s)
9273
+ };
9274
+ }
9275
+ function declaredFieldIn(f, value, settings) {
9276
+ return { field_key: f.key, value, handling: settings?.handling ?? f.handling, ...settingsIn(settings ?? {}) };
9277
+ }
9196
9278
 
9197
9279
  // src/sharing/api.ts
9198
9280
  function grantOf(w) {
@@ -9293,7 +9375,7 @@ function createSharingPort(ctx, options = {}) {
9293
9375
  initiation: "user",
9294
9376
  non_secret_fields: [...meta.map((f) => ({ key: f.key, label: f.label, value: draft.values[f.key] ?? "" })), ...draft.custom.filter((c) => c.label.trim() !== "").map((c) => ({ key: c.key || fieldKeyOf(c.label), label: c.label, value: c.value }))],
9295
9377
  fields: [
9296
- ...encrypted.map((f) => ({ field_key: f.key, value: draft.values[f.key] ?? "", handling: f.handling })),
9378
+ ...encrypted.map((f) => declaredFieldIn(f, draft.values[f.key] ?? "", draft.fieldSettings?.[f.key])),
9297
9379
  ...draft.secrets.filter((s) => s.label.trim() !== "" && s.value !== "").map(secretFieldIn)
9298
9380
  ]
9299
9381
  }
@@ -9422,7 +9504,7 @@ async function readNativeImportLimits(api, organizationId) {
9422
9504
  var recoveryOf = (w) => w.lifecycle?.vault_recovery;
9423
9505
  var isDeleted = (w) => recoveryOf(w)?.state === "deleted";
9424
9506
  var MATCH_MODES2 = ["host", "domain", "exact", "never"];
9425
- var FIELD_WORDS = { totp_seed: "One-time password key", authenticator_last_time_step: "Last code used" };
9507
+ var FIELD_WORDS = { secure_notes: "Secure note", totp_seed: "One-time password key", authenticator_last_time_step: "Last code used" };
9426
9508
  var handlingOf = (v) => v === "visible" || v === "sealed" ? v : "revealable";
9427
9509
  function fieldOf(w, def) {
9428
9510
  const declared = def?.fields.find((f) => f.key === w.field_key);
@@ -9431,7 +9513,8 @@ function fieldOf(w, def) {
9431
9513
  id: w.id,
9432
9514
  key: w.field_key,
9433
9515
  label: declared?.label ?? FIELD_WORDS[w.field_key] ?? humanize(w.field_key),
9434
- kind: declared?.kind ?? (handling === "visible" ? "text" : "secret"),
9516
+ // A field no type declares still edits as several lines when its key says so (an old "Secure note").
9517
+ kind: declared?.kind ?? (isMultilineKey(w.field_key) ? "multiline" : handling === "visible" ? "text" : "secret"),
9435
9518
  handling,
9436
9519
  editable: w.editable ?? true,
9437
9520
  envKey: w.env_key ?? null,
@@ -9586,7 +9669,7 @@ function createApiVaultPort(api, options = {}) {
9586
9669
  initiation: "user",
9587
9670
  non_secret_fields: [...meta.map((f) => ({ key: f.key, label: f.label, value: draft.values[f.key] ?? "" })), ...draft.custom.filter((c) => c.label.trim() !== "").map((c) => ({ key: c.key || fieldKeyOf(c.label), label: c.label, value: c.value }))],
9588
9671
  fields: [
9589
- ...encrypted.map((f) => ({ field_key: f.key, value: draft.values[f.key] ?? "", handling: f.handling })),
9672
+ ...encrypted.map((f) => declaredFieldIn(f, draft.values[f.key] ?? "", draft.fieldSettings?.[f.key])),
9590
9673
  ...draft.secrets.filter((s) => s.label.trim() !== "" && s.value !== "").map(secretFieldIn),
9591
9674
  // Backup codes on a type that does not declare them (a website login): their own encrypted field.
9592
9675
  ...!declared.some((f) => f.key === RECOVERY_CODES_KEY) && (draft.values[RECOVERY_CODES_KEY] ?? "").trim() !== "" ? [{ field_key: RECOVERY_CODES_KEY, value: parseRecoveryCodes(draft.values[RECOVERY_CODES_KEY] ?? "").join("\n"), handling: "revealable", description: "One-time account recovery codes" }] : []