docs(meditate): ADR 0038, D16, the operator's library, as the developer asked

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-03 16:48:45 +01:00
commit c44cf55fd4
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
2 changed files with 104 additions and 7 deletions

View file

@ -3,9 +3,10 @@
What holds for every app and package of this repository is in the [root AGENTS.md](../../AGENTS.md); it applies here in full, and Learn's patterns (`apps/learn/AGENTS.md`) wherever Meditate needs the same thing. What Meditate is and why: [ADR 0038](../../docs/adr/0038-halkyon-meditate.md) (proposed 3 Nov 2026, built by an agent without the developer's interjection; its decisions await their reading). What holds for every app and package of this repository is in the [root AGENTS.md](../../AGENTS.md); it applies here in full, and Learn's patterns (`apps/learn/AGENTS.md`) wherever Meditate needs the same thing. What Meditate is and why: [ADR 0038](../../docs/adr/0038-halkyon-meditate.md) (proposed 3 Nov 2026, built by an agent without the developer's interjection; its decisions await their reading).
- **Packages**: `halkyon_meditate_server`, `halkyon_meditate_client` (generated, never edited), `halkyon_meditate_flutter`; app ID `app.halkyon.meditate` on every platform; no web build. Licences as Learn's (server SSPL, client and app AGPL; `LICENSE.md`). Copied from Todo's tree on 3 Nov 2026 and stripped to the shell, so its frame, keys and sealed rows are Todo's and Train's. - **Packages**: `halkyon_meditate_server`, `halkyon_meditate_client` (generated, never edited), `halkyon_meditate_flutter`; app ID `app.halkyon.meditate` on every platform; no web build. Licences as Learn's (server SSPL, client and app AGPL; `LICENSE.md`). Copied from Todo's tree on 3 Nov 2026 and stripped to the shell, so its frame, keys and sealed rows are Todo's and Train's.
- **The server** includes `halkyon_base` (ADR 0014) and configures it in `server.dart` (`meditateBase()`, `lib/src/meditate_base.dart`): its basics say it serves `meditate` (never change it once apps use it), sign-in mails name Halkyon Meditate, the source is this repository, `minimumAppBuild` 1, a person's space deleted with them (`deletePersonData: [deleteMeditateData]`, `lib/src/meditate_data.dart`). **Accounts** (ADR 0016): Meditate holds its own register when it runs alone, and is a member of the instance's register where the operator sets `ACCOUNT_REGISTER_URL` (`AccountsMode.fromEnvironment()`) with the instance's shared `INSTANCE_SECRET`; `INSTANCE_MEMBER_MEDITATE` names it to the register. `schedulePruning` (`lib/src/pruning.dart`) keeps the module's pruning scheduled once each. Synced tables (ADR 0038, D7; `database: sync`, every row sealed): `content_key`, `meditate_settings` (one row a person, under an ID keyed by the account), `sitting` and `dream` (D14), each indexing `spaceId`. Migrations: `initial` (Serverpod's, the auth modules', the module's and the sync engine's tables), `sittings` (the three tables) and `dreams` (`dream`). Tests: `test/integration/meditate_server_test.dart` (the module's endpoints through the server's own dispatch, `halkyon_base.<endpoint>`, and the sync engine's connector refusing calls without a session), `test/integration/sitting/person_deletion_test.dart`. - **The server** includes `halkyon_base` (ADR 0014) and configures it in `server.dart` (`meditateBase()`, `lib/src/meditate_base.dart`): its basics say it serves `meditate` (never change it once apps use it), sign-in mails name Halkyon Meditate, the source is this repository, `minimumAppBuild` 1, a person's space deleted with them (`deletePersonData: [deleteMeditateData]`, `lib/src/meditate_data.dart`). **Accounts** (ADR 0016): Meditate holds its own register when it runs alone, and is a member of the instance's register where the operator sets `ACCOUNT_REGISTER_URL` (`AccountsMode.fromEnvironment()`) with the instance's shared `INSTANCE_SECRET`; `INSTANCE_MEMBER_MEDITATE` names it to the register. `schedulePruning` (`lib/src/pruning.dart`) keeps the module's pruning scheduled once each. Synced tables (ADR 0038, D7; `database: sync`, every row sealed): `content_key`, `meditate_settings` (one row a person, under an ID keyed by the account), `sitting`, `dream` (D14) and `lesson_done` (D16), each indexing `spaceId`. Migrations: `initial` (Serverpod's, the auth modules', the module's and the sync engine's tables), `sittings` (the three tables), `dreams` (`dream`) and `library` (`lesson_done`). Tests: `test/integration/meditate_server_test.dart` (the module's endpoints through the server's own dispatch, `halkyon_base.<endpoint>`, and the sync engine's connector refusing calls without a session), `test/integration/sitting/person_deletion_test.dart`, the library's in `test/unit/library/` (the manifest, the files over HTTP) and `test/integration/library/` (the endpoint).
- **The app** is halkyon_app's `InstanceGate` and `SessionGate` with Meditate's frame (`meditateFrame`) and `ServerpodInstanceChecker(app: 'meditate')`; `MeditateDependencies` per instance, `MeditatePerson` per person (their sync, `SettingsRepository`, `SittingRepository`, `DreamRepository`, and the device's `BellPlayer` and `ScreenAwake`); SimpleLogin's way back on `app.halkyon.meditate://` and the loopback port 43826. The person's app (`MeditatePersonApp`) washes in fern (`ScreenColor`, D3) and holds `PracticeCubit` (settings and sittings, read again on every change) and `JournalCubit` (the dreams) for every screen. go_router (`buildMeditateRouter`, `MeditatePaths`): halkyon_ui's `NavigationFrame` (`MeditateShell`) with Today, Explore, Journal and Profile (D2, D14; a bar below medium windows, a rail with the icon from medium on), a practice's page in Explore's branch (`PracticeScreen`), and over the tabs the sitting (`SittingScreen`, handed its `SittingPlan` as `extra`), a dream (`DreamScreen`, `/dream` new, `/dream/:id` kept) and Settings. **Today** (`TodayTab`, D9): greeting by the hour, from 4 to 12 o'clock a card to write down last night's dream while it has none (`dreamPromptDue`, D14), Sit now (5, 10, 20 and the timer's own length), the practice of the day (`practiceOfDay`), the week and the days in a row (`WeekCard`), favourites, recently sat. **Explore** (`ExploreTab`, D10): search folded without case or accents (`matchesSearch`), four goal tiles, kind chips, practice cards. **The Journal** (`JournalTab`, D14; `lib/src/feature/journal/`): dreams by month, the newest first (`dreamsByMonth`), searched over title, text and labels and filtered by marks (`findDreams`), *+* to write one; a dream's night is the date of the morning after it (`defaultNight`: today's date at any hour); its screen writes with the text first and focused, the rest folded, and reads a kept one with Edit and Delete; a microphone under the text dictates where the device recognises speech (D15: halkyon_app's `Dictation` on `MeditatePerson.speech`, `DeviceSpeechInput` preferring Android's on-device recogniser, listening again after each pause until stopped or three silent listens; none on Linux, `NoSpeechInput` in tests that don't say), and Today's card offers *Speak it* (`/dream?speak=1`). The platforms declare the microphone as Learn's do (Android's `RECORD_AUDIO` and recognition query, iOS's and macOS's usage texts, macOS's `audio-input`), and the privacy page's points are `meditate_base.dart`'s. **Profile** (D2, D8): halkyon_app's `HalkyonProfile` with a *Practice* tab (minutes, sittings, days, the week, the journal's dreams this month and lucid ones once it holds one, every sitting with Delete). **The practices** are `practices` in `lib/src/feature/practice/model/practice.dart` (IDs never change once released), their words in the ARBs by ID (`practiceTexts`), their cues spread evenly over the length (`Practice.cueAt`). **A sitting** (`SittingCubit`, D6): its time from `clock.now()`, the ticks only showing it; the bell at the start (where on), between every interval and at the end; the screen kept on while it runs (where on); kept whole at its end, and ended early kept from a minute on where the person says so. **The figures** (`practiceFigures`, D8) on the device: a sitting's day by the wall clock it was sat by (`Sitting.offsetMinutes`), the streak up to today or yesterday, the week from the language's first day. **Settings** (D11): Sync, the operator's card, *Sitting* (bell, starting bell, bells between, screen on, the timer's length), *Practice* (the streak shown), *Journal* (Today's morning card, `dreamPrompt`), the account, What's new, About, the version. Texts in British English (the template) and German (du), `en_US` and `en_GB` empty. Widget tests start the whole app with halkyon_app's fakes (`test/app/meditate_app_test.dart`), the bells and screen recorded (`test/support/recording_device.dart`); a sitting on screen ticks every second, so tests pump seconds (`sit`) rather than `pumpAndSettle`; a test needing the hour (Today's morning card) starts the app inside `withClock` (`startAt`). - **The app** is halkyon_app's `InstanceGate` and `SessionGate` with Meditate's frame (`meditateFrame`) and `ServerpodInstanceChecker(app: 'meditate')`; `MeditateDependencies` per instance, `MeditatePerson` per person (their sync, `SettingsRepository`, `SittingRepository`, `DreamRepository`, `LibraryRepository`, `LessonRepository`, and the device's `BellPlayer`, `GuidePlayer` and `ScreenAwake`); SimpleLogin's way back on `app.halkyon.meditate://` and the loopback port 43826. The person's app (`MeditatePersonApp`) washes in fern (`ScreenColor`, D3) and holds `PracticeCubit` (settings and sittings, read again on every change), `JournalCubit` (the dreams) and `LibraryCubit` (the instance's library, lessons done, recordings kept) for every screen. go_router (`buildMeditateRouter`, `MeditatePaths`): halkyon_ui's `NavigationFrame` (`MeditateShell`) with Today, Explore, Journal and Profile (D2, D14; a bar below medium windows, a rail with the icon from medium on), a practice's page in Explore's branch (`PracticeScreen`) with a library item's and course's (`/explore/library/:id`, `?course=` for a lesson, `/explore/courses/:id`), and over the tabs the sitting (`SittingScreen`, handed its `SittingPlan` as `extra`), a recording playing (`ListeningScreen`, its `ListeningPlan` as `extra`), the library's credits (`/library-credits`), a dream (`DreamScreen`, `/dream` new, `/dream/:id` kept) and Settings. **Today** (`TodayTab`, D9): greeting by the hour, from 4 to 12 o'clock a card to write down last night's dream while it has none (`dreamPromptDue`, D14), Sit now (5, 10, 20 and the timer's own length), the practice of the day (`practiceOfDay`), the week and the days in a row (`WeekCard`), favourites, recently sat. **Explore** (`ExploreTab`, D10): search folded without case or accents (`matchesSearch`), four goal tiles, kind chips, practice cards. **The Journal** (`JournalTab`, D14; `lib/src/feature/journal/`): dreams by month, the newest first (`dreamsByMonth`), searched over title, text and labels and filtered by marks (`findDreams`), *+* to write one; a dream's night is the date of the morning after it (`defaultNight`: today's date at any hour); its screen writes with the text first and focused, the rest folded, and reads a kept one with Edit and Delete; a microphone under the text dictates where the device recognises speech (D15: halkyon_app's `Dictation` on `MeditatePerson.speech`, `DeviceSpeechInput` preferring Android's on-device recogniser, listening again after each pause until stopped or three silent listens; none on Linux, `NoSpeechInput` in tests that don't say), and Today's card offers *Speak it* (`/dream?speak=1`). The platforms declare the microphone as Learn's do (Android's `RECORD_AUDIO` and recognition query, iOS's and macOS's usage texts, macOS's `audio-input`), and the privacy page's points are `meditate_base.dart`'s. **Profile** (D2, D8): halkyon_app's `HalkyonProfile` with a *Practice* tab (minutes, sittings, days, the week, the journal's dreams this month and lucid ones once it holds one, every sitting with Delete). **The practices** are `practices` in `lib/src/feature/practice/model/practice.dart` (IDs never change once released), their words in the ARBs by ID (`practiceTexts`), their cues spread evenly over the length (`Practice.cueAt`). **A sitting** (`SittingCubit`, D6): its time from `clock.now()`, the ticks only showing it; the bell at the start (where on), between every interval and at the end; the screen kept on while it runs (where on); kept whole at its end, and ended early kept from a minute on where the person says so. **The figures** (`practiceFigures`, D8) on the device: a sitting's day by the wall clock it was sat by (`Sitting.offsetMinutes`), the streak up to today or yesterday, the week from the language's first day. **Settings** (D11): Sync, the operator's card, *Sitting* (bell, starting bell, bells between, screen on, the timer's length), *Practice* (the streak shown), *Journal* (Today's morning card, `dreamPrompt`), the account, What's new, About, the version. Texts in British English (the template) and German (du), `en_US` and `en_GB` empty. Widget tests start the whole app with halkyon_app's fakes (`test/app/meditate_app_test.dart`), the bells and screen recorded (`test/support/recording_device.dart`); a sitting on screen ticks every second, so tests pump seconds (`sit`) rather than `pumpAndSettle`; a test needing the hour (Today's morning card) starts the app inside `withClock` (`startAt`).
- **The operator's library** (D16; `lib/src/library/` on the server, `lib/src/feature/library/` in the app): a folder named by `MEDITATE_LIBRARY_DIR` with recordings, pictures and `library.yaml` (commented sample: the server's `library.example.yaml`), read by `readLibrary` (every check and refusal, with the reason), held in memory by `MeditateLibrary` and reread every ten minutes (`LibraryReloadFutureCall`, `startLibrary`); checked before deploying with `dart run bin/check_library.dart <folder>` in the server package. `LibraryEndpoint.catalogue` for signed-in people alone, with an access (`LibraryFileAccess`: the hour begun plus 12 hours, signed with a key made at start); the web server's `/library/<served name>?access=…` serves the files by ranges (`serveLibraryFile`), never by a path. In the app: Explore's section (none without a library), the item's page (recordings, Play, favourite as `library/<id>` in the settings, keep on the device, a sound under a silent sitting, its credit), the course's (lessons ticked), the player (a finished item is a sitting `library/<id>` and its lesson done), Settings' *Library credits*; the last catalogue in the person's folder (`library.json`), recordings kept in `library/` beside it. Tests: `test/feature/library/library_test.dart` and the app test's group, on `test/support/fake_library.dart` (made-up entries only: nothing of 7Mind's, whose structure alone D16 follows).
- **The bells** (D5) are `assets/bells/*.wav`, synthesised by `apps/meditate/tool/bells.py` (Python's standard library, run from `apps/meditate`; the same bytes every run) and played with `audioplayers` (`AudioplayersBellPlayer`). Only while the app runs: bells with the screen off are for later. - **The bells** (D5) are `assets/bells/*.wav`, synthesised by `apps/meditate/tool/bells.py` (Python's standard library, run from `apps/meditate`; the same bytes every run) and played with `audioplayers` (`AudioplayersBellPlayer`). Only while the app runs: bells with the screen off are for later.
- **The icon** is Lucide's `sprout` from cobalt through pacific into fern (ADR 0031, D5, D7), drawn by the repository's `tool/app_icons.py` (`python3 -I tool/app_icons.py meditate` from the root): `assets/icon/halkyon_meditate_icon.png`, Lucide's licence beside it, every platform's launcher icon. - **The icon** is Lucide's `sprout` from cobalt through pacific into fern (ADR 0031, D5, D7), drawn by the repository's `tool/app_icons.py` (`python3 -I tool/app_icons.py meditate` from the root): `assets/icon/halkyon_meditate_icon.png`, Lucide's licence beside it, every platform's launcher icon.
- **`serverpod start`** runs in `apps/meditate/halkyon_meditate_server` on ports of its own (8280 to 8282, PostgreSQL 8290), so it can run beside Learn's (8080…) and Train's (8180…); its tests need throwaway passwords in its git-ignored `config/passwords.yaml` (`database`, `serviceSecret`, `serverSideSessionKeyHashPepper`, `emailSignInPepper`) or from the environment, as CI gives them. - **`serverpod start`** runs in `apps/meditate/halkyon_meditate_server` on ports of its own (8280 to 8282, PostgreSQL 8290), so it can run beside Learn's (8080…) and Train's (8180…); its tests need throwaway passwords in its git-ignored `config/passwords.yaml` (`database`, `serviceSecret`, `serverSideSessionKeyHashPepper`, `emailSignInPepper`) or from the environment, as CI gives them.
- **Not yet**: releases (`apps/meditate/release.dart`, its key, F-Droid listing, snap), the deploy beside learn.halkyon.eu (`compose.meditate.yaml`, `config/production.yaml`, the `Dockerfile`), and what ADR 0038 lists under *Later*. - **Not yet**: releases (`apps/meditate/release.dart`, its key, F-Droid listing, snap), the deploy beside learn.halkyon.eu (`compose.meditate.yaml` with the library's folder mounted read-only, Caddy forwarding `/library/*`, `config/production.yaml`, the `Dockerfile`), and what ADR 0038 lists under *Later*.

View file

@ -204,6 +204,89 @@ names and pictures are its own). Everything below is Halkyon's own.
query on Android, the microphone's and speech recognition's usage texts on iOS and query on Android, the microphone's and speech recognition's usage texts on iOS and
macOS, and macOS's `audio-input` entitlement. 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 ## Steps
1. **The shell** (3 Nov): Todo's copied and renamed, its features left out, the three tabs 1. **The shell** (3 Nov): Todo's copied and renamed, its features left out, the three tabs
@ -216,7 +299,10 @@ names and pictures are its own). Everything below is Halkyon's own.
5. **The dream journal** (3 Nov): D14, the `dream` table and its migration, the Journal 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 tab, a dream's screen, Today's card, Settings' *Journal*, Profile's figures; and
dreams spoken (D15). dreams spoken (D15).
6. **Released and deployed** (later, as Todo's step 5): `apps/meditate/release.dart`, a key 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 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 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. database and role, `redeploy.sh` and `backup.sh` taking it in.
@ -242,13 +328,20 @@ names and pictures are its own). Everything below is Halkyon's own.
`dream_space_idx` on `spaceId`, its foreign key to `offline_sync_spaces("id")` with `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 `ON DELETE CASCADE`, so a person deleted takes their dreams with their space; then the
version rows. A new, empty table: nothing existing changes. 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 ## Later
- Recorded guidance (D4), the cues read out by text-to-speech, bells in the background - 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 (D6), a daily reminder (D12), sittings written to Health Connect as mindfulness
sessions (Android 14's `MindfulnessSessionRecord`), courses of several practices in sessions (Android 14's `MindfulnessSessionRecord`), courses of Meditate's own
order (7Mind's courses), ambient sounds. practices in order; for the library (D16), playing on with the screen off, durations
read from the files, the pictures shown.
## Consequences ## Consequences
@ -256,4 +349,7 @@ names and pictures are its own). Everything below is Halkyon's own.
`practice.dart` and its words in the two ARBs; a practice once used keeps its ID, as `practice.dart` and its words in the two ARBs; a practice once used keeps its ID, as
sittings name it. sittings name it.
- Without recorded voices, Meditate is a timer with guidance more than a library; - Without recorded voices, Meditate is a timer with guidance more than a library;
recorded guidance (D4) is what would make it more. 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.