UnENVerse 0.42.6
Local-first desktop secrets manager — TypeScript frontend
Loading...
Searching...
No Matches
state.ts File Reference
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 Documentation

◆ setRenderFn()

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.

Returns
true when anything was assigned, so callers can persist the migration. Fills in 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:

  1. The oldest matching row wins. An entry deleted and re-added would have two; the first is when the name first appeared.
  2. An ambiguous provider is skipped entirely. Audit rows identify an entry by 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.

Returns
true when anything was assigned, so the caller can persist. The earliest moment an entry is known to have existed, when its creation date is unknown.

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.

◆ matchMedia()

window matchMedia (   '(prefers-color-scheme:dark)')

◆ applyGridSettings()

function export applyGridSettings ( )

◆ isSidebarSectionEnabled()

function export isSidebarSectionEnabled (   key:string)

◆ pushRecentSearch()

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.

Returns
true when anything was applied, so the caller knows to repaint.

◆ usersPanelAvailable()

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.

◆ switchTool()

function export switchTool (   toolId:string)

◆ envSegment()

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.

◆ findNameCollisions()

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.

◆ disambiguateNames()

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.

Variable Documentation

◆ tauri

inTauri from tauri

◆ JsonObject

type JsonObject = Record<string, unknown>

◆ Panel

function jsonObject (value: unknown) type Panel = AppSettings['activePanel']

The delta PATCH /api/vault and save_vault_rows take; see vault_core::apply_row_patch.

◆ CalendarFeed

type CalendarFeed
Initial value:
= {
id: string;
name?: string;
kinds?: string[];
revoked_at?: string | null;
}
const string
Definition auth-panel.ts:37

◆ _invoke

const _invoke = (command: string, args?: unknown) => invokeTauri(command, args)

◆ st

export const st

◆ Settings

export const Settings

◆ ALL_SIDEBAR_SECTIONS

export const ALL_SIDEBAR_SECTIONS
Initial value:
= [
'all',
'price',
'env',
'category',
'project',
'tags',
'pools',
'prefixes',
] as const

All toggleable/reorderable sidebar section keys, in default order.

◆ DATA_GATED_SECTIONS

const DATA_GATED_SECTIONS = ['tags', 'pools', 'prefixes']

Sections whose visibility is also gated on having data (handled by render).

◆ SIDEBAR_MIN_W

export const SIDEBAR_MIN_W = 140

Drag bounds for the sidebar, also used to sanitise the persisted width.

◆ SIDEBAR_MAX_W

export const SIDEBAR_MAX_W = 420

◆ EnvNameCase

export type EnvNameCase = 'upper' | 'preserve' | 'lower'

How the finished name is cased.

upper is the shell convention and the default.

◆ EnvNameOpts

export interface EnvNameOpts
Initial value:
{
role?: string | null

Everything envName needs that does not come from the entry.

◆ __pad0__

case __pad0__

Defaults to the envCopyCase setting.

◆ __pad1__

includePrefix __pad1__

Prepend env_prefixes[0].

Defaults to the envIncludePrefix setting, which is off.

◆ ENV_NAME_RE

export const ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/

What a legal POSIX-ish environment-variable name looks like.

◆ NameCollision

export interface NameCollision
Initial value:
{
name: string

A name two or more values want.

◆ __pad2__

sources __pad2__

◆ ENV_BARE_RE

const ENV_BARE_RE = /^[A-Za-z0-9_./:@-]+$/

Values safe to write bare.

Deliberately narrow: anything else gets quoted.