halkyon/docs/adr/0038-halkyon-meditate.md
Tizian 「ティツィアーン」 504aca3e68
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>
2026-11-04 09:31:56 +01:00

12 KiB

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.