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>
10 KiB
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 IDapp.halkyon.meditateon 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 inserver.dart(meditateBase(),lib/src/meditate_base.dart): its basics say it servesmeditate(never change it once apps use it), sign-in mails name Halkyon Meditate, the source is this repository,minimumAppBuild1, 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 setsACCOUNT_REGISTER_URL(AccountsMode.fromEnvironment()) with the instance's sharedINSTANCE_SECRET;INSTANCE_MEMBER_MEDITATEnames 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) andlesson_done(D16), each indexingspaceId. Migrations:initial(Serverpod's, the auth modules', the module's and the sync engine's tables),sittings(the three tables),dreams(dream) andlibrary(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 intest/unit/library/(the manifest, the files over HTTP) andtest/integration/library/(the endpoint). - The app is halkyon_app's
InstanceGateandSessionGatewith Meditate's frame (meditateFrame) andServerpodInstanceChecker(app: 'meditate');MeditateDependenciesper instance,MeditatePersonper person (their sync,SettingsRepository,SittingRepository,DreamRepository,LibraryRepository,LessonRepository, and the device'sBellPlayer,GuidePlayerandScreenAwake); SimpleLogin's way back onapp.halkyon.meditate://and the loopback port 43826. The person's app (MeditatePersonApp) washes in fern (ScreenColor, D3) and holdsPracticeCubit(settings and sittings, read again on every change),JournalCubit(the dreams) andLibraryCubit(the instance's library, lessons done, recordings kept) for every screen. go_router (buildMeditateRouter,MeditatePaths): halkyon_ui'sNavigationFrame(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 itsSittingPlanasextra), a recording playing (ListeningScreen, itsListeningPlanasextra), the library's credits (/library-credits), a dream (DreamScreen,/dreamnew,/dream/:idkept) 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'sDictationonMeditatePerson.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,NoSpeechInputin 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'sRECORD_AUDIO, the recognition service's andRECOGNIZE_SPEECH's queries,VoiceInput.ktansweringapp.halkyon/voice_inputfromMainActivity, iOS's and macOS's usage texts, macOS'saudio-input), and the privacy page's points aremeditate_base.dart's. Profile (D2, D8): halkyon_app'sHalkyonProfilewith 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 arepracticesinlib/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 fromclock.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'sDeviceLookCardonLookCubit, 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_USanden_GBempty. 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 thanpumpAndSettle; a test needing the hour (Today's morning card) starts the app insidewithClock(startAt). - The operator's library (D16;
lib/src/library/on the server,lib/src/feature/library/in the app): a folder named byMEDITATE_LIBRARY_DIRwith recordings, pictures andlibrary.yaml(commented sample: the server'slibrary.example.yaml), read byreadLibrary(every check and refusal, with the reason), held in memory byMeditateLibraryand reread every ten minutes (LibraryReloadFutureCall,startLibrary); checked before deploying withdart run bin/check_library.dart <folder>in the server package.LibraryEndpoint.cataloguefor 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 aslibrary/<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 sittinglibrary/<id>and its lesson done), Settings' Library credits; the last catalogue in the person's folder (library.json), recordings kept inlibrary/beside it. Tests:test/feature/library/library_test.dartand the app test's group, ontest/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 byapps/meditate/tool/bells.py(Python's standard library, run fromapps/meditate; the same bytes every run) and played withaudioplayers(AudioplayersBellPlayer). Only while the app runs: bells with the screen off are for later. - The icon is Lucide's
sproutfrom cobalt through pacific into fern (ADR 0031, D5, D7), drawn by the repository'stool/app_icons.py(python3 -I tool/app_icons.py meditatefrom the root):assets/icon/halkyon_meditate_icon.png, Lucide's licence beside it, every platform's launcher icon. serverpod startruns inapps/meditate/halkyon_meditate_serveron 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-ignoredconfig/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, withbin/check_librarycompiled beside the server) andconfig/production.yaml(its databasehalkyon_meditateas its own role; the public host from.env,MEDITATE_DOMAIN),deploy/compose.meditate.yaml(a member of the register) withsidecars/meditate.caddy(/library/*to the web server, the rest to the API server), the library indeploy/compose.meditate-library.yaml(MEDITATE_LIBRARY, read-only),redeploy.shandbackup.shtaking it in;test/deploy/meditate_deploy_test.dart; the steps indeploy/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.