|
UnENVerse 0.42.6
Local-first desktop secrets manager — TypeScript frontend
|
import entropySource;Classes | |
| class | LocalVaultStore |
| class | VaultConflictError |
| Thrown when a write was refused because someone else wrote first. More... | |
| class | TauriVaultStore |
The a, b list after "both changed:" in a conflict message, if there is one. More... | |
| class | TotpRequiredError |
| Thrown when a password was accepted but the account also has a second factor. More... | |
| class | RemoteVaultStore |
Functions | |
| function export | setRenderFn (fn:()=> void) |
| Clears every view-scoped selection that can outlive the data it points at. | |
| window | matchMedia ('(prefers-color-scheme:dark)').addEventListener('change' |
| function export | applyGridSettings () |
| function export | isSidebarSectionEnabled (key:string) |
| function export | pushRecentSearch (q:string) |
| Applies the persisted sidebar width and collapsed state. | |
| function export | usersPanelAvailable () |
| Users/RBAC only means something when this vault is actually being served to other people — either we're connected to a remote server, or we're serving our own vault over LAN. | |
| function export | switchTool (toolId:string) |
| function | envSegment (raw:string|null|undefined, fold:boolean) |
| One segment, normalised. | |
| function export | findNameCollisions (entries:VaultEntry[], opts:EnvNameOpts={}) |
| Every collision across a selection, including within a single entry. | |
| function export | disambiguateNames (names:GeneratedName[]) |
| Make every name in a list unique, appending rather than dropping. | |
Variables | |
| inTauri from | tauri |
| type | JsonObject = Record<string, unknown> |
| function jsonObject(value:unknown) type | Panel = AppSettings['activePanel'] |
The delta PATCH /api/vault and save_vault_rows take; see vault_core::apply_row_patch. | |
| type | CalendarFeed |
| const | _invoke = (command: string, args?: unknown) => invokeTauri(command, args) |
| export const | st |
| export const | Settings |
| export const | ALL_SIDEBAR_SECTIONS |
| All toggleable/reorderable sidebar section keys, in default order. | |
| const | DATA_GATED_SECTIONS = ['tags', 'pools', 'prefixes'] |
| Sections whose visibility is also gated on having data (handled by render). | |
| export const | SIDEBAR_MIN_W = 140 |
| Drag bounds for the sidebar, also used to sanitise the persisted width. | |
| export const | SIDEBAR_MAX_W = 420 |
| export type | EnvNameCase = 'upper' | 'preserve' | 'lower' |
| How the finished name is cased. | |
| export interface | EnvNameOpts |
Everything envName needs that does not come from the entry. | |
| case | __pad0__ |
Defaults to the envCopyCase setting. | |
| includePrefix | __pad1__ |
Prepend env_prefixes[0]. | |
| export const | ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/ |
| What a legal POSIX-ish environment-variable name looks like. | |
| export interface | NameCollision |
| A name two or more values want. | |
| sources | __pad2__ |
| const | ENV_BARE_RE = /^[A-Za-z0-9_./:@-]+$/ |
| Values safe to write bare. | |
| function export setRenderFn | ( | fn:(), | |
| void | |||
| ) |
Clears every view-scoped selection that can outlive the data it points at.
Call this whenever st.vault is replaced wholesale — import, backup restore, switching vaults. A project id, tag or category selected against the previous vault will not exist in the new one, and getFiltered() then matches nothing: the user imports a backup and is shown an empty grid, with the data present but invisible. revealed matters for a second reason — it is keyed by entry id, so an imported entry that happens to reuse an id would render its secret unmasked without the user ever asking. Clears every filter narrowing the grid, without touching the rest of the view.
Distinct from resetViewState() on purpose: that one is for when the data is replaced and expand/reveal/bulk state has to go too. This is the user saying "show me everything again", where dropping their expanded cards and bulk ticks as a side effect would be a surprise. Fresh entry identifier. Guarantees every entry has a unique, stable id, in place.
Backfills missing ids (vaults written before the field existed) and replaces duplicates — duplicateKey shallow-copies an entry, which would otherwise hand two entries the same identity and make the RBAC merge and version_history attribution alias them.
created_at for entries written before the field existed, using evidence already in the vault rather than a guess.The only evidence that actually dates a creation is an add row in the audit log, which has been written since Phase 3. Two rules make it safe to use:
entry_provider alone, so two entries sharing a provider cannot be told apart — and attributing one entry's creation date to its neighbour is the same class of mistake as identifying an entry by array index. The CLI refuses ambiguous lookups for exactly this reason; so does this.Everything else stays unset. version_history is deliberately not used as a source: its oldest saved_at is when a value was replaced, which is an upper bound on the creation date and not the date itself. The timeline panel shows that bound as "before <date>" without writing it to the vault, because a stored date is indistinguishable from a known one the moment it is read back by anything else — the CLI, an export, the calendar feed.
This is an upper bound, never a creation date, and every caller has to render it as one ("before 4 Mar"). It exists because "unknown" for an entry with ten revisions going back two years is less true than "at least two years old". Stable id for an entry, assigning one if somehow still absent. The single vault write path.
Normalises entry ids before handing the data to the store, so no code path — import, template, chunk link, manual add — can persist an entry without a stable identity. Always use this instead of calling st.store.save directly. Forget what the store holds; the next save is a whole-document one. True when now is before minus the deleted ids with new ids appended. The delta since the last synced state, or null when it cannot be trusted. A concurrent writer — almost always a LAN peer — changed the vault between our last read and this save.
There is no safe automatic answer: we do not know which change matters more, and silently picking one is how data goes missing. So ask, and make the cost of each option explicit. Adopt what is now stored, replacing our copy. false when nothing could be loaded.
| window matchMedia | ( | '(prefers-color-scheme:dark)' | ) |
| function export applyGridSettings | ( | ) |
| function export isSidebarSectionEnabled | ( | key:string | ) |
| function export pushRecentSearch | ( | q:string | ) |
Applies the persisted sidebar width and collapsed state.
Width is clamped here rather than only at drag time: the value survives in localStorage, so a width written by an older build, a hand-edited settings file or a different screen size can otherwise leave the sidebar at 3px wide with no visible handle to drag it back. How many search strings the history dropdown keeps. Records a search string, most-recent-first, de-duplicated.
Case-insensitive de-dup, but the newest casing wins, so retyping a query differently does not leave two near-identical rows in the dropdown. Snapshots the current grid view so the next launch can restore it. Restores the last grid view, dropping anything the loaded vault does not have.
The validation is the whole point. A persisted selection is a reference held across a restart, and the vault it pointed at may have been edited, replaced by an import, or swapped for a remote in the meantime (invariant 3). An id that no longer resolves matches nothing in getFiltered(), so the user would open the app to an empty grid with their secrets present but invisible — and with no obvious control to un-stick it, because the stale filter is not one they set this session.
| function export usersPanelAvailable | ( | ) |
Users/RBAC only means something when this vault is actually being served to other people — either we're connected to a remote server, or we're serving our own vault over LAN.
On a purely local vault the panel wrote users into the desktop's own vault.db, which unv-server never reads (it uses its own file). Accounts created there could never authenticate anywhere: it looked like it worked and silently did nothing. Show or hide the Users entry in the activity bar, and bail out of it if open.
| function export switchTool | ( | toolId:string | ) |
| function envSegment | ( | raw:string|null| | undefined, |
| fold:boolean | |||
| ) |
One segment, normalised.
Anything outside [A-Za-z0-9] collapses to _, runs of _ collapse to one, and leading/trailing _ are trimmed — which is what stops SPOTIFY__ID when a segment ends in punctuation as well as when it is empty. The version segment: 2, v2, V2 → V2; 2.0 → V2_0; 2026-08-01 → V2026_08_01.
The leading v is stripped only when a digit follows it, so the V is added once and never doubled, and a word-shaped version (beta, vault) keeps its first letter instead of being silently decapitated. Strip a cookie's __Host- / __Secure- prefix.
They are stripped, not transliterated: __HOST_SID is not a name anyone asked for, and the two prefixes are a browser-side attribute of where a cookie may be set rather than part of its name. That three cookies can collapse into one name is exactly the case E2's collision check has to warn about (step 6), which is why this happens before the segment is normalised rather than inside the normaliser. The environment-variable name this entry generates for opts.role.
Always returns a legal identifier: a leading digit gains a _ (no shell will export a name that starts with one), and an entry that normalises away to nothing at all comes back as UNKNOWN rather than as the empty string, which would write a nameless =value line. One generated name and where it came from. Every name one entry generates, in the order a copy emits them.
| function export findNameCollisions | ( | entries:VaultEntry[], | |
opts:EnvNameOpts = {} |
|||
| ) |
Every collision across a selection, including within a single entry.
Returns the groups, not a boolean: naming both sides is the difference between a warning somebody can act on and one they have to go hunting for.
| function export disambiguateNames | ( | names:GeneratedName[] | ) |
Make every name in a list unique, appending rather than dropping.
A copy that silently emitted one line where the user expected two is the failure this exists to prevent; a name with a suffix is visibly odd and recoverable, a missing variable is neither.
Pool members are disambiguated by their position in the pool, everything else by its key_id — and by an ordinal when even that is not enough, because the function must terminate with a unique name whatever the data says.
| inTauri from tauri |
| function jsonObject (value: unknown) type Panel = AppSettings['activePanel'] |
The delta PATCH /api/vault and save_vault_rows take; see vault_core::apply_row_patch.
| type CalendarFeed |
| const _invoke = (command: string, args?: unknown) => invokeTauri(command, args) |
| export const st |
| export const Settings |
| export const ALL_SIDEBAR_SECTIONS |
All toggleable/reorderable sidebar section keys, in default order.
Sections whose visibility is also gated on having data (handled by render).
| export const SIDEBAR_MIN_W = 140 |
Drag bounds for the sidebar, also used to sanitise the persisted width.
| export const SIDEBAR_MAX_W = 420 |
| export type EnvNameCase = 'upper' | 'preserve' | 'lower' |
How the finished name is cased.
upper is the shell convention and the default.
| export interface EnvNameOpts |
Everything envName needs that does not come from the entry.
| case __pad0__ |
Defaults to the envCopyCase setting.
| includePrefix __pad1__ |
Prepend env_prefixes[0].
Defaults to the envIncludePrefix setting, which is off.
| export const ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/ |
What a legal POSIX-ish environment-variable name looks like.
| export interface NameCollision |
A name two or more values want.
| sources __pad2__ |
| const ENV_BARE_RE = /^[A-Za-z0-9_./:@-]+$/ |
Values safe to write bare.
Deliberately narrow: anything else gets quoted.