halkyon/apps/meditate/AGENTS.md
Tizian 「ティツィアーン」 fd9c2dfe76
feat(meditate,deploy): Meditate beside the instance, and every server's public address from .env
Meditate's deploy step (ADR 0038, step 7; ADR 0039, D14), as Todo's: the
server's Dockerfile with the library's check compiled beside it, its
production config on its own database and role, compose.meditate.yaml as a
member of the register, its site sending /library/* to the web server and
the rest to the API server, the operator's library read-only in a compose
file of its own, and redeploy.sh, backup.sh and the image replay taking it
in.

Every server's public address now comes from .env (Serverpod's
SERVERPOD_API_SERVER_PUBLIC_HOST and _WEB_SERVER_, which win over the
config file): Train, Todo and Meditate from their domains, Learn from
LEARN_PUBLIC_HOST, unset learn.halkyon.eu as before. Without it a server on
halkyon.li would name halkyon.eu in its SimpleLogin callback, profile pages
and privacy page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-08 19:59:39 +01:00

10 KiB

Halkyon Meditate

What holds for every app and package of this repository is in the root 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 (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.
  • 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, 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: on Android the voice input app first where one is installed, D18, opened once a tap and its text put where the cursor stood, else Android's recogniser, on the device where it can, 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, the recognition service's and RECOGNIZE_SPEECH's queries, VoiceInput.kt answering app.halkyon/voice_input from MainActivity, 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, the device's appearance and language (halkyon_app's DeviceLookCard on LookCubit, kept in <app support>/look.json, meditateLanguages; ADR 0014's amendment of 4 Nov 2026), 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 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.
  • The deploy (ADR 0038, step 7; ADR 0039, D14, 8 Nov 2026): the server's Dockerfile (as Todo's, with bin/check_library compiled beside the server) and config/production.yaml (its database halkyon_meditate as its own role; the public host from .env, MEDITATE_DOMAIN), deploy/compose.meditate.yaml (a member of the register) with sidecars/meditate.caddy (/library/* to the web server, the rest to the API server), the library in deploy/compose.meditate-library.yaml (MEDITATE_LIBRARY, read-only), redeploy.sh and backup.sh taking it in; test/deploy/meditate_deploy_test.dart; the steps in deploy/sidecars/README.md, Meditate. Not deployed yet: halkyon.li is its first instance.
  • Not yet: releases (apps/meditate/release.dart, its key, F-Droid listing, snap), and what ADR 0038 lists under Later.