docs(meditate): ADR 0038, what Halkyon Meditate does and how it looks
Proposed by the agent on 3 Nov 2026, the developer having asked to go on without their interjection: three tabs (Today, Explore, Profile) on Todo's and Train's frame, fern washes, Halkyon's own practices as text cues with a silent timer, bells synthesised for the app, a sitting over the tabs, sealed and synced settings and sittings, the figures worked out on the device, development ports of its own. Every decision is the agent's recommendation, to be confirmed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
f5b657dfad
commit
504aca3e68
1 changed files with 171 additions and 0 deletions
171
docs/adr/0038-halkyon-meditate.md
Normal file
171
docs/adr/0038-halkyon-meditate.md
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
# 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.
|
||||||
Loading…
Reference in a new issue