From a29ad45ef1e5656b0a96aa6c415422521f3c7e64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tizian=20=E3=80=8C=E3=83=86=E3=82=A3=E3=83=84=E3=82=A3?= =?UTF-8?q?=E3=82=A2=E3=83=BC=E3=83=B3=E3=80=8D?= Date: Thu, 12 Nov 2026 16:24:31 +0100 Subject: [PATCH] build: the organisation on code.halkyon.app is halkyon-days MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ADR 0045, D12, step 1: project-halkyon was renamed halkyon-days, its repositories keeping their names. Every URL naming it moves along: the servers' source-code links, which the AGPL requires, and their tests; the forks in the pubspecs and the lock (appflowy-editor, pure-zstd, serverpod-offline-sync); the snaps, the F-Droid listings and the release configurations; the docs. The curator's credit for the alphabets names the repository by its name since ADR 0015, halkyon, rather than halkyon_learn. Forgejo redirects the old addresses until the name is taken again. ADRs 0015 and 0039 keep the name they were written with, and the local folders theirs. halkyon_editor's gen-l10n caches under build/, committed before its .gitignore named the folder, are no longer tracked. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Tizian 「ティツィアーン」 --- apps/learn/AGENTS.md | 4 ++-- apps/learn/README.md | 4 ++-- .../halkyon_learn_curator/lib/src/sources/alphabets.dart | 2 +- apps/learn/halkyon_learn_flutter/pubspec.yaml | 4 ++-- .../test/support/fake_dictionary_gateway.dart | 2 +- .../learn/halkyon_learn_server/lib/src/auth/learn_base.dart | 3 +-- apps/learn/release.dart | 2 +- .../test/app/meditate_app_test.dart | 4 ++-- .../halkyon_meditate_server/lib/src/meditate_base.dart | 3 +-- .../test/integration/meditate_server_test.dart | 2 +- apps/todo/halkyon_todo_flutter/test/app/todo_app_test.dart | 4 ++-- apps/todo/halkyon_todo_server/lib/src/todo_base.dart | 3 +-- .../test/integration/todo_server_test.dart | 2 +- apps/todo/release.dart | 2 +- apps/todo/snap/snapcraft.yaml | 2 +- .../halkyon_train_flutter/test/app/train_app_test.dart | 4 ++-- apps/train/halkyon_train_server/lib/src/train_base.dart | 3 +-- .../test/integration/train_server_test.dart | 2 +- apps/train/release.dart | 2 +- apps/train/snap/snapcraft.yaml | 2 +- deploy/host/first-start.md | 2 +- docs/adr/0045-halkyon-days.md | 3 ++- packages/halkyon_base_server/README.md | 2 +- .../build/f05bfb3d5907fd0115f5797e28554b94/.filecache | 1 - .../gen_l10n_inputs_and_outputs.json | 1 - .../f05bfb3d5907fd0115f5797e28554b94/gen_localizations.d | 1 - .../gen_localizations.stamp | 1 - .../build/f05bfb3d5907fd0115f5797e28554b94/outputs.json | 1 - packages/halkyon_editor/pubspec.yaml | 2 +- pubspec.lock | 6 +++--- pubspec.yaml | 2 +- snap/snapcraft.yaml | 2 +- test/release/fixtures.dart | 2 +- tool/release/fdroid/metadata/app.halkyon.learn.yml | 2 +- tool/release/fdroid/metadata/app.halkyon.todo.yml | 2 +- tool/release/fdroid/metadata/app.halkyon.train.yml | 2 +- 36 files changed, 40 insertions(+), 48 deletions(-) delete mode 100644 packages/halkyon_editor/build/f05bfb3d5907fd0115f5797e28554b94/.filecache delete mode 100644 packages/halkyon_editor/build/f05bfb3d5907fd0115f5797e28554b94/gen_l10n_inputs_and_outputs.json delete mode 100644 packages/halkyon_editor/build/f05bfb3d5907fd0115f5797e28554b94/gen_localizations.d delete mode 100644 packages/halkyon_editor/build/f05bfb3d5907fd0115f5797e28554b94/gen_localizations.stamp delete mode 100644 packages/halkyon_editor/build/f05bfb3d5907fd0115f5797e28554b94/outputs.json diff --git a/apps/learn/AGENTS.md b/apps/learn/AGENTS.md index 45c815b5..700cd59b 100644 --- a/apps/learn/AGENTS.md +++ b/apps/learn/AGENTS.md @@ -67,7 +67,7 @@ A spaced-repetition learning app: studysets of flashcards, reviewed on an FSRS s - **halkyon.li is deployed by hand** (ADR 0039, D13; learn.halkyon.eu was deployed by CI until its server retired in November 2026) through `deploy/redeploy.sh`, the only command `deploy`'s key may run (the developer's, pinned in its `authorized_keys`), once the commit's night's CI run passed. It deploys the commit while it is still the tip, as a fast-forward, only if every new commit is signed by a key in `deploy/allowed-signers` (the deployed copy decides), after dumping the database (the last 3 dumps stay in `~deploy/backups`: the way back from a deploy, not backups), applies the migrations in a run of their own before switching (`--role=maintenance --apply-migrations`: a failing migration stops the deploy while the old server keeps serving, so migrations must keep the old server working until the switch), and fails unless the new server answers. A failed deploy leaves the checkout at the deployed commit; run it again to retry: `ssh balthasar-deploy ` from melchior, the commit alone (the server's checkout is `~deploy/halkyon`, renamed from `halkyon-learn` on 2 Nov 2026 with the command the keys are pinned to; the Compose project keeps the name `halkyon-learn`, as Docker names its volumes after it). Never `docker compose down -v` there.- **From the single repository** (ADR 0015): learn.halkyon.eu deploys this repository's `main`, whichever app or shared package a push changed. Its images are built from the repository's root with only what they need (Learn's server and the module it builds on, with the module's client, which the module's tests depend on; the curator), each Dockerfile narrowing the workspace to those packages (`tool/workspace_only.sh`). The first deploy after the move starts the server once more than it needs to, as the deployed copy of `redeploy.sh` looks for migrations at their old path. - **Sidecars** (`deploy/sidecars/README.md`, decided 2026-10-14): services an instance may run beside itself, each a compose file of its own beside `deploy/compose.learn.yaml` (`compose.jellyfin.yaml`, `compose.languagetool.yaml`; later Fedify), switched on in `deploy/.env` (`COMPOSE_FILE=compose.learn.yaml:compose.jellyfin.yaml`), so `redeploy.sh` deploys them with the instance. The instance's compose has two networks: `edge` (`halkyon-learn-edge`, 172.30.80.0/24 and the unique local fd4a:6c6b:7e80::/64, so Docker hands Caddy IPv6 visitors' own addresses), Caddy's at fixed addresses (172.30.80.2, fd4a:6c6b:7e80::2; the server's `TRUSTED_PROXIES`), shared with the server, and the internal default one, where the server reaches Postgres; a sidecar joins `edge` alone and publishes no port (the CI runner may reach `127.0.0.1`, never Docker's 172.16.0.0/12 or fd00::/8). A sidecar reached from outside mounts its site from `deploy/sidecars/` into Caddy's `/etc/caddy/sidecars/`, which the Caddyfile imports (none without), and `redeploy.sh` recreates Caddy when a site changes. Each runs as a user ID of its own (Jellyfin 61096, LanguageTool 61081), never one a person, another container or the runner uses; its files on the server are git-ignored (`deploy/jellyfin/`, `deploy/languagetool/`). The Jellyfin sidecar sets the server's `JELLYFIN_URL`, so the instance suggests it; the LanguageTool sidecar (ADR 0024) sets `LANGUAGETOOL_URL` and has no site, as LanguageTool has no sign-in: only the server reaches it, and logs nothing (`logging: none`). - **Backups** (`deploy/backups.md`): every hour, `deploy/backup.sh` (a systemd timer, as `deploy`) dumps the database, which holds people's files too, then bundles each published studyset's repository (`git bundle`, from the server's `repositories` volume), and restic stores both in a Hetzner Storage Box (24 hourly, 7 daily, 4 weekly, 3 monthly); the box's own snapshots protect it from a taken-over server. A restore takes the instance back up to an hour, and people who started since lose what their device held: their app no longer finds its session and asks how to start, and an account made since is gone with it. So a change that makes devices lose their session, or keeps deleted people's data longer, changes what a restore costs; say so in `deploy/backups.md`. -- **Releases** are the project's, not an instance's: the release, the Snap Store listing and the F-Droid listing point to the project's page on Halkyon's website, halkyon.app/learn (its own repository, `project-halkyon/website`; learn.halkyon.app redirects there); the builds name learn.halkyon.eu, the project's instance (`SERVER_URL`), which a new install offers first (see *Instances*). They come from a developer's laptop, never from CI: `dart run tool/release.dart learn` on `main` as pushed and green in CI (the apps' shared release tool, ADR 0015, configured in `apps/learn/release.dart`; `--dry-run` shows the version, the commits that count, every step and the notes without changing anything). Build 21 (`v0.18.0`) was the last from `tool/release.sh`, the Bash script it replaced, removed since. Versions follow semantic versioning, worked out from the commits touching Learn since its last release (`apps/learn/`, the shared packages it uses, the root pubspec and lock, its release files, and its packages' folders at the root from before the move; `feat` raises the minor number, `fix`/`perf` the patch number, breaking ones the major from 1.0 on; other types release nothing), so **commit messages keep their type prefix**. It writes the version and a raised build number into the app's pubspec in a signed release commit (`chore(release): learn-vX.Y.Z`) with a signed tag (`learn-vX.Y.Z`, by a key in `deploy/allowed-signers`; Learn's earlier `vX.Y.Z` tags count as its history), builds one universal APK from a checkout of the tag, signed with the app's key, and the snap (`snap/snapcraft.yaml`, the committed HEAD, built in LXD), both with `SERVER_URL=https://learn.halkyon.eu/`; signs `SHA256SUMS` with GPG; drafts what's new in the release for the developer to edit, committed with the version in `assets/whats_new/` (Train's ADR 0030, D61: the sheet after the update, Settings' entry above About, the notes and F-Droid's changelog); pushes only once both builds passed their checks; and publishes on code.halkyon.app through Forgejo's API and in the F-Droid repository. The snap goes to the Snap Store's edge channel last (`storeChannel` in `apps/learn/release.dart`; the release refuses to start unless `snapcraft whoami` shows a login). A run that stopped after its release commit and tag carries on with them. The key's password and the Forgejo token are asked for at each run and never stored. The app's key is a PKCS #12 keystore outside the repository (also in the maintainer's password manager); there is no Google Play, so it is the app's identity: lost, installed apps can't be updated. Its certificate's SHA-256 is in `tool/release/android-certificate.sha256`, and the release tool releases no APK signed otherwise; other release builds fall back to the debug key (`android/app/build.gradle.kts`), so `flutter run --release` works anywhere. The APK also goes into the project's own F-Droid repository, https://halkyon.app/fdroid/repo, through `tool/fdroid_repo.sh` (fdroidserver v2.4.5): its settings and the app's listing are in `tool/release/fdroid/` (the anti-feature NonFreeNet declared with its reasons and F-Droid's own icon; not TetheredNet, as the app chooses its instance); its index is signed with a key of its own (a PKCS #12 keystore beside the app's, password asked for, certificate pinned in `tool/release/fdroid-index-certificate.sha256`, since a new key makes the F-Droid app refuse the repository); and it is uploaded to the halkyon.app server (`ssh ramiel-fdroid`) as the user `fdroid`, whose key can only write into the folder Caddy serves (rrsync), the APKs first and the indexes last. +- **Releases** are the project's, not an instance's: the release, the Snap Store listing and the F-Droid listing point to the project's page on Halkyon's website, halkyon.app/learn (its own repository, `halkyon-days/website`; learn.halkyon.app redirects there); the builds name learn.halkyon.eu, the project's instance (`SERVER_URL`), which a new install offers first (see *Instances*). They come from a developer's laptop, never from CI: `dart run tool/release.dart learn` on `main` as pushed and green in CI (the apps' shared release tool, ADR 0015, configured in `apps/learn/release.dart`; `--dry-run` shows the version, the commits that count, every step and the notes without changing anything). Build 21 (`v0.18.0`) was the last from `tool/release.sh`, the Bash script it replaced, removed since. Versions follow semantic versioning, worked out from the commits touching Learn since its last release (`apps/learn/`, the shared packages it uses, the root pubspec and lock, its release files, and its packages' folders at the root from before the move; `feat` raises the minor number, `fix`/`perf` the patch number, breaking ones the major from 1.0 on; other types release nothing), so **commit messages keep their type prefix**. It writes the version and a raised build number into the app's pubspec in a signed release commit (`chore(release): learn-vX.Y.Z`) with a signed tag (`learn-vX.Y.Z`, by a key in `deploy/allowed-signers`; Learn's earlier `vX.Y.Z` tags count as its history), builds one universal APK from a checkout of the tag, signed with the app's key, and the snap (`snap/snapcraft.yaml`, the committed HEAD, built in LXD), both with `SERVER_URL=https://learn.halkyon.eu/`; signs `SHA256SUMS` with GPG; drafts what's new in the release for the developer to edit, committed with the version in `assets/whats_new/` (Train's ADR 0030, D61: the sheet after the update, Settings' entry above About, the notes and F-Droid's changelog); pushes only once both builds passed their checks; and publishes on code.halkyon.app through Forgejo's API and in the F-Droid repository. The snap goes to the Snap Store's edge channel last (`storeChannel` in `apps/learn/release.dart`; the release refuses to start unless `snapcraft whoami` shows a login). A run that stopped after its release commit and tag carries on with them. The key's password and the Forgejo token are asked for at each run and never stored. The app's key is a PKCS #12 keystore outside the repository (also in the maintainer's password manager); there is no Google Play, so it is the app's identity: lost, installed apps can't be updated. Its certificate's SHA-256 is in `tool/release/android-certificate.sha256`, and the release tool releases no APK signed otherwise; other release builds fall back to the debug key (`android/app/build.gradle.kts`), so `flutter run --release` works anywhere. The APK also goes into the project's own F-Droid repository, https://halkyon.app/fdroid/repo, through `tool/fdroid_repo.sh` (fdroidserver v2.4.5): its settings and the app's listing are in `tool/release/fdroid/` (the anti-feature NonFreeNet declared with its reasons and F-Droid's own icon; not TetheredNet, as the app chooses its instance); its index is signed with a key of its own (a PKCS #12 keystore beside the app's, password asked for, certificate pinned in `tool/release/fdroid-index-certificate.sha256`, since a new key makes the F-Droid app refuse the repository); and it is uploaded to the halkyon.app server (`ssh ramiel-fdroid`) as the user `fdroid`, whose key can only write into the folder Caddy serves (rrsync), the APKs first and the indexes last. ## Rules the code relies on @@ -106,7 +106,7 @@ A spaced-repetition learning app: studysets of flashcards, reviewed on an FSRS s - **Searching the web and asking an assistant** (the web search decided 2026-10-10; `feature/assistant/`; the hand-off to AI assistants and Claude's connector went on 23 Oct 2026, ADR 0022): a device setting, off unless the person turns it on (Settings › Web search), shows a button that unfolds into a field (`AssistantButton`): at the lower left of Home (folded since 1 Nov 2026, where a field stood at the foot before), and of the Library, across from Create studyset, in one row before a studyset's Add material and Study (floating alone at the lower left from expanded windows on), and at the lower left while studying, above the buttons that answer the card, the ratings below it making way while it is open (decided 2026-10-16, as Vaia places it; ADR 0021, D64), shaped as a floating action button. The field is laid out as Claude's apps lay theirs (ADR 0021, D67, decided 2026-11-02; its row D69, 4 Nov): the files attached; the question; and below it, every button a rounded square, × and + at the start (+ attaching files, an assistant's only, and leading to what goes along, a web search's too), who answers centred, by its model's name alone in its provider's colour (`AssistantProvider.modelName`), and the conversations kept and the microphone at the end, the microphone while nothing could be sent (halkyon_app's `Dictation` on the device's own speech recognition, `SpeechInput` in `Dependencies`: on Android the voice input app first where one is installed, `VoiceInput.kt`, Meditate's ADR 0038, D18, opened once a tap, else `speech_to_text`, in the app's language, where the cursor stands, listening again after each pause; none on Linux; a device without a recogniser says so), giving way to the button sending once something could be (`composer.dart`, which a conversation's follow-up shares); one draft and whether it is open for every screen, `ComposerCubit`, kept from tab to tab, and kept open on another screen of a tab only while something is written or attached, D69). The sheet offers the parts of what it is about (`AssistantTopic.parts`; a search only those `searchable`: a card's front, back, each hint, its explanation, never a studyset's name or description nor a card's images), none chosen, so only what the person picks goes along, each cut at 600 characters. `WebSearch` opens the chosen engine's results for the question and the chosen text (`SearchEngine`: Brave Search by default on desktops, Ecosia, DuckDuckGo, Startpage, Qwant, Google, Bing, or the person's own address with `%s`), or on Android hands them to the device's own web search (`ACTION_WEB_SEARCH` in `MainActivity.kt`, Brave Search where nothing answers). **The assistants of ADR 0021 answer in the same field** (step 2, built 25 Oct 2026, and step 3, 26 Oct, for v0.21.0): Settings shows them (halkyon_app's `AssistantsCard`, after Web search): those the operator granted, under their providers' names (D60), with what is left of the person's budget for them together (D58), read and agreed to once per provider (D66), the person's use and the instance's privacy page; and, where the operator allows own keys, the person's own assistants, kept in their settings (`ownAssistants`, settings schema 12, `SettingsOwnAssistants`), which `SettingsRepository.update` refuses to write with a key unless they are sealed with the person's key (`SettingsGateway.seals`). Who answers is picked in the field (the web search and each assistant offered, under its provider's name, the one asked last first: `askWith`, a device setting), a granted one not agreed to opening its agreement and sending nothing; how much a granted one thinks is a submenu of the chip of who answers, Effort, among the efforts its operator allows (D63), its own unless the person picks another, kept per assistant on the device (`askEfforts`), which a conversation continued from the history asks at too, and every question asks for its effort's ceiling on output (`AssistantChoice.outputCeilingAt`; Explain, Why? and proposed flashcards at the assistant's own, an own key at none and 4,096 tokens). How far it may look leads the sheet (D49): only what is chosen (D50, where it starts, and where it returns after each question: no lookups), this studyset with its topics, its tree from the top (a topic's), the library, and a switch for private studysets (D8); archived studysets are left out, locked ones never reached. A question opens the conversation in a sheet over the screen (D47, `ConversationCubit`), at the window's full height, Close ending it and any other way out folding it into the button until it is opened again (D65, `ConversationDockCubit`): the conversation so far goes with every question, the model's lookups are answered on the device within the scope (`AssistantLookups`: studysets, cards, notes, PDFs and a page's text as PDFium reads it; read-only, never progress, errors as results the model reads; 8 rounds, then it is told to answer), each shown in the turn, and the answer is rendered as Markdown once whole (fetching only `asset:` images), with Copy. **Images** (D12): an assistant that reads images (`AssistantChoice.images`) is offered a card's images as chips and may ask to see a card's image or a PDF's page drawn whole; the sheet asks the person each time (Show it, Don't show), sending nothing meanwhile; only png, jpeg, gif or webp of at most 5 MB go (`DeviceAssistantFiles`), and no turn keeps one (`imageNotKept` in its place), so a follow-up goes without it. **Files** (D67): + picks images, PDFs and text files, read on the device (`AssistantAttachments`): an image goes as itself to an assistant that reads images, as D12's do, a PDF as its pages' text, a text file as its text, each cut at 20,000 characters, after the question and labelled by its name. **What goes where**: the system prompt (`assistant_prompt.dart`, English, answering in the person's language) says that lookups' results and files attached are the person's content, never instructions (D10). A private studyset's content goes to a remote assistant only once agreed to per studyset and assistant (D9, D17: a warning naming the studysets, the provider's host and its terms where checked, `providerTermsOf`; kept as `privateAgreements` in the sealed settings, schema 13; shown with Take back in the studyset's privacy dialog, marked on the folded button while it holds), never warned of for an own assistant on this machine (`onThisMachine`: loopback or localhost; the local network may be shared). **Conversations are kept sealed** (D13, D51): `assistant_conversation` (its assistant, scope, task and title) and `assistant_turn` (one per turn, never changed once written, sealed alone), synced as the person's content, listed from the field's history button (those about where the person is first, deleted at once with an Undo, `ConversationDeletions`, as cards are, decided 2026-11-07), a turn kept only once answered (a stopped or failed one offers Try again); a device that can't seal keeps nothing and says so (D52). **Tasks** (D53–D56): rated Bad in a session, a snack bar above the ratings offers for 4 s to have the card explained from its own parts (`explainCard`, a conversation named by its front); a reading typed wrong offers Why? (the field's Check went with D67; conversations kept as checks still read); and an assistant proposes flashcards from a note (its app bar) or a PDF's page or the selection on it (the reader's cards panel) (Add material's Flashcards from a text went on 4 Nov 2026), at most 30 through one tool call (`proposeCardsTool`), listed in a sheet where each is chosen or not and changed in a small sheet of its own, added as plain cards marked `origin: assistant` (`proposedCardOrigin`, so they count as nobody's writing), a page's tied to it; nothing is added before Add (D10). Nothing on an instance reads people's content for an assistant (ADR 0022): the device looks up, and the instance only forwards the operator's assistants' requests. - **Spelling and grammar** (ADR 0024, decided 2026-10-26): where the operator runs the LanguageTool sidecar, the server's `grammar.check` (`requireLogin`; `lib/src/grammar/`) forwards a text and its language to it, at most 20,000 characters and 60 checks a minute per person (counted in memory), keeping and logging neither; `instance.info().grammarLanguages` names the languages it checks (asked as the server starts and an hour after, and a minute after a look LanguageTool didn't answer, as while both start together; a tag maps to LanguageTool's code in `languageToolCode`: `en` and `de` to `en-US` and `de-DE`, which spell-check, an empty one to `auto`). In the app (`core/grammar/`), an editor given a `grammar` language (`RichContentEditor`, `CardFace`: a card's front in the studyset's front language, its back and answers in the back's, everything else recognised) is wrapped in `GrammarHost`, which checks while the device's setting is on (Settings › Writing, `checkGrammar`) and the instance checks the language: `GrammarCheckController` after a pause of 1.5 s and on blur, a paragraph at a time (`grammarSegmentsOf`, formulas and code masked), only paragraphs whose text changed, an edit dropping what was found at once, and `GrammarOverlay` drawing red wavy lines over the editor (nothing is written into the document), a tap offering the suggestions. In a private studyset it checks only once the person agreed for that studyset, asked once in a banner above the editor while it has the focus (the agreement in `privateAgreements` under `grammarAgreementKey`, a refusal in `grammarDeclined`, settings schema 14, both shown with Take back in the studyset's privacy dialog). The field checks nothing since ADR 0021's D67. - **Where sync stands** is `SyncService.status` (syncing, the last sync that went through, the last failure; since the app started), which `SyncStatusBloc` (halkyon_app, provided by `PersonApp`) hands to the trouble dot on the Settings cogs (on Home and Profile; no tab carries it), the Sync card in Settings and the banner on Home. Only the last outcome counts: one sync that goes through clears a failure. It also counts the changes not on the server yet (`pending`): every save asks for a sync (`requestSync`), and a sync that goes through takes those saved before it started; the engine can't say, since the server only tells it at the start of a sync what it has. The count is kept in `sync_pending.json` in the person's folder (`PendingChangesStore`) and shown on the Sync card, beside whether a sync ever went through on this device (`syncedHere`, read as the person's data opens): until one has, an empty library says it is on its way from the instance rather than that there are no studysets (decided 2026-10-17: a first sync brings everything at once, at its end). The Library's Refresh and pulling it down sync now (`syncAndWait`, which a pull's spinner waits on). -- **Serverpod's version moves together with `serverpod_offline_sync`**, which allows only a narrow range of Serverpod (v0.0.10: `>=4.0.4 <4.1.0`; v0.0.8 pinned v4.0.0 exactly, which the root `pubspec.yaml` overrode until 2026-10-09). Every package pins Serverpod's packages to one version, halkyon_base's too: upgrade them all together with the sync packages, halkyon_base and the CLI version CI generates code with (`SERVERPOD_VERSION` in `.forgejo/workflows/ci.yaml`), after reading the sync package's changelog. Never override a version the sync package doesn't allow without checking what changed in `serverpod_database`, which it builds on. Its shared part, `serverpod_offline_sync`, comes from our fork (the root `pubspec.yaml`'s `dependency_overrides`, `project-halkyon/serverpod-offline-sync` on code.halkyon.app, pinned to a signed commit of branch `halkyon/space-writes`, which stacks nine branches): `halkyon/byte-payloads` (decided 2026-10-12: v0.0.10 compares byte payloads byte by byte through a UUID parse that throws for each byte, so one sync touching a few thousand cards blocked the server for minutes); `halkyon/inbound-merges` (2026-10-17: `onInboundMerge`, called only when changes from the remote peer were merged, which the app's sync uses; `onMergeSuccess` fires for a space only pushed to, so every repository read everything again after each save's sync); `halkyon/fast-deletes` (2026-10-17: a local delete skips the foreign key projection pass when it can't change it, `canSkipProjectionAfterLocalDelete`; the pass loaded nearly every synced row, 8 s for one delete in a library of 8,000 cards, 30 s for a card; not reported upstream yet, the developer's call); `halkyon/fast-merges` (2026-10-17: a merge only inserting new rows proven unable to change the projection skips its passes, `canLeaveProjectionUnchangedByMergedInserts`, 9.5 s to 60 ms for a rating another device made in such a library; and projections are rebuilt once after the synced tables changed, not on every session and merge, which fixes #150 below); `halkyon/fast-merge-deletes` (2026-10-17: a merge only deleting rows skips the pass when every visible row below them goes with them, `canLeaveProjectionUnchangedByMergedDeletes`: a card deleted on one device took the server 5.4 s to merge and each other device 4 s, now 35 and 99 ms); `halkyon/batch-inserts` (2026-10-17: such a merge writes its new rows up to 1,000 to a statement, and the insert proof takes a parent of the row's own table inserted before it, as nested studysets are: a first push of 8,000 cards took the server 35 to 41 s, now about 5; a device receiving it 60 s, now 44, as Serverpod's SQLite adapter still inserts row by row); `halkyon/batched-row-reads` (2026-11-09: collecting the changes a device lacks reads their rows 500 to a statement per table and space, in the same snapshot, rather than one statement per insert and per update: a new device's first sync of a library of 56,000 rows kept halkyon.li's server busy about 100 s, sending nothing until all was collected, so the client closed the stream after its 40 s without a message and every attempt started over; 120,000 changes took 52 s to collect on embedded PostgreSQL, now 17, the same bytes on the wire); `halkyon/streamed-collection` (2026-11-11: the server hands each change a device lacks out as it is read, its snapshot open until the last is out, so a device hears from it at once rather than after the whole collection, which on a large first sync outlasted the client's 40 s; a device's own engine keeps collecting first, as Serverpod's SQLite transactions would hold off its writes); `halkyon/space-writes` (2026-11-11, Halkyon's ADR 0044, D24: a write without a transaction acts in the space it touches, an update or delete in its rows' space and a new row in the one the host names, `OfflineSyncDatabase.newRowSpace`, which Learn's `SpaceRouter` answers, each through `transactionForUser`, so membership and role are checked; one call touching two spaces is refused). Move the fork along when upgrading, and drop each change once a release has it. +- **Serverpod's version moves together with `serverpod_offline_sync`**, which allows only a narrow range of Serverpod (v0.0.10: `>=4.0.4 <4.1.0`; v0.0.8 pinned v4.0.0 exactly, which the root `pubspec.yaml` overrode until 2026-10-09). Every package pins Serverpod's packages to one version, halkyon_base's too: upgrade them all together with the sync packages, halkyon_base and the CLI version CI generates code with (`SERVERPOD_VERSION` in `.forgejo/workflows/ci.yaml`), after reading the sync package's changelog. Never override a version the sync package doesn't allow without checking what changed in `serverpod_database`, which it builds on. Its shared part, `serverpod_offline_sync`, comes from our fork (the root `pubspec.yaml`'s `dependency_overrides`, `halkyon-days/serverpod-offline-sync` on code.halkyon.app, pinned to a signed commit of branch `halkyon/space-writes`, which stacks nine branches): `halkyon/byte-payloads` (decided 2026-10-12: v0.0.10 compares byte payloads byte by byte through a UUID parse that throws for each byte, so one sync touching a few thousand cards blocked the server for minutes); `halkyon/inbound-merges` (2026-10-17: `onInboundMerge`, called only when changes from the remote peer were merged, which the app's sync uses; `onMergeSuccess` fires for a space only pushed to, so every repository read everything again after each save's sync); `halkyon/fast-deletes` (2026-10-17: a local delete skips the foreign key projection pass when it can't change it, `canSkipProjectionAfterLocalDelete`; the pass loaded nearly every synced row, 8 s for one delete in a library of 8,000 cards, 30 s for a card; not reported upstream yet, the developer's call); `halkyon/fast-merges` (2026-10-17: a merge only inserting new rows proven unable to change the projection skips its passes, `canLeaveProjectionUnchangedByMergedInserts`, 9.5 s to 60 ms for a rating another device made in such a library; and projections are rebuilt once after the synced tables changed, not on every session and merge, which fixes #150 below); `halkyon/fast-merge-deletes` (2026-10-17: a merge only deleting rows skips the pass when every visible row below them goes with them, `canLeaveProjectionUnchangedByMergedDeletes`: a card deleted on one device took the server 5.4 s to merge and each other device 4 s, now 35 and 99 ms); `halkyon/batch-inserts` (2026-10-17: such a merge writes its new rows up to 1,000 to a statement, and the insert proof takes a parent of the row's own table inserted before it, as nested studysets are: a first push of 8,000 cards took the server 35 to 41 s, now about 5; a device receiving it 60 s, now 44, as Serverpod's SQLite adapter still inserts row by row); `halkyon/batched-row-reads` (2026-11-09: collecting the changes a device lacks reads their rows 500 to a statement per table and space, in the same snapshot, rather than one statement per insert and per update: a new device's first sync of a library of 56,000 rows kept halkyon.li's server busy about 100 s, sending nothing until all was collected, so the client closed the stream after its 40 s without a message and every attempt started over; 120,000 changes took 52 s to collect on embedded PostgreSQL, now 17, the same bytes on the wire); `halkyon/streamed-collection` (2026-11-11: the server hands each change a device lacks out as it is read, its snapshot open until the last is out, so a device hears from it at once rather than after the whole collection, which on a large first sync outlasted the client's 40 s; a device's own engine keeps collecting first, as Serverpod's SQLite transactions would hold off its writes); `halkyon/space-writes` (2026-11-11, Halkyon's ADR 0044, D24: a write without a transaction acts in the space it touches, an update or delete in its rows' space and a new row in the one the host names, `OfflineSyncDatabase.newRowSpace`, which Learn's `SpaceRouter` answers, each through `transactionForUser`, so membership and role are checked; one call touching two spaces is refused). Move the fork along when upgrading, and drop each change once a release has it. - **The source-code link**: both licences require offering the source of what an instance runs to the people who use it. `instance.info().sourceCodeUrl` names it (the operator's `SOURCE_URL`, else this project's repository, `upstreamSourceCodeUrl` in `learn_base.dart`; halkyon_base's `sourceCodeUrl`) and the app's About dialog shows it next to the licences and every package's notice (halkyon_app's `showAppAbout`, asking `Dependencies.sourceCodeUrl`, with Learn's credits as its own parts, `feature/about/`). Keep it working when changing the dialog or the endpoint. - **Public profiles** (ADR 0005, stage 1 of the public tier; halkyon_base's since ADR 0016, decided 2026-10-19: the instance's register keeps them, the same in every service of the instance, so the module serves the endpoints below under `base`, the page and the link checks, and Learn's code reads people's handles only through the module's `profileOf`, `profilesOf`, `personWithHandle` and `fixHandle`, never the table, except Explore's search, which joins it in its query, so **Learn holds the register wherever it runs**): `public_profile` (`serverOnly`, never sent; the app gets a `Profile`, without the person's id) holds a handle (Mastodon's rules: 1 to 30 letters, digits or underscores, unique whatever their case through `handleKey`, a few reserved for the instance; changeable until the first publish, `firstPublishedAt`, which publishing sets through the module's `fixHandle` just before it writes the release), a display name, a bio, up to 4 links and an avatar. `ProfileEndpoint` (`identified` only: a profile is for others to see) reads, saves and removes the caller's own, and says why a handle couldn't be theirs (`checkHandle`, null when it can); `ProfilesEndpoint` finds anyone's by handle (`ProfileNotFoundException` for none, and for one whose person hasn't gone public yet: ADR 0020, D5, published nothing and left the profile empty; the page answers so too, while Learn's code still finds whose a handle is, `personWithHandle`). Every answer about a new handle tells that an account has it, so the module counts them together (`countHandleCheck`: checking, saving, signing up or setting first keys with one, and Learn's `inbox.block` and `unblock`, which look people up by handle), at most 30 an hour per person and 60 per network (`tooManyHandleChecks`, which the handle's dialog words), which the app shows in a sheet (`showProfileOf`, decided 2026-10-14) from a studyset's author in Explore and from a licence holder written as a handle, `© raisondetredev`, a link set apart from a name from elsewhere. Handles are on the account domain (`ACCOUNT_DOMAIN`, else the server's public host; `instance.info().accountDomain`), which can never change once anyone follows a handle on it. **A profile has a page on the web** (ADR 0011, decided 2026-10-15): `https:///@handle` where the operator set `ACCOUNT_DOMAIN` (so the address belongs to the identity, which other services such as a future train.halkyon.eu may share), else on the web server (`profilePageUrl`); served by the module's `profile_page.dart` on the web server (`addProfilePageRoute`, `/*`, one segment, after the server's literal routes; its foot names the app as `learnBase()`'s `profilePageNote` says; Caddy forwards `/@…` on the instance's domain and on the account domain, which serves nothing else), in English or German, no JavaScript, `noindex`, every link `rel="me"`; asked for on another host, it redirects to its own address. **Links are verified as Mastodon verifies them**: `profile.checkLinks` (at most 10 an hour; the app asks after a save that brings an unchecked link, and on Check links) fetches each link's page (`SafeLinkFetcher`: HTTPS on 443, public addresses only, checked before connecting and connected to as checked, 3 redirects, 1 MiB, 10 s) and keeps in `verifiedLinks` those linking back with `rel="me"` (`linksBackTo`, pub.dev's `html`); a changed link loses its check, a new handle all of them. An avatar is a PNG of at most 1 MB that the app draws anew from the picture chosen (`avatarPng`, so a photo's metadata never leaves the device), kept in the public storage at `avatars/.png`, named by its content so its URL tells nothing about whose it is, and deleted once no profile shows it. In the app, the Profile tab holds it in a card of its own after the account's and the inbox's (each part a card, as Settings groups its own); since 2 Nov 2026 the profile's code is halkyon_app's (`PublicProfileRepository` on the module's endpoints, `PublicProfileSection`, `showProfileOf`) and the Profile tab every Halkyon app's (`HalkyonProfile`: the person's own tab, then Learn's Statistics), so Train and Todo show the same; it is a form, for people with an identity, that saves as it is written (decided 2026-10-12): a field when it is left or Enter is pressed in it, a link when removed, never twice the same; the handle is chosen in a dialog that asks the instance while it is typed whether it is free, and choosing the first makes the profile with what was written (`PublicProfileBloc` takes saves one at a time, the latest of those waiting next, and a handle chosen holds for every save after it). - **Publishing** (ADR 0005, stage 2 of the public tier): `PublishEndpoint` (`identified` only, and a public profile: a release names its author) publishes a studyset with everything inside it as its next release. The server reads the person's synced rows (`readReleaseFiles`, `lib/src/publish/`) and writes them through the `git` command (`ReleaseRepository`, never a library linked in) as a commit on `main` and the annotated tag `v` in a bare repository of the studyset's own, `/.git` (`published_studyset`, `serverOnly`; the public id is random, made at the first publish, never a synced row's id). A release holds a directory per studyset (the published one at the top, each inside it named by its id) with `studyset.json`, `cards/.json`, `notes/.json`, `paths//path.json` with `steps/.json`, `pdfs/.json`, and the files the content names in `files/`, described in `files.json`; each item file holds its id, position, tag names and payload as synced, keys sorted and indented, without what is only its author's (a card's `report`, a studyset's `exam`, a PDF's `deadline`), and items being deleted (blank payloads) are left out. Publishing is refused (`PublishRefusedException`) for a private studyset or a tree holding a private row (`private`; ADR 0019), without a profile, unless every studyset of it is under a licence allowing it to be passed on (`release_license.dart`; "no derivatives" allowed, and marked in the app), with a card, note or step of more than one current revision, with files not uploaded yet, when nothing changed, or for a name (`slug`: lower-case letters, digits and hyphens, one per studyset of a person, fixed from the first publish) that won't do. Versions count past both the database's highest and the tags', so a version git has and the database lost is never given again, and a release the database can't record is taken back from git. The person's tags are encrypted, so a release's tag names come from the device (`publish`'s `tagNames`): a name for a tag that isn't the person's, or an encrypted tag of the release without one, refuses it (`tagNamesMismatch`); a readable tag keeps its own name. The first publish fixes the person's handle. Git runs apart from the machine's configuration (no system or user configuration, no prompts) with `gc.auto` off, so objects are never removed. Deleting a person deletes their published studysets with their repositories. In the app, Share in a studyset's action row (a lock, or a globe once published; an icon below medium windows) opens the Publish sheet (`feature/publish/`): its name, whether its licence allows publishing, what changed, its releases; Continue leads to the step confirming the notes, PDFs and other files that go (ADR 0044, D8 and D9: `MaterialChoice`; none ticked at first but those the latest release held, `publish.releasedMaterial`; each kind ticked whole, any item's menu choosing everything but it or nothing but it; cards and learning paths always go), the rest sent as `publish`'s `leftOut`, which the server leaves out with the files only they name, and whatever the sync before publishing brings unseen with it. Publishing syncs first, since the server builds the release from what it holds; on a refusal for revisions to settle, the app saves merged those whose changes merge (ADR 0003) and tries once more, and lists those changed differently on one side, each opening where it is settled. An author can take a studyset off Explore again (`withdraw`, `withdrawnAt`): nobody can add it, copies get no updates, its releases stay; publishing it again, changed or not, brings it back. The library's cards say who made a studyset ("By you", or a copy's author by the handle in its `publishedAt`) and whether it is out on Explore (a lock or a globe, opening the Publish sheet through `showShareSheet`): `published()` lists the person's studysets published and not withdrawn, and `PublishRepository` remembers them in `published.json` in the person's folder (`PublishedStore`), so the library shows them offline too; publishing, withdrawing and looking one up update it. diff --git a/apps/learn/README.md b/apps/learn/README.md index 0150568a..a92952ff 100644 --- a/apps/learn/README.md +++ b/apps/learn/README.md @@ -74,7 +74,7 @@ instance's basics; a Serverpod module), `halkyon_app` (instances, sign-in, sync) `python3-cairo`); for releases what [Releases](#releases) lists. The rich-text editor comes from our fork of AppFlowy Editor -([project-halkyon/appflowy-editor](https://code.halkyon.app/project-halkyon/appflowy-editor)), +([halkyon-days/appflowy-editor](https://code.halkyon.app/halkyon-days/appflowy-editor)), which `flutter pub get` fetches by itself. ## Running it locally @@ -128,7 +128,7 @@ the CI runner described in `deploy/runner/`, deploys only commits signed by a ke ## Releases -Releases are published on [code.halkyon.app](https://code.halkyon.app/project-halkyon/halkyon/releases), +Releases are published on [code.halkyon.app](https://code.halkyon.app/halkyon-days/halkyon/releases), with the APK and the snap, and the APK also in the project's F-Droid repository (`https://halkyon.app/fdroid/repo?fingerprint=E8082788756F6B7E682AA22FCEAD24B53E2A8B0912AD66D65E8C2D4F07BB94B0`, added in the F-Droid app as a repository). The Snap Store (`halkyon-learn`) follows. diff --git a/apps/learn/halkyon_learn_curator/lib/src/sources/alphabets.dart b/apps/learn/halkyon_learn_curator/lib/src/sources/alphabets.dart index 42fea113..fee4e83e 100644 --- a/apps/learn/halkyon_learn_curator/lib/src/sources/alphabets.dart +++ b/apps/learn/halkyon_learn_curator/lib/src/sources/alphabets.dart @@ -107,7 +107,7 @@ const alphabetCredit = Credit( name: 'Alphabets (Hangul, Greek)', by: 'Halkyon Learn contributors', license: 'CC0-1.0', - url: 'https://code.halkyon.app/project-halkyon/halkyon_learn', + url: 'https://code.halkyon.app/halkyon-days/halkyon', ); /// The name of the dictionary file of the Greek alphabet. diff --git a/apps/learn/halkyon_learn_flutter/pubspec.yaml b/apps/learn/halkyon_learn_flutter/pubspec.yaml index 68bc32fc..221a594c 100644 --- a/apps/learn/halkyon_learn_flutter/pubspec.yaml +++ b/apps/learn/halkyon_learn_flutter/pubspec.yaml @@ -28,7 +28,7 @@ dependencies: # workspace, pinned to a signed commit of its branch halkyon/win32-v6. appflowy_editor: git: - url: https://code.halkyon.app/project-halkyon/appflowy-editor.git + url: https://code.halkyon.app/halkyon-days/appflowy-editor.git ref: 81c3006c74e789ed2e470b8612529541b358fd26 # Anki packages are zip files. archive: ^4.0.7 @@ -141,7 +141,7 @@ dependencies: pdfrx: ^2.6.5 pure_zstd: git: - url: https://code.halkyon.app/project-halkyon/pure-zstd.git + url: https://code.halkyon.app/halkyon-days/pure-zstd.git ref: 596b63d5741d479073fbfc78377cc7dbdd342da3 serverpod_auth_idp_flutter: 4.0.4 serverpod_flutter: 4.0.4 diff --git a/apps/learn/halkyon_learn_flutter/test/support/fake_dictionary_gateway.dart b/apps/learn/halkyon_learn_flutter/test/support/fake_dictionary_gateway.dart index fdb1f2c6..46a33e3c 100644 --- a/apps/learn/halkyon_learn_flutter/test/support/fake_dictionary_gateway.dart +++ b/apps/learn/halkyon_learn_flutter/test/support/fake_dictionary_gateway.dart @@ -131,7 +131,7 @@ const greekAlphabet = Dictionary( name: 'Alphabets (Hangul, Greek)', by: 'Halkyon Learn contributors', license: 'CC0-1.0', - url: 'https://code.halkyon.app/project-halkyon/halkyon_learn', + url: 'https://code.halkyon.app/halkyon-days/halkyon', ), ], entryCount: 1, diff --git a/apps/learn/halkyon_learn_server/lib/src/auth/learn_base.dart b/apps/learn/halkyon_learn_server/lib/src/auth/learn_base.dart index c6d778e0..175b249f 100644 --- a/apps/learn/halkyon_learn_server/lib/src/auth/learn_base.dart +++ b/apps/learn/halkyon_learn_server/lib/src/auth/learn_base.dart @@ -8,8 +8,7 @@ import 'package:halkyon_learn_server/src/shared_space/sync_space_grants.dart'; /// unless its operator sets `SOURCE_URL`. Both licences require offering it /// to the people who use the instance (AGPL-3.0-or-later for the app, /// SSPL-1.0 for the server). -const upstreamSourceCodeUrl = - 'https://code.halkyon.app/project-halkyon/halkyon'; +const upstreamSourceCodeUrl = 'https://code.halkyon.app/halkyon-days/halkyon'; /// How Halkyon Learn sets halkyon_base up (ADR 0014), the module giving every /// Halkyon server its sign-in, the account and its deletion, the pruning, the diff --git a/apps/learn/release.dart b/apps/learn/release.dart index 10e76f70..56e65a7d 100644 --- a/apps/learn/release.dart +++ b/apps/learn/release.dart @@ -14,7 +14,7 @@ const learnRelease = AppRelease( artifactName: 'halkyon-learn', serverUrl: 'https://learn.halkyon.eu/', forgejo: 'https://code.halkyon.app', - repository: 'project-halkyon/halkyon', + repository: 'halkyon-days/halkyon', // A PKCS #12 keystore outside the repository (also in the maintainer's // password manager): there is no Google Play, so it is the app's identity, // and lost, installed apps can't be updated. diff --git a/apps/meditate/halkyon_meditate_flutter/test/app/meditate_app_test.dart b/apps/meditate/halkyon_meditate_flutter/test/app/meditate_app_test.dart index 313e35a6..c0d7e4e9 100644 --- a/apps/meditate/halkyon_meditate_flutter/test/app/meditate_app_test.dart +++ b/apps/meditate/halkyon_meditate_flutter/test/app/meditate_app_test.dart @@ -164,7 +164,7 @@ void main() { ), appVersion: (version: '0.1.0', build: 1), sourceCodeUrl: () async => - 'https://code.halkyon.app/project-halkyon/halkyon', + 'https://code.halkyon.app/halkyon-days/halkyon', openPerson: (person) async { opened.add(person); return MeditatePerson( @@ -1288,7 +1288,7 @@ void main() { expect(find.text('The source of what this instance runs:'), findsOneWidget); expect( - find.text('https://code.halkyon.app/project-halkyon/halkyon'), + find.text('https://code.halkyon.app/halkyon-days/halkyon'), findsOneWidget, ); }); diff --git a/apps/meditate/halkyon_meditate_server/lib/src/meditate_base.dart b/apps/meditate/halkyon_meditate_server/lib/src/meditate_base.dart index 9c268827..5a214aec 100644 --- a/apps/meditate/halkyon_meditate_server/lib/src/meditate_base.dart +++ b/apps/meditate/halkyon_meditate_server/lib/src/meditate_base.dart @@ -5,8 +5,7 @@ import 'package:halkyon_meditate_server/src/meditate_data.dart'; /// unless its operator sets `SOURCE_URL`. Both licences require offering it /// to the people who use the instance (AGPL-3.0-or-later for the app, /// SSPL-1.0 for the server). -const upstreamSourceCodeUrl = - 'https://code.halkyon.app/project-halkyon/halkyon'; +const upstreamSourceCodeUrl = 'https://code.halkyon.app/halkyon-days/halkyon'; /// The oldest build of the app this server works with (the number after `+` /// in the app's version); apps below it are asked to update. Raise it in the diff --git a/apps/meditate/halkyon_meditate_server/test/integration/meditate_server_test.dart b/apps/meditate/halkyon_meditate_server/test/integration/meditate_server_test.dart index d1d59db6..c0671978 100644 --- a/apps/meditate/halkyon_meditate_server/test/integration/meditate_server_test.dart +++ b/apps/meditate/halkyon_meditate_server/test/integration/meditate_server_test.dart @@ -68,7 +68,7 @@ void main() { expect(basics.minimumAppBuild, 1); expect( basics.sourceCodeUrl, - 'https://code.halkyon.app/project-halkyon/halkyon', + 'https://code.halkyon.app/halkyon-days/halkyon', ); }); diff --git a/apps/todo/halkyon_todo_flutter/test/app/todo_app_test.dart b/apps/todo/halkyon_todo_flutter/test/app/todo_app_test.dart index c3d36d72..dd80d6e7 100644 --- a/apps/todo/halkyon_todo_flutter/test/app/todo_app_test.dart +++ b/apps/todo/halkyon_todo_flutter/test/app/todo_app_test.dart @@ -238,7 +238,7 @@ void main() { weather: weather, appVersion: (version: '0.1.0', build: 1), sourceCodeUrl: () async => - 'https://code.halkyon.app/project-halkyon/halkyon', + 'https://code.halkyon.app/halkyon-days/halkyon', openPerson: (person) async { opened.add(person); final projects = ProjectRepository(projectRows, keys); @@ -7273,7 +7273,7 @@ void main() { expect(find.text('The source of what this instance runs:'), findsOneWidget); expect( - find.text('https://code.halkyon.app/project-halkyon/halkyon'), + find.text('https://code.halkyon.app/halkyon-days/halkyon'), findsOneWidget, ); }); diff --git a/apps/todo/halkyon_todo_server/lib/src/todo_base.dart b/apps/todo/halkyon_todo_server/lib/src/todo_base.dart index 3fc51802..523d4042 100644 --- a/apps/todo/halkyon_todo_server/lib/src/todo_base.dart +++ b/apps/todo/halkyon_todo_server/lib/src/todo_base.dart @@ -6,8 +6,7 @@ import 'package:halkyon_todo_server/src/todo_data.dart'; /// unless its operator sets `SOURCE_URL`. Both licences require offering it /// to the people who use the instance (AGPL-3.0-or-later for the app, /// SSPL-1.0 for the server). -const upstreamSourceCodeUrl = - 'https://code.halkyon.app/project-halkyon/halkyon'; +const upstreamSourceCodeUrl = 'https://code.halkyon.app/halkyon-days/halkyon'; /// The oldest build of the app this server works with (the number after `+` /// in the app's version); apps below it are asked to update. Raise it in the diff --git a/apps/todo/halkyon_todo_server/test/integration/todo_server_test.dart b/apps/todo/halkyon_todo_server/test/integration/todo_server_test.dart index 314bba1f..704847ce 100644 --- a/apps/todo/halkyon_todo_server/test/integration/todo_server_test.dart +++ b/apps/todo/halkyon_todo_server/test/integration/todo_server_test.dart @@ -50,7 +50,7 @@ void main() { expect(basics.minimumAppBuild, 1); expect( basics.sourceCodeUrl, - 'https://code.halkyon.app/project-halkyon/halkyon', + 'https://code.halkyon.app/halkyon-days/halkyon', ); }); diff --git a/apps/todo/release.dart b/apps/todo/release.dart index 8e6954ee..0c026a0e 100644 --- a/apps/todo/release.dart +++ b/apps/todo/release.dart @@ -14,7 +14,7 @@ const todoRelease = AppRelease( artifactName: 'halkyon-todo', serverUrl: 'https://todo.halkyon.eu/', forgejo: 'https://code.halkyon.app', - repository: 'project-halkyon/halkyon', + repository: 'halkyon-days/halkyon', // A PKCS #12 keystore of its own outside the repository (also in the // maintainer's password manager), as Learn's and Train's: it is the app's // identity, and lost, installed apps can't be updated; one key for several diff --git a/apps/todo/snap/snapcraft.yaml b/apps/todo/snap/snapcraft.yaml index c4a275e4..f3a41e1b 100644 --- a/apps/todo/snap/snapcraft.yaml +++ b/apps/todo/snap/snapcraft.yaml @@ -34,7 +34,7 @@ description: | Questions and reports: support@halkyon.app license: AGPL-3.0-or-later -source-code: https://code.halkyon.app/project-halkyon/halkyon +source-code: https://code.halkyon.app/halkyon-days/halkyon # The project's, not an instance's: the app works with any instance. website: https://halkyon.app/todo # No contact or issues, as Learn's (its recipe says why): the address is in the diff --git a/apps/train/halkyon_train_flutter/test/app/train_app_test.dart b/apps/train/halkyon_train_flutter/test/app/train_app_test.dart index 590bb13f..a90047a1 100644 --- a/apps/train/halkyon_train_flutter/test/app/train_app_test.dart +++ b/apps/train/halkyon_train_flutter/test/app/train_app_test.dart @@ -209,7 +209,7 @@ void main() { ), appVersion: (version: '0.1.0', build: 7), sourceCodeUrl: () async => - 'https://code.halkyon.app/project-halkyon/halkyon', + 'https://code.halkyon.app/halkyon-days/halkyon', openPerson: (person) async { opened.add(person); return TrainPerson( @@ -721,7 +721,7 @@ void main() { expect(find.text('The source of what this instance runs:'), findsOneWidget); expect( - find.text('https://code.halkyon.app/project-halkyon/halkyon'), + find.text('https://code.halkyon.app/halkyon-days/halkyon'), findsOneWidget, ); }); diff --git a/apps/train/halkyon_train_server/lib/src/train_base.dart b/apps/train/halkyon_train_server/lib/src/train_base.dart index caf1f135..f1924aea 100644 --- a/apps/train/halkyon_train_server/lib/src/train_base.dart +++ b/apps/train/halkyon_train_server/lib/src/train_base.dart @@ -5,8 +5,7 @@ import 'package:halkyon_train_server/src/train_data.dart'; /// unless its operator sets `SOURCE_URL`. Both licences require offering it /// to the people who use the instance (AGPL-3.0-or-later for the app, /// SSPL-1.0 for the server). -const upstreamSourceCodeUrl = - 'https://code.halkyon.app/project-halkyon/halkyon'; +const upstreamSourceCodeUrl = 'https://code.halkyon.app/halkyon-days/halkyon'; /// The oldest build of the app this server works with (the number after `+` /// in the app's version); apps below it are asked to update. Raise it in the diff --git a/apps/train/halkyon_train_server/test/integration/train_server_test.dart b/apps/train/halkyon_train_server/test/integration/train_server_test.dart index bb9a3d9e..dbbba510 100644 --- a/apps/train/halkyon_train_server/test/integration/train_server_test.dart +++ b/apps/train/halkyon_train_server/test/integration/train_server_test.dart @@ -50,7 +50,7 @@ void main() { expect(basics.minimumAppBuild, 1); expect( basics.sourceCodeUrl, - 'https://code.halkyon.app/project-halkyon/halkyon', + 'https://code.halkyon.app/halkyon-days/halkyon', ); }); diff --git a/apps/train/release.dart b/apps/train/release.dart index 56835861..6d1adb32 100644 --- a/apps/train/release.dart +++ b/apps/train/release.dart @@ -15,7 +15,7 @@ const trainRelease = AppRelease( artifactName: 'halkyon-train', serverUrl: 'https://train.halkyon.eu/', forgejo: 'https://code.halkyon.app', - repository: 'project-halkyon/halkyon', + repository: 'halkyon-days/halkyon', // A PKCS #12 keystore of its own outside the repository (also in the // maintainer's password manager), as Learn's: it is the app's identity, and // lost, installed apps can't be updated. diff --git a/apps/train/snap/snapcraft.yaml b/apps/train/snap/snapcraft.yaml index c8494bed..43852e2c 100644 --- a/apps/train/snap/snapcraft.yaml +++ b/apps/train/snap/snapcraft.yaml @@ -33,7 +33,7 @@ description: | Questions and reports: support@halkyon.app license: AGPL-3.0-or-later -source-code: https://code.halkyon.app/project-halkyon/halkyon +source-code: https://code.halkyon.app/halkyon-days/halkyon # The project's, not an instance's: the app works with any instance. website: https://halkyon.app/train # No contact or issues, as Learn's (its recipe says why): the address is in the diff --git a/deploy/host/first-start.md b/deploy/host/first-start.md index 7dd5ec60..2161ed13 100644 --- a/deploy/host/first-start.md +++ b/deploy/host/first-start.md @@ -36,7 +36,7 @@ for `deploy` after `sudo -iu deploy`. Later deploys are `redeploy.sh`'s (D13). `SHA256:98mCW/wdTIK7Jl6vwDE4qQqLECa8wkgDWye7+nQONn4` (ED25519): ```sh - git clone ssh://git@code.halkyon.app/project-halkyon/halkyon.git ~/halkyon + git clone ssh://git@code.halkyon.app/halkyon-days/halkyon.git ~/halkyon ``` 3. **The developer's keys**: the GPG key `redeploy.sh` checks every commit's signature diff --git a/docs/adr/0045-halkyon-days.md b/docs/adr/0045-halkyon-days.md index 8108dca2..99a16977 100644 --- a/docs/adr/0045-halkyon-days.md +++ b/docs/adr/0045-halkyon-days.md @@ -3,7 +3,8 @@ - **Status:** decided 12 Nov 2026 by the developer: D1 to D8 and D12 are their own choices, D11 too (over the agent's recommendation to keep `halkyon_`), and D16; D9, D10, D13 to D15 and D17 the agent's recommendations, which they chose, as the answers - to the open questions. Nothing is built yet. + to the open questions. Step 1 built 12 Nov 2026: the organisation renamed, every + URL naming it changed; the repository is public again since that day. - **Builds on:** ADR 0015 (one repository), ADR 0016 (one account across an instance), ADR 0017 (a password and an authenticator), ADR 0020 (SimpleLogin), ADR 0031 (the apps' icons), ADR 0039 (halkyon.li at home). diff --git a/packages/halkyon_base_server/README.md b/packages/halkyon_base_server/README.md index 7065763c..886b23fe 100644 --- a/packages/halkyon_base_server/README.md +++ b/packages/halkyon_base_server/README.md @@ -484,7 +484,7 @@ Built on the three parts above, on the register (members forward every step thro HalkyonBase( app: 'todo', // its apps accept only instances naming it; never change it appName: 'Halkyon Todo', // in sign-in mails - upstreamSourceCodeUrl: 'https://code.halkyon.app/project-halkyon/halkyon_todo', + upstreamSourceCodeUrl: 'https://code.halkyon.app/halkyon-days/halkyon', minimumAppBuild: 1, // raise it in the commit that breaks older apps deletePersonData: [deleteTodosOf], // the host's own data of a person profilePageNote: ( // what profile pages say of the app at their foot diff --git a/packages/halkyon_editor/build/f05bfb3d5907fd0115f5797e28554b94/.filecache b/packages/halkyon_editor/build/f05bfb3d5907fd0115f5797e28554b94/.filecache deleted file mode 100644 index 171332b5..00000000 --- a/packages/halkyon_editor/build/f05bfb3d5907fd0115f5797e28554b94/.filecache +++ /dev/null @@ -1 +0,0 @@ -{"version":2,"files":[{"path":"/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_de.arb","hash":"6b8a8b0527dd52d280c1faa1a07882f1"},{"path":"/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/l10n.yaml","hash":"0342d6e136de130d2e707036a597a295"},{"path":"/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_en_US.arb","hash":"3b8698153fa405ef962fde0e8e8cc40a"},{"path":"/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_en.arb","hash":"80c19296a03bb2361c02030d3f371a9a"},{"path":"/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_localizations.dart","hash":"ea32e9f4dec50a505359de4dc05a3248"},{"path":"/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_localizations_de.dart","hash":"d0a9725747d946a015c47bfb34cb6c2b"},{"path":"/home/raisondetredev/snap/flutter/common/flutter/packages/flutter_tools/lib/src/build_system/targets/localizations.dart","hash":"65f1e06e150468c7e7f576d8a46ce835"},{"path":"/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_en_GB.arb","hash":"78693c50512af6a3bf684a4987e55377"},{"path":"/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_localizations_en.dart","hash":"9995fccbee2f45978b08979fa101a676"}]} \ No newline at end of file diff --git a/packages/halkyon_editor/build/f05bfb3d5907fd0115f5797e28554b94/gen_l10n_inputs_and_outputs.json b/packages/halkyon_editor/build/f05bfb3d5907fd0115f5797e28554b94/gen_l10n_inputs_and_outputs.json deleted file mode 100644 index d43b2c2e..00000000 --- a/packages/halkyon_editor/build/f05bfb3d5907fd0115f5797e28554b94/gen_l10n_inputs_and_outputs.json +++ /dev/null @@ -1 +0,0 @@ -{"inputs":["/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_de.arb","/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_en.arb","/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_en_GB.arb","/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_en_US.arb"],"outputs":["/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_localizations_de.dart","/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_localizations_en.dart","/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_localizations.dart"]} \ No newline at end of file diff --git a/packages/halkyon_editor/build/f05bfb3d5907fd0115f5797e28554b94/gen_localizations.d b/packages/halkyon_editor/build/f05bfb3d5907fd0115f5797e28554b94/gen_localizations.d deleted file mode 100644 index 891d8eb3..00000000 --- a/packages/halkyon_editor/build/f05bfb3d5907fd0115f5797e28554b94/gen_localizations.d +++ /dev/null @@ -1 +0,0 @@ - /home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_localizations_de.dart /home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_localizations_en.dart /home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_localizations.dart: /home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/l10n.yaml /home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_de.arb /home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_en.arb /home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_en_GB.arb /home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_en_US.arb \ No newline at end of file diff --git a/packages/halkyon_editor/build/f05bfb3d5907fd0115f5797e28554b94/gen_localizations.stamp b/packages/halkyon_editor/build/f05bfb3d5907fd0115f5797e28554b94/gen_localizations.stamp deleted file mode 100644 index 2b4fd81e..00000000 --- a/packages/halkyon_editor/build/f05bfb3d5907fd0115f5797e28554b94/gen_localizations.stamp +++ /dev/null @@ -1 +0,0 @@ -{"inputs":["/home/raisondetredev/snap/flutter/common/flutter/packages/flutter_tools/lib/src/build_system/targets/localizations.dart","/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/l10n.yaml","/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_de.arb","/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_en.arb","/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_en_GB.arb","/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_en_US.arb"],"outputs":["/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_localizations_de.dart","/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_localizations_en.dart","/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_localizations.dart"]} \ No newline at end of file diff --git a/packages/halkyon_editor/build/f05bfb3d5907fd0115f5797e28554b94/outputs.json b/packages/halkyon_editor/build/f05bfb3d5907fd0115f5797e28554b94/outputs.json deleted file mode 100644 index 0585777b..00000000 --- a/packages/halkyon_editor/build/f05bfb3d5907fd0115f5797e28554b94/outputs.json +++ /dev/null @@ -1 +0,0 @@ -["/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_localizations_de.dart","/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_localizations_en.dart","/home/raisondetredev/project-halkyon/halkyon/.claude/worktrees/agent-ae343a19185e1b19c/packages/halkyon_editor/lib/l10n/halkyon_editor_localizations.dart"] \ No newline at end of file diff --git a/packages/halkyon_editor/pubspec.yaml b/packages/halkyon_editor/pubspec.yaml index 6304f6af..4124969d 100644 --- a/packages/halkyon_editor/pubspec.yaml +++ b/packages/halkyon_editor/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: # URL and ref as Learn's, which pub requires of one resolution. appflowy_editor: git: - url: https://code.halkyon.app/project-halkyon/appflowy-editor.git + url: https://code.halkyon.app/halkyon-days/appflowy-editor.git ref: 81c3006c74e789ed2e470b8612529541b358fd26 equatable: ^3.0.0 flutter: diff --git a/pubspec.lock b/pubspec.lock index 05afbbf8..33b7d4f8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -71,7 +71,7 @@ packages: path: "." ref: "81c3006c74e789ed2e470b8612529541b358fd26" resolved-ref: "81c3006c74e789ed2e470b8612529541b358fd26" - url: "https://code.halkyon.app/project-halkyon/appflowy-editor.git" + url: "https://code.halkyon.app/halkyon-days/appflowy-editor.git" source: git version: "6.1.0" archive: @@ -1689,7 +1689,7 @@ packages: path: "." ref: "596b63d5741d479073fbfc78377cc7dbdd342da3" resolved-ref: "596b63d5741d479073fbfc78377cc7dbdd342da3" - url: "https://code.halkyon.app/project-halkyon/pure-zstd.git" + url: "https://code.halkyon.app/halkyon-days/pure-zstd.git" source: git version: "1.0.0" qr: @@ -1906,7 +1906,7 @@ packages: path: "packages/serverpod_offline_sync" ref: "845fb557db3e21cf78b152f9e39ba6a26947bab9" resolved-ref: "845fb557db3e21cf78b152f9e39ba6a26947bab9" - url: "https://code.halkyon.app/project-halkyon/serverpod-offline-sync.git" + url: "https://code.halkyon.app/halkyon-days/serverpod-offline-sync.git" source: git version: "0.0.10" serverpod_offline_sync_client: diff --git a/pubspec.yaml b/pubspec.yaml index 83678f03..93525d5d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -104,7 +104,7 @@ dependency_overrides: # each change once a release of the package has it. serverpod_offline_sync: git: - url: https://code.halkyon.app/project-halkyon/serverpod-offline-sync.git + url: https://code.halkyon.app/halkyon-days/serverpod-offline-sync.git path: packages/serverpod_offline_sync ref: 845fb557db3e21cf78b152f9e39ba6a26947bab9 diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index c0def3b1..8000117e 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -37,7 +37,7 @@ description: | Questions and reports: support@halkyon.app license: AGPL-3.0-or-later -source-code: https://code.halkyon.app/project-halkyon/halkyon +source-code: https://code.halkyon.app/halkyon-days/halkyon # The project's, not an instance's: the app works with any instance. website: https://halkyon.app/learn # No contact or issues: the Store refuses an upload whose links aren't http(s) URLs diff --git a/test/release/fixtures.dart b/test/release/fixtures.dart index 35e663d4..745f9eda 100644 --- a/test/release/fixtures.dart +++ b/test/release/fixtures.dart @@ -10,7 +10,7 @@ const testApp = AppRelease( artifactName: 'halkyon-notes', serverUrl: 'https://notes.halkyon.eu/', forgejo: 'https://code.example.org', - repository: 'project-halkyon/halkyon', + repository: 'halkyon-days/halkyon', android: AndroidSigning( certificateFile: 'tool/release/notes-certificate.sha256', keystoreVariable: 'HALKYON_NOTES_KEYSTORE', diff --git a/tool/release/fdroid/metadata/app.halkyon.learn.yml b/tool/release/fdroid/metadata/app.halkyon.learn.yml index 3c5f32eb..aee092c0 100644 --- a/tool/release/fdroid/metadata/app.halkyon.learn.yml +++ b/tool/release/fdroid/metadata/app.halkyon.learn.yml @@ -6,7 +6,7 @@ License: AGPL-3.0-or-later AuthorName: Halkyon AuthorEmail: support@halkyon.app WebSite: https://halkyon.app/learn -SourceCode: https://code.halkyon.app/project-halkyon/halkyon +SourceCode: https://code.halkyon.app/halkyon-days/halkyon AntiFeatures: NonFreeNet: YouTube videos in study material play through YouTube, and the diff --git a/tool/release/fdroid/metadata/app.halkyon.todo.yml b/tool/release/fdroid/metadata/app.halkyon.todo.yml index 788b6a7a..5ac7af34 100644 --- a/tool/release/fdroid/metadata/app.halkyon.todo.yml +++ b/tool/release/fdroid/metadata/app.halkyon.todo.yml @@ -8,7 +8,7 @@ License: AGPL-3.0-or-later AuthorName: Halkyon AuthorEmail: support@halkyon.app WebSite: https://halkyon.app/todo -SourceCode: https://code.halkyon.app/project-halkyon/halkyon +SourceCode: https://code.halkyon.app/halkyon-days/halkyon AntiFeatures: NonFreeNet: The server's licence (SSPL) isn't a free licence by F-Droid's diff --git a/tool/release/fdroid/metadata/app.halkyon.train.yml b/tool/release/fdroid/metadata/app.halkyon.train.yml index 03bd8a06..9d297c3c 100644 --- a/tool/release/fdroid/metadata/app.halkyon.train.yml +++ b/tool/release/fdroid/metadata/app.halkyon.train.yml @@ -6,7 +6,7 @@ License: AGPL-3.0-or-later AuthorName: Halkyon AuthorEmail: support@halkyon.app WebSite: https://halkyon.app/train -SourceCode: https://code.halkyon.app/project-halkyon/halkyon +SourceCode: https://code.halkyon.app/halkyon-days/halkyon AntiFeatures: NonFreeDep: It reads the Polar Loop through Polar's BLE SDK, which isn't free