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:
parent
811182b376
commit
c44cf55fd4
1 changed files with 104 additions and 7 deletions
|
|
@ -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
|
||||
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
|
||||
|
|
@ -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
|
||||
tab, a dream's screen, Today's card, Settings' *Journal*, Profile's figures; and
|
||||
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
|
||||
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.
|
||||
|
|
@ -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
|
||||
`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 several practices in
|
||||
order (7Mind's courses), ambient sounds.
|
||||
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
|
||||
|
||||
|
|
@ -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
|
||||
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.
|
||||
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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue