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