@ai-matrx/vault 0.1.28 → 0.1.29
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 +12 -0
- package/dist/index.cjs +190 -59
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -5
- package/dist/index.d.ts +46 -5
- package/dist/index.js +204 -73
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.29
|
|
4
|
+
|
|
5
|
+
- **Lock and Open on the web at every width.** The three-pane sidebar's foot offers **Lock** whenever the host gives `lock.lock`, and **Open on the web** whenever it gives `openWebVault` (before, both lived only in the narrow layout's More menu, so a desktop window 1120px or wider had no manual lock). Guard: `src/no-feature-lost.test.tsx` § D1. Consumer action: none.
|
|
6
|
+
- **"This page" has the list's search row and New.** A "Search the vault" box (so "/" finds), the compact actions and a **New item** button sit above the site on every page, https or not; typing shows the matching list with the cursor kept in the search, clearing it brings the page back. Guard: § E1. Consumer action: none.
|
|
7
|
+
- **Sign me in is a visible button beside Fill** (primary when there is no Fill), never inside a More menu. Guard: § E2. Consumer action: none.
|
|
8
|
+
- **"Save this login?" and "Turn on filling" show above the list in every view**, not only on "This page" (`host.capture`, `host.fillSetup`; `PageView` no longer receives them from `VaultApp`). Guard: § E3. Consumer action: none.
|
|
9
|
+
- **The copied line says when the clipboard clears.** `VaultClipboard.copy` may answer `{ clearsAtMs }` (`VaultCopied`); the vault then says "Password copied. Clears at 3:42 PM" (`copiedLine`). Answering nothing keeps "Password copied". Guard: § D3. **Consumer action:** a host whose clipboard clears a secret later (the desktop) returns main's `clearsAtMs` from `copy`.
|
|
10
|
+
- **Activity, Share and Delete on every row's menu** (`RowMenuEntryKind` `manage`, `ItemList` `onItemAction`, `manageEntries`): Activity and Share open the item's own panels, Delete asks the item's own question (`confirmDelete`) and moves it to Trash; Share and Delete show off when the item says why it cannot be managed. A host's right-click menu reading `rowMenuEntriesAt` may take the `manage` entries too. Guard: § D4. Consumer action: none.
|
|
11
|
+
- **The organization's backup passphrase minimum is enforced.** `host.backupPassphraseMinLength` (else the organization's `desktop.vault/backup_passphrase_min_length`, `BACKUP_PASSPHRASE_MIN_KNOB`, read through `host.knobs`): the Passphrase row says "At least N characters", a shorter one is refused by name and Encrypt and download stays off. Guard: § D5. **Consumer action:** a host that reads the setting itself (the desktop) passes `backupPassphraseMinLength`.
|
|
12
|
+
- **Every host obeys the organization's reveal time.** Without `host.revealSeconds` the vault reads `desktop.vault/reveal_hide_seconds` (`REVEAL_SECONDS_KNOB`) through `host.knobs` (the extension and the website: 30s by default, was the package's 20s). `positiveKnob` reads either. Guard: § E5. Consumer action: none (a host with `knobs` inherits it).
|
|
13
|
+
- **`useDisposable(create, deps)`**: a host object the screen owns, made and disposed in the same effect, so StrictMode's mount → cleanup → mount leaves a live one (a `useMemo` + cleanup-only effect left the first, disposed copy). Guard: § E4/D2. **Consumer action:** extension: make the new-password-fields host with it; desktop: make the vault lock with it.
|
|
14
|
+
|
|
3
15
|
## 0.1.28
|
|
4
16
|
|
|
5
17
|
- **A website typed as a bare domain always saves.** Every bare domain is sent as `https://…` on create, edit, assign and the item settings call, whether or not the server could reach it (an intranet, a site not live yet) (`normalizeLoginUrls`); the Website box shows the `https://` form after leaving it, and "Not a public website. You can still save it." is a warning, never a block. Guard: `src/review-0-1-28.test.tsx` § 1. Consumer action: none.
|
package/dist/index.cjs
CHANGED
|
@@ -135,6 +135,7 @@ var init_keepass_xml_worker_client = __esm({
|
|
|
135
135
|
// src/index.ts
|
|
136
136
|
var src_exports = {};
|
|
137
137
|
__export(src_exports, {
|
|
138
|
+
BACKUP_PASSPHRASE_MIN_KNOB: () => BACKUP_PASSPHRASE_MIN_KNOB,
|
|
138
139
|
CATEGORIES: () => CATEGORIES,
|
|
139
140
|
CONFIRM_RETURN_KEY: () => CONFIRM_RETURN_KEY,
|
|
140
141
|
DEFAULT_LOCATION: () => DEFAULT_LOCATION,
|
|
@@ -143,9 +144,11 @@ __export(src_exports, {
|
|
|
143
144
|
HOLDINGS_DOOR_MIGRATION: () => HOLDINGS_DOOR_MIGRATION,
|
|
144
145
|
IdentityGate: () => IdentityGate,
|
|
145
146
|
LOCKED_MESSAGE: () => LOCKED_MESSAGE,
|
|
147
|
+
REVEAL_SECONDS_KNOB: () => REVEAL_SECONDS_KNOB,
|
|
146
148
|
VaultApp: () => VaultApp,
|
|
147
149
|
VaultLockedError: () => VaultLockedError,
|
|
148
150
|
categoryOf: () => categoryOf,
|
|
151
|
+
copiedLine: () => copiedLine,
|
|
149
152
|
copyValue: () => copyValue,
|
|
150
153
|
createApiVaultPort: () => createApiVaultPort,
|
|
151
154
|
createSupabaseIdentity: () => createSupabaseIdentity,
|
|
@@ -162,11 +165,13 @@ __export(src_exports, {
|
|
|
162
165
|
matchesQuery: () => matchesQuery,
|
|
163
166
|
needsRecentSignIn: () => needsRecentSignIn,
|
|
164
167
|
parseDefinitions: () => parseDefinitions,
|
|
168
|
+
positiveKnob: () => positiveKnob,
|
|
165
169
|
providerOf: () => providerOf,
|
|
166
170
|
rowMenuEntriesAt: () => rowMenuEntriesAt,
|
|
167
171
|
siteDomainOf: () => siteDomainOf,
|
|
168
172
|
sortItems: () => sortItems,
|
|
169
173
|
useConfirmed: () => useConfirmed,
|
|
174
|
+
useDisposable: () => useDisposable,
|
|
170
175
|
vaultInventoryScope: () => vaultInventoryScope,
|
|
171
176
|
vaultItemFacts: () => vaultItemFacts,
|
|
172
177
|
wasCancelled: () => wasCancelled
|
|
@@ -192,8 +197,8 @@ async function copyValue(text, label, options, deps) {
|
|
|
192
197
|
const secret = options.secret ?? true;
|
|
193
198
|
if (deps.clipboard) {
|
|
194
199
|
try {
|
|
195
|
-
await deps.clipboard.copy(text, { secret });
|
|
196
|
-
deps.notify(
|
|
200
|
+
const copied = await deps.clipboard.copy(text, { secret });
|
|
201
|
+
deps.notify(copiedLine(label, copied?.clearsAtMs ?? null), "success");
|
|
197
202
|
return true;
|
|
198
203
|
} catch (e) {
|
|
199
204
|
deps.notify(e instanceof Error && e.message ? e.message : `${label} could not be copied`, "error");
|
|
@@ -202,6 +207,11 @@ async function copyValue(text, label, options, deps) {
|
|
|
202
207
|
}
|
|
203
208
|
return (0, import_clipboard.copyText)(text, { successMessage: `${label} copied`, failureMessage: `${label} could not be copied`, notify: (m, k) => deps.notify(m, k === "error" ? "error" : "success") });
|
|
204
209
|
}
|
|
210
|
+
function copiedLine(label, clearsAtMs) {
|
|
211
|
+
if (clearsAtMs === null || !Number.isFinite(clearsAtMs)) return `${label} copied`;
|
|
212
|
+
const at = new Date(clearsAtMs).toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit" });
|
|
213
|
+
return `${label} copied. Clears at ${at}`;
|
|
214
|
+
}
|
|
205
215
|
var ClipboardCtx = (0, import_react.createContext)(void 0);
|
|
206
216
|
function ClipboardProvider({ clipboard, children }) {
|
|
207
217
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ClipboardCtx.Provider, { value: clipboard, children });
|
|
@@ -602,6 +612,17 @@ function useElementWidth(ref) {
|
|
|
602
612
|
}, [ref]);
|
|
603
613
|
return width;
|
|
604
614
|
}
|
|
615
|
+
function useDisposable(create, deps) {
|
|
616
|
+
const [held, setHeld] = (0, import_react3.useState)(null);
|
|
617
|
+
const make = (0, import_react3.useRef)(create);
|
|
618
|
+
make.current = create;
|
|
619
|
+
(0, import_react3.useEffect)(() => {
|
|
620
|
+
const made = make.current();
|
|
621
|
+
setHeld(made);
|
|
622
|
+
return () => made.dispose();
|
|
623
|
+
}, deps);
|
|
624
|
+
return held;
|
|
625
|
+
}
|
|
605
626
|
|
|
606
627
|
// src/ui.tsx
|
|
607
628
|
var import_react4 = require("react");
|
|
@@ -3149,6 +3170,27 @@ function importLimitsFrom(resolved) {
|
|
|
3149
3170
|
return out;
|
|
3150
3171
|
}
|
|
3151
3172
|
|
|
3173
|
+
// src/knobs.ts
|
|
3174
|
+
var import_knobs = require("@ai-matrx/data/knobs");
|
|
3175
|
+
var FRESH_MS = 6e4;
|
|
3176
|
+
function createVaultKnobs(client, address) {
|
|
3177
|
+
const reader = (0, import_knobs.createKnobReader)(client);
|
|
3178
|
+
let kept = null;
|
|
3179
|
+
return async () => {
|
|
3180
|
+
if (kept && Date.now() - kept.at < FRESH_MS) return kept.resolved;
|
|
3181
|
+
const snap = await reader.snapshot(address);
|
|
3182
|
+
kept = { at: Date.now(), resolved: snap.resolved };
|
|
3183
|
+
return snap.resolved;
|
|
3184
|
+
};
|
|
3185
|
+
}
|
|
3186
|
+
var REVEAL_SECONDS_KNOB = "desktop.vault.reveal_hide_seconds";
|
|
3187
|
+
var BACKUP_PASSPHRASE_MIN_KNOB = "desktop.vault.backup_passphrase_min_length";
|
|
3188
|
+
function positiveKnob(resolved, key) {
|
|
3189
|
+
const v = resolved[key];
|
|
3190
|
+
const n = typeof v === "string" && v.trim() !== "" ? Number(v) : v;
|
|
3191
|
+
return typeof n === "number" && Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
|
|
3192
|
+
}
|
|
3193
|
+
|
|
3152
3194
|
// src/lock.ts
|
|
3153
3195
|
var import_react16 = require("react");
|
|
3154
3196
|
var LOCKED_MESSAGE = "The vault is locked. Unlock it first.";
|
|
@@ -3344,7 +3386,8 @@ function Omissions({ omissions, accepted, onAccepted, verb }) {
|
|
|
3344
3386
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VaultRow, { label: "Accept", hint: `${verb} without these`, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_controls13.Switch, { checked: accepted, onCheckedChange: onAccepted, "aria-label": `${verb} without these` }) })
|
|
3345
3387
|
] });
|
|
3346
3388
|
}
|
|
3347
|
-
function BackupPanel({ port, items, onClose, onChanged }) {
|
|
3389
|
+
function BackupPanel({ port, host, items, onClose, onChanged }) {
|
|
3390
|
+
const minLength = passphraseMinimum(host.backupPassphraseMinLength);
|
|
3348
3391
|
const confirmed = useConfirmed();
|
|
3349
3392
|
const fileInput = (0, import_react18.useRef)(null);
|
|
3350
3393
|
const abort = (0, import_react18.useRef)(null);
|
|
@@ -3399,7 +3442,7 @@ function BackupPanel({ port, items, onClose, onChanged }) {
|
|
|
3399
3442
|
setAccepted(false);
|
|
3400
3443
|
});
|
|
3401
3444
|
const downloadBackup = () => guarded(async () => {
|
|
3402
|
-
if (!backup) return;
|
|
3445
|
+
if (!backup || tooShort(passphrase, minLength)) return;
|
|
3403
3446
|
const bytes2 = await confirmed(() => port.io.downloadBackup({ itemIds: [...selected], revision: backup.revision, passphrase, acceptOmissions: accepted }));
|
|
3404
3447
|
(0, import_download.downloadFile)(BACKUP_FILENAME, bytes2, "application/octet-stream");
|
|
3405
3448
|
setPassphrase("");
|
|
@@ -3446,10 +3489,11 @@ function BackupPanel({ port, items, onClose, onChanged }) {
|
|
|
3446
3489
|
};
|
|
3447
3490
|
const retryable = (result?.retryable_failure ?? 0) > 0;
|
|
3448
3491
|
const mismatch = repeat !== "" && repeat !== passphrase;
|
|
3492
|
+
const short = passphrase !== "" && tooShort(passphrase, minLength);
|
|
3449
3493
|
const names = new Map(restorePreview?.records.map((r) => [r.source_item_id, r.display_name]) ?? []);
|
|
3450
3494
|
const footer = mode === "backup" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
|
|
3451
3495
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_controls13.Button, { variant: "quiet", onClick: onClose, children: "Close" }),
|
|
3452
|
-
backup ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_controls13.Button, { variant: "primary", disabled: busy || !passphrase || passphrase !== repeat || backup.omissions.length > 0 && !accepted, onClick: () => void downloadBackup(), children: "Encrypt and download" }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_controls13.Button, { variant: "primary", disabled: busy || selected.size === 0, onClick: () => void reviewBackup(), children: [
|
|
3496
|
+
backup ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_controls13.Button, { variant: "primary", disabled: busy || !passphrase || short || passphrase !== repeat || backup.omissions.length > 0 && !accepted, onClick: () => void downloadBackup(), children: "Encrypt and download" }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_controls13.Button, { variant: "primary", disabled: busy || selected.size === 0, onClick: () => void reviewBackup(), children: [
|
|
3453
3497
|
"Review ",
|
|
3454
3498
|
selected.size
|
|
3455
3499
|
] })
|
|
@@ -3496,7 +3540,14 @@ function BackupPanel({ port, items, onClose, onChanged }) {
|
|
|
3496
3540
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Summary, { value: backup }),
|
|
3497
3541
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Omissions, { omissions: backup.omissions, accepted, onAccepted: setAccepted, verb: "Back up" }),
|
|
3498
3542
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(VaultSection, { children: [
|
|
3499
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VaultRow, { label: "Passphrase", hint: "Not saved. It can't be recovered.", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_controls13.Field, { type: "password", value: passphrase, autoComplete: "new-password", "aria-label": "Backup passphrase", onChange: (e) => setPassphrase(e.target.value) }) }),
|
|
3543
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VaultRow, { label: "Passphrase", hint: minLength ? `At least ${minLength} characters. Not saved.` : "Not saved. It can't be recovered.", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_controls13.Field, { type: "password", value: passphrase, autoComplete: "new-password", "aria-label": "Backup passphrase", "aria-invalid": short || void 0, onChange: (e) => setPassphrase(e.target.value) }) }),
|
|
3544
|
+
short ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("p", { role: "alert", className: "px-1 text-xs text-destructive", "data-passphrase-too-short": "", children: [
|
|
3545
|
+
"Use at least ",
|
|
3546
|
+
minLength,
|
|
3547
|
+
" characters. This one has ",
|
|
3548
|
+
passphrase.length,
|
|
3549
|
+
"."
|
|
3550
|
+
] }) : null,
|
|
3500
3551
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VaultRow, { label: "Repeat", ...mismatch ? { hint: "Doesn't match" } : {}, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_controls13.Field, { type: "password", value: repeat, autoComplete: "new-password", "aria-label": "Repeat the passphrase", onChange: (e) => setRepeat(e.target.value) }) })
|
|
3501
3552
|
] })
|
|
3502
3553
|
] }) : null
|
|
@@ -3529,6 +3580,12 @@ function BackupPanel({ port, items, onClose, onChanged }) {
|
|
|
3529
3580
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ErrorLine, { message: error })
|
|
3530
3581
|
] });
|
|
3531
3582
|
}
|
|
3583
|
+
function passphraseMinimum(given) {
|
|
3584
|
+
return given !== void 0 && Number.isFinite(given) && given > 1 ? Math.floor(given) : null;
|
|
3585
|
+
}
|
|
3586
|
+
function tooShort(passphrase, minLength) {
|
|
3587
|
+
return minLength !== null && [...passphrase].length < minLength;
|
|
3588
|
+
}
|
|
3532
3589
|
|
|
3533
3590
|
// src/io/ExportPanel.tsx
|
|
3534
3591
|
var import_react19 = require("react");
|
|
@@ -5986,6 +6043,9 @@ var import_jsx_runtime27 = require("react/jsx-runtime");
|
|
|
5986
6043
|
var ENV_VALUE_KEY = "env_value";
|
|
5987
6044
|
var secureNoteDraft = () => ({ key: SECURE_NOTE_KEY, label: "Secure note", value: "", multiline: true, description: "Encrypted notes" });
|
|
5988
6045
|
var USERNAME_KEYS = /^(username|user_name|email|login|username_or_email|account_email)$/;
|
|
6046
|
+
function confirmDelete(item) {
|
|
6047
|
+
return (0, import_confirm_opener10.confirm)({ title: `Delete \u201C${item.name}\u201D?`, description: "It moves to Trash. You can restore it from there.", confirmLabel: "Delete", variant: "destructive" });
|
|
6048
|
+
}
|
|
5989
6049
|
function ItemPane(props) {
|
|
5990
6050
|
const { port, item, definition, mode, host, organizations, defaultOwner, seed, onBack, onMode, onSaved, onDeleted, onDirty, onCancelNew, onPanel, onChanged, favorite, notice, refreshKey } = props;
|
|
5991
6051
|
const confirmed = useConfirmed();
|
|
@@ -6138,8 +6198,7 @@ function ItemPane(props) {
|
|
|
6138
6198
|
}
|
|
6139
6199
|
async function remove() {
|
|
6140
6200
|
if (!item) return;
|
|
6141
|
-
|
|
6142
|
-
if (!ok) return;
|
|
6201
|
+
if (!await confirmDelete(item)) return;
|
|
6143
6202
|
setBusy(true);
|
|
6144
6203
|
try {
|
|
6145
6204
|
await port.remove(item);
|
|
@@ -6550,7 +6609,7 @@ function subtitleOf(item, withOwner) {
|
|
|
6550
6609
|
const what = loginSiteOf(item) || item.definitionLabel;
|
|
6551
6610
|
return withOwner ? `${what} \xB7 ${item.owner.label}` : what;
|
|
6552
6611
|
}
|
|
6553
|
-
function ItemList({ title, items, definitions, port, selectedId, onSelect, query, onQuery, sort, onSort, onNew, matchingIds, header, actions, showOwner, canSortViewed = false, onRefresh, refreshing = false, rowContextMenu = "package", rowMenuItems, stillLoading, favorites, onToggleFavorite }) {
|
|
6612
|
+
function ItemList({ title, items, definitions, port, selectedId, onSelect, query, onQuery, sort, onSort, onNew, matchingIds, header, actions, showOwner, canSortViewed = false, onRefresh, refreshing = false, rowContextMenu = "package", rowMenuItems, stillLoading, favorites, onToggleFavorite, onItemAction }) {
|
|
6554
6613
|
const sorts = SORTS2.filter((s) => canSortViewed || s.value !== "viewed");
|
|
6555
6614
|
const pinned = matchingIds ? items.filter((i) => matchingIds.has(i.id)) : [];
|
|
6556
6615
|
const rest = matchingIds ? items.filter((i) => !matchingIds.has(i.id)) : items;
|
|
@@ -6571,7 +6630,7 @@ function ItemList({ title, items, definitions, port, selectedId, onSelect, query
|
|
|
6571
6630
|
}
|
|
6572
6631
|
};
|
|
6573
6632
|
const mixedOwners = showOwner ?? new Set(items.map((i) => i.owner.organizationId ?? i.owner.kind)).size > 1;
|
|
6574
|
-
const row = (i) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ListRow, { item: i, family: familyOf2(i.definitionKey), port, on: i.id === selectedId, onSelect, withOwner: mixedOwners, ownContextMenu: rowContextMenu === "package", extra: rowMenuItems, favorite: onToggleFavorite ? { on: favorites?.has(i.id) ?? false, toggle: () => onToggleFavorite(i.id) } : void 0 }, i.id);
|
|
6633
|
+
const row = (i) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ListRow, { item: i, family: familyOf2(i.definitionKey), port, on: i.id === selectedId, onSelect, withOwner: mixedOwners, ownContextMenu: rowContextMenu === "package", extra: rowMenuItems, onAction: onItemAction, favorite: onToggleFavorite ? { on: favorites?.has(i.id) ?? false, toggle: () => onToggleFavorite(i.id) } : void 0 }, i.id);
|
|
6575
6634
|
const sortLabel = sorts.find((s) => s.value === sort)?.label ?? "Name A\u2013Z";
|
|
6576
6635
|
return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("section", { "aria-label": title, className: "flex h-full min-h-0 flex-col border-r border-border bg-background", children: [
|
|
6577
6636
|
/* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex flex-col gap-2 px-3 pb-2 pt-3", children: [
|
|
@@ -6615,7 +6674,7 @@ function ItemList({ title, items, definitions, port, selectedId, onSelect, query
|
|
|
6615
6674
|
] }) })
|
|
6616
6675
|
] });
|
|
6617
6676
|
}
|
|
6618
|
-
function ListRow({ item, family, port, on, onSelect, withOwner, ownContextMenu, extra, favorite }) {
|
|
6677
|
+
function ListRow({ item, family, port, on, onSelect, withOwner, ownContextMenu, extra, onAction, favorite }) {
|
|
6619
6678
|
const confirmed = useConfirmed();
|
|
6620
6679
|
const copy = useCopy();
|
|
6621
6680
|
const notify = useNotify();
|
|
@@ -6656,11 +6715,12 @@ function ListRow({ item, family, port, on, onSelect, withOwner, ownContextMenu,
|
|
|
6656
6715
|
...favoriteEntry ? [favoriteEntry] : [],
|
|
6657
6716
|
{ id: "open", kind: "open", label: "Open", onSelect: () => onSelect(item.id) },
|
|
6658
6717
|
{ id: "copy-name", kind: "copy-name", label: "Copy name", onSelect: copyName },
|
|
6718
|
+
...manageEntries(item, onAction),
|
|
6659
6719
|
...hostEntries.map((h) => ({ ...h, kind: h.kind ?? "host" }))
|
|
6660
6720
|
];
|
|
6661
6721
|
const entriesRef = (0, import_react29.useRef)(entries);
|
|
6662
6722
|
entriesRef.current = entries;
|
|
6663
|
-
const glyphOf = (e) => e.id === "copy:username" ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(UserGlyph, { className: "size-4" }) : e.id === "copy:password" ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(KeyGlyph, { className: "size-4" }) : e.id === "copy:code" ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ClockGlyph, { className: "size-4" }) : e.kind === "favorite" ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(StarGlyph, { className: (0, import_design_system20.cn)("size-4", favorite?.on && "fill-current text-warning") }) : e.kind === "open" ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ChevronRightGlyph, { className: "size-4" }) : e.kind === "host" ? null : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_icons12.CopyIcon, { className: "size-4" });
|
|
6723
|
+
const glyphOf = (e) => e.id === "copy:username" ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(UserGlyph, { className: "size-4" }) : e.id === "copy:password" ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(KeyGlyph, { className: "size-4" }) : e.id === "copy:code" ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ClockGlyph, { className: "size-4" }) : e.kind === "favorite" ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(StarGlyph, { className: (0, import_design_system20.cn)("size-4", favorite?.on && "fill-current text-warning") }) : e.kind === "open" ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ChevronRightGlyph, { className: "size-4" }) : e.kind === "host" || e.kind === "manage" ? null : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_icons12.CopyIcon, { className: "size-4" });
|
|
6664
6724
|
const menuItem = (e) => /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(import_design_system19.DropdownMenuItem, { disabled: e.disabled ?? false, onSelect: e.onSelect, children: [
|
|
6665
6725
|
glyphOf(e),
|
|
6666
6726
|
e.label
|
|
@@ -6714,6 +6774,8 @@ function ListRow({ item, family, port, on, onSelect, withOwner, ownContextMenu,
|
|
|
6714
6774
|
copyEntries.map(menuItem),
|
|
6715
6775
|
copyEntries.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_design_system19.DropdownMenuSeparator, {}) : null,
|
|
6716
6776
|
entries.filter((e) => e.kind === "favorite" || e.kind === "open" || e.kind === "copy-name").map(menuItem),
|
|
6777
|
+
entries.some((e) => e.kind === "manage") ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_design_system19.DropdownMenuSeparator, {}) : null,
|
|
6778
|
+
entries.filter((e) => e.kind === "manage").map(menuItem),
|
|
6717
6779
|
hostEntries.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_design_system19.DropdownMenuSeparator, {}) : null,
|
|
6718
6780
|
entries.filter((e) => e.kind === "host").map(menuItem)
|
|
6719
6781
|
] })
|
|
@@ -6723,6 +6785,17 @@ function ListRow({ item, family, port, on, onSelect, withOwner, ownContextMenu,
|
|
|
6723
6785
|
}
|
|
6724
6786
|
);
|
|
6725
6787
|
}
|
|
6788
|
+
function manageEntries(item, onAction) {
|
|
6789
|
+
if (!onAction) return [];
|
|
6790
|
+
const canManage = item.capabilities.canManage;
|
|
6791
|
+
const offManage = !canManage && manageUnavailableReason(item) !== null;
|
|
6792
|
+
const run = (action) => () => onAction(item, action);
|
|
6793
|
+
return [
|
|
6794
|
+
{ id: "activity", kind: "manage", label: "Activity", onSelect: run("activity") },
|
|
6795
|
+
...canManage || offManage ? [{ id: "share", kind: "manage", label: "Share", onSelect: run("share"), disabled: !canManage }] : [],
|
|
6796
|
+
...canManage || offManage ? [{ id: "delete", kind: "manage", label: "Delete", onSelect: run("delete"), disabled: !canManage }] : []
|
|
6797
|
+
];
|
|
6798
|
+
}
|
|
6726
6799
|
|
|
6727
6800
|
// src/lists/TrashView.tsx
|
|
6728
6801
|
var import_react31 = require("react");
|
|
@@ -8259,22 +8332,10 @@ function MatchRow({ itemId, item, tag, family, port, busy, running, onOpen, onFi
|
|
|
8259
8332
|
] })
|
|
8260
8333
|
] }),
|
|
8261
8334
|
onFill ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_controls33.Button, { variant: "primary", disabled: busy, "aria-busy": running || void 0, onClick: onFill, "aria-label": `Fill ${item?.name ?? "this login"}`, children: running ? "Filling\u2026" : "Fill" }) : null,
|
|
8335
|
+
onSignIn ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_controls33.Button, { variant: onFill ? "outline" : "primary", icon: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ShieldGlyph, {}), disabled: busy, onClick: onSignIn, "aria-label": `Sign me in with ${item?.name ?? "this login"}`, title: "Sign me in", children: "Sign me in" }) : null,
|
|
8262
8336
|
user && item ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_controls33.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(UserGlyph, {}), "aria-label": `Copy ${user.label}`, title: `Copy ${user.label}`, disabled: reading, onClick: () => void read(user.label, () => port.reveal(item, user.key)) }) : null,
|
|
8263
8337
|
secret && item ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_controls33.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(KeyGlyph, {}), "aria-label": "Copy password", title: "Copy password", disabled: reading, onClick: () => void read("Password", () => port.reveal(item, secret.key)) }) : null,
|
|
8264
|
-
otp ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_controls33.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ClockGlyph, {}), "aria-label": "Copy one-time code", title: "Copy one-time code", disabled: reading, onClick: () => void read("One-time code", async () => (await port.authenticator.code(otp)).code) }) : null
|
|
8265
|
-
onSignIn ? /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_design_system24.DropdownMenu, { children: [
|
|
8266
|
-
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_design_system24.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_controls33.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(MoreGlyph, {}), "aria-label": "More", disabled: busy }) }),
|
|
8267
|
-
/* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_design_system24.DropdownMenuContent, { align: "end", children: [
|
|
8268
|
-
/* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_design_system24.DropdownMenuItem, { onSelect: onSignIn, children: [
|
|
8269
|
-
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ShieldGlyph, { className: "size-4" }),
|
|
8270
|
-
"Sign me in"
|
|
8271
|
-
] }),
|
|
8272
|
-
/* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(import_design_system24.DropdownMenuItem, { onSelect: () => onOpen(itemId), children: [
|
|
8273
|
-
/* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ListGlyph, { className: "size-4" }),
|
|
8274
|
-
"Open item"
|
|
8275
|
-
] })
|
|
8276
|
-
] })
|
|
8277
|
-
] }) : null
|
|
8338
|
+
otp ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_controls33.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(ClockGlyph, {}), "aria-label": "Copy one-time code", title: "Copy one-time code", disabled: reading, onClick: () => void read("One-time code", async () => (await port.authenticator.code(otp)).code) }) : null
|
|
8278
8339
|
] });
|
|
8279
8340
|
}
|
|
8280
8341
|
|
|
@@ -8386,7 +8447,7 @@ function Group({ title, children }) {
|
|
|
8386
8447
|
children
|
|
8387
8448
|
] });
|
|
8388
8449
|
}
|
|
8389
|
-
function VaultSidebar({ vault, choices, onVault, view, allCount, deletedCount, views, hasFavorites, hasRecents, onView, onPanel, nativeProvider, hasNativeImport = false, hasPage = false }) {
|
|
8450
|
+
function VaultSidebar({ vault, choices, onVault, view, allCount, deletedCount, views, hasFavorites, hasRecents, onView, onPanel, nativeProvider, hasNativeImport = false, hasPage = false, onLock, onOpenWeb }) {
|
|
8390
8451
|
const [tagsOpen, setTagsOpen] = (0, import_react41.useState)(view.startsWith("tag:"));
|
|
8391
8452
|
return /* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("nav", { "aria-label": "Vault", className: "flex h-full min-h-0 flex-col border-r border-sidebar-border bg-sidebar-background text-sidebar-foreground", children: [
|
|
8392
8453
|
/* @__PURE__ */ (0, import_jsx_runtime39.jsxs)("div", { className: "flex items-center gap-2 px-3 pb-2 pt-3", children: [
|
|
@@ -8425,7 +8486,9 @@ function VaultSidebar({ vault, choices, onVault, view, allCount, deletedCount, v
|
|
|
8425
8486
|
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)(ShieldGlyph, { className: "size-4 shrink-0" }),
|
|
8426
8487
|
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: "min-w-0 flex-1 truncate", children: "System AutoFill" }),
|
|
8427
8488
|
/* @__PURE__ */ (0, import_jsx_runtime39.jsx)("span", { className: (0, import_design_system25.cn)("text-xs", nativeProvider.status === "ready" ? "text-success" : "text-warning"), children: nativeProvider.status === "ready" ? "On" : "Set up" })
|
|
8428
|
-
] }) : null
|
|
8489
|
+
] }) : null,
|
|
8490
|
+
onOpenWeb ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(Entry, { on: false, glyph: ExternalGlyph, label: "Open on the web", onClick: onOpenWeb }) : null,
|
|
8491
|
+
onLock ? /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(Entry, { on: false, glyph: LockGlyph, label: "Lock", onClick: onLock }) : null
|
|
8429
8492
|
] })
|
|
8430
8493
|
] });
|
|
8431
8494
|
}
|
|
@@ -8468,12 +8531,42 @@ function ImportWithLimits(props) {
|
|
|
8468
8531
|
if (state && "limits" in state) return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(ImportPanel, { ...rest, limits: state.limits });
|
|
8469
8532
|
return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Panel, { title: "Import", onClose: rest.onClose, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Empty, { children: state ? state.error : "Reading this organization\u2019s import settings\u2026" }) });
|
|
8470
8533
|
}
|
|
8471
|
-
function VaultApp(
|
|
8534
|
+
function VaultApp(given) {
|
|
8535
|
+
const props = { ...given, host: useOrganizationSettings(given.host ?? NO_HOST) };
|
|
8472
8536
|
const lock = props.host?.lock;
|
|
8473
8537
|
const locked = useLocked(lock);
|
|
8474
8538
|
const port = (0, import_react42.useMemo)(() => lock ? guardPort(props.port, () => lock.locked()) : props.port, [props.port, lock]);
|
|
8475
8539
|
return /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_design_system26.LookScope, { look: props.look ?? "web", className: "h-full min-h-0 w-full", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "relative h-full min-h-0 w-full", children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(NoticeProvider, { notify: props.notify, children: lock && locked ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(LockedScreen, { lock }) : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(ClipboardProvider, { clipboard: props.host?.clipboard, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(RevealSecondsProvider, { seconds: props.host?.revealSeconds, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(IdentityGate, { identity: props.host?.identity, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(GeneratorLimitsProvider, { resolve: generatorLimitsFrom(props.host?.knobs), children: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Workspace, { ...props, port }) }) }) }) }) }) }) });
|
|
8476
8540
|
}
|
|
8541
|
+
function useOrganizationSettings(host) {
|
|
8542
|
+
const knobs = host.knobs;
|
|
8543
|
+
const needs = knobs !== void 0 && (host.revealSeconds === void 0 || host.backupPassphraseMinLength === void 0);
|
|
8544
|
+
const [read, setRead] = (0, import_react42.useState)(null);
|
|
8545
|
+
(0, import_react42.useEffect)(() => {
|
|
8546
|
+
if (!needs || !knobs) return;
|
|
8547
|
+
let live = true;
|
|
8548
|
+
knobs().then(
|
|
8549
|
+
(r) => live && setRead({ reveal: positiveKnob(r, REVEAL_SECONDS_KNOB), backupMin: positiveKnob(r, BACKUP_PASSPHRASE_MIN_KNOB) }),
|
|
8550
|
+
() => void 0
|
|
8551
|
+
);
|
|
8552
|
+
return () => {
|
|
8553
|
+
live = false;
|
|
8554
|
+
};
|
|
8555
|
+
}, [needs, knobs]);
|
|
8556
|
+
if (!read) return host;
|
|
8557
|
+
return {
|
|
8558
|
+
...host,
|
|
8559
|
+
...host.revealSeconds === void 0 && read.reveal !== null ? { revealSeconds: read.reveal } : {},
|
|
8560
|
+
...host.backupPassphraseMinLength === void 0 && read.backupMin !== null ? { backupPassphraseMinLength: read.backupMin } : {}
|
|
8561
|
+
};
|
|
8562
|
+
}
|
|
8563
|
+
var NO_STORE = () => () => void 0;
|
|
8564
|
+
var NOTHING = () => null;
|
|
8565
|
+
function useHostPrompts(capture, fillSetup) {
|
|
8566
|
+
const prompt = (0, import_react42.useSyncExternalStore)(capture ? capture.subscribe : NO_STORE, capture ? capture.get : NOTHING, capture ? capture.get : NOTHING);
|
|
8567
|
+
const setup = (0, import_react42.useSyncExternalStore)(fillSetup ? fillSetup.subscribe : NO_STORE, fillSetup ? fillSetup.get : NOTHING, fillSetup ? fillSetup.get : NOTHING);
|
|
8568
|
+
return prompt !== null || setup?.status === "off";
|
|
8569
|
+
}
|
|
8477
8570
|
function LockedScreen({ lock }) {
|
|
8478
8571
|
const [busy, setBusy] = (0, import_react42.useState)(false);
|
|
8479
8572
|
const [error, setError] = (0, import_react42.useState)(null);
|
|
@@ -8561,6 +8654,16 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8561
8654
|
document.addEventListener("keydown", onKey);
|
|
8562
8655
|
return () => document.removeEventListener("keydown", onKey);
|
|
8563
8656
|
}, []);
|
|
8657
|
+
const keepSearchFocus = (0, import_react42.useRef)(false);
|
|
8658
|
+
(0, import_react42.useEffect)(() => {
|
|
8659
|
+
if (!keepSearchFocus.current) return;
|
|
8660
|
+
keepSearchFocus.current = false;
|
|
8661
|
+
const search = rootRef.current?.querySelector('input[aria-label^="Search"]');
|
|
8662
|
+
if (!search || document.activeElement === search) return;
|
|
8663
|
+
search.focus();
|
|
8664
|
+
search.setSelectionRange(search.value.length, search.value.length);
|
|
8665
|
+
}, [loc.q]);
|
|
8666
|
+
const hasPrompts = useHostPrompts(host.capture, host.fillSetup);
|
|
8564
8667
|
const knobsRef = (0, import_react42.useRef)(host.knobs);
|
|
8565
8668
|
knobsRef.current = host.knobs;
|
|
8566
8669
|
const [signedOut, setSignedOut] = (0, import_react42.useState)(false);
|
|
@@ -8703,6 +8806,25 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8703
8806
|
notify(e instanceof Error ? e.message : "Favorites could not be saved.", "error");
|
|
8704
8807
|
});
|
|
8705
8808
|
};
|
|
8809
|
+
const deletedItem = (id) => {
|
|
8810
|
+
setItems((cur) => cur.filter((i) => i.id !== id));
|
|
8811
|
+
dirty.current = false;
|
|
8812
|
+
if (loc.itemId === id) move({ ...loc, itemId: null, mode: "view", panel: null }, "push");
|
|
8813
|
+
notify("Moved to Trash");
|
|
8814
|
+
load();
|
|
8815
|
+
};
|
|
8816
|
+
const rowAction = (item, action) => {
|
|
8817
|
+
if (action !== "delete") return go({ view: loc.view === "page" ? "all" : loc.view, itemId: item.id, mode: "view", panel: action });
|
|
8818
|
+
void confirmDelete(item).then(async (ok) => {
|
|
8819
|
+
if (!ok) return;
|
|
8820
|
+
try {
|
|
8821
|
+
await port.remove(item);
|
|
8822
|
+
deletedItem(item.id);
|
|
8823
|
+
} catch (e) {
|
|
8824
|
+
notify(e instanceof Error && e.message ? e.message : "Deleting failed.", "error");
|
|
8825
|
+
}
|
|
8826
|
+
});
|
|
8827
|
+
};
|
|
8706
8828
|
if (error) {
|
|
8707
8829
|
return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { ref: rootRef, role: "alert", className: "flex h-full flex-col items-center justify-center gap-3 bg-background p-6 text-center text-sm", children: [
|
|
8708
8830
|
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(SafeGlyph, { className: "size-8 text-muted-foreground" }),
|
|
@@ -8786,6 +8908,27 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8786
8908
|
compactActions
|
|
8787
8909
|
] })
|
|
8788
8910
|
] });
|
|
8911
|
+
const newItem = () => go({ itemId: null, mode: "new", kind: null, panel: null });
|
|
8912
|
+
const pageHeader = /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "flex flex-col gap-2", "data-vault-page-header": "", children: [
|
|
8913
|
+
compactHeader,
|
|
8914
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
8915
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
8916
|
+
import_controls35.SearchField,
|
|
8917
|
+
{
|
|
8918
|
+
value: loc.q,
|
|
8919
|
+
onChange: (e) => {
|
|
8920
|
+
keepSearchFocus.current = true;
|
|
8921
|
+
go({ q: e.target.value }, "replace");
|
|
8922
|
+
},
|
|
8923
|
+
placeholder: "Search the vault",
|
|
8924
|
+
"aria-label": "Search the vault",
|
|
8925
|
+
className: "min-w-0 flex-1"
|
|
8926
|
+
}
|
|
8927
|
+
),
|
|
8928
|
+
compactActions,
|
|
8929
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)(import_controls35.Button, { variant: "primary", icon: /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(PlusGlyph, {}), "aria-label": "New item", onClick: newItem })
|
|
8930
|
+
] })
|
|
8931
|
+
] });
|
|
8789
8932
|
const viewProps = { port, host, items: inThisVault, deleted, definitions, query: loc.q, onOpen: (id) => go({ view: "all", itemId: id, mode: "view", panel: null }), onChanged: load, header: viewHeader };
|
|
8790
8933
|
const pageView = loc.view === "page" && host.page && !loc.q ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
|
|
8791
8934
|
PageView,
|
|
@@ -8793,8 +8936,6 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8793
8936
|
port,
|
|
8794
8937
|
page: host.page,
|
|
8795
8938
|
fill: host.fill,
|
|
8796
|
-
capture: host.capture,
|
|
8797
|
-
fillSetup: host.fillSetup,
|
|
8798
8939
|
newPasswordFields: host.newPasswordFields,
|
|
8799
8940
|
items: inThisVault,
|
|
8800
8941
|
definitions,
|
|
@@ -8802,10 +8943,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8802
8943
|
onNewForPage: (url) => go({ mode: "new", kind: null, url, itemId: null, panel: null }),
|
|
8803
8944
|
onBrowse: () => go({ view: "all", itemId: null }),
|
|
8804
8945
|
onChanged: load,
|
|
8805
|
-
|
|
8806
|
-
compactHeader,
|
|
8807
|
-
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: "flex justify-end", children: compactActions })
|
|
8808
|
-
] }) } : {}
|
|
8946
|
+
header: pageHeader
|
|
8809
8947
|
}
|
|
8810
8948
|
) : null;
|
|
8811
8949
|
const specialView = pageView ?? (loc.view === "authenticator" ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(AuthenticatorView, { ...viewProps }) : loc.view === "trash" ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(TrashView, { ...viewProps }) : null);
|
|
@@ -8820,11 +8958,15 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8820
8958
|
selectedId: loc.itemId,
|
|
8821
8959
|
onSelect: (id) => go({ itemId: id, mode: "view", kind: null, panel: null }),
|
|
8822
8960
|
query: loc.q,
|
|
8823
|
-
onQuery: (q) =>
|
|
8961
|
+
onQuery: (q) => {
|
|
8962
|
+
if (loc.view === "page" && host.page && !q) keepSearchFocus.current = true;
|
|
8963
|
+
go({ q }, "replace");
|
|
8964
|
+
},
|
|
8824
8965
|
sort: loc.sort,
|
|
8825
8966
|
onSort: (sort) => go({ sort }, "replace"),
|
|
8826
8967
|
canSortViewed: port.lists.recents !== void 0,
|
|
8827
|
-
onNew:
|
|
8968
|
+
onNew: newItem,
|
|
8969
|
+
onItemAction: rowAction,
|
|
8828
8970
|
onRefresh: refresh,
|
|
8829
8971
|
refreshing,
|
|
8830
8972
|
...snap.pending && snap.pending.length > 0 ? { stillLoading: snap.pending } : {},
|
|
@@ -8899,13 +9041,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8899
9041
|
setNotice(n);
|
|
8900
9042
|
notify("Saved");
|
|
8901
9043
|
},
|
|
8902
|
-
onDeleted:
|
|
8903
|
-
setItems((cur) => cur.filter((i) => i.id !== id));
|
|
8904
|
-
dirty.current = false;
|
|
8905
|
-
move({ ...loc, itemId: null, mode: "view", panel: null }, "push");
|
|
8906
|
-
notify("Moved to Trash");
|
|
8907
|
-
load();
|
|
8908
|
-
},
|
|
9044
|
+
onDeleted: deletedItem,
|
|
8909
9045
|
onDirty: (d) => {
|
|
8910
9046
|
dirty.current = d;
|
|
8911
9047
|
},
|
|
@@ -8966,6 +9102,13 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8966
9102
|
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)("span", { className: "min-w-0 flex-1 truncate text-warning", title: refreshError, children: "Couldn\u2019t refresh. Showing what was loaded." }),
|
|
8967
9103
|
/* @__PURE__ */ (0, import_jsx_runtime40.jsx)("button", { type: "button", className: "shrink-0 text-primary hover:underline", onClick: refresh, children: "Try again" })
|
|
8968
9104
|
] }) : null,
|
|
9105
|
+
hasPrompts ? (
|
|
9106
|
+
// "Save this login?" and "Turn on filling" show above the list in every view, not only "This page".
|
|
9107
|
+
/* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { className: "flex flex-col gap-3 border-b border-border px-3 py-3", "data-vault-host-prompts": "", children: [
|
|
9108
|
+
host.fillSetup ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(FillSetupCard, { fillSetup: host.fillSetup }) : null,
|
|
9109
|
+
host.capture ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(CaptureCard, { capture: host.capture, onChanged: load }) : null
|
|
9110
|
+
] })
|
|
9111
|
+
) : null,
|
|
8969
9112
|
snap.unavailable.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("div", { role: "status", className: "flex items-center gap-3 border-b border-border bg-muted px-4 py-2 text-sm", children: [
|
|
8970
9113
|
/* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "min-w-0 flex-1 truncate text-warning", children: [
|
|
8971
9114
|
"Couldn\u2019t load ",
|
|
@@ -8991,7 +9134,9 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8991
9134
|
onPanel: (p) => go({ panel: p }),
|
|
8992
9135
|
nativeProvider: host.nativeProvider,
|
|
8993
9136
|
hasNativeImport: host.nativeImport !== void 0,
|
|
8994
|
-
hasPage: host.page !== void 0
|
|
9137
|
+
hasPage: host.page !== void 0,
|
|
9138
|
+
onLock: host.lock?.lock ? () => void host.lock?.lock?.() : void 0,
|
|
9139
|
+
onOpenWeb: host.openWebVault
|
|
8995
9140
|
}
|
|
8996
9141
|
) : null,
|
|
8997
9142
|
shown.list ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("div", { className: specialView && mode !== "stack" ? "col-span-2 min-h-0" : "min-h-0", children: list }) : null,
|
|
@@ -9964,20 +10109,6 @@ function createApiVaultPort(hostApi, options = {}) {
|
|
|
9964
10109
|
};
|
|
9965
10110
|
}
|
|
9966
10111
|
|
|
9967
|
-
// src/knobs.ts
|
|
9968
|
-
var import_knobs = require("@ai-matrx/data/knobs");
|
|
9969
|
-
var FRESH_MS = 6e4;
|
|
9970
|
-
function createVaultKnobs(client, address) {
|
|
9971
|
-
const reader = (0, import_knobs.createKnobReader)(client);
|
|
9972
|
-
let kept = null;
|
|
9973
|
-
return async () => {
|
|
9974
|
-
if (kept && Date.now() - kept.at < FRESH_MS) return kept.resolved;
|
|
9975
|
-
const snap = await reader.snapshot(address);
|
|
9976
|
-
kept = { at: Date.now(), resolved: snap.resolved };
|
|
9977
|
-
return snap.resolved;
|
|
9978
|
-
};
|
|
9979
|
-
}
|
|
9980
|
-
|
|
9981
10112
|
// src/agent-context.ts
|
|
9982
10113
|
var hostOf = (url) => {
|
|
9983
10114
|
try {
|