App Lifecycle

← Lifecycle
STRVCT

SvApp initialization, extension points, and readiness signaling.

Overview

SvApp is the application singleton. It owns the persistent store, the root model object, and the top-level UI. Subclasses override a small number of hook methods to set up their model and UI without needing to manage the initialization order themselves.

Extension Points

MethodWhen it runsWhat to do here
setupModel()Store is open, root object existsInitialize your data model, add top-level nodes
setupUi()Model is readyCustomize the UI setup
appDidInit()Everything is wired upPost-init work, the app is now live
afterAppUiDidInit()UI is visibleHandle URL parameters, trigger initial navigation
handleSearchParams(params)Called from afterAppUiDidInitProcess URL query parameters

Use rootSubnodeWithTitleForProto(title, proto) to add top-level objects — it creates them on first run and reuses them from storage on subsequent loads.

Waiting for Readiness

The app exposes a promise for code that needs to wait:

await SvApp.shared().didInitPromise();
// App is now fully initialized

For synchronous checks:

if (SvApp.hasShared() && SvApp.shared().hasDoneAppInit()) {
    // Safe to interact with app
}

Notifications

  • "appDidInit" — Posted when initialization is complete. Useful for observers that need to run setup code after the app is ready.
  • "onAppDeveloperModeChangedNote" — Posted when developer mode is toggled.

Runtime Lifecycle Events

Beyond startup, the app reacts to environment events at runtime. These arrive as environment-agnostic hooks on SvModel (default no-ops — override in your model), routed through SvApp. The environment layer translates concrete signals into them, so the same overrides work in a browser and headless. A model implementing these never touches the DOM, window, or any view class, so it stays headless-testable.

Hook (on SvModel)Browser sourceHeadless sourcePurpose
onAppDidGoOnline()online eventConnectivity restored
onAppDidGoOffline()offline eventConnectivity lost
onAppWillSuspend()visibilitychange (hidden)App backgrounded — flush volatile state
onAppWillTerminate() → BooleanbeforeunloadSIGTERM / SIGINTShutdown/unload — do synchronous cleanup; return true to request a block (e.g. the browser's unsaved-changes prompt)

Routing and translation:

  • SvApp.onApp* fans each event out to this.model() — and is the natural place to notify other participants later.
  • Browser: SvWebUserInterface owns an SvWindowListener + SvDocumentListener (delegate = itself) via the event system and maps the DOM events. beforeunload is delivered synchronously to its delegate method, which returns false to drive the framework's preventDefault() — so onAppWillTerminate() returning true produces the prompt, and the cleanup runs in time (a queued notification would fire too late).
  • Headless: SvHeadlessUserInterface maps process termination signals to onAppWillTerminate().

User Alerts

Any model can ask that a message be shown to the user — without referencing views, the DOM, or the concept of a user. It posts a notification; the UI layer presents it (or logs it, headless):

this.postNoteNamed("onRequestUserAlert", {
    name: "someMachineId",   // for logs/telemetry — not shown to the user
    title: "Heading",         // optional
    message: "The text shown to the user.",
    level: "error"            // "info" | "warning" | "error"
});

SvUserInterface observes onRequestUserAlert and calls presentUserAlert(info). SvWebUserInterface opens an SvPanelView; the base (headless) implementation logs it. The presenting view reference stays in the UI layer, never in the model that posted the alert.

UI Readiness

UI-dependent work (e.g. posting a navigation request) should wait for the UI rather than poll or retry. The app exposes a promise that resolves once the UI is ready to navigate — or, headless, once the environment reports there is no navigable UI:

const ui = await SvApp.shared().promiseUserInterfaceReady();
if (ui.providesNavigation()) {
    // safe to navigate, e.g. post "onRequestSelectNodePath"
}
  • Resolves with the userInterface(). providesNavigation() is true for a web UI, false headless — so the same model code skips UI-only work when there's no navigable UI.
  • The promise is created up front, so awaiting after readiness resolves immediately.
  • Browser: SvBrowserView calls SvApp.markUserInterfaceReady() once its root column is materialized (the genuine "ready to navigate" point). Headless: SvHeadlessUserInterface marks it after init.

This replaces DOM polling / retry loops in models: await the promise, then post the request once.