Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
355 lines
25 KiB
Markdown
355 lines
25 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; the dream journal (D14, the
|
|
developer's request of the same day) as step 5.
|
|
- **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 (four
|
|
since D14, the Journal between Explore and Profile).
|
|
*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.
|
|
|
|
## The dream journal (3 Nov 2026, the developer's request)
|
|
|
|
- **D14 A dream journal**, asked for by the developer on 3 Nov 2026 with what an entry
|
|
holds, that it is sealed and synced as the sittings, the Journal's list, search and
|
|
filters, Today's morning card and its switch in Settings, the empty state's word on
|
|
sealing, quick writing, and optional figures in Profile. What follows beyond that is
|
|
the agent's recommendation, to be confirmed:
|
|
- **An entry** (`Dream`): the night, named by the morning after it (a date at
|
|
midnight UTC, as every day the app counts) and by default today's date at any hour,
|
|
so "last night" even when written at 1 o'clock; a title, empty where none; the text;
|
|
the marks lucid, nightmare and recurring, as a set; clarity 1 to 5 or none; the mood
|
|
on waking from five (glad, calm, puzzled, sad, uneasy: few enough to choose half
|
|
awake, a mixed or neutral one left out as *none* says it); free labels (people,
|
|
places, themes, one list rather than kinds of label, each once whatever its case,
|
|
those used before offered by how often); and when it was first written, kept through
|
|
edits. Marks and moods by name in the payload (schema 1), so never renamed; a value a
|
|
version doesn't know reads as none.
|
|
- **Its own table, `dream`**, a row a dream, sealed for its table and row as the
|
|
sittings (D7), rather than rows in `sitting` or one row of all dreams: a dream is
|
|
changed and deleted on its own, and a row each keeps the sync's merges to the one
|
|
changed.
|
|
- **Where it lives: a fourth tab, Journal**, between Explore and Profile (Today,
|
|
Explore, Journal, Profile), as the developer suggested: a dream is written every
|
|
morning and searched later, a list of its own like Explore's, which under Profile
|
|
would be two taps deep and crowd the person's figures; four destinations still fit
|
|
the bar on a phone. Its icon is Lucide's `moon-star`. A dream opens over the tabs,
|
|
as a sitting and Settings do, for the whole screen while writing.
|
|
- **The Journal**: the newest month first, each month a card with its dreams the
|
|
newest night first; a search over title, text and labels as Explore's (every word,
|
|
without case or accents); the marks as filter chips, a dream shown where it carries
|
|
every one chosen; *+* above the bar as Train's buttons. Empty, it says what it is for
|
|
and that every entry is sealed on the device before it syncs.
|
|
- **Writing**: the text field first, focused, the rest (night, title, marks, clarity,
|
|
mood, labels) folded below it for a new dream and unfolded for a kept one; Save
|
|
once there is text. A kept dream opens to read, with Edit and Delete (asked first);
|
|
leaving a dream written but not saved asks first.
|
|
- **Today's card** from 4 to 12 o'clock while last night has no dream, below the
|
|
greeting, leading to a new dream; switched off in Settings' *Journal* card
|
|
(`dreamPrompt`, on at first, in the sealed settings: schema 1 stays, as a reader
|
|
that doesn't know it reads it as at first).
|
|
- **Profile's Practice** shows, once the journal holds a dream, a small *Dream
|
|
journal* card: the dreams of this month and the lucid ones of all.
|
|
- Not done: reminders to write (D12), dreams linked to sittings, figures over time.
|
|
- **D15 Dreams spoken** (3 Nov 2026, the developer's request): speech-to-text in the
|
|
journal's editor, as Learn's assistant field takes questions spoken (Halkyon Learn's
|
|
ADR 0021, D67: the device's own speech recognition, `speech_to_text`). The agent's
|
|
recommendations, to be confirmed:
|
|
- **Shared, not copied**: Learn's `SpeechInput` and `DeviceSpeechInput` move into
|
|
halkyon_app with their fake, Learn importing them from there unchanged; beside them
|
|
`Dictation`, generic, which writes what is heard where the cursor stands (in place
|
|
of a selection, set apart by a space on either side where the text has none) and
|
|
shows the words so far. Learn's button stays Learn's: it sends, and replaces from
|
|
where it started. Todo and Train link the plugin now but declare no microphone, so
|
|
nothing there listens.
|
|
- **In the editor**: a microphone under the text, where the device recognises speech
|
|
(none on Linux, as Learn's); listening, it turns into Stop with "Listening…" and the
|
|
words heard beside it. It listens in the app's language, else the device's own.
|
|
- **A dream is longer than a question: it restarts by itself** while the button stays
|
|
on, rather than offering to continue: half awake, a tap after every pause would
|
|
interrupt the telling, and the button says throughout that it listens. Each listen
|
|
may pause 30 s and last 10 minutes (Apple's recogniser stops sooner, Android's after
|
|
a few seconds' silence whatever is asked), and after three listens running hear
|
|
nothing it stops by itself, so a phone left on the table doesn't listen on.
|
|
- **Privacy**: on Android Meditate asks for recognition on the device
|
|
(`DeviceSpeechInput(preferOnDevice: true)`: Android 12's on-device recogniser where
|
|
the language is installed, else the recogniser with *prefer offline*); should that
|
|
fail before a word is heard, it listens as Android otherwise does from then on, which
|
|
may send the sound to Google or the phone's maker. Apple's and Windows' recognisers
|
|
choose for themselves (the plugin's on-device flag on Apple fails outright where
|
|
unsupported, so it isn't asked). The instance never hears it. The privacy page says
|
|
so where Meditate's server serves it (`privacyPagePoints` in `meditate_base.dart`,
|
|
with what Meditate keeps sealed), as Learn's does.
|
|
- **Today's card** offers *Speak it* beside it, opening a new dream already listening.
|
|
- The platforms declare it as Learn's do: `RECORD_AUDIO` and the recognition service's
|
|
query on Android, the microphone's and speech recognition's usage texts on iOS and
|
|
macOS, and macOS's `audio-input` entitlement.
|
|
|
|
## The operator's library (3 Nov 2026, the developer's request)
|
|
|
|
- **D16 Guided content the operator provides**, asked for by the developer on 3 Nov
|
|
2026, structured as 7Mind's catalogue is (items with a title, a category, a
|
|
description, a speaker and recordings; categories; courses of ordered lessons). Only
|
|
the structure is taken: nothing of 7Mind's (texts, recordings, names, ids) is in the
|
|
repository, its tests or its fixtures, whose entries are made up. The agent's
|
|
recommendations, to be confirmed:
|
|
- **A folder and a manifest.** The operator mounts a folder into Meditate's server and
|
|
names it in `MEDITATE_LIBRARY_DIR`: recordings and pictures, and `library.yaml`, YAML
|
|
rather than JSON because operators write it by hand and want comments (a commented,
|
|
made-up example: `halkyon_meditate_server/library.example.yaml`). An item: `id` (the
|
|
operator's slug, a-z, 0-9 and `-`, which sittings, favourites and lessons done refer
|
|
to, so never changed), `title`, `category`, `description`, `speaker`, `language`
|
|
(BCP 47), `image`, one or more recordings under `audio` (each a `file`, a `label`
|
|
telling them apart by length or voice, and a `duration`), and **`licence` and
|
|
`credit`, required**, with an optional `licence-url`. A course: `id`, `title`,
|
|
`description`, `category`, `language`, `image` and `lessons` in order, each an
|
|
`item` with an optional `title` of its own. The duration is the manifest's, in
|
|
seconds or `m:ss`, not read from the file: reading MP3's, AAC's and Opus's lengths
|
|
without a decoder is unreliable, and a wrong length would count wrong sittings; a
|
|
check that reads it can come with the deploy's image.
|
|
- **Categories**: Meditate's eleven keys (`meditation`, `sleep-story`, `sound`,
|
|
`movement`, `autogenic-training`, `progressive-muscle-relaxation`, `routine`,
|
|
`dream-journey`, `knowledge`, `morning-story`, `other`), named by the app in English
|
|
and German; and **the operator's own** under `categories:` with a key and an English
|
|
and a German name, never one of Meditate's keys: an instance's library is its own,
|
|
and "other" alone would hide what it offers.
|
|
- **Checked as read, refusing what fails, with the reason**: an item without a licence
|
|
or credit, a recording or picture outside the folder (a `..`, an absolute path or a
|
|
link leading out, each resolved), missing, or of another type (recordings: MP3, M4A,
|
|
AAC, Ogg, Opus, FLAC; pictures: JPEG, PNG, WebP), a wrong id, category or language,
|
|
no duration, an id taken twice, a course pointing at an item not served; the rest is
|
|
served. A manifest that doesn't read at all leaves the library as it was served (an
|
|
edit half done), one removed ends it. `bin/check_library.dart <folder>` reads a folder
|
|
as the server would, before it is deployed, and exits 1 on any problem.
|
|
- **In memory, not in the database**: the manifest is the library's whole truth and
|
|
small, read as the server starts and again every ten minutes by a future call
|
|
(`LibraryReloadFutureCall`, as Learn's dictionaries are reread), an unchanged folder
|
|
costing a hash of the manifest and a stat of each file. No table, no migration, and
|
|
nothing to keep in step.
|
|
- **For signed-in people alone** (`LibraryEndpoint.catalogue`, `requireLogin`): the
|
|
operator's licences may not allow offering it to the public.
|
|
- **The files on Serverpod's web server**, `/library/<served name>`, by ranges (relic's
|
|
`StaticHandler`: `Range`, `If-Range`, `ETag`), as the platforms' players fetch a
|
|
recording. A file is asked for by a served name the reading gave it (a hash of its
|
|
place, size and time with its extension); no request's path reaches the file system.
|
|
Since players can't all send the session's header, the catalogue brings an
|
|
**access**: an hour's start plus 12 hours, signed with a key the server makes as it
|
|
starts (a restart ends every access; the app asks again), in the URL's query.
|
|
Without one: 401; one not signed here or ended: 403; any other name: 404. Starting
|
|
from the hour, the access is the same for everyone given it within that hour, so a
|
|
fetch names no account (the privacy page says so). Rejected: an endpoint streaming
|
|
the bytes (Serverpod's endpoints answer whole, no ranges), Caddy serving the folder
|
|
(it can't check a session or an access without a plugin).
|
|
- **In the app**: Explore shows the library below Meditate's six practices, with chips
|
|
of the categories it uses and the same search, its courses (lessons done counted)
|
|
then its items; an item's page has its recordings, Play, the heart, keeping it on the
|
|
device, for a sound a silent sitting with it looped (the timer's length), and its
|
|
credit and licence; a course's page lists its lessons, ticked once done. The player
|
|
(`GuidePlayer` on audioplayers, beside the bells') plays, pauses and seeks; played to
|
|
its end, an item is kept as a sitting of its whole length under `library/<id>`, so
|
|
the figures count it, and as its course's lesson done; stopped before, nothing.
|
|
**Playing on with the screen off** (Android's foreground service and media
|
|
notification, iOS's `audio` background mode, as `audio_service` does) is for later:
|
|
not cheap to do well.
|
|
- **The person's, sealed and synced**: favourites of the library in the settings' list
|
|
beside the practices', as `library/<id>`; lessons done in a new table, `lesson_done`,
|
|
a row each under an ID keyed by the account, the course and the item, so two devices
|
|
doing it make one row; both name the operator's ids, so a lesson whose course went
|
|
counts again should it return.
|
|
- **Offline**: the last catalogue is kept in the person's folder (`library.json`), and a
|
|
recording is kept on the device only on the person's asking, removed again on it (and
|
|
with their folder); a kept one plays from there.
|
|
- **No library, nothing**: an instance without one shows no section in Explore and no
|
|
credits in Settings.
|
|
- **Credits**: Settings lists every item's credit and licence beside About (*Library
|
|
credits*), and each item's page shows its own.
|
|
- **The deploy, later** (step 6): the folder mounted read-only into Meditate's
|
|
container (`/srv/meditate-library:/library:ro`, `MEDITATE_LIBRARY_DIR=/library`),
|
|
Caddy forwarding `/library/*` to the web server with the rest, `check_library`
|
|
compiled into the image so an operator checks a folder on the host.
|
|
|
|
## 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. **The dream journal** (3 Nov): D14, the `dream` table and its migration, the Journal
|
|
tab, a dream's screen, Today's card, Settings' *Journal*, Profile's figures; and
|
|
dreams spoken (D15).
|
|
6. **The operator's library** (3 Nov): D16, the server's reading, endpoint, files and
|
|
check, the `lesson_done` table and its migration, Explore's section, an item's and a
|
|
course's pages, the player, keeping on the device, the credits.
|
|
7. **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.
|
|
- `dreams` (D14): the table `dream` as `sitting` is (`CREATE TABLE "dream"`: `id` a UUID
|
|
v7 by default, the function defined again with `CREATE OR REPLACE`, unchanged; a
|
|
nullable `spaceId`; `keyId`, `payload` not null and `nonce`, the envelope), the index
|
|
`dream_space_idx` on `spaceId`, its foreign key to `offline_sync_spaces("id")` with
|
|
`ON DELETE CASCADE`, so a person deleted takes their dreams with their space; then the
|
|
version rows. A new, empty table: nothing existing changes.
|
|
- `library` (D16): the table `lesson_done` as `dream` is (`CREATE TABLE "lesson_done"`
|
|
with the same columns, `id` a UUID v7 by default, the function again `CREATE OR
|
|
REPLACE`), the index `lesson_done_space_idx` on `spaceId` (btree), its foreign key to
|
|
`offline_sync_spaces("id")` `ON DELETE CASCADE`, so a person deleted takes their
|
|
lessons done with their space; then the version rows. The library itself has no
|
|
table. A new, empty table: nothing existing changes.
|
|
|
|
## 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 Meditate's own
|
|
practices in order; for the library (D16), playing on with the screen off, durations
|
|
read from the files, the pictures shown.
|
|
|
|
## 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, and an instance's library (D16)
|
|
brings it where its operator offers one.
|
|
- An operator's library is theirs to license: Meditate refuses an item without a licence
|
|
and credit, but can't check that they hold.
|