Skip to content

Product and runtime

Baseline reference

This chapter describes the established product and runtime model. The current interface and native Companion boundary are documented on the final architecture page.

Product view

What Steady is designed for

Steady is intended to make change visible without turning everyday life into a mandatory questionnaire. Everything is voluntary. Unanswered values remain unknown and are not interpreted as zero, failure or an uncompleted habit.

Main areas of the interface

flowchart TD
    T[Today] --> J[Journal]
    T --> R[Recovery]
    T --> P[Patterns]
    T --> E[Explorer]
    T --> X[Toolbox / More]

    J:::read -->|chronological review| U[Stored data]
    R:::read -->|Counters, urges, triggers| U
    P:::read -->|possible relationships| U
    E:::read -->|time series| U

    classDef read fill:#eaf6f1,stroke:#5b9b82,color:#173d31
Area Guiding question Role
Today What would I like to record today? Input and autosave
Journal What happened when? Chronological review
Recovery How is my change area developing? Counters, urges, triggers, strategies
Patterns What may be moving together? Life Wheel, people, exercise, correlations
Explorer How does a single metric develop? Freely configurable charts
Toolbox / More What else do I need? Tasks, milestones, export, settings

Product boundaries

  • no diagnosis and no medical device,
  • no automatic relapse decision,
  • no mandatory account,
  • no telemetry and no Steady backend,
  • correlations are not described as causes,
  • the Companion has no write access to tracking data.

Sources in the repository


System architecture

Runtime model

Steady is a React application, built with Vite and packaged as an Android app through Capacitor. The browser serves as a development and test environment; Android is the actual product target.

A build capability boundary in src/product-variant.js creates the general-audience product variant when VITE_PRODUCT_VARIANT=general-audience. It removes the built-in Alcohol and Pornography/compulsive-sexual-behavior profiles, their counters and 12-Step resources from the effective runtime configuration. Inventory, favorite texts and Twelve Steps are also excluded from General Audience navigation and feature chunks. Matching stored records are not passed to the interface in this variant, but they are not deleted. Both variants therefore retain a compatible schema.

The variant boundary also applies to initial configuration: general-audience uses the explicit canonical set in src/initial-user-config.js, with six general sections and 21 active fields. The Recovery variant uses its own canonical set with seven sections and 25 active fields. Browser and Android SQLite receive the same variant-specific seed. An initialConfigurationId marker prevents compatibility normalization from filling the clean Recovery start with absent v5 fields again; historical definitions that are actually present remain preserved.

Sample data follows the same boundary. src/data/demo-data.js contains only the neutral General Audience history. src/data/recovery-demo-data.js adds urges, triggers, urge-free check-ins, relapses and atomically linked resets for Recovery. Both variants provide a 90-day and a 1,095-day history. Every record carries source: "demo" so removal does not touch real data or Companion memories.

flowchart TB
    UI[React views and dialogs]
    APP[App state and application logic]
    SVC[Services: tracking, export, Companion]
    DB[db.js – shared data abstraction]
    IDX[(IndexedDB<br/>browser/test)]
    NATIVE[native-database.js]
    CAP[Capacitor plugin]
    SQL[(encrypted SQLite database)]

    UI --> APP --> SVC --> DB
    DB -->|Browser| IDX
    DB -->|Android| NATIVE --> CAP --> SQL

Layers and responsibilities

Layer Responsibility Examples
Views Presentation and interaction TodayView, RecoveryView, AnalysisViews
App orchestration Navigation, state, dialogs, data transfer App.jsx
Services Atomic domain operations tracking-service.js, export service
Data abstraction Reading, writing, migration, browser/Android switching db.js
Native boundary SQLite, Keystore, privacy, local AI Capacitor plugins

Why use a generic record store?

The native SQLite database uses logically separated stores but saves the actual objects as encrypted JSON payloads. This fits the configurable field structure: a new field does not automatically require a new SQL column.

Atomic operations

An urge with a relapse can create one event and several counter resets at the same time. These changes are written with applyBatch in one transaction. There is therefore no intermediate state in which the relapse has been stored but the counter has not yet been reset.

Native managed-device tests force failures after writes have already started for putMany, store replacement, applyBatch, full-store import and plaintext migration. All original records must remain unchanged afterwards. A manipulated data-key envelope or a replacement Android Keystore key is rejected; Steady neither overwrites the existing envelope nor resets the database automatically.

sequenceDiagram
    participant UI as Urge dialog
    participant S as tracking-service.js
    participant DB as db.js / applyBatch
    participant Store as events + resets

    UI->>S: store payload
    S->>S: prepare event and reset records
    S->>DB: one batch operation
    DB->>Store: commit everything
    Store-->>UI: success or complete failure

Sources