2026-09-16 19:24:59 +02:00
|
|
|
name: halkyon_learn_flutter
|
|
|
|
|
description: A new Flutter project with Serverpod.
|
|
|
|
|
|
|
|
|
|
# The following line prevents the package from being accidentally published to
|
|
|
|
|
# pub.dev using `pub publish`. This is preferred for private packages.
|
|
|
|
|
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|
|
|
|
|
|
|
|
|
# The following defines the version and build number for your application.
|
|
|
|
|
# A version number is three numbers separated by dots, like 1.2.43
|
|
|
|
|
# followed by an optional build number separated by a +.
|
|
|
|
|
# Both the version and the builder number may be overridden in flutter
|
|
|
|
|
# build by specifying --build-name and --build-number, respectively.
|
|
|
|
|
# In Android, build-name is used as versionName while build-number used as versionCode.
|
|
|
|
|
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
|
|
|
|
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
|
|
|
|
# Read more about iOS versioning at
|
|
|
|
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
2026-10-07 14:49:29 +02:00
|
|
|
version: 0.2.2+5
|
2026-09-16 19:24:59 +02:00
|
|
|
|
|
|
|
|
environment:
|
|
|
|
|
sdk: '^3.13.0'
|
|
|
|
|
flutter: '^3.44.4'
|
|
|
|
|
|
|
|
|
|
resolution: workspace
|
|
|
|
|
|
|
|
|
|
dependencies:
|
2026-09-30 08:38:08 +02:00
|
|
|
# Our fork of the editor's main branch, moved to win32 v6 so the app can stay in the pub
|
2026-09-30 21:49:04 +02:00
|
|
|
# workspace, pinned to a signed commit of its branch halkyon/win32-v6.
|
2026-09-30 08:38:08 +02:00
|
|
|
appflowy_editor:
|
2026-09-30 21:49:04 +02:00
|
|
|
git:
|
|
|
|
|
url: https://code.halkyon.app/project-halkyon/appflowy-editor.git
|
2026-10-02 19:52:22 +02:00
|
|
|
ref: 83bdb2a3e66881eff9b2f8afe416b57fc791daf9
|
Import Anki decks with their files and history
Files in content: the person's images and sounds as asset rows synced like
all content, their files kept by hash on each device, uploaded after each
sync, downloaded on demand and checked against their hash
(core/asset/AssetRepository). Rich text names them as asset:<id> URLs, in
the editor's image block and an audio block of ours; any other URL is never
fetched. Sounds play through audioplayers, one at a time. Content holding
only an image or a sound isn't blank, and lists show it as such.
The Anki import ("Import from Anki" under Add material): .apkg and .colpkg
packages in all three formats Anki has written, read on an isolate of their
own. Modern packages compress everything with zstd in frames that don't
record their size, which pure_zstd v1.0.0 refuses; our fork decodes them.
Fields become rich text by a converter of our own (lines, lists, formatting,
images, sounds, cloze deletions hidden on the front). One card per note,
the files first, studysets as chosen (flatten, sub-studysets, or tags by
subdeck), the notes' tags, and, if wanted, the review history, which FSRS
replays: the first template's reviews to the card's schedule, a reversed
note type's second to the reverse one.
Imported cards say where they came from in their first revision (card
schema 3); the dashboard doesn't count them as written. The test fixtures
are exported by Anki itself (tool/anki/make_fixtures.py).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 04:52:02 +02:00
|
|
|
# Anki packages are zip files.
|
|
|
|
|
archive: ^4.0.7
|
|
|
|
|
# Sounds in content (pronunciations): one package for all five platforms; on
|
|
|
|
|
# Linux it plays through GStreamer, whose development files a Linux build needs.
|
|
|
|
|
audioplayers: ^6.8.1
|
feat(app): the UI foundation: theme, tabs and routes, strings
The app gets its look and navigation before more features land on them.
- Theme (core/theme/): hand-written light and dark Material 3 colour schemes around
cobalt and copper; ThemeExtras for the colours Material has no role for (states, the
four FSRS ratings); the 20 colours a person can give content. Tests hold text to WCAG
AA contrast on each palette colour and on the dark theme's primary colour and surface.
- Layout and shared widgets: Material 3's window-size classes (core/layout/);
SectionCard, AppLoadingIndicator, confirmDestructiveAction and FoundById
(core/widget/). Lucide icons.
- Navigation with go_router: four tabs (Home, Library, Explore, Profile), each keeping
its own stack of screens, as a navigation rail with the app's icon from 600 dp and as
a navigation bar below; choosing the open tab again returns to its first screen. A
studyset's screen sits in the library; studying, cards and settings cover the tabs.
- Routes carry ids: FoundById finds the item in the repository's live list, so a
studyset deleted on another device, or an old link, says it no longer exists. A
screen that deletes its own item keeps showing it while it closes, rather than
flashing that message.
- Every person gets an app of their own (PersonApp, lib/src/app/): a
MaterialApp.router with its own GoRouter and PersonScope around the navigator, built
by SessionGate inside the person's keyed subtree. Screens read the person's
repositories wherever they are built, so the static route() helpers and the listener
that closed every pushed screen on a change of person are gone: a change of person
now disposes the whole router and starts again at home.
- The account dialog becomes the Profile tab, with Settings (sign out, delete the
account, About) behind a gear. A waiting GitHub sign-in now keeps waiting while
another tab shows; a change of person still cancels it.
- The new screens' strings come from Flutter's gen-l10n (English for now). The
generated files are committed, left out of analysis and formatting, and written
anew by `flutter pub get`, so CI's check of generated code also catches stale ones.
Widget tests now start the whole app (test/support/pump_app.dart) at the screen's path
and move through it as a person would, by tabs and taps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 00:18:31 +02:00
|
|
|
clock: ^1.1.3
|
Import Anki decks with their files and history
Files in content: the person's images and sounds as asset rows synced like
all content, their files kept by hash on each device, uploaded after each
sync, downloaded on demand and checked against their hash
(core/asset/AssetRepository). Rich text names them as asset:<id> URLs, in
the editor's image block and an audio block of ours; any other URL is never
fetched. Sounds play through audioplayers, one at a time. Content holding
only an image or a sound isn't blank, and lists show it as such.
The Anki import ("Import from Anki" under Add material): .apkg and .colpkg
packages in all three formats Anki has written, read on an isolate of their
own. Modern packages compress everything with zstd in frames that don't
record their size, which pure_zstd v1.0.0 refuses; our fork decodes them.
Fields become rich text by a converter of our own (lines, lists, formatting,
images, sounds, cloze deletions hidden on the front). One card per note,
the files first, studysets as chosen (flatten, sub-studysets, or tags by
subdeck), the notes' tags, and, if wanted, the review history, which FSRS
replays: the first template's reviews to the card's schedule, a reversed
note type's second to the reverse one.
Imported cards say where they came from in their first revision (card
schema 3); the dashboard doesn't count them as written. The test fixtures
are exported by Anki itself (tool/anki/make_fixtures.py).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 04:52:02 +02:00
|
|
|
crypto: ^3.0.7
|
2026-09-16 19:24:59 +02:00
|
|
|
# The following adds the Cupertino Icons font to your application.
|
|
|
|
|
# Use with the CupertinoIcons class for iOS style icons.
|
|
|
|
|
cupertino_icons: ^1.0.5
|
feat(app): a Pomodoro timer on Home, in the notification and in the top bar
Home has a Pomodoro timer (decided 2026-10-07, after Goodtime): the
profile it runs, chosen there while no timer is set, the time left of
the phase, and Start, Pause or Resume, Skip and Stop. While studying, a
small countdown sits in the study screen's header, and a tap pauses it
or goes on. A phase that ends leads to the next, which starts by itself
where the profile says so and otherwise waits, paused at its full
length.
Profiles follow the person to every device, in their settings (schema
4, still written as schema 3 while the profiles are as they come, so
older apps go on reading the settings): Classic, 25 minutes of focus,
5 of break and 15 after four, and 52/17 without long breaks, built in;
both can be changed, more added and deleted, from Home's card or from
Settings. A profile can start breaks and focus by themselves, and keep
the device quiet while focusing. The running timer is the device's own,
kept in the person's folder: closed and opened again, the app catches
up with the phases that ran out meanwhile.
Android: the app's own foreground service (Kotlin, specialUse, as
Goodtime does) shows an ongoing notification counting the phase down,
with Pause or Resume, Skip and Stop; on Android 16 it is a Live Update,
its progress bar the profile's cycle, each phase a segment, focus in
cobalt and breaks in copper. An exact alarm (USE_EXACT_ALARM) alerts
with sound and vibration when the phase ends, also with the app
closed, and Do Not Disturb goes on while focusing once the person
allowed it, the filter before given back afterwards. No notification
plugin: flutter_local_notifications has Live Updates only in a
prerelease that needs compileSdk 37; androidx.core 1.18, which the
build resolved already, has every API needed.
Linux, over the session bus (package:dbus, already in the build): where
the desktop shows icons in its top bar (GNOME with the AppIndicator
extension, which Ubuntu has built in; KDE) the timer counts down there,
the buttons in the icon's menu, as a StatusNotifierItem written in Dart
on the names and paths the snap's desktop plug allows; elsewhere a
notification with the buttons says until when the phase runs. An alert
with sound ends each phase while the app runs, and GNOME's banners stay
off while focusing, through gsettings. xdg-desktop-portal's Background
status, the planned fallback, serves Flatpaks only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-07 02:54:52 +02:00
|
|
|
# The Pomodoro timer on Linux talks to the desktop's notification server over
|
|
|
|
|
# the session bus; the version the network packages bring along already.
|
|
|
|
|
dbus: ^0.7.15
|
2026-09-30 08:38:08 +02:00
|
|
|
equatable: ^3.0.0
|
feat(app): the UI foundation: theme, tabs and routes, strings
The app gets its look and navigation before more features land on them.
- Theme (core/theme/): hand-written light and dark Material 3 colour schemes around
cobalt and copper; ThemeExtras for the colours Material has no role for (states, the
four FSRS ratings); the 20 colours a person can give content. Tests hold text to WCAG
AA contrast on each palette colour and on the dark theme's primary colour and surface.
- Layout and shared widgets: Material 3's window-size classes (core/layout/);
SectionCard, AppLoadingIndicator, confirmDestructiveAction and FoundById
(core/widget/). Lucide icons.
- Navigation with go_router: four tabs (Home, Library, Explore, Profile), each keeping
its own stack of screens, as a navigation rail with the app's icon from 600 dp and as
a navigation bar below; choosing the open tab again returns to its first screen. A
studyset's screen sits in the library; studying, cards and settings cover the tabs.
- Routes carry ids: FoundById finds the item in the repository's live list, so a
studyset deleted on another device, or an old link, says it no longer exists. A
screen that deletes its own item keeps showing it while it closes, rather than
flashing that message.
- Every person gets an app of their own (PersonApp, lib/src/app/): a
MaterialApp.router with its own GoRouter and PersonScope around the navigator, built
by SessionGate inside the person's keyed subtree. Screens read the person's
repositories wherever they are built, so the static route() helpers and the listener
that closed every pushed screen on a change of person are gone: a change of person
now disposes the whole router and starts again at home.
- The account dialog becomes the Profile tab, with Settings (sign out, delete the
account, About) behind a gear. A waiting GitHub sign-in now keeps waiting while
another tab shows; a change of person still cancels it.
- The new screens' strings come from Flutter's gen-l10n (English for now). The
generated files are committed, left out of analysis and formatting, and written
anew by `flutter pub get`, so CI's check of generated code also catches stale ones.
Widget tests now start the whole app (test/support/pump_app.dart) at the screen's path
and move through it as a person would, by tabs and taps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 00:18:31 +02:00
|
|
|
expressive_loading_indicator: ^0.0.1
|
feat(app): export a studyset as Markdown
Export content, in the studyset's options, writes the studyset and
everything inside it as one readable Markdown document (decided
2026-10-08: no re-importable archive before the public tier's release
files): its title, secondary title, description and licence, then its
flashcards (answers below their questions, choices as task lists),
notes, learning paths (steps with their kinds, answers, feedback and
sources), PDFs and bibliography, then each sub-studyset a level deeper.
Rich text keeps its formatting; formulas go between dollars, callouts
become quotes, spoilers HTML details, YouTube videos links. With files
(images, sounds, PDFs) it keeps a .zip of the document and them under
files/; a file this device can't have is named and counted.
Saving goes through a FileSaver: file_selector's dialog on the desktop,
file_picker's (Android's Storage Access Framework) on phones, a direct
dependency now at the version the editor already brings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-08 15:02:22 +02:00
|
|
|
# Saves exports on Android and iOS, where file_selector has no save dialog
|
|
|
|
|
# (the system's through it: Android's Storage Access Framework); the editor
|
|
|
|
|
# brings this version along already.
|
|
|
|
|
file_picker: ^13.1.0
|
|
|
|
|
# Picks the files to import (and later the images to add), and saves exports
|
|
|
|
|
# on the desktop: flutter.dev's own.
|
Import Anki decks with their files and history
Files in content: the person's images and sounds as asset rows synced like
all content, their files kept by hash on each device, uploaded after each
sync, downloaded on demand and checked against their hash
(core/asset/AssetRepository). Rich text names them as asset:<id> URLs, in
the editor's image block and an audio block of ours; any other URL is never
fetched. Sounds play through audioplayers, one at a time. Content holding
only an image or a sound isn't blank, and lists show it as such.
The Anki import ("Import from Anki" under Add material): .apkg and .colpkg
packages in all three formats Anki has written, read on an isolate of their
own. Modern packages compress everything with zstd in frames that don't
record their size, which pure_zstd v1.0.0 refuses; our fork decodes them.
Fields become rich text by a converter of our own (lines, lists, formatting,
images, sounds, cloze deletions hidden on the front). One card per note,
the files first, studysets as chosen (flatten, sub-studysets, or tags by
subdeck), the notes' tags, and, if wanted, the review history, which FSRS
replays: the first template's reviews to the card's schedule, a reversed
note type's second to the reverse one.
Imported cards say where they came from in their first revision (card
schema 3); the dashboard doesn't count them as written. The test fixtures
are exported by Anki itself (tool/anki/make_fixtures.py).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 04:52:02 +02:00
|
|
|
file_selector: ^1.1.0
|
2026-09-16 19:24:59 +02:00
|
|
|
flutter:
|
|
|
|
|
sdk: flutter
|
feat(app): sign in anonymously on first launch
The app's first patterns. Dependencies (lib/src/core/dependencies) is the composition root, built at startup and handed down by DependenciesScope; it replaces the scaffold's global client and restores a stored session before the first frame, so a restart never creates a second anonymous account. AuthGateway with ServerpodAuthGateway is the boundary to the server's authentication, AuthRepository signs in anonymously when nobody is signed in, and SessionBloc (flutter_bloc) keeps a person signed in, also after signing out or a revoked session. SessionGate shows a spinner while signing in, offers to try again when that fails, and builds the app anew for every person. AccountButton explains that progress is saved on this device and asks before signing out of an anonymous account, which can't be restored. AppBlocObserver logs all blocs.
Tests: the repository, the bloc's sequences (a stored session is kept, a failed sign-in is retried, signing out starts anew), and the widgets, including that cancelling the sign-out keeps the account. test/support holds the fake gateway and testDependencies(). AGENTS.md describes the composition root and the test factory.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 09:04:02 +02:00
|
|
|
flutter_bloc: ^9.1.1
|
feat(app): the UI foundation: theme, tabs and routes, strings
The app gets its look and navigation before more features land on them.
- Theme (core/theme/): hand-written light and dark Material 3 colour schemes around
cobalt and copper; ThemeExtras for the colours Material has no role for (states, the
four FSRS ratings); the 20 colours a person can give content. Tests hold text to WCAG
AA contrast on each palette colour and on the dark theme's primary colour and surface.
- Layout and shared widgets: Material 3's window-size classes (core/layout/);
SectionCard, AppLoadingIndicator, confirmDestructiveAction and FoundById
(core/widget/). Lucide icons.
- Navigation with go_router: four tabs (Home, Library, Explore, Profile), each keeping
its own stack of screens, as a navigation rail with the app's icon from 600 dp and as
a navigation bar below; choosing the open tab again returns to its first screen. A
studyset's screen sits in the library; studying, cards and settings cover the tabs.
- Routes carry ids: FoundById finds the item in the repository's live list, so a
studyset deleted on another device, or an old link, says it no longer exists. A
screen that deletes its own item keeps showing it while it closes, rather than
flashing that message.
- Every person gets an app of their own (PersonApp, lib/src/app/): a
MaterialApp.router with its own GoRouter and PersonScope around the navigator, built
by SessionGate inside the person's keyed subtree. Screens read the person's
repositories wherever they are built, so the static route() helpers and the listener
that closed every pushed screen on a change of person are gone: a change of person
now disposes the whole router and starts again at home.
- The account dialog becomes the Profile tab, with Settings (sign out, delete the
account, About) behind a gear. A waiting GitHub sign-in now keeps waiting while
another tab shows; a change of person still cancels it.
- The new screens' strings come from Flutter's gen-l10n (English for now). The
generated files are committed, left out of analysis and formatting, and written
anew by `flutter pub get`, so CI's check of generated code also catches stale ones.
Widget tests now start the whole app (test/support/pump_app.dart) at the screen's path
and move through it as a person would, by tabs and taps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 00:18:31 +02:00
|
|
|
flutter_localizations:
|
|
|
|
|
sdk: flutter
|
fix(app): GitHub sign-in on Linux and Windows waits for the browser
Signing in with GitHub on Linux ended in "Unable to connect" at 127.0.0.1:43823 after authorizing. flutter_web_auth_2 (v5.1.0, unchanged on its master) ends every pending sign-in when the app is resumed. That suits mobile, where coming back without finishing means giving up, but on the desktop the window regaining focus resumes the app, and the package's loopback server closed before GitHub sent the browser back. FluffyChat's issue #3341 and the package's #171 report the same.
The app now puts its own Linux and Windows implementation behind the package's platform interface (LoopbackWebAuth): the system browser and a loopback address, as RFC 8252 recommends, waiting until GitHub answers, the person cancels, or five minutes pass. It listens on 127.0.0.1 only and answers only the callback's path. Serverpod's PKCE and state check stay in use. There, the account dialog shows "Continue in your browser." with a Cancel button, and closing the dialog cancels the sign-in.
Serverpod (v4.0.3) reports a cancelled browser round trip as an unknown error, so the gateway knows a cancel by having asked for it. A cancel on GitHub's own page, or leaving the browser on Android, still reads as a failure; AGENTS.md says so.
The regression test drives the package's front through a change of focus. With the package's behaviour it fails with the same refused connection.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 20:18:18 +02:00
|
|
|
# The app's own browser half of GitHub sign-in on Linux and Windows
|
|
|
|
|
# (LoopbackWebAuth) plugs in here, and opens the browser and brings the window
|
|
|
|
|
# back with the same packages flutter_web_auth_2 uses.
|
feat(app): editor blocks: callout, spoiler, formulas, files, video, study button
Blocks beyond the editor's own, each built once for editor and view:
- callout: eight kinds in theme colours, kind, title and "folded at
first" from its header; written in place, left with Enter on its
empty last line; never inside another
- spoiler: written in plain sight, hidden behind Reveal read-only
- formula on its own line, written on its own screen with a preview
and snippet buttons, and formulas within a line (Σ in the format
bar); set by flutter_math_fork behind MathView, LaTeX's numbered
environments read as their aligned forms
- image and sound picked from the device, kept as synced files
- YouTube video from its link, with its start and a caption: a card
until played, nothing fetched before; played in place where a web
view exists (youtube_player_iframe, privacy-enhanced), else opened
in the browser, behind YoutubeVideos in Dependencies
- study button: a studyset, a tag or all cards, with or without its
topics, back first; its StudyFocus rides on the study route and
narrows that session only
- tables, already in the editor, now insertable everywhere
An Insert button opens on the format bar, so blocks can be inserted on
phones too; the desktop's / menu lists the same blocks, translated.
Card faces now dock the format bar while written, so cards get blocks
and formatting on phones as well. The blocks are listed in
EditorBlocks: the core's, plus the features' through
EditorBlocksScope. Blocks of a type the app doesn't know say they need
a newer version and stay as they are, so formatVersion stays 1.
Their types and keys are in content_blocks.dart, which RichContent
reads: a block's text counts as text, and a video or study button
isn't blank. The path import now makes formulas real ones.
integration_test/youtube_player_test.dart plays a real video through
the app's player on a device, which no widget test can: YouTube
refuses embeds that don't say where they are embedded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-03 12:20:41 +02:00
|
|
|
flutter_math_fork: ^0.7.4
|
fix(app): GitHub sign-in on Linux and Windows waits for the browser
Signing in with GitHub on Linux ended in "Unable to connect" at 127.0.0.1:43823 after authorizing. flutter_web_auth_2 (v5.1.0, unchanged on its master) ends every pending sign-in when the app is resumed. That suits mobile, where coming back without finishing means giving up, but on the desktop the window regaining focus resumes the app, and the package's loopback server closed before GitHub sent the browser back. FluffyChat's issue #3341 and the package's #171 report the same.
The app now puts its own Linux and Windows implementation behind the package's platform interface (LoopbackWebAuth): the system browser and a loopback address, as RFC 8252 recommends, waiting until GitHub answers, the person cancels, or five minutes pass. It listens on 127.0.0.1 only and answers only the callback's path. Serverpod's PKCE and state check stay in use. There, the account dialog shows "Continue in your browser." with a Cancel button, and closing the dialog cancels the sign-in.
Serverpod (v4.0.3) reports a cancelled browser round trip as an unknown error, so the gateway knows a cancel by having asked for it. A cancel on GitHub's own page, or leaving the browser on Android, still reads as a failure; AGENTS.md says so.
The regression test drives the package's front through a change of focus. With the package's behaviour it fails with the same refused connection.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 20:18:18 +02:00
|
|
|
flutter_web_auth_2_platform_interface: ^5.0.0
|
feat(app): flashcards on append-only revisions
A studyset's screen lists its cards (reorderable; a marker on cards changed on two devices) and opens a card's screen with an editor for each side. Saving appends a revision whose parents are all current heads. mergeRevisions merges several heads per side against their newest common ancestor, as decided: different sides merge automatically, the same side changed differently is a conflict. The card screen then shows every version of that side with "Keep this", and saving is possible once each conflicted side is settled, which resolves the conflict for every device. Nothing is merged silently inside a side.
CardRepository watches a studyset's cards, creates at the end and moves by fractional index (fractional_indexing_dart, checked against the reference algorithm's test vectors), and deletes by blanking every revision first (ADR 0002, rule 2). Deleting a studyset now deletes its cards the same way before the studyset goes. Every person gets a CardRepository whose revisions name them as author. RichContent gains plainText for the list's previews.
Tests: the merge (chains, different and same sides, conflicts, resolving, three heads, missing parents, arrival order), the payload, the repository against a fake gateway, both blocs, and the widgets, including that cancelling a delete keeps the card. The sync tests, which need the running server, cover merging and conflicts across two devices and blank revisions on the server; their device helper is now shared in test/sync/support. AGENTS.md describes the revision pattern, the fractional order, and two tooling traps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 14:41:44 +02:00
|
|
|
fractional_indexing_dart: ^1.0.7
|
feat(app): study with FSRS
A studyset's card list gains "Study". The study screen shows the front, then the answer and the four ratings, each labelled with when the card would come back; every rating is recorded with how long answering took, and the queue is worked out anew after each.
Schedules are never stored: scheduleOf replays a card's reviews in time order with FSRS-6 (fsrs v2.0.1, pinned exactly so every device on one app version computes the same). The package's fuzz can only be seeded through an API meant for tests, and due dates don't feed back into a card's state, so the replay runs without fuzz and fuzzedDays spreads the final due date over FSRS's range, picked by a stable FNV-1a fraction of the card's id and review count. studyQueue takes due cards earliest first, then new cards in studyset order, 20 per studyset and local day, and brings a learning card due within 20 minutes early when nothing else is due. Deleting a card deletes its reviews.
Tests: the replay (learning, growing intervals, the same schedule whatever order reviews arrive in, previews matching what a rating yields), the fuzz range, the queue (order, the daily limit counted per local day, learn-ahead), the repository, the session bloc under a controlled clock, the screen, and a sync test for ratings on two devices. AGENTS.md records the review log and scheduling, and how plugin lint rules are ignored.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 16:59:07 +02:00
|
|
|
# Pinned exactly: every device on one app version must compute the same
|
|
|
|
|
# schedules.
|
|
|
|
|
fsrs: 2.0.1
|
feat(app): the UI foundation: theme, tabs and routes, strings
The app gets its look and navigation before more features land on them.
- Theme (core/theme/): hand-written light and dark Material 3 colour schemes around
cobalt and copper; ThemeExtras for the colours Material has no role for (states, the
four FSRS ratings); the 20 colours a person can give content. Tests hold text to WCAG
AA contrast on each palette colour and on the dark theme's primary colour and surface.
- Layout and shared widgets: Material 3's window-size classes (core/layout/);
SectionCard, AppLoadingIndicator, confirmDestructiveAction and FoundById
(core/widget/). Lucide icons.
- Navigation with go_router: four tabs (Home, Library, Explore, Profile), each keeping
its own stack of screens, as a navigation rail with the app's icon from 600 dp and as
a navigation bar below; choosing the open tab again returns to its first screen. A
studyset's screen sits in the library; studying, cards and settings cover the tabs.
- Routes carry ids: FoundById finds the item in the repository's live list, so a
studyset deleted on another device, or an old link, says it no longer exists. A
screen that deletes its own item keeps showing it while it closes, rather than
flashing that message.
- Every person gets an app of their own (PersonApp, lib/src/app/): a
MaterialApp.router with its own GoRouter and PersonScope around the navigator, built
by SessionGate inside the person's keyed subtree. Screens read the person's
repositories wherever they are built, so the static route() helpers and the listener
that closed every pushed screen on a change of person are gone: a change of person
now disposes the whole router and starts again at home.
- The account dialog becomes the Profile tab, with Settings (sign out, delete the
account, About) behind a gear. A waiting GitHub sign-in now keeps waiting while
another tab shows; a change of person still cancels it.
- The new screens' strings come from Flutter's gen-l10n (English for now). The
generated files are committed, left out of analysis and formatting, and written
anew by `flutter pub get`, so CI's check of generated code also catches stale ones.
Widget tests now start the whole app (test/support/pump_app.dart) at the screen's path
and move through it as a person would, by tabs and taps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 00:18:31 +02:00
|
|
|
go_router: ^18.0.2
|
2026-09-16 19:24:59 +02:00
|
|
|
halkyon_learn_client:
|
|
|
|
|
path: ../halkyon_learn_client
|
Import Anki decks with their files and history
Files in content: the person's images and sounds as asset rows synced like
all content, their files kept by hash on each device, uploaded after each
sync, downloaded on demand and checked against their hash
(core/asset/AssetRepository). Rich text names them as asset:<id> URLs, in
the editor's image block and an audio block of ours; any other URL is never
fetched. Sounds play through audioplayers, one at a time. Content holding
only an image or a sound isn't blank, and lists show it as such.
The Anki import ("Import from Anki" under Add material): .apkg and .colpkg
packages in all three formats Anki has written, read on an isolate of their
own. Modern packages compress everything with zstd in frames that don't
record their size, which pure_zstd v1.0.0 refuses; our fork decodes them.
Fields become rich text by a converter of our own (lines, lists, formatting,
images, sounds, cloze deletions hidden on the front). One card per note,
the files first, studysets as chosen (flatten, sub-studysets, or tags by
subdeck), the notes' tags, and, if wanted, the review history, which FSRS
replays: the first template's reviews to the card's schedule, a reversed
note type's second to the reverse one.
Imported cards say where they came from in their first revision (card
schema 3); the dashboard doesn't count them as written. The test fixtures
are exported by Anki itself (tool/anki/make_fixtures.py).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 04:52:02 +02:00
|
|
|
# Anki's fields are HTML; the editor brings this parser along already.
|
|
|
|
|
html: ^0.15.6
|
feat(app): the UI foundation: theme, tabs and routes, strings
The app gets its look and navigation before more features land on them.
- Theme (core/theme/): hand-written light and dark Material 3 colour schemes around
cobalt and copper; ThemeExtras for the colours Material has no role for (states, the
four FSRS ratings); the 20 colours a person can give content. Tests hold text to WCAG
AA contrast on each palette colour and on the dark theme's primary colour and surface.
- Layout and shared widgets: Material 3's window-size classes (core/layout/);
SectionCard, AppLoadingIndicator, confirmDestructiveAction and FoundById
(core/widget/). Lucide icons.
- Navigation with go_router: four tabs (Home, Library, Explore, Profile), each keeping
its own stack of screens, as a navigation rail with the app's icon from 600 dp and as
a navigation bar below; choosing the open tab again returns to its first screen. A
studyset's screen sits in the library; studying, cards and settings cover the tabs.
- Routes carry ids: FoundById finds the item in the repository's live list, so a
studyset deleted on another device, or an old link, says it no longer exists. A
screen that deletes its own item keeps showing it while it closes, rather than
flashing that message.
- Every person gets an app of their own (PersonApp, lib/src/app/): a
MaterialApp.router with its own GoRouter and PersonScope around the navigator, built
by SessionGate inside the person's keyed subtree. Screens read the person's
repositories wherever they are built, so the static route() helpers and the listener
that closed every pushed screen on a change of person are gone: a change of person
now disposes the whole router and starts again at home.
- The account dialog becomes the Profile tab, with Settings (sign out, delete the
account, About) behind a gear. A waiting GitHub sign-in now keeps waiting while
another tab shows; a change of person still cancels it.
- The new screens' strings come from Flutter's gen-l10n (English for now). The
generated files are committed, left out of analysis and formatting, and written
anew by `flutter pub get`, so CI's check of generated code also catches stale ones.
Widget tests now start the whole app (test/support/pump_app.dart) at the screen's path
and move through it as a person would, by tabs and taps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 00:18:31 +02:00
|
|
|
intl: ^0.20.3
|
|
|
|
|
lucide_icons_flutter: ^3.1.20
|
2026-09-29 23:11:00 +02:00
|
|
|
material_ui: ^1.5.0
|
feat: the app tells when sync fails, and when it is too old
Published apps can't be made to update, so the app now notices when it
no longer matches the server: the server names the oldest app build it
works with (instance.info's minimumAppBuild), asked before the first
sync of each launch, and the sync engine's refusal of differing synced
tables counts the same. Either way the app says "update the app; if it
is up to date, the server needs an update" in a banner on Home and in
the new Sync card in Settings, and what the person does meanwhile stays
on the device.
The Sync card shows where sync stands since the app started (syncing,
everything synced, sync failing, with the last completion and failure)
and offers Sync now. While the last sync failed, a dot sits on the
Profile tab and the Settings cog. One sync that goes through clears it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-03 23:26:47 +02:00
|
|
|
package_info_plus: ^10.2.2
|
feat(server): studysets, the first synced table (migration pending)
Adopts serverpod_offline_sync v0.0.8 (ADR 0001): its module (nickname offline_sync) and the experimental database: sync in generator.yaml, its server and client packages, and overrides that keep the four Serverpod core packages on v4.0.2, since the package pins v4.0.0 exactly and the spike showed it works on v4.0.2. The app's new dependencies come along because the workspace shares one lock file.
Studyset is the first synced model. Its space owns it, so it has no owner column, and only the envelope is a column: keyId and nonce stay null (no end-to-end encryption; ADR 0002, rule 6), and payload holds the content as versioned UTF-8 JSON, so a new content field changes the payload's schema version instead of this table, whose every migration locks older app versions out of syncing (rule 3). The audience column of the original design is gone: it served key selection, and the engine's spaces decide who reads a row.
The generated server and client code is included. The migration isn't: it is created together with the move to Serverpod v4.0.3 and reviewed first, like the client's local migration (the client's lib/migrations is still empty).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 10:11:24 +02:00
|
|
|
path: ^1.9.1
|
|
|
|
|
path_provider: ^2.1.6
|
Import Anki decks with their files and history
Files in content: the person's images and sounds as asset rows synced like
all content, their files kept by hash on each device, uploaded after each
sync, downloaded on demand and checked against their hash
(core/asset/AssetRepository). Rich text names them as asset:<id> URLs, in
the editor's image block and an audio block of ours; any other URL is never
fetched. Sounds play through audioplayers, one at a time. Content holding
only an image or a sound isn't blank, and lists show it as such.
The Anki import ("Import from Anki" under Add material): .apkg and .colpkg
packages in all three formats Anki has written, read on an isolate of their
own. Modern packages compress everything with zstd in frames that don't
record their size, which pure_zstd v1.0.0 refuses; our fork decodes them.
Fields become rich text by a converter of our own (lines, lists, formatting,
images, sounds, cloze deletions hidden on the front). One card per note,
the files first, studysets as chosen (flatten, sub-studysets, or tags by
subdeck), the notes' tags, and, if wanted, the review history, which FSRS
replays: the first template's reviews to the card's schedule, a reversed
note type's second to the reverse one.
Imported cards say where they came from in their first revision (card
schema 3); the dashboard doesn't count them as written. The test fixtures
are exported by Anki itself (tool/anki/make_fixtures.py).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 04:52:02 +02:00
|
|
|
# Modern Anki packages are zstd-compressed, every frame without its content
|
|
|
|
|
# size, which upstream's v1.0.0 refuses. Our fork decodes them, pinned to a
|
|
|
|
|
# signed commit of its branch halkyon/unknown-content-size; back to upstream
|
|
|
|
|
# once it decodes them too.
|
feat: PDF sources: read, take notes and cards from pages, deadlines
PDFs live in a studyset's new Files tab: uploaded from Add material
(a file the studyset holds already asks first), each row with its
pages, cards, notes and size, how far it is read and its reading
deadline; renamed, given a deadline (a page or a chapter by a day),
marked as read, started afresh, deleted with a moment to undo.
The reader (/pdfs/:id) opens where reading left off: on wide windows
a rail of tools (search, fit, zoom, fullscreen), a page counter with
the chapter, and a sidebar; on phones a top bar, a page scrubber and
a sheet. The sidebar holds the PDF's flashcards, by page or chapter,
made from selected text or written there, each tied to its page and
tagged with its chapter; and the page's note, an ordinary note tied
to the page. Screenshots of a region become images that know their
page. Cards and screenshots lead back to their page; reading on past
a chapter with cards suggests studying it. Home shows the deadlines
running and to come, and a timeline of them.
Server: pdf_source rows (content in the payload, last writer wins)
and an append-only pdf_reading log, the furthest page derived from
it so devices never conflict. Files up to 50 MB for PDFs (20 MB for
others; a larger file that isn't a PDF is deleted once stored). A
deleted PDF takes its file's asset row along unless another PDF has
the same file. Card payload 4 and note payload 2 keep the page.
The reader draws with pdfrx (PDFium) behind PdfEngine in
Dependencies; widget tests use a fake, and
integration_test/pdf_engine_test.dart checks the real engine on a
device or the desktop.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-03 14:40:08 +02:00
|
|
|
pdfrx: ^2.6.5
|
Import Anki decks with their files and history
Files in content: the person's images and sounds as asset rows synced like
all content, their files kept by hash on each device, uploaded after each
sync, downloaded on demand and checked against their hash
(core/asset/AssetRepository). Rich text names them as asset:<id> URLs, in
the editor's image block and an audio block of ours; any other URL is never
fetched. Sounds play through audioplayers, one at a time. Content holding
only an image or a sound isn't blank, and lists show it as such.
The Anki import ("Import from Anki" under Add material): .apkg and .colpkg
packages in all three formats Anki has written, read on an isolate of their
own. Modern packages compress everything with zstd in frames that don't
record their size, which pure_zstd v1.0.0 refuses; our fork decodes them.
Fields become rich text by a converter of our own (lines, lists, formatting,
images, sounds, cloze deletions hidden on the front). One card per note,
the files first, studysets as chosen (flatten, sub-studysets, or tags by
subdeck), the notes' tags, and, if wanted, the review history, which FSRS
replays: the first template's reviews to the card's schedule, a reversed
note type's second to the reverse one.
Imported cards say where they came from in their first revision (card
schema 3); the dashboard doesn't count them as written. The test fixtures
are exported by Anki itself (tool/anki/make_fixtures.py).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 04:52:02 +02:00
|
|
|
pure_zstd:
|
|
|
|
|
git:
|
|
|
|
|
url: https://code.halkyon.app/project-halkyon/pure-zstd.git
|
|
|
|
|
ref: 596b63d5741d479073fbfc78377cc7dbdd342da3
|
build: Serverpod v4.0.3
Moves every Serverpod package, and the four overrides for serverpod_offline_sync, from v4.0.2 to v4.0.3 before the first synced table's migration exists, so that migration is created on the version that stays.
v4.0.3 contains fixes only. Checked before moving: serverpod_database gained only backwards-compatible changes, none of which the sync package calls into. Server-side session keys are hashed as before (the cheaper argon2 applies to JWT refresh tokens, which we don't use), so no session ends. The auth module's profile image relation is now deferred, which fixes restoring a Postgres dump; step 7's migration carries that change.
argon2 now runs in serverpod_argon2 (BSD-3), a new dependency of the server only: a prebuilt, statically linked library that needs no glibc, bundled by dart build cli like the SQLite libraries, so the Docker build is unchanged. It doesn't reach the app.
The code generator still fails on Dart v3.13's primary constructors in endpoint files (tried again), so the server keeps the four syntax lints off. The regenerated test tools gain withServerpod's ephemeralDatabase option.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 10:42:34 +02:00
|
|
|
serverpod_auth_idp_flutter: 4.0.3
|
|
|
|
|
serverpod_flutter: 4.0.3
|
feat(server): studysets, the first synced table (migration pending)
Adopts serverpod_offline_sync v0.0.8 (ADR 0001): its module (nickname offline_sync) and the experimental database: sync in generator.yaml, its server and client packages, and overrides that keep the four Serverpod core packages on v4.0.2, since the package pins v4.0.0 exactly and the spike showed it works on v4.0.2. The app's new dependencies come along because the workspace shares one lock file.
Studyset is the first synced model. Its space owns it, so it has no owner column, and only the envelope is a column: keyId and nonce stay null (no end-to-end encryption; ADR 0002, rule 6), and payload holds the content as versioned UTF-8 JSON, so a new content field changes the payload's schema version instead of this table, whose every migration locks older app versions out of syncing (rule 3). The audience column of the original design is gone: it served key selection, and the engine's spaces decide who reads a row.
The generated server and client code is included. The migration isn't: it is created together with the move to Serverpod v4.0.3 and reviewed first, like the client's local migration (the client's lib/migrations is still empty).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 10:11:24 +02:00
|
|
|
serverpod_offline_sync_client: 0.0.8
|
Import Anki decks with their files and history
Files in content: the person's images and sounds as asset rows synced like
all content, their files kept by hash on each device, uploaded after each
sync, downloaded on demand and checked against their hash
(core/asset/AssetRepository). Rich text names them as asset:<id> URLs, in
the editor's image block and an audio block of ours; any other URL is never
fetched. Sounds play through audioplayers, one at a time. Content holding
only an image or a sound isn't blank, and lists show it as such.
The Anki import ("Import from Anki" under Add material): .apkg and .colpkg
packages in all three formats Anki has written, read on an isolate of their
own. Modern packages compress everything with zstd in frames that don't
record their size, which pure_zstd v1.0.0 refuses; our fork decodes them.
Fields become rich text by a converter of our own (lines, lists, formatting,
images, sounds, cloze deletions hidden on the front). One card per note,
the files first, studysets as chosen (flatten, sub-studysets, or tags by
subdeck), the notes' tags, and, if wanted, the review history, which FSRS
replays: the first template's reviews to the card's schedule, a reversed
note type's second to the reverse one.
Imported cards say where they came from in their first revision (card
schema 3); the dashboard doesn't count them as written. The test fixtures
are exported by Anki itself (tool/anki/make_fixtures.py).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 04:52:02 +02:00
|
|
|
# Anki collections are SQLite databases (the sync engine already bundles it).
|
|
|
|
|
sqlite3: ^3.6.0
|
fix(app): GitHub sign-in on Linux and Windows waits for the browser
Signing in with GitHub on Linux ended in "Unable to connect" at 127.0.0.1:43823 after authorizing. flutter_web_auth_2 (v5.1.0, unchanged on its master) ends every pending sign-in when the app is resumed. That suits mobile, where coming back without finishing means giving up, but on the desktop the window regaining focus resumes the app, and the package's loopback server closed before GitHub sent the browser back. FluffyChat's issue #3341 and the package's #171 report the same.
The app now puts its own Linux and Windows implementation behind the package's platform interface (LoopbackWebAuth): the system browser and a loopback address, as RFC 8252 recommends, waiting until GitHub answers, the person cancels, or five minutes pass. It listens on 127.0.0.1 only and answers only the callback's path. Serverpod's PKCE and state check stay in use. There, the account dialog shows "Continue in your browser." with a Cancel button, and closing the dialog cancels the sign-in.
Serverpod (v4.0.3) reports a cancelled browser round trip as an unknown error, so the gateway knows a cancel by having asked for it. A cancel on GitHub's own page, or leaving the browser on Android, still reads as a failure; AGENTS.md says so.
The regression test drives the package's front through a change of focus. With the package's behaviour it fails with the same refused connection.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 20:18:18 +02:00
|
|
|
url_launcher: ^6.3.2
|
|
|
|
|
window_to_front: ^0.0.4
|
feat(app): editor blocks: callout, spoiler, formulas, files, video, study button
Blocks beyond the editor's own, each built once for editor and view:
- callout: eight kinds in theme colours, kind, title and "folded at
first" from its header; written in place, left with Enter on its
empty last line; never inside another
- spoiler: written in plain sight, hidden behind Reveal read-only
- formula on its own line, written on its own screen with a preview
and snippet buttons, and formulas within a line (Σ in the format
bar); set by flutter_math_fork behind MathView, LaTeX's numbered
environments read as their aligned forms
- image and sound picked from the device, kept as synced files
- YouTube video from its link, with its start and a caption: a card
until played, nothing fetched before; played in place where a web
view exists (youtube_player_iframe, privacy-enhanced), else opened
in the browser, behind YoutubeVideos in Dependencies
- study button: a studyset, a tag or all cards, with or without its
topics, back first; its StudyFocus rides on the study route and
narrows that session only
- tables, already in the editor, now insertable everywhere
An Insert button opens on the format bar, so blocks can be inserted on
phones too; the desktop's / menu lists the same blocks, translated.
Card faces now dock the format bar while written, so cards get blocks
and formatting on phones as well. The blocks are listed in
EditorBlocks: the core's, plus the features' through
EditorBlocksScope. Blocks of a type the app doesn't know say they need
a newer version and stay as they are, so formatVersion stays 1.
Their types and keys are in content_blocks.dart, which RichContent
reads: a block's text counts as text, and a video or study button
isn't blank. The path import now makes formulas real ones.
integration_test/youtube_player_test.dart plays a real video through
the app's player on a device, which no widget test can: YouTube
refuses embeds that don't say where they are embedded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-03 12:20:41 +02:00
|
|
|
youtube_player_iframe: ^6.0.2
|
2026-09-16 19:24:59 +02:00
|
|
|
|
|
|
|
|
dev_dependencies:
|
feat(server): studysets, the first synced table (migration pending)
Adopts serverpod_offline_sync v0.0.8 (ADR 0001): its module (nickname offline_sync) and the experimental database: sync in generator.yaml, its server and client packages, and overrides that keep the four Serverpod core packages on v4.0.2, since the package pins v4.0.0 exactly and the spike showed it works on v4.0.2. The app's new dependencies come along because the workspace shares one lock file.
Studyset is the first synced model. Its space owns it, so it has no owner column, and only the envelope is a column: keyId and nonce stay null (no end-to-end encryption; ADR 0002, rule 6), and payload holds the content as versioned UTF-8 JSON, so a new content field changes the payload's schema version instead of this table, whose every migration locks older app versions out of syncing (rule 3). The audience column of the original design is gone: it served key selection, and the engine's spaces decide who reads a row.
The generated server and client code is included. The migration isn't: it is created together with the move to Serverpod v4.0.3 and reviewed first, like the client's local migration (the client's lib/migrations is still empty).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 10:11:24 +02:00
|
|
|
fake_async: ^1.3.3
|
2026-09-16 19:24:59 +02:00
|
|
|
# Used for agents to drive the app through the Dart MCP
|
|
|
|
|
flutter_driver:
|
|
|
|
|
sdk: flutter
|
fix(app): the app has its own icon instead of Flutter's
The launcher showed Flutter's logo: the project template's icons were
never replaced. tool/app_icons.py now makes every icon from the
kingfisher (assets/icon/halkyon_icon.png), through flutter_launcher_icons
v0.14.4 configured in the app's pubspec: Android's adaptive icon (the
kingfisher inset 16 % on white, for each launcher to cut to its shape)
and the square one before Android 8; iOS's, without transparency;
macOS's, the rounded tile on Apple's grid (824 of 1024 pixels); and
Windows', the rounded tile, its .ico with every size from 16 to 256
pixels (the tool writes only 256). It keeps the iOS project file as it
was, which the tool would break (flutter_launcher_icons issue 634), and
writes the snap's icon as tool/snap_icon.py did, which it replaces.
The Linux runner gives its window the bundled icon too, for desktops
that don't take it from the desktop file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-06 22:49:25 +02:00
|
|
|
# Writes the app's icons for every platform from assets/icon/halkyon_icon.png, run
|
|
|
|
|
# through tool/app_icons.py (see the flutter_launcher_icons section below).
|
|
|
|
|
flutter_launcher_icons: ^0.14.4
|
2026-09-16 19:24:59 +02:00
|
|
|
flutter_test:
|
|
|
|
|
sdk: flutter
|
fix(app): GitHub sign-in on Linux and Windows waits for the browser
Signing in with GitHub on Linux ended in "Unable to connect" at 127.0.0.1:43823 after authorizing. flutter_web_auth_2 (v5.1.0, unchanged on its master) ends every pending sign-in when the app is resumed. That suits mobile, where coming back without finishing means giving up, but on the desktop the window regaining focus resumes the app, and the package's loopback server closed before GitHub sent the browser back. FluffyChat's issue #3341 and the package's #171 report the same.
The app now puts its own Linux and Windows implementation behind the package's platform interface (LoopbackWebAuth): the system browser and a loopback address, as RFC 8252 recommends, waiting until GitHub answers, the person cancels, or five minutes pass. It listens on 127.0.0.1 only and answers only the callback's path. Serverpod's PKCE and state check stay in use. There, the account dialog shows "Continue in your browser." with a Cancel button, and closing the dialog cancels the sign-in.
Serverpod (v4.0.3) reports a cancelled browser round trip as an unknown error, so the gateway knows a cancel by having asked for it. A cancel on GitHub's own page, or leaving the browser on Android, still reads as a failure; AGENTS.md says so.
The regression test drives the package's front through a change of focus. With the package's behaviour it fails with the same refused connection.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 20:18:18 +02:00
|
|
|
# A test drives sign-in through its front, as Serverpod's GitHub sign-in does.
|
|
|
|
|
flutter_web_auth_2: ^5.1.0
|
feat(app): editor blocks: callout, spoiler, formulas, files, video, study button
Blocks beyond the editor's own, each built once for editor and view:
- callout: eight kinds in theme colours, kind, title and "folded at
first" from its header; written in place, left with Enter on its
empty last line; never inside another
- spoiler: written in plain sight, hidden behind Reveal read-only
- formula on its own line, written on its own screen with a preview
and snippet buttons, and formulas within a line (Σ in the format
bar); set by flutter_math_fork behind MathView, LaTeX's numbered
environments read as their aligned forms
- image and sound picked from the device, kept as synced files
- YouTube video from its link, with its start and a caption: a card
until played, nothing fetched before; played in place where a web
view exists (youtube_player_iframe, privacy-enhanced), else opened
in the browser, behind YoutubeVideos in Dependencies
- study button: a studyset, a tag or all cards, with or without its
topics, back first; its StudyFocus rides on the study route and
narrows that session only
- tables, already in the editor, now insertable everywhere
An Insert button opens on the format bar, so blocks can be inserted on
phones too; the desktop's / menu lists the same blocks, translated.
Card faces now dock the format bar while written, so cards get blocks
and formatting on phones as well. The blocks are listed in
EditorBlocks: the core's, plus the features' through
EditorBlocksScope. Blocks of a type the app doesn't know say they need
a newer version and stay as they are, so formatVersion stays 1.
Their types and keys are in content_blocks.dart, which RichContent
reads: a block's text counts as text, and a video or study button
isn't blank. The path import now makes formulas real ones.
integration_test/youtube_player_test.dart plays a real video through
the app's player on a device, which no widget test can: YouTube
refuses embeds that don't say where they are embedded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-03 12:20:41 +02:00
|
|
|
# Checks on a device what no widget test can see (integration_test/).
|
|
|
|
|
integration_test:
|
|
|
|
|
sdk: flutter
|
test(app): studysets synced against a running server
The first tests that need serverpod start running (ADR 0001, rule 9: the client's and the server's sync engines can't share one process), tagged requires-server: tool/check.sh skips them, flutter test -t requires-server runs them. Every device is its own client and local database with the app's real gateway and repository, and syncs only when a test says so.
They state: two devices of one person end up with the same studysets; a deleted studyset leaves the other devices, and its payload is blank on the server, checked with SQL against the development database because the engine keeps deleted rows; a studyset written into another person's space never reaches them; claiming another person's space shows none of their studysets, since the server sends only the spaces the signed-in person has access to. As written here, the forgery test also expects the server to refuse and log the write, and the SQL checks connect over TCP; the first run against the server corrected both (see the commit "the sync tests pass against the running server").
They live in the app package: the server's Docker build resolves its dependencies with only the server package present, so a dev dependency on the client package would break it. They never initialize a Flutter test binding, which would replace real HTTP with a mock. Without a server they fail at sign-in with "connection refused".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-09-30 12:15:30 +02:00
|
|
|
# The sync tests (test/sync/) sign in like the app and read the server's database.
|
|
|
|
|
postgres: ^3.5.16
|
|
|
|
|
serverpod_auth_core_client: 4.0.3
|
|
|
|
|
yaml: ^3.1.4
|
2026-09-16 19:24:59 +02:00
|
|
|
|
|
|
|
|
dependency_overrides:
|
|
|
|
|
flutter_secure_storage: ^10.0.0
|
|
|
|
|
|
|
|
|
|
# For information on the generic Dart part of this file, see the
|
|
|
|
|
# following page: https://dart.dev/tools/pub/pubspec
|
|
|
|
|
|
|
|
|
|
# The following section is specific to Flutter.
|
|
|
|
|
flutter:
|
feat(app): the UI foundation: theme, tabs and routes, strings
The app gets its look and navigation before more features land on them.
- Theme (core/theme/): hand-written light and dark Material 3 colour schemes around
cobalt and copper; ThemeExtras for the colours Material has no role for (states, the
four FSRS ratings); the 20 colours a person can give content. Tests hold text to WCAG
AA contrast on each palette colour and on the dark theme's primary colour and surface.
- Layout and shared widgets: Material 3's window-size classes (core/layout/);
SectionCard, AppLoadingIndicator, confirmDestructiveAction and FoundById
(core/widget/). Lucide icons.
- Navigation with go_router: four tabs (Home, Library, Explore, Profile), each keeping
its own stack of screens, as a navigation rail with the app's icon from 600 dp and as
a navigation bar below; choosing the open tab again returns to its first screen. A
studyset's screen sits in the library; studying, cards and settings cover the tabs.
- Routes carry ids: FoundById finds the item in the repository's live list, so a
studyset deleted on another device, or an old link, says it no longer exists. A
screen that deletes its own item keeps showing it while it closes, rather than
flashing that message.
- Every person gets an app of their own (PersonApp, lib/src/app/): a
MaterialApp.router with its own GoRouter and PersonScope around the navigator, built
by SessionGate inside the person's keyed subtree. Screens read the person's
repositories wherever they are built, so the static route() helpers and the listener
that closed every pushed screen on a change of person are gone: a change of person
now disposes the whole router and starts again at home.
- The account dialog becomes the Profile tab, with Settings (sign out, delete the
account, About) behind a gear. A waiting GitHub sign-in now keeps waiting while
another tab shows; a change of person still cancels it.
- The new screens' strings come from Flutter's gen-l10n (English for now). The
generated files are committed, left out of analysis and formatting, and written
anew by `flutter pub get`, so CI's check of generated code also catches stale ones.
Widget tests now start the whole app (test/support/pump_app.dart) at the screen's path
and move through it as a person would, by tabs and taps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 00:18:31 +02:00
|
|
|
# Generates AppLocalizations from lib/l10n (see l10n.yaml).
|
|
|
|
|
generate: true
|
2026-09-16 19:24:59 +02:00
|
|
|
|
|
|
|
|
# The following line ensures that the Material Icons font is
|
|
|
|
|
# included with your application, so that you can use the icons in
|
|
|
|
|
# the material Icons class.
|
|
|
|
|
uses-material-design: true
|
|
|
|
|
|
|
|
|
|
# To add assets to your application, add an assets section, like this:
|
|
|
|
|
# assets:
|
|
|
|
|
# - images/a_dot_burr.jpeg
|
|
|
|
|
# - images/a_dot_ham.jpeg
|
|
|
|
|
|
|
|
|
|
# An image asset can refer to one or more resolution-specific "variants", see
|
|
|
|
|
# https://flutter.dev/assets-and-images/#resolution-aware.
|
|
|
|
|
|
|
|
|
|
# For details regarding adding assets from package dependencies, see
|
|
|
|
|
# https://flutter.dev/assets-and-images/#from-packages
|
|
|
|
|
assets:
|
|
|
|
|
- assets/config.json
|
feat(app): the UI foundation: theme, tabs and routes, strings
The app gets its look and navigation before more features land on them.
- Theme (core/theme/): hand-written light and dark Material 3 colour schemes around
cobalt and copper; ThemeExtras for the colours Material has no role for (states, the
four FSRS ratings); the 20 colours a person can give content. Tests hold text to WCAG
AA contrast on each palette colour and on the dark theme's primary colour and surface.
- Layout and shared widgets: Material 3's window-size classes (core/layout/);
SectionCard, AppLoadingIndicator, confirmDestructiveAction and FoundById
(core/widget/). Lucide icons.
- Navigation with go_router: four tabs (Home, Library, Explore, Profile), each keeping
its own stack of screens, as a navigation rail with the app's icon from 600 dp and as
a navigation bar below; choosing the open tab again returns to its first screen. A
studyset's screen sits in the library; studying, cards and settings cover the tabs.
- Routes carry ids: FoundById finds the item in the repository's live list, so a
studyset deleted on another device, or an old link, says it no longer exists. A
screen that deletes its own item keeps showing it while it closes, rather than
flashing that message.
- Every person gets an app of their own (PersonApp, lib/src/app/): a
MaterialApp.router with its own GoRouter and PersonScope around the navigator, built
by SessionGate inside the person's keyed subtree. Screens read the person's
repositories wherever they are built, so the static route() helpers and the listener
that closed every pushed screen on a change of person are gone: a change of person
now disposes the whole router and starts again at home.
- The account dialog becomes the Profile tab, with Settings (sign out, delete the
account, About) behind a gear. A waiting GitHub sign-in now keeps waiting while
another tab shows; a change of person still cancels it.
- The new screens' strings come from Flutter's gen-l10n (English for now). The
generated files are committed, left out of analysis and formatting, and written
anew by `flutter pub get`, so CI's check of generated code also catches stale ones.
Widget tests now start the whole app (test/support/pump_app.dart) at the screen's path
and move through it as a person would, by tabs and taps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-01 00:18:31 +02:00
|
|
|
# The kingfisher: the app icon, also shown atop the navigation rail.
|
|
|
|
|
- assets/icon/halkyon_icon.png
|
2026-10-01 19:56:29 +02:00
|
|
|
# Region maps for map questions (see assets/maps/ATTRIBUTION.md).
|
|
|
|
|
- assets/maps/japan_prefectures.geojson
|
2026-09-16 19:24:59 +02:00
|
|
|
|
|
|
|
|
# To add custom fonts to your application, add a fonts section here,
|
|
|
|
|
# in this "flutter" section. Each entry in this list should have a
|
|
|
|
|
# "family" key with the font family name, and a "fonts" key with a
|
|
|
|
|
# list giving the asset and other descriptors for the font. For
|
|
|
|
|
# example:
|
|
|
|
|
# fonts:
|
|
|
|
|
# - family: Schyler
|
|
|
|
|
# fonts:
|
|
|
|
|
# - asset: fonts/Schyler-Regular.ttf
|
|
|
|
|
# - asset: fonts/Schyler-Italic.ttf
|
|
|
|
|
# style: italic
|
|
|
|
|
# - family: Trajan Pro
|
|
|
|
|
# fonts:
|
|
|
|
|
# - asset: fonts/TrajanPro.ttf
|
|
|
|
|
# - asset: fonts/TrajanPro_Bold.ttf
|
|
|
|
|
# weight: 700
|
|
|
|
|
#
|
|
|
|
|
# For details regarding fonts from package dependencies,
|
|
|
|
|
# see https://flutter.dev/custom-fonts/#from-packages
|
fix(app): the app has its own icon instead of Flutter's
The launcher showed Flutter's logo: the project template's icons were
never replaced. tool/app_icons.py now makes every icon from the
kingfisher (assets/icon/halkyon_icon.png), through flutter_launcher_icons
v0.14.4 configured in the app's pubspec: Android's adaptive icon (the
kingfisher inset 16 % on white, for each launcher to cut to its shape)
and the square one before Android 8; iOS's, without transparency;
macOS's, the rounded tile on Apple's grid (824 of 1024 pixels); and
Windows', the rounded tile, its .ico with every size from 16 to 256
pixels (the tool writes only 256). It keeps the iOS project file as it
was, which the tool would break (flutter_launcher_icons issue 634), and
writes the snap's icon as tool/snap_icon.py did, which it replaces.
The Linux runner gives its window the bundled icon too, for desktops
that don't take it from the desktop file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-06 22:49:25 +02:00
|
|
|
|
|
|
|
|
# The app's icons, written by tool/app_icons.py: it prepares the images derived from
|
|
|
|
|
# the kingfisher in build/icons/ first, then runs `dart run flutter_launcher_icons`
|
|
|
|
|
# with this. Android: an adaptive icon, the kingfisher inset on white for each
|
|
|
|
|
# launcher to cut to its shape, and the square one older Androids show. iOS: without
|
|
|
|
|
# transparency, as Apple requires. macOS: the rounded tile on Apple's grid. Windows:
|
|
|
|
|
# the rounded tile (tool/app_icons.py then writes its .ico with every size).
|
|
|
|
|
flutter_launcher_icons:
|
|
|
|
|
image_path: "assets/icon/halkyon_icon.png"
|
|
|
|
|
android: true
|
|
|
|
|
adaptive_icon_background: "#ffffff"
|
|
|
|
|
adaptive_icon_foreground: "assets/icon/halkyon_icon.png"
|
|
|
|
|
ios: true
|
|
|
|
|
remove_alpha_ios: true
|
|
|
|
|
background_color_ios: "#ffffff"
|
|
|
|
|
macos:
|
|
|
|
|
generate: true
|
|
|
|
|
image_path: "build/icons/macos.png"
|
|
|
|
|
windows:
|
|
|
|
|
generate: true
|
|
|
|
|
image_path: "build/icons/rounded.png"
|
|
|
|
|
icon_size: 256
|