- Dart 78.6%
- PLpgSQL 19.3%
- Kotlin 1.1%
- C++ 0.4%
- CMake 0.2%
- Other 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
The instance's half of Halkyon's ADR 0044, step 5 (D37 to D40).
An invitation by mail (sharedSpace.inviteByEmail) goes out through the
register (send-space-invitation), naming who invites by their handle
and nothing of what is shared, with a link to /join/<token>. The
instance keeps only the address's keyed hash, as for sign-in, and the
SHA-256 of the token. Whoever opens it joins with a code mailed to that
address (EmailCodePurpose.joinSpace, proven through the register,
prove-address-code), so a forwarded mail admits nobody else; a code
proving another address is refused. It lapses after 14 days, and the
hourly pruning forgets it; inviting the address again replaces it.
A space's own link (setJoinLink, the Owner's) admits whoever opens it
as a Viewer (joinByLink) while it is on; switched off and on, it gets a
new token. Its token is kept as it is, so that whoever may invite copies
the link again (joinLink). The page at /join/<token>, in English and
German, says who invites and as whom; preview tells the app the same.
A membership someone else made is new to its member until they mark it
seen (isNew, addedBy; mine names who shared it), for the Inbox.
halkyon_app learns the two new refusals (handleNeeded, otherAddress),
which Learn reports as not allowed until its screens offer these ways
in. The ADR records D37 to D40 and how the instance's half is built.
The migration, the same in the module and in each server including it
(Learn, Meditate, Todo, Train):
ALTER TABLE "shared_space" ADD COLUMN "joinToken" text;
CREATE UNIQUE INDEX "shared_space_join_token_idx"
ON "shared_space" ("joinToken");
CREATE TABLE "shared_space_email_invitation" (…), with its unique
indexes on the space and address hash, and on the token's hash,
and its foreign key to "shared_space" ON DELETE CASCADE;
ALTER TABLE "shared_space_member" ADD COLUMN "addedBy" uuid;
ALTER TABLE "shared_space_member"
ADD COLUMN "isNew" boolean NOT NULL DEFAULT false;
Every space gets no token, and the unique index allows any number of
nulls; every membership gets no one who added it and isn't new, so
nothing already shared shows in an Inbox. The new table starts empty.
None of it rewrites a table, and the server before the switch names its
own columns, so it keeps working.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
|
||
| .forgejo/workflows | ||
| .vscode | ||
| apps | ||
| deploy | ||
| docs/adr | ||
| packages | ||
| snap | ||
| test | ||
| third_party | ||
| tool | ||
| .dockerignore | ||
| .gitignore | ||
| AGENTS.md | ||
| analysis_options.yaml | ||
| CLAUDE.md | ||
| CONTRIBUTING.md | ||
| LICENSE.md | ||
| pubspec.lock | ||
| pubspec.yaml | ||
| README.md | ||
Halkyon
Halkyon's apps and the packages they share, in one repository. Each app is a Flutter app for Linux, Windows, macOS, Android and iOS with a Serverpod server of its own, offline-first, and anyone may run an instance of it: the apps, the servers and everything needed to host an instance are published.
| App | What it is | Where |
|---|---|---|
| Halkyon Learn | Creating, sharing and studying study material, flashcards scheduled with FSRS | apps/learn/, at learn.halkyon.eu |
| Halkyon Todo | To-dos; for now a shell on the shared packages: sign-in, instances, the account | apps/todo/ |
| Halkyon Train | Running, cycling and rowing (ADR 0030); for now a shell on the shared packages | apps/train/ |
Halkyon Meditate is to follow.
Layout
apps/<app>/: each app's server, generated client and Flutter app.packages/: what the apps share, taken by path:halkyon_ui(the look: theme, bundled Noto Sans, layout, shared widgets),halkyon_app(instances, signing in, each person's data and its sync),halkyon_base(what every server has, as a Serverpod module with its generated clienthalkyon_base_client: signing in, the account and its deletion, the pruning, the instance's basics),halkyon_editor(rich text: the editor, its view and the document they keep),halkyon_core(low-level primitives) andhalkyon_interop(contracts between apps).deploy/: what runs an instance (Docker Compose: Caddy, the server, PostgreSQL), learn.halkyon.eu's today.tool/: the checks, the pre-push hook and the release scripts.docs/adr/: the decisions and their reasons.
One pub workspace holds every package: one pubspec.lock, one version of each dependency.
Melos runs scripts across them.
Getting started
- Flutter v3.47.8, the version CI uses (
FLUTTER_VERSIONin.forgejo/workflows/ci.yaml), with each platform you build for set up by Flutter's own guides; what an app needs beyond that is in its README. - The Serverpod CLI v4.0.4:
dart install serverpod_cli@4.0.4. Pass--no-analyticsto every call; otherwise it reports the commands run to Serverpod. - A C++ compiler, for
flutter testtoo: a dependency of Learn's compiles MeCab as it builds.
flutter pub get # every package of the workspace
dart run melos list # the packages
ln -s ../../tool/pre-push .git/hooks/pre-push # the checks before every push
dart run tool/check.dart # format, analysis, every package's tests
dart run tool/check.dart --changed-since=origin/main tests only what changed since
main and what depends on it; dart run melos run test:flutter --scope=<package> one
package's Flutter tests. Each server's tests start an embedded PostgreSQL (no Docker) and
need test passwords in the server's git-ignored config/passwords.yaml (the module's
README and each app's say which). How the code is organised and the rules it follows are
in AGENTS.md and each app's own.
Licences
The servers and the server module are under the Server Side Public License 1.0, the apps,
the client packages and the other shared packages under the GNU Affero General Public
License 3.0 or later. LICENSE.md says
which covers what, CONTRIBUTING.md how to contribute.