171 lines
12 KiB
Markdown
171 lines
12 KiB
Markdown
|
|
# ADR 0038: Halkyon Meditate, what it does and how it looks
|
||
|
|
|
||
|
|
- **Status:** proposed 3 Nov 2026 by the agent, the developer having asked to "continue as
|
||
|
|
far as possible without my interjection"; every decision below is the agent's
|
||
|
|
recommendation unless it says the developer's, and is put to the developer with the
|
||
|
|
first version. Built the same day: steps 1 to 4.
|
||
|
|
- **Builds on:** ADR 0033 (Todo's shell, which Meditate copies as Train copied Todo's,
|
||
|
|
ADR 0030, D12); ADR 0030, D7, D14, D15 and D61 (sealed rows, the person's key, What's
|
||
|
|
new); ADR 0031, D5 and D7 (Meditate's icon: Lucide's `sprout`, cobalt through pacific
|
||
|
|
into fern); ADR 0014 and 0016 (the module and the register).
|
||
|
|
|
||
|
|
## Context
|
||
|
|
|
||
|
|
On 3 Nov 2026 the developer asked for Halkyon Meditate, following "the basic approach as
|
||
|
|
the other applications": the bar and the rail, the theme, the settings and account
|
||
|
|
screens; and to look at eleven screenshots of 7Mind's Android app (1 Nov 2026,
|
||
|
|
`~/Downloads/7mind-screenshots/`). On 2 Nov they chose fern as Meditate's backdrop
|
||
|
|
(ADR 0031, D5). What the screenshots show:
|
||
|
|
|
||
|
|
- **Three tabs**: *For you*, *Explore*, *Profile*.
|
||
|
|
- **For you**: a greeting with the person's name; a band of newly added content, each a
|
||
|
|
card with its kind (Meditation, Sound, Sleep Story, Movement, Morning Stories, a
|
||
|
|
course of several tracks), its length and its speaker; *Your favorites* and *Recently
|
||
|
|
played*, each a row of the same cards with *More*.
|
||
|
|
- **Explore**: a search field, four goals as large tiles (reduce stress, sleep better,
|
||
|
|
learn to meditate, take care of yourself), categories as a row of icons (autogenic
|
||
|
|
training, breathing, dream journeys, knowledge, meditation, morning stories, movement,
|
||
|
|
progressive muscle relaxation, routines, sleep stories, sounds), and topics as a list
|
||
|
|
of cards with a line each (sleep, strength, personal development, relaxation, feelings
|
||
|
|
and relationships, daily life, silence and sounds).
|
||
|
|
- **Profile**: minutes, sessions and days as large figures; the week's days as dots with
|
||
|
|
the days in a row; Favorites, Downloads and *Your path*; then Account, Subscription,
|
||
|
|
Notifications, Language, Appearance, the streak's visibility, help, legal information.
|
||
|
|
- No player is shown; 7Mind's content is recorded speech by its speakers, behind a
|
||
|
|
subscription.
|
||
|
|
|
||
|
|
Halkyon has no recorded meditations, and none may be taken from 7Mind (its content, texts,
|
||
|
|
names and pictures are its own). Everything below is Halkyon's own.
|
||
|
|
|
||
|
|
## Decisions (3 Nov 2026, the agent's, to be confirmed)
|
||
|
|
|
||
|
|
- **D1 Meditate's frame is Todo's and Train's** (ADR 0033, D1; ADR 0030, D60): go_router
|
||
|
|
with a `StatefulShellRoute`, each tab keeping its own stack, halkyon_ui's
|
||
|
|
`NavigationFrame` (the bar floating above the foot below medium windows, a rail with
|
||
|
|
Meditate's icon on top from medium on), halkyon_app's first start, sign-in and
|
||
|
|
account, What's new after an update, About, English and German. Copied from Todo's
|
||
|
|
and renamed, as Train's was (ADR 0030, D12), Todo's own features left out.
|
||
|
|
- **D2 Three tabs: Today, Explore, Profile**, as 7Mind has three, Today opening.
|
||
|
|
*Today* (not *For you*: nothing is chosen for the person by anyone) starts a sitting at
|
||
|
|
once, shows the week and the streak, the favourite practices and the ones done last.
|
||
|
|
*Explore* holds every practice, by kind and by what it is for, with search. *Profile* is
|
||
|
|
every Halkyon app's (`HalkyonProfile`, decided 2 Nov 2026): the person's own tab first,
|
||
|
|
then Meditate's *Practice* tab with the figures, the week and the sittings. The cog to
|
||
|
|
Settings stands atop Today and Profile, as Train's does atop Home and Profile. Over four
|
||
|
|
tabs with the timer as one: the timer is one tap from Today and from Explore, and a
|
||
|
|
sitting is a screen over the tabs (D6), as Train's recording is.
|
||
|
|
- **D3 The washes are fern** (the developer's, 2 Nov: ADR 0031, D5): every tab and the
|
||
|
|
sitting's screen wash down from the top in `PaletteColor.fern`; buttons and links stay
|
||
|
|
cobalt, as in every app.
|
||
|
|
- **D4 What is practised: Halkyon's own practices, guided by text and bells, and the
|
||
|
|
silent timer.** Without recorded speech, a *practice* is a sitting of a set or chosen
|
||
|
|
length with **cues**: short lines of guidance shown on the sitting's screen one after
|
||
|
|
the other, spread evenly over the length chosen (so a practice scales to each of its
|
||
|
|
lengths), and later read out by the device's text-to-speech (see *Later*). The
|
||
|
|
practices are written for Meditate in English and German (`practice.dart` holds what
|
||
|
|
each is, the ARBs its words), so their licence is the app's. Version 0.1.0 has six:
|
||
|
|
counting breaths, a three-minute pause, a body scan, open awareness, kind wishes
|
||
|
|
(loving-kindness) and settling for sleep, under the kinds Breathing, Body, Awareness,
|
||
|
|
Kindness, Sleep and Pause, and the goals *Calm down*, *Sleep*, *Focus* and *Begin*;
|
||
|
|
breath at the nostrils, walking, the 4-7-8 breath and box breathing come next, the
|
||
|
|
breathing ones with a paced ring. A practice's ID never changes once released. The
|
||
|
|
**silent timer** is a sitting of any length from 1 to 120 minutes with a bell at the
|
||
|
|
start, at the end and, if chosen, every 1 to 30 minutes between. Recorded guidance (the
|
||
|
|
instance's or the person's own files) is a later step: it needs the instance to hold and
|
||
|
|
serve files, their licences checked, and their downloads kept on the device.
|
||
|
|
- **D5 The bells are made for Meditate**: three sounds (a singing bowl, a small bell, a
|
||
|
|
wooden block) synthesised by `apps/meditate/tool/bells.py` (Python's standard library
|
||
|
|
alone: decaying partials with the bowl's beating) into `assets/bells/*.wav`, so they
|
||
|
|
carry the app's licence and no one else's; played with `audioplayers` (already in the
|
||
|
|
workspace's lock, Learn's). Over recorded bells from freesound.org and the like, whose
|
||
|
|
licences differ file by file.
|
||
|
|
- **D6 A sitting is a screen over the tabs**: a ring filling as the time passes, what
|
||
|
|
is left inside it counted down by the seconds begun (5:00 until a second has passed),
|
||
|
|
the practice's cue
|
||
|
|
of the moment, Pause and Resume, and End (also the close button and back). Time is counted from the clock, not from a
|
||
|
|
ticker, so a sitting paused by the system or the app going to the background is right
|
||
|
|
as it comes back. The screen stays on while it runs where the person's setting says so
|
||
|
|
(on by default, `wakelock_plus`, as the lock already holds it). Ended early, it asks
|
||
|
|
whether to keep what was sat (Keep, Discard, or Sit on); a sitting under a minute is
|
||
|
|
never kept and ends without asking. At the end, the bell, and *Done* with what was
|
||
|
|
sat. Bells while the app is in the background are a later step (a scheduled
|
||
|
|
notification's sound), as is reading the cues out.
|
||
|
|
- **D7 What is kept: sealed rows, synced, as Todo's and Train's** (ADR 0033, D5;
|
||
|
|
ADR 0030, D7, D15): the person's key (`content_key`), one **settings** row
|
||
|
|
(`meditate_settings`: the bell, the interval and starting bells, the screen kept on, the
|
||
|
|
favourite practices, whether the streak shows) and a row a **sitting** (`sitting`: the
|
||
|
|
practice or the timer, when it started as a wall-clock time with the device's offset,
|
||
|
|
how long it was planned and sat). Everything is in the sealed payload; the instance
|
||
|
|
sees that rows exist, their sizes and when they changed. Called *sitting*, not
|
||
|
|
*session*, as Serverpod's `Session` is every endpoint's. A person's data goes with them
|
||
|
|
(`deleteMeditateData`, the sync spaces' cascade, as Todo's).
|
||
|
|
- **D8 The figures are worked out on the device from the sittings**: minutes, sittings,
|
||
|
|
days with a sitting, the days in a row (up to today, or up to yesterday while today has
|
||
|
|
none yet, so a streak doesn't break before the day is over), and the week's days as
|
||
|
|
dots from the week's first day in the person's language. The streak can be hidden
|
||
|
|
(Settings, as 7Mind's *Streak visibility*), as some find streaks a pressure.
|
||
|
|
- **D9 Today** greets by the time of day (no name: the account has a handle, not
|
||
|
|
necessarily a first name), offers *Sit now* with 5, 10 and 20 minutes and the timer's
|
||
|
|
own length, the practice of the day (one after the other by the date, the same on
|
||
|
|
every device), the week with the streak, the favourites and the last three sittings.
|
||
|
|
- **D10 Explore** is a search field (titles, summaries and kinds, every word typed,
|
||
|
|
without case or accents), the goals as four tiles (chosen again to let go), the kinds
|
||
|
|
as chips, and every practice as a card,
|
||
|
|
narrowed by what is chosen. A practice's page says what it is, its length (a choice of
|
||
|
|
lengths where it scales), how it goes (its cues in order), its favourite heart and
|
||
|
|
*Start*.
|
||
|
|
- **D11 Settings** as Todo's (ADR 0033, D4): Sync's card first, the operator's card, then
|
||
|
|
Meditate's own *Sitting* card (the bell, the starting bell, the interval bell's
|
||
|
|
default, the screen kept on, the timer's default length) and *Practice* card (the
|
||
|
|
streak shown), the account, What's new and About, the version below.
|
||
|
|
- **D12 No reminders and no notifications in the first version**: a daily reminder at a
|
||
|
|
time the person sets is the first thing to add after it (Todo's notifications show the
|
||
|
|
way).
|
||
|
|
- **D13 Development ports of its own**: API 8280, Insights 8281, web 8282, PostgreSQL
|
||
|
|
8290 (Learn's and Todo's are 8080 to 8090, Train's 8180 to 8190), so Meditate's server
|
||
|
|
runs beside the others as a member of Learn's register.
|
||
|
|
|
||
|
|
## Steps
|
||
|
|
|
||
|
|
1. **The shell** (3 Nov): Todo's copied and renamed, its features left out, the three tabs
|
||
|
|
(D1, D2, D3), Settings (D11), the icon (ADR 0031, D7).
|
||
|
|
2. **The data** (3 Nov): the server's models and its migrations, the device's key,
|
||
|
|
settings and sittings (D7).
|
||
|
|
3. **The practices and the sitting** (3 Nov): the catalogue (D4), the bells (D5), the
|
||
|
|
sitting's screen (D6), Explore (D10).
|
||
|
|
4. **Today and Profile** (3 Nov): D8, D9.
|
||
|
|
5. **Released and deployed** (later, as Todo's step 5): `apps/meditate/release.dart`, a key
|
||
|
|
of its own, the F-Droid listing, a snap, `deploy/compose.meditate.yaml` at
|
||
|
|
meditate.halkyon.eu (its `.env` values the developer has set already, 2 Nov), its
|
||
|
|
database and role, `redeploy.sh` and `backup.sh` taking it in.
|
||
|
|
|
||
|
|
## Migrations
|
||
|
|
|
||
|
|
- `initial`: Serverpod's tables, its auth modules', halkyon_base's and the OIDC module's
|
||
|
|
as they stand, as Train's first migration (ADR 0030); only `CREATE TABLE`, `CREATE
|
||
|
|
INDEX` and foreign keys, nothing to transform.
|
||
|
|
- `initial` also holds the sync engine's tables (`offline_sync_*`), as the module is
|
||
|
|
in `config/generator.yaml` from the start.
|
||
|
|
- `sittings`: Meditate's three tables, `content_key`, `meditate_settings` and `sitting`
|
||
|
|
(`CREATE TABLE`, each an `id` UUID made by `gen_random_uuid_v7()`, which the migration
|
||
|
|
defines again with `CREATE OR REPLACE`, a nullable `spaceId`, and the envelope's
|
||
|
|
columns), an index on each `spaceId` (`CREATE INDEX … USING btree`) so deleting a
|
||
|
|
person doesn't read the tables, and each `spaceId`'s foreign key to
|
||
|
|
`offline_sync_spaces` deleting with the space (`ON DELETE CASCADE`); then the version
|
||
|
|
rows of every module (`INSERT … ON CONFLICT DO UPDATE` into `serverpod_migrations`).
|
||
|
|
Purely additive: no row exists to transform.
|
||
|
|
|
||
|
|
## Later
|
||
|
|
|
||
|
|
- Recorded guidance (D4), the cues read out by text-to-speech, bells in the background
|
||
|
|
(D6), a daily reminder (D12), sittings written to Health Connect as mindfulness
|
||
|
|
sessions (Android 14's `MindfulnessSessionRecord`), courses of several practices in
|
||
|
|
order (7Mind's courses), ambient sounds.
|
||
|
|
|
||
|
|
## Consequences
|
||
|
|
|
||
|
|
- The practices are text, short and translatable, so a new one is a change to
|
||
|
|
`practice.dart` and its words in the two ARBs; a practice once used keeps its ID, as
|
||
|
|
sittings name it.
|
||
|
|
- Without recorded voices, Meditate is a timer with guidance more than a library;
|
||
|
|
recorded guidance (D4) is what would make it more.
|